diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2c0e88993..b72c1da95 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ # This should match the owning team set up in https://github.com/orgs/opensearch-project/teams -* @opensearch-project/k-nn \ No newline at end of file +* @heemin32 @navneet1v @VijayanB @vamshin @jmazanec15 @naveentatikonda @junqiu-lei @martin-gaievski @ryanbogan @luyuncheng diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index f9e914099..c37596a1f 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -6,79 +6,190 @@ on: branches: - "*" - "feature/**" + paths: + - 'build.gradle' + - 'settings.gradle' + - 'src/**' + - 'build-tools/**' + - 'buildSrc/**' + - 'gradle/**' + - 'jni/**' + - 'micro-benchmarks/**' + - '.github/workflows/CI.yml' pull_request: branches: - "*" - "feature/**" + paths: + - 'build.gradle' + - 'settings.gradle' + - 'src/**' + - 'build-tools/**' + - 'buildSrc/**' + - 'gradle/**' + - 'jni/**' + - 'micro-benchmarks/**' + - '.github/workflows/CI.yml' +env: + ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true jobs: - Build-k-NN: + Get-CI-Image-Tag: + uses: opensearch-project/opensearch-build/.github/workflows/get-ci-image-tag.yml@main + with: + product: opensearch + + Build-k-NN-Linux: strategy: matrix: - java: [11, 17] + java: [11, 17, 21] - name: Build and Test k-NN Plugin + name: Build and Test k-NN Plugin on Linux runs-on: ubuntu-latest + needs: Get-CI-Image-Tag + container: + # using the same image which is used by opensearch-build team to build the OpenSearch Distribution + # this image tag is subject to change as more dependencies and updates will arrive over time + image: ${{ needs.Get-CI-Image-Tag.outputs.ci-image-version-linux }} + # need to switch to root so that github actions can install runner binary on container without permission issues. + options: --user root steps: - name: Checkout k-NN uses: actions/checkout@v1 + # Setup git user so that patches for native libraries can be applied and committed + - name: Setup git user + run: | + su `id -un 1000` -c 'git config --global user.name "github-actions[bot]"' + su `id -un 1000` -c 'git config --global user.email "github-actions[bot]@users.noreply.github.com"' + - name: Setup Java ${{ matrix.java }} uses: actions/setup-java@v1 with: java-version: ${{ matrix.java }} - - name: Install dependencies - run: | - sudo apt-get install libopenblas-dev gfortran -y - - name: Run build + # switching the user, as OpenSearch cluster can only be started as root/Administrator on linux-deb/linux-rpm/windows-zip. run: | - ./gradlew build + chown -R 1000:1000 `pwd` + if lscpu | grep -i avx512f | grep -i avx512cd | grep -i avx512vl | grep -i avx512dq | grep -i avx512bw + then + echo "avx512 available on system" + su `id -un 1000` -c "whoami && java -version && ./gradlew build -Dnproc.count=`nproc`" + elif lscpu | grep -i avx2 + then + echo "avx2 available on system" + su `id -un 1000` -c "whoami && java -version && ./gradlew build -Dnproc.count=`nproc` -Davx512.enabled=false" + else + echo "avx512 and avx2 not available on system" + su `id -un 1000` -c "whoami && java -version && ./gradlew build -Davx2.enabled=false -Davx512.enabled=false -Dnproc.count=`nproc`" + fi + - name: Upload Coverage Report uses: codecov/codecov-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} -# - name: Pull and Run Docker for security tests -# run: | -# plugin=`ls build/distributions/*.zip` -# version=`echo $plugin|awk -F- '{print $4}'| cut -d. -f 1-3` -# plugin_version=`echo $plugin|awk -F- '{print $4}'| cut -d. -f 1-4` -# echo $version -# cd .. -# if docker pull opendistroforelasticsearch/opendistroforelasticsearch:$version -# then -# echo "FROM opendistroforelasticsearch/opendistroforelasticsearch:$version" >> Dockerfile -# echo "RUN if [ -d /usr/share/elasticsearch/plugins/opendistro-knn ]; then /usr/share/elasticsearch/bin/elasticsearch-plugin remove opendistro-knn; fi" >> Dockerfile -# echo "RUN yum -y update \ && yum -y groupinstall "Development Tools" \ && yum install -y unzip glibc.x86_64 cmake \ && yum clean all" >> Dockerfile -# echo "RUN git clone --recursive --branch ${GITHUB_REF##*/} https://github.com/opendistro-for-elasticsearch/k-NN.git /usr/share/elasticsearch/k-NN \ " >> Dockerfile -# echo "&& cd /usr/share/elasticsearch/k-NN/jni \ && sed -i 's/-march=native/-march=x86-64/g' external/nmslib/similarity_search/CMakeLists.txt \ && cmake . \ && make \ " >> Dockerfile -# echo "&& mkdir /tmp/jni/ && cp release/*.so /tmp/jni/ && ls -ltr /tmp/jni/ \ && cp /tmp/jni/libKNNIndex*.so /usr/lib \ && rm -rf /usr/share/elasticsearch/k-NN" >> Dockerfile -# echo "RUN cd /usr/share/elasticsearch/" >> Dockerfile -# echo "ADD k-NN/build/distributions/opendistro-knn-$plugin_version.zip /tmp/" >> Dockerfile -# echo "RUN /usr/share/elasticsearch/bin/elasticsearch-plugin install --batch file:/tmp/opendistro-knn-$plugin_version.zip" >> Dockerfile -# docker build -t odfe-knn:test . -# echo "imagePresent=true" >> $GITHUB_ENV -# else -# echo "imagePresent=false" >> $GITHUB_ENV -# fi -# - name: Run Docker Image -# if: env.imagePresent == 'true' -# run: | -# cd .. -# docker run -p 9200:9200 -d -p 9600:9600 -e "discovery.type=single-node" odfe-knn:test -# sleep 90 -# - name: Run k-NN Test -# if: env.imagePresent == 'true' -# run: | -# security=`curl -XGET https://localhost:9200/_cat/plugins?v -u admin:admin --insecure |grep opendistro_security|wc -l` -# if [ $security -gt 0 ] -# then -# echo "Security plugin is available. Running tests in security mode" -# ./gradlew :integTest -Dtests.rest.cluster=localhost:9200 -Dtests.cluster=localhost:9200 -Dtests.clustername="docker-cluster" -Dhttps=true -Duser=admin -Dpassword=admin -# else -# echo "Security plugin is NOT available. Skipping tests as they are already ran part of ./gradlew build" -# fi + Build-k-NN-MacOS: + strategy: + matrix: + java: [ 11, 17, 21 ] + + name: Build and Test k-NN Plugin on MacOS + needs: Get-CI-Image-Tag + runs-on: macos-13 + + steps: + - name: Checkout k-NN + uses: actions/checkout@v1 + + # Setup git user so that patches for native libraries can be applied and committed + - name: Setup git user + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + - name: Setup Java ${{ matrix.java }} + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.java }} + + - name: Install dependencies on macos + run: | + brew install libomp + brew reinstall gcc + export FC=/usr/local/Cellar/gcc/12.2.0/bin/gfortran + + # TODO: Detect processor count and set the value of nproc.count + - name: Run build + run: | + if sysctl -n machdep.cpu.features machdep.cpu.leaf7_features | grep -i AVX2 + then + echo "avx2 available on system" + ./gradlew build -Dnproc.count=3 + else + echo "avx2 not available on system" + ./gradlew build -Davx2.enabled=false -Davx512.enabled=false -Dnproc.count=3 + fi + + Build-k-NN-Windows: + strategy: + matrix: + java: [ 11, 17, 21 ] + + name: Build and Test k-NN Plugin on Windows + needs: Get-CI-Image-Tag + runs-on: windows-latest + + steps: + - name: Checkout k-NN + uses: actions/checkout@v1 + + # Setup git user so that patches for native libraries can be applied and committed + - name: Setup git user + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + - name: Setup Java ${{ matrix.java }} + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.java }} + + - name: Install MinGW Using Scoop + run: | + iex "& {$(irm get.scoop.sh)} -RunAsAdmin" + scoop bucket add main + scoop install mingw + + - name: Add MinGW to PATH + run: | + echo "C:/Users/runneradmin/scoop/apps/mingw/current/bin" >> $env:GITHUB_PATH + Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" + refreshenv + + - name: Install Zlib Using Scoop + run: | + echo "C:/Users/runneradmin/scoop/shims" >> $env:GITHUB_PATH + Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" + refreshenv + scoop bucket add extras + scoop install zlib + regedit /s "C:\\Users\\runneradmin\\scoop\\apps\\zlib\\current\\register.reg" + + - name: Download OpenBLAS + run: | + curl -L -O https://github.com/xianyi/OpenBLAS/releases/download/v0.3.21/OpenBLAS-0.3.21-x64.zip + mkdir OpenBLAS + Expand-Archive -Path .\OpenBLAS-0.3.21-x64.zip -DestinationPath .\OpenBLAS\ + mkdir ./src/main/resources/windowsDependencies + cp ./OpenBLAS/bin/libopenblas.dll ./src/main/resources/windowsDependencies/ + rm .\OpenBLAS-0.3.21-x64.zip + rm -r .\OpenBLAS\ + + # TODO: Detect processor count and set the value of nproc.count + - name: Run build + run: | + ./gradlew.bat build -D'avx2.enabled=false' -D'avx512.enabled=false' -D'nproc.count=4' diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml new file mode 100644 index 000000000..c67e12bad --- /dev/null +++ b/.github/workflows/auto-release.yml @@ -0,0 +1,28 @@ +name: Releases + +on: + push: + tags: + - '*' + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: GitHub App token + id: github_app_token + uses: tibdex/github-app-token@v1.5.0 + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} + installation_id: 22958780 + - name: Get tag + id: tag + uses: dawidd6/action-get-tag@v1 + - uses: actions/checkout@v2 + - uses: ncipollo/release-action@v1 + with: + github_token: ${{ steps.github_app_token.outputs.token }} + bodyFile: release-notes/opensearch-knn.release-notes-${{steps.tag.outputs.tag}}.md diff --git a/.github/workflows/backwards_compatibility_tests_workflow.yml b/.github/workflows/backwards_compatibility_tests_workflow.yml index ce654eee9..3229b5d69 100644 --- a/.github/workflows/backwards_compatibility_tests_workflow.yml +++ b/.github/workflows/backwards_compatibility_tests_workflow.yml @@ -4,18 +4,38 @@ on: branches: - "*" - "feature/**" + paths: + - 'build.gradle' + - 'settings.gradle' + - 'src/**' + - 'build-tools/**' + - 'buildSrc/**' + - 'gradle/**' + - 'jni/**' + - 'qa/**' + - '.github/workflows/backwards_compatibility_tests_workflow.yml' pull_request: branches: - "*" - "feature/**" + paths: + - 'build.gradle' + - 'settings.gradle' + - 'src/**' + - 'build-tools/**' + - 'buildSrc/**' + - 'gradle/**' + - 'jni/**' + - 'qa/**' + - '.github/workflows/backwards_compatibility_tests_workflow.yml' jobs: Restart-Upgrade-BWCTests-k-NN: strategy: matrix: java: [ 11, 17 ] - bwc_version : [ "1.0.0", "1.1.0", "1.2.4", "1.3.2", "2.0.0" ] - opensearch_version : [ "2.1.0-SNAPSHOT" ] + bwc_version : [ "1.1.0", "1.2.4", "1.3.8", "2.0.1", "2.1.0", "2.2.1", "2.3.0", "2.4.1", "2.5.0", "2.6.0", "2.7.0", "2.8.0", "2.9.0", "2.10.0", "2.11.0", "2.12.0", "2.13.0", "2.14.0", "2.15.0", "2.16.0", "2.17.0", "2.18.0" ] + opensearch_version : [ "2.19.0-SNAPSHOT" ] name: k-NN Restart-Upgrade BWC Tests runs-on: ubuntu-latest @@ -26,6 +46,12 @@ jobs: - name: Checkout k-NN uses: actions/checkout@v1 + # Setup git user so that patches for native libraries can be applied and committed + - name: Setup git user + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + - name: Setup Java ${{ matrix.java }} uses: actions/setup-java@v1 with: @@ -38,26 +64,42 @@ jobs: - name: Run k-NN Restart-Upgrade BWC Tests from BWCVersion-${{ matrix.bwc_version }} to OpenSearch Version-${{ matrix.opensearch_version }} run: | - echo "Running restart-upgrade backwards compatibility tests ..." - ./gradlew :qa:restart-upgrade:testRestartUpgrade -Dtests.bwc.version=$BWC_VERSION_RESTART_UPGRADE - + echo "Running restart-upgrade backwards compatibility tests ..." + if lscpu | grep -i avx512f | grep -i avx512cd | grep -i avx512vl | grep -i avx512dq | grep -i avx512bw + then + echo "avx512 available on system" + ./gradlew :qa:restart-upgrade:testRestartUpgrade -Dtests.bwc.version=$BWC_VERSION_RESTART_UPGRADE -Dnproc.count=`nproc` + elif lscpu | grep -i avx2 + then + echo "avx2 available on system" + ./gradlew :qa:restart-upgrade:testRestartUpgrade -Dtests.bwc.version=$BWC_VERSION_RESTART_UPGRADE -Dnproc.count=`nproc` -Davx512.enabled=false + else + echo "avx512 and avx2 not available on system" + ./gradlew :qa:restart-upgrade:testRestartUpgrade -Dtests.bwc.version=$BWC_VERSION_RESTART_UPGRADE -Davx2.enabled=false -Davx512.enabled=false -Dsimd.enabled=false -Dnproc.count=`nproc` + fi Rolling-Upgrade-BWCTests-k-NN: strategy: matrix: java: [ 11, 17 ] - bwc_version: [ "1.3.2", "2.0.0" ] - opensearch_version: [ "2.1.0-SNAPSHOT" ] + bwc_version: [ "1.3.8", "2.0.1", "2.1.0", "2.2.1", "2.3.0", "2.4.1", "2.5.0", "2.6.0", "2.7.0", "2.8.0", "2.9.0", "2.10.0", "2.11.0", "2.12.0", "2.13.0", "2.14.0", "2.15.0", "2.16.0", "2.17.0", "2.18.0"] + opensearch_version: [ "2.19.0-SNAPSHOT" ] name: k-NN Rolling-Upgrade BWC Tests runs-on: ubuntu-latest env: - OPENSEARCH_VERSION_ROLLING_UPGRADE: ${{ matrix.opensearch_version }} + BWC_VERSION_ROLLING_UPGRADE: ${{ matrix.bwc_version }} steps: - name: Checkout k-NN uses: actions/checkout@v1 + # Setup git user so that patches for native libraries can be applied and committed + - name: Setup git user + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + - name: Setup Java ${{ matrix.java }} uses: actions/setup-java@v1 with: @@ -70,4 +112,15 @@ jobs: - name: Run k-NN Rolling-Upgrade BWC Tests from BWCVersion-${{ matrix.bwc_version }} to OpenSearch Version-${{ matrix.opensearch_version }} run: | echo "Running rolling-upgrade backwards compatibility tests ..." - ./gradlew :qa:rolling-upgrade:testRollingUpgrade -Drolling.bwctests.opensearch.version=$OPENSEARCH_VERSION_ROLLING_UPGRADE + if lscpu | grep -i avx512f | grep -i avx512cd | grep -i avx512vl | grep -i avx512dq | grep -i avx512bw + then + echo "avx512 available on system" + ./gradlew :qa:rolling-upgrade:testRollingUpgrade -Dtests.bwc.version=$BWC_VERSION_ROLLING_UPGRADE -Dnproc.count=`nproc` + elif lscpu | grep -i avx2 + then + echo "avx2 available on system" + ./gradlew :qa:rolling-upgrade:testRollingUpgrade -Dtests.bwc.version=$BWC_VERSION_ROLLING_UPGRADE -Dnproc.count=`nproc` -Davx512.enabled=false + else + echo "avx512 and avx2 not available on system" + ./gradlew :qa:rolling-upgrade:testRollingUpgrade -Dtests.bwc.version=$BWC_VERSION_ROLLING_UPGRADE -Davx2.enabled=false -Davx512.enabled=false -Dsimd.enabled=false -Dnproc.count=`nproc` + fi diff --git a/.github/workflows/changelog_verifier.yml b/.github/workflows/changelog_verifier.yml new file mode 100644 index 000000000..992a38b62 --- /dev/null +++ b/.github/workflows/changelog_verifier.yml @@ -0,0 +1,18 @@ +name: "Changelog Verifier" +on: + pull_request: + types: [opened, edited, review_requested, synchronize, reopened, ready_for_review, labeled, unlabeled] + +jobs: + # Enforces the update of a changelog file on every pull request + verify-changelog: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ref: ${{ github.event.pull_request.head.sha }} + + - uses: dangoslen/changelog-enforcer@v3 + with: + skipLabels: "autocut, skip-changelog" diff --git a/.github/workflows/dco.yml b/.github/workflows/dco.yml deleted file mode 100644 index cf30ea89d..000000000 --- a/.github/workflows/dco.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Developer Certificate of Origin Check - -on: [pull_request] - -jobs: - check: - runs-on: ubuntu-latest - - steps: - - name: Get PR Commits - id: 'get-pr-commits' - uses: tim-actions/get-pr-commits@v1.1.0 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - name: DCO Check - uses: tim-actions/dco@v1.1.0 - with: - commits: ${{ steps.get-pr-commits.outputs.commits }} diff --git a/.github/workflows/delete_backport_branch.yml b/.github/workflows/delete_backport_branch.yml index d654df6b4..a4d186ca7 100644 --- a/.github/workflows/delete_backport_branch.yml +++ b/.github/workflows/delete_backport_branch.yml @@ -7,9 +7,16 @@ on: jobs: delete-branch: runs-on: ubuntu-latest - if: startsWith(github.event.pull_request.head.ref,'backport/') + permissions: + contents: write + if: github.repository == 'opensearch-project/k-NN' && startsWith(github.event.pull_request.head.ref,'backport/') steps: - - name: Delete merged branch - uses: SvanBoxel/delete-merged-branch@main - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + - name: Delete merged branch + uses: actions/github-script@v7 + with: + script: | + github.rest.git.deleteRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `heads/${context.payload.pull_request.head.ref}`, + }) diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml new file mode 100644 index 000000000..724e3a213 --- /dev/null +++ b/.github/workflows/maven-publish.yml @@ -0,0 +1,35 @@ +name: Publish snapshots to maven + +on: + workflow_dispatch: + push: + branches: + - 'main' + - '1.*' + - '2.*' + +jobs: + build-and-publish-snapshots: + runs-on: ubuntu-latest + + permissions: + id-token: write + contents: write + + steps: + - uses: actions/setup-java@v3 + with: + distribution: temurin # Temurin is a distribution of adoptium + java-version: 11 + - uses: actions/checkout@v3 + - uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: ${{ secrets.PUBLISH_SNAPSHOTS_ROLE }} + aws-region: us-east-1 + - name: publish snapshots to maven + run: | + export SONATYPE_USERNAME=$(aws secretsmanager get-secret-value --secret-id maven-snapshots-username --query SecretString --output text) + export SONATYPE_PASSWORD=$(aws secretsmanager get-secret-value --secret-id maven-snapshots-password --query SecretString --output text) + echo "::add-mask::$SONATYPE_USERNAME" + echo "::add-mask::$SONATYPE_PASSWORD" + ./gradlew publishPluginZipPublicationToSnapshotsRepository diff --git a/.github/workflows/test_security.yml b/.github/workflows/test_security.yml new file mode 100644 index 000000000..f3deb096e --- /dev/null +++ b/.github/workflows/test_security.yml @@ -0,0 +1,84 @@ +name: Test k-NN on Secure Cluster +on: + schedule: + - cron: '0 0 * * *' # every night + push: + branches: + - "*" + - "feature/**" + paths: + - 'build.gradle' + - 'settings.gradle' + - 'src/**' + - 'build-tools/**' + - 'buildSrc/**' + - 'gradle/**' + - 'jni/**' + - '.github/workflows/test_security.yml' + pull_request: + branches: + - "*" + - "feature/**" + paths: + - 'build.gradle' + - 'settings.gradle' + - 'src/**' + - 'build-tools/**' + - 'buildSrc/**' + - 'gradle/**' + - 'jni/**' + - '.github/workflows/test_security.yml' +env: + ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true + +jobs: + Get-CI-Image-Tag: + uses: opensearch-project/opensearch-build/.github/workflows/get-ci-image-tag.yml@main + with: + product: opensearch + + integ-test-with-security-linux: + strategy: + matrix: + java: [21] + + name: Run Integration Tests on Linux + runs-on: ubuntu-latest + needs: Get-CI-Image-Tag + container: + # using the same image which is used by opensearch-build team to build the OpenSearch Distribution + # this image tag is subject to change as more dependencies and updates will arrive over time + image: ${{ needs.Get-CI-Image-Tag.outputs.ci-image-version-linux }} + # need to switch to root so that github actions can install runner binary on container without permission issues. + options: --user root + + steps: + - name: Checkout k-NN + uses: actions/checkout@v1 + # Setup git user so that patches for native libraries can be applied and committed + - name: Setup git user + run: | + su `id -un 1000` -c 'git config --global user.name "github-actions[bot]"' + su `id -un 1000` -c 'git config --global user.email "github-actions[bot]@users.noreply.github.com"' + + - name: Setup Java ${{ matrix.java }} + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.java }} + + - name: Run build + # switching the user, as OpenSearch cluster can only be started as root/Administrator on linux-deb/linux-rpm/windows-zip. + run: | + chown -R 1000:1000 `pwd` + if lscpu | grep -i avx512f | grep -i avx512cd | grep -i avx512vl | grep -i avx512dq | grep -i avx512bw + then + echo "avx512 available on system" + su `id -un 1000` -c "whoami && java -version && ./gradlew build -Dnproc.count=`nproc`" + elif lscpu | grep -i avx2 + then + echo "avx2 available on system" + su `id -un 1000` -c "whoami && java -version && ./gradlew build -Dnproc.count=`nproc` -Davx512.enabled=false" + else + echo "avx512 and avx2 not available on system" + su `id -un 1000` -c "whoami && java -version && ./gradlew build -Davx2.enabled=false -Davx512.enabled=false -Dnproc.count=`nproc`" + fi diff --git a/.gitignore b/.gitignore index 160757c41..34f7ff154 100644 --- a/.gitignore +++ b/.gitignore @@ -17,9 +17,7 @@ oss/* jni/CMakeCache.txt jni/CMakeFiles jni/Makefile -jni/cmake* -jni/CPack* -jni/_CPack* +jni/cmake_install.cmake jni/release jni/packages jni/CTestTestfile.cmake @@ -30,6 +28,12 @@ jni/bin/ jni/lib/ jni/jni_test* jni/googletest* +jni/cmake/*.cmake-e +jni/.cmake +jni/.idea +jni/build.ninja +jni/.ninja_deps +jni/.ninja_log benchmarks/perf-tool/okpt/output benchmarks/perf-tool/okpt/dev diff --git a/.idea/copyright/SPDX_ALv2.xml b/.idea/copyright/SPDX_ALv2.xml index a2485beef..3c6f9c768 100644 --- a/.idea/copyright/SPDX_ALv2.xml +++ b/.idea/copyright/SPDX_ALv2.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..a39546843 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,29 @@ + +# CHANGELOG +All notable changes to this project are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). See the [CONTRIBUTING guide](./CONTRIBUTING.md#Changelog) for instructions on how to add changelog entries. + +## [Unreleased 3.0](https://github.com/opensearch-project/k-NN/compare/2.x...HEAD) +### Features +### Enhancements +### Bug Fixes +### Infrastructure +### Documentation +### Maintenance +### Refactoring + +## [Unreleased 2.x](https://github.com/opensearch-project/k-NN/compare/2.18...2.x) +### Features +### Enhancements +- Introduced a writing layer in native engines where relies on the writing interface to process IO. (#2241)[https://github.com/opensearch-project/k-NN/pull/2241] +### Bug Fixes +* Fixing the bug when a segment has no vector field present for disk based vector search (#2282)[https://github.com/opensearch-project/k-NN/pull/2282] +### Infrastructure +* Updated C++ version in JNI from c++11 to c++17 [#2259](https://github.com/opensearch-project/k-NN/pull/2259) +* Upgrade bytebuddy and objenesis version to match OpenSearch core and, update github ci runner for macos [#2279](https://github.com/opensearch-project/k-NN/pull/2279) +### Documentation +### Maintenance +* Select index settings based on cluster version[2236](https://github.com/opensearch-project/k-NN/pull/2236) +* Added null checks for fieldInfo in ExactSearcher to avoid NPE while running exact search for segments with no vector field (#2278)[https://github.com/opensearch-project/k-NN/pull/2278] +### Refactoring diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 958c2986d..49c6b1d9b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,6 +8,7 @@ OpenSearch k-NN is a community project that is built and maintained by people ju - [Ways to Contribute](#ways-to-contribute) - [Developer Certificate of Origin](#developer-certificate-of-origin) - [License Headers](#license-headers) +- [Changelog](#changelog) - [Review Process](#review-process) @@ -129,6 +130,42 @@ New files in your code contributions should contain the following license header # SPDX-License-Identifier: Apache-2.0 ``` +## Changelog + +OpenSearch maintains version specific changelog by enforcing a change to the ongoing [CHANGELOG](CHANGELOG.md) file adhering to the [Keep A Changelog](https://keepachangelog.com/en/1.0.0/) format. The purpose of the changelog is for the contributors and maintainers to incrementally build the release notes throughout the development process to avoid a painful and error-prone process of attempting to compile the release notes at release time. On each release the "unreleased" entries of the changelog are moved to the appropriate release notes document in the `./release-notes` folder. Also, incrementally building the changelog provides a concise, human-readable list of significant features that have been added to the unreleased version under development. + +### Which changes require a CHANGELOG entry? +Changelogs are intended for operators/administrators, developers integrating with libraries and APIs, and end-users interacting with OpenSearch Dashboards and/or the REST API (collectively referred to as "user"). In short, any change that a user of OpenSearch might want to be aware of should be included in the changelog. The changelog is _not_ intended to replace the git commit log that developers of OpenSearch itself rely upon. The following are some examples of changes that should be in the changelog: + +- A newly added feature +- A fix for a user-facing bug +- Dependency updates +- Fixes for security issues + +The following are some examples where a changelog entry is not necessary: + +- Adding, modifying, or fixing tests +- An incremental PR for a larger feature (such features should include _one_ changelog entry for the feature) +- Documentation changes or code refactoring +- Build-related changes + +Any PR that does not include a changelog entry will result in a failure of the validation workflow in GitHub. If the contributor and maintainers agree that no changelog entry is required, then the `skip-changelog` label can be applied to the PR which will result in the workflow passing. + +### How to add my changes to [CHANGELOG](CHANGELOG.md)? + +Adding in the change is two step process: +1. Add your changes to the corresponding section within the CHANGELOG file with dummy pull request information, publish the PR +2. Update the entry for your change in [`CHANGELOG.md`](CHANGELOG.md) and make sure that you reference the pull request there. + +### Where should I put my CHANGELOG entry? +Please review the [branching strategy](https://github.com/opensearch-project/.github/blob/main/RELEASING.md#opensearch-branching) document. The changelog on the `main` branch will contain sections for the _next major_ and _next minor_ releases. Your entry should go into the section it is intended to be released in. In practice, most changes to `main` will be backported to the next minor release so most entries will likely be in that section. + +The following examples assume the _next major_ release on main is 3.0, then _next minor_ release is 2.5, and the _current_ release is 2.4. + +- **Add a new feature to release in next minor:** Add a changelog entry to `[Unreleased 2.x]` on main, then backport to 2.x (including the changelog entry). +- **Introduce a breaking API change to release in next major:** Add a changelog entry to `[Unreleased 3.0]` on main, do not backport. +- **Upgrade a dependency to fix a CVE:** Add a changelog entry to `[Unreleased 2.x]` on main, then backport to 2.x (including the changelog entry), then backport to 2.4 and ensure the changelog entry is added to `[Unreleased 2.4.1]`. + ## Review Process We deeply appreciate everyone who takes the time to make a contribution. We will review all contributions as quickly as possible. As a reminder, [opening an issue](https://github.com/opensearch-project/k-NN/issues/new/choose) discussing your change before you make it is the best way to smooth the PR process. This will prevent a rejection because someone else is already working on the problem, or because the solution is incompatible with the architectural direction. diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index 291b902b8..c39a6f1b6 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -3,11 +3,16 @@ - [Fork OpenSearch k-NN Repo](#fork-opensearch-k-nn-repo) - [Install Prerequisites](#install-prerequisites) - [JDK 11](#jdk-11) + - [CMake](#cmake) + - [Faiss Dependencies](#Faiss-Dependencies) + - [Environment](#Environment) - [Use an Editor](#use-an-editor) - [IntelliJ IDEA](#intellij-idea) - [Build](#build) - [JNI Library](#jni-library) - [JNI Library Artifacts](#jni-library-artifacts) + - [Parallelize make](#parallelize-make) + - [Enable SIMD Optimization](#enable-simd-optimization) - [Run OpenSearch k-NN](#run-opensearch-k-nn) - [Run Single-node Cluster Locally](#run-single-node-cluster-locally) - [Run Multi-node Cluster Locally](#run-multi-node-cluster-locally) @@ -53,13 +58,90 @@ In addition to this, the plugin has been tested with JDK 17, and this JDK versio #### CMake -The plugin requires that cmake >= 3.17.2 is installed in order to build the JNI libraries. +The plugin requires that cmake >= 3.24.0 is installed in order to build the JNI libraries. + +One easy way to install on mac or linux is to use pip: +```bash +pip install cmake==3.24.0 +``` + +On Mac M series machines, install cmake using: +```bash +brew install cmake +``` #### Faiss Dependencies To build the *faiss* JNI library, you need to have openmp, lapack and blas installed. For more information on *faiss* dependencies, please refer to [their documentation](https://github.com/facebookresearch/faiss/blob/main/INSTALL.md). +[Openblas](https://www.openblas.net/) can be used for both lapack and blas. To install on Mac, run: +```bash +brew install openblas +``` + +Additionally, the `gcc` toolchain needs to be installed on Mac. To install, run: +```bash +brew install gcc +``` + +#### Additional setup for Mac M series Machines + +The following commands enable running/building k-NN on M series machines: + +```bash +// Go to k-NN folder +cd k-NN + +// Build to generate the necessary files to be modified below (will fail) +./gradlew build + +//Go to jni folder +cd jni + +// File changes required +sed -i -e 's/\/usr\/local\/opt\/libomp\//\/opt\/homebrew\/opt\/llvm\//g' cmake/init-faiss.cmake +sed -i -e 's/__aarch64__/__undefine_aarch64__/g' external/faiss/faiss/utils/distances_simd.cpp +sed -i -e 's/pragma message WARN/pragma message /g' external/nmslib/similarity_search/src/distcomp_scalar.cc +// Change -mcpu value to use chip version according to your M series, for example, -mcpu=apple-m1 +sed -i -e 's/-march=native/-mcpu=apple-m1/g' external/nmslib/similarity_search/CMakeLists.txt +sed -i -e 's/-mcpu=apple-a14/-mcpu=apple-m1/g' external/nmslib/python_bindings/setup.py + +// Install llvm +brew install llvm +echo 'export PATH="/opt/homebrew/opt/llvm/bin:$PATH"' >> ~/.zshrc +source ~/.zshrc + +// Set compiler path for CMAKE +export CC=/opt/homebrew/opt/llvm/bin/clang +export CXX=/opt/homebrew/opt/llvm/bin/clang++ + +// In case of linking issues with the external libraries and clang, you can try setting the CMAKE compiler to gcc/g++ instead through the following commands: +export CC=gcc +export CXX=g++ +sed -i '' '/set(CMAKE_CXX_STANDARD_REQUIRED True)/a\'$'\n''set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -fopenmp -L/opt/homebrew/opt/libomp/lib -I/opt/homebrew/opt/libomp/include -lomp -arch arm64 -fexceptions")'$'\n''' CMakeLists.txt + +// Build +cmake . --fresh +make +``` + +Next, obtain a minimum distribution tarball of the k-NN version you want to build: + +1. Fork the [OpenSearch Repo](https://github.com/opensearch-project/OpenSearch) into your github account. +2. Clone the repository locally +3. Run the following commands: +```cd OpenSearch && ./gradlew -p distribution/archives/darwin-tar assemble``` +4. You should see a opensearch-min--SNAPSHOT-darwin-x64.tar.gz file present in distribution/archives/darwin-tar/build/distributions/ +5. Build k-NN by passing the OpenSearch distribution path in `./gradlew -PcustomDistributionUrl=""` + +If you want to start OpenSearch directly on Mac M series, make sure to use JDK for ARM. Otherwise, you will see the following error: `mach-o file, but is an incompatible architecture (have 'arm64', need 'x86_64')`. It is better to start OpenSearch by running `bash opensearch-tar-install.sh` instead of `./bin/opensearch`. To run `./bin/opensearch`, the environment variable `JAVA_LIBRARY_PATH` needs to be set correctly so that OpenSearch can find the JNI library: + +``` +export OPENSEARCH_HOME=the directory of opensearch... +export JAVA_LIBRARY_PATH=$JAVA_LIBRARY_PATH:$OPENSEARCH_HOME/plugins/opensearch-knn/lib +``` + #### Environment Currently, the plugin only supports Linux on x64 and arm platforms. @@ -96,19 +178,32 @@ Please follow these formatting guidelines: * Wildcard imports (`import foo.bar.baz.*`) are forbidden and will cause the build to fail. * If *absolutely* necessary, you can disable formatting for regions of code with the `// tag::NAME` and `// end::NAME` directives, but note that these are intended for use in documentation, so please make it clear what you have done, and only do this where the benefit clearly outweighs the decrease in consistency. * Note that JavaDoc and block comments i.e. `/* ... */` are not formatted, but line comments i.e `// ...` are. -* There is an implicit rule that negative boolean expressions should use the form `foo == false` instead of `!foo` for better readability of the code. While this isn't strictly enforced, if might get called out in PR reviews as something to change. +* There is an implicit rule that negative boolean expressions should use the form `foo == false` instead of `!foo` for better readability of the code. While this isn't strictly enforced, it might get called out in PR reviews as something to change. ## Build OpenSearch k-NN uses a [Gradle](https://docs.gradle.org/6.6.1/userguide/userguide.html) wrapper for its build. Run `gradlew` on Unix systems. +Tests use `JAVA11_HOME` environment variable, make sure to add it in the export path else the tests might fail. +e.g +``` +echo "export JAVA11_HOME=" >> ~/.zshrc +source ~/.zshrc +``` + Build OpenSearch k-NN using `gradlew build` ``` ./gradlew build ``` +For Mac M series machines use +``` +./gradlew build -PcustomDistributionUrl="" +``` + + ### JNI Library The plugin relies on 2 JNI libraries to perform approximate k-NN search. `./gradlew build` will first build the @@ -121,11 +216,11 @@ To build the JNI Library manually, follow these steps: cd jni cmake . -# To build everything, including tests +# To build everything, including tests. If your computer has multiple cores you can speed it up by building in parallel using make -j 2 (or a higher number for more parallelism) make # To just build the libraries -make opensearchknn_nmslib opensearchknn_nmslib +make opensearchknn_faiss opensearchknn_nmslib ``` The libraries will be placed in the `jni/release` directory. @@ -138,10 +233,10 @@ run: ./bin/jni_test # To run nmslib tests -./bin/jni_test --gtest_filter=Nmslib* +./bin/jni_test --gtest_filter='Nmslib*' # To run faiss tests -./bin/jni_test --gtest_filter=Faiss* +./bin/jni_test --gtest_filter='Faiss*' ``` ### JNI Library Artifacts @@ -155,14 +250,67 @@ For users that want to get the most out of the libraries, they should follow [th and build the libraries from source in their production environment, so that if their environment has optimized instruction sets, they take advantage of them. +### Custom patch on JNI Library +If you want to make a custom patch on JNI library +1. Make a change on top of current version of JNI library and push the commit locally. +2. Create a patch file for the change using `git format-patch -o patches HEAD^` +3. Place the patch file under `jni/patches` +4. Make a change in `jni/CmakeLists.txt`, `.github/workflows/CI.yml` to apply the patch during build + +By default, in the cmake build system, these patches will be applied and committed to the native libraries. In order to +successfully make the commits the `user.name` and `user.email` git configurations need to be setup. If you cannot set +these in your environment, you can disable committing the changes to the library by passing gradle this flag: +`build.lib.commit_patches=false`. For example, `gradlew build -Dbuild.lib.commit_patches=false`. If the patches are +not committed, then the full library build process will run each time `cmake` is invoked. In a development environment, +it is recommended to setup the user git configuration to avoid this cost. + +### Parallelize make +When we are building the plugin for the first time, it takes some time to build the JNI libraries. We can parallelize make and speed up the build time by setting and passing +this flag to gradle, `nproc.count` if your computer has more number of cores (greater than or equal to 2). +``` +# While building OpenSearch k-NN +./gradlew build -Dnproc.count=4 + +# While running OpenSearch k-NN +./gradlew run -Dnproc.count=4 + +# When building the JNI library manually +cd jni +cmake . +# Pass the processor count with make using `-j` +make -j 4 +``` + +### Enable SIMD Optimization +SIMD(Single Instruction/Multiple Data) Optimization is enabled by default on Linux and Mac which boosts the performance +by enabling `AVX2` and `AVX512` on `x86 architecture` and `NEON` on `ARM64 architecture` where applicable while building the Faiss library. But to enable SIMD, +the underlying processor should support these capabilities (AVX512, AVX2 or NEON). It can be disabled by setting the parameter `avx2.enabled` to `false` and +`avx512.enabled` to `false`. If your processor supports `AVX512` or `AVX2`, they can be set by enabling the setting . By default, these values are enabled on +OpenSearch. Some exceptions: As of now, SIMD support is not supported on Windows OS, and AVX512 is not present on MAC systems due to hardware not supporting the +feature. + +``` +# While building OpenSearch k-NN +./gradlew build -Davx2.enabled=true -Davx512.enabled=true + +# While running OpenSearch k-NN +./gradlew run -Davx2.enabled=true -Davx512.enabled=true + +# While building the JNI libraries +cd jni +cmake . -DAVX2_ENABLED=true -DAVX512_ENABLED=true +``` + ## Run OpenSearch k-NN ### Run Single-node Cluster Locally -Run OpenSearch k-NN using `gradlew run`. +Run OpenSearch k-NN using `gradlew run`. For Mac M series add ```-PcustomDistributionUrl=``` argument. ```shell script ./gradlew run ``` + + That will build OpenSearch and start it, writing its log above Gradle's status message. We log a lot of stuff on startup, specifically these lines tell you that plugin is ready. ``` [2020-05-29T14:50:35,167][INFO ][o.e.h.AbstractHttpServerTransport] [runTask-0] publish_address {127.0.0.1:9200}, bound_addresses {[::1]:9200}, {127.0.0.1:9200} @@ -190,6 +338,35 @@ curl localhost:9200 } } ``` + +Additionally, it is also possible to run a cluster with security enabled: +```shell script +./gradlew run -Dsecurity.enabled=true -Dhttps=true -Duser=admin -Dpassword= +``` + +Then, to access the cluster, we can run +```bash +curl https://localhost:9200 --insecure -u admin: + +{ + "name" : "integTest-0", + "cluster_name" : "integTest", + "cluster_uuid" : "kLsNk4JDTMyp1yQRqog-3g", + "version" : { + "distribution" : "opensearch", + "number" : "3.0.0-SNAPSHOT", + "build_type" : "tar", + "build_hash" : "9d85e566894ef53e5f2093618b3d455e4d0a04ce", + "build_date" : "2023-10-30T18:34:06.996519Z", + "build_snapshot" : true, + "lucene_version" : "9.8.0", + "minimum_wire_compatibility_version" : "2.12.0", + "minimum_index_compatibility_version" : "2.0.0" + }, + "tagline" : "The OpenSearch Project: https://opensearch.org/" +} +``` + ### Run Multi-node Cluster Locally It can be useful to test and debug on a multi-node cluster. In order to launch a 3 node cluster with the KNN plugin installed, run the following command: @@ -198,12 +375,17 @@ It can be useful to test and debug on a multi-node cluster. In order to launch a ./gradlew run -PnumNodes=3 ``` -In order to run the integration tests with a 3 node cluster, run this command: +In order to run the integration tests, run this command: ``` ./gradlew :integTest -PnumNodes=3 ``` +Additionally, to run integration tests with security enabled, run +``` +./gradlew :integTest -Dsecurity.enabled=true -PnumNodes=3 +``` + Integration tests can be run with remote cluster. For that run the following command and replace host/port/cluster name values with ones for the target cluster: ``` @@ -213,7 +395,7 @@ Integration tests can be run with remote cluster. For that run the following com In case remote cluster is secured it's possible to pass username and password with the following command: ``` -./gradlew :integTestRemote -Dtests.rest.cluster=localhost:9200 -Dtests.cluster=localhost:9200 -Dtests.clustername="integTest-0" -Dhttps=true -Duser=admin -Dpassword=admin +./gradlew :integTestRemote -Dtests.rest.cluster=localhost:9200 -Dtests.cluster=localhost:9200 -Dtests.clustername="integTest-0" -Dhttps=true -Duser=admin -Dpassword= ``` ### Debugging @@ -268,10 +450,12 @@ Before adding any new tests to Backward Compatibility Tests, we should be aware Starting from 2.0 release the new versioning for codec has been introduced. Two positions will be used to define the version, in format 'X.Y', where 'X' corresponds to underlying version of Lucene and 'Y' is the version of the format. +Please note that Lucene version along with corresponding Lucene codec is part of the core OpenSearch. KNN codec should be in sync with Lucene codec version from core OpenSearch. Codec version is used in following classes and methods: - org.opensearch.knn.index.codec.KNNXYCodec.KNNXYCodec -- org.opensearch.knn.index.codec.KNNFormatFactory.createKNNXYFormat +- org.opensearch.knn.index.codec.KNNXYCodec.KNNXYPerFieldKnnVectorsFormat +- org.opensearch.knn.index.codec.KNNCodecVersion These classes and methods are tied directly to Lucene version represented by 'X' part. Other classes use the delegate pattern so no direct tie to Lucene version are related to format and represented by 'Y' diff --git a/LICENSE.txt b/LICENSE.txt index d64569567..62364a94c 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -200,3 +200,205 @@ 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. + +--------------- +OpenSearch k-NN plugin includes the following third-party software/licensing: + +** faiss; version 1.7.6 -- +MIT License + +Copyright (c) Facebook, Inc. and its affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +** OpenBLAS; version 0.3.3, 0.3.21 +Copyright (c) 2011-2014, The OpenBLAS Project +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + 3. Neither the name of the OpenBLAS project nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Cerberus + +ISC License + +Copyright (c) 2012-2016 Nicola Iarocci. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + +------ +PyYAML + + +Copyright (c) 2017-2021 Ingy döt Net +Copyright (c) 2006-2016 Kirill Simonov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +------ + +Copyright (c) 2005-2021, NumPy Developers. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of the NumPy Developers nor the names of any + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------ + +** libwinpthread; version 12.2.0 -- https://www.mingw-w64.org/ +Copyright (c) 2011 mingw-w64 project + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +/* + * Parts of this library are derived by: + * + * Posix Threads library for Microsoft Windows + * + * Use at own risk, there is no implied warranty to this code. + * It uses undocumented features of Microsoft Windows that can change + * at any time in the future. + * + * (C) 2010 Lockless Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without +modification, + * are permitted provided that the following conditions are met: + * + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Lockless Inc. nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AN + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 6c8ef5ec2..73fdc028a 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -1,71 +1,16 @@ -- [Overview](#overview) -- [Current Maintainers](#current-maintainers) -- [Maintainer Responsibilities](#maintainer-responsibilities) - - [Uphold Code of Conduct](#uphold-code-of-conduct) - - [Prioritize Security](#prioritize-security) - - [Review Pull Requests](#review-pull-requests) - - [Triage Open Issues](#triage-open-issues) - - [Be Responsive](#be-responsive) - - [Maintain Overall Health of the Repo](#maintain-overall-health-of-the-repo) - - [Use Semver](#use-semver) - - [Release Frequently](#release-frequently) - - [Promote Other Maintainers](#promote-other-maintainers) - ## Overview -This document explains who the maintainers are (see below), what they do in this repo, and how they should be doing it. If you're interested in contributing, see [CONTRIBUTING](CONTRIBUTING.md). - +This document contains a list of maintainers in this repo. See [opensearch-project/.github/RESPONSIBILITIES.md](https://github.com/opensearch-project/.github/blob/main/RESPONSIBILITIES.md#maintainer-responsibilities) that explains what the role of maintainer means, what maintainers do in this and other repos, and how they should be doing it. If you're interested in contributing, and becoming a maintainer, see [CONTRIBUTING](CONTRIBUTING.md). ## Current Maintainers -| Maintainer | GitHub ID | Affiliation | -| ------------------------| --------------------------------------------| ----------| -| Jack Mazanec | [jmazanec15](https://github.com/jmazanec15) | Amazon | -| Vamshi Vijay Nakkirtha | [vamshin](https://github.com/vamshin) | Amazon | -| Vijayan Balasubramanian | [VijayanB](https://github.com/VijayanB) | Amazon | - -## Maintainer Responsibilities - -Maintainers are active and visible members of the community, and have [maintain-level permissions on a repository](https://docs.github.com/en/organizations/managing-access-to-your-organizations-repositories/repository-permission-levels-for-an-organization). Use those privileges to serve the community and evolve code as follows. - -### Uphold Code of Conduct - -Model the behavior set forward by the [Code of Conduct](CODE_OF_CONDUCT.md) and raise any violations to other maintainers and admins. - -### Prioritize Security - -Security is your number one priority. Maintainer's Github keys must be password protected securely and any reported security vulnerabilities are addressed before features or bugs. - -Note that this repository is monitored and supported 24/7 by Amazon Security, see [Reporting a Vulnerability](SECURITY.md) for details. - -### Review Pull Requests - -Review pull requests regularly, comment, suggest, reject, merge and close. Accept only high quality pull-requests. Provide code reviews and guidance on incomming pull requests. Don't let PRs be stale and do your best to be helpful to contributors. - -### Triage Open Issues - -Manage labels, review issues regularly, and triage by labelling them. - -All repositories in this organization have a standard set of labels, including `bug`, `documentation`, `duplicate`, `enhancement`, `good first issue`, `help wanted`, `blocker`, `invalid`, `question`, `wontfix`, and `untriaged`, along with release labels, such as `v1.0.0`, `v1.1.0` and `v2.0.0`, and `backport`. - -Use labels to target an issue or a PR for a given release, add `help wanted` to good issues for new community members, and `blocker` for issues that scare you or need immediate attention. Request for more information from a submitter if an issue is not clear. Create new labels as needed by the project. - -### Be Responsive - -Respond to enhancement requests, and forum posts. Allocate time to reviewing and commenting on issues and conversations as they come in. - -### Maintain Overall Health of the Repo - -Keep the `main` branch at production quality at all times. Backport features as needed. Cut release branches and tags to enable future patches. - -### Use Semver - -Use and enforce [semantic versioning](https://semver.org/) and do not let breaking changes be made outside of major releases. - -### Release Frequently - -Make frequent project releases to the community. - -### Promote Other Maintainers - -Assist, add, and remove [MAINTAINERS](MAINTAINERS.md). Exercise good judgement, and propose high quality contributors to become co-maintainers. - +| Maintainer | GitHub ID | Affiliation | +|-------------------------|-------------------------------------------------------|-------------| +| Heemin Kim | [heemin32](https://github.com/heemin32) | Amazon | +| Jack Mazanec | [jmazanec15](https://github.com/jmazanec15) | Amazon | +| Junqiu Lei | [junqiu-lei](https://github.com/junqiu-lei) | Amazon | +| Martin Gaievski | [martin-gaievski](https://github.com/martin-gaievski) | Amazon | +| Naveen Tatikonda | [naveentatikonda](https://github.com/naveentatikonda) | Amazon | +| Navneet Verma | [navneet1v](https://github.com/navneet1v) | Amazon | +| Vamshi Vijay Nakkirtha | [vamshin](https://github.com/vamshin) | Amazon | +| Vijayan Balasubramanian | [VijayanB](https://github.com/VijayanB) | Amazon | +| Yuncheng Lu | [luyuncheng](https://github.com/luyuncheng) | Bytedance | diff --git a/README.md b/README.md index bb8ca928b..bd4baac00 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![Build and Test k-NN](https://github.com/opensearch-project/k-NN/actions/workflows/CI.yml/badge.svg)](https://github.com/opensearch-project/k-NN/actions/workflows/CI.yml) [![codecov](https://codecov.io/gh/opensearch-project/k-NN/branch/main/graph/badge.svg?token=PYQO2GW39S)](https://codecov.io/gh/opensearch-project/k-NN) [![Documentation](https://img.shields.io/badge/doc-reference-blue)](https://opensearch.org/docs/search-plugins/knn/index/) -[![Chat](https://img.shields.io/badge/chat-on%20forums-blue)](https://discuss.opendistrocommunity.dev/c/k-NN/) +[![Chat](https://img.shields.io/badge/chat-on%20forums-blue)](https://forum.opensearch.org/c/plugins/k-nn/48) ![PRs welcome!](https://img.shields.io/badge/PRs-welcome!-success) # OpenSearch k-NN @@ -21,7 +21,7 @@ * [Project Website](https://opensearch.org/) * [Downloads](https://opensearch.org/downloads.html). * [Documentation](https://opensearch.org/docs/search-plugins/knn/index/) -* Need help? Try [Forums](https://discuss.opendistrocommunity.dev/c/k-nn/) +* Need help? Try the [Forum](https://forum.opensearch.org/c/plugins/k-nn/48) * [Project Principles](https://opensearch.org/#principles) * [Contributing to OpenSearch k-NN](CONTRIBUTING.md) * [Maintainer Responsibilities](MAINTAINERS.md) diff --git a/SECURITY.md b/SECURITY.md index 0b85ca04e..be4ac7463 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,3 +1,3 @@ ## Reporting a Vulnerability -If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/) or directly via email to aws-security@amazon.com. Please do **not** create a public GitHub issue. \ No newline at end of file +If you discover a potential security issue in this project we ask that you notify OpenSearch Security directly via email to security@opensearch.org. Please do **not** create a public GitHub issue. diff --git a/THIRD-PARTY b/THIRD-PARTY deleted file mode 100644 index 2add2c0c2..000000000 --- a/THIRD-PARTY +++ /dev/null @@ -1,129 +0,0 @@ -** faiss; version 1.7.6 -- -MIT License - -Copyright (c) Facebook, Inc. and its affiliates. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -** OpenBLAS; version 0.3.3 -Copyright (c) 2011-2014, The OpenBLAS Project -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - 3. Neither the name of the OpenBLAS project nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written - permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -Cerberus - -ISC License - -Copyright (c) 2012-2016 Nicola Iarocci. - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. - ------- -PyYAML - - -Copyright (c) 2017-2021 Ingy döt Net -Copyright (c) 2006-2016 Kirill Simonov - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ------- - -Copyright (c) 2005-2021, NumPy Developers. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of the NumPy Developers nor the names of any - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/TRIAGING.md b/TRIAGING.md new file mode 100644 index 000000000..9949b3390 --- /dev/null +++ b/TRIAGING.md @@ -0,0 +1,89 @@ + + +The maintainers of the k-NN/neural-search Repo's seek to promote an inclusive and engaged community of contributors. In +order to facilitate this, bi-weekly triage meetings are open-to-all and attendance is encouraged for anyone who hopes to +contribute, discuss an issue, or learn more about the project. To learn more about contributing to the +k-NN/neural-search Repo visit the [Contributing](./CONTRIBUTING.md) documentation. + +### Do I need to attend for my issue to be addressed/triaged? + +Attendance is not required for your issue to be triaged or addressed. All new issues are triaged bi-weekly. + +### What happens if my issue does not get covered this time? + +Each meeting we seek to address all new issues. However, should we run out of time before your issue is discussed, you +are always welcome to attend the next meeting or to follow up on the issue post itself. + +### How do I join the Backlog & Triage meeting? + +Meetings are hosted regularly at 5 PM Pacific Time on Tuesdays bi-weekly and can be joined via the links posted on the +[OpenSearch Meetup Group](https://www.meetup.com/opensearch/events/) list of events. The event will be titled +`Development Backlog & Triage Meeting - k-NN/neural-search`. + +After joining the Chime meeting, you can enable your video / voice to join the discussion. If you do not have a webcam +or microphone available, you can still join in via the text chat. + +If you have an issue you'd like to bring forth please consider getting a link to the issue so it can be presented to +everyone in the meeting. + +### Is there an agenda for each week? + +Meetings are 60 minutes and structured as follows: + +1. Initial Gathering: As we gather, feel free to turn on video and engage in informal and open-to-all conversation. After a bit a volunteer will share their screen and proceed with the agenda. +2. Announcements: If there are any announcements to be made they will happen at the start of the meeting. +3. Review of New Issues: The meetings always start with reviewing all untriaged/recent issues for the k-NN and neural-search repositories. +4. Member Requests: Opportunity for any meeting member to ask for consideration of an issue or pull request. +5. Pull Request Discussion: Then, we review the status of outstanding pull requests from the k-NN and neural-search repositories. +6. Open Discussion: Allow for members of the meeting to surface any topics without issues filed or pull request created. + + +There is no specific ordering within each category. + +If you have an issue you would like to discuss but do not have the ability to attend the entire meeting please attend when is best for you and signal that you have an issue to discuss when you arrive. + +### Do I need to have already contributed to the project to attend a triage meeting? + +No, all are welcome and encouraged to attend. Attending the Backlog & Triage meetings is a great way for a new contributor to learn about the project as well as explore different avenues of contribution. + +### What if I have an issue that is almost a duplicate, should I open a new one to be triaged? + +You can always open an issue including one that you think may be a duplicate. However, in cases where you believe there +is an important distinction to be made between an existing issue and your newly created one, you are encouraged to +attend the triaging meeting to explain. + +### What if I have follow-up questions on an issue? + +If you have an existing issue you would like to discuss, you can always comment on the issue itself. Alternatively, you +are welcome to come to the triage meeting to discuss. + +### Is this meeting a good place to get help setting up k-NN/neural-search features on my OpenSearch instance? + +While we are always happy to help the community, the best resource for implementation questions is [the OpenSearch forum](https://forum.opensearch.org/c/plugins/k-nn/48). + +There you can find answers to many common questions as well as speak with implementation experts. + +### What are the issue labels associated with triaging? + +Yes, there are several labels that are used to identify the 'state' of issues filed in OpenSearch and the Security Plugin. + +| Label | When applied | Meaning | +| ----- | ------------ | ------- | +| Untriaged | When issues are created or re-opened. | Issues labeled as 'Untriaged' require the attention of the repository maintainers and may need to be prioritized for quicker resolution. It's crucial to keep the count of 'Untriaged' labels low to ensure all potential security issues are addressed in a timely manner. See [SECURITY.md](https://github.com/opensearch-project/security/blob/main/SECURITY.md) for more details on handling these issues. | +| Triaged | During triage meetings. | Issues labeled as 'Triaged' have been reviewed and are deemed actionable. Opening a pull request for an issue with the 'Triaged' label has a higher likelihood of approval from the project maintainers, particularly in novel areas. | +| Neither Label | During triage meetings. | This category is for issues that lack sufficient details to formulate a potential solution. Until more details are provided, it's difficult to ascertain if a proposed solution would be acceptable. When dealing with an 'Untriaged' issue that falls into this category, the triage team should provide further insights so the issue can be appropriately closed or labeled as 'Triaged'. Issues in this state are reviewed during every triage meeting. | +| Help Wanted | Anytime. | Issues marked as 'Help Wanted' signal that they are actionable and not the current focus of the project maintainers. Community contributions are especially encouraged for these issues. | +| Good First Issue | Anytime. | Issues labeled as 'Good First Issue' are small in scope and can be resolved with a single pull request. These are recommended starting points for newcomers looking to make their first contributions. | + + +### What if my issue is critical to OpenSearch operations, do I have to wait for the bi-weekly meeting for it to be addressed? + +All new issues for the [k-NN](https://github.com/opensearch-project/k-NN/issues?q=is%3Aissue+is%3Aopen+label%3Auntriaged) repo and [neural-search](https://github.com/opensearch-project/neural-search/issues?q=is%3Aissue+is%3Aopen+-label%3Atriaged) repo are reviewed daily to check for critical issues which require immediate triaging. If an issue relates to a severe concern for OpenSearch operation, it will be triaged by a maintainer mid-week. You can still come to discuss an issue at the following meeting even if it has already been triaged during the week. + +### Is this where I should bring up potential security vulnerabilities? + +Due to the sensitive nature of security vulnerabilities, please report all potential vulnerabilities directly by following the steps outlined on the [SECURITY.md](https://github.com/opensearch-project/k-NN/blob/main/SECURITY.md) document. + +### Who should I contact if I have further questions? + +You can always file an issue for any question you have about the project. diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 000000000..2e642d41b --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,4 @@ +## Benchmark Folder Tools Deprecated +All benchmark workloads have been moved to [OpenSearch Benchmark Workloads](https://github.com/opensearch-project/opensearch-benchmark-workloads/tree/main/vectorsearch). Please use OSB tool to run the benchmarks. + +If you are still interested in using the old tool, the benchmarks are moved to the [branch](https://github.com/opensearch-project/k-NN/tree/old-benchmarks/benchmarks). diff --git a/benchmarks/osb/README.md b/benchmarks/osb/README.md deleted file mode 100644 index 92272e20b..000000000 --- a/benchmarks/osb/README.md +++ /dev/null @@ -1,468 +0,0 @@ -# OpenSearch Benchmarks for k-NN - -## Overview - -This directory contains code and configurations to run k-NN benchmarking -workloads using OpenSearch Benchmarks. - -The [extensions](extensions) directory contains common code shared between -procedures. The [procedures](procedures) directory contains the individual -test procedures for this workload. - -## Getting Started - -### OpenSearch Benchmarks Background - -OpenSearch Benchmark is a framework for performance benchmarking an OpenSearch -cluster. For more details, checkout their -[repo](https://github.com/opensearch-project/opensearch-benchmark/). - -Before getting into the benchmarks, it is helpful to know a few terms: -1. Workload - Top level description of a benchmark suite. A workload will have a `workload.json` file that defines different components of the tests -2. Test Procedures - A workload can have a schedule of operations that run the test. However, a workload can also have several test procedures that define their own schedule of operations. This is helpful for sharing code between tests -3. Operation - An action against the OpenSearch cluster -4. Parameter source - Producers of parameters for OpenSearch operations -5. Runners - Code that actually will execute the OpenSearch operations - -### Setup - -OpenSearch Benchmarks requires Python 3.8 or greater to be installed. One of -the easier ways to do this is through Conda, a package and environment -management system for Python. - -First, follow the -[installation instructions](https://docs.conda.io/projects/conda/en/latest/user-guide/install/index.html) -to install Conda on your system. - -Next, create a Python 3.8 environment: -``` -conda create -n knn-osb python=3.8 -``` - -After the environment is created, activate it: -``` -source activate knn-osb -``` - -Lastly, clone the k-NN repo and install all required python packages: -``` -git clone https://github.com/opensearch-project/k-NN.git -cd k-NN/benchmarks/osb -pip install -r requirements.txt -``` - -After all of this completes, you should be ready to run your first benchmark! - -### Running a benchmark - -Before running a benchmark, make sure you have the endpoint of your cluster and - the machine you are running the benchmarks from can access it. - Additionally, ensure that all data has been pulled to the client. - -Currently, we support 2 test procedures for the k-NN workload: train-test and -no-train-test. The train test has steps to train a model included in the -schedule, while no train does not. Both test procedures will index a data set -of vectors into an OpenSearch index and then run a set of queries against them. - -Once you have decided which test procedure you want to use, open up -[params/train-params.json](params/train-params.json) or -[params/no-train-params.json](params/no-train-params.json) and -fill out the parameters. Notice, at the bottom of `no-train-params.json` there -are several parameters that relate to training. Ignore these. They need to be -defined for the workload but not used. - -Once the parameters are set, set the URL and PORT of your cluster and run the -command to run the test procedure. - -``` -export URL= -export PORT= -export PARAMS_FILE= -export PROCEDURE={no-train-test | train-test} - -opensearch-benchmark execute_test \ - --target-hosts $URL:$PORT \ - --workload-path ./workload.json \ - --workload-params ${PARAMS_FILE} \ - --test-procedure=${PROCEDURE} \ - --pipeline benchmark-only -``` - -## Current Procedures - -### No Train Test - -The No Train Test procedure is used to test `knn_vector` indices that do not -use an algorithm that requires training. - -#### Workflow - -1. Delete old resources in the cluster if they are present -2. Create an OpenSearch index with `knn_vector` configured to use the HNSW algorithm -3. Wait for cluster to be green -4. Ingest data set into the cluster -5. Refresh the index -6. Run queries from data set against the cluster - -#### Parameters - -| Name | Description | -|-----------------------------------------|----------------------------------------------------------------------------------| -| target_index_name | Name of index to add vectors to | -| target_field_name | Name of field to add vectors to | -| target_index_body | Path to target index definition | -| target_index_primary_shards | Target index primary shards | -| target_index_replica_shards | Target index replica shards | -| target_index_dimension | Dimension of target index | -| target_index_space_type | Target index space type | -| target_index_bulk_size | Target index bulk size | -| target_index_bulk_index_data_set_format | Format of vector data set | -| target_index_bulk_index_data_set_path | Path to vector data set | -| target_index_bulk_index_clients | Clients to be used for bulk ingestion (must be divisor of data set size) | -| hnsw_ef_search | HNSW ef search parameter | -| hnsw_ef_construction | HNSW ef construction parameter | -| hnsw_m | HNSW m parameter | -| query_k | The number of neighbors to return for the search | -| query_clients | Number of clients to use for running queries | -| query_data_set_format | Format of vector data set for queries | -| query_data_set_path | Path to vector data set for queries | - -#### Metrics - -The result metrics of this procedure will look like: -``` ------------------------------------------------------- - _______ __ _____ - / ____(_)___ ____ _/ / / ___/_________ ________ - / /_ / / __ \/ __ `/ / \__ \/ ___/ __ \/ ___/ _ \ - / __/ / / / / / /_/ / / ___/ / /__/ /_/ / / / __/ -/_/ /_/_/ /_/\__,_/_/ /____/\___/\____/_/ \___/ ------------------------------------------------------- - -| Metric | Task | Value | Unit | -|---------------------------------------------------------------:|------------------------:|------------:|-------:| -| Cumulative indexing time of primary shards | | 0.00173333 | min | -| Min cumulative indexing time across primary shards | | 0 | min | -| Median cumulative indexing time across primary shards | | 0 | min | -| Max cumulative indexing time across primary shards | | 0.000616667 | min | -| Cumulative indexing throttle time of primary shards | | 0 | min | -| Min cumulative indexing throttle time across primary shards | | 0 | min | -| Median cumulative indexing throttle time across primary shards | | 0 | min | -| Max cumulative indexing throttle time across primary shards | | 0 | min | -| Cumulative merge time of primary shards | | 0 | min | -| Cumulative merge count of primary shards | | 0 | | -| Min cumulative merge time across primary shards | | 0 | min | -| Median cumulative merge time across primary shards | | 0 | min | -| Max cumulative merge time across primary shards | | 0 | min | -| Cumulative merge throttle time of primary shards | | 0 | min | -| Min cumulative merge throttle time across primary shards | | 0 | min | -| Median cumulative merge throttle time across primary shards | | 0 | min | -| Max cumulative merge throttle time across primary shards | | 0 | min | -| Cumulative refresh time of primary shards | | 0.00271667 | min | -| Cumulative refresh count of primary shards | | 115 | | -| Min cumulative refresh time across primary shards | | 0 | min | -| Median cumulative refresh time across primary shards | | 0 | min | -| Max cumulative refresh time across primary shards | | 0.00135 | min | -| Cumulative flush time of primary shards | | 0 | min | -| Cumulative flush count of primary shards | | 43 | | -| Min cumulative flush time across primary shards | | 0 | min | -| Median cumulative flush time across primary shards | | 0 | min | -| Max cumulative flush time across primary shards | | 0 | min | -| Total Young Gen GC time | | 0.849 | s | -| Total Young Gen GC count | | 20 | | -| Total Old Gen GC time | | 0 | s | -| Total Old Gen GC count | | 0 | | -| Store size | | 0.647921 | GB | -| Translog size | | 0.00247511 | GB | -| Heap used for segments | | 0.284451 | MB | -| Heap used for doc values | | 0.0872688 | MB | -| Heap used for terms | | 0.0714417 | MB | -| Heap used for norms | | 6.10352e-05 | MB | -| Heap used for points | | 0 | MB | -| Heap used for stored fields | | 0.125679 | MB | -| Segment count | | 257 | | -| Min Throughput | custom-vector-bulk | 18018.5 | docs/s | -| Mean Throughput | custom-vector-bulk | 18018.5 | docs/s | -| Median Throughput | custom-vector-bulk | 18018.5 | docs/s | -| Max Throughput | custom-vector-bulk | 18018.5 | docs/s | -| 50th percentile latency | custom-vector-bulk | 98.5565 | ms | -| 90th percentile latency | custom-vector-bulk | 100.033 | ms | -| 100th percentile latency | custom-vector-bulk | 103.792 | ms | -| 50th percentile service time | custom-vector-bulk | 98.5565 | ms | -| 90th percentile service time | custom-vector-bulk | 100.033 | ms | -| 100th percentile service time | custom-vector-bulk | 103.792 | ms | -| error rate | custom-vector-bulk | 0 | % | -| Min Throughput | refresh-target-index | 76.22 | ops/s | -| Mean Throughput | refresh-target-index | 76.22 | ops/s | -| Median Throughput | refresh-target-index | 76.22 | ops/s | -| Max Throughput | refresh-target-index | 76.22 | ops/s | -| 100th percentile latency | refresh-target-index | 12.7619 | ms | -| 100th percentile service time | refresh-target-index | 12.7619 | ms | -| error rate | refresh-target-index | 0 | % | -| Min Throughput | knn-query-from-data-set | 1587.47 | ops/s | -| Mean Throughput | knn-query-from-data-set | 1649.97 | ops/s | -| Median Throughput | knn-query-from-data-set | 1661.79 | ops/s | -| Max Throughput | knn-query-from-data-set | 1677.06 | ops/s | -| 50th percentile latency | knn-query-from-data-set | 4.79125 | ms | -| 90th percentile latency | knn-query-from-data-set | 5.38 | ms | -| 99th percentile latency | knn-query-from-data-set | 46.8965 | ms | -| 99.9th percentile latency | knn-query-from-data-set | 58.2049 | ms | -| 99.99th percentile latency | knn-query-from-data-set | 59.6476 | ms | -| 100th percentile latency | knn-query-from-data-set | 60.9245 | ms | -| 50th percentile service time | knn-query-from-data-set | 4.79125 | ms | -| 90th percentile service time | knn-query-from-data-set | 5.38 | ms | -| 99th percentile service time | knn-query-from-data-set | 46.8965 | ms | -| 99.9th percentile service time | knn-query-from-data-set | 58.2049 | ms | -| 99.99th percentile service time | knn-query-from-data-set | 59.6476 | ms | -| 100th percentile service time | knn-query-from-data-set | 60.9245 | ms | -| error rate | knn-query-from-data-set | 0 | % | - - --------------------------------- -[INFO] SUCCESS (took 46 seconds) --------------------------------- -``` - -### Train Test - -The Train Test procedure is used to test `knn_vector` indices that do use an -algorithm that requires training. - -#### Workflow - -1. Delete old resources in the cluster if they are present -2. Create an OpenSearch index with `knn_vector` configured to load with training data -3. Wait for cluster to be green -4. Ingest data set into the training index -5. Refresh the index -6. Train a model based on user provided input parameters -7. Create an OpenSearch index with `knn_vector` configured to use the model -8. Ingest vectors into the target index -9. Refresh the target index -10. Run queries from data set against the cluster - -#### Parameters - -| Name | Description | -|-----------------------------------------|----------------------------------------------------------------------------------| -| target_index_name | Name of index to add vectors to | -| target_field_name | Name of field to add vectors to | -| target_index_body | Path to target index definition | -| target_index_primary_shards | Target index primary shards | -| target_index_replica_shards | Target index replica shards | -| target_index_dimension | Dimension of target index | -| target_index_space_type | Target index space type | -| target_index_bulk_size | Target index bulk size | -| target_index_bulk_index_data_set_format | Format of vector data set for ingestion | -| target_index_bulk_index_data_set_path | Path to vector data set for ingestion | -| target_index_bulk_index_clients | Clients to be used for bulk ingestion (must be divisor of data set size) | -| ivf_nlists | IVF nlist parameter | -| ivf_nprobes | IVF nprobe parameter | -| pq_code_size | PQ code_size parameter | -| pq_m | PQ m parameter | -| train_model_method | Method to be used for model (ivf or ivfpq) | -| train_model_id | Model ID | -| train_index_name | Name of index to put training data into | -| train_field_name | Name of field to put training data into | -| train_index_body | Path to train index definition | -| train_search_size | Search size to use when pulling training data | -| train_timeout | Timeout to wait for training to finish | -| train_index_primary_shards | Train index primary shards | -| train_index_replica_shards | Train index replica shards | -| train_index_bulk_size | Train index bulk size | -| train_index_data_set_format | Format of vector data set for training | -| train_index_data_set_path | Path to vector data set for training | -| train_index_num_vectors | Number of vectors to use from vector data set for training | -| train_index_bulk_index_clients | Clients to be used for bulk ingestion (must be divisor of data set size) | -| query_k | The number of neighbors to return for the search | -| query_clients | Number of clients to use for running queries | -| query_data_set_format | Format of vector data set for queries | -| query_data_set_path | Path to vector data set for queries | - -#### Metrics - -The result metrics of this procedure will look like: -``` ------------------------------------------------------- - _______ __ _____ - / ____(_)___ ____ _/ / / ___/_________ ________ - / /_ / / __ \/ __ `/ / \__ \/ ___/ __ \/ ___/ _ \ [63/1855] - / __/ / / / / / /_/ / / ___/ / /__/ /_/ / / / __/ -/_/ /_/_/ /_/\__,_/_/ /____/\___/\____/_/ \___/ ------------------------------------------------------- -| Metric | Task | Value | Unit | -|---------------------------------------------------------------:|------------------------:|------------:|-----------------:| -| Cumulative indexing time of primary shards | | 2.92355 | min | -| Min cumulative indexing time across primary shards | | 0 | min | -| Median cumulative indexing time across primary shards | | 0.497817 | min | -| Max cumulative indexing time across primary shards | | 1.37717 | min | -| Cumulative indexing throttle time of primary shards | | 0 | min | -| Min cumulative indexing throttle time across primary shards | | 0 | min | -| Median cumulative indexing throttle time across primary shards | | 0 | min | -| Max cumulative indexing throttle time across primary shards | | 0 | min | -| Cumulative merge time of primary shards | | 1.34895 | min | -| Cumulative merge count of primary shards | | 39 | | -| Min cumulative merge time across primary shards | | 0 | min | -| Median cumulative merge time across primary shards | | 0.292033 | min | -| Max cumulative merge time across primary shards | | 0.6268 | min | -| Cumulative merge throttle time of primary shards | | 0.62845 | min | -| Min cumulative merge throttle time across primary shards | | 0 | min | -| Median cumulative merge throttle time across primary shards | | 0.155617 | min | -| Max cumulative merge throttle time across primary shards | | 0.290117 | min | -| Cumulative refresh time of primary shards | | 0.369433 | min | -| Cumulative refresh count of primary shards | | 96 | | -| Min cumulative refresh time across primary shards | | 0 | min | -| Median cumulative refresh time across primary shards | | 0.0903833 | min | -| Max cumulative refresh time across primary shards | | 0.10365 | min | -| Cumulative flush time of primary shards | | 0.0278667 | min | -| Cumulative flush count of primary shards | | 2 | | -| Min cumulative flush time across primary shards | | 0 | min | -| Median cumulative flush time across primary shards | | 0 | min | -| Max cumulative flush time across primary shards | | 0.0278667 | min | -| Total Young Gen GC time | | 13.106 | s | -| Total Young Gen GC count | | 263 | | -| Total Old Gen GC time | | 0 | s | -| Total Old Gen GC count | | 0 | | -| Store size | | 2.60183 | GB | -| Translog size | | 1.34787 | GB | -| Heap used for segments | | 0.0646248 | MB | -| Heap used for doc values | | 0.00899887 | MB | -| Heap used for terms | | 0.0203552 | MB | -| Heap used for norms | | 6.10352e-05 | MB | -| Heap used for points | | 0 | MB | -| Heap used for stored fields | | 0.0352097 | MB | -| Segment count | | 71 | | -| Min Throughput | delete-model | 10.55 | ops/s | -| Mean Throughput | delete-model | 10.55 | ops/s | -| Median Throughput | delete-model | 10.55 | ops/s | -| Max Throughput | delete-model | 10.55 | ops/s | -| 100th percentile latency | delete-model | 94.4726 | ms | -| 100th percentile service time | delete-model | 94.4726 | ms | -| error rate | delete-model | 0 | % | -| Min Throughput | train-vector-bulk | 44763.1 | docs/s | -| Mean Throughput | train-vector-bulk | 52022.4 | docs/s | -| Median Throughput | train-vector-bulk | 52564.8 | docs/s | -| Max Throughput | train-vector-bulk | 53833 | docs/s | -| 50th percentile latency | train-vector-bulk | 22.3364 | ms | -| 90th percentile latency | train-vector-bulk | 47.799 | ms | -| 99th percentile latency | train-vector-bulk | 195.954 | ms | -| 99.9th percentile latency | train-vector-bulk | 495.217 | ms | -| 100th percentile latency | train-vector-bulk | 663.48 | ms | -| 50th percentile service time | train-vector-bulk | 22.3364 | ms | -| 90th percentile service time | train-vector-bulk | 47.799 | ms | -| 99th percentile service time | train-vector-bulk | 195.954 | ms | -| 99.9th percentile service time | train-vector-bulk | 495.217 | ms | -| 100th percentile service time | train-vector-bulk | 663.48 | ms | -| error rate | train-vector-bulk | 0 | % | -| Min Throughput | refresh-train-index | 0.98 | ops/s | -| Mean Throughput | refresh-train-index | 0.98 | ops/s | -| Median Throughput | refresh-train-index | 0.98 | ops/s | -| Max Throughput | refresh-train-index | 0.98 | ops/s | -| 100th percentile latency | refresh-train-index | 1019.54 | ms | -| 100th percentile service time | refresh-train-index | 1019.54 | ms | -| error rate | refresh-train-index | 0 | % | -| Min Throughput | ivfpq-train-model | 0.01 | models_trained/s | -| Mean Throughput | ivfpq-train-model | 0.01 | models_trained/s | -| Median Throughput | ivfpq-train-model | 0.01 | models_trained/s | -| Max Throughput | ivfpq-train-model | 0.01 | models_trained/s | -| 100th percentile latency | ivfpq-train-model | 150952 | ms | -| 100th percentile service time | ivfpq-train-model | 150952 | ms | -| error rate | ivfpq-train-model | 0 | % | -| Min Throughput | custom-vector-bulk | 32367.4 | docs/s | -| Mean Throughput | custom-vector-bulk | 36027.5 | docs/s | -| Median Throughput | custom-vector-bulk | 35276.7 | docs/s | -| Max Throughput | custom-vector-bulk | 41095 | docs/s | -| 50th percentile latency | custom-vector-bulk | 22.2419 | ms | -| 90th percentile latency | custom-vector-bulk | 70.163 | ms | -| 99th percentile latency | custom-vector-bulk | 308.395 | ms | -| 99.9th percentile latency | custom-vector-bulk | 548.558 | ms | -| 100th percentile latency | custom-vector-bulk | 655.628 | ms | -| 50th percentile service time | custom-vector-bulk | 22.2419 | ms | -| 90th percentile service time | custom-vector-bulk | 70.163 | ms | -| 99th percentile service time | custom-vector-bulk | 308.395 | ms | -| 99.9th percentile service time | custom-vector-bulk | 548.558 | ms | -| 100th percentile service time | custom-vector-bulk | 655.628 | ms | -| error rate | custom-vector-bulk | 0 | % | -| Min Throughput | refresh-target-index | 0.23 | ops/s | -| Mean Throughput | refresh-target-index | 0.23 | ops/s | -| Median Throughput | refresh-target-index | 0.23 | ops/s | -| Max Throughput | refresh-target-index | 0.23 | ops/s | -| 100th percentile latency | refresh-target-index | 4331.17 | ms | -| 100th percentile service time | refresh-target-index | 4331.17 | ms | -| error rate | refresh-target-index | 0 | % | -| Min Throughput | knn-query-from-data-set | 455.19 | ops/s | -| Mean Throughput | knn-query-from-data-set | 511.74 | ops/s | -| Median Throughput | knn-query-from-data-set | 510.85 | ops/s | -| Max Throughput | knn-query-from-data-set | 570.07 | ops/s | -| 50th percentile latency | knn-query-from-data-set | 14.1626 | ms | -| 90th percentile latency | knn-query-from-data-set | 30.2389 | ms | -| 99th percentile latency | knn-query-from-data-set | 71.2793 | ms | -| 99.9th percentile latency | knn-query-from-data-set | 104.733 | ms | -| 99.99th percentile latency | knn-query-from-data-set | 127.298 | ms | -| 100th percentile latency | knn-query-from-data-set | 145.229 | ms | -| 50th percentile service time | knn-query-from-data-set | 14.1626 | ms | -| 90th percentile service time | knn-query-from-data-set | 30.2389 | ms | -| 99th percentile service time | knn-query-from-data-set | 71.2793 | ms | -| 99.9th percentile service time | knn-query-from-data-set | 104.733 | ms | -| 99.99th percentile service time | knn-query-from-data-set | 127.298 | ms | -| 100th percentile service time | knn-query-from-data-set | 145.229 | ms | -| error rate | knn-query-from-data-set | 0 | % | - - ---------------------------------- -[INFO] SUCCESS (took 295 seconds) ---------------------------------- -``` - -## Adding a procedure - -Adding additional benchmarks is very simple. First, place any custom parameter -sources or runners in the [extensions](extensions) directory so that other tests -can use them and also update the [documentation](#custom-extensions) -accordingly. - -Next, create a new test procedure file and add the operations you want your test -to run. Lastly, be sure to update documentation. - -## Custom Extensions - -OpenSearch Benchmarks is very extendable. To fit the plugins needs, we add -customer parameter sources and custom runners. Parameter sources allow users to -supply custom parameters to an operation. Runners are what actually performs -the operations against OpenSearch. - -### Custom Parameter Sources - -Custom parameter sources are defined in [extensions/param_sources.py](extensions/param_sources.py). - -| Name | Description | Parameters | -|-------------------------|------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| bulk-from-data-set | Provides bulk payloads containing vectors from a data set for indexing | 1. data_set_format - (hdf5, bigann)
2. data_set_path - path to data set
3. index - name of index for bulk ingestion
4. field - field to place vector in
5. bulk_size - vectors per bulk request
6. num_vectors - number of vectors to use from the data set. Defaults to the whole data set. | -| knn-query-from-data-set | Provides a query generated from a data set | 1. data_set_format - (hdf5, bigann)
2. data_set_path - path to data set
3. index - name of index to query against
4. field - field to to query against
5. k - number of results to return
6. dimension - size of vectors to produce
7. num_vectors - number of vectors to use from the data set. Defaults to the whole data set. | - - -### Custom Runners - -Custom runners are defined in [extensions/runners.py](extensions/runners.py). - -| Syntax | Description | Parameters | -|--------------------|-----------------------------------------------------|:-------------------------------------------------------------------------------------------------------------| -| custom-vector-bulk | Bulk index a set of vectors in an OpenSearch index. | 1. bulk-from-data-set | -| custom-refresh | Run refresh with retry capabilities. | 1. index - name of index to refresh
2. retries - number of times to retry the operation | -| train-model | Trains a model. | 1. body - model definition
2. timeout - time to wait for model to finish
3. model_id - ID of model | -| delete-model | Deletes a model if it exists. | 1. model_id - ID of model | - -### Testing - -We have a set of unit tests for our extensions in -[tests](tests). To run all the tests, run the following -command: - -```commandline -python -m unittest discover ./tests -``` - -To run an individual test: -```commandline -python -m unittest tests.test_param_sources.VectorsFromDataSetParamSourceTestCase.test_partition_hdf5 -``` diff --git a/benchmarks/osb/__init__.py b/benchmarks/osb/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/benchmarks/osb/extensions/__init__.py b/benchmarks/osb/extensions/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/benchmarks/osb/extensions/data_set.py b/benchmarks/osb/extensions/data_set.py deleted file mode 100644 index 7e8058844..000000000 --- a/benchmarks/osb/extensions/data_set.py +++ /dev/null @@ -1,202 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -import os -import numpy as np -from abc import ABC, ABCMeta, abstractmethod -from enum import Enum -from typing import cast -import h5py -import struct - - -class Context(Enum): - """DataSet context enum. Can be used to add additional context for how a - data-set should be interpreted. - """ - INDEX = 1 - QUERY = 2 - NEIGHBORS = 3 - - -class DataSet(ABC): - """DataSet interface. Used for reading data-sets from files. - - Methods: - read: Read a chunk of data from the data-set - seek: Get to position in the data-set - size: Gets the number of items in the data-set - reset: Resets internal state of data-set to beginning - """ - __metaclass__ = ABCMeta - - BEGINNING = 0 - - @abstractmethod - def read(self, chunk_size: int): - pass - - @abstractmethod - def seek(self, offset: int): - pass - - @abstractmethod - def size(self): - pass - - @abstractmethod - def reset(self): - pass - - -class HDF5DataSet(DataSet): - """ Data-set format corresponding to `ANN Benchmarks - `_ - """ - - FORMAT_NAME = "hdf5" - - def __init__(self, dataset_path: str, context: Context): - file = h5py.File(dataset_path) - self.data = cast(h5py.Dataset, file[self.parse_context(context)]) - self.current = self.BEGINNING - - def read(self, chunk_size: int): - if self.current >= self.size(): - return None - - end_offset = self.current + chunk_size - if end_offset > self.size(): - end_offset = self.size() - - v = cast(np.ndarray, self.data[self.current:end_offset]) - self.current = end_offset - return v - - def seek(self, offset: int): - - if offset < self.BEGINNING: - raise Exception("Offset must be greater than or equal to 0") - - if offset >= self.size(): - raise Exception("Offset must be less than the data set size") - - self.current = offset - - def size(self): - return self.data.len() - - def reset(self): - self.current = self.BEGINNING - - @staticmethod - def parse_context(context: Context) -> str: - if context == Context.NEIGHBORS: - return "neighbors" - - if context == Context.INDEX: - return "train" - - if context == Context.QUERY: - return "test" - - raise Exception("Unsupported context") - - -class BigANNVectorDataSet(DataSet): - """ Data-set format for vector data-sets for `Big ANN Benchmarks - `_ - """ - - DATA_SET_HEADER_LENGTH = 8 - U8BIN_EXTENSION = "u8bin" - FBIN_EXTENSION = "fbin" - FORMAT_NAME = "bigann" - - BYTES_PER_U8INT = 1 - BYTES_PER_FLOAT = 4 - - def __init__(self, dataset_path: str): - self.file = open(dataset_path, 'rb') - self.file.seek(BigANNVectorDataSet.BEGINNING, os.SEEK_END) - num_bytes = self.file.tell() - self.file.seek(BigANNVectorDataSet.BEGINNING) - - if num_bytes < BigANNVectorDataSet.DATA_SET_HEADER_LENGTH: - raise Exception("File is invalid") - - self.num_points = int.from_bytes(self.file.read(4), "little") - self.dimension = int.from_bytes(self.file.read(4), "little") - self.bytes_per_num = self._get_data_size(dataset_path) - - if (num_bytes - BigANNVectorDataSet.DATA_SET_HEADER_LENGTH) != self.num_points * \ - self.dimension * self.bytes_per_num: - raise Exception("File is invalid") - - self.reader = self._value_reader(dataset_path) - self.current = BigANNVectorDataSet.BEGINNING - - def read(self, chunk_size: int): - if self.current >= self.size(): - return None - - end_offset = self.current + chunk_size - if end_offset > self.size(): - end_offset = self.size() - - v = np.asarray([self._read_vector() for _ in - range(end_offset - self.current)]) - self.current = end_offset - return v - - def seek(self, offset: int): - - if offset < self.BEGINNING: - raise Exception("Offset must be greater than or equal to 0") - - if offset >= self.size(): - raise Exception("Offset must be less than the data set size") - - bytes_offset = BigANNVectorDataSet.DATA_SET_HEADER_LENGTH + \ - self.dimension * self.bytes_per_num * offset - self.file.seek(bytes_offset) - self.current = offset - - def _read_vector(self): - return np.asarray([self.reader(self.file) for _ in - range(self.dimension)]) - - def size(self): - return self.num_points - - def reset(self): - self.file.seek(BigANNVectorDataSet.DATA_SET_HEADER_LENGTH) - self.current = BigANNVectorDataSet.BEGINNING - - def __del__(self): - self.file.close() - - @staticmethod - def _get_data_size(file_name): - ext = file_name.split('.')[-1] - if ext == BigANNVectorDataSet.U8BIN_EXTENSION: - return BigANNVectorDataSet.BYTES_PER_U8INT - - if ext == BigANNVectorDataSet.FBIN_EXTENSION: - return BigANNVectorDataSet.BYTES_PER_FLOAT - - raise Exception("Unknown extension") - - @staticmethod - def _value_reader(file_name): - ext = file_name.split('.')[-1] - if ext == BigANNVectorDataSet.U8BIN_EXTENSION: - return lambda file: float(int.from_bytes(file.read(BigANNVectorDataSet.BYTES_PER_U8INT), "little")) - - if ext == BigANNVectorDataSet.FBIN_EXTENSION: - return lambda file: struct.unpack('= self.num_vectors + self.offset: - raise StopIteration - - if self.vector_batch is None or len(self.vector_batch) == 0: - self.vector_batch = self._batch_read(self.data_set) - if self.vector_batch is None: - raise StopIteration - vector = self.vector_batch.pop(0) - self.current += 1 - self.percent_completed = self.current / self.total - - return self._build_query_body(self.index_name, self.field_name, self.k, - vector) - - def _batch_read(self, data_set: DataSet): - return list(data_set.read(self.VECTOR_READ_BATCH_SIZE)) - - def _build_query_body(self, index_name: str, field_name: str, k: int, - vector) -> dict: - """Builds a k-NN query that can be used to execute an approximate nearest - neighbor search against a k-NN plugin index - Args: - index_name: name of index to search - field_name: name of field to search - k: number of results to return - vector: vector used for query - Returns: - A dictionary containing the body used for search, a set of request - parameters to attach to the search and the name of the index. - """ - return { - "index": index_name, - "request-params": { - "_source": { - "exclude": [field_name] - } - }, - "body": { - "size": k, - "query": { - "knn": { - field_name: { - "vector": vector, - "k": k - } - } - } - } - } - - -class BulkVectorsFromDataSetParamSource(VectorsFromDataSetParamSource): - """ Create bulk index requests from a data set of vectors. - - Attributes: - bulk_size: number of vectors per request - retries: number of times to retry the request when it fails - """ - - DEFAULT_RETRIES = 10 - - def __init__(self, workload, params, **kwargs): - super().__init__(params, Context.INDEX) - self.bulk_size: int = parse_int_parameter("bulk_size", params) - self.retries: int = parse_int_parameter("retries", params, - self.DEFAULT_RETRIES) - - def params(self): - """ - Returns: A bulk index parameter with vectors from a data set. - """ - if self.current >= self.num_vectors + self.offset: - raise StopIteration - - def action(doc_id): - return {'index': {'_index': self.index_name, '_id': doc_id}} - - partition = self.data_set.read(self.bulk_size) - body = bulk_transform(partition, self.field_name, action, self.current) - size = len(body) // 2 - self.current += size - self.percent_completed = self.current / self.total - - return { - "body": body, - "retries": self.retries, - "size": size - } diff --git a/benchmarks/osb/extensions/registry.py b/benchmarks/osb/extensions/registry.py deleted file mode 100644 index 5ce17ab6f..000000000 --- a/benchmarks/osb/extensions/registry.py +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -from .param_sources import register as param_sources_register -from .runners import register as runners_register - - -def register(registry): - param_sources_register(registry) - runners_register(registry) diff --git a/benchmarks/osb/extensions/runners.py b/benchmarks/osb/extensions/runners.py deleted file mode 100644 index d048f80b0..000000000 --- a/benchmarks/osb/extensions/runners.py +++ /dev/null @@ -1,121 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. -from opensearchpy.exceptions import ConnectionTimeout -from .util import parse_int_parameter, parse_string_parameter -import logging -import time - - -def register(registry): - registry.register_runner( - "custom-vector-bulk", BulkVectorsFromDataSetRunner(), async_runner=True - ) - registry.register_runner( - "custom-refresh", CustomRefreshRunner(), async_runner=True - ) - registry.register_runner( - "train-model", TrainModelRunner(), async_runner=True - ) - registry.register_runner( - "delete-model", DeleteModelRunner(), async_runner=True - ) - - -class BulkVectorsFromDataSetRunner: - - async def __call__(self, opensearch, params): - size = parse_int_parameter("size", params) - retries = parse_int_parameter("retries", params, 0) + 1 - - for _ in range(retries): - try: - await opensearch.bulk( - body=params["body"], - timeout='5m' - ) - - return size, "docs" - except ConnectionTimeout: - logging.getLogger(__name__)\ - .warning("Bulk vector ingestion timed out. Retrying") - - raise TimeoutError("Failed to submit bulk request in specified number " - "of retries: {}".format(retries)) - - def __repr__(self, *args, **kwargs): - return "custom-vector-bulk" - - -class CustomRefreshRunner: - - async def __call__(self, opensearch, params): - retries = parse_int_parameter("retries", params, 0) + 1 - - for _ in range(retries): - try: - await opensearch.indices.refresh( - index=parse_string_parameter("index", params) - ) - - return - except ConnectionTimeout: - logging.getLogger(__name__)\ - .warning("Custom refresh timed out. Retrying") - - raise TimeoutError("Failed to refresh the index in specified number " - "of retries: {}".format(retries)) - - def __repr__(self, *args, **kwargs): - return "custom-refresh" - - -class TrainModelRunner: - - async def __call__(self, opensearch, params): - # Train a model and wait for it training to complete - body = params["body"] - timeout = parse_int_parameter("timeout", params) - model_id = parse_string_parameter("model_id", params) - - method = "POST" - model_uri = "/_plugins/_knn/models/{}".format(model_id) - await opensearch.transport.perform_request(method, "{}/_train".format(model_uri), body=body) - - start_time = time.time() - while time.time() < start_time + timeout: - time.sleep(1) - model_response = await opensearch.transport.perform_request("GET", model_uri) - - if 'state' not in model_response.keys(): - continue - - if model_response['state'] == 'created': - #TODO: Return model size as well - return 1, "models_trained" - - if model_response['state'] == 'failed': - raise Exception("Failed to create model: {}".format(model_response)) - - raise Exception('Failed to create model: {} within timeout {} seconds' - .format(model_id, timeout)) - - def __repr__(self, *args, **kwargs): - return "train-model" - - -class DeleteModelRunner: - - async def __call__(self, opensearch, params): - # Delete model provided by model id - method = "DELETE" - model_id = parse_string_parameter("model_id", params) - uri = "/_plugins/_knn/models/{}".format(model_id) - - # Ignore if model doesnt exist - await opensearch.transport.perform_request(method, uri, params={"ignore": [400, 404]}) - - def __repr__(self, *args, **kwargs): - return "delete-model" diff --git a/benchmarks/osb/extensions/util.py b/benchmarks/osb/extensions/util.py deleted file mode 100644 index f7f6aab62..000000000 --- a/benchmarks/osb/extensions/util.py +++ /dev/null @@ -1,71 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -import numpy as np -from typing import List -from typing import Dict -from typing import Any - - -def bulk_transform(partition: np.ndarray, field_name: str, action, - offset: int) -> List[Dict[str, Any]]: - """Partitions and transforms a list of vectors into OpenSearch's bulk - injection format. - Args: - offset: to start counting from - partition: An array of vectors to transform. - field_name: field name for action - action: Bulk API action. - Returns: - An array of transformed vectors in bulk format. - """ - actions = [] - _ = [ - actions.extend([action(i + offset), None]) - for i in range(len(partition)) - ] - actions[1::2] = [{field_name: vec} for vec in partition.tolist()] - return actions - - -def parse_string_parameter(key: str, params: dict, default: str = None) -> str: - if key not in params: - if default is not None: - return default - raise ConfigurationError( - "Value cannot be None for param {}".format(key) - ) - - if type(params[key]) is str: - return params[key] - - raise ConfigurationError("Value must be a string for param {}".format(key)) - - -def parse_int_parameter(key: str, params: dict, default: int = None) -> int: - if key not in params: - if default: - return default - raise ConfigurationError( - "Value cannot be None for param {}".format(key) - ) - - if type(params[key]) is int: - return params[key] - - raise ConfigurationError("Value must be a int for param {}".format(key)) - - -class ConfigurationError(Exception): - """Exception raised for errors configuration. - - Attributes: - message -- explanation of the error - """ - - def __init__(self, message: str): - self.message = f'{message}' - super().__init__(self.message) diff --git a/benchmarks/osb/indices/faiss-index.json b/benchmarks/osb/indices/faiss-index.json deleted file mode 100644 index 2db4d34d4..000000000 --- a/benchmarks/osb/indices/faiss-index.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "number_of_shards": {{ target_index_primary_shards }}, - "number_of_replicas": {{ target_index_replica_shards }} - } - }, - "mappings": { - "properties": { - "target_field": { - "type": "knn_vector", - "dimension": {{ target_index_dimension }}, - "method": { - "name": "hnsw", - "space_type": "{{ target_index_space_type }}", - "engine": "faiss", - "parameters": { - "ef_search": {{ hnsw_ef_search }}, - "ef_construction": {{ hnsw_ef_construction }}, - "m": {{ hnsw_m }} - } - } - } - } - } -} diff --git a/benchmarks/osb/indices/model-index.json b/benchmarks/osb/indices/model-index.json deleted file mode 100644 index 0e92c8903..000000000 --- a/benchmarks/osb/indices/model-index.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "number_of_shards": {{ target_index_primary_shards | default(1) }}, - "number_of_replicas": {{ target_index_replica_shards | default(0) }} - } - }, - "mappings": { - "properties": { - "{{ target_field_name }}": { - "type": "knn_vector", - "model_id": "{{ train_model_id }}" - } - } - } -} diff --git a/benchmarks/osb/indices/nmslib-index.json b/benchmarks/osb/indices/nmslib-index.json deleted file mode 100644 index 4ceb57977..000000000 --- a/benchmarks/osb/indices/nmslib-index.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "knn.algo_param.ef_search": {{ hnsw_ef_search }}, - "number_of_shards": {{ target_index_primary_shards }}, - "number_of_replicas": {{ target_index_replica_shards }} - } - }, - "mappings": { - "properties": { - "target_field": { - "type": "knn_vector", - "dimension": {{ target_index_dimension }}, - "method": { - "name": "hnsw", - "space_type": "{{ target_index_space_type }}", - "engine": "nmslib", - "parameters": { - "ef_construction": {{ hnsw_ef_construction }}, - "m": {{ hnsw_m }} - } - } - } - } - } -} diff --git a/benchmarks/osb/indices/train-index.json b/benchmarks/osb/indices/train-index.json deleted file mode 100644 index 82af8215e..000000000 --- a/benchmarks/osb/indices/train-index.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "settings": { - "index": { - "number_of_shards": {{ train_index_primary_shards }}, - "number_of_replicas": {{ train_index_replica_shards }} - } - }, - "mappings": { - "properties": { - "{{ train_field_name }}": { - "type": "knn_vector", - "dimension": {{ target_index_dimension }} - } - } - } -} diff --git a/benchmarks/osb/operations/default.json b/benchmarks/osb/operations/default.json deleted file mode 100644 index ee33166f0..000000000 --- a/benchmarks/osb/operations/default.json +++ /dev/null @@ -1,53 +0,0 @@ -[ - { - "name": "ivfpq-train-model", - "operation-type": "train-model", - "model_id": "{{ train_model_id }}", - "timeout": {{ train_timeout }}, - "body": { - "training_index": "{{ train_index_name }}", - "training_field": "{{ train_field_name }}", - "dimension": {{ target_index_dimension }}, - "search_size": {{ train_search_size }}, - "max_training_vector_count": {{ train_index_num_vectors }}, - "method": { - "name":"ivf", - "engine":"faiss", - "space_type": "{{ target_index_space_type }}", - "parameters":{ - "nlist": {{ ivf_nlists }}, - "nprobes": {{ ivf_nprobes }}, - "encoder":{ - "name":"pq", - "parameters":{ - "code_size": {{ pq_code_size }}, - "m": {{ pq_m }} - } - } - } - } - } - }, - { - "name": "ivf-train-model", - "operation-type": "train-model", - "model_id": "{{ train_model_id }}", - "timeout": {{ train_timeout | default(1000) }}, - "body": { - "training_index": "{{ train_index_name }}", - "training_field": "{{ train_field_name }}", - "search_size": {{ train_search_size }}, - "dimension": {{ target_index_dimension }}, - "max_training_vector_count": {{ train_index_num_vectors }}, - "method": { - "name":"ivf", - "engine":"faiss", - "space_type": "{{ target_index_space_type }}", - "parameters":{ - "nlist": {{ ivf_nlists }}, - "nprobes": {{ ivf_nprobes }} - } - } - } - } -] diff --git a/benchmarks/osb/params/no-train-params.json b/benchmarks/osb/params/no-train-params.json deleted file mode 100644 index 64fe3c296..000000000 --- a/benchmarks/osb/params/no-train-params.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "target_index_name": "target_index", - "target_field_name": "target_field", - "target_index_body": "indices/nmslib-index.json", - "target_index_primary_shards": 3, - "target_index_replica_shards": 1, - "target_index_dimension": 128, - "target_index_space_type": "l2", - "target_index_bulk_size": 200, - "target_index_bulk_index_data_set_format": "hdf5", - "target_index_bulk_index_data_set_path": "", - "target_index_bulk_index_clients": 10, - "hnsw_ef_search": 512, - "hnsw_ef_construction": 512, - "hnsw_m": 16, - - "query_k": 10, - "query_clients": 10, - "query_data_set_format": "hdf5", - "query_data_set_path": "", - - "ivf_nlists": 1, - "ivf_nprobes": 1, - "pq_code_size": 1, - "pq_m": 1, - "train_model_method": "", - "train_model_id": "", - "train_index_name": "", - "train_field_name": "", - "train_index_body": "", - "train_search_size": 1, - "train_timeout": 1, - "train_index_bulk_size": 1, - "train_index_data_set_format": "", - "train_index_data_set_path": "", - "train_index_num_vectors": 1, - "train_index_bulk_index_clients": 1 -} diff --git a/benchmarks/osb/params/train-params.json b/benchmarks/osb/params/train-params.json deleted file mode 100644 index 4c598d25b..000000000 --- a/benchmarks/osb/params/train-params.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "target_index_name": "target_index", - "target_field_name": "target_field", - "target_index_body": "indices/model-index.json", - "target_index_primary_shards": 3, - "target_index_replica_shards": 1, - "target_index_dimension": 128, - "target_index_space_type": "l2", - "target_index_bulk_size": 200, - "target_index_bulk_index_data_set_format": "hdf5", - "target_index_bulk_index_data_set_path": "", - "target_index_bulk_index_clients": 10, - "ivf_nlists": 10, - "ivf_nprobes": 1, - "pq_code_size": 8, - "pq_m": 8, - "train_model_method": "ivfpq", - "train_model_id": "test-model", - "train_index_name": "train_index", - "train_field_name": "train_field", - "train_index_body": "indices/train-index.json", - "train_search_size": 500, - "train_timeout": 5000, - "train_index_primary_shards": 1, - "train_index_replica_shards": 0, - "train_index_bulk_size": 200, - "train_index_data_set_format": "hdf5", - "train_index_data_set_path": "", - "train_index_num_vectors": 1000000, - "train_index_bulk_index_clients": 10, - - "query_k": 10, - "query_clients": 10, - "query_data_set_format": "hdf5", - "query_data_set_path": "" -} diff --git a/benchmarks/osb/procedures/no-train-test.json b/benchmarks/osb/procedures/no-train-test.json deleted file mode 100644 index 03d72d6bd..000000000 --- a/benchmarks/osb/procedures/no-train-test.json +++ /dev/null @@ -1,63 +0,0 @@ -{% import "benchmark.helpers" as benchmark with context %} -{ - "name": "no-train-test", - "default": true, - "schedule": [ - { - "operation": { - "name": "delete-target-index", - "operation-type": "delete-index", - "only-if-exists": true, - "index": "{{ target_index_name }}" - } - }, - { - "operation": { - "name": "create-target-index", - "operation-type": "create-index", - "index": "{{ target_index_name }}" - } - }, - { - "name": "wait-for-cluster-to-be-green", - "operation": "cluster-health", - "request-params": { - "wait_for_status": "green" - } - }, - { - "operation": { - "name": "custom-vector-bulk", - "operation-type": "custom-vector-bulk", - "param-source": "bulk-from-data-set", - "index": "{{ target_index_name }}", - "field": "{{ target_field_name }}", - "bulk_size": {{ target_index_bulk_size }}, - "data_set_format": "{{ target_index_bulk_index_data_set_format }}", - "data_set_path": "{{ target_index_bulk_index_data_set_path }}" - }, - "clients": {{ target_index_bulk_index_clients }} - }, - { - "operation": { - "name": "refresh-target-index", - "operation-type": "custom-refresh", - "index": "{{ target_index_name }}", - "retries": 100 - } - }, - { - "operation": { - "name": "knn-query-from-data-set", - "operation-type": "search", - "index": "{{ target_index_name }}", - "param-source": "knn-query-from-data-set", - "k": {{ query_k }}, - "field": "{{ target_field_name }}", - "data_set_format": "{{ query_data_set_format }}", - "data_set_path": "{{ query_data_set_path }}" - }, - "clients": {{ query_clients }} - } - ] -} diff --git a/benchmarks/osb/procedures/train-test.json b/benchmarks/osb/procedures/train-test.json deleted file mode 100644 index 49930044a..000000000 --- a/benchmarks/osb/procedures/train-test.json +++ /dev/null @@ -1,117 +0,0 @@ -{% import "benchmark.helpers" as benchmark with context %} -{ - "name": "train-test", - "default": false, - "schedule": [ - { - "operation": { - "name": "delete-target-index", - "operation-type": "delete-index", - "only-if-exists": true, - "index": "{{ target_index_name }}" - } - }, - { - "operation": { - "name": "delete-train-index", - "operation-type": "delete-index", - "only-if-exists": true, - "index": "{{ train_index_name }}" - } - }, - { - "operation": { - "operation-type": "delete-model", - "name": "delete-model", - "model_id": "{{ train_model_id }}" - } - }, - { - "operation": { - "name": "create-train-index", - "operation-type": "create-index", - "index": "{{ train_index_name }}" - } - }, - { - "name": "wait-for-train-index-to-be-green", - "operation": "cluster-health", - "request-params": { - "wait_for_status": "green" - } - }, - { - "operation": { - "name": "train-vector-bulk", - "operation-type": "custom-vector-bulk", - "param-source": "bulk-from-data-set", - "index": "{{ train_index_name }}", - "field": "{{ train_field_name }}", - "bulk_size": {{ train_index_bulk_size }}, - "data_set_format": "{{ train_index_data_set_format }}", - "data_set_path": "{{ train_index_data_set_path }}", - "num_vectors": {{ train_index_num_vectors }} - }, - "clients": {{ train_index_bulk_index_clients }} - }, - { - "operation": { - "name": "refresh-train-index", - "operation-type": "custom-refresh", - "index": "{{ train_index_name }}", - "retries": 100 - } - }, - { - "operation": "{{ train_model_method }}-train-model" - }, - { - "operation": { - "name": "create-target-index", - "operation-type": "create-index", - "index": "{{ target_index_name }}" - } - }, - { - "name": "wait-for-target-index-to-be-green", - "operation": "cluster-health", - "request-params": { - "wait_for_status": "green" - } - }, - { - "operation": { - "name": "custom-vector-bulk", - "operation-type": "custom-vector-bulk", - "param-source": "bulk-from-data-set", - "index": "{{ target_index_name }}", - "field": "{{ target_field_name }}", - "bulk_size": {{ target_index_bulk_size }}, - "data_set_format": "{{ target_index_bulk_index_data_set_format }}", - "data_set_path": "{{ target_index_bulk_index_data_set_path }}" - }, - "clients": {{ target_index_bulk_index_clients }} - }, - { - "operation": { - "name": "refresh-target-index", - "operation-type": "custom-refresh", - "index": "{{ target_index_name }}", - "retries": 100 - } - }, - { - "operation": { - "name": "knn-query-from-data-set", - "operation-type": "search", - "index": "{{ target_index_name }}", - "param-source": "knn-query-from-data-set", - "k": {{ query_k }}, - "field": "{{ target_field_name }}", - "data_set_format": "{{ query_data_set_format }}", - "data_set_path": "{{ query_data_set_path }}" - }, - "clients": {{ query_clients }} - } - ] -} diff --git a/benchmarks/osb/requirements.in b/benchmarks/osb/requirements.in deleted file mode 100644 index a9e12b5d3..000000000 --- a/benchmarks/osb/requirements.in +++ /dev/null @@ -1,4 +0,0 @@ -opensearch-py -numpy -h5py -opensearch-benchmark diff --git a/benchmarks/osb/requirements.txt b/benchmarks/osb/requirements.txt deleted file mode 100644 index 271e8ab07..000000000 --- a/benchmarks/osb/requirements.txt +++ /dev/null @@ -1,98 +0,0 @@ -# -# This file is autogenerated by pip-compile with python 3.8 -# To update, run: -# -# pip-compile -# -aiohttp==3.8.1 - # via opensearch-py -aiosignal==1.2.0 - # via aiohttp -async-timeout==4.0.2 - # via aiohttp -attrs==21.4.0 - # via - # aiohttp - # jsonschema -cachetools==4.2.4 - # via google-auth -certifi==2021.10.8 - # via - # opensearch-benchmark - # opensearch-py -charset-normalizer==2.0.12 - # via aiohttp -frozenlist==1.3.0 - # via - # aiohttp - # aiosignal -google-auth==1.22.1 - # via opensearch-benchmark -google-crc32c==1.3.0 - # via google-resumable-media -google-resumable-media==1.1.0 - # via opensearch-benchmark -h5py==3.6.0 - # via -r requirements.in -idna==3.3 - # via yarl -ijson==2.6.1 - # via opensearch-benchmark -importlib-metadata==4.11.3 - # via jsonschema -jinja2==2.11.3 - # via opensearch-benchmark -jsonschema==3.1.1 - # via opensearch-benchmark -markupsafe==2.0.1 - # via - # jinja2 - # opensearch-benchmark -multidict==6.0.2 - # via - # aiohttp - # yarl -numpy==1.22.3 - # via - # -r requirements.in - # h5py -opensearch-benchmark==0.0.2 - # via -r requirements.in -opensearch-py[async]==1.0.0 - # via - # -r requirements.in - # opensearch-benchmark -psutil==5.8.0 - # via opensearch-benchmark -py-cpuinfo==7.0.0 - # via opensearch-benchmark -pyasn1==0.4.8 - # via - # pyasn1-modules - # rsa -pyasn1-modules==0.2.8 - # via google-auth -pyrsistent==0.18.1 - # via jsonschema -rsa==4.8 - # via google-auth -six==1.16.0 - # via - # google-auth - # google-resumable-media - # jsonschema -tabulate==0.8.7 - # via opensearch-benchmark -thespian==3.10.1 - # via opensearch-benchmark -urllib3==1.26.9 - # via opensearch-py -yappi==1.2.3 - # via opensearch-benchmark -yarl==1.7.2 - # via aiohttp -zipp==3.7.0 - # via importlib-metadata - -# The following packages are considered to be unsafe in a requirements file: -# setuptools \ No newline at end of file diff --git a/benchmarks/osb/tests/__init__.py b/benchmarks/osb/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/benchmarks/osb/tests/data_set_helper.py b/benchmarks/osb/tests/data_set_helper.py deleted file mode 100644 index 2b144da49..000000000 --- a/benchmarks/osb/tests/data_set_helper.py +++ /dev/null @@ -1,197 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -from abc import ABC, abstractmethod - -import h5py -import numpy as np - -from osb.extensions.data_set import Context, HDF5DataSet, BigANNVectorDataSet - -""" Module containing utility classes and functions for working with data sets. - -Included are utilities that can be used to build data sets and write them to -paths. -""" - - -class DataSetBuildContext: - """ Data class capturing information needed to build a particular data set - - Attributes: - data_set_context: Indicator of what the data set is used for, - vectors: A 2D array containing vectors that are used to build data set. - path: string representing path where data set should be serialized to. - """ - def __init__(self, data_set_context: Context, vectors: np.ndarray, path: str): - self.data_set_context: Context = data_set_context - self.vectors: np.ndarray = vectors #TODO: Validate shape - self.path: str = path - - def get_num_vectors(self) -> int: - return self.vectors.shape[0] - - def get_dimension(self) -> int: - return self.vectors.shape[1] - - def get_type(self) -> np.dtype: - return self.vectors.dtype - - -class DataSetBuilder(ABC): - """ Abstract builder used to create a build a collection of data sets - - Attributes: - data_set_build_contexts: list of data set build contexts that builder - will build. - """ - def __init__(self): - self.data_set_build_contexts = list() - - def add_data_set_build_context(self, data_set_build_context: DataSetBuildContext): - """ Adds a data set build context to list of contexts to be built. - - Args: - data_set_build_context: DataSetBuildContext to be added to list - - Returns: Updated DataSetBuilder - - """ - self._validate_data_set_context(data_set_build_context) - self.data_set_build_contexts.append(data_set_build_context) - return self - - def build(self): - """ Builds and serializes all data sets build contexts - - Returns: - - """ - [self._build_data_set(data_set_build_context) for data_set_build_context - in self.data_set_build_contexts] - - @abstractmethod - def _build_data_set(self, context: DataSetBuildContext): - """ Builds an individual data set - - Args: - context: DataSetBuildContext of data set to be built - - Returns: - - """ - pass - - @abstractmethod - def _validate_data_set_context(self, context: DataSetBuildContext): - """ Validates that data set context can be added to this builder - - Args: - context: DataSetBuildContext to be validated - - Returns: - - """ - pass - - -class HDF5Builder(DataSetBuilder): - - def __init__(self): - super(HDF5Builder, self).__init__() - self.data_set_meta_data = dict() - - def _validate_data_set_context(self, context: DataSetBuildContext): - if context.path not in self.data_set_meta_data.keys(): - self.data_set_meta_data[context.path] = { - context.data_set_context: context - } - return - - if context.data_set_context in \ - self.data_set_meta_data[context.path].keys(): - raise IllegalDataSetBuildContext("Path and context for data set " - "are already present in builder.") - - self.data_set_meta_data[context.path][context.data_set_context] = \ - context - - @staticmethod - def _validate_extension(context: DataSetBuildContext): - ext = context.path.split('.')[-1] - - if ext != HDF5DataSet.FORMAT_NAME: - raise IllegalDataSetBuildContext("Invalid file extension") - - def _build_data_set(self, context: DataSetBuildContext): - # For HDF5, because multiple data sets can be grouped in the same file, - # we will build data sets in memory and not write to disk until - # _flush_data_sets_to_disk is called - with h5py.File(context.path, 'a') as hf: - hf.create_dataset( - HDF5DataSet.parse_context(context.data_set_context), - data=context.vectors - ) - - -class BigANNBuilder(DataSetBuilder): - - def _validate_data_set_context(self, context: DataSetBuildContext): - self._validate_extension(context) - - # prevent the duplication of paths for data sets - data_set_paths = [c.path for c in self.data_set_build_contexts] - if any(data_set_paths.count(x) > 1 for x in data_set_paths): - raise IllegalDataSetBuildContext("Build context paths have to be " - "unique.") - - @staticmethod - def _validate_extension(context: DataSetBuildContext): - ext = context.path.split('.')[-1] - - if ext != BigANNVectorDataSet.U8BIN_EXTENSION and ext != \ - BigANNVectorDataSet.FBIN_EXTENSION: - raise IllegalDataSetBuildContext("Invalid file extension") - - if ext == BigANNVectorDataSet.U8BIN_EXTENSION and context.get_type() != \ - np.u8int: - raise IllegalDataSetBuildContext("Invalid data type for {} ext." - .format(BigANNVectorDataSet - .U8BIN_EXTENSION)) - - if ext == BigANNVectorDataSet.FBIN_EXTENSION and context.get_type() != \ - np.float32: - print(context.get_type()) - raise IllegalDataSetBuildContext("Invalid data type for {} ext." - .format(BigANNVectorDataSet - .FBIN_EXTENSION)) - - def _build_data_set(self, context: DataSetBuildContext): - num_vectors = context.get_num_vectors() - dimension = context.get_dimension() - - with open(context.path, 'wb') as f: - f.write(int.to_bytes(num_vectors, 4, "little")) - f.write(int.to_bytes(dimension, 4, "little")) - context.vectors.tofile(f) - - -def create_random_2d_array(num_vectors: int, dimension: int) -> np.ndarray: - rng = np.random.default_rng() - return rng.random(size=(num_vectors, dimension), dtype=np.float32) - - -class IllegalDataSetBuildContext(Exception): - """Exception raised when passed in DataSetBuildContext is illegal - - Attributes: - message -- explanation of the error - """ - - def __init__(self, message: str): - self.message = f'{message}' - super().__init__(self.message) - diff --git a/benchmarks/osb/tests/test_param_sources.py b/benchmarks/osb/tests/test_param_sources.py deleted file mode 100644 index cda730cee..000000000 --- a/benchmarks/osb/tests/test_param_sources.py +++ /dev/null @@ -1,353 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -import os -import random -import shutil -import string -import sys -import tempfile -import unittest - -# Add parent directory to path -import numpy as np - -sys.path.append(os.path.abspath(os.path.join(os.getcwd(), os.pardir))) - -from osb.tests.data_set_helper import HDF5Builder, create_random_2d_array, \ - DataSetBuildContext, BigANNBuilder -from osb.extensions.data_set import Context, HDF5DataSet -from osb.extensions.param_sources import VectorsFromDataSetParamSource, \ - QueryVectorsFromDataSetParamSource, BulkVectorsFromDataSetParamSource -from osb.extensions.util import ConfigurationError - -DEFAULT_INDEX_NAME = "test-index" -DEFAULT_FIELD_NAME = "test-field" -DEFAULT_CONTEXT = Context.INDEX -DEFAULT_TYPE = HDF5DataSet.FORMAT_NAME -DEFAULT_NUM_VECTORS = 10 -DEFAULT_DIMENSION = 10 -DEFAULT_RANDOM_STRING_LENGTH = 8 - - -class VectorsFromDataSetParamSourceTestCase(unittest.TestCase): - - def setUp(self) -> None: - self.data_set_dir = tempfile.mkdtemp() - - # Create a data set we know to be valid for convenience - self.valid_data_set_path = _create_data_set( - DEFAULT_NUM_VECTORS, - DEFAULT_DIMENSION, - DEFAULT_TYPE, - DEFAULT_CONTEXT, - self.data_set_dir - ) - - def tearDown(self): - shutil.rmtree(self.data_set_dir) - - def test_missing_params(self): - empty_params = dict() - self.assertRaises( - ConfigurationError, - lambda: VectorsFromDataSetParamSourceTestCase. - TestVectorsFromDataSetParamSource(empty_params, DEFAULT_CONTEXT) - ) - - def test_invalid_data_set_format(self): - invalid_data_set_format = "invalid-data-set-format" - - test_param_source_params = { - "index": DEFAULT_INDEX_NAME, - "field": DEFAULT_FIELD_NAME, - "data_set_format": invalid_data_set_format, - "data_set_path": self.valid_data_set_path, - } - self.assertRaises( - ConfigurationError, - lambda: self.TestVectorsFromDataSetParamSource( - test_param_source_params, - DEFAULT_CONTEXT - ) - ) - - def test_invalid_data_set_path(self): - invalid_data_set_path = "invalid-data-set-path" - test_param_source_params = { - "index": DEFAULT_INDEX_NAME, - "field": DEFAULT_FIELD_NAME, - "data_set_format": HDF5DataSet.FORMAT_NAME, - "data_set_path": invalid_data_set_path, - } - self.assertRaises( - FileNotFoundError, - lambda: self.TestVectorsFromDataSetParamSource( - test_param_source_params, - DEFAULT_CONTEXT - ) - ) - - def test_partition_hdf5(self): - num_vectors = 100 - - hdf5_data_set_path = _create_data_set( - num_vectors, - DEFAULT_DIMENSION, - HDF5DataSet.FORMAT_NAME, - DEFAULT_CONTEXT, - self.data_set_dir - ) - - test_param_source_params = { - "index": DEFAULT_INDEX_NAME, - "field": DEFAULT_FIELD_NAME, - "data_set_format": HDF5DataSet.FORMAT_NAME, - "data_set_path": hdf5_data_set_path, - } - test_param_source = self.TestVectorsFromDataSetParamSource( - test_param_source_params, - DEFAULT_CONTEXT - ) - - num_partitions = 10 - vecs_per_partition = test_param_source.num_vectors // num_partitions - - self._test_partition( - test_param_source, - num_partitions, - vecs_per_partition - ) - - def test_partition_bigann(self): - num_vectors = 100 - float_extension = "fbin" - - bigann_data_set_path = _create_data_set( - num_vectors, - DEFAULT_DIMENSION, - float_extension, - DEFAULT_CONTEXT, - self.data_set_dir - ) - - test_param_source_params = { - "index": DEFAULT_INDEX_NAME, - "field": DEFAULT_FIELD_NAME, - "data_set_format": "bigann", - "data_set_path": bigann_data_set_path, - } - test_param_source = self.TestVectorsFromDataSetParamSource( - test_param_source_params, - DEFAULT_CONTEXT - ) - - num_partitions = 10 - vecs_per_partition = test_param_source.num_vectors // num_partitions - - self._test_partition( - test_param_source, - num_partitions, - vecs_per_partition - ) - - def _test_partition( - self, - test_param_source: VectorsFromDataSetParamSource, - num_partitions: int, - vec_per_partition: int - ): - for i in range(num_partitions): - test_param_source_i = test_param_source.partition(i, num_partitions) - self.assertEqual(test_param_source_i.num_vectors, vec_per_partition) - self.assertEqual(test_param_source_i.offset, i * vec_per_partition) - - class TestVectorsFromDataSetParamSource(VectorsFromDataSetParamSource): - """ - Empty implementation of ABC VectorsFromDataSetParamSource so that we can - test the concrete methods. - """ - - def params(self): - pass - - -class QueryVectorsFromDataSetParamSourceTestCase(unittest.TestCase): - - def setUp(self) -> None: - self.data_set_dir = tempfile.mkdtemp() - - def tearDown(self): - shutil.rmtree(self.data_set_dir) - - def test_params(self): - # Create a data set - k = 12 - data_set_path = _create_data_set( - DEFAULT_NUM_VECTORS, - DEFAULT_DIMENSION, - DEFAULT_TYPE, - Context.QUERY, - self.data_set_dir - ) - - # Create a QueryVectorsFromDataSetParamSource with relevant params - test_param_source_params = { - "index": DEFAULT_INDEX_NAME, - "field": DEFAULT_FIELD_NAME, - "data_set_format": DEFAULT_TYPE, - "data_set_path": data_set_path, - "k": k, - } - query_param_source = QueryVectorsFromDataSetParamSource( - None, test_param_source_params - ) - - # Check each - for i in range(DEFAULT_NUM_VECTORS): - self._check_params( - query_param_source.params(), - DEFAULT_INDEX_NAME, - DEFAULT_FIELD_NAME, - DEFAULT_DIMENSION, - k - ) - - # Assert last call creates stop iteration - self.assertRaises( - StopIteration, - lambda: query_param_source.params() - ) - - def _check_params( - self, - params: dict, - expected_index: str, - expected_field: str, - expected_dimension: int, - expected_k: int - ): - index_name = params.get("index") - self.assertEqual(expected_index, index_name) - body = params.get("body") - self.assertIsInstance(body, dict) - query = body.get("query") - self.assertIsInstance(query, dict) - query_knn = query.get("knn") - self.assertIsInstance(query_knn, dict) - field = query_knn.get(expected_field) - self.assertIsInstance(field, dict) - vector = field.get("vector") - self.assertIsInstance(vector, np.ndarray) - self.assertEqual(len(list(vector)), expected_dimension) - k = field.get("k") - self.assertEqual(k, expected_k) - - -class BulkVectorsFromDataSetParamSourceTestCase(unittest.TestCase): - - def setUp(self) -> None: - self.data_set_dir = tempfile.mkdtemp() - - def tearDown(self): - shutil.rmtree(self.data_set_dir) - - def test_params(self): - num_vectors = 49 - bulk_size = 10 - data_set_path = _create_data_set( - num_vectors, - DEFAULT_DIMENSION, - DEFAULT_TYPE, - Context.INDEX, - self.data_set_dir - ) - - test_param_source_params = { - "index": DEFAULT_INDEX_NAME, - "field": DEFAULT_FIELD_NAME, - "data_set_format": DEFAULT_TYPE, - "data_set_path": data_set_path, - "bulk_size": bulk_size - } - bulk_param_source = BulkVectorsFromDataSetParamSource( - None, test_param_source_params - ) - - # Check each payload returned - vectors_consumed = 0 - while vectors_consumed < num_vectors: - expected_num_vectors = min(num_vectors - vectors_consumed, bulk_size) - self._check_params( - bulk_param_source.params(), - DEFAULT_INDEX_NAME, - DEFAULT_FIELD_NAME, - DEFAULT_DIMENSION, - expected_num_vectors - ) - vectors_consumed += expected_num_vectors - - # Assert last call creates stop iteration - self.assertRaises( - StopIteration, - lambda: bulk_param_source.params() - ) - - def _check_params( - self, - params: dict, - expected_index: str, - expected_field: str, - expected_dimension: int, - expected_num_vectors_in_payload: int - ): - size = params.get("size") - self.assertEqual(size, expected_num_vectors_in_payload) - body = params.get("body") - self.assertIsInstance(body, list) - self.assertEqual(len(body) // 2, expected_num_vectors_in_payload) - - # Bulk payload has 2 parts: first one is the header and the second one - # is the body. The header will have the index name and the body will - # have the vector - for header, req_body in zip(*[iter(body)] * 2): - index = header.get("index") - self.assertIsInstance(index, dict) - index_name = index.get("_index") - self.assertEqual(index_name, expected_index) - - vector = req_body.get(expected_field) - self.assertIsInstance(vector, list) - self.assertEqual(len(vector), expected_dimension) - - -def _create_data_set( - num_vectors: int, - dimension: int, - extension: str, - data_set_context: Context, - data_set_dir -) -> str: - - file_name_base = ''.join(random.choice(string.ascii_letters) for _ in - range(DEFAULT_RANDOM_STRING_LENGTH)) - data_set_file_name = "{}.{}".format(file_name_base, extension) - data_set_path = os.path.join(data_set_dir, data_set_file_name) - context = DataSetBuildContext( - data_set_context, - create_random_2d_array(num_vectors, dimension), - data_set_path) - - if extension == HDF5DataSet.FORMAT_NAME: - HDF5Builder().add_data_set_build_context(context).build() - else: - BigANNBuilder().add_data_set_build_context(context).build() - - return data_set_path - - -if __name__ == '__main__': - unittest.main() diff --git a/benchmarks/osb/workload.json b/benchmarks/osb/workload.json deleted file mode 100644 index bd0d84195..000000000 --- a/benchmarks/osb/workload.json +++ /dev/null @@ -1,17 +0,0 @@ -{% import "benchmark.helpers" as benchmark with context %} -{ - "version": 2, - "description": "k-NN Plugin train workload", - "indices": [ - { - "name": "{{ target_index_name }}", - "body": "{{ target_index_body }}" - }, - { - "name": "{{ train_index_name }}", - "body": "{{ train_index_body }}" - } - ], - "operations": {{ benchmark.collect(parts="operations/*.json") }}, - "test_procedures": [{{ benchmark.collect(parts="procedures/*.json") }}] -} diff --git a/benchmarks/osb/workload.py b/benchmarks/osb/workload.py deleted file mode 100644 index 32e6ad02c..000000000 --- a/benchmarks/osb/workload.py +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -# This code needs to be included at the top of every workload.py file. -# OpenSearch Benchmarks is not able to find other helper files unless the path -# is updated. -import os -import sys -sys.path.append(os.path.abspath(os.getcwd())) - -from extensions.registry import register as custom_register - - -def register(registry): - custom_register(registry) diff --git a/benchmarks/perf-tool/.pylintrc b/benchmarks/perf-tool/.pylintrc deleted file mode 100644 index 15bf4ccc3..000000000 --- a/benchmarks/perf-tool/.pylintrc +++ /dev/null @@ -1,443 +0,0 @@ -# This Pylint rcfile contains a best-effort configuration to uphold the -# best-practices and style described in the Google Python style guide: -# https://google.github.io/styleguide/pyguide.html -# -# Its canonical open-source location is: -# https://google.github.io/styleguide/pylintrc - -[MASTER] - -fail-under=9.0 - -# Files or directories to be skipped. They should be base names, not paths. -ignore=third_party - -# Files or directories matching the regex patterns are skipped. The regex -# matches against base names, not paths. -ignore-patterns= - -# Pickle collected data for later comparisons. -persistent=no - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Use multiple processes to speed up Pylint. -jobs=4 - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -#enable= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -disable=abstract-method, - apply-builtin, - arguments-differ, - attribute-defined-outside-init, - backtick, - bad-option-value, - basestring-builtin, - buffer-builtin, - c-extension-no-member, - consider-using-enumerate, - cmp-builtin, - cmp-method, - coerce-builtin, - coerce-method, - delslice-method, - div-method, - duplicate-code, - eq-without-hash, - execfile-builtin, - file-builtin, - filter-builtin-not-iterating, - fixme, - getslice-method, - global-statement, - hex-method, - idiv-method, - implicit-str-concat-in-sequence, - import-error, - import-self, - import-star-module-level, - inconsistent-return-statements, - input-builtin, - intern-builtin, - invalid-str-codec, - locally-disabled, - long-builtin, - long-suffix, - map-builtin-not-iterating, - misplaced-comparison-constant, - missing-function-docstring, - metaclass-assignment, - next-method-called, - next-method-defined, - no-absolute-import, - no-else-break, - no-else-continue, - no-else-raise, - no-else-return, - no-init, # added - no-member, - no-name-in-module, - no-self-use, - nonzero-method, - oct-method, - old-division, - old-ne-operator, - old-octal-literal, - old-raise-syntax, - parameter-unpacking, - print-statement, - raising-string, - range-builtin-not-iterating, - raw_input-builtin, - rdiv-method, - reduce-builtin, - relative-import, - reload-builtin, - round-builtin, - setslice-method, - signature-differs, - standarderror-builtin, - suppressed-message, - sys-max-int, - too-few-public-methods, - too-many-ancestors, - too-many-arguments, - too-many-boolean-expressions, - too-many-branches, - too-many-instance-attributes, - too-many-locals, - too-many-nested-blocks, - too-many-public-methods, - too-many-return-statements, - too-many-statements, - trailing-newlines, - unichr-builtin, - unicode-builtin, - unnecessary-pass, - unpacking-in-except, - useless-else-on-loop, - useless-object-inheritance, - useless-suppression, - using-cmp-argument, - wrong-import-order, - xrange-builtin, - zip-builtin-not-iterating, - - -[REPORTS] - -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html. You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". This option is deprecated -# and it will be removed in Pylint 2.0. -files-output=no - -# Tells whether to display a full report or only the messages -reports=no - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - - -[BASIC] - -# Good variable names which should always be accepted, separated by a comma -good-names=main,_ - -# Bad variable names which should always be refused, separated by a comma -bad-names= - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -property-classes=abc.abstractproperty,cached_property.cached_property,cached_property.threaded_cached_property,cached_property.cached_property_with_ttl,cached_property.threaded_cached_property_with_ttl - -# Regular expression matching correct function names -function-rgx=^(?:(?PsetUp|tearDown|setUpModule|tearDownModule)|(?P_?[A-Z][a-zA-Z0-9]*)|(?P_?[a-z][a-z0-9_]*))$ - -# Regular expression matching correct variable names -variable-rgx=^[a-z][a-z0-9_]*$ - -# Regular expression matching correct constant names -const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$ - -# Regular expression matching correct attribute names -attr-rgx=^_{0,2}[a-z][a-z0-9_]*$ - -# Regular expression matching correct argument names -argument-rgx=^[a-z][a-z0-9_]*$ - -# Regular expression matching correct class attribute names -class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=^[a-z][a-z0-9_]*$ - -# Regular expression matching correct class names -class-rgx=^_?[A-Z][a-zA-Z0-9]*$ - -# Regular expression matching correct module names -module-rgx=^(_?[a-z][a-z0-9_]*|__init__)$ - -# Regular expression matching correct method names -method-rgx=(?x)^(?:(?P_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase|tearDownTestCase|setupSelf|tearDownClass|setUpClass|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)|(?P_{0,2}[A-Z][a-zA-Z0-9_]*)|(?P_{0,2}[a-z][a-z0-9_]*))$ - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=(__.*__|main|test.*|.*test|.*Test)$ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=10 - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - - -[FORMAT] - -# Maximum number of characters on a single line. -max-line-length=80 - -# TODO(https://github.com/PyCQA/pylint/issues/3352): Direct pylint to exempt -# lines made too long by directives to pytype. - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=(?x)( - ^\s*(\#\ )??$| - ^\s*(from\s+\S+\s+)?import\s+.+$) - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=yes - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check= - -# Maximum number of lines in a module -max-module-lines=99999 - -# String used as indentation unit. The internal Google style guide mandates 2 -# spaces. Google's externaly-published style guide says 4, consistent with -# PEP 8. Here, we use 2 spaces, for conformity with many open-sourced Google -# projects (like TensorFlow). -indent-string=' ' - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=TODO - - -[STRING] - -# This flag controls whether inconsistent-quotes generates a warning when the -# character used as a quote delimiter is used inconsistently within a module. -check-quote-consistency=yes - - -[VARIABLES] - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=^\*{0,2}(_$|unused_|dummy_) - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six,six.moves,past.builtins,future.builtins,functools - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging,absl.logging,tensorflow.io.logging - - -[SIMILARITIES] - -# Minimum lines number of a similarity. -min-similarity-lines=4 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub, - TERMIOS, - Bastion, - rexec, - sets - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant, absl - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__, - __new__, - setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict, - _fields, - _replace, - _source, - _make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls, - class_ - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=StandardError, - Exception, - BaseException diff --git a/benchmarks/perf-tool/.style.yapf b/benchmarks/perf-tool/.style.yapf deleted file mode 100644 index 39b663a7a..000000000 --- a/benchmarks/perf-tool/.style.yapf +++ /dev/null @@ -1,10 +0,0 @@ -[style] -COLUMN_LIMIT: 80 -DEDENT_CLOSING_BRACKETS: True -INDENT_DICTIONARY_VALUE: True -SPLIT_ALL_COMMA_SEPARATED_VALUES: True -SPLIT_ARGUMENTS_WHEN_COMMA_TERMINATED: True -SPLIT_BEFORE_CLOSING_BRACKET: True -SPLIT_BEFORE_EXPRESSION_AFTER_OPENING_PAREN: True -SPLIT_BEFORE_FIRST_ARGUMENT: True -SPLIT_BEFORE_NAMED_ASSIGNS: True diff --git a/benchmarks/perf-tool/README.md b/benchmarks/perf-tool/README.md deleted file mode 100644 index eb4ac0dc1..000000000 --- a/benchmarks/perf-tool/README.md +++ /dev/null @@ -1,279 +0,0 @@ -# OpenSearch k-NN Benchmarking -- [Welcome!](#welcome) -- [Install Prerequisites](#install-prerequisites) -- [Usage](#usage) -- [Contributing](#contributing) - -## Welcome! - -This directory contains the code related to benchmarking the k-NN plugin. -Benchmarks can be run against any OpenSearch cluster with the k-NN plugin -installed. Benchmarks are highly configurable using the test configuration -file. - -## Install Prerequisites - -### Python - -Python 3.7 or above is required. - -### Pip - -Use pip to install the necessary requirements: - -``` -pip install -r requirements.txt -``` - -## Usage - -### Quick Start - -In order to run a benchmark, you must first create a test configuration yml -file. Checkout [this example](https://github.com/opensearch-project/k-NN/blob/main/benchmarks/perf-tool/sample-configs) file -for benchmarking *faiss*'s IVF method. This file contains the definition for -the benchmark that you want to run. At the top are -[test parameters](#test-parameters). These define high level settings of the -test, such as the endpoint of the OpenSearch cluster. - -Next, you define the actions that the test will perform. These actions are -referred to as steps. First, you can define "setup" steps. These are steps that -are run once at the beginning of the execution to configure the cluster how you -want it. These steps do not contribute to the final metrics. - -After that, you define the "steps". These are the steps that the test will be -collecting metrics on. Each step emits certain metrics. These are run -multiple times, depending on the test parameter "num_runs". At the end of the -execution of all of the runs, the metrics from each run are collected and -averaged. - -Lastly, you define the "cleanup" steps. The "cleanup" steps are executed after -each test run. For instance, if you are measuring index performance, you may -want to delete the index after each run. - -To run the test, execute the following command: -``` -python knn-perf-tool.py [--log LOGLEVEL] test config-path.yml output.json - ---log log level of tool, options are: info, debug, warning, error, critical -``` - -The output will be a json document containing the results. - -Additionally, you can get the difference between two test runs using the diff -command: -``` -python knn-perf-tool.py [--log LOGLEVEL] diff result1.json result2.json - ---log log level of tool, options are: info, debug, warning, error, critical -``` - -The output will be the delta between the two metrics. - -### Test Parameters - -| Parameter Name | Description | Default | -| ----------- | ----------- | ----------- | -| endpoint | Endpoint OpenSearch cluster is running on | localhost | -| test_name | Name of test | No default | -| test_id | String ID of test | No default | -| num_runs | Number of runs to execute steps | 1 | -| show_runs | Whether to output each run in addition to the total summary | false | -| setup | List of steps to run once before metric collection starts | [] | -| steps | List of steps that make up one test run. Metrics will be collected on these steps. | No default | -| cleanup | List of steps to run after each test run | [] | - -### Steps - -Included are the list of steps that are currently supported. Each step contains -a set of parameters that are passed in the test configuration file and a set -of metrics that the test produces. - -#### create_index - -Creates an OpenSearch index. - -##### Parameters -| Parameter Name | Description | Default | -| ----------- | ----------- | ----------- | -| index_name | Name of index to create | No default | -| index_spec | Path to index specification | No default | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- | ----------- | ----------- | -| took | Time to execute step end to end. | ms | - -#### disable_refresh - -Disables refresh for all indices in the cluster. - -##### Parameters - -| Parameter Name | Description | Default | -| ----------- | ----------- | ----------- | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- | ----------- | ----------- | -| took | Time to execute step end to end. | ms | - -#### refresh_index - -Refreshes an OpenSearch index. - -##### Parameters - -| Parameter Name | Description | Default | -| ----------- | ----------- | ----------- | -| index_name | Name of index to refresh | No default | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- | ----------- | ----------- | -| took | Time to execute step end to end. | ms | -| store_kb | Size of index after refresh completes | KB | - -#### force_merge - -Force merges an index to a specified number of segments. - -##### Parameters - -| Parameter Name | Description | Default | -| ----------- | ----------- | ----------- | -| index_name | Name of index to force merge | No default | -| max_num_segments | Number of segments to force merge to | No default | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- | ----------- | ----------- | -| took | Time to execute step end to end. | ms | - -#### train_model - -Trains a model. - -##### Parameters - -| Parameter Name | Description | Default | -| ----------- | ----------- | ----------- | -| model_id | Model id to set | Test | -| train_index | Index to pull training data from | No default | -| train_field | Field to pull training data from | No default | -| dimension | Dimension of model | No default | -| description | Description of model | No default | -| max_training_vector_count | Number of training vectors to used | No default | -| method_spec | Path to method specification | No default | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- | ----------- | ----------- | -| took | Time to execute step end to end | ms | - -#### delete_model - -Deletes a model from the cluster. - -##### Parameters - -| Parameter Name | Description | Default | -| ----------- | ----------- | ----------- | -| model_id | Model id to delete | Test | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- | ----------- | ----------- | -| took | Time to execute step end to end | ms | - -#### delete_index - -Deletes an index from the cluster. - -##### Parameters - -| Parameter Name | Description | Default | -| ----------- | ----------- | ----------- | -| index_name | Name of index to delete | No default | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- | ----------- | ----------- | -| took | Time to execute step end to end | ms | - -#### ingest - -Ingests a dataset of vectors into the cluster. - -##### Parameters - -| Parameter Name | Description | Default | -| ----------- | ----------- | ----------- | -| index_name | Name of index to ingest into | No default | -| field_name | Name of field to ingest into | No default | -| bulk_size | Documents per bulk request | 300 | -| dataset_format | Format the data-set is in. Currently hdf5 and bigann is supported. The hdf5 file must be organized in the same way that the ann-benchmarks organizes theirs. | 'hdf5' | -| dataset_path | Path to data-set | No default | -| doc_count | Number of documents to create from data-set | Size of the data-set | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- | ----------- | ----------- | -| took | Total time to ingest the dataset into the index.| ms | - -#### query - -Runs a set of queries against an index. - -##### Parameters - -| Parameter Name | Description | Default | -| ----------- | ----------- | ----------- | -| k | Number of neighbors to return on search | 100 | -| r | r value in Recall@R | 1 | -| index_name | Name of index to search | No default | -| field_name | Name field to search | No default | -| calculate_recall | Whether to calculate recall values | False | -| dataset_format | Format the dataset is in. Currently hdf5 and bigann is supported. The hdf5 file must be organized in the same way that the ann-benchmarks organizes theirs. | 'hdf5' | -| dataset_path | Path to dataset | No default | -| neighbors_format | Format the neighbors dataset is in. Currently hdf5 and bigann is supported. The hdf5 file must be organized in the same way that the ann-benchmarks organizes theirs. | 'hdf5' | -| neighbors_path | Path to neighbors dataset | No default | -| query_count | Number of queries to create from data-set | Size of the data-set | - -##### Metrics - -| Metric Name | Description | Unit | -| ----------- | ----------- | ----------- | -| took | Took times returned per query aggregated as total, p50, p90 and p99 (when applicable) | ms | -| memory_kb | Native memory k-NN is using at the end of the query workload | KB | -| recall@R | ratio of top R results from the ground truth neighbors that are in the K results returned by the plugin | float 0.0-1.0 | -| recall@K | ratio of results returned that were ground truth nearest neighbors | float 0.0-1.0 | - -## Contributing - -### Linting - -Use pylint to lint the code: -``` -pylint knn-perf-tool.py okpt/**/*.py okpt/**/**/*.py -``` - -### Formatting - -We use yapf and the google style to format our code. After installing yapf, you can format your code by running: - -``` -yapf --style google knn-perf-tool.py okpt/**/*.py okpt/**/**/*.py -``` - -### Updating requirements - -Add new requirements to "requirements.in" and run `pip-compile` diff --git a/benchmarks/perf-tool/dataset/data.hdf5 b/benchmarks/perf-tool/dataset/data.hdf5 deleted file mode 100644 index c9268606d..000000000 Binary files a/benchmarks/perf-tool/dataset/data.hdf5 and /dev/null differ diff --git a/benchmarks/perf-tool/knn-perf-tool.py b/benchmarks/perf-tool/knn-perf-tool.py deleted file mode 100644 index 48eedc427..000000000 --- a/benchmarks/perf-tool/knn-perf-tool.py +++ /dev/null @@ -1,10 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. -"""Script for user to run the testing tool.""" - -import okpt.main - -okpt.main.main() diff --git a/benchmarks/perf-tool/okpt/diff/diff.py b/benchmarks/perf-tool/okpt/diff/diff.py deleted file mode 100644 index 23f424ab9..000000000 --- a/benchmarks/perf-tool/okpt/diff/diff.py +++ /dev/null @@ -1,142 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -"""Provides the Diff class.""" - -from enum import Enum -from typing import Any, Dict, Tuple - - -class InvalidTestResultsError(Exception): - """Exception raised when the test results are invalid. - - The results can be invalid if they have different fields, non-numeric - values, or if they don't follow the standard result format. - """ - def __init__(self, msg: str): - self.message = msg - super().__init__(self.message) - - -def _is_numeric(a) -> bool: - return isinstance(a, (int, float)) - - -class TestResultFields(str, Enum): - METADATA = 'metadata' - RESULTS = 'results' - TEST_PARAMETERS = 'test_parameters' - - -class TestResultNames(str, Enum): - BASE = 'base_result' - CHANGED = 'changed_result' - - -class Diff: - """Diff class for validating and diffing two test result files. - - Methods: - diff: Returns the diff between two test results. (changed - base) - """ - def __init__( - self, - base_result: Dict[str, - Any], - changed_result: Dict[str, - Any], - metadata: bool - ): - """Initializes test results and validate them.""" - self.base_result = base_result - self.changed_result = changed_result - self.metadata = metadata - - # make sure results have proper test result fields - is_valid, key, result = self._validate_keys() - if not is_valid: - raise InvalidTestResultsError( - f'{result} has a missing or invalid key `{key}`.' - ) - - self.base_results = self.base_result[TestResultFields.RESULTS] - self.changed_results = self.changed_result[TestResultFields.RESULTS] - - # make sure results have the same fields - is_valid, key, result = self._validate_structure() - if not is_valid: - raise InvalidTestResultsError( - f'key `{key}` is not present in {result}.' - ) - - # make sure results have numeric values - is_valid, key, result = self._validate_types() - if not is_valid: - raise InvalidTestResultsError( - f'key `{key}` in {result} points to a non-numeric value.' - ) - - def _validate_keys(self) -> Tuple[bool, str, str]: - """Ensure both test results have `metadata` and `results` keys.""" - check_keydict = lambda key, res: key in res and isinstance( - res[key], dict) - - # check if results have a `metadata` field and if `metadata` is a dict - if self.metadata: - if not check_keydict(TestResultFields.METADATA, self.base_result): - return (False, TestResultFields.METADATA, TestResultNames.BASE) - if not check_keydict(TestResultFields.METADATA, - self.changed_result): - return ( - False, - TestResultFields.METADATA, - TestResultNames.CHANGED - ) - # check if results have a `results` field and `results` is a dict - if not check_keydict(TestResultFields.RESULTS, self.base_result): - return (False, TestResultFields.RESULTS, TestResultNames.BASE) - if not check_keydict(TestResultFields.RESULTS, self.changed_result): - return (False, TestResultFields.RESULTS, TestResultNames.CHANGED) - return (True, '', '') - - def _validate_structure(self) -> Tuple[bool, str, str]: - """Ensure both test results have the same keys.""" - for k in self.base_results: - if not k in self.changed_results: - return (False, k, TestResultNames.CHANGED) - for k in self.changed_results: - if not k in self.base_results: - return (False, k, TestResultNames.BASE) - return (True, '', '') - - def _validate_types(self) -> Tuple[bool, str, str]: - """Ensure both test results have numeric values.""" - for k, v in self.base_results.items(): - if not _is_numeric(v): - return (False, k, TestResultNames.BASE) - for k, v in self.changed_results.items(): - if not _is_numeric(v): - return (False, k, TestResultNames.BASE) - return (True, '', '') - - def diff(self) -> Dict[str, Any]: - """Return the diff between the two test results. (changed - base)""" - results_diff = { - key: self.changed_results[key] - self.base_results[key] - for key in self.base_results - } - - # add metadata if specified - if self.metadata: - return { - f'{TestResultNames.BASE}_{TestResultFields.METADATA}': - self.base_result[TestResultFields.METADATA], - f'{TestResultNames.CHANGED}_{TestResultFields.METADATA}': - self.changed_result[TestResultFields.METADATA], - 'diff': - results_diff - } - return results_diff diff --git a/benchmarks/perf-tool/okpt/io/args.py b/benchmarks/perf-tool/okpt/io/args.py deleted file mode 100644 index f8c5d8809..000000000 --- a/benchmarks/perf-tool/okpt/io/args.py +++ /dev/null @@ -1,178 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -"""Parses and defines command line arguments for the program. - -Defines the subcommands `test` and `diff` and the corresponding -files that are required by each command. - -Functions: - define_args(): Define the command line arguments. - get_args(): Returns a dictionary of the command line args. -""" - -import argparse -import sys -from dataclasses import dataclass -from io import TextIOWrapper -from typing import Union - -_read_type = argparse.FileType('r') -_write_type = argparse.FileType('w') - - -def _add_config(parser, name, **kwargs): - """"Add configuration file path argument.""" - opts = { - 'type': _read_type, - 'help': 'Path of configuration file.', - 'metavar': 'config_path', - **kwargs, - } - parser.add_argument(name, **opts) - - -def _add_result(parser, name, **kwargs): - """"Add results files paths argument.""" - opts = { - 'type': _read_type, - 'help': 'Path of one result file.', - 'metavar': 'result_path', - **kwargs, - } - parser.add_argument(name, **opts) - - -def _add_results(parser, name, **kwargs): - """"Add results files paths argument.""" - opts = { - 'nargs': '+', - 'type': _read_type, - 'help': 'Paths of result files.', - 'metavar': 'result_paths', - **kwargs, - } - parser.add_argument(name, **opts) - - -def _add_output(parser, name, **kwargs): - """"Add output file path argument.""" - opts = { - 'type': _write_type, - 'help': 'Path of output file.', - 'metavar': 'output_path', - **kwargs, - } - parser.add_argument(name, **opts) - - -def _add_metadata(parser, name, **kwargs): - opts = { - 'action': 'store_true', - **kwargs, - } - parser.add_argument(name, **opts) - - -def _add_test_cmd(subparsers): - test_parser = subparsers.add_parser('test') - _add_config(test_parser, 'config') - _add_output(test_parser, 'output') - - -def _add_diff_cmd(subparsers): - diff_parser = subparsers.add_parser('diff') - _add_metadata(diff_parser, '--metadata') - _add_result( - diff_parser, - 'base_result', - help='Base test result.', - metavar='base_result' - ) - _add_result( - diff_parser, - 'changed_result', - help='Changed test result.', - metavar='changed_result' - ) - _add_output(diff_parser, '--output', default=sys.stdout) - - -@dataclass -class TestArgs: - log: str - command: str - config: TextIOWrapper - output: TextIOWrapper - - -@dataclass -class DiffArgs: - log: str - command: str - metadata: bool - base_result: TextIOWrapper - changed_result: TextIOWrapper - output: TextIOWrapper - - -def get_args() -> Union[TestArgs, DiffArgs]: - """Define, parse and return command line args. - - Returns: - A dict containing the command line args. - """ - parser = argparse.ArgumentParser( - description= - 'Run performance tests against the OpenSearch plugin and various ANN ' - 'libaries.' - ) - - def define_args(): - """Define tool commands.""" - - # add log level arg - parser.add_argument( - '--log', - default='info', - type=str, - choices=['debug', - 'info', - 'warning', - 'error', - 'critical'], - help='Log level of the tool.' - ) - - subparsers = parser.add_subparsers( - title='commands', - dest='command', - help='sub-command help' - ) - subparsers.required = True - - # add subcommands - _add_test_cmd(subparsers) - _add_diff_cmd(subparsers) - - define_args() - args = parser.parse_args() - if args.command == 'test': - return TestArgs( - log=args.log, - command=args.command, - config=args.config, - output=args.output - ) - else: - return DiffArgs( - log=args.log, - command=args.command, - metadata=args.metadata, - base_result=args.base_result, - changed_result=args.changed_result, - output=args.output - ) diff --git a/benchmarks/perf-tool/okpt/io/config/parsers/base.py b/benchmarks/perf-tool/okpt/io/config/parsers/base.py deleted file mode 100644 index 795aab1b2..000000000 --- a/benchmarks/perf-tool/okpt/io/config/parsers/base.py +++ /dev/null @@ -1,67 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -"""Base Parser class. - -Classes: - BaseParser: Base class for config parsers. - -Exceptions: - ConfigurationError: An error in the configuration syntax. -""" - -import os -from io import TextIOWrapper - -import cerberus - -from okpt.io.utils import reader - - -class ConfigurationError(Exception): - """Exception raised for errors in the tool configuration. - - Attributes: - message -- explanation of the error - """ - - def __init__(self, message: str): - self.message = f'{message}' - super().__init__(self.message) - - -def _get_validator_from_schema_name(schema_name: str): - """Get the corresponding Cerberus validator from a schema name.""" - curr_file_dir = os.path.dirname(os.path.abspath(__file__)) - schemas_dir = os.path.join(os.path.dirname(curr_file_dir), 'schemas') - schema_file_path = os.path.join(schemas_dir, f'{schema_name}.yml') - schema_obj = reader.parse_yaml_from_path(schema_file_path) - return cerberus.Validator(schema_obj) - - -class BaseParser: - """Base class for config parsers. - - Attributes: - validator: Cerberus validator for a particular schema - errors: Cerberus validation errors (if any are found during validation) - - Methods: - parse: Parse config. - """ - - def __init__(self, schema_name: str): - self.validator = _get_validator_from_schema_name(schema_name) - self.errors = '' - - def parse(self, file_obj: TextIOWrapper): - """Convert file object to dict, while validating against config schema.""" - config_obj = reader.parse_yaml(file_obj) - is_config_valid = self.validator.validate(config_obj) - if not is_config_valid: - raise ConfigurationError(self.validator.errors) - - return self.validator.document diff --git a/benchmarks/perf-tool/okpt/io/config/parsers/test.py b/benchmarks/perf-tool/okpt/io/config/parsers/test.py deleted file mode 100644 index 34b1752c7..000000000 --- a/benchmarks/perf-tool/okpt/io/config/parsers/test.py +++ /dev/null @@ -1,74 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -"""Provides ToolParser. - -Classes: - ToolParser: Tool config parser. -""" -from dataclasses import dataclass -from io import TextIOWrapper -from typing import List - -from okpt.io.config.parsers import base -from okpt.test.steps.base import Step, StepConfig -from okpt.test.steps.factory import create_step - - -@dataclass -class TestConfig: - test_name: str - test_id: str - endpoint: str - num_runs: int - show_runs: bool - setup: List[Step] - steps: List[Step] - cleanup: List[Step] - - -class TestParser(base.BaseParser): - """Parser for Test config. - - Methods: - parse: Parse and validate the Test config. - """ - - def __init__(self): - super().__init__('test') - - def parse(self, file_obj: TextIOWrapper) -> TestConfig: - """See base class.""" - config_obj = super().parse(file_obj) - - implicit_step_config = dict() - if 'endpoint' in config_obj: - implicit_step_config['endpoint'] = config_obj['endpoint'] - - # Each step should have its own parse - take the config object and check if its valid - setup = [] - if 'setup' in config_obj: - setup = [create_step(StepConfig(step["name"], step, implicit_step_config)) for step in config_obj['setup']] - - steps = [create_step(StepConfig(step["name"], step, implicit_step_config)) for step in config_obj['steps']] - - cleanup = [] - if 'cleanup' in config_obj: - cleanup = [create_step(StepConfig(step["name"], step, implicit_step_config)) for step - in config_obj['cleanup']] - - test_config = TestConfig( - endpoint=config_obj['endpoint'], - test_name=config_obj['test_name'], - test_id=config_obj['test_id'], - num_runs=config_obj['num_runs'], - show_runs=config_obj['show_runs'], - setup=setup, - steps=steps, - cleanup=cleanup - ) - - return test_config diff --git a/benchmarks/perf-tool/okpt/io/config/parsers/util.py b/benchmarks/perf-tool/okpt/io/config/parsers/util.py deleted file mode 100644 index cecb9f2d0..000000000 --- a/benchmarks/perf-tool/okpt/io/config/parsers/util.py +++ /dev/null @@ -1,116 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -"""Utility functions for parsing""" - - -from okpt.io.config.parsers.base import ConfigurationError -from okpt.io.dataset import HDF5DataSet, BigANNNeighborDataSet, \ - BigANNVectorDataSet, DataSet, Context - - -def parse_dataset(dataset_format: str, dataset_path: str, - context: Context) -> DataSet: - if dataset_format == 'hdf5': - return HDF5DataSet(dataset_path, context) - - if dataset_format == 'bigann' and context == Context.NEIGHBORS: - return BigANNNeighborDataSet(dataset_path) - - if dataset_format == 'bigann': - return BigANNVectorDataSet(dataset_path) - - raise Exception("Unsupported data-set format") - - -def parse_string_param(key: str, first_map, second_map, default) -> str: - value = first_map.get(key) - if value is not None: - if type(value) is str: - return value - raise ConfigurationError("Invalid type for {}".format(key)) - - value = second_map.get(key) - if value is not None: - if type(value) is str: - return value - raise ConfigurationError("Invalid type for {}".format(key)) - - if default is None: - raise ConfigurationError("{} must be set".format(key)) - return default - - -def parse_int_param(key: str, first_map, second_map, default) -> int: - value = first_map.get(key) - if value is not None: - if type(value) is int: - return value - raise ConfigurationError("Invalid type for {}".format(key)) - - value = second_map.get(key) - if value is not None: - if type(value) is int: - return value - raise ConfigurationError("Invalid type for {}".format(key)) - - if default is None: - raise ConfigurationError("{} must be set".format(key)) - return default - - -def parse_bool_param(key: str, first_map, second_map, default) -> bool: - value = first_map.get(key) - if value is not None: - if type(value) is bool: - return value - raise ConfigurationError("Invalid type for {}".format(key)) - - value = second_map.get(key) - if value is not None: - if type(value) is bool: - return value - raise ConfigurationError("Invalid type for {}".format(key)) - - if default is None: - raise ConfigurationError("{} must be set".format(key)) - return default - - -def parse_dict_param(key: str, first_map, second_map, default) -> dict: - value = first_map.get(key) - if value is not None: - if type(value) is dict: - return value - raise ConfigurationError("Invalid type for {}".format(key)) - - value = second_map.get(key) - if value is not None: - if type(value) is dict: - return value - raise ConfigurationError("Invalid type for {}".format(key)) - - if default is None: - raise ConfigurationError("{} must be set".format(key)) - return default - - -def parse_list_param(key: str, first_map, second_map, default) -> list: - value = first_map.get(key) - if value is not None: - if type(value) is list: - return value - raise ConfigurationError("Invalid type for {}".format(key)) - - value = second_map.get(key) - if value is not None: - if type(value) is list: - return value - raise ConfigurationError("Invalid type for {}".format(key)) - - if default is None: - raise ConfigurationError("{} must be set".format(key)) - return default diff --git a/benchmarks/perf-tool/okpt/io/config/schemas/test.yml b/benchmarks/perf-tool/okpt/io/config/schemas/test.yml deleted file mode 100644 index 1939a8a31..000000000 --- a/benchmarks/perf-tool/okpt/io/config/schemas/test.yml +++ /dev/null @@ -1,29 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -# defined using the cerberus validation API -# https://docs.python-cerberus.org/en/stable/index.html -endpoint: - type: string - default: "localhost" -test_name: - type: string -test_id: - type: string -num_runs: - type: integer - default: 1 - min: 1 - max: 10000 -show_runs: - type: boolean - default: false -setup: - type: list -steps: - type: list -cleanup: - type: list diff --git a/benchmarks/perf-tool/okpt/io/dataset.py b/benchmarks/perf-tool/okpt/io/dataset.py deleted file mode 100644 index 4f8bc22a2..000000000 --- a/benchmarks/perf-tool/okpt/io/dataset.py +++ /dev/null @@ -1,218 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -"""Defines DataSet interface and implements particular formats - -A DataSet is the basic functionality that it can be read in chunks, or -read completely and reset to the start. - -Currently, we support HDF5 formats from ann-benchmarks and big-ann-benchmarks -datasets. - -Classes: - HDF5DataSet: Format used in ann-benchmarks - BigANNNeighborDataSet: Neighbor format for big-ann-benchmarks - BigANNVectorDataSet: Vector format for big-ann-benchmarks -""" -import os -from abc import ABC, ABCMeta, abstractmethod -from enum import Enum -from typing import cast -import h5py -import numpy as np - -import struct - - -class Context(Enum): - """DataSet context enum. Can be used to add additional context for how a - data-set should be interpreted. - """ - INDEX = 1 - QUERY = 2 - NEIGHBORS = 3 - - -class DataSet(ABC): - """DataSet interface. Used for reading data-sets from files. - - Methods: - read: Read a chunk of data from the data-set - size: Gets the number of items in the data-set - reset: Resets internal state of data-set to beginning - """ - __metaclass__ = ABCMeta - - @abstractmethod - def read(self, chunk_size: int): - pass - - @abstractmethod - def size(self): - pass - - @abstractmethod - def reset(self): - pass - - -class HDF5DataSet(DataSet): - """ Data-set format corresponding to `ANN Benchmarks - `_ - """ - - def __init__(self, dataset_path: str, context: Context): - file = h5py.File(dataset_path) - self.data = cast(h5py.Dataset, file[self._parse_context(context)]) - self.current = 0 - - def read(self, chunk_size: int): - if self.current >= self.size(): - return None - - end_i = self.current + chunk_size - if end_i > self.size(): - end_i = self.size() - - v = cast(np.ndarray, self.data[self.current:end_i]) - self.current = end_i - return v - - def size(self): - return self.data.len() - - def reset(self): - self.current = 0 - - @staticmethod - def _parse_context(context: Context) -> str: - if context == Context.NEIGHBORS: - return "neighbors" - - if context == Context.INDEX: - return "train" - - if context == Context.QUERY: - return "test" - - raise Exception("Unsupported context") - - -class BigANNNeighborDataSet(DataSet): - """ Data-set format for neighbor data-sets for `Big ANN Benchmarks - `_""" - - def __init__(self, dataset_path: str): - self.file = open(dataset_path, 'rb') - self.file.seek(0, os.SEEK_END) - num_bytes = self.file.tell() - self.file.seek(0) - - if num_bytes < 8: - raise Exception("File is invalid") - - self.num_queries = int.from_bytes(self.file.read(4), "little") - self.k = int.from_bytes(self.file.read(4), "little") - - # According to the website, the number of bytes that will follow will - # be: num_queries X K x sizeof(uint32_t) bytes + num_queries X K x - # sizeof(float) - if (num_bytes - 8) != 2 * (self.num_queries * self.k * 4): - raise Exception("File is invalid") - - self.current = 0 - - def read(self, chunk_size: int): - if self.current >= self.size(): - return None - - end_i = self.current + chunk_size - if end_i > self.size(): - end_i = self.size() - - v = [[int.from_bytes(self.file.read(4), "little") for _ in - range(self.k)] for _ in range(end_i - self.current)] - - self.current = end_i - return v - - def size(self): - return self.num_queries - - def reset(self): - self.file.seek(8) - self.current = 0 - - -class BigANNVectorDataSet(DataSet): - """ Data-set format for vector data-sets for `Big ANN Benchmarks - `_ - """ - - def __init__(self, dataset_path: str): - self.file = open(dataset_path, 'rb') - self.file.seek(0, os.SEEK_END) - num_bytes = self.file.tell() - self.file.seek(0) - - if num_bytes < 8: - raise Exception("File is invalid") - - self.num_points = int.from_bytes(self.file.read(4), "little") - self.dimension = int.from_bytes(self.file.read(4), "little") - bytes_per_num = self._get_data_size(dataset_path) - - if (num_bytes - 8) != self.num_points * self.dimension * bytes_per_num: - raise Exception("File is invalid") - - self.reader = self._value_reader(dataset_path) - self.current = 0 - - def read(self, chunk_size: int): - if self.current >= self.size(): - return None - - end_i = self.current + chunk_size - if end_i > self.size(): - end_i = self.size() - - v = np.asarray([self._read_vector() for _ in - range(end_i - self.current)]) - self.current = end_i - return v - - def _read_vector(self): - return np.asarray([self.reader(self.file) for _ in - range(self.dimension)]) - - def size(self): - return self.num_points - - def reset(self): - self.file.seek(8) # Seek to 8 bytes to skip re-reading metadata - self.current = 0 - - @staticmethod - def _get_data_size(file_name): - ext = file_name.split('.')[-1] - if ext == "u8bin": - return 1 - - if ext == "fbin": - return 4 - - raise Exception("Unknown extension") - - @staticmethod - def _value_reader(file_name): - ext = file_name.split('.')[-1] - if ext == "u8bin": - return lambda file: float(int.from_bytes(file.read(1), "little")) - - if ext == "fbin": - return lambda file: struct.unpack(' TextIOWrapper: - """Given a file path, get a readable file object. - - Args: - file path - - Returns: - Writeable file object - """ - return open(path, 'r', encoding='UTF-8') - - -def parse_yaml(file: TextIOWrapper) -> Dict[str, Any]: - """Parses YAML file from file object. - - Args: - file: file object to parse - - Returns: - A dict representing the YAML file. - """ - return yaml.load(file, Loader=yaml.SafeLoader) - - -def parse_yaml_from_path(path: str) -> Dict[str, Any]: - """Parses YAML file from file path. - - Args: - path: file path to parse - - Returns: - A dict representing the YAML file. - """ - file = reader.get_file_obj(path) - return parse_yaml(file) - - -def parse_json(file: TextIOWrapper) -> Dict[str, Any]: - """Parses JSON file from file object. - - Args: - file: file object to parse - - Returns: - A dict representing the JSON file. - """ - return json.load(file) - - -def parse_json_from_path(path: str) -> Dict[str, Any]: - """Parses JSON file from file path. - - Args: - path: file path to parse - - Returns: - A dict representing the JSON file. - """ - file = reader.get_file_obj(path) - return json.load(file) diff --git a/benchmarks/perf-tool/okpt/io/utils/writer.py b/benchmarks/perf-tool/okpt/io/utils/writer.py deleted file mode 100644 index 1f14bfd94..000000000 --- a/benchmarks/perf-tool/okpt/io/utils/writer.py +++ /dev/null @@ -1,40 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. -"""Provides functions for writing to file. - -Functions: - get_file_obj(): Get a writeable file object. - write_json(): Writes a python dictionary to a JSON file -""" - -import json -from io import TextIOWrapper -from typing import Any, Dict, TextIO, Union - - -def get_file_obj(path: str) -> TextIOWrapper: - """Get a writeable file object from a file path. - - Args: - file path - - Returns: - Writeable file object - """ - return open(path, 'w', encoding='UTF-8') - - -def write_json(data: Dict[str, Any], - file: Union[TextIOWrapper, TextIO], - pretty=False): - """Writes a dictionary to a JSON file. - - Args: - data: A dict to write to JSON. - file: Path of output file. - """ - indent = 2 if pretty else 0 - json.dump(data, file, indent=indent) diff --git a/benchmarks/perf-tool/okpt/main.py b/benchmarks/perf-tool/okpt/main.py deleted file mode 100644 index 3e6e022d4..000000000 --- a/benchmarks/perf-tool/okpt/main.py +++ /dev/null @@ -1,55 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -""" Runner script that serves as the main controller of the testing tool.""" - -import logging -import sys -from typing import cast - -from okpt.diff import diff -from okpt.io import args -from okpt.io.config.parsers import test -from okpt.io.utils import reader, writer -from okpt.test import runner - - -def main(): - """Main function of entry module.""" - cli_args = args.get_args() - output = cli_args.output - if cli_args.log: - log_level = getattr(logging, cli_args.log.upper()) - logging.basicConfig(level=log_level) - - if cli_args.command == 'test': - cli_args = cast(args.TestArgs, cli_args) - - # parse config - parser = test.TestParser() - test_config = parser.parse(cli_args.config) - logging.info('Configs are valid.') - - # run tests - test_runner = runner.TestRunner(test_config=test_config) - test_result = test_runner.execute() - - # write test results - logging.debug( - f'Test Result:\n {writer.write_json(test_result, sys.stdout, pretty=True)}' - ) - writer.write_json(test_result, output, pretty=True) - elif cli_args.command == 'diff': - cli_args = cast(args.DiffArgs, cli_args) - - # parse test results - base_result = reader.parse_json(cli_args.base_result) - changed_result = reader.parse_json(cli_args.changed_result) - - # get diff - diff_result = diff.Diff(base_result, changed_result, - cli_args.metadata).diff() - writer.write_json(data=diff_result, file=output, pretty=True) diff --git a/benchmarks/perf-tool/okpt/test/__init__.py b/benchmarks/perf-tool/okpt/test/__init__.py deleted file mode 100644 index ff4fd04d1..000000000 --- a/benchmarks/perf-tool/okpt/test/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. diff --git a/benchmarks/perf-tool/okpt/test/profile.py b/benchmarks/perf-tool/okpt/test/profile.py deleted file mode 100644 index d96860f9a..000000000 --- a/benchmarks/perf-tool/okpt/test/profile.py +++ /dev/null @@ -1,86 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -"""Provides decorators to profile functions. - -The decorators work by adding a `measureable` (time, memory, etc) field to a -dictionary returned by the wrapped function. So the wrapped functions must -return a dictionary in order to be profiled. -""" -import functools -import time -from typing import Callable - - -class TimerStoppedWithoutStartingError(Exception): - """Error raised when Timer is stopped without having been started.""" - - def __init__(self): - super().__init__() - self.message = 'Timer must call start() before calling end().' - - -class _Timer(): - """Timer class for timing. - - Methods: - start: Starts the timer. - end: Stops the timer and returns the time elapsed since start. - - Raises: - TimerStoppedWithoutStartingError: Timer must start before ending. - """ - - def __init__(self): - self.start_time = None - - def start(self): - """Starts the timer.""" - self.start_time = time.perf_counter() - - def end(self) -> float: - """Stops the timer. - - Returns: - The time elapsed in milliseconds. - """ - # ensure timer has started before ending - if self.start_time is None: - raise TimerStoppedWithoutStartingError() - - elapsed = (time.perf_counter() - self.start_time) * 1000 - self.start_time = None - return elapsed - - -def took(f: Callable): - """Profiles a functions execution time. - - Args: - f: Function to profile. - - Returns: - A function that wraps the passed in function and adds a time took field - to the return value. - """ - - @functools.wraps(f) - def wrapper(*args, **kwargs): - """Wrapper function.""" - timer = _Timer() - timer.start() - result = f(*args, **kwargs) - time_took = timer.end() - - # if result already has a `took` field, don't modify the result - if isinstance(result, dict) and 'took' in result: - return result - # `result` may not be a dictionary, so it may not be unpackable - elif isinstance(result, dict): - return {**result, 'took': time_took} - return {'took': time_took} - - return wrapper diff --git a/benchmarks/perf-tool/okpt/test/runner.py b/benchmarks/perf-tool/okpt/test/runner.py deleted file mode 100644 index 150154691..000000000 --- a/benchmarks/perf-tool/okpt/test/runner.py +++ /dev/null @@ -1,107 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -"""Provides a test runner class.""" -import logging -import platform -import sys -from datetime import datetime -from typing import Any, Dict, List - -import psutil - -from okpt.io.config.parsers import test -from okpt.test.test import Test, get_avg - - -def _aggregate_runs(runs: List[Dict[str, Any]]): - """Aggregates and averages a list of test results. - - Args: - results: A list of test results. - num_runs: Number of times the tests were ran. - - Returns: - A dictionary containing the averages of the test results. - """ - aggregate: Dict[str, Any] = {} - for run in runs: - for key, value in run.items(): - if key in aggregate: - aggregate[key].append(value) - else: - aggregate[key] = [value] - - aggregate = {key: get_avg(value) for key, value in aggregate.items()} - return aggregate - - -class TestRunner: - """Test runner class for running tests and aggregating the results. - - Methods: - execute: Run the tests and aggregate the results. - """ - - def __init__(self, test_config: test.TestConfig): - """"Initializes test state.""" - self.test_config = test_config - self.test = Test(test_config) - - def _get_metadata(self): - """"Retrieves the test metadata.""" - svmem = psutil.virtual_memory() - return { - 'test_name': - self.test_config.test_name, - 'test_id': - self.test_config.test_id, - 'date': - datetime.now().strftime('%m/%d/%Y %H:%M:%S'), - 'python_version': - sys.version, - 'os_version': - platform.platform(), - 'processor': - platform.processor() + ', ' + - str(psutil.cpu_count(logical=True)) + ' cores', - 'memory': - str(svmem.used) + ' (used) / ' + str(svmem.available) + - ' (available) / ' + str(svmem.total) + ' (total)', - } - - def execute(self) -> Dict[str, Any]: - """Runs the tests and aggregates the results. - - Returns: - A dictionary containing the aggregate of test results. - """ - logging.info('Setting up tests.') - self.test.setup() - logging.info('Beginning to run tests.') - runs = [] - for i in range(self.test_config.num_runs): - logging.info( - f'Running test {i + 1} of {self.test_config.num_runs}' - ) - runs.append(self.test.execute()) - - logging.info('Finished running tests.') - aggregate = _aggregate_runs(runs) - - # add metadata to test results - test_result = { - 'metadata': - self._get_metadata(), - 'results': - aggregate - } - - # include info about all test runs if specified in config - if self.test_config.show_runs: - test_result['runs'] = runs - - return test_result diff --git a/benchmarks/perf-tool/okpt/test/steps/base.py b/benchmarks/perf-tool/okpt/test/steps/base.py deleted file mode 100644 index 829980421..000000000 --- a/benchmarks/perf-tool/okpt/test/steps/base.py +++ /dev/null @@ -1,60 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. -"""Provides base Step interface.""" - -from dataclasses import dataclass -from typing import Any, Dict, List - -from okpt.test import profile - - -@dataclass -class StepConfig: - step_name: str - config: Dict[str, object] - implicit_config: Dict[str, object] - - -class Step: - """Test step interface. - - Attributes: - label: Name of the step. - - Methods: - execute: Run the step and return a step response with the label and - corresponding measures. - """ - - label = 'base_step' - - def __init__(self, step_config: StepConfig): - self.step_config = step_config - - def _action(self): - """Step logic/behavior to be executed and profiled.""" - pass - - def _get_measures(self) -> List[str]: - """Gets the measures for a particular test""" - pass - - def execute(self) -> List[Dict[str, Any]]: - """Execute step logic while profiling various measures. - - Returns: - Dict containing step label and various step measures. - """ - action = self._action - - # profile the action with measure decorators - add if necessary - action = getattr(profile, 'took')(action) - - result = action() - if isinstance(result, dict): - return [{'label': self.label, **result}] - - raise ValueError('Invalid return by a step') diff --git a/benchmarks/perf-tool/okpt/test/steps/factory.py b/benchmarks/perf-tool/okpt/test/steps/factory.py deleted file mode 100644 index 2b7bcc68d..000000000 --- a/benchmarks/perf-tool/okpt/test/steps/factory.py +++ /dev/null @@ -1,37 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. -"""Factory for creating steps.""" - -from okpt.io.config.parsers.base import ConfigurationError -from okpt.test.steps.base import Step, StepConfig - -from okpt.test.steps.steps import CreateIndexStep, DisableRefreshStep, RefreshIndexStep, DeleteIndexStep, \ - TrainModelStep, DeleteModelStep, ForceMergeStep, ClearCacheStep, IngestStep, QueryStep - - -def create_step(step_config: StepConfig) -> Step: - if step_config.step_name == CreateIndexStep.label: - return CreateIndexStep(step_config) - elif step_config.step_name == DisableRefreshStep.label: - return DisableRefreshStep(step_config) - elif step_config.step_name == RefreshIndexStep.label: - return RefreshIndexStep(step_config) - elif step_config.step_name == TrainModelStep.label: - return TrainModelStep(step_config) - elif step_config.step_name == DeleteModelStep.label: - return DeleteModelStep(step_config) - elif step_config.step_name == DeleteIndexStep.label: - return DeleteIndexStep(step_config) - elif step_config.step_name == IngestStep.label: - return IngestStep(step_config) - elif step_config.step_name == QueryStep.label: - return QueryStep(step_config) - elif step_config.step_name == ForceMergeStep.label: - return ForceMergeStep(step_config) - elif step_config.step_name == ClearCacheStep.label: - return ClearCacheStep(step_config) - - raise ConfigurationError(f'Invalid step {step_config.step_name}') diff --git a/benchmarks/perf-tool/okpt/test/steps/steps.py b/benchmarks/perf-tool/okpt/test/steps/steps.py deleted file mode 100644 index b61781a6e..000000000 --- a/benchmarks/perf-tool/okpt/test/steps/steps.py +++ /dev/null @@ -1,579 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. -"""Provides steps for OpenSearch tests. - -Some of the OpenSearch operations return a `took` field in the response body, -so the profiling decorators aren't needed for some functions. -""" -import json -from typing import Any, Dict, List - -import numpy as np -import requests -import time - -from opensearchpy import OpenSearch, RequestsHttpConnection - -from okpt.io.config.parsers.base import ConfigurationError -from okpt.io.config.parsers.util import parse_string_param, parse_int_param, parse_dataset, parse_bool_param -from okpt.io.dataset import Context -from okpt.io.utils.reader import parse_json_from_path -from okpt.test.steps import base -from okpt.test.steps.base import StepConfig - - -class OpenSearchStep(base.Step): - """See base class.""" - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - self.endpoint = parse_string_param('endpoint', step_config.config, - step_config.implicit_config, - 'localhost') - default_port = 9200 if self.endpoint == 'localhost' else 80 - self.port = parse_int_param('port', step_config.config, - step_config.implicit_config, default_port) - self.opensearch = get_opensearch_client(str(self.endpoint), - int(self.port)) - - -class CreateIndexStep(OpenSearchStep): - """See base class.""" - - label = 'create_index' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - self.index_name = parse_string_param('index_name', step_config.config, - {}, None) - index_spec = parse_string_param('index_spec', step_config.config, {}, - None) - self.body = parse_json_from_path(index_spec) - if self.body is None: - raise ConfigurationError('Index body must be passed in') - - def _action(self): - """Creates an OpenSearch index, applying the index settings/mappings. - - Returns: - An OpenSearch index creation response body. - """ - self.opensearch.indices.create(index=self.index_name, body=self.body) - return {} - - def _get_measures(self) -> List[str]: - return ['took'] - - -class DisableRefreshStep(OpenSearchStep): - """See base class.""" - - label = 'disable_refresh' - - def _action(self): - """Disables the refresh interval for an OpenSearch index. - - Returns: - An OpenSearch index settings update response body. - """ - self.opensearch.indices.put_settings( - body={'index': { - 'refresh_interval': -1 - }}) - - return {} - - def _get_measures(self) -> List[str]: - return ['took'] - - -class RefreshIndexStep(OpenSearchStep): - """See base class.""" - - label = 'refresh_index' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - self.index_name = parse_string_param('index_name', step_config.config, - {}, None) - - def _action(self): - while True: - try: - self.opensearch.indices.refresh(index=self.index_name) - return {'store_kb': get_index_size_in_kb(self.opensearch, - self.index_name)} - except: - pass - - def _get_measures(self) -> List[str]: - return ['took', 'store_kb'] - - -class ForceMergeStep(OpenSearchStep): - """See base class.""" - - label = 'force_merge' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - self.index_name = parse_string_param('index_name', step_config.config, - {}, None) - self.max_num_segments = parse_int_param('max_num_segments', - step_config.config, {}, None) - - def _action(self): - while True: - try: - self.opensearch.indices.forcemerge( - index=self.index_name, - max_num_segments=self.max_num_segments) - return {} - except: - pass - - def _get_measures(self) -> List[str]: - return ['took'] - -class ClearCacheStep(OpenSearchStep): - """See base class.""" - - label = 'clear_cache' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - self.index_name = parse_string_param('index_name', step_config.config, - {}, None) - - def _action(self): - while True: - try: - self.opensearch.indices.clear_cache( - index=self.index_name) - return {} - except: - pass - - def _get_measures(self) -> List[str]: - return ['took'] - - -class TrainModelStep(OpenSearchStep): - """See base class.""" - - label = 'train_model' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - - self.model_id = parse_string_param('model_id', step_config.config, {}, - 'Test') - self.train_index_name = parse_string_param('train_index', - step_config.config, {}, None) - self.train_index_field = parse_string_param('train_field', - step_config.config, {}, - None) - self.dimension = parse_int_param('dimension', step_config.config, {}, - None) - self.description = parse_string_param('description', step_config.config, - {}, 'Default') - self.max_training_vector_count = parse_int_param( - 'max_training_vector_count', step_config.config, {}, 10000000000000) - - method_spec = parse_string_param('method_spec', step_config.config, {}, - None) - self.method = parse_json_from_path(method_spec) - if self.method is None: - raise ConfigurationError('method must be passed in') - - def _action(self): - """Train a model for an index. - - Returns: - The trained model - """ - - # Build body - body = { - 'training_index': self.train_index_name, - 'training_field': self.train_index_field, - 'description': self.description, - 'dimension': self.dimension, - 'method': self.method, - 'max_training_vector_count': self.max_training_vector_count - } - - # So, we trained the model. Now we need to wait until we have to wait - # until the model is created. Poll every - # 1/10 second - requests.post('http://' + self.endpoint + ':' + str(self.port) + - '/_plugins/_knn/models/' + str(self.model_id) + '/_train', - json.dumps(body), - headers={'content-type': 'application/json'}) - - sleep_time = 0.1 - timeout = 100000 - i = 0 - while i < timeout: - time.sleep(sleep_time) - model_response = get_model(self.endpoint, self.port, self.model_id) - if 'state' in model_response.keys() and model_response['state'] == \ - 'created': - return {} - i += 1 - - raise TimeoutError('Failed to create model') - - def _get_measures(self) -> List[str]: - return ['took'] - - -class DeleteModelStep(OpenSearchStep): - """See base class.""" - - label = 'delete_model' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - - self.model_id = parse_string_param('model_id', step_config.config, {}, - 'Test') - - def _action(self): - """Train a model for an index. - - Returns: - The trained model - """ - delete_model(self.endpoint, self.port, self.model_id) - return {} - - def _get_measures(self) -> List[str]: - return ['took'] - - -class DeleteIndexStep(OpenSearchStep): - """See base class.""" - - label = 'delete_index' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - - self.index_name = parse_string_param('index_name', step_config.config, - {}, None) - - def _action(self): - """Delete the index - - Returns: - An empty dict - """ - delete_index(self.opensearch, self.index_name) - return {} - - def _get_measures(self) -> List[str]: - return ['took'] - - -class IngestStep(OpenSearchStep): - """See base class.""" - - label = 'ingest' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - self.index_name = parse_string_param('index_name', step_config.config, - {}, None) - self.field_name = parse_string_param('field_name', step_config.config, - {}, None) - self.bulk_size = parse_int_param('bulk_size', step_config.config, {}, - 300) - self.implicit_config = step_config.implicit_config - dataset_format = parse_string_param('dataset_format', - step_config.config, {}, 'hdf5') - dataset_path = parse_string_param('dataset_path', step_config.config, - {}, None) - self.dataset = parse_dataset(dataset_format, dataset_path, - Context.INDEX) - - input_doc_count = parse_int_param('doc_count', step_config.config, {}, - self.dataset.size()) - self.doc_count = min(input_doc_count, self.dataset.size()) - - def _action(self): - - def action(doc_id): - return {'index': {'_index': self.index_name, '_id': doc_id}} - - # Maintain minimal state outside of this loop. For large data sets, too - # much state may cause out of memory failure - for i in range(0, self.doc_count, self.bulk_size): - partition = self.dataset.read(self.bulk_size) - if partition is None: - break - body = bulk_transform(partition, self.field_name, action, i) - bulk_index(self.opensearch, self.index_name, body) - - self.dataset.reset() - - return {} - - def _get_measures(self) -> List[str]: - return ['took'] - - -class QueryStep(OpenSearchStep): - """See base class.""" - - label = 'query' - - def __init__(self, step_config: StepConfig): - super().__init__(step_config) - self.k = parse_int_param('k', step_config.config, {}, 100) - self.r = parse_int_param('r', step_config.config, {}, 1) - self.index_name = parse_string_param('index_name', step_config.config, - {}, None) - self.field_name = parse_string_param('field_name', step_config.config, - {}, None) - self.calculate_recall = parse_bool_param('calculate_recall', - step_config.config, {}, False) - dataset_format = parse_string_param('dataset_format', - step_config.config, {}, 'hdf5') - dataset_path = parse_string_param('dataset_path', - step_config.config, {}, None) - self.dataset = parse_dataset(dataset_format, dataset_path, - Context.QUERY) - - input_query_count = parse_int_param('query_count', - step_config.config, {}, - self.dataset.size()) - self.query_count = min(input_query_count, self.dataset.size()) - - neighbors_format = parse_string_param('neighbors_format', - step_config.config, {}, 'hdf5') - neighbors_path = parse_string_param('neighbors_path', - step_config.config, {}, None) - self.neighbors = parse_dataset(neighbors_format, neighbors_path, - Context.NEIGHBORS) - self.implicit_config = step_config.implicit_config - - def _action(self): - - def get_body(vec): - return { - 'size': self.k, - 'query': { - 'knn': { - self.field_name: { - 'vector': vec, - 'k': self.k - } - } - } - } - - results = {} - query_responses = [] - for _ in range(self.query_count): - query = self.dataset.read(1) - if query is None: - break - query_responses.append( - query_index(self.opensearch, self.index_name, - get_body(query[0]), [self.field_name])) - - results['took'] = [ - float(query_response['took']) for query_response in query_responses - ] - results['memory_kb'] = get_cache_size_in_kb(self.endpoint, 80) - - if self.calculate_recall: - ids = [[int(hit['_id']) - for hit in query_response['hits']['hits']] - for query_response in query_responses] - results['recall@K'] = recall_at_r(ids, self.neighbors, - self.k, self.k, self.query_count) - self.neighbors.reset() - results[f'recall@{str(self.r)}'] = recall_at_r( - ids, self.neighbors, self.r, self.k, self.query_count) - self.neighbors.reset() - - self.dataset.reset() - - return results - - def _get_measures(self) -> List[str]: - measures = ['took', 'memory_kb'] - - if self.calculate_recall: - measures.extend(['recall@K', f'recall@{str(self.r)}']) - - return measures - - -# Helper functions - (AKA not steps) -def bulk_transform(partition: np.ndarray, field_name: str, action, - offset: int) -> List[Dict[str, Any]]: - """Partitions and transforms a list of vectors into OpenSearch's bulk - injection format. - Args: - offset: to start counting from - partition: An array of vectors to transform. - field_name: field name for action - action: Bulk API action. - Returns: - An array of transformed vectors in bulk format. - """ - actions = [] - _ = [ - actions.extend([action(i + offset), None]) - for i in range(len(partition)) - ] - actions[1::2] = [{field_name: vec} for vec in partition.tolist()] - return actions - - -def delete_index(opensearch: OpenSearch, index_name: str): - """Deletes an OpenSearch index. - - Args: - opensearch: An OpenSearch client. - index_name: Name of the OpenSearch index to be deleted. - """ - opensearch.indices.delete(index=index_name, ignore=[400, 404]) - - -def get_model(endpoint, port, model_id): - """ - Retrieve a model from an OpenSearch cluster - Args: - endpoint: Endpoint OpenSearch is running on - port: Port OpenSearch is running on - model_id: ID of model to be deleted - Returns: - Get model response - """ - response = requests.get('http://' + endpoint + ':' + str(port) + - '/_plugins/_knn/models/' + model_id, - headers={'content-type': 'application/json'}) - return response.json() - - -def delete_model(endpoint, port, model_id): - """ - Deletes a model from OpenSearch cluster - Args: - endpoint: Endpoint OpenSearch is running on - port: Port OpenSearch is running on - model_id: ID of model to be deleted - Returns: - Deleted model response - """ - response = requests.delete('http://' + endpoint + ':' + str(port) + - '/_plugins/_knn/models/' + model_id, - headers={'content-type': 'application/json'}) - return response.json() - - -def get_opensearch_client(endpoint: str, port: int): - """ - Get an opensearch client from an endpoint and port - Args: - endpoint: Endpoint OpenSearch is running on - port: Port OpenSearch is running on - Returns: - OpenSearch client - - """ - # TODO: fix for security in the future - return OpenSearch( - hosts=[{ - 'host': endpoint, - 'port': port - }], - use_ssl=False, - verify_certs=False, - connection_class=RequestsHttpConnection, - timeout=60, - ) - - -def recall_at_r(results, neighbor_dataset, r, k, query_count): - """ - Calculates the recall@R for a set of queries against a ground truth nearest - neighbor set - Args: - results: 2D list containing ids of results returned by OpenSearch. - results[i][j] i refers to query, j refers to - result in the query - neighbor_dataset: 2D dataset containing ids of the true nearest - neighbors for a set of queries - r: number of top results to check if they are in the ground truth k-NN - set. - k: k value for the query - query_count: number of queries - Returns: - Recall at R - """ - correct = 0.0 - for query in range(query_count): - true_neighbors = neighbor_dataset.read(1) - if true_neighbors is None: - break - true_neighbors_set = set(true_neighbors[0][:k]) - for j in range(r): - if results[query][j] in true_neighbors_set: - correct += 1.0 - - return correct / (r * query_count) - - -def get_index_size_in_kb(opensearch, index_name): - """ - Gets the size of an index in kilobytes - Args: - opensearch: opensearch client - index_name: name of index to look up - Returns: - size of index in kilobytes - """ - return int( - opensearch.indices.stats(index_name, metric='store')['indices'] - [index_name]['total']['store']['size_in_bytes']) / 1024 - - -def get_cache_size_in_kb(endpoint, port): - """ - Gets the size of the k-NN cache in kilobytes - Args: - endpoint: endpoint of OpenSearch cluster - port: port of endpoint OpenSearch is running on - Returns: - size of cache in kilobytes - """ - response = requests.get('http://' + endpoint + ':' + str(port) + - '/_plugins/_knn/stats', - headers={'content-type': 'application/json'}) - stats = response.json() - - keys = stats['nodes'].keys() - - total_used = 0 - for key in keys: - total_used += int(stats['nodes'][key]['graph_memory_usage']) - return total_used - - -def query_index(opensearch: OpenSearch, index_name: str, body: dict, - excluded_fields: list): - return opensearch.search(index=index_name, - body=body, - _source_excludes=excluded_fields) - - -def bulk_index(opensearch: OpenSearch, index_name: str, body: List): - return opensearch.bulk(index=index_name, body=body, timeout='5m') diff --git a/benchmarks/perf-tool/okpt/test/test.py b/benchmarks/perf-tool/okpt/test/test.py deleted file mode 100644 index dbd65d053..000000000 --- a/benchmarks/perf-tool/okpt/test/test.py +++ /dev/null @@ -1,180 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. - -"""Provides a base Test class.""" -from math import floor -from typing import Any, Dict, List - -from okpt.io.config.parsers.test import TestConfig -from okpt.test.steps.base import Step - - -def get_avg(values: List[Any]): - """Get average value of a list. - - Args: - values: A list of values. - - Returns: - The average value in the list. - """ - valid_total = len(values) - running_sum = 0.0 - - for value in values: - if value == -1: - valid_total -= 1 - continue - running_sum += value - - if valid_total == 0: - return -1 - return running_sum / valid_total - - -def _pxx(values: List[Any], p: float): - """Calculates the pXX statistics for a given list. - - Args: - values: List of values. - p: Percentile (between 0 and 1). - - Returns: - The corresponding pXX metric. - """ - lowest_percentile = 1 / len(values) - highest_percentile = (len(values) - 1) / len(values) - - # return -1 if p is out of range or if the list doesn't have enough elements - # to support the specified percentile - if p < 0 or p > 1: - return -1.0 - elif p < lowest_percentile or p > highest_percentile: - return -1.0 - else: - return float(values[floor(len(values) * p)]) - - -def _aggregate_steps(step_results: List[Dict[str, Any]], - measure_labels=None): - """Aggregates the steps for a given Test. - - The aggregation process extracts the measures from each step and calculates - the total time spent performing each step measure, including the - percentile metrics, if possible. - - The aggregation process also extracts the test measures by simply summing - up the respective step measures. - - A step measure is formatted as `{step_name}_{measure_name}`, for example, - {bulk_index}_{took} or {query_index}_{memory}. The braces are not included - in the actual key string. - - Percentile/Total step measures are give as - `{step_name}_{measure_name}_{percentile|total}`. - - Test measures are just step measure sums so they just given as - `test_{measure_name}`. - - Args: - steps: List of test steps to be aggregated. - measures: List of step metrics to account for. - - Returns: - A complete test result. - """ - if measure_labels is None: - measure_labels = ['took'] - test_measures = { - f'test_{measure_label}': 0 - for measure_label in measure_labels - } - step_measures: Dict[str, Any] = {} - - # iterate over all test steps - for step in step_results: - step_label = step['label'] - - step_measure_labels = list(step.keys()) - step_measure_labels.remove('label') - - # iterate over all measures in each test step - for measure_label in step_measure_labels: - - step_measure = step[measure_label] - step_measure_label = f'{step_label}_{measure_label}' - - # Add cumulative test measures from steps to test measures - if measure_label in measure_labels: - test_measures[f'test_{measure_label}'] += sum(step_measure) if \ - isinstance(step_measure, list) else step_measure - - if step_measure_label in step_measures: - _ = step_measures[step_measure_label].extend(step_measure) \ - if isinstance(step_measure, list) else \ - step_measures[step_measure_label].append(step_measure) - else: - step_measures[step_measure_label] = step_measure if \ - isinstance(step_measure, list) else [step_measure] - - aggregate = {**test_measures} - # calculate the totals and percentile statistics for each step measure - # where relevant - for step_measure_label, step_measure in step_measures.items(): - step_measure.sort() - - aggregate[step_measure_label + '_total'] = float(sum(step_measure)) - - p50 = _pxx(step_measure, 0.50) - if p50 != -1: - aggregate[step_measure_label + '_p50'] = p50 - p90 = _pxx(step_measure, 0.90) - if p90 != -1: - aggregate[step_measure_label + '_p90'] = p90 - p99 = _pxx(step_measure, 0.99) - if p99 != -1: - aggregate[step_measure_label + '_p99'] = p99 - - return aggregate - - -class Test: - """A base Test class, representing a collection of steps to profiled and - aggregated. - - Methods: - setup: Performs test setup. Usually for steps not intended to be - profiled. - run_steps: Runs the test steps, aggregating the results into the - `step_results` instance field. - cleanup: Perform test cleanup. Useful for clearing the state of a - persistent process like OpenSearch. Cleanup steps are executed after - each run. - execute: Runs steps, cleans up, and aggregates the test result. - """ - def __init__(self, test_config: TestConfig): - """Initializes the test state. - """ - self.test_config = test_config - self.setup_steps: List[Step] = test_config.setup - self.test_steps: List[Step] = test_config.steps - self.cleanup_steps: List[Step] = test_config.cleanup - - def setup(self): - _ = [step.execute() for step in self.setup_steps] - - def _run_steps(self): - step_results = [] - _ = [step_results.extend(step.execute()) for step in self.test_steps] - return step_results - - def _cleanup(self): - _ = [step.execute() for step in self.cleanup_steps] - - def execute(self): - results = self._run_steps() - self._cleanup() - return _aggregate_steps(results) diff --git a/benchmarks/perf-tool/requirements.in b/benchmarks/perf-tool/requirements.in deleted file mode 100644 index fd3555aab..000000000 --- a/benchmarks/perf-tool/requirements.in +++ /dev/null @@ -1,7 +0,0 @@ -Cerberus -opensearch-py -PyYAML -numpy -h5py -requests -psutil diff --git a/benchmarks/perf-tool/requirements.txt b/benchmarks/perf-tool/requirements.txt deleted file mode 100644 index 2886795dc..000000000 --- a/benchmarks/perf-tool/requirements.txt +++ /dev/null @@ -1,39 +0,0 @@ -# -# This file is autogenerated by pip-compile with python 3.9 -# To update, run: -# -# pip-compile -# -cached-property==1.5.2 - # via h5py -cerberus==1.3.4 - # via -r requirements.in -certifi==2021.5.30 - # via - # opensearch-py - # requests -charset-normalizer==2.0.4 - # via requests -h5py==3.3.0 - # via -r requirements.in -idna==3.2 - # via requests -numpy==1.22.1 - # via - # -r requirements.in - # h5py -opensearch-py==1.0.0 - # via -r requirements.in -psutil==5.8.0 - # via -r requirements.in -pyyaml==5.4.1 - # via -r requirements.in -requests==2.26.0 - # via -r requirements.in -urllib3==1.26.6 - # via - # opensearch-py - # requests - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/index-spec.json b/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/index-spec.json deleted file mode 100644 index 5542ef387..000000000 --- a/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/index-spec.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "number_of_shards": 3, - "number_of_replicas": 0 - } - }, - "mappings": { - "properties": { - "target_field": { - "type": "knn_vector", - "model_id": "test-model" - } - } - } -} diff --git a/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/method-spec.json b/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/method-spec.json deleted file mode 100644 index 1aa7f809f..000000000 --- a/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/method-spec.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name":"ivf", - "engine":"faiss", - "parameters":{ - "nlist":16, - "nprobes": 4 - } -} diff --git a/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/test.yml b/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/test.yml deleted file mode 100644 index c8fb42ec4..000000000 --- a/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/test.yml +++ /dev/null @@ -1,60 +0,0 @@ -endpoint: localhost -test_name: faiss_sift_ivf -test_id: "Test workflow for faiss ivf" -num_runs: 3 -show_runs: true -setup: - - name: delete_model - model_id: test-model - - name: delete_index - index_name: target_index - - name: delete_index - index_name: train_index - - name: create_index - index_name: train_index - index_spec: sample-configs/faiss-sift-ivf/train-index-spec.json - - name: ingest - index_name: train_index - field_name: train_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: ../dataset/sift-128-euclidean.hdf5 - - name: refresh_index - index_name: train_index -steps: - - name: train_model - model_id: test-model - train_index: train_index - train_field: train_field - dimension: 128 - method_spec: sample-configs/faiss-sift-ivf/method-spec.json - max_training_vector_count: 1000000000 - - name: create_index - index_name: target_index - index_spec: sample-configs/faiss-sift-ivf/index-spec.json - - name: ingest - index_name: target_index - field_name: target_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: ../dataset/sift-128-euclidean.hdf5 - - name: refresh_index - index_name: target_index - - name: force_merge - index_name: target_index - max_num_segments: 10 - - name: query - k: 100 - r: 1 - calculate_recall: true - index_name: target_index - field_name: target_field - dataset_format: hdf5 - dataset_path: ../dataset/sift-128-euclidean.hdf5 - neighbors_format: hdf5 - neighbors_path: ../dataset/sift-128-euclidean.hdf5 -cleanup: - - name: delete_model - model_id: test-model - - name: delete_index - index_name: target_index diff --git a/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/train-index-spec.json b/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/train-index-spec.json deleted file mode 100644 index 00a418e4f..000000000 --- a/benchmarks/perf-tool/sample-configs/faiss-sift-ivf/train-index-spec.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "settings": { - "index": { - "number_of_shards": 3, - "number_of_replicas": 0 - } - }, - "mappings": { - "properties": { - "train_field": { - "type": "knn_vector", - "dimension": 128 - } - } - } -} diff --git a/benchmarks/perf-tool/sample-configs/nmslib-sift-hnsw/index-spec.json b/benchmarks/perf-tool/sample-configs/nmslib-sift-hnsw/index-spec.json deleted file mode 100644 index 75abe7baa..000000000 --- a/benchmarks/perf-tool/sample-configs/nmslib-sift-hnsw/index-spec.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "settings": { - "index": { - "knn": true, - "knn.algo_param.ef_search": 512, - "refresh_interval": "10s", - "number_of_shards": 1, - "number_of_replicas": 0 - } - }, - "mappings": { - "properties": { - "target_field": { - "type": "knn_vector", - "dimension": 128, - "method": { - "name": "hnsw", - "space_type": "l2", - "engine": "nmslib", - "parameters": { - "ef_construction": 512, - "m": 16 - } - } - } - } - } -} diff --git a/benchmarks/perf-tool/sample-configs/nmslib-sift-hnsw/test.yml b/benchmarks/perf-tool/sample-configs/nmslib-sift-hnsw/test.yml deleted file mode 100644 index deea1ad47..000000000 --- a/benchmarks/perf-tool/sample-configs/nmslib-sift-hnsw/test.yml +++ /dev/null @@ -1,36 +0,0 @@ -endpoint: localhost -test_name: nmslib_sift_hnsw -test_id: "Test workflow for nmslib hnsw" -num_runs: 2 -show_runs: false -setup: - - name: delete_index - index_name: target_index -steps: - - name: create_index - index_name: target_index - index_spec: sample-configs/nmslib-sift-hnsw/index-spec.json - - name: ingest - index_name: target_index - field_name: target_field - bulk_size: 500 - dataset_format: hdf5 - dataset_path: ../dataset/sift-128-euclidean.hdf5 - - name: refresh_index - index_name: target_index - - name: force_merge - index_name: target_index - max_num_segments: 10 - - name: query - k: 100 - r: 1 - calculate_recall: true - index_name: target_index - field_name: target_field - dataset_format: hdf5 - dataset_path: ../dataset/sift-128-euclidean.hdf5 - neighbors_format: hdf5 - neighbors_path: ../dataset/sift-128-euclidean.hdf5 -cleanup: - - name: delete_index - index_name: target_index diff --git a/build-tools/knnplugin-coverage.gradle b/build-tools/knnplugin-coverage.gradle index 57a0eeeec..eb3582dab 100644 --- a/build-tools/knnplugin-coverage.gradle +++ b/build-tools/knnplugin-coverage.gradle @@ -3,12 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ +apply plugin: 'jacoco' + +jacoco { + toolVersion = "0.8.10" +} + /** - * ES Plugin build tools don't work with the Gradle Jacoco Plugin to report coverage out of the box. - * https://github.com/elastic/elasticsearch/issues/28867. - * - * This code sets up coverage reporting manually for ES plugin tests. This is complicated because: - * 1. The ES integTest Task doesn't implement Gradle's JavaForkOptions so we have to manually start the jacoco agent with the test JVM + * This code sets up coverage reporting manually for the k-NN plugin tests. This is complicated because: + * 1. The OS integTest Task doesn't implement Gradle's JavaForkOptions so we have to manually start the jacoco agent with the test JVM * 2. The cluster nodes are stopped using 'kill -9' which means jacoco can't dump it's execution output to a file on VM shutdown * 3. The Java Security Manager prevents JMX from writing execution output to the file. * @@ -16,58 +19,31 @@ * cluster is stopped and dump it to a file. Luckily our current security policy seems to allow this. This will also probably * break if there are multiple nodes in the integTestCluster. But for now... it sorta works. */ -apply plugin: 'jacoco' - -// Get gradle to generate the required jvm agent arg for us using a dummy tasks of type Test. Unfortunately Elastic's -// testing tasks don't derive from Test so the jacoco plugin can't do this automatically. -def jacocoDir = "${buildDir}/jacoco" -task dummyTest(type: Test) { - enabled = false - workingDir = file("/") // Force absolute path to jacoco agent jar - jacoco { - destinationFile = file("${jacocoDir}/test.exec") - destinationFile.parentFile.mkdirs() - jmx = true - } -} - -task dummyIntegTest(type: Test) { - enabled = false - workingDir = file("/") // Force absolute path to jacoco agent jar +integTest { jacoco { - destinationFile = file("${jacocoDir}/integTest.exec") - destinationFile.parentFile.mkdirs() jmx = true } -} -integTest { - systemProperty 'jacoco.dir', "${jacocoDir}" + systemProperty 'jacoco.dir', project.layout.buildDirectory.get().file("jacoco").asFile.absolutePath systemProperty 'jmx.serviceUrl', "service:jmx:rmi:///jndi/rmi://127.0.0.1:7777/jmxrmi" } jacocoTestReport { dependsOn integTest, test - executionData.from = [dummyTest.jacoco.destinationFile, dummyIntegTest.jacoco.destinationFile] - sourceDirectories.from = sourceSets.main.java.sourceDirectories - classDirectories.from = files(sourceSets.main.java.outputDir) - + executionData.from = [integTest.jacoco.destinationFile, test.jacoco.destinationFile] reports { - html.enabled = true // human readable - csv.enabled = true - xml.enabled = true // for coverlay + html.getRequired().set(true) // human readable + csv.getRequired().set(true) + xml.getRequired().set(true) // for coverlay } } -afterEvaluate { - jacocoTestReport.dependsOn integTest +testClusters.integTest { + jvmArgs " ${integTest.jacoco.getAsJvmArg()}" - testClusters.integTest { - jvmArgs " ${dummyIntegTest.jacoco.getAsJvmArg()}".replace('javaagent:','javaagent:/') - systemProperty 'com.sun.management.jmxremote', "true" - systemProperty 'com.sun.management.jmxremote.authenticate', "false" - systemProperty 'com.sun.management.jmxremote.port', "7777" - systemProperty 'com.sun.management.jmxremote.ssl', "false" - systemProperty 'java.rmi.server.hostname', "127.0.0.1" - } + systemProperty 'com.sun.management.jmxremote', "true" + systemProperty 'com.sun.management.jmxremote.authenticate', "false" + systemProperty 'com.sun.management.jmxremote.port', "7777" + systemProperty 'com.sun.management.jmxremote.ssl', "false" + systemProperty 'java.rmi.server.hostname', "127.0.0.1" } diff --git a/build.gradle b/build.gradle index 63a299666..ead0cba08 100644 --- a/build.gradle +++ b/build.gradle @@ -4,14 +4,44 @@ */ import org.opensearch.gradle.test.RestIntegTestTask +import org.opensearch.gradle.testclusters.OpenSearchCluster +import org.apache.tools.ant.taskdefs.condition.Os +import java.nio.file.Paths +import java.util.concurrent.Callable buildscript { ext { // build.version_qualifier parameter applies to knn plugin artifacts only. OpenSearch version must be set // explicitly as 'opensearch.version' property, for instance opensearch.version=2.0.0-rc1-SNAPSHOT - opensearch_version = System.getProperty("opensearch.version", "2.1.0-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "2.19.0-SNAPSHOT") version_qualifier = System.getProperty("build.version_qualifier", "") opensearch_group = "org.opensearch" + isSnapshot = "true" == System.getProperty("build.snapshot", "true") + avx2_enabled = System.getProperty("avx2.enabled", "true") + nproc_count = System.getProperty("nproc.count", "1") + avx512_enabled = System.getProperty("avx512.enabled", "true") + // This flag determines whether the CMake build system should apply a custom patch. It prevents build failures + // when the cmakeJniLib task is run multiple times. If the build.lib.commit_patches is true, the CMake build + // system skips applying the patch if the patches have been applied already. If build.lib.commit_patches is + // false, the patches are always applied. To avoid patch conflicts, disable this flag manually after the first + // run of buildJniLib + apply_lib_patches = System.getProperty("build.lib.apply_patches", "true") + // Flag to determine whether cmake build system should commit the patch or not. In automated build environments + // set this to false. In dev environments, set to true. If false, repetitive execution of cmakeJniLib may fail. + // To prevent this, set build.lib.apply_patches to false after the first cmakeJniLib run. + commit_lib_patches = System.getProperty("build.lib.commit_patches", "true") + + version_tokens = opensearch_version.tokenize('-') + opensearch_build = version_tokens[0] + '.0' + plugin_no_snapshot = opensearch_build + if (version_qualifier) { + opensearch_build += "-${version_qualifier}" + plugin_no_snapshot += "-${version_qualifier}" + } + if (isSnapshot) { + opensearch_build += "-SNAPSHOT" + } + opensearch_no_snapshot = opensearch_build.replace("-SNAPSHOT","") } // This isn't applying from repositories.gradle so repeating git diff it here @@ -24,6 +54,12 @@ buildscript { dependencies { classpath "${opensearch_group}.gradle:build-tools:${opensearch_version}" + configurations.all { + resolutionStrategy { + force("org.eclipse.platform:org.eclipse.core.runtime:4.29.0") // CVE for < 4.29 + force("org.eclipse.platform:org.eclipse.core.resources:4.20.0") // CVE for < 4.20 + } + } } } @@ -35,9 +71,9 @@ plugins { id 'java-library' id 'java-test-fixtures' id 'idea' - id 'jacoco' - id "com.diffplug.spotless" version "6.3.0" apply false - id 'io.freefair.lombok' version '6.4.3' + id "com.diffplug.spotless" version "6.25.0" apply false + id 'io.freefair.lombok' version '8.4' + id "de.undercouch.download" version "5.3.0" } apply from: 'gradle/formatting.gradle' @@ -46,9 +82,91 @@ apply plugin: 'opensearch.rest-test' apply plugin: 'opensearch.pluginzip' apply plugin: 'opensearch.repositories' + +def opensearch_tmp_dir = rootProject.file('build/private/opensearch_tmp').absoluteFile +opensearch_tmp_dir.mkdirs() + ext { - isSnapshot = "true" == System.getProperty("build.snapshot", "true") projectSubstitutions = [:] + + configureSecurityPlugin = { OpenSearchCluster cluster -> + configurations.zipArchive.asFileTree.each { + cluster.plugin(provider(new Callable() { + @Override + RegularFile call() throws Exception { + return new RegularFile() { + @Override + File getAsFile() { + return it + } + } + } + })) + } + + cluster.getNodes().forEach { node -> + var creds = node.getCredentials() + if (creds.isEmpty()) { + creds.add(Map.of('username', 'admin', 'password', 'admin')) + } else { + creds.get(0).putAll(Map.of('username', 'admin', 'password', 'admin')) + } + } + + // Config below including files are copied from security demo configuration + ['esnode.pem', 'esnode-key.pem', 'root-ca.pem'].forEach { file -> + File local = Paths.get(opensearch_tmp_dir.absolutePath, file).toFile() + download.run { + src "https://raw.githubusercontent.com/opensearch-project/security/main/bwc-test/src/test/resources/security/" + file + dest local + overwrite false + } + cluster.extraConfigFile(file, local) + } + + // This configuration is copied from the security plugins demo install: + // https://github.com/opensearch-project/security/blob/2.11.1.0/tools/install_demo_configuration.sh#L365-L388 + cluster.setting("plugins.security.ssl.transport.pemcert_filepath", "esnode.pem") + cluster.setting("plugins.security.ssl.transport.pemkey_filepath", "esnode-key.pem") + cluster.setting("plugins.security.ssl.transport.pemtrustedcas_filepath", "root-ca.pem") + cluster.setting("plugins.security.ssl.transport.enforce_hostname_verification", "false") + cluster.setting("plugins.security.ssl.http.enabled", "true") + cluster.setting("plugins.security.ssl.http.pemcert_filepath", "esnode.pem") + cluster.setting("plugins.security.ssl.http.pemkey_filepath", "esnode-key.pem") + cluster.setting("plugins.security.ssl.http.pemtrustedcas_filepath", "root-ca.pem") + cluster.setting("plugins.security.allow_unsafe_democertificates", "true") + cluster.setting("plugins.security.allow_default_init_securityindex", "true") + cluster.setting("plugins.security.unsupported.inject_user.enabled", "true") + + cluster.setting("plugins.security.authcz.admin_dn", "\n- CN=kirk,OU=client,O=client,L=test, C=de") + cluster.setting('plugins.security.restapi.roles_enabled', '["all_access", "security_rest_api_access"]') + cluster.setting('plugins.security.system_indices.enabled', "true") + cluster.setting('plugins.security.system_indices.indices', '[' + + '".plugins-ml-config", ' + + '".plugins-ml-connector", ' + + '".plugins-ml-model-group", ' + + '".plugins-ml-model", ".plugins-ml-task", ' + + '".plugins-ml-conversation-meta", ' + + '".plugins-ml-conversation-interactions", ' + + '".opendistro-alerting-config", ' + + '".opendistro-alerting-alert*", ' + + '".opendistro-anomaly-results*", ' + + '".opendistro-anomaly-detector*", ' + + '".opendistro-anomaly-checkpoints", ' + + '".opendistro-anomaly-detection-state", ' + + '".opendistro-reports-*", ' + + '".opensearch-notifications-*", ' + + '".opensearch-notebooks", ' + + '".opensearch-observability", ' + + '".ql-datasources", ' + + '".opendistro-asynchronous-search-response*", ' + + '".replication-metadata-store", ' + + '".opensearch-knn-models", ' + + '".geospatial-ip2geo-data*"' + + ']' + ) + cluster.setSecure(true) + } } allprojects { @@ -82,12 +200,27 @@ allprojects { } } +configurations { + zipArchive +} + publishing { + repositories { + maven { + name = "Snapshots" + url = "https://aws.oss.sonatype.org/content/repositories/snapshots" + credentials { + username "$System.env.SONATYPE_USERNAME" + password "$System.env.SONATYPE_PASSWORD" + } + } + } publications { pluginZip(MavenPublication) { publication -> pom { name = "opensearch-knn" description = "OpenSearch k-NN plugin" + groupId = "org.opensearch.plugin" licenses { license { name = "The Apache License, Version 2.0" @@ -111,6 +244,9 @@ compileJava { compileTestJava { options.compilerArgs.addAll(["-processor", 'lombok.launch.AnnotationProcessorHider$AnnotationProcessor']) } +compileTestFixturesJava { + options.compilerArgs.addAll(["-processor", 'lombok.launch.AnnotationProcessorHider$AnnotationProcessor']) +} def usingRemoteCluster = System.properties.containsKey('tests.rest.cluster') || System.properties.containsKey('tests.cluster') def usingMultiNode = project.properties.containsKey('numNodes') @@ -123,10 +259,6 @@ if (!usingRemoteCluster) { } } -jacoco { - toolVersion = "0.8.7" -} - check.dependsOn spotlessCheck check.dependsOn jacocoTestReport @@ -160,30 +292,61 @@ dependencies { api "org.opensearch:opensearch:${opensearch_version}" compileOnly "org.opensearch.plugin:opensearch-scripting-painless-spi:${versions.opensearch}" api group: 'com.google.guava', name: 'failureaccess', version:'1.0.1' - api group: 'com.google.guava', name: 'guava', version:'30.0-jre' + api group: 'com.google.guava', name: 'guava', version:'32.1.3-jre' api group: 'commons-lang', name: 'commons-lang', version: '2.6' testFixturesImplementation "org.opensearch.test:framework:${opensearch_version}" + testImplementation group: 'net.bytebuddy', name: 'byte-buddy', version: '1.15.10' + testImplementation group: 'org.objenesis', name: 'objenesis', version: '3.3' + testImplementation group: 'net.bytebuddy', name: 'byte-buddy-agent', version: '1.15.4' + testFixturesImplementation "org.opensearch:common-utils:${version}" + implementation 'com.github.oshi:oshi-core:6.4.13' + api "net.java.dev.jna:jna:5.13.0" + api "net.java.dev.jna:jna-platform:5.13.0" + implementation 'org.slf4j:slf4j-api:1.7.36' + + zipArchive group: 'org.opensearch.plugin', name:'opensearch-security', version: "${opensearch_build}" } - -def opensearch_tmp_dir = rootProject.file('build/private/opensearch_tmp').absoluteFile -opensearch_tmp_dir.mkdirs() +task windowsPatches(type:Exec) { + commandLine 'cmd', '/c', "Powershell -File $rootDir\\scripts\\windowsScript.ps1" +} task cmakeJniLib(type:Exec) { workingDir 'jni' - commandLine 'cmake', '.', "-DKNN_PLUGIN_VERSION=${opensearch_version}" + def args = [] + args.add("cmake") + args.add(".") + args.add("-DKNN_PLUGIN_VERSION=${opensearch_version}") + args.add("-DAVX2_ENABLED=${avx2_enabled}") + args.add("-DAVX512_ENABLED=${avx512_enabled}") + args.add("-DCOMMIT_LIB_PATCHES=${commit_lib_patches}") + args.add("-DAPPLY_LIB_PATCHES=${apply_lib_patches}") + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + dependsOn windowsPatches + args.add("-G") + args.add("Unix Makefiles") + args.add("-DBLAS_LIBRARIES=$rootDir\\src\\main\\resources\\windowsDependencies\\libopenblas.dll") + args.add("-DLAPACK_LIBRARIES=$rootDir\\src\\main\\resources\\windowsDependencies\\libopenblas.dll") + } + commandLine args } task buildJniLib(type:Exec) { dependsOn cmakeJniLib workingDir 'jni' - commandLine 'make', 'opensearchknn_nmslib', 'opensearchknn_faiss' + commandLine 'make', 'opensearchknn_nmslib', 'opensearchknn_faiss', 'opensearchknn_common', '-j', "${nproc_count}" } test { dependsOn buildJniLib systemProperty 'tests.security.manager', 'false' systemProperty "java.library.path", "$rootDir/jni/release" + //this change enables mockito-inline that supports mocking of static classes/calls + systemProperty "jdk.attach.allowAttachSelf", true + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + // Add the paths of built JNI libraries and its dependent libraries to PATH variable in System variables + environment('PATH', System.getenv('PATH') + ";$rootDir/jni/release" + ";$rootDir/src/main/resources/windowsDependencies") + } } def _numNodes = findProperty('numNodes') as Integer ?: 1 @@ -197,9 +360,20 @@ integTest { // allows integration test classes to access test resource from project root path systemProperty('project.root', project.rootDir.absolutePath) - systemProperty "https", System.getProperty("https") - systemProperty "user", System.getProperty("user") - systemProperty "password", System.getProperty("password") + var is_https = System.getProperty("https") + var user = System.getProperty("user") + var password = System.getProperty("password") + + if (System.getProperty("security.enabled") != null) { + // If security is enabled, set is_https/user/password defaults + is_https = is_https == null ? "true" : is_https + user = user == null ? "admin" : user + password = password == null ? "admin" : password + } + + systemProperty("https", is_https) + systemProperty("user", user) + systemProperty("password", password) doFirst { // Tell the test JVM if the cluster JVM is running under a debugger so that tests can @@ -223,7 +397,18 @@ integTest { testClusters.integTest { testDistribution = "ARCHIVE" + + // Optionally install security + if (System.getProperty("security.enabled") != null) { + configureSecurityPlugin(testClusters.integTest) + } + plugin(project.tasks.bundlePlugin.archiveFile) + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + // Add the paths of built JNI libraries and its dependent libraries to PATH variable in System variables + environment('PATH', System.getenv('PATH') + ";$rootDir/jni/release" + ";$rootDir/src/main/resources/windowsDependencies") + } + // Cluster shrink exception thrown if we try to set numberOfNodes to 1, so only apply if > 1 if (_numNodes > 1) numberOfNodes = _numNodes // When running integration tests it doesn't forward the --debug-jvm to the cluster anymore @@ -270,3 +455,33 @@ run { } } } + +// updateVersion: Task to auto increment to the next development iteration +task updateVersion { + onlyIf { System.getProperty('newVersion') } + doLast { + ext.newVersion = System.getProperty('newVersion') + println "Setting version to ${newVersion}." + // String tokenization to support -SNAPSHOT + // Include the required files that needs to be updated with new Version + ant.replaceregexp(match: opensearch_version.tokenize('-')[0], replace: newVersion.tokenize('-')[0], flags:'g', byline:true) { + fileset(dir: projectDir) { + // Include the required files that needs to be updated with new Version + include(name: ".github/workflows/backwards_compatibility_tests_workflow.yml") + } + } + ant.replaceregexp(file:'build.gradle', match: '"opensearch.version", "\\d.*"', replace: '"opensearch.version", "' + newVersion.tokenize('-')[0] + '-SNAPSHOT"', flags:'g', byline:true) + + ext.os_version_without_snapshot = opensearch_version.tokenize('-')[0] + ext.os_version_major = os_version_without_snapshot.tokenize('.')[0] + ext.os_version_minor = os_version_without_snapshot.tokenize('.')[1] + ext.os_version_patch = os_version_without_snapshot.tokenize('.')[2] + // This condition will check if the BWC workflow is already updated or not and will run next steps if not updated + if (!fileTree(".github/workflows/backwards_compatibility_tests_workflow.yml").getSingleFile().text.contains(os_version_without_snapshot)) { + // Extract the oldBWCVersion from the existing OpenSearch Version (oldBWCVersion = major . (minor-1) . patch) + ext.oldBWCVersion = os_version_major + '.' + Integer.toString(Integer.valueOf(os_version_minor) - 1) + '.' + os_version_patch + // Include the current OpenSearch Version before version bump to the bwc_version matrix + ant.replaceregexp(file:".github/workflows/backwards_compatibility_tests_workflow.yml", match: oldBWCVersion, replace: oldBWCVersion + '", "' + opensearch_version.tokenize('-')[0], flags:'g', byline:true) + } + } +} diff --git a/gradle.properties b/gradle.properties index 162d212e5..c36e5cca0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ # version=1.0.0 -systemProp.bwc.version=1.3.2 +systemProp.bwc.version=1.3.4 org.gradle.jvmargs=--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2..7f93135c4 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 97060749f..d54311bfa 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,12 +1,7 @@ -# -# Copyright OpenSearch Contributors -# SPDX-License-Identifier: Apache-2.0 -# - distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip +distributionSha256Sum=f2b9ed0faf8472cbe469255ae6c86eddb77076c75191741b4a462f33128dd419 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionSha256Sum=e6d864e3b5bc05cc62041842b306383fc1fefcec359e70cebb1d470a6094ca82 - diff --git a/gradlew b/gradlew index 8250acdd7..1aa94a426 100755 --- a/gradlew +++ b/gradlew @@ -1,17 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# SPDX-License-Identifier: Apache-2.0 -# -# The OpenSearch Contributors require contributions made to -# this file be licensed under the Apache-2.0 license or a -# compatible open source license. -# -# Modifications Copyright OpenSearch Contributors. See -# GitHub history for details. -# -# -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -27,78 +17,111 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -107,87 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 9109989e3..6689b85be 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +55,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,38 +65,26 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/jni/CMakeLists.txt b/jni/CMakeLists.txt index c6b2f5f22..6253eed15 100644 --- a/jni/CMakeLists.txt +++ b/jni/CMakeLists.txt @@ -3,20 +3,23 @@ # SPDX-License-Identifier: Apache-2.0 # -cmake_minimum_required(VERSION 3.17) +cmake_minimum_required(VERSION 3.24.0) project(KNNPlugin_JNI) +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/macros.cmake) + # ---------------------------------- SETUP ---------------------------------- # Target libraries to be compiled -set(TARGET_LIB_COMMON opensearchknn_common) # Shared library with common utilities +# Shared library with common utilities. Not a JNI library. Other JNI libs should depend on this one. +set(TARGET_LIB_UTIL opensearchknn_util) +set(TARGET_LIB_COMMON opensearchknn_common) # common lib for JNI set(TARGET_LIB_NMSLIB opensearchknn_nmslib) # nmslib JNI set(TARGET_LIB_FAISS opensearchknn_faiss) # faiss JNI set(TARGET_LIBS "") # Libs to be installed -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) - option(CONFIG_FAISS "Configure faiss library build when this is on") option(CONFIG_NMSLIB "Configure nmslib library build when this is on") option(CONFIG_TEST "Configure tests when this is on") @@ -27,6 +30,18 @@ else() set(CONFIG_ALL OFF) endif () +# `git am` will create commits from the patches in the native libraries. This is ideal for development envs +# because it prevents full lib rebuild everytime cmake is run. However, for build systems that will run the +# build workflow once, it can cause issues because git commits require that the user and the user's email be set. +# See https://github.com/opensearch-project/k-NN/issues/1651. So, we provide a flag that allows users to select between +# the two +if(NOT DEFINED COMMIT_LIB_PATCHES OR "${COMMIT_LIB_PATCHES}" STREQUAL true) + set(GIT_PATCH_COMMAND am) +else() + set(GIT_PATCH_COMMAND apply) +endif() +message(STATUS "Using the following git patch command: \"${GIT_PATCH_COMMAND}\"") + # Set OS specific variables if (${CMAKE_SYSTEM_NAME} STREQUAL Darwin) set(CMAKE_MACOSX_RPATH 1) @@ -35,6 +50,14 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL Darwin) elseif(${CMAKE_SYSTEM_NAME} STREQUAL Linux) set(JVM_OS_TYPE linux) set(LIB_EXT .so) +elseif(${CMAKE_SYSTEM_NAME} STREQUAL Windows) +# Set the CXX_COMPILER_VERSION, CMAKE_CXX_FLAGS, JVM_OS_TYPE, prefix and extension for the target libraries that are built. + set(CXX_COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION}) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpermissive") + set(JVM_OS_TYPE win32) + set(LIB_EXT .dll) + set(CMAKE_SHARED_LIBRARY_PREFIX "") + set(CMAKE_STATIC_LIBRARY_PREFIX "") else() message(FATAL_ERROR "Unable to run on system: ${CMAKE_SYSTEM_NAME}") endif() @@ -52,35 +75,28 @@ elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL x86_64) endif() # ---------------------------------------------------------------------------- +# ---------------------------------- UTIL ---------------------------------- +add_library(${TARGET_LIB_UTIL} SHARED ${CMAKE_CURRENT_SOURCE_DIR}/src/jni_util.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/commons.cpp) +target_include_directories(${TARGET_LIB_UTIL} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include $ENV{JAVA_HOME}/include $ENV{JAVA_HOME}/include/${JVM_OS_TYPE}) +opensearch_set_common_properties(${TARGET_LIB_UTIL}) +list(APPEND TARGET_LIBS ${TARGET_LIB_UTIL}) +# ---------------------------------------------------------------------------- + # ---------------------------------- COMMON ---------------------------------- -add_library(${TARGET_LIB_COMMON} SHARED ${CMAKE_CURRENT_SOURCE_DIR}/src/jni_util.cpp) +add_library(${TARGET_LIB_COMMON} SHARED ${CMAKE_CURRENT_SOURCE_DIR}/src/org_opensearch_knn_jni_JNICommons.cpp) +target_link_libraries(${TARGET_LIB_COMMON} ${TARGET_LIB_UTIL}) target_include_directories(${TARGET_LIB_COMMON} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include $ENV{JAVA_HOME}/include $ENV{JAVA_HOME}/include/${JVM_OS_TYPE}) -set_target_properties(${TARGET_LIB_COMMON} PROPERTIES SUFFIX ${LIB_EXT}) -set_target_properties(${TARGET_LIB_COMMON} PROPERTIES POSITION_INDEPENDENT_CODE ON) -set_target_properties(${TARGET_LIB_COMMON} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/release) +opensearch_set_common_properties(${TARGET_LIB_COMMON}) list(APPEND TARGET_LIBS ${TARGET_LIB_COMMON}) # ---------------------------------------------------------------------------- # ---------------------------------- NMSLIB ---------------------------------- if (${CONFIG_NMSLIB} STREQUAL ON OR ${CONFIG_ALL} STREQUAL ON OR ${CONFIG_TEST} STREQUAL ON) - # Check if nmslib exists - find_path(NMS_REPO_DIR NAMES similarity_search PATHS ${CMAKE_CURRENT_SOURCE_DIR}/external/nmslib) - - # If not, pull the updated submodule - if (NOT EXISTS ${NMS_REPO_DIR}) - message(STATUS "Could not find nmslib. Pulling updated submodule.") - execute_process(COMMAND git submodule update --init -- external/nmslib WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) - endif () - - add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/external/nmslib/similarity_search) - + include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/init-nmslib.cmake) add_library(${TARGET_LIB_NMSLIB} SHARED ${CMAKE_CURRENT_SOURCE_DIR}/src/org_opensearch_knn_jni_NmslibService.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/nmslib_wrapper.cpp) - target_link_libraries(${TARGET_LIB_NMSLIB} NonMetricSpaceLib ${TARGET_LIB_COMMON}) + target_link_libraries(${TARGET_LIB_NMSLIB} NonMetricSpaceLib ${TARGET_LIB_UTIL}) target_include_directories(${TARGET_LIB_NMSLIB} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include $ENV{JAVA_HOME}/include $ENV{JAVA_HOME}/include/${JVM_OS_TYPE} ${CMAKE_CURRENT_SOURCE_DIR}/external/nmslib/similarity_search/include) - set_target_properties(${TARGET_LIB_NMSLIB} PROPERTIES SUFFIX ${LIB_EXT}) - set_target_properties(${TARGET_LIB_NMSLIB} PROPERTIES POSITION_INDEPENDENT_CODE ON) - set_target_properties(${TARGET_LIB_NMSLIB} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/release) - + opensearch_set_common_properties(${TARGET_LIB_NMSLIB}) list(APPEND TARGET_LIBS ${TARGET_LIB_NMSLIB}) endif () @@ -88,150 +104,87 @@ endif () # ---------------------------------- FAISS ---------------------------------- if (${CONFIG_FAISS} STREQUAL ON OR ${CONFIG_ALL} STREQUAL ON OR ${CONFIG_TEST} STREQUAL ON) - set(BUILD_TESTING OFF) # Avoid building faiss tests - set(BLA_STATIC ON) # Statically link BLAS - set(FAISS_OPT_LEVEL generic) # Keep optimization level generic - - if (${CMAKE_SYSTEM_NAME} STREQUAL Darwin) - if(CMAKE_C_COMPILER_ID MATCHES "Clang\$") - set(OpenMP_C_FLAGS "-Xpreprocessor -fopenmp") - set(OpenMP_C_LIB_NAMES "omp") - set(OpenMP_omp_LIBRARY /usr/local/opt/libomp/lib/libomp.dylib) - endif() - - if(CMAKE_CXX_COMPILER_ID MATCHES "Clang\$") - set(OpenMP_CXX_FLAGS "-Xpreprocessor -fopenmp -I/usr/local/opt/libomp/include") - set(OpenMP_CXX_LIB_NAMES "omp") - set(OpenMP_omp_LIBRARY /usr/local/opt/libomp/lib/libomp.dylib) - endif() - endif() - + include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/init-faiss.cmake) find_package(OpenMP REQUIRED) - find_package(ZLIB REQUIRED) - find_package(BLAS REQUIRED) - enable_language(Fortran) - find_package(LAPACK REQUIRED) - - # Check if faiss exists - find_path(FAISS_REPO_DIR NAMES faiss PATHS ${CMAKE_CURRENT_SOURCE_DIR}/external/faiss) - - # If not, pull the updated submodule - if (NOT EXISTS ${FAISS_REPO_DIR}) - message(STATUS "Could not find faiss. Pulling updated submodule.") - execute_process(COMMAND git submodule update --init -- external/faiss WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) - endif () - - set(FAISS_ENABLE_GPU OFF) - set(FAISS_ENABLE_PYTHON OFF) - add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/external/faiss EXCLUDE_FROM_ALL) - - add_library(${TARGET_LIB_FAISS} SHARED ${CMAKE_CURRENT_SOURCE_DIR}/src/org_opensearch_knn_jni_FaissService.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/faiss_wrapper.cpp) - target_link_libraries(${TARGET_LIB_FAISS} faiss ${TARGET_LIB_COMMON} OpenMP::OpenMP_CXX) - target_include_directories(${TARGET_LIB_FAISS} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include $ENV{JAVA_HOME}/include $ENV{JAVA_HOME}/include/${JVM_OS_TYPE} ${CMAKE_CURRENT_SOURCE_DIR}/external/faiss) - set_target_properties(${TARGET_LIB_FAISS} PROPERTIES SUFFIX ${LIB_EXT}) - set_target_properties(${TARGET_LIB_FAISS} PROPERTIES POSITION_INDEPENDENT_CODE ON) - set_target_properties(${TARGET_LIB_FAISS} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/release) - + add_library( + ${TARGET_LIB_FAISS} SHARED + ${CMAKE_CURRENT_SOURCE_DIR}/src/org_opensearch_knn_jni_FaissService.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/faiss_wrapper.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/faiss_util.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/faiss_index_service.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/faiss_methods.cpp + ) + target_link_libraries(${TARGET_LIB_FAISS} ${TARGET_LINK_FAISS_LIB} ${TARGET_LIB_UTIL} OpenMP::OpenMP_CXX) + target_include_directories(${TARGET_LIB_FAISS} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include + $ENV{JAVA_HOME}/include + $ENV{JAVA_HOME}/include/${JVM_OS_TYPE} + ${CMAKE_CURRENT_SOURCE_DIR}/external/faiss + ) + opensearch_set_common_properties(${TARGET_LIB_FAISS}) list(APPEND TARGET_LIBS ${TARGET_LIB_FAISS}) endif () # --------------------------------------------------------------------------- # --------------------------------- TESTS ----------------------------------- -if (${CONFIG_ALL} STREQUAL ON OR ${CONFIG_TEST} STREQUAL ON) - # Reference - https://crascit.com/2015/07/25/cmake-gtest/ - configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt) - execute_process(COMMAND "${CMAKE_COMMAND}" -G "${CMAKE_GENERATOR}" . - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/googletest-download" - ) - execute_process(COMMAND "${CMAKE_COMMAND}" --build . - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/googletest-download" - ) - set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) - - add_subdirectory("${CMAKE_BINARY_DIR}/googletest-src" - "${CMAKE_BINARY_DIR}/googletest-build" EXCLUDE_FROM_ALL - ) - add_executable( - jni_test - tests/faiss_wrapper_test.cpp - tests/nmslib_wrapper_test.cpp - tests/test_util.cpp) - - target_link_libraries( - jni_test - gtest_main - gmock_main - faiss - NonMetricSpaceLib - OpenMP::OpenMP_CXX - ${TARGET_LIB_FAISS} - ${TARGET_LIB_NMSLIB} - ${TARGET_LIB_COMMON} - ) - - target_include_directories(jni_test PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/tests - ${CMAKE_CURRENT_SOURCE_DIR}/include - $ENV{JAVA_HOME}/include - $ENV{JAVA_HOME}/include/${JVM_OS_TYPE} - ${CMAKE_CURRENT_SOURCE_DIR}/external/faiss - ${CMAKE_CURRENT_SOURCE_DIR}/external/nmslib/similarity_search/include - ${gtest_SOURCE_DIR}/include - ${gmock_SOURCE_DIR}/include) - - - set_target_properties(jni_test PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bin) -endif () -# --------------------------------------------------------------------------- +# Windows : Comment the TESTS for now because the tests are failing(failing to build jni_tests.exe) if we are building our target libraries as SHARED libraries. +# TODO: Fix the failing JNI TESTS on Windows +if ("${WIN32}" STREQUAL "") + if (${CONFIG_ALL} STREQUAL ON OR ${CONFIG_TEST} STREQUAL ON) + # Reference - https://crascit.com/2015/07/25/cmake-gtest/ + configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt) + execute_process(COMMAND "${CMAKE_COMMAND}" -G "${CMAKE_GENERATOR}" . + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/googletest-download" + ) + execute_process(COMMAND "${CMAKE_COMMAND}" --build . + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/googletest-download" + ) + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + + add_subdirectory("${CMAKE_BINARY_DIR}/googletest-src" + "${CMAKE_BINARY_DIR}/googletest-build" EXCLUDE_FROM_ALL + ) + add_executable( + jni_test + tests/faiss_wrapper_test.cpp + tests/faiss_wrapper_unit_test.cpp + tests/faiss_util_test.cpp + tests/nmslib_wrapper_test.cpp + tests/nmslib_wrapper_unit_test.cpp + tests/test_util.cpp + tests/commons_test.cpp + tests/faiss_stream_support_test.cpp + tests/faiss_index_service_test.cpp + tests/nmslib_stream_support_test.cpp + ) + + target_link_libraries( + jni_test + gtest_main + gmock_main + faiss + NonMetricSpaceLib + OpenMP::OpenMP_CXX + ${TARGET_LIB_FAISS} + ${TARGET_LIB_NMSLIB} + ${TARGET_LIB_COMMON} + ${TARGET_LIB_UTIL} + ) + + target_include_directories(jni_test PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/tests + ${CMAKE_CURRENT_SOURCE_DIR}/include + $ENV{JAVA_HOME}/include + $ENV{JAVA_HOME}/include/${JVM_OS_TYPE} + ${CMAKE_CURRENT_SOURCE_DIR}/external/faiss + ${CMAKE_CURRENT_SOURCE_DIR}/external/nmslib/similarity_search/include + ${gtest_SOURCE_DIR}/include + ${gmock_SOURCE_DIR}/include) + + + set_target_properties(jni_test PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bin) + endif () +endif() -# -------------------------------- INSTALL ---------------------------------- -# Installation rules for shared library -install(TARGETS ${TARGET_LIBS} - LIBRARY DESTINATION lib - COMPONENT library) - -set(KNN_MAINTAINER "OpenSearch Team ") -set(OPENSEARCH_DOWNLOAD_URL "https://opensearch.org/downloads.html") -set(CPACK_PACKAGE_NAME ${KNN_PACKAGE_NAME}) -set(CPACK_PACKAGE_VERSION ${KNN_PLUGIN_VERSION}) -set(CMAKE_INSTALL_PREFIX /usr) -set(CPACK_GENERATOR "RPM;DEB") -set(CPACK_OUTPUT_FILE_PREFIX packages) -set(CPACK_PACKAGE_RELEASE 1) -set(CPACK_PACKAGE_VENDOR "Amazon") -set(CPACK_PACKAGE_CONTACT "Maintainer: ${KNN_MAINTAINER}") -set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}) -set(CPACK_COMPONENTS_GROUPING IGNORE) -get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS) -list(REMOVE_ITEM CPACK_COMPONENTS_ALL "Unspecified") - -# Component variable -set(KNN_PACKAGE_NAME opensearch-knnlib) -set(KNN_PACKAGE_DESCRIPTION "KNN JNI libraries built off of nmslib and faiss for OpenSearch") - -# RPM -set(CPACK_RPM_PACKAGE_LICENSE "ASL-2.0") -set(CPACK_RPM_COMPONENT_INSTALL ON) -set(CPACK_RPM_PACKAGE_URL ${OPENSEARCH_DOWNLOAD_URL}) -set(CPACK_RPM_PACKAGE_RELEASE ${CPACK_PACKAGE_RELEASE}) - -set(CPACK_RPM_PACKAGE_NAME ${KNN_PACKAGE_NAME}) -set(CPACK_RPM_FILE_NAME "${CPACK_RPM_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${JVM_OS_TYPE}-${MACH_ARCH}.rpm") -set(CPACK_RPM_PACKAGE_DESCRIPTION ${KNN_PACKAGE_DESCRIPTION}) -set(CPACK_RPM_PACKAGE_SUMMARY "OpenSearch k-NN JNI Library with nmslib and faiss") - -# DEB -set(CPACK_DEBIAN_PACKAGE_HOMEPAGE ${OPENSEARCH_DOWNLOAD_URL}) -set(CPACK_DEBIAN_PACKAGE_MAINTAINER ${KNN_MAINTAINER}) -set(CPACK_DEBIAN_PACKAGE_VERSION ${CPACK_PACKAGE_VERSION}) -set(CPACK_DEBIAN_PACKAGE_SECTION "libs") -set(CPACK_DEB_COMPONENT_INSTALL ON) - -set(CPACK_DEBIAN_PACKAGE_NAME ${KNN_PACKAGE_NAME}) -set(CPACK_DEBIAN_FILE_NAME "${CPACK_DEBIAN_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${JVM_OS_TYPE}-${MACH_ARCH}.deb") -set(CPACK_DEBIAN_DESCRIPTION ${KNN_PACKAGE_DESCRIPTION}) -set(CPACK_DEBIAN_PACKAGE_SOURCE ${CPACK_DEBIAN_PACKAGE_NAME}) - -include(CPack) # --------------------------------------------------------------------------- diff --git a/jni/cmake/init-faiss.cmake b/jni/cmake/init-faiss.cmake new file mode 100644 index 000000000..4492d9f45 --- /dev/null +++ b/jni/cmake/init-faiss.cmake @@ -0,0 +1,105 @@ +# +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# + +# Check if faiss exists +find_path(FAISS_REPO_DIR NAMES faiss PATHS ${CMAKE_CURRENT_SOURCE_DIR}/external/faiss NO_DEFAULT_PATH) + +# If not, pull the updated submodule +if (NOT EXISTS ${FAISS_REPO_DIR}) + message(STATUS "Could not find faiss. Pulling updated submodule.") + execute_process(COMMAND git submodule update --init -- external/faiss WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +endif () + +# Apply patches +if(NOT DEFINED APPLY_LIB_PATCHES OR "${APPLY_LIB_PATCHES}" STREQUAL true) + # Define list of patch files + set(PATCH_FILE_LIST) + list(APPEND PATCH_FILE_LIST "${CMAKE_CURRENT_SOURCE_DIR}/patches/faiss/0001-Custom-patch-to-support-multi-vector.patch") + list(APPEND PATCH_FILE_LIST "${CMAKE_CURRENT_SOURCE_DIR}/patches/faiss/0002-Enable-precomp-table-to-be-shared-ivfpq.patch") + list(APPEND PATCH_FILE_LIST "${CMAKE_CURRENT_SOURCE_DIR}/patches/faiss/0003-Custom-patch-to-support-range-search-params.patch") + list(APPEND PATCH_FILE_LIST "${CMAKE_CURRENT_SOURCE_DIR}/patches/faiss/0004-Custom-patch-to-support-binary-vector.patch") + + # Get patch id of the last commit + execute_process(COMMAND sh -c "git --no-pager show HEAD | git patch-id --stable" OUTPUT_VARIABLE PATCH_ID_OUTPUT_FROM_COMMIT WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/external/faiss) + string(REPLACE " " ";" PATCH_ID_LIST_FROM_COMMIT ${PATCH_ID_OUTPUT_FROM_COMMIT}) + list(GET PATCH_ID_LIST_FROM_COMMIT 0 PATCH_ID_FROM_COMMIT) + + # Find all patch files need to apply + list(SORT PATCH_FILE_LIST ORDER DESCENDING) + set(PATCH_FILES_TO_APPLY) + foreach(PATCH_FILE IN LISTS PATCH_FILE_LIST) + # Get patch id of a patch file + execute_process(COMMAND sh -c "cat ${PATCH_FILE} | git patch-id --stable" OUTPUT_VARIABLE PATCH_ID_OUTPUT) + string(REPLACE " " ";" PATCH_ID_LIST ${PATCH_ID_OUTPUT}) + list(GET PATCH_ID_LIST 0 PATCH_ID) + + # Add the file to patch list if patch id does not match + if (${PATCH_ID} STREQUAL ${PATCH_ID_FROM_COMMIT}) + break() + else() + list(APPEND PATCH_FILES_TO_APPLY ${PATCH_FILE}) + endif() + endforeach() + + # Apply patch files + list(SORT PATCH_FILES_TO_APPLY) + foreach(PATCH_FILE IN LISTS PATCH_FILES_TO_APPLY) + message(STATUS "Applying patch of ${PATCH_FILE}") + execute_process(COMMAND git ${GIT_PATCH_COMMAND} --3way --ignore-space-change --ignore-whitespace ${PATCH_FILE} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/external/faiss ERROR_VARIABLE ERROR_MSG RESULT_VARIABLE RESULT_CODE) + if(RESULT_CODE) + message(FATAL_ERROR "Failed to apply patch:\n${ERROR_MSG}") + endif() + endforeach() +endif() + +if (${CMAKE_SYSTEM_NAME} STREQUAL Darwin) + if(CMAKE_C_COMPILER_ID MATCHES "Clang\$") + set(OpenMP_C_FLAGS "-Xpreprocessor -fopenmp") + set(OpenMP_C_LIB_NAMES "omp") + set(OpenMP_omp_LIBRARY /usr/local/opt/libomp/lib/libomp.dylib) + endif() + + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang\$") + set(OpenMP_CXX_FLAGS "-Xpreprocessor -fopenmp -I/usr/local/opt/libomp/include") + set(OpenMP_CXX_LIB_NAMES "omp") + set(OpenMP_omp_LIBRARY /usr/local/opt/libomp/lib/libomp.dylib) + endif() +endif() + +find_package(ZLIB REQUIRED) + +# Statically link BLAS - ensure this is before we find the blas package so we dont dynamically link +set(BLA_STATIC ON) +find_package(BLAS REQUIRED) +enable_language(Fortran) +find_package(LAPACK REQUIRED) + +# Set relevant properties +set(BUILD_TESTING OFF) # Avoid building faiss tests +set(FAISS_ENABLE_GPU OFF) +set(FAISS_ENABLE_PYTHON OFF) + +if(NOT DEFINED AVX2_ENABLED) + set(AVX2_ENABLED true) # set default value as true if the argument is not set +endif() + +if(NOT DEFINED AVX512_ENABLED) + set(AVX512_ENABLED true) # set default value as true if the argument is not set +endif() + +if(${CMAKE_SYSTEM_NAME} STREQUAL Windows OR ${CMAKE_SYSTEM_PROCESSOR} MATCHES "aarch64" OR ${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm64" OR ( NOT AVX2_ENABLED AND NOT AVX512_ENABLED)) + set(FAISS_OPT_LEVEL generic) # Keep optimization level as generic on Windows OS as it is not supported due to MINGW64 compiler issue. Also, on aarch64 avx2 is not supported. + set(TARGET_LINK_FAISS_LIB faiss) +elseif(${CMAKE_SYSTEM_NAME} STREQUAL Linux AND AVX512_ENABLED) + set(FAISS_OPT_LEVEL avx512) # Keep optimization level as avx512 to improve performance on Linux. This is not present on mac systems, and presently not supported on Windows OS. + set(TARGET_LINK_FAISS_LIB faiss_avx512) + string(PREPEND LIB_EXT "_avx512") # Prepend "_avx512" to lib extension to create the library as "libopensearchknn_faiss_avx512.so" on linux +else() + set(FAISS_OPT_LEVEL avx2) # Keep optimization level as avx2 to improve performance on Linux and Mac. + set(TARGET_LINK_FAISS_LIB faiss_avx2) + string(PREPEND LIB_EXT "_avx2") # Prepend "_avx2" to lib extension to create the library as "libopensearchknn_faiss_avx2.so" on linux and "libopensearchknn_faiss_avx2.jnilib" on mac +endif() + +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/external/faiss EXCLUDE_FROM_ALL) diff --git a/jni/cmake/init-nmslib.cmake b/jni/cmake/init-nmslib.cmake new file mode 100644 index 000000000..a7c3f7d93 --- /dev/null +++ b/jni/cmake/init-nmslib.cmake @@ -0,0 +1,57 @@ +# +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# + +# Check if nmslib exists +find_path(NMS_REPO_DIR NAMES similarity_search PATHS ${CMAKE_CURRENT_SOURCE_DIR}/external/nmslib NO_DEFAULT_PATH) + +# If not, pull the updated submodule +if (NOT EXISTS ${NMS_REPO_DIR}) + message(STATUS "Could not find nmslib. Pulling updated submodule.") + execute_process(COMMAND git submodule update --init -- external/nmslib WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +endif () + +# Apply patches +if(NOT DEFINED APPLY_LIB_PATCHES OR "${APPLY_LIB_PATCHES}" STREQUAL true) + # Define list of patch files + set(PATCH_FILE_LIST) + list(APPEND PATCH_FILE_LIST "${CMAKE_CURRENT_SOURCE_DIR}/patches/nmslib/0001-Initialize-maxlevel-during-add-from-enterpoint-level.patch") + list(APPEND PATCH_FILE_LIST "${CMAKE_CURRENT_SOURCE_DIR}/patches/nmslib/0002-Adds-ability-to-pass-ef-parameter-in-the-query-for-h.patch") + list(APPEND PATCH_FILE_LIST "${CMAKE_CURRENT_SOURCE_DIR}/patches/nmslib/0003-Added-streaming-apis-for-vector-index-loading-in-Hnsw.patch") + list(APPEND PATCH_FILE_LIST "${CMAKE_CURRENT_SOURCE_DIR}/patches/nmslib/0004-Added-a-new-save-apis-in-Hnsw-with-streaming-interfa.patch") + + # Get patch id of the last commit + execute_process(COMMAND sh -c "git --no-pager show HEAD | git patch-id --stable" OUTPUT_VARIABLE PATCH_ID_OUTPUT_FROM_COMMIT WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/external/nmslib) + string(REPLACE " " ";" PATCH_ID_LIST_FROM_COMMIT ${PATCH_ID_OUTPUT_FROM_COMMIT}) + list(GET PATCH_ID_LIST_FROM_COMMIT 0 PATCH_ID_FROM_COMMIT) + + # Find all patch files need to apply + list(SORT PATCH_FILE_LIST ORDER DESCENDING) + set(PATCH_FILES_TO_APPLY) + foreach(PATCH_FILE IN LISTS PATCH_FILE_LIST) + # Get patch id of a patch file + execute_process(COMMAND sh -c "cat ${PATCH_FILE} | git patch-id --stable" OUTPUT_VARIABLE PATCH_ID_OUTPUT) + string(REPLACE " " ";" PATCH_ID_LIST ${PATCH_ID_OUTPUT}) + list(GET PATCH_ID_LIST 0 PATCH_ID) + + # Add the file to patch list if patch id does not match + if (${PATCH_ID} STREQUAL ${PATCH_ID_FROM_COMMIT}) + break() + else() + list(APPEND PATCH_FILES_TO_APPLY ${PATCH_FILE}) + endif() + endforeach() +endif() + +# Apply patch files +list(SORT PATCH_FILES_TO_APPLY) +foreach(PATCH_FILE IN LISTS PATCH_FILES_TO_APPLY) + message(STATUS "Applying patch of ${PATCH_FILE}") + execute_process(COMMAND git ${GIT_PATCH_COMMAND} --3way --ignore-space-change --ignore-whitespace ${PATCH_FILE} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/external/nmslib ERROR_VARIABLE ERROR_MSG RESULT_VARIABLE RESULT_CODE) + if(RESULT_CODE) + message(FATAL_ERROR "Failed to apply patch:\n${ERROR_MSG}") + endif() +endforeach() + +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/external/nmslib/similarity_search) diff --git a/jni/cmake/macros.cmake b/jni/cmake/macros.cmake new file mode 100644 index 000000000..773033b7e --- /dev/null +++ b/jni/cmake/macros.cmake @@ -0,0 +1,16 @@ +# +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# + +macro(opensearch_set_common_properties TARGET) + set_target_properties(${TARGET} PROPERTIES SUFFIX ${LIB_EXT}) + set_target_properties(${TARGET} PROPERTIES POSITION_INDEPENDENT_CODE ON) + + if (NOT "${WIN32}" STREQUAL "") + # Use RUNTIME_OUTPUT_DIRECTORY, to build the target library in the specified directory at runtime. + set_target_properties(${TARGET} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/release) + else() + set_target_properties(${TARGET} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/release) + endif() +endmacro() diff --git a/jni/external/faiss b/jni/external/faiss index 88eabe97f..1f42e815d 160000 --- a/jni/external/faiss +++ b/jni/external/faiss @@ -1 +1 @@ -Subproject commit 88eabe97f96d0c0964dfa075f74373c64d46da80 +Subproject commit 1f42e815db7754297e3b4467763352b829b6cde0 diff --git a/jni/include/commons.h b/jni/include/commons.h new file mode 100644 index 000000000..38b00cc5d --- /dev/null +++ b/jni/include/commons.h @@ -0,0 +1,107 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +#ifndef OPENSEARCH_KNN_COMMONS_H +#define OPENSEARCH_KNN_COMMONS_H + +#include "jni_util.h" +#include +namespace knn_jni { + namespace commons { + /** + * This is utility function that can be used to store data in native memory. This function will allocate memory for + * the data(rows*columns) with initialCapacity and return the memory address where the data is stored. + * If you are using this function for first time use memoryAddress = 0 to ensure that a new memory location is created. + * For subsequent calls you can pass the same memoryAddress. If the data cannot be stored in the memory location + * will throw Exception. + * + * append tells the method to keep appending to the existing vector. Passing the value as false will clear the vector + * without reallocating new memory. This helps with reducing memory frangmentation and overhead of allocating + * and deallocating when the memory address needs to be reused. + * + * CAUTION: The behavior is undefined if the memory address is deallocated and the method is called + * + * @param memoryAddress The address of the memory location where data will be stored. + * @param data 2D float array containing data to be stored in native memory. + * @param initialCapacity The initial capacity of the memory location. + * @param append whether to append or start from index 0 when called subsequently with the same address + * @return memory address of std::vector where the data is stored. + */ + jlong storeVectorData(knn_jni::JNIUtilInterface *, JNIEnv *, jlong , jobjectArray, jlong, jboolean); + + /** + * This is utility function that can be used to store data in native memory. This function will allocate memory for + * the data(rows*columns) with initialCapacity and return the memory address where the data is stored. + * If you are using this function for first time use memoryAddress = 0 to ensure that a new memory location is created. + * For subsequent calls you can pass the same memoryAddress. If the data cannot be stored in the memory location + * will throw Exception. + * + * append tells the method to keep appending to the existing vector. Passing the value as false will clear the vector + * without reallocating new memory. This helps with reducing memory frangmentation and overhead of allocating + * and deallocating when the memory address needs to be reused. + * + * CAUTION: The behavior is undefined if the memory address is deallocated and the method is called + * + * @param memoryAddress The address of the memory location where data will be stored. + * @param data 2D byte array containing binary data to be stored in native memory. + * @param initialCapacity The initial capacity of the memory location. + * @param append whether to append or start from index 0 when called subsequently with the same address + * @return memory address of std::vector where the data is stored. + */ + jlong storeBinaryVectorData(knn_jni::JNIUtilInterface *, JNIEnv *, jlong , jobjectArray, jlong, jboolean); + + /** + * This is utility function that can be used to store signed int8 data in native memory. This function will allocate memory for + * the data(rows*columns) with initialCapacity and return the memory address where the data is stored. + * If you are using this function for first time use memoryAddress = 0 to ensure that a new memory location is created. + * For subsequent calls you can pass the same memoryAddress. If the data cannot be stored in the memory location + * will throw Exception. + * + * @param memoryAddress The address of the memory location where data will be stored. + * @param data 2D byte array containing int8 data to be stored in native memory. + * @param initialCapacity The initial capacity of the memory location. + * @param append whether to append or start from index 0 when called subsequently with the same address + * @return memory address of std::vector where the data is stored. + */ + jlong storeByteVectorData(knn_jni::JNIUtilInterface *, JNIEnv *, jlong , jobjectArray, jlong, jboolean); + + /** + * Free up the memory allocated for the data stored in memory address. This function should be used with the memory + * address returned by {@link JNICommons#storeVectorData(long, float[][], long, long)} + * + * @param memoryAddress address to be freed. + */ + void freeVectorData(jlong); + + /** + * Free up the memory allocated for the data stored in memory address. This function should be used with the memory + * address returned by {@link JNICommons#storeByteVectorData(long, byte[][], long, long)} + * + * @param memoryAddress address to be freed. + */ + void freeByteVectorData(jlong); + + /** + * Free up the memory allocated for the data stored in memory address. This function should be used with the memory + * address returned by {@link JNICommons#storeBinaryVectorData(long, byte[][], long, long)} + * + * @param memoryAddress address to be freed. + */ + void freeBinaryVectorData(jlong); + + /** + * Extracts query time efSearch from method parameters + **/ + int getIntegerMethodParameter(JNIEnv *, knn_jni::JNIUtilInterface *, std::unordered_map, std::string, int); + } +} + +#endif \ No newline at end of file diff --git a/jni/include/faiss_index_service.h b/jni/include/faiss_index_service.h new file mode 100644 index 000000000..d96c3e755 --- /dev/null +++ b/jni/include/faiss_index_service.h @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// The OpenSearch Contributors require contributions made to +// this file be licensed under the Apache-2.0 license or a +// compatible open source license. +// +// Modifications Copyright OpenSearch Contributors. See +// GitHub history for details. + +/** + * This file contains classes for index operations which are free of JNI + */ + +#ifndef OPENSEARCH_KNN_FAISS_INDEX_SERVICE_H +#define OPENSEARCH_KNN_FAISS_INDEX_SERVICE_H + +#include +#include "faiss/MetricType.h" +#include "faiss/impl/io.h" +#include "jni_util.h" +#include "faiss_methods.h" +#include "faiss_stream_support.h" +#include + +namespace knn_jni { +namespace faiss_wrapper { + + +/** + * A class to provide operations on index + * This class should evolve to have only cpp object but not jni object + */ +class IndexService { +public: + explicit IndexService(std::unique_ptr faissMethods); + + /** + * Initialize index + * + * @param jniUtil jni util + * @param env jni environment + * @param metric space type for distance calculation + * @param indexDescription index description to be used by faiss index factory + * @param dim dimension of vectors + * @param numVectors number of vectors + * @param threadCount number of thread count to be used while adding data + * @param parameters parameters to be applied to faiss index + * @return memory address of the native index object + */ + virtual jlong initIndex(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, faiss::MetricType metric, std::string indexDescription, int dim, int numVectors, int threadCount, std::unordered_map parameters); + + /** + * Add vectors to index + * + * @param dim dimension of vectors + * @param numIds number of vectors + * @param threadCount number of thread count to be used while adding data + * @param vectorsAddress memory address which is holding vector data + * @param idMapAddress memory address of the native index object + */ + virtual void insertToIndex(int dim, int numIds, int threadCount, int64_t vectorsAddress, std::vector &ids, jlong idMapAddress); + + /** + * Write index to disk + * + * @param writer IOWriter implementation doing IO processing. + * In most cases, it is expected to have underlying Lucene's IndexOuptut. + * @param idMapAddress memory address of the native index object + */ + virtual void writeIndex(faiss::IOWriter* writer, jlong idMapAddress); + + virtual ~IndexService() = default; + +protected: + virtual void allocIndex(faiss::Index * index, size_t dim, size_t numVectors); + + std::unique_ptr faissMethods; +}; // class IndexService + +/** + * A class to provide operations on index + * This class should evolve to have only cpp object but not jni object + */ +class BinaryIndexService final : public IndexService { +public: + //TODO Remove dependency on JNIUtilInterface and JNIEnv + //TODO Reduce the number of parameters + explicit BinaryIndexService(std::unique_ptr faissMethods); + + /** + * Initialize index + * + * @param jniUtil jni util + * @param env jni environment + * @param metric space type for distance calculation + * @param indexDescription index description to be used by faiss index factory + * @param dim dimension of vectors + * @param numVectors number of vectors + * @param threadCount number of thread count to be used while adding data + * @param parameters parameters to be applied to faiss index + * @return memory address of the native index object + */ + jlong initIndex(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, faiss::MetricType metric, std::string indexDescription, int dim, int numVectors, int threadCount, std::unordered_map parameters) final; + + /** + * Add vectors to index + * + * @param jniUtil jni util + * @param env jni environment + * @param metric space type for distance calculation + * @param indexDescription index description to be used by faiss index factory + * @param dim dimension of vectors + * @param numIds number of vectors + * @param threadCount number of thread count to be used while adding data + * @param vectorsAddress memory address which is holding vector data + * @param idMap a map of document id and vector id + * @param parameters parameters to be applied to faiss index + */ + void insertToIndex(int dim, int numIds, int threadCount, int64_t vectorsAddress, std::vector &ids, jlong idMapAddress) final; + + /** + * Write index to disk + * + * @param jniUtil jni util + * @param env jni environment + * @param metric space type for distance calculation + * @param indexDescription index description to be used by faiss index factory + * @param threadCount number of thread count to be used while adding data + * @param indexPath path to write index + * @param idMap a map of document id and vector id + * @param parameters parameters to be applied to faiss index + */ + void writeIndex(faiss::IOWriter* writer, jlong idMapAddress) final; + +protected: + void allocIndex(faiss::Index * index, size_t dim, size_t numVectors) final; +}; // class BinaryIndexService + +/** + * A class to provide operations on index + * This class should evolve to have only cpp object but not jni object + */ +class ByteIndexService final : public IndexService { +public: + //TODO Remove dependency on JNIUtilInterface and JNIEnv + //TODO Reduce the number of parameters + explicit ByteIndexService(std::unique_ptr faissMethods); + + /** + * Initialize index + * + * @param jniUtil jni util + * @param env jni environment + * @param metric space type for distance calculation + * @param indexDescription index description to be used by faiss index factory + * @param dim dimension of vectors + * @param numVectors number of vectors + * @param threadCount number of thread count to be used while adding data + * @param parameters parameters to be applied to faiss index + * @return memory address of the native index object + */ + jlong initIndex(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, faiss::MetricType metric, std::string indexDescription, int dim, int numVectors, int threadCount, std::unordered_map parameters) final; + + /** + * Add vectors to index + * + * @param jniUtil jni util + * @param env jni environment + * @param metric space type for distance calculation + * @param indexDescription index description to be used by faiss index factory + * @param dim dimension of vectors + * @param numIds number of vectors + * @param threadCount number of thread count to be used while adding data + * @param vectorsAddress memory address which is holding vector data + * @param idMap a map of document id and vector id + * @param parameters parameters to be applied to faiss index + */ + void insertToIndex(int dim, int numIds, int threadCount, int64_t vectorsAddress, std::vector &ids, jlong idMapAddress) final; + + /** + * Write index to disk + * + * @param jniUtil jni util + * @param env jni environment + * @param metric space type for distance calculation + * @param indexDescription index description to be used by faiss index factory + * @param threadCount number of thread count to be used while adding data + * @param indexPath path to write index + * @param idMap a map of document id and vector id + * @param parameters parameters to be applied to faiss index + */ + void writeIndex(faiss::IOWriter* writer, jlong idMapAddress) final; + + protected: + void allocIndex(faiss::Index * index, size_t dim, size_t numVectors) final; +}; // class ByteIndexService + +} +} + + +#endif //OPENSEARCH_KNN_FAISS_INDEX_SERVICE_H diff --git a/jni/include/faiss_methods.h b/jni/include/faiss_methods.h new file mode 100644 index 000000000..d8f14d03f --- /dev/null +++ b/jni/include/faiss_methods.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// The OpenSearch Contributors require contributions made to +// this file be licensed under the Apache-2.0 license or a +// compatible open source license. +// +// Modifications Copyright OpenSearch Contributors. See +// GitHub history for details. + +#ifndef OPENSEARCH_KNN_FAISS_METHODS_H +#define OPENSEARCH_KNN_FAISS_METHODS_H + +#include "faiss/impl/io.h" +#include "faiss/Index.h" +#include "faiss/IndexBinary.h" +#include "faiss/IndexIDMap.h" +#include "faiss/index_io.h" + +namespace knn_jni { +namespace faiss_wrapper { + +/** + * A class having wrapped faiss methods + * + * This class helps to mock faiss methods during unit test + */ +class FaissMethods { +public: + FaissMethods() = default; + + virtual faiss::Index* indexFactory(int d, const char* description, faiss::MetricType metric); + + virtual faiss::IndexBinary* indexBinaryFactory(int d, const char* description); + + virtual faiss::IndexIDMapTemplate* indexIdMap(faiss::Index* index); + + virtual faiss::IndexIDMapTemplate* indexBinaryIdMap(faiss::IndexBinary* index); + + virtual void writeIndex(const faiss::Index* idx, faiss::IOWriter* writer); + + virtual void writeIndexBinary(const faiss::IndexBinary* idx, faiss::IOWriter* writer); + + virtual ~FaissMethods() = default; +}; // class FaissMethods + +} //namespace faiss_wrapper +} //namespace knn_jni + + +#endif //OPENSEARCH_KNN_FAISS_METHODS_H \ No newline at end of file diff --git a/jni/include/faiss_stream_support.h b/jni/include/faiss_stream_support.h new file mode 100644 index 000000000..eb1b2a404 --- /dev/null +++ b/jni/include/faiss_stream_support.h @@ -0,0 +1,98 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +#ifndef OPENSEARCH_KNN_JNI_FAISS_STREAM_SUPPORT_H +#define OPENSEARCH_KNN_JNI_FAISS_STREAM_SUPPORT_H + +#include "faiss/impl/io.h" +#include "jni_util.h" +#include "native_engines_stream_support.h" +#include "parameter_utils.h" + +#include +#include +#include +#include + +namespace knn_jni { +namespace stream { + + + +/** + * A glue component inheriting IOReader to be passed down to Faiss library. + * This will then indirectly call the mediator component and eventually read required bytes from Lucene's IndexInput. + */ +class FaissOpenSearchIOReader final : public faiss::IOReader { + public: + explicit FaissOpenSearchIOReader(NativeEngineIndexInputMediator *_mediator) + : faiss::IOReader(), + mediator(knn_jni::util::ParameterCheck::require_non_null(_mediator, "mediator")) { + name = "FaissOpenSearchIOReader"; + } + + size_t operator()(void *ptr, size_t size, size_t nitems) final { + const auto readBytes = size * nitems; + if (readBytes > 0) { + // Mediator calls IndexInput, then copy read bytes to `ptr`. + mediator->copyBytes(readBytes, (uint8_t *) ptr); + } + return nitems; + } + + int filedescriptor() final { + throw std::runtime_error("filedescriptor() is not supported in FaissOpenSearchIOReader."); + } + + private: + NativeEngineIndexInputMediator *mediator; +}; // class FaissOpenSearchIOReader + + +/** + * A glue component inheriting IOWriter to delegate IO processing down to the given + * mediator. The mediator is expected to do write bytes via the provided Lucene's IndexOutput. + */ +class FaissOpenSearchIOWriter final : public faiss::IOWriter { + public: + explicit FaissOpenSearchIOWriter(NativeEngineIndexOutputMediator *_mediator) + : faiss::IOWriter(), + mediator(knn_jni::util::ParameterCheck::require_non_null(_mediator, "mediator")) { + name = "FaissOpenSearchIOWriter"; + } + + size_t operator()(const void *ptr, size_t size, size_t nitems) final { + const auto writeBytes = size * nitems; + if (writeBytes > 0) { + mediator->writeBytes(reinterpret_cast(ptr), writeBytes); + } + return nitems; + } + + // return a file number that can be memory-mapped + int filedescriptor() final { + throw std::runtime_error("filedescriptor() is not supported in FaissOpenSearchIOWriter."); + } + + void flush() { + mediator->flush(); + } + + private: + NativeEngineIndexOutputMediator *mediator; +}; // class FaissOpenSearchIOWriter + + + +} +} + +#endif //OPENSEARCH_KNN_JNI_FAISS_STREAM_SUPPORT_H diff --git a/jni/include/faiss_util.h b/jni/include/faiss_util.h new file mode 100644 index 000000000..f23540aef --- /dev/null +++ b/jni/include/faiss_util.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// The OpenSearch Contributors require contributions made to +// this file be licensed under the Apache-2.0 license or a +// compatible open source license. +// +// Modifications Copyright OpenSearch Contributors. See +// GitHub history for details. + +/** + * This file contains util methods which are free of JNI to be used in faiss_wrapper.cpp + */ + +#ifndef OPENSEARCH_KNN_FAISS_UTIL_H +#define OPENSEARCH_KNN_FAISS_UTIL_H + +#include "faiss/impl/IDGrouper.h" +#include + +namespace faiss_util { + std::unique_ptr buildIDGrouperBitmap(int *parentIdsArray, int parentIdsLength, std::vector* bitmap); +}; + + +#endif //OPENSEARCH_KNN_FAISS_UTIL_H diff --git a/jni/include/faiss_wrapper.h b/jni/include/faiss_wrapper.h index 6c8a86143..e48e6faa9 100644 --- a/jni/include/faiss_wrapper.h +++ b/jni/include/faiss_wrapper.h @@ -13,35 +13,102 @@ #define OPENSEARCH_KNN_FAISS_WRAPPER_H #include "jni_util.h" - +#include "faiss_index_service.h" +#include "faiss_stream_support.h" #include namespace knn_jni { namespace faiss_wrapper { - // Create an index with ids and vectors. The configuration is defined by values in the Java map, parametersJ. - // The index is serialized to indexPathJ. - void CreateIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, jobjectArray vectorsJ, - jstring indexPathJ, jobject parametersJ); + jlong InitIndex(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jlong numDocs, jint dimJ, jobject parametersJ, IndexService *indexService); + + void InsertToIndex(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jintArray idsJ, jlong vectorsAddressJ, jint dimJ, jlong indexAddr, jint threadCount, IndexService *indexService); + + void WriteIndex(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jobject output, jlong indexAddr, IndexService *indexService); // Create an index with ids and vectors. Instead of creating a new index, this function creates the index // based off of the template index passed in. The index is serialized to indexPathJ. void CreateIndexFromTemplate(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, - jobjectArray vectorsJ, jstring indexPathJ, jbyteArray templateIndexJ, + jlong vectorsAddressJ, jint dimJ, jobject output, jbyteArray templateIndexJ, jobject parametersJ); + // Create an index with ids and vectors. Instead of creating a new index, this function creates the index + // based off of the template index passed in. The index is serialized to indexPathJ. + void CreateBinaryIndexFromTemplate(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, + jlong vectorsAddressJ, jint dimJ, jobject output, jbyteArray templateIndexJ, + jobject parametersJ); + + // Create a index with ids and byte vectors. Instead of creating a new index, this function creates the index + // based off of the template index passed in. The index is serialized to indexPathJ. + void CreateByteIndexFromTemplate(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, + jlong vectorsAddressJ, jint dimJ, jobject output, jbyteArray templateIndexJ, + jobject parametersJ); + // Load an index from indexPathJ into memory. // // Return a pointer to the loaded index jlong LoadIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jstring indexPathJ); - // Execute a query against the index located in memory at indexPointerJ. + // Loads an index with a reader implemented IOReader // - // Return an array of KNNQueryResults + // Returns a pointer of the loaded index + jlong LoadIndexWithStream(faiss::IOReader* ioReader); + + // Load a binary index from indexPathJ into memory. + // + // Return a pointer to the loaded index + jlong LoadBinaryIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jstring indexPathJ); + + // Loads a binary index with a reader implemented IOReader + // + // Returns a pointer of the loaded index + jlong LoadBinaryIndexWithStream(faiss::IOReader* ioReader); + + // Check if a loaded index requires shared state + bool IsSharedIndexStateRequired(jlong indexPointerJ); + + // Initializes the shared index state from an index. Note, this will not set the state for + // the index pointed to by indexPointerJ. To set it, SetSharedIndexState needs to be called. + // + // Return a pointer to the shared index state + jlong InitSharedIndexState(jlong indexPointerJ); + + // Sets the sharedIndexState for an index + void SetSharedIndexState(jlong indexPointerJ, jlong shareIndexStatePointerJ); + + /** + * Execute a query against the index located in memory at indexPointerJ + * + * Parameters: + * methodParamsJ: introduces a map to have additional method parameters + * + * Return an array of KNNQueryResults + */ jobjectArray QueryIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jlong indexPointerJ, - jfloatArray queryVectorJ, jint kJ); + jfloatArray queryVectorJ, jint kJ, jobject methodParamsJ, jintArray parentIdsJ); + + /** + * Execute a query against the index located in memory at indexPointerJ along with Filters + * + * Parameters: + * methodParamsJ: introduces a map to have additional method parameters + * + * Return an array of KNNQueryResults + */ + jobjectArray QueryIndex_WithFilter(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jlong indexPointerJ, + jfloatArray queryVectorJ, jint kJ, jobject methodParamsJ, jlongArray filterIdsJ, + jint filterIdsTypeJ, jintArray parentIdsJ); + + // Execute a query against the binary index located in memory at indexPointerJ along with Filters + // + // Return an array of KNNQueryResults + jobjectArray QueryBinaryIndex_WithFilter(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jlong indexPointerJ, + jbyteArray queryVectorJ, jint kJ, jobject methodParamsJ, jlongArray filterIdsJ, jint filterIdsTypeJ, jintArray parentIdsJ); // Free the index located in memory at indexPointerJ - void Free(jlong indexPointer); + void Free(jlong indexPointer, jboolean isBinaryIndexJ); + + // Free shared index state in memory at shareIndexStatePointerJ + void FreeSharedIndexState(jlong shareIndexStatePointerJ); // Perform initilization operations for the library void InitLibrary(); @@ -52,6 +119,52 @@ namespace knn_jni { // Return the serialized representation jbyteArray TrainIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jobject parametersJ, jint dimension, jlong trainVectorsPointerJ); + + // Create an empty binary index defined by the values in the Java map, parametersJ. Train the index with + // the vector of floats located at trainVectorsPointerJ. + // + // Return the serialized representation + jbyteArray TrainBinaryIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jobject parametersJ, jint dimension, + jlong trainVectorsPointerJ); + + // Create an empty byte index defined by the values in the Java map, parametersJ. Train the index with + // the byte vectors located at trainVectorsPointerJ. + // + // Return the serialized representation + jbyteArray TrainByteIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jobject parametersJ, jint dimension, + jlong trainVectorsPointerJ); + + /* + * Perform a range search with filter against the index located in memory at indexPointerJ. + * + * @param indexPointerJ - pointer to the index + * @param queryVectorJ - the query vector + * @param radiusJ - the radius for the range search + * @param methodParamsJ - the method parameters + * @param maxResultsWindowJ - the maximum number of results to return + * @param filterIdsJ - the filter ids + * @param filterIdsTypeJ - the filter ids type + * @param parentIdsJ - the parent ids + * + * @return an array of RangeQueryResults + */ + jobjectArray RangeSearchWithFilter(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jlong indexPointerJ, jfloatArray queryVectorJ, + jfloat radiusJ, jobject methodParamsJ, jint maxResultWindowJ, jlongArray filterIdsJ, jint filterIdsTypeJ, jintArray parentIdsJ); + + /* + * Perform a range search against the index located in memory at indexPointerJ. + * + * @param indexPointerJ - pointer to the index + * @param queryVectorJ - the query vector + * @param radiusJ - the radius for the range search + * @param methodParamsJ - the method parameters + * @param maxResultsWindowJ - the maximum number of results to return + * @param parentIdsJ - the parent ids + * + * @return an array of RangeQueryResults + */ + jobjectArray RangeSearch(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jlong indexPointerJ, jfloatArray queryVectorJ, + jfloat radiusJ, jobject methodParamsJ, jint maxResultWindowJ, jintArray parentIdsJ); } } diff --git a/jni/include/jni_util.h b/jni/include/jni_util.h index 8d6f2b6f6..45068ae1b 100644 --- a/jni/include/jni_util.h +++ b/jni/include/jni_util.h @@ -17,12 +17,13 @@ #include #include #include +#include +#include namespace knn_jni { // Interface for making calls to JNI - class JNIUtilInterface { - public: + struct JNIUtilInterface { // -------------------------- EXCEPTION HANDLING ---------------------------- // Takes the name of a Java exception type and a message and throws the corresponding exception // to the JVM @@ -33,7 +34,7 @@ namespace knn_jni { virtual void HasExceptionInStack(JNIEnv* env) = 0; // HasExceptionInStack with ability to specify message - virtual void HasExceptionInStack(JNIEnv* env, const std::string& message) = 0; + virtual void HasExceptionInStack(JNIEnv* env, const char *message) = 0; // Catches a C++ exception and throws the corresponding exception to the JVM virtual void CatchCppExceptionAndThrowJava(JNIEnv* env) = 0; @@ -68,6 +69,13 @@ namespace knn_jni { virtual std::vector Convert2dJavaObjectArrayToCppFloatVector(JNIEnv *env, jobjectArray array2dJ, int dim) = 0; + virtual void Convert2dJavaObjectArrayAndStoreToFloatVector(JNIEnv *env, jobjectArray array2dJ, + int dim, std::vector *vect ) = 0; + virtual void Convert2dJavaObjectArrayAndStoreToBinaryVector(JNIEnv *env, jobjectArray array2dJ, + int dim, std::vector *vect ) = 0; + virtual void Convert2dJavaObjectArrayAndStoreToByteVector(JNIEnv *env, jobjectArray array2dJ, + int dim, std::vector *vect ) = 0; + virtual std::vector ConvertJavaIntArrayToCppIntVector(JNIEnv *env, jintArray arrayJ) = 0; // -------------------------------------------------------------------------- @@ -75,10 +83,14 @@ namespace knn_jni { // ------------------------------ MISC HELPERS ------------------------------ virtual int GetInnerDimensionOf2dJavaFloatArray(JNIEnv *env, jobjectArray array2dJ) = 0; + virtual int GetInnerDimensionOf2dJavaByteArray(JNIEnv *env, jobjectArray array2dJ) = 0; + virtual int GetJavaObjectArrayLength(JNIEnv *env, jobjectArray arrayJ) = 0; virtual int GetJavaIntArrayLength(JNIEnv *env, jintArray arrayJ) = 0; + virtual int GetJavaLongArrayLength(JNIEnv *env, jlongArray arrayJ) = 0; + virtual int GetJavaBytesArrayLength(JNIEnv *env, jbyteArray arrayJ) = 0; virtual int GetJavaFloatArrayLength(JNIEnv *env, jfloatArray arrayJ) = 0; @@ -93,6 +105,8 @@ namespace knn_jni { virtual jint * GetIntArrayElements(JNIEnv *env, jintArray array, jboolean * isCopy) = 0; + virtual jlong * GetLongArrayElements(JNIEnv *env, jlongArray array, jboolean * isCopy) = 0; + virtual jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index) = 0; virtual jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodId, int id, float distance) = 0; @@ -107,58 +121,121 @@ namespace knn_jni { virtual void ReleaseIntArrayElements(JNIEnv *env, jintArray array, jint *elems, jint mode) = 0; + virtual void ReleaseLongArrayElements(JNIEnv *env, jlongArray array, jlong *elems, jint mode) = 0; + virtual void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject val) = 0; virtual void SetByteArrayRegion(JNIEnv *env, jbyteArray array, jsize start, jsize len, const jbyte * buf) = 0; + virtual jobject GetObjectField(JNIEnv * env, jobject obj, jfieldID fieldID) = 0; + + virtual jclass FindClassFromJNIEnv(JNIEnv * env, const char *name) = 0; + + virtual jmethodID GetMethodID(JNIEnv * env, jclass clazz, const char *name, const char *sig) = 0; + + virtual jfieldID GetFieldID(JNIEnv * env, jclass clazz, const char *name, const char *sig) = 0; + + virtual void * GetPrimitiveArrayCritical(JNIEnv * env, jarray array, jboolean *isCopy) = 0; + + virtual void ReleasePrimitiveArrayCritical(JNIEnv * env, jarray array, void *carray, jint mode) = 0; + + virtual jint CallNonvirtualIntMethodA(JNIEnv *env, jobject obj, jclass clazz, + jmethodID methodID, jvalue *args) = 0; + + virtual jlong CallNonvirtualLongMethodA(JNIEnv * env, jobject obj, jclass clazz, + jmethodID methodID, jvalue* args) = 0; + + virtual void CallNonvirtualVoidMethodA(JNIEnv * env, jobject obj, jclass clazz, + jmethodID methodID, jvalue* args) = 0; + // -------------------------------------------------------------------------- }; jobject GetJObjectFromMapOrThrow(std::unordered_map map, std::string key); // Class that implements JNIUtilInterface methods - class JNIUtil: public JNIUtilInterface { + class JNIUtil final : public JNIUtilInterface { public: // Initialize and Uninitialize methods are used for caching/cleaning up Java classes and methods void Initialize(JNIEnv* env); void Uninitialize(JNIEnv* env); - void ThrowJavaException(JNIEnv* env, const char* type = "", const char* message = ""); - void HasExceptionInStack(JNIEnv* env); - void HasExceptionInStack(JNIEnv* env, const std::string& message); - void CatchCppExceptionAndThrowJava(JNIEnv* env); - jclass FindClass(JNIEnv * env, const std::string& className); - jmethodID FindMethod(JNIEnv * env, const std::string& className, const std::string& methodName); - std::string ConvertJavaStringToCppString(JNIEnv * env, jstring javaString); - std::unordered_map ConvertJavaMapToCppMap(JNIEnv *env, jobject parametersJ); - std::string ConvertJavaObjectToCppString(JNIEnv *env, jobject objectJ); - int ConvertJavaObjectToCppInteger(JNIEnv *env, jobject objectJ); - std::vector Convert2dJavaObjectArrayToCppFloatVector(JNIEnv *env, jobjectArray array2dJ, int dim); - std::vector ConvertJavaIntArrayToCppIntVector(JNIEnv *env, jintArray arrayJ); - int GetInnerDimensionOf2dJavaFloatArray(JNIEnv *env, jobjectArray array2dJ); - int GetJavaObjectArrayLength(JNIEnv *env, jobjectArray arrayJ); - int GetJavaIntArrayLength(JNIEnv *env, jintArray arrayJ); - int GetJavaBytesArrayLength(JNIEnv *env, jbyteArray arrayJ); - int GetJavaFloatArrayLength(JNIEnv *env, jfloatArray arrayJ); - - void DeleteLocalRef(JNIEnv *env, jobject obj); - jbyte * GetByteArrayElements(JNIEnv *env, jbyteArray array, jboolean * isCopy); - jfloat * GetFloatArrayElements(JNIEnv *env, jfloatArray array, jboolean * isCopy); - jint * GetIntArrayElements(JNIEnv *env, jintArray array, jboolean * isCopy); - jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index); - jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodId, int id, float distance); - jobjectArray NewObjectArray(JNIEnv *env, jsize len, jclass clazz, jobject init); - jbyteArray NewByteArray(JNIEnv *env, jsize len); - void ReleaseByteArrayElements(JNIEnv *env, jbyteArray array, jbyte *elems, int mode); - void ReleaseFloatArrayElements(JNIEnv *env, jfloatArray array, jfloat *elems, int mode); - void ReleaseIntArrayElements(JNIEnv *env, jintArray array, jint *elems, jint mode); - void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject val); - void SetByteArrayRegion(JNIEnv *env, jbyteArray array, jsize start, jsize len, const jbyte * buf); + void ThrowJavaException(JNIEnv* env, const char* type = "", const char* message = "") final; + void HasExceptionInStack(JNIEnv* env) final; + void HasExceptionInStack(JNIEnv* env, const char* message) final; + void CatchCppExceptionAndThrowJava(JNIEnv* env) final; + jclass FindClass(JNIEnv * env, const std::string& className) final; + jmethodID FindMethod(JNIEnv * env, const std::string& className, const std::string& methodName) final; + std::string ConvertJavaStringToCppString(JNIEnv * env, jstring javaString) final; + std::unordered_map ConvertJavaMapToCppMap(JNIEnv *env, jobject parametersJ) final; + std::string ConvertJavaObjectToCppString(JNIEnv *env, jobject objectJ) final; + int ConvertJavaObjectToCppInteger(JNIEnv *env, jobject objectJ) final; + std::vector Convert2dJavaObjectArrayToCppFloatVector(JNIEnv *env, jobjectArray array2dJ, int dim) final; + std::vector ConvertJavaIntArrayToCppIntVector(JNIEnv *env, jintArray arrayJ) final; + int GetInnerDimensionOf2dJavaFloatArray(JNIEnv *env, jobjectArray array2dJ) final; + int GetInnerDimensionOf2dJavaByteArray(JNIEnv *env, jobjectArray array2dJ) final; + int GetJavaObjectArrayLength(JNIEnv *env, jobjectArray arrayJ) final; + int GetJavaIntArrayLength(JNIEnv *env, jintArray arrayJ) final; + int GetJavaLongArrayLength(JNIEnv *env, jlongArray arrayJ) final; + int GetJavaBytesArrayLength(JNIEnv *env, jbyteArray arrayJ) final; + int GetJavaFloatArrayLength(JNIEnv *env, jfloatArray arrayJ) final; + + void DeleteLocalRef(JNIEnv *env, jobject obj) final; + jbyte * GetByteArrayElements(JNIEnv *env, jbyteArray array, jboolean * isCopy) final; + jfloat * GetFloatArrayElements(JNIEnv *env, jfloatArray array, jboolean * isCopy) final; + jint * GetIntArrayElements(JNIEnv *env, jintArray array, jboolean * isCopy) final; + jlong * GetLongArrayElements(JNIEnv *env, jlongArray array, jboolean * isCopy) final; + jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index) final; + jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodId, int id, float distance) final; + jobjectArray NewObjectArray(JNIEnv *env, jsize len, jclass clazz, jobject init) final; + jbyteArray NewByteArray(JNIEnv *env, jsize len) final; + void ReleaseByteArrayElements(JNIEnv *env, jbyteArray array, jbyte *elems, int mode) final; + void ReleaseFloatArrayElements(JNIEnv *env, jfloatArray array, jfloat *elems, int mode) final; + void ReleaseIntArrayElements(JNIEnv *env, jintArray array, jint *elems, jint mode) final; + void ReleaseLongArrayElements(JNIEnv *env, jlongArray array, jlong *elems, jint mode) final; + void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject val) final; + void SetByteArrayRegion(JNIEnv *env, jbyteArray array, jsize start, jsize len, const jbyte * buf) final; + void Convert2dJavaObjectArrayAndStoreToFloatVector(JNIEnv *env, jobjectArray array2dJ, int dim, std::vector *vect) final; + void Convert2dJavaObjectArrayAndStoreToBinaryVector(JNIEnv *env, jobjectArray array2dJ, int dim, std::vector *vect) final; + void Convert2dJavaObjectArrayAndStoreToByteVector(JNIEnv *env, jobjectArray array2dJ, int dim, std::vector *vect) final; + jobject GetObjectField(JNIEnv * env, jobject obj, jfieldID fieldID) final; + jclass FindClassFromJNIEnv(JNIEnv * env, const char *name) final; + jmethodID GetMethodID(JNIEnv * env, jclass clazz, const char *name, const char *sig) final; + jfieldID GetFieldID(JNIEnv * env, jclass clazz, const char *name, const char *sig) final; + jint CallNonvirtualIntMethodA(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, jvalue *args) final; + jlong CallNonvirtualLongMethodA(JNIEnv * env, jobject obj, jclass clazz, jmethodID methodID, jvalue* args) final; + void CallNonvirtualVoidMethodA(JNIEnv * env, jobject obj, jclass clazz, jmethodID methodID, jvalue* args) final; + void * GetPrimitiveArrayCritical(JNIEnv * env, jarray array, jboolean *isCopy) final; + void ReleasePrimitiveArrayCritical(JNIEnv * env, jarray array, void *carray, jint mode) final; private: std::unordered_map cachedClasses; std::unordered_map cachedMethods; - }; + }; // class JNIUtil + + /** + * It's common cleaner to release a primitive array within its destructor. + * Ex: JNIReleaseElements release_int_array_elements {[=](){ + * jniUtil->ReleaseIntArrayElements(env, idsJ, idsCpp, JNI_ABORT); + * }}; + */ + struct JNIReleaseElements { + explicit JNIReleaseElements(std::function _release_func) + : release_func(std::move(_release_func)) { + } + + ~JNIReleaseElements() { + try { + if (release_func) { + release_func(); + } + } catch (...) { + // Ignore + } + } + + std::function release_func; + }; // struct ReleaseIntArrayElements // ------------------------------- CONSTANTS -------------------------------- extern const std::string FAISS_NAME; @@ -179,6 +256,7 @@ namespace knn_jni { extern const std::string COSINESIMIL; extern const std::string INNER_PRODUCT; extern const std::string NEG_DOT_PRODUCT; + extern const std::string HAMMING; extern const std::string NPROBES; extern const std::string COARSE_QUANTIZER; diff --git a/jni/include/memory_util.h b/jni/include/memory_util.h new file mode 100644 index 000000000..5e1fc13ae --- /dev/null +++ b/jni/include/memory_util.h @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +#ifndef KNNPLUGIN_JNI_INCLUDE_MEMORY_UTIL_H_ +#define KNNPLUGIN_JNI_INCLUDE_MEMORY_UTIL_H_ + +#if defined(__GNUC__) || defined(__clang__) +#define RESTRICT __restrict__ +#elif defined(_MSC_VER) +#define RESTRICT __declspec(restrict) +#else +#define RESTRICT +#endif + +#endif //KNNPLUGIN_JNI_INCLUDE_MEMORY_UTIL_H_ diff --git a/jni/include/native_engines_stream_support.h b/jni/include/native_engines_stream_support.h new file mode 100644 index 000000000..07f97f3ac --- /dev/null +++ b/jni/include/native_engines_stream_support.h @@ -0,0 +1,234 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +#ifndef OPENSEARCH_KNN_JNI_STREAM_SUPPORT_H +#define OPENSEARCH_KNN_JNI_STREAM_SUPPORT_H + +#include "jni_util.h" +#include "parameter_utils.h" +#include "memory_util.h" + +#include +#include +#include +#include + +namespace knn_jni { +namespace stream { + +/** + * This class contains Java IndexInputWithBuffer reference and calls its API to copy required bytes into a read buffer. + */ +class NativeEngineIndexInputMediator { + public: + // Expect IndexInputWithBuffer is given as `_indexInput`. + NativeEngineIndexInputMediator(JNIUtilInterface *_jni_interface, + JNIEnv *_env, + jobject _indexInput) + : jni_interface(knn_jni::util::ParameterCheck::require_non_null( + _jni_interface, "jni_interface")), + env(knn_jni::util::ParameterCheck::require_non_null(_env, "env")), + indexInput(knn_jni::util::ParameterCheck::require_non_null(_indexInput, "indexInput")), + bufferArray((jbyteArray) (_jni_interface->GetObjectField(_env, + _indexInput, + getBufferFieldId(_jni_interface, _env)))), + copyBytesMethod(getCopyBytesMethod(_jni_interface, _env)), + remainingBytesMethod(getRemainingBytesMethod(_jni_interface, _env)) { + } + + void copyBytes(int64_t nbytes, uint8_t * RESTRICT destination) { + auto jclazz = getIndexInputWithBufferClass(jni_interface, env); + + while (nbytes > 0) { + // Call `copyBytes` to read bytes as many as possible. + jvalue args; + args.j = nbytes; + const auto readBytes = + jni_interface->CallNonvirtualIntMethodA(env, indexInput, jclazz, copyBytesMethod, &args); + jni_interface->HasExceptionInStack(env, "Reading bytes via IndexInput has failed."); + + // === Critical Section Start === + + // Get primitive array pointer, no copy is happening in OpenJDK. + jbyte * RESTRICT primitiveArray = + (jbyte *) jni_interface->GetPrimitiveArrayCritical(env, bufferArray, nullptr); + + // Copy Java bytes to C++ destination address. + std::memcpy(destination, primitiveArray, readBytes); + + // Release the acquired primitive array pointer. + // JNI_ABORT tells JVM to directly free memory without copying back to Java byte[]. + // Since we're merely copying data, we don't need to copying back. + // Note than when we received an internal primitive array pointer, then the mode will be ignored. + jni_interface->ReleasePrimitiveArrayCritical(env, bufferArray, primitiveArray, JNI_ABORT); + + // === Critical Section End === + + destination += readBytes; + nbytes -= readBytes; + } // End while + } + + int64_t remainingBytes() { + auto bytes = jni_interface->CallNonvirtualLongMethodA(env, + indexInput, + getIndexInputWithBufferClass(jni_interface, env), + remainingBytesMethod, + nullptr); + jni_interface->HasExceptionInStack(env, "Checking remaining bytes has failed."); + return bytes; + } + + private: + static jclass getIndexInputWithBufferClass(JNIUtilInterface *jni_interface, JNIEnv *env) { + static jclass INDEX_INPUT_WITH_BUFFER_CLASS = + jni_interface->FindClassFromJNIEnv(env, "org/opensearch/knn/index/store/IndexInputWithBuffer"); + return INDEX_INPUT_WITH_BUFFER_CLASS; + } + + static jmethodID getCopyBytesMethod(JNIUtilInterface *jni_interface, JNIEnv *env) { + static jmethodID COPY_METHOD_ID = + jni_interface->GetMethodID(env, getIndexInputWithBufferClass(jni_interface, env), "copyBytes", "(J)I"); + return COPY_METHOD_ID; + } + + static jmethodID getRemainingBytesMethod(JNIUtilInterface *jni_interface, JNIEnv *env) { + static jmethodID COPY_METHOD_ID = + jni_interface->GetMethodID(env, getIndexInputWithBufferClass(jni_interface, env), "remainingBytes", "()J"); + return COPY_METHOD_ID; + } + + static jfieldID getBufferFieldId(JNIUtilInterface *jni_interface, JNIEnv *env) { + static jfieldID BUFFER_FIELD_ID = + jni_interface->GetFieldID(env, getIndexInputWithBufferClass(jni_interface, env), "buffer", "[B"); + return BUFFER_FIELD_ID; + } + + JNIUtilInterface *jni_interface; + JNIEnv *env; + + // `IndexInputWithBuffer` instance having `IndexInput` instance obtained from `Directory` for reading. + jobject indexInput; + jbyteArray bufferArray; + jmethodID copyBytesMethod; + jmethodID remainingBytesMethod; +}; // class NativeEngineIndexInputMediator + + + +/** + * This class delegates the provided index output to do IO processing. + * In most cases, it is expected that IndexOutputWithBuffer was passed down to this, + * which eventually have Lucene's IndexOutput to write bytes. + */ +class NativeEngineIndexOutputMediator { + public: + NativeEngineIndexOutputMediator(JNIUtilInterface *_jni_interface, + JNIEnv *_env, + jobject _indexOutput) + : jni_interface(knn_jni::util::ParameterCheck::require_non_null(_jni_interface, "jni_interface")), + env(knn_jni::util::ParameterCheck::require_non_null(_env, "env")), + indexOutput(knn_jni::util::ParameterCheck::require_non_null(_indexOutput, "indexOutput")), + bufferArray((jbyteArray) (_jni_interface->GetObjectField(_env, + _indexOutput, + getBufferFieldId(_jni_interface, _env)))), + writeBytesMethod(getWriteBytesMethod(_jni_interface, _env)), + bufferLength(jni_interface->GetJavaBytesArrayLength(env, bufferArray)), + nextWriteIndex() { + } + + void writeBytes(const uint8_t * RESTRICT source, size_t nbytes) { + auto left = nbytes; + while (left > 0) { + const auto writeBytes = std::min(bufferLength - nextWriteIndex, left); + + // === Critical Section Start === + + // Get primitive array pointer, no copy is happening in OpenJDK. + jbyte * RESTRICT primitiveArray = + (jbyte *) jni_interface->GetPrimitiveArrayCritical(env, bufferArray, nullptr); + + // Copy the given bytes to Java byte[] address. + std::memcpy(primitiveArray + nextWriteIndex, source, writeBytes); + + // Release the acquired primitive array pointer. + // 0 tells JVM to copy back the content, and to free the pointer. It will be ignored if we acquired an internal + // primitive array pointer instead of a copied version. + // From JNI docs: + // Mode 0 : copy back the content and free the elems buffer + // The mode argument provides information on how the array buffer should be released. mode has no effect if elems + // is not a copy of the elements in array. + jni_interface->ReleasePrimitiveArrayCritical(env, bufferArray, primitiveArray, 0); + + // === Critical Section End === + + nextWriteIndex += writeBytes; + if (nextWriteIndex >= bufferLength) { + callWriteBytesInIndexOutput(); + } + + source += writeBytes; + left -= writeBytes; + } // End while + } + + void flush() { + if (nextWriteIndex > 0) { + callWriteBytesInIndexOutput(); + } + } + + private: + static jclass getIndexOutputWithBufferClass(JNIUtilInterface *jni_interface, JNIEnv *env) { + static jclass INDEX_OUTPUT_WITH_BUFFER_CLASS = + jni_interface->FindClassFromJNIEnv(env, "org/opensearch/knn/index/store/IndexOutputWithBuffer"); + return INDEX_OUTPUT_WITH_BUFFER_CLASS; + } + + static jmethodID getWriteBytesMethod(JNIUtilInterface *jni_interface, JNIEnv *env) { + static jmethodID WRITE_METHOD_ID = + jni_interface->GetMethodID(env, getIndexOutputWithBufferClass(jni_interface, env), "writeBytes", "(I)V"); + return WRITE_METHOD_ID; + } + + static jfieldID getBufferFieldId(JNIUtilInterface *jni_interface, JNIEnv *env) { + static jfieldID BUFFER_FIELD_ID = + jni_interface->GetFieldID(env, getIndexOutputWithBufferClass(jni_interface, env), "buffer", "[B"); + return BUFFER_FIELD_ID; + } + + void callWriteBytesInIndexOutput() { + auto jclazz = getIndexOutputWithBufferClass(jni_interface, env); + // Initializing the first integer parameter of `writeBytes`. + // `i` represents an integer parameter. + jvalue args {.i = nextWriteIndex}; + jni_interface->CallNonvirtualVoidMethodA(env, indexOutput, jclazz, writeBytesMethod, &args); + jni_interface->HasExceptionInStack(env, "Writing bytes via IndexOutput has failed."); + nextWriteIndex = 0; + } + + JNIUtilInterface *jni_interface; + JNIEnv *env; + + // `IndexOutputWithBuffer` instance having `IndexOutput` instance obtained from `Directory` for reading. + jobject indexOutput; + jbyteArray bufferArray; + jmethodID writeBytesMethod; + size_t bufferLength; + int32_t nextWriteIndex; +}; // NativeEngineIndexOutputMediator + + + +} +} + +#endif //OPENSEARCH_KNN_JNI_STREAM_SUPPORT_H diff --git a/jni/include/nmslib_stream_support.h b/jni/include/nmslib_stream_support.h new file mode 100644 index 000000000..2c410dde6 --- /dev/null +++ b/jni/include/nmslib_stream_support.h @@ -0,0 +1,73 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +#ifndef OPENSEARCH_KNN_JNI_NMSLIB_STREAM_SUPPORT_H +#define OPENSEARCH_KNN_JNI_NMSLIB_STREAM_SUPPORT_H + +#include "native_engines_stream_support.h" +#include "utils.h" // This is from NMSLIB +#include "parameter_utils.h" + +namespace knn_jni { +namespace stream { + +/** + * NmslibIOReader implementation delegating NativeEngineIndexInputMediator to read bytes. + */ +class NmslibOpenSearchIOReader final : public similarity::NmslibIOReader { + public: + explicit NmslibOpenSearchIOReader(NativeEngineIndexInputMediator *_mediator) + : similarity::NmslibIOReader(), + mediator(knn_jni::util::ParameterCheck::require_non_null(_mediator, "mediator")) { + } + + void read(char *bytes, size_t len) final { + if (len > 0) { + // Mediator calls IndexInput, then copy read bytes to `ptr`. + mediator->copyBytes(len, (uint8_t *) bytes); + } + } + + size_t remainingBytes() final { + return mediator->remainingBytes(); + } + + private: + NativeEngineIndexInputMediator *mediator; +}; // class NmslibOpenSearchIOReader + + +class NmslibOpenSearchIOWriter final : public similarity::NmslibIOWriter { + public: + explicit NmslibOpenSearchIOWriter(NativeEngineIndexOutputMediator *_mediator) + : similarity::NmslibIOWriter(), + mediator(knn_jni::util::ParameterCheck::require_non_null(_mediator, "mediator")) { + } + + void write(char *bytes, size_t len) final { + if (len > 0) { + mediator->writeBytes((uint8_t *) bytes, len); + } + } + + void flush() final { + mediator->flush(); + } + + private: + NativeEngineIndexOutputMediator *mediator; +}; // class NmslibOpenSearchIOWriter + + +} +} + +#endif //OPENSEARCH_KNN_JNI_NMSLIB_STREAM_SUPPORT_H diff --git a/jni/include/nmslib_wrapper.h b/jni/include/nmslib_wrapper.h index 6d862048a..687a96d59 100644 --- a/jni/include/nmslib_wrapper.h +++ b/jni/include/nmslib_wrapper.h @@ -25,19 +25,27 @@ namespace knn_jni { namespace nmslib_wrapper { // Create an index with ids and vectors. The configuration is defined by values in the Java map, parametersJ. // The index is serialized to indexPathJ. - void CreateIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, jobjectArray vectorsJ, - jstring indexPathJ, jobject parametersJ); + void CreateIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, jlong vectorsAddress, jint dim, + jobject output, jobject parametersJ); // Load an index from indexPathJ into memory. Use parametersJ to set any query time parameters // // Return a pointer to the loaded index jlong LoadIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jstring indexPathJ, jobject parametersJ); + // Load an index via an input stream into memory. Use parametersJ to set any query time parameters + // + // Return a pointer to the loaded index + jlong LoadIndexWithStream(knn_jni::JNIUtilInterface * jniUtil, + JNIEnv * env, + jobject readStream, + jobject parametersJ); + // Execute a query against the index located in memory at indexPointerJ. // // Return an array of KNNQueryResults jobjectArray QueryIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jlong indexPointerJ, - jfloatArray queryVectorJ, jint kJ); + jfloatArray queryVectorJ, jint kJ, jobject methodParamsJ); // Free the index located in memory at indexPointerJ void Free(jlong indexPointer); @@ -48,10 +56,10 @@ namespace knn_jni { struct IndexWrapper { explicit IndexWrapper(const std::string& spaceType) { // Index gets constructed with a reference to data (see above) but is otherwise unused - similarity::ObjectVector data; space.reset(similarity::SpaceFactoryRegistry::Instance().CreateSpace(spaceType, similarity::AnyParams())); index.reset(similarity::MethodFactoryRegistry::Instance().CreateMethod(false, "hnsw", spaceType, *space, data)); } + similarity::ObjectVector data; std::unique_ptr> space; std::unique_ptr> index; }; diff --git a/jni/include/org_opensearch_knn_jni_FaissService.h b/jni/include/org_opensearch_knn_jni_FaissService.h index 4af9a24bc..dce580138 100644 --- a/jni/include/org_opensearch_knn_jni_FaissService.h +++ b/jni/include/org_opensearch_knn_jni_FaissService.h @@ -20,19 +20,101 @@ extern "C" { #endif /* * Class: org_opensearch_knn_jni_FaissService - * Method: createIndex - * Signature: ([I[[FLjava/lang/String;Ljava/util/Map;)V + * Method: initIndex + * Signature: ([IJILjava/lang/String;Ljava/util/Map;)V */ -JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createIndex - (JNIEnv *, jclass, jintArray, jobjectArray, jstring, jobject); +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initIndex(JNIEnv * env, jclass cls, + jlong numDocs, jint dimJ, + jobject parametersJ); +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: initBinaryIndex + * Signature: ([IJILjava/lang/String;Ljava/util/Map;)V + */ +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initBinaryIndex(JNIEnv * env, jclass cls, + jlong numDocs, jint dimJ, + jobject parametersJ); + +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: initByteIndex + * Signature: ([IJILjava/lang/String;Ljava/util/Map;)V + */ +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initByteIndex(JNIEnv * env, jclass cls, + jlong numDocs, jint dimJ, + jobject parametersJ); + +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: insertToIndex + * Signature: ([IJILjava/lang/String;Ljava/util/Map;)V + */ +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_insertToIndex(JNIEnv * env, jclass cls, jintArray idsJ, + jlong vectorsAddressJ, jint dimJ, + jlong indexAddress, jint threadCount); +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: insertToBinaryIndex + * Signature: ([IJILjava/lang/String;Ljava/util/Map;)V + */ +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_insertToBinaryIndex(JNIEnv * env, jclass cls, jintArray idsJ, + jlong vectorsAddressJ, jint dimJ, + jlong indexAddress, jint threadCount); + +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: insertToByteIndex + * Signature: ([IJILjava/lang/String;Ljava/util/Map;)V + */ +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_insertToByteIndex(JNIEnv * env, jclass cls, jintArray idsJ, + jlong vectorsAddressJ, jint dimJ, + jlong indexAddress, jint threadCount); + +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: writeIndex + * Signature: (JLorg/opensearch/knn/index/store/IndexOutputWithBuffer;)V + */ +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_writeIndex(JNIEnv *, jclass, jlong, jobject); + + +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: writeBinaryIndex + * Signature: (JLorg/opensearch/knn/index/store/IndexOutputWithBuffer;)V + */ +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_writeBinaryIndex(JNIEnv *, jclass, jlong, jobject); + +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: writeByteIndex + * Signature: (JLorg/opensearch/knn/index/store/IndexOutputWithBuffer;)V + */ +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_writeByteIndex(JNIEnv *, jclass, jlong, jobject); /* * Class: org_opensearch_knn_jni_FaissService * Method: createIndexFromTemplate - * Signature: ([I[[FLjava/lang/String;[BLjava/util/Map;)V + * Signature: ([IJILorg/opensearch/knn/index/store/IndexOutputWithBuffer;[BLjava/util/Map;)V */ JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createIndexFromTemplate - (JNIEnv *, jclass, jintArray, jobjectArray, jstring, jbyteArray, jobject); + (JNIEnv *, jclass, jintArray, jlong, jint, jobject, jbyteArray, jobject); + +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: createBinaryIndexFromTemplate + * Signature: ([IJILorg/opensearch/knn/index/store/IndexOutputWithBuffer;[BLjava/util/Map;)V + */ +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createBinaryIndexFromTemplate + (JNIEnv *, jclass, jintArray, jlong, jint, jobject, jbyteArray, jobject); + +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: createByteIndexFromTemplate + * Signature: ([IJILorg/opensearch/knn/index/store/IndexOutputWithBuffer;[BLjava/util/Map;)V + */ +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createByteIndexFromTemplate + (JNIEnv *, jclass, jintArray, jlong, jint, jobject, jbyteArray, jobject); /* * Class: org_opensearch_knn_jni_FaissService @@ -42,20 +124,92 @@ JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createIndexFromT JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadIndex (JNIEnv *, jclass, jstring); +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: loadIndexWithStream + * Signature: (Lorg/opensearch/knn/index/util/IndexInputWithBuffer;)J + */ +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadIndexWithStream + (JNIEnv *, jclass, jobject); + +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: loadBinaryIndex + * Signature: (Ljava/lang/String;)J + */ +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadBinaryIndex + (JNIEnv *, jclass, jstring); + +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: loadBinaryIndexWithStream + * Signature: (Lorg/opensearch/knn/index/util/IndexInputWithBuffer;)J + */ +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadBinaryIndexWithStream + (JNIEnv *, jclass, jobject); + +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: isSharedIndexStateRequired + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_opensearch_knn_jni_FaissService_isSharedIndexStateRequired + (JNIEnv *, jclass, jlong); + +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: initSharedIndexState + * Signature: (J)J + */ +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initSharedIndexState + (JNIEnv *, jclass, jlong); + +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: setSharedIndexState + * Signature: (JJ)V + */ +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_setSharedIndexState + (JNIEnv *, jclass, jlong, jlong); + /* * Class: org_opensearch_knn_jni_FaissService * Method: queryIndex - * Signature: (J[FI)[Lorg/opensearch/knn/index/KNNQueryResult; + * Signature: (J[FILjava/util/Map[I)[Lorg/opensearch/knn/index/query/KNNQueryResult; */ JNIEXPORT jobjectArray JNICALL Java_org_opensearch_knn_jni_FaissService_queryIndex - (JNIEnv *, jclass, jlong, jfloatArray, jint); + (JNIEnv *, jclass, jlong, jfloatArray, jint, jobject, jintArray); + +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: queryIndexWithFilter + * Signature: (J[FILjava/util/Map[JI[I)[Lorg/opensearch/knn/index/query/KNNQueryResult; + */ +JNIEXPORT jobjectArray JNICALL Java_org_opensearch_knn_jni_FaissService_queryIndexWithFilter + (JNIEnv *, jclass, jlong, jfloatArray, jint, jobject, jlongArray, jint, jintArray); + +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: queryBIndexWithFilter + * Signature: (J[BILjava/util/Map[JI[I)[Lorg/opensearch/knn/index/query/KNNQueryResult; + */ +JNIEXPORT jobjectArray JNICALL Java_org_opensearch_knn_jni_FaissService_queryBinaryIndexWithFilter + (JNIEnv *, jclass, jlong, jbyteArray, jint, jobject, jlongArray, jint, jintArray); /* * Class: org_opensearch_knn_jni_FaissService * Method: free - * Signature: (J)V + * Signature: (JZ)V */ JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_free + (JNIEnv *, jclass, jlong, jboolean); + +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: freeSharedIndexState + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_freeSharedIndexState (JNIEnv *, jclass, jlong); /* @@ -74,6 +228,22 @@ JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_initLibrary JNIEXPORT jbyteArray JNICALL Java_org_opensearch_knn_jni_FaissService_trainIndex (JNIEnv *, jclass, jobject, jint, jlong); +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: trainBinaryIndex + * Signature: (Ljava/util/Map;IJ)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_opensearch_knn_jni_FaissService_trainBinaryIndex + (JNIEnv *, jclass, jobject, jint, jlong); + +/* + * Class: org_opensearch_knn_jni_FaissService + * Method: trainByteIndex + * Signature: (Ljava/util/Map;IJ)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_opensearch_knn_jni_FaissService_trainByteIndex + (JNIEnv *, jclass, jobject, jint, jlong); + /* * Class: org_opensearch_knn_jni_FaissService * Method: transferVectors @@ -82,13 +252,21 @@ JNIEXPORT jbyteArray JNICALL Java_org_opensearch_knn_jni_FaissService_trainIndex JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_transferVectors (JNIEnv *, jclass, jlong, jobjectArray); +/* +* Class: org_opensearch_knn_jni_FaissService +* Method: rangeSearchIndexWithFilter +* Signature: (J[FJLjava/util/MapI[JII)[Lorg/opensearch/knn/index/query/RangeQueryResult; +*/ +JNIEXPORT jobjectArray JNICALL Java_org_opensearch_knn_jni_FaissService_rangeSearchIndexWithFilter + (JNIEnv *, jclass, jlong, jfloatArray, jfloat, jobject, jint, jlongArray, jint, jintArray); + /* * Class: org_opensearch_knn_jni_FaissService - * Method: freeVectors - * Signature: (J)V + * Method: rangeSearchIndex + * Signature: (J[FJLjava/util/MapII)[Lorg/opensearch/knn/index/query/RangeQueryResult; */ -JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_freeVectors - (JNIEnv *, jclass, jlong); +JNIEXPORT jobjectArray JNICALL Java_org_opensearch_knn_jni_FaissService_rangeSearchIndex + (JNIEnv *, jclass, jlong, jfloatArray, jfloat, jobject, jint, jintArray); #ifdef __cplusplus } diff --git a/jni/include/org_opensearch_knn_jni_JNICommons.h b/jni/include/org_opensearch_knn_jni_JNICommons.h new file mode 100644 index 000000000..8bfbcc266 --- /dev/null +++ b/jni/include/org_opensearch_knn_jni_JNICommons.h @@ -0,0 +1,72 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_opensearch_knn_jni_JNICommons */ + +#ifndef _Included_org_opensearch_knn_jni_JNICommons +#define _Included_org_opensearch_knn_jni_JNICommons +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_opensearch_knn_jni_JNICommons + * Method: storeVectorData + * Signature: (J[[FJJJ) + */ +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_JNICommons_storeVectorData + (JNIEnv *, jclass, jlong, jobjectArray, jlong, jboolean); + +/* + * Class: org_opensearch_knn_jni_JNICommons + * Method: storeBinaryVectorData + * Signature: (J[[FJJ) + */ +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_JNICommons_storeBinaryVectorData + (JNIEnv *, jclass, jlong, jobjectArray, jlong, jboolean); + +/* + * Class: org_opensearch_knn_jni_JNICommons + * Method: storeByteVectorData + * Signature: (J[[FJJ) + */ +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_JNICommons_storeByteVectorData + (JNIEnv *, jclass, jlong, jobjectArray, jlong, jboolean); + +/* + * Class: org_opensearch_knn_jni_JNICommons + * Method: freeVectorData + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_JNICommons_freeVectorData + (JNIEnv *, jclass, jlong); + +/* +* Class: org_opensearch_knn_jni_JNICommons +* Method: freeBinaryVectorData +* Signature: (J)V +*/ +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_JNICommons_freeBinaryVectorData +(JNIEnv *, jclass, jlong); + +/* +* Class: org_opensearch_knn_jni_JNICommons +* Method: freeByteVectorData +* Signature: (J)V +*/ +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_JNICommons_freeByteVectorData +(JNIEnv *, jclass, jlong); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jni/include/org_opensearch_knn_jni_NmslibService.h b/jni/include/org_opensearch_knn_jni_NmslibService.h index dd907581d..0e035c3dd 100644 --- a/jni/include/org_opensearch_knn_jni_NmslibService.h +++ b/jni/include/org_opensearch_knn_jni_NmslibService.h @@ -21,10 +21,10 @@ extern "C" { /* * Class: org_opensearch_knn_jni_NmslibService * Method: createIndex - * Signature: ([I[[FLjava/lang/String;Ljava/util/Map;)V + * Signature: ([IJILorg/opensearch/knn/index/store/IndexOutputWithBuffer;Ljava/util/Map;)V */ JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_NmslibService_createIndex - (JNIEnv *, jclass, jintArray, jobjectArray, jstring, jobject); + (JNIEnv *, jclass, jintArray, jlong, jint, jobject, jobject); /* * Class: org_opensearch_knn_jni_NmslibService @@ -34,13 +34,21 @@ JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_NmslibService_createIndex JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_NmslibService_loadIndex (JNIEnv *, jclass, jstring, jobject); +/* + * Class: org_opensearch_knn_jni_NmslibService + * Method: loadIndexWithStream + * Signature: (Lorg/opensearch/knn/index/store/IndexInputWithBuffer;Ljava/util/Map;)J + */ +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_NmslibService_loadIndexWithStream + (JNIEnv *, jclass, jobject, jobject); + /* * Class: org_opensearch_knn_jni_NmslibService * Method: queryIndex - * Signature: (J[FI)[Lorg/opensearch/knn/index/KNNQueryResult; + * Signature: (J[FI)[Lorg/opensearch/knn/index/query/KNNQueryResult; */ JNIEXPORT jobjectArray JNICALL Java_org_opensearch_knn_jni_NmslibService_queryIndex - (JNIEnv *, jclass, jlong, jfloatArray, jint); + (JNIEnv *, jclass, jlong, jfloatArray, jint, jobject); /* * Class: org_opensearch_knn_jni_NmslibService diff --git a/jni/include/parameter_utils.h b/jni/include/parameter_utils.h new file mode 100644 index 000000000..aff922324 --- /dev/null +++ b/jni/include/parameter_utils.h @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +#ifndef KNNPLUGIN_JNI_INCLUDE_PARAMETER_UTILS_H_ +#define KNNPLUGIN_JNI_INCLUDE_PARAMETER_UTILS_H_ + +#include +#include + +namespace knn_jni { +namespace util { + +struct ParameterCheck { + template + static PtrType *require_non_null(PtrType *ptr, const char *parameter_name) { + if (ptr == nullptr) { + throw std::invalid_argument(std::string("Parameter [") + parameter_name + "] should not be null."); + } + return ptr; + } + + private: + ParameterCheck() = default; +}; // class ParameterCheck + + + +} +} // namespace knn_jni + +#endif //KNNPLUGIN_JNI_INCLUDE_PARAMETER_UTILS_H_ diff --git a/jni/patches/faiss/0001-Custom-patch-to-support-multi-vector.patch b/jni/patches/faiss/0001-Custom-patch-to-support-multi-vector.patch new file mode 100644 index 000000000..26b143346 --- /dev/null +++ b/jni/patches/faiss/0001-Custom-patch-to-support-multi-vector.patch @@ -0,0 +1,1091 @@ +From e775a8e65da96232822d5aed77f538592fccffda Mon Sep 17 00:00:00 2001 +From: Heemin Kim +Date: Tue, 30 Jan 2024 14:43:56 -0800 +Subject: [PATCH] Add IDGrouper for HNSW + +Signed-off-by: Heemin Kim +--- + faiss/CMakeLists.txt | 3 + + faiss/Index.h | 6 +- + faiss/IndexHNSW.cpp | 13 +- + faiss/IndexIDMap.cpp | 29 +++++ + faiss/IndexIDMap.h | 22 ++++ + faiss/impl/HNSW.cpp | 6 + + faiss/impl/IDGrouper.cpp | 51 ++++++++ + faiss/impl/IDGrouper.h | 51 ++++++++ + faiss/impl/ResultHandler.h | 189 +++++++++++++++++++++++++++++ + faiss/utils/GroupHeap.h | 182 ++++++++++++++++++++++++++++ + tests/CMakeLists.txt | 2 + + tests/test_group_heap.cpp | 98 +++++++++++++++ + tests/test_id_grouper.cpp | 241 +++++++++++++++++++++++++++++++++++++ + 13 files changed, 889 insertions(+), 4 deletions(-) + create mode 100644 faiss/impl/IDGrouper.cpp + create mode 100644 faiss/impl/IDGrouper.h + create mode 100644 faiss/utils/GroupHeap.h + create mode 100644 tests/test_group_heap.cpp + create mode 100644 tests/test_id_grouper.cpp + +diff --git a/faiss/CMakeLists.txt b/faiss/CMakeLists.txt +index 2871d974..d0bcec6a 100644 +--- a/faiss/CMakeLists.txt ++++ b/faiss/CMakeLists.txt +@@ -55,6 +55,7 @@ set(FAISS_SRC + impl/AuxIndexStructures.cpp + impl/CodePacker.cpp + impl/IDSelector.cpp ++ impl/IDGrouper.cpp + impl/FaissException.cpp + impl/HNSW.cpp + impl/NSG.cpp +@@ -151,6 +152,7 @@ set(FAISS_HEADERS + impl/AuxIndexStructures.h + impl/CodePacker.h + impl/IDSelector.h ++ impl/IDGrouper.h + impl/DistanceComputer.h + impl/FaissAssert.h + impl/FaissException.h +@@ -186,6 +188,7 @@ set(FAISS_HEADERS + invlists/InvertedListsIOHook.h + utils/AlignedTable.h + utils/bf16.h ++ utils/GroupHeap.h + utils/Heap.h + utils/WorkerThread.h + utils/distances.h +diff --git a/faiss/Index.h b/faiss/Index.h +index f57140ec..8f511e5d 100644 +--- a/faiss/Index.h ++++ b/faiss/Index.h +@@ -51,8 +51,9 @@ + namespace faiss { + + /// Forward declarations see impl/AuxIndexStructures.h, impl/IDSelector.h +-/// and impl/DistanceComputer.h ++/// ,impl/IDGrouper.h and impl/DistanceComputer.h + struct IDSelector; ++struct IDGrouper; + struct RangeSearchResult; + struct DistanceComputer; + +@@ -64,6 +65,9 @@ struct DistanceComputer; + struct SearchParameters { + /// if non-null, only these IDs will be considered during search. + IDSelector* sel = nullptr; ++ /// if non-null, only best matched ID per group will be included in the ++ /// result. ++ IDGrouper* grp = nullptr; + /// make sure we can dynamic_cast this + virtual ~SearchParameters() {} + }; +diff --git a/faiss/IndexHNSW.cpp b/faiss/IndexHNSW.cpp +index 6a1186ca..9c8a8255 100644 +--- a/faiss/IndexHNSW.cpp ++++ b/faiss/IndexHNSW.cpp +@@ -301,10 +301,17 @@ void IndexHNSW::search( + const SearchParameters* params_in) const { + FAISS_THROW_IF_NOT(k > 0); + +- using RH = HeapBlockResultHandler; +- RH bres(n, distances, labels, k); ++ if (params_in && params_in->grp) { ++ using RH = GroupedHeapBlockResultHandler; ++ RH bres(n, distances, labels, k, params_in->grp); + +- hnsw_search(this, n, x, bres, params_in); ++ hnsw_search(this, n, x, bres, params_in); ++ } else { ++ using RH = HeapBlockResultHandler; ++ RH bres(n, distances, labels, k); ++ ++ hnsw_search(this, n, x, bres, params_in); ++ } + + if (is_similarity_metric(this->metric_type)) { + // we need to revert the negated distances +diff --git a/faiss/IndexIDMap.cpp b/faiss/IndexIDMap.cpp +index dc84052b..3f375e7b 100644 +--- a/faiss/IndexIDMap.cpp ++++ b/faiss/IndexIDMap.cpp +@@ -102,6 +102,23 @@ struct ScopedSelChange { + } + }; + ++/// RAII object to reset the IDGrouper in the params object ++struct ScopedGrpChange { ++ SearchParameters* params = nullptr; ++ IDGrouper* old_grp = nullptr; ++ ++ void set(SearchParameters* params_2, IDGrouper* new_grp) { ++ this->params = params_2; ++ old_grp = params_2->grp; ++ params_2->grp = new_grp; ++ } ++ ~ScopedGrpChange() { ++ if (params) { ++ params->grp = old_grp; ++ } ++ } ++}; ++ + } // namespace + + template +@@ -114,6 +131,8 @@ void IndexIDMapTemplate::search( + const SearchParameters* params) const { + IDSelectorTranslated this_idtrans(this->id_map, nullptr); + ScopedSelChange sel_change; ++ IDGrouperTranslated this_idgrptrans(this->id_map, nullptr); ++ ScopedGrpChange grp_change; + + if (params && params->sel) { + auto idtrans = dynamic_cast(params->sel); +@@ -131,6 +150,16 @@ void IndexIDMapTemplate::search( + sel_change.set(params_non_const, &this_idtrans); + } + } ++ ++ if (params && params->grp) { ++ auto idtrans = dynamic_cast(params->grp); ++ ++ if (!idtrans) { ++ auto params_non_const = const_cast(params); ++ this_idgrptrans.grp = params->grp; ++ grp_change.set(params_non_const, &this_idgrptrans); ++ } ++ } + index->search(n, x, k, distances, labels, params); + idx_t* li = labels; + #pragma omp parallel for +diff --git a/faiss/IndexIDMap.h b/faiss/IndexIDMap.h +index 2d164123..a68887bd 100644 +--- a/faiss/IndexIDMap.h ++++ b/faiss/IndexIDMap.h +@@ -9,6 +9,7 @@ + + #include + #include ++#include + #include + + #include +@@ -124,4 +125,25 @@ struct IDSelectorTranslated : IDSelector { + } + }; + ++// IDGrouper that translates the ids using an IDMap ++struct IDGrouperTranslated : IDGrouper { ++ const std::vector& id_map; ++ const IDGrouper* grp; ++ ++ IDGrouperTranslated( ++ const std::vector& id_map, ++ const IDGrouper* grp) ++ : id_map(id_map), grp(grp) {} ++ ++ IDGrouperTranslated(IndexBinaryIDMap& index_idmap, const IDGrouper* grp) ++ : id_map(index_idmap.id_map), grp(grp) {} ++ ++ IDGrouperTranslated(IndexIDMap& index_idmap, const IDGrouper* grp) ++ : id_map(index_idmap.id_map), grp(grp) {} ++ ++ idx_t get_group(idx_t id) const override { ++ return grp->get_group(id_map[id]); ++ } ++}; ++ + } // namespace faiss +diff --git a/faiss/impl/HNSW.cpp b/faiss/impl/HNSW.cpp +index c3693fd9..7ae28062 100644 +--- a/faiss/impl/HNSW.cpp ++++ b/faiss/impl/HNSW.cpp +@@ -906,6 +906,12 @@ int extract_k_from_ResultHandler(ResultHandler& res) { + if (auto hres = dynamic_cast(&res)) { + return hres->k; + } ++ ++ if (auto hres = dynamic_cast< ++ GroupedHeapBlockResultHandler::SingleResultHandler*>(&res)) { ++ return hres->k; ++ } ++ + return 1; + } + +diff --git a/faiss/impl/IDGrouper.cpp b/faiss/impl/IDGrouper.cpp +new file mode 100644 +index 00000000..ca9f5fda +--- /dev/null ++++ b/faiss/impl/IDGrouper.cpp +@@ -0,0 +1,51 @@ ++/** ++ * Copyright (c) Facebook, Inc. and its affiliates. ++ * ++ * This source code is licensed under the MIT license found in the ++ * LICENSE file in the root directory of this source tree. ++ */ ++ ++#include ++#include ++#include ++ ++namespace faiss { ++ ++/*********************************************************************** ++ * IDGrouperBitmap ++ ***********************************************************************/ ++ ++IDGrouperBitmap::IDGrouperBitmap(size_t n, uint64_t* bitmap) ++ : n(n), bitmap(bitmap) {} ++ ++idx_t IDGrouperBitmap::get_group(idx_t id) const { ++ assert(id >= 0 && "id shouldn't be less than zero"); ++ assert(id < this->n * 64 && "is should be less than total number of bits"); ++ ++ idx_t index = id >> 6; // div by 64 ++ uint64_t block = this->bitmap[index] >> ++ (id & 63); // Equivalent of words[i] >> (index % 64) ++ // block is non zero after right shift, it means, next set bit is in current ++ // block The index of set bit is "given index" + "trailing zero in the right ++ // shifted word" ++ if (block != 0) { ++ return id + __builtin_ctzll(block); ++ } ++ ++ while (++index < this->n) { ++ block = this->bitmap[index]; ++ if (block != 0) { ++ return (index << 6) + __builtin_ctzll(block); ++ } ++ } ++ ++ return NO_MORE_DOCS; ++} ++ ++void IDGrouperBitmap::set_group(idx_t group_id) { ++ idx_t index = group_id >> 6; ++ this->bitmap[index] |= 1ULL ++ << (group_id & 63); // Equivalent of 1ULL << (value % 64) ++} ++ ++} // namespace faiss +diff --git a/faiss/impl/IDGrouper.h b/faiss/impl/IDGrouper.h +new file mode 100644 +index 00000000..d56113d9 +--- /dev/null ++++ b/faiss/impl/IDGrouper.h +@@ -0,0 +1,51 @@ ++/** ++ * Copyright (c) Facebook, Inc. and its affiliates. ++ * ++ * This source code is licensed under the MIT license found in the ++ * LICENSE file in the root directory of this source tree. ++ */ ++ ++#pragma once ++ ++#include ++#include ++#include ++ ++#include ++ ++/** IDGrouper is intended to define a group of vectors to include only ++ * the nearest vector of each group during search */ ++ ++namespace faiss { ++ ++/** Encapsulates a group id of ids */ ++struct IDGrouper { ++ const idx_t NO_MORE_DOCS = std::numeric_limits::max(); ++ virtual idx_t get_group(idx_t id) const = 0; ++ virtual ~IDGrouper() {} ++}; ++ ++/** One bit per element. Constructed with a bitmap, size ceil(n / 8). ++ */ ++struct IDGrouperBitmap : IDGrouper { ++ // length of the bitmap array ++ size_t n; ++ ++ // Array of uint64_t holding the bits ++ // Using uint64_t to leverage function __builtin_ctzll which is defined in ++ // faiss/impl/platform_macros.h Group id of a given id is next set bit in ++ // the bitmap ++ uint64_t* bitmap; ++ ++ /** Construct with a binary mask ++ * ++ * @param n size of the bitmap array ++ * @param bitmap group id of a given id is next set bit in the bitmap ++ */ ++ IDGrouperBitmap(size_t n, uint64_t* bitmap); ++ idx_t get_group(idx_t id) const final; ++ void set_group(idx_t group_id); ++ ~IDGrouperBitmap() override {} ++}; ++ ++} // namespace faiss +diff --git a/faiss/impl/ResultHandler.h b/faiss/impl/ResultHandler.h +index 3116eb24..126ed015 100644 +--- a/faiss/impl/ResultHandler.h ++++ b/faiss/impl/ResultHandler.h +@@ -14,6 +14,8 @@ + #include + #include + #include ++#include ++#include + #include + #include + +@@ -286,6 +288,193 @@ struct HeapBlockResultHandler : BlockResultHandler { + } + }; + ++/***************************************************************** ++ * Heap based result handler with grouping ++ *****************************************************************/ ++ ++template ++struct GroupedHeapBlockResultHandler : BlockResultHandler { ++ using T = typename C::T; ++ using TI = typename C::TI; ++ using BlockResultHandler::i0; ++ using BlockResultHandler::i1; ++ ++ T* heap_dis_tab; ++ TI* heap_ids_tab; ++ int64_t k; // number of results to keep ++ ++ IDGrouper* id_grouper; ++ TI* heap_group_ids_tab; ++ std::unordered_map* group_id_to_index_in_heap_tab; ++ ++ GroupedHeapBlockResultHandler( ++ size_t nq, ++ T* heap_dis_tab, ++ TI* heap_ids_tab, ++ size_t k, ++ IDGrouper* id_grouper) ++ : BlockResultHandler(nq), ++ heap_dis_tab(heap_dis_tab), ++ heap_ids_tab(heap_ids_tab), ++ k(k), ++ id_grouper(id_grouper) {} ++ ++ /****************************************************** ++ * API for 1 result at a time (each SingleResultHandler is ++ * called from 1 thread) ++ */ ++ ++ struct SingleResultHandler : ResultHandler { ++ GroupedHeapBlockResultHandler& hr; ++ using ResultHandler::threshold; ++ size_t k; ++ ++ T* heap_dis; ++ TI* heap_ids; ++ TI* heap_group_ids; ++ std::unordered_map group_id_to_index_in_heap; ++ ++ explicit SingleResultHandler(GroupedHeapBlockResultHandler& hr) ++ : hr(hr), k(hr.k) {} ++ ++ /// begin results for query # i ++ void begin(size_t i) { ++ heap_dis = hr.heap_dis_tab + i * k; ++ heap_ids = hr.heap_ids_tab + i * k; ++ heap_heapify(k, heap_dis, heap_ids); ++ threshold = heap_dis[0]; ++ heap_group_ids = new TI[hr.k]; ++ for (size_t i = 0; i < hr.k; i++) { ++ heap_group_ids[i] = -1; ++ } ++ } ++ ++ /// add one result for query i ++ bool add_result(T dis, TI idx) final { ++ if (!C::cmp(threshold, dis)) { ++ return false; ++ } ++ ++ idx_t group_id = hr.id_grouper->get_group(idx); ++ typename std::unordered_map::const_iterator it_pos = ++ group_id_to_index_in_heap.find(group_id); ++ if (it_pos == group_id_to_index_in_heap.end()) { ++ group_heap_replace_top( ++ k, ++ heap_dis, ++ heap_ids, ++ heap_group_ids, ++ dis, ++ idx, ++ group_id, ++ &group_id_to_index_in_heap); ++ threshold = heap_dis[0]; ++ return true; ++ } else { ++ size_t pos = it_pos->second; ++ if (!C::cmp(heap_dis[pos], dis)) { ++ return false; ++ } ++ group_heap_replace_at( ++ pos, ++ k, ++ heap_dis, ++ heap_ids, ++ heap_group_ids, ++ dis, ++ idx, ++ group_id, ++ &group_id_to_index_in_heap); ++ threshold = heap_dis[0]; ++ return true; ++ } ++ } ++ ++ /// series of results for query i is done ++ void end() { ++ heap_reorder(k, heap_dis, heap_ids); ++ delete[] heap_group_ids; ++ } ++ }; ++ ++ /****************************************************** ++ * API for multiple results (called from 1 thread) ++ */ ++ ++ /// begin ++ void begin_multiple(size_t i0_2, size_t i1_2) final { ++ this->i0 = i0_2; ++ this->i1 = i1_2; ++ for (size_t i = i0; i < i1; i++) { ++ heap_heapify(k, heap_dis_tab + i * k, heap_ids_tab + i * k); ++ } ++ size_t size = (i1 - i0) * k; ++ heap_group_ids_tab = new TI[size]; ++ for (size_t i = 0; i < size; i++) { ++ heap_group_ids_tab[i] = -1; ++ } ++ group_id_to_index_in_heap_tab = ++ new std::unordered_map[i1 - i0]; ++ } ++ ++ /// add results for query i0..i1 and j0..j1 ++ void add_results(size_t j0, size_t j1, const T* dis_tab) final { ++#pragma omp parallel for ++ for (int64_t i = i0; i < i1; i++) { ++ T* heap_dis = heap_dis_tab + i * k; ++ TI* heap_ids = heap_ids_tab + i * k; ++ const T* dis_tab_i = dis_tab + (j1 - j0) * (i - i0) - j0; ++ T thresh = heap_dis[0]; // NOLINT(*-use-default-none) ++ for (size_t j = j0; j < j1; j++) { ++ T dis = dis_tab_i[j]; ++ if (C::cmp(thresh, dis)) { ++ idx_t group_id = id_grouper->get_group(j); ++ typename std::unordered_map::const_iterator ++ it_pos = group_id_to_index_in_heap_tab[i - i0].find( ++ group_id); ++ if (it_pos == group_id_to_index_in_heap_tab[i - i0].end()) { ++ group_heap_replace_top( ++ k, ++ heap_dis, ++ heap_ids, ++ heap_group_ids_tab + ((i - i0) * k), ++ dis, ++ j, ++ group_id, ++ &group_id_to_index_in_heap_tab[i - i0]); ++ thresh = heap_dis[0]; ++ } else { ++ size_t pos = it_pos->first; ++ if (C::cmp(heap_dis[pos], dis)) { ++ group_heap_replace_at( ++ pos, ++ k, ++ heap_dis, ++ heap_ids, ++ heap_group_ids_tab + ((i - i0) * k), ++ dis, ++ j, ++ group_id, ++ &group_id_to_index_in_heap_tab[i - i0]); ++ thresh = heap_dis[0]; ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ /// series of results for queries i0..i1 is done ++ void end_multiple() final { ++ // maybe parallel for ++ for (size_t i = i0; i < i1; i++) { ++ heap_reorder(k, heap_dis_tab + i * k, heap_ids_tab + i * k); ++ } ++ delete[] group_id_to_index_in_heap_tab; ++ delete[] heap_group_ids_tab; ++ } ++}; ++ + /***************************************************************** + * Reservoir result handler + * +diff --git a/faiss/utils/GroupHeap.h b/faiss/utils/GroupHeap.h +new file mode 100644 +index 00000000..3b7078da +--- /dev/null ++++ b/faiss/utils/GroupHeap.h +@@ -0,0 +1,182 @@ ++/** ++ * Copyright (c) Facebook, Inc. and its affiliates. ++ * ++ * This source code is licensed under the MIT license found in the ++ * LICENSE file in the root directory of this source tree. ++ */ ++ ++#pragma once ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++#include ++ ++namespace faiss { ++ ++/** ++ * From start_index, it compare its value with parent node's and swap if needed. ++ * Continue until either there is no swap or it reaches the top node. ++ */ ++template ++static inline void group_up_heap( ++ typename C::T* heap_dis, ++ typename C::TI* heap_ids, ++ typename C::TI* heap_group_ids, ++ std::unordered_map* group_id_to_index_in_heap, ++ size_t start_index) { ++ heap_dis--; /* Use 1-based indexing for easier node->child translation */ ++ heap_ids--; ++ heap_group_ids--; ++ size_t i = start_index + 1, i_father; ++ typename C::T target_dis = heap_dis[i]; ++ typename C::TI target_id = heap_ids[i]; ++ typename C::TI target_group_id = heap_group_ids[i]; ++ ++ while (i > 1) { ++ i_father = i >> 1; ++ if (!C::cmp2( ++ target_dis, ++ heap_dis[i_father], ++ target_id, ++ heap_ids[i_father])) { ++ /* the heap structure is ok */ ++ break; ++ } ++ heap_dis[i] = heap_dis[i_father]; ++ heap_ids[i] = heap_ids[i_father]; ++ heap_group_ids[i] = heap_group_ids[i_father]; ++ (*group_id_to_index_in_heap)[heap_group_ids[i]] = i - 1; ++ i = i_father; ++ } ++ heap_dis[i] = target_dis; ++ heap_ids[i] = target_id; ++ heap_group_ids[i] = target_group_id; ++ (*group_id_to_index_in_heap)[heap_group_ids[i]] = i - 1; ++} ++ ++/** ++ * From start_index, it compare its value with child node's and swap if needed. ++ * Continue until either there is no swap or it reaches the leaf node. ++ */ ++template ++static inline void group_down_heap( ++ size_t k, ++ typename C::T* heap_dis, ++ typename C::TI* heap_ids, ++ typename C::TI* heap_group_ids, ++ std::unordered_map* group_id_to_index_in_heap, ++ size_t start_index) { ++ heap_dis--; /* Use 1-based indexing for easier node->child translation */ ++ heap_ids--; ++ heap_group_ids--; ++ size_t i = start_index + 1, i1, i2; ++ typename C::T target_dis = heap_dis[i]; ++ typename C::TI target_id = heap_ids[i]; ++ typename C::TI target_group_id = heap_group_ids[i]; ++ ++ while (1) { ++ i1 = i << 1; ++ i2 = i1 + 1; ++ if (i1 > k) { ++ break; ++ } ++ ++ // Note that C::cmp2() is a bool function answering ++ // `(a1 > b1) || ((a1 == b1) && (a2 > b2))` for max ++ // heap and same with the `<` sign for min heap. ++ if ((i2 == k + 1) || ++ C::cmp2(heap_dis[i1], heap_dis[i2], heap_ids[i1], heap_ids[i2])) { ++ if (C::cmp2(target_dis, heap_dis[i1], target_id, heap_ids[i1])) { ++ break; ++ } ++ heap_dis[i] = heap_dis[i1]; ++ heap_ids[i] = heap_ids[i1]; ++ heap_group_ids[i] = heap_group_ids[i1]; ++ (*group_id_to_index_in_heap)[heap_group_ids[i]] = i - 1; ++ i = i1; ++ } else { ++ if (C::cmp2(target_dis, heap_dis[i2], target_id, heap_ids[i2])) { ++ break; ++ } ++ heap_dis[i] = heap_dis[i2]; ++ heap_ids[i] = heap_ids[i2]; ++ heap_group_ids[i] = heap_group_ids[i2]; ++ (*group_id_to_index_in_heap)[heap_group_ids[i]] = i - 1; ++ i = i2; ++ } ++ } ++ heap_dis[i] = target_dis; ++ heap_ids[i] = target_id; ++ heap_group_ids[i] = target_group_id; ++ (*group_id_to_index_in_heap)[heap_group_ids[i]] = i - 1; ++} ++ ++template ++static inline void group_heap_replace_top( ++ size_t k, ++ typename C::T* heap_dis, ++ typename C::TI* heap_ids, ++ typename C::TI* heap_group_ids, ++ typename C::T dis, ++ typename C::TI id, ++ typename C::TI group_id, ++ std::unordered_map* group_id_to_index_in_heap) { ++ assert(group_id_to_index_in_heap->find(group_id) == ++ group_id_to_index_in_heap->end() && ++ "group id should not exist in the binary heap"); ++ ++ group_id_to_index_in_heap->erase(heap_group_ids[0]); ++ heap_group_ids[0] = group_id; ++ heap_dis[0] = dis; ++ heap_ids[0] = id; ++ (*group_id_to_index_in_heap)[group_id] = 0; ++ group_down_heap( ++ k, ++ heap_dis, ++ heap_ids, ++ heap_group_ids, ++ group_id_to_index_in_heap, ++ 0); ++} ++ ++template ++static inline void group_heap_replace_at( ++ size_t pos, ++ size_t k, ++ typename C::T* heap_dis, ++ typename C::TI* heap_ids, ++ typename C::TI* heap_group_ids, ++ typename C::T dis, ++ typename C::TI id, ++ typename C::TI group_id, ++ std::unordered_map* group_id_to_index_in_heap) { ++ assert(group_id_to_index_in_heap->find(group_id) != ++ group_id_to_index_in_heap->end() && ++ "group id should exist in the binary heap"); ++ assert(group_id_to_index_in_heap->find(group_id)->second == pos && ++ "index of group id in the heap should be same as pos"); ++ ++ heap_dis[pos] = dis; ++ heap_ids[pos] = id; ++ group_up_heap( ++ heap_dis, heap_ids, heap_group_ids, group_id_to_index_in_heap, pos); ++ group_down_heap( ++ k, ++ heap_dis, ++ heap_ids, ++ heap_group_ids, ++ group_id_to_index_in_heap, ++ pos); ++} ++ ++} // namespace faiss +\ No newline at end of file +diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt +index c41edf0c..87ab2020 100644 +--- a/tests/CMakeLists.txt ++++ b/tests/CMakeLists.txt +@@ -27,6 +27,8 @@ set(FAISS_TEST_SRC + test_approx_topk.cpp + test_RCQ_cropping.cpp + test_distances_simd.cpp ++ test_id_grouper.cpp ++ test_group_heap.cpp + test_heap.cpp + test_code_distance.cpp + test_hnsw.cpp +diff --git a/tests/test_group_heap.cpp b/tests/test_group_heap.cpp +new file mode 100644 +index 00000000..0e8fe7a7 +--- /dev/null ++++ b/tests/test_group_heap.cpp +@@ -0,0 +1,98 @@ ++/** ++ * Copyright (c) Facebook, Inc. and its affiliates. ++ * ++ * This source code is licensed under the MIT license found in the ++ * LICENSE file in the root directory of this source tree. ++ */ ++#include ++#include ++#include ++#include ++ ++using namespace faiss; ++ ++TEST(GroupHeap, group_heap_replace_top) { ++ using C = CMax; ++ const int k = 100; ++ float binary_heap_values[k]; ++ int64_t binary_heap_ids[k]; ++ heap_heapify(k, binary_heap_values, binary_heap_ids); ++ int64_t binary_heap_group_ids[k]; ++ for (size_t i = 0; i < k; i++) { ++ binary_heap_group_ids[i] = -1; ++ } ++ std::unordered_map group_id_to_index_in_heap; ++ for (int i = 1000; i > 0; i--) { ++ group_heap_replace_top( ++ k, ++ binary_heap_values, ++ binary_heap_ids, ++ binary_heap_group_ids, ++ i * 10.0, ++ i, ++ i, ++ &group_id_to_index_in_heap); ++ } ++ ++ heap_reorder(k, binary_heap_values, binary_heap_ids); ++ ++ for (int i = 0; i < k; i++) { ++ ASSERT_EQ((i + 1) * 10.0, binary_heap_values[i]); ++ ASSERT_EQ(i + 1, binary_heap_ids[i]); ++ } ++} ++ ++TEST(GroupHeap, group_heap_replace_at) { ++ using C = CMax; ++ const int k = 10; ++ float binary_heap_values[k]; ++ int64_t binary_heap_ids[k]; ++ heap_heapify(k, binary_heap_values, binary_heap_ids); ++ int64_t binary_heap_group_ids[k]; ++ for (size_t i = 0; i < k; i++) { ++ binary_heap_group_ids[i] = -1; ++ } ++ std::unordered_map group_id_to_index_in_heap; ++ ++ std::unordered_map group_id_to_id; ++ for (int i = 1000; i > 0; i--) { ++ int64_t group_id = rand() % 100; ++ group_id_to_id[group_id] = i; ++ if (group_id_to_index_in_heap.find(group_id) == ++ group_id_to_index_in_heap.end()) { ++ group_heap_replace_top( ++ k, ++ binary_heap_values, ++ binary_heap_ids, ++ binary_heap_group_ids, ++ i * 10.0, ++ i, ++ group_id, ++ &group_id_to_index_in_heap); ++ } else { ++ group_heap_replace_at( ++ group_id_to_index_in_heap.at(group_id), ++ k, ++ binary_heap_values, ++ binary_heap_ids, ++ binary_heap_group_ids, ++ i * 10.0, ++ i, ++ group_id, ++ &group_id_to_index_in_heap); ++ } ++ } ++ ++ heap_reorder(k, binary_heap_values, binary_heap_ids); ++ ++ std::vector sorted_ids; ++ for (const auto& pair : group_id_to_id) { ++ sorted_ids.push_back(pair.second); ++ } ++ std::sort(sorted_ids.begin(), sorted_ids.end()); ++ ++ for (int i = 0; i < k && binary_heap_ids[i] != -1; i++) { ++ ASSERT_EQ(sorted_ids[i] * 10.0, binary_heap_values[i]); ++ ASSERT_EQ(sorted_ids[i], binary_heap_ids[i]); ++ } ++} +diff --git a/tests/test_id_grouper.cpp b/tests/test_id_grouper.cpp +new file mode 100644 +index 00000000..6601795b +--- /dev/null ++++ b/tests/test_id_grouper.cpp +@@ -0,0 +1,241 @@ ++/** ++ * Copyright (c) Facebook, Inc. and its affiliates. ++ * ++ * This source code is licensed under the MIT license found in the ++ * LICENSE file in the root directory of this source tree. ++ */ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++// 64-bit int ++using idx_t = faiss::idx_t; ++ ++using namespace faiss; ++ ++TEST(IdGrouper, get_group) { ++ uint64_t ids1[1] = {0b1000100010001000}; ++ IDGrouperBitmap bitmap(1, ids1); ++ ++ ASSERT_EQ(3, bitmap.get_group(0)); ++ ASSERT_EQ(3, bitmap.get_group(1)); ++ ASSERT_EQ(3, bitmap.get_group(2)); ++ ASSERT_EQ(3, bitmap.get_group(3)); ++ ASSERT_EQ(7, bitmap.get_group(4)); ++ ASSERT_EQ(7, bitmap.get_group(5)); ++ ASSERT_EQ(7, bitmap.get_group(6)); ++ ASSERT_EQ(7, bitmap.get_group(7)); ++ ASSERT_EQ(11, bitmap.get_group(8)); ++ ASSERT_EQ(11, bitmap.get_group(9)); ++ ASSERT_EQ(11, bitmap.get_group(10)); ++ ASSERT_EQ(11, bitmap.get_group(11)); ++ ASSERT_EQ(15, bitmap.get_group(12)); ++ ASSERT_EQ(15, bitmap.get_group(13)); ++ ASSERT_EQ(15, bitmap.get_group(14)); ++ ASSERT_EQ(15, bitmap.get_group(15)); ++ ASSERT_EQ(bitmap.NO_MORE_DOCS, bitmap.get_group(16)); ++} ++ ++TEST(IdGrouper, set_group) { ++ idx_t group_ids[] = {64, 127, 128, 1022}; ++ uint64_t ids[16] = {}; // 1023 / 64 + 1 ++ IDGrouperBitmap bitmap(16, ids); ++ ++ for (int i = 0; i < 4; i++) { ++ bitmap.set_group(group_ids[i]); ++ } ++ ++ int group_id_index = 0; ++ for (int i = 0; i <= group_ids[3]; i++) { ++ ASSERT_EQ(group_ids[group_id_index], bitmap.get_group(i)); ++ if (group_ids[group_id_index] == i) { ++ group_id_index++; ++ } ++ } ++ ASSERT_EQ(bitmap.NO_MORE_DOCS, bitmap.get_group(group_ids[3] + 1)); ++} ++ ++TEST(IdGrouper, sanity_test) { ++ int d = 1; // dimension ++ int nb = 10; // database size ++ ++ std::mt19937 rng; ++ std::uniform_real_distribution<> distrib; ++ ++ float* xb = new float[d * nb]; ++ ++ for (int i = 0; i < nb; i++) { ++ for (int j = 0; j < d; j++) ++ xb[d * i + j] = distrib(rng); ++ xb[d * i] += i / 1000.; ++ } ++ ++ uint64_t bitmap[1] = {}; ++ faiss::IDGrouperBitmap id_grouper(1, bitmap); ++ for (int i = 0; i < nb; i++) { ++ id_grouper.set_group(i); ++ } ++ ++ int k = 5; ++ int m = 8; ++ faiss::Index* index = ++ new faiss::IndexHNSWFlat(d, m, faiss::MetricType::METRIC_L2); ++ index->add(nb, xb); // add vectors to the index ++ ++ // search ++ auto pSearchParameters = new faiss::SearchParametersHNSW(); ++ ++ idx_t* expectedI = new idx_t[k]; ++ float* expectedD = new float[k]; ++ index->search(1, xb, k, expectedD, expectedI, pSearchParameters); ++ ++ idx_t* I = new idx_t[k]; ++ float* D = new float[k]; ++ pSearchParameters->grp = &id_grouper; ++ index->search(1, xb, k, D, I, pSearchParameters); ++ ++ // compare ++ for (int j = 0; j < k; j++) { ++ ASSERT_EQ(expectedI[j], I[j]); ++ ASSERT_EQ(expectedD[j], D[j]); ++ } ++ ++ delete[] expectedI; ++ delete[] expectedD; ++ delete[] I; ++ delete[] D; ++ delete[] xb; ++} ++ ++TEST(IdGrouper, bitmap_with_hnsw) { ++ int d = 1; // dimension ++ int nb = 10; // database size ++ ++ std::mt19937 rng; ++ std::uniform_real_distribution<> distrib; ++ ++ float* xb = new float[d * nb]; ++ ++ for (int i = 0; i < nb; i++) { ++ for (int j = 0; j < d; j++) ++ xb[d * i + j] = distrib(rng); ++ xb[d * i] += i / 1000.; ++ } ++ ++ uint64_t bitmap[1] = {}; ++ faiss::IDGrouperBitmap id_grouper(1, bitmap); ++ for (int i = 0; i < nb; i++) { ++ if (i % 2 == 1) { ++ id_grouper.set_group(i); ++ } ++ } ++ ++ int k = 10; ++ int m = 8; ++ faiss::Index* index = ++ new faiss::IndexHNSWFlat(d, m, faiss::MetricType::METRIC_L2); ++ index->add(nb, xb); // add vectors to the index ++ ++ // search ++ idx_t* I = new idx_t[k]; ++ float* D = new float[k]; ++ ++ auto pSearchParameters = new faiss::SearchParametersHNSW(); ++ pSearchParameters->grp = &id_grouper; ++ ++ index->search(1, xb, k, D, I, pSearchParameters); ++ ++ std::unordered_set group_ids; ++ ASSERT_EQ(0, I[0]); ++ ASSERT_EQ(0, D[0]); ++ group_ids.insert(id_grouper.get_group(I[0])); ++ for (int j = 1; j < 5; j++) { ++ ASSERT_NE(-1, I[j]); ++ ASSERT_NE(std::numeric_limits::max(), D[j]); ++ group_ids.insert(id_grouper.get_group(I[j])); ++ } ++ for (int j = 5; j < k; j++) { ++ ASSERT_EQ(-1, I[j]); ++ ASSERT_EQ(std::numeric_limits::max(), D[j]); ++ } ++ ASSERT_EQ(5, group_ids.size()); ++ ++ delete[] I; ++ delete[] D; ++ delete[] xb; ++} ++ ++TEST(IdGrouper, bitmap_with_hnswn_idmap) { ++ int d = 1; // dimension ++ int nb = 10; // database size ++ ++ std::mt19937 rng; ++ std::uniform_real_distribution<> distrib; ++ ++ float* xb = new float[d * nb]; ++ idx_t* xids = new idx_t[d * nb]; ++ ++ for (int i = 0; i < nb; i++) { ++ for (int j = 0; j < d; j++) ++ xb[d * i + j] = distrib(rng); ++ xb[d * i] += i / 1000.; ++ } ++ ++ uint64_t bitmap[1] = {}; ++ faiss::IDGrouperBitmap id_grouper(1, bitmap); ++ int num_grp = 0; ++ int grp_size = 2; ++ int id_in_grp = 0; ++ for (int i = 0; i < nb; i++) { ++ xids[i] = i + num_grp; ++ id_in_grp++; ++ if (id_in_grp == grp_size) { ++ id_grouper.set_group(i + num_grp + 1); ++ num_grp++; ++ id_in_grp = 0; ++ } ++ } ++ ++ int k = 10; ++ int m = 8; ++ faiss::Index* index = ++ new faiss::IndexHNSWFlat(d, m, faiss::MetricType::METRIC_L2); ++ faiss::IndexIDMap id_map = ++ faiss::IndexIDMap(index); // add vectors to the index ++ id_map.add_with_ids(nb, xb, xids); ++ ++ // search ++ idx_t* I = new idx_t[k]; ++ float* D = new float[k]; ++ ++ auto pSearchParameters = new faiss::SearchParametersHNSW(); ++ pSearchParameters->grp = &id_grouper; ++ ++ id_map.search(1, xb, k, D, I, pSearchParameters); ++ ++ std::unordered_set group_ids; ++ ASSERT_EQ(0, I[0]); ++ ASSERT_EQ(0, D[0]); ++ group_ids.insert(id_grouper.get_group(I[0])); ++ for (int j = 1; j < 5; j++) { ++ ASSERT_NE(-1, I[j]); ++ ASSERT_NE(std::numeric_limits::max(), D[j]); ++ group_ids.insert(id_grouper.get_group(I[j])); ++ } ++ for (int j = 5; j < k; j++) { ++ ASSERT_EQ(-1, I[j]); ++ ASSERT_EQ(std::numeric_limits::max(), D[j]); ++ } ++ ASSERT_EQ(5, group_ids.size()); ++ ++ delete[] I; ++ delete[] D; ++ delete[] xb; ++} +-- +2.37.0 + diff --git a/jni/patches/faiss/0002-Enable-precomp-table-to-be-shared-ivfpq.patch b/jni/patches/faiss/0002-Enable-precomp-table-to-be-shared-ivfpq.patch new file mode 100644 index 000000000..88e6a8106 --- /dev/null +++ b/jni/patches/faiss/0002-Enable-precomp-table-to-be-shared-ivfpq.patch @@ -0,0 +1,512 @@ +From 9b33874562c9e62abf4a863657c54f0d349b0f67 Mon Sep 17 00:00:00 2001 +From: John Mazanec +Date: Wed, 21 Feb 2024 15:34:15 -0800 +Subject: [PATCH] Enable precomp table to be shared ivfpq + +Changes IVFPQ and IVFPQFastScan indices to be able to share the +precomputed table amongst other instances. Switches var to a pointer and +add necessary functions to set them correctly. + +Adds a tests to validate the behavior. + +Signed-off-by: John Mazanec +--- + faiss/IndexIVFPQ.cpp | 47 +++++++- + faiss/IndexIVFPQ.h | 16 ++- + faiss/IndexIVFPQFastScan.cpp | 47 ++++++-- + faiss/IndexIVFPQFastScan.h | 11 +- + tests/CMakeLists.txt | 1 + + tests/test_disable_pq_sdc_tables.cpp | 4 +- + tests/test_ivfpq_share_table.cpp | 173 +++++++++++++++++++++++++++ + 7 files changed, 284 insertions(+), 15 deletions(-) + create mode 100644 tests/test_ivfpq_share_table.cpp + +diff --git a/faiss/IndexIVFPQ.cpp b/faiss/IndexIVFPQ.cpp +index 100f499c..09508890 100644 +--- a/faiss/IndexIVFPQ.cpp ++++ b/faiss/IndexIVFPQ.cpp +@@ -59,6 +59,29 @@ IndexIVFPQ::IndexIVFPQ( + polysemous_training = nullptr; + do_polysemous_training = false; + polysemous_ht = 0; ++ precomputed_table = new AlignedTable(); ++ owns_precomputed_table = true; ++} ++ ++IndexIVFPQ::IndexIVFPQ(const IndexIVFPQ& orig) : IndexIVF(orig), pq(orig.pq) { ++ code_size = orig.pq.code_size; ++ invlists->code_size = code_size; ++ is_trained = orig.is_trained; ++ by_residual = orig.by_residual; ++ use_precomputed_table = orig.use_precomputed_table; ++ scan_table_threshold = orig.scan_table_threshold; ++ ++ polysemous_training = orig.polysemous_training; ++ do_polysemous_training = orig.do_polysemous_training; ++ polysemous_ht = orig.polysemous_ht; ++ precomputed_table = new AlignedTable(*orig.precomputed_table); ++ owns_precomputed_table = true; ++} ++ ++IndexIVFPQ::~IndexIVFPQ() { ++ if (owns_precomputed_table) { ++ delete precomputed_table; ++ } + } + + /**************************************************************** +@@ -464,11 +487,23 @@ void IndexIVFPQ::precompute_table() { + use_precomputed_table, + quantizer, + pq, +- precomputed_table, ++ *precomputed_table, + by_residual, + verbose); + } + ++void IndexIVFPQ::set_precomputed_table( ++ AlignedTable* _precompute_table, ++ int _use_precomputed_table) { ++ // Clean up old pre-computed table ++ if (owns_precomputed_table) { ++ delete precomputed_table; ++ } ++ owns_precomputed_table = false; ++ precomputed_table = _precompute_table; ++ use_precomputed_table = _use_precomputed_table; ++} ++ + namespace { + + #define TIC t0 = get_cycles() +@@ -648,7 +683,7 @@ struct QueryTables { + + fvec_madd( + pq.M * pq.ksub, +- ivfpq.precomputed_table.data() + key * pq.ksub * pq.M, ++ ivfpq.precomputed_table->data() + key * pq.ksub * pq.M, + -2.0, + sim_table_2, + sim_table); +@@ -677,7 +712,7 @@ struct QueryTables { + k >>= cpq.nbits; + + // get corresponding table +- const float* pc = ivfpq.precomputed_table.data() + ++ const float* pc = ivfpq.precomputed_table->data() + + (ki * pq.M + cm * Mf) * pq.ksub; + + if (polysemous_ht == 0) { +@@ -707,7 +742,7 @@ struct QueryTables { + dis0 = coarse_dis; + + const float* s = +- ivfpq.precomputed_table.data() + key * pq.ksub * pq.M; ++ ivfpq.precomputed_table->data() + key * pq.ksub * pq.M; + for (int m = 0; m < pq.M; m++) { + sim_table_ptrs[m] = s; + s += pq.ksub; +@@ -727,7 +762,7 @@ struct QueryTables { + int ki = k & ((uint64_t(1) << cpq.nbits) - 1); + k >>= cpq.nbits; + +- const float* pc = ivfpq.precomputed_table.data() + ++ const float* pc = ivfpq.precomputed_table->data() + + (ki * pq.M + cm * Mf) * pq.ksub; + + for (int m = m0; m < m0 + Mf; m++) { +@@ -1344,6 +1379,8 @@ IndexIVFPQ::IndexIVFPQ() { + do_polysemous_training = false; + polysemous_ht = 0; + polysemous_training = nullptr; ++ precomputed_table = new AlignedTable(); ++ owns_precomputed_table = true; + } + + struct CodeCmp { +diff --git a/faiss/IndexIVFPQ.h b/faiss/IndexIVFPQ.h +index d5d21da4..850bbe44 100644 +--- a/faiss/IndexIVFPQ.h ++++ b/faiss/IndexIVFPQ.h +@@ -48,7 +48,8 @@ struct IndexIVFPQ : IndexIVF { + + /// if use_precompute_table + /// size nlist * pq.M * pq.ksub +- AlignedTable precomputed_table; ++ bool owns_precomputed_table; ++ AlignedTable* precomputed_table; + + IndexIVFPQ( + Index* quantizer, +@@ -58,6 +59,10 @@ struct IndexIVFPQ : IndexIVF { + size_t nbits_per_idx, + MetricType metric = METRIC_L2); + ++ IndexIVFPQ(const IndexIVFPQ& orig); ++ ++ ~IndexIVFPQ(); ++ + void encode_vectors( + idx_t n, + const float* x, +@@ -139,6 +144,15 @@ struct IndexIVFPQ : IndexIVF { + /// build precomputed table + void precompute_table(); + ++ /** ++ * Initialize the precomputed table ++ * @param precompute_table ++ * @param _use_precomputed_table ++ */ ++ void set_precomputed_table( ++ AlignedTable* precompute_table, ++ int _use_precomputed_table); ++ + IndexIVFPQ(); + }; + +diff --git a/faiss/IndexIVFPQFastScan.cpp b/faiss/IndexIVFPQFastScan.cpp +index 2844ae49..895df342 100644 +--- a/faiss/IndexIVFPQFastScan.cpp ++++ b/faiss/IndexIVFPQFastScan.cpp +@@ -46,6 +46,8 @@ IndexIVFPQFastScan::IndexIVFPQFastScan( + : IndexIVFFastScan(quantizer, d, nlist, 0, metric), pq(d, M, nbits) { + by_residual = false; // set to false by default because it's faster + ++ precomputed_table = new AlignedTable(); ++ owns_precomputed_table = true; + init_fastscan(M, nbits, nlist, metric, bbs); + } + +@@ -53,6 +55,17 @@ IndexIVFPQFastScan::IndexIVFPQFastScan() { + by_residual = false; + bbs = 0; + M2 = 0; ++ precomputed_table = new AlignedTable(); ++ owns_precomputed_table = true; ++} ++ ++IndexIVFPQFastScan::IndexIVFPQFastScan(const IndexIVFPQFastScan& orig) ++ : IndexIVFFastScan(orig), pq(orig.pq) { ++ by_residual = orig.by_residual; ++ bbs = orig.bbs; ++ M2 = orig.M2; ++ precomputed_table = new AlignedTable(*orig.precomputed_table); ++ owns_precomputed_table = true; + } + + IndexIVFPQFastScan::IndexIVFPQFastScan(const IndexIVFPQ& orig, int bbs) +@@ -71,13 +84,15 @@ IndexIVFPQFastScan::IndexIVFPQFastScan(const IndexIVFPQ& orig, int bbs) + ntotal = orig.ntotal; + is_trained = orig.is_trained; + nprobe = orig.nprobe; ++ precomputed_table = new AlignedTable(); ++ owns_precomputed_table = true; + +- precomputed_table.resize(orig.precomputed_table.size()); ++ precomputed_table->resize(orig.precomputed_table->size()); + +- if (precomputed_table.nbytes() > 0) { +- memcpy(precomputed_table.get(), +- orig.precomputed_table.data(), +- precomputed_table.nbytes()); ++ if (precomputed_table->nbytes() > 0) { ++ memcpy(precomputed_table->get(), ++ orig.precomputed_table->data(), ++ precomputed_table->nbytes()); + } + + for (size_t i = 0; i < nlist; i++) { +@@ -102,6 +117,12 @@ IndexIVFPQFastScan::IndexIVFPQFastScan(const IndexIVFPQ& orig, int bbs) + orig_invlists = orig.invlists; + } + ++IndexIVFPQFastScan::~IndexIVFPQFastScan() { ++ if (owns_precomputed_table) { ++ delete precomputed_table; ++ } ++} ++ + /********************************************************* + * Training + *********************************************************/ +@@ -127,11 +148,23 @@ void IndexIVFPQFastScan::precompute_table() { + use_precomputed_table, + quantizer, + pq, +- precomputed_table, ++ *precomputed_table, + by_residual, + verbose); + } + ++void IndexIVFPQFastScan::set_precomputed_table( ++ AlignedTable* _precompute_table, ++ int _use_precomputed_table) { ++ // Clean up old pre-computed table ++ if (owns_precomputed_table) { ++ delete precomputed_table; ++ } ++ owns_precomputed_table = false; ++ precomputed_table = _precompute_table; ++ use_precomputed_table = _use_precomputed_table; ++} ++ + /********************************************************* + * Code management functions + *********************************************************/ +@@ -229,7 +262,7 @@ void IndexIVFPQFastScan::compute_LUT( + if (cij >= 0) { + fvec_madd_simd( + dim12, +- precomputed_table.get() + cij * dim12, ++ precomputed_table->get() + cij * dim12, + -2, + ip_table.get() + i * dim12, + tab); +diff --git a/faiss/IndexIVFPQFastScan.h b/faiss/IndexIVFPQFastScan.h +index 00dd2f11..91f35a6e 100644 +--- a/faiss/IndexIVFPQFastScan.h ++++ b/faiss/IndexIVFPQFastScan.h +@@ -38,7 +38,8 @@ struct IndexIVFPQFastScan : IndexIVFFastScan { + /// precomputed tables management + int use_precomputed_table = 0; + /// if use_precompute_table size (nlist, pq.M, pq.ksub) +- AlignedTable precomputed_table; ++ bool owns_precomputed_table; ++ AlignedTable* precomputed_table; + + IndexIVFPQFastScan( + Index* quantizer, +@@ -51,6 +52,10 @@ struct IndexIVFPQFastScan : IndexIVFFastScan { + + IndexIVFPQFastScan(); + ++ IndexIVFPQFastScan(const IndexIVFPQFastScan& orig); ++ ++ ~IndexIVFPQFastScan(); ++ + // built from an IndexIVFPQ + explicit IndexIVFPQFastScan(const IndexIVFPQ& orig, int bbs = 32); + +@@ -60,6 +65,10 @@ struct IndexIVFPQFastScan : IndexIVFFastScan { + + /// build precomputed table, possibly updating use_precomputed_table + void precompute_table(); ++ /// Pass in externally a precomputed ++ void set_precomputed_table( ++ AlignedTable* precompute_table, ++ int _use_precomputed_table); + + /// same as the regular IVFPQ encoder. The codes are not reorganized by + /// blocks a that point +diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt +index 87ab2020..a859516c 100644 +--- a/tests/CMakeLists.txt ++++ b/tests/CMakeLists.txt +@@ -38,6 +38,7 @@ set(FAISS_TEST_SRC + test_common_ivf_empty_index.cpp + test_callback.cpp + test_utils.cpp ++ test_ivfpq_share_table.cpp + ) + + add_executable(faiss_test ${FAISS_TEST_SRC}) +diff --git a/tests/test_disable_pq_sdc_tables.cpp b/tests/test_disable_pq_sdc_tables.cpp +index b211a5c4..a27973d5 100644 +--- a/tests/test_disable_pq_sdc_tables.cpp ++++ b/tests/test_disable_pq_sdc_tables.cpp +@@ -15,7 +15,9 @@ + #include "faiss/index_io.h" + #include "test_util.h" + +-pthread_mutex_t temp_file_mutex = PTHREAD_MUTEX_INITIALIZER; ++namespace { ++ pthread_mutex_t temp_file_mutex = PTHREAD_MUTEX_INITIALIZER; ++} + + TEST(IO, TestReadHNSWPQ_whenSDCDisabledFlagPassed_thenDisableSDCTable) { + Tempfilename index_filename(&temp_file_mutex, "/tmp/faiss_TestReadHNSWPQ"); +diff --git a/tests/test_ivfpq_share_table.cpp b/tests/test_ivfpq_share_table.cpp +new file mode 100644 +index 00000000..f827315d +--- /dev/null ++++ b/tests/test_ivfpq_share_table.cpp +@@ -0,0 +1,173 @@ ++/** ++ * Copyright (c) Facebook, Inc. and its affiliates. ++ * ++ * This source code is licensed under the MIT license found in the ++ * LICENSE file in the root directory of this source tree. ++ */ ++ ++#include ++ ++#include ++ ++#include "faiss/Index.h" ++#include "faiss/IndexHNSW.h" ++#include "faiss/IndexIVFPQFastScan.h" ++#include "faiss/index_factory.h" ++#include "faiss/index_io.h" ++#include "test_util.h" ++ ++namespace { ++ pthread_mutex_t temp_file_mutex = PTHREAD_MUTEX_INITIALIZER; ++} ++ ++std::vector generate_data( ++ int d, ++ int n, ++ std::default_random_engine rng, ++ std::uniform_real_distribution u) { ++ std::vector vectors(n * d); ++ for (size_t i = 0; i < n * d; i++) { ++ vectors[i] = u(rng); ++ } ++ return vectors; ++} ++ ++void assert_float_vectors_almost_equal( ++ std::vector a, ++ std::vector b) { ++ float margin = 0.000001; ++ ASSERT_EQ(a.size(), b.size()); ++ for (int i = 0; i < a.size(); i++) { ++ ASSERT_NEAR(a[i], b[i], margin); ++ } ++} ++ ++/// Test case test precomputed table sharing for IVFPQ indices. ++template /// T represents class cast to use for index ++void test_ivfpq_table_sharing( ++ const std::string& index_description, ++ const std::string& filename, ++ faiss::MetricType metric) { ++ // Setup the index: ++ // 1. Build an index ++ // 2. ingest random data ++ // 3. serialize to disk ++ int d = 32, n = 1000; ++ std::default_random_engine rng( ++ std::chrono::system_clock::now().time_since_epoch().count()); ++ std::uniform_real_distribution u(0, 100); ++ ++ std::vector index_vectors = generate_data(d, n, rng, u); ++ std::vector query_vectors = generate_data(d, n, rng, u); ++ ++ Tempfilename index_filename(&temp_file_mutex, filename); ++ { ++ std::unique_ptr index_writer( ++ faiss::index_factory(d, index_description.c_str(), metric)); ++ ++ index_writer->train(n, index_vectors.data()); ++ index_writer->add(n, index_vectors.data()); ++ faiss::write_index(index_writer.get(), index_filename.c_str()); ++ } ++ ++ // Load index from disk. Confirm that the sdc table is equal to 0 when ++ // disable sdc is set ++ std::unique_ptr> sharedAlignedTable( ++ new faiss::AlignedTable()); ++ int shared_use_precomputed_table = 0; ++ int k = 10; ++ std::vector distances_test_a(k * n); ++ std::vector labels_test_a(k * n); ++ { ++ std::vector distances_baseline(k * n); ++ std::vector labels_baseline(k * n); ++ ++ std::unique_ptr index_read_pq_table_enabled( ++ dynamic_cast(faiss::read_index( ++ index_filename.c_str(), faiss::IO_FLAG_READ_ONLY))); ++ std::unique_ptr index_read_pq_table_disabled( ++ dynamic_cast(faiss::read_index( ++ index_filename.c_str(), ++ faiss::IO_FLAG_READ_ONLY | ++ faiss::IO_FLAG_SKIP_PRECOMPUTE_TABLE))); ++ faiss::initialize_IVFPQ_precomputed_table( ++ shared_use_precomputed_table, ++ index_read_pq_table_disabled->quantizer, ++ index_read_pq_table_disabled->pq, ++ *sharedAlignedTable, ++ index_read_pq_table_disabled->by_residual, ++ index_read_pq_table_disabled->verbose); ++ index_read_pq_table_disabled->set_precomputed_table( ++ sharedAlignedTable.get(), shared_use_precomputed_table); ++ ++ ASSERT_TRUE(index_read_pq_table_enabled->owns_precomputed_table); ++ ASSERT_FALSE(index_read_pq_table_disabled->owns_precomputed_table); ++ index_read_pq_table_enabled->search( ++ n, ++ query_vectors.data(), ++ k, ++ distances_baseline.data(), ++ labels_baseline.data()); ++ index_read_pq_table_disabled->search( ++ n, ++ query_vectors.data(), ++ k, ++ distances_test_a.data(), ++ labels_test_a.data()); ++ ++ assert_float_vectors_almost_equal(distances_baseline, distances_test_a); ++ ASSERT_EQ(labels_baseline, labels_test_a); ++ } ++ ++ // The precomputed table should only be set for L2 metric type ++ if (metric == faiss::METRIC_L2) { ++ ASSERT_EQ(shared_use_precomputed_table, 1); ++ } else { ++ ASSERT_EQ(shared_use_precomputed_table, 0); ++ } ++ ++ // At this point, the original has gone out of scope, the destructor has ++ // been called. Confirm that initializing a new index from the table ++ // preserves the functionality. ++ { ++ std::vector distances_test_b(k * n); ++ std::vector labels_test_b(k * n); ++ ++ std::unique_ptr index_read_pq_table_disabled( ++ dynamic_cast(faiss::read_index( ++ index_filename.c_str(), ++ faiss::IO_FLAG_READ_ONLY | ++ faiss::IO_FLAG_SKIP_PRECOMPUTE_TABLE))); ++ index_read_pq_table_disabled->set_precomputed_table( ++ sharedAlignedTable.get(), shared_use_precomputed_table); ++ ASSERT_FALSE(index_read_pq_table_disabled->owns_precomputed_table); ++ index_read_pq_table_disabled->search( ++ n, ++ query_vectors.data(), ++ k, ++ distances_test_b.data(), ++ labels_test_b.data()); ++ assert_float_vectors_almost_equal(distances_test_a, distances_test_b); ++ ASSERT_EQ(labels_test_a, labels_test_b); ++ } ++} ++ ++TEST(TestIVFPQTableSharing, L2) { ++ test_ivfpq_table_sharing( ++ "IVF16,PQ8x4", "/tmp/ivfpql2", faiss::METRIC_L2); ++} ++ ++TEST(TestIVFPQTableSharing, IP) { ++ test_ivfpq_table_sharing( ++ "IVF16,PQ8x4", "/tmp/ivfpqip", faiss::METRIC_INNER_PRODUCT); ++} ++ ++TEST(TestIVFPQTableSharing, FastScanL2) { ++ test_ivfpq_table_sharing( ++ "IVF16,PQ8x4fsr", "/tmp/ivfpqfsl2", faiss::METRIC_L2); ++} ++ ++TEST(TestIVFPQTableSharing, FastScanIP) { ++ test_ivfpq_table_sharing( ++ "IVF16,PQ8x4fsr", "/tmp/ivfpqfsip", faiss::METRIC_INNER_PRODUCT); ++} +-- +2.37.0 + diff --git a/jni/patches/faiss/0003-Custom-patch-to-support-range-search-params.patch b/jni/patches/faiss/0003-Custom-patch-to-support-range-search-params.patch new file mode 100644 index 000000000..bdc202bf6 --- /dev/null +++ b/jni/patches/faiss/0003-Custom-patch-to-support-range-search-params.patch @@ -0,0 +1,53 @@ +From af6770b505a32b2c4eab2036d2509dec4b137f28 Mon Sep 17 00:00:00 2001 +From: Junqiu Lei +Date: Tue, 23 Apr 2024 17:18:56 -0700 +Subject: [PATCH] Custom patch to support range search params + +Signed-off-by: Junqiu Lei +--- + faiss/IndexIDMap.cpp | 28 ++++++++++++++++++++++++---- + 1 file changed, 24 insertions(+), 4 deletions(-) + +diff --git a/faiss/IndexIDMap.cpp b/faiss/IndexIDMap.cpp +index 3f375e7b..11f3a847 100644 +--- a/faiss/IndexIDMap.cpp ++++ b/faiss/IndexIDMap.cpp +@@ -176,11 +176,31 @@ void IndexIDMapTemplate::range_search( + RangeSearchResult* result, + const SearchParameters* params) const { + if (params) { +- SearchParameters internal_search_parameters; +- IDSelectorTranslated id_selector_translated(id_map, params->sel); +- internal_search_parameters.sel = &id_selector_translated; ++ IDSelectorTranslated this_idtrans(this->id_map, nullptr); ++ ScopedSelChange sel_change; ++ IDGrouperTranslated this_idgrptrans(this->id_map, nullptr); ++ ScopedGrpChange grp_change; ++ ++ if (params->sel) { ++ auto idtrans = dynamic_cast(params->sel); ++ ++ if (!idtrans) { ++ auto params_non_const = const_cast(params); ++ this_idtrans.sel = params->sel; ++ sel_change.set(params_non_const, &this_idtrans); ++ } ++ } ++ ++ if (params->grp) { ++ auto idtrans = dynamic_cast(params->grp); + +- index->range_search(n, x, radius, result, &internal_search_parameters); ++ if (!idtrans) { ++ auto params_non_const = const_cast(params); ++ this_idgrptrans.grp = params->grp; ++ grp_change.set(params_non_const, &this_idgrptrans); ++ } ++ } ++ index->range_search(n, x, radius, result, params); + } else { + index->range_search(n, x, radius, result); + } +-- +2.39.0 + diff --git a/jni/patches/faiss/0004-Custom-patch-to-support-binary-vector.patch b/jni/patches/faiss/0004-Custom-patch-to-support-binary-vector.patch new file mode 100644 index 000000000..e2bac8f1a --- /dev/null +++ b/jni/patches/faiss/0004-Custom-patch-to-support-binary-vector.patch @@ -0,0 +1,294 @@ +From 4d374aa47d4415cbda04b299788988f4ff6e5da0 Mon Sep 17 00:00:00 2001 +From: Heemin Kim +Date: Wed, 10 Jul 2024 16:06:36 -0700 +Subject: [PATCH] 0004-Custom-patch-to-support-binary-vector + +Signed-off-by: Heemin Kim +--- + faiss/IndexBinaryHNSW.cpp | 59 +++++++++++++------ + faiss/IndexBinaryIVF.cpp | 28 +++++++-- + tests/test_id_grouper.cpp | 117 +++++++++++++++++++++++++++++++++++++- + 3 files changed, 179 insertions(+), 25 deletions(-) + +diff --git a/faiss/IndexBinaryHNSW.cpp b/faiss/IndexBinaryHNSW.cpp +index f1bda08f..32627cb0 100644 +--- a/faiss/IndexBinaryHNSW.cpp ++++ b/faiss/IndexBinaryHNSW.cpp +@@ -189,37 +189,62 @@ void IndexBinaryHNSW::train(idx_t n, const uint8_t* x) { + is_trained = true; + } + ++namespace { ++template ++void hnsw_search( ++ const IndexBinaryHNSW* index, ++ idx_t n, ++ const uint8_t* x, ++ BlockResultHandler& bres, ++ const SearchParameters* params_in) { ++ const SearchParametersHNSW* params = nullptr; ++ const HNSW& hnsw = index->hnsw; ++ ++ if (params_in) { ++ params = dynamic_cast(params_in); ++ FAISS_THROW_IF_NOT_MSG(params, "params type invalid"); ++ } ++#pragma omp parallel ++ { ++ VisitedTable vt(index->ntotal); ++ std::unique_ptr dis(index->get_distance_computer()); ++ typename BlockResultHandler::SingleResultHandler res(bres); ++ ++#pragma omp for ++ for (idx_t i = 0; i < n; i++) { ++ res.begin(i); ++ dis->set_query((float*)(x + i * index->code_size)); ++ hnsw.search(*dis, res, vt, params); ++ res.end(); ++ } ++ } ++} ++ ++} // anonymous namespace ++ + void IndexBinaryHNSW::search( + idx_t n, + const uint8_t* x, + idx_t k, + int32_t* distances, + idx_t* labels, +- const SearchParameters* params) const { +- FAISS_THROW_IF_NOT_MSG( +- !params, "search params not supported for this index"); ++ const SearchParameters* params_in) const { + FAISS_THROW_IF_NOT(k > 0); + + // we use the buffer for distances as float but convert them back + // to int in the end + float* distances_f = (float*)distances; + +- using RH = HeapBlockResultHandler; +- RH bres(n, distances_f, labels, k); ++ if (params_in && params_in->grp) { ++ using RH = GroupedHeapBlockResultHandler; ++ RH bres(n, distances_f, labels, k, params_in->grp); + +-#pragma omp parallel +- { +- VisitedTable vt(ntotal); +- std::unique_ptr dis(get_distance_computer()); +- RH::SingleResultHandler res(bres); ++ hnsw_search(this, n, x, bres, params_in); ++ } else { ++ using RH = HeapBlockResultHandler; ++ RH bres(n, distances_f, labels, k); + +-#pragma omp for +- for (idx_t i = 0; i < n; i++) { +- res.begin(i); +- dis->set_query((float*)(x + i * code_size)); +- hnsw.search(*dis, res, vt); +- res.end(); +- } ++ hnsw_search(this, n, x, bres, params_in); + } + + #pragma omp parallel for +diff --git a/faiss/IndexBinaryIVF.cpp b/faiss/IndexBinaryIVF.cpp +index ab1b9fd8..de996df3 100644 +--- a/faiss/IndexBinaryIVF.cpp ++++ b/faiss/IndexBinaryIVF.cpp +@@ -113,25 +113,41 @@ void IndexBinaryIVF::search( + idx_t k, + int32_t* distances, + idx_t* labels, +- const SearchParameters* params) const { +- FAISS_THROW_IF_NOT_MSG( +- !params, "search params not supported for this index"); ++ const SearchParameters* params_in) const { + FAISS_THROW_IF_NOT(k > 0); ++ const IVFSearchParameters* params = nullptr; ++ if (params_in) { ++ params = dynamic_cast(params_in); ++ FAISS_THROW_IF_NOT_MSG(params, "IndexIVF params have incorrect type"); ++ } ++ const size_t nprobe_2 = std::min(nlist, params ? params->nprobe : this->nprobe); + FAISS_THROW_IF_NOT(nprobe > 0); + +- const size_t nprobe_2 = std::min(nlist, this->nprobe); + std::unique_ptr idx(new idx_t[n * nprobe_2]); + std::unique_ptr coarse_dis(new int32_t[n * nprobe_2]); + + double t0 = getmillisecs(); +- quantizer->search(n, x, nprobe_2, coarse_dis.get(), idx.get()); ++ quantizer->search( ++ n, ++ x, ++ nprobe_2, ++ coarse_dis.get(), ++ idx.get(), ++ params ? params->quantizer_params : nullptr); + indexIVF_stats.quantization_time += getmillisecs() - t0; + + t0 = getmillisecs(); + invlists->prefetch_lists(idx.get(), n * nprobe_2); + + search_preassigned( +- n, x, k, idx.get(), coarse_dis.get(), distances, labels, false); ++ n, ++ x, ++ k, ++ idx.get(), ++ coarse_dis.get(), ++ distances, labels, ++ false, ++ params); + indexIVF_stats.search_time += getmillisecs() - t0; + } + +diff --git a/tests/test_id_grouper.cpp b/tests/test_id_grouper.cpp +index 6601795b..bd8ab5f9 100644 +--- a/tests/test_id_grouper.cpp ++++ b/tests/test_id_grouper.cpp +@@ -14,10 +14,10 @@ + #include + #include + #include ++#include "faiss/IndexBinaryHNSW.h" + + // 64-bit int + using idx_t = faiss::idx_t; +- + using namespace faiss; + + TEST(IdGrouper, get_group) { +@@ -172,7 +172,58 @@ TEST(IdGrouper, bitmap_with_hnsw) { + delete[] xb; + } + +-TEST(IdGrouper, bitmap_with_hnswn_idmap) { ++TEST(IdGrouper, bitmap_with_binary_hnsw) { ++ int d = 16; // dimension ++ int nb = 10; // database size ++ ++ std::vector database(nb * (d / 8)); ++ for (size_t i = 0; i < nb * (d / 8); i++) { ++ database[i] = rand() % 0x100; ++ } ++ ++ uint64_t bitmap[1] = {}; ++ faiss::IDGrouperBitmap id_grouper(1, bitmap); ++ for (int i = 0; i < nb; i++) { ++ if (i % 2 == 1) { ++ id_grouper.set_group(i); ++ } ++ } ++ ++ int k = 10; ++ int m = 8; ++ faiss::IndexBinary* index = ++ new faiss::IndexBinaryHNSW(d, m); ++ index->add(nb, database.data()); // add vectors to the index ++ ++ // search ++ idx_t* I = new idx_t[k]; ++ int32_t* D = new int32_t[k]; ++ ++ auto pSearchParameters = new faiss::SearchParametersHNSW(); ++ pSearchParameters->grp = &id_grouper; ++ ++ index->search(1, database.data(), k, D, I, pSearchParameters); ++ ++ std::unordered_set group_ids; ++ ASSERT_EQ(0, I[0]); ++ ASSERT_EQ(0, D[0]); ++ group_ids.insert(id_grouper.get_group(I[0])); ++ for (int j = 1; j < 5; j++) { ++ ASSERT_NE(-1, I[j]); ++ ASSERT_NE(std::numeric_limits::max(), D[j]); ++ group_ids.insert(id_grouper.get_group(I[j])); ++ } ++ for (int j = 5; j < k; j++) { ++ ASSERT_EQ(-1, I[j]); ++ ASSERT_EQ(std::numeric_limits::max(), D[j]); ++ } ++ ASSERT_EQ(5, group_ids.size()); ++ ++ delete[] I; ++ delete[] D; ++} ++ ++TEST(IdGrouper, bitmap_with_hnsw_idmap) { + int d = 1; // dimension + int nb = 10; // database size + +@@ -239,3 +290,65 @@ TEST(IdGrouper, bitmap_with_hnswn_idmap) { + delete[] D; + delete[] xb; + } ++ ++TEST(IdGrouper, bitmap_with_binary_hnsw_idmap) { ++ int d = 16; // dimension ++ int nb = 10; // database size ++ ++ std::vector database(nb * (d / 8)); ++ for (size_t i = 0; i < nb * (d / 8); i++) { ++ database[i] = rand() % 0x100; ++ } ++ ++ idx_t* xids = new idx_t[nb]; ++ uint64_t bitmap[1] = {}; ++ faiss::IDGrouperBitmap id_grouper(1, bitmap); ++ int num_grp = 0; ++ int grp_size = 2; ++ int id_in_grp = 0; ++ for (int i = 0; i < nb; i++) { ++ xids[i] = i + num_grp; ++ id_in_grp++; ++ if (id_in_grp == grp_size) { ++ id_grouper.set_group(i + num_grp + 1); ++ num_grp++; ++ id_in_grp = 0; ++ } ++ } ++ ++ int k = 10; ++ int m = 8; ++ ++ faiss::IndexBinary* index = ++ new faiss::IndexBinaryHNSW(d, m); ++ faiss::IndexBinaryIDMap id_map = ++ faiss::IndexBinaryIDMap(index); // add vectors to the index ++ id_map.add_with_ids(nb, database.data(), xids); ++ ++ // search ++ idx_t* I = new idx_t[k]; ++ int32_t* D = new int32_t[k]; ++ ++ auto pSearchParameters = new faiss::SearchParametersHNSW(); ++ pSearchParameters->grp = &id_grouper; ++ ++ id_map.search(1, database.data(), k, D, I, pSearchParameters); ++ ++ std::unordered_set group_ids; ++ ASSERT_EQ(0, I[0]); ++ ASSERT_EQ(0, D[0]); ++ group_ids.insert(id_grouper.get_group(I[0])); ++ for (int j = 1; j < 5; j++) { ++ ASSERT_NE(-1, I[j]); ++ ASSERT_NE(std::numeric_limits::max(), D[j]); ++ group_ids.insert(id_grouper.get_group(I[j])); ++ } ++ for (int j = 5; j < k; j++) { ++ ASSERT_EQ(-1, I[j]); ++ ASSERT_EQ(std::numeric_limits::max(), D[j]); ++ } ++ ASSERT_EQ(5, group_ids.size()); ++ ++ delete[] I; ++ delete[] D; ++} +\ No newline at end of file +-- +2.39.3 (Apple Git-146) + diff --git a/jni/patches/nmslib/0001-Initialize-maxlevel-during-add-from-enterpoint-level.patch b/jni/patches/nmslib/0001-Initialize-maxlevel-during-add-from-enterpoint-level.patch new file mode 100644 index 000000000..a9d9381f9 --- /dev/null +++ b/jni/patches/nmslib/0001-Initialize-maxlevel-during-add-from-enterpoint-level.patch @@ -0,0 +1,31 @@ +From aa1ca485c0ab8b79dae1fb5c1567149c5f61b533 Mon Sep 17 00:00:00 2001 +From: John Mazanec +Date: Thu, 14 Mar 2024 12:22:06 -0700 +Subject: [PATCH] Initialize maxlevel during add from enterpoint->level + +Signed-off-by: John Mazanec +--- + similarity_search/src/method/hnsw.cc | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/similarity_search/src/method/hnsw.cc b/similarity_search/src/method/hnsw.cc +index 35b372c..e9a725e 100644 +--- a/similarity_search/src/method/hnsw.cc ++++ b/similarity_search/src/method/hnsw.cc +@@ -542,8 +542,12 @@ namespace similarity { + + NewElement->init(curlevel, maxM_, maxM0_); + +- int maxlevelcopy = maxlevel_; ++ // Get the enterpoint at this moment and then use it to set the ++ // max level that is used. Copying maxlevel from this->maxlevel_ ++ // can lead to race conditions during concurrent insertion. See: ++ // https://github.com/nmslib/nmslib/issues/544 + HnswNode *ep = enterpoint_; ++ int maxlevelcopy = ep->level; + if (curlevel < maxlevelcopy) { + const Object *currObj = ep->getData(); + +-- +2.39.3 (Apple Git-146) + diff --git a/jni/patches/nmslib/0002-Adds-ability-to-pass-ef-parameter-in-the-query-for-h.patch b/jni/patches/nmslib/0002-Adds-ability-to-pass-ef-parameter-in-the-query-for-h.patch new file mode 100644 index 000000000..e55e52cb3 --- /dev/null +++ b/jni/patches/nmslib/0002-Adds-ability-to-pass-ef-parameter-in-the-query-for-h.patch @@ -0,0 +1,303 @@ +From d700d93d5efda7349b90f6f0b2373580ced8097d Mon Sep 17 00:00:00 2001 +From: Tejas Shah +Date: Mon, 27 May 2024 22:02:12 -0700 +Subject: [PATCH] Adds ability to pass ef parameter in the query for hnsw + +It defaults to index ef_ value if its not type HNSWQuery +--- + similarity_search/include/hnswquery.h | 37 +++++++++++++++++++ + similarity_search/include/method/hnsw.h | 1 + + similarity_search/include/space.h | 4 ++ + similarity_search/src/hnswquery.cc | 32 ++++++++++++++++ + similarity_search/src/method/hnsw.cc | 28 +++++++++----- + .../src/method/hnsw_distfunc_opt.cc | 12 +++--- + 6 files changed, 100 insertions(+), 14 deletions(-) + create mode 100644 similarity_search/include/hnswquery.h + create mode 100644 similarity_search/src/hnswquery.cc + +diff --git a/similarity_search/include/hnswquery.h b/similarity_search/include/hnswquery.h +new file mode 100644 +index 0000000..a4f65ac +--- /dev/null ++++ b/similarity_search/include/hnswquery.h +@@ -0,0 +1,37 @@ ++/** ++ * Non-metric Space Library ++ * ++ * Main developers: Bilegsaikhan Naidan, Leonid Boytsov, Yury Malkov, Ben Frederickson, David Novak ++ * ++ * For the complete list of contributors and further details see: ++ * https://github.com/nmslib/nmslib ++ * ++ * Copyright (c) 2013-2018 ++ * ++ * This code is released under the ++ * Apache License Version 2.0 http://www.apache.org/licenses/. ++ * ++ */ ++ ++#ifndef HNSWQUERY_H ++#define HNSWQUERY_H ++#include "global.h" ++#include "knnquery.h" ++ ++namespace similarity { ++ ++template ++class HNSWQuery : public KNNQuery { ++public: ++ ~HNSWQuery(); ++ HNSWQuery(const Space& space, const Object *query_object, unsigned K, unsigned ef = 100, float eps = 0); ++ ++ unsigned getEf() { return ef_; } ++ ++protected: ++ unsigned ef_; ++}; ++ ++} ++ ++#endif //HNSWQUERY_H +diff --git a/similarity_search/include/method/hnsw.h b/similarity_search/include/method/hnsw.h +index 57d99d0..e6dcea7 100644 +--- a/similarity_search/include/method/hnsw.h ++++ b/similarity_search/include/method/hnsw.h +@@ -474,6 +474,7 @@ namespace similarity { + void baseSearchAlgorithmV1Merge(KNNQuery *query); + void SearchOld(KNNQuery *query, bool normalize); + void SearchV1Merge(KNNQuery *query, bool normalize); ++ size_t extractEf(KNNQuery *query, size_t defaultEf) const; + + int getRandomLevel(double revSize) + { +diff --git a/similarity_search/include/space.h b/similarity_search/include/space.h +index fedad46..a0e9ea9 100644 +--- a/similarity_search/include/space.h ++++ b/similarity_search/include/space.h +@@ -63,6 +63,9 @@ class Query; + template + class KNNQuery; + ++ template ++class HNSWQuery; ++ + template + class RangeQuery; + +@@ -263,6 +266,7 @@ class Space { + friend class Query; + friend class RangeQuery; + friend class KNNQuery; ++ friend class HNSWQuery; + friend class Experiments; + /* + * This function is private, but it will be accessible by the friend class Query +diff --git a/similarity_search/src/hnswquery.cc b/similarity_search/src/hnswquery.cc +new file mode 100644 +index 0000000..4ee7b38 +--- /dev/null ++++ b/similarity_search/src/hnswquery.cc +@@ -0,0 +1,32 @@ ++/** ++* Non-metric Space Library ++ * ++ * Main developers: Bilegsaikhan Naidan, Leonid Boytsov, Yury Malkov, Ben Frederickson, David Novak ++ * ++ * For the complete list of contributors and further details see: ++ * https://github.com/nmslib/nmslib ++ * ++ * Copyright (c) 2013-2018 ++ * ++ * This code is released under the ++ * Apache License Version 2.0 http://www.apache.org/licenses/. ++ * ++ */ ++ ++#include "hnswquery.h" ++ ++namespace similarity { ++ ++template ++HNSWQuery::HNSWQuery(const Space &space, const Object* query_object, const unsigned K, unsigned ef, float eps) ++ : KNNQuery(space, query_object, K, eps), ++ ef_(ef) { ++} ++ ++template ++HNSWQuery::~HNSWQuery() = default; ++ ++template class HNSWQuery; ++template class HNSWQuery; ++template class HNSWQuery; ++} +diff --git a/similarity_search/src/method/hnsw.cc b/similarity_search/src/method/hnsw.cc +index 35b372c..69ee9e4 100644 +--- a/similarity_search/src/method/hnsw.cc ++++ b/similarity_search/src/method/hnsw.cc +@@ -46,6 +46,7 @@ + #include + #include + ++#include "hnswquery.h" + #include "sort_arr_bi.h" + #define MERGE_BUFFER_ALGO_SWITCH_THRESHOLD 100 + +@@ -101,9 +102,16 @@ namespace similarity { + return nullptr; + } + ++ template ++ size_t Hnsw::extractEf(KNNQuery* searchQuery, size_t defaultEf) const { ++ auto* hnswQueryPtr = dynamic_cast*>(searchQuery); ++ if (hnswQueryPtr) { ++ return hnswQueryPtr->getEf(); ++ } ++ return defaultEf; ++ } + +- +-// This is the counter to keep the size of neighborhood information (for one node) ++ // This is the counter to keep the size of neighborhood information (for one node) + // TODO Can this one overflow? I really doubt + typedef uint32_t SIZEMASS_TYPE; + +@@ -718,10 +726,11 @@ namespace similarity { + void + Hnsw::Search(KNNQuery *query, IdType) const + { ++ size_t ef = this->extractEf(query, ef_); + if (this->data_.empty() && this->data_rearranged_.empty()) { + return; + } +- bool useOld = searchAlgoType_ == kOld || (searchAlgoType_ == kHybrid && ef_ >= 1000); ++ bool useOld = searchAlgoType_ == kOld || (searchAlgoType_ == kHybrid && ef >= 1000); + // cout << "Ef = " << ef_ << " use old = " << useOld << endl; + switch (searchMethod_) { + case 0: +@@ -1148,6 +1157,7 @@ namespace similarity { + PREFETCH((char *)(massVisited + (*iter)->getId()), _MM_HINT_T0); + } + // calculate distance to each neighbor ++ size_t ef = this->extractEf(query, ef_); + for (auto iter = neighbor.begin(); iter != neighbor.end(); ++iter) { + curId = (*iter)->getId(); + +@@ -1155,12 +1165,12 @@ namespace similarity { + massVisited[curId] = currentV; + currObj = (*iter)->getData(); + d = query->DistanceObjLeft(currObj); +- if (closestDistQueue1.top().getDistance() > d || closestDistQueue1.size() < ef_) { ++ if (closestDistQueue1.top().getDistance() > d || closestDistQueue1.size() < ef) { + { + query->CheckAndAddToResult(d, currObj); + candidateQueue.emplace(d, *iter); + closestDistQueue1.emplace(d, *iter); +- if (closestDistQueue1.size() > ef_) { ++ if (closestDistQueue1.size() > ef) { + closestDistQueue1.pop(); + } + } +@@ -1185,6 +1195,7 @@ namespace similarity { + + const Object *currObj = provider->getData(); + ++ size_t ef = this->extractEf(query, ef_); + dist_t d = query->DistanceObjLeft(currObj); + dist_t curdist = d; + HnswNode *curNode = provider; +@@ -1209,7 +1220,7 @@ namespace similarity { + } + } + +- SortArrBI sortedArr(max(ef_, query->GetK())); ++ SortArrBI sortedArr(max(ef, query->GetK())); + sortedArr.push_unsorted_grow(curdist, curNode); + + int_fast32_t currElem = 0; +@@ -1225,8 +1236,7 @@ namespace similarity { + // PHASE TWO OF THE SEARCH + // Extraction of the neighborhood to find k nearest neighbors. + //////////////////////////////////////////////////////////////////////////////// +- +- while (currElem < min(sortedArr.size(), ef_)) { ++ while (currElem < min(sortedArr.size(), ef)) { + auto &e = queueData[currElem]; + CHECK(!e.used); + e.used = true; +@@ -1255,7 +1265,7 @@ namespace similarity { + currObj = (*iter)->getData(); + d = query->DistanceObjLeft(currObj); + +- if (d < topKey || sortedArr.size() < ef_) { ++ if (d < topKey || sortedArr.size() < ef) { + CHECK_MSG(itemBuff.size() > itemQty, + "Perhaps a bug: buffer size is not enough " + + ConvertToString(itemQty) + " >= " + ConvertToString(itemBuff.size())); +diff --git a/similarity_search/src/method/hnsw_distfunc_opt.cc b/similarity_search/src/method/hnsw_distfunc_opt.cc +index 5c219cd..1913936 100644 +--- a/similarity_search/src/method/hnsw_distfunc_opt.cc ++++ b/similarity_search/src/method/hnsw_distfunc_opt.cc +@@ -120,6 +120,7 @@ namespace similarity { + PREFETCH(data_level0_memory_ + (*(data + 1)) * memoryPerObject_ + offsetData_, _MM_HINT_T0); + PREFETCH((char *)(data + 2), _MM_HINT_T0); + ++ size_t ef = this->extractEf(query, ef_); + for (int j = 1; j <= size; j++) { + int tnum = *(data + j); + PREFETCH((char *)(massVisited + *(data + j + 1)), _MM_HINT_T0); +@@ -131,7 +132,7 @@ namespace similarity { + massVisited[tnum] = currentV; + char *currObj1 = (data_level0_memory_ + tnum * memoryPerObject_ + offsetData_); + dist_t d = (fstdistfunc_(pVectq, (float *)(currObj1 + 16), qty, TmpRes)); +- if (closestDistQueuei.top().getDistance() > d || closestDistQueuei.size() < ef_) { ++ if (closestDistQueuei.top().getDistance() > d || closestDistQueuei.size() < ef) { + candidateQueuei.emplace(-d, tnum); + PREFETCH(data_level0_memory_ + candidateQueuei.top().element * memoryPerObject_ + offsetLevel0_, + _MM_HINT_T0); +@@ -139,7 +140,7 @@ namespace similarity { + query->CheckAndAddToResult(d, data_rearranged_[tnum]); + closestDistQueuei.emplace(d, tnum); + +- if (closestDistQueuei.size() > ef_) { ++ if (closestDistQueuei.size() > ef) { + closestDistQueuei.pop(); + } + } +@@ -153,6 +154,7 @@ namespace similarity { + void + Hnsw::SearchV1Merge(KNNQuery *query, bool normalize) + { ++ size_t ef = this->extractEf(query, ef_); + float *pVectq = (float *)((char *)query->QueryObject()->data()); + TMP_RES_ARRAY(TmpRes); + size_t qty = query->QueryObject()->datalength() >> 2; +@@ -197,7 +199,7 @@ namespace similarity { + } + } + +- SortArrBI sortedArr(max(ef_, query->GetK())); ++ SortArrBI sortedArr(max(ef, query->GetK())); + sortedArr.push_unsorted_grow(curdist, curNodeNum); + + int_fast32_t currElem = 0; +@@ -208,7 +210,7 @@ namespace similarity { + + massVisited[curNodeNum] = currentV; + +- while (currElem < min(sortedArr.size(), ef_)) { ++ while (currElem < min(sortedArr.size(), ef)) { + auto &e = queueData[currElem]; + CHECK(!e.used); + e.used = true; +@@ -237,7 +239,7 @@ namespace similarity { + char *currObj1 = (data_level0_memory_ + tnum * memoryPerObject_ + offsetData_); + dist_t d = (fstdistfunc_(pVectq, (float *)(currObj1 + 16), qty, TmpRes)); + +- if (d < topKey || sortedArr.size() < ef_) { ++ if (d < topKey || sortedArr.size() < ef) { + CHECK_MSG(itemBuff.size() > itemQty, + "Perhaps a bug: buffer size is not enough " + + ConvertToString(itemQty) + " >= " + ConvertToString(itemBuff.size())); +-- +2.44.0 + diff --git a/jni/patches/nmslib/0003-Added-streaming-apis-for-vector-index-loading-in-Hnsw.patch b/jni/patches/nmslib/0003-Added-streaming-apis-for-vector-index-loading-in-Hnsw.patch new file mode 100644 index 000000000..55e7a8c81 --- /dev/null +++ b/jni/patches/nmslib/0003-Added-streaming-apis-for-vector-index-loading-in-Hnsw.patch @@ -0,0 +1,221 @@ +From 2e9b7f7117842009e081dd79e8ab8b019122a3de Mon Sep 17 00:00:00 2001 +From: Dooyong Kim +Date: Fri, 11 Oct 2024 16:19:45 -0700 +Subject: [PATCH] Added streaming apis for vector index loading in Hnsw. + +Signed-off-by: Dooyong Kim +--- + similarity_search/include/method/hnsw.h | 3 + + similarity_search/include/utils.h | 12 +++ + similarity_search/src/method/hnsw.cc | 138 +++++++++++++++++++++++- + 3 files changed, 152 insertions(+), 1 deletion(-) + +diff --git a/similarity_search/include/method/hnsw.h b/similarity_search/include/method/hnsw.h +index e6dcea7..433f98f 100644 +--- a/similarity_search/include/method/hnsw.h ++++ b/similarity_search/include/method/hnsw.h +@@ -457,6 +457,8 @@ namespace similarity { + + virtual void LoadIndex(const string &location) override; + ++ void LoadIndexWithStream(similarity::NmslibIOReader& in); ++ + Hnsw(bool PrintProgress, const Space &space, const ObjectVector &data); + void CreateIndex(const AnyParams &IndexParams) override; + +@@ -500,6 +502,7 @@ namespace similarity { + + void SaveOptimizedIndex(std::ostream& output); + void LoadOptimizedIndex(std::istream& input); ++ void LoadOptimizedIndex(NmslibIOReader& input); + + void SaveRegularIndexBin(std::ostream& output); + void LoadRegularIndexBin(std::istream& input); +diff --git a/similarity_search/include/utils.h b/similarity_search/include/utils.h +index b521c26..a3931b7 100644 +--- a/similarity_search/include/utils.h ++++ b/similarity_search/include/utils.h +@@ -299,12 +299,24 @@ inline void WriteField(ostream& out, const string& fieldName, const FieldType& f + } + } + ++struct NmslibIOReader { ++ virtual ~NmslibIOReader() = default; ++ ++ virtual void read(char* bytes, size_t len) = 0; ++ ++ virtual size_t remainingBytes() = 0; ++}; + + template + void writeBinaryPOD(ostream& out, const T& podRef) { + out.write((char*)&podRef, sizeof(T)); + } + ++template ++static void readBinaryPOD(NmslibIOReader& in, T& podRef) { ++ in.read((char*)&podRef, sizeof(T)); ++} ++ + template + static void readBinaryPOD(istream& in, T& podRef) { + in.read((char*)&podRef, sizeof(T)); +diff --git a/similarity_search/src/method/hnsw.cc b/similarity_search/src/method/hnsw.cc +index 4080b3b..662f06c 100644 +--- a/similarity_search/src/method/hnsw.cc ++++ b/similarity_search/src/method/hnsw.cc +@@ -950,7 +950,6 @@ namespace similarity { + " read so far doesn't match the number of read lines: " + ConvertToString(lineNum)); + } + +- + template + void + Hnsw::LoadRegularIndexBin(std::istream& input) { +@@ -1034,6 +1033,143 @@ namespace similarity { + + } + ++ constexpr bool _isLittleEndian() { ++ return (((uint32_t) 1) & 0xFFU) == 1; ++ } ++ ++ SIZEMASS_TYPE _readIntBigEndian(uint8_t byte0, uint8_t byte1, uint8_t byte2, uint8_t byte3) noexcept { ++ return (static_cast(byte0) << 24) | ++ (static_cast(byte1) << 16) | ++ (static_cast(byte2) << 8) | ++ static_cast(byte3); ++ } ++ ++ SIZEMASS_TYPE _readIntLittleEndian(uint8_t byte0, uint8_t byte1, uint8_t byte2, uint8_t byte3) noexcept { ++ return (static_cast(byte3) << 24) | ++ (static_cast(byte2) << 16) | ++ (static_cast(byte1) << 8) | ++ static_cast(byte0); ++ } ++ ++ template ++ void Hnsw::LoadIndexWithStream(NmslibIOReader& input) { ++ LOG(LIB_INFO) << "Loading index from an input stream(NmslibIOReader)."; ++ ++ unsigned int optimIndexFlag= 0; ++ readBinaryPOD(input, optimIndexFlag); ++ ++ if (!optimIndexFlag) { ++ throw std::runtime_error("With stream, we only support optimized index type."); ++ } else { ++ LoadOptimizedIndex(input); ++ } ++ ++ LOG(LIB_INFO) << "Finished loading index"; ++ visitedlistpool = new VisitedListPool(1, totalElementsStored_); ++ } ++ ++ template ++ void Hnsw::LoadOptimizedIndex(NmslibIOReader& input) { ++ static_assert(sizeof(SIZEMASS_TYPE) == 4, "Expected sizeof(SIZEMASS_TYPE) == 4."); ++ ++ LOG(LIB_INFO) << "Loading optimized index(NmslibIOReader)."; ++ ++ readBinaryPOD(input, totalElementsStored_); ++ readBinaryPOD(input, memoryPerObject_); ++ readBinaryPOD(input, offsetLevel0_); ++ readBinaryPOD(input, offsetData_); ++ readBinaryPOD(input, maxlevel_); ++ readBinaryPOD(input, enterpointId_); ++ readBinaryPOD(input, maxM_); ++ readBinaryPOD(input, maxM0_); ++ readBinaryPOD(input, dist_func_type_); ++ readBinaryPOD(input, searchMethod_); ++ ++ LOG(LIB_INFO) << "searchMethod: " << searchMethod_; ++ ++ fstdistfunc_ = getDistFunc(dist_func_type_); ++ iscosine_ = (dist_func_type_ == kNormCosine); ++ CHECK_MSG(fstdistfunc_ != nullptr, "Unknown distance function code: " + ConvertToString(dist_func_type_)); ++ ++ LOG(LIB_INFO) << "Total: " << totalElementsStored_ << ", Memory per object: " << memoryPerObject_; ++ size_t data_plus_links0_size = memoryPerObject_ * totalElementsStored_; ++ ++ // we allocate a few extra bytes to prevent prefetch from accessing out of range memory ++ data_level0_memory_ = (char *)malloc(data_plus_links0_size + EXTRA_MEM_PAD_SIZE); ++ CHECK(data_level0_memory_); ++ input.read(data_level0_memory_, data_plus_links0_size); ++ // we allocate a few extra bytes to prevent prefetch from accessing out of range memory ++ linkLists_ = (char **)malloc( (sizeof(void *) * totalElementsStored_) + EXTRA_MEM_PAD_SIZE); ++ CHECK(linkLists_); ++ ++ data_rearranged_.resize(totalElementsStored_); ++ ++ const size_t bufferSize = 64 * 1024; // 64KB ++ std::unique_ptr buffer (new char[bufferSize]); ++ uint32_t end = 0; ++ uint32_t pos = 0; ++ constexpr bool isLittleEndian = _isLittleEndian(); ++ ++ for (size_t i = 0, remainingBytes = input.remainingBytes(); i < totalElementsStored_; i++) { ++ if ((pos + sizeof(SIZEMASS_TYPE)) >= end) { ++ // Underflow during reading an integer size field. ++ // So the idea is to move the first partial bytes (which is < 4 bytes) to the beginning section of ++ // buffer. ++ // Ex: buffer -> [..., b0, b1] where we only have two bytes and still need to read two bytes more ++ // buffer -> [b0, b1, ...] after move the first part. firstPartLen = 2. ++ const auto firstPartLen = end - pos; ++ if (firstPartLen > 0) { ++ std::memcpy(buffer.get(), buffer.get() + pos, firstPartLen); ++ } ++ // Then, bulk load bytes from input stream. Note that the first few bytes are already occupied by ++ // earlier moving logic, hence required bytes are bufferSize - firstPartLen. ++ const auto copyBytes = std::min(remainingBytes, bufferSize - firstPartLen); ++ input.read(buffer.get() + firstPartLen, copyBytes); ++ remainingBytes -= copyBytes; ++ end = copyBytes + firstPartLen; ++ pos = 0; ++ } ++ ++ // Read data size field. ++ // Since NMSLIB directly write 4 bytes integer casting to char*, bytes outline may differ among systems. ++ SIZEMASS_TYPE linkListSize = 0; ++ if (isLittleEndian) { ++ linkListSize = _readIntLittleEndian(buffer[pos], buffer[pos + 1], buffer[pos + 2], buffer[pos + 3]); ++ } else { ++ linkListSize = _readIntBigEndian(buffer[pos], buffer[pos + 1], buffer[pos + 2], buffer[pos + 3]); ++ } ++ pos += sizeof(SIZEMASS_TYPE); ++ ++ if (linkListSize == 0) { ++ linkLists_[i] = nullptr; ++ } else { ++ linkLists_[i] = (char *)malloc(linkListSize); ++ CHECK(linkLists_[i]); ++ ++ SIZEMASS_TYPE leftLinkListData = linkListSize; ++ auto dataPtr = linkLists_[i]; ++ while (leftLinkListData > 0) { ++ if (pos >= end) { ++ // Underflow during read linked list bytes. ++ const auto copyBytes = std::min(remainingBytes, bufferSize); ++ input.read(buffer.get(), copyBytes); ++ remainingBytes -= copyBytes; ++ end = copyBytes; ++ pos = 0; ++ } ++ ++ // Read linked list bytes. ++ const auto copyBytes = std::min(leftLinkListData, end - pos); ++ std::memcpy(dataPtr, buffer.get() + pos, copyBytes); ++ dataPtr += copyBytes; ++ leftLinkListData -= copyBytes; ++ pos += copyBytes; ++ } // End while ++ } // End if ++ ++ data_rearranged_[i] = new Object(data_level0_memory_ + (i)*memoryPerObject_ + offsetData_); ++ } // End for ++ } + + template + void +-- +2.39.5 (Apple Git-154) + diff --git a/jni/patches/nmslib/0004-Added-a-new-save-apis-in-Hnsw-with-streaming-interfa.patch b/jni/patches/nmslib/0004-Added-a-new-save-apis-in-Hnsw-with-streaming-interfa.patch new file mode 100644 index 000000000..0d324bc29 --- /dev/null +++ b/jni/patches/nmslib/0004-Added-a-new-save-apis-in-Hnsw-with-streaming-interfa.patch @@ -0,0 +1,165 @@ +From eb9ceb60c695f795895b80ea66b251aea1fbf781 Mon Sep 17 00:00:00 2001 +From: Dooyong Kim +Date: Wed, 30 Oct 2024 15:44:34 -0700 +Subject: [PATCH] Added a new IOWriter interface in Hnsw node to support + streaming writing. + +Signed-off-by: Dooyong Kim +--- + similarity_search/include/method/hnsw.h | 3 ++ + similarity_search/include/utils.h | 20 ++++++++-- + similarity_search/src/method/hnsw.cc | 52 +++++++++++++++++++++++-- + 3 files changed, 68 insertions(+), 7 deletions(-) + +diff --git a/similarity_search/include/method/hnsw.h b/similarity_search/include/method/hnsw.h +index 433f98f..d235c15 100644 +--- a/similarity_search/include/method/hnsw.h ++++ b/similarity_search/include/method/hnsw.h +@@ -459,6 +459,8 @@ namespace similarity { + + void LoadIndexWithStream(similarity::NmslibIOReader& in); + ++ void SaveIndexWithStream(similarity::NmslibIOWriter& out); ++ + Hnsw(bool PrintProgress, const Space &space, const ObjectVector &data); + void CreateIndex(const AnyParams &IndexParams) override; + +@@ -501,6 +503,7 @@ namespace similarity { + + + void SaveOptimizedIndex(std::ostream& output); ++ void SaveOptimizedIndex(NmslibIOWriter& output); + void LoadOptimizedIndex(std::istream& input); + void LoadOptimizedIndex(NmslibIOReader& input); + +diff --git a/similarity_search/include/utils.h b/similarity_search/include/utils.h +index a3931b7..1cc55b2 100644 +--- a/similarity_search/include/utils.h ++++ b/similarity_search/include/utils.h +@@ -305,19 +305,33 @@ struct NmslibIOReader { + virtual void read(char* bytes, size_t len) = 0; + + virtual size_t remainingBytes() = 0; +-}; ++}; // class NmslibIOReader ++ ++struct NmslibIOWriter { ++ virtual ~NmslibIOWriter() = default; ++ ++ virtual void write(char* bytes, size_t len) = 0; ++ ++ virtual void flush() { ++ } ++}; // class NmslibIOWriter + + template + void writeBinaryPOD(ostream& out, const T& podRef) { + out.write((char*)&podRef, sizeof(T)); + } + +-template ++template ++void writeBinaryPOD(NmslibIOWriter& out, const T& podRef) { ++ out.write((char*)&podRef, sizeof(T)); ++} ++ ++template + static void readBinaryPOD(NmslibIOReader& in, T& podRef) { + in.read((char*)&podRef, sizeof(T)); + } + +-template ++template + static void readBinaryPOD(istream& in, T& podRef) { + in.read((char*)&podRef, sizeof(T)); + } +diff --git a/similarity_search/src/method/hnsw.cc b/similarity_search/src/method/hnsw.cc +index 662f06c..bfa3a23 100644 +--- a/similarity_search/src/method/hnsw.cc ++++ b/similarity_search/src/method/hnsw.cc +@@ -784,6 +784,19 @@ namespace similarity { + output.close(); + } + ++ template ++ void Hnsw::SaveIndexWithStream(NmslibIOWriter& output) { ++ unsigned int optimIndexFlag = data_level0_memory_ != nullptr; ++ ++ writeBinaryPOD(output, optimIndexFlag); ++ ++ if (!optimIndexFlag) { ++ throw std::runtime_error("With stream, we only support optimized index type."); ++ } else { ++ SaveOptimizedIndex(output); ++ } ++ } ++ + template + void + Hnsw::SaveOptimizedIndex(std::ostream& output) { +@@ -818,6 +831,37 @@ namespace similarity { + + } + ++ template ++ void ++ Hnsw::SaveOptimizedIndex(NmslibIOWriter& output) { ++ totalElementsStored_ = ElList_.size(); ++ ++ writeBinaryPOD(output, totalElementsStored_); ++ writeBinaryPOD(output, memoryPerObject_); ++ writeBinaryPOD(output, offsetLevel0_); ++ writeBinaryPOD(output, offsetData_); ++ writeBinaryPOD(output, maxlevel_); ++ writeBinaryPOD(output, enterpointId_); ++ writeBinaryPOD(output, maxM_); ++ writeBinaryPOD(output, maxM0_); ++ writeBinaryPOD(output, dist_func_type_); ++ writeBinaryPOD(output, searchMethod_); ++ ++ const size_t data_plus_links0_size = memoryPerObject_ * totalElementsStored_; ++ LOG(LIB_INFO) << "writing " << data_plus_links0_size << " bytes"; ++ output.write(data_level0_memory_, data_plus_links0_size); ++ ++ for (size_t i = 0; i < totalElementsStored_; i++) { ++ // TODO Can this one overflow? I really doubt ++ const SIZEMASS_TYPE sizemass = ((ElList_[i]->level) * (maxM_ + 1)) * sizeof(int); ++ writeBinaryPOD(output, sizemass); ++ if (sizemass) { ++ output.write(linkLists_[i], sizemass); ++ } ++ } ++ output.flush(); ++ } ++ + template + void + Hnsw::SaveRegularIndexBin(std::ostream& output) { +@@ -1036,20 +1080,20 @@ namespace similarity { + constexpr bool _isLittleEndian() { + return (((uint32_t) 1) & 0xFFU) == 1; + } +- ++ + SIZEMASS_TYPE _readIntBigEndian(uint8_t byte0, uint8_t byte1, uint8_t byte2, uint8_t byte3) noexcept { + return (static_cast(byte0) << 24) | + (static_cast(byte1) << 16) | + (static_cast(byte2) << 8) | + static_cast(byte3); +- } +- ++ } ++ + SIZEMASS_TYPE _readIntLittleEndian(uint8_t byte0, uint8_t byte1, uint8_t byte2, uint8_t byte3) noexcept { + return (static_cast(byte3) << 24) | + (static_cast(byte2) << 16) | + (static_cast(byte1) << 8) | + static_cast(byte0); +- } ++ } + + template + void Hnsw::LoadIndexWithStream(NmslibIOReader& input) { +-- +2.39.5 (Apple Git-154) + diff --git a/jni/src/commons.cpp b/jni/src/commons.cpp new file mode 100644 index 000000000..444dc18b0 --- /dev/null +++ b/jni/src/commons.cpp @@ -0,0 +1,109 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ +#include + +#include + +#include "jni_util.h" +#include "commons.h" + +jlong knn_jni::commons::storeVectorData(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jlong memoryAddressJ, + jobjectArray dataJ, jlong initialCapacityJ, jboolean appendJ) { + std::vector *vect; + if ((long) memoryAddressJ == 0) { + vect = new std::vector(); + vect->reserve((long)initialCapacityJ); + } else { + vect = reinterpret_cast*>(memoryAddressJ); + } + + if (appendJ == JNI_FALSE) { + vect->clear(); + } + + int dim = jniUtil->GetInnerDimensionOf2dJavaFloatArray(env, dataJ); + jniUtil->Convert2dJavaObjectArrayAndStoreToFloatVector(env, dataJ, dim, vect); + + return (jlong) vect; +} + +jlong knn_jni::commons::storeBinaryVectorData(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jlong memoryAddressJ, + jobjectArray dataJ, jlong initialCapacityJ, jboolean appendJ) { + std::vector *vect; + if ((long) memoryAddressJ == 0) { + vect = new std::vector(); + vect->reserve((long)initialCapacityJ); + } else { + vect = reinterpret_cast*>(memoryAddressJ); + } + + if (appendJ == JNI_FALSE) { + vect->clear(); + } + + int dim = jniUtil->GetInnerDimensionOf2dJavaByteArray(env, dataJ); + jniUtil->Convert2dJavaObjectArrayAndStoreToBinaryVector(env, dataJ, dim, vect); + + return (jlong) vect; +} + +jlong knn_jni::commons::storeByteVectorData(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jlong memoryAddressJ, + jobjectArray dataJ, jlong initialCapacityJ, jboolean appendJ) { + std::vector *vect; + if (memoryAddressJ == 0) { + vect = new std::vector(); + vect->reserve(static_cast(initialCapacityJ)); + } else { + vect = reinterpret_cast*>(memoryAddressJ); + } + + if (appendJ == JNI_FALSE) { + vect->clear(); + } + + int dim = jniUtil->GetInnerDimensionOf2dJavaByteArray(env, dataJ); + jniUtil->Convert2dJavaObjectArrayAndStoreToByteVector(env, dataJ, dim, vect); + + return (jlong) vect; +} + +void knn_jni::commons::freeVectorData(jlong memoryAddressJ) { + if (memoryAddressJ != 0) { + auto *vect = reinterpret_cast*>(memoryAddressJ); + delete vect; + } +} + +void knn_jni::commons::freeBinaryVectorData(jlong memoryAddressJ) { + if (memoryAddressJ != 0) { + auto *vect = reinterpret_cast*>(memoryAddressJ); + delete vect; + } +} + +void knn_jni::commons::freeByteVectorData(jlong memoryAddressJ) { + if (memoryAddressJ != 0) { + auto *vect = reinterpret_cast*>(memoryAddressJ); + delete vect; + } +} + +int knn_jni::commons::getIntegerMethodParameter(JNIEnv * env, knn_jni::JNIUtilInterface * jniUtil, std::unordered_map methodParams, std::string methodParam, int defaultValue) { + if (methodParams.empty()) { + return defaultValue; + } + auto efSearchIt = methodParams.find(methodParam); + if (efSearchIt != methodParams.end()) { + return jniUtil->ConvertJavaObjectToCppInteger(env, methodParams[methodParam]); + } + + return defaultValue; +} diff --git a/jni/src/faiss_index_service.cpp b/jni/src/faiss_index_service.cpp new file mode 100644 index 000000000..4999e3172 --- /dev/null +++ b/jni/src/faiss_index_service.cpp @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// The OpenSearch Contributors require contributions made to +// this file be licensed under the Apache-2.0 license or a +// compatible open source license. +// +// Modifications Copyright OpenSearch Contributors. See +// GitHub history for details. + +#include "faiss_index_service.h" +#include "faiss_methods.h" +#include "faiss/Index.h" +#include "faiss/IndexBinary.h" +#include "faiss/IndexHNSW.h" +#include "faiss/IndexBinaryHNSW.h" +#include "faiss/IndexIVFFlat.h" +#include "faiss/IndexBinaryIVF.h" +#include "faiss/IndexIDMap.h" + +#include +#include +#include +#include + +namespace knn_jni { +namespace faiss_wrapper { + +template +void SetExtraParameters(knn_jni::JNIUtilInterface * jniUtil, JNIEnv *env, + const std::unordered_map& parametersCpp, INDEX * index) { + std::unordered_map::const_iterator value; + if (auto * indexIvf = dynamic_cast(index)) { + if ((value = parametersCpp.find(knn_jni::NPROBES)) != parametersCpp.end()) { + indexIvf->nprobe = jniUtil->ConvertJavaObjectToCppInteger(env, value->second); + } + + if ((value = parametersCpp.find(knn_jni::COARSE_QUANTIZER)) != parametersCpp.end() + && indexIvf->quantizer != nullptr) { + auto subParametersCpp = jniUtil->ConvertJavaMapToCppMap(env, value->second); + SetExtraParameters(jniUtil, env, subParametersCpp, indexIvf->quantizer); + } + } + + if (auto * indexHnsw = dynamic_cast(index)) { + + if ((value = parametersCpp.find(knn_jni::EF_CONSTRUCTION)) != parametersCpp.end()) { + indexHnsw->hnsw.efConstruction = jniUtil->ConvertJavaObjectToCppInteger(env, value->second); + } + + if ((value = parametersCpp.find(knn_jni::EF_SEARCH)) != parametersCpp.end()) { + indexHnsw->hnsw.efSearch = jniUtil->ConvertJavaObjectToCppInteger(env, value->second); + } + } +} + +IndexService::IndexService(std::unique_ptr _faissMethods) : faissMethods(std::move(_faissMethods)) {} + +void IndexService::allocIndex(faiss::Index * index, size_t dim, size_t numVectors) { + if (auto * indexHNSWSQ = dynamic_cast(index)) { + if (auto * indexScalarQuantizer = dynamic_cast(indexHNSWSQ->storage)) { + indexScalarQuantizer->codes.reserve(indexScalarQuantizer->code_size * numVectors); + } + } + if (auto * indexHNSW = dynamic_cast(index)) { + if(auto * indexFlat = dynamic_cast(indexHNSW->storage)) { + indexFlat->codes.reserve(indexFlat->code_size * numVectors); + } + } +} + +jlong IndexService::initIndex( + knn_jni::JNIUtilInterface * jniUtil, + JNIEnv * env, + faiss::MetricType metric, + std::string indexDescription, + int dim, + int numVectors, + int threadCount, + std::unordered_map parameters + ) { + // Create index using Faiss factory method + std::unique_ptr index(faissMethods->indexFactory(dim, indexDescription.c_str(), metric)); + + // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread + if (threadCount != 0) { + omp_set_num_threads(threadCount); + } + + // Add extra parameters that cant be configured with the index factory + SetExtraParameters(jniUtil, env, parameters, index.get()); + + // Check that the index does not need to be trained + if (!index->is_trained) { + throw std::runtime_error("Index is not trained"); + } + + std::unique_ptr idMap (faissMethods->indexIdMap(index.get())); + //Makes sure the index is deleted when the destructor is called, this cannot be passed in the constructor + idMap->own_fields = true; + + allocIndex(dynamic_cast(idMap->index), dim, numVectors); + + //Release the ownership so as to make sure not delete the underlying index that is created. The index is needed later + //in insert and write operations + index.release(); + return reinterpret_cast(idMap.release()); +} + +void IndexService::insertToIndex( + int dim, + int numIds, + int threadCount, + int64_t vectorsAddress, + std::vector & ids, + jlong idMapAddress + ) { + // Read vectors from memory address + std::vector * inputVectors = reinterpret_cast*>(vectorsAddress); + + // The number of vectors can be int here because a lucene segment number of total docs never crosses INT_MAX value + int numVectors = (int) (inputVectors->size() / (uint64_t) dim); + if (numVectors == 0) { + throw std::runtime_error("Number of vectors cannot be 0"); + } + + if (numIds != numVectors) { + throw std::runtime_error("Number of IDs does not match number of vectors"); + } + + // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread + if (threadCount != 0) { + omp_set_num_threads(threadCount); + } + + faiss::IndexIDMap * idMap = reinterpret_cast (idMapAddress); + + // Add vectors + idMap->add_with_ids(numVectors, inputVectors->data(), ids.data()); +} + +void IndexService::writeIndex( + faiss::IOWriter* writer, + jlong idMapAddress +) { + std::unique_ptr idMap (reinterpret_cast (idMapAddress)); + + try { + // Write the index to disk + faissMethods->writeIndex(idMap.get(), writer); + if (auto openSearchIOWriter = dynamic_cast(writer)) { + openSearchIOWriter->flush(); + } + } catch(std::exception &e) { + throw std::runtime_error("Failed to write index to disk"); + } +} + +BinaryIndexService::BinaryIndexService(std::unique_ptr _faissMethods) + : IndexService(std::move(_faissMethods)) { +} + +void BinaryIndexService::allocIndex(faiss::Index * index, size_t dim, size_t numVectors) { + if (auto * indexBinaryHNSW = dynamic_cast(index)) { + auto * indexBinaryFlat = dynamic_cast(indexBinaryHNSW->storage); + indexBinaryFlat->xb.reserve(dim * numVectors / 8); + } +} + +jlong BinaryIndexService::initIndex( + knn_jni::JNIUtilInterface * jniUtil, + JNIEnv * env, + faiss::MetricType metric, + std::string indexDescription, + int dim, + int numVectors, + int threadCount, + std::unordered_map parameters + ) { + // Create index using Faiss factory method + std::unique_ptr index(faissMethods->indexBinaryFactory(dim, indexDescription.c_str())); + // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread + if (threadCount != 0) { + omp_set_num_threads(threadCount); + } + + // Add extra parameters that cant be configured with the index factory + SetExtraParameters(jniUtil, env, parameters, index.get()); + + // Check that the index does not need to be trained + if (!index->is_trained) { + throw std::runtime_error("Index is not trained"); + } + + std::unique_ptr idMap(faissMethods->indexBinaryIdMap(index.get())); + //Makes sure the index is deleted when the destructor is called + idMap->own_fields = true; + + allocIndex(dynamic_cast(idMap->index), dim, numVectors); + + //Release the ownership so as to make sure not delete the underlying index that is created. The index is needed later + //in insert and write operations + index.release(); + return reinterpret_cast(idMap.release()); +} + +void BinaryIndexService::insertToIndex( + int dim, + int numIds, + int threadCount, + int64_t vectorsAddress, + std::vector & ids, + jlong idMapAddress + ) { + // Read vectors from memory address (unique ptr since we want to remove from memory after use) + std::vector * inputVectors = reinterpret_cast*>(vectorsAddress); + + // The number of vectors can be int here because a lucene segment number of total docs never crosses INT_MAX value + int numVectors = (int) (inputVectors->size() / (uint64_t) (dim / 8)); + if (numVectors == 0) { + throw std::runtime_error("Number of vectors cannot be 0"); + } + + if (numIds != numVectors) { + throw std::runtime_error("Number of IDs does not match number of vectors"); + } + + // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread + if (threadCount != 0) { + omp_set_num_threads(threadCount); + } + + faiss::IndexBinaryIDMap * idMap = reinterpret_cast (idMapAddress); + + // Add vectors + idMap->add_with_ids(numVectors, inputVectors->data(), ids.data()); +} + +void BinaryIndexService::writeIndex( + faiss::IOWriter* writer, + jlong idMapAddress +) { + std::unique_ptr idMap (reinterpret_cast (idMapAddress)); + + try { + // Write the index to disk + faissMethods->writeIndexBinary(idMap.get(), writer); + if (auto openSearchIOWriter = dynamic_cast(writer)) { + openSearchIOWriter->flush(); + } + } catch(std::exception &e) { + throw std::runtime_error("Failed to write index to disk"); + } +} + +ByteIndexService::ByteIndexService(std::unique_ptr _faissMethods) + : IndexService(std::move(_faissMethods)) { +} + +void ByteIndexService::allocIndex(faiss::Index * index, size_t dim, size_t numVectors) { + if (auto * indexHNSWSQ = dynamic_cast(index)) { + if(auto * indexScalarQuantizer = dynamic_cast(indexHNSWSQ->storage)) { + indexScalarQuantizer->codes.reserve(indexScalarQuantizer->code_size * numVectors); + } + } +} + +jlong ByteIndexService::initIndex( + knn_jni::JNIUtilInterface * jniUtil, + JNIEnv * env, + faiss::MetricType metric, + std::string indexDescription, + int dim, + int numVectors, + int threadCount, + std::unordered_map parameters + ) { + // Create index using Faiss factory method + std::unique_ptr index(faissMethods->indexFactory(dim, indexDescription.c_str(), metric)); + + // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread + if (threadCount != 0) { + omp_set_num_threads(threadCount); + } + + // Add extra parameters that cant be configured with the index factory + SetExtraParameters(jniUtil, env, parameters, index.get()); + + // Check that the index does not need to be trained + if(!index->is_trained) { + throw std::runtime_error("Index is not trained"); + } + + std::unique_ptr idMap (faissMethods->indexIdMap(index.get())); + //Makes sure the index is deleted when the destructor is called, this cannot be passed in the constructor + idMap->own_fields = true; + + allocIndex(dynamic_cast(idMap->index), dim, numVectors); + + //Release the ownership so as to make sure not delete the underlying index that is created. The index is needed later + //in insert and write operations + index.release(); + return reinterpret_cast(idMap.release()); +} + +void ByteIndexService::insertToIndex( + int dim, + int numIds, + int threadCount, + int64_t vectorsAddress, + std::vector & ids, + jlong idMapAddress + ) { + // Read vectors from memory address + auto *inputVectors = reinterpret_cast*>(vectorsAddress); + + // The number of vectors can be int here because a lucene segment number of total docs never crosses INT_MAX value + int numVectors = inputVectors->size() / dim; + if (numVectors == 0) { + throw std::runtime_error("Number of vectors cannot be 0"); + } + + if (numIds != numVectors) { + throw std::runtime_error("Number of IDs does not match number of vectors"); + } + + // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread + if (threadCount != 0) { + omp_set_num_threads(threadCount); + } + + faiss::IndexIDMap * idMap = reinterpret_cast (idMapAddress); + + // Add vectors in batches by casting int8 vectors into float with a batch size of 1000 to avoid additional memory spike. + // Refer to this github issue for more details https://github.com/opensearch-project/k-NN/issues/1659#issuecomment-2307390255 + int batchSize = 1000; + std::vector inputFloatVectors(batchSize * dim); + std::vector floatVectorsIds(batchSize); + auto iter = inputVectors->begin(); + + for (int id = 0; id < numVectors; id += batchSize) { + if (numVectors - id < batchSize) { + batchSize = numVectors - id; + } + + for (int i = 0; i < batchSize; ++i) { + floatVectorsIds[i] = ids[id + i]; + for (int j = 0; j < dim; ++j, ++iter) { + inputFloatVectors[i * dim + j] = static_cast(*iter); + } + } + idMap->add_with_ids(batchSize, inputFloatVectors.data(), floatVectorsIds.data()); + } +} + +void ByteIndexService::writeIndex( + faiss::IOWriter* writer, + jlong idMapAddress +) { + std::unique_ptr idMap (reinterpret_cast (idMapAddress)); + + try { + // Write the index to disk + faissMethods->writeIndex(idMap.get(), writer); + if (auto openSearchIOWriter = dynamic_cast(writer)) { + openSearchIOWriter->flush(); + } + } catch(std::exception &e) { + throw std::runtime_error("Failed to write index to disk"); + } +} +} // namespace faiss_wrapper +} // namesapce knn_jni diff --git a/jni/src/faiss_methods.cpp b/jni/src/faiss_methods.cpp new file mode 100644 index 000000000..dc44c0df9 --- /dev/null +++ b/jni/src/faiss_methods.cpp @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// The OpenSearch Contributors require contributions made to +// this file be licensed under the Apache-2.0 license or a +// compatible open source license. +// +// Modifications Copyright OpenSearch Contributors. See +// GitHub history for details. + +#include "faiss_methods.h" +#include "faiss/index_factory.h" + +namespace knn_jni { +namespace faiss_wrapper { + +faiss::Index* FaissMethods::indexFactory(int d, const char* description, faiss::MetricType metric) { + return faiss::index_factory(d, description, metric); +} + +faiss::IndexBinary* FaissMethods::indexBinaryFactory(int d, const char* description) { + return faiss::index_binary_factory(d, description); +} + +faiss::IndexIDMapTemplate* FaissMethods::indexIdMap(faiss::Index* index) { + return new faiss::IndexIDMap(index); +} + +faiss::IndexIDMapTemplate* FaissMethods::indexBinaryIdMap(faiss::IndexBinary* index) { + return new faiss::IndexBinaryIDMap(index); +} + +void FaissMethods::writeIndex(const faiss::Index* idx, faiss::IOWriter* writer) { + faiss::write_index(idx, writer); +} + +void FaissMethods::writeIndexBinary(const faiss::IndexBinary* idx, faiss::IOWriter* writer) { + faiss::write_index_binary(idx, writer); +} + +} // namespace faiss_wrapper +} // namesapce knn_jni diff --git a/jni/src/faiss_util.cpp b/jni/src/faiss_util.cpp new file mode 100644 index 000000000..c2abe7f26 --- /dev/null +++ b/jni/src/faiss_util.cpp @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// The OpenSearch Contributors require contributions made to +// this file be licensed under the Apache-2.0 license or a +// compatible open source license. +// +// Modifications Copyright OpenSearch Contributors. See +// GitHub history for details. + +#include "faiss_util.h" +#include + +std::unique_ptr faiss_util::buildIDGrouperBitmap(int *parentIdsArray, int parentIdsLength, std::vector* bitmap) { + const int* maxValue = std::max_element(parentIdsArray, parentIdsArray + parentIdsLength); + int num_bits = *maxValue + 1; + int num_blocks = (num_bits >> 6) + 1; // div by 64 + bitmap->resize(num_blocks, 0); + std::unique_ptr idGrouper(new faiss::IDGrouperBitmap(num_blocks, bitmap->data())); + for (int i = 0; i < parentIdsLength; i++) { + idGrouper->set_group(parentIdsArray[i]); + } + return idGrouper; +} diff --git a/jni/src/faiss_wrapper.cpp b/jni/src/faiss_wrapper.cpp index 2e0a4e7cb..98c40cf6b 100644 --- a/jni/src/faiss_wrapper.cpp +++ b/jni/src/faiss_wrapper.cpp @@ -11,19 +11,61 @@ #include "jni_util.h" #include "faiss_wrapper.h" +#include "faiss_util.h" +#include "faiss_index_service.h" +#include "faiss_stream_support.h" #include "faiss/impl/io.h" #include "faiss/index_factory.h" #include "faiss/index_io.h" #include "faiss/IndexHNSW.h" #include "faiss/IndexIVFFlat.h" -#include "faiss/MetaIndexes.h" +#include "faiss/Index.h" +#include "faiss/impl/IDSelector.h" +#include "faiss/IndexIVFPQ.h" +#include "commons.h" +#include "faiss/IndexBinaryIVF.h" +#include "faiss/IndexBinaryHNSW.h" #include #include #include #include +// Defines type of IDSelector +enum FilterIdsSelectorType{ + BITMAP = 0, BATCH = 1, +}; +namespace faiss { + +// Using jlong to do Bitmap selector, jlong[] equals to lucene FixedBitSet#bits +struct IDSelectorJlongBitmap : IDSelector { + size_t n; + const jlong* bitmap; + + /** Construct with a binary mask like Lucene FixedBitSet + * + * @param n size of the bitmap array + * @param bitmap id like Lucene FixedBitSet bits + */ + IDSelectorJlongBitmap(size_t _n, const jlong* _bitmap) + : IDSelector(), + n(_n), + bitmap(_bitmap) { + } + + bool is_member(idx_t id) const final { + const uint64_t index = id; + const uint64_t i = index >> 6ULL; // div 64 + if (i >= n) { + return false; + } + return (bitmap[i] >> (index & 63ULL)) & 1ULL; + } +}; // class IDSelectorJlongBitmap + +} // namespace faiss + // Translate space type to faiss metric faiss::MetricType TranslateSpaceToMetric(const std::string& spaceType); @@ -33,21 +75,31 @@ void SetExtraParameters(knn_jni::JNIUtilInterface * jniUtil, JNIEnv *env, const std::unordered_map& parametersCpp, faiss::Index * index); // Train an index with data provided -void InternalTrainIndex(faiss::Index * index, faiss::Index::idx_t n, const float* x); +void InternalTrainIndex(faiss::Index * index, faiss::idx_t n, const float* x); -void knn_jni::faiss_wrapper::CreateIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, - jobjectArray vectorsJ, jstring indexPathJ, jobject parametersJ) { +// Train a binary index with data provided +void InternalTrainBinaryIndex(faiss::IndexBinary * index, faiss::idx_t n, const uint8_t* x); - if (idsJ == nullptr) { - throw std::runtime_error("IDs cannot be null"); - } +// Converts the int FilterIds to Faiss ids type array. +void convertFilterIdsToFaissIdType(const int* filterIds, int filterIdsLength, faiss::idx_t* convertedFilterIds); - if (vectorsJ == nullptr) { - throw std::runtime_error("Vectors cannot be null"); - } +// Concerts the FilterIds to BitMap +void buildFilterIdsBitMap(const int* filterIds, int filterIdsLength, uint8_t* bitsetVector); - if (indexPathJ == nullptr) { - throw std::runtime_error("Index path cannot be null"); +std::unique_ptr buildIDGrouperBitmap(knn_jni::JNIUtilInterface * jniUtil, JNIEnv *env, jintArray parentIdsJ, std::vector* bitmap); + +// Check if a loaded index is an IVFPQ index with l2 space type +bool isIndexIVFPQL2(faiss::Index * index); + +// Gets IVFPQ index from a faiss index. For faiss, we wrap the index in the type +// IndexIDMap which has member that will point to underlying index that stores the data +faiss::IndexIVFPQ * extractIVFPQIndex(faiss::Index * index); + +jlong knn_jni::faiss_wrapper::InitIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jlong numDocs, jint dimJ, + jobject parametersJ, IndexService* indexService) { + + if(dimJ <= 0) { + throw std::runtime_error("Vectors dimensions cannot be less than or equal to 0"); } if (parametersJ == nullptr) { @@ -58,70 +110,254 @@ void knn_jni::faiss_wrapper::CreateIndex(knn_jni::JNIUtilInterface * jniUtil, JN // so that it is easier to access. auto parametersCpp = jniUtil->ConvertJavaMapToCppMap(env, parametersJ); - // Get space type for this index + // Parameters to pass + // Metric type jobject spaceTypeJ = knn_jni::GetJObjectFromMapOrThrow(parametersCpp, knn_jni::SPACE_TYPE); std::string spaceTypeCpp(jniUtil->ConvertJavaObjectToCppString(env, spaceTypeJ)); faiss::MetricType metric = TranslateSpaceToMetric(spaceTypeCpp); + jniUtil->DeleteLocalRef(env, spaceTypeJ); + + // Dimension + int dim = (int)dimJ; + + // Number of docs + int docs = (int)numDocs; + + // Index description + jobject indexDescriptionJ = knn_jni::GetJObjectFromMapOrThrow(parametersCpp, knn_jni::INDEX_DESCRIPTION); + std::string indexDescriptionCpp(jniUtil->ConvertJavaObjectToCppString(env, indexDescriptionJ)); + jniUtil->DeleteLocalRef(env, indexDescriptionJ); + + // Thread count + int threadCount = 0; + if(parametersCpp.find(knn_jni::INDEX_THREAD_QUANTITY) != parametersCpp.end()) { + threadCount = jniUtil->ConvertJavaObjectToCppInteger(env, parametersCpp[knn_jni::INDEX_THREAD_QUANTITY]); + } + + // Extra parameters + // TODO: parse the entire map and remove jni object + std::unordered_map subParametersCpp; + if(parametersCpp.find(knn_jni::PARAMETERS) != parametersCpp.end()) { + subParametersCpp = jniUtil->ConvertJavaMapToCppMap(env, parametersCpp[knn_jni::PARAMETERS]); + } + // end parameters to pass + + // Create index + return indexService->initIndex(jniUtil, + env, + metric, + std::move(indexDescriptionCpp), + dim, + numDocs, + threadCount, + std::move(subParametersCpp)); +} + +void knn_jni::faiss_wrapper::InsertToIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, jlong vectorsAddressJ, jint dimJ, + jlong index_ptr, jint threadCount, IndexService* indexService) { + if (idsJ == nullptr) { + throw std::runtime_error("IDs cannot be null"); + } + + if (vectorsAddressJ <= 0) { + throw std::runtime_error("VectorsAddress cannot be less than 0"); + } + + if(dimJ <= 0) { + throw std::runtime_error("Vectors dimensions cannot be less than or equal to 0"); + } + + // Dimension + int dim = (int)dimJ; + + // Number of vectors + int numIds = jniUtil->GetJavaIntArrayLength(env, idsJ); + + // Vectors address + int64_t vectorsAddress = (int64_t)vectorsAddressJ; + + // Ids + auto ids = jniUtil->ConvertJavaIntArrayToCppIntVector(env, idsJ); + + // Create index + indexService->insertToIndex(dim, numIds, threadCount, vectorsAddress, ids, index_ptr); +} + +void knn_jni::faiss_wrapper::WriteIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, + jobject output, jlong index_ptr, IndexService* indexService) { + + if (output == nullptr) { + throw std::runtime_error("Index output stream cannot be null"); + } + + // IndexOutput wrapper. + knn_jni::stream::NativeEngineIndexOutputMediator mediator {jniUtil, env, output}; + knn_jni::stream::FaissOpenSearchIOWriter writer {&mediator}; + + // Create index. + indexService->writeIndex(&writer, index_ptr); +} + +void knn_jni::faiss_wrapper::CreateIndexFromTemplate(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, + jlong vectorsAddressJ, jint dimJ, jobject output, + jbyteArray templateIndexJ, jobject parametersJ) { + if (idsJ == nullptr) { + throw std::runtime_error("IDs cannot be null"); + } + + if (vectorsAddressJ <= 0) { + throw std::runtime_error("VectorsAddress cannot be less than 0"); + } + + if(dimJ <= 0) { + throw std::runtime_error("Vectors dimensions cannot be less than or equal to 0"); + } + + if (output == nullptr) { + throw std::runtime_error("Index output stream cannot be null"); + } + + if (templateIndexJ == nullptr) { + throw std::runtime_error("Template index cannot be null"); + } + + // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread + auto parametersCpp = jniUtil->ConvertJavaMapToCppMap(env, parametersJ); + if(parametersCpp.find(knn_jni::INDEX_THREAD_QUANTITY) != parametersCpp.end()) { + auto threadCount = jniUtil->ConvertJavaObjectToCppInteger(env, parametersCpp[knn_jni::INDEX_THREAD_QUANTITY]); + omp_set_num_threads(threadCount); + } + jniUtil->DeleteLocalRef(env, parametersJ); // Read data set - int numVectors = jniUtil->GetJavaObjectArrayLength(env, vectorsJ); + // Read vectors from memory address + auto *inputVectors = reinterpret_cast*>(vectorsAddressJ); + int dim = (int)dimJ; + int numVectors = (int) (inputVectors->size() / (uint64_t) dim); int numIds = jniUtil->GetJavaIntArrayLength(env, idsJ); if (numIds != numVectors) { throw std::runtime_error("Number of IDs does not match number of vectors"); } - int dim = jniUtil->GetInnerDimensionOf2dJavaFloatArray(env, vectorsJ); - auto dataset = jniUtil->Convert2dJavaObjectArrayToCppFloatVector(env, vectorsJ, dim); + // Get vector of bytes from jbytearray + int indexBytesCount = jniUtil->GetJavaBytesArrayLength(env, templateIndexJ); + jbyte * indexBytesJ = jniUtil->GetByteArrayElements(env, templateIndexJ, nullptr); - // Create faiss index - jobject indexDescriptionJ = knn_jni::GetJObjectFromMapOrThrow(parametersCpp, knn_jni::INDEX_DESCRIPTION); - std::string indexDescriptionCpp(jniUtil->ConvertJavaObjectToCppString(env, indexDescriptionJ)); + faiss::VectorIOReader vectorIoReader; + for (int i = 0; i < indexBytesCount; i++) { + vectorIoReader.data.push_back((uint8_t) indexBytesJ[i]); + } + jniUtil->ReleaseByteArrayElements(env, templateIndexJ, indexBytesJ, JNI_ABORT); + // Create faiss index std::unique_ptr indexWriter; - indexWriter.reset(faiss::index_factory(dim, indexDescriptionCpp.c_str(), metric)); + indexWriter.reset(faiss::read_index(&vectorIoReader, 0)); + + auto idVector = jniUtil->ConvertJavaIntArrayToCppIntVector(env, idsJ); + faiss::IndexIDMap idMap = faiss::IndexIDMap(indexWriter.get()); + idMap.add_with_ids(numVectors, inputVectors->data(), idVector.data()); + // Releasing the vectorsAddressJ memory as that is not required once we have created the index. + // This is not the ideal approach, please refer this gh issue for long term solution: + // https://github.com/opensearch-project/k-NN/issues/1600 + delete inputVectors; + + // Write the index to disk + knn_jni::stream::NativeEngineIndexOutputMediator mediator {jniUtil, env, output}; + knn_jni::stream::FaissOpenSearchIOWriter writer {&mediator}; + faiss::write_index(&idMap, &writer); + mediator.flush(); +} + +void knn_jni::faiss_wrapper::CreateBinaryIndexFromTemplate(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, + jlong vectorsAddressJ, jint dimJ, jobject output, + jbyteArray templateIndexJ, jobject parametersJ) { + if (idsJ == nullptr) { + throw std::runtime_error("IDs cannot be null"); + } + + if (vectorsAddressJ <= 0) { + throw std::runtime_error("VectorsAddress cannot be less than 0"); + } + + if (dimJ <= 0) { + throw std::runtime_error("Vectors dimensions cannot be less than or equal to 0"); + } + + if (output == nullptr) { + throw std::runtime_error("Index output stream cannot be null"); + } + + if (templateIndexJ == nullptr) { + throw std::runtime_error("Template index cannot be null"); + } // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread + auto parametersCpp = jniUtil->ConvertJavaMapToCppMap(env, parametersJ); if(parametersCpp.find(knn_jni::INDEX_THREAD_QUANTITY) != parametersCpp.end()) { auto threadCount = jniUtil->ConvertJavaObjectToCppInteger(env, parametersCpp[knn_jni::INDEX_THREAD_QUANTITY]); omp_set_num_threads(threadCount); } + jniUtil->DeleteLocalRef(env, parametersJ); - // Add extra parameters that cant be configured with the index factory - if(parametersCpp.find(knn_jni::PARAMETERS) != parametersCpp.end()) { - jobject subParametersJ = parametersCpp[knn_jni::PARAMETERS]; - auto subParametersCpp = jniUtil->ConvertJavaMapToCppMap(env, subParametersJ); - SetExtraParameters(jniUtil, env, subParametersCpp, indexWriter.get()); - jniUtil->DeleteLocalRef(env, subParametersJ); + // Read data set + // Read vectors from memory address + auto *inputVectors = reinterpret_cast*>(vectorsAddressJ); + int dim = (int)dimJ; + if (dim % 8 != 0) { + throw std::runtime_error("Dimensions should be multiple of 8"); + } + int numVectors = (int) (inputVectors->size() / (uint64_t) (dim / 8)); + int numIds = jniUtil->GetJavaIntArrayLength(env, idsJ); + if (numIds != numVectors) { + throw std::runtime_error("Number of IDs does not match number of vectors"); } - jniUtil->DeleteLocalRef(env, parametersJ); - // Check that the index does not need to be trained - if(!indexWriter->is_trained) { - throw std::runtime_error("Index is not trained"); + // Get vector of bytes from jbytearray + int indexBytesCount = jniUtil->GetJavaBytesArrayLength(env, templateIndexJ); + jbyte * indexBytesJ = jniUtil->GetByteArrayElements(env, templateIndexJ, nullptr); + + faiss::VectorIOReader vectorIoReader; + for (int i = 0; i < indexBytesCount; i++) { + vectorIoReader.data.push_back((uint8_t) indexBytesJ[i]); } + jniUtil->ReleaseByteArrayElements(env, templateIndexJ, indexBytesJ, JNI_ABORT); + + // Create faiss index + std::unique_ptr indexWriter; + indexWriter.reset(faiss::read_index_binary(&vectorIoReader, 0)); auto idVector = jniUtil->ConvertJavaIntArrayToCppIntVector(env, idsJ); - faiss::IndexIDMap idMap = faiss::IndexIDMap(indexWriter.get()); - idMap.add_with_ids(numVectors, dataset.data(), idVector.data()); + faiss::IndexBinaryIDMap idMap = faiss::IndexBinaryIDMap(indexWriter.get()); + idMap.add_with_ids(numVectors, reinterpret_cast(inputVectors->data()), idVector.data()); + // Releasing the vectorsAddressJ memory as that is not required once we have created the index. + // This is not the ideal approach, please refer this gh issue for long term solution: + // https://github.com/opensearch-project/k-NN/issues/1600 + delete inputVectors; // Write the index to disk - std::string indexPathCpp(jniUtil->ConvertJavaStringToCppString(env, indexPathJ)); - faiss::write_index(&idMap, indexPathCpp.c_str()); + knn_jni::stream::NativeEngineIndexOutputMediator mediator {jniUtil, env, output}; + knn_jni::stream::FaissOpenSearchIOWriter writer {&mediator}; + faiss::write_index_binary(&idMap, &writer); + mediator.flush(); } -void knn_jni::faiss_wrapper::CreateIndexFromTemplate(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, - jobjectArray vectorsJ, jstring indexPathJ, - jbyteArray templateIndexJ, jobject parametersJ) { +void knn_jni::faiss_wrapper::CreateByteIndexFromTemplate(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, + jlong vectorsAddressJ, jint dimJ, jobject output, + jbyteArray templateIndexJ, jobject parametersJ) { if (idsJ == nullptr) { throw std::runtime_error("IDs cannot be null"); } - if (vectorsJ == nullptr) { - throw std::runtime_error("Vectors cannot be null"); + if (vectorsAddressJ <= 0) { + throw std::runtime_error("VectorsAddress cannot be less than 0"); } - if (indexPathJ == nullptr) { - throw std::runtime_error("Index path cannot be null"); + if (dimJ <= 0) { + throw std::runtime_error("Vectors dimensions cannot be less than or equal to 0"); + } + + if (output == nullptr) { + throw std::runtime_error("Index output stream cannot be null"); } if (templateIndexJ == nullptr) { @@ -130,43 +366,73 @@ void knn_jni::faiss_wrapper::CreateIndexFromTemplate(knn_jni::JNIUtilInterface * // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread auto parametersCpp = jniUtil->ConvertJavaMapToCppMap(env, parametersJ); - if(parametersCpp.find(knn_jni::INDEX_THREAD_QUANTITY) != parametersCpp.end()) { - auto threadCount = jniUtil->ConvertJavaObjectToCppInteger(env, parametersCpp[knn_jni::INDEX_THREAD_QUANTITY]); + auto it = parametersCpp.find(knn_jni::INDEX_THREAD_QUANTITY); + if (it != parametersCpp.end()) { + auto threadCount = jniUtil->ConvertJavaObjectToCppInteger(env, it->second); omp_set_num_threads(threadCount); } jniUtil->DeleteLocalRef(env, parametersJ); // Read data set - int numVectors = jniUtil->GetJavaObjectArrayLength(env, vectorsJ); + // Read vectors from memory address + auto *inputVectors = reinterpret_cast*>(vectorsAddressJ); + auto dim = (int) dimJ; + auto numVectors = (int) (inputVectors->size() / (uint64_t) dim); int numIds = jniUtil->GetJavaIntArrayLength(env, idsJ); + if (numIds != numVectors) { throw std::runtime_error("Number of IDs does not match number of vectors"); } - int dim = jniUtil->GetInnerDimensionOf2dJavaFloatArray(env, vectorsJ); - auto dataset = jniUtil->Convert2dJavaObjectArrayToCppFloatVector(env, vectorsJ, dim); - // Get vector of bytes from jbytearray int indexBytesCount = jniUtil->GetJavaBytesArrayLength(env, templateIndexJ); jbyte * indexBytesJ = jniUtil->GetByteArrayElements(env, templateIndexJ, nullptr); faiss::VectorIOReader vectorIoReader; + vectorIoReader.data.reserve(indexBytesCount); for (int i = 0; i < indexBytesCount; i++) { vectorIoReader.data.push_back((uint8_t) indexBytesJ[i]); } jniUtil->ReleaseByteArrayElements(env, templateIndexJ, indexBytesJ, JNI_ABORT); // Create faiss index - std::unique_ptr indexWriter; - indexWriter.reset(faiss::read_index(&vectorIoReader, 0)); + std::unique_ptr indexWriter (faiss::read_index(&vectorIoReader, 0)); - auto idVector = jniUtil->ConvertJavaIntArrayToCppIntVector(env, idsJ); + auto ids = jniUtil->ConvertJavaIntArrayToCppIntVector(env, idsJ); faiss::IndexIDMap idMap = faiss::IndexIDMap(indexWriter.get()); - idMap.add_with_ids(numVectors, dataset.data(), idVector.data()); + + // Add vectors in batches by casting int8 vectors into float with a batch size of 1000 to avoid additional memory spike. + // Refer to this github issue for more details https://github.com/opensearch-project/k-NN/issues/1659#issuecomment-2307390255 + int batchSize = 1000; + std::vector inputFloatVectors(batchSize * dim); + std::vector floatVectorsIds(batchSize); + int id = 0; + auto iter = inputVectors->begin(); + + for (int id = 0; id < numVectors; id += batchSize) { + if (numVectors - id < batchSize) { + batchSize = numVectors - id; + } + + for (int i = 0; i < batchSize; ++i) { + floatVectorsIds[i] = ids[id + i]; + for (int j = 0; j < dim; ++j, ++iter) { + inputFloatVectors[i * dim + j] = static_cast(*iter); + } + } + idMap.add_with_ids(batchSize, inputFloatVectors.data(), floatVectorsIds.data()); + } + + // Releasing the vectorsAddressJ memory as that is not required once we have created the index. + // This is not the ideal approach, please refer this gh issue for long term solution: + // https://github.com/opensearch-project/k-NN/issues/1600 + delete inputVectors; // Write the index to disk - std::string indexPathCpp(jniUtil->ConvertJavaStringToCppString(env, indexPathJ)); - faiss::write_index(&idMap, indexPathCpp.c_str()); + knn_jni::stream::NativeEngineIndexOutputMediator mediator {jniUtil, env, output}; + knn_jni::stream::FaissOpenSearchIOWriter writer {&mediator}; + faiss::write_index(&idMap, &writer); + mediator.flush(); } jlong knn_jni::faiss_wrapper::LoadIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jstring indexPathJ) { @@ -175,33 +441,207 @@ jlong knn_jni::faiss_wrapper::LoadIndex(knn_jni::JNIUtilInterface * jniUtil, JNI } std::string indexPathCpp(jniUtil->ConvertJavaStringToCppString(env, indexPathJ)); - faiss::Index* indexReader = faiss::read_index(indexPathCpp.c_str(), faiss::IO_FLAG_READ_ONLY); + // Skipping IO_FLAG_PQ_SKIP_SDC_TABLE because the index is read only and the sdc table is only used during ingestion + // Skipping IO_PRECOMPUTE_TABLE because it is only needed for IVFPQ-l2 and it leads to high memory consumption if + // done for each segment. Instead, we will set it later on with `setSharedIndexState` + faiss::Index* indexReader = faiss::read_index(indexPathCpp.c_str(), faiss::IO_FLAG_READ_ONLY | faiss::IO_FLAG_PQ_SKIP_SDC_TABLE | faiss::IO_FLAG_SKIP_PRECOMPUTE_TABLE); return (jlong) indexReader; } +jlong knn_jni::faiss_wrapper::LoadIndexWithStream(faiss::IOReader* ioReader) { + if (ioReader == nullptr) { + throw std::runtime_error("IOReader cannot be null"); + } + + faiss::Index* indexReader = + faiss::read_index(ioReader, + faiss::IO_FLAG_READ_ONLY + | faiss::IO_FLAG_PQ_SKIP_SDC_TABLE + | faiss::IO_FLAG_SKIP_PRECOMPUTE_TABLE); + + return (jlong) indexReader; +} + +jlong knn_jni::faiss_wrapper::LoadBinaryIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jstring indexPathJ) { + if (indexPathJ == nullptr) { + throw std::runtime_error("Index path cannot be null"); + } + + std::string indexPathCpp(jniUtil->ConvertJavaStringToCppString(env, indexPathJ)); + // Skipping IO_FLAG_PQ_SKIP_SDC_TABLE because the index is read only and the sdc table is only used during ingestion + // Skipping IO_PRECOMPUTE_TABLE because it is only needed for IVFPQ-l2 and it leads to high memory consumption if + // done for each segment. Instead, we will set it later on with `setSharedIndexState` + faiss::IndexBinary* indexReader = faiss::read_index_binary(indexPathCpp.c_str(), faiss::IO_FLAG_READ_ONLY | faiss::IO_FLAG_PQ_SKIP_SDC_TABLE | faiss::IO_FLAG_SKIP_PRECOMPUTE_TABLE); + return (jlong) indexReader; +} + +jlong knn_jni::faiss_wrapper::LoadBinaryIndexWithStream(faiss::IOReader* ioReader) { + if (ioReader == nullptr) { + throw std::runtime_error("IOReader cannot be null"); + } + + faiss::IndexBinary* indexReader = + faiss::read_index_binary(ioReader, + faiss::IO_FLAG_READ_ONLY + | faiss::IO_FLAG_PQ_SKIP_SDC_TABLE + | faiss::IO_FLAG_SKIP_PRECOMPUTE_TABLE); + + return (jlong) indexReader; +} + +bool knn_jni::faiss_wrapper::IsSharedIndexStateRequired(jlong indexPointerJ) { + auto * index = reinterpret_cast(indexPointerJ); + return isIndexIVFPQL2(index); +} + +jlong knn_jni::faiss_wrapper::InitSharedIndexState(jlong indexPointerJ) { + auto * index = reinterpret_cast(indexPointerJ); + if (!isIndexIVFPQL2(index)) { + throw std::runtime_error("Unable to init shared index state from index. index is not of type IVFPQ-l2"); + } + + auto * indexIVFPQ = extractIVFPQIndex(index); + int use_precomputed_table = 0; + auto * sharedMemoryAddress = new faiss::AlignedTable(); + faiss::initialize_IVFPQ_precomputed_table( + use_precomputed_table, + indexIVFPQ->quantizer, + indexIVFPQ->pq, + *sharedMemoryAddress, + indexIVFPQ->by_residual, + indexIVFPQ->verbose); + return (jlong) sharedMemoryAddress; +} + +void knn_jni::faiss_wrapper::SetSharedIndexState(jlong indexPointerJ, jlong shareIndexStatePointerJ) { + auto * index = reinterpret_cast(indexPointerJ); + if (!isIndexIVFPQL2(index)) { + throw std::runtime_error("Unable to set shared index state from index. index is not of type IVFPQ-l2"); + } + auto * indexIVFPQ = extractIVFPQIndex(index); + + //TODO: Currently, the only shared state is that of the AlignedTable associated with + // IVFPQ-l2 index type (see https://github.com/opensearch-project/k-NN/issues/1507). In the future, + // this will be generalized and more information will be needed to determine the shared type. But, until then, + // this is fine. + auto *alignTable = reinterpret_cast*>(shareIndexStatePointerJ); + // In faiss, usePrecomputedTable can have a couple different values: + // -1 -> dont use the table + // 0 -> tell initialize_IVFPQ_precomputed_table to select the best value and change the value + // 1 -> default behavior + // 2 -> Index is of type "MultiIndexQuantizer" + // This index will be of type IndexIVFPQ always. We never create "MultiIndexQuantizer". So, the value we + // want is 1. + // (ref: https://github.com/facebookresearch/faiss/blob/v1.8.0/faiss/IndexIVFPQ.cpp#L383-L410) + int usePrecomputedTable = 1; + indexIVFPQ->set_precomputed_table(alignTable, usePrecomputedTable); +} + jobjectArray knn_jni::faiss_wrapper::QueryIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jlong indexPointerJ, - jfloatArray queryVectorJ, jint kJ) { + jfloatArray queryVectorJ, jint kJ, jobject methodParamsJ, jintArray parentIdsJ) { + return knn_jni::faiss_wrapper::QueryIndex_WithFilter(jniUtil, env, indexPointerJ, queryVectorJ, kJ, methodParamsJ, nullptr, 0, parentIdsJ); +} + +jobjectArray knn_jni::faiss_wrapper::QueryIndex_WithFilter(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jlong indexPointerJ, + jfloatArray queryVectorJ, jint kJ, jobject methodParamsJ, jlongArray filterIdsJ, jint filterIdsTypeJ, jintArray parentIdsJ) { if (queryVectorJ == nullptr) { throw std::runtime_error("Query Vector cannot be null"); } - auto *indexReader = reinterpret_cast(indexPointerJ); + auto *indexReader = reinterpret_cast(indexPointerJ); if (indexReader == nullptr) { throw std::runtime_error("Invalid pointer to index"); } - int dim = jniUtil->GetJavaFloatArrayLength(env, queryVectorJ); - std::vector dis(kJ * dim); - std::vector ids(kJ * dim); + std::unordered_map methodParams; + if (methodParamsJ != nullptr) { + methodParams = jniUtil->ConvertJavaMapToCppMap(env, methodParamsJ); + } + // The ids vector will hold the top k ids from the search and the dis vector will hold the top k distances from + // the query point + std::vector dis(kJ); + std::vector ids(kJ); float* rawQueryvector = jniUtil->GetFloatArrayElements(env, queryVectorJ, nullptr); - - try { - indexReader->search(1, rawQueryvector, kJ, dis.data(), ids.data()); - } catch (...) { - jniUtil->ReleaseFloatArrayElements(env, queryVectorJ, rawQueryvector, JNI_ABORT); - throw; + /* + Setting the omp_set_num_threads to 1 to make sure that no new OMP threads are getting created. + */ + omp_set_num_threads(1); + // create the filterSearch params if the filterIdsJ is not a null pointer + if(filterIdsJ != nullptr) { + jlong *filteredIdsArray = jniUtil->GetLongArrayElements(env, filterIdsJ, nullptr); + int filterIdsLength = jniUtil->GetJavaLongArrayLength(env, filterIdsJ); + std::unique_ptr idSelector; + if(filterIdsTypeJ == BITMAP) { + idSelector.reset(new faiss::IDSelectorJlongBitmap(filterIdsLength, filteredIdsArray)); + } else { + faiss::idx_t* batchIndices = reinterpret_cast(filteredIdsArray); + idSelector.reset(new faiss::IDSelectorBatch(filterIdsLength, batchIndices)); + } + faiss::SearchParameters *searchParameters; + faiss::SearchParametersHNSW hnswParams; + faiss::SearchParametersIVF ivfParams; + std::unique_ptr idGrouper; + std::vector idGrouperBitmap; + auto hnswReader = dynamic_cast(indexReader->index); + if(hnswReader) { + // Query param efsearch supersedes ef_search provided during index setting. + hnswParams.efSearch = knn_jni::commons::getIntegerMethodParameter(env, jniUtil, methodParams, EF_SEARCH, hnswReader->hnsw.efSearch); + hnswParams.sel = idSelector.get(); + if (parentIdsJ != nullptr) { + idGrouper = buildIDGrouperBitmap(jniUtil, env, parentIdsJ, &idGrouperBitmap); + hnswParams.grp = idGrouper.get(); + } + searchParameters = &hnswParams; + } else { + auto ivfReader = dynamic_cast(indexReader->index); + auto ivfFlatReader = dynamic_cast(indexReader->index); + + if(ivfReader || ivfFlatReader) { + int indexNprobe = ivfReader == nullptr ? ivfFlatReader->nprobe : ivfReader->nprobe; + ivfParams.nprobe = commons::getIntegerMethodParameter(env, jniUtil, methodParams, NPROBES, indexNprobe); + ivfParams.sel = idSelector.get(); + searchParameters = &ivfParams; + } + } + try { + indexReader->search(1, rawQueryvector, kJ, dis.data(), ids.data(), searchParameters); + } catch (...) { + jniUtil->ReleaseFloatArrayElements(env, queryVectorJ, rawQueryvector, JNI_ABORT); + jniUtil->ReleaseLongArrayElements(env, filterIdsJ, filteredIdsArray, JNI_ABORT); + throw; + } + jniUtil->ReleaseLongArrayElements(env, filterIdsJ, filteredIdsArray, JNI_ABORT); + } else { + faiss::SearchParameters *searchParameters = nullptr; + faiss::SearchParametersHNSW hnswParams; + faiss::SearchParametersIVF ivfParams; + std::unique_ptr idGrouper; + std::vector idGrouperBitmap; + auto hnswReader = dynamic_cast(indexReader->index); + if(hnswReader != nullptr) { + // Query param efsearch supersedes ef_search provided during index setting. + hnswParams.efSearch = knn_jni::commons::getIntegerMethodParameter(env, jniUtil, methodParams, EF_SEARCH, hnswReader->hnsw.efSearch); + if (parentIdsJ != nullptr) { + idGrouper = buildIDGrouperBitmap(jniUtil, env, parentIdsJ, &idGrouperBitmap); + hnswParams.grp = idGrouper.get(); + } + searchParameters = &hnswParams; + } else { + auto ivfReader = dynamic_cast(indexReader->index); + if (ivfReader) { + int indexNprobe = ivfReader->nprobe; + ivfParams.nprobe = commons::getIntegerMethodParameter(env, jniUtil, methodParams, NPROBES, indexNprobe); + searchParameters = &ivfParams; + } + } + try { + indexReader->search(1, rawQueryvector, kJ, dis.data(), ids.data(), searchParameters); + } catch (...) { + jniUtil->ReleaseFloatArrayElements(env, queryVectorJ, rawQueryvector, JNI_ABORT); + throw; + } } jniUtil->ReleaseFloatArrayElements(env, queryVectorJ, rawQueryvector, JNI_ABORT); @@ -213,8 +653,132 @@ jobjectArray knn_jni::faiss_wrapper::QueryIndex(knn_jni::JNIUtilInterface * jniU resultSize = it - ids.begin(); } - jclass resultClass = jniUtil->FindClass(env,"org/opensearch/knn/index/KNNQueryResult"); - jmethodID allArgs = jniUtil->FindMethod(env, "org/opensearch/knn/index/KNNQueryResult", ""); + jclass resultClass = jniUtil->FindClass(env,"org/opensearch/knn/index/query/KNNQueryResult"); + jmethodID allArgs = jniUtil->FindMethod(env, "org/opensearch/knn/index/query/KNNQueryResult", ""); + + jobjectArray results = jniUtil->NewObjectArray(env, resultSize, resultClass, nullptr); + + jobject result; + for(int i = 0; i < resultSize; ++i) { + result = jniUtil->NewObject(env, resultClass, allArgs, ids[i], dis[i]); + jniUtil->SetObjectArrayElement(env, results, i, result); + } + return results; +} + +jobjectArray knn_jni::faiss_wrapper::QueryBinaryIndex_WithFilter(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jlong indexPointerJ, + jbyteArray queryVectorJ, jint kJ, jobject methodParamsJ, jlongArray filterIdsJ, jint filterIdsTypeJ, jintArray parentIdsJ) { + + if (queryVectorJ == nullptr) { + throw std::runtime_error("Query Vector cannot be null"); + } + + auto *indexReader = reinterpret_cast(indexPointerJ); + + if (indexReader == nullptr) { + throw std::runtime_error("Invalid pointer to index"); + } + + std::unordered_map methodParams; + if (methodParamsJ != nullptr) { + methodParams = jniUtil->ConvertJavaMapToCppMap(env, methodParamsJ); + } + + // The ids vector will hold the top k ids from the search and the dis vector will hold the top k distances from + // the query point + std::vector dis(kJ); + std::vector ids(kJ); + int8_t* rawQueryvector = jniUtil->GetByteArrayElements(env, queryVectorJ, nullptr); + /* + Setting the omp_set_num_threads to 1 to make sure that no new OMP threads are getting created. + */ + omp_set_num_threads(1); + // create the filterSearch params if the filterIdsJ is not a null pointer + if(filterIdsJ != nullptr) { + jlong *filteredIdsArray = jniUtil->GetLongArrayElements(env, filterIdsJ, nullptr); + int filterIdsLength = jniUtil->GetJavaLongArrayLength(env, filterIdsJ); + std::unique_ptr idSelector; + if(filterIdsTypeJ == BITMAP) { + idSelector.reset(new faiss::IDSelectorJlongBitmap(filterIdsLength, filteredIdsArray)); + } else { + faiss::idx_t* batchIndices = reinterpret_cast(filteredIdsArray); + idSelector.reset(new faiss::IDSelectorBatch(filterIdsLength, batchIndices)); + } + faiss::SearchParameters *searchParameters; + faiss::SearchParametersHNSW hnswParams; + faiss::SearchParametersIVF ivfParams; + std::unique_ptr idGrouper; + std::vector idGrouperBitmap; + auto hnswReader = dynamic_cast(indexReader->index); + if(hnswReader) { + // Query param efsearch supersedes ef_search provided during index setting. + hnswParams.efSearch = knn_jni::commons::getIntegerMethodParameter(env, jniUtil, methodParams, EF_SEARCH, hnswReader->hnsw.efSearch); + hnswParams.sel = idSelector.get(); + if (parentIdsJ != nullptr) { + idGrouper = buildIDGrouperBitmap(jniUtil, env, parentIdsJ, &idGrouperBitmap); + hnswParams.grp = idGrouper.get(); + } + searchParameters = &hnswParams; + } else { + auto ivfReader = dynamic_cast(indexReader->index); + if(ivfReader) { + ivfParams.sel = idSelector.get(); + searchParameters = &ivfParams; + } + } + try { + indexReader->search(1, reinterpret_cast(rawQueryvector), kJ, dis.data(), ids.data(), searchParameters); + } catch (...) { + jniUtil->ReleaseByteArrayElements(env, queryVectorJ, rawQueryvector, JNI_ABORT); + jniUtil->ReleaseLongArrayElements(env, filterIdsJ, filteredIdsArray, JNI_ABORT); + throw; + } + jniUtil->ReleaseLongArrayElements(env, filterIdsJ, filteredIdsArray, JNI_ABORT); + } else { + faiss::SearchParameters *searchParameters = nullptr; + faiss::SearchParametersHNSW hnswParams; + faiss::SearchParametersIVF ivfParams; + std::unique_ptr idGrouper; + std::vector idGrouperBitmap; + auto ivfReader = dynamic_cast(indexReader->index); + // TODO currently, search parameter is not supported in binary index + // To avoid test failure, we skip setting ef search when methodPramsJ is null temporary + if (ivfReader) { + int indexNprobe = ivfReader->nprobe; + ivfParams.nprobe = commons::getIntegerMethodParameter(env, jniUtil, methodParams, NPROBES, indexNprobe); + searchParameters = &ivfParams; + } else { + auto hnswReader = dynamic_cast(indexReader->index); + if(hnswReader != nullptr && (methodParamsJ != nullptr || parentIdsJ != nullptr)) { + // Query param efsearch supersedes ef_search provided during index setting. + hnswParams.efSearch = knn_jni::commons::getIntegerMethodParameter(env, jniUtil, methodParams, EF_SEARCH, hnswReader->hnsw.efSearch); + if (parentIdsJ != nullptr) { + idGrouper = buildIDGrouperBitmap(jniUtil, env, parentIdsJ, &idGrouperBitmap); + hnswParams.grp = idGrouper.get(); + } + searchParameters = &hnswParams; + } + } + + try { + indexReader->search(1, reinterpret_cast(rawQueryvector), kJ, dis.data(), ids.data(), searchParameters); + } catch (...) { + jniUtil->ReleaseByteArrayElements(env, queryVectorJ, rawQueryvector, JNI_ABORT); + throw; + } + } + jniUtil->ReleaseByteArrayElements(env, queryVectorJ, rawQueryvector, JNI_ABORT); + + // If there are not k results, the results will be padded with -1. Find the first -1, and set result size to that + // index + int resultSize = kJ; + auto it = std::find(ids.begin(), ids.end(), -1); + if (it != ids.end()) { + resultSize = it - ids.begin(); + } + + jclass resultClass = jniUtil->FindClass(env,"org/opensearch/knn/index/query/KNNQueryResult"); + jmethodID allArgs = jniUtil->FindMethod(env, "org/opensearch/knn/index/query/KNNQueryResult", ""); jobjectArray results = jniUtil->NewObjectArray(env, resultSize, resultClass, nullptr); @@ -226,9 +790,25 @@ jobjectArray knn_jni::faiss_wrapper::QueryIndex(knn_jni::JNIUtilInterface * jniU return results; } -void knn_jni::faiss_wrapper::Free(jlong indexPointer) { - auto *indexWrapper = reinterpret_cast(indexPointer); - delete indexWrapper; +void knn_jni::faiss_wrapper::Free(jlong indexPointer, jboolean isBinaryIndexJ) { + bool isBinaryIndex = static_cast(isBinaryIndexJ); + if (isBinaryIndex) { + auto *indexWrapper = reinterpret_cast(indexPointer); + delete indexWrapper; + } + else { + auto *indexWrapper = reinterpret_cast(indexPointer); + delete indexWrapper; + } +} + +void knn_jni::faiss_wrapper::FreeSharedIndexState(jlong shareIndexStatePointerJ) { + //TODO: Currently, the only shared state is that of the AlignedTable associated with + // IVFPQ-l2 index type (see https://github.com/opensearch-project/k-NN/issues/1507). In the future, + // this will be generalized and more information will be needed to determine the shared type. But, until then, + // this is fine. + auto *alignTable = reinterpret_cast*>(shareIndexStatePointerJ); + delete alignTable; } void knn_jni::faiss_wrapper::InitLibrary() { @@ -265,13 +845,13 @@ jbyteArray knn_jni::faiss_wrapper::TrainIndex(knn_jni::JNIUtilInterface * jniUti } // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread - if(parametersCpp.find(knn_jni::INDEX_THREAD_QUANTITY) != parametersCpp.end()) { + if (parametersCpp.find(knn_jni::INDEX_THREAD_QUANTITY) != parametersCpp.end()) { auto threadCount = jniUtil->ConvertJavaObjectToCppInteger(env, parametersCpp[knn_jni::INDEX_THREAD_QUANTITY]); omp_set_num_threads(threadCount); } // Add extra parameters that cant be configured with the index factory - if(parametersCpp.find(knn_jni::PARAMETERS) != parametersCpp.end()) { + if (parametersCpp.find(knn_jni::PARAMETERS) != parametersCpp.end()) { jobject subParametersJ = parametersCpp[knn_jni::PARAMETERS]; auto subParametersCpp = jniUtil->ConvertJavaMapToCppMap(env, subParametersJ); SetExtraParameters(jniUtil, env, subParametersCpp, indexWriter.get()); @@ -303,6 +883,128 @@ jbyteArray knn_jni::faiss_wrapper::TrainIndex(knn_jni::JNIUtilInterface * jniUti return ret; } +jbyteArray knn_jni::faiss_wrapper::TrainBinaryIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jobject parametersJ, + jint dimensionJ, jlong trainVectorsPointerJ) { + // First, we need to build the index + if (parametersJ == nullptr) { + throw std::runtime_error("Parameters cannot be null"); + } + + auto parametersCpp = jniUtil->ConvertJavaMapToCppMap(env, parametersJ); + + jobject spaceTypeJ = knn_jni::GetJObjectFromMapOrThrow(parametersCpp, knn_jni::SPACE_TYPE); + std::string spaceTypeCpp(jniUtil->ConvertJavaObjectToCppString(env, spaceTypeJ)); + faiss::MetricType metric = TranslateSpaceToMetric(spaceTypeCpp); + + // Create faiss index + jobject indexDescriptionJ = knn_jni::GetJObjectFromMapOrThrow(parametersCpp, knn_jni::INDEX_DESCRIPTION); + std::string indexDescriptionCpp(jniUtil->ConvertJavaObjectToCppString(env, indexDescriptionJ)); + + std::unique_ptr indexWriter; + indexWriter.reset(faiss::index_binary_factory((int) dimensionJ, indexDescriptionCpp.c_str())); + + // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread + if(parametersCpp.find(knn_jni::INDEX_THREAD_QUANTITY) != parametersCpp.end()) { + auto threadCount = jniUtil->ConvertJavaObjectToCppInteger(env, parametersCpp[knn_jni::INDEX_THREAD_QUANTITY]); + omp_set_num_threads(threadCount); + } + + // Train index if needed + int dim = (int)dimensionJ; + if (dim % 8 != 0) { + throw std::runtime_error("Dimensions should be multiple of 8"); + } + auto *trainingVectorsPointerCpp = reinterpret_cast*>(trainVectorsPointerJ); + int numVectors = (int) (trainingVectorsPointerCpp->size() / (dim / 8)); + if(!indexWriter->is_trained) { + InternalTrainBinaryIndex(indexWriter.get(), numVectors, trainingVectorsPointerCpp->data()); + } + jniUtil->DeleteLocalRef(env, parametersJ); + + // Now that indexWriter is trained, we just load the bytes into an array and return + faiss::VectorIOWriter vectorIoWriter; + faiss::write_index_binary(indexWriter.get(), &vectorIoWriter); + + // Wrap in smart pointer + std::unique_ptr jbytesBuffer; + jbytesBuffer.reset(new jbyte[vectorIoWriter.data.size()]); + int c = 0; + for (auto b : vectorIoWriter.data) { + jbytesBuffer[c++] = (jbyte) b; + } + + jbyteArray ret = jniUtil->NewByteArray(env, vectorIoWriter.data.size()); + jniUtil->SetByteArrayRegion(env, ret, 0, vectorIoWriter.data.size(), jbytesBuffer.get()); + return ret; +} + +jbyteArray knn_jni::faiss_wrapper::TrainByteIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jobject parametersJ, + jint dimensionJ, jlong trainVectorsPointerJ) { + // First, we need to build the index + if (parametersJ == nullptr) { + throw std::runtime_error("Parameters cannot be null"); + } + + auto parametersCpp = jniUtil->ConvertJavaMapToCppMap(env, parametersJ); + + jobject spaceTypeJ = knn_jni::GetJObjectFromMapOrThrow(parametersCpp, knn_jni::SPACE_TYPE); + std::string spaceTypeCpp(jniUtil->ConvertJavaObjectToCppString(env, spaceTypeJ)); + faiss::MetricType metric = TranslateSpaceToMetric(spaceTypeCpp); + + // Create faiss index + jobject indexDescriptionJ = knn_jni::GetJObjectFromMapOrThrow(parametersCpp, knn_jni::INDEX_DESCRIPTION); + std::string indexDescriptionCpp(jniUtil->ConvertJavaObjectToCppString(env, indexDescriptionJ)); + + std::unique_ptr indexWriter; + indexWriter.reset(faiss::index_factory((int) dimensionJ, indexDescriptionCpp.c_str(), metric)); + + // Set thread count if it is passed in as a parameter. Setting this variable will only impact the current thread + if (parametersCpp.find(knn_jni::INDEX_THREAD_QUANTITY) != parametersCpp.end()) { + auto threadCount = jniUtil->ConvertJavaObjectToCppInteger(env, parametersCpp[knn_jni::INDEX_THREAD_QUANTITY]); + omp_set_num_threads(threadCount); + } + + // Add extra parameters that cant be configured with the index factory + if (parametersCpp.find(knn_jni::PARAMETERS) != parametersCpp.end()) { + jobject subParametersJ = parametersCpp[knn_jni::PARAMETERS]; + auto subParametersCpp = jniUtil->ConvertJavaMapToCppMap(env, subParametersJ); + SetExtraParameters(jniUtil, env, subParametersCpp, indexWriter.get()); + jniUtil->DeleteLocalRef(env, subParametersJ); + } + + // Train index if needed + auto *trainingVectorsPointerCpp = reinterpret_cast*>(trainVectorsPointerJ); + int numVectors = trainingVectorsPointerCpp->size()/(int) dimensionJ; + + auto iter = trainingVectorsPointerCpp->begin(); + std::vector trainingFloatVectors(numVectors * dimensionJ); + for(int i=0; i < numVectors * dimensionJ; ++i, ++iter) { + trainingFloatVectors[i] = static_cast(*iter); + } + + if (!indexWriter->is_trained) { + InternalTrainIndex(indexWriter.get(), numVectors, trainingFloatVectors.data()); + } + jniUtil->DeleteLocalRef(env, parametersJ); + + // Now that indexWriter is trained, we just load the bytes into an array and return + faiss::VectorIOWriter vectorIoWriter; + faiss::write_index(indexWriter.get(), &vectorIoWriter); + + // Wrap in smart pointer + std::unique_ptr jbytesBuffer; + jbytesBuffer.reset(new jbyte[vectorIoWriter.data.size()]); + int c = 0; + for (auto b : vectorIoWriter.data) { + jbytesBuffer[c++] = (jbyte) b; + } + + jbyteArray ret = jniUtil->NewByteArray(env, vectorIoWriter.data.size()); + jniUtil->SetByteArrayRegion(env, ret, 0, vectorIoWriter.data.size(), jbytesBuffer.get()); + return ret; +} + + faiss::MetricType TranslateSpaceToMetric(const std::string& spaceType) { if (spaceType == knn_jni::L2) { return faiss::METRIC_L2; @@ -312,6 +1014,11 @@ faiss::MetricType TranslateSpaceToMetric(const std::string& spaceType) { return faiss::METRIC_INNER_PRODUCT; } + // Space type is not used for binary index. Use L2 just to avoid an error. + if (spaceType == knn_jni::HAMMING) { + return faiss::METRIC_L2; + } + throw std::runtime_error("Invalid spaceType"); } @@ -343,7 +1050,7 @@ void SetExtraParameters(knn_jni::JNIUtilInterface * jniUtil, JNIEnv *env, } } -void InternalTrainIndex(faiss::Index * index, faiss::Index::idx_t n, const float* x) { +void InternalTrainIndex(faiss::Index * index, faiss::idx_t n, const float* x) { if (auto * indexIvf = dynamic_cast(index)) { if (indexIvf->quantizer_trains_alone == 2) { InternalTrainIndex(indexIvf->quantizer, n, x); @@ -355,3 +1062,168 @@ void InternalTrainIndex(faiss::Index * index, faiss::Index::idx_t n, const float index->train(n, x); } } + +void InternalTrainBinaryIndex(faiss::IndexBinary * index, faiss::idx_t n, const uint8_t* x) { + if (auto * indexIvf = dynamic_cast(index)) { + indexIvf->make_direct_map(); + } + if (!index->is_trained) { + index->train(n, x); + } +} + +std::unique_ptr buildIDGrouperBitmap(knn_jni::JNIUtilInterface * jniUtil, JNIEnv *env, jintArray parentIdsJ, std::vector* bitmap) { + int *parentIdsArray = jniUtil->GetIntArrayElements(env, parentIdsJ, nullptr); + int parentIdsLength = jniUtil->GetJavaIntArrayLength(env, parentIdsJ); + std::unique_ptr idGrouper = faiss_util::buildIDGrouperBitmap(parentIdsArray, parentIdsLength, bitmap); + jniUtil->ReleaseIntArrayElements(env, parentIdsJ, parentIdsArray, JNI_ABORT); + return idGrouper; +} + +bool isIndexIVFPQL2(faiss::Index * index) { + faiss::Index * candidateIndex = index; + // Unwrap the index if it is wrapped in IndexIDMap. Dynamic cast will "Safely converts pointers and references to + // classes up, down, and sideways along the inheritance hierarchy." It will return a nullptr if the + // cast fails. (ref: https://en.cppreference.com/w/cpp/language/dynamic_cast) + if (auto indexIDMap = dynamic_cast(index)) { + candidateIndex = indexIDMap->index; + } + + // Check if the index is of type IndexIVFPQ. If so, confirm its metric type is + // l2. + if (auto indexIVFPQ = dynamic_cast(candidateIndex)) { + return faiss::METRIC_L2 == indexIVFPQ->metric_type; + } + + return false; +} + +faiss::IndexIVFPQ * extractIVFPQIndex(faiss::Index * index) { + faiss::Index * candidateIndex = index; + if (auto indexIDMap = dynamic_cast(index)) { + candidateIndex = indexIDMap->index; + } + + faiss::IndexIVFPQ * indexIVFPQ; + if ((indexIVFPQ = dynamic_cast(candidateIndex))) { + return indexIVFPQ; + } + + throw std::runtime_error("Unable to extract IVFPQ index. IVFPQ index not present."); +} + +jobjectArray knn_jni::faiss_wrapper::RangeSearch(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jlong indexPointerJ, + jfloatArray queryVectorJ, jfloat radiusJ, jobject methodParamsJ, jint maxResultWindowJ, jintArray parentIdsJ) { + return knn_jni::faiss_wrapper::RangeSearchWithFilter(jniUtil, env, indexPointerJ, queryVectorJ, radiusJ, methodParamsJ, maxResultWindowJ, nullptr, 0, parentIdsJ); +} + +jobjectArray knn_jni::faiss_wrapper::RangeSearchWithFilter(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jlong indexPointerJ, + jfloatArray queryVectorJ, jfloat radiusJ, jobject methodParamsJ, jint maxResultWindowJ, jlongArray filterIdsJ, jint filterIdsTypeJ, jintArray parentIdsJ) { + if (queryVectorJ == nullptr) { + throw std::runtime_error("Query Vector cannot be null"); + } + + auto *indexReader = reinterpret_cast(indexPointerJ); + + if (indexReader == nullptr) { + throw std::runtime_error("Invalid pointer to indexReader"); + } + + float *rawQueryVector = jniUtil->GetFloatArrayElements(env, queryVectorJ, nullptr); + + std::unordered_map methodParams; + if (methodParamsJ != nullptr) { + methodParams = jniUtil->ConvertJavaMapToCppMap(env, methodParamsJ); + } + + // The res will be freed by ~RangeSearchResult() in FAISS + // The second parameter is always true, as lims is allocated by FAISS + faiss::RangeSearchResult res(1, true); + + if (filterIdsJ != nullptr) { + jlong *filteredIdsArray = jniUtil->GetLongArrayElements(env, filterIdsJ, nullptr); + int filterIdsLength = jniUtil->GetJavaLongArrayLength(env, filterIdsJ); + std::unique_ptr idSelector; + if (filterIdsTypeJ == BITMAP) { + idSelector.reset(new faiss::IDSelectorJlongBitmap(filterIdsLength, filteredIdsArray)); + } else { + faiss::idx_t* batchIndices = reinterpret_cast(filteredIdsArray); + idSelector.reset(new faiss::IDSelectorBatch(filterIdsLength, batchIndices)); + } + faiss::SearchParameters *searchParameters; + faiss::SearchParametersHNSW hnswParams; + faiss::SearchParametersIVF ivfParams; + std::unique_ptr idGrouper; + std::vector idGrouperBitmap; + auto hnswReader = dynamic_cast(indexReader->index); + if (hnswReader) { + // Query param ef_search supersedes ef_search provided during index setting. + hnswParams.efSearch = knn_jni::commons::getIntegerMethodParameter(env, jniUtil, methodParams, EF_SEARCH, hnswReader->hnsw.efSearch); + hnswParams.sel = idSelector.get(); + if (parentIdsJ != nullptr) { + idGrouper = buildIDGrouperBitmap(jniUtil, env, parentIdsJ, &idGrouperBitmap); + hnswParams.grp = idGrouper.get(); + } + searchParameters = &hnswParams; + } else { + auto ivfReader = dynamic_cast(indexReader->index); + auto ivfFlatReader = dynamic_cast(indexReader->index); + if(ivfReader || ivfFlatReader) { + ivfParams.sel = idSelector.get(); + searchParameters = &ivfParams; + } + } + try { + indexReader->range_search(1, rawQueryVector, radiusJ, &res, searchParameters); + } catch (...) { + jniUtil->ReleaseFloatArrayElements(env, queryVectorJ, rawQueryVector, JNI_ABORT); + jniUtil->ReleaseLongArrayElements(env, filterIdsJ, filteredIdsArray, JNI_ABORT); + throw; + } + } else { + faiss::SearchParameters *searchParameters = nullptr; + faiss::SearchParametersHNSW hnswParams; + std::unique_ptr idGrouper; + std::vector idGrouperBitmap; + auto hnswReader = dynamic_cast(indexReader->index); + if(hnswReader!= nullptr) { + // Query param ef_search supersedes ef_search provided during index setting. + hnswParams.efSearch = knn_jni::commons::getIntegerMethodParameter(env, jniUtil, methodParams, EF_SEARCH, hnswReader->hnsw.efSearch); + if (parentIdsJ != nullptr) { + idGrouper = buildIDGrouperBitmap(jniUtil, env, parentIdsJ, &idGrouperBitmap); + hnswParams.grp = idGrouper.get(); + } + searchParameters = &hnswParams; + } + try { + indexReader->range_search(1, rawQueryVector, radiusJ, &res, searchParameters); + } catch (...) { + jniUtil->ReleaseFloatArrayElements(env, queryVectorJ, rawQueryVector, JNI_ABORT); + throw; + } + } + + // lims is structured to support batched queries, it has a length of nq + 1 (where nq is the number of queries), + // lims[i] - lims[i-1] gives the number of results for the i-th query. With a single query we used in k-NN, + // res.lims[0] is always 0, and res.lims[1] gives the total number of matching entries found. + int resultSize = res.lims[1]; + + // Limit the result size to maxResultWindowJ so that we don't return more than the max result window + // TODO: In the future, we should prevent this via FAISS's ResultHandler. + if (resultSize > maxResultWindowJ) { + resultSize = maxResultWindowJ; + } + + jclass resultClass = jniUtil->FindClass(env,"org/opensearch/knn/index/query/KNNQueryResult"); + jmethodID allArgs = jniUtil->FindMethod(env, "org/opensearch/knn/index/query/KNNQueryResult", ""); + + jobjectArray results = jniUtil->NewObjectArray(env, resultSize, resultClass, nullptr); + + jobject result; + for (int i = 0; i < resultSize; ++i) { + result = jniUtil->NewObject(env, resultClass, allArgs, res.labels[i], res.distances[i]); + jniUtil->SetObjectArrayElement(env, results, i, result); + } + + return results; +} diff --git a/jni/src/jni_util.cpp b/jni/src/jni_util.cpp index de0ed7856..3ff79752a 100644 --- a/jni/src/jni_util.cpp +++ b/jni/src/jni_util.cpp @@ -61,9 +61,9 @@ void knn_jni::JNIUtil::Initialize(JNIEnv *env) { this->cachedMethods["java/lang/Integer:intValue"] = env->GetMethodID(tempLocalClassRef, "intValue", "()I"); env->DeleteLocalRef(tempLocalClassRef); - tempLocalClassRef = env->FindClass("org/opensearch/knn/index/KNNQueryResult"); - this->cachedClasses["org/opensearch/knn/index/KNNQueryResult"] = (jclass) env->NewGlobalRef(tempLocalClassRef); - this->cachedMethods["org/opensearch/knn/index/KNNQueryResult:"] = env->GetMethodID(tempLocalClassRef, "", "(IF)V"); + tempLocalClassRef = env->FindClass("org/opensearch/knn/index/query/KNNQueryResult"); + this->cachedClasses["org/opensearch/knn/index/query/KNNQueryResult"] = (jclass) env->NewGlobalRef(tempLocalClassRef); + this->cachedMethods["org/opensearch/knn/index/query/KNNQueryResult:"] = env->GetMethodID(tempLocalClassRef, "", "(IF)V"); env->DeleteLocalRef(tempLocalClassRef); } @@ -88,7 +88,7 @@ void knn_jni::JNIUtil::HasExceptionInStack(JNIEnv* env) { this->HasExceptionInStack(env, "Exception in jni occurred"); } -void knn_jni::JNIUtil::HasExceptionInStack(JNIEnv* env, const std::string& message) { +void knn_jni::JNIUtil::HasExceptionInStack(JNIEnv* env, const char* message) { if (env->ExceptionCheck() == JNI_TRUE) { throw std::runtime_error(message); } @@ -223,6 +223,13 @@ int knn_jni::JNIUtil::ConvertJavaObjectToCppInteger(JNIEnv *env, jobject objectJ std::vector knn_jni::JNIUtil::Convert2dJavaObjectArrayToCppFloatVector(JNIEnv *env, jobjectArray array2dJ, int dim) { + std::vector vect; + Convert2dJavaObjectArrayAndStoreToFloatVector(env, array2dJ, dim, &vect); + return vect; +} + +void knn_jni::JNIUtil::Convert2dJavaObjectArrayAndStoreToFloatVector(JNIEnv *env, jobjectArray array2dJ, + int dim, std::vector *vect) { if (array2dJ == nullptr) { throw std::runtime_error("Array cannot be null"); @@ -231,7 +238,6 @@ std::vector knn_jni::JNIUtil::Convert2dJavaObjectArrayToCppFloatVector(JN int numVectors = env->GetArrayLength(array2dJ); this->HasExceptionInStack(env); - std::vector floatVectorCpp; for (int i = 0; i < numVectors; ++i) { auto vectorArray = (jfloatArray)env->GetObjectArrayElement(array2dJ, i); this->HasExceptionInStack(env, "Unable to get object array element"); @@ -246,14 +252,78 @@ std::vector knn_jni::JNIUtil::Convert2dJavaObjectArrayToCppFloatVector(JN throw std::runtime_error("Unable to get float array elements"); } - for(int j = 0; j < dim; ++j) { - floatVectorCpp.push_back(vector[j]); + for (int j = 0; j < dim; ++j) { + vect->push_back(vector[j]); } env->ReleaseFloatArrayElements(vectorArray, vector, JNI_ABORT); + } // End for + this->HasExceptionInStack(env); + env->DeleteLocalRef(array2dJ); +} + +void knn_jni::JNIUtil::Convert2dJavaObjectArrayAndStoreToBinaryVector(JNIEnv *env, jobjectArray array2dJ, + int dim, std::vector *vect) { + + if (array2dJ == nullptr) { + throw std::runtime_error("Array cannot be null"); + } + + int numVectors = env->GetArrayLength(array2dJ); + this->HasExceptionInStack(env); + + for (int i = 0; i < numVectors; ++i) { + auto vectorArray = (jbyteArray)env->GetObjectArrayElement(array2dJ, i); + this->HasExceptionInStack(env, "Unable to get object array element"); + + if (dim != env->GetArrayLength(vectorArray)) { + throw std::runtime_error("Dimension of vectors is inconsistent"); + } + + uint8_t* vector = reinterpret_cast(env->GetByteArrayElements(vectorArray, nullptr)); + if (vector == nullptr) { + this->HasExceptionInStack(env); + throw std::runtime_error("Unable to get byte array elements"); + } + + for (int j = 0; j < dim; ++j) { + vect->push_back(vector[j]); + } + env->ReleaseByteArrayElements(vectorArray, reinterpret_cast(vector), JNI_ABORT); + } + this->HasExceptionInStack(env); + env->DeleteLocalRef(array2dJ); +} + +void knn_jni::JNIUtil::Convert2dJavaObjectArrayAndStoreToByteVector(JNIEnv *env, jobjectArray array2dJ, + int dim, std::vector *vect) { + + if (array2dJ == nullptr) { + throw std::runtime_error("Array cannot be null"); + } + + int numVectors = env->GetArrayLength(array2dJ); + this->HasExceptionInStack(env, "Unable to get array length"); + + for (int i = 0; i < numVectors; ++i) { + auto vectorArray = static_cast(env->GetObjectArrayElement(array2dJ, i)); + this->HasExceptionInStack(env, "Unable to get object array element"); + + if (dim != env->GetArrayLength(vectorArray)) { + env->DeleteLocalRef(array2dJ); + throw std::runtime_error("Dimension of vectors is inconsistent"); + } + + int8_t* vector = reinterpret_cast(env->GetByteArrayElements(vectorArray, nullptr)); + if (vector == nullptr) { + this->HasExceptionInStack(env); + throw std::runtime_error("Unable to get byte array elements"); + } + + vect->insert(vect->end(), vector, vector + dim); + env->ReleaseByteArrayElements(vectorArray, reinterpret_cast(vector), JNI_ABORT); } this->HasExceptionInStack(env); env->DeleteLocalRef(array2dJ); - return floatVectorCpp; } std::vector knn_jni::JNIUtil::ConvertJavaIntArrayToCppIntVector(JNIEnv *env, jintArray arrayJ) { @@ -297,6 +367,23 @@ int knn_jni::JNIUtil::GetInnerDimensionOf2dJavaFloatArray(JNIEnv *env, jobjectAr return dim; } +int knn_jni::JNIUtil::GetInnerDimensionOf2dJavaByteArray(JNIEnv *env, jobjectArray array2dJ) { + + if (array2dJ == nullptr) { + throw std::runtime_error("Array cannot be null"); + } + + if (env->GetArrayLength(array2dJ) <= 0) { + return 0; + } + + auto vectorArray = (jbyteArray)env->GetObjectArrayElement(array2dJ, 0); + this->HasExceptionInStack(env); + int dim = env->GetArrayLength(vectorArray); + this->HasExceptionInStack(env); + return dim; +} + int knn_jni::JNIUtil::GetJavaObjectArrayLength(JNIEnv *env, jobjectArray arrayJ) { if (arrayJ == nullptr) { @@ -319,6 +406,17 @@ int knn_jni::JNIUtil::GetJavaIntArrayLength(JNIEnv *env, jintArray arrayJ) { return length; } +int knn_jni::JNIUtil::GetJavaLongArrayLength(JNIEnv *env, jlongArray arrayJ) { + + if (arrayJ == nullptr) { + throw std::runtime_error("Array cannot be null"); + } + + int length = env->GetArrayLength(arrayJ); + this->HasExceptionInStack(env, "Unable to get array length"); + return length; +} + int knn_jni::JNIUtil::GetJavaBytesArrayLength(JNIEnv *env, jbyteArray arrayJ) { if (arrayJ == nullptr) { @@ -376,6 +474,17 @@ jint * knn_jni::JNIUtil::GetIntArrayElements(JNIEnv *env, jintArray array, jbool return intArray; } +jlong * knn_jni::JNIUtil::GetLongArrayElements(JNIEnv *env, jlongArray array, jboolean * isCopy) { + // Lets check for error here + jlong * longArray = env->GetLongArrayElements(array, isCopy); + if (longArray == nullptr) { + this->HasExceptionInStack(env, "Unable to get long array"); + throw std::runtime_error("Unable to get long array"); + } + + return longArray; +} + jobject knn_jni::JNIUtil::GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index) { jobject object = env->GetObjectArrayElement(array, index); this->HasExceptionInStack(env, "Unable to get object"); @@ -424,6 +533,10 @@ void knn_jni::JNIUtil::ReleaseIntArrayElements(JNIEnv *env, jintArray array, jin env->ReleaseIntArrayElements(array, elems, mode); } +void knn_jni::JNIUtil::ReleaseLongArrayElements(JNIEnv *env, jlongArray array, jlong *elems, jint mode) { + env->ReleaseLongArrayElements(array, elems, mode); +} + void knn_jni::JNIUtil::SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject val) { env->SetObjectArrayElement(array, index, val); this->HasExceptionInStack(env, "Unable to set object array element"); @@ -434,11 +547,51 @@ void knn_jni::JNIUtil::SetByteArrayRegion(JNIEnv *env, jbyteArray array, jsize s this->HasExceptionInStack(env, "Unable to set byte array region"); } +jobject knn_jni::JNIUtil::GetObjectField(JNIEnv * env, jobject obj, jfieldID fieldID) { + return env->GetObjectField(obj, fieldID); +} + +jclass knn_jni::JNIUtil::FindClassFromJNIEnv(JNIEnv * env, const char *name) { + return env->FindClass(name); +} + +jmethodID knn_jni::JNIUtil::GetMethodID(JNIEnv * env, jclass clazz, const char *name, const char *sig) { + return env->GetMethodID(clazz, name, sig); +} + +jfieldID knn_jni::JNIUtil::GetFieldID(JNIEnv * env, jclass clazz, const char *name, const char *sig) { + return env->GetFieldID(clazz, name, sig); +} + +jint knn_jni::JNIUtil::CallNonvirtualIntMethodA(JNIEnv * env, jobject obj, jclass clazz, + jmethodID methodID, jvalue* args) { + return env->CallNonvirtualIntMethodA(obj, clazz, methodID, args); +} + +jlong knn_jni::JNIUtil::CallNonvirtualLongMethodA(JNIEnv * env, jobject obj, jclass clazz, + jmethodID methodID, jvalue* args) { + return env->CallNonvirtualLongMethodA(obj, clazz, methodID, args); +} + +void knn_jni::JNIUtil::CallNonvirtualVoidMethodA(JNIEnv * env, jobject obj, jclass clazz, + jmethodID methodID, jvalue* args) { + return env->CallNonvirtualVoidMethodA(obj, clazz, methodID, args); +} + +void * knn_jni::JNIUtil::GetPrimitiveArrayCritical(JNIEnv * env, jarray array, jboolean *isCopy) { + return env->GetPrimitiveArrayCritical(array, isCopy); +} + +void knn_jni::JNIUtil::ReleasePrimitiveArrayCritical(JNIEnv * env, jarray array, void *carray, jint mode) { + return env->ReleasePrimitiveArrayCritical(array, carray, mode); +} + jobject knn_jni::GetJObjectFromMapOrThrow(std::unordered_map map, std::string key) { - if(map.find(key) == map.end()) { - throw std::runtime_error(key + " not found"); + auto it = map.find(key); + if (it != map.end()) { + return it->second; } - return map[key]; + throw std::runtime_error(key + " not found"); } //TODO: This potentially should use const char * @@ -459,6 +612,7 @@ const std::string knn_jni::LINF = "linf"; const std::string knn_jni::COSINESIMIL = "cosinesimil"; const std::string knn_jni::INNER_PRODUCT = "innerproduct"; const std::string knn_jni::NEG_DOT_PRODUCT = "negdotprod"; +const std::string knn_jni::HAMMING = "hamming"; const std::string knn_jni::NPROBES = "nprobes"; const std::string knn_jni::COARSE_QUANTIZER = "coarse_quantizer"; diff --git a/jni/src/nmslib_wrapper.cpp b/jni/src/nmslib_wrapper.cpp index 083850e88..e8fc77fdb 100644 --- a/jni/src/nmslib_wrapper.cpp +++ b/jni/src/nmslib_wrapper.cpp @@ -11,6 +11,9 @@ #include "jni_util.h" #include "nmslib_wrapper.h" +#include "nmslib_stream_support.h" + +#include "commons.h" #include "init.h" #include "index.h" @@ -24,237 +27,360 @@ #include #include +#include "hnswquery.h" +#include "method/hnsw.h" -std::string TranslateSpaceType(const std::string& spaceType); +std::string TranslateSpaceType(const std::string &spaceType); -void knn_jni::nmslib_wrapper::CreateIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jintArray idsJ, - jobjectArray vectorsJ, jstring indexPathJ, jobject parametersJ) { +// We do not use label functionality of nmslib so we pass default label. Setting as a const allows us to avoid a few +// allocations +const similarity::LabelType DEFAULT_LABEL = -1; - if (idsJ == nullptr) { - throw std::runtime_error("IDs cannot be null"); - } +void knn_jni::nmslib_wrapper::CreateIndex(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jintArray idsJ, + jlong vectorsAddressJ, jint dimJ, + jobject output, jobject parametersJ) { - if (vectorsJ == nullptr) { - throw std::runtime_error("Vectors cannot be null"); - } + if (idsJ == nullptr) { + throw std::runtime_error("IDs cannot be null"); + } - if (indexPathJ == nullptr) { - throw std::runtime_error("Index path cannot be null"); - } + if (vectorsAddressJ <= 0) { + throw std::runtime_error("VectorsAddress cannot be less than 0"); + } - if (parametersJ == nullptr) { - throw std::runtime_error("Parameters cannot be null"); - } + if (dimJ <= 0) { + throw std::runtime_error("Vectors dimensions cannot be less than or equal to 0"); + } - // Handle parameters - auto parametersCpp = jniUtil->ConvertJavaMapToCppMap(env, parametersJ); - std::vector indexParameters; + if (output == nullptr) { + throw std::runtime_error("Index output stream cannot be null"); + } - // Algorithm parameters will be in a sub map - if(parametersCpp.find(knn_jni::PARAMETERS) != parametersCpp.end()) { - jobject subParametersJ = parametersCpp[knn_jni::PARAMETERS]; - auto subParametersCpp = jniUtil->ConvertJavaMapToCppMap(env, subParametersJ); + if (parametersJ == nullptr) { + throw std::runtime_error("Parameters cannot be null"); + } - if(subParametersCpp.find(knn_jni::EF_CONSTRUCTION) != subParametersCpp.end()) { - auto efConstruction = jniUtil->ConvertJavaObjectToCppInteger(env, subParametersCpp[knn_jni::EF_CONSTRUCTION]); - indexParameters.push_back(knn_jni::EF_CONSTRUCTION_NMSLIB + "=" + std::to_string(efConstruction)); - } + // Handle parameters + auto parametersCpp = jniUtil->ConvertJavaMapToCppMap(env, parametersJ); + std::vector indexParameters; - if(subParametersCpp.find(knn_jni::M) != subParametersCpp.end()) { - auto m = jniUtil->ConvertJavaObjectToCppInteger(env, subParametersCpp[knn_jni::M]); - indexParameters.push_back(knn_jni::M_NMSLIB + "=" + std::to_string(m)); - } + // Algorithm parameters will be in a sub map + if (parametersCpp.find(knn_jni::PARAMETERS) != parametersCpp.end()) { + jobject subParametersJ = parametersCpp[knn_jni::PARAMETERS]; + auto subParametersCpp = jniUtil->ConvertJavaMapToCppMap(env, subParametersJ); - jniUtil->DeleteLocalRef(env, subParametersJ); + if (subParametersCpp.find(knn_jni::EF_CONSTRUCTION) != subParametersCpp.end()) { + auto efConstruction = jniUtil->ConvertJavaObjectToCppInteger(env, subParametersCpp[knn_jni::EF_CONSTRUCTION]); + indexParameters.push_back(knn_jni::EF_CONSTRUCTION_NMSLIB + "=" + std::to_string(efConstruction)); } - if(parametersCpp.find(knn_jni::INDEX_THREAD_QUANTITY) != parametersCpp.end()) { - auto indexThreadQty = jniUtil->ConvertJavaObjectToCppInteger(env, parametersCpp[knn_jni::INDEX_THREAD_QUANTITY]); - indexParameters.push_back(knn_jni::INDEX_THREAD_QUANTITY + "=" + std::to_string(indexThreadQty)); + if (subParametersCpp.find(knn_jni::M) != subParametersCpp.end()) { + auto m = jniUtil->ConvertJavaObjectToCppInteger(env, subParametersCpp[knn_jni::M]); + indexParameters.push_back(knn_jni::M_NMSLIB + "=" + std::to_string(m)); } - jniUtil->DeleteLocalRef(env, parametersJ); - - // Get the path to save the index - std::string indexPathCpp(jniUtil->ConvertJavaStringToCppString(env, indexPathJ)); - - // Get space type for this index - jobject spaceTypeJ = knn_jni::GetJObjectFromMapOrThrow(parametersCpp, knn_jni::SPACE_TYPE); - std::string spaceTypeCpp(jniUtil->ConvertJavaObjectToCppString(env, spaceTypeJ)); - spaceTypeCpp = TranslateSpaceType(spaceTypeCpp); - - std::unique_ptr> space; - space.reset(similarity::SpaceFactoryRegistry::Instance().CreateSpace(spaceTypeCpp,similarity::AnyParams())); + jniUtil->DeleteLocalRef(env, subParametersJ); + } + + if (parametersCpp.find(knn_jni::INDEX_THREAD_QUANTITY) != parametersCpp.end()) { + auto indexThreadQty = jniUtil->ConvertJavaObjectToCppInteger(env, parametersCpp[knn_jni::INDEX_THREAD_QUANTITY]); + indexParameters.push_back(knn_jni::INDEX_THREAD_QUANTITY + "=" + std::to_string(indexThreadQty)); + } + + jniUtil->DeleteLocalRef(env, parametersJ); + + // Get space type for this index + jobject spaceTypeJ = knn_jni::GetJObjectFromMapOrThrow(parametersCpp, knn_jni::SPACE_TYPE); + std::string spaceTypeCpp(jniUtil->ConvertJavaObjectToCppString(env, spaceTypeJ)); + spaceTypeCpp = TranslateSpaceType(spaceTypeCpp); + + std::unique_ptr> space; + space.reset(similarity::SpaceFactoryRegistry::Instance().CreateSpace(spaceTypeCpp, similarity::AnyParams())); + + // Get number of ids and vectors and dimension + auto *inputVectors = reinterpret_cast *>(vectorsAddressJ); + int dim = (int) dimJ; + // The number of vectors can be int here because a lucene segment number of total docs never crosses INT_MAX value + int numVectors = (int) (inputVectors->size() / (uint64_t) dim); + if (numVectors == 0) { + throw std::runtime_error("Number of vectors cannot be 0"); + } + + int numIds = jniUtil->GetJavaIntArrayLength(env, idsJ); + if (numIds != numVectors) { + throw std::runtime_error("Number of IDs does not match number of vectors"); + } + + // Read dataset + similarity::ObjectVector dataset; + dataset.reserve(numVectors); + int *idsCpp; + try { + // Read in data set + idsCpp = jniUtil->GetIntArrayElements(env, idsJ, nullptr); + size_t vectorSizeInBytes = dim * sizeof(float); + // vectorPointer needs to be unsigned long long, this will ensure that out of range doesn't happen for this pointer + // when the values of numVectors * dim becomes very large. + // Example: for 10M vectors of 1536 dim vectorPointer max value will be ~15.3B which is already > range of ints. + // keeping it unsigned long long we will never go above the range. + unsigned long long vectorPointer = 0; + + // Allocate a large buffer that will contain all the vectors. Allocating the objects in one large buffer as + // opposed to individually will prevent heap fragmentation. We have observed that allocating individual + // objects causes RSS to rise throughout the lifetime of a process + // (see https://github.com/opensearch-project/k-NN/issues/772 and + // https://github.com/opensearch-project/k-NN/issues/72). This is because, in typical systems, small + // allocations will reside on some kind of heap managed by an allocator. Once freed, the allocator does not + // always return the memory to the OS. If the heap gets fragmented, this will cause the allocator + // to ask for more memory, causing RSS to grow. On large allocations (> 128 kb), most allocators will + // internally use mmap. Once freed, unmap will be called, which will immediately return memory to the OS + // which in turn prevents RSS from growing out of control. Wrap with a smart pointer so that buffer will be + // freed once variable goes out of scope. For reference, the code that specifies the layout of the buffer can be + // found: https://github.com/nmslib/nmslib/blob/v2.1.1/similarity_search/include/object.h#L61-L75 + std::unique_ptr objectBuffer + (new char[(similarity::ID_SIZE + similarity::LABEL_SIZE + similarity::DATALENGTH_SIZE + vectorSizeInBytes) + * numVectors]); + char *ptr = objectBuffer.get(); + for (int i = 0; i < numVectors; i++) { + dataset.push_back(new similarity::Object(ptr)); + + memcpy(ptr, &idsCpp[i], similarity::ID_SIZE); + ptr += similarity::ID_SIZE; + memcpy(ptr, &DEFAULT_LABEL, similarity::LABEL_SIZE); + ptr += similarity::LABEL_SIZE; + memcpy(ptr, &vectorSizeInBytes, similarity::DATALENGTH_SIZE); + ptr += similarity::DATALENGTH_SIZE; + + memcpy(ptr, &(inputVectors->at(vectorPointer)), vectorSizeInBytes); + ptr += vectorSizeInBytes; + vectorPointer += dim; + } + JNIReleaseElements release_int_array_elements {[=](){ + jniUtil->ReleaseIntArrayElements(env, idsJ, idsCpp, JNI_ABORT); + }}; + + // Releasing the vectorsAddressJ memory as that is not required once we have created the index. + // This is not the ideal approach, please refer this gh issue for long term solution: + // https://github.com/opensearch-project/k-NN/issues/1600 + //commons::freeVectorData(vectorsAddressJ); + delete inputVectors; + + std::unique_ptr> index; + index.reset(similarity::MethodFactoryRegistry::Instance().CreateMethod(false, + "hnsw", + spaceTypeCpp, + *(space), + dataset)); + index->CreateIndex(similarity::AnyParams(indexParameters)); + + knn_jni::stream::NativeEngineIndexOutputMediator mediator {jniUtil, env, output}; + knn_jni::stream::NmslibOpenSearchIOWriter writer {&mediator}; + + if (auto hnswFloatIndex = dynamic_cast *>(index.get())) { + hnswFloatIndex->SaveIndexWithStream(writer); + } else { + throw std::runtime_error("We only support similarity::Hnsw in NMSLIB."); + } - // Get number of ids and vectors and dimension - int numVectors = jniUtil->GetJavaObjectArrayLength(env, vectorsJ); - int numIds = jniUtil->GetJavaIntArrayLength(env, idsJ); - if (numIds != numVectors) { - throw std::runtime_error("Number of IDs does not match number of vectors"); + for (auto it : dataset) { + delete it; } - int dim = jniUtil->GetInnerDimensionOf2dJavaFloatArray(env, vectorsJ); - - // Read dataset - similarity::ObjectVector dataset; - int* idsCpp; - try { - // Read in data set - idsCpp = jniUtil->GetIntArrayElements(env, idsJ, nullptr); - - float* floatArrayCpp; - jfloatArray floatArrayJ; - for (int i = 0; i < numVectors; i++) { - floatArrayJ = (jfloatArray)jniUtil->GetObjectArrayElement(env, vectorsJ, i); - - if (dim != jniUtil->GetJavaFloatArrayLength(env, floatArrayJ)) { - throw std::runtime_error("Dimension of vectors is inconsistent"); - } - - floatArrayCpp = jniUtil->GetFloatArrayElements(env, floatArrayJ, nullptr); - - dataset.push_back(new similarity::Object(idsCpp[i], -1, dim*sizeof(float), floatArrayCpp)); - jniUtil->ReleaseFloatArrayElements(env, floatArrayJ, floatArrayCpp, JNI_ABORT); - } - jniUtil->ReleaseIntArrayElements(env, idsJ, idsCpp, JNI_ABORT); - - std::unique_ptr> index; - index.reset(similarity::MethodFactoryRegistry::Instance().CreateMethod(false, "hnsw", spaceTypeCpp, *(space), dataset)); - index->CreateIndex(similarity::AnyParams(indexParameters)); - index->SaveIndex(indexPathCpp); - - for (auto & it : dataset) { - delete it; - } - } catch (...) { - for (auto & it : dataset) { - delete it; - } - - jniUtil->ReleaseIntArrayElements(env, idsJ, idsCpp, JNI_ABORT); - throw; + } catch (...) { + for (auto it : dataset) { + delete it; } + + throw; + } } -jlong knn_jni::nmslib_wrapper::LoadIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jstring indexPathJ, +jlong knn_jni::nmslib_wrapper::LoadIndex(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jstring indexPathJ, jobject parametersJ) { - if (indexPathJ == nullptr) { - throw std::runtime_error("Index path cannot be null"); - } + if (indexPathJ == nullptr) { + throw std::runtime_error("Index path cannot be null"); + } - if (parametersJ == nullptr) { - throw std::runtime_error("Parameters cannot be null"); - } + if (parametersJ == nullptr) { + throw std::runtime_error("Parameters cannot be null"); + } - std::string indexPathCpp(jniUtil->ConvertJavaStringToCppString(env, indexPathJ)); + std::string indexPathCpp(jniUtil->ConvertJavaStringToCppString(env, indexPathJ)); - auto parametersCpp = jniUtil->ConvertJavaMapToCppMap(env, parametersJ); + auto parametersCpp = jniUtil->ConvertJavaMapToCppMap(env, parametersJ); - // Get space type for this index - jobject spaceTypeJ = knn_jni::GetJObjectFromMapOrThrow(parametersCpp, knn_jni::SPACE_TYPE); - std::string spaceTypeCpp(jniUtil->ConvertJavaObjectToCppString(env, spaceTypeJ)); - spaceTypeCpp = TranslateSpaceType(spaceTypeCpp); + // Get space type for this index + jobject spaceTypeJ = knn_jni::GetJObjectFromMapOrThrow(parametersCpp, knn_jni::SPACE_TYPE); + std::string spaceTypeCpp(jniUtil->ConvertJavaObjectToCppString(env, spaceTypeJ)); + spaceTypeCpp = TranslateSpaceType(spaceTypeCpp); - // Parse query params - std::vector queryParams; + // Parse query params + std::vector queryParams; - if(parametersCpp.find("efSearch") != parametersCpp.end()) { - auto efSearch = std::to_string(jniUtil->ConvertJavaObjectToCppInteger(env, parametersCpp["efSearch"])); - queryParams.push_back("efSearch=" + efSearch); - } + auto it = parametersCpp.find("efSearch"); + if (it != parametersCpp.end()) { + auto efSearch = std::to_string(jniUtil->ConvertJavaObjectToCppInteger(env, it->second)); + queryParams.push_back("efSearch=" + efSearch); + } - // Load index - knn_jni::nmslib_wrapper::IndexWrapper * indexWrapper; - try { - indexWrapper = new knn_jni::nmslib_wrapper::IndexWrapper(spaceTypeCpp); - indexWrapper->index->LoadIndex(indexPathCpp); - indexWrapper->index->SetQueryTimeParams(similarity::AnyParams(queryParams)); - } catch (...) { - delete indexWrapper; - throw; - } + // Load index + knn_jni::nmslib_wrapper::IndexWrapper *indexWrapper = nullptr; + try { + indexWrapper = new knn_jni::nmslib_wrapper::IndexWrapper(spaceTypeCpp); + indexWrapper->index->LoadIndex(indexPathCpp); + indexWrapper->index->SetQueryTimeParams(similarity::AnyParams(queryParams)); + } catch (...) { + delete indexWrapper; + throw; + } - return (jlong) indexWrapper; + return (jlong) indexWrapper; } -jobjectArray knn_jni::nmslib_wrapper::QueryIndex(knn_jni::JNIUtilInterface * jniUtil, JNIEnv * env, jlong indexPointerJ, - jfloatArray queryVectorJ, jint kJ) { - - if (queryVectorJ == nullptr) { - throw std::runtime_error("Query Vector cannot be null"); - } - - if (indexPointerJ == 0) { - throw std::runtime_error("Invalid pointer to index"); +jlong knn_jni::nmslib_wrapper::LoadIndexWithStream(knn_jni::JNIUtilInterface *jniUtil, + JNIEnv *env, + jobject readStream, + jobject parametersJ) { + if (readStream == nullptr) { + throw std::runtime_error("Read stream cannot be null"); + } + + if (parametersJ == nullptr) { + throw std::runtime_error("Parameters cannot be null"); + } + + auto parametersCpp = jniUtil->ConvertJavaMapToCppMap(env, parametersJ); + + // Get space type for this index + jobject spaceTypeJ = knn_jni::GetJObjectFromMapOrThrow(parametersCpp, knn_jni::SPACE_TYPE); + std::string spaceTypeCpp(jniUtil->ConvertJavaObjectToCppString(env, spaceTypeJ)); + spaceTypeCpp = TranslateSpaceType(spaceTypeCpp); + + // Parse query params + std::vector queryParams; + + auto it = parametersCpp.find("efSearch"); + if (it != parametersCpp.end()) { + auto efSearch = std::to_string(jniUtil->ConvertJavaObjectToCppInteger(env, it->second)); + queryParams.push_back("efSearch=" + efSearch); + } + + // Create a mediator locally. + // Note that `indexInput` is `IndexInputWithBuffer` type. + knn_jni::stream::NativeEngineIndexInputMediator mediator{jniUtil, env, readStream}; + + knn_jni::stream::NmslibOpenSearchIOReader ioReader {&mediator}; + + // Load index + knn_jni::nmslib_wrapper::IndexWrapper *indexWrapper = nullptr; + try { + indexWrapper = new knn_jni::nmslib_wrapper::IndexWrapper(spaceTypeCpp); + indexWrapper->index->SetQueryTimeParams(similarity::AnyParams(queryParams)); + + if (auto hnswFloatIndex = dynamic_cast *>(indexWrapper->index.get())) { + hnswFloatIndex->LoadIndexWithStream(ioReader); + } else { + throw std::runtime_error("We only support similarity::Hnsw in NMSLIB."); } + } catch (...) { + delete indexWrapper; + throw; + } - auto *indexWrapper = reinterpret_cast(indexPointerJ); - - int dim = jniUtil->GetJavaFloatArrayLength(env, queryVectorJ); + return (jlong) indexWrapper; +} - float* rawQueryvector = jniUtil->GetFloatArrayElements(env, queryVectorJ, nullptr); // Have to call release on this +jobjectArray knn_jni::nmslib_wrapper::QueryIndex(knn_jni::JNIUtilInterface *jniUtil, JNIEnv *env, jlong indexPointerJ, + jfloatArray queryVectorJ, jint kJ, jobject methodParamsJ) { - std::unique_ptr queryObject; - try { - queryObject.reset(new similarity::Object(-1, -1, dim*sizeof(float), rawQueryvector)); - } catch (...) { - jniUtil->ReleaseFloatArrayElements(env, queryVectorJ, rawQueryvector, JNI_ABORT); - throw; - } - jniUtil->ReleaseFloatArrayElements(env, queryVectorJ, rawQueryvector, JNI_ABORT); + if (queryVectorJ == nullptr) { + throw std::runtime_error("Query Vector cannot be null"); + } - similarity::KNNQuery knnQuery(*(indexWrapper->space), queryObject.get(), kJ); - indexWrapper->index->Search(&knnQuery); + if (indexPointerJ == 0) { + throw std::runtime_error("Invalid pointer to index"); + } - std::unique_ptr> neighbors(knnQuery.Result()->Clone()); - int resultSize = neighbors->Size(); + auto *indexWrapper = reinterpret_cast(indexPointerJ); - jclass resultClass = jniUtil->FindClass(env,"org/opensearch/knn/index/KNNQueryResult"); - jmethodID allArgs = jniUtil->FindMethod(env, "org/opensearch/knn/index/KNNQueryResult", ""); + int dim = jniUtil->GetJavaFloatArrayLength(env, queryVectorJ); - jobjectArray results = jniUtil->NewObjectArray(env, resultSize, resultClass, nullptr); + float *rawQueryvector = jniUtil->GetFloatArrayElements(env, queryVectorJ, nullptr); // Have to call release on this - jobject result; - float distance; - long id; - for(int i = 0; i < resultSize; ++i) { - distance = neighbors->TopDistance(); - id = neighbors->Pop()->id(); - result = jniUtil->NewObject(env, resultClass, allArgs, id, distance); - jniUtil->SetObjectArrayElement(env, results, i, result); - } - return results; + std::unique_ptr queryObject; + try { + queryObject.reset(new similarity::Object(-1, -1, dim * sizeof(float), rawQueryvector)); + } catch (...) { + jniUtil->ReleaseFloatArrayElements(env, queryVectorJ, rawQueryvector, JNI_ABORT); + throw; + } + + jniUtil->ReleaseFloatArrayElements(env, queryVectorJ, rawQueryvector, JNI_ABORT); + std::unordered_map methodParams; + if (methodParamsJ != nullptr) { + methodParams = jniUtil->ConvertJavaMapToCppMap(env, methodParamsJ); + } + + int queryEfSearch = knn_jni::commons::getIntegerMethodParameter(env, jniUtil, methodParams, EF_SEARCH, -1); + std::unique_ptr> query; + std::unique_ptr> neighbors; + if (queryEfSearch == -1) { + query.reset(new similarity::KNNQuery(*(indexWrapper->space), queryObject.get(), kJ)); + } else { + query.reset(new similarity::HNSWQuery(*(indexWrapper->space), queryObject.get(), kJ, queryEfSearch)); + } + + indexWrapper->index->Search(query.get()); + neighbors.reset(query->Result()->Clone()); + + int resultSize = neighbors->Size(); + jclass resultClass = jniUtil->FindClass(env, "org/opensearch/knn/index/query/KNNQueryResult"); + jmethodID allArgs = jniUtil->FindMethod(env, "org/opensearch/knn/index/query/KNNQueryResult", ""); + + jobjectArray results = jniUtil->NewObjectArray(env, resultSize, resultClass, nullptr); + + jobject result; + float distance; + long id; + for (int i = 0; i < resultSize; ++i) { + distance = neighbors->TopDistance(); + id = neighbors->Pop()->id(); + result = jniUtil->NewObject(env, resultClass, allArgs, id, distance); + jniUtil->SetObjectArrayElement(env, results, i, result); + } + + return results; } void knn_jni::nmslib_wrapper::Free(jlong indexPointerJ) { - auto *indexWrapper = reinterpret_cast(indexPointerJ); - delete indexWrapper; + auto *indexWrapper = reinterpret_cast(indexPointerJ); + delete indexWrapper; } void knn_jni::nmslib_wrapper::InitLibrary() { - similarity::initLibrary(); + similarity::initLibrary(); } -std::string TranslateSpaceType(const std::string& spaceType) { - if (spaceType == knn_jni::L2) { - return spaceType; - } +std::string TranslateSpaceType(const std::string &spaceType) { + if (spaceType == knn_jni::L2) { + return spaceType; + } - if (spaceType == knn_jni::L1) { - return spaceType; - } + if (spaceType == knn_jni::L1) { + return spaceType; + } - if (spaceType == knn_jni::LINF) { - return spaceType; - } + if (spaceType == knn_jni::LINF) { + return spaceType; + } - if (spaceType == knn_jni::COSINESIMIL) { - return spaceType; - } + if (spaceType == knn_jni::COSINESIMIL) { + return spaceType; + } - if (spaceType == knn_jni::INNER_PRODUCT) { - return knn_jni::NEG_DOT_PRODUCT; - } + if (spaceType == knn_jni::INNER_PRODUCT) { + return knn_jni::NEG_DOT_PRODUCT; + } - throw std::runtime_error("Invalid spaceType"); + throw std::runtime_error("Invalid spaceType"); } diff --git a/jni/src/org_opensearch_knn_jni_FaissService.cpp b/jni/src/org_opensearch_knn_jni_FaissService.cpp index 543ce8ec4..836774402 100644 --- a/jni/src/org_opensearch_knn_jni_FaissService.cpp +++ b/jni/src/org_opensearch_knn_jni_FaissService.cpp @@ -13,11 +13,11 @@ #include -#include #include #include "faiss_wrapper.h" #include "jni_util.h" +#include "faiss_stream_support.h" static knn_jni::JNIUtil jniUtil; static const jint KNN_FAISS_JNI_VERSION = JNI_VERSION_1_1; @@ -40,47 +40,304 @@ void JNI_OnUnload(JavaVM *vm, void *reserved) { jniUtil.Uninitialize(env); } -JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createIndex(JNIEnv * env, jclass cls, jintArray idsJ, - jobjectArray vectorsJ, jstring indexPathJ, - jobject parametersJ) +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initIndex(JNIEnv * env, jclass cls, + jlong numDocs, jint dimJ, + jobject parametersJ) { try { - knn_jni::faiss_wrapper::CreateIndex(&jniUtil, env, idsJ, vectorsJ, indexPathJ, parametersJ); + std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); + knn_jni::faiss_wrapper::IndexService indexService(std::move(faissMethods)); + return knn_jni::faiss_wrapper::InitIndex(&jniUtil, env, numDocs, dimJ, parametersJ, &indexService); } catch (...) { jniUtil.CatchCppExceptionAndThrowJava(env); } + return (jlong)0; } -JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createIndexFromTemplate(JNIEnv * env, jclass cls, +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initBinaryIndex(JNIEnv * env, jclass cls, + jlong numDocs, jint dimJ, + jobject parametersJ) +{ + try { + std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); + knn_jni::faiss_wrapper::BinaryIndexService binaryIndexService(std::move(faissMethods)); + return knn_jni::faiss_wrapper::InitIndex(&jniUtil, env, numDocs, dimJ, parametersJ, &binaryIndexService); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return (jlong)0; +} + +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initByteIndex(JNIEnv * env, jclass cls, + jlong numDocs, jint dimJ, + jobject parametersJ) +{ + try { + std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); + knn_jni::faiss_wrapper::ByteIndexService byteIndexService(std::move(faissMethods)); + return knn_jni::faiss_wrapper::InitIndex(&jniUtil, env, numDocs, dimJ, parametersJ, &byteIndexService); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return (jlong)0; +} + +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_insertToIndex(JNIEnv * env, jclass cls, jintArray idsJ, + jlong vectorsAddressJ, jint dimJ, + jlong indexAddress, jint threadCount) +{ + try { + std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); + knn_jni::faiss_wrapper::IndexService indexService(std::move(faissMethods)); + knn_jni::faiss_wrapper::InsertToIndex(&jniUtil, env, idsJ, vectorsAddressJ, dimJ, indexAddress, threadCount, &indexService); + } catch (...) { + // NOTE: ADDING DELETE STATEMENT HERE CAUSES A CRASH! + jniUtil.CatchCppExceptionAndThrowJava(env); + } +} + +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_insertToBinaryIndex(JNIEnv * env, jclass cls, jintArray idsJ, + jlong vectorsAddressJ, jint dimJ, + jlong indexAddress, jint threadCount) +{ + try { + std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); + knn_jni::faiss_wrapper::BinaryIndexService binaryIndexService(std::move(faissMethods)); + knn_jni::faiss_wrapper::InsertToIndex(&jniUtil, env, idsJ, vectorsAddressJ, dimJ, indexAddress, threadCount, &binaryIndexService); + } catch (...) { + // NOTE: ADDING DELETE STATEMENT HERE CAUSES A CRASH! + jniUtil.CatchCppExceptionAndThrowJava(env); + } +} + +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_insertToByteIndex(JNIEnv * env, jclass cls, jintArray idsJ, + jlong vectorsAddressJ, jint dimJ, + jlong indexAddress, jint threadCount) +{ + try { + std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); + knn_jni::faiss_wrapper::ByteIndexService byteIndexService(std::move(faissMethods)); + knn_jni::faiss_wrapper::InsertToIndex(&jniUtil, env, idsJ, vectorsAddressJ, dimJ, indexAddress, threadCount, &byteIndexService); + } catch (...) { + // NOTE: ADDING DELETE STATEMENT HERE CAUSES A CRASH! + jniUtil.CatchCppExceptionAndThrowJava(env); + } +} + +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_writeIndex(JNIEnv * env, + jclass cls, + jlong indexAddress, + jobject output) +{ + try { + std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); + knn_jni::faiss_wrapper::IndexService indexService(std::move(faissMethods)); + knn_jni::faiss_wrapper::WriteIndex(&jniUtil, env, output, indexAddress, &indexService); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } +} + +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_writeBinaryIndex(JNIEnv * env, + jclass cls, + jlong indexAddress, + jobject output) +{ + try { + std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); + knn_jni::faiss_wrapper::BinaryIndexService binaryIndexService(std::move(faissMethods)); + knn_jni::faiss_wrapper::WriteIndex(&jniUtil, env, output, indexAddress, &binaryIndexService); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } +} + +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_writeByteIndex(JNIEnv * env, + jclass cls, + jlong indexAddress, + jobject output) +{ + try { + std::unique_ptr faissMethods(new knn_jni::faiss_wrapper::FaissMethods()); + knn_jni::faiss_wrapper::ByteIndexService byteIndexService(std::move(faissMethods)); + knn_jni::faiss_wrapper::WriteIndex(&jniUtil, env, output, indexAddress, &byteIndexService); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } +} + +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createIndexFromTemplate(JNIEnv * env, + jclass cls, jintArray idsJ, - jobjectArray vectorsJ, - jstring indexPathJ, + jlong vectorsAddressJ, + jint dimJ, + jobject output, jbyteArray templateIndexJ, jobject parametersJ) { try { - knn_jni::faiss_wrapper::CreateIndexFromTemplate(&jniUtil, env, idsJ, vectorsJ, indexPathJ, templateIndexJ, parametersJ); + knn_jni::faiss_wrapper::CreateIndexFromTemplate(&jniUtil, + env, + idsJ, + vectorsAddressJ, + dimJ, + output, + templateIndexJ, + parametersJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } +} + +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createBinaryIndexFromTemplate(JNIEnv * env, + jclass cls, + jintArray idsJ, + jlong vectorsAddressJ, + jint dimJ, + jobject output, + jbyteArray templateIndexJ, + jobject parametersJ) +{ + try { + knn_jni::faiss_wrapper::CreateBinaryIndexFromTemplate(&jniUtil, + env, + idsJ, + vectorsAddressJ, + dimJ, + output, + templateIndexJ, + parametersJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } +} + +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_createByteIndexFromTemplate(JNIEnv * env, + jclass cls, + jintArray idsJ, + jlong vectorsAddressJ, + jint dimJ, + jobject output, + jbyteArray templateIndexJ, + jobject parametersJ) +{ + try { + knn_jni::faiss_wrapper::CreateByteIndexFromTemplate(&jniUtil, + env, + idsJ, + vectorsAddressJ, + dimJ, + output, + templateIndexJ, + parametersJ); } catch (...) { jniUtil.CatchCppExceptionAndThrowJava(env); } } JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadIndex(JNIEnv * env, jclass cls, jstring indexPathJ) +{ + try { + return knn_jni::faiss_wrapper::LoadIndex(&jniUtil, env, indexPathJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return NULL; +} + +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadIndexWithStream(JNIEnv * env, + jclass cls, + jobject readStream) { try { - return knn_jni::faiss_wrapper::LoadIndex(&jniUtil, env, indexPathJ); + // Create a mediator locally. + // Note that `indexInput` is `IndexInputWithBuffer` type. + knn_jni::stream::NativeEngineIndexInputMediator mediator {&jniUtil, env, readStream}; + + // Wrap the mediator with a glue code inheriting IOReader. + knn_jni::stream::FaissOpenSearchIOReader faissOpenSearchIOReader {&mediator}; + + // Pass IOReader to Faiss for loading vector index. + return knn_jni::faiss_wrapper::LoadIndexWithStream( + &faissOpenSearchIOReader); } catch (...) { jniUtil.CatchCppExceptionAndThrowJava(env); } + return NULL; } +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadBinaryIndex(JNIEnv * env, jclass cls, jstring indexPathJ) +{ + try { + return knn_jni::faiss_wrapper::LoadBinaryIndex(&jniUtil, env, indexPathJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return NULL; +} + +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_loadBinaryIndexWithStream(JNIEnv * env, + jclass cls, + jobject readStream) +{ + try { + // Create a mediator locally. + // Note that `indexInput` is `IndexInputWithBuffer` type. + knn_jni::stream::NativeEngineIndexInputMediator mediator {&jniUtil, env, readStream}; + + // Wrap the mediator with a glue code inheriting IOReader. + knn_jni::stream::FaissOpenSearchIOReader faissOpenSearchIOReader {&mediator}; + + // Pass IOReader to Faiss for loading vector index. + return knn_jni::faiss_wrapper::LoadBinaryIndexWithStream( + &faissOpenSearchIOReader); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + + return NULL; +} + +JNIEXPORT jboolean JNICALL Java_org_opensearch_knn_jni_FaissService_isSharedIndexStateRequired(JNIEnv * env, + jclass cls, + jlong indexPointerJ) +{ + try { + return knn_jni::faiss_wrapper::IsSharedIndexStateRequired(indexPointerJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return NULL; +} + +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_initSharedIndexState + (JNIEnv * env, jclass cls, jlong indexPointerJ) +{ + try { + return knn_jni::faiss_wrapper::InitSharedIndexState(indexPointerJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return NULL; +} + +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_setSharedIndexState + (JNIEnv * env, jclass cls, jlong indexPointerJ, jlong shareIndexStatePointerJ) +{ + try { + knn_jni::faiss_wrapper::SetSharedIndexState(indexPointerJ, shareIndexStatePointerJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } +} + JNIEXPORT jobjectArray JNICALL Java_org_opensearch_knn_jni_FaissService_queryIndex(JNIEnv * env, jclass cls, jlong indexPointerJ, - jfloatArray queryVectorJ, jint kJ) + jfloatArray queryVectorJ, jint kJ, jobject methodParamsJ, jintArray parentIdsJ) { try { - return knn_jni::faiss_wrapper::QueryIndex(&jniUtil, env, indexPointerJ, queryVectorJ, kJ); + return knn_jni::faiss_wrapper::QueryIndex(&jniUtil, env, indexPointerJ, queryVectorJ, kJ, methodParamsJ, parentIdsJ); } catch (...) { jniUtil.CatchCppExceptionAndThrowJava(env); @@ -88,10 +345,44 @@ JNIEXPORT jobjectArray JNICALL Java_org_opensearch_knn_jni_FaissService_queryInd return nullptr; } -JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_free(JNIEnv * env, jclass cls, jlong indexPointerJ) +JNIEXPORT jobjectArray JNICALL Java_org_opensearch_knn_jni_FaissService_queryIndexWithFilter + (JNIEnv * env, jclass cls, jlong indexPointerJ, jfloatArray queryVectorJ, jint kJ, jobject methodParamsJ, jlongArray filteredIdsJ, jint filterIdsTypeJ, jintArray parentIdsJ) { + + try { + return knn_jni::faiss_wrapper::QueryIndex_WithFilter(&jniUtil, env, indexPointerJ, queryVectorJ, kJ, methodParamsJ, filteredIdsJ, filterIdsTypeJ, parentIdsJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return nullptr; + +} + +JNIEXPORT jobjectArray JNICALL Java_org_opensearch_knn_jni_FaissService_queryBinaryIndexWithFilter + (JNIEnv * env, jclass cls, jlong indexPointerJ, jbyteArray queryVectorJ, jint kJ, jobject methodParamsJ, jlongArray filteredIdsJ, jint filterIdsTypeJ, jintArray parentIdsJ) { + + try { + return knn_jni::faiss_wrapper::QueryBinaryIndex_WithFilter(&jniUtil, env, indexPointerJ, queryVectorJ, kJ, methodParamsJ, filteredIdsJ, filterIdsTypeJ, parentIdsJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return nullptr; + +} + +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_free(JNIEnv * env, jclass cls, jlong indexPointerJ, jboolean isBinaryIndexJ) { try { - return knn_jni::faiss_wrapper::Free(indexPointerJ); + return knn_jni::faiss_wrapper::Free(indexPointerJ, isBinaryIndexJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } +} + +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_freeSharedIndexState + (JNIEnv * env, jclass cls, jlong shareIndexStatePointerJ) +{ + try { + knn_jni::faiss_wrapper::FreeSharedIndexState(shareIndexStatePointerJ); } catch (...) { jniUtil.CatchCppExceptionAndThrowJava(env); } @@ -119,6 +410,32 @@ JNIEXPORT jbyteArray JNICALL Java_org_opensearch_knn_jni_FaissService_trainIndex return nullptr; } +JNIEXPORT jbyteArray JNICALL Java_org_opensearch_knn_jni_FaissService_trainBinaryIndex(JNIEnv * env, jclass cls, + jobject parametersJ, + jint dimensionJ, + jlong trainVectorsPointerJ) +{ + try { + return knn_jni::faiss_wrapper::TrainBinaryIndex(&jniUtil, env, parametersJ, dimensionJ, trainVectorsPointerJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return nullptr; +} + +JNIEXPORT jbyteArray JNICALL Java_org_opensearch_knn_jni_FaissService_trainByteIndex(JNIEnv * env, jclass cls, + jobject parametersJ, + jint dimensionJ, + jlong trainVectorsPointerJ) +{ + try { + return knn_jni::faiss_wrapper::TrainByteIndex(&jniUtil, env, parametersJ, dimensionJ, trainVectorsPointerJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return nullptr; +} + JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_transferVectors(JNIEnv * env, jclass cls, jlong vectorsPointerJ, jobjectArray vectorsJ) @@ -137,11 +454,30 @@ JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_FaissService_transferVectors return (jlong) vect; } -JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_FaissService_freeVectors(JNIEnv * env, jclass cls, - jlong vectorsPointerJ) +JNIEXPORT jobjectArray JNICALL Java_org_opensearch_knn_jni_FaissService_rangeSearchIndex(JNIEnv * env, jclass cls, + jlong indexPointerJ, + jfloatArray queryVectorJ, + jfloat radiusJ, jobject methodParamsJ, + jint maxResultWindowJ, jintArray parentIdsJ) { - if (vectorsPointerJ != 0) { - auto *vect = reinterpret_cast*>(vectorsPointerJ); - delete vect; + try { + return knn_jni::faiss_wrapper::RangeSearch(&jniUtil, env, indexPointerJ, queryVectorJ, radiusJ, methodParamsJ, maxResultWindowJ, parentIdsJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); } + return nullptr; +} + +JNIEXPORT jobjectArray JNICALL Java_org_opensearch_knn_jni_FaissService_rangeSearchIndexWithFilter(JNIEnv * env, jclass cls, + jlong indexPointerJ, + jfloatArray queryVectorJ, + jfloat radiusJ, jobject methodParamsJ, jint maxResultWindowJ, + jlongArray filterIdsJ, jint filterIdsTypeJ, jintArray parentIdsJ) +{ + try { + return knn_jni::faiss_wrapper::RangeSearchWithFilter(&jniUtil, env, indexPointerJ, queryVectorJ, radiusJ, methodParamsJ, maxResultWindowJ, filterIdsJ, filterIdsTypeJ, parentIdsJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return nullptr; } diff --git a/jni/src/org_opensearch_knn_jni_JNICommons.cpp b/jni/src/org_opensearch_knn_jni_JNICommons.cpp new file mode 100644 index 000000000..906592b2d --- /dev/null +++ b/jni/src/org_opensearch_knn_jni_JNICommons.cpp @@ -0,0 +1,105 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +#include "org_opensearch_knn_jni_JNICommons.h" + +#include +#include "commons.h" +#include "jni_util.h" + +static knn_jni::JNIUtil jniUtil; +static const jint KNN_JNICOMMONS_JNI_VERSION = JNI_VERSION_1_1; + +jint JNI_OnLoad(JavaVM* vm, void* reserved) { + // Obtain the JNIEnv from the VM and confirm JNI_VERSION + JNIEnv* env; + if (vm->GetEnv((void**)&env, KNN_JNICOMMONS_JNI_VERSION) != JNI_OK) { + return JNI_ERR; + } + + jniUtil.Initialize(env); + + return KNN_JNICOMMONS_JNI_VERSION; +} + +void JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv* env; + vm->GetEnv((void**)&env, KNN_JNICOMMONS_JNI_VERSION); + jniUtil.Uninitialize(env); +} + + +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_JNICommons_storeVectorData(JNIEnv * env, jclass cls, +jlong memoryAddressJ, jobjectArray dataJ, jlong initialCapacityJ, jboolean appendJ) + +{ + try { + return knn_jni::commons::storeVectorData(&jniUtil, env, memoryAddressJ, dataJ, initialCapacityJ, appendJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return (long)memoryAddressJ; +} + +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_JNICommons_storeBinaryVectorData(JNIEnv * env, jclass cls, +jlong memoryAddressJ, jobjectArray dataJ, jlong initialCapacityJ, jboolean appendJ) + +{ + try { + return knn_jni::commons::storeBinaryVectorData(&jniUtil, env, memoryAddressJ, dataJ, initialCapacityJ, appendJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return (long)memoryAddressJ; +} + +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_JNICommons_storeByteVectorData(JNIEnv * env, jclass cls, +jlong memoryAddressJ, jobjectArray dataJ, jlong initialCapacityJ, jboolean appendJ) + +{ + try { + return knn_jni::commons::storeByteVectorData(&jniUtil, env, memoryAddressJ, dataJ, initialCapacityJ, appendJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return (long)memoryAddressJ; +} + +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_JNICommons_freeVectorData(JNIEnv * env, jclass cls, + jlong memoryAddressJ) +{ + try { + return knn_jni::commons::freeVectorData(memoryAddressJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } +} + + +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_JNICommons_freeBinaryVectorData(JNIEnv * env, jclass cls, + jlong memoryAddressJ) +{ + try { + return knn_jni::commons::freeBinaryVectorData(memoryAddressJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } +} + +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_JNICommons_freeByteVectorData(JNIEnv * env, jclass cls, + jlong memoryAddressJ) +{ + try { + return knn_jni::commons::freeByteVectorData(memoryAddressJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } +} diff --git a/jni/src/org_opensearch_knn_jni_NmslibService.cpp b/jni/src/org_opensearch_knn_jni_NmslibService.cpp index 11dd885b1..15bc8420e 100644 --- a/jni/src/org_opensearch_knn_jni_NmslibService.cpp +++ b/jni/src/org_opensearch_knn_jni_NmslibService.cpp @@ -12,7 +12,6 @@ #include "org_opensearch_knn_jni_NmslibService.h" #include -#include #include "jni_util.h" #include "nmslib_wrapper.h" @@ -20,71 +19,85 @@ static knn_jni::JNIUtil jniUtil; static const jint KNN_NMSLIB_JNI_VERSION = JNI_VERSION_1_1; -jint JNI_OnLoad(JavaVM* vm, void* reserved) { - JNIEnv* env; - if (vm->GetEnv((void**)&env, KNN_NMSLIB_JNI_VERSION) != JNI_OK) { - return JNI_ERR; - } +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv *env; + if (vm->GetEnv((void **) &env, KNN_NMSLIB_JNI_VERSION) != JNI_OK) { + return JNI_ERR; + } - jniUtil.Initialize(env); + jniUtil.Initialize(env); - return KNN_NMSLIB_JNI_VERSION; + return KNN_NMSLIB_JNI_VERSION; } void JNI_OnUnload(JavaVM *vm, void *reserved) { - JNIEnv* env; - vm->GetEnv((void**)&env, KNN_NMSLIB_JNI_VERSION); - jniUtil.Uninitialize(env); + JNIEnv *env; + vm->GetEnv((void **) &env, KNN_NMSLIB_JNI_VERSION); + jniUtil.Uninitialize(env); } -JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_NmslibService_createIndex(JNIEnv * env, jclass cls, jintArray idsJ, - jobjectArray vectorsJ, jstring indexPathJ, - jobject parametersJ) -{ - try { - knn_jni::nmslib_wrapper::CreateIndex(&jniUtil, env, idsJ, vectorsJ, indexPathJ, parametersJ); - } catch (...) { - jniUtil.CatchCppExceptionAndThrowJava(env); - } +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_NmslibService_createIndex(JNIEnv *env, + jclass cls, + jintArray idsJ, + jlong vectorsAddressJ, + jint dimJ, + jobject output, + jobject parametersJ) { + try { + knn_jni::nmslib_wrapper::CreateIndex(&jniUtil, env, idsJ, vectorsAddressJ, dimJ, output, parametersJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } } -JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_NmslibService_loadIndex(JNIEnv * env, jclass cls, - jstring indexPathJ, jobject parametersJ) -{ - try { - return knn_jni::nmslib_wrapper::LoadIndex(&jniUtil, env, indexPathJ, parametersJ); - } catch (...) { - jniUtil.CatchCppExceptionAndThrowJava(env); - } - return NULL; +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_NmslibService_loadIndex(JNIEnv *env, jclass cls, + jstring indexPathJ, jobject parametersJ) { + try { + return knn_jni::nmslib_wrapper::LoadIndex(&jniUtil, env, indexPathJ, parametersJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return NULL; } -JNIEXPORT jobjectArray JNICALL Java_org_opensearch_knn_jni_NmslibService_queryIndex(JNIEnv * env, jclass cls, +JNIEXPORT jlong JNICALL Java_org_opensearch_knn_jni_NmslibService_loadIndexWithStream(JNIEnv *env, + jclass cls, + jobject readStream, + jobject parametersJ) { + try { + return knn_jni::nmslib_wrapper::LoadIndexWithStream(&jniUtil, env, readStream, parametersJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return NULL; +} + +JNIEXPORT jobjectArray JNICALL Java_org_opensearch_knn_jni_NmslibService_queryIndex(JNIEnv *env, + jclass cls, jlong indexPointerJ, - jfloatArray queryVectorJ, jint kJ) -{ - try { - return knn_jni::nmslib_wrapper::QueryIndex(&jniUtil, env, indexPointerJ, queryVectorJ, kJ); - } catch (...) { - jniUtil.CatchCppExceptionAndThrowJava(env); - } - return nullptr; + jfloatArray queryVectorJ, + jint kJ, + jobject methodParamsJ) { + try { + return knn_jni::nmslib_wrapper::QueryIndex(&jniUtil, env, indexPointerJ, queryVectorJ, kJ, methodParamsJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } + return nullptr; } -JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_NmslibService_free(JNIEnv * env, jclass cls, jlong indexPointerJ) -{ - try { - return knn_jni::nmslib_wrapper::Free(indexPointerJ); - } catch (...) { - jniUtil.CatchCppExceptionAndThrowJava(env); - } +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_NmslibService_free(JNIEnv *env, jclass cls, jlong indexPointerJ) { + try { + return knn_jni::nmslib_wrapper::Free(indexPointerJ); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } } -JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_NmslibService_initLibrary(JNIEnv * env, jclass cls) -{ - try { - knn_jni::nmslib_wrapper::InitLibrary(); - } catch (...) { - jniUtil.CatchCppExceptionAndThrowJava(env); - } +JNIEXPORT void JNICALL Java_org_opensearch_knn_jni_NmslibService_initLibrary(JNIEnv *env, jclass cls) { + try { + knn_jni::nmslib_wrapper::InitLibrary(); + } catch (...) { + jniUtil.CatchCppExceptionAndThrowJava(env); + } } diff --git a/jni/tests/commons_test.cpp b/jni/tests/commons_test.cpp new file mode 100644 index 000000000..39d7f3c99 --- /dev/null +++ b/jni/tests/commons_test.cpp @@ -0,0 +1,202 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + + +#include "test_util.h" +#include +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "jni_util.h" +#include "commons.h" + +TEST(CommonsTests, BasicAssertions) { + long dim = 3; + long totalNumberOfVector = 5; + std::vector> data; + for(int i = 0 ; i < totalNumberOfVector - 1 ; i++) { + std::vector vector; + for(int j = 0 ; j < dim ; j ++) { + vector.push_back((float)j); + } + data.push_back(vector); + } + JNIEnv *jniEnv = nullptr; + + testing::NiceMock mockJNIUtil; + + jlong memoryAddress = knn_jni::commons::storeVectorData(&mockJNIUtil, jniEnv, (jlong)0, + reinterpret_cast(&data), (jlong)(totalNumberOfVector * dim), true); + ASSERT_NE(memoryAddress, 0); + auto *vect = reinterpret_cast*>(memoryAddress); + ASSERT_EQ(vect->size(), data.size() * dim); + ASSERT_EQ(vect->capacity(), totalNumberOfVector * dim); + + // Check by inserting more vectors at same memory location + jlong oldMemoryAddress = memoryAddress; + std::vector> data2; + std::vector vector; + for(int j = 0 ; j < dim ; j ++) { + vector.push_back((float)j); + } + data2.push_back(vector); + memoryAddress = knn_jni::commons::storeVectorData(&mockJNIUtil, jniEnv, memoryAddress, + reinterpret_cast(&data2), (jlong)(totalNumberOfVector * dim), true); + ASSERT_NE(memoryAddress, 0); + ASSERT_EQ(memoryAddress, oldMemoryAddress); + vect = reinterpret_cast*>(memoryAddress); + int currentIndex = 0; + std::cout << vect->size() + "\n"; + ASSERT_EQ(vect->size(), totalNumberOfVector * dim); + ASSERT_EQ(vect->capacity(), totalNumberOfVector * dim); + + // Validate if all vectors data are at correct location + for(auto & i : data) { + for(float j : i) { + ASSERT_FLOAT_EQ(vect->at(currentIndex), j); + currentIndex++; + } + } + + for(auto & i : data2) { + for(float j : i) { + ASSERT_FLOAT_EQ(vect->at(currentIndex), j); + currentIndex++; + } + } + + // test append == true + std::vector> data3; + std::vector vecto3; + for(int j = 0 ; j < dim ; j ++) { + vecto3.push_back((float)j); + } + data3.push_back(vecto3); + memoryAddress = knn_jni::commons::storeVectorData(&mockJNIUtil, jniEnv, memoryAddress, + reinterpret_cast(&data3), (jlong)(totalNumberOfVector * dim), false); + ASSERT_NE(memoryAddress, 0); + ASSERT_EQ(memoryAddress, oldMemoryAddress); + vect = reinterpret_cast*>(memoryAddress); + + ASSERT_EQ(vect->size(), dim); //Since we just added 1 vector + ASSERT_EQ(vect->capacity(), totalNumberOfVector * dim); //This is the initial capacity allocated + + currentIndex = 0; + for(auto & i : data3) { + for(float j : i) { + ASSERT_FLOAT_EQ(vect->at(currentIndex), j); + currentIndex++; + } + } + + // Check that freeing vector data works + knn_jni::commons::freeVectorData(memoryAddress); +} + +TEST(StoreByteVectorTest, BasicAssertions) { + long dim = 3; + long totalNumberOfVector = 5; + std::vector> data; + for(int i = 0 ; i < totalNumberOfVector - 1 ; i++) { + std::vector vector; + for(int j = 0 ; j < dim ; j ++) { + vector.push_back((uint8_t)j); + } + data.push_back(vector); + } + JNIEnv *jniEnv = nullptr; + + testing::NiceMock mockJNIUtil; + + jlong memoryAddress = knn_jni::commons::storeByteVectorData(&mockJNIUtil, jniEnv, (jlong)0, + reinterpret_cast(&data), (jlong)(totalNumberOfVector * dim), true); + ASSERT_NE(memoryAddress, 0); + auto *vect = reinterpret_cast*>(memoryAddress); + ASSERT_EQ(vect->size(), data.size() * dim); + ASSERT_EQ(vect->capacity(), totalNumberOfVector * dim); + + // Check by inserting more vectors at same memory location + jlong oldMemoryAddress = memoryAddress; + std::vector> data2; + std::vector vector; + for(int j = 0 ; j < dim ; j ++) { + vector.push_back((uint8_t)j); + } + data2.push_back(vector); + memoryAddress = knn_jni::commons::storeByteVectorData(&mockJNIUtil, jniEnv, memoryAddress, + reinterpret_cast(&data2), (jlong)(totalNumberOfVector * dim), true); + ASSERT_NE(memoryAddress, 0); + ASSERT_EQ(memoryAddress, oldMemoryAddress); + vect = reinterpret_cast*>(memoryAddress); + int currentIndex = 0; + ASSERT_EQ(vect->size(), totalNumberOfVector*dim); + ASSERT_EQ(vect->capacity(), totalNumberOfVector * dim); + + // Validate if all vectors data are at correct location + for(auto & i : data) { + for(uint8_t j : i) { + ASSERT_EQ(vect->at(currentIndex), j); + currentIndex++; + } + } + + for(auto & i : data2) { + for(uint8_t j : i) { + ASSERT_EQ(vect->at(currentIndex), j); + currentIndex++; + } + } + + // test append == true + std::vector> data3; + std::vector vecto3; + for(int j = 0 ; j < dim ; j ++) { + vecto3.push_back((uint8_t)j); + } + data3.push_back(vecto3); + memoryAddress = knn_jni::commons::storeByteVectorData(&mockJNIUtil, jniEnv, memoryAddress, + reinterpret_cast(&data3), (jlong)(totalNumberOfVector * dim), false); + ASSERT_NE(memoryAddress, 0); + ASSERT_EQ(memoryAddress, oldMemoryAddress); + vect = reinterpret_cast*>(memoryAddress); + + ASSERT_EQ(vect->size(), dim); + ASSERT_EQ(vect->capacity(), totalNumberOfVector * dim); + + currentIndex = 0; + for(auto & i : data3) { + for(uint8_t j : i) { + ASSERT_EQ(vect->at(currentIndex), j); + currentIndex++; + } + } + + // Check that freeing vector data works + knn_jni::commons::freeBinaryVectorData(memoryAddress); +} + +TEST(CommonTests, GetIntegerMethodParam) { + JNIEnv *jniEnv = nullptr; + testing::NiceMock mockJNIUtil; + + std::unordered_map methodParams1; + int efSearch = 10; + methodParams1[knn_jni::EF_SEARCH] = reinterpret_cast(&efSearch); + + int actualValue1 = knn_jni::commons::getIntegerMethodParameter(jniEnv, &mockJNIUtil, methodParams1, knn_jni::EF_SEARCH, 1); + EXPECT_EQ(efSearch, actualValue1); + + int actualValue2 = knn_jni::commons::getIntegerMethodParameter(jniEnv, &mockJNIUtil, methodParams1, "param", 1); + EXPECT_EQ(1, actualValue2); + + std::unordered_map methodParams2; + int actualValue3 = knn_jni::commons::getIntegerMethodParameter(jniEnv, &mockJNIUtil, methodParams2, knn_jni::EF_SEARCH, 1); + EXPECT_EQ(1, actualValue3); +} diff --git a/jni/tests/faiss_index_service_test.cpp b/jni/tests/faiss_index_service_test.cpp new file mode 100644 index 000000000..127ca07b8 --- /dev/null +++ b/jni/tests/faiss_index_service_test.cpp @@ -0,0 +1,163 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + + +#include "faiss_index_service.h" +#include "mocks/faiss_methods_mock.h" +#include "mocks/faiss_index_mock.h" +#include "test_util.h" +#include +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "commons.h" + +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::_; + +TEST(CreateIndexTest, BasicAssertions) { + // Define the data + faiss::idx_t numIds = 200; + std::vector ids; + std::vector vectors; + int dim = 2; + vectors.reserve(dim * numIds); + for (int64_t i = 0; i < numIds; ++i) { + ids.push_back(i); + for (int j = 0; j < dim; ++j) { + vectors.push_back(test_util::RandomFloat(-500.0, 500.0)); + } + } + + std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); + faiss::FileIOWriter fileIOWriter {indexPath.c_str()}; + faiss::MetricType metricType = faiss::METRIC_L2; + std::string indexDescription = "HNSW32,Flat"; + int threadCount = 1; + std::unordered_map parametersMap; + + // Set up jni + JNIEnv *jniEnv = nullptr; + NiceMock mockJNIUtil; + + // Setup faiss method mock + // This object is handled by unique_ptr inside indexService.createIndex() + MockIndex* index = new MockIndex(); + EXPECT_CALL(*index, add(numIds, vectors.data())) + .Times(1); + // This object is handled by unique_ptr inside indexService.createIndex() + faiss::IndexIDMap* indexIdMap = new faiss::IndexIDMap(index); + std::unique_ptr mockFaissMethods(new MockFaissMethods()); + EXPECT_CALL(*mockFaissMethods, indexFactory(dim, ::testing::StrEq(indexDescription.c_str()), metricType)) + .WillOnce(Return(index)); + EXPECT_CALL(*mockFaissMethods, indexIdMap(index)) + .WillOnce(Return(indexIdMap)); + EXPECT_CALL(*mockFaissMethods, writeIndex(indexIdMap, ::testing::Eq(&fileIOWriter))) + .Times(1); + + // Create the index + knn_jni::faiss_wrapper::IndexService indexService(std::move(mockFaissMethods)); + long indexAddress = indexService.initIndex(&mockJNIUtil, jniEnv, metricType, indexDescription, dim, numIds, threadCount, parametersMap); + indexService.insertToIndex(dim, numIds, threadCount, (int64_t) &vectors, ids, indexAddress); + indexService.writeIndex(&fileIOWriter, indexAddress); +} + +TEST(CreateBinaryIndexTest, BasicAssertions) { + // Define the data + faiss::idx_t numIds = 200; + std::vector ids; + std::vector vectors; + int dim = 128; + vectors.reserve(numIds); + for (int64_t i = 0; i < numIds; ++i) { + ids.push_back(i); + for (int j = 0; j < dim / 8; ++j) { + vectors.push_back(test_util::RandomInt(0, 255)); + } + } + + std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); + faiss::FileIOWriter fileIOWriter {indexPath.c_str()}; + faiss::MetricType metricType = faiss::METRIC_L2; + std::string indexDescription = "BHNSW32"; + int threadCount = 1; + std::unordered_map parametersMap; + + // Set up jni + JNIEnv *jniEnv = nullptr; + NiceMock mockJNIUtil; + + // Setup faiss method mock + // This object is handled by unique_ptr inside indexService.createIndex() + MockIndexBinary* index = new MockIndexBinary(); + EXPECT_CALL(*index, add(numIds, vectors.data())) + .Times(1); + // This object is handled by unique_ptr inside indexService.createIndex() + faiss::IndexBinaryIDMap* indexIdMap = new faiss::IndexBinaryIDMap(index); + std::unique_ptr mockFaissMethods(new MockFaissMethods()); + EXPECT_CALL(*mockFaissMethods, indexBinaryFactory(dim, ::testing::StrEq(indexDescription.c_str()))) + .WillOnce(Return(index)); + EXPECT_CALL(*mockFaissMethods, indexBinaryIdMap(index)) + .WillOnce(Return(indexIdMap)); + EXPECT_CALL(*mockFaissMethods, writeIndexBinary(indexIdMap, ::testing::Eq(&fileIOWriter))) + .Times(1); + + // Create the index + knn_jni::faiss_wrapper::BinaryIndexService indexService(std::move(mockFaissMethods)); + long indexAddress = indexService.initIndex(&mockJNIUtil, jniEnv, metricType, indexDescription, dim, numIds, threadCount, parametersMap); + indexService.insertToIndex(dim, numIds, threadCount, (int64_t) &vectors, ids, indexAddress); + indexService.writeIndex(&fileIOWriter, indexAddress); +} + +TEST(CreateByteIndexTest, BasicAssertions) { + // Define the data + faiss::idx_t numIds = 200; + std::vector ids; + std::vector vectors; + int dim = 8; + vectors.reserve(numIds * dim); + for (int64_t i = 0; i < numIds; ++i) { + ids.push_back(i); + for (int j = 0; j < dim; ++j) { + vectors.push_back(test_util::RandomInt(-128, 127)); + } + } + + std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); + faiss::FileIOWriter fileIOWriter {indexPath.c_str()}; + faiss::MetricType metricType = faiss::METRIC_L2; + std::string indexDescription = "HNSW16,SQ8_direct_signed"; + int threadCount = 1; + std::unordered_map parametersMap; + + // Set up jni + JNIEnv *jniEnv = nullptr; + NiceMock mockJNIUtil; + + // Setup faiss method mock + // This object is handled by unique_ptr inside indexService.createIndex() + MockIndex* index = new MockIndex(); + // This object is handled by unique_ptr inside indexService.createIndex() + faiss::IndexIDMap* indexIdMap = new faiss::IndexIDMap(index); + std::unique_ptr mockFaissMethods(new MockFaissMethods()); + EXPECT_CALL(*mockFaissMethods, indexFactory(dim, ::testing::StrEq(indexDescription.c_str()), metricType)) + .WillOnce(Return(index)); + EXPECT_CALL(*mockFaissMethods, indexIdMap(index)) + .WillOnce(Return(indexIdMap)); + EXPECT_CALL(*mockFaissMethods, writeIndex(indexIdMap, ::testing::Eq(&fileIOWriter))) + .Times(1); + + // Create the index + knn_jni::faiss_wrapper::ByteIndexService indexService(std::move(mockFaissMethods)); + long indexAddress = indexService.initIndex(&mockJNIUtil, jniEnv, metricType, indexDescription, dim, numIds, threadCount, parametersMap); + indexService.insertToIndex(dim, numIds, threadCount, (int64_t) &vectors, ids, indexAddress); + indexService.writeIndex(&fileIOWriter, indexAddress); +} \ No newline at end of file diff --git a/jni/tests/faiss_stream_support_test.cpp b/jni/tests/faiss_stream_support_test.cpp new file mode 100644 index 000000000..79beea7cb --- /dev/null +++ b/jni/tests/faiss_stream_support_test.cpp @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// The OpenSearch Contributors require contributions made to +// this file be licensed under the Apache-2.0 license or a +// compatible open source license. +// +// Modifications Copyright OpenSearch Contributors. See +// GitHub history for details. + +#include "faiss_stream_support.h" +#include "native_stream_support_util.h" +#include "test_util.h" + +#include +#include +#include +#include + +using ::testing::_; +using ::testing::Return; +using knn_jni::stream::FaissOpenSearchIOReader; +using knn_jni::stream::NativeEngineIndexInputMediator; +using test_util::MockJNIUtil; +using test_util::JavaIndexInputMock; +using ::testing::NiceMock; +using ::testing::Return; + +void setUpMockJNIUtil(JavaIndexInputMock &javaIndexInputMock, MockJNIUtil &mockJni) { + // Set up mocking values + mocking behavior in a method. + EXPECT_CALL(mockJni, CallNonvirtualIntMethodA(_, _, _, _, _)) + .WillRepeatedly([&javaIndexInputMock](JNIEnv *env, + jobject obj, + jclass clazz, + jmethodID methodID, + jvalue* args) { + return javaIndexInputMock.simulateCopyReads(args[0].j); + }); + EXPECT_CALL(mockJni, CallNonvirtualLongMethodA(_, _, _, _, _)) + .WillRepeatedly([&javaIndexInputMock](JNIEnv *env, + jobject obj, + jclass clazz, + jmethodID methodID, + jvalue* args) { + return javaIndexInputMock.remainingBytes(); + }); + EXPECT_CALL(mockJni, GetPrimitiveArrayCritical(_, _, _)) + .WillRepeatedly([&javaIndexInputMock](JNIEnv *env, + jarray array, + jboolean *isCopy) { + return (jbyte *) javaIndexInputMock.buffer.data(); + }); + EXPECT_CALL(mockJni, ReleasePrimitiveArrayCritical(_, _, _, _)) + .WillRepeatedly(Return()); +} + +TEST(FaissStreamSupportTest, NativeEngineIndexInputMediatorCopyWhenEmpty) { + for (auto contentSize : std::vector{0, 2222, 7777, 1024, 77, 1}) { + // Set up mockings + MockJNIUtil mockJni; + JavaIndexInputMock javaIndexInputMock{ + JavaIndexInputMock::makeRandomBytes(contentSize), 1024}; + setUpMockJNIUtil(javaIndexInputMock, mockJni); + + // Prepare copying + NiceMock jniEnv; + // It's a dummy value, which will not be used. If we pass a null, then NPE will be raised. + jobject jobjectDummy = reinterpret_cast(1); + NativeEngineIndexInputMediator mediator{&mockJni, &jniEnv, jobjectDummy}; + std::string readBuffer(javaIndexInputMock.readTargetBytes.size(), '\0'); + + // Call copyBytes + mediator.copyBytes((int32_t) javaIndexInputMock.readTargetBytes.size(), (uint8_t *) readBuffer.data()); + + // Expected that we acquired the same contents as readTargetBytes + ASSERT_EQ(javaIndexInputMock.readTargetBytes, readBuffer); + } // End for +} + +TEST(FaissStreamSupportTest, FaissOpenSearchIOReaderCopy) { + for (auto contentSize : std::vector{0, 2222, 7777, 1024, 77, 1}) { + // Set up mockings + NiceMock mockJni; + JavaIndexInputMock javaIndexInputMock{ + JavaIndexInputMock::makeRandomBytes(contentSize), 1024}; + setUpMockJNIUtil(javaIndexInputMock, mockJni); + + // Prepare copying + NiceMock jniEnv; + // It's a dummy value, which will not be used. If we pass a null, then NPE will be raised. + jobject jobjectDummy = reinterpret_cast(1); + NativeEngineIndexInputMediator mediator{&mockJni, &jniEnv, jobjectDummy}; + std::string readBuffer; + readBuffer.resize(javaIndexInputMock.readTargetBytes.size()); + FaissOpenSearchIOReader ioReader{&mediator}; + + // Read bytes + const auto readBytes = + ioReader((void *) readBuffer.data(), 1, javaIndexInputMock.readTargetBytes.size()); + + // Expected that we acquired the same contents as readTargetBytes + ASSERT_EQ(javaIndexInputMock.readTargetBytes.size(), readBytes); + ASSERT_EQ(javaIndexInputMock.readTargetBytes, readBuffer); + } // End for +} diff --git a/jni/tests/faiss_util_test.cpp b/jni/tests/faiss_util_test.cpp new file mode 100644 index 000000000..d8b45d951 --- /dev/null +++ b/jni/tests/faiss_util_test.cpp @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// The OpenSearch Contributors require contributions made to +// this file be licensed under the Apache-2.0 license or a +// compatible open source license. +// +// Modifications Copyright OpenSearch Contributors. See +// GitHub history for details. + +#include "faiss_util.h" + +#include + +#include "gtest/gtest.h" + +TEST(IDGrouperBitMapTest, BasicAssertions) { + int ids[] = {128, 1024}; + size_t length = sizeof(ids) / sizeof(ids[0]); + std::vector bitmap; + std::unique_ptr idGrouperBitmap = faiss_util::buildIDGrouperBitmap(ids, length, &bitmap); + int groupIndex = 0; + for (int i = 0; i <= ids[length - 1]; i++) { + if (i > ids[groupIndex]) { + groupIndex++; + } + ASSERT_EQ(ids[groupIndex], idGrouperBitmap->get_group(i)); + } +} diff --git a/jni/tests/faiss_wrapper_test.cpp b/jni/tests/faiss_wrapper_test.cpp index 440061b0e..651201964 100644 --- a/jni/tests/faiss_wrapper_test.cpp +++ b/jni/tests/faiss_wrapper_test.cpp @@ -17,125 +17,325 @@ #include "gtest/gtest.h" #include "jni_util.h" #include "test_util.h" +#include "faiss/IndexHNSW.h" +#include "faiss/IndexIVFPQ.h" +#include "mocks/faiss_index_service_mock.h" +#include "native_stream_support_util.h" +using ::test_util::JavaFileIndexOutputMock; +using ::test_util::MockJNIUtil; +using ::test_util::StreamIOError; +using ::test_util::setUpJavaFileOutputMocking; +using ::testing::Mock; using ::testing::NiceMock; using ::testing::Return; +using ::testing::_; + +const float randomDataMin = -500.0; +const float randomDataMax = 500.0; +const float rangeSearchRandomDataMin = -50; +const float rangeSearchRandomDataMax = 50; +const float rangeSearchRadius = 20000; + +void createIndexIteratively( + knn_jni::JNIUtilInterface * JNIUtil, + JNIEnv *jniEnv, + std::vector & ids, + std::vector & vectors, + int dim, + jobject javaFileOutputMock, + std::unordered_map parametersMap, + IndexService * indexService, + int insertions = 10 + ) { + long numDocs = ids.size(); + if (numDocs % insertions != 0) { + throw std::invalid_argument("Number of documents should be divisible by number of insertions"); + } + long docsPerInsertion = numDocs / insertions; + long index_ptr = knn_jni::faiss_wrapper::InitIndex(JNIUtil, jniEnv, numDocs, dim, (jobject)¶metersMap, indexService); + std::vector insertIds; + std::vector insertVecs; + for (int i = 0; i < insertions; i++) { + insertIds.clear(); + insertVecs.clear(); + int start_idx = i * docsPerInsertion; + int end_idx = start_idx + docsPerInsertion; + for (int j = start_idx; j < end_idx; j++) { + insertIds.push_back(j); + for(int k = 0; k < dim; k++) { + insertVecs.push_back(vectors[j * dim + k]); + } + } + knn_jni::faiss_wrapper::InsertToIndex(JNIUtil, jniEnv, reinterpret_cast(&insertIds), (jlong)&insertVecs, dim, index_ptr, 0, indexService); + } + knn_jni::faiss_wrapper::WriteIndex(JNIUtil, jniEnv, javaFileOutputMock, index_ptr, indexService); +} + +void createBinaryIndexIteratively( + knn_jni::JNIUtilInterface * JNIUtil, + JNIEnv *jniEnv, + std::vector & ids, + std::vector & vectors, + int dim, + jobject javaFileOutputMock, + std::unordered_map parametersMap, + IndexService * indexService, + int insertions = 10 + ) { + long numDocs = ids.size(); + long index_ptr = knn_jni::faiss_wrapper::InitIndex(JNIUtil, jniEnv, numDocs, dim, (jobject)¶metersMap, indexService); + std::vector insertIds; + std::vector insertVecs; + for (int i = 0; i < insertions; i++) { + int start_idx = numDocs * i / insertions; + int end_idx = numDocs * (i + 1) / insertions; + int docs_to_insert = end_idx - start_idx; + if (docs_to_insert == 0) { + continue; + } + insertIds.clear(); + insertVecs.clear(); + for (int j = start_idx; j < end_idx; j++) { + insertIds.push_back(j); + for(int k = 0; k < dim / 8; k++) { + insertVecs.push_back(vectors[j * (dim / 8) + k]); + } + } + knn_jni::faiss_wrapper::InsertToIndex(JNIUtil, jniEnv, reinterpret_cast(&insertIds), (jlong)&insertVecs, dim, index_ptr, 0, indexService); + } + + knn_jni::faiss_wrapper::WriteIndex(JNIUtil, jniEnv, javaFileOutputMock, index_ptr, indexService); +} TEST(FaissCreateIndexTest, BasicAssertions) { // Define the data - faiss::Index::idx_t numIds = 200; - std::vector ids; - std::vector> vectors; + faiss::idx_t numIds = 200; + std::vector ids; + std::vector vectors; int dim = 2; + vectors.reserve(dim * numIds); for (int64_t i = 0; i < numIds; ++i) { - ids.push_back(i); - - std::vector vect; - vect.reserve(dim); - for (int j = 0; j < dim; ++j) { - vect.push_back(test_util::RandomFloat(-500.0, 500.0)); - } - vectors.push_back(vect); + ids.push_back(i); + for (int j = 0; j < dim; ++j) { + vectors.push_back(test_util::RandomFloat(-500.0, 500.0)); + } } std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); std::string spaceType = knn_jni::L2; - std::string index_description = "Flat"; // TODO: Revert bach to HNSW32,Flat + std::string indexDescription = "HNSW32,Flat"; std::unordered_map parametersMap; parametersMap[knn_jni::SPACE_TYPE] = (jobject)&spaceType; - parametersMap[knn_jni::INDEX_DESCRIPTION] = (jobject)&index_description; + parametersMap[knn_jni::INDEX_DESCRIPTION] = (jobject)&indexDescription; + std::unordered_map subParametersMap; + parametersMap[knn_jni::PARAMETERS] = (jobject)&subParametersMap; // Set up jni - JNIEnv *jniEnv = nullptr; + NiceMock jniEnv; NiceMock mockJNIUtil; - - EXPECT_CALL(mockJNIUtil, - GetJavaObjectArrayLength( - jniEnv, reinterpret_cast(&vectors))) - .WillRepeatedly(Return(vectors.size())); + JavaFileIndexOutputMock javaFileIndexOutputMock {indexPath}; + setUpJavaFileOutputMocking(javaFileIndexOutputMock, mockJNIUtil, false); // Create the index - knn_jni::faiss_wrapper::CreateIndex( - &mockJNIUtil, jniEnv, reinterpret_cast(&ids), - reinterpret_cast(&vectors), (jstring)&indexPath, - (jobject)¶metersMap); - - // Make sure index can be loaded - std::unique_ptr index(test_util::FaissLoadIndex(indexPath)); + std::unique_ptr faissMethods(new FaissMethods()); + NiceMock mockIndexService(std::move(faissMethods)); + int insertions = 10; + EXPECT_CALL(mockIndexService, initIndex(_, _, faiss::METRIC_L2, indexDescription, dim, (int)numIds, 0, subParametersMap)) + .Times(1); + EXPECT_CALL(mockIndexService, insertToIndex(dim, numIds / insertions, 0, _, _, _)) + .Times(insertions); + EXPECT_CALL(mockIndexService, writeIndex(_, _)) + .Times(1); - // Clean up - std::remove(indexPath.c_str()); + createIndexIteratively(&mockJNIUtil, + &jniEnv, + ids, + vectors, + dim, + (jobject) (&javaFileIndexOutputMock), + parametersMap, + &mockIndexService, + insertions); } -TEST(FaissCreateIndexFromTemplateTest, BasicAssertions) { +TEST(FaissCreateBinaryIndexTest, BasicAssertions) { // Define the data - faiss::Index::idx_t numIds = 100; - std::vector ids; - std::vector> vectors; - int dim = 2; + faiss::idx_t numIds = 200; + std::vector ids; + std::vector vectors; + int dim = 128; + vectors.reserve(numIds); for (int64_t i = 0; i < numIds; ++i) { - ids.push_back(i); - - std::vector vect; - vect.reserve(dim); - for (int j = 0; j < dim; ++j) { - vect.push_back(test_util::RandomFloat(-500.0, 500.0)); - } - vectors.push_back(vect); + ids.push_back(i); + for (int j = 0; j < dim / 8; ++j) { + vectors.push_back(test_util::RandomInt(0, 255)); + } } std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); - faiss::MetricType metricType = faiss::METRIC_L2; - std::string method = "Flat"; // TODO: Revert bach to HNSW32,Flat + std::string spaceType = knn_jni::HAMMING; + std::string indexDescription = "BHNSW32"; - std::unique_ptr createdIndex( - test_util::FaissCreateIndex(dim, method, metricType)); - auto vectorIoWriter = test_util::FaissGetSerializedIndex(createdIndex.get()); + std::unordered_map parametersMap; + parametersMap[knn_jni::SPACE_TYPE] = (jobject)&spaceType; + parametersMap[knn_jni::INDEX_DESCRIPTION] = (jobject)&indexDescription; + std::unordered_map subParametersMap; + parametersMap[knn_jni::PARAMETERS] = (jobject)&subParametersMap; - // Setup jni - JNIEnv *jniEnv = nullptr; + // Set up jni + NiceMock jniEnv; NiceMock mockJNIUtil; + JavaFileIndexOutputMock javaFileIndexOutputMock {indexPath}; + setUpJavaFileOutputMocking(javaFileIndexOutputMock, mockJNIUtil, false); - EXPECT_CALL(mockJNIUtil, - GetJavaObjectArrayLength( - jniEnv, reinterpret_cast(&vectors))) - .WillRepeatedly(Return(vectors.size())); + // Create the index + std::unique_ptr faissMethods(new FaissMethods()); + NiceMock mockIndexService(std::move(faissMethods)); + int insertions = 10; + EXPECT_CALL(mockIndexService, initIndex(_, _, faiss::METRIC_L2, indexDescription, dim, (int)numIds, 0, subParametersMap)) + .Times(1); + EXPECT_CALL(mockIndexService, insertToIndex(dim, numIds / insertions, 0, _, _, _)) + .Times(insertions); + EXPECT_CALL(mockIndexService, writeIndex(_, _)) + .Times(1); - std::string spaceType = knn_jni::L2; - std::unordered_map parametersMap; - parametersMap[knn_jni::SPACE_TYPE] = (jobject) &spaceType; + // This method calls delete vectors at the end + createBinaryIndexIteratively(&mockJNIUtil, + &jniEnv, + ids, + vectors, + dim, + (jobject) (&javaFileIndexOutputMock), + parametersMap, + &mockIndexService, + insertions); +} + +TEST(FaissCreateIndexFromTemplateTest, BasicAssertions) { + for (auto throwIOException : std::array {false, true}) { + // Define the data + faiss::idx_t numIds = 100; + std::vector ids; + auto *vectors = new std::vector(); + int dim = 2; + vectors->reserve(dim * numIds); + for (int64_t i = 0; i < numIds; ++i) { + ids.push_back(i); + for (int j = 0; j < dim; ++j) { + vectors->push_back(test_util::RandomFloat(-500.0, 500.0)); + } + } + + std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); + faiss::MetricType metricType = faiss::METRIC_L2; + std::string method = "HNSW32,Flat"; + + std::unique_ptr createdIndex( + test_util::FaissCreateIndex(dim, method, metricType)); + auto vectorIoWriter = test_util::FaissGetSerializedIndex(createdIndex.get()); + + // Setup jni + NiceMock jniEnv; + NiceMock mockJNIUtil; + JavaFileIndexOutputMock javaFileIndexOutputMock {indexPath}; + setUpJavaFileOutputMocking(javaFileIndexOutputMock, mockJNIUtil, throwIOException); + + std::string spaceType = knn_jni::L2; + std::unordered_map parametersMap; + parametersMap[knn_jni::SPACE_TYPE] = (jobject) &spaceType; + + try { + knn_jni::faiss_wrapper::CreateIndexFromTemplate( + &mockJNIUtil, &jniEnv, reinterpret_cast(&ids), + (jlong)vectors, dim, (jobject)(&javaFileIndexOutputMock), + reinterpret_cast(&(vectorIoWriter.data)), + (jobject) ¶metersMap); + javaFileIndexOutputMock.file_writer.close(); + } catch (const StreamIOError& e) { + ASSERT_TRUE(throwIOException); + continue; + } + + ASSERT_FALSE(throwIOException); + + // Make sure index can be loaded + std::unique_ptr index(test_util::FaissLoadIndex(indexPath)); + + // Clean up + std::remove(indexPath.c_str()); + } // End for +} + +TEST(FaissCreateByteIndexFromTemplateTest, BasicAssertions) { + for (auto throwIOException : std::array {false, true}) { + // Define the data + faiss::idx_t numIds = 100; + std::vector ids; + auto *vectors = new std::vector(); + int dim = 8; + vectors->reserve(dim * numIds); + for (int64_t i = 0; i < numIds; ++i) { + ids.push_back(i); + for (int j = 0; j < dim; ++j) { + vectors->push_back(test_util::RandomInt(-128, 127)); + } + } + + std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); + faiss::MetricType metricType = faiss::METRIC_L2; + std::string method = "HNSW32,SQ8_direct_signed"; + + std::unique_ptr createdIndex( + test_util::FaissCreateIndex(dim, method, metricType)); + auto vectorIoWriter = test_util::FaissGetSerializedIndex(createdIndex.get()); + + // Setup jni + NiceMock jniEnv; + NiceMock mockJNIUtil; + JavaFileIndexOutputMock javaFileIndexOutputMock {indexPath}; + setUpJavaFileOutputMocking(javaFileIndexOutputMock, mockJNIUtil, throwIOException); - knn_jni::faiss_wrapper::CreateIndexFromTemplate( - &mockJNIUtil, jniEnv, reinterpret_cast(&ids), - reinterpret_cast(&vectors), (jstring)&indexPath, - reinterpret_cast(&(vectorIoWriter.data)), - (jobject) ¶metersMap + std::string spaceType = knn_jni::L2; + std::unordered_map parametersMap; + parametersMap[knn_jni::SPACE_TYPE] = (jobject) &spaceType; + + try { + knn_jni::faiss_wrapper::CreateByteIndexFromTemplate( + &mockJNIUtil, &jniEnv, reinterpret_cast(&ids), + (jlong) vectors, dim, (jstring) (&javaFileIndexOutputMock), + reinterpret_cast(&(vectorIoWriter.data)), + (jobject) ¶metersMap ); - // Make sure index can be loaded - std::unique_ptr index(test_util::FaissLoadIndex(indexPath)); + // Make sure we close a file stream before reopening the created file. + javaFileIndexOutputMock.file_writer.close(); + } catch (const StreamIOError& e) { + ASSERT_TRUE(throwIOException); + continue; + } - // Clean up - std::remove(indexPath.c_str()); + ASSERT_FALSE(throwIOException); + + // Make sure index can be loaded + std::unique_ptr index(test_util::FaissLoadIndex(indexPath)); + + // Clean up + std::remove(indexPath.c_str()); + } // End for } TEST(FaissLoadIndexTest, BasicAssertions) { // Define the data - faiss::Index::idx_t numIds = 100; - std::vector ids; - std::vector vectors; + faiss::idx_t numIds = 100; int dim = 2; - for (int64_t i = 0; i < numIds; i++) { - ids.push_back(i); - for (int j = 0; j < dim; j++) { - vectors.push_back(test_util::RandomFloat(-500.0, 500.0)); - } - } + std::vector ids = test_util::Range(numIds); + std::vector vectors = test_util::RandomVectors(dim, numIds, randomDataMin, randomDataMax); std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); faiss::MetricType metricType = faiss::METRIC_L2; - std::string method = "Flat"; // TODO: Revert bach to HNSW32,Flat + std::string method = "HNSW32,Flat"; // Create the index std::unique_ptr createdIndex( @@ -146,12 +346,12 @@ TEST(FaissLoadIndexTest, BasicAssertions) { test_util::FaissWriteIndex(&createdIndexWithData, indexPath); // Setup jni - JNIEnv *jniEnv = nullptr; + NiceMock jniEnv; NiceMock mockJNIUtil; std::unique_ptr loadedIndexPointer( reinterpret_cast(knn_jni::faiss_wrapper::LoadIndex( - &mockJNIUtil, jniEnv, (jstring)&indexPath))); + &mockJNIUtil, &jniEnv, (jstring)&indexPath))); // Compare serialized versions auto createIndexSerialization = @@ -172,24 +372,138 @@ TEST(FaissLoadIndexTest, BasicAssertions) { std::remove(indexPath.c_str()); } -TEST(FaissQueryIndexTest, BasicAssertions) { - // Define the index data - faiss::Index::idx_t numIds = 100; - std::vector ids; - std::vector vectors; - int dim = 16; - for (int64_t i = 0; i < numIds; i++) { +TEST(FaissLoadBinaryIndexTest, BasicAssertions) { + // Define the data + faiss::idx_t numIds = 200; + std::vector ids; + auto vectors = std::vector(numIds); + int dim = 128; + for (int64_t i = 0; i < numIds; ++i) { ids.push_back(i); - for (int j = 0; j < dim; j++) { - vectors.push_back(test_util::RandomFloat(-500.0, 500.0)); + for (int j = 0; j < dim / 8; ++j) { + vectors.push_back(test_util::RandomInt(0, 255)); } } + std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); + std::string method = "BHNSW32"; + + // Create the index + std::unique_ptr createdIndex( + test_util::FaissCreateBinaryIndex(dim, method)); + auto createdIndexWithData = + test_util::FaissAddBinaryData(createdIndex.get(), ids, vectors); + + test_util::FaissWriteBinaryIndex(&createdIndexWithData, indexPath); + + // Setup jni + NiceMock jniEnv; + NiceMock mockJNIUtil; + + std::unique_ptr loadedIndexPointer( + reinterpret_cast(knn_jni::faiss_wrapper::LoadBinaryIndex( + &mockJNIUtil, &jniEnv, (jstring)&indexPath))); + + // Compare serialized versions + auto createIndexSerialization = + test_util::FaissGetSerializedBinaryIndex(&createdIndexWithData); + auto loadedIndexSerialization = test_util::FaissGetSerializedBinaryIndex( + reinterpret_cast(loadedIndexPointer.get())); + + ASSERT_NE(0, loadedIndexSerialization.data.size()); + ASSERT_EQ(createIndexSerialization.data.size(), + loadedIndexSerialization.data.size()); + + for (int i = 0; i < loadedIndexSerialization.data.size(); ++i) { + ASSERT_EQ(createIndexSerialization.data[i], + loadedIndexSerialization.data[i]); + } + + // Clean up + std::remove(indexPath.c_str()); +} + +TEST(FaissLoadIndexTest, HNSWPQDisableSdcTable) { + // Check that when we load an HNSWPQ index, the sdc table is not present. + faiss::idx_t numIds = 256; + int dim = 2; + std::vector ids = test_util::Range(numIds); + std::vector vectors = test_util::RandomVectors(dim, numIds, randomDataMin, randomDataMax); + + std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); + faiss::MetricType metricType = faiss::METRIC_L2; + std::string indexDescription = "HNSW16,PQ1x4"; + + std::unique_ptr faissIndex(test_util::FaissCreateIndex(dim, indexDescription, metricType)); + test_util::FaissTrainIndex(faissIndex.get(), numIds, vectors.data()); + auto faissIndexWithIDMap = test_util::FaissAddData(faissIndex.get(), ids, vectors); + test_util::FaissWriteIndex(&faissIndexWithIDMap, indexPath); + + // Setup jni + NiceMock jniEnv; + NiceMock mockJNIUtil; + + std::unique_ptr loadedIndexPointer( + reinterpret_cast(knn_jni::faiss_wrapper::LoadIndex( + &mockJNIUtil, &jniEnv, (jstring)&indexPath))); + + // Cast down until we get to the pq backed storage index and checke the size of the table + auto idMapIndex = dynamic_cast(loadedIndexPointer.get()); + ASSERT_NE(idMapIndex, nullptr); + auto hnswPQIndex = dynamic_cast(idMapIndex->index); + ASSERT_NE(hnswPQIndex, nullptr); + auto pqIndex = dynamic_cast(hnswPQIndex->storage); + ASSERT_NE(pqIndex, nullptr); + ASSERT_EQ(0, pqIndex->pq.sdc_table.size()); +} + +TEST(FaissLoadIndexTest, IVFPQDisablePrecomputeTable) { + faiss::idx_t numIds = 256; + int dim = 2; + std::vector ids = test_util::Range(numIds); + std::vector vectors = test_util::RandomVectors(dim, numIds, randomDataMin, randomDataMax); + + std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); + faiss::MetricType metricType = faiss::METRIC_L2; + std::string indexDescription = "IVF4,PQ1x4"; + + std::unique_ptr faissIndex(test_util::FaissCreateIndex(dim, indexDescription, metricType)); + test_util::FaissTrainIndex(faissIndex.get(), numIds, vectors.data()); + auto faissIndexWithIDMap = test_util::FaissAddData(faissIndex.get(), ids, vectors); + test_util::FaissWriteIndex(&faissIndexWithIDMap, indexPath); + + // Setup jni + NiceMock jniEnv; + NiceMock mockJNIUtil; + + std::unique_ptr loadedIndexPointer( + reinterpret_cast(knn_jni::faiss_wrapper::LoadIndex( + &mockJNIUtil, &jniEnv, (jstring)&indexPath))); + + // Cast down until we get to the ivfpq-l2 state + auto idMapIndex = dynamic_cast(loadedIndexPointer.get()); + ASSERT_NE(idMapIndex, nullptr); + auto ivfpqIndex = dynamic_cast(idMapIndex->index); + ASSERT_NE(ivfpqIndex, nullptr); + ASSERT_EQ(0, ivfpqIndex->precomputed_table->size()); +} + +TEST(FaissQueryIndexTest, BasicAssertions) { + // Define the index data + faiss::idx_t numIds = 100; + int dim = 16; + std::vector ids = test_util::Range(numIds); + std::vector vectors = test_util::RandomVectors(dim, numIds, randomDataMin, randomDataMax); + faiss::MetricType metricType = faiss::METRIC_L2; - std::string method = "Flat"; // TODO: Revert bach to HNSW32,Flat + std::string method = "HNSW32,Flat"; // Define query data int k = 10; + int efSearch = 20; + std::unordered_map methodParams; + methodParams[knn_jni::EF_SEARCH] = reinterpret_cast(&efSearch); + int numQueries = 100; std::vector> queries; @@ -204,21 +518,22 @@ TEST(FaissQueryIndexTest, BasicAssertions) { // Create the index std::unique_ptr createdIndex( - test_util::FaissCreateIndex(2, method, metricType)); + test_util::FaissCreateIndex(dim, method, metricType)); auto createdIndexWithData = test_util::FaissAddData(createdIndex.get(), ids, vectors); // Setup jni - JNIEnv *jniEnv = nullptr; + NiceMock jniEnv; NiceMock mockJNIUtil; + auto methodParamsJ = reinterpret_cast(&methodParams); for (auto query : queries) { std::unique_ptr *>> results( reinterpret_cast *> *>( knn_jni::faiss_wrapper::QueryIndex( - &mockJNIUtil, jniEnv, + &mockJNIUtil, &jniEnv, reinterpret_cast(&createdIndexWithData), - reinterpret_cast(&query), k))); + reinterpret_cast(&query), k, methodParamsJ, nullptr))); ASSERT_EQ(k, results->size()); @@ -229,58 +544,718 @@ TEST(FaissQueryIndexTest, BasicAssertions) { } } -TEST(FaissFreeTest, BasicAssertions) { +TEST(FaissQueryBinaryIndexTest, BasicAssertions) { // Define the data - int dim = 2; - faiss::MetricType metricType = faiss::METRIC_L2; - std::string method = "Flat"; // TODO: Revert bach to HNSW32,Flat + faiss::idx_t numIds = 200; + std::vector ids; + auto vectors = std::vector(numIds); + int dim = 128; + for (int64_t i = 0; i < numIds; ++i) { + ids.push_back(i); + for (int j = 0; j < dim / 8; ++j) { + vectors.push_back(test_util::RandomInt(0, 255)); + } + } + + // Define query data + int k = 10; + int numQueries = 100; + std::vector> queries; + + for (int i = 0; i < numQueries; i++) { + std::vector query; + query.reserve(dim); + for (int j = 0; j < dim; j++) { + query.push_back(test_util::RandomInt(0, 255)); + } + queries.push_back(query); + } // Create the index - faiss::Index *createdIndex( - test_util::FaissCreateIndex(dim, method, metricType)); + std::string method = "BHNSW32"; + std::unique_ptr createdIndex( + test_util::FaissCreateBinaryIndex(dim, method)); + auto createdIndexWithData = + test_util::FaissAddBinaryData(createdIndex.get(), ids, vectors); - // Free created index --> memory check should catch failure - knn_jni::faiss_wrapper::Free(reinterpret_cast(createdIndex)); -} + // Setup jni + NiceMock jniEnv; + NiceMock mockJNIUtil; -TEST(FaissInitLibraryTest, BasicAssertions) { - knn_jni::faiss_wrapper::InitLibrary(); -} + for (auto query : queries) { + std::unique_ptr *>> results( + reinterpret_cast *> *>( + knn_jni::faiss_wrapper::QueryBinaryIndex_WithFilter( + &mockJNIUtil, &jniEnv, + reinterpret_cast(&createdIndexWithData), + reinterpret_cast(&query), k, nullptr, nullptr, 0, nullptr))); -TEST(FaissTrainIndexTest, BasicAssertions) { - // Define the index configuration - int dim = 2; - std::string spaceType = knn_jni::L2; - std::string index_description = "IVF4,Flat"; + ASSERT_EQ(k, results->size()); - std::unordered_map parametersMap; - parametersMap[knn_jni::SPACE_TYPE] = (jobject) &spaceType; - parametersMap[knn_jni::INDEX_DESCRIPTION] = (jobject) &index_description; + // Need to free up each result + for (auto it : *results.get()) { + delete it; + } + } +} - // Define training data - int numTrainingVectors = 256; - std::vector trainingVectors; +//Test for a bug reported in https://github.com/opensearch-project/k-NN/issues/1435 +TEST(FaissQueryIndexWithFilterTest1435, BasicAssertions) { + // Define the index data + faiss::idx_t numIds = 200; + std::vector ids; + std::vector vectors; + std::vector> queries; - for (int i = 0; i < numTrainingVectors; ++i) { - for (int j = 0; j < dim; ++j) { - trainingVectors.push_back(test_util::RandomFloat(-500.0, 500.0)); + int dim = 16; + for (int64_t i = 1; i < numIds + 1; i++) { + std::vector query; + query.reserve(dim); + ids.push_back(i); + for (int j = 0; j < dim; j++) { + float vector = test_util::RandomFloat(-500.0, 500.0); + vectors.push_back(vector); + query.push_back(vector); } + queries.push_back(query); + } + + int num_bits = test_util::bits2words(164); + std::vector bitmap(num_bits,0); + std::vector filterIds; + + for (int64_t i = 154; i < 163; i++) { + filterIds.push_back(i); + test_util::setBitSet(i, bitmap.data(), bitmap.size()); } + std::unordered_set filterIdSet(filterIds.begin(), filterIds.end()); + + faiss::MetricType metricType = faiss::METRIC_L2; + std::string method = "HNSW32,Flat"; + + // Create the index + std::unique_ptr createdIndex( + test_util::FaissCreateIndex(dim, method, metricType)); + auto createdIndexWithData = + test_util::FaissAddData(createdIndex.get(), ids, vectors); // Setup jni - JNIEnv *jniEnv = nullptr; + NiceMock jniEnv; NiceMock mockJNIUtil; + EXPECT_CALL(mockJNIUtil, + GetJavaLongArrayLength( + &jniEnv, reinterpret_cast(&bitmap))) + .WillRepeatedly(Return(bitmap.size())); - // Perform training - std::unique_ptr> trainedIndexSerialization( - reinterpret_cast *>( - knn_jni::faiss_wrapper::TrainIndex( - &mockJNIUtil, jniEnv, (jobject) ¶metersMap, dim, - reinterpret_cast(&trainingVectors)))); + int k = 20; + for (auto query : queries) { + std::unique_ptr *>> results( + reinterpret_cast *> *>( + knn_jni::faiss_wrapper::QueryIndex_WithFilter( + &mockJNIUtil, &jniEnv, + reinterpret_cast(&createdIndexWithData), + reinterpret_cast(&query), k, nullptr, + reinterpret_cast(&bitmap), 0, nullptr))); - std::unique_ptr trainedIndex( - test_util::FaissLoadFromSerializedIndex(trainedIndexSerialization.get())); + ASSERT_TRUE(results->size() <= filterIds.size()); + ASSERT_TRUE(results->size() > 0); + for (const auto& pairPtr : *results) { + auto it = filterIdSet.find(pairPtr->first); + ASSERT_NE(it, filterIdSet.end()); + } - // Confirm that training succeeded - ASSERT_TRUE(trainedIndex->is_trained); + // Need to free up each result + for (auto it : *results.get()) { + delete it; + } + } +} + +TEST(FaissQueryIndexWithParentFilterTest, BasicAssertions) { + // Define the index data + faiss::idx_t numIds = 100; + std::vector ids; + std::vector vectors; + std::vector parentIds; + int dim = 16; + for (int64_t i = 1; i < numIds + 1; i++) { + if (i % 10 == 0) { + parentIds.push_back(i); + continue; + } + ids.push_back(i); + for (int j = 0; j < dim; j++) { + vectors.push_back(test_util::RandomFloat(-500.0, 500.0)); + } + } + + faiss::MetricType metricType = faiss::METRIC_L2; + std::string method = "HNSW32,Flat"; + + // Define query data + int k = 20; + int numQueries = 100; + std::vector> queries; + + for (int i = 0; i < numQueries; i++) { + std::vector query; + query.reserve(dim); + for (int j = 0; j < dim; j++) { + query.push_back(test_util::RandomFloat(-500.0, 500.0)); + } + queries.push_back(query); + } + + // Create the index + std::unique_ptr createdIndex( + test_util::FaissCreateIndex(dim, method, metricType)); + auto createdIndexWithData = + test_util::FaissAddData(createdIndex.get(), ids, vectors); + + int efSearch = 100; + std::unordered_map methodParams; + methodParams[knn_jni::EF_SEARCH] = reinterpret_cast(&efSearch); + + // Setup jni + NiceMock jniEnv; + NiceMock mockJNIUtil; + EXPECT_CALL(mockJNIUtil, + GetJavaIntArrayLength( + &jniEnv, reinterpret_cast(&parentIds))) + .WillRepeatedly(Return(parentIds.size())); + for (auto query : queries) { + std::unique_ptr *>> results( + reinterpret_cast *> *>( + knn_jni::faiss_wrapper::QueryIndex( + &mockJNIUtil, &jniEnv, + reinterpret_cast(&createdIndexWithData), + reinterpret_cast(&query), k, reinterpret_cast(&methodParams), + reinterpret_cast(&parentIds)))); + + // Even with k 20, result should have only 10 which is total number of groups + ASSERT_EQ(10, results->size()); + // Result should be one for each group + std::set idSet; + for (const auto& pairPtr : *results) { + idSet.insert(pairPtr->first / 10); + } + ASSERT_EQ(10, idSet.size()); + + // Need to free up each result + for (auto it : *results.get()) { + delete it; + } + } +} + +TEST(FaissFreeTest, BasicAssertions) { + // Define the data + int dim = 2; + faiss::MetricType metricType = faiss::METRIC_L2; + std::string method = "HNSW32,Flat"; + + // Create the index + faiss::Index *createdIndex( + test_util::FaissCreateIndex(dim, method, metricType)); + + // Free created index --> memory check should catch failure + knn_jni::faiss_wrapper::Free(reinterpret_cast(createdIndex), JNI_FALSE); +} + + +TEST(FaissBinaryFreeTest, BasicAssertions) { + // Define the data + int dim = 8; + std::string method = "BHNSW32"; + + // Create the index + faiss::IndexBinary *createdIndex( + test_util::FaissCreateBinaryIndex(dim, method)); + + // Free created index --> memory check should catch failure + knn_jni::faiss_wrapper::Free(reinterpret_cast(createdIndex), JNI_TRUE); +} + +TEST(FaissInitLibraryTest, BasicAssertions) { + knn_jni::faiss_wrapper::InitLibrary(); +} + +TEST(FaissTrainIndexTest, BasicAssertions) { + // Define the index configuration + int dim = 2; + std::string spaceType = knn_jni::L2; + std::string index_description = "IVF4,Flat"; + + std::unordered_map parametersMap; + parametersMap[knn_jni::SPACE_TYPE] = (jobject) &spaceType; + parametersMap[knn_jni::INDEX_DESCRIPTION] = (jobject) &index_description; + + // Define training data + int numTrainingVectors = 256; + std::vector trainingVectors = test_util::RandomVectors(dim, numTrainingVectors, randomDataMin, randomDataMax); + + // Setup jni + NiceMock jniEnv; + NiceMock mockJNIUtil; + + // Perform training + std::unique_ptr> trainedIndexSerialization( + reinterpret_cast *>( + knn_jni::faiss_wrapper::TrainIndex( + &mockJNIUtil, &jniEnv, (jobject) ¶metersMap, dim, + reinterpret_cast(&trainingVectors)))); + + std::unique_ptr trainedIndex( + test_util::FaissLoadFromSerializedIndex(trainedIndexSerialization.get())); + + // Confirm that training succeeded + ASSERT_TRUE(trainedIndex->is_trained); +} + +TEST(FaissTrainByteIndexTest, BasicAssertions) { + // Define the index configuration + int dim = 2; + std::string spaceType = knn_jni::L2; + std::string index_description = "IVF4,SQ8_direct_signed"; + + std::unordered_map parametersMap; + parametersMap[knn_jni::SPACE_TYPE] = (jobject) &spaceType; + parametersMap[knn_jni::INDEX_DESCRIPTION] = (jobject) &index_description; + + // Define training data + int numTrainingVectors = 256; + std::vector trainingVectors = test_util::RandomByteVectors(dim, numTrainingVectors, -128, 127); + + // Setup jni + NiceMock jniEnv; + NiceMock mockJNIUtil; + + // Perform training + std::unique_ptr> trainedIndexSerialization( + reinterpret_cast *>( + knn_jni::faiss_wrapper::TrainByteIndex( + &mockJNIUtil, &jniEnv, (jobject) ¶metersMap, dim, + reinterpret_cast(&trainingVectors)))); + + std::unique_ptr trainedIndex( + test_util::FaissLoadFromSerializedIndex(trainedIndexSerialization.get())); + + // Confirm that training succeeded + ASSERT_TRUE(trainedIndex->is_trained); +} + +TEST(FaissCreateHnswSQfp16IndexTest, BasicAssertions) { + // Define the data + faiss::idx_t numIds = 200; + std::vector ids; + std::vector vectors; + int dim = 2; + vectors.reserve(dim * numIds); + for (int64_t i = 0; i < numIds; ++i) { + ids.push_back(i); + for (int j = 0; j < dim; ++j) { + vectors.push_back(test_util::RandomFloat(-500.0, 500.0)); + } + } + + std::string spaceType = knn_jni::L2; + std::string index_description = "HNSW32,SQfp16"; + + std::unordered_map parametersMap; + parametersMap[knn_jni::SPACE_TYPE] = (jobject)&spaceType; + parametersMap[knn_jni::INDEX_DESCRIPTION] = (jobject)&index_description; + + for (auto throwIOException : std::array {false, true}) { + const std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); + + // Set up jni + NiceMock jniEnv; + NiceMock mockJNIUtil; + JavaFileIndexOutputMock javaFileIndexOutputMock {indexPath}; + setUpJavaFileOutputMocking(javaFileIndexOutputMock, mockJNIUtil, throwIOException); + + EXPECT_CALL(mockJNIUtil, + GetJavaObjectArrayLength( + &jniEnv, reinterpret_cast(&vectors))) + .WillRepeatedly(Return(vectors.size())); + + // Create the index + std::unique_ptr faissMethods(new FaissMethods()); + knn_jni::faiss_wrapper::IndexService IndexService(std::move(faissMethods)); + + try { + createIndexIteratively(&mockJNIUtil, &jniEnv, ids, vectors, dim, (jobject) (&javaFileIndexOutputMock), parametersMap, &IndexService); + // Make sure we close a file stream before reopening the created file. + javaFileIndexOutputMock.file_writer.close(); + } catch (const std::exception& e) { + ASSERT_STREQ("Failed to write index to disk", e.what()); + ASSERT_TRUE(throwIOException); + continue; + } + ASSERT_FALSE(throwIOException); + + // Make sure index can be loaded + std::unique_ptr index(test_util::FaissLoadIndex(indexPath)); + auto indexIDMap = dynamic_cast(index.get()); + + // Assert that Index is of type IndexHNSWSQ + ASSERT_NE(indexIDMap, nullptr); + ASSERT_NE(dynamic_cast(indexIDMap->index), nullptr); + + // Clean up + std::remove(indexPath.c_str()); + } // End for +} + +TEST(FaissIsSharedIndexStateRequired, BasicAssertions) { + int d = 128; + int hnswM = 16; + int ivfNlist = 4; + int pqM = 1; + int pqCodeSize = 8; + std::unique_ptr indexHNSWL2(new faiss::IndexHNSW(d, hnswM, faiss::METRIC_L2)); + std::unique_ptr indexIVFPQIP(new faiss::IndexIVFPQ( + new faiss::IndexFlat(d, faiss::METRIC_INNER_PRODUCT), + d, + ivfNlist, + pqM, + pqCodeSize, + faiss::METRIC_INNER_PRODUCT + )); + std::unique_ptr indexIVFPQL2(new faiss::IndexIVFPQ( + new faiss::IndexFlat(d, faiss::METRIC_L2), + d, + ivfNlist, + pqM, + pqCodeSize, + faiss::METRIC_L2 + )); + std::unique_ptr indexIDMapIVFPQL2(new faiss::IndexIDMap( + new faiss::IndexIVFPQ( + new faiss::IndexFlat(d, faiss::METRIC_L2), + d, + ivfNlist, + pqM, + pqCodeSize, + faiss::METRIC_L2 + ) + )); + std::unique_ptr indexIDMapIVFPQIP(new faiss::IndexIDMap( + new faiss::IndexIVFPQ( + new faiss::IndexFlat(d, faiss::METRIC_INNER_PRODUCT), + d, + ivfNlist, + pqM, + pqCodeSize, + faiss::METRIC_INNER_PRODUCT + ) + )); + jlong nullAddress = 0; + + ASSERT_FALSE(knn_jni::faiss_wrapper::IsSharedIndexStateRequired((jlong) indexHNSWL2.get())); + ASSERT_FALSE(knn_jni::faiss_wrapper::IsSharedIndexStateRequired((jlong) indexIVFPQIP.get())); + ASSERT_FALSE(knn_jni::faiss_wrapper::IsSharedIndexStateRequired((jlong) indexIDMapIVFPQIP.get())); + ASSERT_FALSE(knn_jni::faiss_wrapper::IsSharedIndexStateRequired((jlong) nullAddress)); + + ASSERT_TRUE(knn_jni::faiss_wrapper::IsSharedIndexStateRequired((jlong) indexIVFPQL2.get())); + ASSERT_TRUE(knn_jni::faiss_wrapper::IsSharedIndexStateRequired((jlong) indexIDMapIVFPQL2.get())); +} + +TEST(FaissInitAndSetSharedIndexState, BasicAssertions) { + faiss::idx_t numIds = 256; + int dim = 2; + std::vector ids = test_util::Range(numIds); + std::vector vectors = test_util::RandomVectors(dim, numIds, randomDataMin, randomDataMax); + + std::string indexPath = test_util::RandomString(10, "tmp/", ".faiss"); + faiss::MetricType metricType = faiss::METRIC_L2; + std::string indexDescription = "IVF4,PQ1x4"; + + std::unique_ptr faissIndex(test_util::FaissCreateIndex(dim, indexDescription, metricType)); + test_util::FaissTrainIndex(faissIndex.get(), numIds, vectors.data()); + auto faissIndexWithIDMap = test_util::FaissAddData(faissIndex.get(), ids, vectors); + test_util::FaissWriteIndex(&faissIndexWithIDMap, indexPath); + + // Setup jni + NiceMock jniEnv; + NiceMock mockJNIUtil; + + std::unique_ptr loadedIndexPointer( + reinterpret_cast(knn_jni::faiss_wrapper::LoadIndex( + &mockJNIUtil, &jniEnv, (jstring)&indexPath))); + + auto idMapIndex = dynamic_cast(loadedIndexPointer.get()); + ASSERT_NE(idMapIndex, nullptr); + auto ivfpqIndex = dynamic_cast(idMapIndex->index); + ASSERT_NE(ivfpqIndex, nullptr); + ASSERT_EQ(0, ivfpqIndex->precomputed_table->size()); + jlong sharedModelAddress = knn_jni::faiss_wrapper::InitSharedIndexState((jlong) loadedIndexPointer.get()); + ASSERT_EQ(0, ivfpqIndex->precomputed_table->size()); + knn_jni::faiss_wrapper::SetSharedIndexState((jlong) loadedIndexPointer.get(), sharedModelAddress); + ASSERT_EQ(sharedModelAddress, (jlong) ivfpqIndex->precomputed_table); + ASSERT_NE(0, ivfpqIndex->precomputed_table->size()); + ASSERT_EQ(1, ivfpqIndex->use_precomputed_table); + knn_jni::faiss_wrapper::FreeSharedIndexState(sharedModelAddress); +} + +TEST(FaissRangeSearchQueryIndexTest, BasicAssertions) { + // Define the index data + faiss::idx_t numIds = 200; + int dim = 2; + std::vector ids = test_util::Range(numIds); + std::vector vectors = test_util::RandomVectors(dim, numIds, rangeSearchRandomDataMin, rangeSearchRandomDataMax); + + faiss::MetricType metricType = faiss::METRIC_L2; + std::string method = "HNSW32,Flat"; + + int efSearch = 20; + std::unordered_map methodParams; + methodParams[knn_jni::EF_SEARCH] = reinterpret_cast(&efSearch); + auto methodParamsJ = reinterpret_cast(&methodParams); + + // Define query data + int numQueries = 100; + std::vector> queries; + + for (int i = 0; i < numQueries; i++) { + std::vector query; + query.reserve(dim); + for (int j = 0; j < dim; j++) { + query.push_back(test_util::RandomFloat(rangeSearchRandomDataMin, rangeSearchRandomDataMax)); + } + queries.push_back(query); + } + + // Create the index + std::unique_ptr createdIndex( + test_util::FaissCreateIndex(dim, method, metricType)); + auto createdIndexWithData = + test_util::FaissAddData(createdIndex.get(), ids, vectors); + + // Setup jni + NiceMock jniEnv; + NiceMock mockJNIUtil; + + int maxResultWindow = 20000; + + for (auto query : queries) { + std::unique_ptr *>> results( + reinterpret_cast *> *>( + + knn_jni::faiss_wrapper::RangeSearch( + &mockJNIUtil, &jniEnv, + reinterpret_cast(&createdIndexWithData), + reinterpret_cast(&query), rangeSearchRadius, methodParamsJ, maxResultWindow, nullptr))); + + // assert result size is not 0 + ASSERT_NE(0, results->size()); + + + // Need to free up each result + for (auto it : *results) { + delete it; + } + } +} + +TEST(FaissRangeSearchQueryIndexTest_WhenHitMaxWindowResult, BasicAssertions){ + // Define the index data + faiss::idx_t numIds = 200; + int dim = 2; + std::vector ids = test_util::Range(numIds); + std::vector vectors = test_util::RandomVectors(dim, numIds, rangeSearchRandomDataMin, rangeSearchRandomDataMax); + + faiss::MetricType metricType = faiss::METRIC_L2; + std::string method = "HNSW32,Flat"; + + // Define query data + int numQueries = 100; + std::vector> queries; + + for (int i = 0; i < numQueries; i++) { + std::vector query; + query.reserve(dim); + for (int j = 0; j < dim; j++) { + query.push_back(test_util::RandomFloat(rangeSearchRandomDataMin, rangeSearchRandomDataMax)); + } + queries.push_back(query); + } + + // Create the index + std::unique_ptr createdIndex( + test_util::FaissCreateIndex(dim, method, metricType)); + auto createdIndexWithData = + test_util::FaissAddData(createdIndex.get(), ids, vectors); + + // Setup jni + NiceMock jniEnv; + NiceMock mockJNIUtil; + + int maxResultWindow = 10; + + for (auto query : queries) { + std::unique_ptr *>> results( + reinterpret_cast *> *>( + + knn_jni::faiss_wrapper::RangeSearch( + &mockJNIUtil, &jniEnv, + reinterpret_cast(&createdIndexWithData), + reinterpret_cast(&query), rangeSearchRadius, nullptr, maxResultWindow, nullptr))); + + // assert result size is not 0 + ASSERT_NE(0, results->size()); + // assert result size is equal to maxResultWindow + ASSERT_EQ(maxResultWindow, results->size()); + + // Need to free up each result + for (auto it : *results) { + delete it; + } + } +} + +TEST(FaissRangeSearchQueryIndexTestWithFilterTest, BasicAssertions) { + // Define the index data + faiss::idx_t numIds = 200; + int dim = 2; + std::vector ids = test_util::Range(numIds); + std::vector vectors = test_util::RandomVectors(dim, numIds, rangeSearchRandomDataMin, rangeSearchRandomDataMax); + + faiss::MetricType metricType = faiss::METRIC_L2; + std::string method = "HNSW32,Flat"; + + // Define query data + int numQueries = 100; + std::vector> queries; + + for (int i = 0; i < numQueries; i++) { + std::vector query; + query.reserve(dim); + for (int j = 0; j < dim; j++) { + query.push_back(test_util::RandomFloat(rangeSearchRandomDataMin, rangeSearchRandomDataMax)); + } + queries.push_back(query); + } + + // Create the index + std::unique_ptr createdIndex( + test_util::FaissCreateIndex(dim, method, metricType)); + auto createdIndexWithData = + test_util::FaissAddData(createdIndex.get(), ids, vectors); + + // Setup jni + NiceMock jniEnv; + NiceMock mockJNIUtil; + + int num_bits = test_util::bits2words(164); + std::vector bitmap(num_bits,0); + std::vector filterIds; + + for (int64_t i = 1; i < 50; i++) { + filterIds.push_back(i); + test_util::setBitSet(i, bitmap.data(), bitmap.size()); + } + std::unordered_set filterIdSet(filterIds.begin(), filterIds.end()); + + int maxResultWindow = 20000; + + for (auto query : queries) { + std::unique_ptr *>> results( + reinterpret_cast *> *>( + + knn_jni::faiss_wrapper::RangeSearchWithFilter( + &mockJNIUtil, &jniEnv, + reinterpret_cast(&createdIndexWithData), + reinterpret_cast(&query), rangeSearchRadius, nullptr, maxResultWindow, + reinterpret_cast(&bitmap), 0, nullptr))); + + // assert result size is not 0 + ASSERT_NE(0, results->size()); + ASSERT_TRUE(results->size() <= filterIds.size()); + for (const auto& pairPtr : *results) { + auto it = filterIdSet.find(pairPtr->first); + ASSERT_NE(it, filterIdSet.end()); + } + + // Need to free up each result + for (auto it : *results) { + delete it; + } + } +} + +TEST(FaissRangeSearchQueryIndexTestWithParentFilterTest, BasicAssertions) { + // Define the index data + faiss::idx_t numIds = 100; + std::vector ids; + std::vector vectors; + std::vector parentIds; + int dim = 2; + for (int64_t i = 1; i < numIds + 1; i++) { + if (i % 10 == 0) { + parentIds.push_back(i); + continue; + } + ids.push_back(i); + for (int j = 0; j < dim; j++) { + vectors.push_back(test_util::RandomFloat(rangeSearchRandomDataMin, rangeSearchRandomDataMax)); + } + } + + faiss::MetricType metricType = faiss::METRIC_L2; + std::string method = "HNSW32,Flat"; + + // Define query data + int numQueries = 1; + std::vector> queries; + + for (int i = 0; i < numQueries; i++) { + std::vector query; + query.reserve(dim); + for (int j = 0; j < dim; j++) { + query.push_back(test_util::RandomFloat(rangeSearchRandomDataMin, rangeSearchRandomDataMax)); + } + queries.push_back(query); + } + + // Create the index + std::unique_ptr createdIndex( + test_util::FaissCreateIndex(dim, method, metricType)); + auto createdIndexWithData = + test_util::FaissAddData(createdIndex.get(), ids, vectors); + + // Setup jni + NiceMock jniEnv; + NiceMock mockJNIUtil; + EXPECT_CALL(mockJNIUtil, + GetJavaIntArrayLength( + &jniEnv, reinterpret_cast(&parentIds))) + .WillRepeatedly(Return(parentIds.size())); + + int maxResultWindow = 10000; + + for (auto query : queries) { + std::unique_ptr *>> results( + reinterpret_cast *> *>( + + knn_jni::faiss_wrapper::RangeSearchWithFilter( + &mockJNIUtil, &jniEnv, + reinterpret_cast(&createdIndexWithData), + reinterpret_cast(&query), rangeSearchRadius, nullptr, maxResultWindow, nullptr, 0, + reinterpret_cast(&parentIds)))); + + // assert result size is not 0 + ASSERT_NE(0, results->size()); + // Result should be one for each group + std::set idSet; + for (const auto& pairPtr : *results) { + idSet.insert(pairPtr->first / 10); + } + ASSERT_NE(0, idSet.size()); + + // Need to free up each result + for (auto it : *results) { + delete it; + } + } } diff --git a/jni/tests/faiss_wrapper_unit_test.cpp b/jni/tests/faiss_wrapper_unit_test.cpp new file mode 100644 index 000000000..e23ca8528 --- /dev/null +++ b/jni/tests/faiss_wrapper_unit_test.cpp @@ -0,0 +1,488 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +#include "faiss_wrapper.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "jni_util.h" +#include "jni.h" +#include "test_util.h" +#include "faiss/IndexHNSW.h" +#include "faiss/IndexIDMap.h" +#include "faiss/IndexIVFFlat.h" +#include "faiss/IndexIVFPQ.h" + +using ::testing::NiceMock; + +using idx_t = faiss::idx_t; + +struct FaissMockIndex : faiss::IndexHNSW { + explicit FaissMockIndex(idx_t d) : faiss::IndexHNSW(d, 32) { + } +}; + +struct MockIVFIndex : faiss::IndexIVFFlat { + explicit MockIVFIndex() = default; +}; + +struct MockIVFIdMap : faiss::IndexIDMap { + mutable idx_t nCalled{}; + mutable const float *xCalled{}; + mutable idx_t kCalled{}; + mutable float *distancesCalled{}; + mutable idx_t *labelsCalled{}; + mutable const faiss::SearchParametersIVF *paramsCalled{}; + + explicit MockIVFIdMap(MockIVFIndex *index) : faiss::IndexIDMapTemplate(index) { + } + + void search( + idx_t n, + const float *x, + idx_t k, + float *distances, + idx_t *labels, + const faiss::SearchParameters *params) const override { + nCalled = n; + xCalled = x; + kCalled = k; + distancesCalled = distances; + labelsCalled = labels; + paramsCalled = dynamic_cast(params); + } + + void resetMock() const { + nCalled = 0; + xCalled = nullptr; + kCalled = 0; + distancesCalled = nullptr; + labelsCalled = nullptr; + paramsCalled = nullptr; + } +}; + +struct FaissMockIdMap : faiss::IndexIDMap { + mutable idx_t nCalled{}; + mutable const float *xCalled{}; + mutable int kCalled{}; + mutable float radiusCalled{}; + mutable float *distancesCalled{}; + mutable idx_t *labelsCalled{}; + mutable const faiss::SearchParametersHNSW *paramsCalled{}; + mutable faiss::RangeSearchResult *resCalled{}; + + explicit FaissMockIdMap(FaissMockIndex *index) : faiss::IndexIDMapTemplate(index) { + } + + void search( + idx_t n, + const float *x, + idx_t k, + float *distances, + idx_t *labels, + const faiss::SearchParameters *params) const override { + nCalled = n; + xCalled = x; + kCalled = k; + distancesCalled = distances; + labelsCalled = labels; + paramsCalled = dynamic_cast(params); + } + + void range_search( + idx_t n, + const float *x, + float radius, + faiss::RangeSearchResult *res, + const faiss::SearchParameters *params) const override { + nCalled = n; + xCalled = x; + radiusCalled = radius; + resCalled = res; + paramsCalled = dynamic_cast(params); + } + + void resetMock() const { + nCalled = 0; + xCalled = nullptr; + kCalled = 0; + radiusCalled = 0.0; + distancesCalled = nullptr; + labelsCalled = nullptr; + resCalled = nullptr; + paramsCalled = nullptr; + } +}; + +struct QueryIndexInput { + string description; + int k; + int filterIdType; + bool filterIdsPresent; + bool parentIdsPresent; + int efSearch; + int nprobe; +}; + +struct RangeSearchTestInput { + std::string description; + float radius; + int efSearch; + int filterIdType; + bool filterIdsPresent; + bool parentIdsPresent; +}; + +class FaissWrapperParameterizedTestFixture : public testing::TestWithParam { +public: + FaissWrapperParameterizedTestFixture() : index_(3), id_map_(&index_) { + index_.hnsw.efSearch = 100; // assigning 100 to make sure default of 16 is not used anywhere + } + +protected: + FaissMockIndex index_; + FaissMockIdMap id_map_; +}; + +class FaissWrapperParameterizedRangeSearchTestFixture : public testing::TestWithParam { +public: + FaissWrapperParameterizedRangeSearchTestFixture() : index_(3), id_map_(&index_) { + index_.hnsw.efSearch = 100; // assigning 100 to make sure default of 16 is not used anywhere + } + +protected: + FaissMockIndex index_; + FaissMockIdMap id_map_; +}; + +class FaissWrapperIVFQueryTestFixture : public testing::TestWithParam { +public: + FaissWrapperIVFQueryTestFixture() : ivf_id_map_(&ivf_index_) { + ivf_index_.nprobe = 100; + }; + +protected: + MockIVFIndex ivf_index_; + MockIVFIdMap ivf_id_map_; +}; + +namespace query_index_test { + TEST_P(FaissWrapperParameterizedTestFixture, QueryIndexHNSWTests) { + //Given + JNIEnv *jniEnv = nullptr; + NiceMock mockJNIUtil; + + QueryIndexInput const &input = GetParam(); + float query[] = {1.2, 2.3, 3.4}; + + int efSearch = input.efSearch; + int expectedEfSearch = 100; //default set in mock + std::unordered_map methodParams; + if (efSearch != -1) { + expectedEfSearch = input.efSearch; + methodParams[knn_jni::EF_SEARCH] = reinterpret_cast(&efSearch); + } + + std::vector *parentIdPtr = nullptr; + if (input.parentIdsPresent) { + std::vector parentId; + parentId.reserve(2); + parentId.push_back(1); + parentId.push_back(2); + parentIdPtr = &parentId; + + EXPECT_CALL(mockJNIUtil, + GetJavaIntArrayLength( + jniEnv, reinterpret_cast(parentIdPtr))) + .WillOnce(testing::Return(parentId.size())); + + EXPECT_CALL(mockJNIUtil, + GetIntArrayElements( + jniEnv, reinterpret_cast(parentIdPtr), nullptr)) + .WillOnce(testing::Return(new int[2]{1, 2})); + } + + // When + knn_jni::faiss_wrapper::QueryIndex( + &mockJNIUtil, jniEnv, + reinterpret_cast(&id_map_), + reinterpret_cast(&query), input.k, reinterpret_cast(&methodParams), + reinterpret_cast(parentIdPtr)); + + // Then + int actualEfSearch = id_map_.paramsCalled->efSearch; + // Asserting the captured argument + EXPECT_EQ(input.k, id_map_.kCalled); + EXPECT_EQ(expectedEfSearch, actualEfSearch); + if (input.parentIdsPresent) { + faiss::IDGrouper *grouper = id_map_.paramsCalled->grp; + EXPECT_TRUE(grouper != nullptr); + } + + id_map_.resetMock(); + } + + INSTANTIATE_TEST_CASE_P( + QueryIndexHNSWTests, + FaissWrapperParameterizedTestFixture, + ::testing::Values( + QueryIndexInput {"algoParams present, parent absent", 10, 0, false, false, 200, -1 }, + QueryIndexInput {"algoParams present, parent absent", 10, 0, false, false, -1, -1 }, + QueryIndexInput {"algoParams present, parent present", 10, 0, false, true, 200, -1 }, + QueryIndexInput {"algoParams absent, parent present", 10, 0, false, true, -1, -1 } + ) + ); +} + +namespace query_index_with_filter_test { + TEST_P(FaissWrapperParameterizedTestFixture, QueryIndexWithFilterHNSWTests) { + //Given + JNIEnv *jniEnv = nullptr; + NiceMock mockJNIUtil; + + QueryIndexInput const &input = GetParam(); + float query[] = {1.2, 2.3, 3.4}; + + std::vector *parentIdPtr = nullptr; + if (input.parentIdsPresent) { + std::vector parentId; + parentId.reserve(2); + parentId.push_back(1); + parentId.push_back(2); + parentIdPtr = &parentId; + + EXPECT_CALL(mockJNIUtil, + GetJavaIntArrayLength( + jniEnv, reinterpret_cast(parentIdPtr))) + .WillOnce(testing::Return(parentId.size())); + + EXPECT_CALL(mockJNIUtil, + GetIntArrayElements( + jniEnv, reinterpret_cast(parentIdPtr), nullptr)) + .WillOnce(testing::Return(new int[2]{1, 2})); + } + + std::vector *filterptr = nullptr; + if (input.filterIdsPresent) { + std::vector filter; + filter.reserve(2); + filter.push_back(1); + filter.push_back(2); + filterptr = &filter; + } + + int efSearch = input.efSearch; + int expectedEfSearch = 100; //default set in mock + std::unordered_map methodParams; + if (efSearch != -1) { + expectedEfSearch = input.efSearch; + methodParams[knn_jni::EF_SEARCH] = reinterpret_cast(&efSearch); + } + + // When + knn_jni::faiss_wrapper::QueryIndex_WithFilter( + &mockJNIUtil, jniEnv, + reinterpret_cast(&id_map_), + reinterpret_cast(&query), input.k, reinterpret_cast(&methodParams), + reinterpret_cast(filterptr), + input.filterIdType, + reinterpret_cast(parentIdPtr)); + + // Then + int actualEfSearch = id_map_.paramsCalled->efSearch; + // Asserting the captured argument + EXPECT_EQ(input.k, id_map_.kCalled); + EXPECT_EQ(expectedEfSearch, actualEfSearch); + if (input.parentIdsPresent) { + faiss::IDGrouper *grouper = id_map_.paramsCalled->grp; + EXPECT_TRUE(grouper != nullptr); + } + if (input.filterIdsPresent) { + faiss::IDSelector *sel = id_map_.paramsCalled->sel; + EXPECT_TRUE(sel != nullptr); + } + id_map_.resetMock(); + } + + INSTANTIATE_TEST_CASE_P( + QueryIndexWithFilterHNSWTests, + FaissWrapperParameterizedTestFixture, + ::testing::Values( + QueryIndexInput { "algoParams present, parent absent, filter absent", 10, 0, false, false, 200, -1 }, + QueryIndexInput { "algoParams present, parent absent, filter absent, filter type 1", 10, 1, false, false, + 200, -1}, + QueryIndexInput { "algoParams absent, parent absent, filter present", 10, 0, true, false, -1, -1}, + QueryIndexInput { "algoParams absent, parent absent, filter present, filter type 1", 10, 1, true, false, -1, + -1}, + QueryIndexInput { "algoParams present, parent present, filter absent", 10, 0, false, true, 200, -1 }, + QueryIndexInput { "algoParams present, parent present, filter absent, filter type 1", 10, 1, false, true, + 150, -1}, + QueryIndexInput { "algoParams absent, parent present, filter present", 10, 0, true, true, -1, -1}, + QueryIndexInput { "algoParams absent, parent present, filter present, filter type 1",10, 1, true, true, -1, + -1 } + ) + ); +} + +namespace range_search_test { + TEST_P(FaissWrapperParameterizedRangeSearchTestFixture, RangeSearchHNSWTests) { + // Given + JNIEnv *jniEnv = nullptr; + NiceMock mockJNIUtil; + + RangeSearchTestInput const &input = GetParam(); + float query[] = {1.2, 2.3, 3.4}; + float radius = input.radius; + int maxResultWindow = 100; // Set your max result window + + std::unordered_map methodParams; + int efSearch = input.efSearch; + int expectedEfSearch = 100; // default set in mock + if (efSearch != -1) { + expectedEfSearch = input.efSearch; + methodParams[knn_jni::EF_SEARCH] = reinterpret_cast(&efSearch); + } + + std::vector *parentIdPtr = nullptr; + if (input.parentIdsPresent) { + std::vector parentId; + parentId.reserve(2); + parentId.push_back(1); + parentId.push_back(2); + parentIdPtr = &parentId; + + EXPECT_CALL(mockJNIUtil, + GetJavaIntArrayLength( + jniEnv, reinterpret_cast(parentIdPtr))) + .WillOnce(testing::Return(parentId.size())); + + EXPECT_CALL(mockJNIUtil, + GetIntArrayElements( + jniEnv, reinterpret_cast(parentIdPtr), nullptr)) + .WillOnce(testing::Return(new int[2]{1, 2})); + } + + std::vector filter; + std::vector *filterptr = nullptr; + if (input.filterIdsPresent) { + std::vector filter; + filter.reserve(2); + filter.push_back(1); + filter.push_back(2); + filterptr = &filter; + } + + // When + knn_jni::faiss_wrapper::RangeSearchWithFilter( + &mockJNIUtil, jniEnv, + reinterpret_cast(&id_map_), + reinterpret_cast(&query), radius, reinterpret_cast(&methodParams), + maxResultWindow, + reinterpret_cast(filterptr), + input.filterIdType, + reinterpret_cast(parentIdPtr)); + + // Then + int actualEfSearch = id_map_.paramsCalled->efSearch; + // Asserting the captured argument + EXPECT_EQ(expectedEfSearch, actualEfSearch); + if (input.parentIdsPresent) { + faiss::IDGrouper *grouper = id_map_.paramsCalled->grp; + EXPECT_TRUE(grouper != nullptr); + } + if (input.filterIdsPresent) { + faiss::IDSelector *sel = id_map_.paramsCalled->sel; + EXPECT_TRUE(sel != nullptr); + } + id_map_.resetMock(); + } + + INSTANTIATE_TEST_CASE_P( + RangeSearchHNSWTests, + FaissWrapperParameterizedRangeSearchTestFixture, + ::testing::Values( + RangeSearchTestInput{"algoParams present, parent absent, filter absent", 10.0f, 200, 0, false, false}, + RangeSearchTestInput{"algoParams present, parent absent, filter absent, filter type 1", 10.0f, 200, 1, false + , false}, + RangeSearchTestInput{"algoParams absent, parent absent, filter present", 10.0f, -1, 0, true, false}, + RangeSearchTestInput{"algoParams absent, parent absent, filter present, filter type 1", 10.0f, -1, 1, true, + false}, + RangeSearchTestInput{"algoParams present, parent present, filter absent", 10.0f, 200, 0, false, true}, + RangeSearchTestInput{"algoParams present, parent present, filter absent, filter type 1", 10.0f, 150, 1, + false, true}, + RangeSearchTestInput{"algoParams absent, parent present, filter present", 10.0f, -1, 0, true, true}, + RangeSearchTestInput{"algoParams absent, parent present, filter present, filter type 1", 10.0f, -1, 1, true, + true} + ) + ); +} + +namespace query_index_with_filter_test_ivf { + TEST_P(FaissWrapperIVFQueryTestFixture, QueryIndexIVFTest) { + //Given + JNIEnv *jniEnv = nullptr; + NiceMock mockJNIUtil; + + QueryIndexInput const &input = GetParam(); + float query[] = {1.2, 2.3, 3.4}; + + int nprobe = input.nprobe; + int expectedNprobe = 100; //default set in mock + std::unordered_map methodParams; + if (nprobe != -1) { + expectedNprobe = input.nprobe; + methodParams[knn_jni::NPROBES] = reinterpret_cast(&nprobe); + } + + std::vector *filterptr = nullptr; + if (input.filterIdsPresent) { + std::vector filter; + filter.reserve(2); + filter.push_back(1); + filter.push_back(2); + filterptr = &filter; + } + + // When + knn_jni::faiss_wrapper::QueryIndex_WithFilter( + &mockJNIUtil, jniEnv, + reinterpret_cast(&ivf_id_map_), + reinterpret_cast(&query), input.k, reinterpret_cast(&methodParams), + reinterpret_cast(filterptr), + input.filterIdType, + nullptr); + + //Then + int actualEfSearch = ivf_id_map_.paramsCalled->nprobe; + // Asserting the captured argument + EXPECT_EQ(input.k, ivf_id_map_.kCalled); + EXPECT_EQ(expectedNprobe, actualEfSearch); + if (input.parentIdsPresent) { + faiss::IDGrouper *grouper = ivf_id_map_.paramsCalled->grp; + EXPECT_TRUE(grouper != nullptr); + } + ivf_id_map_.resetMock(); + } + + INSTANTIATE_TEST_CASE_P( + QueryIndexIVFTest, + FaissWrapperIVFQueryTestFixture, + ::testing::Values( + QueryIndexInput{"algoParams present, parent absent", 10, 0, false, false, -1, 200 }, + QueryIndexInput{"algoParams present, parent absent", 10,0, false, false, -1, -1 }, + QueryIndexInput{"algoParams present, parent present", 10, 0, true, true, -1, 200 }, + QueryIndexInput{"algoParams absent, parent present", 10, 0, true, true, -1, -1 } + ) + ); +} diff --git a/jni/tests/mocks/faiss_index_mock.h b/jni/tests/mocks/faiss_index_mock.h new file mode 100644 index 000000000..521cbb2d3 --- /dev/null +++ b/jni/tests/mocks/faiss_index_mock.h @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + + #ifndef OPENSEARCH_KNN_FAISS_INDEX_MOCK_H + #define OPENSEARCH_KNN_FAISS_INDEX_MOCK_H + +#include "faiss/Index.h" +#include "faiss/IndexBinary.h" +#include + +using idx_t = int64_t; + +class MockIndex : public faiss::Index { +public: + MOCK_METHOD(void, add, (idx_t n, const float* x), (override)); + MOCK_METHOD(void, search, (idx_t n, const float* x, idx_t k, float* distances, idx_t* labels, const faiss::SearchParameters* params), (const, override)); + MOCK_METHOD(void, reset, (), (override)); +}; + +class MockIndexBinary : public faiss::IndexBinary { +public: + MOCK_METHOD(void, add, (idx_t n, const uint8_t* x), (override)); + MOCK_METHOD(void, search, (idx_t n, const uint8_t* x, idx_t k, int32_t* distances, idx_t* labels, const faiss::SearchParameters* params), (const, override)); + MOCK_METHOD(void, reset, (), (override)); +}; + +#endif // OPENSEARCH_KNN_FAISS_INDEX_MOCK_H \ No newline at end of file diff --git a/jni/tests/mocks/faiss_index_service_mock.h b/jni/tests/mocks/faiss_index_service_mock.h new file mode 100644 index 000000000..6065b5bbe --- /dev/null +++ b/jni/tests/mocks/faiss_index_service_mock.h @@ -0,0 +1,66 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +#ifndef OPENSEARCH_KNN_FAISS_INDEX_SERVICE_MOCK_H +#define OPENSEARCH_KNN_FAISS_INDEX_SERVICE_MOCK_H + +#include "faiss_index_service.h" +#include + +using ::knn_jni::faiss_wrapper::FaissMethods; +using ::knn_jni::faiss_wrapper::IndexService; +typedef std::unordered_map StringToJObjectMap; + +class MockIndexService : public IndexService { +public: + explicit MockIndexService(std::unique_ptr _faissMethods) + : IndexService(std::move(_faissMethods)) { + } + + MOCK_METHOD( + long, + initIndex, + ( + knn_jni::JNIUtilInterface * jniUtil, + JNIEnv * env, + faiss::MetricType metric, + std::string indexDescription, + int dim, + int numIds, + int threadCount, + StringToJObjectMap parameters + ), + (override)); + + MOCK_METHOD( + void, + insertToIndex, + ( + int dim, + int numIds, + int threadCount, + int64_t vectorsAddress, + std::vector & ids, + long indexPtr + ), + (override)); + + MOCK_METHOD( + void, + writeIndex, + ( + faiss::IOWriter* writer, + long indexPtr + ), + (override)); +}; + +#endif // OPENSEARCH_KNN_FAISS_INDEX_SERVICE_MOCK_H diff --git a/jni/tests/mocks/faiss_methods_mock.h b/jni/tests/mocks/faiss_methods_mock.h new file mode 100644 index 000000000..304269501 --- /dev/null +++ b/jni/tests/mocks/faiss_methods_mock.h @@ -0,0 +1,28 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + + #ifndef OPENSEARCH_KNN_FAISS_METHODS_MOCK_H + #define OPENSEARCH_KNN_FAISS_METHODS_MOCK_H + +#include "faiss_methods.h" +#include + +class MockFaissMethods : public knn_jni::faiss_wrapper::FaissMethods { +public: + MOCK_METHOD(faiss::Index*, indexFactory, (int d, const char* description, faiss::MetricType metric), (override)); + MOCK_METHOD(faiss::IndexBinary*, indexBinaryFactory, (int d, const char* description), (override)); + MOCK_METHOD(faiss::IndexIDMapTemplate*, indexIdMap, (faiss::Index* index), (override)); + MOCK_METHOD(faiss::IndexIDMapTemplate*, indexBinaryIdMap, (faiss::IndexBinary* index), (override)); + MOCK_METHOD(void, writeIndex, (const faiss::Index* idx, faiss::IOWriter* writer), (override)); + MOCK_METHOD(void, writeIndexBinary, (const faiss::IndexBinary* idx, faiss::IOWriter* writer), (override)); +}; + +#endif // OPENSEARCH_KNN_FAISS_METHODS_MOCK_H \ No newline at end of file diff --git a/jni/tests/native_stream_support_util.h b/jni/tests/native_stream_support_util.h new file mode 100644 index 000000000..ba926d690 --- /dev/null +++ b/jni/tests/native_stream_support_util.h @@ -0,0 +1,166 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +#ifndef KNNPLUGIN_JNI_TESTS_NATIVE_STREAM_SUPPORT_UTIL_H_ +#define KNNPLUGIN_JNI_TESTS_NATIVE_STREAM_SUPPORT_UTIL_H_ + +#include +#include + +#include "test_util.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace test_util { + +// Mocking IndexInputWithBuffer. +struct JavaIndexInputMock { + JavaIndexInputMock(std::string _readTargetBytes, int32_t _bufSize) + : readTargetBytes(std::move(_readTargetBytes)), + nextReadIdx(), + buffer(_bufSize) { + } + + // This method is simulating `copyBytes` in IndexInputWithBuffer. + int32_t simulateCopyReads(int64_t readBytes) { + readBytes = std::min(readBytes, (int64_t) buffer.size()); + readBytes = std::min(readBytes, (int64_t) (readTargetBytes.size() - nextReadIdx)); + std::memcpy(buffer.data(), readTargetBytes.data() + nextReadIdx, readBytes); + nextReadIdx += readBytes; + return (int32_t) readBytes; + } + + int64_t remainingBytes() { + return readTargetBytes.size() - nextReadIdx; + } + + static std::string makeRandomBytes(int32_t bytesSize) { + // Define the list of possible characters + static const string CHARACTERS + = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv" + "wxyz0123456789"; + + // Create a random number generator + std::random_device rd; + std::mt19937 generator(rd()); + + // Create a distribution to uniformly select from all characters + std::uniform_int_distribution<> distribution( + 0, CHARACTERS.size() - 1); + + // Pre-allocate the string with the desired length + std::string randomString(bytesSize, '\0'); + + // Use generate_n with a back_inserter iterator + std::generate_n(randomString.begin(), bytesSize, [&]() { + return CHARACTERS[distribution(generator)]; + }); + + return randomString; + } + + std::string readTargetBytes; + int64_t nextReadIdx; + std::vector buffer; +}; // struct JavaIndexInputMock + + + +struct JavaFileIndexInputMock { + JavaFileIndexInputMock(std::ifstream &_file_input, int32_t _buf_size) + : file_input(_file_input), + buffer(_buf_size) { + } + + int64_t remainingBytes() { + std::streampos currentPos = file_input.tellg(); + file_input.seekg(0, std::ios::end); + std::streamsize fileSize = file_input.tellg(); + file_input.seekg(currentPos); + return fileSize - currentPos; + } + + int32_t copyBytes(int64_t read_size) { + const auto copy_size = std::min((int64_t) buffer.size(), read_size); + file_input.read(buffer.data(), copy_size); + return (int32_t) copy_size; + } + + std::ifstream &file_input; + std::vector buffer; +}; // struct JavaFileIndexInputMock + + + +struct JavaFileIndexOutputMock { + explicit JavaFileIndexOutputMock(const std::string &_file_path) + : file_writer(_file_path, std::ios::out | std::ios::binary), + buffer(64 * 1024) { + file_writer.exceptions(std::ios::failbit | std::ios::badbit); + } + + void writeBytes(int length) { + file_writer.write(buffer.data(), length); + } + + std::ofstream file_writer; + std::vector buffer; +}; // struct JavaFileIndexOutputMock + +struct StreamIOError : public std::runtime_error { + StreamIOError() + : std::runtime_error(what()) { + } + + const char* what() const noexcept final { + return "Mocking IOError in Java side."; + } +}; // struct StreamIOError + +inline void setUpJavaFileOutputMocking(JavaFileIndexOutputMock &java_index_output, + MockJNIUtil &mockJni, + bool throwIOException) { + EXPECT_CALL(mockJni, GetPrimitiveArrayCritical(::testing::_, ::testing::_, ::testing::_)) + .WillRepeatedly([&java_index_output](JNIEnv *env, + jarray array, + jboolean *isCopy) { + return (jbyte *) java_index_output.buffer.data(); + }); + + EXPECT_CALL(mockJni, CallNonvirtualVoidMethodA(::testing::_, ::testing::_, ::testing::_, ::testing::_, ::testing::_)) + .WillRepeatedly([&java_index_output](JNIEnv *env, + jobject obj, + jclass clazz, + jmethodID methodID, + jvalue *args) { + const auto bytes_to_write = args[0].i; + java_index_output.writeBytes(bytes_to_write); + }); + + EXPECT_CALL(mockJni, GetJavaBytesArrayLength(::testing::_, ::testing::_)) + .WillRepeatedly([&java_index_output](JNIEnv *env, jbyteArray arrayJ) { + return java_index_output.buffer.size(); + }); + + if (throwIOException) { + EXPECT_CALL(mockJni, HasExceptionInStack(::testing::_, ::testing::_)) + .WillRepeatedly([](JNIEnv *env, const char* errorMsg){ + throw StreamIOError{}; + }); + } else { + EXPECT_CALL(mockJni, HasExceptionInStack(::testing::_, ::testing::_)) + .WillRepeatedly(::testing::Return()); + } +} + +} // namespace test_util + +#endif //KNNPLUGIN_JNI_TESTS_NATIVE_STREAM_SUPPORT_UTIL_H_ diff --git a/jni/tests/nmslib_stream_support_test.cpp b/jni/tests/nmslib_stream_support_test.cpp new file mode 100644 index 000000000..7db94d5e3 --- /dev/null +++ b/jni/tests/nmslib_stream_support_test.cpp @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// The OpenSearch Contributors require contributions made to +// this file be licensed under the Apache-2.0 license or a +// compatible open source license. +// +// Modifications Copyright OpenSearch Contributors. See +// GitHub history for details. + +#include "nmslib_wrapper.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "jni_util.h" +#include "test_util.h" +#include "native_stream_support_util.h" + +using ::test_util::JavaFileIndexInputMock; +using ::test_util::JavaFileIndexOutputMock; +using ::test_util::MockJNIUtil; +using ::test_util::StreamIOError; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::_; + +void setUpJavaFileInputMocking(JavaFileIndexInputMock &java_index_input, MockJNIUtil &mockJni) { + // Set up mocking values + mocking behavior in a method. + EXPECT_CALL(mockJni, CallNonvirtualIntMethodA(_, _, _, _, _)) + .WillRepeatedly([&java_index_input](JNIEnv *env, + jobject obj, + jclass clazz, + jmethodID methodID, + jvalue *args) { + return java_index_input.copyBytes(args[0].j); + }); + EXPECT_CALL(mockJni, CallNonvirtualLongMethodA(_, _, _, _, _)) + .WillRepeatedly([&java_index_input](JNIEnv *env, + jobject obj, + jclass clazz, + jmethodID methodID, + jvalue *args) { + return java_index_input.remainingBytes(); + }); + EXPECT_CALL(mockJni, GetPrimitiveArrayCritical(_, _, _)) + .WillRepeatedly([&java_index_input](JNIEnv *env, + jarray array, + jboolean *isCopy) { + return (jbyte *) java_index_input.buffer.data(); + }); + EXPECT_CALL(mockJni, ReleasePrimitiveArrayCritical(_, _, _, _)).WillRepeatedly(Return()); +} + +TEST(NmslibStreamLoadingTest, BasicAssertions) { + for (auto throwIOException : std::array {false, true}) { + // Initialize nmslib + similarity::initLibrary(); + + // Define index data + int numIds = 100; + std::vector ids; + auto vectors = new std::vector(); + int dim = 2; + vectors->reserve(dim * numIds); + for (int i = 0; i < numIds; ++i) { + ids.push_back(i); + for (int j = 0; j < dim; ++j) { + vectors->push_back(test_util::RandomFloat(-500.0, 500.0)); + } + } + + std::string spaceType = knn_jni::L2; + std::string indexPath = test_util::RandomString( + 10, "/tmp/", ".nmslib"); + + std::unordered_map parametersMap; + int efConstruction = 512; + int m = 96; + + parametersMap[knn_jni::SPACE_TYPE] = (jobject) &spaceType; + parametersMap[knn_jni::EF_CONSTRUCTION] = (jobject) &efConstruction; + parametersMap[knn_jni::M] = (jobject) &m; + + // Set up jni + NiceMock jniEnv; + + NiceMock mockJNIUtil; + JavaFileIndexOutputMock javaFileIndexOutputMock {indexPath}; + setUpJavaFileOutputMocking(javaFileIndexOutputMock, mockJNIUtil, throwIOException); + knn_jni::stream::NativeEngineIndexOutputMediator mediator {&mockJNIUtil, &jniEnv, (jobject) (&javaFileIndexOutputMock)}; + knn_jni::stream::NmslibOpenSearchIOWriter writer {&mediator}; + + EXPECT_CALL(mockJNIUtil, + GetJavaObjectArrayLength( + &jniEnv, reinterpret_cast(vectors))) + .WillRepeatedly(Return(vectors->size())); + + EXPECT_CALL(mockJNIUtil, + GetJavaIntArrayLength(&jniEnv, reinterpret_cast(&ids))) + .WillRepeatedly(Return(ids.size())); + + EXPECT_CALL(mockJNIUtil, + ConvertJavaMapToCppMap(&jniEnv, reinterpret_cast(¶metersMap))) + .WillRepeatedly(Return(parametersMap)); + + // Create the index + try { + knn_jni::nmslib_wrapper::CreateIndex( + &mockJNIUtil, &jniEnv, reinterpret_cast(&ids), + (jlong) vectors, dim, (jobject) (&javaFileIndexOutputMock), + (jobject) ¶metersMap); + javaFileIndexOutputMock.file_writer.close(); + } catch (const StreamIOError& e) { + continue; + } + + // Create Java index input mock. + std::ifstream file_input{indexPath, std::ios::binary}; + const int32_t buffer_size = 128; + JavaFileIndexInputMock java_file_index_input_mock{file_input, buffer_size}; + setUpJavaFileInputMocking(java_file_index_input_mock, mockJNIUtil); + + // Make sure index can be loaded + jlong index = knn_jni::nmslib_wrapper::LoadIndexWithStream( + &mockJNIUtil, &jniEnv, + (jobject) (&java_file_index_input_mock), + (jobject) (¶metersMap)); + + knn_jni::nmslib_wrapper::Free(index); + + // Clean up + file_input.close(); + std::remove(indexPath.c_str()); + } // End for +} diff --git a/jni/tests/nmslib_wrapper_test.cpp b/jni/tests/nmslib_wrapper_test.cpp index 96c3e1d1c..b7690ee94 100644 --- a/jni/tests/nmslib_wrapper_test.cpp +++ b/jni/tests/nmslib_wrapper_test.cpp @@ -10,6 +10,7 @@ */ #include "nmslib_wrapper.h" +#include "nmslib_stream_support.h" #include @@ -17,125 +18,166 @@ #include "gtest/gtest.h" #include "jni_util.h" #include "test_util.h" +#include "native_stream_support_util.h" +using ::test_util::JavaFileIndexOutputMock; +using ::test_util::StreamIOError; +using ::test_util::setUpJavaFileOutputMocking; using ::testing::NiceMock; using ::testing::Return; -TEST(NmslibCreateIndexTest, BasicAssertions) { - // Initialize nmslib +TEST(NmslibIndexWrapperSearchTest, BasicAssertions) { similarity::initLibrary(); - - // Define index data - int numIds = 100; - std::vector ids; - std::vector> vectors; + knn_jni::nmslib_wrapper::IndexWrapper indexWrapper = knn_jni::nmslib_wrapper::IndexWrapper("l2"); + int k = 10; int dim = 2; - for (int i = 0; i < numIds; ++i) { - ids.push_back(i); + std::unique_ptr rawQueryvector(new float[dim]); + std::unique_ptr queryObject(new similarity::Object(-1, -1, dim*sizeof(float), rawQueryvector.get())); + similarity::KNNQuery knnQuery(*(indexWrapper.space), queryObject.get(), k); + indexWrapper.index->Search((similarity::KNNQuery *)nullptr); +} - std::vector vect; - vect.reserve(dim); - for (int j = 0; j < dim; ++j) { - vect.push_back(test_util::RandomFloat(-500.0, 500.0)); +TEST(NmslibCreateIndexTest, BasicAssertions) { + for (auto throwIOException : std::array {false, true}) { + // Initialize nmslib + similarity::initLibrary(); + + // Define index data + int numIds = 100; + std::vector ids; + auto *vectors = new std::vector(); + int dim = 2; + vectors->reserve(dim * numIds); + for (int64_t i = 0; i < numIds; ++i) { + ids.push_back(i); + for (int j = 0; j < dim; ++j) { + vectors->push_back(test_util::RandomFloat(-500.0, 500.0)); + } } - vectors.push_back(vect); - } - std::string indexPath = test_util::RandomString(10, "tmp/", ".nmslib"); - std::string spaceType = knn_jni::L2; + std::string indexPath = test_util::RandomString(10, "tmp/", ".nmslib"); + std::string spaceType = knn_jni::L2; - std::unordered_map parametersMap; - int efConstruction = 512; - int m = 96; + std::unordered_map parametersMap; + int efConstruction = 512; + int m = 96; - parametersMap[knn_jni::SPACE_TYPE] = (jobject)&spaceType; - parametersMap[knn_jni::EF_CONSTRUCTION] = (jobject)&efConstruction; - parametersMap[knn_jni::M] = (jobject)&m; + parametersMap[knn_jni::SPACE_TYPE] = (jobject)&spaceType; + parametersMap[knn_jni::EF_CONSTRUCTION] = (jobject)&efConstruction; + parametersMap[knn_jni::M] = (jobject)&m; - // Set up jni - JNIEnv *jniEnv = nullptr; - NiceMock mockJNIUtil; + // Set up jni + NiceMock jniEnv; + NiceMock mockJNIUtil; + JavaFileIndexOutputMock javaFileIndexOutputMock {indexPath}; + setUpJavaFileOutputMocking(javaFileIndexOutputMock, mockJNIUtil, throwIOException); - EXPECT_CALL(mockJNIUtil, - GetJavaObjectArrayLength( - jniEnv, reinterpret_cast(&vectors))) - .WillRepeatedly(Return(vectors.size())); + EXPECT_CALL(mockJNIUtil, + GetJavaObjectArrayLength( + &jniEnv, reinterpret_cast(&vectors))) + .WillRepeatedly(Return(vectors->size())); - EXPECT_CALL(mockJNIUtil, - GetJavaIntArrayLength(jniEnv, reinterpret_cast(&ids))) + EXPECT_CALL(mockJNIUtil, + GetJavaIntArrayLength(&jniEnv, reinterpret_cast(&ids))) .WillRepeatedly(Return(ids.size())); - // Create the index - knn_jni::nmslib_wrapper::CreateIndex( - &mockJNIUtil, jniEnv, reinterpret_cast(&ids), - reinterpret_cast(&vectors), (jstring)&indexPath, - (jobject)¶metersMap); + // Create the index + try { + knn_jni::nmslib_wrapper::CreateIndex( + &mockJNIUtil, &jniEnv, reinterpret_cast(&ids), + (jlong) vectors, dim, (jobject)(&javaFileIndexOutputMock), + (jobject)¶metersMap); + } catch (const StreamIOError& e) { + ASSERT_TRUE(throwIOException); + continue; + } + ASSERT_FALSE(throwIOException); - // Make sure index can be loaded - std::unique_ptr> space( + // Make sure we close a file stream before reopening the created file. + javaFileIndexOutputMock.file_writer.close(); + + // Make sure index can be loaded + std::unique_ptr> space( similarity::SpaceFactoryRegistry::Instance().CreateSpace( - spaceType, similarity::AnyParams())); - std::vector params; - std::unique_ptr> loadedIndex( + spaceType, similarity::AnyParams())); + std::vector params; + std::unique_ptr> loadedIndex( test_util::NmslibLoadIndex(indexPath, space.get(), spaceType, params)); - // Clean up - std::remove(indexPath.c_str()); + // Clean up + std::remove(indexPath.c_str()); + } } TEST(NmslibLoadIndexTest, BasicAssertions) { - // Initialize nmslib - similarity::initLibrary(); - - // Define index data - int numIds = 100; - std::vector ids; - std::vector> vectors; - int dim = 2; - for (int i = 0; i < numIds; ++i) { - ids.push_back(i); - - std::vector vect; - vect.reserve(dim); - for (int j = 0; j < dim; ++j) { + for (auto throwIOException : std::array {false, true}) { + // Initialize nmslib + similarity::initLibrary(); + + // Define index data + int numIds = 100; + std::vector ids; + std::vector> vectors; + int dim = 2; + for (int i = 0; i < numIds; ++i) { + ids.push_back(i); + + std::vector vect; + vect.reserve(dim); + for (int j = 0; j < dim; ++j) { vect.push_back(test_util::RandomFloat(-500.0, 500.0)); + } + vectors.push_back(vect); } - vectors.push_back(vect); - } - std::string indexPath = test_util::RandomString(10, "tmp/", ".nmslib"); - std::string spaceType = knn_jni::L2; - std::unique_ptr> space( + std::string indexPath = test_util::RandomString(10, "tmp/", ".nmslib"); + std::string spaceType = knn_jni::L2; + std::unique_ptr> space( similarity::SpaceFactoryRegistry::Instance().CreateSpace( - spaceType, similarity::AnyParams())); + spaceType, similarity::AnyParams())); - std::vector indexParameters; + std::vector indexParameters; + + // Setup jni + NiceMock jniEnv; + NiceMock mockJNIUtil; + JavaFileIndexOutputMock javaFileIndexOutputMock {indexPath}; + setUpJavaFileOutputMocking(javaFileIndexOutputMock, mockJNIUtil, throwIOException); + knn_jni::stream::NativeEngineIndexOutputMediator mediator {&mockJNIUtil, &jniEnv, (jobject) (&javaFileIndexOutputMock)}; + knn_jni::stream::NmslibOpenSearchIOWriter writer {&mediator}; - // Create index and write to disk - std::unique_ptr> createdIndex( + // Create index and write to disk + std::unique_ptr> createdIndex( test_util::NmslibCreateIndex(ids.data(), vectors, space.get(), spaceType, indexParameters)); - test_util::NmslibWriteIndex(createdIndex.get(), indexPath); - // Setup jni - JNIEnv *jniEnv = nullptr; - NiceMock mockJNIUtil; + try { + test_util::NmslibWriteIndex(createdIndex.get(), writer); - // Load index - std::unordered_map parametersMap; - parametersMap[knn_jni::SPACE_TYPE] = (jobject)&spaceType; + // Make sure we close a file stream before reopening the created file. + javaFileIndexOutputMock.file_writer.close(); + } catch (const StreamIOError& e) { + ASSERT_TRUE(throwIOException); + continue; + } + ASSERT_FALSE(throwIOException); + + // Load index + std::unordered_map parametersMap; + parametersMap[knn_jni::SPACE_TYPE] = (jobject)&spaceType; - std::unique_ptr loadedIndex( + std::unique_ptr loadedIndex( reinterpret_cast( - knn_jni::nmslib_wrapper::LoadIndex(&mockJNIUtil, jniEnv, - (jstring)&indexPath, - (jobject)¶metersMap))); + knn_jni::nmslib_wrapper::LoadIndex(&mockJNIUtil, &jniEnv, + (jstring)&indexPath, + (jobject)¶metersMap))); - // Check that load succeeds - ASSERT_EQ(createdIndex->StrDesc(), loadedIndex->index->StrDesc()); + // Check that load succeeds + ASSERT_EQ(createdIndex->StrDesc(), loadedIndex->index->StrDesc()); - // Clean up - std::remove(indexPath.c_str()); + // Clean up + std::remove(indexPath.c_str()); + } } TEST(NmslibQueryIndexTest, BasicAssertions) { @@ -158,7 +200,6 @@ TEST(NmslibQueryIndexTest, BasicAssertions) { vectors.push_back(vect); } - std::string indexPath = test_util::RandomString(10, "tmp/", ".nmslib"); std::string spaceType = knn_jni::L2; std::unique_ptr> space( similarity::SpaceFactoryRegistry::Instance().CreateSpace( @@ -174,8 +215,11 @@ TEST(NmslibQueryIndexTest, BasicAssertions) { // Define query data int k = 10; + int efSearch = 20; int numQueries = 100; std::vector> queries; + std::unordered_map methodParams; + methodParams[knn_jni::EF_SEARCH] = reinterpret_cast(&efSearch); for (int i = 0; i < numQueries; i++) { std::vector query; @@ -197,7 +241,7 @@ TEST(NmslibQueryIndexTest, BasicAssertions) { knn_jni::nmslib_wrapper::QueryIndex( &mockJNIUtil, jniEnv, reinterpret_cast(indexWrapper.get()), - reinterpret_cast(&query), k))); + reinterpret_cast(&query), k, nullptr))); ASSERT_EQ(k, results->size()); @@ -228,7 +272,6 @@ TEST(NmslibFreeTest, BasicAssertions) { vectors.push_back(vect); } - std::string indexPath = test_util::RandomString(10, "tmp/", ".nmslib"); std::string spaceType = knn_jni::L2; std::unique_ptr> space( similarity::SpaceFactoryRegistry::Instance().CreateSpace( diff --git a/jni/tests/nmslib_wrapper_unit_test.cpp b/jni/tests/nmslib_wrapper_unit_test.cpp new file mode 100644 index 000000000..ff6b94fc7 --- /dev/null +++ b/jni/tests/nmslib_wrapper_unit_test.cpp @@ -0,0 +1,135 @@ +/* +* SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ +#include "hnswquery.h" +#include "knnquery.h" +#include "nmslib_wrapper.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "jni_util.h" +#include "jni.h" +#include "test_util.h" +#include "method/hnsw.h" +#include "space/space_dummy.h" + +namespace nmslib_query_index_test { + + using ::testing::NiceMock; + + struct QueryIndexHNSWTestInput { + string description; + int k; + int efSearch; + bool expectedHNSWQuery; + }; + + struct MockNMSIndex : similarity::Hnsw { + mutable int kCalled; + mutable int efCalled = -1; + + explicit MockNMSIndex(const similarity::Space &space, const similarity::ObjectVector &data): Hnsw(false, + space, data) { + std::vector input; + input.emplace_back("ef=10"); + similarity::AnyParams ef(input); + this->Hnsw::SetQueryTimeParams(ef); + } + + void Search(similarity::KNNQuery *query, similarity::IdType id) const override { + auto hnsw = dynamic_cast *>(query); + if (hnsw != nullptr) { + kCalled = hnsw->GetK(); + efCalled = hnsw->getEf(); + } else { + kCalled = query->GetK(); + } + similarity::Object object(5, 0, 3*sizeof(float), new float[] { 2.2f, 2.5f, 2.6f }); + similarity::Object* objectPtr = &object; + bool added = query->CheckAndAddToResult(0.0f, objectPtr); + }; + + void resetMocks() const { + kCalled = -1; + efCalled = -1; + } + }; + + class NmslibWrapperParametrizedTestFixture : public testing::TestWithParam { + public: + NmslibWrapperParametrizedTestFixture() : space_(nullptr), index_(nullptr) { + similarity::initLibrary(); + std::string spaceType = knn_jni::L2; + space_ = similarity::SpaceFactoryRegistry::Instance().CreateSpace( + spaceType, similarity::AnyParams()); + index_ = new MockNMSIndex(*space_, similarity::ObjectVector()); + }; + + protected: + MockNMSIndex* index_; + similarity::Space* space_; // Moved from local to member variable + }; + + + TEST_P(NmslibWrapperParametrizedTestFixture, QueryIndexHNSWTests) { + //Given + JNIEnv *jniEnv = nullptr; + NiceMock mockJNIUtil; + + + QueryIndexHNSWTestInput const &input = GetParam(); + float query[] = { 1.2f, 2.3f, 3.4f }; + + std::string spaceType = knn_jni::L2; + std::unique_ptr indexWrapper( + new knn_jni::nmslib_wrapper::IndexWrapper(spaceType)); + indexWrapper->index.reset(index_); + + int efSearch = input.efSearch; + std::unordered_map methodParams; + if (efSearch != -1) { + methodParams[knn_jni::EF_SEARCH] = reinterpret_cast(&efSearch); + } + EXPECT_CALL(mockJNIUtil, + GetJavaFloatArrayLength( + jniEnv, reinterpret_cast(query))) + .WillOnce(testing::Return(3)); + + EXPECT_CALL(mockJNIUtil, + ReleaseFloatArrayElements( + jniEnv, reinterpret_cast(query), query, JNI_ABORT)); + EXPECT_CALL(mockJNIUtil, + GetFloatArrayElements( + jniEnv, reinterpret_cast(query), nullptr)) + .WillOnce(testing::Return(query)); + + knn_jni::nmslib_wrapper::QueryIndex( + &mockJNIUtil, jniEnv, + reinterpret_cast(indexWrapper.get()), + reinterpret_cast(&query), input.k, reinterpret_cast(&methodParams)); + + if (input.expectedHNSWQuery) { + EXPECT_EQ(input.efSearch, index_->efCalled); + EXPECT_EQ(input.k, index_->kCalled); + } else { + EXPECT_EQ(input.k, index_->kCalled); + } + index_->resetMocks(); + } + + INSTANTIATE_TEST_CASE_P( + QueryIndexHNSWTests, + NmslibWrapperParametrizedTestFixture, + ::testing::Values( + QueryIndexHNSWTestInput{"methodParams present", 10, 200, true}, + QueryIndexHNSWTestInput{"methodParams absent", 5, -1, false } + ) + ); +} diff --git a/jni/tests/test_util.cpp b/jni/tests/test_util.cpp index 096f2f415..e337eaaf3 100644 --- a/jni/tests/test_util.cpp +++ b/jni/tests/test_util.cpp @@ -29,6 +29,7 @@ #include "methodfactory.h" #include "params.h" #include "space.h" +#include "method/hnsw.h" test_util::MockJNIUtil::MockJNIUtil() { // Set default for calls. If necessary, these can be overriden with @@ -45,6 +46,26 @@ test_util::MockJNIUtil::MockJNIUtil() { return data; }); + ON_CALL(*this, Convert2dJavaObjectArrayAndStoreToFloatVector) + .WillByDefault([this](JNIEnv *env, jobjectArray array2dJ, int dim, std::vector* data) { + for (const auto &v : + (*reinterpret_cast> *>(array2dJ))) + for (auto item : v) data->push_back(item); + }); + ON_CALL(*this, Convert2dJavaObjectArrayAndStoreToBinaryVector) + .WillByDefault([this](JNIEnv *env, jobjectArray array2dJ, int dim, std::vector* data) { + for (const auto &v : + (*reinterpret_cast> *>(array2dJ))) + for (auto item : v) data->push_back(item); + }); + ON_CALL(*this, Convert2dJavaObjectArrayAndStoreToByteVector) + .WillByDefault([this](JNIEnv *env, jobjectArray array2dJ, int dim, std::vector* data) { + for (const auto &v : + (*reinterpret_cast> *>(array2dJ))) + for (auto item : v) data->push_back(item); + }); + + // arrayJ is re-interpreted as std::vector * ON_CALL(*this, ConvertJavaIntArrayToCppIntVector) .WillByDefault([this](JNIEnv *env, jintArray arrayJ) { @@ -116,6 +137,15 @@ test_util::MockJNIUtil::MockJNIUtil() { reinterpret_cast *>(arrayJ)->data()); }); + + // arrayJ is re-interpreted as a std::vector * and then the data is + // re-interpreted as a jlong * + ON_CALL(*this, GetLongArrayElements) + .WillByDefault([this](JNIEnv *env, jlongArray arrayJ, jboolean *isCopy) { + return reinterpret_cast( + reinterpret_cast *>(arrayJ)->data()); + }); + // arrayJ is re-interpreted as a std::vector * and then the data is // re-interpreted as a jfloat * ON_CALL(*this, GetFloatArrayElements) @@ -133,6 +163,15 @@ test_util::MockJNIUtil::MockJNIUtil() { .size(); }); + // array2dJ is re-interpreted as a std::vector> * and then + // the size of the first element is returned + ON_CALL(*this, GetInnerDimensionOf2dJavaByteArray) + .WillByDefault([this](JNIEnv *env, jobjectArray array2dJ) { + return (*reinterpret_cast> *>( + array2dJ))[0] + .size(); + }); + // arrayJ is re-interpreted as a std::vector * and the size is returned ON_CALL(*this, GetJavaFloatArrayLength) .WillByDefault([this](JNIEnv *env, jfloatArray arrayJ) { @@ -146,6 +185,13 @@ test_util::MockJNIUtil::MockJNIUtil() { return reinterpret_cast *>(arrayJ)->size(); }); + // arrayJ is re-interpreted as a std::vector * and then the size is + // returned + ON_CALL(*this, GetJavaLongArrayLength) + .WillByDefault([this](JNIEnv *env, jlongArray arrayJ) { + return reinterpret_cast *>(arrayJ)->size(); + }); + // arrayJ is re-interpreted as a std::vector> * and then // the 'index' element is re-interpreted as a jobject ON_CALL(*this, GetObjectArrayElement) @@ -193,6 +239,11 @@ test_util::MockJNIUtil::MockJNIUtil() { .WillByDefault( [this](JNIEnv *env, jintArray array, jint *elems, int mode) {}); + // This function should not do anything meaningful in the unit tests + ON_CALL(*this, ReleaseLongArrayElements) + .WillByDefault( + [this](JNIEnv *env, jlongArray array, jlong *elems, int mode) {}); + // array is re-interpreted as a std::vector * and then the bytes from // buf are copied to it ON_CALL(*this, SetByteArrayRegion) @@ -220,12 +271,22 @@ faiss::Index *test_util::FaissCreateIndex(int dim, const std::string &method, return faiss::index_factory(dim, method.c_str(), metric); } +faiss::IndexBinary *test_util::FaissCreateBinaryIndex(int dim, const std::string &method) { + return faiss::index_binary_factory(dim, method.c_str()); +} + faiss::VectorIOWriter test_util::FaissGetSerializedIndex(faiss::Index *index) { faiss::VectorIOWriter vectorIoWriter; faiss::write_index(index, &vectorIoWriter); return vectorIoWriter; } +faiss::VectorIOWriter test_util::FaissGetSerializedBinaryIndex(faiss::IndexBinary *index) { + faiss::VectorIOWriter vectorIoWriter; + faiss::write_index_binary(index, &vectorIoWriter); + return vectorIoWriter; +} + faiss::Index *test_util::FaissLoadFromSerializedIndex( std::vector *indexSerial) { faiss::VectorIOReader vectorIoReader; @@ -233,29 +294,53 @@ faiss::Index *test_util::FaissLoadFromSerializedIndex( return faiss::read_index(&vectorIoReader, 0); } +faiss::IndexBinary *test_util::FaissLoadFromSerializedBinaryIndex( + std::vector *indexSerial) { + faiss::VectorIOReader vectorIoReader; + vectorIoReader.data = *indexSerial; + return faiss::read_index_binary(&vectorIoReader, 0); +} + faiss::IndexIDMap test_util::FaissAddData(faiss::Index *index, - std::vector ids, + std::vector ids, std::vector dataset) { faiss::IndexIDMap idMap = faiss::IndexIDMap(index); idMap.add_with_ids(ids.size(), dataset.data(), ids.data()); return idMap; } +faiss::IndexBinaryIDMap test_util::FaissAddBinaryData(faiss::IndexBinary *index, + std::vector ids, + std::vector dataset) { + faiss::IndexBinaryIDMap idMap = faiss::IndexBinaryIDMap(index); + idMap.add_with_ids(ids.size(), dataset.data(), ids.data()); + return idMap; +} + void test_util::FaissWriteIndex(faiss::Index *index, const std::string &indexPath) { faiss::write_index(index, indexPath.c_str()); } +void test_util::FaissWriteBinaryIndex(faiss::IndexBinary *index, + const std::string &indexPath) { + faiss::write_index_binary(index, indexPath.c_str()); +} + faiss::Index *test_util::FaissLoadIndex(const std::string &indexPath) { return faiss::read_index(indexPath.c_str(), faiss::IO_FLAG_READ_ONLY); } +faiss::IndexBinary *test_util::FaissLoadBinaryIndex(const std::string &indexPath) { + return faiss::read_index_binary(indexPath.c_str(), faiss::IO_FLAG_READ_ONLY); +} + void test_util::FaissQueryIndex(faiss::Index *index, float *query, int k, - float *distances, faiss::Index::idx_t *ids) { + float *distances, faiss::idx_t *ids) { index->search(1, query, k, distances, ids); } -void test_util::FaissTrainIndex(faiss::Index *index, faiss::Index::idx_t n, +void test_util::FaissTrainIndex(faiss::Index *index, faiss::idx_t n, const float *x) { if (auto *indexIvf = dynamic_cast(index)) { if (indexIvf->quantizer_trains_alone == 2) { @@ -290,8 +375,13 @@ similarity::Index *test_util::NmslibCreateIndex( } void test_util::NmslibWriteIndex(similarity::Index *index, - const std::string &indexPath) { - index->SaveIndex(indexPath); + knn_jni::stream::NmslibOpenSearchIOWriter& writer) { + if (auto hnswFloatIndex = dynamic_cast *>(index)) { + hnswFloatIndex->SaveIndexWithStream(writer); + writer.flush(); + } else { + throw std::runtime_error("We only support similarity::Hnsw in NMSLIB."); + } } similarity::Index *test_util::NmslibLoadIndex( @@ -347,3 +437,47 @@ float test_util::RandomFloat(float min, float max) { std::uniform_real_distribution distribution(min, max); return distribution(e1); } + +int test_util::RandomInt(int min, int max) { + std::random_device r; + std::default_random_engine e1(r()); + std::uniform_int_distribution distribution(min, max); + return distribution(e1); +} + +std::vector test_util::RandomVectors(int dim, int64_t numVectors, float min, float max) { + std::vector vectors(dim*numVectors); + for (int64_t i = 0; i < dim*numVectors; i++) { + vectors[i] = test_util::RandomFloat(min, max); + } + return vectors; +} + +std::vector test_util::RandomByteVectors(int dim, int64_t numVectors, int min, int max) { + std::vector vectors(dim*numVectors); + for (int64_t i = 0; i < dim*numVectors; i++) { + vectors[i] = test_util::RandomInt(min, max); + } + return vectors; +} + +std::vector test_util::Range(int64_t numElements) { + std::vector rangeVector(numElements); + for (int64_t i = 0; i < numElements; i++) { + rangeVector[i] = i; + } + return rangeVector; +} + +size_t test_util::bits2words(uint64_t numBits) { + return ((numBits - 1) >> 6) + 1; +} + +void test_util::setBitSet(uint64_t value, jlong* array, size_t size) { + uint64_t wordNum = value >> 6; + if (wordNum >= size ) { + return; + } + jlong bitmask = (1L << (value & 63)); + array[wordNum] |= bitmask; +} \ No newline at end of file diff --git a/jni/tests/test_util.h b/jni/tests/test_util.h index cd47be476..0262c8467 100644 --- a/jni/tests/test_util.h +++ b/jni/tests/test_util.h @@ -24,6 +24,7 @@ #include "faiss/MetaIndexes.h" #include "faiss/MetricType.h" #include "faiss/impl/io.h" +#include "nmslib_stream_support.h" #include "index.h" #include "init.h" #include "jni_util.h" @@ -44,6 +45,12 @@ namespace test_util { // TODO: Figure out why this cant use "new" MOCK_METHOD MOCK_METHOD(std::vector, Convert2dJavaObjectArrayToCppFloatVector, (JNIEnv * env, jobjectArray array2dJ, int dim)); + MOCK_METHOD(void, Convert2dJavaObjectArrayAndStoreToFloatVector, + (JNIEnv * env, jobjectArray array2dJ, int dim, std::vector*vect)); + MOCK_METHOD(void, Convert2dJavaObjectArrayAndStoreToBinaryVector, + (JNIEnv * env, jobjectArray array2dJ, int dim, std::vector*vect)); + MOCK_METHOD(void, Convert2dJavaObjectArrayAndStoreToByteVector, + (JNIEnv * env, jobjectArray array2dJ, int dim, std::vector*vect)); MOCK_METHOD(std::vector, ConvertJavaIntArrayToCppIntVector, (JNIEnv * env, jintArray arrayJ)); MOCK_METHOD2(ConvertJavaMapToCppMap, @@ -62,18 +69,23 @@ namespace test_util { (JNIEnv * env, jfloatArray array, jboolean* isCopy)); MOCK_METHOD(int, GetInnerDimensionOf2dJavaFloatArray, (JNIEnv * env, jobjectArray array2dJ)); + MOCK_METHOD(int, GetInnerDimensionOf2dJavaByteArray, + (JNIEnv * env, jobjectArray array2dJ)); MOCK_METHOD(jint*, GetIntArrayElements, (JNIEnv * env, jintArray array, jboolean* isCopy)); + MOCK_METHOD(jlong*, GetLongArrayElements, + (JNIEnv * env, jlongArray array, jboolean* isCopy)); MOCK_METHOD(int, GetJavaBytesArrayLength, (JNIEnv * env, jbyteArray arrayJ)); MOCK_METHOD(int, GetJavaFloatArrayLength, (JNIEnv * env, jfloatArray arrayJ)); MOCK_METHOD(int, GetJavaIntArrayLength, (JNIEnv * env, jintArray arrayJ)); + MOCK_METHOD(int, GetJavaLongArrayLength, (JNIEnv * env, jlongArray arrayJ)); MOCK_METHOD(int, GetJavaObjectArrayLength, (JNIEnv * env, jobjectArray arrayJ)); MOCK_METHOD(jobject, GetObjectArrayElement, (JNIEnv * env, jobjectArray arrayJ, jsize index)); MOCK_METHOD(void, HasExceptionInStack, (JNIEnv * env)); MOCK_METHOD(void, HasExceptionInStack, - (JNIEnv * env, const std::string& message)); + (JNIEnv * env, const char* message)); MOCK_METHOD(jbyteArray, NewByteArray, (JNIEnv * env, jsize len)); MOCK_METHOD(jobject, NewObject, (JNIEnv * env, jclass clazz, jmethodID methodId, int id, @@ -86,6 +98,8 @@ namespace test_util { (JNIEnv * env, jfloatArray array, jfloat* elems, int mode)); MOCK_METHOD(void, ReleaseIntArrayElements, (JNIEnv * env, jintArray array, jint* elems, jint mode)); + MOCK_METHOD(void, ReleaseLongArrayElements, + (JNIEnv * env, jlongArray array, jlong* elems, jint mode)); MOCK_METHOD(void, SetByteArrayRegion, (JNIEnv * env, jbyteArray array, jsize start, jsize len, const jbyte* buf)); @@ -93,6 +107,16 @@ namespace test_util { (JNIEnv * env, jobjectArray array, jsize index, jobject val)); MOCK_METHOD(void, ThrowJavaException, (JNIEnv * env, const char* type, const char* message)); + MOCK_METHOD(jobject, GetObjectField, + (JNIEnv * env, jobject obj, jfieldID fieldID)); + MOCK_METHOD(jclass, FindClassFromJNIEnv, (JNIEnv * env, const char *name)); + MOCK_METHOD(jmethodID, GetMethodID, (JNIEnv * env, jclass clazz, const char *name, const char *sig)); + MOCK_METHOD(jfieldID, GetFieldID, (JNIEnv * env, jclass clazz, const char *name, const char *sig)); + MOCK_METHOD(jint, CallNonvirtualIntMethodA, (JNIEnv * env, jobject obj, jclass clazz, jmethodID methodID, jvalue* args)); + MOCK_METHOD(jlong, CallNonvirtualLongMethodA, (JNIEnv * env, jobject obj, jclass clazz, jmethodID methodID, jvalue* args)); + MOCK_METHOD(void *, GetPrimitiveArrayCritical, (JNIEnv * env, jarray array, jboolean *isCopy)); + MOCK_METHOD(void, ReleasePrimitiveArrayCritical, (JNIEnv * env, jarray array, void *carray, jint mode)); + MOCK_METHOD(void, CallNonvirtualVoidMethodA, (JNIEnv * env, jobject obj, jclass clazz, jmethodID methodID, jvalue* args)); }; // For our unit tests, we want to ensure that each test tests one function in @@ -102,23 +126,30 @@ namespace test_util { faiss::Index* FaissCreateIndex(int dim, const std::string& method, faiss::MetricType metric); + faiss::IndexBinary* FaissCreateBinaryIndex(int dim, const std::string& method); faiss::VectorIOWriter FaissGetSerializedIndex(faiss::Index* index); + faiss::VectorIOWriter FaissGetSerializedBinaryIndex(faiss::IndexBinary* index); faiss::Index* FaissLoadFromSerializedIndex(std::vector* indexSerial); + faiss::IndexBinary* FaissLoadFromSerializedBinaryIndex(std::vector* indexSerial); faiss::IndexIDMap FaissAddData(faiss::Index* index, - std::vector ids, + std::vector ids, std::vector dataset); - + faiss::IndexBinaryIDMap FaissAddBinaryData(faiss::IndexBinary* index, + std::vector ids, + std::vector dataset); void FaissWriteIndex(faiss::Index* index, const std::string& indexPath); + void FaissWriteBinaryIndex(faiss::IndexBinary* index, const std::string& indexPath); faiss::Index* FaissLoadIndex(const std::string& indexPath); + faiss::IndexBinary* FaissLoadBinaryIndex(const std::string &indexPath); void FaissQueryIndex(faiss::Index* index, float* query, int k, float* distances, - faiss::Index::idx_t* ids); + faiss::idx_t* ids); - void FaissTrainIndex(faiss::Index* index, faiss::Index::idx_t n, + void FaissTrainIndex(faiss::Index* index, faiss::idx_t n, const float* x); // ------------------------------------------------------------------------------- @@ -131,7 +162,7 @@ namespace test_util { const std::vector& indexParameters); void NmslibWriteIndex(similarity::Index* index, - const std::string& indexPath); + knn_jni::stream::NmslibOpenSearchIOWriter& writer); similarity::Index* NmslibLoadIndex( const std::string& indexPath, similarity::Space* space, @@ -149,7 +180,18 @@ namespace test_util { std::string RandomString(size_t length, const std::string& prefix, const std::string& suffix); float RandomFloat(float min, float max); + int RandomInt(int min, int max); + + std::vector RandomVectors(int dim, int64_t numVectors, float min, float max); + + std::vector RandomByteVectors(int dim, int64_t numVectors, int min, int max); + + std::vector Range(int64_t numElements); + + // returns the number of 64 bit words it would take to hold numBits + size_t bits2words(uint64_t numBits); + void setBitSet(uint64_t value, jlong* array, size_t size); // ------------------------------------------------------------------------------- } // namespace test_util diff --git a/micro-benchmarks/README.md b/micro-benchmarks/README.md new file mode 100644 index 000000000..0a676004b --- /dev/null +++ b/micro-benchmarks/README.md @@ -0,0 +1,97 @@ +# OpenSearch K-NN Microbenchmark Suite + +This directory contains the microbenchmark suite of Opensearch K-NN Plugin. It relies on [JMH](http://openjdk.java.net/projects/code-tools/jmh/). + +This module draws a lot of inspiration from [Opensearch benchmarks](https://github.com/opensearch-project/OpenSearch/tree/main/benchmarks). + +## Purpose + +Micro benchmarks are intended to spot performance regressions in performance-critical components. + +The microbenchmark suite is also handy for ad-hoc micro benchmarks but please remove them again before merging your PR. + +## Getting Started + +Just run `gradlew -p micro-benchmarks run` from the project root +directory. It will build all microbenchmarks, execute them and print +the result. + +## Running Microbenchmarks + +Running via an IDE is not supported as the results are meaningless +because we have no control over the JVM running the benchmarks. + +If you want to run a specific benchmark class like, say, +`TransferVectorsBenchmarks`, you can use `--args`: + +``` +gradlew -p micro-benchmarks run --args ' TransferVectorsBenchmarks' +``` + +Setting Heap while running the benchmarks +``` +./gradlew -p micro-benchmarks run --args ' -gc true ' -Djvm.heap.size=4g +``` + +Everything in the `'` gets sent on the command line to JMH. The leading ` ` +inside the `'`s is important. Without it parameters are sometimes sent to +gradle. + +## Adding Microbenchmarks + +Before adding a new microbenchmark, make yourself familiar with the JMH API. You can check our existing microbenchmarks and also the +[JMH samples](http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/). + +In contrast to tests, the actual name of the benchmark class is not relevant to JMH. However, stick to the naming convention and +end the class name of a benchmark with `Benchmark`. To have JMH execute a benchmark, annotate the respective methods with `@Benchmark`. + +## Tips and Best Practices + +To get realistic results, you should exercise care when running benchmarks. Here are a few tips: + +### Do + +* Ensure that the system executing your microbenchmarks has as little load as possible. Shutdown every process that can cause unnecessary + runtime jitter. Watch the `Error` column in the benchmark results to see the run-to-run variance. +* Ensure to run enough warmup iterations to get the benchmark into a stable state. If you are unsure, don't change the defaults. +* Avoid CPU migrations by pinning your benchmarks to specific CPU cores. On Linux you can use `taskset`. +* Fix the CPU frequency to avoid Turbo Boost from kicking in and skewing your results. On Linux you can use `cpufreq-set` and the + `performance` CPU governor. +* Vary the problem input size with `@Param`. +* Use the integrated profilers in JMH to dig deeper if benchmark results to not match your hypotheses: + * Add `-prof gc` to the options to check whether the garbage collector runs during a microbenchmarks and skews + your results. If so, try to force a GC between runs (`-gc true`) but watch out for the caveats. + * Add `-prof perf` or `-prof perfasm` (both only available on Linux) to see hotspots. +* Have your benchmarks peer-reviewed. + +### Don't + +* Blindly believe the numbers that your microbenchmark produces but verify them by measuring e.g. with `-prof perfasm`. +* Run more threads than your number of CPU cores (in case you run multi-threaded microbenchmark). +* Look only at the `Score` column and ignore `Error`. Instead, take countermeasures to keep `Error` low / variance explainable. + +## Disassembling + +Disassembling is fun! Maybe not always useful, but always fun! Generally, you'll want to install `perf` and FCML's `hsdis`. +`perf` is generally available via `apg-get install perf` or `pacman -S perf`. FCML is a little more involved. This worked +on 2020-08-01: + +``` +wget https://github.com/swojtasiak/fcml-lib/releases/download/v1.2.2/fcml-1.2.2.tar.gz +tar xf fcml* +cd fcml* +./configure +make +cd example/hsdis +make +sudo cp .libs/libhsdis.so.0.0.0 /usr/lib/jvm/java-14-adoptopenjdk/lib/hsdis-amd64.so +``` + +If you want to disassemble a single method do something like this: + +``` +gradlew -p micro-benchmarks run --args ' MemoryStatsBenchmark -jvmArgs "-XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*.yourMethodName -XX:PrintAssemblyOptions=intel" +``` + + +If you want `perf` to find the hot methods for you then do add `-prof:perfasm`. diff --git a/micro-benchmarks/build.gradle b/micro-benchmarks/build.gradle new file mode 100644 index 000000000..b1da431fa --- /dev/null +++ b/micro-benchmarks/build.gradle @@ -0,0 +1,68 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import org.opensearch.gradle.info.BuildParams + +apply plugin: 'opensearch.build' +apply plugin: 'application' +apply plugin: 'java' +apply plugin: 'io.freefair.lombok' + +assemble.enabled = false + +application { + mainClass = 'org.openjdk.jmh.Main' +} + +test.enabled = false + +repositories { + mavenLocal() + maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } + mavenCentral() + maven { url "https://plugins.gradle.org/m2/" } +} + +dependencies { + // This will take root project as the dependency + api(project(':')) + api "org.openjdk.jmh:jmh-core:$versions.jmh" + annotationProcessor "org.openjdk.jmh:jmh-generator-annprocess:$versions.jmh" + // Dependencies of JMH + runtimeOnly 'net.sf.jopt-simple:jopt-simple:5.0.4' + runtimeOnly 'org.apache.commons:commons-math3:3.6.1' +} + +// enable the JMH's BenchmarkProcessor to generate the final benchmark classes +// needs to be added separately otherwise Gradle will quote it and javac will fail +compileJava.options.compilerArgs.addAll(["-processor", "org.openjdk.jmh.generators.BenchmarkProcessor"]) + + +run { + // This is required for C++ code + systemProperty "java.library.path", "$rootDir/jni/release" + executable = "${BuildParams.runtimeJavaHome}/bin/java" + var jvmHeapSize = System.getProperty("jvm.heap.size", "6g") + jvmArgs("-Xms" + jvmHeapSize, "-Xmx" + jvmHeapSize) +} + + +// No licenses for our benchmark deps (we don't ship benchmarks) +tasks.named("dependencyLicenses").configure { it.enabled = false } +dependenciesInfo.enabled = false + +thirdPartyAudit.ignoreViolations( + // these classes intentionally use JDK internal API (and this is ok since the project is maintained by Oracle employees) + 'org.openjdk.jmh.util.Utils' +) + +spotless { + java { + // IDEs can sometimes run annotation processors that leave files in + // here, causing Spotless to complain. Even though this path ought not + // to exist, exclude it anyway in order to avoid spurious failures. + targetExclude 'src/main/generated/**/*.java' + } +} + diff --git a/micro-benchmarks/src/main/java/org/opensearch/knn/QueryParsingBenchmarks.java b/micro-benchmarks/src/main/java/org/opensearch/knn/QueryParsingBenchmarks.java new file mode 100644 index 000000000..1c5a3b875 --- /dev/null +++ b/micro-benchmarks/src/main/java/org/opensearch/knn/QueryParsingBenchmarks.java @@ -0,0 +1,109 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; +import org.opensearch.cluster.ClusterModule; +import org.opensearch.common.xcontent.LoggingDeprecationHandler; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.common.xcontent.json.JsonXContent; +import org.opensearch.core.common.bytes.BytesArray; +import org.opensearch.core.common.bytes.BytesReference; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.QueryBuilders; +import org.opensearch.index.query.TermQueryBuilder; +import org.opensearch.knn.index.query.KNNQueryBuilder; +import org.opensearch.knn.index.query.parser.KNNQueryBuilderParser; +import org.opensearch.plugins.SearchPlugin; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +/** + * Benchmarks for impact of changes around query parsing + */ +@Warmup(iterations = 5, time = 10) +@Measurement(iterations = 3, time = 10) +@Fork(3) +@State(Scope.Benchmark) +public class QueryParsingBenchmarks { + private static final TermQueryBuilder TERM_QUERY = QueryBuilders.termQuery("field", "value"); + private static final NamedXContentRegistry NAMED_X_CONTENT_REGISTRY = xContentRegistry(); + + @Param({ "128", "1024" }) + private int dimension; + @Param({ "basic", "filter" }) + private String type; + + private BytesReference bytesReference; + + @Setup + public void setup() throws IOException { + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.startObject("test"); + builder.field(KNNQueryBuilder.VECTOR_FIELD.getPreferredName(), generateVectorWithOnes(dimension)); + builder.field(KNNQueryBuilder.K_FIELD.getPreferredName(), 1); + if (type.equals("filter")) { + builder.field(KNNQueryBuilder.FILTER_FIELD.getPreferredName(), TERM_QUERY); + } + builder.endObject(); + builder.endObject(); + bytesReference = BytesReference.bytes(builder); + } + + @Benchmark + public void fromXContent(final Blackhole bh) throws IOException { + XContentParser xContentParser = createParser(); + bh.consume(KNNQueryBuilderParser.fromXContent(xContentParser)); + } + + private XContentParser createParser() throws IOException { + XContentParser contentParser = createParser(bytesReference); + contentParser.nextToken(); + return contentParser; + } + + private float[] generateVectorWithOnes(final int dimensions) { + float[] vector = new float[dimensions]; + Arrays.fill(vector, (float) 1); + return vector; + } + + private XContentParser createParser(final BytesReference data) throws IOException { + BytesArray array = (BytesArray) data; + return JsonXContent.jsonXContent.createParser( + NAMED_X_CONTENT_REGISTRY, + LoggingDeprecationHandler.INSTANCE, + array.array(), + array.offset(), + array.length() + ); + } + + private static NamedXContentRegistry xContentRegistry() { + List list = ClusterModule.getNamedXWriteables(); + SearchPlugin.QuerySpec spec = new SearchPlugin.QuerySpec<>( + TermQueryBuilder.NAME, + TermQueryBuilder::new, + TermQueryBuilder::fromXContent + ); + list.add(new NamedXContentRegistry.Entry(QueryBuilder.class, spec.getName(), (p, c) -> spec.getParser().fromXContent(p))); + return new NamedXContentRegistry(list); + } +} diff --git a/micro-benchmarks/src/main/java/org/opensearch/knn/TransferVectorsBenchmarks.java b/micro-benchmarks/src/main/java/org/opensearch/knn/TransferVectorsBenchmarks.java new file mode 100644 index 000000000..2bce54ee6 --- /dev/null +++ b/micro-benchmarks/src/main/java/org/opensearch/knn/TransferVectorsBenchmarks.java @@ -0,0 +1,97 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.opensearch.knn.jni.JNICommons; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/** + * The class provides runs some benchmarks and provide the performance data around how much time it will take to + * transfer vectors from java to jni layer for different configuration. + */ +@Warmup(iterations = 1, timeUnit = TimeUnit.SECONDS, time = 300) +@Measurement(iterations = 1, timeUnit = TimeUnit.SECONDS, time = 300) +@Fork(3) +@BenchmarkMode(Mode.SingleShotTime) +@OutputTimeUnit(TimeUnit.SECONDS) +@State(Scope.Benchmark) +public class TransferVectorsBenchmarks { + private static final Random random = new Random(1212121212); + private static final long TOTAL_NUMBER_OF_VECTOR_TO_BE_TRANSFERRED = 1000000; + + @Param({ "128", "256", "384", "512", "960", "1024", "1536" }) + private int dimension; + + @Param({ "100000", "500000", "1000000" }) + private int vectorsPerTransfer; + + private List vectorList; + + @Setup(Level.Trial) + public void setup() { + vectorList = new ArrayList<>(); + for (int i = 0; i < TOTAL_NUMBER_OF_VECTOR_TO_BE_TRANSFERRED; i++) { + vectorList.add(generateRandomVector(dimension)); + } + } + + @Benchmark + public void transferVectors_withCapacity() { + long vectorsAddress = 0; + List vectorToTransfer = new ArrayList<>(); + long startingIndex = 0; + for (float[] floats : vectorList) { + if (vectorToTransfer.size() == vectorsPerTransfer) { + vectorsAddress = JNICommons.storeVectorData( + vectorsAddress, + vectorToTransfer.toArray(new float[][] {}), + dimension * TOTAL_NUMBER_OF_VECTOR_TO_BE_TRANSFERRED + ); + startingIndex += vectorsPerTransfer; + vectorToTransfer = new ArrayList<>(); + } + vectorToTransfer.add(floats); + } + if (!vectorToTransfer.isEmpty()) { + vectorsAddress = JNICommons.storeVectorData( + vectorsAddress, + vectorToTransfer.toArray(new float[][] {}), + dimension * TOTAL_NUMBER_OF_VECTOR_TO_BE_TRANSFERRED + ); + } + JNICommons.freeVectorData(vectorsAddress); + } + + private float[] generateRandomVector(int dimensions) { + float[] vector = new float[dimensions]; + for (int i = 0; i < dimensions; i++) { + vector[i] = -500 + (float) random.nextGaussian() * (1000); + } + return vector; + } +} diff --git a/micro-benchmarks/src/main/resources/log4j2.properties b/micro-benchmarks/src/main/resources/log4j2.properties new file mode 100644 index 000000000..2cd74124e --- /dev/null +++ b/micro-benchmarks/src/main/resources/log4j2.properties @@ -0,0 +1,19 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. +# +# Modifications Copyright OpenSearch Contributors. See +# GitHub history for details. +# + +appender.console.type = Console +appender.console.name = console +appender.console.layout.type = PatternLayout +appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c] %marker %m%n + +# Do not log at all if it is not really critical - we're in a benchmark +rootLogger.level = error +rootLogger.appenderRef.console.ref = console diff --git a/qa/build.gradle b/qa/build.gradle index b292d413c..fccbf3cb1 100644 --- a/qa/build.gradle +++ b/qa/build.gradle @@ -31,6 +31,8 @@ def tmp_dir = project.file('build/private/artifact_tmp').absoluteFile tmp_dir.mkdirs() String default_bwc_version = System.getProperty("bwc.version") String knn_bwc_version = System.getProperty("tests.bwc.version", default_bwc_version) +boolean isSnapshot = knn_bwc_version.contains("-SNAPSHOT") +String knn_bwc_version_no_qualifier = isSnapshot ? knn_bwc_version - "-SNAPSHOT" : knn_bwc_version // Task to pull k-NN plugin from archive task pullBwcPlugin { @@ -39,20 +41,27 @@ task pullBwcPlugin { } doLast { + ext { + if (isSnapshot) { + srcUrl = "https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/${knn_bwc_version_no_qualifier}/latest/linux/x64/tar/dist/opensearch/opensearch-${knn_bwc_version_no_qualifier}-linux-x64.tar.gz" + } else { + srcUrl = "https://artifacts.opensearch.org/releases/bundle/opensearch/${knn_bwc_version}/opensearch-${knn_bwc_version}-linux-x64.tar.gz" + } + } ant.get( - src: "https://artifacts.opensearch.org/releases/bundle/opensearch/${knn_bwc_version}/opensearch-${knn_bwc_version}-linux-x64.tar.gz", + src: srcUrl, dest: tmp_dir.absolutePath, httpusecaches: false ) copy { - from tarTree(java.nio.file.Path.of(tmp_dir.absolutePath, "opensearch-${knn_bwc_version}-linux-x64.tar.gz")) + from tarTree(java.nio.file.Path.of(tmp_dir.absolutePath, "opensearch-${knn_bwc_version_no_qualifier}-linux-x64.tar.gz")) into tmp_dir.absolutePath } copy { - from(java.nio.file.Path.of(tmp_dir.absolutePath, "opensearch-${knn_bwc_version}", "plugins", "opensearch-knn")) + from(java.nio.file.Path.of(tmp_dir.absolutePath, "opensearch-${knn_bwc_version_no_qualifier}", "plugins", "opensearch-knn")) into java.nio.file.Path.of(tmp_dir.absolutePath, "opensearch-knn") } - delete java.nio.file.Path.of(tmp_dir.absolutePath, "opensearch-${knn_bwc_version}"), java.nio.file.Path.of(tmp_dir.absolutePath, "opensearch-${knn_bwc_version}-linux-x64.tar.gz") + delete java.nio.file.Path.of(tmp_dir.absolutePath, "opensearch-${knn_bwc_version_no_qualifier}"), java.nio.file.Path.of(tmp_dir.absolutePath, "opensearch-${knn_bwc_version_no_qualifier}-linux-x64.tar.gz") } } @@ -61,7 +70,7 @@ task zipBwcPlugin(type: Zip) { dependsOn "pullBwcPlugin" from(java.nio.file.Path.of(tmp_dir.absolutePath, "opensearch-knn")) destinationDirectory = tmp_dir - archiveFileName = "opensearch-knn-${knn_bwc_version}.zip" + archiveFileName = "opensearch-knn-${knn_bwc_version_no_qualifier}.zip" doLast { delete java.nio.file.Path.of(tmp_dir.absolutePath, "opensearch-knn") } diff --git a/qa/restart-upgrade/build.gradle b/qa/restart-upgrade/build.gradle index 3dbea0f51..a308699aa 100644 --- a/qa/restart-upgrade/build.gradle +++ b/qa/restart-upgrade/build.gradle @@ -9,6 +9,8 @@ apply from : "$rootDir/qa/build.gradle" String default_bwc_version = System.getProperty("bwc.version") String knn_bwc_version = System.getProperty("tests.bwc.version", default_bwc_version) +boolean isSnapshot = knn_bwc_version.contains("-SNAPSHOT") +String knn_bwc_version_no_qualifier = isSnapshot ? knn_bwc_version - "-SNAPSHOT" : knn_bwc_version String baseName = "knnBwcCluster-restart" // Creates a test cluster of previous version and loads k-NN plugin of bwcVersion @@ -20,11 +22,13 @@ testClusters { plugin(project.tasks.zipBwcPlugin.archiveFile) setting 'path.repo', "${buildDir}/cluster/shared/repo/${baseName}" setting 'http.content_type.required', 'true' - environment "LD_LIBRARY_PATH", "${buildDir}/testclusters/${baseName}-0/distro/${knn_bwc_version}-ARCHIVE/plugins/opensearch-knn/knnlib;${buildDir}/testclusters/${baseName}-0/distro/${knn_bwc_version}-ARCHIVE/plugins/opensearch-knn/lib" - systemProperty "java.library.path", "${buildDir}/testclusters/${baseName}-0/distro/${knn_bwc_version}-ARCHIVE/plugins/opensearch-knn/knnlib:${buildDir}/testclusters/${baseName}-0/distro/${knn_bwc_version}-ARCHIVE/plugins/opensearch-knn/lib" + environment "LD_LIBRARY_PATH", "${buildDir}/testclusters/${baseName}-0/distro/${knn_bwc_version_no_qualifier}-ARCHIVE/plugins/opensearch-knn/knnlib;${buildDir}/testclusters/${baseName}-0/distro/${knn_bwc_version_no_qualifier}-ARCHIVE/plugins/opensearch-knn/lib" + systemProperty "java.library.path", "${buildDir}/testclusters/${baseName}-0/distro/${knn_bwc_version_no_qualifier}-ARCHIVE/plugins/opensearch-knn/knnlib:${buildDir}/testclusters/${baseName}-0/distro/${knn_bwc_version_no_qualifier}-ARCHIVE/plugins/opensearch-knn/lib" } } + def versionsBelow1_3 = ["1.1.", "1.2."] + def versionsBelow2_3 = ["1.", "2.0.", "2.1.", "2.2."] // Task to run BWC tests against the old cluster task testAgainstOldCluster(type: StandaloneRestIntegTestTask) { dependsOn "zipBwcPlugin" @@ -51,11 +55,48 @@ testClusters { } // Skip test if version is 1.0, 1.1, 1.2 or 1.3 as they are not supported in those versions - if (knn_bwc_version.startsWith("1.") || knn_bwc_version.startsWith("2.0")) { + if (knn_bwc_version.startsWith("1.") || knn_bwc_version.startsWith("2.")) { filter { excludeTestsMatching "org.opensearch.knn.bwc.IndexingIT.testEmptyParametersOnUpgrade" } } + + if (knn_bwc_version.startsWith("1.") || + knn_bwc_version.startsWith("2.0.") || + knn_bwc_version.startsWith("2.1.") || + knn_bwc_version.startsWith("2.2.") || + knn_bwc_version.startsWith("2.3.") || + knn_bwc_version.startsWith("2.4") || + knn_bwc_version.startsWith("2.5.") || + knn_bwc_version.startsWith("2.6.") || + knn_bwc_version.startsWith("2.7.") || + knn_bwc_version.startsWith("2.8.") || + knn_bwc_version.startsWith("2.9.") || + knn_bwc_version.startsWith("2.10.") || + knn_bwc_version.startsWith("2.11.") || + knn_bwc_version.startsWith("2.12.") || + knn_bwc_version.startsWith("2.13.") || + knn_bwc_version.startsWith("2.14.") || + knn_bwc_version.startsWith("2.15.")) { + filter { + excludeTestsMatching "org.opensearch.knn.bwc.IndexingIT.testKNNIndexBinaryForceMerge" + } + } + + if (versionsBelow2_3.any { knn_bwc_version.startsWith(it) }) { + filter { + excludeTestsMatching "org.opensearch.knn.bwc.QueryANNIT.testQueryOnLuceneIndex" + excludeTestsMatching "org.opensearch.knn.bwc.IndexingIT.testKNNIndexLuceneForceMerge" + } + } + + if (versionsBelow1_3.any { knn_bwc_version.startsWith(it) }) { + filter { + excludeTestsMatching "org.opensearch.knn.bwc.QueryANNIT.testQueryOnFaissIndex" + excludeTestsMatching "org.opensearch.knn.bwc.IndexingIT.testKNNIndexFaissForceMerge" + } + } + nonInputProperties.systemProperty('tests.rest.cluster', "${-> testClusters."${baseName}".allHttpSocketURI.join(",")}") nonInputProperties.systemProperty('tests.clustername', "${-> testClusters."${baseName}".getName()}") systemProperty 'tests.security.manager', 'false' @@ -94,11 +135,48 @@ testClusters { } // Skip test if version is 1.0, 1.1, 1.2 or 1.3 as they are not supported in those versions - if (knn_bwc_version.startsWith("1.") || knn_bwc_version.startsWith("2.0")) { + if (knn_bwc_version.startsWith("1.") || knn_bwc_version.startsWith("2.")) { filter { excludeTestsMatching "org.opensearch.knn.bwc.IndexingIT.testEmptyParametersOnUpgrade" } } + + if (knn_bwc_version.startsWith("1.") || + knn_bwc_version.startsWith("2.0.") || + knn_bwc_version.startsWith("2.1.") || + knn_bwc_version.startsWith("2.2.") || + knn_bwc_version.startsWith("2.3.") || + knn_bwc_version.startsWith("2.4") || + knn_bwc_version.startsWith("2.5.") || + knn_bwc_version.startsWith("2.6.") || + knn_bwc_version.startsWith("2.7.") || + knn_bwc_version.startsWith("2.8.") || + knn_bwc_version.startsWith("2.9.") || + knn_bwc_version.startsWith("2.10.") || + knn_bwc_version.startsWith("2.11.") || + knn_bwc_version.startsWith("2.12.") || + knn_bwc_version.startsWith("2.13.") || + knn_bwc_version.startsWith("2.14.") || + knn_bwc_version.startsWith("2.15.")) { + filter { + excludeTestsMatching "org.opensearch.knn.bwc.IndexingIT.testKNNIndexBinaryForceMerge" + } + } + + if (versionsBelow2_3.any {knn_bwc_version.startsWith(it) }) { + filter { + excludeTestsMatching "org.opensearch.knn.bwc.QueryANNIT.testQueryOnLuceneIndex" + excludeTestsMatching "org.opensearch.knn.bwc.IndexingIT.testKNNIndexLuceneForceMerge" + } + } + + if (versionsBelow1_3.any { knn_bwc_version.startsWith(it) }) { + filter { + excludeTestsMatching "org.opensearch.knn.bwc.QueryANNIT.testQueryOnFaissIndex" + excludeTestsMatching "org.opensearch.knn.bwc.IndexingIT.testKNNIndexFaissForceMerge" + } + } + nonInputProperties.systemProperty('tests.rest.cluster', "${-> testClusters."${baseName}".allHttpSocketURI.join(",")}") nonInputProperties.systemProperty('tests.clustername', "${-> testClusters."${baseName}".getName()}") systemProperty 'tests.security.manager', 'false' diff --git a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/AbstractRestartUpgradeTestCase.java b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/AbstractRestartUpgradeTestCase.java index ed11ca9d8..667a39d16 100644 --- a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/AbstractRestartUpgradeTestCase.java +++ b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/AbstractRestartUpgradeTestCase.java @@ -58,5 +58,4 @@ protected static final boolean isRunningAgainstOldCluster() { protected final Optional getBWCVersion() { return Optional.ofNullable(System.getProperty(BWC_VERSION, null)); } - } diff --git a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/ClearCacheIT.java b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/ClearCacheIT.java new file mode 100644 index 000000000..e4ac25000 --- /dev/null +++ b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/ClearCacheIT.java @@ -0,0 +1,47 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.bwc; + +import org.opensearch.common.settings.Settings; + +import java.util.Collections; + +import static org.opensearch.knn.TestUtils.NODES_BWC_CLUSTER; + +public class ClearCacheIT extends AbstractRestartUpgradeTestCase { + private static final String TEST_FIELD = "test-field"; + private static final int DIMENSIONS = 5; + private static int docId = 0; + private static final int NUM_DOCS = 10; + private static int queryCnt = 0; + private static final int K = 5; + + // Restart Upgrade BWC Tests to validate Clear Cache API + public void testClearCache() throws Exception { + waitForClusterHealthGreen(NODES_BWC_CLUSTER); + if (isRunningAgainstOldCluster()) { + // if approximate threshold is supported, set value to 0, to build graph always + Settings indexSettings = isApproximateThresholdSupported(getBWCVersion()) + ? buildKNNIndexSettings(0) + : getKNNDefaultIndexSettings(); + createKnnIndex(testIndex, indexSettings, createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, docId, NUM_DOCS); + queryCnt = NUM_DOCS; + int graphCount = getTotalGraphsInCache(); + knnWarmup(Collections.singletonList(testIndex)); + assertTrue(getTotalGraphsInCache() > graphCount); + } else { + validateClearCacheOnUpgrade(queryCnt); + deleteKNNIndex(testIndex); + } + } + + // validation steps for Clear Cache API after upgrading node to new version + private void validateClearCacheOnUpgrade(int queryCount) throws Exception { + clearCache(Collections.singletonList(testIndex)); + assertEquals(0, getTotalGraphsInCache()); + } +} diff --git a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/FaissSQIT.java b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/FaissSQIT.java new file mode 100644 index 000000000..c9a74418f --- /dev/null +++ b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/FaissSQIT.java @@ -0,0 +1,384 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.bwc; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import org.apache.http.util.EntityUtils; +import org.opensearch.client.Response; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.knn.KNNResult; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.query.KNNQueryBuilder; +import org.opensearch.knn.index.engine.KNNEngine; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.TreeMap; + +import static org.opensearch.knn.common.KNNConstants.ENCODER_SQ; +import static org.opensearch.knn.common.KNNConstants.FAISS_NAME; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_CLIP; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_ENCODER_FP16; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_TYPE; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_IVF; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NLIST; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; +import static org.opensearch.knn.common.KNNConstants.MODEL_ID; +import static org.opensearch.knn.common.KNNConstants.NAME; +import static org.opensearch.knn.common.KNNConstants.PARAMETERS; + +public class FaissSQIT extends AbstractRestartUpgradeTestCase { + private static final String TEST_FIELD = "test-field"; + private static final String TRAIN_TEST_FIELD = "train-test-field"; + private static final String TRAIN_INDEX = "train-index"; + private static final String TEST_MODEL = "test-model"; + private static final int DIMENSION = 128; + private static final int NUM_DOCS = 100; + + public void testHNSWSQFP16_onUpgradeWhenIndexedAndQueried_thenSucceed() throws Exception { + if (!isRunningAgainstOldCluster()) { + SpaceType[] spaceTypes = { SpaceType.L2, SpaceType.INNER_PRODUCT }; + Random random = new Random(); + SpaceType spaceType = spaceTypes[random.nextInt(spaceTypes.length)]; + + List mValues = ImmutableList.of(16, 32, 64, 128); + List efConstructionValues = ImmutableList.of(16, 32, 64, 128); + List efSearchValues = ImmutableList.of(16, 32, 64, 128); + + // Create an index + /** + * "properties": { + * "test-field": { + * "type": "knn_vector", + * "dimension": 128, + * "method": { + * "name": "hnsw", + * "space_type": "l2", + * "engine": "faiss", + * "parameters": { + * "m": 16, + * "ef_construction": 128, + * "ef_search": 128, + * "encoder": { + * "name": "sq", + * "parameters": { + * "type": "fp16" + * } + * } + * } + * } + * } + * } + */ + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(TEST_FIELD) + .field("type", "knn_vector") + .field("dimension", DIMENSION) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNNConstants.KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(KNNConstants.PARAMETERS) + .field(KNNConstants.METHOD_PARAMETER_M, mValues.get(random().nextInt(mValues.size()))) + .field( + KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, + efConstructionValues.get(random().nextInt(efConstructionValues.size())) + ) + .field(KNNConstants.METHOD_PARAMETER_EF_SEARCH, efSearchValues.get(random().nextInt(efSearchValues.size()))) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_SQ) + .startObject(PARAMETERS) + .field(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + Map mappingMap = xContentBuilderToMap(builder); + String mapping = builder.toString(); + + createKnnIndex(testIndex, mapping); + assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(testIndex))); + indexTestData(testIndex, TEST_FIELD, DIMENSION, NUM_DOCS); + queryTestData(testIndex, TEST_FIELD, DIMENSION, NUM_DOCS); + deleteKNNIndex(testIndex); + validateGraphEviction(); + } + } + + public void testHNSWSQFP16_onUpgradeWhenClipToFp16isTrueAndIndexedWithOutOfFP16Range_thenSucceed() throws Exception { + if (!isRunningAgainstOldCluster()) { + + List mValues = ImmutableList.of(16, 32, 64, 128); + List efConstructionValues = ImmutableList.of(16, 32, 64, 128); + List efSearchValues = ImmutableList.of(16, 32, 64, 128); + + int dimension = 2; + + // Create an index + /** + * "properties": { + * "test-field": { + * "type": "knn_vector", + * "dimension": 128, + * "method": { + * "name": "hnsw", + * "space_type": "l2", + * "engine": "faiss", + * "parameters": { + * "m": 16, + * "ef_construction": 128, + * "ef_search": 128, + * "encoder": { + * "name": "sq", + * "parameters": { + * "type": "fp16", + * "clip": true + * } + * } + * } + * } + * } + * } + */ + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(TEST_FIELD) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2.getValue()) + .field(KNNConstants.KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(PARAMETERS) + .field(KNNConstants.METHOD_PARAMETER_M, mValues.get(random().nextInt(mValues.size()))) + .field( + KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, + efConstructionValues.get(random().nextInt(efConstructionValues.size())) + ) + .field(KNNConstants.METHOD_PARAMETER_EF_SEARCH, efSearchValues.get(random().nextInt(efSearchValues.size()))) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_SQ) + .startObject(PARAMETERS) + .field(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16) + .field(FAISS_SQ_CLIP, true) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + Map mappingMap = xContentBuilderToMap(builder); + String mapping = builder.toString(); + + createKnnIndex(testIndex, mapping); + assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(testIndex))); + Float[] vector1 = { -65523.76f, 65504.2f }; + Float[] vector2 = { -270.85f, 65514.2f }; + Float[] vector3 = { -150.9f, 65504.0f }; + Float[] vector4 = { -20.89f, 100000000.0f }; + addKnnDoc(testIndex, "1", TEST_FIELD, vector1); + addKnnDoc(testIndex, "2", TEST_FIELD, vector2); + addKnnDoc(testIndex, "3", TEST_FIELD, vector3); + addKnnDoc(testIndex, "4", TEST_FIELD, vector4); + + float[] queryVector = { -10.5f, 25.48f }; + int k = 4; + Response searchResponse = searchKNNIndex(testIndex, new KNNQueryBuilder(TEST_FIELD, queryVector, k), k); + List results = parseSearchResponse(EntityUtils.toString(searchResponse.getEntity()), TEST_FIELD); + assertEquals(k, results.size()); + for (int i = 0; i < k; i++) { + assertEquals(k - i, Integer.parseInt(results.get(i).getDocId())); + } + deleteKNNIndex(testIndex); + validateGraphEviction(); + } + } + + public void testIVFSQFP16_onUpgradeWhenIndexedAndQueried_thenSucceed() throws Exception { + if (!isRunningAgainstOldCluster()) { + + // Add training data + createBasicKnnIndex(TRAIN_INDEX, TRAIN_TEST_FIELD, DIMENSION); + int trainingDataCount = 200; + bulkIngestRandomVectors(TRAIN_INDEX, TRAIN_TEST_FIELD, trainingDataCount, DIMENSION); + + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_IVF) + .field(KNN_ENGINE, FAISS_NAME) + .field(METHOD_PARAMETER_SPACE_TYPE, "l2") + .startObject(PARAMETERS) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_SQ) + .startObject(PARAMETERS) + .field(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16) + .endObject() + .endObject() + .endObject() + .endObject(); + Map method = xContentBuilderToMap(builder); + + trainModel(TEST_MODEL, TRAIN_INDEX, TRAIN_TEST_FIELD, DIMENSION, method, "faiss ivf sqfp16 test description"); + + // Make sure training succeeds after 30 seconds + assertTrainingSucceeds(TEST_MODEL, 30, 1000); + + // Create knn index from model + String indexMapping = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(TEST_FIELD) + .field("type", "knn_vector") + .field(MODEL_ID, TEST_MODEL) + .endObject() + .endObject() + .endObject() + .toString(); + + createKnnIndex(testIndex, getKNNDefaultIndexSettings(), indexMapping); + + indexTestData(testIndex, TEST_FIELD, DIMENSION, NUM_DOCS); + queryTestData(testIndex, TEST_FIELD, DIMENSION, NUM_DOCS); + deleteKNNIndex(TRAIN_INDEX); + deleteKNNIndex(testIndex); + deleteModel(TEST_MODEL); + validateGraphEviction(); + } + } + + public void testIVFSQFP16_onUpgradeWhenClipToFp16isTrueAndIndexedWithOutOfFP16Range_thenSucceed() throws Exception { + if (!isRunningAgainstOldCluster()) { + int dimension = 2; + + // Add training data + createBasicKnnIndex(TRAIN_INDEX, TRAIN_TEST_FIELD, dimension); + int trainingDataCount = 200; + bulkIngestRandomVectors(TRAIN_INDEX, TRAIN_TEST_FIELD, trainingDataCount, dimension); + + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_IVF) + .field(KNN_ENGINE, FAISS_NAME) + .field(METHOD_PARAMETER_SPACE_TYPE, "l2") + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_NLIST, 1) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_SQ) + .startObject(PARAMETERS) + .field(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16) + .field(FAISS_SQ_CLIP, true) + .endObject() + .endObject() + .endObject() + .endObject(); + Map method = xContentBuilderToMap(builder); + + trainModel(TEST_MODEL, TRAIN_INDEX, TRAIN_TEST_FIELD, dimension, method, "faiss ivf sqfp16 test description"); + + // Make sure training succeeds after 30 seconds + assertTrainingSucceeds(TEST_MODEL, 30, 1000); + + // Create knn index from model + String indexMapping = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(TEST_FIELD) + .field("type", "knn_vector") + .field(MODEL_ID, TEST_MODEL) + .endObject() + .endObject() + .endObject() + .toString(); + + createKnnIndex(testIndex, getKNNDefaultIndexSettings(), indexMapping); + Float[] vector1 = { -65523.76f, 65504.2f }; + Float[] vector2 = { -270.85f, 65514.2f }; + Float[] vector3 = { -150.9f, 65504.0f }; + Float[] vector4 = { -20.89f, 100000000.0f }; + addKnnDoc(testIndex, "1", TEST_FIELD, vector1); + addKnnDoc(testIndex, "2", TEST_FIELD, vector2); + addKnnDoc(testIndex, "3", TEST_FIELD, vector3); + addKnnDoc(testIndex, "4", TEST_FIELD, vector4); + + float[] queryVector = { -10.5f, 25.48f }; + int k = 4; + Response searchResponse = searchKNNIndex(testIndex, new KNNQueryBuilder(TEST_FIELD, queryVector, k), k); + List results = parseSearchResponse(EntityUtils.toString(searchResponse.getEntity()), TEST_FIELD); + assertEquals(k, results.size()); + for (int i = 0; i < k; i++) { + assertEquals(k - i, Integer.parseInt(results.get(i).getDocId())); + } + + deleteKNNIndex(testIndex); + deleteKNNIndex(TRAIN_INDEX); + deleteModel(TEST_MODEL); + validateGraphEviction(); + } + } + + private void validateGraphEviction() throws Exception { + // Search every 5 seconds 14 times to confirm graph gets evicted + int intervals = 14; + for (int i = 0; i < intervals; i++) { + if (getTotalGraphsInCache() == 0) { + return; + } + + Thread.sleep(5 * 1000); + } + + fail("Graphs are not getting evicted"); + } + + private void queryTestData(final String indexName, final String fieldName, final int dimension, final int numDocs) throws IOException { + float[] queryVector = new float[dimension]; + Arrays.fill(queryVector, (float) numDocs); + int k = 10; + + Response searchResponse = searchKNNIndex(indexName, new KNNQueryBuilder(fieldName, queryVector, k), k); + List results = parseSearchResponse(EntityUtils.toString(searchResponse.getEntity()), fieldName); + assertEquals(k, results.size()); + for (int i = 0; i < k; i++) { + assertEquals(numDocs - i - 1, Integer.parseInt(results.get(i).getDocId())); + } + } + + private void indexTestData(final String indexName, final String fieldName, final int dimension, final int numDocs) throws Exception { + for (int i = 0; i < numDocs; i++) { + float[] indexVector = new float[dimension]; + Arrays.fill(indexVector, (float) i); + addKnnDocWithAttributes(indexName, Integer.toString(i), fieldName, indexVector, ImmutableMap.of("rating", String.valueOf(i))); + } + + // Assert that all docs are ingested + refreshAllNonSystemIndices(); + assertEquals(numDocs, getDocCount(indexName)); + } + +} diff --git a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java index 7437cf0c1..b212d844f 100644 --- a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java +++ b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java @@ -5,9 +5,16 @@ package org.opensearch.knn.bwc; -import org.opensearch.common.Strings; +import org.junit.Assert; +import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.knn.index.KNNSettings; import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; + +import java.util.List; +import java.util.Map; import static org.opensearch.knn.TestUtils.KNN_ALGO_PARAM_EF_CONSTRUCTION_MIN_VALUE; import static org.opensearch.knn.TestUtils.KNN_ALGO_PARAM_M_MIN_VALUE; @@ -17,8 +24,14 @@ import static org.opensearch.knn.TestUtils.VECTOR_TYPE; import static org.opensearch.knn.common.KNNConstants.DIMENSION; import static org.opensearch.knn.common.KNNConstants.FAISS_NAME; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; import static org.opensearch.knn.common.KNNConstants.KNN_METHOD; +import static org.opensearch.knn.common.KNNConstants.LUCENE_NAME; import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_SEARCH; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_M; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; import static org.opensearch.knn.common.KNNConstants.NAME; import static org.opensearch.knn.common.KNNConstants.PARAMETERS; @@ -29,6 +42,7 @@ public class IndexingIT extends AbstractRestartUpgradeTestCase { private static final int K = 5; private static final int M = 50; private static final int EF_CONSTRUCTION = 1024; + private static final int EF_SEARCH = 200; private static final int NUM_DOCS = 10; private static int QUERY_COUNT = 0; @@ -41,7 +55,101 @@ public void testKNNIndexDefaultLegacyFieldMapping() throws Exception { createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); } else { - validateKNNIndexingOnUpgrade(); + // update index setting to allow build graph always since we test graph count that are loaded into memory + updateIndexSettings(testIndex, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0)); + validateKNNIndexingOnUpgrade(NUM_DOCS); + } + } + + // ensure that index is created using legacy mapping in old cluster, and, then, add docs to both old and new cluster. + // when search is requested on new cluster it should return all docs irrespective of cluster. + public void testKNNIndexDefaultEngine() throws Exception { + waitForClusterHealthGreen(NODES_BWC_CLUSTER); + if (isRunningAgainstOldCluster()) { + createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, 5); + // Flush to ensure that index is not re-indexed when node comes back up + flush(testIndex, true); + } else { + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, 5, 5); + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, 5, 5); + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, 10, 10); + deleteKNNIndex(testIndex); + } + } + + // Ensure that when segments created with old mapping are forcemerged in new cluster, they + // succeed + public void testKNNIndexDefaultLegacyFieldMappingForceMerge() throws Exception { + waitForClusterHealthGreen(NODES_BWC_CLUSTER); + + if (isRunningAgainstOldCluster()) { + createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, 100); + // Flush to ensure that index is not re-indexed when node comes back up + flush(testIndex, true); + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, 100, K); + } else { + validateKNNIndexingOnUpgrade(100); + } + } + + public void testKNNIndexFaissForceMerge() throws Exception { + waitForClusterHealthGreen(NODES_BWC_CLUSTER); + + if (isRunningAgainstOldCluster()) { + createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS, METHOD_HNSW, FAISS_NAME)); + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, 100); + // Flush to ensure that index is not re-indexed when node comes back up + flush(testIndex, true); + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, 100, K); + } else { + validateKNNIndexingOnUpgrade(100); + } + } + + public void testKNNIndexLuceneForceMerge() throws Exception { + waitForClusterHealthGreen(NODES_BWC_CLUSTER); + + if (isRunningAgainstOldCluster()) { + createKnnIndex( + testIndex, + getKNNDefaultIndexSettings(), + createKnnIndexMapping(TEST_FIELD, DIMENSIONS, METHOD_HNSW, LUCENE_NAME) + ); + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, 100); + // Flush to ensure that index is not re-indexed when node comes back up + flush(testIndex, true); + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, 100, K); + } else { + validateKNNIndexingOnUpgrade(100); + } + } + + // Ensure bwc works for binary force merge + public void testKNNIndexBinaryForceMerge() throws Exception { + int dimension = 40; + + waitForClusterHealthGreen(NODES_BWC_CLUSTER); + if (isRunningAgainstOldCluster()) { + createKnnIndex( + testIndex, + getKNNDefaultIndexSettings(), + createKnnIndexMapping( + TEST_FIELD, + dimension, + METHOD_HNSW, + KNNEngine.FAISS.getName(), + SpaceType.HAMMING.getValue(), + true, + VectorDataType.BINARY + ) + ); + addKNNByteDocs(testIndex, TEST_FIELD, dimension / 8, DOC_ID, 100); + // Flush to ensure that index is not re-indexed when node comes back up + flush(testIndex, true); + } else { + forceMergeKnnIndex(testIndex); } } @@ -52,18 +160,18 @@ public void testKNNIndexCustomLegacyFieldMapping() throws Exception { // When the cluster is in old version, create a KNN index with custom legacy field mapping settings // and add documents into that index if (isRunningAgainstOldCluster()) { - createKnnIndex( - testIndex, - createKNNIndexCustomLegacyFieldMappingSettings( - SpaceType.LINF, - KNN_ALGO_PARAM_M_MIN_VALUE, - KNN_ALGO_PARAM_EF_CONSTRUCTION_MIN_VALUE - ), - createKnnIndexMapping(TEST_FIELD, DIMENSIONS) + Settings.Builder indexMappingSettings = createKNNIndexCustomLegacyFieldMappingIndexSettingsBuilder( + SpaceType.LINF, + KNN_ALGO_PARAM_M_MIN_VALUE, + KNN_ALGO_PARAM_EF_CONSTRUCTION_MIN_VALUE ); + if (isApproximateThresholdSupported(getBWCVersion())) { + indexMappingSettings.put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0); + } + createKnnIndex(testIndex, indexMappingSettings.build(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); } else { - validateKNNIndexingOnUpgrade(); + validateKNNIndexingOnUpgrade(NUM_DOCS); } } @@ -74,43 +182,65 @@ public void testKNNIndexDefaultMethodFieldMapping() throws Exception { createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKNNIndexMethodFieldMapping(TEST_FIELD, DIMENSIONS)); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); } else { - validateKNNIndexingOnUpgrade(); + validateKNNIndexingOnUpgrade(NUM_DOCS); } } // Custom Method Field Mapping - // space_type : "inner_product", engine : "faiss", m : 50, ef_construction : 1024 + // space_type : "inner_product", engine : "faiss", m : 50, ef_construction : 1024, ef_search : 200 public void testKNNIndexCustomMethodFieldMapping() throws Exception { if (isRunningAgainstOldCluster()) { createKnnIndex( testIndex, getKNNDefaultIndexSettings(), - createKNNIndexCustomMethodFieldMapping(TEST_FIELD, DIMENSIONS, SpaceType.INNER_PRODUCT, FAISS_NAME, M, EF_CONSTRUCTION) + createKNNIndexCustomMethodFieldMapping( + TEST_FIELD, + DIMENSIONS, + SpaceType.INNER_PRODUCT, + FAISS_NAME, + M, + EF_CONSTRUCTION, + EF_SEARCH + ) ); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); } else { - validateKNNIndexingOnUpgrade(); + validateCustomMethodFieldMappingAfterUpgrade(); + validateKNNIndexingOnUpgrade(NUM_DOCS); } } + private void validateCustomMethodFieldMappingAfterUpgrade() throws Exception { + final Map indexMappings = getIndexMappingAsMap(testIndex); + final Map properties = (Map) indexMappings.get(PROPERTIES); + final Map knnMethod = ((Map) ((Map) properties.get(TEST_FIELD)).get(KNN_METHOD)); + final Map methodParameters = (Map) knnMethod.get(PARAMETERS); + + Assert.assertEquals(METHOD_HNSW, knnMethod.get(NAME)); + Assert.assertEquals(SpaceType.INNER_PRODUCT.getValue(), knnMethod.get(METHOD_PARAMETER_SPACE_TYPE)); + Assert.assertEquals(FAISS_NAME, knnMethod.get(KNN_ENGINE)); + Assert.assertEquals(EF_CONSTRUCTION, ((Integer) methodParameters.get(METHOD_PARAMETER_EF_CONSTRUCTION)).intValue()); + Assert.assertEquals(EF_SEARCH, ((Integer) methodParameters.get(METHOD_PARAMETER_EF_SEARCH)).intValue()); + Assert.assertEquals(M, ((Integer) methodParameters.get(METHOD_PARAMETER_M)).intValue()); + } + // test null parameters public void testNullParametersOnUpgrade() throws Exception { if (isRunningAgainstOldCluster()) { - String mapping = Strings.toString( - XContentFactory.jsonBuilder() - .startObject() - .startObject(PROPERTIES) - .startObject(TEST_FIELD) - .field(VECTOR_TYPE, KNN_VECTOR) - .field(DIMENSION, String.valueOf(DIMENSIONS)) - .startObject(KNN_METHOD) - .field(NAME, METHOD_HNSW) - .field(PARAMETERS, (String) null) - .endObject() - .endObject() - .endObject() - .endObject() - ); + String mapping = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES) + .startObject(TEST_FIELD) + .field(VECTOR_TYPE, KNN_VECTOR) + .field(DIMENSION, String.valueOf(DIMENSIONS)) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(PARAMETERS, (String) null) + .endObject() + .endObject() + .endObject() + .endObject() + .toString(); createKnnIndex(testIndex, getKNNDefaultIndexSettings(), mapping); } else { @@ -121,21 +251,20 @@ public void testNullParametersOnUpgrade() throws Exception { // test empty parameters public void testEmptyParametersOnUpgrade() throws Exception { if (isRunningAgainstOldCluster()) { - String mapping = Strings.toString( - XContentFactory.jsonBuilder() - .startObject() - .startObject(PROPERTIES) - .startObject(TEST_FIELD) - .field(VECTOR_TYPE, KNN_VECTOR) - .field(DIMENSION, String.valueOf(DIMENSIONS)) - .startObject(KNN_METHOD) - .field(NAME, METHOD_HNSW) - .field(PARAMETERS, "") - .endObject() - .endObject() - .endObject() - .endObject() - ); + String mapping = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES) + .startObject(TEST_FIELD) + .field(VECTOR_TYPE, KNN_VECTOR) + .field(DIMENSION, String.valueOf(DIMENSIONS)) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(PARAMETERS, "") + .endObject() + .endObject() + .endObject() + .endObject() + .toString(); createKnnIndex(testIndex, getKNNDefaultIndexSettings(), mapping); } else { @@ -146,20 +275,19 @@ public void testEmptyParametersOnUpgrade() throws Exception { // test no parameters public void testNoParametersOnUpgrade() throws Exception { if (isRunningAgainstOldCluster()) { - String mapping = Strings.toString( - XContentFactory.jsonBuilder() - .startObject() - .startObject(PROPERTIES) - .startObject(TEST_FIELD) - .field(VECTOR_TYPE, KNN_VECTOR) - .field(DIMENSION, String.valueOf(DIMENSIONS)) - .startObject(KNN_METHOD) - .field(NAME, METHOD_HNSW) - .endObject() - .endObject() - .endObject() - .endObject() - ); + String mapping = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES) + .startObject(TEST_FIELD) + .field(VECTOR_TYPE, KNN_VECTOR) + .field(DIMENSION, String.valueOf(DIMENSIONS)) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .endObject() + .endObject() + .endObject() + .endObject() + .toString(); createKnnIndex(testIndex, getKNNDefaultIndexSettings(), mapping); } else { @@ -168,14 +296,15 @@ public void testNoParametersOnUpgrade() throws Exception { } // KNN indexing tests when the cluster is upgraded to latest version - public void validateKNNIndexingOnUpgrade() throws Exception { - QUERY_COUNT = NUM_DOCS; + public void validateKNNIndexingOnUpgrade(int numOfDocs) throws Exception { + updateIndexSettings(testIndex, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0)); + forceMergeKnnIndex(testIndex); + QUERY_COUNT = numOfDocs; validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, QUERY_COUNT, K); - cleanUpCache(); - DOC_ID = NUM_DOCS; + clearCache(List.of(testIndex)); + DOC_ID = numOfDocs; addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); QUERY_COUNT = QUERY_COUNT + NUM_DOCS; - validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, QUERY_COUNT, K); forceMergeKnnIndex(testIndex); validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, QUERY_COUNT, K); deleteKNNIndex(testIndex); diff --git a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/ModelIT.java b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/ModelIT.java index 829382e13..7df651a3c 100644 --- a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/ModelIT.java +++ b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/ModelIT.java @@ -10,14 +10,13 @@ import org.opensearch.action.search.SearchResponse; import org.opensearch.client.Request; import org.opensearch.client.Response; -import org.opensearch.common.Strings; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; -import org.opensearch.common.xcontent.XContentParser; +import org.opensearch.core.xcontent.XContentParser; import org.opensearch.common.xcontent.XContentType; import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.plugin.KNNPlugin; -import org.opensearch.rest.RestStatus; +import org.opensearch.core.rest.RestStatus; import org.opensearch.search.SearchHit; import java.io.IOException; @@ -38,7 +37,6 @@ import static org.opensearch.knn.common.KNNConstants.PARAMETERS; import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NLIST; import static org.opensearch.knn.common.KNNConstants.NMSLIB_NAME; -import static org.opensearch.knn.common.KNNConstants.MODEL_INDEX_NAME; public class ModelIT extends AbstractRestartUpgradeTestCase { private static final String TEST_MODEL_INDEX = KNN_BWC_PREFIX + "test-model-index"; @@ -79,6 +77,7 @@ public void testKNNModel() throws Exception { createKnnIndex(testIndex, modelIndexMapping(TEST_FIELD, TEST_MODEL_ID)); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); } else { + Thread.sleep(1000); DOC_ID = NUM_DOCS; addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); QUERY_COUNT = 2 * NUM_DOCS; @@ -111,6 +110,7 @@ public void testKNNModelDefault() throws Exception { createKnnIndex(testIndex, modelIndexMapping(TEST_FIELD, TEST_MODEL_ID_DEFAULT)); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); } else { + Thread.sleep(1000); DOC_ID = NUM_DOCS; addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); QUERY_COUNT = 2 * NUM_DOCS; @@ -141,11 +141,6 @@ public static void wipeAllModels() throws IOException { if (!isRunningAgainstOldCluster()) { deleteKNNModel(TEST_MODEL_ID); deleteKNNModel(TEST_MODEL_ID_DEFAULT); - - Request request = new Request("DELETE", "/" + MODEL_INDEX_NAME); - - Response response = client().performRequest(request); - assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); } } @@ -182,7 +177,7 @@ public void searchKNNModel(String testModelID) throws IOException { } // Confirm that the model gets created using Get Model API - public void validateModelCreated(String modelId) throws IOException, InterruptedException { + public void validateModelCreated(String modelId) throws Exception { Response getResponse = getModel(modelId, null); String responseBody = EntityUtils.toString(getResponse.getEntity()); assertNotNull(responseBody); @@ -229,16 +224,15 @@ public void trainKNNModelDefault(String modelId, String trainingIndexName, Strin // mapping to create index from model public String modelIndexMapping(String fieldName, String modelId) throws IOException { - return Strings.toString( - XContentFactory.jsonBuilder() - .startObject() - .startObject(PROPERTIES) - .startObject(fieldName) - .field(VECTOR_TYPE, KNN_VECTOR) - .field(MODEL_ID, modelId) - .endObject() - .endObject() - .endObject() - ); + return XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES) + .startObject(fieldName) + .field(VECTOR_TYPE, KNN_VECTOR) + .field(MODEL_ID, modelId) + .endObject() + .endObject() + .endObject() + .toString(); } } diff --git a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/QueryANNIT.java b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/QueryANNIT.java new file mode 100644 index 000000000..2cf4f335b --- /dev/null +++ b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/QueryANNIT.java @@ -0,0 +1,61 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.bwc; + +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.LUCENE_NAME; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_SEARCH; +import static org.opensearch.knn.common.KNNConstants.NMSLIB_NAME; + +/** + * Use case: Test queries on indexes created on older versions + */ +public class QueryANNIT extends AbstractRestartUpgradeTestCase { + + private static final String TEST_FIELD = "test-field"; + private static final int DIMENSIONS = 5; + private static final int K = 5; + private static final Integer EF_SEARCH = 10; + private static final int NUM_DOCS = 10; + private static final String ALGORITHM = "hnsw"; + + public void testQueryOnFaissIndex() throws Exception { + if (isRunningAgainstOldCluster()) { + createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, 0, NUM_DOCS); + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, NUM_DOCS, K); + } else { + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, NUM_DOCS, K); + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, NUM_DOCS, K, Map.of(METHOD_PARAMETER_EF_SEARCH, EF_SEARCH)); + deleteKNNIndex(testIndex); + } + } + + public void testQueryOnNmslibIndex() throws Exception { + if (isRunningAgainstOldCluster()) { + createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS, ALGORITHM, NMSLIB_NAME)); + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, 0, NUM_DOCS); + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, NUM_DOCS, K); + } else { + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, NUM_DOCS, K); + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, NUM_DOCS, K, Map.of(METHOD_PARAMETER_EF_SEARCH, EF_SEARCH)); + deleteKNNIndex(testIndex); + } + } + + public void testQueryOnLuceneIndex() throws Exception { + if (isRunningAgainstOldCluster()) { + createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS, ALGORITHM, LUCENE_NAME)); + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, 0, NUM_DOCS); + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, NUM_DOCS, K); + } else { + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, NUM_DOCS, K); + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, NUM_DOCS, K, Map.of(METHOD_PARAMETER_EF_SEARCH, EF_SEARCH)); + deleteKNNIndex(testIndex); + } + } +} diff --git a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/ScriptScoringIT.java b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/ScriptScoringIT.java index 409a07507..2932f32fb 100644 --- a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/ScriptScoringIT.java +++ b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/ScriptScoringIT.java @@ -13,8 +13,9 @@ import org.opensearch.knn.IDVectorProducer; import org.opensearch.knn.KNNResult; import org.opensearch.knn.index.SpaceType; -import org.opensearch.rest.RestStatus; +import org.opensearch.core.rest.RestStatus; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -107,7 +108,7 @@ private void validateKNNInnerProductScriptScoreSearch(String testIndex, String t params.put(QUERY_VALUE, queryVector); params.put(METHOD_PARAMETER_SPACE_TYPE, SpaceType.INNER_PRODUCT.getValue()); - Request request = constructKNNScriptQueryRequest(testIndex, qb, params, k); + Request request = constructKNNScriptQueryRequest(testIndex, qb, params, k, Collections.emptyMap()); Response response = client().performRequest(request); assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); diff --git a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java index 8a965d2fa..64c55f6fd 100644 --- a/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java +++ b/qa/restart-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java @@ -6,6 +6,7 @@ package org.opensearch.knn.bwc; import org.opensearch.common.settings.Settings; +import org.opensearch.knn.index.KNNSettings; import org.opensearch.knn.index.SpaceType; import java.util.Collections; @@ -31,9 +32,14 @@ public void testKNNWarmupDefaultLegacyFieldMapping() throws Exception { waitForClusterHealthGreen(NODES_BWC_CLUSTER); if (isRunningAgainstOldCluster()) { - createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + Settings indexSettings = isApproximateThresholdSupported(getBWCVersion()) + ? buildKNNIndexSettings(0) + : getKNNDefaultIndexSettings(); + createKnnIndex(testIndex, indexSettings, createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); } else { + // update index setting to allow build graph always since we test graph count that are loaded into memory + updateIndexSettings(testIndex, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0)); validateKNNWarmupOnUpgrade(); } } @@ -45,13 +51,16 @@ public void testKNNWarmupCustomLegacyFieldMapping() throws Exception { // When the cluster is in old version, create a KNN index with custom legacy field mapping settings // and add documents into that index if (isRunningAgainstOldCluster()) { - Settings indexMappingSettings = createKNNIndexCustomLegacyFieldMappingSettings( + Settings.Builder indexMappingSettings = createKNNIndexCustomLegacyFieldMappingIndexSettingsBuilder( SpaceType.LINF, KNN_ALGO_PARAM_M_MIN_VALUE, KNN_ALGO_PARAM_EF_CONSTRUCTION_MIN_VALUE ); + if (isApproximateThresholdSupported(getBWCVersion())) { + indexMappingSettings.put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0); + } String indexMapping = createKnnIndexMapping(TEST_FIELD, DIMENSIONS); - createKnnIndex(testIndex, indexMappingSettings, indexMapping); + createKnnIndex(testIndex, indexMappingSettings.build(), indexMapping); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); } else { validateKNNWarmupOnUpgrade(); @@ -62,9 +71,14 @@ public void testKNNWarmupCustomLegacyFieldMapping() throws Exception { // space_type : "l2", engine : "nmslib", m : 16, ef_construction : 512 public void testKNNWarmupDefaultMethodFieldMapping() throws Exception { if (isRunningAgainstOldCluster()) { - createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKNNIndexMethodFieldMapping(TEST_FIELD, DIMENSIONS)); + Settings indexSettings = isApproximateThresholdSupported(getBWCVersion()) + ? buildKNNIndexSettings(0) + : getKNNDefaultIndexSettings(); + createKnnIndex(testIndex, indexSettings, createKNNIndexMethodFieldMapping(TEST_FIELD, DIMENSIONS)); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); } else { + // update index setting to allow build graph always since we test graph count that are loaded into memory + updateIndexSettings(testIndex, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0)); validateKNNWarmupOnUpgrade(); } } @@ -73,9 +87,12 @@ public void testKNNWarmupDefaultMethodFieldMapping() throws Exception { // space_type : "innerproduct", engine : "faiss", m : 50, ef_construction : 1024 public void testKNNWarmupCustomMethodFieldMapping() throws Exception { if (isRunningAgainstOldCluster()) { + Settings indexSettings = isApproximateThresholdSupported(getBWCVersion()) + ? buildKNNIndexSettings(0) + : getKNNDefaultIndexSettings(); createKnnIndex( testIndex, - getKNNDefaultIndexSettings(), + indexSettings, createKNNIndexCustomMethodFieldMapping(TEST_FIELD, DIMENSIONS, SpaceType.INNER_PRODUCT, FAISS_NAME, M, EF_CONSTRUCTION) ); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); @@ -85,21 +102,26 @@ public void testKNNWarmupCustomMethodFieldMapping() throws Exception { } public void validateKNNWarmupOnUpgrade() throws Exception { + // update index setting to allow build graph always since we test graph count that are loaded into memory + updateIndexSettings(testIndex, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0)); int graphCount = getTotalGraphsInCache(); knnWarmup(Collections.singletonList(testIndex)); - assertTrue(getTotalGraphsInCache() > graphCount); + int totalGraph = getTotalGraphsInCache(); + assertTrue(totalGraph > graphCount); QUERY_COUNT = NUM_DOCS; validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, QUERY_COUNT, K); DOC_ID = NUM_DOCS; addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, DOC_ID, NUM_DOCS); + forceMergeKnnIndex(testIndex); int updatedGraphCount = getTotalGraphsInCache(); knnWarmup(Collections.singletonList(testIndex)); assertTrue(getTotalGraphsInCache() > updatedGraphCount); QUERY_COUNT = QUERY_COUNT + NUM_DOCS; + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, QUERY_COUNT, K); deleteKNNIndex(testIndex); } diff --git a/qa/rolling-upgrade/build.gradle b/qa/rolling-upgrade/build.gradle index eb3ea7899..3309566c0 100644 --- a/qa/rolling-upgrade/build.gradle +++ b/qa/rolling-upgrade/build.gradle @@ -7,21 +7,23 @@ import org.opensearch.gradle.testclusters.StandaloneRestIntegTestTask apply from : "$rootDir/qa/build.gradle" -String knn_bwc_version = System.getProperty("bwc.version") +String default_bwc_version = System.getProperty("bwc.version") +String knn_bwc_version = System.getProperty("tests.bwc.version", default_bwc_version) +boolean isSnapshot = knn_bwc_version.contains("-SNAPSHOT") +String knn_bwc_version_no_qualifier = isSnapshot ? knn_bwc_version - "-SNAPSHOT" : knn_bwc_version String baseName = "knnBwcCluster-rolling" -String opensearch_version_upgraded = System.getProperty("rolling.bwctests.opensearch.version", opensearch_version) // Creates a test cluster of previous version and loads k-NN plugin of bwcVersion testClusters { "${baseName}" { testDistribution = "ARCHIVE" - versions = [knn_bwc_version, opensearch_version_upgraded] + versions = [knn_bwc_version, opensearch_version] numberOfNodes = 3 plugin(project.tasks.zipBwcPlugin.archiveFile) setting 'path.repo', "${buildDir}/cluster/shared/repo/${baseName}" setting 'http.content_type.required', 'true' - environment "LD_LIBRARY_PATH", "${buildDir}/testclusters/${baseName}-0/distro/${knn_bwc_version}-ARCHIVE/plugins/opensearch-knn/knnlib;${buildDir}/testclusters/${baseName}-0/distro/${knn_bwc_version}-ARCHIVE/plugins/opensearch-knn/lib" - systemProperty "java.library.path", "${buildDir}/testclusters/${baseName}-0/distro/${knn_bwc_version}-ARCHIVE/plugins/opensearch-knn/knnlib:${buildDir}/testclusters/${baseName}-0/distro/${knn_bwc_version}-ARCHIVE/plugins/opensearch-knn/lib" + environment "LD_LIBRARY_PATH", "${buildDir}/testclusters/${baseName}-0/distro/${knn_bwc_version_no_qualifier}-ARCHIVE/plugins/opensearch-knn/knnlib;${buildDir}/testclusters/${baseName}-0/distro/${knn_bwc_version_no_qualifier}-ARCHIVE/plugins/opensearch-knn/lib" + systemProperty "java.library.path", "${buildDir}/testclusters/${baseName}-0/distro/${knn_bwc_version_no_qualifier}-ARCHIVE/plugins/opensearch-knn/knnlib:${buildDir}/testclusters/${baseName}-0/distro/${knn_bwc_version_no_qualifier}-ARCHIVE/plugins/opensearch-knn/lib" } } @@ -33,6 +35,12 @@ task testAgainstOldCluster(type: StandaloneRestIntegTestTask) { systemProperty 'tests.rest.bwcsuite_cluster', 'old_cluster' systemProperty 'tests.plugin_bwc_version', knn_bwc_version systemProperty 'tests.skip_delete_model_index', 'true' + // Skip test if version is anything lower than 2.2 as they are not supported in those versions + if (knn_bwc_version.startsWith("1.") || knn_bwc_version.startsWith("2.0") || knn_bwc_version.startsWith("2.1")) { + filter { + excludeTestsMatching "org.opensearch.knn.bwc.LuceneFilteringIT.testLuceneFiltering" + } + } nonInputProperties.systemProperty('tests.rest.cluster', "${-> testClusters."${baseName}".allHttpSocketURI.join(",")}") nonInputProperties.systemProperty('tests.clustername', "${-> testClusters."${baseName}".getName()}") systemProperty 'tests.security.manager', 'false' @@ -55,6 +63,12 @@ task testAgainstOneThirdUpgradedCluster(type: StandaloneRestIntegTestTask) { systemProperty 'tests.rest.first_round', 'true' systemProperty 'tests.skip_delete_model_index', 'true' systemProperty 'tests.plugin_bwc_version', knn_bwc_version + // Skip test if version is anything lower than 2.2 as they are not supported in those versions + if (knn_bwc_version.startsWith("1.") || knn_bwc_version.startsWith("2.0") || knn_bwc_version.startsWith("2.1")) { + filter { + excludeTestsMatching "org.opensearch.knn.bwc.LuceneFilteringIT.testLuceneFiltering" + } + } nonInputProperties.systemProperty('tests.rest.cluster', "${-> testClusters."${baseName}".allHttpSocketURI.join(",")}") nonInputProperties.systemProperty('tests.clustername', "${-> testClusters."${baseName}".getName()}") systemProperty 'tests.security.manager', 'false' @@ -74,6 +88,12 @@ task testAgainstTwoThirdsUpgradedCluster(type: StandaloneRestIntegTestTask) { systemProperty 'tests.rest.first_round', 'false' systemProperty 'tests.skip_delete_model_index', 'true' systemProperty 'tests.plugin_bwc_version', knn_bwc_version + // Skip test if version is anything lower than 2.2 as they are not supported in those versions + if (knn_bwc_version.startsWith("1.") || knn_bwc_version.startsWith("2.0") || knn_bwc_version.startsWith("2.1")) { + filter { + excludeTestsMatching "org.opensearch.knn.bwc.LuceneFilteringIT.testLuceneFiltering" + } + } nonInputProperties.systemProperty('tests.rest.cluster', "${-> testClusters."${baseName}".allHttpSocketURI.join(",")}") nonInputProperties.systemProperty('tests.clustername', "${-> testClusters."${baseName}".getName()}") systemProperty 'tests.security.manager', 'false' @@ -93,6 +113,12 @@ task testRollingUpgrade(type: StandaloneRestIntegTestTask) { systemProperty 'tests.rest.bwcsuite_cluster', 'upgraded_cluster' systemProperty 'tests.skip_delete_model_index', 'true' systemProperty 'tests.plugin_bwc_version', knn_bwc_version + // Skip test if version is anything lower than 2.2 as they are not supported in those versions + if (knn_bwc_version.startsWith("1.") || knn_bwc_version.startsWith("2.0") || knn_bwc_version.startsWith("2.1")) { + filter { + excludeTestsMatching "org.opensearch.knn.bwc.LuceneFilteringIT.testLuceneFiltering" + } + } nonInputProperties.systemProperty('tests.rest.cluster', "${-> testClusters."${baseName}".allHttpSocketURI.join(",")}") nonInputProperties.systemProperty('tests.clustername', "${-> testClusters."${baseName}".getName()}") systemProperty 'tests.security.manager', 'false' diff --git a/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/AbstractRollingUpgradeTestCase.java b/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/AbstractRollingUpgradeTestCase.java index 9f54fe3ef..235e3e4df 100644 --- a/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/AbstractRollingUpgradeTestCase.java +++ b/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/AbstractRollingUpgradeTestCase.java @@ -71,6 +71,9 @@ public static ClusterType instance(String value) { } protected final ClusterType getClusterType() { + if (System.getProperty(BWCSUITE_CLUSTER) == null) { + throw new IllegalArgumentException(String.format("[%s] value is null", BWCSUITE_CLUSTER)); + } return ClusterType.instance(System.getProperty(BWCSUITE_CLUSTER)); } diff --git a/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/ClearCacheIT.java b/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/ClearCacheIT.java new file mode 100644 index 000000000..0e8748eb6 --- /dev/null +++ b/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/ClearCacheIT.java @@ -0,0 +1,57 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.bwc; + +import org.opensearch.common.settings.Settings; + +import java.util.Collections; + +import static org.opensearch.knn.TestUtils.NODES_BWC_CLUSTER; + +public class ClearCacheIT extends AbstractRollingUpgradeTestCase { + private static final String TEST_FIELD = "test-field"; + private static final int DIMENSIONS = 5; + private static int docId = 0; + private static final int K = 5; + private static final int NUM_DOCS = 10; + private static int queryCnt = 0; + + // Rolling Upgrade BWC Tests to validate Clear Cache API + public void testClearCache() throws Exception { + waitForClusterHealthGreen(NODES_BWC_CLUSTER); + switch (getClusterType()) { + case OLD: + Settings indexSettings = isApproximateThresholdSupported(getBWCVersion()) + ? buildKNNIndexSettings(0) + : getKNNDefaultIndexSettings(); + createKnnIndex(testIndex, indexSettings, createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + int docIdOld = 0; + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, docIdOld, NUM_DOCS); + int graphCount = getTotalGraphsInCache(); + knnWarmup(Collections.singletonList(testIndex)); + assertTrue(getTotalGraphsInCache() > graphCount); + break; + case UPGRADED: + queryCnt = NUM_DOCS; + validateClearCacheOnUpgrade(queryCnt); + + docId = NUM_DOCS; + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, docId, NUM_DOCS); + + queryCnt = queryCnt + NUM_DOCS; + validateClearCacheOnUpgrade(queryCnt); + deleteKNNIndex(testIndex); + } + + } + + // validation steps for Clear Cache API after upgrading all nodes from old version to new version + public void validateClearCacheOnUpgrade(int queryCount) throws Exception { + clearCache(Collections.singletonList(testIndex)); + assertEquals(0, getTotalGraphsInCache()); + } + +} diff --git a/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java b/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java index 982a05dd7..10df1a79b 100644 --- a/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java +++ b/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/IndexingIT.java @@ -5,8 +5,6 @@ package org.opensearch.knn.bwc; -import java.io.IOException; - import static org.opensearch.knn.TestUtils.NODES_BWC_CLUSTER; public class IndexingIT extends AbstractRollingUpgradeTestCase { @@ -15,6 +13,10 @@ public class IndexingIT extends AbstractRollingUpgradeTestCase { private static final int K = 5; private static final int NUM_DOCS = 10; + private static final String ALGO = "hnsw"; + + private static final String ENGINE = "faiss"; + public void testKNNDefaultIndexSettings() throws Exception { waitForClusterHealthGreen(NODES_BWC_CLUSTER); switch (getClusterType()) { @@ -48,8 +50,84 @@ public void testKNNDefaultIndexSettings() throws Exception { } } + public void testKNNIndexCreation_withLegacyMapper() throws Exception { + waitForClusterHealthGreen(NODES_BWC_CLUSTER); + final String firstMixRoundIndex = testIndex + "first-mix-round"; + final String otherMixRoundIndex = testIndex + "other-mix-round"; + final String upgradedIndex = testIndex + "upgraded"; + switch (getClusterType()) { + case OLD: + createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + int docIdOld = 0; + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, docIdOld, NUM_DOCS); + break; + case MIXED: + if (isFirstMixedRound()) { + docIdOld = 0; + createKnnIndex(firstMixRoundIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + addKNNDocs(firstMixRoundIndex, TEST_FIELD, DIMENSIONS, docIdOld, NUM_DOCS); + } else { + docIdOld = 0; + createKnnIndex(otherMixRoundIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + addKNNDocs(otherMixRoundIndex, TEST_FIELD, DIMENSIONS, docIdOld, NUM_DOCS); + } + break; + case UPGRADED: + docIdOld = 0; + createKnnIndex(upgradedIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + addKNNDocs(upgradedIndex, TEST_FIELD, DIMENSIONS, docIdOld, NUM_DOCS); + + deleteKNNIndex(testIndex); + deleteKNNIndex(firstMixRoundIndex); + deleteKNNIndex(otherMixRoundIndex); + deleteKNNIndex(upgradedIndex); + } + } + + public void testKNNIndexCreation_withMethodMapper() throws Exception { + waitForClusterHealthGreen(NODES_BWC_CLUSTER); + final String firstMixRoundIndex = testIndex + "first-mix-round"; + final String otherMixRoundIndex = testIndex + "other-mix-round"; + final String upgradedIndex = testIndex + "upgraded"; + switch (getClusterType()) { + case OLD: + createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS, ALGO, ENGINE)); + int docIdOld = 0; + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, docIdOld, NUM_DOCS); + break; + case MIXED: + if (isFirstMixedRound()) { + docIdOld = 0; + createKnnIndex( + firstMixRoundIndex, + getKNNDefaultIndexSettings(), + createKnnIndexMapping(TEST_FIELD, DIMENSIONS, ALGO, ENGINE) + ); + addKNNDocs(firstMixRoundIndex, TEST_FIELD, DIMENSIONS, docIdOld, NUM_DOCS); + } else { + docIdOld = 0; + createKnnIndex( + otherMixRoundIndex, + getKNNDefaultIndexSettings(), + createKnnIndexMapping(TEST_FIELD, DIMENSIONS, ALGO, ENGINE) + ); + addKNNDocs(otherMixRoundIndex, TEST_FIELD, DIMENSIONS, docIdOld, NUM_DOCS); + } + break; + case UPGRADED: + docIdOld = 0; + createKnnIndex(upgradedIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS, ALGO, ENGINE)); + addKNNDocs(upgradedIndex, TEST_FIELD, DIMENSIONS, docIdOld, NUM_DOCS); + + deleteKNNIndex(testIndex); + deleteKNNIndex(firstMixRoundIndex); + deleteKNNIndex(otherMixRoundIndex); + deleteKNNIndex(upgradedIndex); + } + } + // validation steps for indexing after upgrading each node from old version to new version - public void validateKNNIndexingOnUpgrade(int totalDocsCount, int docId) throws IOException { + public void validateKNNIndexingOnUpgrade(int totalDocsCount, int docId) throws Exception { validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, totalDocsCount, K); addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, docId, NUM_DOCS); diff --git a/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/QueryANNIT.java b/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/QueryANNIT.java new file mode 100644 index 000000000..080e63241 --- /dev/null +++ b/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/QueryANNIT.java @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.bwc; + +import java.util.Map; + +import static org.opensearch.knn.TestUtils.NODES_BWC_CLUSTER; +import static org.opensearch.knn.common.KNNConstants.FAISS_NAME; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_SEARCH; + +public class QueryANNIT extends AbstractRollingUpgradeTestCase { + private static final String TEST_FIELD = "test-field"; + private static final int DIMENSIONS = 5; + private static final int K = 5; + private static final Integer EF_SEARCH = 10; + private static final int NUM_DOCS = 10; + private static final String ALGORITHM = "hnsw"; + + public void testQueryANNIT() throws Exception { + waitForClusterHealthGreen(NODES_BWC_CLUSTER); + switch (getClusterType()) { + case OLD: + createKnnIndex( + testIndex, + getKNNDefaultIndexSettings(), + createKnnIndexMapping(TEST_FIELD, DIMENSIONS, ALGORITHM, FAISS_NAME) + ); + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, 0, NUM_DOCS); + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, NUM_DOCS, K); + break; + case MIXED: + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, NUM_DOCS, K); + break; + case UPGRADED: + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, NUM_DOCS, K); + validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, NUM_DOCS, K, Map.of(METHOD_PARAMETER_EF_SEARCH, EF_SEARCH)); + deleteKNNIndex(testIndex); + } + } +} diff --git a/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/StatsIT.java b/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/StatsIT.java index df9babd77..25e1f030f 100644 --- a/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/StatsIT.java +++ b/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/StatsIT.java @@ -6,6 +6,7 @@ package org.opensearch.knn.bwc; import org.apache.http.util.EntityUtils; +import org.junit.Before; import org.opensearch.client.Response; import org.opensearch.client.ResponseException; import org.opensearch.knn.plugin.stats.KNNStats; @@ -15,19 +16,25 @@ import java.util.List; import java.util.Map; -import static org.opensearch.knn.plugin.stats.KNNStatsConfig.KNN_STATS; - public class StatsIT extends AbstractRollingUpgradeTestCase { - private KNNStats knnStats = new KNNStats(KNN_STATS); + private KNNStats knnStats; + + @Before + public void setUp() throws Exception { + super.setUp(); + this.knnStats = new KNNStats(); + } - // Validate if all the KNN Stats metrics are returned + // Validate if all the KNN Stats metrics from old version are present in new version public void testAllMetricStatsReturned() throws IOException { Response response = getKnnStats(Collections.emptyList(), Collections.emptyList()); String responseBody = EntityUtils.toString(response.getEntity()); Map clusterStats = parseClusterStatsResponse(responseBody); - assertEquals(knnStats.getClusterStats().keySet(), clusterStats.keySet()); + assertNotNull(clusterStats); + assertTrue(knnStats.getClusterStats().keySet().containsAll(clusterStats.keySet())); List> nodeStats = parseNodeStatsResponse(responseBody); - assertEquals(knnStats.getNodeStats().keySet(), nodeStats.get(0).keySet()); + assertNotNull(nodeStats.get(0)); + assertTrue(knnStats.getNodeStats().keySet().containsAll(nodeStats.get(0).keySet())); } // Verify if it returns failure for invalid metric diff --git a/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java b/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java index a34f4b3cf..7e7e9c1df 100644 --- a/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java +++ b/qa/rolling-upgrade/src/test/java/org/opensearch/knn/bwc/WarmupIT.java @@ -5,8 +5,11 @@ package org.opensearch.knn.bwc; -import java.io.IOException; +import org.opensearch.common.settings.Settings; +import org.opensearch.knn.index.KNNSettings; + import java.util.Collections; +import java.util.List; import static org.opensearch.knn.TestUtils.NODES_BWC_CLUSTER; @@ -20,45 +23,26 @@ public void testKNNWarmup() throws Exception { waitForClusterHealthGreen(NODES_BWC_CLUSTER); switch (getClusterType()) { case OLD: - createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); - int docIdOld = 0; - addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, docIdOld, NUM_DOCS); + Settings indexSettings = isApproximateThresholdSupported(getBWCVersion()) + ? buildKNNIndexSettings(0) + : getKNNDefaultIndexSettings(); + createKnnIndex(testIndex, indexSettings, createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, 0, NUM_DOCS); break; case MIXED: - int totalDocsCountMixed; - int docIdMixed; - if (isFirstMixedRound()) { - docIdMixed = NUM_DOCS; - totalDocsCountMixed = 2 * NUM_DOCS; - } else { - docIdMixed = 2 * NUM_DOCS; - totalDocsCountMixed = 3 * NUM_DOCS; - } - validateKNNWarmupOnUpgrade(totalDocsCountMixed, docIdMixed); + int graphCount = getTotalGraphsInCache(); + knnWarmup(Collections.singletonList(testIndex)); + assertTrue(getTotalGraphsInCache() > graphCount); + clearCache(List.of(testIndex)); break; case UPGRADED: - int docIdUpgraded = 3 * NUM_DOCS; - int totalDocsCountUpgraded = 4 * NUM_DOCS; - validateKNNWarmupOnUpgrade(totalDocsCountUpgraded, docIdUpgraded); - + updateIndexSettings(testIndex, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0)); + addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, NUM_DOCS, NUM_DOCS); + int updatedGraphCount = getTotalGraphsInCache(); + knnWarmup(Collections.singletonList(testIndex)); + assertTrue(getTotalGraphsInCache() > updatedGraphCount); deleteKNNIndex(testIndex); } - - } - - // validation steps for KNN Warmup after upgrading each node from old version to new version - public void validateKNNWarmupOnUpgrade(int totalDocsCount, int docId) throws IOException { - int graphCount = getTotalGraphsInCache(); - knnWarmup(Collections.singletonList(testIndex)); - assertTrue(getTotalGraphsInCache() > graphCount); - - addKNNDocs(testIndex, TEST_FIELD, DIMENSIONS, docId, NUM_DOCS); - - int updatedGraphCount = getTotalGraphsInCache(); - knnWarmup(Collections.singletonList(testIndex)); - assertTrue(getTotalGraphsInCache() > updatedGraphCount); - - validateKNNSearch(testIndex, TEST_FIELD, DIMENSIONS, totalDocsCount, K); } } diff --git a/release-notes/opendistro-elasticsearch-knn.release-notes-1.2.0.0-alpha.1.md b/release-notes/opendistro-elasticsearch-knn.release-notes-1.2.0.0-alpha.1.md deleted file mode 100644 index 5311a7e62..000000000 --- a/release-notes/opendistro-elasticsearch-knn.release-notes-1.2.0.0-alpha.1.md +++ /dev/null @@ -1,8 +0,0 @@ -## 2019-09-17 Version 1.2.0.0-alpha.1 -### New Features - * Adds support for Elasticsearch 7.2.0 - [Commit #](https://github.com/opendistro-for-elasticsearch/k-NN/commit/15ae8c7b3a4ab88e2be974af107161b10d0204bb) - -### Bug fixes - * Performance improvement and bug fixes - [PR 2](https://github.com/opendistro-for-elasticsearch/k-NN/pull/2) - -Note:- For configuring native library for plugin installations from Archive, please refer to [ReadME](https://github.com/opendistro-for-elasticsearch/k-NN/blob/development/README.md#java-native-library-usage) diff --git a/release-notes/opendistro-elasticsearch-knn.release-notes-1.3.0.md b/release-notes/opendistro-elasticsearch-knn.release-notes-1.3.0.md deleted file mode 100644 index d377a5d64..000000000 --- a/release-notes/opendistro-elasticsearch-knn.release-notes-1.3.0.md +++ /dev/null @@ -1,14 +0,0 @@ -## 2019-12-03 Version 1.3.0.0 -### Notable changes - -* opendistro1.3 for KNN for ES 7.3.2 -* Performance improvement and bug fixes -** JNI Memory leak fixes -** Cache time out fix -** Doc values memory fix -** Other minor bugs -** Updated ReadMe -* fixed memory leak in saveIndex -* fixed issue jvm heap leak in JNI - -Note:- For configuring native library for plugin installations from Archive, please refer to [ReadME](https://github.com/opendistro-for-elasticsearch/k-NN/blob/development/README.md#java-native-library-usage) \ No newline at end of file diff --git a/release-notes/opendistro-elasticsearch-knn.release-notes-1.4.0.md b/release-notes/opendistro-elasticsearch-knn.release-notes-1.4.0.md deleted file mode 100644 index 38736b343..000000000 --- a/release-notes/opendistro-elasticsearch-knn.release-notes-1.4.0.md +++ /dev/null @@ -1,19 +0,0 @@ -## 2020-01-24 Version 1.4.0.0 -### Features -#### Elasticsearch Compatibility -* Feature [#11 ](https://github.com/opendistro-for-elasticsearch/k-NN/issues/11): Elasticsearch 7.4.2 compatibility - -#### Documentation -* Feature ( [#40 ](https://github.com/opendistro-for-elasticsearch/k-NN/issues/40 ), [#37 ](https://github.com/opendistro-for-elasticsearch/k-NN/issues/37)). Documentation on knn index creation, settings and stats - -### Enhancements -* KNN Codec Backward Compatibility support [#20 ](https://github.com/opendistro-for-elasticsearch/k-NN/issues/20) - -### Bug Fixes -* Avoid recreating space for each query [#29 ] -* Fix a leak where FileWatchers are added but never removed [#36 ] -* JNI clean up and race conditions [#25 ] -* native memory leak in saveIndex and JVM leak [#14 ] [#15 ] - -### Note -For configuring native library for plugin installations from Archive, please refer to [ReadME](https://github.com/opendistro-for-elasticsearch/k-NN/blob/development/README.md#java-native-library-usage) \ No newline at end of file diff --git a/release-notes/opendistro-elasticsearch-knn.release-notes-1.6.0.md b/release-notes/opendistro-elasticsearch-knn.release-notes-1.6.0.md deleted file mode 100644 index 8dc51aa90..000000000 --- a/release-notes/opendistro-elasticsearch-knn.release-notes-1.6.0.md +++ /dev/null @@ -1,16 +0,0 @@ -## 2020-03-24 Version 1.6.0.0 (Current) -### Features -* Feature [#76](https://github.com/opendistro-for-elasticsearch/k-NN/pull/72): Elasticsearch 7.6.1 compatibility (issue [#71](https://github.com/opendistro-for-elasticsearch/k-NN/issues/71)) -* Feature [#73](https://github.com/opendistro-for-elasticsearch/k-NN/pull/73): Add Github Actions so that changes are automatically tested and artifacts are uploaded to S3 (issue [#74](https://github.com/opendistro-for-elasticsearch/k-NN/issues/74)) - -### Enhancements -* Enhancement [#61](https://github.com/opendistro-for-elasticsearch/k-NN/pull/61): Convert integration tests from ESIntegTestCase to ESRestTestCase, so that they can be run on a remote cluster (issue [#60](https://github.com/opendistro-for-elasticsearch/k-NN/issues/60)) -* Enhancement [#54](https://github.com/opendistro-for-elasticsearch/k-NN/pull/54): Add check in gradle build for license headers (issue [#7](https://github.com/opendistro-for-elasticsearch/k-NN/issues/7)) -* Enhancement [#52](https://github.com/opendistro-for-elasticsearch/k-NN/pull/52): Lazily load efSearch parameter (issue [#51](https://github.com/opendistro-for-elasticsearch/k-NN/issues/51)) - -### Bug Fixes -* Bugfix [#66](https://github.com/opendistro-for-elasticsearch/k-NN/pull/66): Flaky failure in KNN80HnswIndexTests testFooter (issue [#65](https://github.com/opendistro-for-elasticsearch/k-NN/issues/65)) -* Bugfix [#63](https://github.com/opendistro-for-elasticsearch/k-NN/pull/63): Circuit Breaker fails to turn off (issue [#62](https://github.com/opendistro-for-elasticsearch/k-NN/issues/62)) -* Bugfix [#59](https://github.com/opendistro-for-elasticsearch/k-NN/pull/59): Gradle build failure on Mac due to library error (issue [#58](https://github.com/opendistro-for-elasticsearch/k-NN/issues/58)) -* Bugfix [#53](https://github.com/opendistro-for-elasticsearch/k-NN/pull/53): AccessControlException when HNSW library is loaded (issue [#49](https://github.com/opendistro-for-elasticsearch/k-NN/issues/49)) -* Bugfix [#47](https://github.com/opendistro-for-elasticsearch/k-NN/pull/47): Stats API failure in Transport Layer (issue [#45](https://github.com/opendistro-for-elasticsearch/k-NN/issues/45)) diff --git a/release-notes/opendistro-elasticsearch-knn.release-notes-1.7.0.md b/release-notes/opendistro-elasticsearch-knn.release-notes-1.7.0.md deleted file mode 100644 index e370baa4b..000000000 --- a/release-notes/opendistro-elasticsearch-knn.release-notes-1.7.0.md +++ /dev/null @@ -1,12 +0,0 @@ -## Version 1.7.0 (Version compatible with elasticsearch 7.6.1) -### Features -* Feature [#90](https://github.com/opendistro-for-elasticsearch/k-NN/pull/90): Support cosine similarity (issue [#28](https://github.com/opendistro-for-elasticsearch/k-NN/issues/28)). ```Note``` this feature is experimental - -### Enhancements -* Enhancement [#89](https://github.com/opendistro-for-elasticsearch/k-NN/pull/89): Add stats to track the number of requests and errors for KNN query and index operations. (issue [#88](https://github.com/opendistro-for-elasticsearch/k-NN/issues/88)) -* Enhancement [#92](https://github.com/opendistro-for-elasticsearch/k-NN/pull/92): Switched the default value of the circuit breaker from 60% to 50%. (issue [#82](https://github.com/opendistro-for-elasticsearch/k-NN/issues/82)) -* Enhancement [#73](https://github.com/opendistro-for-elasticsearch/k-NN/pull/73): Create Github action that automatically runs integration tests against docker image whenever code is checked into main or opendistro branch. (issue [#74](https://github.com/opendistro-for-elasticsearch/k-NN/issues/74)) - -### Bug Fixes -* Bugfix [#100](https://github.com/opendistro-for-elasticsearch/k-NN/pull/100): Added validation in VectorFieldMapper to check for vector values of NaN and throwing an Exception if so. (issue [#99](https://github.com/opendistro-for-elasticsearch/k-NN/issues/99)) -* Bugfix [#78](https://github.com/opendistro-for-elasticsearch/k-NN/pull/78): Fix debugging integration tests (issue [#77](https://github.com/opendistro-for-elasticsearch/k-NN/issues/77)) diff --git a/release-notes/opendistro-elasticsearch-knn.release-notes-1.8.0.md b/release-notes/opendistro-elasticsearch-knn.release-notes-1.8.0.md deleted file mode 100644 index 091d01e0e..000000000 --- a/release-notes/opendistro-elasticsearch-knn.release-notes-1.8.0.md +++ /dev/null @@ -1,9 +0,0 @@ -## Version 1.8.0 (Version compatible with elasticsearch 7.7.0) -### Features -* Feature [#115](https://github.com/opendistro-for-elasticsearch/k-NN/pull/115): ODFE 1.8 support for Elasticsearch version 7.7.0 (issue [#113](https://github.com/opendistro-for-elasticsearch/k-NN/issues/113)). - -### Enhancements -* Enhancement [#108](https://github.com/opendistro-for-elasticsearch/k-NN/pull/108): Block knn index writes if the Circuit breaker triggers. (issue [#96](https://github.com/opendistro-for-elasticsearch/k-NN/issues/96)) - -### Bug Fixes -* None \ No newline at end of file diff --git a/release-notes/opendistro-elasticsearch-knn.release-notes-1.9.0.0.md b/release-notes/opendistro-elasticsearch-knn.release-notes-1.9.0.0.md deleted file mode 100644 index bbf3f0f3c..000000000 --- a/release-notes/opendistro-elasticsearch-knn.release-notes-1.9.0.0.md +++ /dev/null @@ -1,19 +0,0 @@ -## Version 1.9.0.0 (Version compatible with elasticsearch 7.8.0) -### Features -* Feature [#147](https://github.com/opendistro-for-elasticsearch/k-NN/pull/147): ODFE 1.9 support for Elasticsearch version 7.8.0 (issue [#146](https://github.com/opendistro-for-elasticsearch/k-NN/issues/146)). - -### Enhancements -* Enhancement [#149](https://github.com/opendistro-for-elasticsearch/k-NN/pull/149): Remove/depricate shared library in buildSrc. (issue [#148](https://github.com/opendistro-for-elasticsearch/k-NN/issues/96)) -* Enhancement [#141](https://github.com/opendistro-for-elasticsearch/k-NN/pull/141): Modify artifact release Github action. (issue [#140](https://github.com/opendistro-for-elasticsearch/k-NN/issues/140)) -* Enhancement [#132](https://github.com/opendistro-for-elasticsearch/k-NN/pull/132): Add github action to build library artifacts. (issue [#122](https://github.com/opendistro-for-elasticsearch/k-NN/issues/122)) -* Enhancement [#126](https://github.com/opendistro-for-elasticsearch/k-NN/pull/126): Ability to dynamically update efSearch setting. (issue [#116](https://github.com/opendistro-for-elasticsearch/k-NN/issues/116)) -* Enhancement [#125](https://github.com/opendistro-for-elasticsearch/k-NN/pull/125): Fix test structure. (issue [#124](https://github.com/opendistro-for-elasticsearch/k-NN/issues/124)) -* Enhancement [#123](https://github.com/opendistro-for-elasticsearch/k-NN/pull/123): Build separate artifacts for library using CPack. (issue [#122](https://github.com/opendistro-for-elasticsearch/k-NN/issues/122)) - -### Bug Fixes -* Bugfix [#155](https://github.com/opendistro-for-elasticsearch/k-NN/pull/155): Bad recall from Lucene upgrade 8.5.1. (issue [#154](https://github.com/opendistro-for-elasticsearch/k-NN/issues/154)) -* Bugfix [#143](https://github.com/opendistro-for-elasticsearch/k-NN/pull/143): Add recursive option to zip. (issue [#140](https://github.com/opendistro-for-elasticsearch/k-NN/issues/140)) -* Bugfix [#138](https://github.com/opendistro-for-elasticsearch/k-NN/pull/138): CMake fails to use c++11 CMake 2.8. (issue [#137](https://github.com/opendistro-for-elasticsearch/k-NN/issues/137)) -* Bugfix [#134](https://github.com/opendistro-for-elasticsearch/k-NN/pull/134): Fix Jacoco coverage issue introduced in odfe 1.8. (issue [#127](https://github.com/opendistro-for-elasticsearch/k-NN/issues/127)) -* Bugfix [#130](https://github.com/opendistro-for-elasticsearch/k-NN/pull/130): Fixes parent directory in makeJniLib gradle task. (issue [#137](https://github.com/opendistro-for-elasticsearch/k-NN/issues/137)) -* Bugfix [#125](https://github.com/opendistro-for-elasticsearch/k-NN/pull/125): Flaky test cases caused by Counter Enum. (issue [#124](https://github.com/opendistro-for-elasticsearch/k-NN/issues/124)) diff --git a/release-notes/opendistro-for-elasticsearch-knn.release-notes-1.10.1.0.md b/release-notes/opendistro-for-elasticsearch-knn.release-notes-1.10.1.0.md deleted file mode 100644 index 192bd14b2..000000000 --- a/release-notes/opendistro-for-elasticsearch-knn.release-notes-1.10.1.0.md +++ /dev/null @@ -1,48 +0,0 @@ -## Version 1.10.1.0 Release Notes - -Compatible with Elasticsearch 7.9.1 -### Features - -* Add Warmup API to load indices graphs into memory ([#162](https://github.com/opendistro-for-elasticsearch/k-NN/pull/162)) - -### Enhancements - -* Upgrade nmslib to v2.0.6 ([#160](https://github.com/opendistro-for-elasticsearch/k-NN/pull/160)) - -### Bug Fixes - -* Update guava version to 29.0 ([#182](https://github.com/opendistro-for-elasticsearch/k-NN/pull/182)) -* Add default index settings when parsing index ([#205](https://github.com/opendistro-for-elasticsearch/k-NN/pull/205)) -* NPE in force merge when non knn doc gets updated to knn doc across segments ([#212](https://github.com/opendistro-for-elasticsearch/k-NN/pull/212)) -* Fix casting issue with cache expiration ([#215](https://github.com/opendistro-for-elasticsearch/k-NN/pull/215)) - -### Infrastructure - -* Reset state for uTs so tests run independently ([#159](https://github.com/opendistro-for-elasticsearch/k-NN/pull/159)) -* Pass -march=x86-64 to build JNI library ([#164](https://github.com/opendistro-for-elasticsearch/k-NN/pull/164)) -* Fix versioning for lib artifacts ([#166](https://github.com/opendistro-for-elasticsearch/k-NN/pull/166)) -* Add release notes automation ([#168](https://github.com/opendistro-for-elasticsearch/k-NN/pull/168)) -* Add Github action to build library artifacts ([#170](https://github.com/opendistro-for-elasticsearch/k-NN/pull/170)) -* Flaky rest test case fix ([#183](https://github.com/opendistro-for-elasticsearch/k-NN/pull/183)) -* Add code coverage widget and badges ([#191](https://github.com/opendistro-for-elasticsearch/k-NN/pull/191)) -* Add Codecov configuration to set a coverage threshold to pass the check on a commit ([#192](https://github.com/opendistro-for-elasticsearch/k-NN/pull/192)) -* Add AWS CLI in order to ship library artifacts from container ([#194](https://github.com/opendistro-for-elasticsearch/k-NN/pull/194)) -* Remove sudo from "./aws install" in library build action ([#202](https://github.com/opendistro-for-elasticsearch/k-NN/pull/202)) -* Fix download link in package description ([#214](https://github.com/opendistro-for-elasticsearch/k-NN/pull/214)) - -### Documentation - -* Performance tuning/Recommendations ([#177](https://github.com/opendistro-for-elasticsearch/k-NN/pull/177)) -* Fix cluster setting example in README.md ([#186](https://github.com/opendistro-for-elasticsearch/k-NN/pull/186)) -* Add scoring documentation ([#193](https://github.com/opendistro-for-elasticsearch/k-NN/pull/193)) -* Add 1.10.0.0 release notes ([#201](https://github.com/opendistro-for-elasticsearch/k-NN/pull/201)) - -### Maintenance - -* ODFE 1.10 support for k-NN plugin ([#199](https://github.com/opendistro-for-elasticsearch/k-NN/pull/199)) -* Upgrade Elasticsearch to 7.9.1 and ODFE to 1.10.1 ([#217](https://github.com/opendistro-for-elasticsearch/k-NN/pull/217)) - -### Refactoring - -* Update default variable settings name ([#209](https://github.com/opendistro-for-elasticsearch/k-NN/pull/209)) - diff --git a/release-notes/opendistro-for-elasticsearch-knn.release-notes-1.11.0.0.md b/release-notes/opendistro-for-elasticsearch-knn.release-notes-1.11.0.0.md deleted file mode 100644 index ba9bf4b4f..000000000 --- a/release-notes/opendistro-for-elasticsearch-knn.release-notes-1.11.0.0.md +++ /dev/null @@ -1,21 +0,0 @@ -## Version 1.11.0.0 Release Notes - -Compatible with Elasticsearch 7.9.1 -### Features - -* Pre filter support through custom scoring ([#196](https://github.com/opendistro-for-elasticsearch/k-NN/pull/196)) - -### Enhancements - -* Add existsQuery method implementation to KNNVectorFieldType ([#228](https://github.com/opendistro-for-elasticsearch/k-NN/pull/228)) -* Change "space" parameter to "space_type" for custom scoring ([#232](https://github.com/opendistro-for-elasticsearch/k-NN/pull/232)) -* change space -> space_type ([#234](https://github.com/opendistro-for-elasticsearch/k-NN/pull/234)) -* Add stats for custom scoring feature ([#233](https://github.com/opendistro-for-elasticsearch/k-NN/pull/233)) - -### Bug Fixes - -* KNN score fix for non knn documents ([#231](https://github.com/opendistro-for-elasticsearch/k-NN/pull/231)) -* Fix script statistics flaky test case ([#235](https://github.com/opendistro-for-elasticsearch/k-NN/pull/235)) -* Refactor KNNVectorFieldMapper ([#240](https://github.com/opendistro-for-elasticsearch/k-NN/pull/240)) -* Fix PostingsFormat in KNN Codec ([#236](https://github.com/opendistro-for-elasticsearch/k-NN/pull/236)) - diff --git a/release-notes/opendistro-for-elasticsearch-knn.release-notes-1.12.0.0.md b/release-notes/opendistro-for-elasticsearch-knn.release-notes-1.12.0.0.md deleted file mode 100644 index f52c0f3c9..000000000 --- a/release-notes/opendistro-for-elasticsearch-knn.release-notes-1.12.0.0.md +++ /dev/null @@ -1,13 +0,0 @@ -## Version 1.12.0.0 Release Notes - -Compatible with Elasticsearch 7.10.0 - -### Features - -* Support for hamming bit distance in custom scoring ([#267](https://github.com/opendistro-for-elasticsearch/k-NN/pull/267)) - -### Maintenance - -* k-NN plugin support for Elasticsearch version 7.10.0 ([#271](https://github.com/opendistro-for-elasticsearch/k-NN/pull/271)) -* Bump odfe version to 1.12 ([#273](https://github.com/opendistro-for-elasticsearch/k-NN/pull/273)) - diff --git a/release-notes/opendistro-for-elasticsearch-knn.release-notes-1.13.0.0.md b/release-notes/opendistro-for-elasticsearch-knn.release-notes-1.13.0.0.md deleted file mode 100644 index 0b0199c97..000000000 --- a/release-notes/opendistro-for-elasticsearch-knn.release-notes-1.13.0.0.md +++ /dev/null @@ -1,34 +0,0 @@ -## Version 1.13.0.0 Release Notes - -Compatible with Elasticsearch 7.10.2 - -### Features - -* Support k-NN similarity functions in painless scripting ([#281](https://github.com/opendistro-for-elasticsearch/k-NN/pull/281)) -* Add support for L1 distance in AKNN, custom scoring and painless scripting ([#310](https://github.com/opendistro-for-elasticsearch/k-NN/pull/310)) - -### Enhancements - -* Upgrade nmslib to 2.0.11 ([#302](https://github.com/opendistro-for-elasticsearch/k-NN/pull/302)) -* Upgrade commons-beanutils ([#297](https://github.com/opendistro-for-elasticsearch/k-NN/pull/297)) - -### Bug Fixes - -* Fix find_path bug in CMakeLists ([#280](https://github.com/opendistro-for-elasticsearch/k-NN/pull/280)) -* Add builder constructor that takes algo params ([#289](https://github.com/opendistro-for-elasticsearch/k-NN/pull/289)) - -### Infrastructure - -* Add arm64 support and correct the naming convention to the new standards ([#299](https://github.com/opendistro-for-elasticsearch/k-NN/pull/299)) -* Run KNN integ tests with security plugin enabled ([#304](https://github.com/opendistro-for-elasticsearch/k-NN/pull/304)) -* Update artifact naming ([#309](https://github.com/opendistro-for-elasticsearch/k-NN/pull/309)) -* Change CD workflow to use new staging bucket for artifacts ([#301](https://github.com/opendistro-for-elasticsearch/k-NN/pull/301)) - -### Documentation - -* Add copyright header ([#307](https://github.com/opendistro-for-elasticsearch/k-NN/pull/307)) - -### Maintenance - -* Upgrade odfe version to 1.13.0 ([#312](https://github.com/opendistro-for-elasticsearch/k-NN/pull/312)) - diff --git a/release-notes/opensearch-knn.release-notes-2.10.0.0.md b/release-notes/opensearch-knn.release-notes-2.10.0.0.md new file mode 100644 index 000000000..5ac0004df --- /dev/null +++ b/release-notes/opensearch-knn.release-notes-2.10.0.0.md @@ -0,0 +1,16 @@ +## Version 2.10.0.0 Release Notes + +Compatible with OpenSearch 2.10.0 + +### Features +* Add Clear Cache API ([#740](https://github.com/opensearch-project/k-NN/pull/740)) +### Enhancements +* Enabled the IVF algorithm to work with Filters of K-NN Query. ([#1013](https://github.com/opensearch-project/k-NN/pull/1013)) +* Improved the logic to switch to exact search for restrictive filters search for better recall. ([#1059](https://github.com/opensearch-project/k-NN/pull/1059)) +* Added max distance computation logic to enhance the switch to exact search in filtered Nearest Neighbor Search. ([#1066](https://github.com/opensearch-project/k-NN/pull/1066)) +### Bug Fixes +* Update Faiss parameter construction to allow HNSW+PQ to work ([#1074](https://github.com/opensearch-project/k-NN/pull/1074)) +### Maintenance +* Update Guava Version to 32.0.1 ([#1019](https://github.com/opensearch-project/k-NN/pull/1019)) +### Refactoring +* Fix TransportAddress Refactoring Changes in Core ([#1020](https://github.com/opensearch-project/k-NN/pull/1020)) diff --git a/release-notes/opensearch-knn.release-notes-2.11.0.0.md b/release-notes/opensearch-knn.release-notes-2.11.0.0.md new file mode 100644 index 000000000..8cb6dde36 --- /dev/null +++ b/release-notes/opensearch-knn.release-notes-2.11.0.0.md @@ -0,0 +1,9 @@ +## Version 2.11.0.0 Release Notes + +Compatible with OpenSearch 2.11.0 + +### Enhancements +* Added support for ignore_unmapped in KNN queries. [#1071](https://github.com/opensearch-project/k-NN/pull/1071) +* Add graph creation stats to the KNNStats API. [#1141](https://github.com/opensearch-project/k-NN/pull/1141) +### Maintenance +* Update bytebuddy to 1.14.7 [#1135](https://github.com/opensearch-project/k-NN/pull/1135) diff --git a/release-notes/opensearch-knn.release-notes-2.11.1.0.md b/release-notes/opensearch-knn.release-notes-2.11.1.0.md new file mode 100644 index 000000000..238d05bcc --- /dev/null +++ b/release-notes/opensearch-knn.release-notes-2.11.1.0.md @@ -0,0 +1,6 @@ +## Version 2.11.1.0 Release Notes + +Compatible with OpenSearch 2.11.1 + +### Infrastructure +* Make sure not hardcoding user name when switching to uid 1000 on CI.yml [#1252](https://github.com/opensearch-project/k-NN/pull/1252) diff --git a/release-notes/opensearch-knn.release-notes-2.12.0.0.md b/release-notes/opensearch-knn.release-notes-2.12.0.0.md new file mode 100644 index 000000000..0bc6be9ec --- /dev/null +++ b/release-notes/opensearch-knn.release-notes-2.12.0.0.md @@ -0,0 +1,32 @@ +## Version 2.12.0.0 Release Notes + +Compatible with OpenSearch 2.12.0 + +### Features +* Add parent join support for lucene knn [#1182](https://github.com/opensearch-project/k-NN/pull/1182) +* Add parent join support for faiss hnsw [#1398](https://github.com/opensearch-project/k-NN/pull/1398) +### Enhancements +* Increase Lucene max dimension limit to 16,000 [#1346](https://github.com/opensearch-project/k-NN/pull/1346) +* Tuned default values for ef_search and ef_construction for better indexing and search performance for vector search [#1353](https://github.com/opensearch-project/k-NN/pull/1353) +* Enabled Filtering on Nested Vector fields with top level filters [#1372](https://github.com/opensearch-project/k-NN/pull/1372) +* Throw proper exception to invalid k-NN query [#1380](https://github.com/opensearch-project/k-NN/pull/1380) +### Bug Fixes +* Fix use-after-free case on nmslib search path [#1305](https://github.com/opensearch-project/k-NN/pull/1305) +* Allow nested knn field mapping when train model [#1318](https://github.com/opensearch-project/k-NN/pull/1318) +* Properly designate model state for actively training models when nodes crash or leave cluster [#1317](https://github.com/opensearch-project/k-NN/pull/1317) +* Fix script score queries not getting cached [#1367](https://github.com/opensearch-project/k-NN/pull/1367) +* Fix KNNScorer to apply boost [#1403](https://github.com/opensearch-project/k-NN/pull/1403) +* Fix equals and hashCode methods for KNNQuery and KNNQueryBuilder [#1397](https://github.com/opensearch-project/k-NN/pull/1397) +* Pass correct value on IDSelectorBitmap initialization [#1444](https://github.com/opensearch-project/k-NN/pull/1444) +### Infrastructure +* Upgrade gradle to 8.4 [1289](https://github.com/opensearch-project/k-NN/pull/1289) +* Refactor security testing to install from individual components [#1307](https://github.com/opensearch-project/k-NN/pull/1307) +* Refactor integ tests that access model index [#1423](https://github.com/opensearch-project/k-NN/pull/1423) +* Fix flaky model tests [#1429](https://github.com/opensearch-project/k-NN/pull/1429) +### Maintenance +* Update developer guide to include M1 Setup [#1222](https://github.com/opensearch-project/k-NN/pull/1222) +* Upgrade urllib to 1.26.17 [#1278](https://github.com/opensearch-project/k-NN/pull/1278) +* Upgrade urllib to 1.26.18 [#1319](https://github.com/opensearch-project/k-NN/pull/1319) +* Upgrade guava to 32.1.3 [#1319](https://github.com/opensearch-project/k-NN/pull/1319) +* Bump lucene codec to 99 [#1383](https://github.com/opensearch-project/k-NN/pull/1383) +* Update spotless and eclipse dependencies [#1450](https://github.com/opensearch-project/k-NN/pull/1450) \ No newline at end of file diff --git a/release-notes/opensearch-knn.release-notes-2.13.0.0.md b/release-notes/opensearch-knn.release-notes-2.13.0.0.md new file mode 100644 index 000000000..9daa9bd4a --- /dev/null +++ b/release-notes/opensearch-knn.release-notes-2.13.0.0.md @@ -0,0 +1,24 @@ +## Version 2.13.0.0 Release Notes + +Compatible with OpenSearch 2.13.0 + +### Enhancements +* Optize Faiss Query With Filters: Reduce iteration and memory for id filter [#1402](https://github.com/opensearch-project/k-NN/pull/1402) +* Detect AVX2 Dynamically on the System [#1502](https://github.com/opensearch-project/k-NN/pull/1502) +* Validate zero vector when using cosine metric [#1501](https://github.com/opensearch-project/k-NN/pull/1501) +* Persist model definition in model metadata [#1527] (https://github.com/opensearch-project/k-NN/pull/1527) +* Added Inner Product Space type support for Lucene Engine [#1551](https://github.com/opensearch-project/k-NN/pull/1551) +* Add Range Validation for Faiss SQFP16 [#1493](https://github.com/opensearch-project/k-NN/pull/1493) +* SQFP16 Range Validation for Faiss IVF Models [#1557](https://github.com/opensearch-project/k-NN/pull/1557) +### Bug Fixes +* Disable sdc table for HNSWPQ read-only indices [#1518](https://github.com/opensearch-project/k-NN/pull/1518) +* Switch SpaceType.INNERPRODUCT's vector similarity function to MAXIMUM_INNER_PRODUCT [#1532](https://github.com/opensearch-project/k-NN/pull/1532) +* Add patch to fix arm segfault in nmslib during ingestion [#1541](https://github.com/opensearch-project/k-NN/pull/1541) +* Share ivfpq-l2 table allocations across indices on load [#1558](https://github.com/opensearch-project/k-NN/pull/1558) +### Infrastructure +* Manually install zlib for win CI [#1513](https://github.com/opensearch-project/k-NN/pull/1513) +* Update k-NN build artifact script to enable SIMD on ARM for Faiss [#1543](https://github.com/opensearch-project/k-NN/pull/1543) +### Maintenance +* Bump faiss lib commit to 32f0e8cf92cd2275b60364517bb1cce67aa29a55 [#1443](https://github.com/opensearch-project/k-NN/pull/1443) +* Fix FieldInfo Parameters Mismatch [#1490](https://github.com/opensearch-project/k-NN/pull/1490) +* Upgrade faiss to 12b92e9 [#1509](https://github.com/opensearch-project/k-NN/pull/1509) \ No newline at end of file diff --git a/release-notes/opensearch-knn.release-notes-2.14.0.0.md b/release-notes/opensearch-knn.release-notes-2.14.0.0.md new file mode 100644 index 000000000..113a30600 --- /dev/null +++ b/release-notes/opensearch-knn.release-notes-2.14.0.0.md @@ -0,0 +1,20 @@ +## Version 2.14.0.0 Release Notes + +Compatible with OpenSearch 2.14.0 + +### Features +* Add k-NN clear cache api [#740](https://github.com/opensearch-project/k-NN/pull/740) +* Support radial search in k-NN plugin [#1617](https://github.com/opensearch-project/k-NN/pull/1617) +* Support filter and nested field in faiss engine radial search [#1652](https://github.com/opensearch-project/k-NN/pull/1652) +### Enhancements +* Make the HitQueue size more appropriate for exact search [#1549](https://github.com/opensearch-project/k-NN/pull/1549) +* Implement the Streaming Feature to stream vectors from Java to JNI layer to enable creation of larger segments for vector indices [#1604](https://github.com/opensearch-project/k-NN/pull/1604) +* Remove unnecessary toString conversion of vector field and added some minor optimization in KNNCodec [1613](https://github.com/opensearch-project/k-NN/pull/1613) +* Serialize all models into cluster metadata [#1499](https://github.com/opensearch-project/k-NN/pull/1499) +### Bug Fixes +* Add stored fields for knn_vector type [#1630](https://github.com/opensearch-project/k-NN/pull/1630) +* Enable script score to work with model based indices [#1649](https://github.com/opensearch-project/k-NN/pull/1649) +### Infrastructure +* Add micro-benchmark module in k-NN plugin for benchmark streaming vectors to JNI layer functionality. [#1583](https://github.com/opensearch-project/k-NN/pull/1583) +* Add arm64 check when SIMD is disabled [#1618](https://github.com/opensearch-project/k-NN/pull/1618) +* Skip rebuild from scratch after cmake is run [#1636](https://github.com/opensearch-project/k-NN/pull/1636) diff --git a/release-notes/opensearch-knn.release-notes-2.15.0.0.md b/release-notes/opensearch-knn.release-notes-2.15.0.0.md new file mode 100644 index 000000000..198c32ce9 --- /dev/null +++ b/release-notes/opensearch-knn.release-notes-2.15.0.0.md @@ -0,0 +1,15 @@ +## Version 2.15.0.0 Release Notes + +Compatible with OpenSearch 2.15.0 + +### Features +* Use the Lucene Distance Calculation Function in Script Scoring for doing exact search [#1699](https://github.com/opensearch-project/k-NN/pull/1699) +### Enhancements +* Add KnnCircuitBreakerException and modify exception message [#1688](https://github.com/opensearch-project/k-NN/pull/1688) +* Add stats for radial search [#1684](https://github.com/opensearch-project/k-NN/pull/1684) +* Support script score when doc value is disabled and fix misusing DISI [#1696](https://github.com/opensearch-project/k-NN/pull/1696) +* Add validation for pq m parameter before training starts [#1713](https://github.com/opensearch-project/k-NN/pull/1713) +* Block delete model requests if an index uses the model [#1722](https://github.com/opensearch-project/k-NN/pull/1722) +### Bug Fixes +* Block commas in model description [#1692](https://github.com/opensearch-project/k-NN/pull/1692) +* Update threshold value after new result is added [#1715](https://github.com/opensearch-project/k-NN/pull/1715) diff --git a/release-notes/opensearch-knn.release-notes-2.16.0.0.md b/release-notes/opensearch-knn.release-notes-2.16.0.0.md new file mode 100644 index 000000000..da60c2cbf --- /dev/null +++ b/release-notes/opensearch-knn.release-notes-2.16.0.0.md @@ -0,0 +1,26 @@ +## Version 2.16.0.0 Release Notes + +Compatible with OpenSearch 2.16.0 + +### Features +* Adds dynamic query parameter ef_search [#1783](https://github.com/opensearch-project/k-NN/pull/1783) +* Adds dynamic query parameter ef_search in radial search faiss engine [#1790](https://github.com/opensearch-project/k-NN/pull/1790) +* Adds dynamic query parameter nprobes [#1792](https://github.com/opensearch-project/k-NN/pull/1792) +* Add binary format support with HNSW method in Faiss Engine [#1781](https://github.com/opensearch-project/k-NN/pull/1781) +* Add script scoring support for knn field with binary data type [#1826](https://github.com/opensearch-project/k-NN/pull/1826) +* Add painless script support for hamming with binary vector data type [#1839](https://github.com/opensearch-project/k-NN/pull/1839) +* Add binary format support with IVF method in Faiss Engine [#1784](https://github.com/opensearch-project/k-NN/pull/1784) +* Add support for Lucene inbuilt Scalar Quantizer [#1848](https://github.com/opensearch-project/k-NN/pull/1848) +### Enhancements +* Switch from byte stream to byte ref for serde [#1825](https://github.com/opensearch-project/k-NN/pull/1825) +### Bug Fixes +* Fixing the arithmetic to find the number of vectors to stream from java to jni layer.[#1804](https://github.com/opensearch-project/k-NN/pull/1804) +* Fixed LeafReaders casting errors to SegmentReaders when segment replication is enabled during search.[#1808](https://github.com/opensearch-project/k-NN/pull/1808) +* Release memory properly for an array type [#1820](https://github.com/opensearch-project/k-NN/pull/1820) +* FIX Same Suffix Cause Recall Drop to zero [#1802](https://github.com/opensearch-project/k-NN/pull/1802) +### Infrastructure +* Apply custom patch only once by comparing the last patch id [#1833](https://github.com/opensearch-project/k-NN/pull/1833) +### Documentation +* Update dev guide to fix clang linking issue on arm [#1746](https://github.com/opensearch-project/k-NN/pull/1746) +### Maintenance +* Bump faiss commit to 33c0ba5 [#1796](https://github.com/opensearch-project/k-NN/pull/1796) \ No newline at end of file diff --git a/release-notes/opensearch-knn.release-notes-2.17.0.0.md b/release-notes/opensearch-knn.release-notes-2.17.0.0.md new file mode 100644 index 000000000..d5bf80319 --- /dev/null +++ b/release-notes/opensearch-knn.release-notes-2.17.0.0.md @@ -0,0 +1,39 @@ +## Version 2.17.0.0 Release Notes + +Compatible with OpenSearch 2.17.0 + +### Features +* Integrate Lucene Vector field with native engines to use KNNVectorFormat during segment creation [#1945](https://github.com/opensearch-project/k-NN/pull/1945) +* k-NN query rescore support for native engines [#1984](https://github.com/opensearch-project/k-NN/pull/1984) +* Add support for byte vector with Faiss Engine HNSW algorithm [#1823](https://github.com/opensearch-project/k-NN/pull/1823) +* Add support for byte vector with Faiss Engine IVF algorithm [#2002](https://github.com/opensearch-project/k-NN/pull/2002) +* Add mode/compression configuration support for disk-based vector search [#2034](https://github.com/opensearch-project/k-NN/pull/2034) +* Add spaceType as a top level optional parameter while creating vector field. [#2044](https://github.com/opensearch-project/k-NN/pull/2044) +### Enhancements +* Adds iterative graph build capability into a faiss index to improve the memory footprint during indexing and Integrates KNNVectorsFormat for native engines[#1950](https://github.com/opensearch-project/k-NN/pull/1950) +* Add model version to model metadata and change model metadata reads to be from cluster metadata [#2005](https://github.com/opensearch-project/k-NN/pull/2005) +### Bug Fixes +* Corrected search logic for scenario with non-existent fields in filter [#1874](https://github.com/opensearch-project/k-NN/pull/1874) +* Add script_fields context to KNNAllowlist [#1917] (https://github.com/opensearch-project/k-NN/pull/1917) +* Fix graph merge stats size calculation [#1844](https://github.com/opensearch-project/k-NN/pull/1844) +* Disallow a vector field to have an invalid character for a physical file name. [#1936](https://github.com/opensearch-project/k-NN/pull/1936) +* Fix memory overflow caused by cache behavior [#2015](https://github.com/opensearch-project/k-NN/pull/2015) +* Use correct type for binary vector in ivf training [#2086](https://github.com/opensearch-project/k-NN/pull/2086) +* Switch MINGW32 to MINGW64 [#2090](https://github.com/opensearch-project/k-NN/pull/2090) +### Infrastructure +* Parallelize make to reduce build time [#2006] (https://github.com/opensearch-project/k-NN/pull/2006) +### Maintenance +* Fix a flaky unit test:testMultiFieldsKnnIndex, which was failing due to inconsistent merge behaviors [#1924](https://github.com/opensearch-project/k-NN/pull/1924) +### Refactoring +* Introduce KNNVectorValues interface to iterate on different types of Vector values during indexing and search [#1897](https://github.com/opensearch-project/k-NN/pull/1897) +* Integrate KNNVectorValues with vector ANN Search flow [#1952](https://github.com/opensearch-project/k-NN/pull/1952) +* Clean up parsing for query [#1824](https://github.com/opensearch-project/k-NN/pull/1824) +* Refactor engine package structure [#1913](https://github.com/opensearch-project/k-NN/pull/1913) +* Refactor method structure and definitions [#1920](https://github.com/opensearch-project/k-NN/pull/1920) +* Refactor KNNVectorFieldType from KNNVectorFieldMapper to a separate class for better readability. [#1931](https://github.com/opensearch-project/k-NN/pull/1931) +* Generalize lib interface to return context objects [#1925](https://github.com/opensearch-project/k-NN/pull/1925) +* Restructure mappers to better handle null cases and avoid branching in parsing [#1939](https://github.com/opensearch-project/k-NN/pull/1939) +* Added Quantization Framework and implemented 1Bit and multibit quantizer[#1889](https://github.com/opensearch-project/k-NN/issues/1889) +* Encapsulate dimension, vector data type validation/processing inside Library [#1957](https://github.com/opensearch-project/k-NN/pull/1957) +* Add quantization state cache [#1960](https://github.com/opensearch-project/k-NN/pull/1960) +* Add quantization state reader and writer [#1997](https://github.com/opensearch-project/k-NN/pull/1997) diff --git a/release-notes/opensearch-knn.release-notes-2.17.1.0.md b/release-notes/opensearch-knn.release-notes-2.17.1.0.md new file mode 100644 index 000000000..0d2083959 --- /dev/null +++ b/release-notes/opensearch-knn.release-notes-2.17.1.0.md @@ -0,0 +1,7 @@ +## Version 2.17.1.0 Release Notes + +Compatible with OpenSearch 2.17.1 + +### Bug Fixes +* Adds concurrent segment search support for mode auto [#2111](https://github.com/opensearch-project/k-NN/pull/2111) +* Change min oversample to 1 [#2117](https://github.com/opensearch-project/k-NN/pull/2117) diff --git a/release-notes/opensearch-knn.release-notes-2.18.0.0.md b/release-notes/opensearch-knn.release-notes-2.18.0.0.md new file mode 100644 index 000000000..844605a8e --- /dev/null +++ b/release-notes/opensearch-knn.release-notes-2.18.0.0.md @@ -0,0 +1,31 @@ +## Version 2.18.0.0 Release Notes + +Compatible with OpenSearch 2.18.0 + +### Features +* Add AVX512 support to k-NN for FAISS library [#2069](https://github.com/opensearch-project/k-NN/pull/2069) +### Enhancements +* Introducing a loading layer in FAISS [#2033](https://github.com/opensearch-project/k-NN/issues/2033) +* Add short circuit if no live docs are in segments [#2059](https://github.com/opensearch-project/k-NN/pull/2059) +* Optimize reduceToTopK in ResultUtil by removing pre-filling and reducing peek calls [#2146](https://github.com/opensearch-project/k-NN/pull/2146) +* Update Default Rescore Context based on Dimension [#2149](https://github.com/opensearch-project/k-NN/pull/2149) +* KNNIterators should support with and without filters [#2155](https://github.com/opensearch-project/k-NN/pull/2155) +* Adding Support to Enable/Disble Share level Rescoring and Update Oversampling Factor[#2172](https://github.com/opensearch-project/k-NN/pull/2172) +* Add support to build vector data structures greedily and perform exact search when there are no engine files [#1942](https://github.com/opensearch-project/k-NN/issues/1942) +* Add CompressionLevel Calculation for PQ [#2200](https://github.com/opensearch-project/k-NN/pull/2200) +* Remove FSDirectory dependency from native engine constructing side and deprecated FileWatcher [#2182](https://github.com/opensearch-project/k-NN/pull/2182) +* Update approximate_threshold to 15K documents [#2229](https://github.com/opensearch-project/k-NN/pull/2229) +* Update default engine to FAISS [#2221](https://github.com/opensearch-project/k-NN/pull/2221) +### Bug Fixes +* Add DocValuesProducers for releasing memory when close index [#1946](https://github.com/opensearch-project/k-NN/pull/1946) +* KNN80DocValues should only be considered for BinaryDocValues fields [#2147](https://github.com/opensearch-project/k-NN/pull/2147) +* Score Fix for Binary Quantized Vector and Setting Default value in case of shard level rescoring is disabled for oversampling factor[#2183](https://github.com/opensearch-project/k-NN/pull/2183) +* Java Docs Fix For 2.x[#2190](https://github.com/opensearch-project/k-NN/pull/2190) +### Documentation +* Fix sed command in DEVELOPER_GUIDE.md to append a new line character '\n'. [#2181](https://github.com/opensearch-project/k-NN/pull/2181) +### Maintenance +* Remove benchmarks folder from k-NN repo [#2127](https://github.com/opensearch-project/k-NN/pull/2127) +* Fix lucene codec after lucene version bumped to 9.12. [#2195](https://github.com/opensearch-project/k-NN/pull/2195) +### Refactoring +* Does not create additional KNNVectorValues in NativeEngines990KNNVectorWriter when quantization is not needed [#2133](https://github.com/opensearch-project/k-NN/pull/2133) +* Minor refactoring and refactored some unit test [#2167](https://github.com/opensearch-project/k-NN/pull/2167) diff --git a/release-notes/opensearch-knn.release-notes-2.2.0.0.md b/release-notes/opensearch-knn.release-notes-2.2.0.0.md new file mode 100644 index 000000000..16aa9acef --- /dev/null +++ b/release-notes/opensearch-knn.release-notes-2.2.0.0.md @@ -0,0 +1,31 @@ +## Version 2.2.0.0 Release Notes + +Compatible with OpenSearch 2.2.0 + +### Features +* Lucene Based k-NN search support([#486](https://github.com/opensearch-project/k-NN/pull/486)) + +### Enhancements +* Add KNN codec that is based on Lucene92 codec([#444](https://github.com/opensearch-project/k-NN/pull/444)) +* Remove support for innerproduct for lucene engine([#488](https://github.com/opensearch-project/k-NN/pull/488)) +* Increase max dimension to 16k for nmslib and faiss([#490](https://github.com/opensearch-project/k-NN/pull/490)) + +### Bug Fixes +* Reject delete model request if model is in Training([#424](https://github.com/opensearch-project/k-NN/pull/424)) +* Change call to Lucene VectorSimilarityFunction.convertToScore([#487](https://github.com/opensearch-project/k-NN/pull/487)) + +### Infrastructure +* Add fix to flaky test in ModelDaoTests([#463](https://github.com/opensearch-project/k-NN/pull/463)) +* Read BWC Version from GitHub workflow([#476](https://github.com/opensearch-project/k-NN/pull/476)) +* Staging for version increment automation([#442](https://github.com/opensearch-project/k-NN/pull/442)) +* Remove 1.0.0 for BWC test([#492](https://github.com/opensearch-project/k-NN/pull/492)) + +### Maintenance +* Bump OpenSearch version to 2.2.0([#471](https://github.com/opensearch-project/k-NN/pull/471)) +* Bump Gradle version to 7.5([#472](https://github.com/opensearch-project/k-NN/pull/472)) +* Bump default bwc version to 1.3.4([#477](https://github.com/opensearch-project/k-NN/pull/477)) + +### Refactoring +* Move engine and lib components into separate files([#438](https://github.com/opensearch-project/k-NN/pull/438)) +* Refactor knn type and codecs([#439](https://github.com/opensearch-project/k-NN/pull/439)) +* Move mappers to separate files([#448](https://github.com/opensearch-project/k-NN/pull/448)) diff --git a/release-notes/opensearch-knn.release-notes-2.3.0.0.md b/release-notes/opensearch-knn.release-notes-2.3.0.0.md new file mode 100644 index 000000000..eda123e9c --- /dev/null +++ b/release-notes/opensearch-knn.release-notes-2.3.0.0.md @@ -0,0 +1,17 @@ +## Version 2.3.0.0 Release Notes + +Compatible with OpenSearch 2.3.0 + +### Enhancements +* Change initial size of DocIdSetBuilder ([#502](https://github.com/opensearch-project/k-NN/pull/502)) + +### Bug Fixes +* Remove overallocation in faiss query path ([#501](https://github.com/opensearch-project/k-NN/pull/501)) + +### Refactoring +* Replace terminology 'master' with 'cluster manager' ([#521](https://github.com/opensearch-project/k-NN/pull/521)) +* Nomenclature changes from Whitelist to Allowlist ([#534](https://github.com/opensearch-project/k-NN/pull/534)) + +### Maintenance +* Updated the BWC workflow to have 2.2.0 as the backward supported version in BWC tests ([#536](https://github.com/opensearch-project/k-NN/pull/536)) +* [AUTO] Increment version to 2.3.0-SNAPSHOT ([#526](https://github.com/opensearch-project/k-NN/pull/526)) diff --git a/release-notes/opensearch-knn.release-notes-2.4.0.0.md b/release-notes/opensearch-knn.release-notes-2.4.0.0.md new file mode 100644 index 000000000..5b5cbcc7e --- /dev/null +++ b/release-notes/opensearch-knn.release-notes-2.4.0.0.md @@ -0,0 +1,34 @@ +## Version 2.4.0.0 Release Notes + +Compatible with OpenSearch 2.4.0 + +### Enhancements +* Merge efficient filtering from feature branch ([#588](https://github.com/opensearch-project/k-NN/pull/588)) +* add groupId to pluginzip publication ([#578](https://github.com/opensearch-project/k-NN/pull/578)) +* Added sample perf-test configs for faiss-ivf, faiss-ivfpq, lucene-hnsw ([#555](https://github.com/opensearch-project/k-NN/pull/555)) +* Adding OSB index specification json for lucene hnsw ([#552](https://github.com/opensearch-project/k-NN/pull/552)) +* Adding k-NN engine stat ([#523](https://github.com/opensearch-project/k-NN/pull/523)) + +### Infrastructure +* Fixed failing unit test ([#610](https://github.com/opensearch-project/k-NN/pull/610)) +* Disable Code Coverage for Windows and Mac Platforms ([#603](https://github.com/opensearch-project/k-NN/pull/603)) +* Update build script to publish to maven local ([#596](https://github.com/opensearch-project/k-NN/pull/596)) +* Add Windows Build.sh Related Changes in k-NN ([#595](https://github.com/opensearch-project/k-NN/pull/595)) +* Add mac platform to CI ([#590](https://github.com/opensearch-project/k-NN/pull/590)) +* Add windows support ([#583](https://github.com/opensearch-project/k-NN/pull/583)) + +### Documentation +* Replace Forum link in k-NN plugin README.md ([#540](https://github.com/opensearch-project/k-NN/pull/540)) +* Update dev guide with instructions for mac ([#518](https://github.com/opensearch-project/k-NN/pull/518)) + +### Bug Fixes +* Fix NPE on null script context ([#560](https://github.com/opensearch-project/k-NN/pull/560)) +* Add fix to fromXContent and toXContent in ModelGraveyard ([#618](https://github.com/opensearch-project/k-NN/pull/618)) + +### Refactoring +* Refactor kNN codec related classes ([#582](https://github.com/opensearch-project/k-NN/pull/582)) +* Refactor unit tests for codec ([#562](https://github.com/opensearch-project/k-NN/pull/562)) + +### Maintenance +* Backport lucene changes ([#575](https://github.com/opensearch-project/k-NN/pull/575)) +* Increment version to 2.4.0-SNAPSHOT ([#545](https://github.com/opensearch-project/k-NN/pull/545)) \ No newline at end of file diff --git a/release-notes/opensearch-knn.release-notes-2.5.0.0.md b/release-notes/opensearch-knn.release-notes-2.5.0.0.md new file mode 100644 index 000000000..138d03149 --- /dev/null +++ b/release-notes/opensearch-knn.release-notes-2.5.0.0.md @@ -0,0 +1,34 @@ +## Version 2.5.0.0 Release Notes + +Compatible with OpenSearch 2.5.0 + +### Enhancements + +* Extend SystemIndexPlugin for k-NN model system index ([#630](https://github.com/opensearch-project/k-NN/pull/630)) +* Add Lucene specific file extensions to core HybridFS ([#721](https://github.com/opensearch-project/k-NN/pull/721)) + +### Bug Fixes + +* Add fix to fromXContent and toXContent in ModelGraveyard ([#624](https://github.com/opensearch-project/k-NN/pull/624)) +* Allow mapping service to be null for scenarios of shard recovery from translog ([#685](https://github.com/opensearch-project/k-NN/pull/685)) +* Add backward compatibility and validation checks to ModelGraveyard XContent bug fix ([#692](https://github.com/opensearch-project/k-NN/pull/692)) + +### Infrastructure + +* Add benchmark workflow for queries with filters ([#598](https://github.com/opensearch-project/k-NN/pull/598)) +* Fix failing codec unit test ([#610](https://github.com/opensearch-project/k-NN/pull/610)) +* Update bwc tests for 2.5.0 ([#661](https://github.com/opensearch-project/k-NN/pull/661)) +* Add release configs for lucene filtering ([#663](https://github.com/opensearch-project/k-NN/pull/663)) +* Update backwards compatibility versions ([#701](https://github.com/opensearch-project/k-NN/pull/701)) +* Update tests for backwards codecs ([#710](https://github.com/opensearch-project/k-NN/pull/710)) + +### Documentation + +* Update MAINTAINERS.md format ([#709](https://github.com/opensearch-project/k-NN/pull/709)) + +### Maintenance + +* Fix the codec94 version import statements ([#684](https://github.com/opensearch-project/k-NN/pull/684)) +* Add integ test for index close/open scenario ([#693](https://github.com/opensearch-project/k-NN/pull/693)) +* Make version of lucene k-nn engine match lucene current version ([#691](https://github.com/opensearch-project/k-NN/pull/691)) +* Increment version to 2.5.0-SNAPSHOT ([#632](https://github.com/opensearch-project/k-NN/pull/632)) diff --git a/release-notes/opensearch-knn.release-notes-2.6.0.0.md b/release-notes/opensearch-knn.release-notes-2.6.0.0.md new file mode 100644 index 000000000..639e6b955 --- /dev/null +++ b/release-notes/opensearch-knn.release-notes-2.6.0.0.md @@ -0,0 +1,24 @@ +## Version 2.6.0.0 Release Notes + +Compatible with OpenSearch 2.6.0 + + +### Bug Fixes + +* Remove latestSettings cache from KNNSettings ([#727](https://github.com/opensearch-project/k-NN/pull/727)) + +### Infrastructure + +* Add p99.9, p100 and num_of_segments metrics to perf-tool ([#739](https://github.com/opensearch-project/k-NN/pull/739)) +* Update bwc to 2.6.0-SNAPSHOT ([#723](https://github.com/opensearch-project/k-NN/pull/723)) +* Add Windows Support to BWC Tests ([#726](https://github.com/opensearch-project/k-NN/pull/726)) +* Add test for KNNWeight ([#759](https://github.com/opensearch-project/k-NN/pull/759)) +* Set NoMergePolicy for codec tests ([#754](https://github.com/opensearch-project/k-NN/pull/754)) + +### Maintenance + +* Replace KnnQueryVector by KnnFloatVectorQuery for Lucene knn ([#767](https://github.com/opensearch-project/k-NN/pull/767)) + +### Refactoring + +* Refactor structure of stats module ([#736](https://github.com/opensearch-project/k-NN/pull/736)) diff --git a/release-notes/opensearch-knn.release-notes-2.7.0.0.md b/release-notes/opensearch-knn.release-notes-2.7.0.0.md new file mode 100644 index 000000000..93e505e03 --- /dev/null +++ b/release-notes/opensearch-knn.release-notes-2.7.0.0.md @@ -0,0 +1,28 @@ +## Version 2.7.0.0 Release Notes + +Compatible with OpenSearch 2.7.0 + +### Enhancements + +* Support .opensearch-knn-model index as system index with security enabled ([#827](https://github.com/opensearch-project/k-NN/pull/827)) + +### Bug Fixes + +* Throw errors on model deletion failures ([#834](https://github.com/opensearch-project/k-NN/pull/834)) + +### Infrastructure + +* Add filter type to filtering release configs ([#792](https://github.com/opensearch-project/k-NN/pull/792)) +* Add CHANGELOG ([#800](https://github.com/opensearch-project/k-NN/pull/800)) +* Bump byte-buddy version from 1.12.22 to 1.14.2 ([#804](https://github.com/opensearch-project/k-NN/pull/804)) +* Add 2.6.0 to BWC Version Matrix (([#810](https://github.com/opensearch-project/k-NN/pull/810))) +* Bump numpy version from 1.22.x to 1.24.2 ([#811](https://github.com/opensearch-project/k-NN/pull/811)) +* Update BWC Version with OpenSearch Version Bump (([#813](https://github.com/opensearch-project/k-NN/pull/813))) +* Add GitHub action for secure integ tests ([#836](https://github.com/opensearch-project/k-NN/pull/836)) +* Bump byte-buddy version to 1.14.3 ([#839](https://github.com/opensearch-project/k-NN/pull/839)) +* Set gradle dependency scope for common-utils to testFixturesImplementation ([#844](https://github.com/opensearch-project/k-NN/pull/844)) +* Add client setting to ignore warning exceptions ([#850](https://github.com/opensearch-project/k-NN/pull/850)) + +### Refactoring + +* Replace Map, List, and Set in org.opensearch.common.collect with java.util references ([#816](https://github.com/opensearch-project/k-NN/pull/816)) diff --git a/release-notes/opensearch-knn.release-notes-2.8.0.0.md b/release-notes/opensearch-knn.release-notes-2.8.0.0.md new file mode 100644 index 000000000..8639aba3f --- /dev/null +++ b/release-notes/opensearch-knn.release-notes-2.8.0.0.md @@ -0,0 +1,12 @@ +## Version 2.8.0.0 Release Notes + +Compatible with OpenSearch 2.8.0 + +### Enhancements + +* Bulk allocate objects for nmslib index creation to avoid malloc fragmentation ([#773](https://github.com/opensearch-project/k-NN/pull/773)) + +### Infrastructure + +* Bump requests version from 2.26.0 to 2.31.0 ([#913](https://github.com/opensearch-project/k-NN/pull/913)) +* Disable index refresh for system indices ([#773](https://github.com/opensearch-project/k-NN/pull/915)) diff --git a/release-notes/opensearch-knn.release-notes-2.9.0.0.md b/release-notes/opensearch-knn.release-notes-2.9.0.0.md new file mode 100644 index 000000000..0ea90d037 --- /dev/null +++ b/release-notes/opensearch-knn.release-notes-2.9.0.0.md @@ -0,0 +1,7 @@ +## Version 2.9.0.0 Release Notes + +Compatible with OpenSearch 2.9.0 + +### Features +* Added support for Efficient Pre-filtering for Faiss Engine ([#936](https://github.com/opensearch-project/k-NN/pull/936)) +* Add Support for Lucene Byte Sized Vector ([#971](https://github.com/opensearch-project/k-NN/pull/971)) diff --git a/scripts/build.sh b/scripts/build.sh old mode 100644 new mode 100755 index 2ff2bc2b6..203b76c99 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -18,10 +18,11 @@ function usage() { echo -e "-p PLATFORM\t[Optional] Platform, ignored." echo -e "-a ARCHITECTURE\t[Optional] Build architecture, ignored." echo -e "-o OUTPUT\t[Optional] Output path, default is 'artifacts'." + echo -e "-j NPROC_COUNT\t[Optional] Number of CPUs to use when building JNI library. Default is 1." echo -e "-h help" } -while getopts ":h:v:q:s:o:p:a:" arg; do +while getopts ":h:v:q:s:o:p:a:j:" arg; do case $arg in h) usage @@ -45,6 +46,9 @@ while getopts ":h:v:q:s:o:p:a:" arg; do a) ARCHITECTURE=$OPTARG ;; + j) + NPROC_COUNT=$OPTARG + ;; :) echo "Error: -${OPTARG} requires an argument" usage @@ -75,7 +79,17 @@ work_dir=$PWD git submodule update --init -- jni/external/nmslib git submodule update --init -- jni/external/faiss -# Build knn libs +# Setup compile time dependency for Windows only +# As Linux version already have OpenBlas in the runner +if [ "$PLATFORM" = "windows" ]; then + openBlasVersion="0.3.21" + openBlasFile="openblas_${openBlasVersion}" + curl -SL https://github.com/xianyi/OpenBLAS/releases/download/v${openBlasVersion}/OpenBLAS-${openBlasVersion}-x64.zip -o ${openBlasFile}.zip + unzip -j -o ${openBlasFile}.zip bin/libopenblas.dll -d ./src/main/resources/windowsDependencies + rm -rf ${openBlasFile}.zip +fi + +# Setup knnlib build params for all platforms cd jni # For x64, generalize arch so library is compatible for processors without simd instruction extensions @@ -83,11 +97,9 @@ if [ "$ARCHITECTURE" = "x64" ]; then sed -i -e 's/-march=native/-march=x86-64/g' external/nmslib/similarity_search/CMakeLists.txt fi -# For arm, march=native is broken in centos 7. Manually override to lowest version of armv8. Also, disable simd in faiss -# file. This is broken on centos 7 as well. +# For arm, march=native is broken in centos 7. Manually override to lowest version of armv8. if [ "$ARCHITECTURE" = "arm64" ]; then sed -i -e 's/-march=native/-march=armv8-a/g' external/nmslib/similarity_search/CMakeLists.txt - sed -i -e 's/__aarch64__/__undefine_aarch64__/g' external/faiss/faiss/utils/distances_simd.cpp fi if [ "$JAVA_HOME" = "" ]; then @@ -95,30 +107,65 @@ if [ "$JAVA_HOME" = "" ]; then echo "SET JAVA_HOME=$JAVA_HOME" fi -cmake . -make opensearchknn_faiss opensearchknn_nmslib +# Ensure gcc version is above 4.9.0 and at least 9.0.0 for faiss 1.7.4+ / SIMD Neon support on ARM64 compilation +# https://github.com/opensearch-project/k-NN/issues/975 +# https://github.com/opensearch-project/k-NN/issues/1138 +# https://github.com/opensearch-project/opensearch-build/issues/4386 +GCC_VERSION=`gcc --version | head -n 1 | cut -d ' ' -f3` +GCC_REQUIRED_VERSION=9.0.0 +COMPARE_VERSION=`echo $GCC_REQUIRED_VERSION $GCC_VERSION | tr ' ' '\n' | sort -V | uniq | head -n 1` +if [ "$COMPARE_VERSION" != "$GCC_REQUIRED_VERSION" ]; then + echo "gcc version on this env is older than $GCC_REQUIRED_VERSION, exit 1" + exit 1 +fi +# Build k-NN lib and plugin through gradle tasks cd $work_dir -./gradlew assemble --no-daemon --refresh-dependencies -DskipTests=true -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT -Dbuild.version_qualifier=$QUALIFIER +./gradlew build --no-daemon --refresh-dependencies -x integTest -x test -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT -Dbuild.version_qualifier=$QUALIFIER -Dbuild.lib.commit_patches=false +./gradlew :buildJniLib -Davx512.enabled=false -Davx2.enabled=false -Dbuild.lib.commit_patches=false -Dnproc.count=${NPROC_COUNT:-1} + +if [ "$PLATFORM" != "windows" ] && [ "$ARCHITECTURE" = "x64" ]; then + echo "Building k-NN library after enabling AVX2" + # Skip applying patches as patches were applied already from previous :buildJniLib task + # If we apply patches again, it fails with conflict + ./gradlew :buildJniLib -Davx2.enabled=true -Davx512.enabled=false -Dbuild.lib.commit_patches=false -Dbuild.lib.apply_patches=false + + echo "Building k-NN library after enabling AVX512" + ./gradlew :buildJniLib -Davx512.enabled=true -Dbuild.lib.commit_patches=false -Dbuild.lib.apply_patches=false +fi + ./gradlew publishPluginZipPublicationToZipStagingRepository -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT -Dbuild.version_qualifier=$QUALIFIER +./gradlew publishPluginZipPublicationToMavenLocal -Dbuild.snapshot=$SNAPSHOT -Dbuild.version_qualifier=$QUALIFIER -Dopensearch.version=$VERSION # Add lib to zip -zipPath=$(find "$(pwd)" -path \*build/distributions/*.zip) +zipPath=$(find "$(pwd)/build/distributions" -path \*.zip) distributions="$(dirname "${zipPath}")" mkdir $distributions/lib -cp ./jni/release/libopensearchknn* $distributions/lib - -# Copy libomp to be packaged with the lib contents -ompPath=$(ldconfig -p | grep libgomp | cut -d ' ' -f 4) -cp $ompPath $distributions/lib +libPrefix="libopensearchknn" +if [ "$PLATFORM" = "windows" ]; then + libPrefix="opensearchknn" + cp -v ./src/main/resources/windowsDependencies/libopenblas.dll $distributions/lib + + # Have to define $MINGW_BIN either in ENV VAR or User Provided Var + cp -v "$MINGW_BIN/libgcc_s_seh-1.dll" $distributions/lib + cp -v "$MINGW_BIN/libwinpthread-1.dll" $distributions/lib + cp -v "$MINGW_BIN/libstdc++-6.dll" $distributions/lib + cp -v "$MINGW_BIN/libgomp-1.dll" $distributions/lib +else + ompPath=$(ldconfig -p | grep libgomp | cut -d ' ' -f 4) + cp -v $ompPath $distributions/lib +fi +cp -v ./jni/release/${libPrefix}* $distributions/lib +ls -l $distributions/lib +# Add lib directory to the k-NN plugin zip cd $distributions zip -ur $zipPath lib cd $work_dir echo "COPY ${distributions}/*.zip" mkdir -p $OUTPUT/plugins -cp ${distributions}/*.zip $OUTPUT/plugins +cp -v ${distributions}/*.zip $OUTPUT/plugins mkdir -p $OUTPUT/maven/org/opensearch cp -r ./build/local-staging-repo/org/opensearch/. $OUTPUT/maven/org/opensearch diff --git a/scripts/windowsScript.ps1 b/scripts/windowsScript.ps1 new file mode 100644 index 000000000..e9373223a --- /dev/null +++ b/scripts/windowsScript.ps1 @@ -0,0 +1,39 @@ +# +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# + +git submodule update --init -- jni/external/nmslib +git submodule update --init -- jni/external/faiss + +# _MSC_VER is a predefined macro which defines the version of Visual Studio Compiler +# As we are using x86_64-w64-mingw32-gcc compiler we need to replace this macro with __MINGW64__ +(Get-Content jni/external/faiss/faiss/impl/index_read.cpp).replace('_MSC_VER', '__MINGW64__') | Set-Content jni/external/faiss/faiss/impl/index_read.cpp +(Get-Content jni/external/faiss/faiss/impl/index_write.cpp).replace('_MSC_VER', '__MINGW64__') | Set-Content jni/external/faiss/faiss/impl/index_write.cpp +(Get-Content jni/external/faiss/faiss/impl/platform_macros.h).replace('_MSC_VER', '__MINGW64__') | Set-Content jni/external/faiss/faiss/impl/platform_macros.h +(Get-Content jni/external/faiss/faiss/impl/platform_macros.h).replace('#define __PRETTY_FUNCTION__ __FUNCSIG__', ' ') | Set-Content jni/external/faiss/faiss/impl/platform_macros.h +(Get-Content jni/external/faiss/faiss/utils/utils.cpp).replace('_MSC_VER', '__MINGW64__') | Set-Content jni/external/faiss/faiss/utils/utils.cpp +(Get-Content jni/external/faiss/faiss/utils/prefetch.h).replace('_MSC_VER', '__MINGW64__') | Set-Content jni/external/faiss/faiss/utils/prefetch.h +(Get-Content jni/external/faiss/faiss/invlists/InvertedListsIOHook.cpp).replace('_MSC_VER', '__MINGW64__') | Set-Content jni/external/faiss/faiss/invlists/InvertedListsIOHook.cpp +(Get-Content jni/external/faiss/faiss/AutoTune.cpp).replace('__PRETTY_FUNCTION__', 'NULL') | Set-Content jni/external/faiss/faiss/AutoTune.cpp +(Get-Content jni/external/faiss/faiss/utils/distances_simd.cpp).replace('FAISS_PRAGMA_IMPRECISE_FUNCTION_BEGIN', ' ') | Set-Content jni/external/faiss/faiss/utils/distances_simd.cpp +(Get-Content jni/external/faiss/faiss/utils/distances_simd.cpp).replace('FAISS_PRAGMA_IMPRECISE_FUNCTION_END', ' ') | Set-Content jni/external/faiss/faiss/utils/distances_simd.cpp + + + +# is a Unix header and is not available on Windows. So, adding condition to include it if not running on Windows +# Replace '#include ' with +# #ifndef __MINGW64__ +# #include +# #endif +(Get-Content jni/external/faiss/faiss/invlists/OnDiskInvertedLists.cpp).replace('#include ', "#ifndef __MINGW64__`n#include `n#endif") | Set-Content jni/external/faiss/faiss/invlists/OnDiskInvertedLists.cpp +# intrin.h function like __builtin_ctz, __builtin_clzll is not available in MINGW64. So, adding condition to include it if not running on Windows +# Replace '#include ' with +# #ifndef __MINGW64__ +# include +# and +# Closing the above #ifndef with +# #define __builtin_popcountl __popcnt64 +# #endif +(Get-Content jni/external/faiss/faiss/impl/platform_macros.h).replace('#include ', "#ifndef __MINGW64__`n#include `n") | Set-Content jni/external/faiss/faiss/impl/platform_macros.h +(Get-Content jni/external/faiss/faiss/impl/platform_macros.h).replace('#define __builtin_popcountl __popcnt64', "#define __builtin_popcountl __popcnt64`n#endif`n") | Set-Content jni/external/faiss/faiss/impl/platform_macros.h diff --git a/settings.gradle b/settings.gradle index 9056e382e..fd4369d4a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,4 +8,5 @@ rootProject.name = 'opensearch-knn' include ":qa" include ":qa:rolling-upgrade" include ":qa:restart-upgrade" +include ":micro-benchmarks" diff --git a/src/main/java/org/opensearch/knn/common/FieldInfoExtractor.java b/src/main/java/org/opensearch/knn/common/FieldInfoExtractor.java new file mode 100644 index 000000000..16bf0fb54 --- /dev/null +++ b/src/main/java/org/opensearch/knn/common/FieldInfoExtractor.java @@ -0,0 +1,120 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.common; + +import lombok.experimental.UtilityClass; +import org.apache.commons.lang.StringUtils; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.LeafReader; +import org.opensearch.common.Nullable; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.indices.ModelMetadata; +import org.opensearch.knn.indices.ModelUtil; + +import static org.opensearch.knn.common.KNNConstants.MODEL_ID; +import static org.opensearch.knn.indices.ModelUtil.getModelMetadata; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.index.engine.qframe.QuantizationConfigParser; + +import static org.opensearch.knn.common.KNNConstants.MODEL_ID; +import static org.opensearch.knn.common.KNNConstants.QFRAMEWORK_CONFIG; +import org.opensearch.knn.indices.ModelDao; + +import java.util.Locale; + +import static org.opensearch.knn.common.KNNConstants.SPACE_TYPE; + +/** + * A utility class to extract information from FieldInfo and also provides utility functions to extract fieldInfo + */ +@UtilityClass +public class FieldInfoExtractor { + + /** + * Extracts KNNEngine from FieldInfo + * @param field {@link FieldInfo} + * @return {@link KNNEngine} + */ + public static KNNEngine extractKNNEngine(final FieldInfo field) { + final ModelMetadata modelMetadata = getModelMetadata(field.attributes().get(MODEL_ID)); + if (modelMetadata != null) { + return modelMetadata.getKnnEngine(); + } + final String engineName = field.attributes().getOrDefault(KNNConstants.KNN_ENGINE, KNNEngine.DEFAULT.getName()); + return KNNEngine.getEngine(engineName); + } + + /** + * Extracts VectorDataType from FieldInfo + * @param fieldInfo {@link FieldInfo} + * @return {@link VectorDataType} + */ + public static VectorDataType extractVectorDataType(final FieldInfo fieldInfo) { + String vectorDataTypeString = fieldInfo.getAttribute(KNNConstants.VECTOR_DATA_TYPE_FIELD); + if (StringUtils.isEmpty(vectorDataTypeString)) { + final ModelMetadata modelMetadata = ModelUtil.getModelMetadata(fieldInfo.getAttribute(MODEL_ID)); + if (modelMetadata != null) { + VectorDataType vectorDataType = modelMetadata.getVectorDataType(); + vectorDataTypeString = vectorDataType == null ? null : vectorDataType.getValue(); + } + } + return StringUtils.isNotEmpty(vectorDataTypeString) ? VectorDataType.get(vectorDataTypeString) : VectorDataType.DEFAULT; + } + + /** + * Extract quantization config from fieldInfo + * + * @param fieldInfo {@link FieldInfo} + * @return {@link QuantizationConfig} + */ + public static QuantizationConfig extractQuantizationConfig(final FieldInfo fieldInfo) { + String quantizationConfigString = fieldInfo.getAttribute(QFRAMEWORK_CONFIG); + if (StringUtils.isEmpty(quantizationConfigString)) { + return QuantizationConfig.EMPTY; + } + return QuantizationConfigParser.fromCsv(quantizationConfigString); + } + + /** + * Get the space type for the given field info. + * + * @param modelDao ModelDao instance to retrieve model metadata + * @param fieldInfo FieldInfo instance to extract space type from + * @return SpaceType for the given field info + */ + public static SpaceType getSpaceType(final ModelDao modelDao, final FieldInfo fieldInfo) { + final String spaceTypeString = fieldInfo.getAttribute(SPACE_TYPE); + if (StringUtils.isNotEmpty(spaceTypeString)) { + return SpaceType.getSpace(spaceTypeString); + } + + final String modelId = fieldInfo.getAttribute(MODEL_ID); + if (StringUtils.isEmpty(modelId)) { + throw new IllegalArgumentException( + String.format(Locale.ROOT, "Unable to find the Space Type from Field Info attribute for field %s", fieldInfo.getName()) + ); + } + + ModelMetadata modelMetadata = modelDao.getMetadata(modelId); + if (modelMetadata == null) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "Unable to find the model metadata for model id %s", modelId)); + } + return modelMetadata.getSpaceType(); + } + + /** + * Get the field info for the given field name, do a null check on the fieldInfo, as this function can return null, + * if the field is not found. + * @param leafReader {@link LeafReader} + * @param fieldName {@link String} + * @return {@link FieldInfo} + */ + public static @Nullable FieldInfo getFieldInfo(final LeafReader leafReader, final String fieldName) { + return leafReader.getFieldInfos().fieldInfo(fieldName); + } +} diff --git a/src/main/java/org/opensearch/knn/common/KNNConstants.java b/src/main/java/org/opensearch/knn/common/KNNConstants.java index 46d429fcc..4869e9896 100644 --- a/src/main/java/org/opensearch/knn/common/KNNConstants.java +++ b/src/main/java/org/opensearch/knn/common/KNNConstants.java @@ -5,6 +5,10 @@ package org.opensearch.knn.common; +import org.opensearch.knn.index.VectorDataType; + +import java.util.List; + public class KNNConstants { // shared across library constants public static final String DIMENSION = "dimension"; @@ -13,12 +17,24 @@ public class KNNConstants { public static final String NAME = "name"; public static final String PARAMETERS = "parameters"; public static final String METHOD_HNSW = "hnsw"; + public static final String TYPE = "type"; + public static final String TYPE_NESTED = "nested"; + public static final String PATH = "path"; + public static final String QUERY = "query"; + public static final String KNN = "knn"; + public static final String VECTOR = "vector"; + public static final String K = "k"; + public static final String TYPE_KNN_VECTOR = "knn_vector"; + public static final String PROPERTIES = "properties"; + public static final String METHOD_PARAMETER = "method_parameters"; public static final String METHOD_PARAMETER_EF_SEARCH = "ef_search"; public static final String METHOD_PARAMETER_EF_CONSTRUCTION = "ef_construction"; public static final String METHOD_PARAMETER_M = "m"; public static final String METHOD_IVF = "ivf"; public static final String METHOD_PARAMETER_NLIST = "nlist"; public static final String METHOD_PARAMETER_SPACE_TYPE = "space_type"; // used for mapping parameter + // used for defining toplevel parameter + public static final String TOP_LEVEL_PARAMETER_SPACE_TYPE = METHOD_PARAMETER_SPACE_TYPE; public static final String COMPOUND_EXTENSION = "c"; public static final String MODEL = "model"; public static final String MODELS = "models"; @@ -35,6 +51,8 @@ public class KNNConstants { public static final String MODEL_TIMESTAMP = "timestamp"; public static final String MODEL_DESCRIPTION = "description"; public static final String MODEL_ERROR = "error"; + public static final String MODEL_NODE_ASSIGNMENT = "training_node_assignment"; + public static final String MODEL_METHOD_COMPONENT_CONTEXT = "model_definition"; public static final String PARAM_SIZE = "size"; public static final Integer SEARCH_MODEL_MIN_SIZE = 1; public static final Integer SEARCH_MODEL_MAX_SIZE = 1000; @@ -50,8 +68,30 @@ public class KNNConstants { public static final String MAX_VECTOR_COUNT_PARAMETER = "max_training_vector_count"; public static final String SEARCH_SIZE_PARAMETER = "search_size"; + public static final String QFRAMEWORK_CONFIG = "qframe_config"; + + public static final String VECTOR_DATA_TYPE_FIELD = "data_type"; + public static final String MODEL_VECTOR_DATA_TYPE_KEY = VECTOR_DATA_TYPE_FIELD; + public static final VectorDataType DEFAULT_VECTOR_DATA_TYPE_FIELD = VectorDataType.FLOAT; + public static final String MINIMAL_MODE_AND_COMPRESSION_FEATURE = "mode_and_compression_feature"; + public static final String TOP_LEVEL_SPACE_TYPE_FEATURE = "top_level_space_type_feature"; + + public static final String RADIAL_SEARCH_KEY = "radial_search"; + public static final String MODEL_VERSION = "model_version"; + public static final String QUANTIZATION_STATE_FILE_SUFFIX = "osknnqstate"; + + // Lucene specific constants + public static final String LUCENE_NAME = "lucene"; + public static final String LUCENE_SQ_CONFIDENCE_INTERVAL = "confidence_interval"; + public static final int DYNAMIC_CONFIDENCE_INTERVAL = 0; + public static final double MINIMUM_CONFIDENCE_INTERVAL = 0.9; + public static final double MAXIMUM_CONFIDENCE_INTERVAL = 1.0; + public static final String LUCENE_SQ_BITS = "bits"; + public static final int LUCENE_SQ_DEFAULT_BITS = 7; + // nmslib specific constants public static final String NMSLIB_NAME = "nmslib"; + public static final String COMMONS_NAME = "common"; public static final String SPACE_TYPE = "spaceType"; // used as field info key public static final String HNSW_ALGO_M = "M"; public static final String HNSW_ALGO_EF_CONSTRUCTION = "efConstruction"; @@ -72,6 +112,13 @@ public class KNNConstants { public static final String FAISS_IVF_DESCRIPTION = "IVF"; public static final String FAISS_FLAT_DESCRIPTION = "Flat"; public static final String FAISS_PQ_DESCRIPTION = "PQ"; + public static final String ENCODER_SQ = "sq"; + public static final String FAISS_SQ_DESCRIPTION = "SQ"; + public static final String FAISS_SQ_TYPE = "type"; + public static final String FAISS_SQ_ENCODER_FP16 = "fp16"; + public static final List FAISS_SQ_ENCODER_TYPES = List.of(FAISS_SQ_ENCODER_FP16); + public static final String FAISS_SIGNED_BYTE_SQ = "SQ8_direct_signed"; + public static final String FAISS_SQ_CLIP = "clip"; // Parameter defaults/limits public static final Integer ENCODER_PARAMETER_PQ_CODE_COUNT_DEFAULT = 1; @@ -86,8 +133,30 @@ public class KNNConstants { public static final Integer MODEL_CACHE_CAPACITY_ATROPHY_THRESHOLD_IN_MINUTES = 30; public static final Integer MODEL_CACHE_EXPIRE_AFTER_ACCESS_TIME_MINUTES = 30; + public static final Float FP16_MAX_VALUE = 65504.0f; + public static final Float FP16_MIN_VALUE = -65504.0f; + // Lib names private static final String JNI_LIBRARY_PREFIX = "opensearchknn_"; public static final String FAISS_JNI_LIBRARY_NAME = JNI_LIBRARY_PREFIX + FAISS_NAME; + public static final String FAISS_AVX2_JNI_LIBRARY_NAME = JNI_LIBRARY_PREFIX + FAISS_NAME + "_avx2"; + public static final String FAISS_AVX512_JNI_LIBRARY_NAME = JNI_LIBRARY_PREFIX + FAISS_NAME + "_avx512"; public static final String NMSLIB_JNI_LIBRARY_NAME = JNI_LIBRARY_PREFIX + NMSLIB_NAME; + public static final String COMMON_JNI_LIBRARY_NAME = JNI_LIBRARY_PREFIX + COMMONS_NAME; + + // Filtered Search Constants + // Please refer this github issue for more details for choosing this value: + // https://github.com/opensearch-project/k-NN/issues/1049#issuecomment-1694741092 + public static int MAX_DISTANCE_COMPUTATIONS = 2048000; + + // API Constants + public static final String CLEAR_CACHE = "clear_cache"; + + // Radial search constants + public static final Float DEFAULT_LUCENE_RADIAL_SEARCH_TRAVERSAL_SIMILARITY_RATIO = 0.95f; + public static final String MIN_SCORE = "min_score"; + public static final String MAX_DISTANCE = "max_distance"; + + public static final String MODE_PARAMETER = "mode"; + public static final String COMPRESSION_LEVEL_PARAMETER = "compression_level"; } diff --git a/src/main/java/org/opensearch/knn/common/KNNValidationUtil.java b/src/main/java/org/opensearch/knn/common/KNNValidationUtil.java new file mode 100644 index 000000000..59ca8993a --- /dev/null +++ b/src/main/java/org/opensearch/knn/common/KNNValidationUtil.java @@ -0,0 +1,102 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.common; + +import java.util.Locale; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.opensearch.knn.index.VectorDataType; + +import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class KNNValidationUtil { + /** + * Validate the float vector value and throw exception if it is not a number or not in the finite range. + * + * @param value float vector value + */ + public static void validateFloatVectorValue(float value) { + if (Float.isNaN(value)) { + throw new IllegalArgumentException("KNN vector values cannot be NaN"); + } + + if (Float.isInfinite(value)) { + throw new IllegalArgumentException("KNN vector values cannot be infinity"); + } + } + + /** + * Validate the float vector value in the byte range if it is a finite number, + * with no decimal values and in the byte range of [-128 to 127]. If not throw IllegalArgumentException. + * + * @param value float value in byte range + */ + public static void validateByteVectorValue(float value, final VectorDataType dataType) { + validateFloatVectorValue(value); + if (value % 1 != 0) { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "[%s] field was set as [%s] in index mapping. But, KNN vector values are floats instead of byte integers", + VECTOR_DATA_TYPE_FIELD, + dataType.getValue() + ) + + ); + } + if ((int) value < Byte.MIN_VALUE || (int) value > Byte.MAX_VALUE) { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "[%s] field was set as [%s] in index mapping. But, KNN vector values are not within in the byte range [%d, %d]", + VECTOR_DATA_TYPE_FIELD, + dataType.getValue(), + Byte.MIN_VALUE, + Byte.MAX_VALUE + ) + ); + } + } + + /** + * Validate if the given vector size matches with the dimension provided in mapping. + * + * For binary index, the dimension is 8 times larger than vector size because 8 bits is packed into single byte + * + * @param dimension dimension of vector + * @param vectorSize size of the vector + * @param dataType vector data type + */ + public static void validateVectorDimension(final int dimension, final int vectorSize, final VectorDataType dataType) { + int actualDimension = VectorDataType.BINARY == dataType ? vectorSize * Byte.SIZE : vectorSize; + if (dimension != actualDimension) { + if (VectorDataType.BINARY == dataType) { + String errorMessage = String.format( + Locale.ROOT, + "The dimension of the binary vector must be 8 times the length of the provided vector. Expected: %d, Given: %d", + dimension, + actualDimension + ); + throw new IllegalArgumentException(errorMessage); + } else { + String errorMessage = String.format( + Locale.ROOT, + "Vector dimension mismatch. Expected: %d, Given: %d", + dimension, + actualDimension + ); + throw new IllegalArgumentException(errorMessage); + } + } + } +} diff --git a/src/main/java/org/opensearch/knn/common/KNNVectorUtil.java b/src/main/java/org/opensearch/knn/common/KNNVectorUtil.java new file mode 100644 index 000000000..778cc164d --- /dev/null +++ b/src/main/java/org/opensearch/knn/common/KNNVectorUtil.java @@ -0,0 +1,78 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.common; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.opensearch.knn.index.vectorvalues.KNNVectorValues; + +import java.io.IOException; +import java.util.List; +import java.util.Objects; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class KNNVectorUtil { + /** + * Check if all the elements of a given vector are zero + * + * @param vector the vector + * @return true if yes; otherwise false + */ + public static boolean isZeroVector(byte[] vector) { + Objects.requireNonNull(vector, "vector must not be null"); + for (byte e : vector) { + if (e != 0) { + return false; + } + } + return true; + } + + /** + * Check if all the elements of a given vector are zero + * + * @param vector the vector + * @return true if yes; otherwise false + */ + public static boolean isZeroVector(float[] vector) { + Objects.requireNonNull(vector, "vector must not be null"); + for (float e : vector) { + if (e != 0f) { + return false; + } + } + return true; + } + + /** + * Converts an integer List to and array + * @param integerList + * @return null if list is null or empty, int[] otherwise + */ + public static int[] intListToArray(final List integerList) { + if (integerList == null || integerList.isEmpty()) { + return null; + } + int[] intArray = new int[integerList.size()]; + for (int i = 0; i < integerList.size(); i++) { + intArray[i] = integerList.get(i); + } + return intArray; + } + + /** + * Iterates vector values once if it is not at start of the location, + * Intended to be done to make sure dimension and bytesPerVector are available + * @param vectorValues + * @throws IOException + */ + public static void iterateVectorValuesOnce(final KNNVectorValues vectorValues) throws IOException { + if (vectorValues.docId() == -1) { + vectorValues.nextDoc(); + vectorValues.getVector(); + } + } +} diff --git a/src/main/java/org/opensearch/knn/common/exception/DeleteModelException.java b/src/main/java/org/opensearch/knn/common/exception/DeleteModelException.java new file mode 100644 index 000000000..d9590c3f8 --- /dev/null +++ b/src/main/java/org/opensearch/knn/common/exception/DeleteModelException.java @@ -0,0 +1,32 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.common.exception; + +import org.opensearch.OpenSearchException; +import org.opensearch.core.common.logging.LoggerMessageFormat; +import org.opensearch.core.rest.RestStatus; + +/** + * Exception thrown when a model is deleted while it is in the training state or in use by an index. The RestStatus associated with this + * exception should be a {@link RestStatus#CONFLICT} because the request cannot be deleted due to the model being in + * the training state or in use by an index. + */ +public class DeleteModelException extends OpenSearchException { + /** + * Constructor + * + * @param msg detailed exception message + * @param args arguments of the message + */ + public DeleteModelException(String msg, Object... args) { + super(LoggerMessageFormat.format(msg, args)); + } + + @Override + public RestStatus status() { + return RestStatus.CONFLICT; + } +} diff --git a/src/main/java/org/opensearch/knn/common/featureflags/KNNFeatureFlags.java b/src/main/java/org/opensearch/knn/common/featureflags/KNNFeatureFlags.java new file mode 100644 index 000000000..bab5b97bb --- /dev/null +++ b/src/main/java/org/opensearch/knn/common/featureflags/KNNFeatureFlags.java @@ -0,0 +1,49 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + * + */ + +package org.opensearch.knn.common.featureflags; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import lombok.experimental.UtilityClass; +import org.opensearch.common.Booleans; +import org.opensearch.common.settings.Setting; +import org.opensearch.knn.index.KNNSettings; + +import java.util.List; + +import static org.opensearch.common.settings.Setting.Property.Dynamic; +import static org.opensearch.common.settings.Setting.Property.NodeScope; + +/** + * Class to manage KNN feature flags + */ +@UtilityClass +public class KNNFeatureFlags { + + // Feature flags + private static final String KNN_FORCE_EVICT_CACHE_ENABLED = "knn.feature.cache.force_evict.enabled"; + + @VisibleForTesting + public static final Setting KNN_FORCE_EVICT_CACHE_ENABLED_SETTING = Setting.boolSetting( + KNN_FORCE_EVICT_CACHE_ENABLED, + false, + NodeScope, + Dynamic + ); + + public static List> getFeatureFlags() { + return ImmutableList.of(KNN_FORCE_EVICT_CACHE_ENABLED_SETTING); + } + + /** + * Checks if force evict for cache is enabled by executing a check against cluster settings + * @return true if force evict setting is set to true + */ + public static boolean isForceEvictCacheEnabled() { + return Booleans.parseBoolean(KNNSettings.state().getSettingValue(KNN_FORCE_EVICT_CACHE_ENABLED).toString(), false); + } +} diff --git a/src/main/java/org/opensearch/knn/index/IndexUtil.java b/src/main/java/org/opensearch/knn/index/IndexUtil.java deleted file mode 100644 index f0d9ee944..000000000 --- a/src/main/java/org/opensearch/knn/index/IndexUtil.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.knn.index; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; -import org.opensearch.cluster.metadata.IndexMetadata; -import org.opensearch.cluster.metadata.MappingMetadata; -import org.opensearch.common.ValidationException; -import org.opensearch.knn.common.KNNConstants; -import org.opensearch.knn.index.util.KNNEngine; -import org.opensearch.knn.indices.ModelDao; -import org.opensearch.knn.indices.ModelMetadata; - -import java.io.File; -import java.util.Collections; -import java.util.Map; - -import static org.opensearch.knn.common.KNNConstants.BYTES_PER_KILOBYTES; -import static org.opensearch.knn.common.KNNConstants.HNSW_ALGO_EF_SEARCH; -import static org.opensearch.knn.common.KNNConstants.SPACE_TYPE; - -public class IndexUtil { - - /** - * Determines the size of a file on disk in kilobytes - * - * @param filePath path to the file - * @return file size in kilobytes - */ - public static int getFileSizeInKB(String filePath) { - if (filePath == null || filePath.isEmpty()) { - return 0; - } - File file = new File(filePath); - if (!file.exists() || !file.isFile()) { - return 0; - } - - return Math.toIntExact((file.length() / BYTES_PER_KILOBYTES) + 1L); // Add one so that integer division rounds up - } - - /** - * Validate that a field is a k-NN vector field and has the expected dimension - * - * @param indexMetadata metadata for index to validate - * @param field field name to validate - * @param expectedDimension expected dimension of the field. If this value is negative, dimension will not be - * checked - * @param modelDao used to look up dimension if field uses a model for initialization. Can be null if - * expectedDimension is negative - * @return ValidationException exception produced by field validation - */ - @SuppressWarnings("unchecked") - public static ValidationException validateKnnField( - IndexMetadata indexMetadata, - String field, - int expectedDimension, - ModelDao modelDao - ) { - // Index metadata should not be null - if (indexMetadata == null) { - throw new IllegalArgumentException("IndexMetadata should not be null"); - } - - ValidationException exception = new ValidationException(); - - // Check the mapping - MappingMetadata mappingMetadata = indexMetadata.mapping(); - if (mappingMetadata == null) { - exception.addValidationError("Invalid index. Index does not contain a mapping"); - return exception; - } - - // The mapping output *should* look like this: - // "{properties={field={type=knn_vector, dimension=8}}}" - Map properties = (Map) mappingMetadata.getSourceAsMap().get("properties"); - - if (properties == null) { - exception.addValidationError("Properties in map does not exists. This is unexpected"); - return exception; - } - - Object fieldMapping = properties.get(field); - - // Check field existence - if (fieldMapping == null) { - exception.addValidationError(String.format("Field \"%s\" does not exist.", field)); - return exception; - } - - // Check if field is a map. If not, that is a problem - if (!(fieldMapping instanceof Map)) { - exception.addValidationError(String.format("Field info for \"%s\" is not a map.", field)); - return exception; - } - - Map fieldMap = (Map) fieldMapping; - - // Check fields type is knn_vector - Object type = fieldMap.get("type"); - - if (!(type instanceof String) || !KNNVectorFieldMapper.CONTENT_TYPE.equals(type)) { - exception.addValidationError(String.format("Field \"%s\" is not of type %s.", field, KNNVectorFieldMapper.CONTENT_TYPE)); - return exception; - } - - // Return if dimension does not need to be checked - if (expectedDimension < 0) { - return null; - } - - // Check that the dimension of the method passed in matches that of the model - Object dimension = fieldMap.get(KNNConstants.DIMENSION); - - // If dimension is null, the training index/field could use a model. In this case, we need to get the model id - // for the index and then fetch its dimension from the models metadata - if (dimension == null) { - - String modelId = (String) fieldMap.get(KNNConstants.MODEL_ID); - - if (modelId == null) { - exception.addValidationError(String.format("Field \"%s\" does not have a dimension set.", field)); - return exception; - } - - if (modelDao == null) { - throw new IllegalArgumentException(String.format("Field \"%s\" uses model. modelDao cannot be null.", field)); - } - - ModelMetadata modelMetadata = modelDao.getMetadata(modelId); - if (modelMetadata == null) { - exception.addValidationError(String.format("Model \"%s\" for field \"%s\" does not exist.", modelId, field)); - return exception; - } - - dimension = modelMetadata.getDimension(); - if ((Integer) dimension != expectedDimension) { - exception.addValidationError( - String.format( - "Field \"%s\" has dimension %d, which is different from " + "dimension specified in the training request: %d", - field, - dimension, - expectedDimension - ) - ); - return exception; - } - - return null; - } - - // If the dimension was found in training fields mapping, check that it equals the models proposed dimension. - if ((Integer) dimension != expectedDimension) { - exception.addValidationError( - String.format( - "Field \"%s\" has dimension %d, which is different from " + "dimension specified in the training request: %d", - field, - dimension, - expectedDimension - ) - ); - return exception; - } - - return null; - } - - /** - * Gets the load time parameters for a given engine. - * - * @param spaceType Space for this particular segment - * @param knnEngine Engine used for the native library indices being loaded in - * @param indexName Name of OpenSearch index that the segment files belong to - * @return load parameters that will be passed to the JNI. - */ - public static Map getParametersAtLoading(SpaceType spaceType, KNNEngine knnEngine, String indexName) { - Map loadParameters = Maps.newHashMap(ImmutableMap.of(SPACE_TYPE, spaceType.getValue())); - - // For nmslib, we need to add the dynamic ef_search parameter that needs to be passed in when the - // hnsw graphs are loaded into memory - if (KNNEngine.NMSLIB.equals(knnEngine)) { - loadParameters.put(HNSW_ALGO_EF_SEARCH, KNNSettings.getEfSearchParam(indexName)); - } - - return Collections.unmodifiableMap(loadParameters); - } -} diff --git a/src/main/java/org/opensearch/knn/index/KNNCircuitBreaker.java b/src/main/java/org/opensearch/knn/index/KNNCircuitBreaker.java index 5375beddd..f5d49e1a3 100644 --- a/src/main/java/org/opensearch/knn/index/KNNCircuitBreaker.java +++ b/src/main/java/org/opensearch/knn/index/KNNCircuitBreaker.java @@ -6,7 +6,6 @@ package org.opensearch.knn.index; import org.opensearch.knn.index.memory.NativeMemoryCacheManager; -import org.opensearch.knn.plugin.stats.KNNStatsConfig; import org.opensearch.knn.plugin.stats.StatNames; import org.opensearch.knn.plugin.transport.KNNStatsAction; import org.opensearch.knn.plugin.transport.KNNStatsNodeResponse; @@ -72,8 +71,8 @@ public void initialize(ThreadPool threadPool, ClusterService clusterService, Cli } // Leader node untriggers CB if all nodes have not reached their max capacity - if (KNNSettings.isCircuitBreakerTriggered() && clusterService.state().nodes().isLocalNodeElectedMaster()) { - KNNStatsRequest knnStatsRequest = new KNNStatsRequest(KNNStatsConfig.KNN_STATS.keySet()); + if (KNNSettings.isCircuitBreakerTriggered() && clusterService.state().nodes().isLocalNodeElectedClusterManager()) { + KNNStatsRequest knnStatsRequest = new KNNStatsRequest(); knnStatsRequest.addStat(StatNames.CACHE_CAPACITY_REACHED.getName()); knnStatsRequest.timeout(new TimeValue(1000 * 10)); // 10 second timeout diff --git a/src/main/java/org/opensearch/knn/index/KNNIndexShard.java b/src/main/java/org/opensearch/knn/index/KNNIndexShard.java index c00eda255..ac4c055b0 100644 --- a/src/main/java/org/opensearch/knn/index/KNNIndexShard.java +++ b/src/main/java/org/opensearch/knn/index/KNNIndexShard.java @@ -5,43 +5,52 @@ package org.opensearch.knn.index; +import com.google.common.annotations.VisibleForTesting; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.log4j.Log4j2; import org.apache.lucene.index.FieldInfo; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.lucene.index.FilterLeafReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.index.SegmentCommitInfo; +import org.apache.lucene.index.SegmentInfo; import org.apache.lucene.index.SegmentReader; -import org.apache.lucene.store.FSDirectory; -import org.apache.lucene.store.FilterDirectory; +import org.apache.lucene.store.Directory; +import org.opensearch.common.lucene.Lucene; import org.opensearch.index.engine.Engine; import org.opensearch.index.shard.IndexShard; +import org.opensearch.knn.common.FieldInfoExtractor; +import org.opensearch.knn.index.codec.util.NativeMemoryCacheKeyHelper; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.index.mapper.KNNVectorFieldMapper; +import org.opensearch.knn.index.memory.NativeMemoryAllocation; import org.opensearch.knn.index.memory.NativeMemoryCacheManager; import org.opensearch.knn.index.memory.NativeMemoryEntryContext; import org.opensearch.knn.index.memory.NativeMemoryLoadStrategy; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.engine.KNNEngine; import java.io.IOException; -import java.nio.file.Path; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; +import static org.opensearch.knn.common.KNNConstants.MODEL_ID; import static org.opensearch.knn.common.KNNConstants.SPACE_TYPE; -import static org.opensearch.knn.index.IndexUtil.getParametersAtLoading; +import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; +import static org.opensearch.knn.index.util.IndexUtil.getParametersAtLoading; import static org.opensearch.knn.index.codec.util.KNNCodecUtil.buildEngineFilePrefix; import static org.opensearch.knn.index.codec.util.KNNCodecUtil.buildEngineFileSuffix; /** * KNNIndexShard wraps IndexShard and adds methods to perform k-NN related operations against the shard */ +@Log4j2 public class KNNIndexShard { private IndexShard indexShard; private NativeMemoryCacheManager nativeMemoryCacheManager; - - private static Logger logger = LogManager.getLogger(KNNIndexShard.class); + private static final String INDEX_SHARD_CLEAR_CACHE_SEARCHER = "knn-clear-cache"; /** * Constructor to generate KNNIndexShard. We do not perform validation that the index the shard is from @@ -79,16 +88,28 @@ public String getIndexName() { * @throws IOException Thrown when getting the HNSW Paths to be loaded in */ public void warmup() throws IOException { - logger.info("[KNN] Warming up index: " + getIndexName()); + log.info("[KNN] Warming up index: [{}]", getIndexName()); + final Directory directory = indexShard.store().directory(); try (Engine.Searcher searcher = indexShard.acquireSearcher("knn-warmup")) { - getAllEnginePaths(searcher.getIndexReader()).forEach((key, value) -> { + getAllEngineFileContexts(searcher.getIndexReader()).forEach((engineFileContext) -> { try { + final String cacheKey = NativeMemoryCacheKeyHelper.constructCacheKey( + engineFileContext.vectorFileName, + engineFileContext.segmentInfo + ); nativeMemoryCacheManager.get( new NativeMemoryEntryContext.IndexEntryContext( - key, + directory, + cacheKey, NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance(), - getParametersAtLoading(value, KNNEngine.getEngineNameFromPath(key), getIndexName()), - getIndexName() + getParametersAtLoading( + engineFileContext.getSpaceType(), + KNNEngine.getEngineNameFromPath(engineFileContext.getVectorFileName()), + getIndexName(), + engineFileContext.getVectorDataType() + ), + getIndexName(), + engineFileContext.getModelId() ), true ); @@ -99,27 +120,59 @@ public void warmup() throws IOException { } } + /** + * Removes all the k-NN segments for this shard from the cache. + * Adding write lock onto the NativeMemoryAllocation of the index that needs to be evicted from cache. + * Write lock will be unlocked after the index is evicted. This locking mechanism is used to avoid + * conflicts with queries fired on this index when the index is being evicted from cache. + */ + public void clearCache() { + String indexName = getIndexName(); + Optional indexAllocationOptional; + NativeMemoryAllocation indexAllocation; + indexAllocationOptional = nativeMemoryCacheManager.getIndexMemoryAllocation(indexName); + if (indexAllocationOptional.isPresent()) { + indexAllocation = indexAllocationOptional.get(); + indexAllocation.writeLock(); + log.info("[KNN] Evicting index from cache: [{}]", indexName); + try (Engine.Searcher searcher = indexShard.acquireSearcher(INDEX_SHARD_CLEAR_CACHE_SEARCHER)) { + getAllEngineFileContexts(searcher.getIndexReader()).forEach((engineFileContext) -> { + final String cacheKey = NativeMemoryCacheKeyHelper.constructCacheKey( + engineFileContext.vectorFileName, + engineFileContext.segmentInfo + ); + nativeMemoryCacheManager.invalidate(cacheKey); + }); + } catch (IOException ex) { + log.error("[KNN] Failed to evict index from cache: [{}]", indexName, ex); + throw new RuntimeException(ex); + } finally { + indexAllocation.writeUnlock(); + } + } + } + /** * For the given shard, get all of its engine paths * - * @param indexReader IndexReader to read the file paths for the shard - * @return List of engine file Paths + * @param indexReader IndexReader to read the information for each segment in the shard + * @return List of engine contexts * @throws IOException Thrown when the SegmentReader is attempting to read the segments files */ - public Map getAllEnginePaths(IndexReader indexReader) throws IOException { - Map engineFiles = new HashMap<>(); - for (KNNEngine knnEngine : KNNEngine.values()) { - engineFiles.putAll(getEnginePaths(indexReader, knnEngine)); + @VisibleForTesting + List getAllEngineFileContexts(IndexReader indexReader) throws IOException { + List engineFiles = new ArrayList<>(); + for (KNNEngine knnEngine : KNNEngine.getEnginesThatCreateCustomSegmentFiles()) { + engineFiles.addAll(getEngineFileContexts(indexReader, knnEngine)); } return engineFiles; } - private Map getEnginePaths(IndexReader indexReader, KNNEngine knnEngine) throws IOException { - Map engineFiles = new HashMap<>(); + List getEngineFileContexts(IndexReader indexReader, KNNEngine knnEngine) throws IOException { + List engineFiles = new ArrayList<>(); for (LeafReaderContext leafReaderContext : indexReader.leaves()) { - SegmentReader reader = (SegmentReader) FilterLeafReader.unwrap(leafReaderContext.reader()); - Path shardPath = ((FSDirectory) FilterDirectory.unwrap(reader.directory())).getDirectory(); + SegmentReader reader = Lucene.segmentReader(leafReaderContext.reader()); String fileExtension = reader.getSegmentInfo().info.getUseCompoundFile() ? knnEngine.getCompoundExtension() : knnEngine.getExtension(); @@ -130,15 +183,19 @@ private Map getEnginePaths(IndexReader indexReader, KNNEngine // was L2. So, if Space Type is not present, just fall back to L2 String spaceTypeName = fieldInfo.attributes().getOrDefault(SPACE_TYPE, SpaceType.L2.getValue()); SpaceType spaceType = SpaceType.getSpace(spaceTypeName); - - engineFiles.putAll( - getEnginePaths( - reader.getSegmentInfo().files(), - reader.getSegmentInfo().info.name, + String modelId = fieldInfo.attributes().getOrDefault(MODEL_ID, null); + engineFiles.addAll( + getEngineFileContexts( + reader.getSegmentInfo(), fieldInfo.name, fileExtension, - shardPath, - spaceType + spaceType, + modelId, + FieldInfoExtractor.extractQuantizationConfig(fieldInfo) == QuantizationConfig.EMPTY + ? VectorDataType.get( + fieldInfo.attributes().getOrDefault(VECTOR_DATA_TYPE_FIELD, VectorDataType.FLOAT.getValue()) + ) + : VectorDataType.BINARY ) ); } @@ -147,20 +204,35 @@ private Map getEnginePaths(IndexReader indexReader, KNNEngine return engineFiles; } - protected Map getEnginePaths( - Collection files, - String segmentName, + @VisibleForTesting + List getEngineFileContexts( + SegmentCommitInfo segmentCommitInfo, String fieldName, String fileExtension, - Path shardPath, - SpaceType spaceType - ) { - String prefix = buildEngineFilePrefix(segmentName); - String suffix = buildEngineFileSuffix(fieldName, fileExtension); - return files.stream() + SpaceType spaceType, + String modelId, + VectorDataType vectorDataType + ) throws IOException { + // Ex: 0_ + final String prefix = buildEngineFilePrefix(segmentCommitInfo.info.name); + // Ex: _my_field.faiss + final String suffix = buildEngineFileSuffix(fieldName, fileExtension); + return segmentCommitInfo.files() + .stream() .filter(fileName -> fileName.startsWith(prefix)) .filter(fileName -> fileName.endsWith(suffix)) - .map(fileName -> shardPath.resolve(fileName).toString()) - .collect(Collectors.toMap(fileName -> fileName, fileName -> spaceType)); + .map(vectorFileName -> new EngineFileContext(spaceType, modelId, vectorFileName, vectorDataType, segmentCommitInfo.info)) + .collect(Collectors.toList()); + } + + @AllArgsConstructor + @Getter + @VisibleForTesting + static class EngineFileContext { + private final SpaceType spaceType; + private final String modelId; + private final String vectorFileName; + private final VectorDataType vectorDataType; + private final SegmentInfo segmentInfo; } } diff --git a/src/main/java/org/opensearch/knn/index/KNNMethod.java b/src/main/java/org/opensearch/knn/index/KNNMethod.java deleted file mode 100644 index da2d9c455..000000000 --- a/src/main/java/org/opensearch/knn/index/KNNMethod.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.knn.index; - -import org.opensearch.common.ValidationException; -import org.opensearch.knn.common.KNNConstants; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * KNNMethod is used to define the structure of a method supported by a particular k-NN library. It is used to validate - * the KNNMethodContext passed in by the user. It is also used to provide superficial string translations. - */ -public class KNNMethod { - - private final MethodComponent methodComponent; - private final Set spaces; - - /** - * KNNMethod Constructor - * - * @param methodComponent top level method component that is compatible with the underlying library - * @param spaces set of valid space types that the method supports - */ - public KNNMethod(MethodComponent methodComponent, Set spaces) { - this.methodComponent = methodComponent; - this.spaces = spaces; - } - - /** - * getMainMethodComponent - * - * @return mainMethodComponent - */ - public MethodComponent getMethodComponent() { - return methodComponent; - } - - /** - * Determines whether the provided space is supported for this method - * - * @param space to be checked - * @return true if the space is supported; false otherwise - */ - public boolean containsSpace(SpaceType space) { - return spaces.contains(space); - } - - /** - * Get all valid spaces for this method - * - * @return spaces that can be used with this method - */ - public Set getSpaces() { - return spaces; - } - - /** - * Validate that the configured KNNMethodContext is valid for this method - * - * @param knnMethodContext to be validated - * @return ValidationException produced by validation errors; null if no validations errors. - */ - public ValidationException validate(KNNMethodContext knnMethodContext) { - List errorMessages = new ArrayList<>(); - if (!containsSpace(knnMethodContext.getSpaceType())) { - errorMessages.add( - String.format( - "\"%s\" configuration does not support space type: " + "\"%s\".", - this.methodComponent.getName(), - knnMethodContext.getSpaceType().getValue() - ) - ); - } - - ValidationException methodValidation = methodComponent.validate(knnMethodContext.getMethodComponent()); - if (methodValidation != null) { - errorMessages.addAll(methodValidation.validationErrors()); - } - - if (errorMessages.isEmpty()) { - return null; - } - - ValidationException validationException = new ValidationException(); - validationException.addValidationErrors(errorMessages); - return validationException; - } - - /** - * returns whether training is required or not - * - * @param knnMethodContext context to check if training is required on - * @return true if training is required; false otherwise - */ - public boolean isTrainingRequired(KNNMethodContext knnMethodContext) { - return methodComponent.isTrainingRequired(knnMethodContext.getMethodComponent()); - } - - /** - * Returns the estimated overhead of the method in KB - * - * @param knnMethodContext context to estimate overhead - * @param dimension dimension to make estimate with - * @return estimate overhead in KB - */ - public int estimateOverheadInKB(KNNMethodContext knnMethodContext, int dimension) { - return methodComponent.estimateOverheadInKB(knnMethodContext.getMethodComponent(), dimension); - } - - /** - * Parse knnMethodContext into a map that the library can use to configure the index - * - * @param knnMethodContext from which to generate map - * @return KNNMethod as a map - */ - public Map getAsMap(KNNMethodContext knnMethodContext) { - Map parameterMap = new HashMap<>(methodComponent.getAsMap(knnMethodContext.getMethodComponent())); - parameterMap.put(KNNConstants.SPACE_TYPE, knnMethodContext.getSpaceType().getValue()); - return parameterMap; - } - - /** - * Builder for KNNMethod - */ - public static class Builder { - - private MethodComponent methodComponent; - private Set spaces; - - /** - * Method to get a Builder instance - * - * @param methodComponent top level method component for the method - * @return Builder instance - */ - public static Builder builder(MethodComponent methodComponent) { - return new Builder(methodComponent); - } - - private Builder(MethodComponent methodComponent) { - this.methodComponent = methodComponent; - this.spaces = new HashSet<>(); - } - - /** - * Add spaces to KNNMethod - * - * @param spaceTypes to be added - * @return Builder - */ - public Builder addSpaces(SpaceType... spaceTypes) { - spaces.addAll(Arrays.asList(spaceTypes)); - return this; - } - - /** - * Build KNNMethod from builder - * - * @return KNNMethod initialized from builder - */ - public KNNMethod build() { - return new KNNMethod(methodComponent, spaces); - } - } -} diff --git a/src/main/java/org/opensearch/knn/index/KNNQuery.java b/src/main/java/org/opensearch/knn/index/KNNQuery.java deleted file mode 100644 index 631b36d7a..000000000 --- a/src/main/java/org/opensearch/knn/index/KNNQuery.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.opensearch.knn.index; - -import org.apache.lucene.search.IndexSearcher; -import org.apache.lucene.search.Query; -import org.apache.lucene.search.QueryVisitor; -import org.apache.lucene.search.ScoreMode; -import org.apache.lucene.search.Weight; - -import java.io.IOException; - -/** - * Class for representing the KNN query - */ -public class KNNQuery extends Query { - - private final String field; - private final float[] queryVector; - private final int k; - private final String indexName; - - public KNNQuery(String field, float[] queryVector, int k, String indexName) { - this.field = field; - this.queryVector = queryVector; - this.k = k; - this.indexName = indexName; - } - - public String getField() { - return this.field; - } - - public float[] getQueryVector() { - return this.queryVector; - } - - public int getK() { - return this.k; - } - - public String getIndexName() { - return this.indexName; - } - - /** - * Constructs Weight implementation for this query - * - * @param searcher searcher for given segment - * @param scoreMode How the produced scorers will be consumed. - * @param boost The boost that is propagated by the parent queries. - * @return Weight For calculating scores - */ - @Override - public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) throws IOException { - if (!KNNSettings.isKNNPluginEnabled()) { - throw new IllegalStateException("KNN plugin is disabled. To enable update knn.plugin.enabled to true"); - } - return new KNNWeight(this, boost); - } - - @Override - public void visit(QueryVisitor visitor) { - - } - - @Override - public String toString(String field) { - return field; - } - - @Override - public int hashCode() { - return field.hashCode() ^ queryVector.hashCode() ^ k; - } - - @Override - public boolean equals(Object other) { - return sameClassAs(other) && equalsTo(getClass().cast(other)); - } - - private boolean equalsTo(KNNQuery other) { - return this.field.equals(other.getField()) && this.queryVector.equals(other.getQueryVector()) && this.k == other.getK(); - } -}; diff --git a/src/main/java/org/opensearch/knn/index/KNNQueryBuilder.java b/src/main/java/org/opensearch/knn/index/KNNQueryBuilder.java deleted file mode 100644 index d573b9194..000000000 --- a/src/main/java/org/opensearch/knn/index/KNNQueryBuilder.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.opensearch.knn.index; - -import org.opensearch.index.mapper.NumberFieldMapper; -import org.opensearch.knn.indices.ModelDao; -import org.opensearch.knn.indices.ModelMetadata; -import org.opensearch.knn.plugin.stats.KNNCounter; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.lucene.search.Query; -import org.opensearch.common.ParseField; -import org.opensearch.common.ParsingException; -import org.opensearch.common.Strings; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; -import org.opensearch.common.xcontent.XContentBuilder; -import org.opensearch.common.xcontent.XContentParser; -import org.opensearch.index.mapper.MappedFieldType; -import org.opensearch.index.query.AbstractQueryBuilder; -import org.opensearch.index.query.QueryShardContext; - -import java.io.IOException; -import java.util.List; -import java.util.Objects; - -/** - * Helper class to build the KNN query - */ -public class KNNQueryBuilder extends AbstractQueryBuilder { - private static Logger logger = LogManager.getLogger(KNNQueryBuilder.class); - private static ModelDao modelDao; - - public static final ParseField VECTOR_FIELD = new ParseField("vector"); - public static final ParseField K_FIELD = new ParseField("k"); - public static int K_MAX = 10000; - /** - * The name for the knn query - */ - public static final String NAME = "knn"; - /** - * The default mode terms are combined in a match query - */ - private final String fieldName; - private final float[] vector; - private int k = 0; - - /** - * Constructs a new knn query - * - * @param fieldName Name of the filed - * @param vector Array of floating points - * @param k K nearest neighbours for the given vector - */ - public KNNQueryBuilder(String fieldName, float[] vector, int k) { - if (Strings.isNullOrEmpty(fieldName)) { - throw new IllegalArgumentException("[" + NAME + "] requires fieldName"); - } - if (vector == null) { - throw new IllegalArgumentException("[" + NAME + "] requires query vector"); - } - if (vector.length == 0) { - throw new IllegalArgumentException("[" + NAME + "] query vector is empty"); - } - if (k <= 0) { - throw new IllegalArgumentException("[" + NAME + "] requires k > 0"); - } - if (k > K_MAX) { - throw new IllegalArgumentException("[" + NAME + "] requires k <= " + K_MAX); - } - - this.fieldName = fieldName; - this.vector = vector; - this.k = k; - } - - public static void initialize(ModelDao modelDao) { - KNNQueryBuilder.modelDao = modelDao; - } - - private static float[] ObjectsToFloats(List objs) { - float[] vec = new float[objs.size()]; - for (int i = 0; i < objs.size(); i++) { - vec[i] = ((Number) objs.get(i)).floatValue(); - } - return vec; - } - - /** - * @param in Reads from stream - * @throws IOException Throws IO Exception - */ - public KNNQueryBuilder(StreamInput in) throws IOException { - super(in); - try { - fieldName = in.readString(); - vector = in.readFloatArray(); - k = in.readInt(); - } catch (IOException ex) { - throw new RuntimeException("[KNN] Unable to create KNNQueryBuilder: " + ex); - } - } - - public static KNNQueryBuilder fromXContent(XContentParser parser) throws IOException { - String fieldName = null; - List vector = null; - float boost = AbstractQueryBuilder.DEFAULT_BOOST; - int k = 0; - String queryName = null; - String currentFieldName = null; - XContentParser.Token token; - KNNCounter.KNN_QUERY_REQUESTS.increment(); - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - currentFieldName = parser.currentName(); - } else if (token == XContentParser.Token.START_OBJECT) { - throwParsingExceptionOnMultipleFields(NAME, parser.getTokenLocation(), fieldName, currentFieldName); - fieldName = currentFieldName; - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - currentFieldName = parser.currentName(); - } else if (token.isValue() || token == XContentParser.Token.START_ARRAY) { - if (VECTOR_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - vector = parser.list(); - } else if (AbstractQueryBuilder.BOOST_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - boost = parser.floatValue(); - } else if (K_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - k = (Integer) NumberFieldMapper.NumberType.INTEGER.parse(parser.objectBytes(), false); - } else if (AbstractQueryBuilder.NAME_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - queryName = parser.text(); - } else { - throw new ParsingException( - parser.getTokenLocation(), - "[" + NAME + "] query does not support [" + currentFieldName + "]" - ); - } - } else { - throw new ParsingException( - parser.getTokenLocation(), - "[" + NAME + "] unknown token [" + token + "] after [" + currentFieldName + "]" - ); - } - } - } else { - throwParsingExceptionOnMultipleFields(NAME, parser.getTokenLocation(), fieldName, parser.currentName()); - fieldName = parser.currentName(); - vector = parser.list(); - } - } - - KNNQueryBuilder knnQuery = new KNNQueryBuilder(fieldName, ObjectsToFloats(vector), k); - knnQuery.queryName(queryName); - knnQuery.boost(boost); - return knnQuery; - } - - @Override - protected void doWriteTo(StreamOutput out) throws IOException { - out.writeString(fieldName); - out.writeFloatArray(vector); - out.writeInt(k); - } - - /** - * @return The field name used in this query - */ - public String fieldName() { - return this.fieldName; - } - - /** - * @return Returns the vector used in this query. - */ - public Object vector() { - return this.vector; - } - - public int getK() { - return this.k; - } - - @Override - public void doXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(NAME); - builder.startObject(fieldName); - - builder.field(VECTOR_FIELD.getPreferredName(), vector); - builder.field(K_FIELD.getPreferredName(), k); - printBoostAndQueryName(builder); - builder.endObject(); - builder.endObject(); - } - - @Override - protected Query doToQuery(QueryShardContext context) throws IOException { - - MappedFieldType mappedFieldType = context.fieldMapper(this.fieldName); - - if (!(mappedFieldType instanceof KNNVectorFieldMapper.KNNVectorFieldType)) { - throw new IllegalArgumentException("Field '" + this.fieldName + "' is not knn_vector type."); - } - - int dimension = ((KNNVectorFieldMapper.KNNVectorFieldType) mappedFieldType).getDimension(); - - // If the dimension is not set, then the only valid route forward is if the field uses a model - if (dimension == -1) { - String modelId = ((KNNVectorFieldMapper.KNNVectorFieldType) mappedFieldType).getModelId(); - - if (modelId == null) { - throw new IllegalArgumentException("Field '" + this.fieldName + "' does not have dimension set."); - } - - ModelMetadata modelMetadata = modelDao.getMetadata(modelId); - - if (modelMetadata == null) { - throw new IllegalArgumentException("Model ID \"" + modelId + "\" does not exist."); - } - dimension = modelMetadata.getDimension(); - } - - if (dimension != vector.length) { - throw new IllegalArgumentException( - "Query vector has invalid dimension: " + vector.length + ". Dimension should be: " + dimension - ); - } - - return new KNNQuery(this.fieldName, vector, k, context.index().getName()); - } - - @Override - protected boolean doEquals(KNNQueryBuilder other) { - return Objects.equals(fieldName, other.fieldName) && Objects.equals(vector, other.vector) && Objects.equals(k, other.k); - } - - @Override - protected int doHashCode() { - return Objects.hash(fieldName, vector, k); - } - - @Override - public String getWriteableName() { - return NAME; - } -} diff --git a/src/main/java/org/opensearch/knn/index/KNNSettings.java b/src/main/java/org/opensearch/knn/index/KNNSettings.java index e0e70310f..b81a54124 100644 --- a/src/main/java/org/opensearch/knn/index/KNNSettings.java +++ b/src/main/java/org/opensearch/knn/index/KNNSettings.java @@ -5,21 +5,27 @@ package org.opensearch.knn.index; +import lombok.extern.log4j.Log4j2; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.OpenSearchParseException; -import org.opensearch.action.ActionListener; import org.opensearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest; import org.opensearch.action.admin.cluster.settings.ClusterUpdateSettingsResponse; import org.opensearch.client.Client; +import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.Booleans; import org.opensearch.common.settings.Setting; import org.opensearch.common.settings.Settings; -import org.opensearch.common.unit.ByteSizeUnit; -import org.opensearch.common.unit.ByteSizeValue; import org.opensearch.common.unit.TimeValue; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.common.unit.ByteSizeUnit; +import org.opensearch.core.common.unit.ByteSizeValue; import org.opensearch.index.IndexModule; import org.opensearch.knn.index.memory.NativeMemoryCacheManager; +import org.opensearch.knn.index.memory.NativeMemoryCacheManagerDto; +import org.opensearch.knn.index.util.IndexHyperParametersUtil; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationStateCacheManager; import org.opensearch.monitor.jvm.JvmInfo; import org.opensearch.monitor.os.OsProbe; @@ -29,41 +35,46 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +import static java.util.stream.Collectors.toUnmodifiableMap; import static org.opensearch.common.settings.Setting.Property.Dynamic; import static org.opensearch.common.settings.Setting.Property.IndexScope; import static org.opensearch.common.settings.Setting.Property.NodeScope; -import static org.opensearch.common.unit.ByteSizeValue.parseBytesSizeValue; import static org.opensearch.common.unit.MemorySizeValue.parseBytesSizeValueOrHeapRatio; +import static org.opensearch.core.common.unit.ByteSizeValue.parseBytesSizeValue; +import static org.opensearch.knn.common.featureflags.KNNFeatureFlags.getFeatureFlags; /** * This class defines - * 1. KNN settings to hold the HNSW algorithm parameters. - * https://github.com/nmslib/hnswlib/blob/master/ALGO_PARAMS.md + * 1. KNN settings to hold the HNSW algorithm parameters. * 2. KNN settings to enable/disable plugin, circuit breaker settings * 3. KNN settings to manage graphs loaded in native memory */ +@Log4j2 public class KNNSettings { - private static Logger logger = LogManager.getLogger(KNNSettings.class); + private static final Logger logger = LogManager.getLogger(KNNSettings.class); private static KNNSettings INSTANCE; - private static OsProbe osProbe = OsProbe.getInstance(); + private static final OsProbe osProbe = OsProbe.getInstance(); private static final int INDEX_THREAD_QTY_MAX = 32; + private static final QuantizationStateCacheManager quantizationStateCacheManager = QuantizationStateCacheManager.getInstance(); /** * Settings name */ public static final String KNN_SPACE_TYPE = "index.knn.space_type"; + public static final String INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD = "index.knn.advanced.approximate_threshold"; public static final String KNN_ALGO_PARAM_M = "index.knn.algo_param.m"; public static final String KNN_ALGO_PARAM_EF_CONSTRUCTION = "index.knn.algo_param.ef_construction"; public static final String KNN_ALGO_PARAM_EF_SEARCH = "index.knn.algo_param.ef_search"; public static final String KNN_ALGO_PARAM_INDEX_THREAD_QTY = "knn.algo_param.index_thread_qty"; public static final String KNN_MEMORY_CIRCUIT_BREAKER_ENABLED = "knn.memory.circuit_breaker.enabled"; public static final String KNN_MEMORY_CIRCUIT_BREAKER_LIMIT = "knn.memory.circuit_breaker.limit"; + public static final String KNN_VECTOR_STREAMING_MEMORY_LIMIT_IN_MB = "knn.vector_streaming_memory.limit"; public static final String KNN_CIRCUIT_BREAKER_TRIGGERED = "knn.circuit_breaker.triggered"; public static final String KNN_CACHE_ITEM_EXPIRY_ENABLED = "knn.cache.item.expiry.enabled"; public static final String KNN_CACHE_ITEM_EXPIRY_TIME_MINUTES = "knn.cache.item.expiry.minutes"; @@ -73,23 +84,74 @@ public class KNNSettings { public static final String MODEL_INDEX_NUMBER_OF_SHARDS = "knn.model.index.number_of_shards"; public static final String MODEL_INDEX_NUMBER_OF_REPLICAS = "knn.model.index.number_of_replicas"; public static final String MODEL_CACHE_SIZE_LIMIT = "knn.model.cache.size.limit"; + public static final String ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD = "index.knn.advanced.filtered_exact_search_threshold"; + public static final String KNN_FAISS_AVX2_DISABLED = "knn.faiss.avx2.disabled"; + public static final String QUANTIZATION_STATE_CACHE_SIZE_LIMIT = "knn.quantization.cache.size.limit"; + public static final String QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES = "knn.quantization.cache.expiry.minutes"; + public static final String KNN_FAISS_AVX512_DISABLED = "knn.faiss.avx512.disabled"; + public static final String KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED = "index.knn.disk.vector.shard_level_rescoring_disabled"; /** * Default setting values + * */ + public static final boolean KNN_DEFAULT_FAISS_AVX2_DISABLED_VALUE = false; + public static final boolean KNN_DEFAULT_FAISS_AVX512_DISABLED_VALUE = false; public static final String INDEX_KNN_DEFAULT_SPACE_TYPE = "l2"; + public static final Integer INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD_DEFAULT_VALUE = 15_000; + public static final Integer INDEX_KNN_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD_MIN = -1; + public static final Integer INDEX_KNN_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD_MAX = Integer.MAX_VALUE - 2; + public static final String INDEX_KNN_DEFAULT_SPACE_TYPE_FOR_BINARY = "hamming"; public static final Integer INDEX_KNN_DEFAULT_ALGO_PARAM_M = 16; - public static final Integer INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH = 512; - public static final Integer INDEX_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION = 512; + public static final Integer INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH = 100; + public static final Integer INDEX_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION = 100; public static final Integer KNN_DEFAULT_ALGO_PARAM_INDEX_THREAD_QTY = 1; public static final Integer KNN_DEFAULT_CIRCUIT_BREAKER_UNSET_PERCENTAGE = 75; public static final Integer KNN_DEFAULT_MODEL_CACHE_SIZE_LIMIT_PERCENTAGE = 10; // By default, set aside 10% of the JVM for the limit public static final Integer KNN_MAX_MODEL_CACHE_SIZE_LIMIT_PERCENTAGE = 25; // Model cache limit cannot exceed 25% of the JVM heap + public static final String KNN_DEFAULT_MEMORY_CIRCUIT_BREAKER_LIMIT = "50%"; + public static final String KNN_DEFAULT_VECTOR_STREAMING_MEMORY_LIMIT_PCT = "1%"; + + public static final Integer ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD_DEFAULT_VALUE = -1; + public static final Integer KNN_DEFAULT_QUANTIZATION_STATE_CACHE_SIZE_LIMIT_PERCENTAGE = 5; // By default, set aside 5% of the JVM for + // the limit + public static final Integer KNN_MAX_QUANTIZATION_STATE_CACHE_SIZE_LIMIT_PERCENTAGE = 10; // Quantization state cache limit cannot exceed + // 10% of the JVM heap + public static final Integer KNN_DEFAULT_QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES = 60; + public static final boolean KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED_VALUE = false; /** * Settings Definition */ + /** + * This setting controls whether shard-level re-scoring for KNN disk-based vectors is turned off. + * The setting uses: + *
    + *
  • KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED: The name of the setting.
  • + *
  • KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED_VALUE: The default value (true or false).
  • + *
  • IndexScope: The setting works at the index level.
  • + *
  • Dynamic: This setting can be changed without restarting the cluster.
  • + *
+ * + * @see Setting#boolSetting(String, boolean, Setting.Property...) + */ + public static final Setting KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED_SETTING = Setting.boolSetting( + KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED, + KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED_VALUE, + IndexScope, + Dynamic + ); + + // This setting controls how much memory should be used to transfer vectors from Java to JNI Layer. The default + // 1% of the JVM heap + public static final Setting KNN_VECTOR_STREAMING_MEMORY_LIMIT_PCT_SETTING = Setting.memorySizeSetting( + KNN_VECTOR_STREAMING_MEMORY_LIMIT_IN_MB, + KNN_DEFAULT_VECTOR_STREAMING_MEMORY_LIMIT_PCT, + Setting.Property.Dynamic, + Setting.Property.NodeScope + ); + public static final Setting INDEX_KNN_SPACE_TYPE = Setting.simpleString( KNN_SPACE_TYPE, INDEX_KNN_DEFAULT_SPACE_TYPE, @@ -98,6 +160,21 @@ public class KNNSettings { Setting.Property.Deprecated ); + /** + * build_vector_data_structure_threshold - This parameter determines when to build vector data structure for knn fields during indexing + * and merging. Setting -1 (min) will skip building graph, whereas on any other values, the graph will be built if + * number of live docs in segment is greater than this threshold. Since max number of documents in a segment can + * be Integer.MAX_VALUE - 1, this setting will allow threshold to be up to 1 less than max number of documents in a segment + */ + public static final Setting INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD_SETTING = Setting.intSetting( + INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, + INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD_DEFAULT_VALUE, + INDEX_KNN_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD_MIN, + INDEX_KNN_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD_MAX, + IndexScope, + Dynamic + ); + /** * M - the number of bi-directional links created for every new element during construction. * Reasonable range for M is 2-100. Higher M work better on datasets with high intrinsic @@ -153,6 +230,13 @@ public class KNNSettings { Setting.Property.Dynamic ); + public static final Setting ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD_SETTING = Setting.intSetting( + ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD, + ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD_DEFAULT_VALUE, + IndexScope, + Setting.Property.Dynamic + ); + public static final Setting MODEL_CACHE_SIZE_LIMIT_SETTING = new Setting<>( MODEL_CACHE_SIZE_LIMIT, percentageAsString(KNN_DEFAULT_MODEL_CACHE_SIZE_LIMIT_PERCENTAGE), @@ -217,6 +301,57 @@ public class KNNSettings { NodeScope, Dynamic ); + + public static final Setting KNN_FAISS_AVX2_DISABLED_SETTING = Setting.boolSetting( + KNN_FAISS_AVX2_DISABLED, + KNN_DEFAULT_FAISS_AVX2_DISABLED_VALUE, + NodeScope + ); + + /* + * Quantization state cache settings + */ + public static final Setting QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING = new Setting( + QUANTIZATION_STATE_CACHE_SIZE_LIMIT, + percentageAsString(KNN_DEFAULT_QUANTIZATION_STATE_CACHE_SIZE_LIMIT_PERCENTAGE), + (s) -> { + ByteSizeValue userDefinedLimit = parseBytesSizeValueOrHeapRatio(s, QUANTIZATION_STATE_CACHE_SIZE_LIMIT); + + // parseBytesSizeValueOrHeapRatio will make sure that the value entered falls between 0 and 100% of the + // JVM heap. However, we want the maximum percentage of the heap to be much smaller. So, we add + // some additional validation here before returning + ByteSizeValue jvmHeapSize = JvmInfo.jvmInfo().getMem().getHeapMax(); + if ((userDefinedLimit.getKbFrac() / jvmHeapSize.getKbFrac()) > percentageAsFraction( + KNN_MAX_QUANTIZATION_STATE_CACHE_SIZE_LIMIT_PERCENTAGE + )) { + throw new OpenSearchParseException( + "{} ({} KB) cannot exceed {}% of the heap ({} KB).", + QUANTIZATION_STATE_CACHE_SIZE_LIMIT, + userDefinedLimit.getKb(), + KNN_MAX_QUANTIZATION_STATE_CACHE_SIZE_LIMIT_PERCENTAGE, + jvmHeapSize.getKb() + ); + } + + return userDefinedLimit; + }, + NodeScope, + Dynamic + ); + + public static final Setting QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING = Setting.positiveTimeSetting( + QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES, + TimeValue.timeValueMinutes(KNN_DEFAULT_QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES), + NodeScope, + Dynamic + ); + + public static final Setting KNN_FAISS_AVX512_DISABLED_SETTING = Setting.boolSetting( + KNN_FAISS_AVX512_DISABLED, + KNN_DEFAULT_FAISS_AVX512_DISABLED_VALUE, + NodeScope + ); + /** * Dynamic settings */ @@ -233,7 +368,13 @@ public class KNNSettings { put(KNN_MEMORY_CIRCUIT_BREAKER_ENABLED, Setting.boolSetting(KNN_MEMORY_CIRCUIT_BREAKER_ENABLED, true, NodeScope, Dynamic)); put( KNN_MEMORY_CIRCUIT_BREAKER_LIMIT, - knnMemoryCircuitBreakerSetting(KNN_MEMORY_CIRCUIT_BREAKER_LIMIT, "50%", NodeScope, Dynamic) + new Setting<>( + KNNSettings.KNN_MEMORY_CIRCUIT_BREAKER_LIMIT, + KNNSettings.KNN_DEFAULT_MEMORY_CIRCUIT_BREAKER_LIMIT, + (s) -> parseknnMemoryCircuitBreakerValue(s, KNNSettings.KNN_MEMORY_CIRCUIT_BREAKER_LIMIT), + NodeScope, + Dynamic + ) ); /** @@ -247,8 +388,8 @@ public class KNNSettings { } }; - /** Latest setting value for each registered key. Thread-safe is required. */ - private final Map latestSettings = new ConcurrentHashMap<>(); + private final static Map> FEATURE_FLAGS = getFeatureFlags().stream() + .collect(toUnmodifiableMap(Setting::getKey, Function.identity())); private ClusterService clusterService; private Client client; @@ -262,35 +403,39 @@ public static synchronized KNNSettings state() { return INSTANCE; } - public void setSettingsUpdateConsumers() { - for (Setting setting : dynamicCacheSettings.values()) { - clusterService.getClusterSettings().addSettingsUpdateConsumer(setting, newVal -> { - logger.debug("The value of setting [{}] changed to [{}]", setting.getKey(), newVal); - latestSettings.put(setting.getKey(), newVal); - - // Rebuild the cache with updated limit - NativeMemoryCacheManager.getInstance().rebuildCache(); - }); - } + private void setSettingsUpdateConsumers() { + clusterService.getClusterSettings().addSettingsUpdateConsumer(updatedSettings -> { + // When any of the dynamic settings are updated, rebuild the cache with the updated values. Use the current + // cluster settings values as defaults. + NativeMemoryCacheManagerDto.NativeMemoryCacheManagerDtoBuilder builder = NativeMemoryCacheManagerDto.builder(); - /** - * We do not have to rebuild the cache for below settings - */ - clusterService.getClusterSettings() - .addSettingsUpdateConsumer( - KNN_CIRCUIT_BREAKER_TRIGGERED_SETTING, - newVal -> { latestSettings.put(KNN_CIRCUIT_BREAKER_TRIGGERED, newVal); } + builder.isWeightLimited( + updatedSettings.getAsBoolean(KNN_MEMORY_CIRCUIT_BREAKER_ENABLED, getSettingValue(KNN_MEMORY_CIRCUIT_BREAKER_ENABLED)) ); - clusterService.getClusterSettings() - .addSettingsUpdateConsumer( - KNN_CIRCUIT_BREAKER_UNSET_PERCENTAGE_SETTING, - newVal -> { latestSettings.put(KNN_CIRCUIT_BREAKER_UNSET_PERCENTAGE, newVal); } + + builder.maxWeight(((ByteSizeValue) getSettingValue(KNN_MEMORY_CIRCUIT_BREAKER_LIMIT)).getKb()); + if (updatedSettings.hasValue(KNN_MEMORY_CIRCUIT_BREAKER_LIMIT)) { + builder.maxWeight(((ByteSizeValue) getSetting(KNN_MEMORY_CIRCUIT_BREAKER_LIMIT).get(updatedSettings)).getKb()); + } + + builder.isExpirationLimited( + updatedSettings.getAsBoolean(KNN_CACHE_ITEM_EXPIRY_ENABLED, getSettingValue(KNN_CACHE_ITEM_EXPIRY_ENABLED)) ); - clusterService.getClusterSettings() - .addSettingsUpdateConsumer( - KNN_ALGO_PARAM_INDEX_THREAD_QTY_SETTING, - newVal -> { latestSettings.put(KNN_ALGO_PARAM_INDEX_THREAD_QTY, newVal); } + + builder.expiryTimeInMin( + updatedSettings.getAsTime(KNN_CACHE_ITEM_EXPIRY_TIME_MINUTES, getSettingValue(KNN_CACHE_ITEM_EXPIRY_TIME_MINUTES)) + .getMinutes() ); + + NativeMemoryCacheManager.getInstance().rebuildCache(builder.build()); + }, Stream.concat(dynamicCacheSettings.values().stream(), FEATURE_FLAGS.values().stream()).collect(Collectors.toUnmodifiableList())); + clusterService.getClusterSettings().addSettingsUpdateConsumer(QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING, it -> { + quantizationStateCacheManager.setMaxCacheSizeInKB(it.getKb()); + quantizationStateCacheManager.rebuildCache(); + }); + clusterService.getClusterSettings().addSettingsUpdateConsumer(QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING, it -> { + quantizationStateCacheManager.rebuildCache(); + }); } /** @@ -302,14 +447,18 @@ public void setSettingsUpdateConsumers() { */ @SuppressWarnings("unchecked") public T getSettingValue(String key) { - return (T) latestSettings.getOrDefault(key, getSetting(key).getDefault(Settings.EMPTY)); + return (T) clusterService.getClusterSettings().get(getSetting(key)); } - public Setting getSetting(String key) { + private Setting getSetting(String key) { if (dynamicCacheSettings.containsKey(key)) { return dynamicCacheSettings.get(key); } + if (FEATURE_FLAGS.containsKey(key)) { + return FEATURE_FLAGS.get(key); + } + if (KNN_CIRCUIT_BREAKER_TRIGGERED.equals(key)) { return KNN_CIRCUIT_BREAKER_TRIGGERED_SETTING; } @@ -322,12 +471,41 @@ public Setting getSetting(String key) { return KNN_ALGO_PARAM_INDEX_THREAD_QTY_SETTING; } + if (ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD.equals(key)) { + return ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD_SETTING; + } + + if (KNN_FAISS_AVX2_DISABLED.equals(key)) { + return KNN_FAISS_AVX2_DISABLED_SETTING; + } + + if (KNN_FAISS_AVX512_DISABLED.equals(key)) { + return KNN_FAISS_AVX512_DISABLED_SETTING; + } + + if (KNN_VECTOR_STREAMING_MEMORY_LIMIT_IN_MB.equals(key)) { + return KNN_VECTOR_STREAMING_MEMORY_LIMIT_PCT_SETTING; + } + + if (QUANTIZATION_STATE_CACHE_SIZE_LIMIT.equals(key)) { + return QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING; + } + + if (QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES.equals(key)) { + return QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING; + } + + if (KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED.equals(key)) { + return KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED_SETTING; + } + throw new IllegalArgumentException("Cannot find setting by key [" + key + "]"); } public List> getSettings() { List> settings = Arrays.asList( INDEX_KNN_SPACE_TYPE, + INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD_SETTING, INDEX_KNN_ALGO_PARAM_M_SETTING, INDEX_KNN_ALGO_PARAM_EF_CONSTRUCTION_SETTING, INDEX_KNN_ALGO_PARAM_EF_SEARCH_SETTING, @@ -337,9 +515,17 @@ public List> getSettings() { IS_KNN_INDEX_SETTING, MODEL_INDEX_NUMBER_OF_SHARDS_SETTING, MODEL_INDEX_NUMBER_OF_REPLICAS_SETTING, - MODEL_CACHE_SIZE_LIMIT_SETTING + MODEL_CACHE_SIZE_LIMIT_SETTING, + ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD_SETTING, + KNN_FAISS_AVX2_DISABLED_SETTING, + KNN_VECTOR_STREAMING_MEMORY_LIMIT_PCT_SETTING, + KNN_FAISS_AVX512_DISABLED_SETTING, + QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING, + QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING, + KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED_SETTING ); - return Stream.concat(settings.stream(), dynamicCacheSettings.values().stream()).collect(Collectors.toList()); + return Stream.concat(settings.stream(), Stream.concat(getFeatureFlags().stream(), dynamicCacheSettings.values().stream())) + .collect(Collectors.toList()); } public static boolean isKNNPluginEnabled() { @@ -358,25 +544,53 @@ public static double getCircuitBreakerUnsetPercentage() { return KNNSettings.state().getSettingValue(KNNSettings.KNN_CIRCUIT_BREAKER_UNSET_PERCENTAGE); } + public static boolean isFaissAVX2Disabled() { + try { + return KNNSettings.state().getSettingValue(KNNSettings.KNN_FAISS_AVX2_DISABLED); + } catch (Exception e) { + // In some UTs we identified that cluster setting is not set properly an leads to NPE. This check will avoid + // those cases and will still return the default value. + log.warn( + "Unable to get setting value {} from cluster settings. Using default value as {}", + KNN_FAISS_AVX2_DISABLED, + KNN_DEFAULT_FAISS_AVX2_DISABLED_VALUE, + e + ); + return KNN_DEFAULT_FAISS_AVX2_DISABLED_VALUE; + } + } + + public static boolean isFaissAVX512Disabled() { + return Booleans.parseBoolean( + Objects.requireNonNullElse( + KNNSettings.state().getSettingValue(KNNSettings.KNN_FAISS_AVX512_DISABLED), + KNN_DEFAULT_FAISS_AVX512_DISABLED_VALUE + ).toString() + ); + } + + public static Integer getFilteredExactSearchThreshold(final String indexName) { + return KNNSettings.state().clusterService.state() + .getMetadata() + .index(indexName) + .getSettings() + .getAsInt(ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD, ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD_DEFAULT_VALUE); + } + + public static boolean isShardLevelRescoringEnabledForDiskBasedVector(String indexName) { + return KNNSettings.state().clusterService.state() + .getMetadata() + .index(indexName) + .getSettings() + .getAsBoolean(KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED, false); + } + public void initialize(Client client, ClusterService clusterService) { this.client = client; this.clusterService = clusterService; setSettingsUpdateConsumers(); } - /** - * Creates a setting which specifies a circuit breaker memory limit. This can either be - * specified as an absolute bytes value or as a percentage. - * - * @param key the key for the setting - * @param defaultValue the default value for this setting - * @param properties properties properties for this setting like scope, filtering... - * @return the setting object - */ - public static Setting knnMemoryCircuitBreakerSetting(String key, String defaultValue, Setting.Property... properties) { - return new Setting<>(key, defaultValue, (s) -> parseknnMemoryCircuitBreakerValue(s, key), properties); - } - public static ByteSizeValue parseknnMemoryCircuitBreakerValue(String sValue, String settingName) { settingName = Objects.requireNonNull(settingName); if (sValue != null && sValue.endsWith("%")) { @@ -430,30 +644,22 @@ public void onFailure(Exception e) { }); } - /** - * - * @param index Name of the index - * @return efSearch value - */ - public static int getEfSearchParam(String index) { - return getIndexSettingValue(index, KNN_ALGO_PARAM_EF_SEARCH, 512); + public static ByteSizeValue getVectorStreamingMemoryLimit() { + return KNNSettings.state().getSettingValue(KNN_VECTOR_STREAMING_MEMORY_LIMIT_IN_MB); } /** * * @param index Name of the index - * @return spaceType name in KNN plugin + * @return efSearch value */ - public static String getSpaceType(String index) { - return KNNSettings.state().clusterService.state() - .getMetadata() - .index(index) - .getSettings() - .get(KNN_SPACE_TYPE, SpaceType.DEFAULT.getValue()); - } - - public static int getIndexSettingValue(String index, String settingName, int defaultValue) { - return KNNSettings.state().clusterService.state().getMetadata().index(index).getSettings().getAsInt(settingName, defaultValue); + public static int getEfSearchParam(String index) { + final IndexMetadata indexMetadata = KNNSettings.state().clusterService.state().getMetadata().index(index); + return indexMetadata.getSettings() + .getAsInt( + KNNSettings.KNN_ALGO_PARAM_EF_SEARCH, + IndexHyperParametersUtil.getHNSWEFSearchValue(indexMetadata.getCreationVersion()) + ); } public void setClusterService(ClusterService clusterService) { @@ -475,7 +681,6 @@ public void validate(String value) { public void onIndexModule(IndexModule module) { module.addSettingsUpdateConsumer(INDEX_KNN_ALGO_PARAM_EF_SEARCH_SETTING, newVal -> { logger.debug("The value of [KNN] setting [{}] changed to [{}]", KNN_ALGO_PARAM_EF_SEARCH, newVal); - latestSettings.put(KNN_ALGO_PARAM_EF_SEARCH, newVal); // TODO: replace cache-rebuild with index reload into the cache NativeMemoryCacheManager.getInstance().rebuildCache(); }); diff --git a/src/main/java/org/opensearch/knn/index/KNNVectorDVLeafFieldData.java b/src/main/java/org/opensearch/knn/index/KNNVectorDVLeafFieldData.java index 5f522e3de..7053e6151 100644 --- a/src/main/java/org/opensearch/knn/index/KNNVectorDVLeafFieldData.java +++ b/src/main/java/org/opensearch/knn/index/KNNVectorDVLeafFieldData.java @@ -5,12 +5,14 @@ package org.opensearch.knn.index; -import org.apache.lucene.index.BinaryDocValues; import org.apache.lucene.index.DocValues; +import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.LeafReader; +import org.apache.lucene.search.DocIdSetIterator; import org.opensearch.index.fielddata.LeafFieldData; import org.opensearch.index.fielddata.ScriptDocValues; import org.opensearch.index.fielddata.SortedBinaryDocValues; +import org.opensearch.knn.common.FieldInfoExtractor; import java.io.IOException; @@ -18,10 +20,12 @@ public class KNNVectorDVLeafFieldData implements LeafFieldData { private final LeafReader reader; private final String fieldName; + private final VectorDataType vectorDataType; - public KNNVectorDVLeafFieldData(LeafReader reader, String fieldName) { + public KNNVectorDVLeafFieldData(LeafReader reader, String fieldName, VectorDataType vectorDataType) { this.reader = reader; this.fieldName = fieldName; + this.vectorDataType = vectorDataType; } @Override @@ -37,10 +41,29 @@ public long ramBytesUsed() { @Override public ScriptDocValues getScriptValues() { try { - BinaryDocValues values = DocValues.getBinary(reader, fieldName); - return new KNNVectorScriptDocValues(values, fieldName); + FieldInfo fieldInfo = FieldInfoExtractor.getFieldInfo(reader, fieldName); + if (fieldInfo == null) { + return KNNVectorScriptDocValues.emptyValues(fieldName, vectorDataType); + } + + DocIdSetIterator values; + if (fieldInfo.hasVectorValues()) { + switch (fieldInfo.getVectorEncoding()) { + case FLOAT32: + values = reader.getFloatVectorValues(fieldName); + break; + case BYTE: + values = reader.getByteVectorValues(fieldName); + break; + default: + throw new IllegalStateException("Unsupported Lucene vector encoding: " + fieldInfo.getVectorEncoding()); + } + } else { + values = DocValues.getBinary(reader, fieldName); + } + return KNNVectorScriptDocValues.create(values, fieldName, vectorDataType); } catch (IOException e) { - throw new IllegalStateException("Cannot load doc values for knn vector field: " + fieldName, e); + throw new IllegalStateException("Cannot load values for knn vector field: " + fieldName, e); } } diff --git a/src/main/java/org/opensearch/knn/index/KNNVectorFieldMapper.java b/src/main/java/org/opensearch/knn/index/KNNVectorFieldMapper.java deleted file mode 100644 index 020e5ae25..000000000 --- a/src/main/java/org/opensearch/knn/index/KNNVectorFieldMapper.java +++ /dev/null @@ -1,692 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.opensearch.knn.index; - -import org.opensearch.common.Strings; -import org.opensearch.common.ValidationException; -import org.opensearch.common.xcontent.XContentFactory; -import org.opensearch.knn.common.KNNConstants; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.lucene.document.FieldType; -import org.apache.lucene.document.StoredField; -import org.apache.lucene.index.DocValuesType; -import org.apache.lucene.index.IndexOptions; -import org.apache.lucene.search.DocValuesFieldExistsQuery; -import org.apache.lucene.search.Query; -import org.opensearch.common.Explicit; -import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.ToXContent; -import org.opensearch.common.xcontent.XContentBuilder; -import org.opensearch.common.xcontent.XContentParser; -import org.opensearch.common.xcontent.support.XContentMapValues; -import org.opensearch.index.fielddata.IndexFieldData; -import org.opensearch.index.mapper.FieldMapper; -import org.opensearch.index.mapper.MappedFieldType; -import org.opensearch.index.mapper.Mapper; -import org.opensearch.index.mapper.MapperParsingException; -import org.opensearch.index.mapper.ParametrizedFieldMapper; -import org.opensearch.index.mapper.ParseContext; -import org.opensearch.index.mapper.TextSearchInfo; -import org.opensearch.index.mapper.ValueFetcher; -import org.opensearch.index.query.QueryShardContext; -import org.opensearch.index.query.QueryShardException; -import org.opensearch.knn.index.util.KNNEngine; -import org.opensearch.knn.indices.ModelDao; -import org.opensearch.knn.indices.ModelMetadata; -import org.opensearch.search.aggregations.support.CoreValuesSourceType; -import org.opensearch.search.lookup.SearchLookup; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.function.Supplier; - -import static org.opensearch.knn.common.KNNConstants.DIMENSION; -import static org.opensearch.knn.common.KNNConstants.HNSW_ALGO_EF_CONSTRUCTION; -import static org.opensearch.knn.common.KNNConstants.HNSW_ALGO_M; -import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; -import static org.opensearch.knn.common.KNNConstants.KNN_METHOD; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; -import static org.opensearch.knn.common.KNNConstants.MODEL_ID; -import static org.opensearch.knn.common.KNNConstants.PARAMETERS; -import static org.opensearch.knn.common.KNNConstants.SPACE_TYPE; - -/** - * Field Mapper for KNN vector type. - * - * Extends ParametrizedFieldMapper in order to easily configure mapping parameters. - * - * Implementations of this class define what needs to be stored in Lucene's fieldType. This allows us to have - * alternative mappings for the same field type. - */ -public abstract class KNNVectorFieldMapper extends ParametrizedFieldMapper { - - private static Logger logger = LogManager.getLogger(KNNVectorFieldMapper.class); - - public static final String CONTENT_TYPE = "knn_vector"; - public static final String KNN_FIELD = "knn_field"; - - /** - * Define the max dimension a knn_vector mapping can have. This limit is somewhat arbitrary. In the future, we - * should make this configurable. - */ - public static final int MAX_DIMENSION = 10000; - - private static KNNVectorFieldMapper toType(FieldMapper in) { - return (KNNVectorFieldMapper) in; - } - - /** - * Builder for KNNVectorFieldMapper. This class defines the set of parameters that can be applied to the knn_vector - * field type - */ - public static class Builder extends ParametrizedFieldMapper.Builder { - protected Boolean ignoreMalformed; - - protected final Parameter stored = Parameter.boolParam("store", false, m -> toType(m).stored, false); - protected final Parameter hasDocValues = Parameter.boolParam("doc_values", false, m -> toType(m).hasDocValues, true); - protected final Parameter dimension = new Parameter<>(KNNConstants.DIMENSION, false, () -> -1, (n, c, o) -> { - if (o == null) { - throw new IllegalArgumentException("Dimension cannot be null"); - } - int value = XContentMapValues.nodeIntegerValue(o); - if (value > MAX_DIMENSION) { - throw new IllegalArgumentException("Dimension value cannot be greater than " + MAX_DIMENSION + " for vector: " + name); - } - - if (value <= 0) { - throw new IllegalArgumentException("Dimension value must be greater than 0 " + "for vector: " + name); - } - return value; - }, m -> toType(m).dimension); - - /** - * modelId provides a way for a user to generate the underlying library indices from an already serialized - * model template index. If this parameter is set, it will take precedence. This parameter is only relevant for - * library indices that require training. - */ - protected final Parameter modelId = Parameter.stringParam(KNNConstants.MODEL_ID, false, m -> toType(m).modelId, null); - - /** - * knnMethodContext parameter allows a user to define their k-NN library index configuration. Defaults to an L2 - * hnsw default engine index without any parameters set - */ - protected final Parameter knnMethodContext = new Parameter<>( - KNN_METHOD, - false, - () -> null, - (n, c, o) -> KNNMethodContext.parse(o), - m -> toType(m).knnMethod - ).setSerializer(((b, n, v) -> { - b.startObject(n); - v.toXContent(b, ToXContent.EMPTY_PARAMS); - b.endObject(); - }), m -> m.getMethodComponent().getName()).setValidator(v -> { - if (v == null) return; - - ValidationException validationException = null; - if (v.isTrainingRequired()) { - validationException = new ValidationException(); - validationException.addValidationError(String.format("\"%s\" requires training.", KNN_METHOD)); - } - - ValidationException methodValidation = v.validate(); - if (methodValidation != null) { - validationException = validationException == null ? new ValidationException() : validationException; - validationException.addValidationErrors(methodValidation.validationErrors()); - } - - if (validationException != null) { - throw validationException; - } - }); - - protected final Parameter> meta = Parameter.metaParam(); - - protected String spaceType; - protected String m; - protected String efConstruction; - - protected ModelDao modelDao; - - public Builder(String name, ModelDao modelDao) { - super(name); - this.modelDao = modelDao; - } - - /** - * This constructor is for legacy purposes. - * Checkout ODFE PR 288 - * - * @param name field name - * @param spaceType Spacetype of field - * @param m m value of field - * @param efConstruction efConstruction value of field - */ - public Builder(String name, String spaceType, String m, String efConstruction) { - super(name); - this.spaceType = spaceType; - this.m = m; - this.efConstruction = efConstruction; - } - - @Override - protected List> getParameters() { - return Arrays.asList(stored, hasDocValues, dimension, meta, knnMethodContext, modelId); - } - - protected Explicit ignoreMalformed(BuilderContext context) { - if (ignoreMalformed != null) { - return new Explicit<>(ignoreMalformed, true); - } - if (context.indexSettings() != null) { - return new Explicit<>(IGNORE_MALFORMED_SETTING.get(context.indexSettings()), false); - } - return KNNVectorFieldMapper.Defaults.IGNORE_MALFORMED; - } - - @Override - public KNNVectorFieldMapper build(BuilderContext context) { - // Originally, a user would use index settings to set the spaceType, efConstruction and m hnsw - // parameters. Upon further review, it makes sense to set these parameters in the mapping of a - // particular field. However, because users migrating from older versions will still use the index - // settings to set these parameters, we will need to provide backwards compatibilty. In order to - // handle this, we first check if the mapping is set, and, if so use it. If not, we check if the model is - // set. If not, we fall back to the parameters set in the index settings. This means that if a user sets - // the mappings, setting the index settings will have no impact. - - KNNMethodContext knnMethodContext = this.knnMethodContext.getValue(); - if (knnMethodContext != null) { - return new MethodFieldMapper( - name, - new KNNVectorFieldType(buildFullName(context), meta.getValue(), dimension.getValue()), - multiFieldsBuilder.build(this, context), - copyTo.build(), - ignoreMalformed(context), - stored.get(), - hasDocValues.get(), - knnMethodContext - ); - } - - String modelIdAsString = this.modelId.get(); - if (modelIdAsString != null) { - // Because model information is stored in cluster metadata, we are unable to get it here. This is - // because to get the cluster metadata, you need access to the cluster state. Because this code is - // sometimes used to initialize the cluster state/update cluster state, we cannot get the state here - // safely. So, we are unable to validate the model. The model gets validated during ingestion. - - return new ModelFieldMapper( - name, - new KNNVectorFieldType(buildFullName(context), meta.getValue(), -1, modelIdAsString), - multiFieldsBuilder.build(this, context), - copyTo.build(), - ignoreMalformed(context), - stored.get(), - hasDocValues.get(), - modelDao, - modelIdAsString - ); - } - - // Build legacy - if (this.spaceType == null) { - this.spaceType = LegacyFieldMapper.getSpaceType(context.indexSettings()); - } - - if (this.m == null) { - this.m = LegacyFieldMapper.getM(context.indexSettings()); - } - - if (this.efConstruction == null) { - this.efConstruction = LegacyFieldMapper.getEfConstruction(context.indexSettings()); - } - - return new LegacyFieldMapper( - name, - new KNNVectorFieldType(buildFullName(context), meta.getValue(), dimension.getValue()), - multiFieldsBuilder.build(this, context), - copyTo.build(), - ignoreMalformed(context), - stored.get(), - hasDocValues.get(), - spaceType, - m, - efConstruction - ); - } - } - - public static class TypeParser implements Mapper.TypeParser { - - // Use a supplier here because in {@link org.opensearch.knn.KNNPlugin#getMappers()} the ModelDao has not yet - // been initialized - private Supplier modelDaoSupplier; - - public TypeParser(Supplier modelDaoSupplier) { - this.modelDaoSupplier = modelDaoSupplier; - } - - @Override - public Mapper.Builder parse(String name, Map node, ParserContext parserContext) throws MapperParsingException { - Builder builder = new KNNVectorFieldMapper.Builder(name, modelDaoSupplier.get()); - builder.parse(name, parserContext, node); - - // All parsing - // is done before any mappers are built. Therefore, validation should be done during parsing - // so that it can fail early. - if (builder.knnMethodContext.get() != null && builder.modelId.get() != null) { - throw new IllegalArgumentException("Method and model can not be both specified in the mapping: " + name); - } - - // Dimension should not be null unless modelId is used - if (builder.dimension.getValue() == -1 && builder.modelId.get() == null) { - throw new IllegalArgumentException("Dimension value missing for vector: " + name); - } - - return builder; - } - } - - public static class KNNVectorFieldType extends MappedFieldType { - - int dimension; - String modelId; - - public KNNVectorFieldType(String name, Map meta, int dimension) { - this(name, meta, dimension, null); - } - - public KNNVectorFieldType(String name, Map meta, int dimension, String modelId) { - super(name, false, false, true, TextSearchInfo.NONE, meta); - this.dimension = dimension; - this.modelId = modelId; - } - - @Override - public ValueFetcher valueFetcher(QueryShardContext context, SearchLookup searchLookup, String format) { - throw new UnsupportedOperationException("KNN Vector do not support fields search"); - } - - @Override - public String typeName() { - return CONTENT_TYPE; - } - - @Override - public Query existsQuery(QueryShardContext context) { - return new DocValuesFieldExistsQuery(name()); - } - - @Override - public Query termQuery(Object value, QueryShardContext context) { - throw new QueryShardException( - context, - "KNN vector do not support exact searching, use KNN queries " + "instead: [" + name() + "]" - ); - } - - public int getDimension() { - return dimension; - } - - public String getModelId() { - return modelId; - } - - @Override - public IndexFieldData.Builder fielddataBuilder(String fullyQualifiedIndexName, Supplier searchLookup) { - failIfNoDocValues(); - return new KNNVectorIndexFieldData.Builder(name(), CoreValuesSourceType.BYTES); - } - } - - protected Explicit ignoreMalformed; - protected boolean stored; - protected boolean hasDocValues; - protected Integer dimension; - protected ModelDao modelDao; - - // These members map to parameters in the builder. They need to be declared in the abstract class due to the - // "toType" function used in the builder. So, when adding a parameter, it needs to be added here, but set in a - // subclass (if it is unique). - protected KNNMethodContext knnMethod; - protected String modelId; - - public KNNVectorFieldMapper( - String simpleName, - KNNVectorFieldType mappedFieldType, - MultiFields multiFields, - CopyTo copyTo, - Explicit ignoreMalformed, - boolean stored, - boolean hasDocValues - ) { - super(simpleName, mappedFieldType, multiFields, copyTo); - this.ignoreMalformed = ignoreMalformed; - this.stored = stored; - this.hasDocValues = hasDocValues; - this.dimension = mappedFieldType.getDimension(); - } - - public KNNVectorFieldMapper clone() { - return (KNNVectorFieldMapper) super.clone(); - } - - @Override - protected String contentType() { - return CONTENT_TYPE; - } - - @Override - protected void parseCreateField(ParseContext context) throws IOException { - parseCreateField(context, fieldType().getDimension()); - } - - protected void parseCreateField(ParseContext context, int dimension) throws IOException { - - if (!KNNSettings.isKNNPluginEnabled()) { - throw new IllegalStateException("KNN plugin is disabled. To enable " + "update knn.plugin.enabled setting to true"); - } - - if (KNNSettings.isCircuitBreakerTriggered()) { - throw new IllegalStateException( - "Indexing knn vector fields is rejected as circuit breaker triggered." + " Check _opendistro/_knn/stats for detailed state" - ); - } - - context.path().add(simpleName()); - - ArrayList vector = new ArrayList<>(); - XContentParser.Token token = context.parser().currentToken(); - float value; - if (token == XContentParser.Token.START_ARRAY) { - token = context.parser().nextToken(); - while (token != XContentParser.Token.END_ARRAY) { - value = context.parser().floatValue(); - - if (Float.isNaN(value)) { - throw new IllegalArgumentException("KNN vector values cannot be NaN"); - } - - if (Float.isInfinite(value)) { - throw new IllegalArgumentException("KNN vector values cannot be infinity"); - } - - vector.add(value); - token = context.parser().nextToken(); - } - } else if (token == XContentParser.Token.VALUE_NUMBER) { - value = context.parser().floatValue(); - - if (Float.isNaN(value)) { - throw new IllegalArgumentException("KNN vector values cannot be NaN"); - } - - if (Float.isInfinite(value)) { - throw new IllegalArgumentException("KNN vector values cannot be infinity"); - } - - vector.add(value); - context.parser().nextToken(); - } else if (token == XContentParser.Token.VALUE_NULL) { - context.path().remove(); - return; - } - - if (dimension != vector.size()) { - String errorMessage = String.format("Vector dimension mismatch. Expected: %d, Given: %d", dimension, vector.size()); - throw new IllegalArgumentException(errorMessage); - } - - float[] array = new float[vector.size()]; - int i = 0; - for (Float f : vector) { - array[i++] = f; - } - - VectorField point = new VectorField(name(), array, fieldType); - - context.doc().add(point); - if (fieldType.stored()) { - context.doc().add(new StoredField(name(), point.toString())); - } - context.path().remove(); - } - - @Override - protected boolean docValuesByDefault() { - return true; - } - - @Override - public ParametrizedFieldMapper.Builder getMergeBuilder() { - return new KNNVectorFieldMapper.Builder(simpleName(), modelDao).init(this); - } - - @Override - public final boolean parsesArrayValue() { - return true; - } - - @Override - public KNNVectorFieldType fieldType() { - return (KNNVectorFieldType) super.fieldType(); - } - - @Override - protected void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException { - super.doXContentBody(builder, includeDefaults, params); - if (includeDefaults || ignoreMalformed.explicit()) { - builder.field(Names.IGNORE_MALFORMED, ignoreMalformed.value()); - } - } - - public static class Names { - public static final String IGNORE_MALFORMED = "ignore_malformed"; - } - - public static class Defaults { - public static final Explicit IGNORE_MALFORMED = new Explicit<>(false, false); - public static final FieldType FIELD_TYPE = new FieldType(); - - static { - FIELD_TYPE.setTokenized(false); - FIELD_TYPE.setIndexOptions(IndexOptions.NONE); - FIELD_TYPE.setDocValuesType(DocValuesType.BINARY); - FIELD_TYPE.putAttribute(KNN_FIELD, "true"); // This attribute helps to determine knn field type - FIELD_TYPE.freeze(); - } - } - - /** - * Field mapper for original implementation - */ - protected static class LegacyFieldMapper extends KNNVectorFieldMapper { - - protected String spaceType; - protected String m; - protected String efConstruction; - - private LegacyFieldMapper( - String simpleName, - KNNVectorFieldType mappedFieldType, - MultiFields multiFields, - CopyTo copyTo, - Explicit ignoreMalformed, - boolean stored, - boolean hasDocValues, - String spaceType, - String m, - String efConstruction - ) { - super(simpleName, mappedFieldType, multiFields, copyTo, ignoreMalformed, stored, hasDocValues); - - this.spaceType = spaceType; - this.m = m; - this.efConstruction = efConstruction; - - this.fieldType = new FieldType(KNNVectorFieldMapper.Defaults.FIELD_TYPE); - - this.fieldType.putAttribute(DIMENSION, String.valueOf(dimension)); - this.fieldType.putAttribute(SPACE_TYPE, spaceType); - this.fieldType.putAttribute(KNN_ENGINE, KNNEngine.NMSLIB.getName()); - - // These are extra just for legacy - this.fieldType.putAttribute(HNSW_ALGO_M, m); - this.fieldType.putAttribute(HNSW_ALGO_EF_CONSTRUCTION, efConstruction); - - this.fieldType.freeze(); - } - - @Override - public ParametrizedFieldMapper.Builder getMergeBuilder() { - return new KNNVectorFieldMapper.Builder(simpleName(), this.spaceType, this.m, this.efConstruction).init(this); - } - - static String getSpaceType(Settings indexSettings) { - String spaceType = indexSettings.get(KNNSettings.INDEX_KNN_SPACE_TYPE.getKey()); - if (spaceType == null) { - logger.info( - "[KNN] The setting \"" - + METHOD_PARAMETER_SPACE_TYPE - + "\" was not set for the index. " - + "Likely caused by recent version upgrade. Setting the setting to the default value=" - + KNNSettings.INDEX_KNN_DEFAULT_SPACE_TYPE - ); - return KNNSettings.INDEX_KNN_DEFAULT_SPACE_TYPE; - } - return spaceType; - } - - static String getM(Settings indexSettings) { - String m = indexSettings.get(KNNSettings.INDEX_KNN_ALGO_PARAM_M_SETTING.getKey()); - if (m == null) { - logger.info( - "[KNN] The setting \"" - + HNSW_ALGO_M - + "\" was not set for the index. " - + "Likely caused by recent version upgrade. Setting the setting to the default value=" - + KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_M - ); - return String.valueOf(KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_M); - } - return m; - } - - static String getEfConstruction(Settings indexSettings) { - String efConstruction = indexSettings.get(KNNSettings.INDEX_KNN_ALGO_PARAM_EF_CONSTRUCTION_SETTING.getKey()); - if (efConstruction == null) { - logger.info( - "[KNN] The setting \"" - + HNSW_ALGO_EF_CONSTRUCTION - + "\" was not set for" - + " the index. Likely caused by recent version upgrade. Setting the setting to the default value=" - + KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION - ); - return String.valueOf(KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION); - } - return efConstruction; - } - } - - /** - * Field mapper for method definition in mapping - */ - protected static class MethodFieldMapper extends KNNVectorFieldMapper { - - private MethodFieldMapper( - String simpleName, - KNNVectorFieldType mappedFieldType, - MultiFields multiFields, - CopyTo copyTo, - Explicit ignoreMalformed, - boolean stored, - boolean hasDocValues, - KNNMethodContext knnMethodContext - ) { - - super(simpleName, mappedFieldType, multiFields, copyTo, ignoreMalformed, stored, hasDocValues); - - this.knnMethod = knnMethodContext; - - this.fieldType = new FieldType(KNNVectorFieldMapper.Defaults.FIELD_TYPE); - - this.fieldType.putAttribute(DIMENSION, String.valueOf(dimension)); - this.fieldType.putAttribute(SPACE_TYPE, knnMethodContext.getSpaceType().getValue()); - - KNNEngine knnEngine = knnMethodContext.getEngine(); - this.fieldType.putAttribute(KNN_ENGINE, knnEngine.getName()); - - try { - this.fieldType.putAttribute( - PARAMETERS, - Strings.toString(XContentFactory.jsonBuilder().map(knnEngine.getMethodAsMap(knnMethodContext))) - ); - } catch (IOException ioe) { - throw new RuntimeException("Unable to create KNNVectorFieldMapper: " + ioe); - } - - this.fieldType.freeze(); - } - } - - /** - * Field mapper for model in mapping - */ - protected static class ModelFieldMapper extends KNNVectorFieldMapper { - - private ModelFieldMapper( - String simpleName, - KNNVectorFieldType mappedFieldType, - MultiFields multiFields, - CopyTo copyTo, - Explicit ignoreMalformed, - boolean stored, - boolean hasDocValues, - ModelDao modelDao, - String modelId - ) { - super(simpleName, mappedFieldType, multiFields, copyTo, ignoreMalformed, stored, hasDocValues); - - this.modelId = modelId; - this.modelDao = modelDao; - - this.fieldType = new FieldType(KNNVectorFieldMapper.Defaults.FIELD_TYPE); - this.fieldType.putAttribute(MODEL_ID, modelId); - this.fieldType.freeze(); - } - - @Override - protected void parseCreateField(ParseContext context) throws IOException { - // For the model field mapper, we cannot validate the model during index creation due to - // an issue with reading cluster state during mapper creation. So, we need to validate the - // model when ingestion starts. - ModelMetadata modelMetadata = this.modelDao.getMetadata(modelId); - - if (modelMetadata == null) { - throw new IllegalStateException( - "Model \"" - + modelId - + "\" from " - + context.mapperService().index().getName() - + "'s mapping does not exist. Because the " - + "\"" - + MODEL_ID - + "\" parameter is not updateable, this index will need to " - + "be recreated with a valid model." - ); - } - - parseCreateField(context, modelMetadata.getDimension()); - } - } -} diff --git a/src/main/java/org/opensearch/knn/index/KNNVectorIndexFieldData.java b/src/main/java/org/opensearch/knn/index/KNNVectorIndexFieldData.java index 367cfae53..745aee977 100644 --- a/src/main/java/org/opensearch/knn/index/KNNVectorIndexFieldData.java +++ b/src/main/java/org/opensearch/knn/index/KNNVectorIndexFieldData.java @@ -8,9 +8,9 @@ import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.search.SortField; import org.opensearch.common.util.BigArrays; +import org.opensearch.core.indices.breaker.CircuitBreakerService; import org.opensearch.index.fielddata.IndexFieldData; import org.opensearch.index.fielddata.IndexFieldDataCache; -import org.opensearch.indices.breaker.CircuitBreakerService; import org.opensearch.search.DocValueFormat; import org.opensearch.search.MultiValueMode; import org.opensearch.search.aggregations.support.ValuesSourceType; @@ -21,10 +21,12 @@ public class KNNVectorIndexFieldData implements IndexFieldData build(IndexFieldDataCache cache, CircuitBreakerService breakerService) { - return new KNNVectorIndexFieldData(name, valuesSourceType); + return new KNNVectorIndexFieldData(name, valuesSourceType, vectorDataType); } } } diff --git a/src/main/java/org/opensearch/knn/index/KNNVectorScriptDocValues.java b/src/main/java/org/opensearch/knn/index/KNNVectorScriptDocValues.java index 0c8240dd4..55ff65516 100644 --- a/src/main/java/org/opensearch/knn/index/KNNVectorScriptDocValues.java +++ b/src/main/java/org/opensearch/knn/index/KNNVectorScriptDocValues.java @@ -5,34 +5,41 @@ package org.opensearch.knn.index; +import java.io.IOException; +import java.util.Objects; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; import org.apache.lucene.index.BinaryDocValues; -import org.apache.lucene.util.BytesRef; +import org.apache.lucene.index.ByteVectorValues; +import org.apache.lucene.index.FloatVectorValues; +import org.apache.lucene.search.DocIdSetIterator; import org.opensearch.ExceptionsHelper; import org.opensearch.index.fielddata.ScriptDocValues; -import org.opensearch.knn.index.codec.util.KNNVectorSerializer; -import org.opensearch.knn.index.codec.util.KNNVectorSerializerFactory; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -public final class KNNVectorScriptDocValues extends ScriptDocValues { +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public abstract class KNNVectorScriptDocValues extends ScriptDocValues { - private final BinaryDocValues binaryDocValues; + private final DocIdSetIterator vectorValues; private final String fieldName; - private boolean docExists; - - public KNNVectorScriptDocValues(BinaryDocValues binaryDocValues, String fieldName) { - this.binaryDocValues = binaryDocValues; - this.fieldName = fieldName; - } + @Getter + private final VectorDataType vectorDataType; + private boolean docExists = false; + private int lastDocID = -1; @Override public void setNextDocId(int docId) throws IOException { - if (binaryDocValues.advanceExact(docId)) { - docExists = true; - return; + if (docId < lastDocID) { + throw new IllegalArgumentException("docs were sent out-of-order: lastDocID=" + lastDocID + " vs docID=" + docId); + } + + lastDocID = docId; + + int curDocID = vectorValues.docID(); + if (lastDocID > curDocID) { + curDocID = vectorValues.advance(docId); } - docExists = false; + docExists = lastDocID == curDocID; } public float[] getValue() { @@ -47,16 +54,14 @@ public float[] getValue() { throw new IllegalStateException(errorMessage); } try { - BytesRef value = binaryDocValues.binaryValue(); - ByteArrayInputStream byteStream = new ByteArrayInputStream(value.bytes, value.offset, value.length); - final KNNVectorSerializer vectorSerializer = KNNVectorSerializerFactory.getSerializerByStreamContent(byteStream); - final float[] vector = vectorSerializer.byteToFloatArray(byteStream); - return vector; + return doGetValue(); } catch (IOException e) { throw ExceptionsHelper.convertToOpenSearchException(e); } } + protected abstract float[] doGetValue() throws IOException; + @Override public int size() { return docExists ? 1 : 0; @@ -66,4 +71,89 @@ public int size() { public float[] get(int i) { throw new UnsupportedOperationException("knn vector does not support this operation"); } + + /** + * Creates a KNNVectorScriptDocValues object based on the provided parameters. + * + * @param values The DocIdSetIterator representing the vector values. + * @param fieldName The name of the field. + * @param vectorDataType The data type of the vector. + * @return A KNNVectorScriptDocValues object based on the type of the values. + * @throws IllegalArgumentException If the type of values is unsupported. + */ + public static KNNVectorScriptDocValues create(DocIdSetIterator values, String fieldName, VectorDataType vectorDataType) { + Objects.requireNonNull(values, "values must not be null"); + if (values instanceof ByteVectorValues) { + return new KNNByteVectorScriptDocValues((ByteVectorValues) values, fieldName, vectorDataType); + } else if (values instanceof FloatVectorValues) { + return new KNNFloatVectorScriptDocValues((FloatVectorValues) values, fieldName, vectorDataType); + } else if (values instanceof BinaryDocValues) { + return new KNNNativeVectorScriptDocValues((BinaryDocValues) values, fieldName, vectorDataType); + } else { + throw new IllegalArgumentException("Unsupported values type: " + values.getClass()); + } + } + + private static final class KNNByteVectorScriptDocValues extends KNNVectorScriptDocValues { + private final ByteVectorValues values; + + KNNByteVectorScriptDocValues(ByteVectorValues values, String field, VectorDataType type) { + super(values, field, type); + this.values = values; + } + + @Override + protected float[] doGetValue() throws IOException { + byte[] bytes = values.vectorValue(); + float[] value = new float[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + value[i] = (float) bytes[i]; + } + return value; + } + } + + private static final class KNNFloatVectorScriptDocValues extends KNNVectorScriptDocValues { + private final FloatVectorValues values; + + KNNFloatVectorScriptDocValues(FloatVectorValues values, String field, VectorDataType type) { + super(values, field, type); + this.values = values; + } + + @Override + protected float[] doGetValue() throws IOException { + return values.vectorValue(); + } + } + + private static final class KNNNativeVectorScriptDocValues extends KNNVectorScriptDocValues { + private final BinaryDocValues values; + + KNNNativeVectorScriptDocValues(BinaryDocValues values, String field, VectorDataType type) { + super(values, field, type); + this.values = values; + } + + @Override + protected float[] doGetValue() throws IOException { + return getVectorDataType().getVectorFromBytesRef(values.binaryValue()); + } + } + + /** + * Creates an empty KNNVectorScriptDocValues object based on the provided field name and vector data type. + * + * @param fieldName The name of the field. + * @param type The data type of the vector. + * @return An empty KNNVectorScriptDocValues object. + */ + public static KNNVectorScriptDocValues emptyValues(String fieldName, VectorDataType type) { + return new KNNVectorScriptDocValues(DocIdSetIterator.empty(), fieldName, type) { + @Override + protected float[] doGetValue() throws IOException { + throw new UnsupportedOperationException("empty values"); + } + }; + } } diff --git a/src/main/java/org/opensearch/knn/index/KNNVectorSimilarityFunction.java b/src/main/java/org/opensearch/knn/index/KNNVectorSimilarityFunction.java new file mode 100644 index 000000000..7eca6287c --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/KNNVectorSimilarityFunction.java @@ -0,0 +1,53 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index; + +import org.apache.lucene.index.VectorSimilarityFunction; +import org.opensearch.knn.plugin.script.KNNScoringUtil; + +/** + * Wrapper class of VectorSimilarityFunction to support more function than what Lucene provides + */ +public enum KNNVectorSimilarityFunction { + EUCLIDEAN(VectorSimilarityFunction.EUCLIDEAN), + DOT_PRODUCT(VectorSimilarityFunction.DOT_PRODUCT), + COSINE(VectorSimilarityFunction.COSINE), + MAXIMUM_INNER_PRODUCT(VectorSimilarityFunction.MAXIMUM_INNER_PRODUCT), + HAMMING(null) { + @Override + public float compare(float[] v1, float[] v2) { + throw new IllegalStateException("Hamming space is not supported with float vectors"); + } + + @Override + public float compare(byte[] v1, byte[] v2) { + return 1.0f / (1 + KNNScoringUtil.calculateHammingBit(v1, v2)); + } + + @Override + public VectorSimilarityFunction getVectorSimilarityFunction() { + throw new IllegalStateException("VectorSimilarityFunction is not available for Hamming space"); + } + }; + + private final VectorSimilarityFunction vectorSimilarityFunction; + + KNNVectorSimilarityFunction(final VectorSimilarityFunction vectorSimilarityFunction) { + this.vectorSimilarityFunction = vectorSimilarityFunction; + } + + public VectorSimilarityFunction getVectorSimilarityFunction() { + return vectorSimilarityFunction; + } + + public float compare(float[] var1, float[] var2) { + return vectorSimilarityFunction.compare(var1, var2); + } + + public float compare(byte[] var1, byte[] var2) { + return vectorSimilarityFunction.compare(var1, var2); + } +} diff --git a/src/main/java/org/opensearch/knn/index/KNNWeight.java b/src/main/java/org/opensearch/knn/index/KNNWeight.java deleted file mode 100644 index 7defb1eeb..000000000 --- a/src/main/java/org/opensearch/knn/index/KNNWeight.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.opensearch.knn.index; - -import org.opensearch.knn.common.KNNConstants; -import org.opensearch.knn.jni.JNIService; -import org.opensearch.knn.index.memory.NativeMemoryAllocation; -import org.opensearch.knn.index.memory.NativeMemoryCacheManager; -import org.opensearch.knn.index.memory.NativeMemoryEntryContext; -import org.opensearch.knn.index.memory.NativeMemoryLoadStrategy; -import org.opensearch.knn.index.util.KNNEngine; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.lucene.index.FieldInfo; -import org.apache.lucene.index.FilterLeafReader; -import org.apache.lucene.index.LeafReaderContext; -import org.apache.lucene.index.SegmentReader; -import org.apache.lucene.search.DocIdSetIterator; -import org.apache.lucene.search.Explanation; -import org.apache.lucene.search.Scorer; -import org.apache.lucene.search.Weight; -import org.apache.lucene.store.FSDirectory; -import org.apache.lucene.store.FilterDirectory; -import org.apache.lucene.util.DocIdSetBuilder; -import org.opensearch.common.io.PathUtils; -import org.opensearch.knn.indices.ModelDao; -import org.opensearch.knn.indices.ModelMetadata; -import org.opensearch.knn.plugin.stats.KNNCounter; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; - -import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; -import static org.opensearch.knn.common.KNNConstants.MODEL_ID; -import static org.opensearch.knn.common.KNNConstants.SPACE_TYPE; -import static org.opensearch.knn.index.IndexUtil.getParametersAtLoading; -import static org.opensearch.knn.plugin.stats.KNNCounter.GRAPH_QUERY_ERRORS; - -/** - * Calculate query weights and build query scorers. - */ -public class KNNWeight extends Weight { - private static Logger logger = LogManager.getLogger(KNNWeight.class); - private static ModelDao modelDao; - - private final KNNQuery knnQuery; - private final float boost; - - private NativeMemoryCacheManager nativeMemoryCacheManager; - - public KNNWeight(KNNQuery query, float boost) { - super(query); - this.knnQuery = query; - this.boost = boost; - this.nativeMemoryCacheManager = NativeMemoryCacheManager.getInstance(); - } - - public static void initialize(ModelDao modelDao) { - KNNWeight.modelDao = modelDao; - } - - @Override - public Explanation explain(LeafReaderContext context, int doc) { - return Explanation.match(1.0f, "No Explanation"); - } - - @Override - public Scorer scorer(LeafReaderContext context) throws IOException { - SegmentReader reader = (SegmentReader) FilterLeafReader.unwrap(context.reader()); - String directory = ((FSDirectory) FilterDirectory.unwrap(reader.directory())).getDirectory().toString(); - - FieldInfo fieldInfo = reader.getFieldInfos().fieldInfo(knnQuery.getField()); - - if (fieldInfo == null) { - logger.debug("[KNN] Field info not found for {}:{}", knnQuery.getField(), reader.getSegmentName()); - return null; - } - - KNNEngine knnEngine; - SpaceType spaceType; - - // Check if a modelId exists. If so, the space type and engine will need to be picked up from the model's - // metadata. - String modelId = fieldInfo.getAttribute(MODEL_ID); - if (modelId != null) { - ModelMetadata modelMetadata = modelDao.getMetadata(modelId); - if (modelMetadata == null) { - throw new RuntimeException("Model \"" + modelId + "\" does not exist."); - } - - knnEngine = modelMetadata.getKnnEngine(); - spaceType = modelMetadata.getSpaceType(); - } else { - String engineName = fieldInfo.attributes().getOrDefault(KNN_ENGINE, KNNEngine.NMSLIB.getName()); - knnEngine = KNNEngine.getEngine(engineName); - String spaceTypeName = fieldInfo.attributes().getOrDefault(SPACE_TYPE, SpaceType.L2.getValue()); - spaceType = SpaceType.getSpace(spaceTypeName); - } - - /* - * In case of compound file, extension would be + c otherwise - */ - String engineExtension = reader.getSegmentInfo().info.getUseCompoundFile() - ? knnEngine.getExtension() + KNNConstants.COMPOUND_EXTENSION - : knnEngine.getExtension(); - String engineSuffix = knnQuery.getField() + engineExtension; - List engineFiles = reader.getSegmentInfo() - .files() - .stream() - .filter(fileName -> fileName.endsWith(engineSuffix)) - .collect(Collectors.toList()); - - if (engineFiles.isEmpty()) { - logger.debug("[KNN] No engine index found for field {} for segment {}", knnQuery.getField(), reader.getSegmentName()); - return null; - } - - Path indexPath = PathUtils.get(directory, engineFiles.get(0)); - final KNNQueryResult[] results; - KNNCounter.GRAPH_QUERY_REQUESTS.increment(); - - // We need to first get index allocation - NativeMemoryAllocation indexAllocation; - try { - indexAllocation = nativeMemoryCacheManager.get( - new NativeMemoryEntryContext.IndexEntryContext( - indexPath.toString(), - NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance(), - getParametersAtLoading(spaceType, knnEngine, knnQuery.getIndexName()), - knnQuery.getIndexName() - ), - true - ); - } catch (ExecutionException e) { - GRAPH_QUERY_ERRORS.increment(); - throw new RuntimeException(e); - } - - // Now that we have the allocation, we need to readLock it - indexAllocation.readLock(); - - try { - if (indexAllocation.isClosed()) { - throw new RuntimeException("Index has already been closed"); - } - - results = JNIService.queryIndex( - indexAllocation.getMemoryAddress(), - knnQuery.getQueryVector(), - knnQuery.getK(), - knnEngine.getName() - ); - } catch (Exception e) { - GRAPH_QUERY_ERRORS.increment(); - throw new RuntimeException(e); - } finally { - indexAllocation.readUnlock(); - } - - /* - * Scores represent the distance of the documents with respect to given query vector. - * Lesser the score, the closer the document is to the query vector. - * Since by default results are retrieved in the descending order of scores, to get the nearest - * neighbors we are inverting the scores. - */ - if (results.length == 0) { - logger.debug("[KNN] Query yielded 0 results"); - return null; - } - - Map scores = Arrays.stream(results) - .collect(Collectors.toMap(KNNQueryResult::getId, result -> knnEngine.score(result.getScore(), spaceType))); - int maxDoc = Collections.max(scores.keySet()) + 1; - DocIdSetBuilder docIdSetBuilder = new DocIdSetBuilder(maxDoc); - DocIdSetBuilder.BulkAdder setAdder = docIdSetBuilder.grow(maxDoc); - Arrays.stream(results).forEach(result -> setAdder.add(result.getId())); - DocIdSetIterator docIdSetIter = docIdSetBuilder.build().iterator(); - return new KNNScorer(this, docIdSetIter, scores, boost); - } - - @Override - public boolean isCacheable(LeafReaderContext context) { - return true; - } - - public static float normalizeScore(float score) { - if (score >= 0) return 1 / (1 + score); - return -score + 1; - } -} diff --git a/src/main/java/org/opensearch/knn/index/KnnCircuitBreakerException.java b/src/main/java/org/opensearch/knn/index/KnnCircuitBreakerException.java new file mode 100644 index 000000000..0bcae8dff --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/KnnCircuitBreakerException.java @@ -0,0 +1,65 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index; + +/** + * An exception to be thrown when the k-NN circuit breaker is triggered. + */ +public class KnnCircuitBreakerException extends RuntimeException { + + /** + * Constructs a KnnCircuitBreakerException with the specified detail + * message. A detail message is a String that describes this particular + * exception. + * + * @param message the String that contains a detailed message + */ + public KnnCircuitBreakerException(final String message) { + super(message); + } + + /** + * Constructs a new exception with the specified detail message and + * cause. + * + *

Note that the detail message associated with {@code cause} is + * not automatically incorporated in this exception's detail + * message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link Throwable#getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link Throwable#getCause()} method). (A {@code null} value + * is permitted, and indicates that the cause is nonexistent or + * unknown.) + */ + public KnnCircuitBreakerException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs a new exception with the specified cause and a detail + * message of {@code (cause==null ? null : cause.toString())} (which + * typically contains the class and detail message of {@code cause}). + * This constructor is useful for exceptions that are little more than + * wrappers for other throwables (for example, {@link + * java.security.PrivilegedActionException}). + * + * @param cause the cause (which is saved for later retrieval by the + * {@link Throwable#getCause()} method). (A {@code null} value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + */ + public KnnCircuitBreakerException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/org/opensearch/knn/index/MethodComponentContext.java b/src/main/java/org/opensearch/knn/index/MethodComponentContext.java deleted file mode 100644 index c43c9f77d..000000000 --- a/src/main/java/org/opensearch/knn/index/MethodComponentContext.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.knn.index; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; -import org.opensearch.common.io.stream.Writeable; -import org.opensearch.common.xcontent.ToXContentFragment; -import org.opensearch.common.xcontent.XContentBuilder; -import org.opensearch.index.mapper.MapperParsingException; - -import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; -import org.apache.commons.lang.builder.EqualsBuilder; -import org.apache.commons.lang.builder.HashCodeBuilder; - -import static org.opensearch.knn.common.KNNConstants.NAME; -import static org.opensearch.knn.common.KNNConstants.PARAMETERS; - -/** - * MethodComponentContext represents a single user provided building block of a knn library index. - * - * Each component is composed of a name and a map of parameters. - */ -public class MethodComponentContext implements ToXContentFragment, Writeable { - - private static final Logger logger = LogManager.getLogger(MethodComponentContext.class); - - private final String name; - private final Map parameters; - - /** - * Constructor - * - * @param name component name - * @param parameters component parameters - */ - public MethodComponentContext(String name, Map parameters) { - this.name = name; - this.parameters = parameters; - } - - /** - * Constructor from stream. - * - * @param in StreamInput - * @throws IOException on stream failure - */ - public MethodComponentContext(StreamInput in) throws IOException { - this.name = in.readString(); - - // Due to backwards compatibility issue, parameters could be null. To prevent any null pointer exceptions, - // do not read if their are no bytes left is null. Make sure this is in sync with the fellow read method. For - // more information, refer to https://github.com/opensearch-project/k-NN/issues/353. - if (in.available() > 0) { - this.parameters = in.readMap(StreamInput::readString, new ParameterMapValueReader()); - } else { - this.parameters = null; - } - } - - /** - * Parses the object into MethodComponentContext - * - * @param in Object to be parsed - * @return MethodComponentContext - */ - public static MethodComponentContext parse(Object in) { - if (!(in instanceof Map)) { - throw new MapperParsingException("Unable to parse MethodComponent"); - } - - @SuppressWarnings("unchecked") - Map methodMap = (Map) in; - String name = ""; - Map parameters = new HashMap<>(); - - String key; - Object value; - - for (Map.Entry methodEntry : methodMap.entrySet()) { - key = methodEntry.getKey(); - value = methodEntry.getValue(); - - if (NAME.equals(key)) { - if (!(value instanceof String)) { - throw new MapperParsingException("Component name should be a string"); - } - name = (String) value; - } else if (PARAMETERS.equals(key)) { - if (value == null) { - parameters = null; - continue; - } - - if (!(value instanceof Map)) { - throw new MapperParsingException("Unable to parse parameters for method component"); - } - - // Check to interpret map parameters as sub-methodComponentContexts - @SuppressWarnings("unchecked") - Map parameters1 = ((Map) value).entrySet() - .stream() - .collect(Collectors.toMap(Map.Entry::getKey, e -> { - Object v = e.getValue(); - if (v instanceof Map) { - return MethodComponentContext.parse(v); - } - return v; - })); - - parameters = parameters1; - } else { - throw new MapperParsingException("Invalid parameter for MethodComponentContext: " + key); - } - } - - if (name.isEmpty()) { - throw new MapperParsingException(NAME + " needs to be set"); - } - - return new MethodComponentContext(name, parameters); - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.field(NAME, name); - // Due to backwards compatibility issue, parameters could be null. To prevent any null pointer exceptions, - // we just create the null field. If parameters are not null, we created a nested structure. For more - // information, refer to https://github.com/opensearch-project/k-NN/issues/353. - if (parameters == null) { - builder.field(PARAMETERS, (String) null); - } else { - builder.startObject(PARAMETERS); - parameters.forEach((key, value) -> { - try { - if (value instanceof MethodComponentContext) { - builder.startObject(key); - ((MethodComponentContext) value).toXContent(builder, params); - builder.endObject(); - } else { - builder.field(key, value); - } - } catch (IOException ioe) { - throw new RuntimeException("Unable to generate xcontent for method component"); - } - - }); - builder.endObject(); - } - - return builder; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null || getClass() != obj.getClass()) return false; - MethodComponentContext other = (MethodComponentContext) obj; - - EqualsBuilder equalsBuilder = new EqualsBuilder(); - equalsBuilder.append(name, other.name); - equalsBuilder.append(parameters, other.parameters); - return equalsBuilder.isEquals(); - } - - @Override - public int hashCode() { - return new HashCodeBuilder().append(name).append(parameters).toHashCode(); - } - - /** - * Gets the name of the component - * - * @return name - */ - public String getName() { - return name; - } - - /** - * Gets the parameters of the component - * - * @return parameters - */ - public Map getParameters() { - // Due to backwards compatibility issue, parameters could be null. To prevent any null pointer exceptions, - // return an empty map if parameters is null. For more information, refer to - // https://github.com/opensearch-project/k-NN/issues/353. - if (parameters == null) { - return Collections.emptyMap(); - } - return parameters; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeString(this.name); - - // Due to backwards compatibility issue, parameters could be null. To prevent any null pointer exceptions, - // do not write if parameters is null. Make sure this is in sync with the fellow read method. For more - // information, refer to https://github.com/opensearch-project/k-NN/issues/353. - if (this.parameters != null) { - out.writeMap(this.parameters, StreamOutput::writeString, new ParameterMapValueWriter()); - } - } - - // Because the generic StreamOutput writeMap method can only write generic values, we need to create a custom one - // that handles the case when a parameter value is another method component context. - private static class ParameterMapValueWriter implements Writer { - - private ParameterMapValueWriter() {} - - @Override - public void write(StreamOutput out, Object o) throws IOException { - if (o instanceof MethodComponentContext) { - out.writeBoolean(true); - ((MethodComponentContext) o).writeTo(out); - } else { - out.writeBoolean(false); - out.writeGenericValue(o); - } - } - } - - // Because the generic StreamInput writeMap method can only read generic values, we need to create a custom one - // that handles the case when a parameter value is another method component context. - private static class ParameterMapValueReader implements Reader { - - private ParameterMapValueReader() {} - - @Override - public Object read(StreamInput in) throws IOException { - boolean isValueMethodComponentContext = in.readBoolean(); - if (isValueMethodComponentContext) { - return new MethodComponentContext(in); - } - return in.readGenericValue(); - } - } -} diff --git a/src/main/java/org/opensearch/knn/index/Parameter.java b/src/main/java/org/opensearch/knn/index/Parameter.java deleted file mode 100644 index 4d69e7838..000000000 --- a/src/main/java/org/opensearch/knn/index/Parameter.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.knn.index; - -import org.opensearch.common.ValidationException; - -import java.util.Map; -import java.util.function.Predicate; - -/** - * Parameter that can be set for a method component - * - * @param Type parameter takes - */ -public abstract class Parameter { - - private String name; - private T defaultValue; - protected Predicate validator; - - /** - * Constructor - * - * @param name of the parameter - * @param defaultValue of the parameter - * @param validator used to validate a parameter value passed - */ - public Parameter(String name, T defaultValue, Predicate validator) { - this.name = name; - this.defaultValue = defaultValue; - this.validator = validator; - } - - /** - * Getter for parameter name - * - * @return parameter name - */ - public String getName() { - return name; - } - - /** - * Get default value for parameter - * - * @return default value of the parameter - */ - public T getDefaultValue() { - return defaultValue; - } - - /** - * Check if the value passed in is valid - * - * @param value to be checked - * @return ValidationException produced by validation errors; null if no validations errors. - */ - public abstract ValidationException validate(Object value); - - /** - * Integer method parameter - */ - public static class IntegerParameter extends Parameter { - public IntegerParameter(String name, Integer defaultValue, Predicate validator) { - super(name, defaultValue, validator); - } - - @Override - public ValidationException validate(Object value) { - ValidationException validationException = null; - if (!(value instanceof Integer)) { - validationException = new ValidationException(); - validationException.addValidationError( - String.format("Value not of type Integer for Integer " + "parameter \"%s\".", getName()) - ); - return validationException; - } - - if (!validator.test((Integer) value)) { - validationException = new ValidationException(); - validationException.addValidationError( - String.format("Parameter validation failed for Integer " + "parameter \"%s\".", getName()) - ); - } - return validationException; - } - } - - /** - * MethodContext parameter. Some methods require sub-methods in order to implement some kind of functionality. For - * instance, faiss methods can contain an encoder along side the approximate nearest neighbor function to compress - * the input. This parameter makes it possible to add sub-methods to methods to support this kind of functionality - */ - public static class MethodComponentContextParameter extends Parameter { - - private Map methodComponents; - - /** - * Constructor - * - * @param name of the parameter - * @param defaultValue value to assign this parameter if it is not set - * @param methodComponents valid components that the MethodComponentContext can map to - */ - public MethodComponentContextParameter( - String name, - MethodComponentContext defaultValue, - Map methodComponents - ) { - super(name, defaultValue, methodComponentContext -> { - if (!methodComponents.containsKey(methodComponentContext.getName())) { - return false; - } - - return methodComponents.get(methodComponentContext.getName()).validate(methodComponentContext) == null; - }); - this.methodComponents = methodComponents; - } - - @Override - public ValidationException validate(Object value) { - ValidationException validationException = null; - if (!(value instanceof MethodComponentContext)) { - validationException = new ValidationException(); - validationException.addValidationError( - String.format("Value not of type MethodComponentContext for" + " MethodComponentContext parameter \"%s\".", getName()) - ); - return validationException; - } - - if (!validator.test((MethodComponentContext) value)) { - validationException = new ValidationException(); - validationException.addValidationError("Parameter validation failed."); - validationException.addValidationError( - String.format("Parameter validation failed for " + "MethodComponentContext parameter \"%s\".", getName()) - ); - } - - return validationException; - } - - /** - * Get method component by name - * - * @param name name of method component - * @return MethodComponent that name maps to - */ - public MethodComponent getMethodComponent(String name) { - return methodComponents.get(name); - } - } -} diff --git a/src/main/java/org/opensearch/knn/index/SpaceType.java b/src/main/java/org/opensearch/knn/index/SpaceType.java index ed96cce03..abe265a01 100644 --- a/src/main/java/org/opensearch/knn/index/SpaceType.java +++ b/src/main/java/org/opensearch/knn/index/SpaceType.java @@ -11,8 +11,14 @@ package org.opensearch.knn.index; +import java.util.Arrays; +import java.util.Locale; + import java.util.HashSet; import java.util.Set; +import java.util.stream.Collectors; + +import static org.opensearch.knn.common.KNNVectorUtil.isZeroVector; /** * Enum contains spaces supported for approximate nearest neighbor search in the k-NN plugin. Each engine's methods are @@ -21,17 +27,66 @@ * nmslib calls the inner_product space "negdotprod". This translation should take place in the nmslib's jni layer. */ public enum SpaceType { + // This undefined space type is used to indicate that space type is not provided by user + // Later, we need to assign a default value based on data type + UNDEFINED("undefined") { + @Override + public float scoreTranslation(final float rawScore) { + throw new IllegalStateException("Unsupported method"); + } + + @Override + public void validateVectorDataType(VectorDataType vectorDataType) { + throw new IllegalStateException("Unsupported method"); + } + }, L2("l2") { @Override public float scoreTranslation(float rawScore) { return 1 / (1 + rawScore); } + + @Override + public KNNVectorSimilarityFunction getKnnVectorSimilarityFunction() { + return KNNVectorSimilarityFunction.EUCLIDEAN; + } + + @Override + public float scoreToDistanceTranslation(float score) { + if (score == 0) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "score cannot be 0 when space type is [%s]", getValue())); + } + return 1 / score - 1; + } }, COSINESIMIL("cosinesimil") { @Override public float scoreTranslation(float rawScore) { return 1 / (1 + rawScore); } + + @Override + public KNNVectorSimilarityFunction getKnnVectorSimilarityFunction() { + return KNNVectorSimilarityFunction.COSINE; + } + + @Override + public void validateVector(byte[] vector) { + if (isZeroVector(vector)) { + throw new IllegalArgumentException( + String.format(Locale.ROOT, "zero vector is not supported when space type is [%s]", getValue()) + ); + } + } + + @Override + public void validateVector(float[] vector) { + if (isZeroVector(vector)) { + throw new IllegalArgumentException( + String.format(Locale.ROOT, "zero vector is not supported when space type is [%s]", getValue()) + ); + } + } }, L1("l1") { @Override @@ -61,15 +116,46 @@ public float scoreTranslation(float rawScore) { } return -rawScore + 1; } + + @Override + public KNNVectorSimilarityFunction getKnnVectorSimilarityFunction() { + return KNNVectorSimilarityFunction.MAXIMUM_INNER_PRODUCT; + } }, - HAMMING_BIT("hammingbit") { + HAMMING("hamming") { @Override public float scoreTranslation(float rawScore) { return 1 / (1 + rawScore); } + + @Override + public void validateVectorDataType(VectorDataType vectorDataType) { + if (VectorDataType.BINARY != vectorDataType) { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "Space type [%s] is not supported with [%s] data type", + getValue(), + vectorDataType.getValue() + ) + ); + } + } + + @Override + public KNNVectorSimilarityFunction getKnnVectorSimilarityFunction() { + return KNNVectorSimilarityFunction.HAMMING; + } }; public static SpaceType DEFAULT = L2; + public static SpaceType DEFAULT_BINARY = HAMMING; + + private static final String[] VALID_VALUES = Arrays.stream(SpaceType.values()) + .filter(space -> space != SpaceType.UNDEFINED) + .map(SpaceType::getValue) + .collect(Collectors.toList()) + .toArray(new String[0]); private final String value; @@ -79,6 +165,46 @@ public float scoreTranslation(float rawScore) { public abstract float scoreTranslation(float rawScore); + /** + * Get KNNVectorSimilarityFunction that maps to this SpaceType + * + * @return KNNVectorSimilarityFunction + */ + public KNNVectorSimilarityFunction getKnnVectorSimilarityFunction() { + throw new UnsupportedOperationException(String.format("Space [%s] does not have a knn vector similarity function", getValue())); + } + + /** + * Validate if the given byte vector is supported by this space type + * + * @param vector the given vector + */ + public void validateVector(byte[] vector) { + // do nothing + } + + /** + * Validate if the given float vector is supported by this space type + * + * @param vector the given vector + */ + public void validateVector(float[] vector) { + // do nothing + } + + /** + * Validate if given vector data type is supported by this space type + * + * @param vectorDataType the given vector data type + */ + public void validateVectorDataType(VectorDataType vectorDataType) { + if (VectorDataType.FLOAT != vectorDataType && VectorDataType.BYTE != vectorDataType) { + throw new IllegalArgumentException( + String.format(Locale.ROOT, "Space type [%s] is not supported with [%s] data type", getValue(), vectorDataType.getValue()) + ); + } + } + /** * Get space type name in engine * @@ -103,6 +229,18 @@ public static SpaceType getSpace(String spaceTypeName) { return currentSpaceType; } } - throw new IllegalArgumentException("Unable to find space: " + spaceTypeName); + throw new IllegalArgumentException( + String.format(Locale.ROOT, "Unable to find space: %s . Valid values are: %s", spaceTypeName, Arrays.toString(VALID_VALUES)) + ); + } + + /** + * Translate a score to a distance for this space type + * + * @param score score to translate + * @return translated distance + */ + public float scoreToDistanceTranslation(float score) { + throw new UnsupportedOperationException(String.format("Space [%s] does not have a score to distance translation", getValue())); } } diff --git a/src/main/java/org/opensearch/knn/index/VectorDataType.java b/src/main/java/org/opensearch/knn/index/VectorDataType.java new file mode 100644 index 000000000..4827a4582 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/VectorDataType.java @@ -0,0 +1,188 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.apache.lucene.document.FieldType; +import org.apache.lucene.document.KnnByteVectorField; +import org.apache.lucene.document.KnnVectorField; +import org.apache.lucene.index.VectorSimilarityFunction; +import org.apache.lucene.util.BytesRef; +import org.opensearch.knn.index.codec.util.KNNVectorSerializer; +import org.opensearch.knn.index.codec.util.KNNVectorSerializerFactory; +import org.opensearch.knn.index.memory.NativeMemoryAllocation; +import org.opensearch.knn.jni.JNICommons; +import org.opensearch.knn.training.BinaryTrainingDataConsumer; +import org.opensearch.knn.training.ByteTrainingDataConsumer; +import org.opensearch.knn.training.FloatTrainingDataConsumer; +import org.opensearch.knn.training.TrainingDataConsumer; + +import java.util.Arrays; +import java.util.Locale; +import java.util.Objects; +import java.util.stream.Collectors; + +import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; + +/** + * Enum contains data_type of vectors + * Lucene supports byte and float data type + * NMSLib supports only float data type + * Faiss supports binary and float data type + */ +@AllArgsConstructor +public enum VectorDataType { + BINARY("binary") { + + @Override + public FieldType createKnnVectorFieldType(int dimension, VectorSimilarityFunction vectorSimilarityFunction) { + throw new IllegalStateException("Unsupported method"); + } + + @Override + public float[] getVectorFromBytesRef(BytesRef binaryValue) { + float[] vector = new float[binaryValue.length]; + int i = 0; + int j = binaryValue.offset; + + while (i < binaryValue.length) { + vector[i++] = binaryValue.bytes[j++]; + } + return vector; + } + + @Override + public TrainingDataConsumer getTrainingDataConsumer(NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation) { + return new BinaryTrainingDataConsumer(trainingDataAllocation); + } + + @Override + public void freeNativeMemory(long memoryAddress) { + JNICommons.freeBinaryVectorData(memoryAddress); + } + }, + BYTE("byte") { + + @Override + public FieldType createKnnVectorFieldType(int dimension, VectorSimilarityFunction vectorSimilarityFunction) { + return KnnByteVectorField.createFieldType(dimension, vectorSimilarityFunction); + } + + @Override + public float[] getVectorFromBytesRef(BytesRef binaryValue) { + float[] vector = new float[binaryValue.length]; + int i = 0; + int j = binaryValue.offset; + + while (i < binaryValue.length) { + vector[i++] = binaryValue.bytes[j++]; + } + return vector; + } + + @Override + public TrainingDataConsumer getTrainingDataConsumer(NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation) { + return new ByteTrainingDataConsumer(trainingDataAllocation); + } + + @Override + public void freeNativeMemory(long memoryAddress) { + JNICommons.freeByteVectorData(memoryAddress); + } + }, + FLOAT("float") { + + @Override + public FieldType createKnnVectorFieldType(int dimension, VectorSimilarityFunction vectorSimilarityFunction) { + return KnnVectorField.createFieldType(dimension, vectorSimilarityFunction); + } + + @Override + public float[] getVectorFromBytesRef(BytesRef binaryValue) { + final KNNVectorSerializer vectorSerializer = KNNVectorSerializerFactory.getSerializerByBytesRef(binaryValue); + return vectorSerializer.byteToFloatArray(binaryValue); + } + + @Override + public TrainingDataConsumer getTrainingDataConsumer(NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation) { + return new FloatTrainingDataConsumer(trainingDataAllocation); + } + + @Override + public void freeNativeMemory(long memoryAddress) { + JNICommons.freeVectorData(memoryAddress); + } + + }; + + public static final String SUPPORTED_VECTOR_DATA_TYPES = Arrays.stream(VectorDataType.values()) + .map(VectorDataType::getValue) + .collect(Collectors.joining(",")); + @Getter + private final String value; + + /** + * Creates a KnnVectorFieldType based on the VectorDataType using the provided dimension and + * VectorSimilarityFunction. + * + * @param dimension Dimension of the vector + * @param vectorSimilarityFunction VectorSimilarityFunction for a given spaceType + * @return FieldType + */ + public abstract FieldType createKnnVectorFieldType(int dimension, VectorSimilarityFunction vectorSimilarityFunction); + + /** + * Deserializes float vector from BytesRef. + * + * @param binaryValue Binary Value + * @return float vector deserialized from binary value + */ + public abstract float[] getVectorFromBytesRef(BytesRef binaryValue); + + /** + * @param trainingDataAllocation training data that has been allocated in native memory + * @return TrainingDataConsumer which consumes training data + */ + public abstract TrainingDataConsumer getTrainingDataConsumer(NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation); + + /** + * @param memoryAddress address to be freed + */ + public abstract void freeNativeMemory(long memoryAddress); + + /** + * Validates if given VectorDataType is in the list of supported data types. + * @param vectorDataType VectorDataType + * @return the same VectorDataType if it is in the supported values + * throws Exception if an invalid value is provided. + */ + public static VectorDataType get(String vectorDataType) { + Objects.requireNonNull( + vectorDataType, + String.format( + Locale.ROOT, + "[%s] should not be null. Supported types are [%s]", + VECTOR_DATA_TYPE_FIELD, + SUPPORTED_VECTOR_DATA_TYPES + ) + ); + try { + return VectorDataType.valueOf(vectorDataType.toUpperCase(Locale.ROOT)); + } catch (Exception e) { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "Invalid value provided for [%s] field. Supported values are [%s]", + VECTOR_DATA_TYPE_FIELD, + SUPPORTED_VECTOR_DATA_TYPES + ) + ); + } + } + + public static VectorDataType DEFAULT = FLOAT; +} diff --git a/src/main/java/org/opensearch/knn/index/VectorField.java b/src/main/java/org/opensearch/knn/index/VectorField.java index c88630f6c..f28ef6238 100644 --- a/src/main/java/org/opensearch/knn/index/VectorField.java +++ b/src/main/java/org/opensearch/knn/index/VectorField.java @@ -23,4 +23,19 @@ public VectorField(String name, float[] value, IndexableFieldType type) { throw new RuntimeException(e); } } + + /** + * @param name FieldType name + * @param value an array of byte vector values + * @param type FieldType to build DocValues + */ + public VectorField(String name, byte[] value, IndexableFieldType type) { + super(name, new BytesRef(), type); + try { + this.setBytesValue(value); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } } diff --git a/src/main/java/org/opensearch/knn/index/VectorQueryType.java b/src/main/java/org/opensearch/knn/index/VectorQueryType.java new file mode 100644 index 000000000..fb7bfaafd --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/VectorQueryType.java @@ -0,0 +1,61 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index; + +import lombok.Getter; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.plugin.stats.KNNCounter; + +@Getter +public enum VectorQueryType { + K(KNNConstants.K) { + @Override + public KNNCounter getQueryStatCounter() { + return KNNCounter.KNN_QUERY_REQUESTS; + } + + @Override + public KNNCounter getQueryWithFilterStatCounter() { + return KNNCounter.KNN_QUERY_WITH_FILTER_REQUESTS; + } + }, + MIN_SCORE(KNNConstants.MIN_SCORE) { + @Override + public KNNCounter getQueryStatCounter() { + return KNNCounter.MIN_SCORE_QUERY_REQUESTS; + } + + @Override + public KNNCounter getQueryWithFilterStatCounter() { + return KNNCounter.MIN_SCORE_QUERY_WITH_FILTER_REQUESTS; + } + }, + MAX_DISTANCE(KNNConstants.MAX_DISTANCE) { + @Override + public KNNCounter getQueryStatCounter() { + return KNNCounter.MAX_DISTANCE_QUERY_REQUESTS; + } + + @Override + public KNNCounter getQueryWithFilterStatCounter() { + return KNNCounter.MAX_DISTANCE_QUERY_WITH_FILTER_REQUESTS; + } + }; + + private final String queryTypeName; + + VectorQueryType(String queryTypeName) { + this.queryTypeName = queryTypeName; + } + + public abstract KNNCounter getQueryStatCounter(); + + public abstract KNNCounter getQueryWithFilterStatCounter(); + + public boolean isRadialSearch() { + return this == MAX_DISTANCE || this == MIN_SCORE; + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/BasePerFieldKnnVectorsFormat.java b/src/main/java/org/opensearch/knn/index/codec/BasePerFieldKnnVectorsFormat.java new file mode 100644 index 000000000..72187516f --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/BasePerFieldKnnVectorsFormat.java @@ -0,0 +1,161 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec; + +import lombok.AllArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.apache.lucene.codecs.KnnVectorsFormat; +import org.apache.lucene.codecs.hnsw.FlatVectorScorerUtil; +import org.apache.lucene.codecs.lucene99.Lucene99FlatVectorsFormat; +import org.apache.lucene.codecs.perfield.PerFieldKnnVectorsFormat; +import org.opensearch.index.IndexSettings; +import org.opensearch.index.mapper.MapperService; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.codec.KNN990Codec.NativeEngines990KnnVectorsFormat; +import org.opensearch.knn.index.codec.params.KNNScalarQuantizedVectorsFormatParams; +import org.opensearch.knn.index.codec.params.KNNVectorsFormatParams; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.mapper.KNNMappingConfig; +import org.opensearch.knn.index.mapper.KNNVectorFieldType; + +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Supplier; + +import static org.opensearch.knn.common.KNNConstants.LUCENE_SQ_BITS; +import static org.opensearch.knn.common.KNNConstants.LUCENE_SQ_CONFIDENCE_INTERVAL; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; + +/** + * Base class for PerFieldKnnVectorsFormat, builds KnnVectorsFormat based on specific Lucene version + */ +@AllArgsConstructor +@Log4j2 +public abstract class BasePerFieldKnnVectorsFormat extends PerFieldKnnVectorsFormat { + + private final Optional mapperService; + private final int defaultMaxConnections; + private final int defaultBeamWidth; + private final Supplier defaultFormatSupplier; + private final Function vectorsFormatSupplier; + private Function scalarQuantizedVectorsFormatSupplier; + private static final String MAX_CONNECTIONS = "max_connections"; + private static final String BEAM_WIDTH = "beam_width"; + + public BasePerFieldKnnVectorsFormat( + Optional mapperService, + int defaultMaxConnections, + int defaultBeamWidth, + Supplier defaultFormatSupplier, + Function vectorsFormatSupplier + ) { + this.mapperService = mapperService; + this.defaultMaxConnections = defaultMaxConnections; + this.defaultBeamWidth = defaultBeamWidth; + this.defaultFormatSupplier = defaultFormatSupplier; + this.vectorsFormatSupplier = vectorsFormatSupplier; + } + + @Override + public KnnVectorsFormat getKnnVectorsFormatForField(final String field) { + if (isKnnVectorFieldType(field) == false) { + log.debug( + "Initialize KNN vector format for field [{}] with default params [{}] = \"{}\" and [{}] = \"{}\"", + field, + MAX_CONNECTIONS, + defaultMaxConnections, + BEAM_WIDTH, + defaultBeamWidth + ); + return defaultFormatSupplier.get(); + } + KNNVectorFieldType mappedFieldType = (KNNVectorFieldType) mapperService.orElseThrow( + () -> new IllegalStateException( + String.format("Cannot read field type for field [%s] because mapper service is not available", field) + ) + ).fieldType(field); + + final KNNMappingConfig knnMappingConfig = mappedFieldType.getKnnMappingConfig(); + if (knnMappingConfig.getModelId().isPresent()) { + return nativeEngineVectorsFormat(); + } + + final KNNMethodContext knnMethodContext = knnMappingConfig.getKnnMethodContext() + .orElseThrow(() -> new IllegalArgumentException("KNN method context cannot be empty")); + final KNNEngine engine = knnMethodContext.getKnnEngine(); + final Map params = knnMethodContext.getMethodComponentContext().getParameters(); + + if (engine == KNNEngine.LUCENE) { + if (params != null && params.containsKey(METHOD_ENCODER_PARAMETER)) { + KNNScalarQuantizedVectorsFormatParams knnScalarQuantizedVectorsFormatParams = new KNNScalarQuantizedVectorsFormatParams( + params, + defaultMaxConnections, + defaultBeamWidth + ); + if (knnScalarQuantizedVectorsFormatParams.validate(params)) { + log.debug( + "Initialize KNN vector format for field [{}] with params [{}] = \"{}\", [{}] = \"{}\", [{}] = \"{}\", [{}] = \"{}\"", + field, + MAX_CONNECTIONS, + knnScalarQuantizedVectorsFormatParams.getMaxConnections(), + BEAM_WIDTH, + knnScalarQuantizedVectorsFormatParams.getBeamWidth(), + LUCENE_SQ_CONFIDENCE_INTERVAL, + knnScalarQuantizedVectorsFormatParams.getConfidenceInterval(), + LUCENE_SQ_BITS, + knnScalarQuantizedVectorsFormatParams.getBits() + ); + return scalarQuantizedVectorsFormatSupplier.apply(knnScalarQuantizedVectorsFormatParams); + } + } + + KNNVectorsFormatParams knnVectorsFormatParams = new KNNVectorsFormatParams(params, defaultMaxConnections, defaultBeamWidth); + log.debug( + "Initialize KNN vector format for field [{}] with params [{}] = \"{}\" and [{}] = \"{}\"", + field, + MAX_CONNECTIONS, + knnVectorsFormatParams.getMaxConnections(), + BEAM_WIDTH, + knnVectorsFormatParams.getBeamWidth() + ); + return vectorsFormatSupplier.apply(knnVectorsFormatParams); + } + + // All native engines to use NativeEngines990KnnVectorsFormat + return nativeEngineVectorsFormat(); + } + + private NativeEngines990KnnVectorsFormat nativeEngineVectorsFormat() { + // mapperService is already checked for null or valid instance type at caller, hence we don't need + // addition isPresent check here. + int approximateThreshold = getApproximateThresholdValue(); + return new NativeEngines990KnnVectorsFormat( + new Lucene99FlatVectorsFormat(FlatVectorScorerUtil.getLucene99FlatVectorsScorer()), + approximateThreshold + ); + } + + private int getApproximateThresholdValue() { + // This is private method and mapperService is already checked for null or valid instance type before this call + // at caller, hence we don't need additional isPresent check here. + final IndexSettings indexSettings = mapperService.get().getIndexSettings(); + final Integer approximateThresholdValue = indexSettings.getValue(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD_SETTING); + return approximateThresholdValue != null + ? approximateThresholdValue + : KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD_DEFAULT_VALUE; + } + + @Override + public int getMaxDimensions(String fieldName) { + return getKnnVectorsFormatForField(fieldName).getMaxDimensions(fieldName); + } + + private boolean isKnnVectorFieldType(final String field) { + return mapperService.isPresent() && mapperService.get().fieldType(field) instanceof KNNVectorFieldType; + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80BinaryDocValues.java b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80BinaryDocValues.java index 832737a6d..df26766b3 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80BinaryDocValues.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80BinaryDocValues.java @@ -5,6 +5,7 @@ package org.opensearch.knn.index.codec.KNN80Codec; +import lombok.Getter; import org.opensearch.knn.index.codec.util.BinaryDocValuesSub; import org.apache.lucene.index.BinaryDocValues; import org.apache.lucene.index.DocIDMerger; @@ -15,10 +16,13 @@ /** * A per-document kNN numeric value. */ -class KNN80BinaryDocValues extends BinaryDocValues { +public class KNN80BinaryDocValues extends BinaryDocValues { private DocIDMerger docIDMerger; + @Getter + private long totalLiveDocs; + KNN80BinaryDocValues(DocIDMerger docIdMerger) { this.docIDMerger = docIdMerger; } @@ -61,4 +65,14 @@ public long cost() { public BytesRef binaryValue() throws IOException { return current.getValues().binaryValue(); } -}; + + /** + * Builder pattern like setter for setting totalLiveDocs. We can use setter also. But this way the code is clean. + * @param totalLiveDocs int + * @return {@link KNN80BinaryDocValues} + */ + public KNN80BinaryDocValues setTotalLiveDocs(long totalLiveDocs) { + this.totalLiveDocs = totalLiveDocs; + return this; + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundDirectory.java b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundDirectory.java new file mode 100644 index 000000000..0821b19ef --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundDirectory.java @@ -0,0 +1,63 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN80Codec; + +import lombok.Getter; +import org.apache.lucene.codecs.CompoundDirectory; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexInput; +import org.opensearch.knn.index.engine.KNNEngine; + +import java.io.IOException; +import java.util.Set; + +public class KNN80CompoundDirectory extends CompoundDirectory { + + @Getter + private CompoundDirectory delegate; + @Getter + private Directory dir; + + public KNN80CompoundDirectory(CompoundDirectory delegate, Directory dir) { + this.delegate = delegate; + this.dir = dir; + } + + @Override + public void checkIntegrity() throws IOException { + delegate.checkIntegrity(); + } + + @Override + public String[] listAll() throws IOException { + return delegate.listAll(); + } + + @Override + public long fileLength(String name) throws IOException { + return delegate.fileLength(name); + } + + @Override + public IndexInput openInput(String name, IOContext context) throws IOException { + if (KNNEngine.getEnginesThatCreateCustomSegmentFiles().stream().anyMatch(engine -> name.endsWith(engine.getCompoundExtension()))) { + return dir.openInput(name, context); + } + return delegate.openInput(name, context); + } + + @Override + public void close() throws IOException { + delegate.close(); + } + + @Override + public Set getPendingDeletions() throws IOException { + return delegate.getPendingDeletions(); + } + +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundFormat.java b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundFormat.java index d001cd1ba..24dbfb78b 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundFormat.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundFormat.java @@ -12,7 +12,7 @@ import org.apache.lucene.index.SegmentInfo; import org.apache.lucene.store.Directory; import org.apache.lucene.store.IOContext; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.engine.KNNEngine; import java.io.IOException; import java.util.HashSet; @@ -41,12 +41,12 @@ public KNN80CompoundFormat(CompoundFormat delegate) { @Override public CompoundDirectory getCompoundReader(Directory dir, SegmentInfo si, IOContext context) throws IOException { - return delegate.getCompoundReader(dir, si, context); + return new KNN80CompoundDirectory(delegate.getCompoundReader(dir, si, context), dir); } @Override public void write(Directory dir, SegmentInfo si, IOContext context) throws IOException { - for (KNNEngine knnEngine : KNNEngine.values()) { + for (KNNEngine knnEngine : KNNEngine.getEnginesThatCreateCustomSegmentFiles()) { writeEngineFiles(dir, si, context, knnEngine.getExtension()); } delegate.write(dir, si, context); diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesConsumer.java b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesConsumer.java index 36ffe16de..443b12b9c 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesConsumer.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesConsumer.java @@ -5,64 +5,40 @@ package org.opensearch.knn.index.codec.KNN80Codec; -import com.google.common.collect.ImmutableMap; -import org.apache.lucene.store.ChecksumIndexInput; -import org.opensearch.common.xcontent.DeprecationHandler; -import org.opensearch.common.xcontent.NamedXContentRegistry; -import org.opensearch.common.xcontent.XContentFactory; -import org.opensearch.common.xcontent.XContentType; -import org.opensearch.knn.index.KNNSettings; -import org.opensearch.knn.jni.JNIService; -import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.codec.util.KNNCodecUtil; -import org.opensearch.knn.index.util.KNNEngine; -import org.opensearch.knn.indices.Model; -import org.opensearch.knn.indices.ModelCache; -import org.opensearch.knn.plugin.stats.KNNCounter; +import lombok.extern.log4j.Log4j2; +import org.opensearch.common.StopWatch; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.vectorvalues.KNNVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNVectorValuesFactory; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.lucene.codecs.DocValuesConsumer; import org.apache.lucene.codecs.DocValuesProducer; -import org.apache.lucene.index.BinaryDocValues; import org.apache.lucene.index.DocValuesType; import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.MergeState; import org.apache.lucene.index.SegmentWriteState; -import org.apache.lucene.store.FSDirectory; -import org.apache.lucene.store.FilterDirectory; -import org.opensearch.knn.index.KNNVectorFieldMapper; -import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.codec.nativeindex.NativeIndexWriter; +import org.opensearch.knn.index.mapper.KNNVectorFieldMapper; +import org.opensearch.knn.plugin.stats.KNNGraphValue; -import java.io.Closeable; import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.HashMap; -import java.util.Map; -import static org.apache.lucene.codecs.CodecUtil.FOOTER_MAGIC; -import static org.opensearch.knn.common.KNNConstants.MODEL_ID; -import static org.opensearch.knn.common.KNNConstants.PARAMETERS; -import static org.opensearch.knn.index.codec.util.KNNCodecUtil.buildEngineFileName; +import static org.opensearch.knn.common.FieldInfoExtractor.extractKNNEngine; +import static org.opensearch.knn.common.FieldInfoExtractor.extractVectorDataType; /** * This class writes the KNN docvalues to the segments */ -class KNN80DocValuesConsumer extends DocValuesConsumer implements Closeable { +@Log4j2 +class KNN80DocValuesConsumer extends DocValuesConsumer { private final Logger logger = LogManager.getLogger(KNN80DocValuesConsumer.class); private final DocValuesConsumer delegatee; private final SegmentWriteState state; - private static final Long CRC32_CHECKSUM_SANITY = 0xFFFFFFFF00000000L; - KNN80DocValuesConsumer(DocValuesConsumer delegatee, SegmentWriteState state) { this.delegatee = delegatee; this.state = state; @@ -71,122 +47,35 @@ class KNN80DocValuesConsumer extends DocValuesConsumer implements Closeable { @Override public void addBinaryField(FieldInfo field, DocValuesProducer valuesProducer) throws IOException { delegatee.addBinaryField(field, valuesProducer); - if (field.attributes().containsKey(KNNVectorFieldMapper.KNN_FIELD)) { - addKNNBinaryField(field, valuesProducer); - } - } - - public void addKNNBinaryField(FieldInfo field, DocValuesProducer valuesProducer) throws IOException { - - // Get values to be indexed - BinaryDocValues values = valuesProducer.getBinary(field); - KNNCodecUtil.Pair pair = KNNCodecUtil.getFloats(values); - if (pair.vectors.length == 0 || pair.docs.length == 0) { - logger.info("Skipping engine index creation as there are no vectors or docs in the documents"); - return; - } - - // Increment counter for number of graph index requests - KNNCounter.GRAPH_INDEX_REQUESTS.increment(); - // Create library index either from model or from scratch - String engineFileName; - String indexPath; - NativeIndexCreator indexCreator; - if (field.attributes().containsKey(MODEL_ID)) { - - String modelId = field.attributes().get(MODEL_ID); - Model model = ModelCache.getInstance().get(modelId); - - KNNEngine knnEngine = model.getModelMetadata().getKnnEngine(); - - engineFileName = buildEngineFileName( - state.segmentInfo.name, - knnEngine.getLatestBuildVersion(), - field.name, - knnEngine.getExtension() - ); - indexPath = Paths.get(((FSDirectory) (FilterDirectory.unwrap(state.directory))).getDirectory().toString(), engineFileName) - .toString(); - - if (model.getModelBlob() == null) { - throw new RuntimeException("There is no trained model with id \"" + modelId + "\""); - } - - indexCreator = () -> createKNNIndexFromTemplate(model.getModelBlob(), pair, knnEngine, indexPath); - } else { - - // Get engine to be used for indexing - String engineName = field.attributes().getOrDefault(KNNConstants.KNN_ENGINE, KNNEngine.DEFAULT.getName()); - KNNEngine knnEngine = KNNEngine.getEngine(engineName); - - engineFileName = buildEngineFileName( - state.segmentInfo.name, - knnEngine.getLatestBuildVersion(), - field.name, - knnEngine.getExtension() - ); - indexPath = Paths.get(((FSDirectory) (FilterDirectory.unwrap(state.directory))).getDirectory().toString(), engineFileName) - .toString(); - - indexCreator = () -> createKNNIndexFromScratch(field, pair, knnEngine, indexPath); + if (isKNNBinaryFieldRequired(field)) { + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + addKNNBinaryField(field, valuesProducer, false); + stopWatch.stop(); + long time_in_millis = stopWatch.totalTime().millis(); + KNNGraphValue.REFRESH_TOTAL_TIME_IN_MILLIS.set(KNNGraphValue.REFRESH_TOTAL_TIME_IN_MILLIS.getValue() + time_in_millis); + logger.warn("Refresh operation complete in " + time_in_millis + " ms"); } - - // This is a bit of a hack. We have to create an output here and then immediately close it to ensure that - // engineFileName is added to the tracked files by Lucene's TrackingDirectoryWrapper. Otherwise, the file will - // not be marked as added to the directory. - state.directory.createOutput(engineFileName, state.context).close(); - indexCreator.createIndex(); - writeFooter(indexPath, engineFileName); } - private void createKNNIndexFromTemplate(byte[] model, KNNCodecUtil.Pair pair, KNNEngine knnEngine, String indexPath) { - Map parameters = ImmutableMap.of( - KNNConstants.INDEX_THREAD_QTY, - KNNSettings.state().getSettingValue(KNNSettings.KNN_ALGO_PARAM_INDEX_THREAD_QTY) - ); - AccessController.doPrivileged((PrivilegedAction) () -> { - JNIService.createIndexFromTemplate(pair.docs, pair.vectors, indexPath, model, parameters, knnEngine.getName()); - return null; - }); + private boolean isKNNBinaryFieldRequired(FieldInfo field) { + final KNNEngine knnEngine = extractKNNEngine(field); + log.debug(String.format("Read engine [%s] for field [%s]", knnEngine.getName(), field.getName())); + return field.attributes().containsKey(KNNVectorFieldMapper.KNN_FIELD) + && KNNEngine.getEnginesThatCreateCustomSegmentFiles().stream().anyMatch(engine -> engine == knnEngine); } - private void createKNNIndexFromScratch(FieldInfo fieldInfo, KNNCodecUtil.Pair pair, KNNEngine knnEngine, String indexPath) - throws IOException { - Map parameters = new HashMap<>(); - Map fieldAttributes = fieldInfo.attributes(); - String parametersString = fieldAttributes.get(KNNConstants.PARAMETERS); - - // parametersString will be null when legacy mapper is used - if (parametersString == null) { - parameters.put(KNNConstants.SPACE_TYPE, fieldAttributes.getOrDefault(KNNConstants.SPACE_TYPE, SpaceType.DEFAULT.getValue())); + public void addKNNBinaryField(FieldInfo field, DocValuesProducer valuesProducer, boolean isMerge) throws IOException { + final VectorDataType vectorDataType = extractVectorDataType(field); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues(vectorDataType, valuesProducer.getBinary(field)); - String efConstruction = fieldAttributes.get(KNNConstants.HNSW_ALGO_EF_CONSTRUCTION); - Map algoParams = new HashMap<>(); - if (efConstruction != null) { - algoParams.put(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, Integer.parseInt(efConstruction)); - } - - String m = fieldAttributes.get(KNNConstants.HNSW_ALGO_M); - if (m != null) { - algoParams.put(KNNConstants.METHOD_PARAMETER_M, Integer.parseInt(m)); - } - parameters.put(PARAMETERS, algoParams); + // For BDV it is fine to use knnVectorValues.totalLiveDocs() as we already run the full loop to calculate total + // live docs + if (isMerge) { + NativeIndexWriter.getWriter(field, state).mergeIndex(knnVectorValues, (int) knnVectorValues.totalLiveDocs()); } else { - parameters.putAll( - XContentFactory.xContent(XContentType.JSON) - .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, parametersString) - .map() - ); + NativeIndexWriter.getWriter(field, state).flushIndex(knnVectorValues, (int) knnVectorValues.totalLiveDocs()); } - - // Used to determine how many threads to use when indexing - parameters.put(KNNConstants.INDEX_THREAD_QTY, KNNSettings.state().getSettingValue(KNNSettings.KNN_ALGO_PARAM_INDEX_THREAD_QTY)); - - // Pass the path for the nms library to save the file - AccessController.doPrivileged((PrivilegedAction) () -> { - JNIService.createIndex(pair.docs, pair.vectors, indexPath, parameters, knnEngine.getName()); - return null; - }); } /** @@ -203,7 +92,13 @@ public void merge(MergeState mergeState) { for (FieldInfo fieldInfo : mergeState.mergeFieldInfos) { DocValuesType type = fieldInfo.getDocValuesType(); if (type == DocValuesType.BINARY && fieldInfo.attributes().containsKey(KNNVectorFieldMapper.KNN_FIELD)) { - addKNNBinaryField(fieldInfo, new KNN80DocValuesReader(mergeState)); + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + addKNNBinaryField(fieldInfo, new KNN80DocValuesReader(mergeState), true); + stopWatch.stop(); + long time_in_millis = stopWatch.totalTime().millis(); + KNNGraphValue.MERGE_TOTAL_TIME_IN_MILLIS.set(KNNGraphValue.MERGE_TOTAL_TIME_IN_MILLIS.getValue() + time_in_millis); + logger.warn("Merge operation complete in " + time_in_millis + " ms"); } } } catch (Exception e) { @@ -235,45 +130,4 @@ public void addNumericField(FieldInfo field, DocValuesProducer valuesProducer) t public void close() throws IOException { delegatee.close(); } - - @FunctionalInterface - private interface NativeIndexCreator { - void createIndex() throws IOException; - } - - private void writeFooter(String indexPath, String engineFileName) throws IOException { - // Opens the engine file that was created and appends a footer to it. The footer consists of - // 1. A Footer magic number (int - 4 bytes) - // 2. A checksum algorithm id (int - 4 bytes) - // 3. A checksum (long - bytes) - // The checksum is computed on all the bytes written to the file up to that point. - // Logic where footer is written in Lucene can be found here: - // https://github.com/apache/lucene/blob/branch_9_0/lucene/core/src/java/org/apache/lucene/codecs/CodecUtil.java#L390-L412 - OutputStream os = Files.newOutputStream(Paths.get(indexPath), StandardOpenOption.APPEND); - ByteBuffer byteBuffer = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN); - byteBuffer.putInt(FOOTER_MAGIC); - byteBuffer.putInt(0); - os.write(byteBuffer.array()); - os.flush(); - - ChecksumIndexInput checksumIndexInput = state.directory.openChecksumInput(engineFileName, state.context); - checksumIndexInput.seek(checksumIndexInput.length()); - long value = checksumIndexInput.getChecksum(); - checksumIndexInput.close(); - - if (isChecksumValid(value)) { - throw new IllegalStateException("Illegal CRC-32 checksum: " + value + " (resource=" + os + ")"); - } - - // Write the CRC checksum to the end of the OutputStream and close the stream - byteBuffer.putLong(0, value); - os.write(byteBuffer.array()); - os.close(); - } - - private boolean isChecksumValid(long value) { - // Check pulled from - // https://github.com/apache/lucene/blob/branch_9_0/lucene/core/src/java/org/apache/lucene/codecs/CodecUtil.java#L644-L647 - return (value & CRC32_CHECKSUM_SANITY) != 0; - } } diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesFormat.java b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesFormat.java index fe329eb1c..7e45270b6 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesFormat.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesFormat.java @@ -41,6 +41,6 @@ public DocValuesConsumer fieldsConsumer(SegmentWriteState state) throws IOExcept @Override public DocValuesProducer fieldsProducer(SegmentReadState state) throws IOException { - return delegate.fieldsProducer(state); + return new KNN80DocValuesProducer(delegate.fieldsProducer(state), state); } } diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducer.java b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducer.java new file mode 100644 index 000000000..23c9f3105 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducer.java @@ -0,0 +1,107 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.codec.KNN80Codec; + +import lombok.extern.log4j.Log4j2; +import org.apache.lucene.codecs.DocValuesProducer; +import org.apache.lucene.index.BinaryDocValues; +import org.apache.lucene.index.DocValuesType; +import org.apache.lucene.index.FieldInfo; + +import java.io.IOException; + +import org.apache.lucene.index.NumericDocValues; +import org.apache.lucene.index.SegmentReadState; +import org.apache.lucene.index.SortedDocValues; +import org.apache.lucene.index.SortedNumericDocValues; +import org.apache.lucene.index.SortedSetDocValues; +import org.opensearch.knn.index.codec.util.KNNCodecUtil; +import org.opensearch.knn.index.codec.util.NativeMemoryCacheKeyHelper; +import org.opensearch.knn.index.memory.NativeMemoryCacheManager; + +import java.util.ArrayList; +import java.util.List; + +@Log4j2 +public class KNN80DocValuesProducer extends DocValuesProducer { + private final DocValuesProducer delegate; + private List cacheKeys; + + public KNN80DocValuesProducer(DocValuesProducer delegate, SegmentReadState state) { + this.delegate = delegate; + this.cacheKeys = getVectorCacheKeysFromSegmentReaderState(state); + } + + @Override + public BinaryDocValues getBinary(FieldInfo field) throws IOException { + return delegate.getBinary(field); + } + + @Override + public NumericDocValues getNumeric(FieldInfo field) throws IOException { + return delegate.getNumeric(field); + } + + @Override + public SortedDocValues getSorted(FieldInfo field) throws IOException { + return delegate.getSorted(field); + } + + @Override + public SortedNumericDocValues getSortedNumeric(FieldInfo field) throws IOException { + return delegate.getSortedNumeric(field); + } + + @Override + public SortedSetDocValues getSortedSet(FieldInfo field) throws IOException { + return delegate.getSortedSet(field); + } + + @Override + public void checkIntegrity() throws IOException { + delegate.checkIntegrity(); + } + + @Override + public void close() throws IOException { + final NativeMemoryCacheManager nativeMemoryCacheManager = NativeMemoryCacheManager.getInstance(); + cacheKeys.forEach(nativeMemoryCacheManager::invalidate); + delegate.close(); + } + + public final List getCacheKeys() { + return new ArrayList<>(cacheKeys); + } + + private static List getVectorCacheKeysFromSegmentReaderState(SegmentReadState segmentReadState) { + final List cacheKeys = new ArrayList<>(); + + for (FieldInfo field : segmentReadState.fieldInfos) { + // Only segments that contains BinaryDocValues and doesn't have vector values should be considered. + // By default, we don't create BinaryDocValues for knn field anymore. However, users can set doc_values = true + // to create binary doc values explicitly like any other field. Hence, we only want to include fields + // where approximate search is possible only by BinaryDocValues. + if (field.getDocValuesType() != DocValuesType.BINARY || field.hasVectorValues()) { + continue; + } + + final String vectorIndexFileName = KNNCodecUtil.getNativeEngineFileFromFieldInfo(field, segmentReadState.segmentInfo); + if (vectorIndexFileName == null) { + continue; + } + final String cacheKey = NativeMemoryCacheKeyHelper.constructCacheKey(vectorIndexFileName, segmentReadState.segmentInfo); + cacheKeys.add(cacheKey); + } + + return cacheKeys; + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesReader.java b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesReader.java index ccfaa68fc..16380c5d9 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesReader.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesReader.java @@ -5,6 +5,10 @@ package org.opensearch.knn.index.codec.KNN80Codec; +import lombok.extern.log4j.Log4j2; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.util.Bits; +import org.opensearch.common.StopWatch; import org.opensearch.knn.index.codec.util.BinaryDocValuesSub; import org.apache.lucene.codecs.DocValuesProducer; import org.apache.lucene.index.BinaryDocValues; @@ -14,12 +18,14 @@ import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.MergeState; +import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * Reader for KNNDocValues from the segments */ +@Log4j2 class KNN80DocValuesReader extends EmptyDocValuesProducer { private final MergeState mergeState; @@ -30,6 +36,7 @@ class KNN80DocValuesReader extends EmptyDocValuesProducer { @Override public BinaryDocValues getBinary(FieldInfo field) { + long totalLiveDocs = 0; try { List subs = new ArrayList<>(this.mergeState.docValuesProducers.length); for (int i = 0; i < this.mergeState.docValuesProducers.length; i++) { @@ -41,13 +48,49 @@ public BinaryDocValues getBinary(FieldInfo field) { values = docValuesProducer.getBinary(readerFieldInfo); } if (values != null) { + totalLiveDocs = totalLiveDocs + getLiveDocsCount(values, this.mergeState.liveDocs[i]); + // docValues will be consumed when liveDocs are not null, hence resetting the docsValues + // pointer. + values = this.mergeState.liveDocs[i] != null ? docValuesProducer.getBinary(readerFieldInfo) : values; + subs.add(new BinaryDocValuesSub(mergeState.docMaps[i], values)); } } } - return new KNN80BinaryDocValues(DocIDMerger.of(subs, mergeState.needsIndexSort)); + return new KNN80BinaryDocValues(DocIDMerger.of(subs, mergeState.needsIndexSort)).setTotalLiveDocs(totalLiveDocs); } catch (Exception e) { throw new RuntimeException(e); } } + + /** + * This function return the liveDocs count present in the BinaryDocValues. If the liveDocsBits is null, then we + * can use {@link BinaryDocValues#cost()} function to get max docIds. But if LiveDocsBits is not null, then we + * iterate over the BinaryDocValues and validate if the docId is present in the live docs bits or not. + * + * @param binaryDocValues {@link BinaryDocValues} + * @param liveDocsBits {@link Bits} + * @return total number of liveDocs. + * @throws IOException + */ + private long getLiveDocsCount(final BinaryDocValues binaryDocValues, final Bits liveDocsBits) throws IOException { + long liveDocs = 0; + if (liveDocsBits != null) { + int docId; + // This is not the right way to log the time. I create a github issue for adding an annotation to track + // the time. https://github.com/opensearch-project/k-NN/issues/1594 + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + for (docId = binaryDocValues.nextDoc(); docId != DocIdSetIterator.NO_MORE_DOCS; docId = binaryDocValues.nextDoc()) { + if (liveDocsBits.get(docId)) { + liveDocs++; + } + } + stopWatch.stop(); + log.debug("Time taken to iterate over binary doc values: {} ms", stopWatch.totalTime().millis()); + } else { + liveDocs = binaryDocValues.cost(); + } + return liveDocs; + } } diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN910Codec/KNN910Codec.java b/src/main/java/org/opensearch/knn/index/codec/KNN910Codec/KNN910Codec.java index 0acaccfbf..77783dc29 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNN910Codec/KNN910Codec.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNN910Codec/KNN910Codec.java @@ -8,10 +8,8 @@ import org.apache.lucene.codecs.CompoundFormat; import org.apache.lucene.codecs.DocValuesFormat; import org.apache.lucene.codecs.FilterCodec; +import org.opensearch.knn.index.codec.KNNCodecVersion; import org.opensearch.knn.index.codec.KNNFormatFacade; -import org.opensearch.knn.index.codec.KNNFormatFactory; - -import static org.opensearch.knn.index.codec.KNNCodecFactory.CodecDelegateFactory.createKNN91DefaultDelegate; /** * Extends the Codec to support a new file format for KNN index @@ -19,15 +17,14 @@ * */ public final class KNN910Codec extends FilterCodec { - - private static final String KNN910 = "KNN910Codec"; + private static final KNNCodecVersion VERSION = KNNCodecVersion.V_9_1_0; private final KNNFormatFacade knnFormatFacade; /** * No arg constructor that uses Lucene91 as the delegate */ public KNN910Codec() { - this(createKNN91DefaultDelegate()); + this(VERSION.getDefaultCodecDelegate()); } /** @@ -36,8 +33,8 @@ public KNN910Codec() { * @param delegate codec that will perform all operations this codec does not override */ public KNN910Codec(Codec delegate) { - super(KNN910, delegate); - knnFormatFacade = KNNFormatFactory.createKNN910Format(delegate); + super(VERSION.getCodecName(), delegate); + knnFormatFacade = VERSION.getKnnFormatFacadeSupplier().apply(delegate); } @Override diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN9120Codec/KNN9120Codec.java b/src/main/java/org/opensearch/knn/index/codec/KNN9120Codec/KNN9120Codec.java new file mode 100644 index 000000000..a370197ec --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNN9120Codec/KNN9120Codec.java @@ -0,0 +1,61 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN9120Codec; + +import lombok.Builder; +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.CompoundFormat; +import org.apache.lucene.codecs.DocValuesFormat; +import org.apache.lucene.codecs.FilterCodec; +import org.apache.lucene.codecs.KnnVectorsFormat; +import org.apache.lucene.codecs.perfield.PerFieldKnnVectorsFormat; +import org.opensearch.knn.index.codec.KNNCodecVersion; +import org.opensearch.knn.index.codec.KNNFormatFacade; + +/** + * KNN Codec that wraps the Lucene Codec which is part of Lucene 9.12 + */ +public class KNN9120Codec extends FilterCodec { + private static final KNNCodecVersion VERSION = KNNCodecVersion.V_9_12_0; + private final KNNFormatFacade knnFormatFacade; + private final PerFieldKnnVectorsFormat perFieldKnnVectorsFormat; + + /** + * No arg constructor that uses Lucene99 as the delegate + */ + public KNN9120Codec() { + this(VERSION.getDefaultCodecDelegate(), VERSION.getPerFieldKnnVectorsFormat()); + } + + /** + * Sole constructor. When subclassing this codec, create a no-arg ctor and pass the delegate codec + * and a unique name to this ctor. + * + * @param delegate codec that will perform all operations this codec does not override + * @param knnVectorsFormat per field format for KnnVector + */ + @Builder + protected KNN9120Codec(Codec delegate, PerFieldKnnVectorsFormat knnVectorsFormat) { + super(VERSION.getCodecName(), delegate); + knnFormatFacade = VERSION.getKnnFormatFacadeSupplier().apply(delegate); + perFieldKnnVectorsFormat = knnVectorsFormat; + } + + @Override + public DocValuesFormat docValuesFormat() { + return knnFormatFacade.docValuesFormat(); + } + + @Override + public CompoundFormat compoundFormat() { + return knnFormatFacade.compoundFormat(); + } + + @Override + public KnnVectorsFormat knnVectorsFormat() { + return perFieldKnnVectorsFormat; + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN920Codec/KNN920Codec.java b/src/main/java/org/opensearch/knn/index/codec/KNN920Codec/KNN920Codec.java new file mode 100644 index 000000000..b79c1b4f2 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNN920Codec/KNN920Codec.java @@ -0,0 +1,61 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.opensearch.knn.index.codec.KNN920Codec; + +import lombok.Builder; +import lombok.extern.log4j.Log4j2; +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.CompoundFormat; +import org.apache.lucene.codecs.DocValuesFormat; +import org.apache.lucene.codecs.FilterCodec; +import org.apache.lucene.codecs.KnnVectorsFormat; +import org.apache.lucene.codecs.perfield.PerFieldKnnVectorsFormat; +import org.opensearch.knn.index.codec.KNNCodecVersion; +import org.opensearch.knn.index.codec.KNNFormatFacade; + +/** + * KNN codec that is based on Lucene92 codec + */ +@Log4j2 +public final class KNN920Codec extends FilterCodec { + private static final KNNCodecVersion VERSION = KNNCodecVersion.V_9_2_0; + private final KNNFormatFacade knnFormatFacade; + private final PerFieldKnnVectorsFormat perFieldKnnVectorsFormat; + + /** + * No arg constructor that uses Lucene91 as the delegate + */ + public KNN920Codec() { + this(VERSION.getDefaultCodecDelegate(), VERSION.getPerFieldKnnVectorsFormat()); + } + + /** + * Constructor that takes a Codec delegate to delegate all methods this code does not implement to. + * + * @param delegate codec that will perform all operations this codec does not override + * @param knnVectorsFormat per field format for KnnVector + */ + @Builder + public KNN920Codec(Codec delegate, PerFieldKnnVectorsFormat knnVectorsFormat) { + super(VERSION.getCodecName(), delegate); + knnFormatFacade = VERSION.getKnnFormatFacadeSupplier().apply(delegate); + perFieldKnnVectorsFormat = knnVectorsFormat; + } + + @Override + public DocValuesFormat docValuesFormat() { + return knnFormatFacade.docValuesFormat(); + } + + @Override + public CompoundFormat compoundFormat() { + return knnFormatFacade.compoundFormat(); + } + + @Override + public KnnVectorsFormat knnVectorsFormat() { + return perFieldKnnVectorsFormat; + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN920Codec/KNN920PerFieldKnnVectorsFormat.java b/src/main/java/org/opensearch/knn/index/codec/KNN920Codec/KNN920PerFieldKnnVectorsFormat.java new file mode 100644 index 000000000..7cca04319 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNN920Codec/KNN920PerFieldKnnVectorsFormat.java @@ -0,0 +1,31 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN920Codec; + +import org.apache.lucene.backward_codecs.lucene92.Lucene92HnswVectorsFormat; +import org.opensearch.index.mapper.MapperService; +import org.opensearch.knn.index.codec.BasePerFieldKnnVectorsFormat; + +import java.util.Optional; + +/** + * Class provides per field format implementation for Lucene Knn vector type + */ +public class KNN920PerFieldKnnVectorsFormat extends BasePerFieldKnnVectorsFormat { + + public KNN920PerFieldKnnVectorsFormat(final Optional mapperService) { + super( + mapperService, + Lucene92HnswVectorsFormat.DEFAULT_MAX_CONN, + Lucene92HnswVectorsFormat.DEFAULT_BEAM_WIDTH, + () -> new Lucene92HnswVectorsFormat(), + knnVectorsFormatParams -> new Lucene92HnswVectorsFormat( + knnVectorsFormatParams.getMaxConnections(), + knnVectorsFormatParams.getBeamWidth() + ) + ); + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN940Codec/KNN940Codec.java b/src/main/java/org/opensearch/knn/index/codec/KNN940Codec/KNN940Codec.java new file mode 100644 index 000000000..a056581d6 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNN940Codec/KNN940Codec.java @@ -0,0 +1,58 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN940Codec; + +import lombok.Builder; +import org.apache.lucene.codecs.CompoundFormat; +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.DocValuesFormat; +import org.apache.lucene.codecs.FilterCodec; +import org.apache.lucene.codecs.KnnVectorsFormat; +import org.apache.lucene.codecs.perfield.PerFieldKnnVectorsFormat; +import org.opensearch.knn.index.codec.KNNCodecVersion; +import org.opensearch.knn.index.codec.KNNFormatFacade; + +public class KNN940Codec extends FilterCodec { + private static final KNNCodecVersion VERSION = KNNCodecVersion.V_9_4_0; + private final KNNFormatFacade knnFormatFacade; + private final PerFieldKnnVectorsFormat perFieldKnnVectorsFormat; + + /** + * No arg constructor that uses Lucene94 as the delegate + */ + public KNN940Codec() { + this(VERSION.getDefaultCodecDelegate(), VERSION.getPerFieldKnnVectorsFormat()); + } + + /** + * Sole constructor. When subclassing this codec, create a no-arg ctor and pass the delegate codec + * and a unique name to this ctor. + * + * @param delegate codec that will perform all operations this codec does not override + * @param knnVectorsFormat per field format for KnnVector + */ + @Builder + protected KNN940Codec(Codec delegate, PerFieldKnnVectorsFormat knnVectorsFormat) { + super(VERSION.getCodecName(), delegate); + knnFormatFacade = VERSION.getKnnFormatFacadeSupplier().apply(delegate); + perFieldKnnVectorsFormat = knnVectorsFormat; + } + + @Override + public DocValuesFormat docValuesFormat() { + return knnFormatFacade.docValuesFormat(); + } + + @Override + public CompoundFormat compoundFormat() { + return knnFormatFacade.compoundFormat(); + } + + @Override + public KnnVectorsFormat knnVectorsFormat() { + return perFieldKnnVectorsFormat; + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN940Codec/KNN940PerFieldKnnVectorsFormat.java b/src/main/java/org/opensearch/knn/index/codec/KNN940Codec/KNN940PerFieldKnnVectorsFormat.java new file mode 100644 index 000000000..1ed9c929c --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNN940Codec/KNN940PerFieldKnnVectorsFormat.java @@ -0,0 +1,31 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN940Codec; + +import org.apache.lucene.backward_codecs.lucene94.Lucene94HnswVectorsFormat; +import org.opensearch.index.mapper.MapperService; +import org.opensearch.knn.index.codec.BasePerFieldKnnVectorsFormat; + +import java.util.Optional; + +/** + * Class provides per field format implementation for Lucene Knn vector type + */ +public class KNN940PerFieldKnnVectorsFormat extends BasePerFieldKnnVectorsFormat { + + public KNN940PerFieldKnnVectorsFormat(final Optional mapperService) { + super( + mapperService, + Lucene94HnswVectorsFormat.DEFAULT_MAX_CONN, + Lucene94HnswVectorsFormat.DEFAULT_BEAM_WIDTH, + () -> new Lucene94HnswVectorsFormat(), + knnVectorsFormatParams -> new Lucene94HnswVectorsFormat( + knnVectorsFormatParams.getMaxConnections(), + knnVectorsFormatParams.getBeamWidth() + ) + ); + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN950Codec/KNN950Codec.java b/src/main/java/org/opensearch/knn/index/codec/KNN950Codec/KNN950Codec.java new file mode 100644 index 000000000..338e54451 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNN950Codec/KNN950Codec.java @@ -0,0 +1,58 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN950Codec; + +import lombok.Builder; +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.CompoundFormat; +import org.apache.lucene.codecs.DocValuesFormat; +import org.apache.lucene.codecs.FilterCodec; +import org.apache.lucene.codecs.KnnVectorsFormat; +import org.apache.lucene.codecs.perfield.PerFieldKnnVectorsFormat; +import org.opensearch.knn.index.codec.KNNCodecVersion; +import org.opensearch.knn.index.codec.KNNFormatFacade; + +public class KNN950Codec extends FilterCodec { + private static final KNNCodecVersion VERSION = KNNCodecVersion.V_9_5_0; + private final KNNFormatFacade knnFormatFacade; + private final PerFieldKnnVectorsFormat perFieldKnnVectorsFormat; + + /** + * No arg constructor that uses Lucene95 as the delegate + */ + public KNN950Codec() { + this(VERSION.getDefaultCodecDelegate(), VERSION.getPerFieldKnnVectorsFormat()); + } + + /** + * Sole constructor. When subclassing this codec, create a no-arg ctor and pass the delegate codec + * and a unique name to this ctor. + * + * @param delegate codec that will perform all operations this codec does not override + * @param knnVectorsFormat per field format for KnnVector + */ + @Builder + protected KNN950Codec(Codec delegate, PerFieldKnnVectorsFormat knnVectorsFormat) { + super(VERSION.getCodecName(), delegate); + knnFormatFacade = VERSION.getKnnFormatFacadeSupplier().apply(delegate); + perFieldKnnVectorsFormat = knnVectorsFormat; + } + + @Override + public DocValuesFormat docValuesFormat() { + return knnFormatFacade.docValuesFormat(); + } + + @Override + public CompoundFormat compoundFormat() { + return knnFormatFacade.compoundFormat(); + } + + @Override + public KnnVectorsFormat knnVectorsFormat() { + return perFieldKnnVectorsFormat; + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN950Codec/KNN950PerFieldKnnVectorsFormat.java b/src/main/java/org/opensearch/knn/index/codec/KNN950Codec/KNN950PerFieldKnnVectorsFormat.java new file mode 100644 index 000000000..7a1458057 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNN950Codec/KNN950PerFieldKnnVectorsFormat.java @@ -0,0 +1,43 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN950Codec; + +import org.apache.lucene.backward_codecs.lucene95.Lucene95HnswVectorsFormat; +import org.opensearch.index.mapper.MapperService; +import org.opensearch.knn.index.codec.BasePerFieldKnnVectorsFormat; +import org.opensearch.knn.index.engine.KNNEngine; + +import java.util.Optional; + +/** + * Class provides per field format implementation for Lucene Knn vector type + */ +public class KNN950PerFieldKnnVectorsFormat extends BasePerFieldKnnVectorsFormat { + + public KNN950PerFieldKnnVectorsFormat(final Optional mapperService) { + super( + mapperService, + Lucene95HnswVectorsFormat.DEFAULT_MAX_CONN, + Lucene95HnswVectorsFormat.DEFAULT_BEAM_WIDTH, + () -> new Lucene95HnswVectorsFormat(), + knnVectorsFormatParams -> new Lucene95HnswVectorsFormat( + knnVectorsFormatParams.getMaxConnections(), + knnVectorsFormatParams.getBeamWidth() + ) + ); + } + + @Override + /** + * This method returns the maximum dimension allowed from KNNEngine for Lucene codec + * + * @param fieldName Name of the field, ignored + * @return Maximum constant dimension set by KNNEngine + */ + public int getMaxDimensions(String fieldName) { + return KNNEngine.getMaxDimensionByEngine(KNNEngine.LUCENE); + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/KNN990Codec.java b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/KNN990Codec.java new file mode 100644 index 000000000..4b8a1d3cd --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/KNN990Codec.java @@ -0,0 +1,61 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN990Codec; + +import lombok.Builder; +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.CompoundFormat; +import org.apache.lucene.codecs.DocValuesFormat; +import org.apache.lucene.codecs.FilterCodec; +import org.apache.lucene.codecs.KnnVectorsFormat; +import org.apache.lucene.codecs.perfield.PerFieldKnnVectorsFormat; +import org.opensearch.knn.index.codec.KNNCodecVersion; +import org.opensearch.knn.index.codec.KNNFormatFacade; + +/** + * KNN Codec that wraps the Lucene Codec which is part of Lucene 9.9 + */ +public class KNN990Codec extends FilterCodec { + private static final KNNCodecVersion VERSION = KNNCodecVersion.V_9_9_0; + private final KNNFormatFacade knnFormatFacade; + private final PerFieldKnnVectorsFormat perFieldKnnVectorsFormat; + + /** + * No arg constructor that uses Lucene99 as the delegate + */ + public KNN990Codec() { + this(VERSION.getDefaultCodecDelegate(), VERSION.getPerFieldKnnVectorsFormat()); + } + + /** + * Sole constructor. When subclassing this codec, create a no-arg ctor and pass the delegate codec + * and a unique name to this ctor. + * + * @param delegate codec that will perform all operations this codec does not override + * @param knnVectorsFormat per field format for KnnVector + */ + @Builder + protected KNN990Codec(Codec delegate, PerFieldKnnVectorsFormat knnVectorsFormat) { + super(VERSION.getCodecName(), delegate); + knnFormatFacade = VERSION.getKnnFormatFacadeSupplier().apply(delegate); + perFieldKnnVectorsFormat = knnVectorsFormat; + } + + @Override + public DocValuesFormat docValuesFormat() { + return knnFormatFacade.docValuesFormat(); + } + + @Override + public CompoundFormat compoundFormat() { + return knnFormatFacade.compoundFormat(); + } + + @Override + public KnnVectorsFormat knnVectorsFormat() { + return perFieldKnnVectorsFormat; + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/KNN990PerFieldKnnVectorsFormat.java b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/KNN990PerFieldKnnVectorsFormat.java new file mode 100644 index 000000000..f565dfe5b --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/KNN990PerFieldKnnVectorsFormat.java @@ -0,0 +1,54 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN990Codec; + +import org.apache.lucene.codecs.lucene99.Lucene99HnswScalarQuantizedVectorsFormat; +import org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsFormat; +import org.opensearch.index.mapper.MapperService; +import org.opensearch.knn.index.codec.BasePerFieldKnnVectorsFormat; +import org.opensearch.knn.index.engine.KNNEngine; + +import java.util.Optional; + +/** + * Class provides per field format implementation for Lucene Knn vector type + */ +public class KNN990PerFieldKnnVectorsFormat extends BasePerFieldKnnVectorsFormat { + private static final int NUM_MERGE_WORKERS = 1; + + public KNN990PerFieldKnnVectorsFormat(final Optional mapperService) { + super( + mapperService, + Lucene99HnswVectorsFormat.DEFAULT_MAX_CONN, + Lucene99HnswVectorsFormat.DEFAULT_BEAM_WIDTH, + () -> new Lucene99HnswVectorsFormat(), + knnVectorsFormatParams -> new Lucene99HnswVectorsFormat( + knnVectorsFormatParams.getMaxConnections(), + knnVectorsFormatParams.getBeamWidth() + ), + knnScalarQuantizedVectorsFormatParams -> new Lucene99HnswScalarQuantizedVectorsFormat( + knnScalarQuantizedVectorsFormatParams.getMaxConnections(), + knnScalarQuantizedVectorsFormatParams.getBeamWidth(), + NUM_MERGE_WORKERS, + knnScalarQuantizedVectorsFormatParams.getBits(), + knnScalarQuantizedVectorsFormatParams.isCompressFlag(), + knnScalarQuantizedVectorsFormatParams.getConfidenceInterval(), + null + ) + ); + } + + @Override + /** + * This method returns the maximum dimension allowed from KNNEngine for Lucene codec + * + * @param fieldName Name of the field, ignored + * @return Maximum constant dimension set by KNNEngine + */ + public int getMaxDimensions(String fieldName) { + return KNNEngine.getMaxDimensionByEngine(KNNEngine.LUCENE); + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/KNN990QuantizationStateReader.java b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/KNN990QuantizationStateReader.java new file mode 100644 index 000000000..d9b73d621 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/KNN990QuantizationStateReader.java @@ -0,0 +1,119 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN990Codec; + +import com.google.common.annotations.VisibleForTesting; +import lombok.extern.log4j.Log4j2; +import org.apache.lucene.codecs.CodecUtil; +import org.apache.lucene.index.IndexFileNames; +import org.apache.lucene.index.SegmentReadState; +import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexInput; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; +import org.opensearch.knn.quantization.models.quantizationParams.ScalarQuantizationParams; +import org.opensearch.knn.quantization.models.quantizationState.MultiBitScalarQuantizationState; +import org.opensearch.knn.quantization.models.quantizationState.OneBitScalarQuantizationState; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationStateReadConfig; + +import java.io.IOException; + +/** + * Reads quantization states + */ +@Log4j2 +public final class KNN990QuantizationStateReader { + + /** + * Reads an individual quantization state for a given field + * File format: + * Header + * QS1 state bytes + * QS2 state bytes + * Number of quantization states + * QS1 field number + * QS1 state bytes length + * QS1 position of state bytes + * QS2 field number + * QS2 state bytes length + * QS2 position of state bytes + * Position of index section (where QS1 field name is located) + * -1 (marker) + * Footer + * + * @param readConfig a config class that contains necessary information for reading the state + * @return quantization state + */ + public static QuantizationState read(QuantizationStateReadConfig readConfig) throws IOException { + SegmentReadState segmentReadState = readConfig.getSegmentReadState(); + String field = readConfig.getField(); + String quantizationStateFileName = getQuantizationStateFileName(segmentReadState); + int fieldNumber = segmentReadState.fieldInfos.fieldInfo(field).getFieldNumber(); + + try (IndexInput input = segmentReadState.directory.openInput(quantizationStateFileName, IOContext.READ)) { + + CodecUtil.retrieveChecksum(input); + int numFields = getNumFields(input); + + long position = -1; + int length = 0; + + // Read each field's metadata from the index section, break when correct field is found + for (int i = 0; i < numFields; i++) { + int tempFieldNumber = input.readInt(); + int tempLength = input.readInt(); + long tempPosition = input.readVLong(); + if (tempFieldNumber == fieldNumber) { + position = tempPosition; + length = tempLength; + break; + } + } + + if (position == -1 || length == 0) { + throw new IllegalArgumentException(String.format("Field %s not found", field)); + } + + byte[] stateBytes = readStateBytes(input, position, length); + + // Deserialize the byte array to a quantization state object + ScalarQuantizationType scalarQuantizationType = ((ScalarQuantizationParams) readConfig.getQuantizationParams()).getSqType(); + switch (scalarQuantizationType) { + case ONE_BIT: + return OneBitScalarQuantizationState.fromByteArray(stateBytes); + case TWO_BIT: + case FOUR_BIT: + return MultiBitScalarQuantizationState.fromByteArray(stateBytes); + default: + throw new IllegalArgumentException(String.format("Unexpected scalar quantization type: %s", scalarQuantizationType)); + } + } + } + + @VisibleForTesting + static int getNumFields(IndexInput input) throws IOException { + long footerStart = input.length() - CodecUtil.footerLength(); + long markerAndIndexPosition = footerStart - Integer.BYTES - Long.BYTES; + input.seek(markerAndIndexPosition); + long indexStartPosition = input.readLong(); + input.seek(indexStartPosition); + return input.readInt(); + } + + @VisibleForTesting + static byte[] readStateBytes(IndexInput input, long position, int length) throws IOException { + input.seek(position); + byte[] stateBytes = new byte[length]; + input.readBytes(stateBytes, 0, length); + return stateBytes; + } + + @VisibleForTesting + static String getQuantizationStateFileName(SegmentReadState state) { + return IndexFileNames.segmentFileName(state.segmentInfo.name, state.segmentSuffix, KNNConstants.QUANTIZATION_STATE_FILE_SUFFIX); + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/KNN990QuantizationStateWriter.java b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/KNN990QuantizationStateWriter.java new file mode 100644 index 000000000..49b1819c1 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/KNN990QuantizationStateWriter.java @@ -0,0 +1,116 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN990Codec; + +import lombok.AllArgsConstructor; +import lombok.Setter; +import org.apache.lucene.codecs.CodecUtil; +import org.apache.lucene.index.IndexFileNames; +import org.apache.lucene.index.SegmentWriteState; +import org.apache.lucene.store.IndexOutput; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Writes quantization states to off heap memory + */ +public final class KNN990QuantizationStateWriter { + + private final IndexOutput output; + private List fieldQuantizationStates = new ArrayList<>(); + static final String NATIVE_ENGINES_990_KNN_VECTORS_FORMAT_QS_DATA = "NativeEngines990KnnVectorsFormatQSData"; + + /** + * Constructor + * Overall file format for writer: + * Header + * QS1 state bytes + * QS2 state bytes + * Number of quantization states + * QS1 field number + * QS1 state bytes length + * QS1 position of state bytes + * QS2 field number + * QS2 state bytes length + * QS2 position of state bytes + * Position of index section (where QS1 field name is located) + * -1 (marker) + * Footer + * @param segmentWriteState segment write state containing segment information + * @throws IOException exception could be thrown while creating the output + */ + public KNN990QuantizationStateWriter(SegmentWriteState segmentWriteState) throws IOException { + String quantizationStateFileName = IndexFileNames.segmentFileName( + segmentWriteState.segmentInfo.name, + segmentWriteState.segmentSuffix, + KNNConstants.QUANTIZATION_STATE_FILE_SUFFIX + ); + + output = segmentWriteState.directory.createOutput(quantizationStateFileName, segmentWriteState.context); + } + + /** + * Writes an index header + * @param segmentWriteState state containing segment information + * @throws IOException exception could be thrown while writing header + */ + public void writeHeader(SegmentWriteState segmentWriteState) throws IOException { + CodecUtil.writeIndexHeader( + output, + NATIVE_ENGINES_990_KNN_VECTORS_FORMAT_QS_DATA, + 0, + segmentWriteState.segmentInfo.getId(), + segmentWriteState.segmentSuffix + ); + } + + /** + * Writes a quantization state as bytes + * + * @param fieldNumber field number + * @param quantizationState quantization state + * @throws IOException could be thrown while writing + */ + public void writeState(int fieldNumber, QuantizationState quantizationState) throws IOException { + byte[] stateBytes = quantizationState.toByteArray(); + long position = output.getFilePointer(); + output.writeBytes(stateBytes, stateBytes.length); + fieldQuantizationStates.add(new FieldQuantizationState(fieldNumber, stateBytes, position)); + } + + /** + * Writes index footer and other index information for parsing later + * @throws IOException could be thrown while writing + */ + public void writeFooter() throws IOException { + long indexStartPosition = output.getFilePointer(); + output.writeInt(fieldQuantizationStates.size()); + for (FieldQuantizationState fieldQuantizationState : fieldQuantizationStates) { + output.writeInt(fieldQuantizationState.fieldNumber); + output.writeInt(fieldQuantizationState.stateBytes.length); + output.writeVLong(fieldQuantizationState.position); + } + output.writeLong(indexStartPosition); + output.writeInt(-1); + CodecUtil.writeFooter(output); + } + + @AllArgsConstructor + private static class FieldQuantizationState { + final int fieldNumber; + final byte[] stateBytes; + @Setter + Long position; + } + + public void closeOutput() throws IOException { + output.close(); + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngineFieldVectorsWriter.java b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngineFieldVectorsWriter.java new file mode 100644 index 000000000..389c76e49 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngineFieldVectorsWriter.java @@ -0,0 +1,130 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.codec.KNN990Codec; + +import lombok.Getter; +import org.apache.lucene.codecs.KnnFieldVectorsWriter; +import org.apache.lucene.codecs.hnsw.FlatFieldVectorsWriter; +import org.apache.lucene.index.DocsWithFieldSet; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.util.InfoStream; +import org.apache.lucene.util.RamUsageEstimator; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * NativeEngineVectorFieldsWriter is a class that will be used to accumulate all the vectors during ingestion before + * lucene does a flush. This class ensures that KNNVectorWriter is free from generics and this class can encapsulate + * all the details related to vectors types and docIds. + * + * @param float[] or byte[] + */ +class NativeEngineFieldVectorsWriter extends KnnFieldVectorsWriter { + private static final long SHALLOW_SIZE = RamUsageEstimator.shallowSizeOfInstance(NativeEngineFieldVectorsWriter.class); + @Getter + private final FieldInfo fieldInfo; + /** + * We are using a map here instead of list, because for sampler interface for quantization we have to advance the iterator + * to a specific docId, there a list cannot be useful because a docId != index of the vector in the list. Similar + * thing is true when we have vector field in child document. There doc Ids will not be consistent. Hence, we need to + * use the map here. + */ + @Getter + private final Map vectors; + private int lastDocID = -1; + @Getter + private final DocsWithFieldSet docsWithField; + private final InfoStream infoStream; + private final FlatFieldVectorsWriter flatFieldVectorsWriter; + + @SuppressWarnings("unchecked") + static NativeEngineFieldVectorsWriter create( + final FieldInfo fieldInfo, + final FlatFieldVectorsWriter flatFieldVectorsWriter, + final InfoStream infoStream + ) { + switch (fieldInfo.getVectorEncoding()) { + case FLOAT32: + return new NativeEngineFieldVectorsWriter<>( + fieldInfo, + (FlatFieldVectorsWriter) flatFieldVectorsWriter, + infoStream + ); + case BYTE: + return new NativeEngineFieldVectorsWriter<>(fieldInfo, (FlatFieldVectorsWriter) flatFieldVectorsWriter, infoStream); + } + throw new IllegalStateException("Unsupported Vector encoding : " + fieldInfo.getVectorEncoding()); + } + + private NativeEngineFieldVectorsWriter( + final FieldInfo fieldInfo, + final FlatFieldVectorsWriter flatFieldVectorsWriter, + final InfoStream infoStream + ) { + this.fieldInfo = fieldInfo; + this.infoStream = infoStream; + vectors = new HashMap<>(); + this.docsWithField = new DocsWithFieldSet(); + this.flatFieldVectorsWriter = flatFieldVectorsWriter; + } + + /** + * Add new docID with its vector value to the given field for indexing. Doc IDs must be added in + * increasing order. + * + * @param docID int + * @param vectorValue T + */ + @Override + public void addValue(int docID, T vectorValue) throws IOException { + if (docID == lastDocID) { + throw new IllegalArgumentException( + "[NativeEngineKNNVectorWriter]VectorValuesField \"" + + fieldInfo.name + + "\" appears more than once in this document (only one value is allowed per field)" + ); + } + // TODO: we can build the graph here too iteratively. but right now I am skipping that as we need iterative + // graph build support on the JNI layer. + assert docID > lastDocID; + // ensuring that vector is provided to flatFieldWriter. + flatFieldVectorsWriter.addValue(docID, vectorValue); + vectors.put(docID, vectorValue); + docsWithField.add(docID); + lastDocID = docID; + } + + /** + * Used to copy values being indexed to internal storage. + * + * @param vectorValue an array containing the vector value to add + * @return a copy of the value; a new array + */ + @Override + public T copyValue(T vectorValue) { + throw new UnsupportedOperationException("NativeEngineVectorFieldsWriter doesn't support copyValue operation"); + } + + /** + * Return the memory usage of this object in bytes. Negative values are illegal. + */ + @Override + public long ramBytesUsed() { + return SHALLOW_SIZE + docsWithField.ramBytesUsed() + (long) this.vectors.size() * (long) (RamUsageEstimator.NUM_BYTES_OBJECT_REF + + RamUsageEstimator.NUM_BYTES_ARRAY_HEADER) + (long) this.vectors.size() * RamUsageEstimator.shallowSizeOfInstance( + Integer.class + ) + (long) vectors.size() * fieldInfo.getVectorDimension() * fieldInfo.getVectorEncoding().byteSize + flatFieldVectorsWriter + .ramBytesUsed(); + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsFormat.java b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsFormat.java new file mode 100644 index 000000000..dd326123e --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsFormat.java @@ -0,0 +1,84 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.codec.KNN990Codec; + +import org.apache.lucene.codecs.KnnVectorsFormat; +import org.apache.lucene.codecs.KnnVectorsReader; +import org.apache.lucene.codecs.KnnVectorsWriter; +import org.apache.lucene.codecs.hnsw.DefaultFlatVectorScorer; +import org.apache.lucene.codecs.hnsw.FlatVectorsFormat; +import org.apache.lucene.codecs.lucene99.Lucene99FlatVectorsFormat; +import org.apache.lucene.index.SegmentReadState; +import org.apache.lucene.index.SegmentWriteState; +import org.opensearch.knn.index.KNNSettings; + +import java.io.IOException; + +/** + * This is a Vector format that will be used for Native engines like Faiss and Nmslib for reading and writing vector + * related data structures. + */ +public class NativeEngines990KnnVectorsFormat extends KnnVectorsFormat { + /** The format for storing, reading, merging vectors on disk */ + private static FlatVectorsFormat flatVectorsFormat; + private static final String FORMAT_NAME = "NativeEngines990KnnVectorsFormat"; + private static int approximateThreshold; + + public NativeEngines990KnnVectorsFormat() { + this(new Lucene99FlatVectorsFormat(new DefaultFlatVectorScorer())); + } + + public NativeEngines990KnnVectorsFormat(int approximateThreshold) { + this(new Lucene99FlatVectorsFormat(new DefaultFlatVectorScorer()), approximateThreshold); + } + + public NativeEngines990KnnVectorsFormat(final FlatVectorsFormat flatVectorsFormat) { + this(flatVectorsFormat, KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD_DEFAULT_VALUE); + } + + public NativeEngines990KnnVectorsFormat(final FlatVectorsFormat flatVectorsFormat, int approximateThreshold) { + super(FORMAT_NAME); + NativeEngines990KnnVectorsFormat.flatVectorsFormat = flatVectorsFormat; + NativeEngines990KnnVectorsFormat.approximateThreshold = approximateThreshold; + } + + /** + * Returns a {@link KnnVectorsWriter} to write the vectors to the index. + * + * @param state {@link SegmentWriteState} + */ + @Override + public KnnVectorsWriter fieldsWriter(final SegmentWriteState state) throws IOException { + return new NativeEngines990KnnVectorsWriter(state, flatVectorsFormat.fieldsWriter(state), approximateThreshold); + } + + /** + * Returns a {@link KnnVectorsReader} to read the vectors from the index. + * + * @param state {@link SegmentReadState} + */ + @Override + public KnnVectorsReader fieldsReader(final SegmentReadState state) throws IOException { + return new NativeEngines990KnnVectorsReader(state, flatVectorsFormat.fieldsReader(state)); + } + + @Override + public String toString() { + return "NativeEngines99KnnVectorsFormat(name=" + + this.getClass().getSimpleName() + + ", flatVectorsFormat=" + + flatVectorsFormat + + ", approximateThreshold=" + + approximateThreshold + + ")"; + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsReader.java b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsReader.java new file mode 100644 index 000000000..efabc3a70 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsReader.java @@ -0,0 +1,232 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.codec.KNN990Codec; + +import org.apache.lucene.codecs.KnnVectorsReader; +import org.apache.lucene.codecs.hnsw.FlatVectorsReader; +import org.apache.lucene.index.ByteVectorValues; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.FloatVectorValues; +import org.apache.lucene.index.SegmentReadState; +import org.apache.lucene.search.KnnCollector; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.TopDocs; +import org.apache.lucene.search.TotalHits; +import org.apache.lucene.util.Bits; +import org.apache.lucene.util.IOUtils; +import org.opensearch.common.UUIDs; +import org.opensearch.knn.index.codec.util.KNNCodecUtil; +import org.opensearch.knn.index.codec.util.NativeMemoryCacheKeyHelper; +import org.opensearch.knn.index.memory.NativeMemoryCacheManager; +import org.opensearch.knn.index.quantizationservice.QuantizationService; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationStateCacheManager; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationStateReadConfig; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Vectors reader class for reading the flat vectors for native engines. The class provides methods for iterating + * over the vectors and retrieving their values. + */ +public class NativeEngines990KnnVectorsReader extends KnnVectorsReader { + + private final FlatVectorsReader flatVectorsReader; + private Map quantizationStateCacheKeyPerField; + private SegmentReadState segmentReadState; + private final List cacheKeys; + + public NativeEngines990KnnVectorsReader(final SegmentReadState state, final FlatVectorsReader flatVectorsReader) { + this.flatVectorsReader = flatVectorsReader; + this.segmentReadState = state; + this.cacheKeys = getVectorCacheKeysFromSegmentReaderState(state); + loadCacheKeyMap(); + } + + /** + * Checks consistency of this reader. + * + *

Note that this may be costly in terms of I/O, e.g. may involve computing a checksum value + * against large data files. + * + */ + @Override + public void checkIntegrity() throws IOException { + flatVectorsReader.checkIntegrity(); + } + + /** + * Returns the {@link FloatVectorValues} for the given {@code field}. The behavior is undefined if + * the given field doesn't have KNN vectors enabled on its {@link FieldInfo}. The return value is + * never {@code null}. + * + * @param field {@link String} + */ + @Override + public FloatVectorValues getFloatVectorValues(final String field) throws IOException { + return flatVectorsReader.getFloatVectorValues(field); + } + + /** + * Returns the {@link ByteVectorValues} for the given {@code field}. The behavior is undefined if + * the given field doesn't have KNN vectors enabled on its {@link FieldInfo}. The return value is + * never {@code null}. + * + * @param field {@link String} + */ + @Override + public ByteVectorValues getByteVectorValues(final String field) throws IOException { + return flatVectorsReader.getByteVectorValues(field); + } + + /** + * Return the k nearest neighbor documents as determined by comparison of their vector values for + * this field, to the given vector, by the field's similarity function. The score of each document + * is derived from the vector similarity in a way that ensures scores are positive and that a + * larger score corresponds to a higher ranking. + * + *

The search is allowed to be approximate, meaning the results are not guaranteed to be the + * true k closest neighbors. For large values of k (for example when k is close to the total + * number of documents), the search may also retrieve fewer than k documents. + * + *

The returned {@link TopDocs} will contain a {@link ScoreDoc} for each nearest neighbor, in + * order of their similarity to the query vector (decreasing scores). The {@link TotalHits} + * contains the number of documents visited during the search. If the search stopped early because + * it hit {@code visitedLimit}, it is indicated through the relation {@code + * TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO}. + * + *

The behavior is undefined if the given field doesn't have KNN vectors enabled on its {@link + * FieldInfo}. The return value is never {@code null}. + * + * @param field the vector field to search + * @param target the vector-valued query + * @param knnCollector a KnnResults collector and relevant settings for gathering vector results + * @param acceptDocs {@link Bits} that represents the allowed documents to match, or {@code null} + * if they are all allowed to match. + */ + @Override + public void search(String field, float[] target, KnnCollector knnCollector, Bits acceptDocs) throws IOException { + // TODO: This is a temporary hack where we are using KNNCollector to initialize the quantization state. + if (knnCollector instanceof QuantizationConfigKNNCollector) { + String cacheKey = quantizationStateCacheKeyPerField.get(field); + FieldInfo fieldInfo = segmentReadState.fieldInfos.fieldInfo(field); + QuantizationState quantizationState = QuantizationStateCacheManager.getInstance() + .getQuantizationState( + new QuantizationStateReadConfig( + segmentReadState, + QuantizationService.getInstance().getQuantizationParams(fieldInfo), + field, + cacheKey + ) + ); + ((QuantizationConfigKNNCollector) knnCollector).setQuantizationState(quantizationState); + return; + } + throw new UnsupportedOperationException("Search functionality using codec is not supported with Native Engine Reader"); + } + + /** + * Return the k nearest neighbor documents as determined by comparison of their vector values for + * this field, to the given vector, by the field's similarity function. The score of each document + * is derived from the vector similarity in a way that ensures scores are positive and that a + * larger score corresponds to a higher ranking. + * + *

The search is allowed to be approximate, meaning the results are not guaranteed to be the + * true k closest neighbors. For large values of k (for example when k is close to the total + * number of documents), the search may also retrieve fewer than k documents. + * + *

The returned {@link TopDocs} will contain a {@link ScoreDoc} for each nearest neighbor, in + * order of their similarity to the query vector (decreasing scores). The {@link TotalHits} + * contains the number of documents visited during the search. If the search stopped early because + * it hit {@code visitedLimit}, it is indicated through the relation {@code + * TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO}. + * + *

The behavior is undefined if the given field doesn't have KNN vectors enabled on its {@link + * FieldInfo}. The return value is never {@code null}. + * + * @param field the vector field to search + * @param target the vector-valued query + * @param knnCollector a KnnResults collector and relevant settings for gathering vector results + * @param acceptDocs {@link Bits} that represents the allowed documents to match, or {@code null} + * if they are all allowed to match. + */ + @Override + public void search(String field, byte[] target, KnnCollector knnCollector, Bits acceptDocs) throws IOException { + throw new UnsupportedOperationException("Search functionality using codec is not supported with Native Engine Reader"); + } + + /** + * Closes this stream and releases any system resources associated + * with it. If the stream is already closed then invoking this + * method has no effect. + * + *

As noted in {@link AutoCloseable#close()}, cases where the + * close may fail require careful attention. It is strongly advised + * to relinquish the underlying resources and to internally + * mark the {@code Closeable} as closed, prior to throwing + * the {@code IOException}. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void close() throws IOException { + // Clean up allocated vector indices resources from cache. + final NativeMemoryCacheManager nativeMemoryCacheManager = NativeMemoryCacheManager.getInstance(); + cacheKeys.forEach(nativeMemoryCacheManager::invalidate); + + // Close a reader. + IOUtils.close(flatVectorsReader); + + // Clean up quantized state cache. + if (quantizationStateCacheKeyPerField != null) { + final QuantizationStateCacheManager quantizationStateCacheManager = QuantizationStateCacheManager.getInstance(); + for (String cacheKey : quantizationStateCacheKeyPerField.values()) { + quantizationStateCacheManager.evict(cacheKey); + } + } + } + + /** + * Return the memory usage of this object in bytes. Negative values are illegal. + */ + @Override + public long ramBytesUsed() { + return flatVectorsReader.ramBytesUsed(); + } + + private void loadCacheKeyMap() { + quantizationStateCacheKeyPerField = new HashMap<>(); + for (FieldInfo fieldInfo : segmentReadState.fieldInfos) { + String cacheKey = UUIDs.base64UUID(); + quantizationStateCacheKeyPerField.put(fieldInfo.getName(), cacheKey); + } + } + + private static List getVectorCacheKeysFromSegmentReaderState(SegmentReadState segmentReadState) { + final List cacheKeys = new ArrayList<>(); + + for (FieldInfo field : segmentReadState.fieldInfos) { + final String vectorIndexFileName = KNNCodecUtil.getNativeEngineFileFromFieldInfo(field, segmentReadState.segmentInfo); + if (vectorIndexFileName == null) { + continue; + } + final String cacheKey = NativeMemoryCacheKeyHelper.constructCacheKey(vectorIndexFileName, segmentReadState.segmentInfo); + cacheKeys.add(cacheKey); + } + + return cacheKeys; + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java new file mode 100644 index 000000000..7c8636577 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriter.java @@ -0,0 +1,295 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.codec.KNN990Codec; + +import lombok.extern.log4j.Log4j2; +import org.apache.lucene.codecs.KnnFieldVectorsWriter; +import org.apache.lucene.codecs.KnnVectorsWriter; +import org.apache.lucene.codecs.hnsw.FlatVectorsWriter; +import org.apache.lucene.index.ByteVectorValues; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.FloatVectorValues; +import org.apache.lucene.index.MergeState; +import org.apache.lucene.index.SegmentWriteState; +import org.apache.lucene.index.Sorter; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.util.IOUtils; +import org.apache.lucene.util.RamUsageEstimator; +import org.opensearch.common.StopWatch; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.codec.nativeindex.NativeIndexWriter; +import org.opensearch.knn.index.quantizationservice.QuantizationService; +import org.opensearch.knn.index.vectorvalues.KNNVectorValues; +import org.opensearch.knn.plugin.stats.KNNGraphValue; +import org.opensearch.knn.quantization.models.quantizationParams.QuantizationParams; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +import static org.opensearch.knn.common.FieldInfoExtractor.extractVectorDataType; +import static org.opensearch.knn.index.vectorvalues.KNNVectorValuesFactory.getVectorValues; + +/** + * A KNNVectorsWriter class for writing the vector data strcutures and flat vectors for Native Engines. + */ +@Log4j2 +public class NativeEngines990KnnVectorsWriter extends KnnVectorsWriter { + private static final long SHALLOW_SIZE = RamUsageEstimator.shallowSizeOfInstance(NativeEngines990KnnVectorsWriter.class); + + private final SegmentWriteState segmentWriteState; + private final FlatVectorsWriter flatVectorsWriter; + private KNN990QuantizationStateWriter quantizationStateWriter; + private final List> fields = new ArrayList<>(); + private boolean finished; + private final Integer approximateThreshold; + + public NativeEngines990KnnVectorsWriter( + SegmentWriteState segmentWriteState, + FlatVectorsWriter flatVectorsWriter, + Integer approximateThreshold + ) { + this.segmentWriteState = segmentWriteState; + this.flatVectorsWriter = flatVectorsWriter; + this.approximateThreshold = approximateThreshold; + } + + /** + * Add new field for indexing. + * @param fieldInfo {@link FieldInfo} + */ + @Override + public KnnFieldVectorsWriter addField(final FieldInfo fieldInfo) throws IOException { + final NativeEngineFieldVectorsWriter newField = NativeEngineFieldVectorsWriter.create( + fieldInfo, + flatVectorsWriter.addField(fieldInfo), + segmentWriteState.infoStream + ); + fields.add(newField); + return newField; + } + + /** + * Flush all buffered data on disk. This is not fsync. This is lucene flush. + * + * @param maxDoc int + * @param sortMap {@link Sorter.DocMap} + */ + @Override + public void flush(int maxDoc, final Sorter.DocMap sortMap) throws IOException { + flatVectorsWriter.flush(maxDoc, sortMap); + + for (final NativeEngineFieldVectorsWriter field : fields) { + final FieldInfo fieldInfo = field.getFieldInfo(); + final VectorDataType vectorDataType = extractVectorDataType(fieldInfo); + int totalLiveDocs = field.getVectors().size(); + if (totalLiveDocs == 0) { + log.debug("[Flush] No live docs for field {}", fieldInfo.getName()); + continue; + } + final Supplier> knnVectorValuesSupplier = () -> getVectorValues( + vectorDataType, + field.getDocsWithField(), + field.getVectors() + ); + final QuantizationState quantizationState = train(field.getFieldInfo(), knnVectorValuesSupplier, totalLiveDocs); + // Check only after quantization state writer finish writing its state, since it is required + // even if there are no graph files in segment, which will be later used by exact search + if (shouldSkipBuildingVectorDataStructure(totalLiveDocs)) { + log.info( + "Skip building vector data structure for field: {}, as liveDoc: {} is less than the threshold {} during flush", + fieldInfo.name, + totalLiveDocs, + approximateThreshold + ); + continue; + } + final NativeIndexWriter writer = NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, quantizationState); + final KNNVectorValues knnVectorValues = knnVectorValuesSupplier.get(); + + StopWatch stopWatch = new StopWatch().start(); + writer.flushIndex(knnVectorValues, totalLiveDocs); + long time_in_millis = stopWatch.stop().totalTime().millis(); + KNNGraphValue.REFRESH_TOTAL_TIME_IN_MILLIS.incrementBy(time_in_millis); + log.debug("Flush took {} ms for vector field [{}]", time_in_millis, fieldInfo.getName()); + } + } + + @Override + public void mergeOneField(final FieldInfo fieldInfo, final MergeState mergeState) throws IOException { + // This will ensure that we are merging the FlatIndex during force merge. + flatVectorsWriter.mergeOneField(fieldInfo, mergeState); + + final VectorDataType vectorDataType = extractVectorDataType(fieldInfo); + final Supplier> knnVectorValuesSupplier = () -> getKNNVectorValuesForMerge( + vectorDataType, + fieldInfo, + mergeState + ); + int totalLiveDocs = getLiveDocs(knnVectorValuesSupplier.get()); + if (totalLiveDocs == 0) { + log.debug("[Merge] No live docs for field {}", fieldInfo.getName()); + return; + } + + final QuantizationState quantizationState = train(fieldInfo, knnVectorValuesSupplier, totalLiveDocs); + // Check only after quantization state writer finish writing its state, since it is required + // even if there are no graph files in segment, which will be later used by exact search + if (shouldSkipBuildingVectorDataStructure(totalLiveDocs)) { + log.info( + "Skip building vector data structure for field: {}, as liveDoc: {} is less than the threshold {} during merge", + fieldInfo.name, + totalLiveDocs, + approximateThreshold + ); + return; + } + final NativeIndexWriter writer = NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, quantizationState); + final KNNVectorValues knnVectorValues = knnVectorValuesSupplier.get(); + + StopWatch stopWatch = new StopWatch().start(); + + writer.mergeIndex(knnVectorValues, totalLiveDocs); + + long time_in_millis = stopWatch.stop().totalTime().millis(); + KNNGraphValue.MERGE_TOTAL_TIME_IN_MILLIS.incrementBy(time_in_millis); + log.debug("Merge took {} ms for vector field [{}]", time_in_millis, fieldInfo.getName()); + } + + /** + * Called once at the end before close + */ + @Override + public void finish() throws IOException { + if (finished) { + throw new IllegalStateException("NativeEnginesKNNVectorsWriter is already finished"); + } + finished = true; + if (quantizationStateWriter != null) { + quantizationStateWriter.writeFooter(); + } + flatVectorsWriter.finish(); + } + + /** + * Closes this stream and releases any system resources associated + * with it. If the stream is already closed then invoking this + * method has no effect. + * + *

As noted in {@link AutoCloseable#close()}, cases where the + * close may fail require careful attention. It is strongly advised + * to relinquish the underlying resources and to internally + * mark the {@code Closeable} as closed, prior to throwing + * the {@code IOException}. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void close() throws IOException { + if (quantizationStateWriter != null) { + quantizationStateWriter.closeOutput(); + } + IOUtils.close(flatVectorsWriter); + } + + /** + * Return the memory usage of this object in bytes. Negative values are illegal. + */ + @Override + public long ramBytesUsed() { + return SHALLOW_SIZE + flatVectorsWriter.ramBytesUsed() + fields.stream() + .mapToLong(NativeEngineFieldVectorsWriter::ramBytesUsed) + .sum(); + } + + /** + * Retrieves the {@link KNNVectorValues} for a specific field during a merge operation, based on the vector data type. + * + * @param vectorDataType The {@link VectorDataType} representing the type of vectors stored. + * @param fieldInfo The {@link FieldInfo} object containing metadata about the field. + * @param mergeState The {@link MergeState} representing the state of the merge operation. + * @param The type of vectors being processed. + * @return The {@link KNNVectorValues} associated with the field during the merge. + * @throws IOException If an I/O error occurs during the retrieval. + */ + private KNNVectorValues getKNNVectorValuesForMerge( + final VectorDataType vectorDataType, + final FieldInfo fieldInfo, + final MergeState mergeState + ) { + try { + switch (fieldInfo.getVectorEncoding()) { + case FLOAT32: + FloatVectorValues mergedFloats = MergedVectorValues.mergeFloatVectorValues(fieldInfo, mergeState); + return getVectorValues(vectorDataType, mergedFloats); + case BYTE: + ByteVectorValues mergedBytes = MergedVectorValues.mergeByteVectorValues(fieldInfo, mergeState); + return getVectorValues(vectorDataType, mergedBytes); + default: + throw new IllegalStateException("Unsupported vector encoding [" + fieldInfo.getVectorEncoding() + "]"); + } + } catch (final IOException e) { + log.error("Unable to merge vectors for field [{}]", fieldInfo.getName(), e); + throw new IllegalStateException("Unable to merge vectors for field [" + fieldInfo.getName() + "]", e); + } + } + + private QuantizationState train( + final FieldInfo fieldInfo, + final Supplier> knnVectorValuesSupplier, + final int totalLiveDocs + ) throws IOException { + + final QuantizationService quantizationService = QuantizationService.getInstance(); + final QuantizationParams quantizationParams = quantizationService.getQuantizationParams(fieldInfo); + QuantizationState quantizationState = null; + if (quantizationParams != null && totalLiveDocs > 0) { + initQuantizationStateWriterIfNecessary(); + KNNVectorValues knnVectorValues = knnVectorValuesSupplier.get(); + quantizationState = quantizationService.train(quantizationParams, knnVectorValues, totalLiveDocs); + quantizationStateWriter.writeState(fieldInfo.getFieldNumber(), quantizationState); + } + + return quantizationState; + } + + /** + * The {@link KNNVectorValues} will be exhausted after this function run. So make sure that you are not sending the + * vectorsValues object which you plan to use later + */ + private int getLiveDocs(KNNVectorValues vectorValues) throws IOException { + // Count all the live docs as there vectorValues.totalLiveDocs() just gives the cost for the FloatVectorValues, + // and doesn't tell the correct number of docs, if there are deleted docs in the segment. So we are counting + // the total live docs here. + int liveDocs = 0; + while (vectorValues.nextDoc() != DocIdSetIterator.NO_MORE_DOCS) { + liveDocs++; + } + return liveDocs; + } + + private void initQuantizationStateWriterIfNecessary() throws IOException { + if (quantizationStateWriter == null) { + quantizationStateWriter = new KNN990QuantizationStateWriter(segmentWriteState); + quantizationStateWriter.writeHeader(segmentWriteState); + } + } + + private boolean shouldSkipBuildingVectorDataStructure(final long docCount) { + if (approximateThreshold < 0) { + return true; + } + return docCount < approximateThreshold; + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/QuantizationConfigKNNCollector.java b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/QuantizationConfigKNNCollector.java new file mode 100644 index 000000000..295b0fe58 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/QuantizationConfigKNNCollector.java @@ -0,0 +1,64 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN990Codec; + +import lombok.Getter; +import lombok.Setter; +import org.apache.lucene.search.KnnCollector; +import org.apache.lucene.search.TopDocs; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; + +/** + * Collector used for passing the quantization state during query flow. + */ +@Setter +@Getter +public class QuantizationConfigKNNCollector implements KnnCollector { + + private QuantizationState quantizationState; + + private final String NATIVE_ENGINE_SEARCH_ERROR_MESSAGE = "Search functionality using codec is not supported with Native Engine Reader"; + + @Override + public boolean earlyTerminated() { + throw new UnsupportedOperationException(NATIVE_ENGINE_SEARCH_ERROR_MESSAGE); + } + + @Override + public void incVisitedCount(int i) { + throw new UnsupportedOperationException(NATIVE_ENGINE_SEARCH_ERROR_MESSAGE); + } + + @Override + public long visitedCount() { + throw new UnsupportedOperationException(NATIVE_ENGINE_SEARCH_ERROR_MESSAGE); + } + + @Override + public long visitLimit() { + throw new UnsupportedOperationException(NATIVE_ENGINE_SEARCH_ERROR_MESSAGE); + } + + @Override + public int k() { + throw new UnsupportedOperationException(NATIVE_ENGINE_SEARCH_ERROR_MESSAGE); + } + + @Override + public boolean collect(int i, float v) { + throw new UnsupportedOperationException(NATIVE_ENGINE_SEARCH_ERROR_MESSAGE); + } + + @Override + public float minCompetitiveSimilarity() { + throw new UnsupportedOperationException(NATIVE_ENGINE_SEARCH_ERROR_MESSAGE); + } + + @Override + public TopDocs topDocs() { + throw new UnsupportedOperationException(NATIVE_ENGINE_SEARCH_ERROR_MESSAGE); + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/UnitTestCodec.java b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/UnitTestCodec.java new file mode 100644 index 000000000..d651f410a --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNN990Codec/UnitTestCodec.java @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.codec.KNN990Codec; + +import org.apache.lucene.codecs.FilterCodec; +import org.apache.lucene.codecs.KnnVectorsFormat; +import org.apache.lucene.codecs.perfield.PerFieldKnnVectorsFormat; +import org.opensearch.knn.index.codec.KNNCodecVersion; + +/** + * This codec is for testing. The reason for putting this codec here is SPI is only scanning the src folder and not + * able to pick this class if its in test folder. Don't use this codec outside of testing + */ +public class UnitTestCodec extends FilterCodec { + private static final Integer BUILD_GRAPH_ALWAYS = 0; + + public UnitTestCodec() { + super("UnitTestCodec", KNNCodecVersion.current().getDefaultKnnCodecSupplier().get()); + } + + @Override + public KnnVectorsFormat knnVectorsFormat() { + return new PerFieldKnnVectorsFormat() { + @Override + public KnnVectorsFormat getKnnVectorsFormatForField(String field) { + return new NativeEngines990KnnVectorsFormat(BUILD_GRAPH_ALWAYS); + } + }; + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNNCodecFactory.java b/src/main/java/org/opensearch/knn/index/codec/KNNCodecFactory.java deleted file mode 100644 index 28b0e528a..000000000 --- a/src/main/java/org/opensearch/knn/index/codec/KNNCodecFactory.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ -package org.opensearch.knn.index.codec; - -import com.google.common.collect.ImmutableMap; -import org.apache.lucene.codecs.Codec; -import org.apache.lucene.backward_codecs.lucene91.Lucene91Codec; -import org.opensearch.knn.index.codec.KNN910Codec.KNN910Codec; - -import java.lang.reflect.Constructor; -import java.util.Map; - -/** - * Factory abstraction for KNN codec - */ -public class KNNCodecFactory { - - private static Map CODEC_BY_VERSION = ImmutableMap.of(KNNCodecVersion.KNN910, KNN910Codec.class); - - private static KNNCodecVersion LATEST_KNN_CODEC_VERSION = KNNCodecVersion.KNN910; - - public static Codec createKNNCodec(final Codec userCodec) { - return getCodec(LATEST_KNN_CODEC_VERSION, userCodec); - } - - public static Codec createKNNCodec(final KNNCodecVersion knnCodecVersion, final Codec userCodec) { - return getCodec(knnCodecVersion, userCodec); - } - - private static Codec getCodec(final KNNCodecVersion knnCodecVersion, final Codec userCodec) { - try { - Constructor constructor = CODEC_BY_VERSION.getOrDefault(knnCodecVersion, CODEC_BY_VERSION.get(LATEST_KNN_CODEC_VERSION)) - .getConstructor(Codec.class); - return (Codec) constructor.newInstance(userCodec); - } catch (Exception ex) { - throw new RuntimeException("Cannot create instance of KNN codec", ex); - } - } - - /** - * Factory abstraction for codec delegate - */ - public static class CodecDelegateFactory { - - public static Codec createKNN91DefaultDelegate() { - return new Lucene91Codec(); - } - } - - /** - * Collection of supported coded versions - */ - enum KNNCodecVersion { - KNN910 - } -} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNNCodecService.java b/src/main/java/org/opensearch/knn/index/codec/KNNCodecService.java index cae6f7fb8..9e210fcd9 100644 --- a/src/main/java/org/opensearch/knn/index/codec/KNNCodecService.java +++ b/src/main/java/org/opensearch/knn/index/codec/KNNCodecService.java @@ -8,14 +8,18 @@ import org.opensearch.index.codec.CodecServiceConfig; import org.apache.lucene.codecs.Codec; import org.opensearch.index.codec.CodecService; +import org.opensearch.index.mapper.MapperService; /** * KNNCodecService to inject the right KNNCodec version */ public class KNNCodecService extends CodecService { + private final MapperService mapperService; + public KNNCodecService(CodecServiceConfig codecServiceConfig) { - super(codecServiceConfig.getMapperService(), codecServiceConfig.getLogger()); + super(codecServiceConfig.getMapperService(), codecServiceConfig.getIndexSettings(), codecServiceConfig.getLogger()); + mapperService = codecServiceConfig.getMapperService(); } /** @@ -26,6 +30,6 @@ public KNNCodecService(CodecServiceConfig codecServiceConfig) { */ @Override public Codec codec(String name) { - return KNNCodecFactory.createKNNCodec(super.codec(name)); + return KNNCodecVersion.current().getKnnCodecSupplier().apply(super.codec(name), mapperService); } } diff --git a/src/main/java/org/opensearch/knn/index/codec/KNNCodecVersion.java b/src/main/java/org/opensearch/knn/index/codec/KNNCodecVersion.java new file mode 100644 index 000000000..fb9af0109 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/KNNCodecVersion.java @@ -0,0 +1,144 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.apache.lucene.backward_codecs.lucene91.Lucene91Codec; +import org.apache.lucene.backward_codecs.lucene92.Lucene92Codec; +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.backward_codecs.lucene94.Lucene94Codec; +import org.apache.lucene.backward_codecs.lucene95.Lucene95Codec; +import org.apache.lucene.backward_codecs.lucene99.Lucene99Codec; +import org.apache.lucene.codecs.lucene912.Lucene912Codec; +import org.apache.lucene.codecs.perfield.PerFieldKnnVectorsFormat; +import org.opensearch.index.mapper.MapperService; +import org.opensearch.knn.index.codec.KNN80Codec.KNN80CompoundFormat; +import org.opensearch.knn.index.codec.KNN80Codec.KNN80DocValuesFormat; +import org.opensearch.knn.index.codec.KNN910Codec.KNN910Codec; +import org.opensearch.knn.index.codec.KNN9120Codec.KNN9120Codec; +import org.opensearch.knn.index.codec.KNN920Codec.KNN920Codec; +import org.opensearch.knn.index.codec.KNN920Codec.KNN920PerFieldKnnVectorsFormat; +import org.opensearch.knn.index.codec.KNN940Codec.KNN940Codec; +import org.opensearch.knn.index.codec.KNN940Codec.KNN940PerFieldKnnVectorsFormat; +import org.opensearch.knn.index.codec.KNN950Codec.KNN950Codec; +import org.opensearch.knn.index.codec.KNN950Codec.KNN950PerFieldKnnVectorsFormat; +import org.opensearch.knn.index.codec.KNN990Codec.KNN990Codec; +import org.opensearch.knn.index.codec.KNN990Codec.KNN990PerFieldKnnVectorsFormat; + +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Abstraction for k-NN codec version, aggregates all details for specific version such as codec name, corresponding + * Lucene codec, formats including one for k-NN vector etc. + */ +@AllArgsConstructor +@Getter +public enum KNNCodecVersion { + + V_9_1_0( + "KNN910Codec", + new Lucene91Codec(), + null, + (delegate) -> new KNNFormatFacade( + new KNN80DocValuesFormat(delegate.docValuesFormat()), + new KNN80CompoundFormat(delegate.compoundFormat()) + ), + (userCodec, mapperService) -> new KNN910Codec(userCodec), + KNN910Codec::new + ), + + V_9_2_0( + "KNN920Codec", + new Lucene92Codec(), + new KNN920PerFieldKnnVectorsFormat(Optional.empty()), + (delegate) -> new KNNFormatFacade( + new KNN80DocValuesFormat(delegate.docValuesFormat()), + new KNN80CompoundFormat(delegate.compoundFormat()) + ), + (userCodec, mapperService) -> KNN920Codec.builder() + .delegate(userCodec) + .knnVectorsFormat(new KNN920PerFieldKnnVectorsFormat(Optional.ofNullable(mapperService))) + .build(), + KNN920Codec::new + ), + + V_9_4_0( + "KNN940Codec", + new Lucene94Codec(), + new KNN940PerFieldKnnVectorsFormat(Optional.empty()), + (delegate) -> new KNNFormatFacade( + new KNN80DocValuesFormat(delegate.docValuesFormat()), + new KNN80CompoundFormat(delegate.compoundFormat()) + ), + (userCodec, mapperService) -> KNN940Codec.builder() + .delegate(userCodec) + .knnVectorsFormat(new KNN940PerFieldKnnVectorsFormat(Optional.ofNullable(mapperService))) + .build(), + KNN940Codec::new + ), + + V_9_5_0( + "KNN950Codec", + new Lucene95Codec(), + new KNN950PerFieldKnnVectorsFormat(Optional.empty()), + (delegate) -> new KNNFormatFacade( + new KNN80DocValuesFormat(delegate.docValuesFormat()), + new KNN80CompoundFormat(delegate.compoundFormat()) + ), + (userCodec, mapperService) -> KNN950Codec.builder() + .delegate(userCodec) + .knnVectorsFormat(new KNN950PerFieldKnnVectorsFormat(Optional.ofNullable(mapperService))) + .build(), + KNN950Codec::new + ), + + V_9_9_0( + "KNN990Codec", + new Lucene99Codec(), + new KNN990PerFieldKnnVectorsFormat(Optional.empty()), + (delegate) -> new KNNFormatFacade( + new KNN80DocValuesFormat(delegate.docValuesFormat()), + new KNN80CompoundFormat(delegate.compoundFormat()) + ), + (userCodec, mapperService) -> KNN990Codec.builder() + .delegate(userCodec) + .knnVectorsFormat(new KNN990PerFieldKnnVectorsFormat(Optional.ofNullable(mapperService))) + .build(), + KNN990Codec::new + ), + + V_9_12_0( + "KNN9120Codec", + new Lucene912Codec(), + new KNN990PerFieldKnnVectorsFormat(Optional.empty()), + (delegate) -> new KNNFormatFacade( + new KNN80DocValuesFormat(delegate.docValuesFormat()), + new KNN80CompoundFormat(delegate.compoundFormat()) + ), + (userCodec, mapperService) -> KNN9120Codec.builder() + .delegate(userCodec) + .knnVectorsFormat(new KNN990PerFieldKnnVectorsFormat(Optional.ofNullable(mapperService))) + .build(), + KNN9120Codec::new + ); + + private static final KNNCodecVersion CURRENT = V_9_12_0; + + private final String codecName; + private final Codec defaultCodecDelegate; + private final PerFieldKnnVectorsFormat perFieldKnnVectorsFormat; + private final Function knnFormatFacadeSupplier; + private final BiFunction knnCodecSupplier; + private final Supplier defaultKnnCodecSupplier; + + public static final KNNCodecVersion current() { + return CURRENT; + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/KNNFormatFactory.java b/src/main/java/org/opensearch/knn/index/codec/KNNFormatFactory.java deleted file mode 100644 index 6742dc3f4..000000000 --- a/src/main/java/org/opensearch/knn/index/codec/KNNFormatFactory.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ -package org.opensearch.knn.index.codec; - -import org.apache.lucene.codecs.Codec; -import org.opensearch.knn.index.codec.KNN80Codec.KNN80CompoundFormat; -import org.opensearch.knn.index.codec.KNN80Codec.KNN80DocValuesFormat; - -/** - * Factory abstraction for KNN format facade creation - */ -public class KNNFormatFactory { - - public static KNNFormatFacade createKNN910Format(final Codec delegate) { - final KNNFormatFacade knnFormatFacade = new KNNFormatFacade( - new KNN80DocValuesFormat(delegate.docValuesFormat()), - new KNN80CompoundFormat(delegate.compoundFormat()) - ); - return knnFormatFacade; - } -} diff --git a/src/main/java/org/opensearch/knn/index/codec/nativeindex/DefaultIndexBuildStrategy.java b/src/main/java/org/opensearch/knn/index/codec/nativeindex/DefaultIndexBuildStrategy.java new file mode 100644 index 000000000..23c3ba116 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/nativeindex/DefaultIndexBuildStrategy.java @@ -0,0 +1,115 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.nativeindex; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.codec.nativeindex.model.BuildIndexParams; +import org.opensearch.knn.index.codec.transfer.OffHeapVectorTransfer; +import org.opensearch.knn.index.vectorvalues.KNNVectorValues; +import org.opensearch.knn.jni.JNIService; + +import java.io.IOException; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS; +import static org.opensearch.knn.common.KNNConstants.MODEL_ID; +import static org.opensearch.knn.common.KNNVectorUtil.intListToArray; +import static org.opensearch.knn.common.KNNVectorUtil.iterateVectorValuesOnce; +import static org.opensearch.knn.index.codec.transfer.OffHeapVectorTransferFactory.getVectorTransfer; + +/** + * Transfers all vectors to off heap and then builds an index + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +final class DefaultIndexBuildStrategy implements NativeIndexBuildStrategy { + + private static DefaultIndexBuildStrategy INSTANCE = new DefaultIndexBuildStrategy(); + + public static DefaultIndexBuildStrategy getInstance() { + return INSTANCE; + } + + /** + * Builds and writes a k-NN index using the provided vector values and index parameters. This method handles both + * quantized and non-quantized vectors, transferring them off-heap before building the index using native JNI services. + * + *

The method first iterates over the vector values to calculate the necessary bytes per vector. If quantization is + * enabled, the vectors are quantized before being transferred off-heap. Once all vectors are transferred, they are + * flushed and used to build the index. The index is then written to the specified path using JNI calls.

+ * + * @param indexInfo The {@link BuildIndexParams} containing the parameters and configuration for building the index. + * @throws IOException If an I/O error occurs during the process of building and writing the index. + */ + public void buildAndWriteIndex(final BuildIndexParams indexInfo) throws IOException { + final KNNVectorValues knnVectorValues = indexInfo.getVectorValues(); + // Needed to make sure we don't get 0 dimensions while initializing index + iterateVectorValuesOnce(knnVectorValues); + IndexBuildSetup indexBuildSetup = QuantizationIndexUtils.prepareIndexBuild(knnVectorValues, indexInfo); + + try ( + final OffHeapVectorTransfer vectorTransfer = getVectorTransfer( + indexInfo.getVectorDataType(), + indexBuildSetup.getBytesPerVector(), + indexInfo.getTotalLiveDocs() + ) + ) { + final List transferredDocIds = new ArrayList<>(indexInfo.getTotalLiveDocs()); + + while (knnVectorValues.docId() != NO_MORE_DOCS) { + Object vector = QuantizationIndexUtils.processAndReturnVector(knnVectorValues, indexBuildSetup); + // append is true here so off heap memory buffer isn't overwritten + vectorTransfer.transfer(vector, true); + transferredDocIds.add(knnVectorValues.docId()); + knnVectorValues.nextDoc(); + } + vectorTransfer.flush(true); + + final Map params = indexInfo.getParameters(); + long vectorAddress = vectorTransfer.getVectorAddress(); + // Currently this is if else as there are only two cases, with more cases this will have to be made + // more maintainable + if (params.containsKey(MODEL_ID)) { + AccessController.doPrivileged((PrivilegedAction) () -> { + JNIService.createIndexFromTemplate( + intListToArray(transferredDocIds), + vectorAddress, + indexBuildSetup.getDimensions(), + indexInfo.getIndexOutputWithBuffer(), + (byte[]) params.get(KNNConstants.MODEL_BLOB_PARAMETER), + params, + indexInfo.getKnnEngine() + ); + return null; + }); + } else { + AccessController.doPrivileged((PrivilegedAction) () -> { + JNIService.createIndex( + intListToArray(transferredDocIds), + vectorAddress, + indexBuildSetup.getDimensions(), + indexInfo.getIndexOutputWithBuffer(), + params, + indexInfo.getKnnEngine() + ); + return null; + }); + } + // Resetting here as vectors are deleted in JNILayer for non-iterative index builds + vectorTransfer.reset(); + } catch (Exception exception) { + throw new RuntimeException( + "Failed to build index, field name " + indexInfo.getFieldName() + ", parameters " + indexInfo, + exception + ); + } + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/nativeindex/IndexBuildSetup.java b/src/main/java/org/opensearch/knn/index/codec/nativeindex/IndexBuildSetup.java new file mode 100644 index 000000000..c6c999c07 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/nativeindex/IndexBuildSetup.java @@ -0,0 +1,40 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.nativeindex; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.opensearch.knn.quantization.models.quantizationOutput.QuantizationOutput; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; + +/** + * IndexBuildSetup encapsulates the configuration and parameters required for building an index. + * This includes the size of each vector, the dimensions of the vectors, and any quantization-related + * settings such as the output and state of quantization. + */ +@Getter +@AllArgsConstructor +final class IndexBuildSetup { + /** + * The number of bytes per vector. + */ + private final int bytesPerVector; + + /** + * Dimension of Vector for Indexing + */ + private final int dimensions; + + /** + * The quantization output that will hold the quantized vector. + */ + private final QuantizationOutput quantizationOutput; + + /** + * The state of quantization, which may include parameters and trained models. + */ + private final QuantizationState quantizationState; +} diff --git a/src/main/java/org/opensearch/knn/index/codec/nativeindex/MemOptimizedNativeIndexBuildStrategy.java b/src/main/java/org/opensearch/knn/index/codec/nativeindex/MemOptimizedNativeIndexBuildStrategy.java new file mode 100644 index 000000000..81f5915a7 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/nativeindex/MemOptimizedNativeIndexBuildStrategy.java @@ -0,0 +1,136 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.nativeindex; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.opensearch.knn.index.codec.nativeindex.model.BuildIndexParams; +import org.opensearch.knn.index.codec.transfer.OffHeapVectorTransfer; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.vectorvalues.KNNVectorValues; +import org.opensearch.knn.jni.JNIService; + +import java.io.IOException; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS; +import static org.opensearch.knn.common.KNNVectorUtil.intListToArray; +import static org.opensearch.knn.common.KNNVectorUtil.iterateVectorValuesOnce; +import static org.opensearch.knn.index.codec.transfer.OffHeapVectorTransferFactory.getVectorTransfer; + +/** + * Iteratively builds the index. Iterative builds are memory optimized as it does not require all vectors + * to be transferred. It transfers vectors in small batches, builds index and can clear the offheap space where + * the vectors were transferred + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +final class MemOptimizedNativeIndexBuildStrategy implements NativeIndexBuildStrategy { + + private static MemOptimizedNativeIndexBuildStrategy INSTANCE = new MemOptimizedNativeIndexBuildStrategy(); + + public static MemOptimizedNativeIndexBuildStrategy getInstance() { + return INSTANCE; + } + + /** + * Builds and writes a k-NN index using the provided vector values and index parameters. This method handles both + * quantized and non-quantized vectors, transferring them off-heap before building the index using native JNI services. + * + *

The method first iterates over the vector values to calculate the necessary bytes per vector. If quantization is + * enabled, the vectors are quantized before being transferred off-heap. Once all vectors are transferred, they are + * flushed and used to build the index. The index is then written to the specified path using JNI calls.

+ * + * @param indexInfo The {@link BuildIndexParams} containing the parameters and configuration for building the index. + * @throws IOException If an I/O error occurs during the process of building and writing the index. + */ + public void buildAndWriteIndex(final BuildIndexParams indexInfo) throws IOException { + final KNNVectorValues knnVectorValues = indexInfo.getVectorValues(); + // Needed to make sure we don't get 0 dimensions while initializing index + iterateVectorValuesOnce(knnVectorValues); + KNNEngine engine = indexInfo.getKnnEngine(); + Map indexParameters = indexInfo.getParameters(); + IndexBuildSetup indexBuildSetup = QuantizationIndexUtils.prepareIndexBuild(knnVectorValues, indexInfo); + + // Initialize the index + long indexMemoryAddress = AccessController.doPrivileged( + (PrivilegedAction) () -> JNIService.initIndex( + indexInfo.getTotalLiveDocs(), + indexBuildSetup.getDimensions(), + indexParameters, + engine + ) + ); + + try ( + final OffHeapVectorTransfer vectorTransfer = getVectorTransfer( + indexInfo.getVectorDataType(), + indexBuildSetup.getBytesPerVector(), + indexInfo.getTotalLiveDocs() + ) + ) { + + final List transferredDocIds = new ArrayList<>(vectorTransfer.getTransferLimit()); + + while (knnVectorValues.docId() != NO_MORE_DOCS) { + Object vector = QuantizationIndexUtils.processAndReturnVector(knnVectorValues, indexBuildSetup); + // append is false to be able to reuse the memory location + boolean transferred = vectorTransfer.transfer(vector, false); + transferredDocIds.add(knnVectorValues.docId()); + if (transferred) { + // Insert vectors + long vectorAddress = vectorTransfer.getVectorAddress(); + AccessController.doPrivileged((PrivilegedAction) () -> { + JNIService.insertToIndex( + intListToArray(transferredDocIds), + vectorAddress, + indexBuildSetup.getDimensions(), + indexParameters, + indexMemoryAddress, + engine + ); + return null; + }); + transferredDocIds.clear(); + } + knnVectorValues.nextDoc(); + } + + boolean flush = vectorTransfer.flush(false); + // Need to make sure that the flushed vectors are indexed + if (flush) { + long vectorAddress = vectorTransfer.getVectorAddress(); + AccessController.doPrivileged((PrivilegedAction) () -> { + JNIService.insertToIndex( + intListToArray(transferredDocIds), + vectorAddress, + indexBuildSetup.getDimensions(), + indexParameters, + indexMemoryAddress, + engine + ); + return null; + }); + transferredDocIds.clear(); + } + + // Write vector + AccessController.doPrivileged((PrivilegedAction) () -> { + JNIService.writeIndex(indexInfo.getIndexOutputWithBuffer(), indexMemoryAddress, engine, indexParameters); + return null; + }); + + } catch (Exception exception) { + throw new RuntimeException( + "Failed to build index, field name [" + indexInfo.getFieldName() + "], parameters " + indexInfo, + exception + ); + } + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/nativeindex/NativeIndexBuildStrategy.java b/src/main/java/org/opensearch/knn/index/codec/nativeindex/NativeIndexBuildStrategy.java new file mode 100644 index 000000000..8c9f6de97 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/nativeindex/NativeIndexBuildStrategy.java @@ -0,0 +1,18 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.nativeindex; + +import org.opensearch.knn.index.codec.nativeindex.model.BuildIndexParams; + +import java.io.IOException; + +/** + * Interface which dictates how the index needs to be built + */ +public interface NativeIndexBuildStrategy { + + void buildAndWriteIndex(BuildIndexParams indexInfo) throws IOException; +} diff --git a/src/main/java/org/opensearch/knn/index/codec/nativeindex/NativeIndexWriter.java b/src/main/java/org/opensearch/knn/index/codec/nativeindex/NativeIndexWriter.java new file mode 100644 index 000000000..27a1ecfb6 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/nativeindex/NativeIndexWriter.java @@ -0,0 +1,322 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.nativeindex; + +import lombok.AllArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.apache.lucene.codecs.CodecUtil; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.SegmentWriteState; +import org.apache.lucene.store.IndexOutput; +import org.opensearch.common.Nullable; +import org.opensearch.common.xcontent.XContentHelper; +import org.opensearch.core.common.bytes.BytesArray; +import org.opensearch.core.xcontent.DeprecationHandler; +import org.opensearch.core.xcontent.MediaTypeRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.knn.common.FieldInfoExtractor; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.codec.nativeindex.model.BuildIndexParams; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.index.quantizationservice.QuantizationService; +import org.opensearch.knn.index.store.IndexOutputWithBuffer; +import org.opensearch.knn.index.util.IndexUtil; +import org.opensearch.knn.index.vectorvalues.KNNVectorValues; +import org.opensearch.knn.indices.Model; +import org.opensearch.knn.indices.ModelCache; +import org.opensearch.knn.plugin.stats.KNNGraphValue; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS; +import static org.opensearch.knn.common.FieldInfoExtractor.extractKNNEngine; +import static org.opensearch.knn.common.FieldInfoExtractor.extractVectorDataType; +import static org.opensearch.knn.common.KNNConstants.MODEL_ID; +import static org.opensearch.knn.common.KNNConstants.PARAMETERS; +import static org.opensearch.knn.common.KNNVectorUtil.iterateVectorValuesOnce; +import static org.opensearch.knn.index.codec.util.KNNCodecUtil.buildEngineFileName; +import static org.opensearch.knn.index.engine.faiss.Faiss.FAISS_BINARY_INDEX_DESCRIPTION_PREFIX; + +/** + * Writes KNN Index for a field in a segment. This is intended to be used for native engines + */ +@AllArgsConstructor +@Log4j2 +public class NativeIndexWriter { + private static final Long CRC32_CHECKSUM_SANITY = 0xFFFFFFFF00000000L; + + private final SegmentWriteState state; + private final FieldInfo fieldInfo; + private final NativeIndexBuildStrategy indexBuilder; + @Nullable + private final QuantizationState quantizationState; + + /** + * Gets the correct writer type from fieldInfo + * + * @param fieldInfo + * @return correct NativeIndexWriter to make index specified in fieldInfo + */ + public static NativeIndexWriter getWriter(final FieldInfo fieldInfo, SegmentWriteState state) { + return createWriter(fieldInfo, state, null); + } + + /** + * Gets the correct writer type for the specified field, using a given QuantizationModel. + * + * This method returns a NativeIndexWriter instance that is tailored to the specific characteristics + * of the field described by the provided FieldInfo. It determines whether to use a template-based + * writer or an iterative approach based on the engine type and whether the field is associated with a template. + * + * If quantization is required, the QuantizationModel is passed to the writer to facilitate the quantization process. + * + * @param fieldInfo The FieldInfo object containing metadata about the field for which the writer is needed. + * @param state The SegmentWriteState representing the current segment's writing context. + * @param quantizationState The QuantizationState that contains quantization state required for quantization + * @return A NativeIndexWriter instance appropriate for the specified field, configured with or without quantization. + */ + public static NativeIndexWriter getWriter( + final FieldInfo fieldInfo, + final SegmentWriteState state, + final QuantizationState quantizationState + ) { + return createWriter(fieldInfo, state, quantizationState); + } + + /** + * flushes the index + * + * @param knnVectorValues + * @throws IOException + */ + public void flushIndex(final KNNVectorValues knnVectorValues, int totalLiveDocs) throws IOException { + iterateVectorValuesOnce(knnVectorValues); + buildAndWriteIndex(knnVectorValues, totalLiveDocs); + recordRefreshStats(); + } + + /** + * Merges kNN index + * @param knnVectorValues + * @throws IOException + */ + public void mergeIndex(final KNNVectorValues knnVectorValues, int totalLiveDocs) throws IOException { + iterateVectorValuesOnce(knnVectorValues); + if (knnVectorValues.docId() == NO_MORE_DOCS) { + // This is in place so we do not add metrics + log.debug("Skipping mergeIndex, vector values are already iterated for {}", fieldInfo.name); + return; + } + + long bytesPerVector = knnVectorValues.bytesPerVector(); + startMergeStats(totalLiveDocs, bytesPerVector); + buildAndWriteIndex(knnVectorValues, totalLiveDocs); + endMergeStats(totalLiveDocs, bytesPerVector); + } + + private void buildAndWriteIndex(final KNNVectorValues knnVectorValues, int totalLiveDocs) throws IOException { + if (totalLiveDocs == 0) { + log.debug("No live docs for field {}", fieldInfo.name); + return; + } + + final KNNEngine knnEngine = extractKNNEngine(fieldInfo); + final String engineFileName = buildEngineFileName( + state.segmentInfo.name, + knnEngine.getVersion(), + fieldInfo.name, + knnEngine.getExtension() + ); + try (IndexOutput output = state.directory.createOutput(engineFileName, state.context)) { + final IndexOutputWithBuffer indexOutputWithBuffer = new IndexOutputWithBuffer(output); + final BuildIndexParams nativeIndexParams = indexParams( + fieldInfo, + indexOutputWithBuffer, + knnEngine, + knnVectorValues, + totalLiveDocs + ); + indexBuilder.buildAndWriteIndex(nativeIndexParams); + CodecUtil.writeFooter(output); + } + } + + // The logic for building parameters need to be cleaned up. There are various cases handled here + // Currently it falls under two categories - with model and without model. Without model is further divided based on vector data type + // TODO: Refactor this so its scalable. Possibly move it out of this class + private BuildIndexParams indexParams( + FieldInfo fieldInfo, + IndexOutputWithBuffer indexOutputWithBuffer, + KNNEngine knnEngine, + KNNVectorValues vectorValues, + int totalLiveDocs + ) throws IOException { + final Map parameters; + VectorDataType vectorDataType; + if (quantizationState != null) { + vectorDataType = QuantizationService.getInstance().getVectorDataTypeForTransfer(fieldInfo); + } else { + vectorDataType = extractVectorDataType(fieldInfo); + } + if (fieldInfo.attributes().containsKey(MODEL_ID)) { + Model model = getModel(fieldInfo); + parameters = getTemplateParameters(fieldInfo, model); + } else { + parameters = getParameters(fieldInfo, vectorDataType, knnEngine); + } + + return BuildIndexParams.builder() + .fieldName(fieldInfo.name) + .parameters(parameters) + .vectorDataType(vectorDataType) + .knnEngine(knnEngine) + .indexOutputWithBuffer(indexOutputWithBuffer) + .quantizationState(quantizationState) + .vectorValues(vectorValues) + .totalLiveDocs(totalLiveDocs) + .build(); + } + + private Map getParameters(FieldInfo fieldInfo, VectorDataType vectorDataType, KNNEngine knnEngine) throws IOException { + Map parameters = new HashMap<>(); + Map fieldAttributes = fieldInfo.attributes(); + String parametersString = fieldAttributes.get(KNNConstants.PARAMETERS); + + // parametersString will be null when legacy mapper is used + if (parametersString == null) { + parameters.put(KNNConstants.SPACE_TYPE, fieldAttributes.getOrDefault(KNNConstants.SPACE_TYPE, SpaceType.DEFAULT.getValue())); + + String efConstruction = fieldAttributes.get(KNNConstants.HNSW_ALGO_EF_CONSTRUCTION); + Map algoParams = new HashMap<>(); + if (efConstruction != null) { + algoParams.put(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, Integer.parseInt(efConstruction)); + } + + String m = fieldAttributes.get(KNNConstants.HNSW_ALGO_M); + if (m != null) { + algoParams.put(KNNConstants.METHOD_PARAMETER_M, Integer.parseInt(m)); + } + parameters.put(PARAMETERS, algoParams); + } else { + parameters.putAll( + XContentHelper.createParser( + NamedXContentRegistry.EMPTY, + DeprecationHandler.THROW_UNSUPPORTED_OPERATION, + new BytesArray(parametersString), + MediaTypeRegistry.getDefaultMediaType() + ).map() + ); + } + + parameters.put(KNNConstants.VECTOR_DATA_TYPE_FIELD, vectorDataType.getValue()); + // In OpenSearch 2.16, we added the prefix for binary indices in the index description in the codec logic. + // After 2.16, we added the binary prefix in the faiss library code. However, to ensure backwards compatibility, + // we need to ensure that if the description does not contain the prefix but the type is binary, we add the + // description. + maybeAddBinaryPrefixForFaissBWC(knnEngine, parameters, fieldAttributes); + + // Used to determine how many threads to use when indexing + parameters.put(KNNConstants.INDEX_THREAD_QTY, KNNSettings.state().getSettingValue(KNNSettings.KNN_ALGO_PARAM_INDEX_THREAD_QTY)); + + return parameters; + } + + private void maybeAddBinaryPrefixForFaissBWC(KNNEngine knnEngine, Map parameters, Map fieldAttributes) { + if (KNNEngine.FAISS != knnEngine) { + return; + } + + if (!VectorDataType.BINARY.getValue() + .equals(fieldAttributes.getOrDefault(KNNConstants.VECTOR_DATA_TYPE_FIELD, VectorDataType.DEFAULT.getValue()))) { + return; + } + + if (parameters.get(KNNConstants.INDEX_DESCRIPTION_PARAMETER) == null) { + return; + } + + if (parameters.get(KNNConstants.INDEX_DESCRIPTION_PARAMETER).toString().startsWith(FAISS_BINARY_INDEX_DESCRIPTION_PREFIX)) { + return; + } + + parameters.put( + KNNConstants.INDEX_DESCRIPTION_PARAMETER, + FAISS_BINARY_INDEX_DESCRIPTION_PREFIX + parameters.get(KNNConstants.INDEX_DESCRIPTION_PARAMETER).toString() + ); + IndexUtil.updateVectorDataTypeToParameters(parameters, VectorDataType.BINARY); + } + + private Map getTemplateParameters(FieldInfo fieldInfo, Model model) throws IOException { + Map parameters = new HashMap<>(); + parameters.put(KNNConstants.INDEX_THREAD_QTY, KNNSettings.state().getSettingValue(KNNSettings.KNN_ALGO_PARAM_INDEX_THREAD_QTY)); + parameters.put(KNNConstants.MODEL_ID, fieldInfo.attributes().get(MODEL_ID)); + parameters.put(KNNConstants.MODEL_BLOB_PARAMETER, model.getModelBlob()); + if (FieldInfoExtractor.extractQuantizationConfig(fieldInfo) != QuantizationConfig.EMPTY) { + IndexUtil.updateVectorDataTypeToParameters(parameters, VectorDataType.BINARY); + } else { + IndexUtil.updateVectorDataTypeToParameters(parameters, model.getModelMetadata().getVectorDataType()); + } + + return parameters; + } + + private Model getModel(FieldInfo fieldInfo) { + String modelId = fieldInfo.attributes().get(MODEL_ID); + Model model = ModelCache.getInstance().get(modelId); + if (model.getModelBlob() == null) { + throw new RuntimeException(String.format("There is no trained model with id \"%s\"", modelId)); + } + return model; + } + + private void startMergeStats(int numDocs, long bytesPerVector) { + KNNGraphValue.MERGE_CURRENT_OPERATIONS.increment(); + KNNGraphValue.MERGE_CURRENT_DOCS.incrementBy(numDocs); + KNNGraphValue.MERGE_CURRENT_SIZE_IN_BYTES.incrementBy(bytesPerVector); + KNNGraphValue.MERGE_TOTAL_OPERATIONS.increment(); + KNNGraphValue.MERGE_TOTAL_DOCS.incrementBy(numDocs); + KNNGraphValue.MERGE_TOTAL_SIZE_IN_BYTES.incrementBy(bytesPerVector); + } + + private void endMergeStats(int numDocs, long arraySize) { + KNNGraphValue.MERGE_CURRENT_OPERATIONS.decrement(); + KNNGraphValue.MERGE_CURRENT_DOCS.decrementBy(numDocs); + KNNGraphValue.MERGE_CURRENT_SIZE_IN_BYTES.decrementBy(arraySize); + } + + private void recordRefreshStats() { + KNNGraphValue.REFRESH_TOTAL_OPERATIONS.increment(); + } + + /** + * Helper method to create the appropriate NativeIndexWriter based on the field info and quantization state. + * + * @param fieldInfo The FieldInfo object containing metadata about the field for which the writer is needed. + * @param state The SegmentWriteState representing the current segment's writing context. + * @param quantizationState The QuantizationState that contains quantization state required for quantization, can be null. + * @return A NativeIndexWriter instance appropriate for the specified field, configured with or without quantization. + */ + private static NativeIndexWriter createWriter( + final FieldInfo fieldInfo, + final SegmentWriteState state, + @Nullable final QuantizationState quantizationState + ) { + final KNNEngine knnEngine = extractKNNEngine(fieldInfo); + boolean isTemplate = fieldInfo.attributes().containsKey(MODEL_ID); + boolean iterative = !isTemplate && KNNEngine.FAISS == knnEngine; + NativeIndexBuildStrategy strategy = iterative + ? MemOptimizedNativeIndexBuildStrategy.getInstance() + : DefaultIndexBuildStrategy.getInstance(); + return new NativeIndexWriter(state, fieldInfo, strategy, quantizationState); + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/nativeindex/QuantizationIndexUtils.java b/src/main/java/org/opensearch/knn/index/codec/nativeindex/QuantizationIndexUtils.java new file mode 100644 index 000000000..c5994d66b --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/nativeindex/QuantizationIndexUtils.java @@ -0,0 +1,72 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.nativeindex; + +import lombok.experimental.UtilityClass; +import org.opensearch.knn.index.codec.nativeindex.model.BuildIndexParams; +import org.opensearch.knn.index.quantizationservice.QuantizationService; +import org.opensearch.knn.index.vectorvalues.KNNVectorValues; +import org.opensearch.knn.quantization.models.quantizationOutput.QuantizationOutput; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; + +import java.io.IOException; + +@UtilityClass +class QuantizationIndexUtils { + + /** + * Processes the vector from {@link KNNVectorValues} and returns either a cloned quantized vector or a cloned original vector. + * + * @param knnVectorValues The KNN vector values containing the original vector. + * @param indexBuildSetup The setup containing the quantization state and output details. + * @return The quantized vector (as a byte array) or the original/cloned vector. + * @throws IOException If an I/O error occurs while processing the vector. + */ + static Object processAndReturnVector(KNNVectorValues knnVectorValues, IndexBuildSetup indexBuildSetup) throws IOException { + QuantizationService quantizationService = QuantizationService.getInstance(); + if (indexBuildSetup.getQuantizationState() != null && indexBuildSetup.getQuantizationOutput() != null) { + quantizationService.quantize( + indexBuildSetup.getQuantizationState(), + knnVectorValues.getVector(), + indexBuildSetup.getQuantizationOutput() + ); + /** + * Returns a copy of the quantized vector. This is because of during transfer same vectors was getting + * added due to reference. + */ + return indexBuildSetup.getQuantizationOutput().getQuantizedVectorCopy(); + } else { + return knnVectorValues.conditionalCloneVector(); + } + } + + /** + * Prepares the quantization setup including bytes per vector and dimensions. + * + * @param knnVectorValues the KNN vector values. + * @param indexInfo the index build parameters. + * @return an instance of QuantizationSetup containing relevant information. + */ + static IndexBuildSetup prepareIndexBuild(KNNVectorValues knnVectorValues, BuildIndexParams indexInfo) { + QuantizationState quantizationState = indexInfo.getQuantizationState(); + QuantizationOutput quantizationOutput = null; + QuantizationService quantizationService = QuantizationService.getInstance(); + + int bytesPerVector; + int dimensions; + + if (quantizationState != null) { + bytesPerVector = quantizationState.getBytesPerVector(); + dimensions = quantizationState.getDimensions(); + quantizationOutput = quantizationService.createQuantizationOutput(quantizationState.getQuantizationParams()); + } else { + bytesPerVector = knnVectorValues.bytesPerVector(); + dimensions = knnVectorValues.dimension(); + } + + return new IndexBuildSetup(bytesPerVector, dimensions, quantizationOutput, quantizationState); + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/nativeindex/model/BuildIndexParams.java b/src/main/java/org/opensearch/knn/index/codec/nativeindex/model/BuildIndexParams.java new file mode 100644 index 000000000..36e874c43 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/nativeindex/model/BuildIndexParams.java @@ -0,0 +1,36 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.nativeindex.model; + +import lombok.Builder; +import lombok.ToString; +import lombok.Value; +import org.opensearch.common.Nullable; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.store.IndexOutputWithBuffer; +import org.opensearch.knn.index.vectorvalues.KNNVectorValues; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; + +import java.util.Map; + +@Value +@Builder +@ToString +public class BuildIndexParams { + String fieldName; + KNNEngine knnEngine; + IndexOutputWithBuffer indexOutputWithBuffer; + VectorDataType vectorDataType; + Map parameters; + /** + * An optional quantization state that contains required information for quantization + */ + @Nullable + QuantizationState quantizationState; + KNNVectorValues vectorValues; + int totalLiveDocs; +} diff --git a/src/main/java/org/opensearch/knn/index/codec/params/KNNScalarQuantizedVectorsFormatParams.java b/src/main/java/org/opensearch/knn/index/codec/params/KNNScalarQuantizedVectorsFormatParams.java new file mode 100644 index 000000000..3498119c1 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/params/KNNScalarQuantizedVectorsFormatParams.java @@ -0,0 +1,90 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.params; + +import lombok.Getter; +import org.opensearch.knn.index.engine.MethodComponentContext; + +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.ENCODER_SQ; +import static org.opensearch.knn.common.KNNConstants.LUCENE_SQ_BITS; +import static org.opensearch.knn.common.KNNConstants.LUCENE_SQ_CONFIDENCE_INTERVAL; +import static org.opensearch.knn.common.KNNConstants.LUCENE_SQ_DEFAULT_BITS; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; + +/** + * Class provides params for LuceneHnswScalarQuantizedVectorsFormat + */ +@Getter +public class KNNScalarQuantizedVectorsFormatParams extends KNNVectorsFormatParams { + private Float confidenceInterval; + private int bits; + private boolean compressFlag; + + public KNNScalarQuantizedVectorsFormatParams(Map params, int defaultMaxConnections, int defaultBeamWidth) { + super(params, defaultMaxConnections, defaultBeamWidth); + MethodComponentContext encoderMethodComponentContext = (MethodComponentContext) params.get(METHOD_ENCODER_PARAMETER); + Map sqEncoderParams = encoderMethodComponentContext.getParameters(); + this.initConfidenceInterval(sqEncoderParams); + this.initBits(sqEncoderParams); + // compression flag should be set after bits has been initialised as compressionFlag depends on bits. + this.setCompressionFlag(); + } + + @Override + public boolean validate(Map params) { + if (params.get(METHOD_ENCODER_PARAMETER) == null) { + return false; + } + + // Validate if the object is of type MethodComponentContext before casting it later + if (!(params.get(METHOD_ENCODER_PARAMETER) instanceof MethodComponentContext)) { + return false; + } + MethodComponentContext encoderMethodComponentContext = (MethodComponentContext) params.get(METHOD_ENCODER_PARAMETER); + if (!ENCODER_SQ.equals(encoderMethodComponentContext.getName())) { + return false; + } + + return true; + } + + private void initConfidenceInterval(final Map params) { + + if (params != null && params.containsKey(LUCENE_SQ_CONFIDENCE_INTERVAL)) { + if (params.get(LUCENE_SQ_CONFIDENCE_INTERVAL).equals(0)) { + this.confidenceInterval = (float) 0; + return; + } + this.confidenceInterval = ((Double) params.get(LUCENE_SQ_CONFIDENCE_INTERVAL)).floatValue(); + return; + } + + // If confidence_interval is not provided by user, then it will be set with a default value as null so that + // it will be computed later in Lucene based on the dimension of the vector as 1 - 1/(1 + d) + this.confidenceInterval = null; + } + + private void initBits(final Map params) { + if (params != null && params.containsKey(LUCENE_SQ_BITS)) { + this.bits = (int) params.get(LUCENE_SQ_BITS); + return; + } + this.bits = LUCENE_SQ_DEFAULT_BITS; + } + + private void setCompressionFlag() { + if (this.bits <= 0) { + throw new IllegalArgumentException( + "Either bits are set to less than 0 or they have not been initialized." + " Bit value: " + this.bits + ); + } + // This check is coming from Lucene. Code ref: + // https://github.com/apache/lucene/blob/branch_9_12/lucene/core/src/java/org/apache/lucene/codecs/lucene99/Lucene99ScalarQuantizedVectorsFormat.java#L113-L116 + this.compressFlag = this.bits <= 4; + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/params/KNNVectorsFormatParams.java b/src/main/java/org/opensearch/knn/index/codec/params/KNNVectorsFormatParams.java new file mode 100644 index 000000000..52134bc7e --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/params/KNNVectorsFormatParams.java @@ -0,0 +1,45 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.params; + +import lombok.Getter; +import org.opensearch.knn.common.KNNConstants; + +import java.util.Map; + +/** + * Class provides params for LuceneHNSWVectorsFormat + */ +@Getter +public class KNNVectorsFormatParams { + private int maxConnections; + private int beamWidth; + + public KNNVectorsFormatParams(final Map params, int defaultMaxConnections, int defaultBeamWidth) { + initMaxConnections(params, defaultMaxConnections); + initBeamWidth(params, defaultBeamWidth); + } + + public boolean validate(final Map params) { + return true; + } + + private void initMaxConnections(final Map params, int defaultMaxConnections) { + if (params != null && params.containsKey(KNNConstants.METHOD_PARAMETER_M)) { + this.maxConnections = (int) params.get(KNNConstants.METHOD_PARAMETER_M); + return; + } + this.maxConnections = defaultMaxConnections; + } + + private void initBeamWidth(final Map params, int defaultBeamWidth) { + if (params != null && params.containsKey(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION)) { + this.beamWidth = (int) params.get(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION); + return; + } + this.beamWidth = defaultBeamWidth; + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/transfer/OffHeapBinaryVectorTransfer.java b/src/main/java/org/opensearch/knn/index/codec/transfer/OffHeapBinaryVectorTransfer.java new file mode 100644 index 000000000..964007fc0 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/transfer/OffHeapBinaryVectorTransfer.java @@ -0,0 +1,38 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.transfer; + +import org.opensearch.knn.jni.JNICommons; + +import java.io.IOException; +import java.util.List; + +/** + * Transfer quantized binary vectors to off heap memory + * The reason this is different from {@link OffHeapByteVectorTransfer} is because of allocation and deallocation + * of memory on JNI layer. Use this if unsigned int is needed on JNI layer + */ +public final class OffHeapBinaryVectorTransfer extends OffHeapVectorTransfer { + + public OffHeapBinaryVectorTransfer(int bytesPerVector, int totalVectorsToTransfer) { + super(bytesPerVector, totalVectorsToTransfer); + } + + @Override + public void deallocate() { + JNICommons.freeBinaryVectorData(getVectorAddress()); + } + + @Override + protected long transfer(List batch, boolean append) throws IOException { + return JNICommons.storeBinaryVectorData( + getVectorAddress(), + batch.toArray(new byte[][] {}), + (long) batch.get(0).length * transferLimit, + append + ); + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/transfer/OffHeapByteVectorTransfer.java b/src/main/java/org/opensearch/knn/index/codec/transfer/OffHeapByteVectorTransfer.java new file mode 100644 index 000000000..16e333478 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/transfer/OffHeapByteVectorTransfer.java @@ -0,0 +1,38 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.transfer; + +import org.opensearch.knn.jni.JNICommons; + +import java.io.IOException; +import java.util.List; + +/** + * Transfer quantized byte vectors to off heap memory. + * The reason this is different from {@link OffHeapBinaryVectorTransfer} is because of allocation and deallocation + * of memory on JNI layer. Use this if signed int is needed on JNI layer + */ +public final class OffHeapByteVectorTransfer extends OffHeapVectorTransfer { + + public OffHeapByteVectorTransfer(int bytesPerVector, int totalVectorsToTransfer) { + super(bytesPerVector, totalVectorsToTransfer); + } + + @Override + protected long transfer(List batch, boolean append) throws IOException { + return JNICommons.storeByteVectorData( + getVectorAddress(), + batch.toArray(new byte[][] {}), + (long) batch.get(0).length * transferLimit, + append + ); + } + + @Override + public void deallocate() { + JNICommons.freeByteVectorData(getVectorAddress()); + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/transfer/OffHeapFloatVectorTransfer.java b/src/main/java/org/opensearch/knn/index/codec/transfer/OffHeapFloatVectorTransfer.java new file mode 100644 index 000000000..767f57271 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/transfer/OffHeapFloatVectorTransfer.java @@ -0,0 +1,36 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.transfer; + +import org.opensearch.knn.jni.JNICommons; + +import java.io.IOException; +import java.util.List; + +/** + * Transfer float vectors to off heap memory. + */ +public final class OffHeapFloatVectorTransfer extends OffHeapVectorTransfer { + + public OffHeapFloatVectorTransfer(int bytesPerVector, int totalVectorsToTransfer) { + super(bytesPerVector, totalVectorsToTransfer); + } + + @Override + protected long transfer(final List vectorsToTransfer, boolean append) throws IOException { + return JNICommons.storeVectorData( + getVectorAddress(), + vectorsToTransfer.toArray(new float[][] {}), + (long) vectorsToTransfer.get(0).length * this.transferLimit, + append + ); + } + + @Override + public void deallocate() { + JNICommons.freeVectorData(getVectorAddress()); + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/transfer/OffHeapVectorTransfer.java b/src/main/java/org/opensearch/knn/index/codec/transfer/OffHeapVectorTransfer.java new file mode 100644 index 000000000..8a248e06c --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/transfer/OffHeapVectorTransfer.java @@ -0,0 +1,106 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.transfer; + +import lombok.Getter; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.vectorvalues.KNNVectorValues; + +import java.io.Closeable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + *

+ * The class is intended to transfer {@link KNNVectorValues} to off heap memory. + *

+ *

+ * The class is not thread safe. + *

+ * + * @param byte[] or float[] + */ +public abstract class OffHeapVectorTransfer implements Closeable { + + @Getter + private long vectorAddress; + @Getter + protected final int transferLimit; + + private List vectorsToTransfer; + + public OffHeapVectorTransfer(int bytesPerVector, int totalVectorsToTransfer) { + this.transferLimit = computeTransferLimit(bytesPerVector, totalVectorsToTransfer); + this.vectorsToTransfer = new ArrayList<>(this.transferLimit); + this.vectorAddress = 0; + } + + private int computeTransferLimit(int bytesPerVector, int totalVectorsToTransfer) { + int limit = (int) Math.max(1, KNNSettings.getVectorStreamingMemoryLimit().getBytes() / bytesPerVector); + return Math.min(limit, totalVectorsToTransfer); + } + + /** + * Transfer vectors to off-heap + * @param vector float[] or byte[] + * @param append This indicates whether to append or rewrite the off-heap buffer + * @return true of the vectors were transferred, false if not + * @throws IOException + */ + public boolean transfer(T vector, boolean append) throws IOException { + vectorsToTransfer.add(vector); + if (vectorsToTransfer.size() == this.transferLimit) { + vectorAddress = transfer(vectorsToTransfer, append); + vectorsToTransfer.clear(); + return true; + } + return false; + } + + /** + * Empties the {@link #vectorsToTransfer} if its not empty. Intended to be used before + * closing the transfer + * + * @param append This indicates whether to append or rewrite the off-heap buffer + * @return true of the vectors were transferred, false if not + * @throws IOException + */ + public boolean flush(boolean append) throws IOException { + // flush before closing + if (!vectorsToTransfer.isEmpty()) { + vectorAddress = transfer(vectorsToTransfer, append); + vectorsToTransfer.clear(); + return true; + } + return false; + } + + @Override + public void close() { + // Remove this if condition once create and write index is separated for nmslib + if (vectorAddress != 0) { + deallocate(); + } + reset(); + } + + /** + * Resets address and vectortoTransfer + * + * DO NOT USE this in the middle of the transfer, The behavior is undefined + * + * TODO: Make it package private once create and write index is separated for nmslib + */ + public void reset() { + vectorAddress = 0; + vectorsToTransfer = null; + } + + protected abstract void deallocate(); + + protected abstract long transfer(final List vectorsToTransfer, boolean append) throws IOException; +} diff --git a/src/main/java/org/opensearch/knn/index/codec/transfer/OffHeapVectorTransferFactory.java b/src/main/java/org/opensearch/knn/index/codec/transfer/OffHeapVectorTransferFactory.java new file mode 100644 index 000000000..3bc55f7fa --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/transfer/OffHeapVectorTransferFactory.java @@ -0,0 +1,42 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.transfer; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.opensearch.knn.index.VectorDataType; + +/** + * Factory to get the right implementation of vector transfer + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class OffHeapVectorTransferFactory { + + /** + * Gets the right vector transfer object based on vector data type + * @param vectorDataType {@link VectorDataType} + * @param bytesPerVector Bytes used per vector + * @param totalVectorsToTransfer total number of vectors that will be transferred off heap + * @return Correct implementation of {@link OffHeapVectorTransfer} + * @param float[] or byte[] + */ + public static OffHeapVectorTransfer getVectorTransfer( + final VectorDataType vectorDataType, + int bytesPerVector, + int totalVectorsToTransfer + ) { + switch (vectorDataType) { + case FLOAT: + return (OffHeapVectorTransfer) new OffHeapFloatVectorTransfer(bytesPerVector, totalVectorsToTransfer); + case BINARY: + return (OffHeapVectorTransfer) new OffHeapBinaryVectorTransfer(bytesPerVector, totalVectorsToTransfer); + case BYTE: + return (OffHeapVectorTransfer) new OffHeapByteVectorTransfer(bytesPerVector, totalVectorsToTransfer); + default: + throw new IllegalArgumentException("Unsupported vector data type: " + vectorDataType); + } + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/util/KNNCodecUtil.java b/src/main/java/org/opensearch/knn/index/codec/util/KNNCodecUtil.java index eef9e6863..3ccfc3c2b 100644 --- a/src/main/java/org/opensearch/knn/index/codec/util/KNNCodecUtil.java +++ b/src/main/java/org/opensearch/knn/index/codec/util/KNNCodecUtil.java @@ -5,42 +5,43 @@ package org.opensearch.knn.index.codec.util; +import lombok.NonNull; import org.apache.lucene.index.BinaryDocValues; -import org.apache.lucene.search.DocIdSetIterator; -import org.apache.lucene.util.BytesRef; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.SegmentInfo; +import org.opensearch.knn.common.FieldInfoExtractor; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.codec.KNN80Codec.KNN80BinaryDocValues; +import org.opensearch.knn.index.engine.KNNEngine; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; -public class KNNCodecUtil { - - public static final String HNSW_EXTENSION = ".hnsw"; - public static final String HNSW_COMPOUND_EXTENSION = ".hnswc"; +import static org.opensearch.knn.index.mapper.KNNVectorFieldMapper.KNN_FIELD; - public static final class Pair { - public Pair(int[] docs, float[][] vectors) { - this.docs = docs; - this.vectors = vectors; - } +public class KNNCodecUtil { + // Floats are 4 bytes in size + public static final int FLOAT_BYTE_SIZE = 4; - public int[] docs; - public float[][] vectors; - } - - public static KNNCodecUtil.Pair getFloats(BinaryDocValues values) throws IOException { - ArrayList vectorList = new ArrayList<>(); - ArrayList docIdList = new ArrayList<>(); - for (int doc = values.nextDoc(); doc != DocIdSetIterator.NO_MORE_DOCS; doc = values.nextDoc()) { - BytesRef bytesref = values.binaryValue(); - try (ByteArrayInputStream byteStream = new ByteArrayInputStream(bytesref.bytes, bytesref.offset, bytesref.length)) { - final KNNVectorSerializer vectorSerializer = KNNVectorSerializerFactory.getSerializerByStreamContent(byteStream); - final float[] vector = vectorSerializer.byteToFloatArray(byteStream); - vectorList.add(vector); - } - docIdList.add(doc); + /** + * This method provides a rough estimate of the number of bytes used for storing an array with the given parameters. + * @param numVectors number of vectors in the array + * @param vectorLength the length of each vector + * @param vectorDataType type of data stored in each vector + * @return rough estimate of number of bytes used to store an array with the given parameters + */ + public static long calculateArraySize(int numVectors, int vectorLength, VectorDataType vectorDataType) { + if (vectorDataType == VectorDataType.FLOAT) { + return numVectors * vectorLength * FLOAT_BYTE_SIZE; + } else if (vectorDataType == VectorDataType.BINARY || vectorDataType == VectorDataType.BYTE) { + return numVectors * vectorLength; + } else { + throw new IllegalArgumentException( + "Float, binary, and byte are the only supported vector data types for array size calculation." + ); } - return new KNNCodecUtil.Pair(docIdList.stream().mapToInt(Integer::intValue).toArray(), vectorList.toArray(new float[][] {})); } public static String buildEngineFileName(String segmentName, String latestBuildVersion, String fieldName, String extension) { @@ -54,4 +55,78 @@ public static String buildEngineFilePrefix(String segmentName) { public static String buildEngineFileSuffix(String fieldName, String extension) { return String.format("_%s%s", fieldName, extension); } + + public static long getTotalLiveDocsCount(final BinaryDocValues binaryDocValues) { + long totalLiveDocs; + if (binaryDocValues instanceof KNN80BinaryDocValues) { + totalLiveDocs = ((KNN80BinaryDocValues) binaryDocValues).getTotalLiveDocs(); + } else { + totalLiveDocs = binaryDocValues.cost(); + } + return totalLiveDocs; + } + + /** + * Get Engine Files from segment with specific fieldName and engine extension + * + * @param extension Engine extension comes from {@link KNNEngine#getExtension()}} + * @param fieldName Filed for knn field + * @param segmentInfo {@link SegmentInfo} One Segment info to use for compute. + * @return List of engine files + */ + public static List getEngineFiles(String extension, String fieldName, SegmentInfo segmentInfo) { + /* + * In case of compound file, extension would be + c otherwise + */ + String engineExtension = segmentInfo.getUseCompoundFile() ? extension + KNNConstants.COMPOUND_EXTENSION : extension; + String engineSuffix = fieldName + engineExtension; + String underLineEngineSuffix = "_" + engineSuffix; + + List engineFiles = segmentInfo.files() + .stream() + .filter(fileName -> fileName.endsWith(underLineEngineSuffix)) + .sorted(Comparator.comparingInt(String::length)) + .collect(Collectors.toList()); + return engineFiles; + } + + /** + * Get engine file name from given field and segment info. + * Ex: _0_165_my_field.faiss + * + * @param field : Field info that might have a vector index file. Not always it has it. + * @param segmentInfo : Segment where we are collecting an engine file list. + * @return : Found vector engine names, if not found, returns null. + */ + public static String getNativeEngineFileFromFieldInfo(FieldInfo field, SegmentInfo segmentInfo) { + if (!field.attributes().containsKey(KNN_FIELD)) { + return null; + } + // Only Native Engine put into indexPathMap + final KNNEngine knnEngine = getNativeKNNEngine(field); + if (knnEngine == null) { + return null; + } + final List engineFiles = KNNCodecUtil.getEngineFiles(knnEngine.getExtension(), field.name, segmentInfo); + if (engineFiles.isEmpty()) { + return null; + } else { + final String vectorIndexFileName = engineFiles.get(0); + return vectorIndexFileName; + } + } + + /** + * Get KNNEngine From FieldInfo + * + * @param field which field we need produce from engine + * @return if and only if Native Engine we return specific engine, else return null + */ + private static KNNEngine getNativeKNNEngine(@NonNull FieldInfo field) { + final KNNEngine engine = FieldInfoExtractor.extractKNNEngine(field); + if (KNNEngine.getEnginesThatCreateCustomSegmentFiles().contains(engine)) { + return engine; + } + return null; + } } diff --git a/src/main/java/org/opensearch/knn/index/codec/util/KNNVectorAsArraySerializer.java b/src/main/java/org/opensearch/knn/index/codec/util/KNNVectorAsArraySerializer.java index 751a229db..929a9aa3e 100644 --- a/src/main/java/org/opensearch/knn/index/codec/util/KNNVectorAsArraySerializer.java +++ b/src/main/java/org/opensearch/knn/index/codec/util/KNNVectorAsArraySerializer.java @@ -5,6 +5,8 @@ package org.opensearch.knn.index.codec.util; +import org.apache.lucene.util.BytesRef; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -31,8 +33,8 @@ public byte[] floatToByteArray(float[] input) { } @Override - public float[] byteToFloatArray(ByteArrayInputStream byteStream) { - try { + public float[] byteToFloatArray(BytesRef bytesRef) { + try (ByteArrayInputStream byteStream = new ByteArrayInputStream(bytesRef.bytes, bytesRef.offset, bytesRef.length)) { final ObjectInputStream objectStream = new ObjectInputStream(byteStream); final float[] vector = (float[]) objectStream.readObject(); return vector; diff --git a/src/main/java/org/opensearch/knn/index/codec/util/KNNVectorAsCollectionOfFloatsSerializer.java b/src/main/java/org/opensearch/knn/index/codec/util/KNNVectorAsCollectionOfFloatsSerializer.java index 13530afc5..31b9b8fea 100644 --- a/src/main/java/org/opensearch/knn/index/codec/util/KNNVectorAsCollectionOfFloatsSerializer.java +++ b/src/main/java/org/opensearch/knn/index/codec/util/KNNVectorAsCollectionOfFloatsSerializer.java @@ -5,7 +5,8 @@ package org.opensearch.knn.index.codec.util; -import java.io.ByteArrayInputStream; +import org.apache.lucene.util.BytesRef; + import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.stream.IntStream; @@ -26,15 +27,13 @@ public byte[] floatToByteArray(float[] input) { } @Override - public float[] byteToFloatArray(ByteArrayInputStream byteStream) { - if (byteStream == null || byteStream.available() % BYTES_IN_FLOAT != 0) { + public float[] byteToFloatArray(BytesRef bytesRef) { + if (bytesRef == null || bytesRef.length % BYTES_IN_FLOAT != 0) { throw new IllegalArgumentException("Byte stream cannot be deserialized to array of floats"); } - final byte[] vectorAsByteArray = new byte[byteStream.available()]; - byteStream.read(vectorAsByteArray, 0, byteStream.available()); - final int sizeOfFloatArray = vectorAsByteArray.length / BYTES_IN_FLOAT; + final int sizeOfFloatArray = bytesRef.length / BYTES_IN_FLOAT; final float[] vector = new float[sizeOfFloatArray]; - ByteBuffer.wrap(vectorAsByteArray).asFloatBuffer().get(vector); + ByteBuffer.wrap(bytesRef.bytes, bytesRef.offset, bytesRef.length).asFloatBuffer().get(vector); return vector; } } diff --git a/src/main/java/org/opensearch/knn/index/codec/util/KNNVectorSerializer.java b/src/main/java/org/opensearch/knn/index/codec/util/KNNVectorSerializer.java index 35f1ff5be..f7e7a6743 100644 --- a/src/main/java/org/opensearch/knn/index/codec/util/KNNVectorSerializer.java +++ b/src/main/java/org/opensearch/knn/index/codec/util/KNNVectorSerializer.java @@ -5,7 +5,7 @@ package org.opensearch.knn.index.codec.util; -import java.io.ByteArrayInputStream; +import org.apache.lucene.util.BytesRef; /** * Interface abstracts the vector serializer object that is responsible for serialization and de-serialization of k-NN vector @@ -20,8 +20,9 @@ public interface KNNVectorSerializer { /** * Deserializes all bytes from the stream to array of floats - * @param byteStream stream of bytes that will be used for deserialization to array of floats + * + * @param bytesRef bytes that will be used for deserialization to array of floats * @return array of floats deserialized from the stream */ - float[] byteToFloatArray(ByteArrayInputStream byteStream); + float[] byteToFloatArray(BytesRef bytesRef); } diff --git a/src/main/java/org/opensearch/knn/index/codec/util/KNNVectorSerializerFactory.java b/src/main/java/org/opensearch/knn/index/codec/util/KNNVectorSerializerFactory.java index f02da0949..d2a991153 100644 --- a/src/main/java/org/opensearch/knn/index/codec/util/KNNVectorSerializerFactory.java +++ b/src/main/java/org/opensearch/knn/index/codec/util/KNNVectorSerializerFactory.java @@ -6,10 +6,9 @@ package org.opensearch.knn.index.codec.util; import com.google.common.collect.ImmutableMap; +import org.apache.lucene.util.BytesRef; -import java.io.ByteArrayInputStream; import java.io.ObjectStreamConstants; -import java.util.Arrays; import java.util.Map; import static org.opensearch.knn.index.codec.util.SerializationMode.ARRAY; @@ -51,25 +50,24 @@ public static KNNVectorSerializer getDefaultSerializer() { return getSerializerBySerializationMode(COLLECTION_OF_FLOATS); } - public static KNNVectorSerializer getSerializerByStreamContent(final ByteArrayInputStream byteStream) { - final SerializationMode serializationMode = serializerModeFromStream(byteStream); + public static KNNVectorSerializer getSerializerByBytesRef(final BytesRef bytesRef) { + final SerializationMode serializationMode = getSerializerModeFromBytesRef(bytesRef); return getSerializerBySerializationMode(serializationMode); } - private static SerializationMode serializerModeFromStream(ByteArrayInputStream byteStream) { - int numberOfAvailableBytesInStream = byteStream.available(); - if (numberOfAvailableBytesInStream < ARRAY_HEADER_OFFSET) { - return getSerializerOrThrowError(numberOfAvailableBytesInStream, COLLECTION_OF_FLOATS); + public static SerializationMode getSerializerModeFromBytesRef(BytesRef bytesRef) { + int numberOfAvailableBytes = bytesRef.length; + if (numberOfAvailableBytes < ARRAY_HEADER_OFFSET) { + return getSerializerOrThrowError(numberOfAvailableBytes, COLLECTION_OF_FLOATS); } - final byte[] byteArray = new byte[SERIALIZATION_PROTOCOL_HEADER_PREFIX.length]; - byteStream.read(byteArray, 0, SERIALIZATION_PROTOCOL_HEADER_PREFIX.length); - byteStream.reset(); - // checking if stream protocol grammar in header is valid for serialized array - if (Arrays.equals(SERIALIZATION_PROTOCOL_HEADER_PREFIX, byteArray)) { - int numberOfAvailableBytesAfterHeader = numberOfAvailableBytesInStream - ARRAY_HEADER_OFFSET; - return getSerializerOrThrowError(numberOfAvailableBytesAfterHeader, ARRAY); + + for (int i = 0; i < SERIALIZATION_PROTOCOL_HEADER_PREFIX.length; i++) { + if (bytesRef.bytes[i + bytesRef.offset] != SERIALIZATION_PROTOCOL_HEADER_PREFIX[i]) { + return getSerializerOrThrowError(numberOfAvailableBytes, COLLECTION_OF_FLOATS); + } } - return getSerializerOrThrowError(numberOfAvailableBytesInStream, COLLECTION_OF_FLOATS); + int numberOfAvailableBytesAfterHeader = numberOfAvailableBytes - ARRAY_HEADER_OFFSET; + return getSerializerOrThrowError(numberOfAvailableBytesAfterHeader, ARRAY); } private static SerializationMode getSerializerOrThrowError(int numberOfRemainingBytes, final SerializationMode serializationMode) { diff --git a/src/main/java/org/opensearch/knn/index/codec/util/NativeMemoryCacheKeyHelper.java b/src/main/java/org/opensearch/knn/index/codec/util/NativeMemoryCacheKeyHelper.java new file mode 100644 index 000000000..8d50bf029 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/codec/util/NativeMemoryCacheKeyHelper.java @@ -0,0 +1,45 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.util; + +import org.apache.lucene.index.SegmentInfo; + +import java.util.Base64; + +public final class NativeMemoryCacheKeyHelper { + private NativeMemoryCacheKeyHelper() {} + + /** + * Construct a unique cache key for look-up operation in {@link org.opensearch.knn.index.memory.NativeMemoryCacheManager} + * + * @param vectorIndexFileName Vector index file name. Ex: _0_165_test_field.faiss. + * @param segmentInfo Segment info object representing a logical segment unit containing a vector index. + * @return Unique cache key that can be used for look-up and invalidating in + * {@link org.opensearch.knn.index.memory.NativeMemoryCacheManager} + */ + public static String constructCacheKey(final String vectorIndexFileName, final SegmentInfo segmentInfo) { + final String segmentId = Base64.getEncoder().encodeToString(segmentInfo.getId()); + final String cacheKey = vectorIndexFileName + "@" + segmentId; + return cacheKey; + } + + /** + * From cacheKey, we extract a vector file name. + * Note that expected format of cacheKey consists of two part with '@' as a delimiter. + * First part would be the vector file name, the second one is the segment id. + * + * @param cacheKey : Cache key for {@link org.opensearch.knn.index.memory.NativeMemoryCacheManager} + * @return : Vector file name, if the given cacheKey was invalid format, returns null. + */ + public static String extractVectorIndexFileName(final String cacheKey) { + final int indexOfDelimiter = cacheKey.indexOf('@'); + if (indexOfDelimiter != -1) { + final String vectorFileName = cacheKey.substring(0, indexOfDelimiter); + return vectorFileName; + } + return null; + } +} diff --git a/src/main/java/org/opensearch/knn/index/codec/util/SerializationMode.java b/src/main/java/org/opensearch/knn/index/codec/util/SerializationMode.java index 1fb82cbfe..f3a32f53e 100644 --- a/src/main/java/org/opensearch/knn/index/codec/util/SerializationMode.java +++ b/src/main/java/org/opensearch/knn/index/codec/util/SerializationMode.java @@ -7,5 +7,6 @@ public enum SerializationMode { ARRAY, - COLLECTION_OF_FLOATS + COLLECTION_OF_FLOATS, + COLLECTIONS_OF_BYTES } diff --git a/src/main/java/org/opensearch/knn/index/engine/AbstractKNNLibrary.java b/src/main/java/org/opensearch/knn/index/engine/AbstractKNNLibrary.java new file mode 100644 index 000000000..9b38b1b6b --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/AbstractKNNLibrary.java @@ -0,0 +1,119 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.opensearch.common.ValidationException; +import org.opensearch.knn.index.VectorDataType; + +import java.util.Locale; +import java.util.Map; + +/** + * AbstractKNNLibrary implements common functionality shared between libraries + */ +@AllArgsConstructor(access = AccessLevel.PACKAGE) +public abstract class AbstractKNNLibrary implements KNNLibrary { + + protected final Map methods; + @Getter + protected final String version; + + @Override + public KNNLibrarySearchContext getKNNLibrarySearchContext(String methodName) { + throwIllegalArgOnNonNull(validateMethodExists(methodName)); + KNNMethod method = methods.get(methodName); + return method.getKNNLibrarySearchContext(); + } + + @Override + public KNNLibraryIndexingContext getKNNLibraryIndexingContext( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + String method = knnMethodContext.getMethodComponentContext().getName(); + throwIllegalArgOnNonNull(validateMethodExists(method)); + KNNMethod knnMethod = methods.get(method); + return knnMethod.getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext); + } + + @Override + public ValidationException validateMethod(KNNMethodContext knnMethodContext, KNNMethodConfigContext knnMethodConfigContext) { + String methodName = knnMethodContext.getMethodComponentContext().getName(); + ValidationException validationException = null; + String invalidErrorMessage = validateMethodExists(methodName); + if (invalidErrorMessage != null) { + validationException = new ValidationException(); + validationException.addValidationError(invalidErrorMessage); + return validationException; + } + invalidErrorMessage = validateDimension(knnMethodContext, knnMethodConfigContext); + if (invalidErrorMessage != null) { + validationException = new ValidationException(); + validationException.addValidationError(invalidErrorMessage); + } + + validateSpaceType(knnMethodContext, knnMethodConfigContext); + ValidationException methodValidation = methods.get(methodName).validate(knnMethodContext, knnMethodConfigContext); + if (methodValidation != null) { + validationException = validationException == null ? new ValidationException() : validationException; + validationException.addValidationErrors(methodValidation.validationErrors()); + } + + return validationException; + } + + private void validateSpaceType(final KNNMethodContext knnMethodContext, KNNMethodConfigContext knnMethodConfigContext) { + if (knnMethodContext == null) { + return; + } + knnMethodContext.getSpaceType().validateVectorDataType(knnMethodConfigContext.getVectorDataType()); + } + + private String validateDimension(final KNNMethodContext knnMethodContext, KNNMethodConfigContext knnMethodConfigContext) { + if (knnMethodContext == null) { + return null; + } + int dimension = knnMethodConfigContext.getDimension(); + if (dimension > KNNEngine.getMaxDimensionByEngine(knnMethodContext.getKnnEngine())) { + return String.format( + Locale.ROOT, + "Dimension value cannot be greater than %s for vector with engine: %s", + KNNEngine.getMaxDimensionByEngine(knnMethodContext.getKnnEngine()), + knnMethodContext.getKnnEngine().getName() + ); + } + + if (VectorDataType.BINARY == knnMethodConfigContext.getVectorDataType() && dimension % 8 != 0) { + return "Dimension should be multiply of 8 for binary vector data type"; + } + + return null; + } + + @Override + public boolean isTrainingRequired(KNNMethodContext knnMethodContext) { + String methodName = knnMethodContext.getMethodComponentContext().getName(); + throwIllegalArgOnNonNull(validateMethodExists(methodName)); + return methods.get(methodName).isTrainingRequired(knnMethodContext); + } + + private String validateMethodExists(String methodName) { + KNNMethod method = methods.get(methodName); + if (method == null) { + return String.format("Invalid method name: %s", methodName); + } + return null; + } + + private void throwIllegalArgOnNonNull(String errorMessage) { + if (errorMessage != null) { + throw new IllegalArgumentException(errorMessage); + } + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/AbstractKNNMethod.java b/src/main/java/org/opensearch/knn/index/engine/AbstractKNNMethod.java new file mode 100644 index 000000000..f53655136 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/AbstractKNNMethod.java @@ -0,0 +1,134 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import lombok.AllArgsConstructor; +import org.opensearch.common.ValidationException; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.mapper.PerDimensionProcessor; +import org.opensearch.knn.index.mapper.PerDimensionValidator; +import org.opensearch.knn.index.mapper.SpaceVectorValidator; +import org.opensearch.knn.index.mapper.VectorValidator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +/** + * Abstract class for KNN methods. This class provides the common functionality for all KNN methods. + * It defines the common attributes and methods that all KNN methods should implement. + */ +@AllArgsConstructor +public abstract class AbstractKNNMethod implements KNNMethod { + + protected final MethodComponent methodComponent; + protected final Set spaces; + protected final KNNLibrarySearchContext knnLibrarySearchContext; + + @Override + public boolean isSpaceTypeSupported(SpaceType space) { + return spaces.contains(space); + } + + @Override + public ValidationException validate(KNNMethodContext knnMethodContext, KNNMethodConfigContext knnMethodConfigContext) { + List errorMessages = new ArrayList<>(); + if (!isSpaceTypeSupported(knnMethodContext.getSpaceType())) { + errorMessages.add( + String.format( + Locale.ROOT, + "\"%s\" with \"%s\" configuration does not support space type: " + "\"%s\".", + this.methodComponent.getName(), + knnMethodContext.getKnnEngine().getName().toLowerCase(Locale.ROOT), + knnMethodContext.getSpaceType().getValue() + ) + ); + } + + ValidationException methodValidation = methodComponent.validate( + knnMethodContext.getMethodComponentContext(), + knnMethodConfigContext + ); + if (methodValidation != null) { + errorMessages.addAll(methodValidation.validationErrors()); + } + + if (errorMessages.isEmpty()) { + return null; + } + + ValidationException validationException = new ValidationException(); + validationException.addValidationErrors(errorMessages); + return validationException; + } + + @Override + public boolean isTrainingRequired(KNNMethodContext knnMethodContext) { + return methodComponent.isTrainingRequired(knnMethodContext.getMethodComponentContext()); + } + + @Override + public int estimateOverheadInKB(KNNMethodContext knnMethodContext, KNNMethodConfigContext knnMethodConfigContext) { + return methodComponent.estimateOverheadInKB(knnMethodContext.getMethodComponentContext(), knnMethodConfigContext.getDimension()); + } + + protected PerDimensionValidator doGetPerDimensionValidator( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + VectorDataType vectorDataType = knnMethodConfigContext.getVectorDataType(); + + if (VectorDataType.BINARY == vectorDataType) { + return PerDimensionValidator.DEFAULT_BIT_VALIDATOR; + } + + if (VectorDataType.BYTE == vectorDataType) { + return PerDimensionValidator.DEFAULT_BYTE_VALIDATOR; + } + return PerDimensionValidator.DEFAULT_FLOAT_VALIDATOR; + } + + protected VectorValidator doGetVectorValidator(KNNMethodContext knnMethodContext, KNNMethodConfigContext knnMethodConfigContext) { + return new SpaceVectorValidator(knnMethodContext.getSpaceType()); + } + + protected PerDimensionProcessor doGetPerDimensionProcessor( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + return PerDimensionProcessor.NOOP_PROCESSOR; + } + + @Override + public KNNLibraryIndexingContext getKNNLibraryIndexingContext( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + KNNLibraryIndexingContext knnLibraryIndexingContext = methodComponent.getKNNLibraryIndexingContext( + knnMethodContext.getMethodComponentContext(), + knnMethodConfigContext + ); + Map parameterMap = knnLibraryIndexingContext.getLibraryParameters(); + parameterMap.put(KNNConstants.SPACE_TYPE, knnMethodContext.getSpaceType().getValue()); + parameterMap.put(KNNConstants.VECTOR_DATA_TYPE_FIELD, knnMethodConfigContext.getVectorDataType().getValue()); + return KNNLibraryIndexingContextImpl.builder() + .quantizationConfig(knnLibraryIndexingContext.getQuantizationConfig()) + .parameters(parameterMap) + .vectorValidator(doGetVectorValidator(knnMethodContext, knnMethodConfigContext)) + .perDimensionValidator(doGetPerDimensionValidator(knnMethodContext, knnMethodConfigContext)) + .perDimensionProcessor(doGetPerDimensionProcessor(knnMethodContext, knnMethodConfigContext)) + .build(); + } + + @Override + public KNNLibrarySearchContext getKNNLibrarySearchContext() { + return knnLibrarySearchContext; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/AbstractMethodResolver.java b/src/main/java/org/opensearch/knn/index/engine/AbstractMethodResolver.java new file mode 100644 index 000000000..8127a041d --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/AbstractMethodResolver.java @@ -0,0 +1,185 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import org.opensearch.common.ValidationException; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; + +/** + * Abstract {@link MethodResolver} with helpful utilitiy functions that can be shared across different + * implementations + */ +public abstract class AbstractMethodResolver implements MethodResolver { + + /** + * Utility method to get the compression level from the context + * + * @param resolvedKnnMethodContext Resolved method context. Should have an encoder set in the params if available + * @return {@link CompressionLevel} Compression level that is configured with the {@link KNNMethodContext} + */ + protected CompressionLevel resolveCompressionLevelFromMethodContext( + KNNMethodContext resolvedKnnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + Map encoderMap + ) { + // If the context is null, the compression is not configured or the encoder is not defined, return not configured + // because the method context does not contain this info + if (isEncoderSpecified(resolvedKnnMethodContext) == false) { + return CompressionLevel.x1; + } + Encoder encoder = encoderMap.get(getEncoderName(resolvedKnnMethodContext)); + if (encoder == null) { + return CompressionLevel.NOT_CONFIGURED; + } + return encoder.calculateCompressionLevel(getEncoderComponentContext(resolvedKnnMethodContext), knnMethodConfigContext); + } + + protected void resolveMethodParams( + MethodComponentContext methodComponentContext, + KNNMethodConfigContext knnMethodConfigContext, + MethodComponent methodComponent + ) { + Map resolvedParams = MethodComponent.getParameterMapWithDefaultsAdded( + methodComponentContext, + methodComponent, + knnMethodConfigContext + ); + methodComponentContext.getParameters().putAll(resolvedParams); + } + + protected KNNMethodContext initResolvedKNNMethodContext( + KNNMethodContext originalMethodContext, + KNNEngine knnEngine, + SpaceType spaceType, + String methodName + ) { + if (originalMethodContext == null) { + return new KNNMethodContext(knnEngine, spaceType, new MethodComponentContext(methodName, new HashMap<>())); + } + return new KNNMethodContext(originalMethodContext); + } + + protected String getEncoderName(KNNMethodContext knnMethodContext) { + if (isEncoderSpecified(knnMethodContext) == false) { + return null; + } + + MethodComponentContext methodComponentContext = getEncoderComponentContext(knnMethodContext); + if (methodComponentContext == null) { + return null; + } + + return methodComponentContext.getName(); + } + + protected MethodComponentContext getEncoderComponentContext(KNNMethodContext knnMethodContext) { + if (isEncoderSpecified(knnMethodContext) == false) { + return null; + } + + return (MethodComponentContext) knnMethodContext.getMethodComponentContext().getParameters().get(METHOD_ENCODER_PARAMETER); + } + + /** + * Determine if the encoder parameter is specified + * + * @param knnMethodContext {@link KNNMethodContext} + * @return true is the encoder is specified in the structure; false otherwise + */ + protected boolean isEncoderSpecified(KNNMethodContext knnMethodContext) { + return knnMethodContext != null + && knnMethodContext.getMethodComponentContext().getParameters() != null + && knnMethodContext.getMethodComponentContext().getParameters().containsKey(METHOD_ENCODER_PARAMETER); + } + + protected boolean shouldEncoderBeResolved(KNNMethodContext knnMethodContext, KNNMethodConfigContext knnMethodConfigContext) { + // The encoder should not be resolved if: + // 1. The encoder is specified + // 2. The compression is x1 + // 3. The compression is not specified and the mode is not disk-based + if (isEncoderSpecified(knnMethodContext)) { + return false; + } + + if (knnMethodConfigContext.getCompressionLevel() == CompressionLevel.x1) { + return false; + } + + if (CompressionLevel.isConfigured(knnMethodConfigContext.getCompressionLevel()) == false + && Mode.ON_DISK != knnMethodConfigContext.getMode()) { + return false; + } + + if (VectorDataType.FLOAT != knnMethodConfigContext.getVectorDataType()) { + return false; + } + + return true; + } + + protected ValidationException validateNotTrainingContext( + boolean shouldRequireTraining, + KNNEngine knnEngine, + ValidationException validationException + ) { + if (shouldRequireTraining) { + validationException = validationException == null ? new ValidationException() : validationException; + validationException.addValidationError( + String.format(Locale.ROOT, "Cannot use \"%s\" engine from training context", knnEngine.getName()) + ); + } + + return validationException; + } + + protected ValidationException validateCompressionSupported( + CompressionLevel compressionLevel, + Set supportedCompressionLevels, + KNNEngine knnEngine, + ValidationException validationException + ) { + if (CompressionLevel.isConfigured(compressionLevel) && supportedCompressionLevels.contains(compressionLevel) == false) { + validationException = validationException == null ? new ValidationException() : validationException; + validationException.addValidationError( + String.format(Locale.ROOT, "\"%s\" does not support \"%s\" compression", knnEngine.getName(), compressionLevel.getName()) + ); + } + return validationException; + } + + protected ValidationException validateCompressionNotx1WhenOnDisk( + KNNMethodConfigContext knnMethodConfigContext, + ValidationException validationException + ) { + if (knnMethodConfigContext.getCompressionLevel() == CompressionLevel.x1 && knnMethodConfigContext.getMode() == Mode.ON_DISK) { + validationException = validationException == null ? new ValidationException() : validationException; + validationException.addValidationError( + String.format(Locale.ROOT, "Cannot specify \"x1\" compression level when using \"%s\" mode", Mode.ON_DISK.getName()) + ); + } + return validationException; + } + + protected void validateCompressionConflicts(CompressionLevel originalCompressionLevel, CompressionLevel resolvedCompressionLevel) { + if (CompressionLevel.isConfigured(originalCompressionLevel) + && CompressionLevel.isConfigured(resolvedCompressionLevel) + && resolvedCompressionLevel != originalCompressionLevel) { + ValidationException validationException = new ValidationException(); + validationException.addValidationError("Cannot specify an encoder that conflicts with the provided compression level"); + throw validationException; + } + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/DefaultHnswSearchContext.java b/src/main/java/org/opensearch/knn/index/engine/DefaultHnswSearchContext.java new file mode 100644 index 000000000..884657442 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/DefaultHnswSearchContext.java @@ -0,0 +1,30 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import com.google.common.collect.ImmutableMap; +import org.opensearch.knn.index.engine.model.QueryContext; +import org.opensearch.knn.index.query.request.MethodParameter; + +import java.util.Map; + +/** + * Default HNSW context for all engines. Have a different implementation if engine context differs. + */ +public final class DefaultHnswSearchContext implements KNNLibrarySearchContext { + + private final Map> supportedMethodParameters = ImmutableMap.>builder() + .put( + MethodParameter.EF_SEARCH.getName(), + new Parameter.IntegerParameter(MethodParameter.EF_SEARCH.getName(), null, (value, context) -> true) + ) + .build(); + + @Override + public Map> supportedMethodParameters(QueryContext ctx) { + return supportedMethodParameters; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/DefaultIVFSearchContext.java b/src/main/java/org/opensearch/knn/index/engine/DefaultIVFSearchContext.java new file mode 100644 index 000000000..16e3f67d8 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/DefaultIVFSearchContext.java @@ -0,0 +1,27 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import com.google.common.collect.ImmutableMap; +import org.opensearch.knn.index.engine.model.QueryContext; +import org.opensearch.knn.index.query.request.MethodParameter; + +import java.util.Map; + +public final class DefaultIVFSearchContext implements KNNLibrarySearchContext { + + private final Map> supportedMethodParameters = ImmutableMap.>builder() + .put( + MethodParameter.NPROBE.getName(), + new Parameter.IntegerParameter(MethodParameter.NPROBE.getName(), null, (value, context) -> true) + ) + .build(); + + @Override + public Map> supportedMethodParameters(QueryContext context) { + return supportedMethodParameters; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/Encoder.java b/src/main/java/org/opensearch/knn/index/engine/Encoder.java new file mode 100644 index 000000000..f15d0afcf --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/Encoder.java @@ -0,0 +1,39 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import org.opensearch.knn.index.mapper.CompressionLevel; + +/** + * Interface representing an encoder. An encoder generally refers to a vector quantizer. + */ +public interface Encoder { + /** + * The name of the encoder does not have to be unique. Howevwer, when using within a method, there cannot be + * 2 encoders with the same name. + * + * @return Name of the encoder + */ + default String getName() { + return getMethodComponent().getName(); + } + + /** + * + * @return Method component associated with the encoder + */ + MethodComponent getMethodComponent(); + + /** + * Calculate the compression level for the give params. Assume float32 vectors are used. All parameters should + * be resolved in the encoderContext passed in. + * + * @param encoderContext Context for the encoder to extract params from + * @return Compression level this encoder produces. If the encoder does not support this calculation yet, it will + * return {@link CompressionLevel#NOT_CONFIGURED} + */ + CompressionLevel calculateCompressionLevel(MethodComponentContext encoderContext, KNNMethodConfigContext knnMethodConfigContext); +} diff --git a/src/main/java/org/opensearch/knn/index/engine/EngineResolver.java b/src/main/java/org/opensearch/knn/index/engine/EngineResolver.java new file mode 100644 index 000000000..d52c21c4c --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/EngineResolver.java @@ -0,0 +1,62 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; + +/** + * Figures out what {@link KNNEngine} to use based on configuration details + */ +public final class EngineResolver { + + public static final EngineResolver INSTANCE = new EngineResolver(); + + private EngineResolver() {} + + /** + * Based on the provided {@link Mode} and {@link CompressionLevel}, resolve to a {@link KNNEngine}. + * + * @param knnMethodConfigContext configuration context + * @param knnMethodContext KNNMethodContext + * @param requiresTraining whether config requires training + * @return {@link KNNEngine} + */ + public KNNEngine resolveEngine( + KNNMethodConfigContext knnMethodConfigContext, + KNNMethodContext knnMethodContext, + boolean requiresTraining + ) { + // User configuration gets precedence + if (knnMethodContext != null && knnMethodContext.isEngineConfigured()) { + return knnMethodContext.getKnnEngine(); + } + + // Faiss is the only engine that supports training, so we default to faiss here for now + if (requiresTraining) { + return KNNEngine.FAISS; + } + + Mode mode = knnMethodConfigContext.getMode(); + CompressionLevel compressionLevel = knnMethodConfigContext.getCompressionLevel(); + // If both mode and compression are not specified, we can just default + if (Mode.isConfigured(mode) == false && CompressionLevel.isConfigured(compressionLevel) == false) { + return KNNEngine.DEFAULT; + } + + // For 1x, we need to default to faiss if mode is provided and use nmslib otherwise + if (CompressionLevel.isConfigured(compressionLevel) == false || compressionLevel == CompressionLevel.x1) { + return mode == Mode.ON_DISK ? KNNEngine.FAISS : KNNEngine.NMSLIB; + } + + // Lucene is only engine that supports 4x - so we have to default to it here. + if (compressionLevel == CompressionLevel.x4) { + return KNNEngine.LUCENE; + } + + return KNNEngine.FAISS; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/JVMLibrary.java b/src/main/java/org/opensearch/knn/index/engine/JVMLibrary.java new file mode 100644 index 000000000..bfb25c7c6 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/JVMLibrary.java @@ -0,0 +1,41 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import java.util.Map; + +/** + * Abstract class for JVM based KNN libraries + */ +public abstract class JVMLibrary extends AbstractKNNLibrary { + + boolean initialized; + + /** + * Constructor + * + * @param methods Map of k-NN methods that the library supports + * @param version String representing version of library + */ + public JVMLibrary(Map methods, String version) { + super(methods, version); + } + + @Override + public int estimateOverheadInKB(KNNMethodContext knnMethodContext, KNNMethodConfigContext knnMethodConfigContext) { + throw new UnsupportedOperationException("Estimating overhead is not supported for JVM based libraries."); + } + + @Override + public Boolean isInitialized() { + return initialized; + } + + @Override + public void setInitialized(Boolean isInitialized) { + initialized = isInitialized; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/KNNEngine.java b/src/main/java/org/opensearch/knn/index/engine/KNNEngine.java new file mode 100644 index 000000000..1e560a11b --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/KNNEngine.java @@ -0,0 +1,214 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import com.google.common.collect.ImmutableSet; +import org.opensearch.common.ValidationException; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.engine.faiss.Faiss; +import org.opensearch.knn.index.engine.lucene.Lucene; +import org.opensearch.knn.index.engine.nmslib.Nmslib; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.opensearch.knn.common.KNNConstants.FAISS_NAME; +import static org.opensearch.knn.common.KNNConstants.LUCENE_NAME; +import static org.opensearch.knn.common.KNNConstants.NMSLIB_NAME; + +/** + * KNNEngine provides the functionality to validate and transform user defined indices into information that can be + * passed to the respective k-NN library's JNI layer. + */ +public enum KNNEngine implements KNNLibrary { + NMSLIB(NMSLIB_NAME, Nmslib.INSTANCE), + FAISS(FAISS_NAME, Faiss.INSTANCE), + LUCENE(LUCENE_NAME, Lucene.INSTANCE); + + public static final KNNEngine DEFAULT = FAISS; + + private static final Set CUSTOM_SEGMENT_FILE_ENGINES = ImmutableSet.of(KNNEngine.NMSLIB, KNNEngine.FAISS); + private static final Set ENGINES_SUPPORTING_FILTERS = ImmutableSet.of(KNNEngine.LUCENE, KNNEngine.FAISS); + public static final Set ENGINES_SUPPORTING_RADIAL_SEARCH = ImmutableSet.of(KNNEngine.LUCENE, KNNEngine.FAISS); + + private static Map MAX_DIMENSIONS_BY_ENGINE = Map.of( + KNNEngine.NMSLIB, + 16_000, + KNNEngine.FAISS, + 16_000, + KNNEngine.LUCENE, + 16_000 + ); + + /** + * Constructor for KNNEngine + * + * @param name name of engine + * @param knnLibrary library the engine uses + */ + KNNEngine(String name, KNNLibrary knnLibrary) { + this.name = name; + this.knnLibrary = knnLibrary; + } + + private final String name; + private final KNNLibrary knnLibrary; + + /** + * Get the engine + * + * @param name of engine to be fetched + * @return KNNEngine corresponding to name + */ + public static KNNEngine getEngine(String name) { + if (NMSLIB.getName().equalsIgnoreCase(name)) { + return NMSLIB; + } + + if (FAISS.getName().equalsIgnoreCase(name)) { + return FAISS; + } + + if (LUCENE.getName().equalsIgnoreCase(name)) { + return LUCENE; + } + + throw new IllegalArgumentException(String.format("Invalid engine type: %s", name)); + } + + /** + * Get the engine from the path. + * + * @param path to be checked + * @return KNNEngine corresponding to path + */ + public static KNNEngine getEngineNameFromPath(String path) { + if (path.endsWith(KNNEngine.NMSLIB.getExtension()) || path.endsWith(KNNEngine.NMSLIB.getCompoundExtension())) { + return KNNEngine.NMSLIB; + } + + if (path.endsWith(KNNEngine.FAISS.getExtension()) || path.endsWith(KNNEngine.FAISS.getCompoundExtension())) { + return KNNEngine.FAISS; + } + + throw new IllegalArgumentException("No engine matches the path's suffix"); + } + + /** + * Returns all engines that create custom segment files. + * + * @return Set of all engines that create custom segment files. + */ + public static Set getEnginesThatCreateCustomSegmentFiles() { + return CUSTOM_SEGMENT_FILE_ENGINES; + } + + public static Set getEnginesThatSupportsFilters() { + return ENGINES_SUPPORTING_FILTERS; + } + + /** + * Return number of max allowed dimensions per single vector based on the knn engine + * @param knnEngine knn engine to check max dimensions value + * @return + */ + public static int getMaxDimensionByEngine(KNNEngine knnEngine) { + return MAX_DIMENSIONS_BY_ENGINE.getOrDefault(knnEngine, MAX_DIMENSIONS_BY_ENGINE.get(KNNEngine.DEFAULT)); + } + + /** + * Get the name of the engine + * + * @return name of the engine + */ + public String getName() { + return name; + } + + @Override + public String getVersion() { + return knnLibrary.getVersion(); + } + + @Override + public String getExtension() { + return knnLibrary.getExtension(); + } + + @Override + public String getCompoundExtension() { + return knnLibrary.getCompoundExtension(); + } + + @Override + public float score(float rawScore, SpaceType spaceType) { + return knnLibrary.score(rawScore, spaceType); + } + + @Override + public Float distanceToRadialThreshold(Float distance, SpaceType spaceType) { + return knnLibrary.distanceToRadialThreshold(distance, spaceType); + } + + @Override + public Float scoreToRadialThreshold(Float score, SpaceType spaceType) { + return knnLibrary.scoreToRadialThreshold(score, spaceType); + } + + @Override + public ValidationException validateMethod(KNNMethodContext knnMethodContext, KNNMethodConfigContext knnMethodConfigContext) { + return knnLibrary.validateMethod(knnMethodContext, knnMethodConfigContext); + } + + @Override + public boolean isTrainingRequired(KNNMethodContext knnMethodContext) { + return knnLibrary.isTrainingRequired(knnMethodContext); + } + + @Override + public KNNLibraryIndexingContext getKNNLibraryIndexingContext( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + return knnLibrary.getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext); + } + + @Override + public KNNLibrarySearchContext getKNNLibrarySearchContext(String methodName) { + return knnLibrary.getKNNLibrarySearchContext(methodName); + } + + @Override + public int estimateOverheadInKB(KNNMethodContext knnMethodContext, KNNMethodConfigContext knnMethodConfigContext) { + return knnLibrary.estimateOverheadInKB(knnMethodContext, knnMethodConfigContext); + } + + @Override + public Boolean isInitialized() { + return knnLibrary.isInitialized(); + } + + @Override + public void setInitialized(Boolean isInitialized) { + knnLibrary.setInitialized(isInitialized); + } + + @Override + public List mmapFileExtensions() { + return knnLibrary.mmapFileExtensions(); + } + + @Override + public ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + final SpaceType spaceType + ) { + return knnLibrary.resolveMethod(knnMethodContext, knnMethodConfigContext, shouldRequireTraining, spaceType); + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/KNNLibrary.java b/src/main/java/org/opensearch/knn/index/engine/KNNLibrary.java new file mode 100644 index 000000000..cf7c4ad82 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/KNNLibrary.java @@ -0,0 +1,142 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import org.opensearch.common.ValidationException; +import org.opensearch.knn.index.SpaceType; + +import java.util.Collections; +import java.util.List; + +/** + * KNNLibrary is an interface that helps the plugin communicate with k-NN libraries + */ +public interface KNNLibrary extends MethodResolver { + + /** + * Gets the version of the library that is being used. In general, this can be used for ensuring compatibility of + * serialized artifacts. For instance, this can be used to check if a given file that was created on a different + * cluster is compatible with this instance of the library. + * + * @return the string representing the library's version + */ + String getVersion(); + + /** + * Gets the extension that files written with this library should have + * + * @return extension + */ + String getExtension(); + + /** + * Gets the compound extension that files written with this library should have + * + * @return compound extension + */ + String getCompoundExtension(); + + /** + * Generate the Lucene score from the rawScore returned by the library. With k-NN, often times the library + * will return a score where the lower the score, the better the result. This is the opposite of how Lucene scores + * documents. + * + * @param rawScore returned by the library + * @param spaceType spaceType used to compute the score + * @return Lucene score for the rawScore + */ + float score(float rawScore, SpaceType spaceType); + + /** + * Translate the distance radius input from end user to the engine's threshold. + * + * @param distance distance radius input from end user + * @param spaceType spaceType used to compute the radius + * + * @return transformed distance for the library + */ + Float distanceToRadialThreshold(Float distance, SpaceType spaceType); + + /** + * Translate the score threshold input from end user to the engine's threshold. + * + * @param score score threshold input from end user + * @param spaceType spaceType used to compute the threshold + * + * @return transformed score for the library + */ + Float scoreToRadialThreshold(Float score, SpaceType spaceType); + + /** + * Validate the knnMethodContext for the given library. A ValidationException should be thrown if the method is + * deemed invalid. + * + * @param knnMethodContext to be validated + * @param knnMethodConfigContext configuration context for the method + * @return ValidationException produced by validation errors; null if no validations errors. + */ + ValidationException validateMethod(KNNMethodContext knnMethodContext, KNNMethodConfigContext knnMethodConfigContext); + + /** + * Returns whether training is required or not from knnMethodContext for the given library. + * + * @param knnMethodContext methodContext + * @return true if training is required; false otherwise + */ + boolean isTrainingRequired(KNNMethodContext knnMethodContext); + + /** + * Estimate overhead of KNNMethodContext in Kilobytes. + * + * @param knnMethodContext to estimate size for + * @param knnMethodConfigContext configuration context for the method + * @return size overhead estimate in KB + */ + int estimateOverheadInKB(KNNMethodContext knnMethodContext, KNNMethodConfigContext knnMethodConfigContext); + + /** + * Get the context from the library needed to build the index. + * + * @param knnMethodContext to get build context for + * @param knnMethodConfigContext configuration context for the method + * @return parameter map + */ + KNNLibraryIndexingContext getKNNLibraryIndexingContext( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext + ); + + /** + * Gets metadata related to methods supported by the library + * + * @param methodName name of method + * @return KNNLibrarySearchContext + */ + KNNLibrarySearchContext getKNNLibrarySearchContext(String methodName); + + /** + * Getter for initialized + * + * @return whether library has been initialized + */ + Boolean isInitialized(); + + /** + * Set initialized to true + * + * @param isInitialized whether library has been initialized + */ + void setInitialized(Boolean isInitialized); + + /** + * Getter for mmap file extensions + * + * @return list of file extensions that will be read/write with mmap + */ + default List mmapFileExtensions() { + return Collections.emptyList(); + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/KNNLibraryIndexingContext.java b/src/main/java/org/opensearch/knn/index/engine/KNNLibraryIndexingContext.java new file mode 100644 index 000000000..9208661af --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/KNNLibraryIndexingContext.java @@ -0,0 +1,50 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.index.mapper.PerDimensionProcessor; +import org.opensearch.knn.index.mapper.PerDimensionValidator; +import org.opensearch.knn.index.mapper.VectorValidator; + +import java.util.Map; + +/** + * Context a library gives to build one of its indices + */ +public interface KNNLibraryIndexingContext { + /** + * Get map of parameters that get passed to the library to build the index + * + * @return Map of parameters + */ + Map getLibraryParameters(); + + /** + * Get map of parameters that get passed to the quantization framework + * + * @return Map of parameters + */ + QuantizationConfig getQuantizationConfig(); + + /** + * + * @return Get the vector validator + */ + VectorValidator getVectorValidator(); + + /** + * + * @return Get the per dimension validator + */ + PerDimensionValidator getPerDimensionValidator(); + + /** + * + * @return Get the per dimension processor + */ + PerDimensionProcessor getPerDimensionProcessor(); +} diff --git a/src/main/java/org/opensearch/knn/index/engine/KNNLibraryIndexingContextImpl.java b/src/main/java/org/opensearch/knn/index/engine/KNNLibraryIndexingContextImpl.java new file mode 100644 index 000000000..f5329fc31 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/KNNLibraryIndexingContextImpl.java @@ -0,0 +1,55 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import lombok.Builder; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.index.mapper.PerDimensionProcessor; +import org.opensearch.knn.index.mapper.PerDimensionValidator; +import org.opensearch.knn.index.mapper.VectorValidator; + +import java.util.Collections; +import java.util.Map; + +/** + * Simple implementation of {@link KNNLibraryIndexingContext} + */ +@Builder +public class KNNLibraryIndexingContextImpl implements KNNLibraryIndexingContext { + + private VectorValidator vectorValidator; + private PerDimensionValidator perDimensionValidator; + private PerDimensionProcessor perDimensionProcessor; + @Builder.Default + private Map parameters = Collections.emptyMap(); + @Builder.Default + private QuantizationConfig quantizationConfig = QuantizationConfig.EMPTY; + + @Override + public Map getLibraryParameters() { + return parameters; + } + + @Override + public QuantizationConfig getQuantizationConfig() { + return quantizationConfig; + } + + @Override + public VectorValidator getVectorValidator() { + return vectorValidator; + } + + @Override + public PerDimensionValidator getPerDimensionValidator() { + return perDimensionValidator; + } + + @Override + public PerDimensionProcessor getPerDimensionProcessor() { + return perDimensionProcessor; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/KNNLibrarySearchContext.java b/src/main/java/org/opensearch/knn/index/engine/KNNLibrarySearchContext.java new file mode 100644 index 000000000..b769745f6 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/KNNLibrarySearchContext.java @@ -0,0 +1,27 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import org.opensearch.knn.index.engine.model.QueryContext; + +import java.util.Collections; +import java.util.Map; + +/** + * Holds the context needed to search a knn library. + */ +public interface KNNLibrarySearchContext { + + /** + * Returns supported parameters for the library. + * + * @param ctx QueryContext + * @return parameters supported by the library + */ + Map> supportedMethodParameters(QueryContext ctx); + + KNNLibrarySearchContext EMPTY = ctx -> Collections.emptyMap(); +} diff --git a/src/main/java/org/opensearch/knn/index/engine/KNNMethod.java b/src/main/java/org/opensearch/knn/index/engine/KNNMethod.java new file mode 100644 index 000000000..0bcccacf0 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/KNNMethod.java @@ -0,0 +1,70 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import org.opensearch.common.ValidationException; +import org.opensearch.knn.index.SpaceType; + +/** + * KNNMethod defines the structure of a method supported by a particular k-NN library. It is used to validate + * the KNNMethodContext passed in by the user, where the KNNMethodContext provides the configuration that the user may + * want. Then, it provides the information necessary to build and search engine knn indices. + */ +public interface KNNMethod { + + /** + * Determines whether the provided space is supported for this method + * + * @param space to be checked + * @return true if the space is supported; false otherwise + */ + boolean isSpaceTypeSupported(SpaceType space); + + /** + * Validate that the configured KNNMethodContext is valid for this method + * + * @param knnMethodContext to be validated + * @param knnMethodConfigContext to be validated + * @return ValidationException produced by validation errors; null if no validations errors. + */ + ValidationException validate(KNNMethodContext knnMethodContext, KNNMethodConfigContext knnMethodConfigContext); + + /** + * returns whether training is required or not + * + * @param knnMethodContext context to check if training is required on + * @return true if training is required; false otherwise + */ + boolean isTrainingRequired(KNNMethodContext knnMethodContext); + + /** + * Returns the estimated overhead of the method in KB + * + * @param knnMethodContext context to estimate overhead + * @param knnMethodConfigContext config context to estimate overhead + * @return estimate overhead in KB + */ + int estimateOverheadInKB(KNNMethodContext knnMethodContext, KNNMethodConfigContext knnMethodConfigContext); + + /** + * Parse knnMethodContext into context that the library can use to build the index + * + * @param knnMethodContext to generate the context for + * @param knnMethodConfigContext to generate the context for + * @return KNNLibraryIndexingContext + */ + KNNLibraryIndexingContext getKNNLibraryIndexingContext( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext + ); + + /** + * Get the search context for a particular method + * + * @return KNNLibrarySearchContext + */ + KNNLibrarySearchContext getKNNLibrarySearchContext(); +} diff --git a/src/main/java/org/opensearch/knn/index/engine/KNNMethodConfigContext.java b/src/main/java/org/opensearch/knn/index/engine/KNNMethodConfigContext.java new file mode 100644 index 000000000..ccb427d29 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/KNNMethodConfigContext.java @@ -0,0 +1,38 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.opensearch.Version; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; + +/** + * This object provides additional context that the user does not provide when {@link KNNMethodContext} is + * created via parsing. The values in this object need to be dynamically set and calling code needs to handle + * the possibility that the values have not been set. + */ +@Setter +@Getter +@Builder +@AllArgsConstructor +@EqualsAndHashCode +public final class KNNMethodConfigContext { + private VectorDataType vectorDataType; + private Integer dimension; + private Version versionCreated; + @Builder.Default + private Mode mode = Mode.NOT_CONFIGURED; + @Builder.Default + private CompressionLevel compressionLevel = CompressionLevel.NOT_CONFIGURED; + + public static final KNNMethodConfigContext EMPTY = KNNMethodConfigContext.builder().build(); +} diff --git a/src/main/java/org/opensearch/knn/index/KNNMethodContext.java b/src/main/java/org/opensearch/knn/index/engine/KNNMethodContext.java similarity index 64% rename from src/main/java/org/opensearch/knn/index/KNNMethodContext.java rename to src/main/java/org/opensearch/knn/index/engine/KNNMethodContext.java index cd7b657f0..4a4c2704e 100644 --- a/src/main/java/org/opensearch/knn/index/KNNMethodContext.java +++ b/src/main/java/org/opensearch/knn/index/engine/KNNMethodContext.java @@ -1,29 +1,25 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. */ -package org.opensearch.knn.index; +package org.opensearch.knn.index.engine; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; import org.opensearch.common.ValidationException; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; -import org.opensearch.common.io.stream.Writeable; -import org.opensearch.knn.index.util.KNNEngine; -import org.opensearch.common.xcontent.ToXContentFragment; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.common.io.stream.Writeable; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.core.xcontent.ToXContentFragment; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.index.mapper.MapperParsingException; import java.io.IOException; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; @@ -31,7 +27,6 @@ import org.apache.commons.lang.builder.HashCodeBuilder; import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; -import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; import static org.opensearch.knn.common.KNNConstants.NAME; import static org.opensearch.knn.common.KNNConstants.PARAMETERS; @@ -40,38 +35,47 @@ * KNNMethodContext will contain the information necessary to produce a library index from an Opensearch mapping. * It will encompass all parameters necessary to build the index. */ +@AllArgsConstructor(access = AccessLevel.PACKAGE) +@Getter public class KNNMethodContext implements ToXContentFragment, Writeable { - private static final Logger logger = LogManager.getLogger(KNNMethodContext.class); + @NonNull + private KNNEngine knnEngine; + @NonNull + @Setter + private SpaceType spaceType; + @NonNull + private final MethodComponentContext methodComponentContext; + // Currently, the KNNEngine member variable cannot be null and defaults during parsing to nmslib. However, in order + // to support disk based engine resolution, this value potentially needs to be updated. Thus, this value is used + // to determine if the variable can be overridden or not based on whether the user explicitly set the value during parsing + private boolean isEngineConfigured; - private static KNNMethodContext defaultInstance = null; - - public static synchronized KNNMethodContext getDefault() { - if (defaultInstance == null) { - defaultInstance = new KNNMethodContext( - KNNEngine.DEFAULT, - SpaceType.DEFAULT, - new MethodComponentContext(METHOD_HNSW, Collections.emptyMap()) - ); + /** + * Copy constructor. Useful for creating a deep copy of a {@link KNNMethodContext}. Note that the engine and + * space type should be set. + * + * @param knnMethodContext original {@link KNNMethodContext}. Must NOT be null + */ + public KNNMethodContext(KNNMethodContext knnMethodContext) { + if (knnMethodContext == null) { + throw new IllegalArgumentException("KNNMethodContext cannot be null"); } - return defaultInstance; - } - private final KNNEngine knnEngine; - private final SpaceType spaceType; - private final MethodComponentContext methodComponent; + this.knnEngine = knnMethodContext.knnEngine; + this.spaceType = knnMethodContext.spaceType; + this.isEngineConfigured = true; + this.methodComponentContext = new MethodComponentContext(knnMethodContext.methodComponentContext); + } /** - * Constructor * - * @param knnEngine engine that this method uses - * @param spaceType space type that this method uses - * @param methodComponent MethodComponent describing the main index + * @param knnEngine {@link KNNEngine} + * @param spaceType {@link SpaceType} + * @param methodComponentContext {@link MethodComponentContext} */ - public KNNMethodContext(KNNEngine knnEngine, SpaceType spaceType, MethodComponentContext methodComponent) { - this.knnEngine = knnEngine; - this.spaceType = spaceType; - this.methodComponent = methodComponent; + public KNNMethodContext(KNNEngine knnEngine, SpaceType spaceType, MethodComponentContext methodComponentContext) { + this(knnEngine, spaceType, methodComponentContext, true); } /** @@ -83,43 +87,32 @@ public KNNMethodContext(KNNEngine knnEngine, SpaceType spaceType, MethodComponen public KNNMethodContext(StreamInput in) throws IOException { this.knnEngine = KNNEngine.getEngine(in.readString()); this.spaceType = SpaceType.getSpace(in.readString()); - this.methodComponent = new MethodComponentContext(in); + this.methodComponentContext = new MethodComponentContext(in); + this.isEngineConfigured = true; } /** - * Gets the main method component + * Set the {@link KNNEngine} if it is not configured (i.e. DEFAULT). This is useful for using different engines + * for different configurations - i.e. dynamic defaults * - * @return methodComponent + * @param knnEngine KNNEngine to set */ - public MethodComponentContext getMethodComponent() { - return methodComponent; - } - - /** - * Gets the engine to be used for this context - * - * @return knnEngine - */ - public KNNEngine getEngine() { - return knnEngine; - } - - /** - * Gets the space type for this context - * - * @return spaceType - */ - public SpaceType getSpaceType() { - return spaceType; + public void setKnnEngine(KNNEngine knnEngine) { + if (isEngineConfigured) { + throw new IllegalArgumentException("Cannot configure KNNEngine if it has already been configured"); + } + this.knnEngine = knnEngine; + this.isEngineConfigured = true; } /** - * This method uses the knnEngine to validate that the method is compatible with the engine + * This method uses the knnEngine to validate that the method is compatible with the engine. * + * @param knnMethodConfigContext context to validate against * @return ValidationException produced by validation errors; null if no validations errors. */ - public ValidationException validate() { - return knnEngine.validateMethod(this); + public ValidationException validate(KNNMethodConfigContext knnMethodConfigContext) { + return knnEngine.validateMethod(this, knnMethodConfigContext); } /** @@ -134,11 +127,11 @@ public boolean isTrainingRequired() { /** * This method estimates the overhead the knn method adds irrespective of the number of vectors * - * @param dimension dimension to make estimate with + * @param knnMethodConfigContext context to estimate overhead * @return size in Kilobytes */ - public int estimateOverheadInKB(int dimension) { - return knnEngine.estimateOverheadInKB(this, dimension); + public int estimateOverheadInKB(KNNMethodConfigContext knnMethodConfigContext) { + return knnEngine.estimateOverheadInKB(this, knnMethodConfigContext); } /** @@ -155,8 +148,9 @@ public static KNNMethodContext parse(Object in) { @SuppressWarnings("unchecked") Map methodMap = (Map) in; + boolean isEngineConfigured = false; KNNEngine engine = KNNEngine.DEFAULT; // Get or default - SpaceType spaceType = SpaceType.DEFAULT; // Get or default + SpaceType spaceType = SpaceType.UNDEFINED; // Get or default String name = ""; Map parameters = new HashMap<>(); @@ -177,6 +171,7 @@ public static KNNMethodContext parse(Object in) { throw new MapperParsingException("Invalid " + KNN_ENGINE + ": " + value); } } + isEngineConfigured = true; } else if (METHOD_PARAMETER_SPACE_TYPE.equals(key)) { if (value != null && !(value instanceof String)) { throw new MapperParsingException("\"" + METHOD_PARAMETER_SPACE_TYPE + "\" must be a string"); @@ -227,14 +222,14 @@ public static KNNMethodContext parse(Object in) { MethodComponentContext method = new MethodComponentContext(name, parameters); - return new KNNMethodContext(engine, spaceType, method); + return new KNNMethodContext(engine, spaceType, method, isEngineConfigured); } @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.field(KNN_ENGINE, knnEngine.getName()); builder.field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()); - builder = methodComponent.toXContent(builder, params); + builder = methodComponentContext.toXContent(builder, params); return builder; } @@ -247,20 +242,20 @@ public boolean equals(Object obj) { EqualsBuilder equalsBuilder = new EqualsBuilder(); equalsBuilder.append(knnEngine, other.knnEngine); equalsBuilder.append(spaceType, other.spaceType); - equalsBuilder.append(methodComponent, other.methodComponent); + equalsBuilder.append(methodComponentContext, other.methodComponentContext); return equalsBuilder.isEquals(); } @Override public int hashCode() { - return new HashCodeBuilder().append(knnEngine).append(spaceType).append(methodComponent).toHashCode(); + return new HashCodeBuilder().append(knnEngine).append(spaceType).append(methodComponentContext).toHashCode(); } @Override public void writeTo(StreamOutput out) throws IOException { out.writeString(knnEngine.getName()); out.writeString(spaceType.getValue()); - this.methodComponent.writeTo(out); + this.methodComponentContext.writeTo(out); } } diff --git a/src/main/java/org/opensearch/knn/index/MethodComponent.java b/src/main/java/org/opensearch/knn/index/engine/MethodComponent.java similarity index 58% rename from src/main/java/org/opensearch/knn/index/MethodComponent.java rename to src/main/java/org/opensearch/knn/index/engine/MethodComponent.java index d7957d74f..d456ea89f 100644 --- a/src/main/java/org/opensearch/knn/index/MethodComponent.java +++ b/src/main/java/org/opensearch/knn/index/engine/MethodComponent.java @@ -1,36 +1,45 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. */ -package org.opensearch.knn.index; +package org.opensearch.knn.index.engine; +import lombok.Getter; +import org.opensearch.Version; import org.opensearch.common.TriFunction; import org.opensearch.common.ValidationException; import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; +import org.opensearch.knn.index.util.IndexHyperParametersUtil; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; +import java.util.HashSet; +import java.util.Locale; import java.util.Map; -import java.util.function.BiFunction; +import java.util.Set; + +import static org.opensearch.knn.index.engine.validation.ParameterValidator.validateParameters; /** * MethodComponent defines the structure of an individual component that can make up an index */ public class MethodComponent { - private String name; - private Map> parameters; - private BiFunction> mapGenerator; - private TriFunction overheadInKBEstimator; - final private boolean requiresTraining; + @Getter + private final String name; + @Getter + private final Map> parameters; + private final TriFunction< + MethodComponent, + MethodComponentContext, + KNNMethodConfigContext, + KNNLibraryIndexingContext> knnLibraryIndexingContextGenerator; + private final TriFunction overheadInKBEstimator; + private final boolean requiresTraining; + private final Set supportedVectorDataTypes; /** * Constructor @@ -40,27 +49,10 @@ public class MethodComponent { private MethodComponent(Builder builder) { this.name = builder.name; this.parameters = builder.parameters; - this.mapGenerator = builder.mapGenerator; + this.knnLibraryIndexingContextGenerator = builder.knnLibraryIndexingContextGenerator; this.overheadInKBEstimator = builder.overheadInKBEstimator; this.requiresTraining = builder.requiresTraining; - } - - /** - * Get the name of the component - * - * @return name - */ - public String getName() { - return name; - } - - /** - * Get the parameters for the component - * - * @return parameters - */ - public Map> getParameters() { - return parameters; + this.supportedVectorDataTypes = builder.supportedDataTypes; } /** @@ -69,49 +61,52 @@ public Map> getParameters() { * @param methodComponentContext from which to generate map * @return Method component as a map */ - public Map getAsMap(MethodComponentContext methodComponentContext) { - if (mapGenerator == null) { + public KNNLibraryIndexingContext getKNNLibraryIndexingContext( + MethodComponentContext methodComponentContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + if (knnLibraryIndexingContextGenerator == null) { Map parameterMap = new HashMap<>(); parameterMap.put(KNNConstants.NAME, methodComponentContext.getName()); - parameterMap.put(KNNConstants.PARAMETERS, getParameterMapWithDefaultsAdded(methodComponentContext, this)); - return parameterMap; + parameterMap.put( + KNNConstants.PARAMETERS, + getParameterMapWithDefaultsAdded(methodComponentContext, this, knnMethodConfigContext) + ); + return KNNLibraryIndexingContextImpl.builder().parameters(parameterMap).build(); } - return mapGenerator.apply(this, methodComponentContext); + return knnLibraryIndexingContextGenerator.apply(this, methodComponentContext, knnMethodConfigContext); } /** * Validate that the methodComponentContext is a valid configuration for this methodComponent * * @param methodComponentContext to be validated + * @param knnMethodConfigContext context for the method configuration * @return ValidationException produced by validation errors; null if no validations errors. */ - public ValidationException validate(MethodComponentContext methodComponentContext) { + public ValidationException validate(MethodComponentContext methodComponentContext, KNNMethodConfigContext knnMethodConfigContext) { Map providedParameters = methodComponentContext.getParameters(); - List errorMessages = new ArrayList<>(); - if (providedParameters == null) { - return null; + ValidationException validationException = null; + if (!supportedVectorDataTypes.contains(knnMethodConfigContext.getVectorDataType())) { + validationException = new ValidationException(); + validationException.addValidationError( + String.format( + Locale.ROOT, + "Method \"%s\" is not supported for vector data type \"%s\".", + name, + knnMethodConfigContext.getVectorDataType() + ) + ); } - ValidationException parameterValidation; - for (Map.Entry parameter : providedParameters.entrySet()) { - if (!parameters.containsKey(parameter.getKey())) { - errorMessages.add(String.format("Invalid parameter for method \"%s\".", getName())); - continue; - } - - parameterValidation = parameters.get(parameter.getKey()).validate(parameter.getValue()); - if (parameterValidation != null) { - errorMessages.addAll(parameterValidation.validationErrors()); - } - } + ValidationException methodValidationException = validateParameters(parameters, providedParameters, knnMethodConfigContext); - if (errorMessages.isEmpty()) { - return null; + if (methodValidationException != null) { + validationException = validationException == null ? new ValidationException() : validationException; + validationException.addValidationErrors(methodValidationException.validationErrors()); } - ValidationException validationException = new ValidationException(); - validationException.addValidationErrors(errorMessages); return validationException; } @@ -221,11 +216,16 @@ public int estimateOverheadInKB(MethodComponentContext methodComponentContext, i */ public static class Builder { - private String name; - private Map> parameters; - private BiFunction> mapGenerator; + private final String name; + private final Map> parameters; + private TriFunction< + MethodComponent, + MethodComponentContext, + KNNMethodConfigContext, + KNNLibraryIndexingContext> knnLibraryIndexingContextGenerator; private TriFunction overheadInKBEstimator; private boolean requiresTraining; + private final Set supportedDataTypes; /** * Method to get a Builder instance @@ -234,14 +234,14 @@ public static class Builder { * @return Builder instance */ public static Builder builder(String name) { - return new MethodComponent.Builder(name); + return new Builder(name); } private Builder(String name) { this.name = name; this.parameters = new HashMap<>(); - this.mapGenerator = null; this.overheadInKBEstimator = (mc, mcc, d) -> 0L; + this.supportedDataTypes = new HashSet<>(); } /** @@ -259,11 +259,17 @@ public Builder addParameter(String parameterName, Parameter parameter) { /** * Set the function used to parse a MethodComponentContext as a map * - * @param mapGenerator function to parse a MethodComponentContext as a map + * @param knnLibraryIndexingContextGenerator function to parse a MethodComponentContext as a knnLibraryIndexingContext * @return this builder */ - public Builder setMapGenerator(BiFunction> mapGenerator) { - this.mapGenerator = mapGenerator; + public Builder setKnnLibraryIndexingContextGenerator( + TriFunction< + MethodComponent, + MethodComponentContext, + KNNMethodConfigContext, + KNNLibraryIndexingContext> knnLibraryIndexingContextGenerator + ) { + this.knnLibraryIndexingContextGenerator = knnLibraryIndexingContextGenerator; return this; } @@ -288,6 +294,17 @@ public Builder setOverheadInKBEstimator(TriFunction dataTypeSet) { + supportedDataTypes.addAll(dataTypeSet); + return this; + } + /** * Build MethodComponent * @@ -307,15 +324,56 @@ public MethodComponent build() { */ public static Map getParameterMapWithDefaultsAdded( MethodComponentContext methodComponentContext, - MethodComponent methodComponent + MethodComponent methodComponent, + KNNMethodConfigContext knnMethodConfigContext ) { Map parametersWithDefaultsMap = new HashMap<>(); Map userProvidedParametersMap = methodComponentContext.getParameters(); + Version indexCreationVersion = knnMethodConfigContext.getVersionCreated(); + Mode mode = knnMethodConfigContext.getMode(); + CompressionLevel compressionLevel = knnMethodConfigContext.getCompressionLevel(); + + // Check if the mode is ON_DISK and the compression level is one of the binary quantization levels (x32, x16, or x8). + // This determines whether to use binary quantization-specific values for parameters like ef_search and ef_construction. + boolean isOnDiskWithBinaryQuantization = (compressionLevel == CompressionLevel.x32 + || compressionLevel == CompressionLevel.x16 + || compressionLevel == CompressionLevel.x8); + for (Parameter parameter : methodComponent.getParameters().values()) { if (methodComponentContext.getParameters().containsKey(parameter.getName())) { parametersWithDefaultsMap.put(parameter.getName(), userProvidedParametersMap.get(parameter.getName())); } else { - parametersWithDefaultsMap.put(parameter.getName(), parameter.getDefaultValue()); + // Picking the right values for the parameters whose values are different based on different index + // created version. + if (parameter.getName().equals(KNNConstants.METHOD_PARAMETER_EF_SEARCH)) { + if (isOnDiskWithBinaryQuantization) { + parametersWithDefaultsMap.put(parameter.getName(), IndexHyperParametersUtil.getBinaryQuantizationEFSearchValue()); + } else { + parametersWithDefaultsMap.put( + parameter.getName(), + IndexHyperParametersUtil.getHNSWEFSearchValue(indexCreationVersion) + ); + } + } else if (parameter.getName().equals(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION)) { + if (isOnDiskWithBinaryQuantization) { + parametersWithDefaultsMap.put( + parameter.getName(), + IndexHyperParametersUtil.getBinaryQuantizationEFConstructionValue() + ); + } else { + parametersWithDefaultsMap.put( + parameter.getName(), + IndexHyperParametersUtil.getHNSWEFConstructionValue(indexCreationVersion) + ); + } + + } else { + Object value = parameter.getDefaultValue(); + if (value != null) { + parametersWithDefaultsMap.put(parameter.getName(), value); + } + } + } } diff --git a/src/main/java/org/opensearch/knn/index/engine/MethodComponentContext.java b/src/main/java/org/opensearch/knn/index/engine/MethodComponentContext.java new file mode 100644 index 000000000..1f0b345e9 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/MethodComponentContext.java @@ -0,0 +1,454 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang.math.NumberUtils; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.common.io.stream.Writeable; +import org.opensearch.core.xcontent.ToXContentFragment; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.index.mapper.MapperParsingException; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.opensearch.knn.indices.ModelMetadata; + +import static org.opensearch.knn.common.KNNConstants.NAME; +import static org.opensearch.knn.common.KNNConstants.PARAMETERS; + +/** + * MethodComponentContext represents a single user provided building block of a knn library index. + * + * Each component is composed of a name and a map of parameters. + */ +@RequiredArgsConstructor +public class MethodComponentContext implements ToXContentFragment, Writeable { + + // EMPTY method component context can only occur if a model originated on a cluster before 2.13.0 and the cluster is then upgraded to + // 2.13.0 + public static final MethodComponentContext EMPTY = new MethodComponentContext("", Collections.emptyMap()); + + private static final String DELIMITER = ";"; + private static final String DELIMITER_PLACEHOLDER = "$%$"; + + @Getter + private final String name; + private final Map parameters; + + /** + * Copy constructor. Creates a deep copy of a {@link MethodComponentContext} + * + * @param methodComponentContext to be copied. Must NOT be null + */ + public MethodComponentContext(MethodComponentContext methodComponentContext) { + if (methodComponentContext == null) { + throw new IllegalArgumentException("MethodComponentContext cannot be null"); + } + + this.name = methodComponentContext.name; + this.parameters = new HashMap<>(); + if (methodComponentContext.parameters != null) { + for (Map.Entry entry : methodComponentContext.parameters.entrySet()) { + if (entry.getValue() instanceof MethodComponentContext) { + parameters.put(entry.getKey(), new MethodComponentContext((MethodComponentContext) entry.getValue())); + } else { + parameters.put(entry.getKey(), entry.getValue()); + } + } + } + } + + /** + * Constructor from stream. + * + * @param in StreamInput + * @throws IOException on stream failure + */ + public MethodComponentContext(StreamInput in) throws IOException { + this.name = in.readString(); + + // Due to backwards compatibility issue, parameters could be null. To prevent any null pointer exceptions, + // do not read if their are no bytes left is null. Make sure this is in sync with the fellow read method. For + // more information, refer to https://github.com/opensearch-project/k-NN/issues/353. + if (in.available() > 0) { + this.parameters = in.readMap(StreamInput::readString, new ParameterMapValueReader()); + } else { + this.parameters = null; + } + } + + /** + * Parses the object into MethodComponentContext + * + * @param in Object to be parsed + * @return MethodComponentContext + */ + public static MethodComponentContext parse(Object in) { + if (!(in instanceof Map)) { + throw new MapperParsingException("Unable to parse MethodComponent"); + } + + @SuppressWarnings("unchecked") + Map methodMap = (Map) in; + String name = ""; + Map parameters = new HashMap<>(); + + String key; + Object value; + + for (Map.Entry methodEntry : methodMap.entrySet()) { + key = methodEntry.getKey(); + value = methodEntry.getValue(); + + if (NAME.equals(key)) { + if (!(value instanceof String)) { + throw new MapperParsingException("Component name should be a string"); + } + name = (String) value; + } else if (PARAMETERS.equals(key)) { + if (value == null) { + parameters = null; + continue; + } + + if (!(value instanceof Map)) { + throw new MapperParsingException("Unable to parse parameters for method component"); + } + + // Check to interpret map parameters as sub-methodComponentContexts + @SuppressWarnings("unchecked") + Map parameters1 = ((Map) value).entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> { + Object v = e.getValue(); + if (v instanceof Map) { + return MethodComponentContext.parse(v); + } + return v; + })); + + parameters = parameters1; + } else { + throw new MapperParsingException("Invalid parameter for MethodComponentContext: " + key); + } + } + + if (name.isEmpty()) { + throw new MapperParsingException(NAME + " needs to be set"); + } + + return new MethodComponentContext(name, parameters); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.field(NAME, name); + // Due to backwards compatibility issue, parameters could be null. To prevent any null pointer exceptions, + // we just create the null field. If parameters are not null, we created a nested structure. For more + // information, refer to https://github.com/opensearch-project/k-NN/issues/353. + if (parameters == null) { + builder.field(PARAMETERS, (String) null); + } else { + builder.startObject(PARAMETERS); + parameters.forEach((key, value) -> { + try { + if (value instanceof MethodComponentContext) { + builder.startObject(key); + ((MethodComponentContext) value).toXContent(builder, params); + builder.endObject(); + } else { + builder.field(key, value); + } + } catch (IOException ioe) { + throw new RuntimeException("Unable to generate xcontent for method component"); + } + + }); + builder.endObject(); + } + + return builder; + } + + public static MethodComponentContext fromXContent(XContentParser xContentParser) throws IOException { + // If it is a fresh parser, move to the first token + if (xContentParser.currentToken() == null) { + xContentParser.nextToken(); + } + Map parsedMap = xContentParser.map(); + return MethodComponentContext.parse(parsedMap); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + MethodComponentContext other = (MethodComponentContext) obj; + + EqualsBuilder equalsBuilder = new EqualsBuilder(); + equalsBuilder.append(name, other.name); + equalsBuilder.append(parameters, other.parameters); + return equalsBuilder.isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder().append(name).append(parameters).toHashCode(); + } + + /** + * Gets the parameters of the component + * + * @return parameters + */ + public Map getParameters() { + // Due to backwards compatibility issue, parameters could be null. To prevent any null pointer exceptions, + // return an empty map if parameters is null. For more information, refer to + // https://github.com/opensearch-project/k-NN/issues/353. + if (parameters == null) { + return Collections.emptyMap(); + } + return parameters; + } + + /** + * + * Provides a String representation of MethodComponentContext + * Sample return: + * {name=ivf;parameters=[nlist=4;type=fp16;encoder={name=sq;parameters=[nprobes=2;clip=false;]};]} + * + * @return string representation + */ + public String toClusterStateString() { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("{name=").append(name).append(DELIMITER); + stringBuilder.append("parameters=["); + if (Objects.nonNull(parameters)) { + for (Map.Entry entry : parameters.entrySet()) { + stringBuilder.append(entry.getKey()).append("="); + Object objectValue = entry.getValue(); + String value; + if (objectValue instanceof MethodComponentContext) { + value = ((MethodComponentContext) objectValue).toClusterStateString(); + } else { + value = entry.getValue().toString(); + } + // Model Metadata uses a delimiter to split the input string in its fromString method + // https://github.com/opensearch-project/k-NN/blob/2.12/src/main/java/org/opensearch/knn/indices/ModelMetadata.java#L265 + // If any of the values in the method component context contain this delimiter, + // then the method will not work correctly. Therefore, we replace the delimiter with an uncommon + // sequence that is very unlikely to appear in the value itself. + // https://github.com/opensearch-project/k-NN/issues/1337 + value = value.replace(ModelMetadata.DELIMITER, DELIMITER_PLACEHOLDER); + stringBuilder.append(value).append(DELIMITER); + } + } + stringBuilder.append("]}"); + return stringBuilder.toString(); + } + + /** + * This method converts a string created by the toClusterStateString() method of MethodComponentContext + * to a MethodComponentContext object. + * + * @param in a string representation of MethodComponentContext + * @return a MethodComponentContext object + */ + public static MethodComponentContext fromClusterStateString(String in) { + String stringToParse = unwrapString(in, '{', '}'); + + // Parse name from string + String[] nameAndParameters = stringToParse.split(DELIMITER, 2); + checkExpectedArrayLength(nameAndParameters, 2); + String name = parseName(nameAndParameters[0]); + String parametersString = nameAndParameters[1]; + Map parameters = parseParameters(parametersString); + return new MethodComponentContext(name, parameters); + } + + private static String parseName(String candidateNameString) { + // Expecting candidateNameString to look like "name=ivf" + checkStringNotEmpty(candidateNameString); + String[] nameKeyAndValue = candidateNameString.split("="); + checkStringMatches(nameKeyAndValue[0], "name"); + if (nameKeyAndValue.length == 1) { + return ""; + } + checkExpectedArrayLength(nameKeyAndValue, 2); + return nameKeyAndValue[1]; + } + + private static Map parseParameters(String candidateParameterString) { + checkStringNotEmpty(candidateParameterString); + String[] parametersKeyAndValue = candidateParameterString.split("=", 2); + checkStringMatches(parametersKeyAndValue[0], "parameters"); + if (parametersKeyAndValue.length == 1) { + return Collections.emptyMap(); + } + checkExpectedArrayLength(parametersKeyAndValue, 2); + return parseParametersValue(parametersKeyAndValue[1]); + } + + private static Map parseParametersValue(String candidateParameterValueString) { + // Expected input is [nlist=4;type=fp16;encoder={name=sq;parameters=[nprobes=2;clip=false;]};] + checkStringNotEmpty(candidateParameterValueString); + candidateParameterValueString = unwrapString(candidateParameterValueString, '[', ']'); + Map parameters = new HashMap<>(); + while (!candidateParameterValueString.isEmpty()) { + String[] keyAndValueToParse = candidateParameterValueString.split("=", 2); + if (keyAndValueToParse.length == 1 && keyAndValueToParse[0].charAt(0) == ';') { + break; + } + String key = keyAndValueToParse[0]; + ValueAndRestToParse parsed = parseParameterValueAndRestToParse(keyAndValueToParse[1]); + parameters.put(key, parsed.getValue()); + candidateParameterValueString = parsed.getRestToParse(); + } + + return parameters; + } + + private static ValueAndRestToParse parseParameterValueAndRestToParse(String candidateParameterValueAndRestToParse) { + if (candidateParameterValueAndRestToParse.charAt(0) == '{') { + int endOfNestedMap = findClosingPosition(candidateParameterValueAndRestToParse, '{', '}'); + String nestedMethodContext = candidateParameterValueAndRestToParse.substring(0, endOfNestedMap + 1); + Object nestedParse = fromClusterStateString(nestedMethodContext); + String restToParse = candidateParameterValueAndRestToParse.substring(endOfNestedMap + 1); + return new ValueAndRestToParse(nestedParse, restToParse); + } + + String[] stringValueAndRestToParse = candidateParameterValueAndRestToParse.split(DELIMITER, 2); + String stringValue = stringValueAndRestToParse[0]; + Object value; + if (NumberUtils.isNumber(stringValue)) { + value = Integer.parseInt(stringValue); + } else if (stringValue.equals("true") || stringValue.equals("false")) { + value = Boolean.parseBoolean(stringValue); + } else { + stringValue = stringValue.replace(DELIMITER_PLACEHOLDER, ModelMetadata.DELIMITER); + value = stringValue; + } + + return new ValueAndRestToParse(value, stringValueAndRestToParse[1]); + } + + private static String unwrapString(String in, char expectedStart, char expectedEnd) { + if (in.length() < 2) { + throw new IllegalArgumentException("Invalid string."); + } + + if (in.charAt(0) != expectedStart || in.charAt(in.length() - 1) != expectedEnd) { + throw new IllegalArgumentException("Invalid string." + in); + } + return in.substring(1, in.length() - 1); + } + + private static int findClosingPosition(String in, char expectedStart, char expectedEnd) { + int nestedLevel = 0; + for (int i = 0; i < in.length(); i++) { + if (in.charAt(i) == expectedStart) { + nestedLevel++; + continue; + } + + if (in.charAt(i) == expectedEnd) { + nestedLevel--; + } + + if (nestedLevel == 0) { + return i; + } + } + + throw new IllegalArgumentException("Invalid string. No end to the nesting"); + } + + private static void checkStringNotEmpty(String string) { + if (string.isEmpty()) { + throw new IllegalArgumentException("Unable to parse MethodComponentContext"); + } + } + + private static void checkStringMatches(String string, String expected) { + if (!Objects.equals(string, expected)) { + throw new IllegalArgumentException("Unexpected key in MethodComponentContext. Expected 'name' or 'parameters'"); + } + } + + private static void checkExpectedArrayLength(String[] array, int expectedLength) { + if (null == array) { + throw new IllegalArgumentException("Error parsing MethodComponentContext. Array is null."); + } + + if (array.length != expectedLength) { + throw new IllegalArgumentException("Error parsing MethodComponentContext. Array is not expected length."); + } + } + + @AllArgsConstructor + @Getter + private static class ValueAndRestToParse { + private final Object value; + private final String restToParse; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(this.name); + + // Due to backwards compatibility issue, parameters could be null. To prevent any null pointer exceptions, + // do not write if parameters is null. Make sure this is in sync with the fellow read method. For more + // information, refer to https://github.com/opensearch-project/k-NN/issues/353. + if (this.parameters != null) { + out.writeMap(this.parameters, StreamOutput::writeString, new ParameterMapValueWriter()); + } + } + + // Because the generic StreamOutput writeMap method can only write generic values, we need to create a custom one + // that handles the case when a parameter value is another method component context. + private static class ParameterMapValueWriter implements Writer { + + private ParameterMapValueWriter() {} + + @Override + public void write(StreamOutput out, Object o) throws IOException { + if (o instanceof MethodComponentContext) { + out.writeBoolean(true); + ((MethodComponentContext) o).writeTo(out); + } else { + out.writeBoolean(false); + out.writeGenericValue(o); + } + } + } + + // Because the generic StreamInput writeMap method can only read generic values, we need to create a custom one + // that handles the case when a parameter value is another method component context. + private static class ParameterMapValueReader implements Reader { + + private ParameterMapValueReader() {} + + @Override + public Object read(StreamInput in) throws IOException { + boolean isValueMethodComponentContext = in.readBoolean(); + if (isValueMethodComponentContext) { + return new MethodComponentContext(in); + } + return in.readGenericValue(); + } + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/MethodResolver.java b/src/main/java/org/opensearch/knn/index/engine/MethodResolver.java new file mode 100644 index 000000000..4df18ad72 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/MethodResolver.java @@ -0,0 +1,36 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import org.opensearch.knn.index.SpaceType; + +/** + * Interface for resolving the {@link ResolvedMethodContext} for an engine and configuration + */ +public interface MethodResolver { + + /** + * Creates a new {@link ResolvedMethodContext} filling parameters based on other configuration details. A validation + * exception will be thrown if the {@link KNNMethodConfigContext} is not compatible with the + * parameters provided by the user. + * + * @param knnMethodContext User provided information regarding the method context. A new context should be + * constructed. This variable will not be modified. + * @param knnMethodConfigContext Configuration details that can be used for resolving the defaults. Should not be null + * @param shouldRequireTraining Should the provided context require training + * @param spaceType Space type for the method. Cannot be null or undefined + * @return {@link ResolvedMethodContext} with dynamic defaults configured. This will include both the resolved + * compression as well as the completely resolve {@link KNNMethodContext}. + * This is guanteed to be a copy of the user provided context. + * @throws org.opensearch.common.ValidationException on invalid configuration and userprovided context. + */ + ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + final SpaceType spaceType + ); +} diff --git a/src/main/java/org/opensearch/knn/index/engine/NativeLibrary.java b/src/main/java/org/opensearch/knn/index/engine/NativeLibrary.java new file mode 100644 index 000000000..c3c61292a --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/NativeLibrary.java @@ -0,0 +1,77 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import lombok.Getter; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.SpaceType; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; + +/** + * Abstract implementation of KNNLibrary. It contains several default methods and fields that + * are common across different underlying libraries. + */ +public abstract class NativeLibrary extends AbstractKNNLibrary { + private final Map> scoreTranslation; + @Getter + private final String extension; + private final AtomicBoolean initialized; + + /** + * Constructor for NativeLibrary + * + * @param methods map of methods the native library supports + * @param scoreTranslation Map of translation of space type to scores returned by the library + * @param version String representation of version of the library + * @param extension String representing the extension that library files should use + */ + public NativeLibrary( + Map methods, + Map> scoreTranslation, + String version, + String extension + ) { + super(methods, version); + this.scoreTranslation = scoreTranslation; + this.extension = extension; + this.initialized = new AtomicBoolean(false); + } + + @Override + public String getCompoundExtension() { + return getExtension() + KNNConstants.COMPOUND_EXTENSION; + } + + @Override + public float score(float rawScore, SpaceType spaceType) { + if (this.scoreTranslation.containsKey(spaceType)) { + return this.scoreTranslation.get(spaceType).apply(rawScore); + } + + return spaceType.scoreTranslation(rawScore); + } + + @Override + public int estimateOverheadInKB(KNNMethodContext knnMethodContext, KNNMethodConfigContext knnMethodConfigContext) { + String methodName = knnMethodContext.getMethodComponentContext().getName(); + return methods.get(methodName).estimateOverheadInKB(knnMethodContext, knnMethodConfigContext); + } + + @Override + public Boolean isInitialized() { + return initialized.get(); + } + + @Override + public void setInitialized(Boolean isInitialized) { + Objects.requireNonNull(isInitialized, "isInitialized must not be null"); + initialized.set(isInitialized); + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/Parameter.java b/src/main/java/org/opensearch/knn/index/engine/Parameter.java new file mode 100644 index 000000000..4dd6b9c33 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/Parameter.java @@ -0,0 +1,244 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import lombok.Getter; +import org.opensearch.common.ValidationException; + +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiFunction; + +/** + * Parameter that can be set for a method component + * + * @param Type parameter takes + */ +public abstract class Parameter { + + @Getter + private final String name; + @Getter + private final T defaultValue; + protected BiFunction validator; + + /** + * Constructor + * + * @param name of the parameter + * @param defaultValue of the parameter + * @param validator used to validate a parameter value passed + */ + public Parameter(String name, T defaultValue, BiFunction validator) { + this.name = name; + this.defaultValue = defaultValue; + this.validator = validator; + } + + /** + * Check if the value passed in is valid + * + * @param value to be checked + * @param knnMethodConfigContext context for the validation + * @return ValidationException produced by validation errors; null if no validations errors. + */ + public abstract ValidationException validate(Object value, KNNMethodConfigContext knnMethodConfigContext); + + /** + * Boolean method parameter + */ + public static class BooleanParameter extends Parameter { + public BooleanParameter(String name, Boolean defaultValue, BiFunction validator) { + super(name, defaultValue, validator); + } + + @Override + public ValidationException validate(Object value, KNNMethodConfigContext knnMethodConfigContext) { + ValidationException validationException = null; + if (!(value instanceof Boolean)) { + validationException = new ValidationException(); + validationException.addValidationError( + String.format("value is not an instance of Boolean for Boolean parameter [%s].", getName()) + ); + return validationException; + } + + if (!validator.apply((Boolean) value, knnMethodConfigContext)) { + validationException = new ValidationException(); + validationException.addValidationError(String.format("parameter validation failed for Boolean parameter [%s].", getName())); + } + return validationException; + } + } + + /** + * Integer method parameter + */ + public static class IntegerParameter extends Parameter { + public IntegerParameter(String name, Integer defaultValue, BiFunction validator) { + super(name, defaultValue, validator); + } + + @Override + public ValidationException validate(Object value, KNNMethodConfigContext knnMethodConfigContext) { + ValidationException validationException = null; + if (!(value instanceof Integer)) { + validationException = new ValidationException(); + validationException.addValidationError( + String.format("value is not an instance of Integer for Integer parameter [%s].", getName()) + ); + return validationException; + } + + if (!validator.apply((Integer) value, knnMethodConfigContext)) { + validationException = new ValidationException(); + validationException.addValidationError(String.format("parameter validation failed for Integer parameter [%s].", getName())); + } + + return validationException; + } + } + + /** + * Double method parameter + */ + public static class DoubleParameter extends Parameter { + public DoubleParameter(String name, Double defaultValue, BiFunction validator) { + super(name, defaultValue, validator); + } + + @Override + public ValidationException validate(Object value, KNNMethodConfigContext knnMethodConfigContext) { + if (Objects.isNull(value)) { + String validationErrorMsg = String.format(Locale.ROOT, "Null value provided for Double " + "parameter \"%s\".", getName()); + return getValidationException(validationErrorMsg); + } + + if (value.equals(0)) value = 0.0; + + if (!(value instanceof Double)) { + String validationErrorMsg = String.format( + Locale.ROOT, + "value is not an instance of Double for Double parameter [%s].", + getName() + ); + return getValidationException(validationErrorMsg); + } + + if (!validator.apply((Double) value, knnMethodConfigContext)) { + String validationErrorMsg = String.format(Locale.ROOT, "parameter validation failed for Double parameter [%s].", getName()); + return getValidationException(validationErrorMsg); + } + return null; + } + + private ValidationException getValidationException(String validationErrorMsg) { + ValidationException validationException = new ValidationException(); + validationException.addValidationError(validationErrorMsg); + return validationException; + } + } + + /** + * String method parameter + */ + public static class StringParameter extends Parameter { + + /** + * Constructor + * + * @param name of the parameter + * @param defaultValue value to assign if the parameter is not set + * @param validator used to validate the parameter value passed + */ + public StringParameter(String name, String defaultValue, BiFunction validator) { + super(name, defaultValue, validator); + } + + @Override + public ValidationException validate(Object value, KNNMethodConfigContext knnMethodConfigContext) { + ValidationException validationException = null; + if (!(value instanceof String)) { + validationException = new ValidationException(); + validationException.addValidationError( + String.format("value is not an instance of String for String parameter [%s].", getName()) + ); + return validationException; + } + + if (!validator.apply((String) value, knnMethodConfigContext)) { + validationException = new ValidationException(); + validationException.addValidationError(String.format("parameter validation failed for String parameter [%s].", getName())); + } + + return validationException; + } + } + + /** + * MethodContext parameter. Some methods require sub-methods in order to implement some kind of functionality. For + * instance, faiss methods can contain an encoder along side the approximate nearest neighbor function to compress + * the input. This parameter makes it possible to add sub-methods to methods to support this kind of functionality + */ + public static class MethodComponentContextParameter extends Parameter { + + private final Map methodComponents; + + /** + * Constructor + * + * @param name of the parameter + * @param defaultValue value to assign this parameter if it is not set + * @param methodComponents valid components that the MethodComponentContext can map to + */ + public MethodComponentContextParameter( + String name, + MethodComponentContext defaultValue, + Map methodComponents + ) { + super(name, defaultValue, (methodComponentContext, knnMethodConfigContext) -> { + if (!methodComponents.containsKey(methodComponentContext.getName())) { + return false; + } + return methodComponents.get(methodComponentContext.getName()) + .validate(methodComponentContext, knnMethodConfigContext) == null; + }); + this.methodComponents = methodComponents; + } + + @Override + public ValidationException validate(Object value, KNNMethodConfigContext knnMethodConfigContext) { + ValidationException validationException = null; + if (!(value instanceof MethodComponentContext)) { + validationException = new ValidationException(); + validationException.addValidationError( + String.format("value is not an instance of for MethodComponentContext parameter [%s].", getName()) + ); + return validationException; + } + + if (!validator.apply((MethodComponentContext) value, knnMethodConfigContext)) { + validationException = new ValidationException(); + validationException.addValidationError( + String.format("parameter validation failed for MethodComponentContext parameter [%s].", getName()) + ); + } + + return validationException; + } + + /** + * Get method component by name + * + * @param name name of method component + * @return MethodComponent that name maps to + */ + public MethodComponent getMethodComponent(String name) { + return methodComponents.get(name); + } + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/ResolvedMethodContext.java b/src/main/java/org/opensearch/knn/index/engine/ResolvedMethodContext.java new file mode 100644 index 000000000..1edc0a98e --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/ResolvedMethodContext.java @@ -0,0 +1,23 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.opensearch.knn.index.mapper.CompressionLevel; + +/** + * Small data class for storing info that gets resolved during resolution process + */ +@RequiredArgsConstructor +@Getter +@Builder +public class ResolvedMethodContext { + private final KNNMethodContext knnMethodContext; + @Builder.Default + private final CompressionLevel compressionLevel = CompressionLevel.NOT_CONFIGURED; +} diff --git a/src/main/java/org/opensearch/knn/index/engine/SpaceTypeResolver.java b/src/main/java/org/opensearch/knn/index/engine/SpaceTypeResolver.java new file mode 100644 index 000000000..a12ffbc7b --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/SpaceTypeResolver.java @@ -0,0 +1,96 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import org.apache.logging.log4j.util.Strings; +import org.opensearch.index.mapper.MapperParsingException; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; + +import java.util.Locale; + +/** + * Class contains the logic to figure out what {@link SpaceType} to use based on configuration + * details. A user can either provide the {@link SpaceType} via the {@link KNNMethodContext} or through a + * top level parameter. This class will take care of this resolution logic (as well as if neither are configured) and + * ensure there are not any contradictions. + */ +public final class SpaceTypeResolver { + + public static final SpaceTypeResolver INSTANCE = new SpaceTypeResolver(); + + private SpaceTypeResolver() {} + + /** + * Resolves space type from configuration details. It is guaranteed not to return either null or + * {@link SpaceType#UNDEFINED} + * + * @param knnMethodContext Method context + * @param vectorDataType Vectordatatype + * @param topLevelSpaceTypeString Alternative top-level space type + * @return {@link SpaceType} for the method + */ + public SpaceType resolveSpaceType( + final KNNMethodContext knnMethodContext, + final VectorDataType vectorDataType, + final String topLevelSpaceTypeString + ) { + SpaceType methodSpaceType = getSpaceTypeFromMethodContext(knnMethodContext); + SpaceType topLevelSpaceType = getSpaceTypeFromString(topLevelSpaceTypeString); + + if (isSpaceTypeConfigured(methodSpaceType) == false && isSpaceTypeConfigured(topLevelSpaceType) == false) { + return getSpaceTypeFromVectorDataType(vectorDataType); + } + + if (isSpaceTypeConfigured(methodSpaceType) == false) { + return topLevelSpaceType; + } + + if (isSpaceTypeConfigured(topLevelSpaceType) == false) { + return methodSpaceType; + } + + if (methodSpaceType == topLevelSpaceType) { + return topLevelSpaceType; + } + + throw new MapperParsingException( + String.format( + Locale.ROOT, + "Cannot specify conflicting space types: \"[%s]\" \"[%s]\"", + methodSpaceType.getValue(), + topLevelSpaceType.getValue() + ) + ); + } + + private SpaceType getSpaceTypeFromMethodContext(final KNNMethodContext knnMethodContext) { + if (knnMethodContext == null) { + return SpaceType.UNDEFINED; + } + + return knnMethodContext.getSpaceType(); + } + + private SpaceType getSpaceTypeFromVectorDataType(final VectorDataType vectorDataType) { + if (vectorDataType == VectorDataType.BINARY) { + return SpaceType.DEFAULT_BINARY; + } + return SpaceType.DEFAULT; + } + + private SpaceType getSpaceTypeFromString(final String spaceType) { + if (Strings.isEmpty(spaceType)) { + return SpaceType.UNDEFINED; + } + + return SpaceType.getSpace(spaceType); + } + + private boolean isSpaceTypeConfigured(final SpaceType spaceType) { + return spaceType != null && spaceType != SpaceType.UNDEFINED; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/AbstractFaissMethod.java b/src/main/java/org/opensearch/knn/index/engine/faiss/AbstractFaissMethod.java new file mode 100644 index 000000000..7ae403445 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/AbstractFaissMethod.java @@ -0,0 +1,135 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import org.apache.commons.lang.StringUtils; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.AbstractKNNMethod; +import org.opensearch.knn.index.engine.KNNLibraryIndexingContext; +import org.opensearch.knn.index.engine.KNNLibrarySearchContext; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.mapper.PerDimensionProcessor; +import org.opensearch.knn.index.mapper.PerDimensionValidator; + +import java.util.Objects; +import java.util.Set; + +import static org.opensearch.knn.common.KNNConstants.FAISS_SIGNED_BYTE_SQ; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.index.engine.faiss.Faiss.FAISS_BINARY_INDEX_DESCRIPTION_PREFIX; +import static org.opensearch.knn.index.engine.faiss.FaissFP16Util.isFaissSQClipToFP16RangeEnabled; +import static org.opensearch.knn.index.engine.faiss.FaissFP16Util.isFaissSQfp16; + +public abstract class AbstractFaissMethod extends AbstractKNNMethod { + + /** + * Constructor for the AbstractFaissMethod class. + * + * @param methodComponent The method component used to create the method + * @param spaces The set of spaces supported by the method + * @param knnLibrarySearchContext The KNN library search context + */ + public AbstractFaissMethod(MethodComponent methodComponent, Set spaces, KNNLibrarySearchContext knnLibrarySearchContext) { + super(methodComponent, spaces, knnLibrarySearchContext); + } + + @Override + protected PerDimensionValidator doGetPerDimensionValidator( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + VectorDataType vectorDataType = knnMethodConfigContext.getVectorDataType(); + if (VectorDataType.BINARY == vectorDataType) { + return PerDimensionValidator.DEFAULT_BIT_VALIDATOR; + } + + if (VectorDataType.BYTE == vectorDataType) { + return PerDimensionValidator.DEFAULT_BYTE_VALIDATOR; + } + + if (VectorDataType.FLOAT == vectorDataType) { + if (isFaissSQfp16(knnMethodContext.getMethodComponentContext())) { + return FaissFP16Util.FP16_VALIDATOR; + } + return PerDimensionValidator.DEFAULT_FLOAT_VALIDATOR; + } + + throw new IllegalStateException("Unsupported vector data type " + vectorDataType); + } + + @Override + protected PerDimensionProcessor doGetPerDimensionProcessor( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + VectorDataType vectorDataType = knnMethodConfigContext.getVectorDataType(); + + if (VectorDataType.BINARY == vectorDataType) { + return PerDimensionProcessor.NOOP_PROCESSOR; + } + + if (VectorDataType.BYTE == vectorDataType) { + return PerDimensionProcessor.NOOP_PROCESSOR; + } + + if (VectorDataType.FLOAT == vectorDataType) { + if (isFaissSQClipToFP16RangeEnabled(knnMethodContext.getMethodComponentContext())) { + return FaissFP16Util.CLIP_TO_FP16_PROCESSOR; + } + return PerDimensionProcessor.NOOP_PROCESSOR; + } + + throw new IllegalStateException("Unsupported vector data type " + vectorDataType); + } + + static KNNLibraryIndexingContext adjustIndexDescription( + MethodAsMapBuilder methodAsMapBuilder, + MethodComponentContext methodComponentContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + String prefix = ""; + MethodComponentContext encoderContext = getEncoderMethodComponent(methodComponentContext); + // We need to update the prefix used to create the faiss index if we are using the quantization + // framework + if (encoderContext != null && Objects.equals(encoderContext.getName(), QFrameBitEncoder.NAME)) { + prefix = FAISS_BINARY_INDEX_DESCRIPTION_PREFIX; + } + + if (knnMethodConfigContext.getVectorDataType() == VectorDataType.BINARY) { + prefix = FAISS_BINARY_INDEX_DESCRIPTION_PREFIX; + } + if (knnMethodConfigContext.getVectorDataType() == VectorDataType.BYTE) { + + // If VectorDataType is Byte using Faiss engine then manipulate Index Description to use "SQ8_direct_signed" scalar quantizer + // For example, Index Description "HNSW16,Flat" will be updated as "HNSW16,SQ8_direct_signed" + String indexDescription = methodAsMapBuilder.indexDescription; + if (StringUtils.isNotEmpty(indexDescription)) { + StringBuilder indexDescriptionBuilder = new StringBuilder(); + indexDescriptionBuilder.append(indexDescription.split(",")[0]); + indexDescriptionBuilder.append(","); + indexDescriptionBuilder.append(FAISS_SIGNED_BYTE_SQ); + methodAsMapBuilder.indexDescription = indexDescriptionBuilder.toString(); + } + } + methodAsMapBuilder.indexDescription = prefix + methodAsMapBuilder.indexDescription; + return methodAsMapBuilder.build(); + } + + static MethodComponentContext getEncoderMethodComponent(MethodComponentContext methodComponentContext) { + if (!methodComponentContext.getParameters().containsKey(METHOD_ENCODER_PARAMETER)) { + return null; + } + Object object = methodComponentContext.getParameters().get(METHOD_ENCODER_PARAMETER); + if (!(object instanceof MethodComponentContext)) { + return null; + } + return (MethodComponentContext) object; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/AbstractFaissPQEncoder.java b/src/main/java/org/opensearch/knn/index/engine/faiss/AbstractFaissPQEncoder.java new file mode 100644 index 000000000..a894d8ed6 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/AbstractFaissPQEncoder.java @@ -0,0 +1,92 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import org.opensearch.common.ValidationException; +import org.opensearch.knn.index.engine.Encoder; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.mapper.CompressionLevel; + +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_SIZE; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_M; + +/** + * Abstract class for Faiss PQ encoders. This class provides the common logic for product quantization based encoders + */ +public abstract class AbstractFaissPQEncoder implements Encoder { + + @Override + public CompressionLevel calculateCompressionLevel( + MethodComponentContext methodComponentContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + // Roughly speaking, PQ can be configured to produce a lot of different compression levels. The "m" parameter + // specifies how many sub-vectors to break the vector up into, and then the "code_size" represents the number + // of bits to encode each subvector. Thus, a d-dimensional vector of float32s goes from + // d*32 -> (m)*code_size bits. So if we want (d*32)/(m*code_size) will be the compression level. + // + // Example: + // d=768, m=384, code_size=8 + // (768*32)/(384*8) = 8x (i.e. 24,576 vs. 3,072). + // + // Because of this variability, we will need to properly round to one of the supported values. + if (methodComponentContext.getParameters().containsKey(ENCODER_PARAMETER_PQ_M) == false + || methodComponentContext.getParameters().containsKey(ENCODER_PARAMETER_PQ_CODE_SIZE) == false) { + return CompressionLevel.NOT_CONFIGURED; + } + + // Map the number of bits passed in, back to the compression level + Object value = methodComponentContext.getParameters().get(ENCODER_PARAMETER_PQ_M); + ValidationException validationException = getMethodComponent().getParameters() + .get(ENCODER_PARAMETER_PQ_M) + .validate(value, knnMethodConfigContext); + if (validationException != null) { + throw validationException; + } + Integer m = (Integer) value; + value = methodComponentContext.getParameters().get(ENCODER_PARAMETER_PQ_CODE_SIZE); + validationException = getMethodComponent().getParameters() + .get(ENCODER_PARAMETER_PQ_CODE_SIZE) + .validate(value, knnMethodConfigContext); + if (validationException != null) { + throw validationException; + } + Integer codeSize = (Integer) value; + int dimension = knnMethodConfigContext.getDimension(); + + float actualCompression = ((float) dimension * 32) / (m * codeSize); + + if (actualCompression < 2.0f) { + return CompressionLevel.x1; + } + + if (actualCompression < 4.0f) { + return CompressionLevel.x2; + } + + if (actualCompression < 8.0f) { + return CompressionLevel.x4; + } + + if (actualCompression < 16.0f) { + return CompressionLevel.x8; + } + + if (actualCompression < 32.0f) { + return CompressionLevel.x16; + } + + if (actualCompression < 64.0f) { + return CompressionLevel.x32; + } + + // TODO: The problem is that the theoretical compression level of PQ can be in the thousands. Thus, Im not sure + // it makes sense to have an enum all the way up to that value. So, for now, we will just return the max + // compression + return CompressionLevel.MAX_COMPRESSION_LEVEL; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/Faiss.java b/src/main/java/org/opensearch/knn/index/engine/faiss/Faiss.java new file mode 100644 index 000000000..a602619a1 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/Faiss.java @@ -0,0 +1,105 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import com.google.common.collect.ImmutableMap; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.engine.KNNMethod; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodResolver; +import org.opensearch.knn.index.engine.NativeLibrary; +import org.opensearch.knn.index.engine.ResolvedMethodContext; + +import java.util.Map; +import java.util.function.Function; + +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_IVF; + +/** + * Implements NativeLibrary for the faiss native library + */ +public class Faiss extends NativeLibrary { + public static final String FAISS_BINARY_INDEX_DESCRIPTION_PREFIX = "B"; + Map> scoreTransform; + + // TODO: Current version is not really current version. Instead, it encodes information in the file name + // about the compatibility version the file is created with. In the future, we should refactor this so that it + // makes sense. See https://github.com/opensearch-project/k-NN/issues/1515 for more details. + private final static String CURRENT_VERSION = "165"; + + // Map that overrides OpenSearch score translation by space type of scores returned by faiss + private final static Map> SCORE_TRANSLATIONS = ImmutableMap.of( + SpaceType.INNER_PRODUCT, + rawScore -> SpaceType.INNER_PRODUCT.scoreTranslation(-1 * rawScore) + ); + + // Map that overrides radial search score threshold to faiss required distance, check more details in knn documentation: + // https://opensearch.org/docs/latest/search-plugins/knn/approximate-knn/#spaces + private final static Map> SCORE_TO_DISTANCE_TRANSFORMATIONS = ImmutableMap.< + SpaceType, + Function>builder().put(SpaceType.INNER_PRODUCT, score -> score > 1 ? 1 - score : 1 / score - 1).build(); + + // Package private so that the method resolving logic can access the methods + final static Map METHODS = ImmutableMap.of(METHOD_HNSW, new FaissHNSWMethod(), METHOD_IVF, new FaissIVFMethod()); + + public final static Faiss INSTANCE = new Faiss( + METHODS, + SCORE_TRANSLATIONS, + CURRENT_VERSION, + KNNConstants.FAISS_EXTENSION, + SCORE_TO_DISTANCE_TRANSFORMATIONS + ); + + private final MethodResolver methodResolver; + + /** + * Constructor for Faiss + * + * @param methods map of methods the native library supports + * @param scoreTranslation Map of translation of space type to scores returned by the library + * @param currentVersion String representation of current version of the library + * @param extension String representing the extension that library files should use + */ + private Faiss( + Map methods, + Map> scoreTranslation, + String currentVersion, + String extension, + Map> scoreTransform + ) { + super(methods, scoreTranslation, currentVersion, extension); + this.scoreTransform = scoreTransform; + this.methodResolver = new FaissMethodResolver(); + } + + @Override + public Float distanceToRadialThreshold(Float distance, SpaceType spaceType) { + // Faiss engine uses distance as is and does not need transformation + return distance; + } + + @Override + public Float scoreToRadialThreshold(Float score, SpaceType spaceType) { + // Faiss engine uses distance as is and need transformation + if (this.scoreTransform.containsKey(spaceType)) { + return this.scoreTransform.get(spaceType).apply(score); + } + return spaceType.scoreToDistanceTranslation(score); + } + + @Override + public ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + final SpaceType spaceType + ) { + return methodResolver.resolveMethod(knnMethodContext, knnMethodConfigContext, shouldRequireTraining, spaceType); + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissFP16Util.java b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissFP16Util.java new file mode 100644 index 000000000..8e76ca0fb --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissFP16Util.java @@ -0,0 +1,145 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.mapper.PerDimensionProcessor; +import org.opensearch.knn.index.mapper.PerDimensionValidator; + +import java.util.Locale; +import java.util.Map; +import java.util.Objects; + +import static org.opensearch.knn.common.KNNConstants.ENCODER_SQ; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_CLIP; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_ENCODER_FP16; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_TYPE; +import static org.opensearch.knn.common.KNNConstants.FP16_MAX_VALUE; +import static org.opensearch.knn.common.KNNConstants.FP16_MIN_VALUE; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNValidationUtil.validateFloatVectorValue; + +public class FaissFP16Util { + + // Validates if it is a finite number and within the fp16 range of [-65504 to 65504]. + static PerDimensionValidator FP16_VALIDATOR = new PerDimensionValidator() { + @Override + public void validate(float value) { + validateFP16VectorValue(value); + } + + @Override + public void validateByte(float value) { + throw new IllegalStateException("DEFAULT_FP16_VALIDATOR should only be used for float vectors"); + } + }; + + // If the encoder parameter, "clip" is set to True, if the vector value is outside the FP16 range then it will be + // clipped to FP16 range. + static PerDimensionProcessor CLIP_TO_FP16_PROCESSOR = new PerDimensionProcessor() { + @Override + public float process(float value) { + return clipVectorValueToFP16Range(value); + } + + @Override + public float processByte(float value) { + throw new IllegalStateException("CLIP_TO_FP16_PROCESSOR should not be called with byte type"); + } + }; + + /** + * Validate the float vector value and if it is outside FP16 range, + * then it will be clipped to FP16 range of [-65504 to 65504]. + * + * @param value float vector value + * @return vector value clipped to FP16 range + */ + public static float clipVectorValueToFP16Range(float value) { + validateFloatVectorValue(value); + if (value < FP16_MIN_VALUE) return FP16_MIN_VALUE; + if (value > FP16_MAX_VALUE) return FP16_MAX_VALUE; + return value; + } + + /** + * Validate the float vector value and throw exception if it is not a number or not in the finite range + * or is not within the FP16 range of [-65504 to 65504]. + * + * @param value float vector value + */ + public static void validateFP16VectorValue(float value) { + validateFloatVectorValue(value); + if (value < FP16_MIN_VALUE || value > FP16_MAX_VALUE) { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "encoder name is set as [%s] and type is set as [%s] in index mapping. But, KNN vector values are not within in the FP16 range [%f, %f]", + ENCODER_SQ, + FAISS_SQ_ENCODER_FP16, + FP16_MIN_VALUE, + FP16_MAX_VALUE + ) + ); + } + } + + /** + * Verify mapping and return true if it is a "faiss" Index using "sq" encoder of type "fp16" + * + * @param methodComponentContext MethodComponentContext + * @return true if it is a "faiss" Index using "sq" encoder of type "fp16" + */ + static boolean isFaissSQfp16(MethodComponentContext methodComponentContext) { + MethodComponentContext encoderContext = extractEncoderMethodComponentContext(methodComponentContext); + if (encoderContext == null) { + return false; + } + + // returns true if encoder name is "sq" and type is "fp16" + return ENCODER_SQ.equals(encoderContext.getName()) + && FAISS_SQ_ENCODER_FP16.equals(encoderContext.getParameters().getOrDefault(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16)); + } + + /** + * Verify mapping and return the value of "clip" parameter(default false) for a "faiss" Index + * using "sq" encoder of type "fp16". + * + * @param methodComponentContext MethodComponentContext + * @return boolean value of "clip" parameter + */ + static boolean isFaissSQClipToFP16RangeEnabled(MethodComponentContext methodComponentContext) { + MethodComponentContext encoderContext = extractEncoderMethodComponentContext(methodComponentContext); + if (encoderContext == null) { + return false; + } + return (boolean) encoderContext.getParameters().getOrDefault(FAISS_SQ_CLIP, false); + } + + static MethodComponentContext extractEncoderMethodComponentContext(MethodComponentContext methodComponentContext) { + if (Objects.isNull(methodComponentContext)) { + return null; + } + + if (methodComponentContext.getParameters().isEmpty()) { + return null; + } + + Map methodComponentParams = methodComponentContext.getParameters(); + + // The method component parameters should have an encoder + if (!methodComponentParams.containsKey(METHOD_ENCODER_PARAMETER)) { + return null; + } + + // Validate if the object is of type MethodComponentContext before casting it later + if (!(methodComponentParams.get(METHOD_ENCODER_PARAMETER) instanceof MethodComponentContext)) { + return null; + } + + return (MethodComponentContext) methodComponentParams.get(METHOD_ENCODER_PARAMETER); + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissFlatEncoder.java b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissFlatEncoder.java new file mode 100644 index 000000000..f7d4342fc --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissFlatEncoder.java @@ -0,0 +1,55 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import com.google.common.collect.ImmutableSet; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.Encoder; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.mapper.CompressionLevel; + +import java.util.Set; + +/** + * Flat faiss encoder. Flat encoding means that it does nothing. It needs an encoder, though, because it + * is used in generating the index description. + */ +public class FaissFlatEncoder implements Encoder { + + private static final Set SUPPORTED_DATA_TYPES = ImmutableSet.of( + VectorDataType.FLOAT, + VectorDataType.BYTE, + VectorDataType.BINARY + ); + + private final static MethodComponent METHOD_COMPONENT = MethodComponent.Builder.builder(KNNConstants.ENCODER_FLAT) + .setKnnLibraryIndexingContextGenerator( + ((methodComponent, methodComponentContext, knnMethodConfigContext) -> MethodAsMapBuilder.builder( + KNNConstants.FAISS_FLAT_DESCRIPTION, + methodComponent, + methodComponentContext, + knnMethodConfigContext + ).build()) + ) + .addSupportedDataTypes(SUPPORTED_DATA_TYPES) + .build(); + + @Override + public MethodComponent getMethodComponent() { + return METHOD_COMPONENT; + } + + @Override + public CompressionLevel calculateCompressionLevel( + MethodComponentContext encoderContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + return CompressionLevel.x1; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissHNSWMethod.java b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissHNSWMethod.java new file mode 100644 index 000000000..c153a9328 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissHNSWMethod.java @@ -0,0 +1,126 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import com.google.common.collect.ImmutableSet; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.AbstractKNNMethod; +import org.opensearch.knn.index.engine.DefaultHnswSearchContext; +import org.opensearch.knn.index.engine.Encoder; +import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.engine.Parameter; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.opensearch.knn.common.KNNConstants.FAISS_HNSW_DESCRIPTION; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_SEARCH; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_M; + +/** + * Faiss HNSW method implementation + */ +public class FaissHNSWMethod extends AbstractFaissMethod { + + private static final Set SUPPORTED_DATA_TYPES = ImmutableSet.of( + VectorDataType.FLOAT, + VectorDataType.BINARY, + VectorDataType.BYTE + ); + + public final static List SUPPORTED_SPACES = Arrays.asList( + SpaceType.UNDEFINED, + SpaceType.HAMMING, + SpaceType.L2, + SpaceType.INNER_PRODUCT + ); + + private final static MethodComponentContext DEFAULT_ENCODER_CONTEXT = new MethodComponentContext( + KNNConstants.ENCODER_FLAT, + Collections.emptyMap() + ); + + // Package private so that the method resolving logic can access the methods + final static Encoder FLAT_ENCODER = new FaissFlatEncoder(); + final static Encoder SQ_ENCODER = new FaissSQEncoder(); + final static Encoder HNSW_PQ_ENCODER = new FaissHNSWPQEncoder(); + final static Encoder QFRAME_BIT_ENCODER = new QFrameBitEncoder(); + final static Map SUPPORTED_ENCODERS = Map.of( + FLAT_ENCODER.getName(), + FLAT_ENCODER, + SQ_ENCODER.getName(), + SQ_ENCODER, + HNSW_PQ_ENCODER.getName(), + HNSW_PQ_ENCODER, + QFRAME_BIT_ENCODER.getName(), + QFRAME_BIT_ENCODER + ); + final static MethodComponent HNSW_COMPONENT = initMethodComponent(); + + /** + * Constructor for FaissHNSWMethod + * + * @see AbstractKNNMethod + */ + public FaissHNSWMethod() { + super(HNSW_COMPONENT, Set.copyOf(SUPPORTED_SPACES), new DefaultHnswSearchContext()); + } + + private static MethodComponent initMethodComponent() { + return MethodComponent.Builder.builder(METHOD_HNSW) + .addSupportedDataTypes(SUPPORTED_DATA_TYPES) + .addParameter( + METHOD_PARAMETER_M, + new Parameter.IntegerParameter(METHOD_PARAMETER_M, KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_M, (v, context) -> v > 0) + ) + .addParameter( + METHOD_PARAMETER_EF_CONSTRUCTION, + new Parameter.IntegerParameter( + METHOD_PARAMETER_EF_CONSTRUCTION, + KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION, + (v, context) -> v > 0 + ) + ) + .addParameter( + METHOD_PARAMETER_EF_SEARCH, + new Parameter.IntegerParameter( + METHOD_PARAMETER_EF_SEARCH, + KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH, + (v, context) -> v > 0 + ) + ) + .addParameter(METHOD_ENCODER_PARAMETER, initEncoderParameter()) + .setKnnLibraryIndexingContextGenerator(((methodComponent, methodComponentContext, knnMethodConfigContext) -> { + MethodAsMapBuilder methodAsMapBuilder = MethodAsMapBuilder.builder( + FAISS_HNSW_DESCRIPTION, + methodComponent, + methodComponentContext, + knnMethodConfigContext + ).addParameter(METHOD_PARAMETER_M, "", "").addParameter(METHOD_ENCODER_PARAMETER, ",", ""); + return adjustIndexDescription(methodAsMapBuilder, methodComponentContext, knnMethodConfigContext); + })) + .build(); + } + + private static Parameter.MethodComponentContextParameter initEncoderParameter() { + return new Parameter.MethodComponentContextParameter( + METHOD_ENCODER_PARAMETER, + DEFAULT_ENCODER_CONTEXT, + SUPPORTED_ENCODERS.values().stream().collect(Collectors.toMap(Encoder::getName, Encoder::getMethodComponent)) + ); + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissHNSWPQEncoder.java b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissHNSWPQEncoder.java new file mode 100644 index 000000000..c22a9dec7 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissHNSWPQEncoder.java @@ -0,0 +1,71 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import com.google.common.collect.ImmutableSet; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.Parameter; + +import java.util.Objects; +import java.util.Set; + +import static org.opensearch.knn.common.KNNConstants.BYTES_PER_KILOBYTES; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_COUNT_DEFAULT; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_COUNT_LIMIT; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_SIZE; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_SIZE_DEFAULT; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_M; +import static org.opensearch.knn.common.KNNConstants.FAISS_PQ_DESCRIPTION; + +/** + * Faiss HNSW PQ encoder. Right now, the implementations are slightly different during validation between this an + * {@link FaissIVFPQEncoder}. Hence, they are separate classes. + */ +public class FaissHNSWPQEncoder extends AbstractFaissPQEncoder { + + private static final Set SUPPORTED_DATA_TYPES = ImmutableSet.of(VectorDataType.FLOAT); + + private final static MethodComponent METHOD_COMPONENT = MethodComponent.Builder.builder(KNNConstants.ENCODER_PQ) + .addSupportedDataTypes(SUPPORTED_DATA_TYPES) + .addParameter( + ENCODER_PARAMETER_PQ_M, + new Parameter.IntegerParameter(ENCODER_PARAMETER_PQ_M, ENCODER_PARAMETER_PQ_CODE_COUNT_DEFAULT, (v, context) -> { + boolean isValueGreaterThan0 = v > 0; + boolean isValueLessThanCodeCountLimit = v < ENCODER_PARAMETER_PQ_CODE_COUNT_LIMIT; + boolean isDimensionDivisibleByValue = context.getDimension() % v == 0; + return isValueGreaterThan0 && isValueLessThanCodeCountLimit && isDimensionDivisibleByValue; + }) + ) + .addParameter( + ENCODER_PARAMETER_PQ_CODE_SIZE, + new Parameter.IntegerParameter( + ENCODER_PARAMETER_PQ_CODE_SIZE, + ENCODER_PARAMETER_PQ_CODE_SIZE_DEFAULT, + (v, context) -> Objects.equals(v, ENCODER_PARAMETER_PQ_CODE_SIZE_DEFAULT) + ) + ) + .setRequiresTraining(true) + .setKnnLibraryIndexingContextGenerator( + ((methodComponent, methodComponentContext, knnMethodConfigContext) -> MethodAsMapBuilder.builder( + FAISS_PQ_DESCRIPTION, + methodComponent, + methodComponentContext, + knnMethodConfigContext + ).addParameter(ENCODER_PARAMETER_PQ_M, "", "").build()) + ) + .setOverheadInKBEstimator((methodComponent, methodComponentContext, dimension) -> { + int codeSize = ENCODER_PARAMETER_PQ_CODE_SIZE_DEFAULT; + return ((4L * (1L << codeSize) * dimension) / BYTES_PER_KILOBYTES) + 1; + }) + .build(); + + @Override + public MethodComponent getMethodComponent() { + return METHOD_COMPONENT; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissIVFMethod.java b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissIVFMethod.java new file mode 100644 index 000000000..340c1f4d8 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissIVFMethod.java @@ -0,0 +1,152 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import com.google.common.collect.ImmutableSet; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.AbstractKNNMethod; +import org.opensearch.knn.index.engine.DefaultIVFSearchContext; +import org.opensearch.knn.index.engine.Encoder; +import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.engine.Parameter; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.opensearch.knn.common.KNNConstants.BYTES_PER_KILOBYTES; +import static org.opensearch.knn.common.KNNConstants.FAISS_IVF_DESCRIPTION; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_IVF; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NLIST; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NLIST_DEFAULT; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NLIST_LIMIT; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NPROBES; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NPROBES_DEFAULT; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NPROBES_LIMIT; + +/** + * Faiss ivf implementation + */ +public class FaissIVFMethod extends AbstractFaissMethod { + + private static final Set SUPPORTED_DATA_TYPES = ImmutableSet.of( + VectorDataType.FLOAT, + VectorDataType.BINARY, + VectorDataType.BYTE + ); + + public final static List SUPPORTED_SPACES = Arrays.asList( + SpaceType.UNDEFINED, + SpaceType.L2, + SpaceType.INNER_PRODUCT, + SpaceType.HAMMING + ); + + private final static MethodComponentContext DEFAULT_ENCODER_CONTEXT = new MethodComponentContext( + KNNConstants.ENCODER_FLAT, + Collections.emptyMap() + ); + + // Package private so that the method resolving logic can access the methods + final static Encoder FLAT_ENCODER = new FaissFlatEncoder(); + final static Encoder SQ_ENCODER = new FaissSQEncoder(); + final static Encoder IVF_PQ_ENCODER = new FaissIVFPQEncoder(); + final static Encoder QFRAME_BIT_ENCODER = new QFrameBitEncoder(); + final static Map SUPPORTED_ENCODERS = Map.of( + FLAT_ENCODER.getName(), + FLAT_ENCODER, + SQ_ENCODER.getName(), + SQ_ENCODER, + IVF_PQ_ENCODER.getName(), + IVF_PQ_ENCODER, + QFRAME_BIT_ENCODER.getName(), + QFRAME_BIT_ENCODER + ); + + final static MethodComponent IVF_COMPONENT = initMethodComponent(); + + /** + * Constructor for FaissIVFMethod + * + * @see AbstractKNNMethod + */ + public FaissIVFMethod() { + super(IVF_COMPONENT, Set.copyOf(SUPPORTED_SPACES), new DefaultIVFSearchContext()); + } + + private static MethodComponent initMethodComponent() { + return MethodComponent.Builder.builder(METHOD_IVF) + .addSupportedDataTypes(SUPPORTED_DATA_TYPES) + .addParameter( + METHOD_PARAMETER_NPROBES, + new Parameter.IntegerParameter( + METHOD_PARAMETER_NPROBES, + METHOD_PARAMETER_NPROBES_DEFAULT, + (v, context) -> v > 0 && v < METHOD_PARAMETER_NPROBES_LIMIT + ) + ) + .addParameter( + METHOD_PARAMETER_NLIST, + new Parameter.IntegerParameter( + METHOD_PARAMETER_NLIST, + METHOD_PARAMETER_NLIST_DEFAULT, + (v, context) -> v > 0 && v < METHOD_PARAMETER_NLIST_LIMIT + ) + ) + .addParameter(METHOD_ENCODER_PARAMETER, initEncoderParameter()) + .setRequiresTraining(true) + .setKnnLibraryIndexingContextGenerator(((methodComponent, methodComponentContext, knnMethodConfigContext) -> { + MethodAsMapBuilder methodAsMapBuilder = MethodAsMapBuilder.builder( + FAISS_IVF_DESCRIPTION, + methodComponent, + methodComponentContext, + knnMethodConfigContext + ).addParameter(METHOD_PARAMETER_NLIST, "", "").addParameter(METHOD_ENCODER_PARAMETER, ",", ""); + return adjustIndexDescription(methodAsMapBuilder, methodComponentContext, knnMethodConfigContext); + })) + .setOverheadInKBEstimator((methodComponent, methodComponentContext, dimension) -> { + // Size estimate formula: (4 * nlists * d) / 1024 + 1 + + // Get value of nlists passed in by user + Object nlistObject = methodComponentContext.getParameters().get(METHOD_PARAMETER_NLIST); + + // If not specified, get default value of nlist + if (nlistObject == null) { + Parameter nlistParameter = methodComponent.getParameters().get(METHOD_PARAMETER_NLIST); + if (nlistParameter == null) { + throw new IllegalStateException( + String.format("%s is not a valid parameter. This is a bug.", METHOD_PARAMETER_NLIST) + ); + } + + nlistObject = nlistParameter.getDefaultValue(); + } + + if (!(nlistObject instanceof Integer)) { + throw new IllegalStateException(String.format("%s must be an integer.", METHOD_PARAMETER_NLIST)); + } + + int centroids = (Integer) nlistObject; + return ((4L * centroids * dimension) / BYTES_PER_KILOBYTES) + 1; + }) + .build(); + } + + private static Parameter.MethodComponentContextParameter initEncoderParameter() { + return new Parameter.MethodComponentContextParameter( + METHOD_ENCODER_PARAMETER, + DEFAULT_ENCODER_CONTEXT, + SUPPORTED_ENCODERS.values().stream().collect(Collectors.toMap(Encoder::getName, Encoder::getMethodComponent)) + ); + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissIVFPQEncoder.java b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissIVFPQEncoder.java new file mode 100644 index 000000000..8c10aebdf --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissIVFPQEncoder.java @@ -0,0 +1,94 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableSet; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.Parameter; + +import java.util.Set; + +import static org.opensearch.knn.common.KNNConstants.BYTES_PER_KILOBYTES; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_COUNT_DEFAULT; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_COUNT_LIMIT; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_SIZE; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_SIZE_DEFAULT; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_SIZE_LIMIT; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_M; +import static org.opensearch.knn.common.KNNConstants.FAISS_PQ_DESCRIPTION; + +/** + * Faiss IVF PQ encoder. Right now, the implementations are slightly different during validation between this an + * {@link FaissHNSWPQEncoder}. Hence, they are separate classes. + */ +public class FaissIVFPQEncoder extends AbstractFaissPQEncoder { + + private static final Set SUPPORTED_DATA_TYPES = ImmutableSet.of(VectorDataType.FLOAT); + + @VisibleForTesting + final static MethodComponent METHOD_COMPONENT = MethodComponent.Builder.builder(KNNConstants.ENCODER_PQ) + .addSupportedDataTypes(SUPPORTED_DATA_TYPES) + .addParameter( + ENCODER_PARAMETER_PQ_M, + new Parameter.IntegerParameter(ENCODER_PARAMETER_PQ_M, ENCODER_PARAMETER_PQ_CODE_COUNT_DEFAULT, (v, context) -> { + boolean isValueGreaterThan0 = v > 0; + boolean isValueLessThanCodeCountLimit = v < ENCODER_PARAMETER_PQ_CODE_COUNT_LIMIT; + boolean isDimensionDivisibleByValue = context.getDimension() % v == 0; + return isValueGreaterThan0 && isValueLessThanCodeCountLimit && isDimensionDivisibleByValue; + }) + ) + .addParameter( + ENCODER_PARAMETER_PQ_CODE_SIZE, + new Parameter.IntegerParameter(ENCODER_PARAMETER_PQ_CODE_SIZE, ENCODER_PARAMETER_PQ_CODE_SIZE_DEFAULT, (v, context) -> { + boolean isValueGreaterThan0 = v > 0; + boolean isValueLessThanCodeSizeLimit = v < ENCODER_PARAMETER_PQ_CODE_SIZE_LIMIT; + return isValueGreaterThan0 && isValueLessThanCodeSizeLimit; + }) + ) + .setRequiresTraining(true) + .setKnnLibraryIndexingContextGenerator( + ((methodComponent, methodComponentContext, knnMethodConfigContext) -> MethodAsMapBuilder.builder( + FAISS_PQ_DESCRIPTION, + methodComponent, + methodComponentContext, + knnMethodConfigContext + ).addParameter(ENCODER_PARAMETER_PQ_M, "", "").addParameter(ENCODER_PARAMETER_PQ_CODE_SIZE, "x", "").build()) + ) + .setOverheadInKBEstimator((methodComponent, methodComponentContext, dimension) -> { + // Size estimate formula: (4 * d * 2^code_size) / 1024 + 1 + + // Get value of code size passed in by user + Object codeSizeObject = methodComponentContext.getParameters().get(ENCODER_PARAMETER_PQ_CODE_SIZE); + + // If not specified, get default value of code size + if (codeSizeObject == null) { + Parameter codeSizeParameter = methodComponent.getParameters().get(ENCODER_PARAMETER_PQ_CODE_SIZE); + if (codeSizeParameter == null) { + throw new IllegalStateException( + String.format("%s is not a valid parameter. This is a bug.", ENCODER_PARAMETER_PQ_CODE_SIZE) + ); + } + + codeSizeObject = codeSizeParameter.getDefaultValue(); + } + + if (!(codeSizeObject instanceof Integer)) { + throw new IllegalStateException(String.format("%s must be an integer.", ENCODER_PARAMETER_PQ_CODE_SIZE)); + } + + int codeSize = (Integer) codeSizeObject; + return ((4L * (1L << codeSize) * dimension) / BYTES_PER_KILOBYTES) + 1; + }) + .build(); + + @Override + public MethodComponent getMethodComponent() { + return METHOD_COMPONENT; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissMethodResolver.java b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissMethodResolver.java new file mode 100644 index 000000000..c976a0959 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissMethodResolver.java @@ -0,0 +1,160 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import org.opensearch.common.ValidationException; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.engine.AbstractMethodResolver; +import org.opensearch.knn.index.engine.Encoder; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.engine.ResolvedMethodContext; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static org.opensearch.knn.common.KNNConstants.ENCODER_FLAT; +import static org.opensearch.knn.common.KNNConstants.ENCODER_SQ; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_ENCODER_FP16; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_TYPE; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_IVF; +import static org.opensearch.knn.index.engine.faiss.FaissHNSWMethod.HNSW_COMPONENT; +import static org.opensearch.knn.index.engine.faiss.FaissIVFMethod.IVF_COMPONENT; + +public class FaissMethodResolver extends AbstractMethodResolver { + + private static final Set SUPPORTED_COMPRESSION_LEVELS = Set.of( + CompressionLevel.x1, + CompressionLevel.x2, + CompressionLevel.x8, + CompressionLevel.x16, + CompressionLevel.x32 + ); + + @Override + public ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + final SpaceType spaceType + ) { + // Initial validation to ensure that there are no contradictions in provided parameters + validateConfig(knnMethodConfigContext); + + KNNMethodContext resolvedKNNMethodContext = initResolvedKNNMethodContext( + knnMethodContext, + KNNEngine.FAISS, + spaceType, + shouldRequireTraining ? METHOD_IVF : METHOD_HNSW + ); + MethodComponent method = METHOD_HNSW.equals(resolvedKNNMethodContext.getMethodComponentContext().getName()) == false + ? IVF_COMPONENT + : HNSW_COMPONENT; + Map encoderMap = method == HNSW_COMPONENT ? FaissHNSWMethod.SUPPORTED_ENCODERS : FaissIVFMethod.SUPPORTED_ENCODERS; + + // Fill in parameters for the encoder and then the method. + resolveEncoder(resolvedKNNMethodContext, knnMethodConfigContext, encoderMap); + // From the resolved method context, get the compression level and validate it against the passed in + // configuration + CompressionLevel resolvedCompressionLevel = resolveCompressionLevelFromMethodContext( + resolvedKNNMethodContext, + knnMethodConfigContext, + encoderMap + ); + + // Validate that resolved compression doesnt have any conflicts + validateCompressionConflicts(knnMethodConfigContext.getCompressionLevel(), resolvedCompressionLevel); + knnMethodConfigContext.setCompressionLevel(resolvedCompressionLevel); + resolveMethodParams(resolvedKNNMethodContext.getMethodComponentContext(), knnMethodConfigContext, method); + + return ResolvedMethodContext.builder() + .knnMethodContext(resolvedKNNMethodContext) + .compressionLevel(resolvedCompressionLevel) + .build(); + } + + private void resolveEncoder( + KNNMethodContext resolvedKNNMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + Map encoderMap + ) { + if (shouldEncoderBeResolved(resolvedKNNMethodContext, knnMethodConfigContext) == false) { + return; + } + + CompressionLevel resolvedCompressionLevel = getDefaultCompressionLevel(knnMethodConfigContext); + if (resolvedCompressionLevel == CompressionLevel.x1) { + return; + } + + MethodComponentContext encoderComponentContext = new MethodComponentContext(ENCODER_FLAT, new HashMap<>()); + Encoder encoder = encoderMap.get(ENCODER_FLAT); + if (CompressionLevel.x2 == resolvedCompressionLevel) { + encoderComponentContext = new MethodComponentContext(ENCODER_SQ, new HashMap<>()); + encoder = encoderMap.get(ENCODER_SQ); + encoderComponentContext.getParameters().put(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16); + } + + if (CompressionLevel.x8 == resolvedCompressionLevel) { + encoderComponentContext = new MethodComponentContext(QFrameBitEncoder.NAME, new HashMap<>()); + encoder = encoderMap.get(QFrameBitEncoder.NAME); + encoderComponentContext.getParameters().put(QFrameBitEncoder.BITCOUNT_PARAM, CompressionLevel.x8.numBitsForFloat32()); + } + + if (CompressionLevel.x16 == resolvedCompressionLevel) { + encoderComponentContext = new MethodComponentContext(QFrameBitEncoder.NAME, new HashMap<>()); + encoder = encoderMap.get(QFrameBitEncoder.NAME); + encoderComponentContext.getParameters().put(QFrameBitEncoder.BITCOUNT_PARAM, CompressionLevel.x16.numBitsForFloat32()); + } + + if (CompressionLevel.x32 == resolvedCompressionLevel) { + encoderComponentContext = new MethodComponentContext(QFrameBitEncoder.NAME, new HashMap<>()); + encoder = encoderMap.get(QFrameBitEncoder.NAME); + encoderComponentContext.getParameters().put(QFrameBitEncoder.BITCOUNT_PARAM, CompressionLevel.x32.numBitsForFloat32()); + } + + Map resolvedParams = MethodComponent.getParameterMapWithDefaultsAdded( + encoderComponentContext, + encoder.getMethodComponent(), + knnMethodConfigContext + ); + encoderComponentContext.getParameters().putAll(resolvedParams); + resolvedKNNMethodContext.getMethodComponentContext().getParameters().put(METHOD_ENCODER_PARAMETER, encoderComponentContext); + } + + // Method validates for explicit contradictions in the config + private void validateConfig(KNNMethodConfigContext knnMethodConfigContext) { + CompressionLevel compressionLevel = knnMethodConfigContext.getCompressionLevel(); + ValidationException validationException = validateCompressionSupported( + compressionLevel, + SUPPORTED_COMPRESSION_LEVELS, + KNNEngine.FAISS, + null + ); + validationException = validateCompressionNotx1WhenOnDisk(knnMethodConfigContext, validationException); + if (validationException != null) { + throw validationException; + } + } + + private CompressionLevel getDefaultCompressionLevel(KNNMethodConfigContext knnMethodConfigContext) { + if (CompressionLevel.isConfigured(knnMethodConfigContext.getCompressionLevel())) { + return knnMethodConfigContext.getCompressionLevel(); + } + if (knnMethodConfigContext.getMode() == Mode.ON_DISK) { + return CompressionLevel.x32; + } + return CompressionLevel.x1; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/FaissSQEncoder.java b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissSQEncoder.java new file mode 100644 index 000000000..cd7e1e5f3 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/FaissSQEncoder.java @@ -0,0 +1,64 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import com.google.common.collect.ImmutableSet; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.Encoder; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.engine.Parameter; +import org.opensearch.knn.index.mapper.CompressionLevel; + +import java.util.Objects; +import java.util.Set; + +import static org.opensearch.knn.common.KNNConstants.ENCODER_SQ; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_CLIP; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_DESCRIPTION; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_ENCODER_FP16; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_ENCODER_TYPES; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_TYPE; + +/** + * Faiss SQ encoder + */ +public class FaissSQEncoder implements Encoder { + + private static final Set SUPPORTED_DATA_TYPES = ImmutableSet.of(VectorDataType.FLOAT); + + private final static MethodComponent METHOD_COMPONENT = MethodComponent.Builder.builder(ENCODER_SQ) + .addSupportedDataTypes(SUPPORTED_DATA_TYPES) + .addParameter( + FAISS_SQ_TYPE, + new Parameter.StringParameter(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16, (v, context) -> FAISS_SQ_ENCODER_TYPES.contains(v)) + ) + .addParameter(FAISS_SQ_CLIP, new Parameter.BooleanParameter(FAISS_SQ_CLIP, false, (v, context) -> Objects.nonNull(v))) + .setKnnLibraryIndexingContextGenerator( + ((methodComponent, methodComponentContext, knnMethodConfigContext) -> MethodAsMapBuilder.builder( + FAISS_SQ_DESCRIPTION, + methodComponent, + methodComponentContext, + knnMethodConfigContext + ).addParameter(FAISS_SQ_TYPE, "", "").build()) + ) + .build(); + + @Override + public MethodComponent getMethodComponent() { + return METHOD_COMPONENT; + } + + @Override + public CompressionLevel calculateCompressionLevel( + MethodComponentContext methodComponentContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + // TODO: Hard code for now + return CompressionLevel.x2; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/MethodAsMapBuilder.java b/src/main/java/org/opensearch/knn/index/engine/faiss/MethodAsMapBuilder.java new file mode 100644 index 000000000..8b688cbcc --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/MethodAsMapBuilder.java @@ -0,0 +1,117 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import lombok.AllArgsConstructor; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.engine.KNNLibraryIndexingContext; +import org.opensearch.knn.index.engine.KNNLibraryIndexingContextImpl; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.engine.Parameter; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; + +import java.util.HashMap; +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.NAME; +import static org.opensearch.knn.common.KNNConstants.PARAMETERS; + +/** + * MethodAsMap builder is used to create the map that will be passed to the jni to create the faiss index. + * Faiss's index factory takes an "index description" that it uses to build the index. In this description, + * some parameters of the index can be configured; others need to be manually set. MethodMap builder creates + * the index description from a set of parameters and removes them from the map. On build, it sets the index + * description in the map and returns the processed map + */ +@AllArgsConstructor +class MethodAsMapBuilder { + String indexDescription; + MethodComponent methodComponent; + Map methodAsMap; + KNNMethodConfigContext knnMethodConfigContext; + QuantizationConfig quantizationConfig; + + /** + * Add a parameter that will be used in the index description for the given method component + * + * @param parameterName name of the parameter + * @param prefix to append to the index description before the parameter + * @param suffix to append to the index description after the parameter + * @return this builder + */ + @SuppressWarnings("unchecked") + MethodAsMapBuilder addParameter(String parameterName, String prefix, String suffix) { + indexDescription += prefix; + + // When we add a parameter, what we are doing is taking it from the methods parameter and building it + // into the index description string faiss uses to create the index. + Map methodParameters = (Map) methodAsMap.get(PARAMETERS); + Parameter parameter = methodComponent.getParameters().get(parameterName); + Object value = methodParameters.containsKey(parameterName) ? methodParameters.get(parameterName) : parameter.getDefaultValue(); + + // Recursion is needed if the parameter is a method component context itself. + if (parameter instanceof Parameter.MethodComponentContextParameter) { + MethodComponentContext subMethodComponentContext = (MethodComponentContext) value; + MethodComponent subMethodComponent = ((Parameter.MethodComponentContextParameter) parameter).getMethodComponent( + subMethodComponentContext.getName() + ); + + KNNLibraryIndexingContext knnLibraryIndexingContext = subMethodComponent.getKNNLibraryIndexingContext( + subMethodComponentContext, + knnMethodConfigContext + ); + Map subMethodAsMap = knnLibraryIndexingContext.getLibraryParameters(); + if (subMethodAsMap != null + && !subMethodAsMap.isEmpty() + && subMethodAsMap.containsKey(KNNConstants.INDEX_DESCRIPTION_PARAMETER)) { + indexDescription += subMethodAsMap.get(KNNConstants.INDEX_DESCRIPTION_PARAMETER); + subMethodAsMap.remove(KNNConstants.INDEX_DESCRIPTION_PARAMETER); + } + + if (quantizationConfig == null || quantizationConfig == QuantizationConfig.EMPTY) { + quantizationConfig = knnLibraryIndexingContext.getQuantizationConfig(); + } + + // We replace parameterName with the map that contains only parameters that are not included in + // the method description + methodParameters.put(parameterName, subMethodAsMap); + } else { + // Just add the value to the method description and remove from map + indexDescription += value; + methodParameters.remove(parameterName); + } + + indexDescription += suffix; + return this; + } + + /** + * Build + * + * @return Method as a map + */ + KNNLibraryIndexingContext build() { + methodAsMap.put(KNNConstants.INDEX_DESCRIPTION_PARAMETER, indexDescription); + return KNNLibraryIndexingContextImpl.builder().parameters(methodAsMap).quantizationConfig(quantizationConfig).build(); + } + + static MethodAsMapBuilder builder( + String baseDescription, + MethodComponent methodComponent, + MethodComponentContext methodComponentContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + Map initialMap = new HashMap<>(); + initialMap.put(NAME, methodComponent.getName()); + initialMap.put( + PARAMETERS, + MethodComponent.getParameterMapWithDefaultsAdded(methodComponentContext, methodComponent, knnMethodConfigContext) + ); + return new MethodAsMapBuilder(baseDescription, methodComponent, initialMap, knnMethodConfigContext, QuantizationConfig.EMPTY); + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/faiss/QFrameBitEncoder.java b/src/main/java/org/opensearch/knn/index/engine/faiss/QFrameBitEncoder.java new file mode 100644 index 000000000..2292dc3cc --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/faiss/QFrameBitEncoder.java @@ -0,0 +1,113 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import com.google.common.collect.ImmutableSet; +import org.opensearch.common.ValidationException; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.Encoder; +import org.opensearch.knn.index.engine.KNNLibraryIndexingContextImpl; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.engine.Parameter; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Set; + +import static org.opensearch.knn.common.KNNConstants.FAISS_FLAT_DESCRIPTION; +import static org.opensearch.knn.common.KNNConstants.INDEX_DESCRIPTION_PARAMETER; + +/** + * Quantization framework binary encoder, + */ +public class QFrameBitEncoder implements Encoder { + + public static final String NAME = "binary"; + public static final String BITCOUNT_PARAM = "bits"; + private static final int DEFAULT_BITS = 1; + private static final Set validBitCounts = ImmutableSet.of(1, 2, 4); + private static final Set SUPPORTED_DATA_TYPES = ImmutableSet.of(VectorDataType.FLOAT); + + /** + * { + * "encoder": { + * "name": "binary", + * "parameters": { + * "bits": 2 + * } + * } + * } + */ + private final static MethodComponent METHOD_COMPONENT = MethodComponent.Builder.builder(NAME) + .addSupportedDataTypes(SUPPORTED_DATA_TYPES) + .addParameter( + BITCOUNT_PARAM, + new Parameter.IntegerParameter(BITCOUNT_PARAM, DEFAULT_BITS, (v, context) -> validBitCounts.contains(v)) + ) + .setKnnLibraryIndexingContextGenerator(((methodComponent, methodComponentContext, knnMethodConfigContext) -> { + QuantizationConfig quantizationConfig; + int bitCount = (int) methodComponentContext.getParameters().getOrDefault(BITCOUNT_PARAM, DEFAULT_BITS); + if (bitCount == 1) { + quantizationConfig = QuantizationConfig.builder().quantizationType(ScalarQuantizationType.ONE_BIT).build(); + } else if (bitCount == 2) { + quantizationConfig = QuantizationConfig.builder().quantizationType(ScalarQuantizationType.TWO_BIT).build(); + } else if (bitCount == 4) { + quantizationConfig = QuantizationConfig.builder().quantizationType(ScalarQuantizationType.FOUR_BIT).build(); + } else { + throw new IllegalArgumentException(String.format(Locale.ROOT, "Invalid bit count: %d", bitCount)); + } + + // We use the flat description because we are doing the quantization + return KNNLibraryIndexingContextImpl.builder().quantizationConfig(quantizationConfig).parameters(new HashMap<>() { + { + put(INDEX_DESCRIPTION_PARAMETER, FAISS_FLAT_DESCRIPTION); + } + }).build(); + })) + .setRequiresTraining(false) + .build(); + + @Override + public MethodComponent getMethodComponent() { + return METHOD_COMPONENT; + } + + @Override + public CompressionLevel calculateCompressionLevel( + MethodComponentContext methodComponentContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + if (methodComponentContext.getParameters().containsKey(BITCOUNT_PARAM) == false) { + return CompressionLevel.NOT_CONFIGURED; + } + + // Map the number of bits passed in, back to the compression level + Object value = methodComponentContext.getParameters().get(BITCOUNT_PARAM); + ValidationException validationException = METHOD_COMPONENT.getParameters() + .get(BITCOUNT_PARAM) + .validate(value, knnMethodConfigContext); + if (validationException != null) { + throw validationException; + } + + Integer bitCount = (Integer) value; + if (bitCount == 1) { + return CompressionLevel.x32; + } + + if (bitCount == 2) { + return CompressionLevel.x16; + } + + // Validation will ensure that only 1 of the supported bit count will be selected. + return CompressionLevel.x8; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/lucene/Lucene.java b/src/main/java/org/opensearch/knn/index/engine/lucene/Lucene.java new file mode 100644 index 000000000..db516d309 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/lucene/Lucene.java @@ -0,0 +1,106 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.lucene; + +import com.google.common.collect.ImmutableMap; +import org.apache.lucene.util.Version; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.engine.JVMLibrary; +import org.opensearch.knn.index.engine.KNNMethod; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodResolver; +import org.opensearch.knn.index.engine.ResolvedMethodContext; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; + +/** + * KNN Library for Lucene + */ +public class Lucene extends JVMLibrary { + + Map> distanceTransform; + + final static Map METHODS = ImmutableMap.of(METHOD_HNSW, new LuceneHNSWMethod()); + + // Map that overrides the default distance translations for Lucene, check more details in knn documentation: + // https://opensearch.org/docs/latest/search-plugins/knn/approximate-knn/#spaces + private final static Map> DISTANCE_TRANSLATIONS = ImmutableMap.< + SpaceType, + Function>builder() + .put(SpaceType.COSINESIMIL, distance -> (2 - distance) / 2) + .put(SpaceType.INNER_PRODUCT, distance -> distance <= 0 ? 1 / (1 - distance) : distance + 1) + .build(); + + public final static Lucene INSTANCE = new Lucene(METHODS, Version.LATEST.toString(), DISTANCE_TRANSLATIONS); + + private final MethodResolver methodResolver; + + /** + * Constructor + * + * @param methods Map of k-NN methods that the library supports + * @param version String representing version of library + * @param distanceTransform Map of space type to distance transformation function + */ + Lucene(Map methods, String version, Map> distanceTransform) { + super(methods, version); + this.distanceTransform = distanceTransform; + this.methodResolver = new LuceneMethodResolver(); + } + + @Override + public String getExtension() { + throw new UnsupportedOperationException("Getting extension for Lucene is not supported"); + } + + @Override + public String getCompoundExtension() { + throw new UnsupportedOperationException("Getting compound extension for Lucene is not supported"); + } + + @Override + public float score(float rawScore, SpaceType spaceType) { + // The score returned by Lucene follows the higher the score, the better the result convention. It will + // actually invert the distance score so that a higher number is a better score. So, we can just return the + // score provided. + return rawScore; + } + + @Override + public Float distanceToRadialThreshold(Float distance, SpaceType spaceType) { + // Lucene requires score threshold to be parameterized when calling the radius search. + if (this.distanceTransform.containsKey(spaceType)) { + return this.distanceTransform.get(spaceType).apply(distance); + } + return spaceType.scoreTranslation(distance); + } + + @Override + public Float scoreToRadialThreshold(Float score, SpaceType spaceType) { + // Lucene engine uses distance as is and does not need transformation + return score; + } + + @Override + public List mmapFileExtensions() { + return List.of("vec", "vex"); + } + + @Override + public ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + final SpaceType spaceType + ) { + return methodResolver.resolveMethod(knnMethodContext, knnMethodConfigContext, shouldRequireTraining, spaceType); + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneHNSWMethod.java b/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneHNSWMethod.java new file mode 100644 index 000000000..57cc016a6 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneHNSWMethod.java @@ -0,0 +1,82 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.lucene; + +import com.google.common.collect.ImmutableSet; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.AbstractKNNMethod; +import org.opensearch.knn.index.engine.Encoder; +import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.Parameter; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_M; + +/** + * Lucene HNSW implementation + */ +public class LuceneHNSWMethod extends AbstractKNNMethod { + + private static final Set SUPPORTED_DATA_TYPES = ImmutableSet.of(VectorDataType.FLOAT, VectorDataType.BYTE); + + public final static List SUPPORTED_SPACES = Arrays.asList( + SpaceType.UNDEFINED, + SpaceType.L2, + SpaceType.COSINESIMIL, + SpaceType.INNER_PRODUCT + ); + + final static Encoder SQ_ENCODER = new LuceneSQEncoder(); + final static Map SUPPORTED_ENCODERS = Map.of(SQ_ENCODER.getName(), SQ_ENCODER); + + final static MethodComponent HNSW_METHOD_COMPONENT = initMethodComponent(); + + /** + * Constructor for LuceneHNSWMethod + * + * @see AbstractKNNMethod + */ + public LuceneHNSWMethod() { + super(HNSW_METHOD_COMPONENT, Set.copyOf(SUPPORTED_SPACES), new LuceneHNSWSearchContext()); + } + + private static MethodComponent initMethodComponent() { + return MethodComponent.Builder.builder(METHOD_HNSW) + .addSupportedDataTypes(SUPPORTED_DATA_TYPES) + .addParameter( + METHOD_PARAMETER_M, + new Parameter.IntegerParameter(METHOD_PARAMETER_M, KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_M, (v, context) -> v > 0) + ) + .addParameter( + METHOD_PARAMETER_EF_CONSTRUCTION, + new Parameter.IntegerParameter( + METHOD_PARAMETER_EF_CONSTRUCTION, + KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION, + (v, context) -> v > 0 + ) + ) + .addParameter(METHOD_ENCODER_PARAMETER, initEncoderParameter()) + .build(); + } + + private static Parameter.MethodComponentContextParameter initEncoderParameter() { + return new Parameter.MethodComponentContextParameter( + METHOD_ENCODER_PARAMETER, + null, + SUPPORTED_ENCODERS.values().stream().collect(Collectors.toMap(Encoder::getName, Encoder::getMethodComponent)) + ); + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneHNSWSearchContext.java b/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneHNSWSearchContext.java new file mode 100644 index 000000000..bcc1c9af0 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneHNSWSearchContext.java @@ -0,0 +1,35 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.lucene; + +import com.google.common.collect.ImmutableMap; +import org.opensearch.knn.index.engine.KNNLibrarySearchContext; +import org.opensearch.knn.index.engine.Parameter; +import org.opensearch.knn.index.engine.model.QueryContext; +import org.opensearch.knn.index.query.request.MethodParameter; + +import java.util.Collections; +import java.util.Map; + +public class LuceneHNSWSearchContext implements KNNLibrarySearchContext { + + private final Map> supportedMethodParameters = ImmutableMap.>builder() + .put( + MethodParameter.EF_SEARCH.getName(), + new Parameter.IntegerParameter(MethodParameter.EF_SEARCH.getName(), null, (v, context) -> true) + ) + .build(); + + @Override + public Map> supportedMethodParameters(QueryContext ctx) { + if (ctx.getQueryType().isRadialSearch()) { + // return empty map if radial search is true + return Collections.emptyMap(); + } + // Return the supported method parameters for non-radial cases + return supportedMethodParameters; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneMethodResolver.java b/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneMethodResolver.java new file mode 100644 index 000000000..6546d9f93 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneMethodResolver.java @@ -0,0 +1,106 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.lucene; + +import org.opensearch.common.ValidationException; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.engine.AbstractMethodResolver; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.engine.ResolvedMethodContext; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.index.engine.lucene.LuceneHNSWMethod.HNSW_METHOD_COMPONENT; +import static org.opensearch.knn.index.engine.lucene.LuceneHNSWMethod.SQ_ENCODER; + +public class LuceneMethodResolver extends AbstractMethodResolver { + + private static final Set SUPPORTED_COMPRESSION_LEVELS = Set.of(CompressionLevel.x1, CompressionLevel.x4); + + @Override + public ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + final SpaceType spaceType + ) { + validateConfig(knnMethodConfigContext, shouldRequireTraining); + KNNMethodContext resolvedKNNMethodContext = initResolvedKNNMethodContext( + knnMethodContext, + KNNEngine.LUCENE, + spaceType, + METHOD_HNSW + ); + resolveEncoder(resolvedKNNMethodContext, knnMethodConfigContext); + resolveMethodParams(resolvedKNNMethodContext.getMethodComponentContext(), knnMethodConfigContext, HNSW_METHOD_COMPONENT); + CompressionLevel resolvedCompressionLevel = resolveCompressionLevelFromMethodContext( + resolvedKNNMethodContext, + knnMethodConfigContext, + LuceneHNSWMethod.SUPPORTED_ENCODERS + ); + validateCompressionConflicts(knnMethodConfigContext.getCompressionLevel(), resolvedCompressionLevel); + return ResolvedMethodContext.builder() + .knnMethodContext(resolvedKNNMethodContext) + .compressionLevel(resolvedCompressionLevel) + .build(); + } + + protected void resolveEncoder(KNNMethodContext resolvedKNNMethodContext, KNNMethodConfigContext knnMethodConfigContext) { + if (shouldEncoderBeResolved(resolvedKNNMethodContext, knnMethodConfigContext) == false) { + return; + } + + CompressionLevel resolvedCompressionLevel = getDefaultCompressionLevel(knnMethodConfigContext); + if (resolvedCompressionLevel == CompressionLevel.x1) { + return; + } + + MethodComponentContext methodComponentContext = resolvedKNNMethodContext.getMethodComponentContext(); + MethodComponentContext encoderComponentContext = new MethodComponentContext(SQ_ENCODER.getName(), new HashMap<>()); + Map resolvedParams = MethodComponent.getParameterMapWithDefaultsAdded( + encoderComponentContext, + SQ_ENCODER.getMethodComponent(), + knnMethodConfigContext + ); + encoderComponentContext.getParameters().putAll(resolvedParams); + methodComponentContext.getParameters().put(METHOD_ENCODER_PARAMETER, encoderComponentContext); + } + + // Method validates for explicit contradictions in the config + private void validateConfig(KNNMethodConfigContext knnMethodConfigContext, boolean shouldRequireTraining) { + ValidationException validationException = validateNotTrainingContext(shouldRequireTraining, KNNEngine.LUCENE, null); + validationException = validateCompressionSupported( + knnMethodConfigContext.getCompressionLevel(), + SUPPORTED_COMPRESSION_LEVELS, + KNNEngine.LUCENE, + validationException + ); + validationException = validateCompressionNotx1WhenOnDisk(knnMethodConfigContext, validationException); + if (validationException != null) { + throw validationException; + } + } + + private CompressionLevel getDefaultCompressionLevel(KNNMethodConfigContext knnMethodConfigContext) { + if (CompressionLevel.isConfigured(knnMethodConfigContext.getCompressionLevel())) { + return knnMethodConfigContext.getCompressionLevel(); + } + if (knnMethodConfigContext.getMode() == Mode.ON_DISK) { + return CompressionLevel.x4; + } + return CompressionLevel.x1; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneSQEncoder.java b/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneSQEncoder.java new file mode 100644 index 000000000..6bd16ebee --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/lucene/LuceneSQEncoder.java @@ -0,0 +1,64 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.lucene; + +import com.google.common.collect.ImmutableSet; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.Encoder; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.engine.Parameter; +import org.opensearch.knn.index.mapper.CompressionLevel; + +import java.util.List; +import java.util.Set; + +import static org.opensearch.knn.common.KNNConstants.DYNAMIC_CONFIDENCE_INTERVAL; +import static org.opensearch.knn.common.KNNConstants.ENCODER_SQ; +import static org.opensearch.knn.common.KNNConstants.LUCENE_SQ_BITS; +import static org.opensearch.knn.common.KNNConstants.LUCENE_SQ_CONFIDENCE_INTERVAL; +import static org.opensearch.knn.common.KNNConstants.LUCENE_SQ_DEFAULT_BITS; +import static org.opensearch.knn.common.KNNConstants.MAXIMUM_CONFIDENCE_INTERVAL; +import static org.opensearch.knn.common.KNNConstants.MINIMUM_CONFIDENCE_INTERVAL; + +/** + * Lucene scalar quantization encoder + */ +public class LuceneSQEncoder implements Encoder { + private static final Set SUPPORTED_DATA_TYPES = ImmutableSet.of(VectorDataType.FLOAT); + + private final static List LUCENE_SQ_BITS_SUPPORTED = List.of(7); + private final static MethodComponent METHOD_COMPONENT = MethodComponent.Builder.builder(ENCODER_SQ) + .addSupportedDataTypes(SUPPORTED_DATA_TYPES) + .addParameter( + LUCENE_SQ_CONFIDENCE_INTERVAL, + new Parameter.DoubleParameter( + LUCENE_SQ_CONFIDENCE_INTERVAL, + null, + (v, context) -> v == DYNAMIC_CONFIDENCE_INTERVAL || (v >= MINIMUM_CONFIDENCE_INTERVAL && v <= MAXIMUM_CONFIDENCE_INTERVAL) + ) + ) + .addParameter( + LUCENE_SQ_BITS, + new Parameter.IntegerParameter(LUCENE_SQ_BITS, LUCENE_SQ_DEFAULT_BITS, (v, context) -> LUCENE_SQ_BITS_SUPPORTED.contains(v)) + ) + .build(); + + @Override + public MethodComponent getMethodComponent() { + return METHOD_COMPONENT; + } + + @Override + public CompressionLevel calculateCompressionLevel( + MethodComponentContext methodComponentContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + // Hard coding to 4x for now, given thats all that is supported. + return CompressionLevel.x4; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/model/QueryContext.java b/src/main/java/org/opensearch/knn/index/engine/model/QueryContext.java new file mode 100644 index 000000000..21182e4aa --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/model/QueryContext.java @@ -0,0 +1,19 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.opensearch.knn.index.VectorQueryType; + +/** + * Context class for query-specific information. + */ +@AllArgsConstructor +@Getter +public class QueryContext { + VectorQueryType queryType; +} diff --git a/src/main/java/org/opensearch/knn/index/engine/nmslib/Nmslib.java b/src/main/java/org/opensearch/knn/index/engine/nmslib/Nmslib.java new file mode 100644 index 000000000..4d7f7f423 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/nmslib/Nmslib.java @@ -0,0 +1,72 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.nmslib; + +import com.google.common.collect.ImmutableMap; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.engine.KNNMethod; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodResolver; +import org.opensearch.knn.index.engine.NativeLibrary; +import org.opensearch.knn.index.engine.ResolvedMethodContext; + +import java.util.Collections; +import java.util.Map; +import java.util.function.Function; + +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; + +/** + * Implements NativeLibrary for the nmslib native library + */ +public class Nmslib extends NativeLibrary { + // Extension to be used for Nmslib files. It is ".hnsw" and not ".nmslib" for legacy purposes. + public final static String EXTENSION = ".hnsw"; + final static String CURRENT_VERSION = "2011"; + + final static Map METHODS = ImmutableMap.of(METHOD_HNSW, new NmslibHNSWMethod()); + + public final static Nmslib INSTANCE = new Nmslib(METHODS, Collections.emptyMap(), CURRENT_VERSION, EXTENSION); + private final MethodResolver methodResolver; + + /** + * Constructor for Nmslib + * + * @param methods Set of methods the native library supports + * @param scoreTranslation Map of translation of space type to scores returned by the library + * @param currentVersion String representation of current version of the library + * @param extension String representing the extension that library files should use + */ + private Nmslib( + Map methods, + Map> scoreTranslation, + String currentVersion, + String extension + ) { + super(methods, scoreTranslation, currentVersion, extension); + this.methodResolver = new NmslibMethodResolver(); + } + + @Override + public Float distanceToRadialThreshold(Float distance, SpaceType spaceType) { + return distance; + } + + public Float scoreToRadialThreshold(Float score, SpaceType spaceType) { + return score; + } + + @Override + public ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + final SpaceType spaceType + ) { + return methodResolver.resolveMethod(knnMethodContext, knnMethodConfigContext, shouldRequireTraining, spaceType); + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/nmslib/NmslibHNSWMethod.java b/src/main/java/org/opensearch/knn/index/engine/nmslib/NmslibHNSWMethod.java new file mode 100644 index 000000000..d2440926e --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/nmslib/NmslibHNSWMethod.java @@ -0,0 +1,68 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.nmslib; + +import com.google.common.collect.ImmutableSet; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.AbstractKNNMethod; +import org.opensearch.knn.index.engine.DefaultHnswSearchContext; +import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.Parameter; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_M; + +/** + * Nmslib's HNSW implementation + */ +public class NmslibHNSWMethod extends AbstractKNNMethod { + + private static final Set SUPPORTED_DATA_TYPES = ImmutableSet.of(VectorDataType.FLOAT); + + public final static List SUPPORTED_SPACES = Arrays.asList( + SpaceType.UNDEFINED, + SpaceType.L2, + SpaceType.L1, + SpaceType.LINF, + SpaceType.COSINESIMIL, + SpaceType.INNER_PRODUCT + ); + + final static MethodComponent HNSW_METHOD_COMPONENT = initMethodComponent(); + + /** + * Constructor. Builds the method with the default parameters and supported spaces. + * @see AbstractKNNMethod + */ + public NmslibHNSWMethod() { + super(HNSW_METHOD_COMPONENT, Set.copyOf(SUPPORTED_SPACES), new DefaultHnswSearchContext()); + } + + private static MethodComponent initMethodComponent() { + return MethodComponent.Builder.builder(METHOD_HNSW) + .addSupportedDataTypes(SUPPORTED_DATA_TYPES) + .addParameter( + METHOD_PARAMETER_M, + new Parameter.IntegerParameter(METHOD_PARAMETER_M, KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_M, (v, context) -> v > 0) + ) + .addParameter( + METHOD_PARAMETER_EF_CONSTRUCTION, + new Parameter.IntegerParameter( + METHOD_PARAMETER_EF_CONSTRUCTION, + KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION, + (v, context) -> v > 0 + ) + ) + .build(); + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/nmslib/NmslibMethodResolver.java b/src/main/java/org/opensearch/knn/index/engine/nmslib/NmslibMethodResolver.java new file mode 100644 index 000000000..619a00eda --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/nmslib/NmslibMethodResolver.java @@ -0,0 +1,69 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.nmslib; + +import org.opensearch.common.ValidationException; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.engine.AbstractMethodResolver; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.ResolvedMethodContext; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; + +import java.util.Set; + +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.index.engine.nmslib.NmslibHNSWMethod.HNSW_METHOD_COMPONENT; + +/** + * Method resolution logic for nmslib. Because nmslib does not support quantization, it is in general a validation + * before returning the original request + */ +public class NmslibMethodResolver extends AbstractMethodResolver { + + private static final Set SUPPORTED_COMPRESSION_LEVELS = Set.of(CompressionLevel.x1); + + @Override + public ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + final SpaceType spaceType + ) { + validateConfig(knnMethodConfigContext, shouldRequireTraining); + KNNMethodContext resolvedKNNMethodContext = initResolvedKNNMethodContext( + knnMethodContext, + KNNEngine.NMSLIB, + spaceType, + METHOD_HNSW + ); + resolveMethodParams(resolvedKNNMethodContext.getMethodComponentContext(), knnMethodConfigContext, HNSW_METHOD_COMPONENT); + return ResolvedMethodContext.builder().knnMethodContext(resolvedKNNMethodContext).compressionLevel(CompressionLevel.x1).build(); + } + + // Method validates for explicit contradictions in the config + private void validateConfig(KNNMethodConfigContext knnMethodConfigContext, boolean shouldRequireTraining) { + ValidationException validationException = validateNotTrainingContext(shouldRequireTraining, KNNEngine.NMSLIB, null); + CompressionLevel compressionLevel = knnMethodConfigContext.getCompressionLevel(); + validationException = validateCompressionSupported( + compressionLevel, + SUPPORTED_COMPRESSION_LEVELS, + KNNEngine.NMSLIB, + validationException + ); + + if (Mode.ON_DISK == knnMethodConfigContext.getMode()) { + validationException = validationException == null ? new ValidationException() : validationException; + validationException.addValidationError("Nmslib engine does not support disk-based search"); + } + + if (validationException != null) { + throw validationException; + } + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/qframe/QuantizationConfig.java b/src/main/java/org/opensearch/knn/index/engine/qframe/QuantizationConfig.java new file mode 100644 index 000000000..666247692 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/qframe/QuantizationConfig.java @@ -0,0 +1,23 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.qframe; + +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; + +/** + * Configuration for quantization + */ +@Builder +@Getter +@EqualsAndHashCode +public class QuantizationConfig { + @Builder.Default + private ScalarQuantizationType quantizationType = null; + public static final QuantizationConfig EMPTY = QuantizationConfig.builder().build(); +} diff --git a/src/main/java/org/opensearch/knn/index/engine/qframe/QuantizationConfigParser.java b/src/main/java/org/opensearch/knn/index/engine/qframe/QuantizationConfigParser.java new file mode 100644 index 000000000..f86d7f886 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/qframe/QuantizationConfigParser.java @@ -0,0 +1,79 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.qframe; + +import org.apache.lucene.analysis.util.CSVUtil; +import org.opensearch.knn.index.engine.faiss.QFrameBitEncoder; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; + +import java.util.Locale; + +/** + * Parse util for quantization config + */ +public class QuantizationConfigParser { + + public static final String SEPARATOR = "="; + public static final String TYPE_NAME = "type"; + public static final String BINARY_TYPE = QFrameBitEncoder.NAME; + public static final String BIT_COUNT_NAME = QFrameBitEncoder.BITCOUNT_PARAM; + + /** + * Parse quantization config to csv format + * Example: type=binary,bits=2 + * @param quantizationConfig Quantization config + * @return Csv format of quantization config + */ + public static String toCsv(QuantizationConfig quantizationConfig) { + if (quantizationConfig == null + || quantizationConfig == QuantizationConfig.EMPTY + || quantizationConfig.getQuantizationType() == null) { + return ""; + } + + return TYPE_NAME + SEPARATOR + BINARY_TYPE + "," + BIT_COUNT_NAME + SEPARATOR + quantizationConfig.getQuantizationType().getId(); + } + + /** + * Parse csv format to quantization config + * + * @param csv Csv format of quantization config + * @return Quantization config + */ + public static QuantizationConfig fromCsv(String csv) { + if (csv == null || csv.isEmpty()) { + return QuantizationConfig.EMPTY; + } + + String[] csvArray = CSVUtil.parse(csv); + if (csvArray.length != 2) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "Invalid csv for quantization config: \"%s\"", csv)); + } + + String typeValue = getValueOrThrow(TYPE_NAME, csvArray[0]); + if (!typeValue.equals(BINARY_TYPE)) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "Unsupported quantization type: \"%s\"", typeValue)); + } + + String bitsValue = getValueOrThrow(BIT_COUNT_NAME, csvArray[1]); + int bitCount = Integer.parseInt(bitsValue); + ScalarQuantizationType quantizationType = ScalarQuantizationType.fromId(bitCount); + return QuantizationConfig.builder().quantizationType(quantizationType).build(); + } + + private static String getValueOrThrow(String expectedKey, String keyValue) { + String[] keyValueArr = keyValue.split(SEPARATOR); + if (keyValueArr.length != 2) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "Invalid csv value for quantization config: \"%s\"", keyValue)); + } + + if (!keyValueArr[0].equals(expectedKey)) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "Expected: \"%s\" But got: \"%s\"", expectedKey, keyValue)); + } + + return keyValueArr[1]; + } +} diff --git a/src/main/java/org/opensearch/knn/index/engine/validation/ParameterValidator.java b/src/main/java/org/opensearch/knn/index/engine/validation/ParameterValidator.java new file mode 100644 index 000000000..c79778503 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/engine/validation/ParameterValidator.java @@ -0,0 +1,63 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.validation; + +import org.opensearch.common.Nullable; +import org.opensearch.common.ValidationException; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.Parameter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public final class ParameterValidator { + + /** + * A function which validates request parameters. + * + * @param validParameters A set of valid parameters that can be requestParameters can be validated against + * @param requestParameters parameters from the request + * @param knnMethodConfigContext context of the knn method + * @return ValidationException if there are any validation errors, null otherwise + */ + @Nullable + public static ValidationException validateParameters( + final Map> validParameters, + final Map requestParameters, + KNNMethodConfigContext knnMethodConfigContext + ) { + + if (validParameters == null) { + throw new IllegalArgumentException("validParameters cannot be null"); + } + + if (requestParameters == null || requestParameters.isEmpty()) { + return null; + } + + final List errorMessages = new ArrayList<>(); + for (Map.Entry parameter : requestParameters.entrySet()) { + if (validParameters.containsKey(parameter.getKey())) { + final ValidationException parameterValidation = validParameters.get(parameter.getKey()) + .validate(parameter.getValue(), knnMethodConfigContext); + if (parameterValidation != null) { + errorMessages.addAll(parameterValidation.validationErrors()); + } + } else { + errorMessages.add("Unknown parameter '" + parameter.getKey() + "' found"); + } + } + + if (errorMessages.isEmpty()) { + return null; + } + + final ValidationException validationException = new ValidationException(); + validationException.addValidationErrors(errorMessages); + return validationException; + } +} diff --git a/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java b/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java new file mode 100644 index 000000000..99f74c246 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/mapper/CompressionLevel.java @@ -0,0 +1,124 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.mapper; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.opensearch.core.common.Strings; +import org.opensearch.knn.index.query.rescore.RescoreContext; + +import java.util.Collections; +import java.util.Locale; +import java.util.Set; + +/** + * Enum representing the compression level for float vectors. Compression in this sense refers to compressing a + * full precision value into a smaller number of bits. For instance. "16x" compression would mean that 2 bits would + * need to be used to represent a 32-bit floating point number. + */ +@AllArgsConstructor +public enum CompressionLevel { + NOT_CONFIGURED(-1, "", null, Collections.emptySet()), + x1(1, "1x", null, Collections.emptySet()), + x2(2, "2x", null, Collections.emptySet()), + x4(4, "4x", null, Collections.emptySet()), + x8(8, "8x", new RescoreContext(2.0f, false), Set.of(Mode.ON_DISK)), + x16(16, "16x", new RescoreContext(3.0f, false), Set.of(Mode.ON_DISK)), + x32(32, "32x", new RescoreContext(3.0f, false), Set.of(Mode.ON_DISK)), + x64(64, "64x", new RescoreContext(5.0f, false), Set.of(Mode.ON_DISK)); + + public static final CompressionLevel MAX_COMPRESSION_LEVEL = CompressionLevel.x64; + + /** + * Default is set to 1x and is a noop + */ + private static final CompressionLevel DEFAULT = x1; + + /** + * Get the compression level from a string representation. The format for the string should be "Nx", where N is + * the factor by which compression should take place + * + * @param name String representation of the compression level + * @return CompressionLevel enum value + */ + public static CompressionLevel fromName(String name) { + if (Strings.isEmpty(name)) { + return NOT_CONFIGURED; + } + for (CompressionLevel config : CompressionLevel.values()) { + if (config.getName() != null && config.getName().equals(name)) { + return config; + } + } + throw new IllegalArgumentException(String.format(Locale.ROOT, "Invalid compression level: \"[%s]\"", name)); + } + + private final int compressionLevel; + @Getter + private final String name; + private final RescoreContext defaultRescoreContext; + private final Set modesForRescore; + + /** + * Gets the number of bits used to represent a float in order to achieve this compression. For instance, for + * 32x compression, each float would need to be encoded in a single bit. + * + * @return number of bits to represent a float at this compression level + */ + public int numBitsForFloat32() { + if (this == NOT_CONFIGURED) { + return DEFAULT.numBitsForFloat32(); + } + + return (Float.BYTES * Byte.SIZE) / compressionLevel; + } + + /** + * Utility method that checks if compression is configured. + * + * @param compressionLevel Compression to check + * @return true if compression is configured, false otherwise + */ + public static boolean isConfigured(CompressionLevel compressionLevel) { + return compressionLevel != null && compressionLevel != NOT_CONFIGURED; + } + + /** + * Returns the appropriate {@link RescoreContext} based on the given {@code mode} and {@code dimension}. + * + *

If the {@code mode} is present in the valid {@code modesForRescore} set, the method checks the value of + * {@code dimension}: + *

    + *
  • If {@code dimension} is less than or equal to 1000, it returns a {@link RescoreContext} with an + * oversample factor of 5.0f.
  • + *
  • If {@code dimension} is greater than 1000, it returns the default {@link RescoreContext} associated with + * the {@link CompressionLevel}. If no default is set, it falls back to {@link RescoreContext#getDefault()}.
  • + *
+ * If the {@code mode} is not valid, the method returns {@code null}. + * + * @param mode The {@link Mode} for which to retrieve the {@link RescoreContext}. + * @param dimension The dimensional value that determines the {@link RescoreContext} behavior. + * @return A {@link RescoreContext} with an oversample factor of 5.0f if {@code dimension} is less than + * or equal to 1000, the default {@link RescoreContext} if greater, or {@code null} if the mode + * is invalid. + */ + public RescoreContext getDefaultRescoreContext(Mode mode, int dimension) { + if (modesForRescore.contains(mode)) { + // Adjust RescoreContext based on dimension + if (dimension <= RescoreContext.DIMENSION_THRESHOLD) { + // For dimensions <= 1000, return a RescoreContext with 5.0f oversample factor + return RescoreContext.builder() + .oversampleFactor(RescoreContext.OVERSAMPLE_FACTOR_BELOW_DIMENSION_THRESHOLD) + .userProvided(false) + .build(); + } else { + return defaultRescoreContext; + } + } + return null; + } + +} diff --git a/src/main/java/org/opensearch/knn/index/mapper/FlatVectorFieldMapper.java b/src/main/java/org/opensearch/knn/index/mapper/FlatVectorFieldMapper.java new file mode 100644 index 000000000..8da41aa59 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/mapper/FlatVectorFieldMapper.java @@ -0,0 +1,112 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.mapper; + +import org.apache.lucene.document.FieldType; +import org.apache.lucene.index.DocValuesType; +import org.opensearch.Version; +import org.opensearch.common.Explicit; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; + +import java.util.Map; + +/** + * Mapper used when you dont want to build an underlying KNN struct - you just want to + * store vectors as doc values + */ +public class FlatVectorFieldMapper extends KNNVectorFieldMapper { + + private final PerDimensionValidator perDimensionValidator; + + public static FlatVectorFieldMapper createFieldMapper( + String fullname, + String simpleName, + Map metaValue, + KNNMethodConfigContext knnMethodConfigContext, + MultiFields multiFields, + CopyTo copyTo, + Explicit ignoreMalformed, + boolean stored, + boolean hasDocValues, + OriginalMappingParameters originalMappingParameters + ) { + final KNNVectorFieldType mappedFieldType = new KNNVectorFieldType( + fullname, + metaValue, + knnMethodConfigContext.getVectorDataType(), + knnMethodConfigContext::getDimension + ); + return new FlatVectorFieldMapper( + simpleName, + mappedFieldType, + multiFields, + copyTo, + ignoreMalformed, + stored, + hasDocValues, + knnMethodConfigContext.getVersionCreated(), + originalMappingParameters + ); + } + + private FlatVectorFieldMapper( + String simpleName, + KNNVectorFieldType mappedFieldType, + MultiFields multiFields, + CopyTo copyTo, + Explicit ignoreMalformed, + boolean stored, + boolean hasDocValues, + Version indexCreatedVersion, + OriginalMappingParameters originalMappingParameters + ) { + super( + simpleName, + mappedFieldType, + multiFields, + copyTo, + ignoreMalformed, + stored, + hasDocValues, + indexCreatedVersion, + originalMappingParameters + ); + // setting it explicitly false here to ensure that when flatmapper is used Lucene based Vector field is not created. + this.useLuceneBasedVectorField = false; + this.perDimensionValidator = selectPerDimensionValidator(vectorDataType); + this.fieldType = new FieldType(KNNVectorFieldMapper.Defaults.FIELD_TYPE); + this.fieldType.setDocValuesType(DocValuesType.BINARY); + this.fieldType.freeze(); + } + + private PerDimensionValidator selectPerDimensionValidator(VectorDataType vectorDataType) { + if (VectorDataType.BINARY == vectorDataType) { + return PerDimensionValidator.DEFAULT_BIT_VALIDATOR; + } + + if (VectorDataType.BYTE == vectorDataType) { + return PerDimensionValidator.DEFAULT_BYTE_VALIDATOR; + } + + return PerDimensionValidator.DEFAULT_FLOAT_VALIDATOR; + } + + @Override + protected VectorValidator getVectorValidator() { + return VectorValidator.NOOP_VECTOR_VALIDATOR; + } + + @Override + protected PerDimensionValidator getPerDimensionValidator() { + return perDimensionValidator; + } + + @Override + protected PerDimensionProcessor getPerDimensionProcessor() { + return PerDimensionProcessor.NOOP_PROCESSOR; + } +} diff --git a/src/main/java/org/opensearch/knn/index/mapper/KNNMappingConfig.java b/src/main/java/org/opensearch/knn/index/mapper/KNNMappingConfig.java new file mode 100644 index 000000000..cd77ebd9a --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/mapper/KNNMappingConfig.java @@ -0,0 +1,65 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.mapper; + +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; + +import java.util.Optional; + +/** + * Class holds information about how the ANN indices are created. The design of this class ensures that we do not + * accidentally configure an index that has multiple ways it can be created. This class is immutable. + */ +public interface KNNMappingConfig { + /** + * + * @return Optional containing the modelId if created from model, otherwise empty + */ + default Optional getModelId() { + return Optional.empty(); + } + + /** + * + * @return Optional containing the KNNMethodContext if created from method, otherwise empty + */ + default Optional getKnnMethodContext() { + return Optional.empty(); + } + + /** + * Return the mode to be used for this field + * + * @return {@link Mode} + */ + default Mode getMode() { + return Mode.NOT_CONFIGURED; + } + + /** + * Return compression level to be used for this field + * + * @return {@link CompressionLevel} + */ + default CompressionLevel getCompressionLevel() { + return CompressionLevel.NOT_CONFIGURED; + } + + /** + * Returns quantization config + * @return + */ + default QuantizationConfig getQuantizationConfig() { + return QuantizationConfig.EMPTY; + } + + /** + * + * @return the dimension of the index; for model based indices, it will be null + */ + int getDimension(); +} diff --git a/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapper.java b/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapper.java new file mode 100644 index 000000000..beceadde5 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapper.java @@ -0,0 +1,847 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.mapper; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import com.google.common.annotations.VisibleForTesting; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.log4j.Log4j2; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.FieldType; +import org.apache.lucene.document.KnnByteVectorField; +import org.apache.lucene.document.KnnFloatVectorField; +import org.apache.lucene.index.IndexOptions; +import org.opensearch.Version; +import org.opensearch.common.Explicit; +import org.opensearch.common.ValidationException; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.xcontent.support.XContentMapValues; +import org.opensearch.core.common.Strings; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.index.mapper.FieldMapper; +import org.opensearch.index.mapper.Mapper; +import org.opensearch.index.mapper.MapperParsingException; +import org.opensearch.index.mapper.ParametrizedFieldMapper; +import org.opensearch.index.mapper.ParseContext; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.engine.EngineResolver; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.VectorField; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.ResolvedMethodContext; +import org.opensearch.knn.index.engine.SpaceTypeResolver; +import org.opensearch.knn.indices.ModelDao; + +import static org.opensearch.knn.common.KNNConstants.DEFAULT_VECTOR_DATA_TYPE_FIELD; +import static org.opensearch.knn.common.KNNConstants.KNN_METHOD; +import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; +import static org.opensearch.knn.common.KNNValidationUtil.validateVectorDimension; +import static org.opensearch.knn.index.mapper.KNNVectorFieldMapperUtil.createKNNMethodContextFromLegacy; +import static org.opensearch.knn.index.mapper.KNNVectorFieldMapperUtil.createStoredFieldForByteVector; +import static org.opensearch.knn.index.mapper.KNNVectorFieldMapperUtil.createStoredFieldForFloatVector; +import static org.opensearch.knn.index.mapper.KNNVectorFieldMapperUtil.validateIfCircuitBreakerIsNotTriggered; +import static org.opensearch.knn.index.mapper.KNNVectorFieldMapperUtil.validateIfKNNPluginEnabled; +import static org.opensearch.knn.index.mapper.ModelFieldMapper.UNSET_MODEL_DIMENSION_IDENTIFIER; + +/** + * Field Mapper for KNN vector type. Implementations of this class define what needs to be stored in Lucene's fieldType. + * This allows us to have alternative mappings for the same field type. + */ +@Log4j2 +public abstract class KNNVectorFieldMapper extends ParametrizedFieldMapper { + + public static final String CONTENT_TYPE = "knn_vector"; + public static final String KNN_FIELD = "knn_field"; + + private static KNNVectorFieldMapper toType(FieldMapper in) { + return (KNNVectorFieldMapper) in; + } + + // Supported compression levels for knn_vector field type + @VisibleForTesting + public static final String[] MAPPING_COMPRESSION_NAMES_ARRAY = new String[] { + CompressionLevel.NOT_CONFIGURED.getName(), + CompressionLevel.x1.getName(), + CompressionLevel.x2.getName(), + CompressionLevel.x4.getName(), + CompressionLevel.x8.getName(), + CompressionLevel.x16.getName(), + CompressionLevel.x32.getName() }; + + /** + * Builder for KNNVectorFieldMapper. This class defines the set of parameters that can be applied to the knn_vector + * field type + */ + public static class Builder extends ParametrizedFieldMapper.Builder { + protected Boolean ignoreMalformed; + + protected final Parameter stored = Parameter.storeParam(m -> toType(m).stored, false); + protected final Parameter hasDocValues = Parameter.docValuesParam(m -> toType(m).hasDocValues, true); + protected final Parameter dimension = new Parameter<>( + KNNConstants.DIMENSION, + false, + () -> UNSET_MODEL_DIMENSION_IDENTIFIER, + (n, c, o) -> { + if (o == null) { + throw new IllegalArgumentException("Dimension cannot be null"); + } + int value; + try { + value = XContentMapValues.nodeIntegerValue(o); + } catch (Exception exception) { + throw new IllegalArgumentException( + String.format(Locale.ROOT, "Unable to parse [dimension] from provided value [%s] for vector [%s]", o, name) + ); + } + if (value <= 0) { + throw new IllegalArgumentException( + String.format(Locale.ROOT, "Dimension value must be greater than 0 for vector: %s", name) + ); + } + return value; + }, + m -> toType(m).originalMappingParameters.getDimension() + ); + + /** + * data_type which defines the datatype of the vector values. This is an optional parameter and + * this is right now only relevant for lucene engine. The default value is float. + */ + protected final Parameter vectorDataType = new Parameter<>( + VECTOR_DATA_TYPE_FIELD, + false, + () -> DEFAULT_VECTOR_DATA_TYPE_FIELD, + (n, c, o) -> VectorDataType.get((String) o), + m -> toType(m).originalMappingParameters.getVectorDataType() + ); + + /** + * modelId provides a way for a user to generate the underlying library indices from an already serialized + * model template index. If this parameter is set, it will take precedence. This parameter is only relevant for + * library indices that require training. + */ + protected final Parameter modelId = Parameter.stringParam( + KNNConstants.MODEL_ID, + false, + m -> toType(m).originalMappingParameters.getModelId(), + null + ); + + /** + * knnMethodContext parameter allows a user to define their k-NN library index configuration. Defaults to an L2 + * hnsw default engine index without any parameters set + */ + protected final Parameter knnMethodContext = new Parameter<>( + KNN_METHOD, + false, + () -> null, + (n, c, o) -> KNNMethodContext.parse(o), + m -> toType(m).originalMappingParameters.getKnnMethodContext() + ).setSerializer(((b, n, v) -> { + b.startObject(n); + v.toXContent(b, ToXContent.EMPTY_PARAMS); + b.endObject(); + }), m -> m.getMethodComponentContext().getName()); + + protected final Parameter mode = Parameter.restrictedStringParam( + KNNConstants.MODE_PARAMETER, + false, + m -> toType(m).originalMappingParameters.getMode(), + Mode.NAMES_ARRAY + ).acceptsNull(); + + protected final Parameter compressionLevel = Parameter.restrictedStringParam( + KNNConstants.COMPRESSION_LEVEL_PARAMETER, + false, + m -> toType(m).originalMappingParameters.getCompressionLevel(), + MAPPING_COMPRESSION_NAMES_ARRAY + ).acceptsNull(); + + // A top level space Type field. + protected final Parameter topLevelSpaceType = Parameter.stringParam( + KNNConstants.TOP_LEVEL_PARAMETER_SPACE_TYPE, + false, + m -> toType(m).originalMappingParameters.getTopLevelSpaceType(), + SpaceType.UNDEFINED.getValue() + ).setValidator(SpaceType::getSpace); + + protected final Parameter> meta = Parameter.metaParam(); + + protected ModelDao modelDao; + protected Version indexCreatedVersion; + @Setter + @Getter + private KNNMethodConfigContext knnMethodConfigContext; + @Setter + @Getter + private OriginalMappingParameters originalParameters; + + public Builder( + String name, + ModelDao modelDao, + Version indexCreatedVersion, + KNNMethodConfigContext knnMethodConfigContext, + OriginalMappingParameters originalParameters + ) { + super(name); + this.modelDao = modelDao; + this.indexCreatedVersion = indexCreatedVersion; + this.knnMethodConfigContext = knnMethodConfigContext; + this.originalParameters = originalParameters; + } + + @Override + protected List> getParameters() { + return Arrays.asList( + stored, + hasDocValues, + dimension, + vectorDataType, + meta, + knnMethodContext, + modelId, + mode, + compressionLevel, + topLevelSpaceType + ); + } + + protected Explicit ignoreMalformed(BuilderContext context) { + if (ignoreMalformed != null) { + return new Explicit<>(ignoreMalformed, true); + } + if (context.indexSettings() != null) { + return new Explicit<>(IGNORE_MALFORMED_SETTING.get(context.indexSettings()), false); + } + return KNNVectorFieldMapper.Defaults.IGNORE_MALFORMED; + } + + @Override + public KNNVectorFieldMapper build(BuilderContext context) { + validateFullFieldName(context); + + final MultiFields multiFieldsBuilder = this.multiFieldsBuilder.build(this, context); + final CopyTo copyToBuilder = copyTo.build(); + final Explicit ignoreMalformed = ignoreMalformed(context); + final Map metaValue = meta.getValue(); + + if (modelId.get() != null) { + return ModelFieldMapper.createFieldMapper( + buildFullName(context), + name, + metaValue, + vectorDataType.getValue(), + multiFieldsBuilder, + copyToBuilder, + ignoreMalformed, + stored.get(), + hasDocValues.get(), + modelDao, + indexCreatedVersion, + originalParameters, + knnMethodConfigContext + ); + } + + if (originalParameters.getResolvedKnnMethodContext() == null) { + return FlatVectorFieldMapper.createFieldMapper( + buildFullName(context), + name, + metaValue, + KNNMethodConfigContext.builder() + .vectorDataType(vectorDataType.getValue()) + .versionCreated(indexCreatedVersion) + .dimension(dimension.getValue()) + .build(), + multiFieldsBuilder, + copyToBuilder, + ignoreMalformed, + stored.get(), + hasDocValues.get(), + originalParameters + ); + } + + if (originalParameters.getResolvedKnnMethodContext().getKnnEngine() == KNNEngine.LUCENE) { + log.debug(String.format(Locale.ROOT, "Use [LuceneFieldMapper] mapper for field [%s]", name)); + LuceneFieldMapper.CreateLuceneFieldMapperInput createLuceneFieldMapperInput = LuceneFieldMapper.CreateLuceneFieldMapperInput + .builder() + .name(name) + .multiFields(multiFieldsBuilder) + .copyTo(copyToBuilder) + .ignoreMalformed(ignoreMalformed) + .stored(stored.getValue()) + .hasDocValues(hasDocValues.getValue()) + .originalKnnMethodContext(knnMethodContext.get()) + .build(); + return LuceneFieldMapper.createFieldMapper( + buildFullName(context), + metaValue, + knnMethodConfigContext, + createLuceneFieldMapperInput, + originalParameters + ); + } + + return MethodFieldMapper.createFieldMapper( + buildFullName(context), + name, + metaValue, + knnMethodConfigContext, + multiFieldsBuilder, + copyToBuilder, + ignoreMalformed, + stored.getValue(), + hasDocValues.getValue(), + originalParameters + ); + } + + /** + * Validate whether provided full field name contain any invalid characters for physical file name. + * At the moment, we use a field name as a part of file name while we throw an exception + * if a physical file name contains any invalid characters when creating snapshot. + * To prevent from this happening, we restrict vector field name and make sure generated file to have a valid name. + * + * @param context : Builder context to have field name info. + */ + private void validateFullFieldName(final BuilderContext context) { + final String fullFieldName = buildFullName(context); + for (char ch : fullFieldName.toCharArray()) { + if (Strings.INVALID_FILENAME_CHARS.contains(ch)) { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "Vector field name must not include invalid characters of %s. " + + "Provided field name=[%s] had a disallowed character [%c]", + Strings.INVALID_FILENAME_CHARS.stream().map(c -> "'" + c + "'").collect(Collectors.toList()), + fullFieldName, + ch + ) + ); + } + } + } + } + + public static class TypeParser implements Mapper.TypeParser { + + // Use a supplier here because in {@link org.opensearch.knn.KNNPlugin#getMappers()} the ModelDao has not yet + // been initialized + private Supplier modelDaoSupplier; + + public TypeParser(Supplier modelDaoSupplier) { + this.modelDaoSupplier = modelDaoSupplier; + } + + @Override + public Mapper.Builder parse(String name, Map node, ParserContext parserContext) throws MapperParsingException { + Builder builder = new KNNVectorFieldMapper.Builder( + name, + modelDaoSupplier.get(), + parserContext.indexVersionCreated(), + null, + null + ); + builder.parse(name, parserContext, node); + builder.setOriginalParameters(new OriginalMappingParameters(builder)); + + // All parsing + // is done before any mappers are built. Therefore, validation should be done during parsing + // so that it can fail early. + if (builder.knnMethodContext.get() != null && builder.modelId.get() != null) { + throw new IllegalArgumentException( + String.format(Locale.ROOT, "Method and model can not be both specified in the mapping: %s", name) + ); + } + + // Check for flat configuration + if (isKNNDisabled(parserContext.getSettings())) { + validateFromFlat(builder); + } else if (builder.modelId.get() != null) { + validateFromModel(builder); + } else { + // Validate that the mode and compression are not set if data type is not float, as they are not + // supported. + validateModeAndCompressionForDataType(builder); + // If the original knnMethodContext is not null, resolve its space type and engine from the rest of the + // configuration. This is consistent with the existing behavior for space type in 2.16 where we modify the + // parsed value + SpaceType resolvedSpaceType = SpaceTypeResolver.INSTANCE.resolveSpaceType( + builder.originalParameters.getKnnMethodContext(), + builder.vectorDataType.get(), + builder.topLevelSpaceType.get() + ); + setSpaceType(builder.originalParameters.getKnnMethodContext(), resolvedSpaceType); + validateSpaceType(builder); + resolveKNNMethodComponents(builder, parserContext, resolvedSpaceType); + validateFromKNNMethod(builder); + } + + return builder; + } + + private void validateSpaceType(KNNVectorFieldMapper.Builder builder) { + final KNNMethodContext knnMethodContext = builder.knnMethodContext.get(); + // if context is defined + if (knnMethodContext != null) { + // now ensure both space types are same. + final SpaceType knnMethodContextSpaceType = knnMethodContext.getSpaceType(); + final SpaceType topLevelSpaceType = SpaceType.getSpace(builder.topLevelSpaceType.get()); + if (topLevelSpaceType != SpaceType.UNDEFINED + && topLevelSpaceType != knnMethodContextSpaceType + && knnMethodContextSpaceType != SpaceType.UNDEFINED) { + throw new MapperParsingException( + "Space type in \"method\" and top level space type should be same or one of them should be defined" + ); + } + } + } + + private void validateModeAndCompressionForDataType(KNNVectorFieldMapper.Builder builder) { + boolean isModeOrCompressionConfigured = builder.mode.isConfigured() || builder.compressionLevel.isConfigured(); + if (isModeOrCompressionConfigured && builder.vectorDataType.getValue() != VectorDataType.FLOAT) { + throw new MapperParsingException( + String.format(Locale.ROOT, "Compression and mode cannot be used for non-float32 data type for field %s", builder.name) + ); + } + } + + private void validateFromFlat(KNNVectorFieldMapper.Builder builder) { + if (builder.modelId.get() != null || builder.knnMethodContext.get() != null) { + throw new IllegalArgumentException("Cannot set modelId or method parameters when index.knn setting is false"); + } + validateDimensionSet(builder); + validateCompressionAndModeNotSet(builder, builder.name(), "flat"); + } + + private void validateFromModel(KNNVectorFieldMapper.Builder builder) { + // Dimension should not be null unless modelId is used + if (builder.dimension.getValue() == UNSET_MODEL_DIMENSION_IDENTIFIER && builder.modelId.get() == null) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "Dimension value missing for vector: %s", builder.name())); + } + // ensure model and top level spaceType is not defined + if (builder.modelId.get() != null && SpaceType.getSpace(builder.topLevelSpaceType.get()) != SpaceType.UNDEFINED) { + throw new IllegalArgumentException("TopLevel Space type and model can not be both specified in the " + "mapping"); + } + + validateCompressionAndModeNotSet(builder, builder.name(), "model"); + } + + private void validateFromKNNMethod(KNNVectorFieldMapper.Builder builder) { + ValidationException validationException; + if (builder.originalParameters.getResolvedKnnMethodContext().isTrainingRequired()) { + validationException = new ValidationException(); + validationException.addValidationError(String.format(Locale.ROOT, "\"%s\" requires training.", KNN_METHOD)); + throw validationException; + } + + if (builder.originalParameters.getResolvedKnnMethodContext() != null) { + validationException = builder.originalParameters.getResolvedKnnMethodContext().validate(builder.knnMethodConfigContext); + if (validationException != null) { + throw validationException; + } + } + validateDimensionSet(builder); + } + + private void validateDimensionSet(KNNVectorFieldMapper.Builder builder) { + if (builder.dimension.getValue() == UNSET_MODEL_DIMENSION_IDENTIFIER) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "Dimension value missing for vector: %s", builder.name())); + } + } + + private void validateCompressionAndModeNotSet(KNNVectorFieldMapper.Builder builder, String name, String context) { + if (builder.mode.isConfigured() || builder.compressionLevel.isConfigured()) { + throw new MapperParsingException( + String.format( + Locale.ROOT, + "Compression and mode can not be specified in a %s mapping configuration for field: %s", + context, + name + ) + ); + } + } + + private void resolveKNNMethodComponents( + KNNVectorFieldMapper.Builder builder, + ParserContext parserContext, + SpaceType resolvedSpaceType + ) { + // Setup the initial configuration that is used to help resolve parameters. + builder.setKnnMethodConfigContext( + KNNMethodConfigContext.builder() + .vectorDataType(builder.originalParameters.getVectorDataType()) + .versionCreated(parserContext.indexVersionCreated()) + .dimension(builder.originalParameters.getDimension()) + .mode(Mode.fromName(builder.originalParameters.getMode())) + .compressionLevel(CompressionLevel.fromName(builder.originalParameters.getCompressionLevel())) + .build() + ); + + if (useKNNMethodContextFromLegacy(builder, parserContext)) { + // Then create KNNMethodContext to be used from the legacy index settings + builder.originalParameters.setResolvedKnnMethodContext( + createKNNMethodContextFromLegacy(parserContext.getSettings(), parserContext.indexVersionCreated(), resolvedSpaceType) + ); + } + + // Based on config context, if the user does not set the engine, set it + KNNEngine resolvedKNNEngine = EngineResolver.INSTANCE.resolveEngine( + builder.knnMethodConfigContext, + builder.originalParameters.getResolvedKnnMethodContext(), + false + ); + setEngine(builder.originalParameters.getResolvedKnnMethodContext(), resolvedKNNEngine); + + // Create a copy of the KNNMethodContext and fill in the parameters left blank by configuration context context + ResolvedMethodContext resolvedMethodContext = resolvedKNNEngine.resolveMethod( + builder.originalParameters.getResolvedKnnMethodContext(), + builder.knnMethodConfigContext, + false, + resolvedSpaceType + ); + + // The original parameters stores both the resolveMethodContext as well as the original provided by the + // user. Now that we have resolved, we need to update this in the original parameters. + builder.originalParameters.setResolvedKnnMethodContext(resolvedMethodContext.getKnnMethodContext()); + builder.knnMethodConfigContext.setCompressionLevel(resolvedMethodContext.getCompressionLevel()); + } + + private boolean isKNNDisabled(Settings settings) { + boolean isSettingPresent = KNNSettings.IS_KNN_INDEX_SETTING.exists(settings); + return !isSettingPresent || !KNNSettings.IS_KNN_INDEX_SETTING.get(settings); + } + + private void setSpaceType(final KNNMethodContext knnMethodContext, final SpaceType spaceType) { + if (knnMethodContext == null) { + return; + } + knnMethodContext.setSpaceType(spaceType); + } + + private void setEngine(final KNNMethodContext knnMethodContext, KNNEngine knnEngine) { + if (knnMethodContext == null || knnMethodContext.isEngineConfigured()) { + return; + } + knnMethodContext.setKnnEngine(knnEngine); + } + } + + static boolean useKNNMethodContextFromLegacy(Builder builder, Mapper.TypeParser.ParserContext parserContext) { + // If the original parameters are from legacy, and it is created on or before 2_17_2 since default is changed to + // FAISS starting 2_18, which doesn't support accepting algo params from index settings + return parserContext.indexVersionCreated().onOrBefore(Version.V_2_17_2) && builder.originalParameters.isLegacyMapping(); + } + + // We store the version of the index with the mapper as different version of Opensearch has different default + // values of KNN engine Algorithms hyperparameters. + protected Version indexCreatedVersion; + protected Explicit ignoreMalformed; + protected boolean stored; + protected boolean hasDocValues; + protected VectorDataType vectorDataType; + protected ModelDao modelDao; + protected boolean useLuceneBasedVectorField; + + // We need to ensure that the original KNNMethodContext as parsed is stored to initialize the + // Builder for serialization. So, we need to store it here. This is mainly to ensure that the legacy field mapper + // can use KNNMethodContext without messing up serialization on mapper merge + protected OriginalMappingParameters originalMappingParameters; + + public KNNVectorFieldMapper( + String simpleName, + KNNVectorFieldType mappedFieldType, + MultiFields multiFields, + CopyTo copyTo, + Explicit ignoreMalformed, + boolean stored, + boolean hasDocValues, + Version indexCreatedVersion, + OriginalMappingParameters originalMappingParameters + ) { + super(simpleName, mappedFieldType, multiFields, copyTo); + this.ignoreMalformed = ignoreMalformed; + this.stored = stored; + this.hasDocValues = hasDocValues; + this.vectorDataType = mappedFieldType.getVectorDataType(); + updateEngineStats(); + this.indexCreatedVersion = indexCreatedVersion; + this.originalMappingParameters = originalMappingParameters; + } + + public KNNVectorFieldMapper clone() { + return (KNNVectorFieldMapper) super.clone(); + } + + @Override + protected String contentType() { + return CONTENT_TYPE; + } + + @Override + protected void parseCreateField(ParseContext context) throws IOException { + parseCreateField(context, fieldType().getKnnMappingConfig().getDimension(), fieldType().getVectorDataType()); + } + + private Field createVectorField(float[] vectorValue) { + if (useLuceneBasedVectorField) { + return new KnnFloatVectorField(name(), vectorValue, fieldType); + } + return new VectorField(name(), vectorValue, fieldType); + } + + private Field createVectorField(byte[] vectorValue) { + if (useLuceneBasedVectorField) { + return new KnnByteVectorField(name(), vectorValue, fieldType); + } + return new VectorField(name(), vectorValue, fieldType); + } + + /** + * Function returns a list of fields to be indexed when the vector is float type. + * + * @param array array of floats + * @return {@link List} of {@link Field} + */ + protected List getFieldsForFloatVector(final float[] array) { + final List fields = new ArrayList<>(); + fields.add(createVectorField(array)); + if (this.stored) { + fields.add(createStoredFieldForFloatVector(name(), array)); + } + return fields; + } + + /** + * Function returns a list of fields to be indexed when the vector is byte type. + * + * @param array array of bytes + * @return {@link List} of {@link Field} + */ + protected List getFieldsForByteVector(final byte[] array) { + final List fields = new ArrayList<>(); + fields.add(createVectorField(array)); + if (this.stored) { + fields.add(createStoredFieldForByteVector(name(), array)); + } + return fields; + } + + /** + * Validation checks before parsing of doc begins + */ + protected void validatePreparse() { + validateIfKNNPluginEnabled(); + validateIfCircuitBreakerIsNotTriggered(); + } + + /** + * Getter for vector validator after vector parsing + * + * @return VectorValidator + */ + protected abstract VectorValidator getVectorValidator(); + + /** + * Getter for per dimension validator during vector parsing + * + * @return PerDimensionValidator + */ + protected abstract PerDimensionValidator getPerDimensionValidator(); + + /** + * Getter for per dimension processor during vector parsing + * + * @return PerDimensionProcessor + */ + protected abstract PerDimensionProcessor getPerDimensionProcessor(); + + protected void parseCreateField(ParseContext context, int dimension, VectorDataType vectorDataType) throws IOException { + validatePreparse(); + + if (VectorDataType.BINARY == vectorDataType || VectorDataType.BYTE == vectorDataType) { + Optional bytesArrayOptional = getBytesFromContext(context, dimension, vectorDataType); + if (bytesArrayOptional.isEmpty()) { + return; + } + final byte[] array = bytesArrayOptional.get(); + getVectorValidator().validateVector(array); + context.doc().addAll(getFieldsForByteVector(array)); + } else if (VectorDataType.FLOAT == vectorDataType) { + Optional floatsArrayOptional = getFloatsFromContext(context, dimension); + + if (floatsArrayOptional.isEmpty()) { + return; + } + final float[] array = floatsArrayOptional.get(); + getVectorValidator().validateVector(array); + context.doc().addAll(getFieldsForFloatVector(array)); + } else { + throw new IllegalArgumentException( + String.format(Locale.ROOT, "Cannot parse context for unsupported values provided for field [%s]", VECTOR_DATA_TYPE_FIELD) + ); + } + + context.path().remove(); + } + + // Returns an optional array of byte values where each value in the vector is parsed as a float and validated + // if it is a finite number without any decimals and within the byte range of [-128 to 127]. + Optional getBytesFromContext(ParseContext context, int dimension, VectorDataType dataType) throws IOException { + context.path().add(simpleName()); + + PerDimensionValidator perDimensionValidator = getPerDimensionValidator(); + PerDimensionProcessor perDimensionProcessor = getPerDimensionProcessor(); + + ArrayList vector = new ArrayList<>(); + XContentParser.Token token = context.parser().currentToken(); + + if (token == XContentParser.Token.START_ARRAY) { + token = context.parser().nextToken(); + while (token != XContentParser.Token.END_ARRAY) { + float value = perDimensionProcessor.processByte(context.parser().floatValue()); + perDimensionValidator.validateByte(value); + vector.add((byte) value); + token = context.parser().nextToken(); + } + } else if (token == XContentParser.Token.VALUE_NUMBER) { + float value = perDimensionProcessor.processByte(context.parser().floatValue()); + perDimensionValidator.validateByte(value); + vector.add((byte) value); + context.parser().nextToken(); + } else if (token == XContentParser.Token.VALUE_NULL) { + context.path().remove(); + return Optional.empty(); + } + validateVectorDimension(dimension, vector.size(), dataType); + byte[] array = new byte[vector.size()]; + int i = 0; + for (Byte f : vector) { + array[i++] = f; + } + return Optional.of(array); + } + + Optional getFloatsFromContext(ParseContext context, int dimension) throws IOException { + context.path().add(simpleName()); + + PerDimensionValidator perDimensionValidator = getPerDimensionValidator(); + PerDimensionProcessor perDimensionProcessor = getPerDimensionProcessor(); + + ArrayList vector = new ArrayList<>(); + XContentParser.Token token = context.parser().currentToken(); + float value; + if (token == XContentParser.Token.START_ARRAY) { + token = context.parser().nextToken(); + while (token != XContentParser.Token.END_ARRAY) { + value = perDimensionProcessor.process(context.parser().floatValue()); + perDimensionValidator.validate(value); + vector.add(value); + token = context.parser().nextToken(); + } + } else if (token == XContentParser.Token.VALUE_NUMBER) { + value = perDimensionProcessor.process(context.parser().floatValue()); + perDimensionValidator.validate(value); + vector.add(value); + context.parser().nextToken(); + } else if (token == XContentParser.Token.VALUE_NULL) { + context.path().remove(); + return Optional.empty(); + } + validateVectorDimension(dimension, vector.size(), vectorDataType); + + float[] array = new float[vector.size()]; + int i = 0; + for (Float f : vector) { + array[i++] = f; + } + return Optional.of(array); + } + + @Override + public ParametrizedFieldMapper.Builder getMergeBuilder() { + // We cannot get the dimension from the model based indices at this field because the + // cluster state may not be available. So, we need to set it to null. + KNNMethodConfigContext knnMethodConfigContext; + if (fieldType().getKnnMappingConfig().getModelId().isPresent()) { + knnMethodConfigContext = null; + } else { + knnMethodConfigContext = KNNMethodConfigContext.builder() + .vectorDataType(vectorDataType) + .versionCreated(indexCreatedVersion) + .dimension(fieldType().getKnnMappingConfig().getDimension()) + .compressionLevel(fieldType().getKnnMappingConfig().getCompressionLevel()) + .mode(fieldType().getKnnMappingConfig().getMode()) + .build(); + } + + return new KNNVectorFieldMapper.Builder( + simpleName(), + modelDao, + indexCreatedVersion, + knnMethodConfigContext, + originalMappingParameters + ).init(this); + } + + @Override + public final boolean parsesArrayValue() { + return true; + } + + @Override + public KNNVectorFieldType fieldType() { + return (KNNVectorFieldType) super.fieldType(); + } + + @Override + protected void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException { + super.doXContentBody(builder, includeDefaults, params); + if (includeDefaults || ignoreMalformed.explicit()) { + builder.field(Names.IGNORE_MALFORMED, ignoreMalformed.value()); + } + } + + /** + * Overwrite at child level in case specific stat needs to be updated + */ + void updateEngineStats() {} + + public static class Names { + public static final String IGNORE_MALFORMED = "ignore_malformed"; + } + + public static class Defaults { + public static final Explicit IGNORE_MALFORMED = new Explicit<>(false, false); + public static final FieldType FIELD_TYPE = new FieldType(); + + static { + FIELD_TYPE.setTokenized(false); + FIELD_TYPE.setIndexOptions(IndexOptions.NONE); + FIELD_TYPE.putAttribute(KNN_FIELD, "true"); // This attribute helps to determine knn field type + FIELD_TYPE.freeze(); + } + } +} diff --git a/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapperUtil.java b/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapperUtil.java new file mode 100644 index 000000000..b3727f2ef --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapperUtil.java @@ -0,0 +1,219 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.mapper; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.apache.lucene.document.FieldType; +import org.apache.lucene.document.StoredField; +import org.apache.lucene.index.DocValuesType; +import org.apache.lucene.util.BytesRef; +import org.opensearch.Version; +import org.opensearch.common.settings.Settings; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.KnnCircuitBreakerException; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.codec.util.KNNVectorSerializerFactory; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.util.IndexHyperParametersUtil; + +import java.util.Arrays; +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.HNSW_ALGO_EF_CONSTRUCTION; +import static org.opensearch.knn.common.KNNConstants.HNSW_ALGO_M; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_M; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; + +/** + * Utility class for KNNVectorFieldMapper + */ +@Log4j2 +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class KNNVectorFieldMapperUtil { + + /** + * @param knnEngine KNNEngine + * @return DocValues FieldType of type Binary + */ + public static FieldType buildDocValuesFieldType(KNNEngine knnEngine) { + FieldType field = new FieldType(); + field.putAttribute(KNN_ENGINE, knnEngine.getName()); + field.setDocValuesType(DocValuesType.BINARY); + field.freeze(); + return field; + } + + /** + * Creates a stored field for a byte vector + * + * @param name field name + * @param vector vector to be added to stored field + */ + public static StoredField createStoredFieldForByteVector(String name, byte[] vector) { + return new StoredField(name, vector); + } + + /** + * Creates a stored field for a float vector + * + * @param name field name + * @param vector vector to be added to stored field + */ + public static StoredField createStoredFieldForFloatVector(String name, float[] vector) { + return new StoredField(name, KNNVectorSerializerFactory.getDefaultSerializer().floatToByteArray(vector)); + } + + /** + * @param storedVector Vector representation in bytes + * @param vectorDataType type of vector + * @return either int[] or float[] of corresponding vector + */ + public static Object deserializeStoredVector(BytesRef storedVector, VectorDataType vectorDataType) { + if (VectorDataType.BYTE == vectorDataType) { + byte[] bytes = storedVector.bytes; + int[] byteAsIntArray = new int[bytes.length]; + Arrays.setAll(byteAsIntArray, i -> bytes[i]); + return byteAsIntArray; + } + + return vectorDataType.getVectorFromBytesRef(storedVector); + } + + /** + * Get the expected vector length from a specified knn vector field type. + * + * If the field is model-based, get dimensions from model metadata. + * For binary vector, the expected vector length is dimension divided by 8 + * + * @param knnVectorFieldType knn vector field type + * @return expected vector length + */ + public static int getExpectedVectorLength(final KNNVectorFieldType knnVectorFieldType) { + int expectedDimensions = knnVectorFieldType.getKnnMappingConfig().getDimension(); + return VectorDataType.BINARY == knnVectorFieldType.getVectorDataType() ? expectedDimensions / 8 : expectedDimensions; + } + + /** + * Validate if the circuit breaker is triggered + */ + static void validateIfCircuitBreakerIsNotTriggered() { + if (KNNSettings.isCircuitBreakerTriggered()) { + throw new KnnCircuitBreakerException( + "Parsing the created knn vector fields prior to indexing has failed as the circuit breaker triggered. This indicates that the cluster is low on memory resources and cannot index more documents at the moment. Check _plugins/_knn/stats for the circuit breaker status." + ); + } + } + + /** + * Validate if plugin is enabled + */ + static void validateIfKNNPluginEnabled() { + if (!KNNSettings.isKNNPluginEnabled()) { + throw new IllegalStateException("KNN plugin is disabled. To enable update knn.plugin.enabled setting to true"); + } + } + + /** + * Prerequisite: Index should a knn index which is validated via index settings index.knn setting. This function + * assumes that caller has already validated that index is a KNN index. + * We will use LuceneKNNVectorsFormat when these below condition satisfy: + *
    + *
  1. Index is created with Version of opensearch >= 2.17
  2. + *
  3. Cluster setting is enabled to use Lucene KNNVectors format. This condition is temporary condition and will be + * removed before release.
  4. + *
+ * @param indexCreatedVersion {@link Version} + * @return true if vector field should use KNNVectorsFormat + */ + static boolean useLuceneKNNVectorsFormat(final Version indexCreatedVersion) { + return indexCreatedVersion.onOrAfter(Version.V_2_17_0); + } + + private static SpaceType getSpaceType(final Settings indexSettings) { + String spaceType = indexSettings.get(KNNSettings.INDEX_KNN_SPACE_TYPE.getKey()); + if (spaceType == null) { + spaceType = KNNSettings.INDEX_KNN_DEFAULT_SPACE_TYPE; + log.info( + String.format( + "[KNN] The setting \"%s\" was not set for the index. Likely caused by recent version upgrade. Setting the setting to the default value=%s", + METHOD_PARAMETER_SPACE_TYPE, + spaceType + ) + ); + } + return SpaceType.getSpace(spaceType); + } + + private static int getM(Settings indexSettings) { + String m = indexSettings.get(KNNSettings.INDEX_KNN_ALGO_PARAM_M_SETTING.getKey()); + if (m == null) { + log.info( + String.format( + "[KNN] The setting \"%s\" was not set for the index. Likely caused by recent version upgrade. Setting the setting to the default value=%s", + HNSW_ALGO_M, + KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_M + ) + ); + return KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_M; + } + return Integer.parseInt(m); + } + + private static int getEfConstruction(Settings indexSettings, Version indexVersion) { + final String efConstruction = indexSettings.get(KNNSettings.INDEX_KNN_ALGO_PARAM_EF_CONSTRUCTION_SETTING.getKey()); + if (efConstruction == null) { + final int defaultEFConstructionValue = IndexHyperParametersUtil.getHNSWEFConstructionValue(indexVersion); + log.info( + String.format( + "[KNN] The setting \"%s\" was not set for the index. Likely caused by recent version upgrade. " + + "Picking up default value for the index =%s", + HNSW_ALGO_EF_CONSTRUCTION, + defaultEFConstructionValue + ) + ); + return defaultEFConstructionValue; + } + return Integer.parseInt(efConstruction); + } + + static KNNMethodContext createKNNMethodContextFromLegacy( + Settings indexSettings, + Version indexCreatedVersion, + SpaceType topLevelSpaceType + ) { + // If top level spaceType is set then use that spaceType otherwise default to spaceType from index-settings + final SpaceType finalSpaceToSet = topLevelSpaceType != SpaceType.UNDEFINED + ? topLevelSpaceType + : KNNVectorFieldMapperUtil.getSpaceType(indexSettings); + return new KNNMethodContext( + KNNEngine.NMSLIB, + finalSpaceToSet, + new MethodComponentContext( + METHOD_HNSW, + Map.of( + METHOD_PARAMETER_M, + KNNVectorFieldMapperUtil.getM(indexSettings), + METHOD_PARAMETER_EF_CONSTRUCTION, + KNNVectorFieldMapperUtil.getEfConstruction(indexSettings, indexCreatedVersion) + ) + ) + ); + } +} diff --git a/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldType.java b/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldType.java new file mode 100644 index 000000000..a0832c1d0 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/mapper/KNNVectorFieldType.java @@ -0,0 +1,102 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.mapper; + +import lombok.Getter; +import org.apache.lucene.search.DocValuesFieldExistsQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.util.BytesRef; +import org.opensearch.index.fielddata.IndexFieldData; +import org.opensearch.index.mapper.MappedFieldType; +import org.opensearch.index.mapper.TextSearchInfo; +import org.opensearch.index.mapper.ValueFetcher; +import org.opensearch.index.query.QueryShardContext; +import org.opensearch.index.query.QueryShardException; +import org.opensearch.knn.index.KNNVectorIndexFieldData; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.query.rescore.RescoreContext; +import org.opensearch.search.aggregations.support.CoreValuesSourceType; +import org.opensearch.search.lookup.SearchLookup; + +import java.util.Locale; +import java.util.Map; +import java.util.function.Supplier; + +import static org.opensearch.knn.index.mapper.KNNVectorFieldMapperUtil.deserializeStoredVector; + +/** + * A KNNVector field type to represent the vector field in Opensearch + */ +@Getter +public class KNNVectorFieldType extends MappedFieldType { + KNNMappingConfig knnMappingConfig; + VectorDataType vectorDataType; + + /** + * Constructor for KNNVectorFieldType. + * + * @param name name of the field + * @param metadata metadata of the field + * @param vectorDataType data type of the vector + * @param annConfig configuration context for the ANN index + */ + public KNNVectorFieldType(String name, Map metadata, VectorDataType vectorDataType, KNNMappingConfig annConfig) { + super(name, false, false, true, TextSearchInfo.NONE, metadata); + this.vectorDataType = vectorDataType; + this.knnMappingConfig = annConfig; + } + + @Override + public ValueFetcher valueFetcher(QueryShardContext context, SearchLookup searchLookup, String format) { + throw new UnsupportedOperationException("KNN Vector do not support fields search"); + } + + @Override + public String typeName() { + return KNNVectorFieldMapper.CONTENT_TYPE; + } + + @Override + public Query existsQuery(QueryShardContext context) { + return new DocValuesFieldExistsQuery(name()); + } + + @Override + public Query termQuery(Object value, QueryShardContext context) { + throw new QueryShardException( + context, + String.format(Locale.ROOT, "KNN vector do not support exact searching, use KNN queries instead: [%s]", name()) + ); + } + + @Override + public IndexFieldData.Builder fielddataBuilder(String fullyQualifiedIndexName, Supplier searchLookup) { + failIfNoDocValues(); + return new KNNVectorIndexFieldData.Builder(name(), CoreValuesSourceType.BYTES, this.vectorDataType); + } + + @Override + public Object valueForDisplay(Object value) { + return deserializeStoredVector((BytesRef) value, vectorDataType); + } + + /** + * Resolve the rescore context provided for a user based on the field configuration + * + * @param userProvidedContext {@link RescoreContext} user passed; if null, the default should be configured + * @return resolved {@link RescoreContext} + */ + public RescoreContext resolveRescoreContext(RescoreContext userProvidedContext) { + if (userProvidedContext != null) { + return userProvidedContext; + } + KNNMappingConfig knnMappingConfig = getKnnMappingConfig(); + int dimension = knnMappingConfig.getDimension(); + CompressionLevel compressionLevel = knnMappingConfig.getCompressionLevel(); + Mode mode = knnMappingConfig.getMode(); + return compressionLevel.getDefaultRescoreContext(mode, dimension); + } +} diff --git a/src/main/java/org/opensearch/knn/index/mapper/LuceneFieldMapper.java b/src/main/java/org/opensearch/knn/index/mapper/LuceneFieldMapper.java new file mode 100644 index 000000000..fcf3aa034 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/mapper/LuceneFieldMapper.java @@ -0,0 +1,188 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.mapper; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NonNull; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.FieldType; +import org.apache.lucene.document.KnnByteVectorField; +import org.apache.lucene.document.KnnFloatVectorField; +import org.apache.lucene.index.VectorSimilarityFunction; +import org.opensearch.common.Explicit; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.VectorField; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNLibraryIndexingContext; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; + +import static org.opensearch.knn.index.mapper.KNNVectorFieldMapperUtil.createStoredFieldForByteVector; +import static org.opensearch.knn.index.mapper.KNNVectorFieldMapperUtil.createStoredFieldForFloatVector; +import static org.opensearch.knn.index.mapper.KNNVectorFieldMapperUtil.buildDocValuesFieldType; + +/** + * Field mapper for case when Lucene has been set as an engine. + */ +public class LuceneFieldMapper extends KNNVectorFieldMapper { + + /** FieldType used for initializing VectorField, which is used for creating binary doc values. **/ + private final FieldType vectorFieldType; + + private final PerDimensionProcessor perDimensionProcessor; + private final PerDimensionValidator perDimensionValidator; + private final VectorValidator vectorValidator; + + static LuceneFieldMapper createFieldMapper( + String fullname, + Map metaValue, + KNNMethodConfigContext knnMethodConfigContext, + CreateLuceneFieldMapperInput createLuceneFieldMapperInput, + OriginalMappingParameters originalMappingParameters + ) { + final KNNVectorFieldType mappedFieldType = new KNNVectorFieldType( + fullname, + metaValue, + knnMethodConfigContext.getVectorDataType(), + new KNNMappingConfig() { + @Override + public Optional getKnnMethodContext() { + return Optional.of(originalMappingParameters.getResolvedKnnMethodContext()); + } + + @Override + public int getDimension() { + return knnMethodConfigContext.getDimension(); + } + + @Override + public Mode getMode() { + return knnMethodConfigContext.getMode(); + } + + @Override + public CompressionLevel getCompressionLevel() { + return knnMethodConfigContext.getCompressionLevel(); + } + } + ); + + return new LuceneFieldMapper(mappedFieldType, createLuceneFieldMapperInput, knnMethodConfigContext, originalMappingParameters); + } + + private LuceneFieldMapper( + final KNNVectorFieldType mappedFieldType, + final CreateLuceneFieldMapperInput input, + KNNMethodConfigContext knnMethodConfigContext, + OriginalMappingParameters originalMappingParameters + ) { + super( + input.getName(), + mappedFieldType, + input.getMultiFields(), + input.getCopyTo(), + input.getIgnoreMalformed(), + input.isStored(), + input.isHasDocValues(), + knnMethodConfigContext.getVersionCreated(), + originalMappingParameters + ); + KNNMappingConfig knnMappingConfig = mappedFieldType.getKnnMappingConfig(); + KNNMethodContext resolvedKnnMethodContext = originalMappingParameters.getResolvedKnnMethodContext(); + VectorDataType vectorDataType = mappedFieldType.getVectorDataType(); + + final VectorSimilarityFunction vectorSimilarityFunction = resolvedKnnMethodContext.getSpaceType() + .getKnnVectorSimilarityFunction() + .getVectorSimilarityFunction(); + + this.fieldType = vectorDataType.createKnnVectorFieldType(knnMappingConfig.getDimension(), vectorSimilarityFunction); + + if (this.hasDocValues) { + this.vectorFieldType = buildDocValuesFieldType(resolvedKnnMethodContext.getKnnEngine()); + } else { + this.vectorFieldType = null; + } + + KNNLibraryIndexingContext knnLibraryIndexingContext = resolvedKnnMethodContext.getKnnEngine() + .getKNNLibraryIndexingContext(resolvedKnnMethodContext, knnMethodConfigContext); + this.perDimensionProcessor = knnLibraryIndexingContext.getPerDimensionProcessor(); + this.perDimensionValidator = knnLibraryIndexingContext.getPerDimensionValidator(); + this.vectorValidator = knnLibraryIndexingContext.getVectorValidator(); + } + + @Override + protected List getFieldsForFloatVector(final float[] array) { + final List fieldsToBeAdded = new ArrayList<>(); + fieldsToBeAdded.add(new KnnFloatVectorField(name(), array, fieldType)); + + if (hasDocValues && vectorFieldType != null) { + fieldsToBeAdded.add(new VectorField(name(), array, vectorFieldType)); + } + + if (this.stored) { + fieldsToBeAdded.add(createStoredFieldForFloatVector(name(), array)); + } + return fieldsToBeAdded; + } + + @Override + protected List getFieldsForByteVector(final byte[] array) { + final List fieldsToBeAdded = new ArrayList<>(); + fieldsToBeAdded.add(new KnnByteVectorField(name(), array, fieldType)); + + if (hasDocValues && vectorFieldType != null) { + fieldsToBeAdded.add(new VectorField(name(), array, vectorFieldType)); + } + + if (this.stored) { + fieldsToBeAdded.add(createStoredFieldForByteVector(name(), array)); + } + return fieldsToBeAdded; + } + + @Override + protected VectorValidator getVectorValidator() { + return vectorValidator; + } + + @Override + protected PerDimensionValidator getPerDimensionValidator() { + return perDimensionValidator; + } + + @Override + protected PerDimensionProcessor getPerDimensionProcessor() { + return perDimensionProcessor; + } + + @Override + void updateEngineStats() { + KNNEngine.LUCENE.setInitialized(true); + } + + @AllArgsConstructor + @lombok.Builder + @Getter + static class CreateLuceneFieldMapperInput { + @NonNull + String name; + @NonNull + MultiFields multiFields; + @NonNull + CopyTo copyTo; + @NonNull + Explicit ignoreMalformed; + boolean stored; + boolean hasDocValues; + KNNMethodContext originalKnnMethodContext; + } +} diff --git a/src/main/java/org/opensearch/knn/index/mapper/MethodFieldMapper.java b/src/main/java/org/opensearch/knn/index/mapper/MethodFieldMapper.java new file mode 100644 index 000000000..bf5bc2b51 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/mapper/MethodFieldMapper.java @@ -0,0 +1,193 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.mapper; + +import org.apache.lucene.document.FieldType; +import org.apache.lucene.index.DocValuesType; +import org.apache.lucene.index.VectorEncoding; +import org.opensearch.common.Explicit; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNLibraryIndexingContext; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.index.engine.qframe.QuantizationConfigParser; + +import java.io.IOException; +import java.util.Map; +import java.util.Optional; + +import static org.opensearch.knn.common.KNNConstants.DIMENSION; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.PARAMETERS; +import static org.opensearch.knn.common.KNNConstants.QFRAMEWORK_CONFIG; +import static org.opensearch.knn.common.KNNConstants.SPACE_TYPE; +import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; + +/** + * Field mapper for method definition in mapping + */ +public class MethodFieldMapper extends KNNVectorFieldMapper { + + private final PerDimensionProcessor perDimensionProcessor; + private final PerDimensionValidator perDimensionValidator; + private final VectorValidator vectorValidator; + + public static MethodFieldMapper createFieldMapper( + String fullname, + String simpleName, + Map metaValue, + KNNMethodConfigContext knnMethodConfigContext, + MultiFields multiFields, + CopyTo copyTo, + Explicit ignoreMalformed, + boolean stored, + boolean hasDocValues, + OriginalMappingParameters originalMappingParameters + ) { + + KNNMethodContext knnMethodContext = originalMappingParameters.getResolvedKnnMethodContext(); + QuantizationConfig quantizationConfig = knnMethodContext.getKnnEngine() + .getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext) + .getQuantizationConfig(); + + final KNNVectorFieldType mappedFieldType = new KNNVectorFieldType( + fullname, + metaValue, + knnMethodConfigContext.getVectorDataType(), + new KNNMappingConfig() { + @Override + public Optional getKnnMethodContext() { + return Optional.of(originalMappingParameters.getResolvedKnnMethodContext()); + } + + @Override + public int getDimension() { + return knnMethodConfigContext.getDimension(); + } + + @Override + public Mode getMode() { + return Mode.fromName(originalMappingParameters.getMode()); + } + + @Override + public CompressionLevel getCompressionLevel() { + return knnMethodConfigContext.getCompressionLevel(); + } + + @Override + public QuantizationConfig getQuantizationConfig() { + return quantizationConfig; + } + } + ); + return new MethodFieldMapper( + simpleName, + mappedFieldType, + multiFields, + copyTo, + ignoreMalformed, + stored, + hasDocValues, + knnMethodConfigContext, + originalMappingParameters + ); + } + + private MethodFieldMapper( + String simpleName, + KNNVectorFieldType mappedFieldType, + MultiFields multiFields, + CopyTo copyTo, + Explicit ignoreMalformed, + boolean stored, + boolean hasDocValues, + KNNMethodConfigContext knnMethodConfigContext, + OriginalMappingParameters originalMappingParameters + ) { + + super( + simpleName, + mappedFieldType, + multiFields, + copyTo, + ignoreMalformed, + stored, + hasDocValues, + knnMethodConfigContext.getVersionCreated(), + originalMappingParameters + ); + this.useLuceneBasedVectorField = KNNVectorFieldMapperUtil.useLuceneKNNVectorsFormat(indexCreatedVersion); + KNNMappingConfig knnMappingConfig = mappedFieldType.getKnnMappingConfig(); + KNNMethodContext resolvedKnnMethodContext = originalMappingParameters.getResolvedKnnMethodContext(); + KNNEngine knnEngine = resolvedKnnMethodContext.getKnnEngine(); + KNNLibraryIndexingContext knnLibraryIndexingContext = knnEngine.getKNNLibraryIndexingContext( + resolvedKnnMethodContext, + knnMethodConfigContext + ); + QuantizationConfig quantizationConfig = knnLibraryIndexingContext.getQuantizationConfig(); + + this.fieldType = new FieldType(KNNVectorFieldMapper.Defaults.FIELD_TYPE); + this.fieldType.putAttribute(DIMENSION, String.valueOf(knnMappingConfig.getDimension())); + this.fieldType.putAttribute(SPACE_TYPE, resolvedKnnMethodContext.getSpaceType().getValue()); + // Conditionally add quantization config + if (quantizationConfig != null && quantizationConfig != QuantizationConfig.EMPTY) { + this.fieldType.putAttribute(QFRAMEWORK_CONFIG, QuantizationConfigParser.toCsv(quantizationConfig)); + } + + this.fieldType.putAttribute(VECTOR_DATA_TYPE_FIELD, vectorDataType.getValue()); + this.fieldType.putAttribute(KNN_ENGINE, knnEngine.getName()); + + try { + this.fieldType.putAttribute( + PARAMETERS, + XContentFactory.jsonBuilder().map(knnLibraryIndexingContext.getLibraryParameters()).toString() + ); + } catch (IOException ioe) { + throw new RuntimeException(String.format("Unable to create KNNVectorFieldMapper: %s", ioe)); + } + + if (useLuceneBasedVectorField) { + int adjustedDimension = mappedFieldType.vectorDataType == VectorDataType.BINARY + ? knnMappingConfig.getDimension() / 8 + : knnMappingConfig.getDimension(); + final VectorEncoding encoding = mappedFieldType.vectorDataType == VectorDataType.FLOAT + ? VectorEncoding.FLOAT32 + : VectorEncoding.BYTE; + fieldType.setVectorAttributes( + adjustedDimension, + encoding, + SpaceType.DEFAULT.getKnnVectorSimilarityFunction().getVectorSimilarityFunction() + ); + } else { + fieldType.setDocValuesType(DocValuesType.BINARY); + } + + this.fieldType.freeze(); + this.perDimensionProcessor = knnLibraryIndexingContext.getPerDimensionProcessor(); + this.perDimensionValidator = knnLibraryIndexingContext.getPerDimensionValidator(); + this.vectorValidator = knnLibraryIndexingContext.getVectorValidator(); + } + + @Override + protected VectorValidator getVectorValidator() { + return vectorValidator; + } + + @Override + protected PerDimensionValidator getPerDimensionValidator() { + return perDimensionValidator; + } + + @Override + protected PerDimensionProcessor getPerDimensionProcessor() { + return perDimensionProcessor; + } +} diff --git a/src/main/java/org/opensearch/knn/index/mapper/Mode.java b/src/main/java/org/opensearch/knn/index/mapper/Mode.java new file mode 100644 index 000000000..51822cae1 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/mapper/Mode.java @@ -0,0 +1,68 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.mapper; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.opensearch.core.common.Strings; + +import java.util.Arrays; +import java.util.Locale; +import java.util.stream.Collectors; + +/** + * Enum representing the intended workload optimization a user wants their k-NN system to have. Based on this value, + * default parameter resolution will be determined. + */ +@Getter +@AllArgsConstructor +public enum Mode { + NOT_CONFIGURED(""), + IN_MEMORY("in_memory"), + ON_DISK("on_disk"); + + // Internally, an empty string is easier to deal with them null. However, from the mapping, + // we do not want users to pass in the empty string and instead want null. So we make the conversion herex + public static final String[] NAMES_ARRAY = Arrays.stream(Mode.values()) + .map(mode -> mode == NOT_CONFIGURED ? null : mode.getName()) + .collect(Collectors.toList()) + .toArray(new String[0]); + + private static final Mode DEFAULT = IN_MEMORY; + + /** + * Convert a string to a Mode enum value + * + * @param name String value to convert + * @return Mode enum value + */ + public static Mode fromName(String name) { + if (Strings.isEmpty(name)) { + return NOT_CONFIGURED; + } + + if (IN_MEMORY.name.equalsIgnoreCase(name)) { + return IN_MEMORY; + } + + if (ON_DISK.name.equalsIgnoreCase(name)) { + return ON_DISK; + } + throw new IllegalArgumentException(String.format(Locale.ROOT, "Invalid mode: \"[%s]\"", name)); + } + + private final String name; + + /** + * Utility method that checks if mode is configured. + * + * @param mode Mode to check + * @return true if mode is configured, false otherwise + */ + public static boolean isConfigured(Mode mode) { + return mode != null && mode != NOT_CONFIGURED; + } +} diff --git a/src/main/java/org/opensearch/knn/index/mapper/ModelFieldMapper.java b/src/main/java/org/opensearch/knn/index/mapper/ModelFieldMapper.java new file mode 100644 index 000000000..013cb0c53 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/mapper/ModelFieldMapper.java @@ -0,0 +1,319 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.mapper; + +import org.apache.lucene.document.FieldType; +import org.apache.lucene.index.DocValuesType; +import org.apache.lucene.index.VectorEncoding; +import org.opensearch.Version; +import org.opensearch.common.Explicit; +import org.opensearch.index.mapper.ParseContext; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNLibraryIndexingContext; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.index.engine.qframe.QuantizationConfigParser; +import org.opensearch.knn.indices.ModelDao; +import org.opensearch.knn.indices.ModelMetadata; +import org.opensearch.knn.indices.ModelUtil; + +import java.io.IOException; +import java.util.Map; +import java.util.Optional; + +import static org.opensearch.knn.common.KNNConstants.MODEL_ID; +import static org.opensearch.knn.common.KNNConstants.QFRAMEWORK_CONFIG; + +/** + * Field mapper for model in mapping + */ +public class ModelFieldMapper extends KNNVectorFieldMapper { + + // If the dimension has not yet been set because we do not have access to model metadata, it will be -1 + public static final int UNSET_MODEL_DIMENSION_IDENTIFIER = -1; + + private PerDimensionProcessor perDimensionProcessor; + private PerDimensionValidator perDimensionValidator; + private VectorValidator vectorValidator; + + private final String modelId; + + public static ModelFieldMapper createFieldMapper( + String fullname, + String simpleName, + Map metaValue, + VectorDataType vectorDataType, + MultiFields multiFields, + CopyTo copyTo, + Explicit ignoreMalformed, + boolean stored, + boolean hasDocValues, + ModelDao modelDao, + Version indexCreatedVersion, + OriginalMappingParameters originalMappingParameters, + KNNMethodConfigContext knnMethodConfigContext + ) { + + final KNNMethodContext knnMethodContext = originalMappingParameters.getKnnMethodContext(); + final QuantizationConfig quantizationConfig = knnMethodContext == null + ? QuantizationConfig.EMPTY + : knnMethodContext.getKnnEngine() + .getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext) + .getQuantizationConfig(); + + final KNNVectorFieldType mappedFieldType = new KNNVectorFieldType(fullname, metaValue, vectorDataType, new KNNMappingConfig() { + private Integer dimension = null; + private Mode mode = null; + private CompressionLevel compressionLevel = null; + + @Override + public Optional getModelId() { + return Optional.of(originalMappingParameters.getModelId()); + } + + @Override + public int getDimension() { + if (dimension == null) { + initFromModelMetadata(); + } + + return dimension; + } + + @Override + public Mode getMode() { + if (mode == null) { + initFromModelMetadata(); + } + return mode; + } + + @Override + public CompressionLevel getCompressionLevel() { + if (compressionLevel == null) { + initFromModelMetadata(); + } + return compressionLevel; + } + + @Override + public QuantizationConfig getQuantizationConfig() { + return quantizationConfig; + } + + // ModelMetadata relies on cluster state which may not be available during field mapper creation. Thus, + // we lazily initialize it. + private void initFromModelMetadata() { + ModelMetadata modelMetadata = getModelMetadata(modelDao, originalMappingParameters.getModelId()); + dimension = modelMetadata.getDimension(); + mode = modelMetadata.getMode(); + compressionLevel = modelMetadata.getCompressionLevel(); + } + }); + return new ModelFieldMapper( + simpleName, + mappedFieldType, + multiFields, + copyTo, + ignoreMalformed, + stored, + hasDocValues, + modelDao, + indexCreatedVersion, + originalMappingParameters + ); + } + + private ModelFieldMapper( + String simpleName, + KNNVectorFieldType mappedFieldType, + MultiFields multiFields, + CopyTo copyTo, + Explicit ignoreMalformed, + boolean stored, + boolean hasDocValues, + ModelDao modelDao, + Version indexCreatedVersion, + OriginalMappingParameters originalMappingParameters + ) { + super( + simpleName, + mappedFieldType, + multiFields, + copyTo, + ignoreMalformed, + stored, + hasDocValues, + indexCreatedVersion, + originalMappingParameters + ); + KNNMappingConfig annConfig = mappedFieldType.getKnnMappingConfig(); + modelId = annConfig.getModelId().orElseThrow(() -> new IllegalArgumentException("KNN method context cannot be empty")); + this.modelDao = modelDao; + + // For the model field mapper, we cannot validate the model during index creation due to + // an issue with reading cluster state during mapper creation. So, we need to validate the + // model when ingestion starts. We do this as lazily as we can + this.perDimensionProcessor = null; + this.perDimensionValidator = null; + this.vectorValidator = null; + + this.fieldType = new FieldType(KNNVectorFieldMapper.Defaults.FIELD_TYPE); + this.fieldType.putAttribute(MODEL_ID, modelId); + this.useLuceneBasedVectorField = KNNVectorFieldMapperUtil.useLuceneKNNVectorsFormat(this.indexCreatedVersion); + } + + @Override + protected VectorValidator getVectorValidator() { + initVectorValidator(); + return vectorValidator; + } + + @Override + protected PerDimensionValidator getPerDimensionValidator() { + initPerDimensionValidator(); + return perDimensionValidator; + } + + @Override + protected PerDimensionProcessor getPerDimensionProcessor() { + initPerDimensionProcessor(); + return perDimensionProcessor; + } + + private void initVectorValidator() { + if (vectorValidator != null) { + return; + } + ModelMetadata modelMetadata = getModelMetadata(modelDao, modelId); + + KNNMethodContext knnMethodContext = getKNNMethodContextFromModelMetadata(modelMetadata); + KNNMethodConfigContext knnMethodConfigContext = getKNNMethodConfigContextFromModelMetadata(modelMetadata); + // Need to handle BWC case + if (knnMethodContext == null || knnMethodConfigContext == null) { + vectorValidator = new SpaceVectorValidator(modelMetadata.getSpaceType()); + return; + } + + KNNLibraryIndexingContext knnLibraryIndexingContext = knnMethodContext.getKnnEngine() + .getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext); + vectorValidator = knnLibraryIndexingContext.getVectorValidator(); + } + + private void initPerDimensionValidator() { + if (perDimensionValidator != null) { + return; + } + ModelMetadata modelMetadata = getModelMetadata(modelDao, modelId); + + KNNMethodContext knnMethodContext = getKNNMethodContextFromModelMetadata(modelMetadata); + KNNMethodConfigContext knnMethodConfigContext = getKNNMethodConfigContextFromModelMetadata(modelMetadata); + // Need to handle BWC case + if (knnMethodContext == null || knnMethodConfigContext == null) { + if (modelMetadata.getVectorDataType() == VectorDataType.BINARY) { + perDimensionValidator = PerDimensionValidator.DEFAULT_BIT_VALIDATOR; + } else if (modelMetadata.getVectorDataType() == VectorDataType.BYTE) { + perDimensionValidator = PerDimensionValidator.DEFAULT_BYTE_VALIDATOR; + } else { + perDimensionValidator = PerDimensionValidator.DEFAULT_FLOAT_VALIDATOR; + } + + return; + } + + KNNLibraryIndexingContext knnLibraryIndexingContext = knnMethodContext.getKnnEngine() + .getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext); + perDimensionValidator = knnLibraryIndexingContext.getPerDimensionValidator(); + } + + private void initPerDimensionProcessor() { + if (perDimensionProcessor != null) { + return; + } + ModelMetadata modelMetadata = getModelMetadata(modelDao, modelId); + + KNNMethodContext knnMethodContext = getKNNMethodContextFromModelMetadata(modelMetadata); + KNNMethodConfigContext knnMethodConfigContext = getKNNMethodConfigContextFromModelMetadata(modelMetadata); + // Need to handle BWC case + if (knnMethodContext == null || knnMethodConfigContext == null) { + perDimensionProcessor = PerDimensionProcessor.NOOP_PROCESSOR; + return; + } + + KNNLibraryIndexingContext knnLibraryIndexingContext = knnMethodContext.getKnnEngine() + .getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext); + perDimensionProcessor = knnLibraryIndexingContext.getPerDimensionProcessor(); + } + + @Override + protected void parseCreateField(ParseContext context) throws IOException { + validatePreparse(); + ModelMetadata modelMetadata = getModelMetadata(modelDao, modelId); + if (useLuceneBasedVectorField) { + int adjustedDimension = modelMetadata.getVectorDataType() == VectorDataType.BINARY + ? modelMetadata.getDimension() / Byte.SIZE + : modelMetadata.getDimension(); + final VectorEncoding encoding = modelMetadata.getVectorDataType() == VectorDataType.FLOAT + ? VectorEncoding.FLOAT32 + : VectorEncoding.BYTE; + fieldType.setVectorAttributes( + adjustedDimension, + encoding, + SpaceType.DEFAULT.getKnnVectorSimilarityFunction().getVectorSimilarityFunction() + ); + } else { + fieldType.setDocValuesType(DocValuesType.BINARY); + } + + // Conditionally add quantization config + KNNMethodContext knnMethodContext = getKNNMethodContextFromModelMetadata(modelMetadata); + KNNMethodConfigContext knnMethodConfigContext = getKNNMethodConfigContextFromModelMetadata(modelMetadata); + if (knnMethodContext != null && knnMethodConfigContext != null) { + KNNLibraryIndexingContext knnLibraryIndexingContext = modelMetadata.getKnnEngine() + .getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext); + QuantizationConfig quantizationConfig = knnLibraryIndexingContext.getQuantizationConfig(); + if (quantizationConfig != null && quantizationConfig != QuantizationConfig.EMPTY) { + this.fieldType.putAttribute(QFRAMEWORK_CONFIG, QuantizationConfigParser.toCsv(quantizationConfig)); + } + } + + parseCreateField(context, modelMetadata.getDimension(), modelMetadata.getVectorDataType()); + } + + private static KNNMethodContext getKNNMethodContextFromModelMetadata(ModelMetadata modelMetadata) { + MethodComponentContext methodComponentContext = modelMetadata.getMethodComponentContext(); + if (methodComponentContext == MethodComponentContext.EMPTY) { + return null; + } + return new KNNMethodContext(modelMetadata.getKnnEngine(), modelMetadata.getSpaceType(), methodComponentContext); + } + + private static KNNMethodConfigContext getKNNMethodConfigContextFromModelMetadata(ModelMetadata modelMetadata) { + MethodComponentContext methodComponentContext = modelMetadata.getMethodComponentContext(); + if (methodComponentContext == MethodComponentContext.EMPTY) { + return null; + } + // TODO: Need to fix this version check by serializing the model + return KNNMethodConfigContext.builder() + .vectorDataType(modelMetadata.getVectorDataType()) + .dimension(modelMetadata.getDimension()) + .versionCreated(modelMetadata.getModelVersion()) + .mode(modelMetadata.getMode()) + .compressionLevel(modelMetadata.getCompressionLevel()) + .build(); + } + + private static ModelMetadata getModelMetadata(ModelDao modelDao, String modelId) { + ModelMetadata modelMetadata = modelDao.getMetadata(modelId); + if (!ModelUtil.isModelCreated(modelMetadata)) { + throw new IllegalStateException(String.format("Model ID '%s' is not created.", modelId)); + } + return modelMetadata; + } +} diff --git a/src/main/java/org/opensearch/knn/index/mapper/OriginalMappingParameters.java b/src/main/java/org/opensearch/knn/index/mapper/OriginalMappingParameters.java new file mode 100644 index 000000000..340c450ee --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/mapper/OriginalMappingParameters.java @@ -0,0 +1,83 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.mapper; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.opensearch.core.common.Strings; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNMethodContext; + +/** + * Utility class to store the original mapping parameters for a KNNVectorFieldMapper. These parameters need to be + * kept around for when a {@link KNNVectorFieldMapper} is built from merge + */ +@Getter +@RequiredArgsConstructor +public final class OriginalMappingParameters { + private final VectorDataType vectorDataType; + private final int dimension; + private final KNNMethodContext knnMethodContext; + + // To support our legacy field mapping, on parsing, if index.knn=true and no method is + // passed, we build a KNNMethodContext using the space type, ef_construction and m that are set in the index + // settings. However, for fieldmappers for merging, we need to be able to initialize one field mapper from + // another (see + // https://github.com/opensearch-project/OpenSearch/blob/2.16.0/server/src/main/java/org/opensearch/index/mapper/ParametrizedFieldMapper.java#L98). + // The problem is that in this case, the settings are set to empty so we cannot properly resolve the KNNMethodContext. + // (see + // https://github.com/opensearch-project/OpenSearch/blob/2.16.0/server/src/main/java/org/opensearch/index/mapper/ParametrizedFieldMapper.java#L130). + // While we could override the KNNMethodContext parameter initializer to set the knnMethodContext based on the + // constructed KNNMethodContext from the other field mapper, this can result in merge conflict/serialization + // exceptions. See + // (https://github.com/opensearch-project/OpenSearch/blob/2.16.0/server/src/main/java/org/opensearch/index/mapper/ParametrizedFieldMapper.java#L322-L324). + // So, what we do is pass in a "resolvedKNNMethodContext" to ensure we track this resolveKnnMethodContext. + // A similar approach was taken for https://github.com/opendistro-for-elasticsearch/k-NN/issues/288 + // + // In almost all cases except when dealing with the mapping, the resolved context should be used + @Setter + private KNNMethodContext resolvedKnnMethodContext; + private final String mode; + private final String compressionLevel; + private final String modelId; + private final String topLevelSpaceType; + + /** + * Initialize the parameters from the builder + * + * @param builder The builder to initialize from + */ + public OriginalMappingParameters(KNNVectorFieldMapper.Builder builder) { + this.vectorDataType = builder.vectorDataType.get(); + this.knnMethodContext = builder.knnMethodContext.get(); + this.resolvedKnnMethodContext = builder.knnMethodContext.get(); + this.dimension = builder.dimension.get(); + this.mode = builder.mode.get(); + this.compressionLevel = builder.compressionLevel.get(); + this.modelId = builder.modelId.get(); + this.topLevelSpaceType = builder.topLevelSpaceType.get(); + } + + /** + * Determine if the mapping used the legacy mechanism to setup the index. The legacy mechanism is used if + * the index is created only by specifying the dimension. If this is the case, the constructed parameters + * need to be collected from the index settings + * + * @return true if the mapping used the legacy mechanism, false otherwise + */ + public boolean isLegacyMapping() { + if (knnMethodContext != null) { + return false; + } + + if (modelId != null) { + return false; + } + + return Strings.isEmpty(mode) && Strings.isEmpty(compressionLevel); + } +} diff --git a/src/main/java/org/opensearch/knn/index/mapper/PerDimensionProcessor.java b/src/main/java/org/opensearch/knn/index/mapper/PerDimensionProcessor.java new file mode 100644 index 000000000..9a3bbfb6b --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/mapper/PerDimensionProcessor.java @@ -0,0 +1,35 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.mapper; + +/** + * Process values per dimension. Good to have if we want to do some kind of cleanup on data as it is coming in. + */ +public interface PerDimensionProcessor { + + /** + * Process float value per dimension. + * + * @param value value to process + * @return processed value + */ + default float process(float value) { + return value; + } + + /** + * Process byte as float value per dimension. + * + * @param value value to process + * @return processed value + */ + default float processByte(float value) { + return value; + } + + PerDimensionProcessor NOOP_PROCESSOR = new PerDimensionProcessor() { + }; +} diff --git a/src/main/java/org/opensearch/knn/index/mapper/PerDimensionValidator.java b/src/main/java/org/opensearch/knn/index/mapper/PerDimensionValidator.java new file mode 100644 index 000000000..60d8540c6 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/mapper/PerDimensionValidator.java @@ -0,0 +1,66 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.mapper; + +import org.opensearch.knn.index.VectorDataType; + +import static org.opensearch.knn.common.KNNValidationUtil.validateByteVectorValue; +import static org.opensearch.knn.common.KNNValidationUtil.validateFloatVectorValue; + +/** + * Validates per dimension fields + */ +public interface PerDimensionValidator { + /** + * Validates the given float is valid for the configuration + * + * @param value to validate + */ + default void validate(float value) {} + + /** + * Validates the given float as a byte is valid for the configuration. + * + * @param value to validate + */ + default void validateByte(float value) {} + + PerDimensionValidator DEFAULT_FLOAT_VALIDATOR = new PerDimensionValidator() { + @Override + public void validate(float value) { + validateFloatVectorValue(value); + } + + @Override + public void validateByte(float value) { + throw new IllegalStateException("DEFAULT_FLOAT_VALIDATOR should only be used for float vectors"); + } + }; + + PerDimensionValidator DEFAULT_BYTE_VALIDATOR = new PerDimensionValidator() { + @Override + public void validate(float value) { + throw new IllegalStateException("DEFAULT_BYTE_VALIDATOR should only be used for byte values"); + } + + @Override + public void validateByte(float value) { + validateByteVectorValue(value, VectorDataType.BYTE); + } + }; + + PerDimensionValidator DEFAULT_BIT_VALIDATOR = new PerDimensionValidator() { + @Override + public void validate(float value) { + throw new IllegalStateException("DEFAULT_BIT_VALIDATOR should only be used for byte values"); + } + + @Override + public void validateByte(float value) { + validateByteVectorValue(value, VectorDataType.BINARY); + } + }; +} diff --git a/src/main/java/org/opensearch/knn/index/mapper/SpaceVectorValidator.java b/src/main/java/org/opensearch/knn/index/mapper/SpaceVectorValidator.java new file mode 100644 index 000000000..6ff088604 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/mapper/SpaceVectorValidator.java @@ -0,0 +1,28 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.mapper; + +import lombok.AllArgsConstructor; +import org.opensearch.knn.index.SpaceType; + +/** + * Confirms that a given vector is valid for the provided space type + */ +@AllArgsConstructor +public class SpaceVectorValidator implements VectorValidator { + + private final SpaceType spaceType; + + @Override + public void validateVector(byte[] vector) { + spaceType.validateVector(vector); + } + + @Override + public void validateVector(float[] vector) { + spaceType.validateVector(vector); + } +} diff --git a/src/main/java/org/opensearch/knn/index/mapper/VectorValidator.java b/src/main/java/org/opensearch/knn/index/mapper/VectorValidator.java new file mode 100644 index 000000000..f4253ae37 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/mapper/VectorValidator.java @@ -0,0 +1,28 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.mapper; + +/** + * Class validates vector after it has been parsed + */ +public interface VectorValidator { + /** + * Validate if the given byte vector is supported + * + * @param vector the given vector + */ + default void validateVector(byte[] vector) {} + + /** + * Validate if the given float vector is supported + * + * @param vector the given vector + */ + default void validateVector(float[] vector) {} + + VectorValidator NOOP_VECTOR_VALIDATOR = new VectorValidator() { + }; +} diff --git a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java index 9279c816f..9ac3caa23 100644 --- a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java +++ b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryAllocation.java @@ -11,11 +11,16 @@ package org.opensearch.knn.index.memory; +import lombok.Getter; +import lombok.Setter; import org.apache.lucene.index.LeafReaderContext; +import org.opensearch.knn.common.featureflags.KNNFeatureFlags; +import org.opensearch.common.concurrent.RefCountedReleasable; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.index.query.KNNWeight; import org.opensearch.knn.jni.JNIService; -import org.opensearch.knn.index.util.KNNEngine; -import org.opensearch.watcher.FileWatcher; -import org.opensearch.watcher.WatcherHandle; +import org.opensearch.knn.index.engine.KNNEngine; import java.util.concurrent.ExecutorService; import java.util.concurrent.Semaphore; @@ -75,6 +80,26 @@ public interface NativeMemoryAllocation { */ int getSizeInKB(); + /** + * Increments the refCount of this instance. + * + * @see #decRef + * @throws IllegalStateException iff the reference counter can not be incremented. + */ + default void incRef() {} + + /** + * Decreases the refCount of this instance. If the refCount drops to 0, then this + * instance is considered as closed and should not be used anymore. + * + * @see #incRef + * + * @return returns {@code true} if the ref count dropped to 0 as a result of calling this method + */ + default boolean decRef() { + return true; + } + /** * Represents native indices loaded into memory. Because these indices are backed by files, they should be * freed when file is deleted. @@ -83,52 +108,99 @@ class IndexAllocation implements NativeMemoryAllocation { private final ExecutorService executor; private final long memoryAddress; - private final int size; + private final int sizeKb; private volatile boolean closed; + @Getter private final KNNEngine knnEngine; - private final String indexPath; + @Getter + private final String vectorFileName; + @Getter private final String openSearchIndexName; private final ReadWriteLock readWriteLock; - private final WatcherHandle watcherHandle; + private final SharedIndexState sharedIndexState; + @Getter + private final boolean isBinaryIndex; + private final RefCountedReleasable refCounted; /** * Constructor * * @param executorService Executor service used to close the allocation * @param memoryAddress Pointer in memory to the index - * @param size Size this index consumes in kilobytes + * @param sizeKb Size this index consumes in kilobytes * @param knnEngine KNNEngine associated with the index allocation - * @param indexPath File path to index + * @param vectorFileName Vector file name. Ex: _0_165_my_field.faiss * @param openSearchIndexName Name of OpenSearch index this index is associated with - * @param watcherHandle Handle for watching index file */ IndexAllocation( ExecutorService executorService, long memoryAddress, - int size, + int sizeKb, KNNEngine knnEngine, - String indexPath, + String vectorFileName, + String openSearchIndexName + ) { + this(executorService, memoryAddress, sizeKb, knnEngine, vectorFileName, openSearchIndexName, null, false); + } + + /** + * Constructor + * + * @param executorService Executor service used to close the allocation + * @param memoryAddress Pointer in memory to the index + * @param sizeKb Size this index consumes in kilobytes + * @param knnEngine KNNEngine associated with the index allocation + * @param vectorFileName Vector file name. Ex: _0_165_my_field.faiss + * @param openSearchIndexName Name of OpenSearch index this index is associated with + * @param sharedIndexState Shared index state. If not shared state present, pass null. + */ + IndexAllocation( + ExecutorService executorService, + long memoryAddress, + int sizeKb, + KNNEngine knnEngine, + String vectorFileName, String openSearchIndexName, - WatcherHandle watcherHandle + SharedIndexState sharedIndexState, + boolean isBinaryIndex ) { this.executor = executorService; this.closed = false; this.knnEngine = knnEngine; - this.indexPath = indexPath; + this.vectorFileName = vectorFileName; this.openSearchIndexName = openSearchIndexName; this.memoryAddress = memoryAddress; this.readWriteLock = new ReentrantReadWriteLock(); - this.size = size; - this.watcherHandle = watcherHandle; + this.sizeKb = sizeKb; + this.sharedIndexState = sharedIndexState; + this.isBinaryIndex = isBinaryIndex; + this.refCounted = new RefCountedReleasable<>("IndexAllocation-Reference", this, this::closeInternal); + } + + protected void closeInternal() { + Runnable onClose = () -> { + writeLock(); + try { + cleanup(); + } finally { + writeUnlock(); + } + }; + + // The close operation needs to be blocking to prevent overflow + // This blocks any entry until the close has completed, preventing creation before close scenarios + if (KNNFeatureFlags.isForceEvictCacheEnabled()) { + onClose.run(); + } else { + executor.execute(onClose); + } } @Override public void close() { - executor.execute(() -> { - writeLock(); - cleanup(); - writeUnlock(); - }); + if (!closed && refCounted.refCount() > 0) { + refCounted.close(); + } } private void cleanup() { @@ -138,11 +210,13 @@ private void cleanup() { this.closed = true; - watcherHandle.stop(); - // memoryAddress is sometimes initialized to 0. If this is ever the case, freeing will surely fail. if (memoryAddress != 0) { - JNIService.free(memoryAddress, knnEngine.getName()); + JNIService.free(memoryAddress, knnEngine, isBinaryIndex); + } + + if (sharedIndexState != null) { + SharedIndexStateManager.getInstance().release(sharedIndexState); } } @@ -158,7 +232,7 @@ public long getMemoryAddress() { /** * The read lock will be obtained in the - * {@link org.opensearch.knn.index.KNNWeight#scorer(LeafReaderContext context) scorer} when a native index needs + * {@link KNNWeight#scorer(LeafReaderContext context) scorer} when a native index needs * to be queried. */ @Override @@ -188,34 +262,17 @@ public void writeUnlock() { @Override public int getSizeInKB() { - return size; + return sizeKb; } - /** - * Getter for k-NN Engine associated with this index allocation. - * - * @return KNNEngine associated with index allocation - */ - public KNNEngine getKnnEngine() { - return knnEngine; - } - - /** - * Getter for the path to the file from which the index was loaded. - * - * @return indexPath to index - */ - public String getIndexPath() { - return indexPath; + @Override + public void incRef() { + refCounted.incRef(); } - /** - * Getter for the OpenSearch index associated with the native index. - * - * @return OpenSearch index name - */ - public String getOpenSearchIndexName() { - return openSearchIndexName; + @Override + public boolean decRef() { + return refCounted.decRef(); } } @@ -227,38 +284,47 @@ class TrainingDataAllocation implements NativeMemoryAllocation { private final ExecutorService executor; private volatile boolean closed; + @Setter private long memoryAddress; - private final int size; + private final int sizeKb; + @Getter + @Setter + private QuantizationConfig quantizationConfig = QuantizationConfig.EMPTY; // Implement reader/writer with semaphores to deal with passing lock conditions between threads private int readCount; - private Semaphore readSemaphore; - private Semaphore writeSemaphore; + private final Semaphore readSemaphore; + private final Semaphore writeSemaphore; + private final VectorDataType vectorDataType; /** * Constructor * * @param executor Executor used for allocation close * @param memoryAddress pointer in memory to the training data allocation - * @param size amount memory needed for allocation in kilobytes + * @param sizeKb amount memory needed for allocation in kilobytes */ - TrainingDataAllocation(ExecutorService executor, long memoryAddress, int size) { + public TrainingDataAllocation(ExecutorService executor, long memoryAddress, int sizeKb, VectorDataType vectorDataType) { this.executor = executor; this.closed = false; this.memoryAddress = memoryAddress; - this.size = size; + this.sizeKb = sizeKb; this.readCount = 0; this.readSemaphore = new Semaphore(1); this.writeSemaphore = new Semaphore(1); + this.vectorDataType = vectorDataType; } @Override public void close() { executor.execute(() -> { writeLock(); - cleanup(); - writeUnlock(); + try { + cleanup(); + } finally { + writeUnlock(); + } }); } @@ -281,7 +347,7 @@ private void cleanup() { closed = true; if (this.memoryAddress != 0) { - JNIService.freeVectors(this.memoryAddress); + vectorDataType.freeNativeMemory(this.memoryAddress); } } @@ -326,7 +392,7 @@ public void readLock() { /** * A write lock will be obtained either on eviction from {@link NativeMemoryCacheManager NativeMemoryManager's} * or when training data is actually being loaded. A semaphore is used because collecting training data - * happens asynchrously, so the thread that obtains the lock will not be the same thread that releases the + * happens asynchronously, so the thread that obtains the lock will not be the same thread that releases the * lock. */ @Override @@ -363,23 +429,14 @@ public void writeUnlock() { @Override public int getSizeInKB() { - return size; - } - - /** - * Setter for memory address to training data - * - * @param memoryAddress Pointer to training data - */ - public void setMemoryAddress(long memoryAddress) { - this.memoryAddress = memoryAddress; + return sizeKb; } } /** * An anonymous allocation is used to reserve space in the native memory cache. It does not have a * memory address. This allocation type should be used when a function allocates a large portion of memory in the - * function, runs for awhile, and then frees it. + * function, runs for a while, and then frees it. */ class AnonymousAllocation implements NativeMemoryAllocation { diff --git a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryCacheManager.java b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryCacheManager.java index efdc4fd31..b8aecc5a5 100644 --- a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryCacheManager.java +++ b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryCacheManager.java @@ -21,12 +21,17 @@ import org.apache.logging.log4j.Logger; import org.opensearch.common.unit.TimeValue; import org.opensearch.knn.common.exception.OutOfNativeMemoryException; +import org.opensearch.knn.common.featureflags.KNNFeatureFlags; import org.opensearch.knn.index.KNNSettings; import org.opensearch.knn.plugin.stats.StatNames; import java.io.Closeable; +import java.util.Deque; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -40,11 +45,12 @@ public class NativeMemoryCacheManager implements Closeable { public static String GRAPH_COUNT = "graph_count"; - private static Logger logger = LogManager.getLogger(NativeMemoryCacheManager.class); + private static final Logger logger = LogManager.getLogger(NativeMemoryCacheManager.class); private static NativeMemoryCacheManager INSTANCE; private Cache cache; - private ExecutorService executor; + private Deque accessRecencyQueue; + private final ExecutorService executor; private AtomicBoolean cacheCapacityReached; private long maxWeight; @@ -68,24 +74,35 @@ public static synchronized NativeMemoryCacheManager getInstance() { } private void initialize() { + initialize( + NativeMemoryCacheManagerDto.builder() + .isWeightLimited(KNNSettings.state().getSettingValue(KNNSettings.KNN_MEMORY_CIRCUIT_BREAKER_ENABLED)) + .maxWeight(KNNSettings.getCircuitBreakerLimit().getKb()) + .isExpirationLimited(KNNSettings.state().getSettingValue(KNNSettings.KNN_CACHE_ITEM_EXPIRY_ENABLED)) + .expiryTimeInMin( + ((TimeValue) KNNSettings.state().getSettingValue(KNNSettings.KNN_CACHE_ITEM_EXPIRY_TIME_MINUTES)).getMinutes() + ) + .build() + ); + } + + private void initialize(NativeMemoryCacheManagerDto nativeMemoryCacheDTO) { CacheBuilder cacheBuilder = CacheBuilder.newBuilder() .recordStats() .concurrencyLevel(1) .removalListener(this::onRemoval); - if (KNNSettings.state().getSettingValue(KNNSettings.KNN_MEMORY_CIRCUIT_BREAKER_ENABLED)) { - maxWeight = KNNSettings.getCircuitBreakerLimit().getKb(); - cacheBuilder.maximumWeight(maxWeight).weigher((k, v) -> v.getSizeInKB()); + if (nativeMemoryCacheDTO.isWeightLimited()) { + this.maxWeight = nativeMemoryCacheDTO.getMaxWeight(); + cacheBuilder.maximumWeight(this.maxWeight).weigher((k, v) -> v.getSizeInKB()); } - if (KNNSettings.state().getSettingValue(KNNSettings.KNN_CACHE_ITEM_EXPIRY_ENABLED)) { - long expiryTime = ((TimeValue) KNNSettings.state().getSettingValue(KNNSettings.KNN_CACHE_ITEM_EXPIRY_TIME_MINUTES)) - .getMinutes(); - cacheBuilder.expireAfterAccess(expiryTime, TimeUnit.MINUTES); + if (nativeMemoryCacheDTO.isExpirationLimited()) { + cacheBuilder.expireAfterAccess(nativeMemoryCacheDTO.getExpiryTimeInMin(), TimeUnit.MINUTES); } cacheCapacityReached = new AtomicBoolean(false); - + accessRecencyQueue = new ConcurrentLinkedDeque<>(); cache = cacheBuilder.build(); } @@ -93,13 +110,32 @@ private void initialize() { * Evicts all entries from the cache and rebuilds. */ public synchronized void rebuildCache() { + rebuildCache( + NativeMemoryCacheManagerDto.builder() + .isWeightLimited(KNNSettings.state().getSettingValue(KNNSettings.KNN_MEMORY_CIRCUIT_BREAKER_ENABLED)) + .maxWeight(KNNSettings.getCircuitBreakerLimit().getKb()) + .isExpirationLimited(KNNSettings.state().getSettingValue(KNNSettings.KNN_CACHE_ITEM_EXPIRY_ENABLED)) + .expiryTimeInMin( + ((TimeValue) KNNSettings.state().getSettingValue(KNNSettings.KNN_CACHE_ITEM_EXPIRY_TIME_MINUTES)).getMinutes() + ) + .build() + ); + } + + /** + * Evict all entries from the cache and rebuilds + * + * @param nativeMemoryCacheDTO DTO for cache configuration + */ + public synchronized void rebuildCache(NativeMemoryCacheManagerDto nativeMemoryCacheDTO) { logger.info("KNN Cache rebuilding."); - // TODO: Does this really need to be executed with an executor? Also, does invalidateAll really need to be - // called? + // TODO: Does this really need to be executed with an executor? executor.execute(() -> { + // Explicitly invalidate all so that we do not have to wait for garbage collection to be invoked to + // free up native memory cache.invalidateAll(); - initialize(); + initialize(nativeMemoryCacheDTO); }); } @@ -255,8 +291,8 @@ public CacheStats getCacheStats() { public NativeMemoryAllocation get(NativeMemoryEntryContext nativeMemoryEntryContext, boolean isAbleToTriggerEviction) throws ExecutionException { if (!isAbleToTriggerEviction - && !cache.asMap().containsKey(nativeMemoryEntryContext.getKey()) - && maxWeight - getCacheSizeInKilobytes() - nativeMemoryEntryContext.calculateSizeInKB() <= 0) { + && (maxWeight - getCacheSizeInKilobytes() - nativeMemoryEntryContext.calculateSizeInKB()) <= 0 + && !cache.asMap().containsKey(nativeMemoryEntryContext.getKey())) { throw new OutOfNativeMemoryException( "Entry cannot be loaded into cache because it would not fit. " + "Entry size: " @@ -270,7 +306,69 @@ public NativeMemoryAllocation get(NativeMemoryEntryContext nativeMemoryEntryC ); } - return cache.get(nativeMemoryEntryContext.getKey(), nativeMemoryEntryContext::load); + if (KNNFeatureFlags.isForceEvictCacheEnabled()) { + // Utilizes a force eviction mechanism to free up memory before the entry can be added to the cache + // In case of a cache hit, the operation just updates the locally maintained recency list + // In case of a cache miss, least recently accessed entries are evicted in a blocking manner + // before the new entry can be added to the cache. + String key = nativeMemoryEntryContext.getKey(); + NativeMemoryAllocation result = cache.getIfPresent(key); + + // Cache Hit + // In case of a cache hit, moving the item to the end of the recency queue adds + // some overhead to the get operation. This can be optimized further to make this operation + // as lightweight as possible. Multiple approaches and their outcomes were documented + // before moving forward with the current solution. + // The details are outlined here: https://github.com/opensearch-project/k-NN/pull/2015#issuecomment-2327064680 + if (result != null) { + accessRecencyQueue.remove(key); + accessRecencyQueue.addLast(key); + return result; + } + + // Cache Miss + // Evict before put + synchronized (this) { + if (getCacheSizeInKilobytes() + nativeMemoryEntryContext.calculateSizeInKB() >= maxWeight) { + Iterator lruIterator = accessRecencyQueue.iterator(); + while (lruIterator.hasNext() + && (getCacheSizeInKilobytes() + nativeMemoryEntryContext.calculateSizeInKB() >= maxWeight)) { + + String keyToRemove = lruIterator.next(); + NativeMemoryAllocation allocationToRemove = cache.getIfPresent(keyToRemove); + if (allocationToRemove != null) { + allocationToRemove.close(); + cache.invalidate(keyToRemove); + } + lruIterator.remove(); + } + } + + result = cache.get(key, nativeMemoryEntryContext::load); + accessRecencyQueue.addLast(key); + + return result; + } + } else { + return cache.get(nativeMemoryEntryContext.getKey(), nativeMemoryEntryContext::load); + } + } + + /** + * Returns the NativeMemoryAllocation associated with given index + * @param indexName name of OpenSearch index + * @return NativeMemoryAllocation associated with given index + */ + public Optional getIndexMemoryAllocation(String indexName) { + Validate.notNull(indexName, "Index name cannot be null"); + return cache.asMap() + .values() + .stream() + .filter(nativeMemoryAllocation -> nativeMemoryAllocation instanceof NativeMemoryAllocation.IndexAllocation) + .filter( + indexAllocation -> indexName.equals(((NativeMemoryAllocation.IndexAllocation) indexAllocation).getOpenSearchIndexName()) + ) + .findFirst(); } /** diff --git a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryCacheManagerDto.java b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryCacheManagerDto.java new file mode 100644 index 000000000..e5c1484ed --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryCacheManagerDto.java @@ -0,0 +1,18 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.memory; + +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +public class NativeMemoryCacheManagerDto { + boolean isWeightLimited; + long maxWeight; + boolean isExpirationLimited; + long expiryTimeInMin; +} diff --git a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryEntryContext.java b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryEntryContext.java index 13f8dae10..0af13fb46 100644 --- a/src/main/java/org/opensearch/knn/index/memory/NativeMemoryEntryContext.java +++ b/src/main/java/org/opensearch/knn/index/memory/NativeMemoryEntryContext.java @@ -11,13 +11,17 @@ package org.opensearch.knn.index.memory; +import lombok.Getter; +import org.apache.lucene.store.Directory; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.knn.index.IndexUtil; +import org.opensearch.common.Nullable; +import org.opensearch.knn.index.codec.util.NativeMemoryCacheKeyHelper; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.index.VectorDataType; import java.io.IOException; import java.util.Map; import java.util.UUID; -import java.util.function.Function; /** * Encapsulates all information needed to load a component into native memory. @@ -60,69 +64,77 @@ public String getKey() { public static class IndexEntryContext extends NativeMemoryEntryContext { + @Getter + private final Directory directory; private final NativeMemoryLoadStrategy.IndexLoadStrategy indexLoadStrategy; + @Getter private final String openSearchIndexName; + @Getter private final Map parameters; + @Nullable + @Getter + private final String modelId; /** * Constructor * - * @param indexPath path to index file. Also used as key in cache. + * @param directory Lucene directory to create required IndexInput/IndexOutput to access files. + * @param vectorIndexCacheKey Cache key for {@link NativeMemoryCacheManager}. It must contain a vector file name. + * @param indexLoadStrategy Strategy to load index into memory + * @param parameters Load time parameters + * @param openSearchIndexName Opensearch index associated with index + */ + public IndexEntryContext( + Directory directory, + String vectorIndexCacheKey, + NativeMemoryLoadStrategy.IndexLoadStrategy indexLoadStrategy, + Map parameters, + String openSearchIndexName + ) { + this(directory, vectorIndexCacheKey, indexLoadStrategy, parameters, openSearchIndexName, null); + } + + /** + * Constructor + * + * @param directory Lucene directory to create required IndexInput/IndexOutput to access files. + * @param vectorIndexCacheKey Cache key for {@link NativeMemoryCacheManager}. It must contain a vector file name. * @param indexLoadStrategy strategy to load index into memory * @param parameters load time parameters * @param openSearchIndexName opensearch index associated with index + * @param modelId model to be loaded. If none available, pass null */ public IndexEntryContext( - String indexPath, + Directory directory, + String vectorIndexCacheKey, NativeMemoryLoadStrategy.IndexLoadStrategy indexLoadStrategy, Map parameters, - String openSearchIndexName + String openSearchIndexName, + String modelId ) { - super(indexPath); + super(vectorIndexCacheKey); + this.directory = directory; this.indexLoadStrategy = indexLoadStrategy; this.openSearchIndexName = openSearchIndexName; this.parameters = parameters; + this.modelId = modelId; } @Override public Integer calculateSizeInKB() { - return IndexSizeCalculator.INSTANCE.apply(this); + final String indexFileName = NativeMemoryCacheKeyHelper.extractVectorIndexFileName(key); + try { + final long fileLength = directory.fileLength(indexFileName); + return (int) (fileLength / 1024L); + } catch (IOException e) { + throw new RuntimeException(e); + } } @Override public NativeMemoryAllocation.IndexAllocation load() throws IOException { return indexLoadStrategy.load(this); } - - /** - * Getter for OpenSearch index name. - * - * @return OpenSearch index name - */ - public String getOpenSearchIndexName() { - return openSearchIndexName; - } - - /** - * Getter for parameters. - * - * @return parameters - */ - public Map getParameters() { - return parameters; - } - - private static class IndexSizeCalculator implements Function { - - static IndexSizeCalculator INSTANCE = new IndexSizeCalculator(); - - IndexSizeCalculator() {} - - @Override - public Integer apply(IndexEntryContext indexEntryContext) { - return IndexUtil.getFileSizeInKB(indexEntryContext.getKey()); - } - } } public static class TrainingDataEntryContext extends NativeMemoryEntryContext { @@ -137,6 +149,9 @@ public static class TrainingDataEntryContext extends NativeMemoryEntryContext, @@ -49,8 +52,6 @@ class IndexLoadStrategy private static IndexLoadStrategy INSTANCE; private final ExecutorService executor; - private final FileChangesListener indexFileOnDeleteListener; - private ResourceWatcherService resourceWatcherService; /** * Get Singleton of this load strategy. @@ -64,45 +65,61 @@ public static synchronized IndexLoadStrategy getInstance() { return INSTANCE; } - /** - * Initialize singleton. - * - * @param resourceWatcherService service used to monitor index files for deletion - */ - public static void initialize(final ResourceWatcherService resourceWatcherService) { - getInstance().resourceWatcherService = resourceWatcherService; - } - private IndexLoadStrategy() { executor = Executors.newSingleThreadExecutor(); - indexFileOnDeleteListener = new FileChangesListener() { - @Override - public void onFileDeleted(Path indexFilePath) { - NativeMemoryCacheManager.getInstance().invalidate(indexFilePath.toString()); - } - }; } @Override public NativeMemoryAllocation.IndexAllocation load(NativeMemoryEntryContext.IndexEntryContext indexEntryContext) throws IOException { - Path indexPath = Paths.get(indexEntryContext.getKey()); - FileWatcher fileWatcher = new FileWatcher(indexPath); - fileWatcher.addListener(indexFileOnDeleteListener); - fileWatcher.init(); + // Extract vector file name from the given cache key. + // Ex: _0_165_my_field.faiss@1vaqiupVUwvkXAG4Qc/RPg== + final String cacheKey = indexEntryContext.getKey(); + final String vectorFileName = NativeMemoryCacheKeyHelper.extractVectorIndexFileName(cacheKey); + if (vectorFileName == null) { + throw new IllegalStateException( + "Invalid cache key was given. The key [" + cacheKey + "] does not contain the corresponding vector file name." + ); + } + + // Prepare for opening index input from directory. + final KNNEngine knnEngine = KNNEngine.getEngineNameFromPath(vectorFileName); + final Directory directory = indexEntryContext.getDirectory(); + final int indexSizeKb = Math.toIntExact(directory.fileLength(vectorFileName) / 1024); - KNNEngine knnEngine = KNNEngine.getEngineNameFromPath(indexPath.toString()); - long memoryAddress = JNIService.loadIndex(indexPath.toString(), indexEntryContext.getParameters(), knnEngine.getName()); - final WatcherHandle watcherHandle = resourceWatcherService.add(fileWatcher); + // Try to open an index input then pass it down to native engine for loading an index. + try (IndexInput readStream = directory.openInput(vectorFileName, IOContext.READONCE)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(readStream); + final long indexAddress = JNIService.loadIndex(indexInputWithBuffer, indexEntryContext.getParameters(), knnEngine); + + return createIndexAllocation(indexEntryContext, knnEngine, indexAddress, indexSizeKb, vectorFileName); + } + } + + private NativeMemoryAllocation.IndexAllocation createIndexAllocation( + final NativeMemoryEntryContext.IndexEntryContext indexEntryContext, + final KNNEngine knnEngine, + final long indexAddress, + final int indexSizeKb, + final String vectorFileName + ) { + SharedIndexState sharedIndexState = null; + String modelId = indexEntryContext.getModelId(); + if (IndexUtil.isSharedIndexStateRequired(knnEngine, modelId, indexAddress)) { + log.info("Index with model: \"{}\" requires shared state. Retrieving shared state.", modelId); + sharedIndexState = SharedIndexStateManager.getInstance().get(indexAddress, modelId, knnEngine); + JNIService.setSharedIndexState(indexAddress, sharedIndexState.getSharedIndexStateAddress(), knnEngine); + } return new NativeMemoryAllocation.IndexAllocation( executor, - memoryAddress, - indexEntryContext.calculateSizeInKB(), + indexAddress, + indexSizeKb, knnEngine, - indexPath.toString(), + vectorFileName, indexEntryContext.getOpenSearchIndexName(), - watcherHandle + sharedIndexState, + IndexUtil.isBinaryIndex(knnEngine, indexEntryContext.getParameters()) ); } @@ -117,7 +134,7 @@ class TrainingLoadStrategy NativeMemoryLoadStrategy, Closeable { - private static TrainingLoadStrategy INSTANCE; + private static volatile TrainingLoadStrategy INSTANCE; private final ExecutorService executor; private VectorReader vectorReader; @@ -155,11 +172,15 @@ public NativeMemoryAllocation.TrainingDataAllocation load( NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation = new NativeMemoryAllocation.TrainingDataAllocation( executor, 0, - nativeMemoryEntryContext.calculateSizeInKB() + nativeMemoryEntryContext.calculateSizeInKB(), + nativeMemoryEntryContext.getVectorDataType() ); - // Start loading all training data. Once the data has been loaded, release the lock - TrainingDataConsumer trainingDataConsumer = new TrainingDataConsumer(trainingDataAllocation); + QuantizationConfig quantizationConfig = nativeMemoryEntryContext.getQuantizationConfig(); + trainingDataAllocation.setQuantizationConfig(quantizationConfig); + + TrainingDataConsumer vectorDataConsumer = nativeMemoryEntryContext.getVectorDataType() + .getTrainingDataConsumer(trainingDataAllocation); trainingDataAllocation.writeLock(); @@ -169,7 +190,7 @@ public NativeMemoryAllocation.TrainingDataAllocation load( nativeMemoryEntryContext.getTrainFieldName(), nativeMemoryEntryContext.getMaxVectorCount(), nativeMemoryEntryContext.getSearchSize(), - trainingDataConsumer, + vectorDataConsumer, ActionListener.wrap(response -> trainingDataAllocation.writeUnlock(), ex -> { // Close unsafe will assume that the caller passes control of the writelock to it. It // will then handle releasing the write lock once the close operations finish. diff --git a/src/main/java/org/opensearch/knn/index/memory/SharedIndexState.java b/src/main/java/org/opensearch/knn/index/memory/SharedIndexState.java new file mode 100644 index 000000000..ba936309b --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/memory/SharedIndexState.java @@ -0,0 +1,21 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.memory; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.opensearch.knn.index.engine.KNNEngine; + +/** + * Class stores information about the shared memory allocations between loaded native indices. + */ +@RequiredArgsConstructor +@Getter +public class SharedIndexState { + private final long sharedIndexStateAddress; + private final String modelId; + private final KNNEngine knnEngine; +} diff --git a/src/main/java/org/opensearch/knn/index/memory/SharedIndexStateManager.java b/src/main/java/org/opensearch/knn/index/memory/SharedIndexStateManager.java new file mode 100644 index 000000000..82c5b0a28 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/memory/SharedIndexStateManager.java @@ -0,0 +1,150 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.memory; + +import com.google.common.annotations.VisibleForTesting; +import lombok.Getter; +import lombok.extern.log4j.Log4j2; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.jni.JNIService; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Class manages allocations that can be shared between native indices. No locking is required. + * Once a caller obtain an instance of a {@link org.opensearch.knn.index.memory.SharedIndexState}, it is guaranteed to + * be valid until it is returned. {@link org.opensearch.knn.index.memory.SharedIndexState} are reference counted + * internally. Once the reference count goes to 0, it will be freed. + */ +@Log4j2 +class SharedIndexStateManager { + // Map storing the shared index state with key being the modelId. + private final ConcurrentHashMap sharedIndexStateCache; + private final ReadWriteLock readWriteLock; + + private static SharedIndexStateManager INSTANCE; + + // TODO: Going to refactor away from doing this in the future. For now, keeping for simplicity. + public static synchronized SharedIndexStateManager getInstance() { + if (INSTANCE == null) { + INSTANCE = new SharedIndexStateManager(); + } + return INSTANCE; + } + + /** + * Constructor + */ + @VisibleForTesting + SharedIndexStateManager() { + this.sharedIndexStateCache = new ConcurrentHashMap<>(); + this.readWriteLock = new ReentrantReadWriteLock(); + } + + /** + * Return a {@link SharedIndexState} associated with the key. If no value exists, it will attempt to create it. + * Once returned, the {@link SharedIndexState} will be valid until + * {@link SharedIndexStateManager#release(SharedIndexState)} is called. Caller must ensure that this is + * called after it is done using it. + * + * In order to create the shared state, it will use the indexAddress passed in to create the shared state from + * using {@link org.opensearch.knn.jni.JNIService#initSharedIndexState(long, KNNEngine)}. + * + * @param indexAddress Address of index to initialize the shared state from + * @param knnEngine engine index belongs to + * @return ShareModelContext + */ + public SharedIndexState get(long indexAddress, String modelId, KNNEngine knnEngine) { + this.readWriteLock.readLock().lock(); + try { + // This can be done safely with readLock because the ConcurrentHasMap.computeIfAbsent guarantees: + // + // "If the specified key is not already associated with a value, attempts to compute its value using the given + // mapping function and enters it into this map unless null. The entire method invocation is performed + // atomically, so the function is applied at most once per key. Some attempted update operations on this map + // by other threads may be blocked while computation is in progress, so the computation should be short and + // simple, and must not attempt to update any other mappings of this map." + // + // Ref: + // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html#computeIfAbsent-K-java.util.function.Function- + SharedIndexStateEntry entry = sharedIndexStateCache.computeIfAbsent(modelId, m -> { + log.info("Loading entry to shared index state cache for model {}", modelId); + long sharedIndexStateAddress = JNIService.initSharedIndexState(indexAddress, knnEngine); + return new SharedIndexStateEntry(new SharedIndexState(sharedIndexStateAddress, modelId, knnEngine)); + }); + entry.incRef(); + return entry.getSharedIndexState(); + } finally { + this.readWriteLock.readLock().unlock(); + } + } + + /** + * Indicate that the {@link SharedIndexState} is no longer being used. If nothing else is using it, it will be + * removed from the cache and evicted. + * + * After calling this method, {@link SharedIndexState} should no longer be used by calling thread. + * + * @param sharedIndexState to return to the system. + */ + public void release(SharedIndexState sharedIndexState) { + this.readWriteLock.writeLock().lock(); + + try { + SharedIndexStateEntry sharedIndexStateEntry; + if ((sharedIndexStateEntry = sharedIndexStateCache.get(sharedIndexState.getModelId())) == null) { + // This should not happen. Will log the error and return to prevent crash + log.error("Attempting to evict model from cache but it is not present: {}", sharedIndexState.getModelId()); + return; + } + + if (sharedIndexStateEntry.decRef() <= 0) { + log.info("Evicting entry from shared index state cache for key {}", sharedIndexState.getModelId()); + sharedIndexStateCache.remove(sharedIndexState.getModelId()); + JNIService.freeSharedIndexState(sharedIndexState.getSharedIndexStateAddress(), sharedIndexState.getKnnEngine()); + } + } finally { + this.readWriteLock.writeLock().unlock(); + } + } + + private static final class SharedIndexStateEntry { + @Getter + private final SharedIndexState sharedIndexState; + private final AtomicLong referenceCount; + + /** + * Constructor + * + * @param sharedIndexState sharedIndexStateContext being wrapped + */ + private SharedIndexStateEntry(SharedIndexState sharedIndexState) { + this.sharedIndexState = sharedIndexState; + this.referenceCount = new AtomicLong(0); + } + + /** + * Increases reference count by 1 + * + * @return ++referenceCount + */ + private long incRef() { + return referenceCount.incrementAndGet(); + } + + /** + * Decrease reference count by 1 + * + * @return --referenceCount + */ + private long decRef() { + return referenceCount.decrementAndGet(); + } + } +} diff --git a/src/main/java/org/opensearch/knn/index/quantizationservice/KNNVectorQuantizationTrainingRequest.java b/src/main/java/org/opensearch/knn/index/quantizationservice/KNNVectorQuantizationTrainingRequest.java new file mode 100644 index 000000000..f7ee12904 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/quantizationservice/KNNVectorQuantizationTrainingRequest.java @@ -0,0 +1,55 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.quantizationservice; + +import lombok.extern.log4j.Log4j2; +import org.opensearch.knn.index.vectorvalues.KNNVectorValues; +import org.opensearch.knn.quantization.models.requests.TrainingRequest; + +import java.io.IOException; + +import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS; + +/** + * KNNVectorQuantizationTrainingRequest is a concrete implementation of the abstract TrainingRequest class. + * It provides a mechanism to retrieve float vectors from the KNNVectorValues by document ID. + */ +@Log4j2 +final class KNNVectorQuantizationTrainingRequest extends TrainingRequest { + + private final KNNVectorValues knnVectorValues; + private int lastIndex; + + /** + * Constructs a new QuantizationFloatVectorTrainingRequest. + * + * @param knnVectorValues the KNNVectorValues instance containing the vectors. + */ + KNNVectorQuantizationTrainingRequest(KNNVectorValues knnVectorValues, long liveDocs) { + super((int) liveDocs); + this.knnVectorValues = knnVectorValues; + this.lastIndex = 0; + } + + /** + * Retrieves the vector associated with the specified document ID. + * + * @param position the document ID. + * @return the float vector corresponding to the specified document ID, or null if the docId is invalid. + */ + @Override + public T getVectorAtThePosition(int position) throws IOException { + while (lastIndex <= position) { + lastIndex++; + if (knnVectorValues.docId() == NO_MORE_DOCS) { + return null; + } + knnVectorValues.nextDoc(); + } + // Return the vector + return knnVectorValues.getVector(); + } +} diff --git a/src/main/java/org/opensearch/knn/index/quantizationservice/QuantizationService.java b/src/main/java/org/opensearch/knn/index/quantizationservice/QuantizationService.java new file mode 100644 index 000000000..771848730 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/quantizationservice/QuantizationService.java @@ -0,0 +1,131 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.quantizationservice; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.apache.lucene.index.FieldInfo; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.index.vectorvalues.KNNVectorValues; +import org.opensearch.knn.quantization.factory.QuantizerFactory; +import org.opensearch.knn.quantization.models.quantizationOutput.BinaryQuantizationOutput; +import org.opensearch.knn.quantization.models.quantizationOutput.QuantizationOutput; +import org.opensearch.knn.quantization.models.quantizationParams.QuantizationParams; +import org.opensearch.knn.quantization.models.quantizationParams.ScalarQuantizationParams; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; +import org.opensearch.knn.quantization.quantizer.Quantizer; +import java.io.IOException; + +import static org.opensearch.knn.common.FieldInfoExtractor.extractQuantizationConfig; + +/** + * A singleton class responsible for handling the quantization process, including training a quantizer + * and applying quantization to vectors. This class is designed to be thread-safe. + * + * @param The type of the input vectors to be quantized. + * @param The type of the quantized output vectors. + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class QuantizationService { + + /** + * The singleton instance of the {@link QuantizationService} class. + */ + private static final QuantizationService INSTANCE = new QuantizationService<>(); + + /** + * Returns the singleton instance of the {@link QuantizationService} class. + * + * @param The type of the input vectors to be quantized. + * @param The type of the quantized output vectors. + * @return The singleton instance of {@link QuantizationService}. + */ + public static QuantizationService getInstance() { + return (QuantizationService) INSTANCE; + } + + /** + * Trains a quantizer using the provided {@link KNNVectorValues} and returns the resulting + * {@link QuantizationState}. The quantizer is determined based on the given {@link QuantizationParams}. + * + * @param quantizationParams The {@link QuantizationParams} containing the parameters for quantization. + * @param knnVectorValues The {@link KNNVectorValues} representing the vector data to be used for training. + * @return The {@link QuantizationState} containing the state of the trained quantizer. + * @throws IOException If an I/O error occurs during the training process. + */ + public QuantizationState train( + final QuantizationParams quantizationParams, + final KNNVectorValues knnVectorValues, + final long liveDocs + ) throws IOException { + Quantizer quantizer = QuantizerFactory.getQuantizer(quantizationParams); + + // Create the training request from the vector values + KNNVectorQuantizationTrainingRequest trainingRequest = new KNNVectorQuantizationTrainingRequest<>(knnVectorValues, liveDocs); + + // Train the quantizer and return the quantization state + return quantizer.train(trainingRequest); + } + + /** + * Applies quantization to the given vector using the specified {@link QuantizationState} and + * {@link QuantizationOutput}. + * + * @param quantizationState The {@link QuantizationState} containing the state of the trained quantizer. + * @param vector The vector to be quantized. + * @param quantizationOutput The {@link QuantizationOutput} to store the quantized vector. + * @return The quantized vector as an object of type {@code R}. + */ + public R quantize(final QuantizationState quantizationState, final T vector, final QuantizationOutput quantizationOutput) { + Quantizer quantizer = QuantizerFactory.getQuantizer(quantizationState.getQuantizationParams()); + quantizer.quantize(vector, quantizationState, quantizationOutput); + return quantizationOutput.getQuantizedVector(); + } + + /** + * Retrieves quantization parameters from the FieldInfo. + */ + public QuantizationParams getQuantizationParams(final FieldInfo fieldInfo) { + QuantizationConfig quantizationConfig = extractQuantizationConfig(fieldInfo); + if (quantizationConfig != QuantizationConfig.EMPTY && quantizationConfig.getQuantizationType() != null) { + return new ScalarQuantizationParams(quantizationConfig.getQuantizationType()); + } + return null; + } + + /** + * Retrieves the appropriate {@link VectorDataType} to be used during the transfer of vectors for indexing or merging. + * This method is intended to determine the correct vector data type based on the provided {@link FieldInfo}. + * + * @param fieldInfo The {@link FieldInfo} object containing metadata about the field for which the vector data type + * is being determined. + * @return The {@link VectorDataType} to be used during the vector transfer process + */ + public VectorDataType getVectorDataTypeForTransfer(final FieldInfo fieldInfo) { + QuantizationConfig quantizationConfig = extractQuantizationConfig(fieldInfo); + if (quantizationConfig != QuantizationConfig.EMPTY && quantizationConfig.getQuantizationType() != null) { + return VectorDataType.BINARY; + } + return null; + } + + /** + * Creates the appropriate {@link QuantizationOutput} based on the given {@link QuantizationParams}. + * + * @param quantizationParams The {@link QuantizationParams} containing the parameters for quantization. + * @return The {@link QuantizationOutput} corresponding to the provided parameters. + * @throws IllegalArgumentException If the quantization parameters are unsupported. + */ + @SuppressWarnings("unchecked") + public QuantizationOutput createQuantizationOutput(final QuantizationParams quantizationParams) { + if (quantizationParams instanceof ScalarQuantizationParams) { + ScalarQuantizationParams scalarParams = (ScalarQuantizationParams) quantizationParams; + return (QuantizationOutput) new BinaryQuantizationOutput(scalarParams.getSqType().getId()); + } + throw new IllegalArgumentException("Unsupported quantization parameters: " + quantizationParams.getClass().getName()); + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/BaseQueryFactory.java b/src/main/java/org/opensearch/knn/index/query/BaseQueryFactory.java new file mode 100644 index 000000000..cfb604c18 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/BaseQueryFactory.java @@ -0,0 +1,103 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NonNull; +import lombok.extern.log4j.Log4j2; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.join.BitSetProducer; +import org.apache.lucene.search.join.ToChildBlockJoinQuery; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.QueryShardContext; +import org.opensearch.index.search.NestedHelper; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.query.rescore.RescoreContext; + +import java.io.IOException; +import java.util.Map; +import java.util.Optional; + +/** +* Base class for creating vector search queries. +*/ +@Log4j2 +public abstract class BaseQueryFactory { + /** + * DTO object to hold data required to create a Query instance. + */ + @AllArgsConstructor + @Builder + @Getter + public static class CreateQueryRequest { + @NonNull + private KNNEngine knnEngine; + @NonNull + private String indexName; + private String fieldName; + private float[] vector; + private byte[] byteVector; + private VectorDataType vectorDataType; + private Map methodParameters; + private Integer k; + private Float radius; + private QueryBuilder filter; + private QueryShardContext context; + private RescoreContext rescoreContext; + + public Optional getFilter() { + return Optional.ofNullable(filter); + } + + public Optional getContext() { + return Optional.ofNullable(context); + } + + public Optional getRescoreContext() { + return Optional.ofNullable(rescoreContext); + } + } + + /** + * Creates a query filter. + * + * @param createQueryRequest request object that has all required fields to construct the query + * @return Lucene Query + */ + protected static Query getFilterQuery(BaseQueryFactory.CreateQueryRequest createQueryRequest) { + if (!createQueryRequest.getFilter().isPresent()) { + return null; + } + + final QueryShardContext queryShardContext = createQueryRequest.getContext() + .orElseThrow(() -> new RuntimeException("Shard context cannot be null")); + log.debug( + String.format( + "Creating query with filter for index [%s], field [%s]", + createQueryRequest.getIndexName(), + createQueryRequest.getFieldName() + ) + ); + final Query filterQuery; + try { + filterQuery = createQueryRequest.getFilter().get().toQuery(queryShardContext); + } catch (IOException e) { + throw new RuntimeException("Cannot create query with filter", e); + } + BitSetProducer parentFilter = queryShardContext.getParentFilter(); + if (parentFilter != null) { + boolean mightMatch = new NestedHelper(queryShardContext.getMapperService()).mightMatchNestedDocs(filterQuery); + if (mightMatch) { + return filterQuery; + } + return new ToChildBlockJoinQuery(filterQuery, parentFilter); + } + return filterQuery; + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/ExactSearcher.java b/src/main/java/org/opensearch/knn/index/query/ExactSearcher.java new file mode 100644 index 000000000..6a97b4083 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/ExactSearcher.java @@ -0,0 +1,257 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query; + +import com.google.common.base.Predicates; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.NonNull; +import lombok.Value; +import lombok.extern.log4j.Log4j2; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.index.SegmentReader; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.search.HitQueue; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.util.BitSet; +import org.opensearch.common.lucene.Lucene; +import org.opensearch.knn.common.FieldInfoExtractor; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.query.iterators.BinaryVectorIdsKNNIterator; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.query.iterators.ByteVectorIdsKNNIterator; +import org.opensearch.knn.index.query.iterators.NestedBinaryVectorIdsKNNIterator; +import org.opensearch.knn.index.query.iterators.VectorIdsKNNIterator; +import org.opensearch.knn.index.query.iterators.KNNIterator; +import org.opensearch.knn.index.query.iterators.NestedByteVectorIdsKNNIterator; +import org.opensearch.knn.index.query.iterators.NestedVectorIdsKNNIterator; +import org.opensearch.knn.index.vectorvalues.KNNBinaryVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNByteVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNFloatVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNVectorValuesFactory; +import org.opensearch.knn.indices.ModelDao; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.function.Predicate; + +@Log4j2 +@AllArgsConstructor +public class ExactSearcher { + + private final ModelDao modelDao; + + /** + * Execute an exact search on a subset of documents of a leaf + * + * @param leafReaderContext {@link LeafReaderContext} + * @param exactSearcherContext {@link ExactSearcherContext} + * @return Map of re-scored results + * @throws IOException exception during execution of exact search + */ + public Map searchLeaf(final LeafReaderContext leafReaderContext, final ExactSearcherContext exactSearcherContext) + throws IOException { + final KNNIterator iterator = getKNNIterator(leafReaderContext, exactSearcherContext); + // because of any reason if we are not able to get KNNIterator, return an empty map + if (iterator == null) { + return Collections.emptyMap(); + } + if (exactSearcherContext.getKnnQuery().getRadius() != null) { + return doRadialSearch(leafReaderContext, exactSearcherContext, iterator); + } + if (exactSearcherContext.getMatchedDocs() != null + && exactSearcherContext.getMatchedDocs().cardinality() <= exactSearcherContext.getK()) { + return scoreAllDocs(iterator); + } + return searchTopCandidates(iterator, exactSearcherContext.getK(), Predicates.alwaysTrue()); + } + + /** + * Perform radial search by comparing scores with min score. Currently, FAISS from native engine supports radial search. + * Hence, we assume that Radius from knnQuery is always distance, and we convert it to score since we do exact search uses scores + * to filter out the documents that does not have given min score. + * @param leafReaderContext {@link LeafReaderContext} + * @param exactSearcherContext {@link ExactSearcherContext} + * @param iterator {@link KNNIterator} + * @return Map of docId and score + * @throws IOException exception raised by iterator during traversal + */ + private Map doRadialSearch( + LeafReaderContext leafReaderContext, + ExactSearcherContext exactSearcherContext, + KNNIterator iterator + ) throws IOException { + final SegmentReader reader = Lucene.segmentReader(leafReaderContext.reader()); + final KNNQuery knnQuery = exactSearcherContext.getKnnQuery(); + final FieldInfo fieldInfo = FieldInfoExtractor.getFieldInfo(reader, knnQuery.getField()); + if (fieldInfo == null) { + return Collections.emptyMap(); + } + final KNNEngine engine = FieldInfoExtractor.extractKNNEngine(fieldInfo); + if (KNNEngine.FAISS != engine) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "Engine [%s] does not support radial search", engine)); + } + final SpaceType spaceType = FieldInfoExtractor.getSpaceType(modelDao, fieldInfo); + final float minScore = spaceType.scoreTranslation(knnQuery.getRadius()); + return filterDocsByMinScore(exactSearcherContext, iterator, minScore); + } + + private Map scoreAllDocs(KNNIterator iterator) throws IOException { + final Map docToScore = new HashMap<>(); + int docId; + while ((docId = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) { + docToScore.put(docId, iterator.score()); + } + return docToScore; + } + + private Map searchTopCandidates(KNNIterator iterator, int limit, @NonNull Predicate filterScore) + throws IOException { + // Creating min heap and init with MAX DocID and Score as -INF. + final HitQueue queue = new HitQueue(limit, true); + ScoreDoc topDoc = queue.top(); + final Map docToScore = new HashMap<>(); + int docId; + while ((docId = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) { + final float currentScore = iterator.score(); + if (filterScore.test(currentScore) && currentScore > topDoc.score) { + topDoc.score = currentScore; + topDoc.doc = docId; + // As the HitQueue is min heap, updating top will bring the doc with -INF score or worst score we + // have seen till now on top. + topDoc = queue.updateTop(); + } + } + + // If scores are negative we will remove them. + // This is done, because there can be negative values in the Heap as we init the heap with Score as -INF. + // If filterIds < k, the some values in heap can have a negative score. + while (queue.size() > 0 && queue.top().score < 0) { + queue.pop(); + } + + while (queue.size() > 0) { + final ScoreDoc doc = queue.pop(); + docToScore.put(doc.doc, doc.score); + } + return docToScore; + } + + private Map filterDocsByMinScore(ExactSearcherContext context, KNNIterator iterator, float minScore) + throws IOException { + int maxResultWindow = context.getKnnQuery().getContext().getMaxResultWindow(); + Predicate scoreGreaterThanOrEqualToMinScore = score -> score >= minScore; + return searchTopCandidates(iterator, maxResultWindow, scoreGreaterThanOrEqualToMinScore); + } + + private KNNIterator getKNNIterator(LeafReaderContext leafReaderContext, ExactSearcherContext exactSearcherContext) throws IOException { + final KNNQuery knnQuery = exactSearcherContext.getKnnQuery(); + final BitSet matchedDocs = exactSearcherContext.getMatchedDocs(); + final SegmentReader reader = Lucene.segmentReader(leafReaderContext.reader()); + final FieldInfo fieldInfo = FieldInfoExtractor.getFieldInfo(reader, knnQuery.getField()); + if (fieldInfo == null) { + log.debug("[KNN] Cannot get KNNIterator as Field info not found for {}:{}", knnQuery.getField(), reader.getSegmentName()); + return null; + } + final SpaceType spaceType = FieldInfoExtractor.getSpaceType(modelDao, fieldInfo); + + boolean isNestedRequired = exactSearcherContext.isParentHits() && knnQuery.getParentsFilter() != null; + + if (VectorDataType.BINARY == knnQuery.getVectorDataType()) { + final KNNVectorValues vectorValues = KNNVectorValuesFactory.getVectorValues(fieldInfo, reader); + if (isNestedRequired) { + return new NestedBinaryVectorIdsKNNIterator( + matchedDocs, + knnQuery.getByteQueryVector(), + (KNNBinaryVectorValues) vectorValues, + spaceType, + knnQuery.getParentsFilter().getBitSet(leafReaderContext) + ); + } + return new BinaryVectorIdsKNNIterator( + matchedDocs, + knnQuery.getByteQueryVector(), + (KNNBinaryVectorValues) vectorValues, + spaceType + ); + } + + if (VectorDataType.BYTE == knnQuery.getVectorDataType()) { + final KNNVectorValues vectorValues = KNNVectorValuesFactory.getVectorValues(fieldInfo, reader); + if (isNestedRequired) { + return new NestedByteVectorIdsKNNIterator( + matchedDocs, + knnQuery.getQueryVector(), + (KNNByteVectorValues) vectorValues, + spaceType, + knnQuery.getParentsFilter().getBitSet(leafReaderContext) + ); + } + return new ByteVectorIdsKNNIterator(matchedDocs, knnQuery.getQueryVector(), (KNNByteVectorValues) vectorValues, spaceType); + } + final byte[] quantizedQueryVector; + final SegmentLevelQuantizationInfo segmentLevelQuantizationInfo; + if (exactSearcherContext.isUseQuantizedVectorsForSearch()) { + // Build Segment Level Quantization info. + segmentLevelQuantizationInfo = SegmentLevelQuantizationInfo.build(reader, fieldInfo, knnQuery.getField()); + // Quantize the Query Vector Once. + quantizedQueryVector = SegmentLevelQuantizationUtil.quantizeVector(knnQuery.getQueryVector(), segmentLevelQuantizationInfo); + } else { + segmentLevelQuantizationInfo = null; + quantizedQueryVector = null; + } + + final KNNVectorValues vectorValues = KNNVectorValuesFactory.getVectorValues(fieldInfo, reader); + if (isNestedRequired) { + return new NestedVectorIdsKNNIterator( + matchedDocs, + knnQuery.getQueryVector(), + (KNNFloatVectorValues) vectorValues, + spaceType, + knnQuery.getParentsFilter().getBitSet(leafReaderContext), + quantizedQueryVector, + segmentLevelQuantizationInfo + ); + } + return new VectorIdsKNNIterator( + matchedDocs, + knnQuery.getQueryVector(), + (KNNFloatVectorValues) vectorValues, + spaceType, + quantizedQueryVector, + segmentLevelQuantizationInfo + ); + } + + /** + * Stores the context that is used to do the exact search. This class will help in reducing the explosion of attributes + * for doing exact search. + */ + @Value + @Builder + public static class ExactSearcherContext { + /** + * controls whether we should use Quantized vectors during exact search or not. This is useful because when we do + * re-scoring we need to re-score using full precision vectors and not quantized vectors. + */ + boolean useQuantizedVectorsForSearch; + int k; + BitSet matchedDocs; + KNNQuery knnQuery; + /** + * whether the matchedDocs contains parent ids or child ids. This is relevant in the case of + * filtered nested search where the matchedDocs contain the parent ids and {@link NestedVectorIdsKNNIterator} + * needs to be used. + */ + boolean isParentHits; + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/FilterIdsSelector.java b/src/main/java/org/opensearch/knn/index/query/FilterIdsSelector.java new file mode 100644 index 000000000..bf06e8c5e --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/FilterIdsSelector.java @@ -0,0 +1,107 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.query; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.util.BitSet; +import org.apache.lucene.util.BitSetIterator; +import org.apache.lucene.util.FixedBitSet; + +import java.io.IOException; + +/** + * Util Class for filter ids selector + */ +@AllArgsConstructor +@Getter +public class FilterIdsSelector { + + /** + * When do ann query with filters, there are two types: + * BitMap using FixedBitSet, BATCH using a long array stands for filter result docids. + */ + @AllArgsConstructor + @Getter + public enum FilterIdsSelectorType { + BITMAP(0), + BATCH(1); + + private final int value; + } + + long[] filterIds; + private FilterIdsSelectorType filterType; + + /** + * This function takes a call on what ID Selector to use: + * https://github.com/facebookresearch/faiss/wiki/Setting-search-parameters-for-one-query#idselectorarray-idselectorbatch-and-idselectorbitmap + * + * class storage lookup construction(Opensearch + Faiss) + * IDSelectorArray O(k) O(k) O(2k) + * IDSelectorBatch O(k) O(1) O(2k) + * IDSelectorBitmap O(n/8) O(1) O(k) n is the max value of id in the index + * + * TODO: We need to ideally decide when we can take another hit of K iterations in latency. Some facts: + * an OpenSearch Index can have max segment size as 5GB which, which on a vector with dimension of 128 boils down to + * 7.5M vectors. + * Ref: https://opensearch.org/docs/latest/search-plugins/knn/knn-index/#hnsw-memory-estimation + * M = 16 + * Dimension = 128 + * (1.1 * ( 4 * 128 + 8 * 16) * 7500000)/(1024*1024*1024) ~ 4.9GB + * Ids are sequential in a Segment which means for IDSelectorBitmap total size if the max ID has value of 7.5M will be + * 7500000/(8*1024) = 915KBs in worst case. But with larger dimensions this worst case value will decrease. + * + * With 915KB how many ids can be represented as an array of 64-bit longs : 117,120 ids + * So iterating on 117k ids for 1 single pass is also time consuming. So, we are currently concluding to consider only size + * as factor. We need to improve on this. + * + * Array Memory: Cardinality * Long.BYTES + * BitSet Memory: MaxId / Byte.SIZE + * When Array Memory less than or equal to BitSet Memory return FilterIdsSelectorType.BATCH + * Else return FilterIdsSelectorType.BITMAP; + * + * @param filterIdsBitSet Filter query result docs + * @param cardinality The number of bits that are set + * @return {@link FilterIdsSelector} + */ + public static FilterIdsSelector getFilterIdSelector(final BitSet filterIdsBitSet, final int cardinality) throws IOException { + long[] filterIds; + FilterIdsSelector.FilterIdsSelectorType filterType; + if (filterIdsBitSet instanceof FixedBitSet) { + /** + * When filterIds is dense filter, using fixed bitset + */ + filterIds = ((FixedBitSet) filterIdsBitSet).getBits(); + filterType = FilterIdsSelector.FilterIdsSelectorType.BITMAP; + } else if ((cardinality * Long.BYTES * Byte.SIZE) <= filterIdsBitSet.length()) { + /** + * When filterIds is sparse bitset, using ram usage to decide FilterIdsSelectorType + */ + BitSetIterator bitSetIterator = new BitSetIterator(filterIdsBitSet, cardinality); + filterIds = new long[cardinality]; + int idx = 0; + for (int docId = bitSetIterator.nextDoc(); docId != DocIdSetIterator.NO_MORE_DOCS; docId = bitSetIterator.nextDoc()) { + filterIds[idx++] = docId; + } + filterType = FilterIdsSelectorType.BATCH; + } else { + FixedBitSet fixedBitSet = new FixedBitSet(filterIdsBitSet.length()); + BitSetIterator sparseBitSetIterator = new BitSetIterator(filterIdsBitSet, cardinality); + fixedBitSet.or(sparseBitSetIterator); + filterIds = fixedBitSet.getBits(); + filterType = FilterIdsSelector.FilterIdsSelectorType.BITMAP; + } + return new FilterIdsSelector(filterIds, filterType); + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/KNNQuery.java b/src/main/java/org/opensearch/knn/index/query/KNNQuery.java new file mode 100644 index 000000000..f5c4d3131 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/KNNQuery.java @@ -0,0 +1,245 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.FieldExistsQuery; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.QueryVisitor; +import org.apache.lucene.search.ScoreMode; +import org.apache.lucene.search.Weight; +import org.apache.lucene.search.join.BitSetProducer; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.query.rescore.RescoreContext; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; + +/** + * Custom KNN query. Query is used for KNNEngine's that create their own custom segment files. These files need to be + * loaded and queried in a custom manner throughout the query path. + */ +@Getter +@Builder +@AllArgsConstructor +public class KNNQuery extends Query { + + private final String field; + private final float[] queryVector; + private final byte[] byteQueryVector; + private int k; + private Map methodParameters; + private final String indexName; + private final VectorDataType vectorDataType; + private final RescoreContext rescoreContext; + + @Setter + private Query filterQuery; + private BitSetProducer parentsFilter; + private Float radius; + private Context context; + + public KNNQuery( + final String field, + final float[] queryVector, + final int k, + final String indexName, + final BitSetProducer parentsFilter + ) { + this(field, queryVector, null, k, indexName, null, parentsFilter, VectorDataType.FLOAT, null); + } + + public KNNQuery( + final String field, + final float[] queryVector, + final int k, + final String indexName, + final Query filterQuery, + final BitSetProducer parentsFilter, + final RescoreContext rescoreContext + ) { + this(field, queryVector, null, k, indexName, filterQuery, parentsFilter, VectorDataType.FLOAT, rescoreContext); + } + + public KNNQuery( + final String field, + final byte[] byteQueryVector, + final int k, + final String indexName, + final Query filterQuery, + final BitSetProducer parentsFilter, + final VectorDataType vectorDataType, + final RescoreContext rescoreContext + ) { + this(field, null, byteQueryVector, k, indexName, filterQuery, parentsFilter, vectorDataType, rescoreContext); + } + + private KNNQuery( + final String field, + final float[] queryVector, + final byte[] byteQueryVector, + final int k, + final String indexName, + final Query filterQuery, + final BitSetProducer parentsFilter, + final VectorDataType vectorDataType, + final RescoreContext rescoreContext + ) { + this.field = field; + this.queryVector = queryVector; + this.byteQueryVector = byteQueryVector; + this.k = k; + this.indexName = indexName; + this.filterQuery = filterQuery; + this.parentsFilter = parentsFilter; + this.vectorDataType = vectorDataType; + this.rescoreContext = rescoreContext; + } + + /** + * Constructor for KNNQuery with query vector, index name and parent filter + * + * @param field field name + * @param queryVector query vector + * @param indexName index name + * @param parentsFilter parent filter + */ + public KNNQuery(String field, float[] queryVector, String indexName, BitSetProducer parentsFilter) { + this(field, queryVector, null, 0, indexName, null, parentsFilter, VectorDataType.FLOAT, null); + } + + /** + * Constructor for KNNQuery with radius + * + * @param radius engine radius + * @return KNNQuery + */ + public KNNQuery radius(Float radius) { + this.radius = radius; + return this; + } + + /** + * Constructor for KNNQuery with Context + * + * @param context Context for KNNQuery + * @return KNNQuery + */ + public KNNQuery kNNQueryContext(Context context) { + this.context = context; + return this; + } + + /** + * Constructor for KNNQuery with filter query + * + * @param filterQuery filter query + * @return KNNQuery + */ + public KNNQuery filterQuery(Query filterQuery) { + this.filterQuery = filterQuery; + return this; + } + + /** + * Constructs Weight implementation for this query + * + * @param searcher searcher for given segment + * @param scoreMode How the produced scorers will be consumed. + * @param boost The boost that is propagated by the parent queries. + * @return Weight For calculating scores + */ + @Override + public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) throws IOException { + if (!KNNSettings.isKNNPluginEnabled()) { + throw new IllegalStateException("KNN plugin is disabled. To enable update knn.plugin.enabled to true"); + } + final Weight filterWeight = getFilterWeight(searcher); + if (filterWeight != null) { + return new KNNWeight(this, boost, filterWeight); + } + return new KNNWeight(this, boost); + } + + private Weight getFilterWeight(IndexSearcher searcher) throws IOException { + if (this.getFilterQuery() != null) { + // Run the filter query + final BooleanQuery booleanQuery = new BooleanQuery.Builder().add(this.getFilterQuery(), BooleanClause.Occur.FILTER) + .add(new FieldExistsQuery(this.getField()), BooleanClause.Occur.FILTER) + .build(); + final Query rewritten = searcher.rewrite(booleanQuery); + return searcher.createWeight(rewritten, ScoreMode.COMPLETE_NO_SCORES, 1f); + } + return null; + } + + @Override + public void visit(QueryVisitor visitor) { + + } + + @Override + public String toString(String field) { + return field; + } + + @Override + public int hashCode() { + return Objects.hash( + field, + Arrays.hashCode(queryVector), + k, + indexName, + filterQuery, + context, + parentsFilter, + radius, + methodParameters, + rescoreContext + ); + } + + @Override + public boolean equals(Object other) { + return sameClassAs(other) && equalsTo(getClass().cast(other)); + } + + private boolean equalsTo(KNNQuery other) { + if (other == this) return true; + return Objects.equals(field, other.field) + && Arrays.equals(queryVector, other.queryVector) + && Arrays.equals(byteQueryVector, other.byteQueryVector) + && Objects.equals(k, other.k) + && Objects.equals(methodParameters, other.methodParameters) + && Objects.equals(radius, other.radius) + && Objects.equals(context, other.context) + && Objects.equals(indexName, other.indexName) + && Objects.equals(parentsFilter, other.parentsFilter) + && Objects.equals(filterQuery, other.filterQuery) + && Objects.equals(rescoreContext, other.rescoreContext); + } + + /** + * Context for KNNQuery + */ + @Setter + @Getter + @AllArgsConstructor + @EqualsAndHashCode + public static class Context { + int maxResultWindow; + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/KNNQueryBuilder.java b/src/main/java/org/opensearch/knn/index/query/KNNQueryBuilder.java new file mode 100644 index 000000000..8f7c5a3ff --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/KNNQueryBuilder.java @@ -0,0 +1,664 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang.StringUtils; +import org.apache.lucene.search.MatchNoDocsQuery; +import org.apache.lucene.search.Query; +import org.opensearch.common.ValidationException; +import org.opensearch.core.ParseField; +import org.opensearch.core.common.Strings; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.index.mapper.MappedFieldType; +import org.opensearch.index.query.AbstractQueryBuilder; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.QueryRewriteContext; +import org.opensearch.index.query.QueryShardContext; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.model.QueryContext; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.index.mapper.KNNMappingConfig; +import org.opensearch.knn.index.mapper.KNNVectorFieldType; +import org.opensearch.knn.index.query.parser.RescoreParser; +import org.opensearch.knn.index.query.rescore.RescoreContext; +import org.opensearch.knn.index.util.IndexUtil; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.VectorQueryType; +import org.opensearch.knn.index.query.parser.KNNQueryBuilderParser; +import org.opensearch.knn.index.engine.KNNLibrarySearchContext; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.indices.ModelDao; +import org.opensearch.knn.indices.ModelMetadata; +import org.opensearch.knn.indices.ModelUtil; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +import static org.opensearch.knn.common.KNNConstants.MAX_DISTANCE; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_SEARCH; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NPROBES; +import static org.opensearch.knn.common.KNNConstants.MIN_SCORE; +import static org.opensearch.knn.common.KNNValidationUtil.validateByteVectorValue; +import static org.opensearch.knn.index.query.parser.MethodParametersParser.validateMethodParameters; +import static org.opensearch.knn.index.engine.KNNEngine.ENGINES_SUPPORTING_RADIAL_SEARCH; +import static org.opensearch.knn.index.engine.validation.ParameterValidator.validateParameters; +import static org.opensearch.knn.index.query.parser.RescoreParser.RESCORE_OVERSAMPLE_PARAMETER; +import static org.opensearch.knn.index.query.parser.RescoreParser.RESCORE_PARAMETER; + +/** + * Helper class to build the KNN query + */ +// The builder validates the member variables so access to the constructor is prohibited to not accidentally bypass validations +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Log4j2 +public class KNNQueryBuilder extends AbstractQueryBuilder { + private static ModelDao modelDao; + + public static final ParseField VECTOR_FIELD = new ParseField("vector"); + public static final ParseField K_FIELD = new ParseField("k"); + public static final ParseField FILTER_FIELD = new ParseField("filter"); + public static final ParseField IGNORE_UNMAPPED_FIELD = new ParseField("ignore_unmapped"); + public static final ParseField MAX_DISTANCE_FIELD = new ParseField(MAX_DISTANCE); + public static final ParseField MIN_SCORE_FIELD = new ParseField(MIN_SCORE); + public static final ParseField EF_SEARCH_FIELD = new ParseField(METHOD_PARAMETER_EF_SEARCH); + public static final ParseField NPROBE_FIELD = new ParseField(METHOD_PARAMETER_NPROBES); + public static final ParseField METHOD_PARAMS_FIELD = new ParseField(METHOD_PARAMETER); + public static final ParseField RESCORE_FIELD = new ParseField(RESCORE_PARAMETER); + public static final ParseField RESCORE_OVERSAMPLE_FIELD = new ParseField(RESCORE_OVERSAMPLE_PARAMETER); + + public static final int K_MAX = 10000; + /** + * The name for the knn query + */ + public static final String NAME = "knn"; + /** + * The default mode terms are combined in a match query + */ + private final String fieldName; + private final float[] vector; + @Getter + private int k; + @Getter + private Float maxDistance; + @Getter + private Float minScore; + @Getter + private Map methodParameters; + @Getter + private QueryBuilder filter; + @Getter + private boolean ignoreUnmapped; + @Getter + private RescoreContext rescoreContext; + + /** + * Constructs a new query with the given field name and vector + * + * @param fieldName Name of the field + * @param vector Array of floating points + * @deprecated Use {@code {@link KNNQueryBuilder.Builder}} instead + */ + @Deprecated + public KNNQueryBuilder(String fieldName, float[] vector) { + if (Strings.isNullOrEmpty(fieldName)) { + throw new IllegalArgumentException(String.format("[%s] requires fieldName", NAME)); + } + if (vector == null) { + throw new IllegalArgumentException(String.format("[%s] requires query vector", NAME)); + } + if (vector.length == 0) { + throw new IllegalArgumentException(String.format("[%s] query vector is empty", NAME)); + } + this.fieldName = fieldName; + this.vector = vector; + } + + /** + * lombok SuperBuilder annotation requires a builder annotation on parent class to work well + * {@link AbstractQueryBuilder#boost()} and {@link AbstractQueryBuilder#queryName()} both need to be called + * A custom builder helps with the calls to the parent class, simultaneously addressing the problem of telescoping + * constructors in this class. + */ + public static class Builder { + private String fieldName; + private float[] vector; + private Integer k; + private Map methodParameters; + private Float maxDistance; + private Float minScore; + private QueryBuilder filter; + private boolean ignoreUnmapped; + private String queryName; + private float boost = DEFAULT_BOOST; + private RescoreContext rescoreContext; + + public Builder() {} + + public Builder fieldName(String fieldName) { + this.fieldName = fieldName; + return this; + } + + public Builder vector(float[] vector) { + this.vector = vector; + return this; + } + + public Builder k(Integer k) { + this.k = k; + return this; + } + + public Builder methodParameters(Map methodParameters) { + this.methodParameters = methodParameters; + return this; + } + + public Builder maxDistance(Float maxDistance) { + this.maxDistance = maxDistance; + return this; + } + + public Builder minScore(Float minScore) { + this.minScore = minScore; + return this; + } + + public Builder ignoreUnmapped(boolean ignoreUnmapped) { + this.ignoreUnmapped = ignoreUnmapped; + return this; + } + + public Builder filter(QueryBuilder filter) { + this.filter = filter; + return this; + } + + public Builder queryName(String queryName) { + this.queryName = queryName; + return this; + } + + public Builder boost(float boost) { + this.boost = boost; + return this; + } + + public Builder rescoreContext(RescoreContext rescoreContext) { + this.rescoreContext = rescoreContext; + return this; + } + + public KNNQueryBuilder build() { + validate(); + int k = this.k == null ? 0 : this.k; + return new KNNQueryBuilder( + fieldName, + vector, + k, + maxDistance, + minScore, + methodParameters, + filter, + ignoreUnmapped, + rescoreContext + ).boost(boost).queryName(queryName); + } + + private void validate() { + if (Strings.isNullOrEmpty(fieldName)) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "[%s] requires fieldName", NAME)); + } + + if (vector == null) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "[%s] requires query vector", NAME)); + } else if (vector.length == 0) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "[%s] query vector is empty", NAME)); + } + + if (k == null && minScore == null && maxDistance == null) { + throw new IllegalArgumentException( + String.format(Locale.ROOT, "[%s] requires exactly one of k, distance or score to be set", NAME) + ); + } + + if ((k != null && maxDistance != null) || (maxDistance != null && minScore != null) || (k != null && minScore != null)) { + throw new IllegalArgumentException( + String.format(Locale.ROOT, "[%s] requires exactly one of k, distance or score to be set", NAME) + ); + } + + if (k != null) { + if (k <= 0 || k > K_MAX) { + final String errorMessage = "[" + NAME + "] requires k to be in the range (0, " + K_MAX + "]"; + throw new IllegalArgumentException(errorMessage); + } + } + + if (minScore != null) { + if (minScore <= 0) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "[%s] requires minScore to be greater than 0", NAME)); + } + } + + if (methodParameters != null) { + ValidationException validationException = validateMethodParameters(methodParameters); + if (validationException != null) { + throw new IllegalArgumentException( + String.format(Locale.ROOT, "[%s] errors in method parameter [%s]", NAME, validationException.getMessage()) + ); + } + } + + if (rescoreContext != null) { + ValidationException validationException = RescoreParser.validate(rescoreContext); + if (validationException != null) { + throw new IllegalArgumentException( + String.format(Locale.ROOT, "[%s] errors in rescore parameter [%s]", NAME, validationException.getMessage()) + ); + } + } + } + } + + public static KNNQueryBuilder.Builder builder() { + return new KNNQueryBuilder.Builder(); + } + + /** + * Constructs a new query for top k search + * + * @param fieldName Name of the filed + * @param vector Array of floating points + * @param k K nearest neighbours for the given vector + */ + @Deprecated + public KNNQueryBuilder(String fieldName, float[] vector, int k) { + this(fieldName, vector, k, null); + } + + @Deprecated + public KNNQueryBuilder(String fieldName, float[] vector, int k, QueryBuilder filter) { + if (Strings.isNullOrEmpty(fieldName)) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "[%s] requires fieldName", NAME)); + } + if (vector == null) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "[%s] requires query vector", NAME)); + } + if (vector.length == 0) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "[%s] query vector is empty", NAME)); + } + if (k <= 0) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "[%s] requires k > 0", NAME)); + } + if (k > K_MAX) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "[%s] requires k <= %d", NAME, K_MAX)); + } + + this.fieldName = fieldName; + this.vector = vector; + this.k = k; + this.filter = filter; + this.ignoreUnmapped = false; + this.maxDistance = null; + this.minScore = null; + this.rescoreContext = null; + } + + public static void initialize(ModelDao modelDao) { + KNNQueryBuilder.modelDao = modelDao; + } + + /** + * @param in Reads from stream + * @throws IOException Throws IO Exception + */ + public KNNQueryBuilder(StreamInput in) throws IOException { + super(in); + KNNQueryBuilder.Builder builder = KNNQueryBuilderParser.streamInput(in, IndexUtil::isClusterOnOrAfterMinRequiredVersion); + fieldName = builder.fieldName; + vector = builder.vector; + k = builder.k; + filter = builder.filter; + ignoreUnmapped = builder.ignoreUnmapped; + maxDistance = builder.maxDistance; + minScore = builder.minScore; + methodParameters = builder.methodParameters; + rescoreContext = builder.rescoreContext; + } + + @Override + protected void doWriteTo(StreamOutput out) throws IOException { + KNNQueryBuilderParser.streamOutput(out, this, IndexUtil::isClusterOnOrAfterMinRequiredVersion); + } + + /** + * @return The field name used in this query + */ + public String fieldName() { + return this.fieldName; + } + + /** + * @return Returns the vector used in this query. + */ + public Object vector() { + return this.vector; + } + + @Override + public void doXContent(XContentBuilder builder, Params params) throws IOException { + KNNQueryBuilderParser.toXContent(builder, params, this); + } + + @Override + protected Query doToQuery(QueryShardContext context) { + MappedFieldType mappedFieldType = context.fieldMapper(this.fieldName); + + if (mappedFieldType == null && ignoreUnmapped) { + return new MatchNoDocsQuery(); + } + + if (!(mappedFieldType instanceof KNNVectorFieldType)) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "Field '%s' is not knn_vector type.", this.fieldName)); + } + KNNVectorFieldType knnVectorFieldType = (KNNVectorFieldType) mappedFieldType; + KNNMappingConfig knnMappingConfig = knnVectorFieldType.getKnnMappingConfig(); + final AtomicReference queryConfigFromMapping = new AtomicReference<>(); + int fieldDimension = knnMappingConfig.getDimension(); + knnMappingConfig.getKnnMethodContext() + .ifPresentOrElse( + knnMethodContext -> queryConfigFromMapping.set( + new QueryConfigFromMapping( + knnMethodContext.getKnnEngine(), + knnMethodContext.getMethodComponentContext(), + knnMethodContext.getSpaceType(), + knnVectorFieldType.getVectorDataType() + ) + ), + () -> knnMappingConfig.getModelId().ifPresentOrElse(modelId -> { + ModelMetadata modelMetadata = getModelMetadataForField(modelId); + queryConfigFromMapping.set( + new QueryConfigFromMapping( + modelMetadata.getKnnEngine(), + modelMetadata.getMethodComponentContext(), + modelMetadata.getSpaceType(), + modelMetadata.getVectorDataType() + ) + ); + }, + () -> { + throw new IllegalArgumentException( + String.format(Locale.ROOT, "Field '%s' is not built for ANN search.", this.fieldName) + ); + } + ) + ); + KNNEngine knnEngine = queryConfigFromMapping.get().getKnnEngine(); + MethodComponentContext methodComponentContext = queryConfigFromMapping.get().getMethodComponentContext(); + SpaceType spaceType = queryConfigFromMapping.get().getSpaceType(); + VectorDataType vectorDataType = queryConfigFromMapping.get().getVectorDataType(); + RescoreContext processedRescoreContext = knnVectorFieldType.resolveRescoreContext(rescoreContext); + + VectorQueryType vectorQueryType = getVectorQueryType(k, maxDistance, minScore); + updateQueryStats(vectorQueryType); + + // This could be null in the case of when a model did not have serialized methodComponent information + final String method = methodComponentContext != null ? methodComponentContext.getName() : null; + if (StringUtils.isNotBlank(method)) { + final KNNLibrarySearchContext engineSpecificMethodContext = knnEngine.getKNNLibrarySearchContext(method); + QueryContext queryContext = new QueryContext(vectorQueryType); + ValidationException validationException = validateParameters( + engineSpecificMethodContext.supportedMethodParameters(queryContext), + (Map) methodParameters, + KNNMethodConfigContext.EMPTY + ); + if (validationException != null) { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "Parameters not valid for [%s]:[%s]:[%s] combination: [%s]", + knnEngine, + method, + vectorQueryType.getQueryTypeName(), + validationException.getMessage() + ) + ); + } + } + + if (this.maxDistance != null || this.minScore != null) { + if (!ENGINES_SUPPORTING_RADIAL_SEARCH.contains(knnEngine)) { + throw new UnsupportedOperationException( + String.format(Locale.ROOT, "Engine [%s] does not support radial search", knnEngine) + ); + } + if (vectorDataType == VectorDataType.BINARY) { + throw new UnsupportedOperationException(String.format(Locale.ROOT, "Binary data type does not support radial search")); + } + + if (knnMappingConfig.getQuantizationConfig() != QuantizationConfig.EMPTY) { + throw new UnsupportedOperationException("Radial search is not supported for indices which have quantization enabled"); + } + } + + // Currently, k-NN supports distance and score types radial search + // We need transform distance/score to right type of engine required radius. + Float radius = null; + if (this.maxDistance != null) { + if (this.maxDistance < 0 && SpaceType.INNER_PRODUCT.equals(spaceType) == false) { + throw new IllegalArgumentException( + String.format("[" + NAME + "] requires distance to be non-negative for space type: %s", spaceType) + ); + } + radius = knnEngine.distanceToRadialThreshold(this.maxDistance, spaceType); + } + + if (this.minScore != null) { + if (this.minScore > 1 && SpaceType.INNER_PRODUCT.equals(spaceType) == false) { + throw new IllegalArgumentException( + String.format("[" + NAME + "] requires score to be in the range [0, 1] for space type: %s", spaceType) + ); + } + radius = knnEngine.scoreToRadialThreshold(this.minScore, spaceType); + } + + int vectorLength = VectorDataType.BINARY == vectorDataType ? vector.length * Byte.SIZE : vector.length; + if (fieldDimension != vectorLength) { + throw new IllegalArgumentException( + String.format("Query vector has invalid dimension: %d. Dimension should be: %d", vectorLength, fieldDimension) + ); + } + + byte[] byteVector = new byte[0]; + switch (vectorDataType) { + case BINARY: + byteVector = new byte[vector.length]; + for (int i = 0; i < vector.length; i++) { + validateByteVectorValue(vector[i], knnVectorFieldType.getVectorDataType()); + byteVector[i] = (byte) vector[i]; + } + spaceType.validateVector(byteVector); + break; + case BYTE: + if (KNNEngine.LUCENE == knnEngine) { + byteVector = new byte[vector.length]; + for (int i = 0; i < vector.length; i++) { + validateByteVectorValue(vector[i], knnVectorFieldType.getVectorDataType()); + byteVector[i] = (byte) vector[i]; + } + spaceType.validateVector(byteVector); + } else { + for (float v : vector) { + validateByteVectorValue(v, knnVectorFieldType.getVectorDataType()); + } + spaceType.validateVector(vector); + } + break; + default: + spaceType.validateVector(vector); + } + + if (KNNEngine.getEnginesThatCreateCustomSegmentFiles().contains(knnEngine) + && filter != null + && !KNNEngine.getEnginesThatSupportsFilters().contains(knnEngine)) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "Engine [%s] does not support filters", knnEngine)); + } + + String indexName = context.index().getName(); + + if (k != 0) { + KNNQueryFactory.CreateQueryRequest createQueryRequest = KNNQueryFactory.CreateQueryRequest.builder() + .knnEngine(knnEngine) + .indexName(indexName) + .fieldName(this.fieldName) + .vector(getVectorForCreatingQueryRequest(vectorDataType, knnEngine)) + .byteVector(getVectorForCreatingQueryRequest(vectorDataType, knnEngine, byteVector)) + .vectorDataType(vectorDataType) + .k(this.k) + .methodParameters(this.methodParameters) + .filter(this.filter) + .context(context) + .rescoreContext(processedRescoreContext) + .build(); + return KNNQueryFactory.create(createQueryRequest); + } + if (radius != null) { + RNNQueryFactory.CreateQueryRequest createQueryRequest = RNNQueryFactory.CreateQueryRequest.builder() + .knnEngine(knnEngine) + .indexName(indexName) + .fieldName(this.fieldName) + .vector(VectorDataType.FLOAT == vectorDataType ? this.vector : null) + .byteVector(VectorDataType.BYTE == vectorDataType ? byteVector : null) + .vectorDataType(vectorDataType) + .radius(radius) + .methodParameters(this.methodParameters) + .filter(this.filter) + .context(context) + .build(); + return RNNQueryFactory.create(createQueryRequest); + } + throw new IllegalArgumentException(String.format(Locale.ROOT, "[%s] requires k or distance or score to be set", NAME)); + } + + private ModelMetadata getModelMetadataForField(String modelId) { + ModelMetadata modelMetadata = modelDao.getMetadata(modelId); + if (!ModelUtil.isModelCreated(modelMetadata)) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "Model ID '%s' is not created.", modelId)); + } + return modelMetadata; + } + + /** + * Function to get the vector query type based on the valid query parameter. + * + * @param k K nearest neighbours for the given vector, if k is set, then the query type is K + * @param maxDistance Maximum distance for the given vector, if maxDistance is set, then the query type is MAX_DISTANCE + * @param minScore Minimum score for the given vector, if minScore is set, then the query type is MIN_SCORE + */ + private VectorQueryType getVectorQueryType(int k, Float maxDistance, Float minScore) { + if (maxDistance != null) { + return VectorQueryType.MAX_DISTANCE; + } + if (minScore != null) { + return VectorQueryType.MIN_SCORE; + } + if (k != 0) { + return VectorQueryType.K; + } + throw new IllegalArgumentException(String.format(Locale.ROOT, "[%s] requires exactly one of k, distance or score to be set", NAME)); + } + + /** + * Function to update query stats. + * + * @param vectorQueryType The type of query to be executed + */ + private void updateQueryStats(VectorQueryType vectorQueryType) { + vectorQueryType.getQueryStatCounter().increment(); + if (filter != null) { + vectorQueryType.getQueryWithFilterStatCounter().increment(); + } + } + + private float[] getVectorForCreatingQueryRequest(VectorDataType vectorDataType, KNNEngine knnEngine) { + if ((VectorDataType.FLOAT == vectorDataType) || (VectorDataType.BYTE == vectorDataType && KNNEngine.FAISS == knnEngine)) { + return this.vector; + } + return null; + } + + private byte[] getVectorForCreatingQueryRequest(VectorDataType vectorDataType, KNNEngine knnEngine, byte[] byteVector) { + if (VectorDataType.BINARY == vectorDataType || (VectorDataType.BYTE == vectorDataType && KNNEngine.LUCENE == knnEngine)) { + return byteVector; + } + return null; + } + + @Override + protected boolean doEquals(KNNQueryBuilder other) { + return Objects.equals(fieldName, other.fieldName) + && Arrays.equals(vector, other.vector) + && Objects.equals(k, other.k) + && Objects.equals(minScore, other.minScore) + && Objects.equals(maxDistance, other.maxDistance) + && Objects.equals(methodParameters, other.methodParameters) + && Objects.equals(filter, other.filter) + && Objects.equals(ignoreUnmapped, other.ignoreUnmapped) + && Objects.equals(rescoreContext, other.rescoreContext); + } + + @Override + protected int doHashCode() { + return Objects.hash( + fieldName, + Arrays.hashCode(vector), + k, + methodParameters, + filter, + ignoreUnmapped, + maxDistance, + minScore, + rescoreContext + ); + } + + @Override + public String getWriteableName() { + return NAME; + } + + @Override + protected QueryBuilder doRewrite(QueryRewriteContext queryShardContext) throws IOException { + // rewrite filter query if it exists to avoid runtime errors in next steps of query phase + if (Objects.nonNull(filter)) { + filter = filter.rewrite(queryShardContext); + } + return super.doRewrite(queryShardContext); + } + + @Getter + @AllArgsConstructor + private static class QueryConfigFromMapping { + private final KNNEngine knnEngine; + private final MethodComponentContext methodComponentContext; + private final SpaceType spaceType; + private final VectorDataType vectorDataType; + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/KNNQueryFactory.java b/src/main/java/org/opensearch/knn/index/query/KNNQueryFactory.java new file mode 100644 index 000000000..dab2e08c8 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/KNNQueryFactory.java @@ -0,0 +1,168 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query; + +import lombok.extern.log4j.Log4j2; +import org.apache.lucene.search.KnnByteVectorQuery; +import org.apache.lucene.search.KnnFloatVectorQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.join.BitSetProducer; +import org.apache.lucene.search.join.DiversifyingChildrenByteKnnVectorQuery; +import org.apache.lucene.search.join.DiversifyingChildrenFloatKnnVectorQuery; +import org.opensearch.index.query.QueryShardContext; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.query.nativelib.NativeEngineKnnVectorQuery; +import org.opensearch.knn.index.query.rescore.RescoreContext; + +import java.util.Locale; +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_SEARCH; +import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; +import static org.opensearch.knn.index.VectorDataType.SUPPORTED_VECTOR_DATA_TYPES; + +/** + * Creates the Lucene k-NN queries + */ +@Log4j2 +public class KNNQueryFactory extends BaseQueryFactory { + + /** + * Creates a Lucene query for a particular engine. + * @param createQueryRequest request object that has all required fields to construct the query + * @return Lucene Query + */ + public static Query create(CreateQueryRequest createQueryRequest) { + // Engines that create their own custom segment files cannot use the Lucene's KnnVectorQuery. They need to + // use the custom query type created by the plugin + final String indexName = createQueryRequest.getIndexName(); + final String fieldName = createQueryRequest.getFieldName(); + final int k = createQueryRequest.getK(); + final float[] vector = createQueryRequest.getVector(); + final byte[] byteVector = createQueryRequest.getByteVector(); + final VectorDataType vectorDataType = createQueryRequest.getVectorDataType(); + final Query filterQuery = getFilterQuery(createQueryRequest); + final Map methodParameters = createQueryRequest.getMethodParameters(); + final RescoreContext rescoreContext = createQueryRequest.getRescoreContext().orElse(null); + + BitSetProducer parentFilter = null; + if (createQueryRequest.getContext().isPresent()) { + QueryShardContext context = createQueryRequest.getContext().get(); + parentFilter = context.getParentFilter(); + } + + if (KNNEngine.getEnginesThatCreateCustomSegmentFiles().contains(createQueryRequest.getKnnEngine())) { + final Query validatedFilterQuery = validateFilterQuerySupport(filterQuery, createQueryRequest.getKnnEngine()); + + log.debug( + "Creating custom k-NN query for index:{}, field:{}, k:{}, filterQuery:{}, efSearch:{}", + indexName, + fieldName, + k, + validatedFilterQuery, + methodParameters + ); + + KNNQuery knnQuery = null; + switch (vectorDataType) { + case BINARY: + knnQuery = KNNQuery.builder() + .field(fieldName) + .byteQueryVector(byteVector) + .indexName(indexName) + .parentsFilter(parentFilter) + .k(k) + .methodParameters(methodParameters) + .filterQuery(validatedFilterQuery) + .vectorDataType(vectorDataType) + .rescoreContext(rescoreContext) + .build(); + break; + default: + knnQuery = KNNQuery.builder() + .field(fieldName) + .queryVector(vector) + .indexName(indexName) + .parentsFilter(parentFilter) + .k(k) + .methodParameters(methodParameters) + .filterQuery(validatedFilterQuery) + .vectorDataType(vectorDataType) + .rescoreContext(rescoreContext) + .build(); + } + return createQueryRequest.getRescoreContext().isPresent() ? new NativeEngineKnnVectorQuery(knnQuery) : knnQuery; + } + + Integer requestEfSearch = null; + if (methodParameters != null && methodParameters.containsKey(METHOD_PARAMETER_EF_SEARCH)) { + requestEfSearch = (Integer) methodParameters.get(METHOD_PARAMETER_EF_SEARCH); + } + int luceneK = requestEfSearch == null ? k : Math.max(k, requestEfSearch); + log.debug(String.format("Creating Lucene k-NN query for index: %s \"\", field: %s \"\", k: %d", indexName, fieldName, k)); + switch (vectorDataType) { + case BYTE: + return getKnnByteVectorQuery(fieldName, byteVector, luceneK, filterQuery, parentFilter); + case FLOAT: + return getKnnFloatVectorQuery(fieldName, vector, luceneK, filterQuery, parentFilter); + default: + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "Invalid value provided for [%s] field. Supported values are [%s], but got: %s", + VECTOR_DATA_TYPE_FIELD, + SUPPORTED_VECTOR_DATA_TYPES, + vectorDataType + ) + ); + } + } + + private static Query validateFilterQuerySupport(final Query filterQuery, final KNNEngine knnEngine) { + log.debug("filter query {}, knnEngine {}", filterQuery, knnEngine); + if (filterQuery != null && KNNEngine.getEnginesThatSupportsFilters().contains(knnEngine)) { + return filterQuery; + } + return null; + } + + /** + * If parentFilter is not null, it is a nested query. Therefore, we return {@link DiversifyingChildrenByteKnnVectorQuery} + * which will dedupe search result per parent so that we can get k parent results at the end. + */ + private static Query getKnnByteVectorQuery( + final String fieldName, + final byte[] byteVector, + final int k, + final Query filterQuery, + final BitSetProducer parentFilter + ) { + if (parentFilter == null) { + return new KnnByteVectorQuery(fieldName, byteVector, k, filterQuery); + } else { + return new DiversifyingChildrenByteKnnVectorQuery(fieldName, byteVector, filterQuery, k, parentFilter); + } + } + + /** + * If parentFilter is not null, it is a nested query. Therefore, we return {@link DiversifyingChildrenFloatKnnVectorQuery} + * which will dedupe search result per parent so that we can get k parent results at the end. + */ + private static Query getKnnFloatVectorQuery( + final String fieldName, + final float[] floatVector, + final int k, + final Query filterQuery, + final BitSetProducer parentFilter + ) { + if (parentFilter == null) { + return new KnnFloatVectorQuery(fieldName, floatVector, k, filterQuery); + } else { + return new DiversifyingChildrenFloatKnnVectorQuery(fieldName, floatVector, filterQuery, k, parentFilter); + } + } +} diff --git a/src/main/java/org/opensearch/knn/index/KNNQueryResult.java b/src/main/java/org/opensearch/knn/index/query/KNNQueryResult.java similarity index 92% rename from src/main/java/org/opensearch/knn/index/KNNQueryResult.java rename to src/main/java/org/opensearch/knn/index/query/KNNQueryResult.java index a62ae3e4b..3ff74b2c6 100644 --- a/src/main/java/org/opensearch/knn/index/KNNQueryResult.java +++ b/src/main/java/org/opensearch/knn/index/query/KNNQueryResult.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.opensearch.knn.index; +package org.opensearch.knn.index.query; /** * Place holder for the score of the document diff --git a/src/main/java/org/opensearch/knn/index/KNNScorer.java b/src/main/java/org/opensearch/knn/index/query/KNNScorer.java similarity index 53% rename from src/main/java/org/opensearch/knn/index/KNNScorer.java rename to src/main/java/org/opensearch/knn/index/query/KNNScorer.java index edef5fdd4..99962d307 100644 --- a/src/main/java/org/opensearch/knn/index/KNNScorer.java +++ b/src/main/java/org/opensearch/knn/index/query/KNNScorer.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.opensearch.knn.index; +package org.opensearch.knn.index.query; import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.search.Scorer; @@ -49,11 +49,51 @@ public float score() { assert docID() != DocIdSetIterator.NO_MORE_DOCS; Float score = scores.get(docID()); if (score == null) throw new RuntimeException("Null score for the docID: " + docID()); - return score; + return score * boost; } @Override public int docID() { return docIdsIter.docID(); } + + /** + * Returns the Empty Scorer implementation. We use this scorer to short circuit the actual search when it is not + * required. + * @param knnWeight {@link KNNWeight} + * @return {@link KNNScorer} + */ + public static Scorer emptyScorer(KNNWeight knnWeight) { + return new Scorer(knnWeight) { + private final DocIdSetIterator docIdsIter = DocIdSetIterator.empty(); + + @Override + public DocIdSetIterator iterator() { + return docIdsIter; + } + + @Override + public float getMaxScore(int upTo) throws IOException { + return 0; + } + + @Override + public float score() throws IOException { + assert docID() != DocIdSetIterator.NO_MORE_DOCS; + return 0; + } + + @Override + public int docID() { + return docIdsIter.docID(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Scorer)) return false; + return getWeight().equals(((Scorer) obj).getWeight()); + } + }; + + } } diff --git a/src/main/java/org/opensearch/knn/index/query/KNNWeight.java b/src/main/java/org/opensearch/knn/index/query/KNNWeight.java new file mode 100644 index 000000000..b64472994 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/KNNWeight.java @@ -0,0 +1,495 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query; + +import com.google.common.annotations.VisibleForTesting; +import lombok.extern.log4j.Log4j2; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.index.SegmentReader; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.search.Explanation; +import org.apache.lucene.search.FilteredDocIdSetIterator; +import org.apache.lucene.search.Scorer; +import org.apache.lucene.search.Weight; +import org.apache.lucene.util.BitSet; +import org.apache.lucene.util.BitSetIterator; +import org.apache.lucene.util.Bits; +import org.apache.lucene.util.FixedBitSet; +import org.opensearch.common.lucene.Lucene; +import org.opensearch.knn.common.FieldInfoExtractor; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.codec.util.KNNCodecUtil; +import org.opensearch.knn.index.codec.util.NativeMemoryCacheKeyHelper; +import org.opensearch.knn.index.memory.NativeMemoryAllocation; +import org.opensearch.knn.index.memory.NativeMemoryCacheManager; +import org.opensearch.knn.index.memory.NativeMemoryEntryContext; +import org.opensearch.knn.index.memory.NativeMemoryLoadStrategy; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.quantizationservice.QuantizationService; +import org.opensearch.knn.index.query.ExactSearcher.ExactSearcherContext.ExactSearcherContextBuilder; +import org.opensearch.knn.indices.ModelDao; +import org.opensearch.knn.indices.ModelMetadata; +import org.opensearch.knn.indices.ModelUtil; +import org.opensearch.knn.jni.JNIService; +import org.opensearch.knn.plugin.stats.KNNCounter; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.MODEL_ID; +import static org.opensearch.knn.common.KNNConstants.SPACE_TYPE; +import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; +import static org.opensearch.knn.index.util.IndexUtil.getParametersAtLoading; +import static org.opensearch.knn.plugin.stats.KNNCounter.GRAPH_QUERY_ERRORS; + +/** + * Calculate query weights and build query scorers. + */ +@Log4j2 +public class KNNWeight extends Weight { + private static ModelDao modelDao; + + private final KNNQuery knnQuery; + private final float boost; + + private final NativeMemoryCacheManager nativeMemoryCacheManager; + private final Weight filterWeight; + private final ExactSearcher exactSearcher; + + private static ExactSearcher DEFAULT_EXACT_SEARCHER; + private final QuantizationService quantizationService; + + public KNNWeight(KNNQuery query, float boost) { + super(query); + this.knnQuery = query; + this.boost = boost; + this.nativeMemoryCacheManager = NativeMemoryCacheManager.getInstance(); + this.filterWeight = null; + this.exactSearcher = DEFAULT_EXACT_SEARCHER; + this.quantizationService = QuantizationService.getInstance(); + } + + public KNNWeight(KNNQuery query, float boost, Weight filterWeight) { + super(query); + this.knnQuery = query; + this.boost = boost; + this.nativeMemoryCacheManager = NativeMemoryCacheManager.getInstance(); + this.filterWeight = filterWeight; + this.exactSearcher = DEFAULT_EXACT_SEARCHER; + this.quantizationService = QuantizationService.getInstance(); + } + + public static void initialize(ModelDao modelDao) { + initialize(modelDao, new ExactSearcher(modelDao)); + } + + @VisibleForTesting + static void initialize(ModelDao modelDao, ExactSearcher exactSearcher) { + KNNWeight.modelDao = modelDao; + KNNWeight.DEFAULT_EXACT_SEARCHER = exactSearcher; + } + + @Override + public Explanation explain(LeafReaderContext context, int doc) { + return Explanation.match(1.0f, "No Explanation"); + } + + @Override + public Scorer scorer(LeafReaderContext context) throws IOException { + final Map docIdToScoreMap = searchLeaf(context, knnQuery.getK()); + if (docIdToScoreMap.isEmpty()) { + return KNNScorer.emptyScorer(this); + } + final int maxDoc = Collections.max(docIdToScoreMap.keySet()) + 1; + return new KNNScorer(this, ResultUtil.resultMapToDocIds(docIdToScoreMap, maxDoc), docIdToScoreMap, boost); + } + + /** + * Executes k nearest neighbor search for a segment to get the top K results + * This is made public purely to be able to be reused in {@link org.opensearch.knn.index.query.nativelib.NativeEngineKnnVectorQuery} + * + * @param context LeafReaderContext + * @param k Number of results to return + * @return A Map of docId to scores for top k results + */ + public Map searchLeaf(LeafReaderContext context, int k) throws IOException { + final BitSet filterBitSet = getFilteredDocsBitSet(context); + int cardinality = filterBitSet.cardinality(); + // We don't need to go to JNI layer if no documents are found which satisfy the filters + // We should give this condition a deeper look that where it should be placed. For now I feel this is a good + // place, + if (filterWeight != null && cardinality == 0) { + return Collections.emptyMap(); + } + /* + * The idea for this optimization is to get K results, we need to at least look at K vectors in the HNSW graph + * . Hence, if filtered results are less than K and filter query is present we should shift to exact search. + * This improves the recall. + */ + if (isFilteredExactSearchPreferred(cardinality)) { + return doExactSearch(context, filterBitSet, k); + } + Map docIdsToScoreMap = doANNSearch(context, filterBitSet, cardinality, k); + // See whether we have to perform exact search based on approx search results + // This is required if there are no native engine files or if approximate search returned + // results less than K, though we have more than k filtered docs + if (isExactSearchRequire(context, cardinality, docIdsToScoreMap.size())) { + final BitSet docs = filterWeight != null ? filterBitSet : null; + return doExactSearch(context, docs, k); + } + return docIdsToScoreMap; + } + + private BitSet getFilteredDocsBitSet(final LeafReaderContext ctx) throws IOException { + if (this.filterWeight == null) { + return new FixedBitSet(0); + } + + final Bits liveDocs = ctx.reader().getLiveDocs(); + final int maxDoc = ctx.reader().maxDoc(); + + final Scorer scorer = filterWeight.scorer(ctx); + if (scorer == null) { + return new FixedBitSet(0); + } + + return createBitSet(scorer.iterator(), liveDocs, maxDoc); + } + + private BitSet createBitSet(final DocIdSetIterator filteredDocIdsIterator, final Bits liveDocs, int maxDoc) throws IOException { + if (liveDocs == null && filteredDocIdsIterator instanceof BitSetIterator) { + // If we already have a BitSet and no deletions, reuse the BitSet + return ((BitSetIterator) filteredDocIdsIterator).getBitSet(); + } + // Create a new BitSet from matching and live docs + FilteredDocIdSetIterator filterIterator = new FilteredDocIdSetIterator(filteredDocIdsIterator) { + @Override + protected boolean match(int doc) { + return liveDocs == null || liveDocs.get(doc); + } + }; + return BitSet.of(filterIterator, maxDoc); + } + + private int[] getParentIdsArray(final LeafReaderContext context) throws IOException { + if (knnQuery.getParentsFilter() == null) { + return null; + } + return bitSetToIntArray(knnQuery.getParentsFilter().getBitSet(context)); + } + + private int[] bitSetToIntArray(final BitSet bitSet) { + final int cardinality = bitSet.cardinality(); + final int[] intArray = new int[cardinality]; + final BitSetIterator bitSetIterator = new BitSetIterator(bitSet, cardinality); + int index = 0; + int docId = bitSetIterator.nextDoc(); + while (docId != DocIdSetIterator.NO_MORE_DOCS) { + assert index < intArray.length; + intArray[index++] = docId; + docId = bitSetIterator.nextDoc(); + } + return intArray; + } + + private Map doExactSearch(final LeafReaderContext context, final BitSet acceptedDocs, int k) throws IOException { + final ExactSearcherContextBuilder exactSearcherContextBuilder = ExactSearcher.ExactSearcherContext.builder() + .isParentHits(true) + .k(k) + // setting to true, so that if quantization details are present we want to do search on the quantized + // vectors as this flow is used in first pass of search. + .useQuantizedVectorsForSearch(true) + .knnQuery(knnQuery); + if (acceptedDocs != null) { + exactSearcherContextBuilder.matchedDocs(acceptedDocs); + } + return exactSearch(context, exactSearcherContextBuilder.build()); + } + + private Map doANNSearch( + final LeafReaderContext context, + final BitSet filterIdsBitSet, + final int cardinality, + final int k + ) throws IOException { + final SegmentReader reader = Lucene.segmentReader(context.reader()); + + FieldInfo fieldInfo = FieldInfoExtractor.getFieldInfo(reader, knnQuery.getField()); + + if (fieldInfo == null) { + log.debug("[KNN] Field info not found for {}:{}", knnQuery.getField(), reader.getSegmentName()); + return Collections.emptyMap(); + } + + KNNEngine knnEngine; + SpaceType spaceType; + VectorDataType vectorDataType; + + // Check if a modelId exists. If so, the space type and engine will need to be picked up from the model's + // metadata. + String modelId = fieldInfo.getAttribute(MODEL_ID); + if (modelId != null) { + ModelMetadata modelMetadata = modelDao.getMetadata(modelId); + if (!ModelUtil.isModelCreated(modelMetadata)) { + throw new RuntimeException("Model \"" + modelId + "\" is not created."); + } + + knnEngine = modelMetadata.getKnnEngine(); + spaceType = modelMetadata.getSpaceType(); + vectorDataType = modelMetadata.getVectorDataType(); + } else { + String engineName = fieldInfo.attributes().getOrDefault(KNN_ENGINE, KNNEngine.DEFAULT.getName()); + knnEngine = KNNEngine.getEngine(engineName); + String spaceTypeName = fieldInfo.attributes().getOrDefault(SPACE_TYPE, SpaceType.L2.getValue()); + spaceType = SpaceType.getSpace(spaceTypeName); + vectorDataType = VectorDataType.get( + fieldInfo.attributes().getOrDefault(VECTOR_DATA_TYPE_FIELD, VectorDataType.FLOAT.getValue()) + ); + } + + final SegmentLevelQuantizationInfo segmentLevelQuantizationInfo = SegmentLevelQuantizationInfo.build( + reader, + fieldInfo, + knnQuery.getField() + ); + // TODO: Change type of vector once more quantization methods are supported + final byte[] quantizedVector = SegmentLevelQuantizationUtil.quantizeVector(knnQuery.getQueryVector(), segmentLevelQuantizationInfo); + + List engineFiles = KNNCodecUtil.getEngineFiles(knnEngine.getExtension(), knnQuery.getField(), reader.getSegmentInfo().info); + if (engineFiles.isEmpty()) { + log.debug("[KNN] No native engine files found for field {} for segment {}", knnQuery.getField(), reader.getSegmentName()); + return Collections.emptyMap(); + } + + final String vectorIndexFileName = engineFiles.get(0); + final String cacheKey = NativeMemoryCacheKeyHelper.constructCacheKey(vectorIndexFileName, reader.getSegmentInfo().info); + + final KNNQueryResult[] results; + KNNCounter.GRAPH_QUERY_REQUESTS.increment(); + + // We need to first get index allocation + NativeMemoryAllocation indexAllocation; + try { + indexAllocation = nativeMemoryCacheManager.get( + new NativeMemoryEntryContext.IndexEntryContext( + reader.directory(), + cacheKey, + NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance(), + getParametersAtLoading( + spaceType, + knnEngine, + knnQuery.getIndexName(), + // TODO: In the future, more vector data types will be supported with quantization + quantizedVector == null ? vectorDataType : VectorDataType.BINARY + ), + knnQuery.getIndexName(), + modelId + ), + true + ); + } catch (ExecutionException e) { + GRAPH_QUERY_ERRORS.increment(); + throw new RuntimeException(e); + } + + // From cardinality select different filterIds type + FilterIdsSelector filterIdsSelector = FilterIdsSelector.getFilterIdSelector(filterIdsBitSet, cardinality); + long[] filterIds = filterIdsSelector.getFilterIds(); + FilterIdsSelector.FilterIdsSelectorType filterType = filterIdsSelector.getFilterType(); + // Now that we have the allocation, we need to readLock it + indexAllocation.readLock(); + indexAllocation.incRef(); + try { + if (indexAllocation.isClosed()) { + throw new RuntimeException("Index has already been closed"); + } + int[] parentIds = getParentIdsArray(context); + if (k > 0) { + if (knnQuery.getVectorDataType() == VectorDataType.BINARY + || quantizedVector != null && quantizationService.getVectorDataTypeForTransfer(fieldInfo) == VectorDataType.BINARY) { + results = JNIService.queryBinaryIndex( + indexAllocation.getMemoryAddress(), + // TODO: In the future, quantizedVector can have other data types than byte + quantizedVector == null ? knnQuery.getByteQueryVector() : quantizedVector, + k, + knnQuery.getMethodParameters(), + knnEngine, + filterIds, + filterType.getValue(), + parentIds + ); + } else { + results = JNIService.queryIndex( + indexAllocation.getMemoryAddress(), + knnQuery.getQueryVector(), + k, + knnQuery.getMethodParameters(), + knnEngine, + filterIds, + filterType.getValue(), + parentIds + ); + } + } else { + results = JNIService.radiusQueryIndex( + indexAllocation.getMemoryAddress(), + knnQuery.getQueryVector(), + knnQuery.getRadius(), + knnQuery.getMethodParameters(), + knnEngine, + knnQuery.getContext().getMaxResultWindow(), + filterIds, + filterType.getValue(), + parentIds + ); + } + } catch (Exception e) { + GRAPH_QUERY_ERRORS.increment(); + throw new RuntimeException(e); + } finally { + indexAllocation.readUnlock(); + indexAllocation.decRef(); + } + if (results.length == 0) { + log.debug("[KNN] Query yielded 0 results"); + return Collections.emptyMap(); + } + + if (quantizedVector != null) { + return Arrays.stream(results) + .collect(Collectors.toMap(KNNQueryResult::getId, result -> knnEngine.score(result.getScore(), SpaceType.HAMMING))); + } + return Arrays.stream(results) + .collect(Collectors.toMap(KNNQueryResult::getId, result -> knnEngine.score(result.getScore(), spaceType))); + } + + /** + * Execute exact search for the given matched doc ids and return the results as a map of docId to score. + * @return Map of docId to score for the exact search results. + * @throws IOException If an error occurs during the search. + */ + public Map exactSearch( + final LeafReaderContext leafReaderContext, + final ExactSearcher.ExactSearcherContext exactSearcherContext + ) throws IOException { + return exactSearcher.searchLeaf(leafReaderContext, exactSearcherContext); + } + + @Override + public boolean isCacheable(LeafReaderContext context) { + return true; + } + + public static float normalizeScore(float score) { + if (score >= 0) return 1 / (1 + score); + return -score + 1; + } + + private boolean isFilteredExactSearchPreferred(final int filterIdsCount) { + if (filterWeight == null) { + return false; + } + log.debug( + "Info for doing exact search filterIdsLength : {}, Threshold value: {}", + filterIdsCount, + KNNSettings.getFilteredExactSearchThreshold(knnQuery.getIndexName()) + ); + int filterThresholdValue = KNNSettings.getFilteredExactSearchThreshold(knnQuery.getIndexName()); + // Refer this GitHub around more details https://github.com/opensearch-project/k-NN/issues/1049 on the logic + if (knnQuery.getRadius() == null && filterIdsCount <= knnQuery.getK()) { + return true; + } + // See user has defined Exact Search filtered threshold. if yes, then use that setting. + if (isExactSearchThresholdSettingSet(filterThresholdValue)) { + return filterThresholdValue >= filterIdsCount; + } + + // if no setting is set, then use the default max distance computation value to see if we can do exact search. + /** + * TODO we can have a different MAX_DISTANCE_COMPUTATIONS for binary index as computation cost for binary index + * is cheaper than computation cost for non binary vector + */ + return KNNConstants.MAX_DISTANCE_COMPUTATIONS >= filterIdsCount * (knnQuery.getVectorDataType() == VectorDataType.FLOAT + ? knnQuery.getQueryVector().length + : knnQuery.getByteQueryVector().length); + } + + /** + * This function validates if {@link KNNSettings#ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD} is set or not. This + * is done by validating if the setting value is equal to the default value. + * @param filterThresholdValue value of the Index Setting: {@link KNNSettings#ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD_SETTING} + * @return boolean true if the setting is set. + */ + private boolean isExactSearchThresholdSettingSet(int filterThresholdValue) { + return filterThresholdValue != KNNSettings.ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD_DEFAULT_VALUE; + } + + /** + * This condition mainly checks whether exact search should be performed or not + * @param context LeafReaderContext + * @param filterIdsCount count of filtered Doc ids + * @param annResultCount Count of Nearest Neighbours we got after doing filtered ANN Search. + * @return boolean - true if exactSearch needs to be done after ANNSearch. + */ + private boolean isExactSearchRequire(final LeafReaderContext context, final int filterIdsCount, final int annResultCount) { + if (annResultCount == 0 && isMissingNativeEngineFiles(context)) { + log.debug("Perform exact search after approximate search since no native engine files are available"); + return true; + } + if (isFilteredExactSearchRequireAfterANNSearch(filterIdsCount, annResultCount)) { + log.debug( + "Doing ExactSearch after doing ANNSearch as the number of documents returned are less than " + + "K, even when we have more than K filtered Ids. K: {}, ANNResults: {}, filteredIdCount: {}", + this.knnQuery.getK(), + annResultCount, + filterIdsCount + ); + return true; + } + return false; + } + + /** + * This condition mainly checks during filtered search we have more than K elements in filterIds but the ANN + * doesn't yield K nearest neighbors. + * @param filterIdsCount count of filtered Doc ids + * @param annResultCount Count of Nearest Neighbours we got after doing filtered ANN Search. + * @return boolean - true if exactSearch needs to be done after ANNSearch. + */ + private boolean isFilteredExactSearchRequireAfterANNSearch(final int filterIdsCount, final int annResultCount) { + return filterWeight != null && filterIdsCount >= knnQuery.getK() && knnQuery.getK() > annResultCount; + } + + /** + * This condition mainly checks whether segments has native engine files or not + * @return boolean - false if exactSearch needs to be done since no native engine files are in segments. + */ + private boolean isMissingNativeEngineFiles(LeafReaderContext context) { + final SegmentReader reader = Lucene.segmentReader(context.reader()); + final FieldInfo fieldInfo = FieldInfoExtractor.getFieldInfo(reader, knnQuery.getField()); + // if segment has no documents with at least 1 vector field, field info will be null + if (fieldInfo == null) { + return false; + } + final KNNEngine knnEngine = FieldInfoExtractor.extractKNNEngine(fieldInfo); + final List engineFiles = KNNCodecUtil.getEngineFiles( + knnEngine.getExtension(), + knnQuery.getField(), + reader.getSegmentInfo().info + ); + return engineFiles.isEmpty(); + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/RNNQueryFactory.java b/src/main/java/org/opensearch/knn/index/query/RNNQueryFactory.java new file mode 100644 index 000000000..b5166866c --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/RNNQueryFactory.java @@ -0,0 +1,154 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query; + +import static org.opensearch.knn.common.KNNConstants.DEFAULT_LUCENE_RADIAL_SEARCH_TRAVERSAL_SIMILARITY_RATIO; +import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; +import static org.opensearch.knn.index.VectorDataType.SUPPORTED_VECTOR_DATA_TYPES; + +import java.util.Locale; +import java.util.Map; + +import lombok.extern.log4j.Log4j2; +import org.apache.lucene.search.ByteVectorSimilarityQuery; +import org.apache.lucene.search.FloatVectorSimilarityQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.join.BitSetProducer; +import org.opensearch.index.IndexSettings; +import org.opensearch.index.query.QueryShardContext; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; + +/** + * Class to create radius nearest neighbor queries + */ +@Log4j2 +public class RNNQueryFactory extends BaseQueryFactory { + + /** + * Creates a Lucene query for a particular engine. + * + * @param knnEngine Engine to create the query for + * @param indexName Name of the OpenSearch index that is being queried + * @param fieldName Name of the field in the OpenSearch index that will be queried + * @param vector The query vector to get the nearest neighbors for + * @param radius the radius threshold for the nearest neighbors + * @return Lucene Query + */ + public static Query create( + KNNEngine knnEngine, + String indexName, + String fieldName, + float[] vector, + Float radius, + VectorDataType vectorDataType + ) { + final CreateQueryRequest createQueryRequest = CreateQueryRequest.builder() + .knnEngine(knnEngine) + .indexName(indexName) + .fieldName(fieldName) + .vector(vector) + .vectorDataType(vectorDataType) + .radius(radius) + .build(); + return create(createQueryRequest); + } + + /** + * Creates a Lucene query for a particular engine. + * @param createQueryRequest request object that has all required fields to construct the query + * @return Lucene Query + */ + public static Query create(RNNQueryFactory.CreateQueryRequest createQueryRequest) { + final String indexName = createQueryRequest.getIndexName(); + final String fieldName = createQueryRequest.getFieldName(); + final Float radius = createQueryRequest.getRadius(); + final float[] vector = createQueryRequest.getVector(); + final byte[] byteVector = createQueryRequest.getByteVector(); + final VectorDataType vectorDataType = createQueryRequest.getVectorDataType(); + final Query filterQuery = getFilterQuery(createQueryRequest); + final Map methodParameters = createQueryRequest.getMethodParameters(); + + if (KNNEngine.getEnginesThatCreateCustomSegmentFiles().contains(createQueryRequest.getKnnEngine())) { + BitSetProducer parentFilter = null; + QueryShardContext context = createQueryRequest.getContext().get(); + + if (createQueryRequest.getContext().isPresent()) { + parentFilter = context.getParentFilter(); + } + IndexSettings indexSettings = context.getIndexSettings(); + KNNQuery.Context knnQueryContext = new KNNQuery.Context(indexSettings.getMaxResultWindow()); + + return KNNQuery.builder() + .field(fieldName) + .queryVector(vector) + .indexName(indexName) + .parentsFilter(parentFilter) + .radius(radius) + .vectorDataType(vectorDataType) + .methodParameters(methodParameters) + .context(knnQueryContext) + .filterQuery(filterQuery) + .build(); + } + + log.debug(String.format("Creating Lucene r-NN query for index: %s \"\", field: %s \"\", k: %f", indexName, fieldName, radius)); + switch (vectorDataType) { + case BYTE: + return getByteVectorSimilarityQuery(fieldName, byteVector, radius, filterQuery); + case FLOAT: + return getFloatVectorSimilarityQuery(fieldName, vector, radius, filterQuery); + default: + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "Invalid value provided for [%s] field. Supported values are [%s], but got: %s", + VECTOR_DATA_TYPE_FIELD, + SUPPORTED_VECTOR_DATA_TYPES, + vectorDataType + ) + ); + } + } + + /** + * If radius is greater than 0, we return {@link FloatVectorSimilarityQuery} which will return all documents with similarity + * greater than or equal to the resultSimilarity. If filterQuery is not null, it will be used to filter the documents. + */ + private static Query getFloatVectorSimilarityQuery( + final String fieldName, + final float[] floatVector, + final float resultSimilarity, + final Query filterQuery + ) { + return new FloatVectorSimilarityQuery( + fieldName, + floatVector, + DEFAULT_LUCENE_RADIAL_SEARCH_TRAVERSAL_SIMILARITY_RATIO * resultSimilarity, + resultSimilarity, + filterQuery + ); + } + + /** + * If radius is greater than 0, we return {@link ByteVectorSimilarityQuery} which will return all documents with similarity + * greater than or equal to the resultSimilarity. If filterQuery is not null, it will be used to filter the documents. + */ + private static Query getByteVectorSimilarityQuery( + final String fieldName, + final byte[] byteVector, + final float resultSimilarity, + final Query filterQuery + ) { + return new ByteVectorSimilarityQuery( + fieldName, + byteVector, + DEFAULT_LUCENE_RADIAL_SEARCH_TRAVERSAL_SIMILARITY_RATIO * resultSimilarity, + resultSimilarity, + filterQuery + ); + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/ResultUtil.java b/src/main/java/org/opensearch/knn/index/query/ResultUtil.java new file mode 100644 index 000000000..df1ce3827 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/ResultUtil.java @@ -0,0 +1,118 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query; + +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.TopDocs; +import org.apache.lucene.search.TotalHits; +import org.apache.lucene.util.BitSet; +import org.apache.lucene.util.DocIdSetBuilder; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; + +/** + * Utility class used for processing results + */ +public final class ResultUtil { + + /** + * Reduce the results to only include the top k results across all leaf results + * + * @param perLeafResults Results from the list + * @param k the number of results across all leaf results to return + */ + public static void reduceToTopK(List> perLeafResults, int k) { + // Iterate over all scores to get min competitive score + PriorityQueue topKMinQueue = new PriorityQueue<>(k); + + int count = 0; + for (Map perLeafResult : perLeafResults) { + count += perLeafResult.size(); + for (Float score : perLeafResult.values()) { + if (topKMinQueue.size() < k) { + topKMinQueue.add(score); + } else if (topKMinQueue.peek() != null && score > topKMinQueue.peek()) { + topKMinQueue.poll(); + topKMinQueue.add(score); + } + } + } + + // If there are at most k results across everything, then no need to filter anything out + if (count <= k) { + return; + } + + // Reduce the results based on min competitive score + float minScore = topKMinQueue.peek() == null ? -Float.MAX_VALUE : topKMinQueue.peek(); + perLeafResults.forEach(results -> results.entrySet().removeIf(entry -> entry.getValue() < minScore)); + } + + /** + * Convert map to bit set, if resultMap is empty or null then returns an Optional. Returning an optional here to + * ensure that the caller is aware that BitSet may not be present + * + * @param resultMap Map of results + * @return BitSet of results; null is returned if the result map is empty + * @throws IOException If an error occurs during the search. + */ + public static BitSet resultMapToMatchBitSet(Map resultMap) throws IOException { + if (resultMap == null || resultMap.isEmpty()) { + return null; + } + final int maxDoc = Collections.max(resultMap.keySet()) + 1; + return BitSet.of(resultMapToDocIds(resultMap, maxDoc), maxDoc); + } + + /** + * Convert map of docs to doc id set iterator + * + * @param resultMap Map of results + * @param maxDoc Max doc id + * @return Doc id set iterator + * @throws IOException If an error occurs during the search. + */ + public static DocIdSetIterator resultMapToDocIds(Map resultMap, final int maxDoc) throws IOException { + if (resultMap.isEmpty()) { + return DocIdSetIterator.empty(); + } + final DocIdSetBuilder docIdSetBuilder = new DocIdSetBuilder(maxDoc); + final DocIdSetBuilder.BulkAdder setAdder = docIdSetBuilder.grow(resultMap.size()); + resultMap.keySet().forEach(setAdder::add); + return docIdSetBuilder.build().iterator(); + } + + /** + * COnvert map of results to top docs. Doc ids have proper offset + * + * @param resultMap map of scores for the leafs + * @param segmentOffset Offset to apply to ids to make them shard ids + * @return Top docs + */ + public static TopDocs resultMapToTopDocs(Map resultMap, int segmentOffset) { + if (resultMap.isEmpty()) { + return new TopDocs(new TotalHits(0, TotalHits.Relation.EQUAL_TO), new ScoreDoc[0]); + } + + int totalHits = 0; + final List scoreDocs = new ArrayList<>(); + final List> topScores = new ArrayList<>(resultMap.entrySet()); + topScores.sort(Map.Entry.comparingByValue().reversed()); + for (Map.Entry entry : topScores) { + ScoreDoc scoreDoc = new ScoreDoc(entry.getKey() + segmentOffset, entry.getValue()); + scoreDocs.add(scoreDoc); + totalHits++; + } + + return new TopDocs(new TotalHits(totalHits, TotalHits.Relation.EQUAL_TO), scoreDocs.toArray(ScoreDoc[]::new)); + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/SegmentLevelQuantizationInfo.java b/src/main/java/org/opensearch/knn/index/query/SegmentLevelQuantizationInfo.java new file mode 100644 index 000000000..d25774cdc --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/SegmentLevelQuantizationInfo.java @@ -0,0 +1,46 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.LeafReader; +import org.opensearch.knn.index.quantizationservice.QuantizationService; +import org.opensearch.knn.quantization.models.quantizationParams.QuantizationParams; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; + +import java.io.IOException; + +/** + * This class encapsulate the necessary details to do the quantization of the vectors present in a lucene segment. + */ +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class SegmentLevelQuantizationInfo { + private final QuantizationParams quantizationParams; + private final QuantizationState quantizationState; + + /** + * A builder like function to build the {@link SegmentLevelQuantizationInfo} + * @param leafReader {@link LeafReader} + * @param fieldInfo {@link FieldInfo} + * @param fieldName {@link String} + * @return {@link SegmentLevelQuantizationInfo} + * @throws IOException exception while creating the {@link SegmentLevelQuantizationInfo} object. + */ + public static SegmentLevelQuantizationInfo build(final LeafReader leafReader, final FieldInfo fieldInfo, final String fieldName) + throws IOException { + final QuantizationParams quantizationParams = QuantizationService.getInstance().getQuantizationParams(fieldInfo); + if (quantizationParams == null) { + return null; + } + final QuantizationState quantizationState = SegmentLevelQuantizationUtil.getQuantizationState(leafReader, fieldName); + return new SegmentLevelQuantizationInfo(quantizationParams, quantizationState); + } + +} diff --git a/src/main/java/org/opensearch/knn/index/query/SegmentLevelQuantizationUtil.java b/src/main/java/org/opensearch/knn/index/query/SegmentLevelQuantizationUtil.java new file mode 100644 index 000000000..46db8bb6b --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/SegmentLevelQuantizationUtil.java @@ -0,0 +1,60 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query; + +import lombok.experimental.UtilityClass; +import org.apache.lucene.index.LeafReader; +import org.opensearch.knn.index.codec.KNN990Codec.QuantizationConfigKNNCollector; +import org.opensearch.knn.index.quantizationservice.QuantizationService; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; + +import java.io.IOException; +import java.util.Locale; + +/** + * A utility class for doing Quantization related operation at a segment level. We can move this utility in {@link SegmentLevelQuantizationInfo} + * but I am keeping it thinking that {@link SegmentLevelQuantizationInfo} free from these utility functions to reduce + * the responsibilities of {@link SegmentLevelQuantizationInfo} class. + */ +@UtilityClass +public class SegmentLevelQuantizationUtil { + + /** + * A simple function to convert a vector to a quantized vector for a segment. + * @param vector array of float + * @return array of byte + */ + @SuppressWarnings("unchecked") + public static byte[] quantizeVector(final float[] vector, final SegmentLevelQuantizationInfo segmentLevelQuantizationInfo) { + if (segmentLevelQuantizationInfo == null) { + return null; + } + final QuantizationService quantizationService = QuantizationService.getInstance(); + // TODO: We are converting the output of Quantize to byte array for now. But this needs to be fixed when + // other types of quantized outputs are returned like float[]. + return (byte[]) quantizationService.quantize( + segmentLevelQuantizationInfo.getQuantizationState(), + vector, + quantizationService.createQuantizationOutput(segmentLevelQuantizationInfo.getQuantizationParams()) + ); + } + + /** + * A utility function to get {@link QuantizationState} for a given segment and field. + * @param leafReader {@link LeafReader} + * @param fieldName {@link String} + * @return {@link QuantizationState} + * @throws IOException exception during reading the {@link QuantizationState} + */ + static QuantizationState getQuantizationState(final LeafReader leafReader, String fieldName) throws IOException { + final QuantizationConfigKNNCollector tempCollector = new QuantizationConfigKNNCollector(); + leafReader.searchNearestVectors(fieldName, new float[0], tempCollector, null); + if (tempCollector.getQuantizationState() == null) { + throw new IllegalStateException(String.format(Locale.ROOT, "No quantization state found for field %s", fieldName)); + } + return tempCollector.getQuantizationState(); + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/iterators/BinaryVectorIdsKNNIterator.java b/src/main/java/org/opensearch/knn/index/query/iterators/BinaryVectorIdsKNNIterator.java new file mode 100644 index 000000000..5bab5b573 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/iterators/BinaryVectorIdsKNNIterator.java @@ -0,0 +1,92 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.iterators; + +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.util.BitSet; +import org.apache.lucene.util.BitSetIterator; +import org.opensearch.common.Nullable; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.vectorvalues.KNNBinaryVectorValues; + +import java.io.IOException; + +/** + * Inspired by DiversifyingChildrenFloatKnnVectorQuery in lucene + * https://github.com/apache/lucene/blob/7b8aece125aabff2823626d5b939abf4747f63a7/lucene/join/src/java/org/apache/lucene/search/join/DiversifyingChildrenFloatKnnVectorQuery.java#L162 + * + * The class is used in KNNWeight to score all docs, but, it iterates over filterIdsArray if filter is provided + */ +public class BinaryVectorIdsKNNIterator implements KNNIterator { + protected final BitSetIterator bitSetIterator; + protected final byte[] queryVector; + protected final KNNBinaryVectorValues binaryVectorValues; + protected final SpaceType spaceType; + protected float currentScore = Float.NEGATIVE_INFINITY; + protected int docId; + + public BinaryVectorIdsKNNIterator( + @Nullable final BitSet filterIdsBitSet, + final byte[] queryVector, + final KNNBinaryVectorValues binaryVectorValues, + final SpaceType spaceType + ) throws IOException { + this.bitSetIterator = filterIdsBitSet == null ? null : new BitSetIterator(filterIdsBitSet, filterIdsBitSet.length()); + this.queryVector = queryVector; + this.binaryVectorValues = binaryVectorValues; + this.spaceType = spaceType; + // This cannot be moved inside nextDoc() method since it will break when we have nested field, where + // nextDoc should already be referring to next knnVectorValues + this.docId = getNextDocId(); + } + + public BinaryVectorIdsKNNIterator(final byte[] queryVector, final KNNBinaryVectorValues binaryVectorValues, final SpaceType spaceType) + throws IOException { + this(null, queryVector, binaryVectorValues, spaceType); + } + + /** + * Advance to the next doc and update score value with score of the next doc. + * DocIdSetIterator.NO_MORE_DOCS is returned when there is no more docs + * + * @return next doc id + */ + @Override + public int nextDoc() throws IOException { + + if (docId == DocIdSetIterator.NO_MORE_DOCS) { + return DocIdSetIterator.NO_MORE_DOCS; + } + currentScore = computeScore(); + int currentDocId = docId; + docId = getNextDocId(); + return currentDocId; + } + + @Override + public float score() { + return currentScore; + } + + protected float computeScore() throws IOException { + final byte[] vector = binaryVectorValues.getVector(); + // Calculates a similarity score between the two vectors with a specified function. Higher similarity + // scores correspond to closer vectors. + return spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector); + } + + protected int getNextDocId() throws IOException { + if (bitSetIterator == null) { + return binaryVectorValues.nextDoc(); + } + int nextDocID = this.bitSetIterator.nextDoc(); + // For filter case, advance vector values to corresponding doc id from filter bit set + if (nextDocID != DocIdSetIterator.NO_MORE_DOCS) { + binaryVectorValues.advance(nextDocID); + } + return nextDocID; + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/iterators/ByteVectorIdsKNNIterator.java b/src/main/java/org/opensearch/knn/index/query/iterators/ByteVectorIdsKNNIterator.java new file mode 100644 index 000000000..0e8005163 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/iterators/ByteVectorIdsKNNIterator.java @@ -0,0 +1,102 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.iterators; + +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.util.BitSet; +import org.apache.lucene.util.BitSetIterator; +import org.opensearch.common.Nullable; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.vectorvalues.KNNByteVectorValues; + +import java.io.IOException; + +/** + * Inspired by DiversifyingChildrenFloatKnnVectorQuery in lucene + * https://github.com/apache/lucene/blob/7b8aece125aabff2823626d5b939abf4747f63a7/lucene/join/src/java/org/apache/lucene/search/join/DiversifyingChildrenFloatKnnVectorQuery.java#L162 + * + * The class is used in KNNWeight to score all docs, but, it iterates over filterIdsArray if filter is provided + */ +public class ByteVectorIdsKNNIterator implements KNNIterator { + protected final BitSetIterator bitSetIterator; + protected final float[] queryVector; + protected final KNNByteVectorValues byteVectorValues; + protected final SpaceType spaceType; + protected float currentScore = Float.NEGATIVE_INFINITY; + protected int docId; + + public ByteVectorIdsKNNIterator( + @Nullable final BitSet filterIdsBitSet, + final float[] queryVector, + final KNNByteVectorValues byteVectorValues, + final SpaceType spaceType + ) throws IOException { + this.bitSetIterator = filterIdsBitSet == null ? null : new BitSetIterator(filterIdsBitSet, filterIdsBitSet.length()); + this.queryVector = queryVector; + this.byteVectorValues = byteVectorValues; + this.spaceType = spaceType; + // This cannot be moved inside nextDoc() method since it will break when we have nested field, where + // nextDoc should already be referring to next knnVectorValues + this.docId = getNextDocId(); + } + + public ByteVectorIdsKNNIterator(final float[] queryVector, final KNNByteVectorValues byteVectorValues, final SpaceType spaceType) + throws IOException { + this(null, queryVector, byteVectorValues, spaceType); + } + + /** + * Advance to the next doc and update score value with score of the next doc. + * DocIdSetIterator.NO_MORE_DOCS is returned when there is no more docs + * + * @return next doc id + */ + @Override + public int nextDoc() throws IOException { + + if (docId == DocIdSetIterator.NO_MORE_DOCS) { + return DocIdSetIterator.NO_MORE_DOCS; + } + currentScore = computeScore(); + int currentDocId = docId; + docId = getNextDocId(); + return currentDocId; + } + + @Override + public float score() { + return currentScore; + } + + protected float computeScore() throws IOException { + final byte[] vector = byteVectorValues.getVector(); + // Calculates a similarity score between the two vectors with a specified function. Higher similarity + // scores correspond to closer vectors. + + // The query vector of Faiss byte vector is a Float array because ScalarQuantizer accepts it as float array. + // To compute the score between this query vector and each vector in KNNByteVectorValues we are casting this query vector into byte + // array directly. + // This is safe to do so because float query vector already has validated byte values. Do not reuse this direct cast at any other + // place. + final byte[] byteQueryVector = new byte[queryVector.length]; + for (int i = 0; i < queryVector.length; i++) { + byteQueryVector[i] = (byte) queryVector[i]; + } + return spaceType.getKnnVectorSimilarityFunction().compare(byteQueryVector, vector); + } + + protected int getNextDocId() throws IOException { + if (bitSetIterator == null) { + return byteVectorValues.nextDoc(); + } + int nextDocID = this.bitSetIterator.nextDoc(); + // For filter case, advance vector values to corresponding doc id from filter bit set + if (nextDocID != DocIdSetIterator.NO_MORE_DOCS) { + byteVectorValues.advance(nextDocID); + } + return nextDocID; + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/iterators/KNNIterator.java b/src/main/java/org/opensearch/knn/index/query/iterators/KNNIterator.java new file mode 100644 index 000000000..00cbb3aa2 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/iterators/KNNIterator.java @@ -0,0 +1,14 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.iterators; + +import java.io.IOException; + +public interface KNNIterator { + int nextDoc() throws IOException; + + float score(); +} diff --git a/src/main/java/org/opensearch/knn/index/query/iterators/NestedBinaryVectorIdsKNNIterator.java b/src/main/java/org/opensearch/knn/index/query/iterators/NestedBinaryVectorIdsKNNIterator.java new file mode 100644 index 000000000..97bf3517e --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/iterators/NestedBinaryVectorIdsKNNIterator.java @@ -0,0 +1,77 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.iterators; + +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.util.BitSet; +import org.opensearch.common.Nullable; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.vectorvalues.KNNBinaryVectorValues; + +import java.io.IOException; + +/** + * This iterator iterates filterIdsArray to scoreif filter is provided else it iterates over all docs. + * However, it dedupe docs per each parent doc + * of which ID is set in parentBitSet and only return best child doc with the highest score. + */ +public class NestedBinaryVectorIdsKNNIterator extends BinaryVectorIdsKNNIterator { + private final BitSet parentBitSet; + + public NestedBinaryVectorIdsKNNIterator( + @Nullable final BitSet filterIdsArray, + final byte[] queryVector, + final KNNBinaryVectorValues binaryVectorValues, + final SpaceType spaceType, + final BitSet parentBitSet + ) throws IOException { + super(filterIdsArray, queryVector, binaryVectorValues, spaceType); + this.parentBitSet = parentBitSet; + } + + public NestedBinaryVectorIdsKNNIterator( + final byte[] queryVector, + final KNNBinaryVectorValues binaryVectorValues, + final SpaceType spaceType, + final BitSet parentBitSet + ) throws IOException { + super(null, queryVector, binaryVectorValues, spaceType); + this.parentBitSet = parentBitSet; + } + + /** + * Advance to the next best child doc per parent and update score with the best score among child docs from the parent. + * DocIdSetIterator.NO_MORE_DOCS is returned when there is no more docs + * + * @return next best child doc id + */ + @Override + public int nextDoc() throws IOException { + if (docId == DocIdSetIterator.NO_MORE_DOCS) { + return DocIdSetIterator.NO_MORE_DOCS; + } + + currentScore = Float.NEGATIVE_INFINITY; + int currentParent = parentBitSet.nextSetBit(docId); + int bestChild = -1; + + // In order to traverse all children for given parent, we have to use docId < parentId, because, + // kNNVectorValues will not have parent id since DocId is unique per segment. For ex: let's say for doc id 1, there is one child + // and for doc id 5, there are three children. In that case knnVectorValues iterator will have [0, 2, 3, 4] + // and parentBitSet will have [1,5] + // Hence, we have to iterate till docId from knnVectorValues is less than parentId instead of till equal to parentId + while (docId != DocIdSetIterator.NO_MORE_DOCS && docId < currentParent) { + float score = computeScore(); + if (score > currentScore) { + bestChild = docId; + currentScore = score; + } + docId = getNextDocId(); + } + + return bestChild; + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/iterators/NestedByteVectorIdsKNNIterator.java b/src/main/java/org/opensearch/knn/index/query/iterators/NestedByteVectorIdsKNNIterator.java new file mode 100644 index 000000000..9644b620f --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/iterators/NestedByteVectorIdsKNNIterator.java @@ -0,0 +1,77 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.iterators; + +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.util.BitSet; +import org.opensearch.common.Nullable; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.vectorvalues.KNNByteVectorValues; + +import java.io.IOException; + +/** + * This iterator iterates filterIdsArray to score if filter is provided else it iterates over all docs. + * However, it dedupe docs per each parent doc + * of which ID is set in parentBitSet and only return best child doc with the highest score. + */ +public class NestedByteVectorIdsKNNIterator extends ByteVectorIdsKNNIterator { + private final BitSet parentBitSet; + + public NestedByteVectorIdsKNNIterator( + @Nullable final BitSet filterIdsArray, + final float[] queryVector, + final KNNByteVectorValues byteVectorValues, + final SpaceType spaceType, + final BitSet parentBitSet + ) throws IOException { + super(filterIdsArray, queryVector, byteVectorValues, spaceType); + this.parentBitSet = parentBitSet; + } + + public NestedByteVectorIdsKNNIterator( + final float[] queryVector, + final KNNByteVectorValues binaryVectorValues, + final SpaceType spaceType, + final BitSet parentBitSet + ) throws IOException { + super(null, queryVector, binaryVectorValues, spaceType); + this.parentBitSet = parentBitSet; + } + + /** + * Advance to the next best child doc per parent and update score with the best score among child docs from the parent. + * DocIdSetIterator.NO_MORE_DOCS is returned when there is no more docs + * + * @return next best child doc id + */ + @Override + public int nextDoc() throws IOException { + if (docId == DocIdSetIterator.NO_MORE_DOCS) { + return DocIdSetIterator.NO_MORE_DOCS; + } + + currentScore = Float.NEGATIVE_INFINITY; + int currentParent = parentBitSet.nextSetBit(docId); + int bestChild = -1; + + // In order to traverse all children for given parent, we have to use docId < parentId, because, + // kNNVectorValues will not have parent id since DocId is unique per segment. For ex: let's say for doc id 1, there is one child + // and for doc id 5, there are three children. In that case knnVectorValues iterator will have [0, 2, 3, 4] + // and parentBitSet will have [1,5] + // Hence, we have to iterate till docId from knnVectorValues is less than parentId instead of till equal to parentId + while (docId != DocIdSetIterator.NO_MORE_DOCS && docId < currentParent) { + float score = computeScore(); + if (score > currentScore) { + bestChild = docId; + currentScore = score; + } + docId = getNextDocId(); + } + + return bestChild; + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/iterators/NestedVectorIdsKNNIterator.java b/src/main/java/org/opensearch/knn/index/query/iterators/NestedVectorIdsKNNIterator.java new file mode 100644 index 000000000..692793b99 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/iterators/NestedVectorIdsKNNIterator.java @@ -0,0 +1,89 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.iterators; + +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.util.BitSet; +import org.opensearch.common.Nullable; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.query.SegmentLevelQuantizationInfo; +import org.opensearch.knn.index.vectorvalues.KNNFloatVectorValues; + +import java.io.IOException; + +/** + * This iterator iterates filterIdsArray to score if filter is provided else it iterates over all docs. + * However, it dedupe docs per each parent doc + * of which ID is set in parentBitSet and only return best child doc with the highest score. + */ +public class NestedVectorIdsKNNIterator extends VectorIdsKNNIterator { + private final BitSet parentBitSet; + + public NestedVectorIdsKNNIterator( + @Nullable final BitSet filterIdsArray, + final float[] queryVector, + final KNNFloatVectorValues knnFloatVectorValues, + final SpaceType spaceType, + final BitSet parentBitSet + ) throws IOException { + this(filterIdsArray, queryVector, knnFloatVectorValues, spaceType, parentBitSet, null, null); + } + + public NestedVectorIdsKNNIterator( + final float[] queryVector, + final KNNFloatVectorValues knnFloatVectorValues, + final SpaceType spaceType, + final BitSet parentBitSet + ) throws IOException { + this(null, queryVector, knnFloatVectorValues, spaceType, parentBitSet, null, null); + } + + public NestedVectorIdsKNNIterator( + @Nullable final BitSet filterIdsArray, + final float[] queryVector, + final KNNFloatVectorValues knnFloatVectorValues, + final SpaceType spaceType, + final BitSet parentBitSet, + final byte[] quantizedVector, + final SegmentLevelQuantizationInfo segmentLevelQuantizationInfo + ) throws IOException { + super(filterIdsArray, queryVector, knnFloatVectorValues, spaceType, quantizedVector, segmentLevelQuantizationInfo); + this.parentBitSet = parentBitSet; + } + + /** + * Advance to the next best child doc per parent and update score with the best score among child docs from the parent. + * DocIdSetIterator.NO_MORE_DOCS is returned when there is no more docs + * + * @return next best child doc id + */ + @Override + public int nextDoc() throws IOException { + if (docId == DocIdSetIterator.NO_MORE_DOCS) { + return DocIdSetIterator.NO_MORE_DOCS; + } + + currentScore = Float.NEGATIVE_INFINITY; + int currentParent = parentBitSet.nextSetBit(docId); + int bestChild = -1; + + // In order to traverse all children for given parent, we have to use docId < parentId, because, + // kNNVectorValues will not have parent id since DocId is unique per segment. For ex: let's say for doc id 1, there is one child + // and for doc id 5, there are three children. In that case knnVectorValues iterator will have [0, 2, 3, 4] + // and parentBitSet will have [1,5] + // Hence, we have to iterate till docId from knnVectorValues is less than parentId instead of till equal to parentId + while (docId != DocIdSetIterator.NO_MORE_DOCS && docId < currentParent) { + float score = computeScore(); + if (score > currentScore) { + bestChild = docId; + currentScore = score; + } + docId = getNextDocId(); + } + + return bestChild; + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/iterators/VectorIdsKNNIterator.java b/src/main/java/org/opensearch/knn/index/query/iterators/VectorIdsKNNIterator.java new file mode 100644 index 000000000..9fb354242 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/iterators/VectorIdsKNNIterator.java @@ -0,0 +1,114 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.iterators; + +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.util.BitSet; +import org.apache.lucene.util.BitSetIterator; +import org.opensearch.common.Nullable; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.query.SegmentLevelQuantizationInfo; +import org.opensearch.knn.index.query.SegmentLevelQuantizationUtil; +import org.opensearch.knn.index.vectorvalues.KNNFloatVectorValues; + +import java.io.IOException; + +/** + * Inspired by DiversifyingChildrenFloatKnnVectorQuery in lucene + * https://github.com/apache/lucene/blob/7b8aece125aabff2823626d5b939abf4747f63a7/lucene/join/src/java/org/apache/lucene/search/join/DiversifyingChildrenFloatKnnVectorQuery.java#L162 + * + * The class is used in KNNWeight to score all docs, but, it iterates over filterIdsArray if filter is provided + */ +public class VectorIdsKNNIterator implements KNNIterator { + protected final BitSetIterator bitSetIterator; + protected final float[] queryVector; + private final byte[] quantizedQueryVector; + protected final KNNFloatVectorValues knnFloatVectorValues; + protected final SpaceType spaceType; + protected float currentScore = Float.NEGATIVE_INFINITY; + protected int docId; + private final SegmentLevelQuantizationInfo segmentLevelQuantizationInfo; + + public VectorIdsKNNIterator( + @Nullable final BitSet filterIdsBitSet, + final float[] queryVector, + final KNNFloatVectorValues knnFloatVectorValues, + final SpaceType spaceType + ) throws IOException { + this(filterIdsBitSet, queryVector, knnFloatVectorValues, spaceType, null, null); + } + + public VectorIdsKNNIterator(final float[] queryVector, final KNNFloatVectorValues knnFloatVectorValues, final SpaceType spaceType) + throws IOException { + this(null, queryVector, knnFloatVectorValues, spaceType, null, null); + } + + public VectorIdsKNNIterator( + @Nullable final BitSet filterIdsBitSet, + final float[] queryVector, + final KNNFloatVectorValues knnFloatVectorValues, + final SpaceType spaceType, + final byte[] quantizedQueryVector, + final SegmentLevelQuantizationInfo segmentLevelQuantizationInfo + ) throws IOException { + this.bitSetIterator = filterIdsBitSet == null ? null : new BitSetIterator(filterIdsBitSet, filterIdsBitSet.length()); + this.queryVector = queryVector; + this.knnFloatVectorValues = knnFloatVectorValues; + this.spaceType = spaceType; + // This cannot be moved inside nextDoc() method since it will break when we have nested field, where + // nextDoc should already be referring to next knnVectorValues + this.docId = getNextDocId(); + this.quantizedQueryVector = quantizedQueryVector; + this.segmentLevelQuantizationInfo = segmentLevelQuantizationInfo; + } + + /** + * Advance to the next doc and update score value with score of the next doc. + * DocIdSetIterator.NO_MORE_DOCS is returned when there is no more docs + * + * @return next doc id + */ + @Override + public int nextDoc() throws IOException { + + if (docId == DocIdSetIterator.NO_MORE_DOCS) { + return DocIdSetIterator.NO_MORE_DOCS; + } + currentScore = computeScore(); + int currentDocId = docId; + docId = getNextDocId(); + return currentDocId; + } + + @Override + public float score() { + return currentScore; + } + + protected float computeScore() throws IOException { + final float[] vector = knnFloatVectorValues.getVector(); + if (segmentLevelQuantizationInfo != null && quantizedQueryVector != null) { + byte[] quantizedVector = SegmentLevelQuantizationUtil.quantizeVector(vector, segmentLevelQuantizationInfo); + return SpaceType.HAMMING.getKnnVectorSimilarityFunction().compare(quantizedQueryVector, quantizedVector); + } else { + // Calculates a similarity score between the two vectors with a specified function. Higher similarity + // scores correspond to closer vectors. + return spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector); + } + } + + protected int getNextDocId() throws IOException { + if (bitSetIterator == null) { + return knnFloatVectorValues.nextDoc(); + } + int nextDocID = this.bitSetIterator.nextDoc(); + // For filter case, advance vector values to corresponding doc id from filter bit set + if (nextDocID != DocIdSetIterator.NO_MORE_DOCS) { + knnFloatVectorValues.advance(nextDocID); + } + return nextDocID; + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/nativelib/DocAndScoreQuery.java b/src/main/java/org/opensearch/knn/index/query/nativelib/DocAndScoreQuery.java new file mode 100644 index 000000000..b94264b4d --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/nativelib/DocAndScoreQuery.java @@ -0,0 +1,186 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.nativelib; + +import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.search.Explanation; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.QueryVisitor; +import org.apache.lucene.search.ScoreMode; +import org.apache.lucene.search.Scorer; +import org.apache.lucene.search.Weight; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Objects; + +import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS; + +/** + * This is the same as {@link org.apache.lucene.search.AbstractKnnVectorQuery.DocAndScoreQuery} + */ +final class DocAndScoreQuery extends Query { + + private final int k; + private final int[] docs; + private final float[] scores; + private final int[] segmentStarts; + private final Object contextIdentity; + + DocAndScoreQuery(int k, int[] docs, float[] scores, int[] segmentStarts, Object contextIdentity) { + this.k = k; + this.docs = docs; + this.scores = scores; + this.segmentStarts = segmentStarts; + this.contextIdentity = contextIdentity; + } + + @Override + public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) { + if (searcher.getIndexReader().getContext().id() != contextIdentity) { + throw new IllegalStateException("This DocAndScore query was created by a different reader"); + } + + return new Weight(this) { + @Override + public Explanation explain(LeafReaderContext context, int doc) { + int found = Arrays.binarySearch(docs, doc + context.docBase); + if (found < 0) { + return Explanation.noMatch("not in top " + k); + } + return Explanation.match(scores[found] * boost, "within top " + k); + } + + @Override + public int count(LeafReaderContext context) { + return segmentStarts[context.ord + 1] - segmentStarts[context.ord]; + } + + @Override + public Scorer scorer(LeafReaderContext context) { + if (segmentStarts[context.ord] == segmentStarts[context.ord + 1]) { + return null; + } + return new Scorer(this) { + final int lower = segmentStarts[context.ord]; + final int upper = segmentStarts[context.ord + 1]; + int upTo = -1; + + @Override + public DocIdSetIterator iterator() { + return new DocIdSetIterator() { + @Override + public int docID() { + return docIdNoShadow(); + } + + @Override + public int nextDoc() { + if (upTo == -1) { + upTo = lower; + } else { + ++upTo; + } + return docIdNoShadow(); + } + + @Override + public int advance(int target) throws IOException { + return slowAdvance(target); + } + + @Override + public long cost() { + return upper - lower; + } + }; + } + + @Override + public float getMaxScore(int docId) { + docId += context.docBase; + float maxScore = 0; + for (int idx = Math.max(0, upTo); idx < upper && docs[idx] <= docId; idx++) { + maxScore = Math.max(maxScore, scores[idx]); + } + return maxScore * boost; + } + + @Override + public float score() { + return scores[upTo] * boost; + } + + @Override + public int advanceShallow(int docid) { + int start = Math.max(upTo, lower); + int docidIndex = Arrays.binarySearch(docs, start, upper, docid + context.docBase); + if (docidIndex < 0) { + docidIndex = -1 - docidIndex; + } + if (docidIndex >= upper) { + return NO_MORE_DOCS; + } + return docs[docidIndex]; + } + + /** + * move the implementation of docID() into a differently-named method so we can call it + * from DocIDSetIterator.docID() even though this class is anonymous + * + * @return the current docid + */ + private int docIdNoShadow() { + if (upTo == -1) { + return -1; + } + if (upTo >= upper) { + return NO_MORE_DOCS; + } + return docs[upTo] - context.docBase; + } + + @Override + public int docID() { + return docIdNoShadow(); + } + }; + } + + @Override + public boolean isCacheable(LeafReaderContext ctx) { + return true; + } + }; + } + + @Override + public String toString(String field) { + return "DocAndScore[" + k + "][docs:" + Arrays.toString(docs) + ", scores:" + Arrays.toString(scores) + "]"; + } + + @Override + public void visit(QueryVisitor visitor) { + visitor.visitLeaf(this); + } + + @Override + public boolean equals(Object obj) { + if (!sameClassAs(obj)) { + return false; + } + return contextIdentity == ((DocAndScoreQuery) obj).contextIdentity + && Arrays.equals(docs, ((DocAndScoreQuery) obj).docs) + && Arrays.equals(scores, ((DocAndScoreQuery) obj).scores); + } + + @Override + public int hashCode() { + return Objects.hash(classHash(), contextIdentity, Arrays.hashCode(docs), Arrays.hashCode(scores)); + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java b/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java new file mode 100644 index 000000000..bf12e63c0 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/nativelib/NativeEngineKnnVectorQuery.java @@ -0,0 +1,198 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.nativelib; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.MatchNoDocsQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.QueryVisitor; +import org.apache.lucene.search.ScoreMode; +import org.apache.lucene.search.TopDocs; +import org.apache.lucene.search.Weight; +import org.apache.lucene.util.BitSet; +import org.apache.lucene.util.Bits; +import org.opensearch.common.StopWatch; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.query.ExactSearcher; +import org.opensearch.knn.index.query.KNNQuery; +import org.opensearch.knn.index.query.KNNWeight; +import org.opensearch.knn.index.query.ResultUtil; +import org.opensearch.knn.index.query.rescore.RescoreContext; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.Callable; + +/** + * {@link KNNQuery} executes approximate nearest neighbor search (ANN) on a segment level. + * {@link NativeEngineKnnVectorQuery} executes approximate nearest neighbor search but gives + * us the control to combine the top k results in each leaf and post process the results just + * for k-NN query if required. This is done by overriding rewrite method to execute ANN on each leaf + * {@link KNNQuery} does not give the ability to post process segment results. + */ +@Log4j2 +@Getter +@RequiredArgsConstructor +public class NativeEngineKnnVectorQuery extends Query { + + private final KNNQuery knnQuery; + + @Override + public Weight createWeight(IndexSearcher indexSearcher, ScoreMode scoreMode, float boost) throws IOException { + final IndexReader reader = indexSearcher.getIndexReader(); + final KNNWeight knnWeight = (KNNWeight) knnQuery.createWeight(indexSearcher, ScoreMode.COMPLETE, 1); + List leafReaderContexts = reader.leaves(); + List> perLeafResults; + RescoreContext rescoreContext = knnQuery.getRescoreContext(); + final int finalK = knnQuery.getK(); + if (rescoreContext == null) { + perLeafResults = doSearch(indexSearcher, leafReaderContexts, knnWeight, finalK); + } else { + boolean isShardLevelRescoringEnabled = KNNSettings.isShardLevelRescoringEnabledForDiskBasedVector(knnQuery.getIndexName()); + int dimension = knnQuery.getQueryVector().length; + int firstPassK = rescoreContext.getFirstPassK(finalK, isShardLevelRescoringEnabled, dimension); + perLeafResults = doSearch(indexSearcher, leafReaderContexts, knnWeight, firstPassK); + if (isShardLevelRescoringEnabled == true) { + ResultUtil.reduceToTopK(perLeafResults, firstPassK); + } + + StopWatch stopWatch = new StopWatch().start(); + perLeafResults = doRescore(indexSearcher, leafReaderContexts, knnWeight, perLeafResults, finalK); + long rescoreTime = stopWatch.stop().totalTime().millis(); + log.debug("Rescoring results took {} ms. oversampled k:{}, segments:{}", rescoreTime, firstPassK, leafReaderContexts.size()); + } + ResultUtil.reduceToTopK(perLeafResults, finalK); + TopDocs[] topDocs = new TopDocs[perLeafResults.size()]; + for (int i = 0; i < perLeafResults.size(); i++) { + topDocs[i] = ResultUtil.resultMapToTopDocs(perLeafResults.get(i), leafReaderContexts.get(i).docBase); + } + + TopDocs topK = TopDocs.merge(knnQuery.getK(), topDocs); + if (topK.scoreDocs.length == 0) { + return new MatchNoDocsQuery().createWeight(indexSearcher, scoreMode, boost); + } + return createDocAndScoreQuery(reader, topK).createWeight(indexSearcher, scoreMode, boost); + } + + private List> doSearch( + final IndexSearcher indexSearcher, + List leafReaderContexts, + KNNWeight knnWeight, + int k + ) throws IOException { + List>> tasks = new ArrayList<>(leafReaderContexts.size()); + for (LeafReaderContext leafReaderContext : leafReaderContexts) { + tasks.add(() -> searchLeaf(leafReaderContext, knnWeight, k)); + } + return indexSearcher.getTaskExecutor().invokeAll(tasks); + } + + private List> doRescore( + final IndexSearcher indexSearcher, + List leafReaderContexts, + KNNWeight knnWeight, + List> perLeafResults, + int k + ) throws IOException { + List>> rescoreTasks = new ArrayList<>(leafReaderContexts.size()); + for (int i = 0; i < perLeafResults.size(); i++) { + LeafReaderContext leafReaderContext = leafReaderContexts.get(i); + int finalI = i; + rescoreTasks.add(() -> { + final BitSet convertedBitSet = ResultUtil.resultMapToMatchBitSet(perLeafResults.get(finalI)); + // if there is no docIds to re-score from a segment we should return early to ensure that we are not + // wasting any computation + if (convertedBitSet == null) { + return Collections.emptyMap(); + } + final ExactSearcher.ExactSearcherContext exactSearcherContext = ExactSearcher.ExactSearcherContext.builder() + .matchedDocs(convertedBitSet) + // setting to false because in re-scoring we want to do exact search on full precision vectors + .useQuantizedVectorsForSearch(false) + .k(k) + .isParentHits(false) + .knnQuery(knnQuery) + .build(); + return knnWeight.exactSearch(leafReaderContext, exactSearcherContext); + }); + } + return indexSearcher.getTaskExecutor().invokeAll(rescoreTasks); + } + + private Query createDocAndScoreQuery(IndexReader reader, TopDocs topK) { + int len = topK.scoreDocs.length; + Arrays.sort(topK.scoreDocs, Comparator.comparingInt(a -> a.doc)); + int[] docs = new int[len]; + float[] scores = new float[len]; + for (int i = 0; i < len; i++) { + docs[i] = topK.scoreDocs[i].doc; + scores[i] = topK.scoreDocs[i].score; + } + int[] segmentStarts = findSegmentStarts(reader, docs); + return new DocAndScoreQuery(knnQuery.getK(), docs, scores, segmentStarts, reader.getContext().id()); + } + + static int[] findSegmentStarts(IndexReader reader, int[] docs) { + int[] starts = new int[reader.leaves().size() + 1]; + starts[starts.length - 1] = docs.length; + if (starts.length == 2) { + return starts; + } + int resultIndex = 0; + for (int i = 1; i < starts.length - 1; i++) { + int upper = reader.leaves().get(i).docBase; + resultIndex = Arrays.binarySearch(docs, resultIndex, docs.length, upper); + if (resultIndex < 0) { + resultIndex = -1 - resultIndex; + } + starts[i] = resultIndex; + } + return starts; + } + + private Map searchLeaf(LeafReaderContext ctx, KNNWeight queryWeight, int k) throws IOException { + final Map leafDocScores = queryWeight.searchLeaf(ctx, k); + final Bits liveDocs = ctx.reader().getLiveDocs(); + if (liveDocs != null) { + leafDocScores.entrySet().removeIf(entry -> liveDocs.get(entry.getKey()) == false); + } + return leafDocScores; + } + + @Override + public String toString(String field) { + return this.getClass().getSimpleName() + "[" + field + "]..." + KNNQuery.class.getSimpleName() + "[" + knnQuery.toString() + "]"; + } + + @Override + public void visit(QueryVisitor visitor) { + visitor.visitLeaf(this); + } + + @Override + public boolean equals(Object obj) { + if (!sameClassAs(obj)) { + return false; + } + return knnQuery == ((NativeEngineKnnVectorQuery) obj).knnQuery; + } + + @Override + public int hashCode() { + return Objects.hash(classHash(), knnQuery.hashCode()); + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/parser/KNNQueryBuilderParser.java b/src/main/java/org/opensearch/knn/index/query/parser/KNNQueryBuilderParser.java new file mode 100644 index 000000000..159480b72 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/parser/KNNQueryBuilderParser.java @@ -0,0 +1,289 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.parser; + +import lombok.extern.log4j.Log4j2; +import org.opensearch.core.common.ParsingException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ObjectParser; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentLocation; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.query.rescore.RescoreContext; +import org.opensearch.knn.index.util.IndexUtil; +import org.opensearch.knn.index.query.KNNQueryBuilder; + +import java.io.IOException; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.function.Function; + +import static org.opensearch.index.query.AbstractQueryBuilder.BOOST_FIELD; +import static org.opensearch.index.query.AbstractQueryBuilder.NAME_FIELD; +import static org.opensearch.index.query.AbstractQueryBuilder.parseInnerQueryBuilder; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER; +import static org.opensearch.knn.index.query.KNNQueryBuilder.RESCORE_FIELD; +import static org.opensearch.knn.index.query.parser.RescoreParser.RESCORE_PARAMETER; +import static org.opensearch.knn.index.util.IndexUtil.isClusterOnOrAfterMinRequiredVersion; +import static org.opensearch.knn.index.query.KNNQueryBuilder.FILTER_FIELD; +import static org.opensearch.knn.index.query.KNNQueryBuilder.IGNORE_UNMAPPED_FIELD; +import static org.opensearch.knn.index.query.KNNQueryBuilder.K_FIELD; +import static org.opensearch.knn.index.query.KNNQueryBuilder.MAX_DISTANCE_FIELD; +import static org.opensearch.knn.index.query.KNNQueryBuilder.METHOD_PARAMS_FIELD; +import static org.opensearch.knn.index.query.KNNQueryBuilder.MIN_SCORE_FIELD; +import static org.opensearch.knn.index.query.KNNQueryBuilder.NAME; +import static org.opensearch.knn.index.query.KNNQueryBuilder.VECTOR_FIELD; + +/** + * Helper class responsible for parsing and reverse parsing KNNQueryBuilder's + */ +@Log4j2 +public final class KNNQueryBuilderParser { + + private static final ObjectParser INTERNAL_PARSER = createInternalObjectParser(); + + /** + * For a k-NN query, we need to parse roughly the following structure into a KNNQueryBuilder: + * "my_vector2": { + * "vector": [2, 3, 5, 6], + * "k": 2, + * ... + * } + * to simplify the parsing process, we can define an object parser that will the internal structure after the + * field name. We cannot unfortunately also parse the field name because it ends up in the same structure + * as the nested portion. So we need to do that separately. + */ + private static ObjectParser createInternalObjectParser() { + ObjectParser internalParser = new ObjectParser<>(NAME, KNNQueryBuilder.Builder::new); + internalParser.declareFloat(KNNQueryBuilder.Builder::boost, BOOST_FIELD); + internalParser.declareString(KNNQueryBuilder.Builder::queryName, NAME_FIELD); + internalParser.declareFloatArray((b, v) -> b.vector(floatListToFloatArray(v)), VECTOR_FIELD); + internalParser.declareInt(KNNQueryBuilder.Builder::k, K_FIELD); + internalParser.declareBoolean((b, v) -> { + if (isClusterOnOrAfterMinRequiredVersion("ignore_unmapped")) { + b.ignoreUnmapped(v); + } + }, IGNORE_UNMAPPED_FIELD); + internalParser.declareFloat(KNNQueryBuilder.Builder::maxDistance, MAX_DISTANCE_FIELD); + internalParser.declareFloat(KNNQueryBuilder.Builder::minScore, MIN_SCORE_FIELD); + + internalParser.declareObject( + KNNQueryBuilder.Builder::methodParameters, + (p, v) -> MethodParametersParser.fromXContent(p), + METHOD_PARAMS_FIELD + ); + internalParser.declareObject(KNNQueryBuilder.Builder::filter, (p, v) -> parseInnerQueryBuilder(p), FILTER_FIELD); + + internalParser.declareObjectOrDefault( + KNNQueryBuilder.Builder::rescoreContext, + (p, v) -> RescoreParser.fromXContent(p), + RescoreContext::getDefault, + RESCORE_FIELD + ); + + // Declare fields that cannot be set at the same time. Right now, rescore and radial is not supported + internalParser.declareExclusiveFieldSet(RESCORE_FIELD.getPreferredName(), MAX_DISTANCE_FIELD.getPreferredName()); + internalParser.declareExclusiveFieldSet(RESCORE_FIELD.getPreferredName(), MIN_SCORE_FIELD.getPreferredName()); + + return internalParser; + } + + /** + * Stream input for KNNQueryBuilder + * + * @param in stream out + * @param minClusterVersionCheck function to check min version + * @return KNNQueryBuilder.Builder class + * @throws IOException on stream failure + */ + public static KNNQueryBuilder.Builder streamInput(StreamInput in, Function minClusterVersionCheck) throws IOException { + KNNQueryBuilder.Builder builder = new KNNQueryBuilder.Builder(); + builder.fieldName(in.readString()); + builder.vector(in.readFloatArray()); + builder.k(in.readInt()); + // We're checking if all cluster nodes has at least that version or higher. This check is required + // to avoid issues with cluster upgrade + if (isClusterOnOrAfterMinRequiredVersion("filter")) { + builder.filter(in.readOptionalNamedWriteable(QueryBuilder.class)); + } + if (minClusterVersionCheck.apply("ignore_unmapped")) { + builder.ignoreUnmapped(in.readOptionalBoolean()); + } + if (minClusterVersionCheck.apply(KNNConstants.RADIAL_SEARCH_KEY)) { + builder.maxDistance(in.readOptionalFloat()); + } + if (minClusterVersionCheck.apply(KNNConstants.RADIAL_SEARCH_KEY)) { + builder.minScore(in.readOptionalFloat()); + } + if (minClusterVersionCheck.apply(METHOD_PARAMETER)) { + builder.methodParameters(MethodParametersParser.streamInput(in, IndexUtil::isClusterOnOrAfterMinRequiredVersion)); + } + + if (minClusterVersionCheck.apply(RESCORE_PARAMETER)) { + builder.rescoreContext(RescoreParser.streamInput(in)); + } + + return builder; + } + + /** + * Stream output for KNNQueryBuilder + * + * @param out stream out + * @param builder KNNQueryBuilder to stream + * @param minClusterVersionCheck function to check min version + * @throws IOException on stream failure + */ + public static void streamOutput(StreamOutput out, KNNQueryBuilder builder, Function minClusterVersionCheck) + throws IOException { + out.writeString(builder.fieldName()); + out.writeFloatArray((float[]) builder.vector()); + out.writeInt(builder.getK()); + // We're checking if all cluster nodes has at least that version or higher. This check is required + // to avoid issues with cluster upgrade + if (isClusterOnOrAfterMinRequiredVersion("filter")) { + out.writeOptionalNamedWriteable(builder.getFilter()); + } + if (minClusterVersionCheck.apply("ignore_unmapped")) { + out.writeOptionalBoolean(builder.isIgnoreUnmapped()); + } + if (minClusterVersionCheck.apply(KNNConstants.RADIAL_SEARCH_KEY)) { + out.writeOptionalFloat(builder.getMaxDistance()); + } + if (minClusterVersionCheck.apply(KNNConstants.RADIAL_SEARCH_KEY)) { + out.writeOptionalFloat(builder.getMinScore()); + } + if (minClusterVersionCheck.apply(METHOD_PARAMETER)) { + MethodParametersParser.streamOutput(out, builder.getMethodParameters(), IndexUtil::isClusterOnOrAfterMinRequiredVersion); + } + if (minClusterVersionCheck.apply(RESCORE_PARAMETER)) { + RescoreParser.streamOutput(out, builder.getRescoreContext()); + } + } + + /** + * Convert XContent to KNNQueryBuilder + * + * @param parser input parser + * @return KNNQueryBuilder + * @throws IOException on parsing failure + */ + public static KNNQueryBuilder fromXContent(XContentParser parser) throws IOException { + String fieldName = null; + String currentFieldName = null; + XContentParser.Token token; + KNNQueryBuilder.Builder builder = null; + List vector = null; + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else if (token == XContentParser.Token.START_OBJECT) { + throwParsingExceptionOnMultipleFields(parser.getTokenLocation(), fieldName, currentFieldName); + fieldName = currentFieldName; + builder = INTERNAL_PARSER.apply(parser, null); + } else { + throwParsingExceptionOnMultipleFields(parser.getTokenLocation(), fieldName, parser.currentName()); + fieldName = parser.currentName(); + vector = parser.list(); + } + } + + if (builder == null) { + builder = KNNQueryBuilder.builder().vector(objectsToFloats(vector)); + } + builder.fieldName(fieldName); + return builder.build(); + } + + /** + * Convert KNNQueryBuilder to XContent + * + * @param builder xcontent builder to add KNNQueryBuilder + * @param params ToXContent params + * @param knnQueryBuilder KNNQueryBuilder to convert + * @throws IOException on conversion failure + */ + public static void toXContent(XContentBuilder builder, ToXContent.Params params, KNNQueryBuilder knnQueryBuilder) throws IOException { + builder.startObject(NAME); + builder.startObject(knnQueryBuilder.fieldName()); + + builder.field(VECTOR_FIELD.getPreferredName(), knnQueryBuilder.vector()); + builder.field(K_FIELD.getPreferredName(), knnQueryBuilder.getK()); + if (knnQueryBuilder.getFilter() != null) { + builder.field(FILTER_FIELD.getPreferredName(), knnQueryBuilder.getFilter()); + } + if (knnQueryBuilder.getMaxDistance() != null) { + builder.field(MAX_DISTANCE_FIELD.getPreferredName(), knnQueryBuilder.getMaxDistance()); + } + if (knnQueryBuilder.isIgnoreUnmapped()) { + builder.field(IGNORE_UNMAPPED_FIELD.getPreferredName(), knnQueryBuilder.isIgnoreUnmapped()); + } + if (knnQueryBuilder.getMinScore() != null) { + builder.field(MIN_SCORE_FIELD.getPreferredName(), knnQueryBuilder.getMinScore()); + } + if (knnQueryBuilder.getMethodParameters() != null) { + MethodParametersParser.doXContent(builder, knnQueryBuilder.getMethodParameters()); + } + if (knnQueryBuilder.getRescoreContext() != null) { + RescoreParser.doXContent(builder, knnQueryBuilder.getRescoreContext()); + } + + builder.field(BOOST_FIELD.getPreferredName(), knnQueryBuilder.boost()); + if (knnQueryBuilder.queryName() != null) { + builder.field(NAME_FIELD.getPreferredName(), knnQueryBuilder.queryName()); + } + + builder.endObject(); + builder.endObject(); + } + + private static float[] floatListToFloatArray(List floats) { + if (Objects.isNull(floats) || floats.isEmpty()) { + throw new IllegalArgumentException(String.format("[%s] field 'vector' requires to be non-null and non-empty", NAME)); + } + float[] vec = new float[floats.size()]; + for (int i = 0; i < floats.size(); i++) { + vec[i] = floats.get(i); + } + return vec; + } + + private static float[] objectsToFloats(List objs) { + if (Objects.isNull(objs) || objs.isEmpty()) { + throw new IllegalArgumentException( + String.format(Locale.ROOT, "[%s] field 'vector' requires to be non-null and non-empty", NAME) + ); + } + float[] vec = new float[objs.size()]; + for (int i = 0; i < objs.size(); i++) { + if ((objs.get(i) instanceof Number) == false) { + throw new IllegalArgumentException( + String.format(Locale.ROOT, "[%s] field 'vector' requires to be an array of numbers", NAME) + ); + } + vec[i] = ((Number) objs.get(i)).floatValue(); + } + return vec; + } + + private static void throwParsingExceptionOnMultipleFields( + XContentLocation contentLocation, + String processedFieldName, + String currentFieldName + ) { + if (processedFieldName != null) { + throw new ParsingException( + contentLocation, + "[" + NAME + "] query doesn't support multiple fields, found [" + processedFieldName + "] and [" + currentFieldName + "]" + ); + } + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/parser/MethodParametersParser.java b/src/main/java/org/opensearch/knn/index/query/parser/MethodParametersParser.java new file mode 100644 index 000000000..41b69f441 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/parser/MethodParametersParser.java @@ -0,0 +1,140 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.query.parser; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.opensearch.common.ValidationException; +import org.opensearch.core.common.ParsingException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.knn.index.query.request.MethodParameter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import static org.opensearch.knn.index.query.KNNQueryBuilder.METHOD_PARAMS_FIELD; +import static org.opensearch.knn.index.query.KNNQueryBuilder.NAME; + +/** + * Note: This parser is used by neural plugin as well, breaking changes will require changes in neural as well + */ +@EqualsAndHashCode +@Getter +@AllArgsConstructor +public class MethodParametersParser { + + // Validation on rest layer + public static ValidationException validateMethodParameters(final Map methodParameters) { + final List errors = new ArrayList<>(); + for (final Map.Entry methodParameter : methodParameters.entrySet()) { + final MethodParameter parameter = MethodParameter.enumOf(methodParameter.getKey()); + if (parameter != null) { + final ValidationException validationException = parameter.validate(methodParameter.getValue()); + if (validationException != null) { + errors.add(validationException.getMessage()); + } + } else { // Should never happen if used in the right sequence + errors.add(methodParameter.getKey() + " is not a valid method parameter"); + } + } + + if (!errors.isEmpty()) { + ValidationException validationException = new ValidationException(); + validationException.addValidationErrors(errors); + return validationException; + } + return null; + } + + // deserialize for node to node communication + public static Map streamInput(StreamInput in, Function minClusterVersionCheck) throws IOException { + if (!in.readBoolean()) { + return null; + } + + final Map methodParameters = new HashMap<>(); + for (final MethodParameter methodParameter : MethodParameter.values()) { + if (minClusterVersionCheck.apply(methodParameter.getName())) { + String name = in.readString(); + Object value = in.readGenericValue(); + if (value != null) { + methodParameters.put(name, methodParameter.parse(value)); + } + } + } + + return !methodParameters.isEmpty() ? methodParameters : null; + } + + // serialize for node to node communication + public static void streamOutput(StreamOutput out, Map methodParameters, Function minClusterVersionCheck) + throws IOException { + if (methodParameters == null || methodParameters.isEmpty()) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + // All values are written to deserialize without ambiguity + for (final MethodParameter methodParameter : MethodParameter.values()) { + if (minClusterVersionCheck.apply(methodParameter.getName())) { + out.writeString(methodParameter.getName()); + out.writeGenericValue(methodParameters.get(methodParameter.getName())); + } + } + } + } + + public static void doXContent(final XContentBuilder builder, final Map methodParameters) throws IOException { + if (methodParameters == null || methodParameters.isEmpty()) { + return; + } + builder.startObject(METHOD_PARAMS_FIELD.getPreferredName()); + for (final Map.Entry entry : methodParameters.entrySet()) { + if (entry.getKey() != null && entry.getValue() != null) { + builder.field(entry.getKey(), entry.getValue()); + } + } + builder.endObject(); + } + + public static Map fromXContent(final XContentParser parser) throws IOException { + final Map methodParametersJson = parser.map(); + if (methodParametersJson.isEmpty()) { + throw new ParsingException(parser.getTokenLocation(), METHOD_PARAMS_FIELD.getPreferredName() + " cannot be empty"); + } + + final Map methodParameters = new HashMap<>(); + for (Map.Entry requestParameter : methodParametersJson.entrySet()) { + final String name = requestParameter.getKey(); + final Object value = requestParameter.getValue(); + final MethodParameter parameter = MethodParameter.enumOf(name); + if (parameter == null) { + throw new ParsingException(parser.getTokenLocation(), "[" + NAME + "] unknown method parameter found [" + name + "]"); + } + + try { + // This makes sure that we throw parsing exception on rest layer. + methodParameters.put(name, parameter.parse(value)); + } catch (final Exception exception) { + throw new ParsingException(parser.getTokenLocation(), exception.getMessage()); + } + } + return methodParameters.isEmpty() ? null : methodParameters; + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/parser/RescoreParser.java b/src/main/java/org/opensearch/knn/index/query/parser/RescoreParser.java new file mode 100644 index 000000000..06062aed1 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/parser/RescoreParser.java @@ -0,0 +1,131 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.parser; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.log4j.Log4j2; +import org.opensearch.common.ValidationException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ObjectParser; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.knn.index.query.rescore.RescoreContext; +import org.opensearch.knn.index.util.IndexUtil; + +import java.io.IOException; +import java.util.Locale; + +import static org.opensearch.knn.index.query.KNNQueryBuilder.RESCORE_OVERSAMPLE_FIELD; + +/** + * Note: This parser is used by neural plugin as well, breaking changes will require changes in neural as well + */ +@Getter +@AllArgsConstructor +@Log4j2 +public final class RescoreParser { + + public static final String RESCORE_PARAMETER = "rescore"; + public static final String RESCORE_OVERSAMPLE_PARAMETER = "oversample_factor"; + + private static final ObjectParser INTERNAL_PARSER = createInternalObjectParser(); + + private static ObjectParser createInternalObjectParser() { + ObjectParser internalParser = new ObjectParser<>( + RESCORE_PARAMETER, + RescoreContext::builder + ); + internalParser.declareFloat(RescoreContext.RescoreContextBuilder::oversampleFactor, RESCORE_OVERSAMPLE_FIELD); + return internalParser; + } + + /** + * Validate the rescore context + * + * @return ValidationException if validation fails, null otherwise + */ + public static ValidationException validate(RescoreContext rescoreContext) { + if (rescoreContext.getOversampleFactor() < RescoreContext.MIN_OVERSAMPLE_FACTOR) { + ValidationException validationException = new ValidationException(); + validationException.addValidationError( + String.format( + Locale.ROOT, + "Oversample factor [%f] cannot be less than [%f]", + rescoreContext.getOversampleFactor(), + RescoreContext.MIN_OVERSAMPLE_FACTOR + ) + ); + return validationException; + } + + if (rescoreContext.getOversampleFactor() > RescoreContext.MAX_OVERSAMPLE_FACTOR) { + ValidationException validationException = new ValidationException(); + validationException.addValidationError( + String.format( + Locale.ROOT, + "Oversample factor [%f] cannot be more than [%f]", + rescoreContext.getOversampleFactor(), + RescoreContext.MAX_OVERSAMPLE_FACTOR + ) + ); + return validationException; + } + return null; + } + + /** + * + * @param in stream input + * @return RescoreContext + * @throws IOException on stream failure + */ + public static RescoreContext streamInput(StreamInput in) throws IOException { + if (!IndexUtil.isVersionOnOrAfterMinRequiredVersion(in.getVersion(), RESCORE_PARAMETER)) { + return null; + } + Float oversample = in.readOptionalFloat(); + if (oversample == null) { + return null; + } + return RescoreContext.builder().oversampleFactor(oversample).build(); + } + + /** + * + * @param out stream output + * @param rescoreContext RescoreContext + * @throws IOException on stream failure + */ + public static void streamOutput(StreamOutput out, RescoreContext rescoreContext) throws IOException { + if (!IndexUtil.isVersionOnOrAfterMinRequiredVersion(out.getVersion(), RESCORE_PARAMETER)) { + return; + } + out.writeOptionalFloat(rescoreContext == null ? null : rescoreContext.getOversampleFactor()); + } + + /** + * + * @param builder XContentBuilder + * @param rescoreContext RescoreContext + * @throws IOException on XContent failure + */ + public static void doXContent(final XContentBuilder builder, final RescoreContext rescoreContext) throws IOException { + builder.startObject(RESCORE_PARAMETER); + builder.field(RESCORE_OVERSAMPLE_PARAMETER, rescoreContext.getOversampleFactor()); + builder.endObject(); + } + + /** + * + * @param parser input parser + * @return RescoreContext + */ + public static RescoreContext fromXContent(final XContentParser parser) { + return INTERNAL_PARSER.apply(parser, null).build(); + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/request/MethodParameter.java b/src/main/java/org/opensearch/knn/index/query/request/MethodParameter.java new file mode 100644 index 000000000..17f04d7e2 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/request/MethodParameter.java @@ -0,0 +1,102 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.query.request; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.opensearch.Version; +import org.opensearch.common.ValidationException; +import org.opensearch.core.ParseField; + +import java.util.HashMap; +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_SEARCH; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NPROBES; +import static org.opensearch.knn.index.query.KNNQueryBuilder.EF_SEARCH_FIELD; +import static org.opensearch.knn.index.query.KNNQueryBuilder.NPROBE_FIELD; + +/** + * MethodParameters are engine and algorithm related parameters that clients can pass in knn query + * This enum holds metadata which helps parse and have basic validation related to MethodParameter + */ +@Getter +@RequiredArgsConstructor +public enum MethodParameter { + + EF_SEARCH(METHOD_PARAMETER_EF_SEARCH, Version.V_2_16_0, EF_SEARCH_FIELD) { + @Override + public Integer parse(Object value) { + return parseInteger(value, METHOD_PARAMETER_EF_SEARCH); + } + + @Override + public ValidationException validate(Object value) { + final Integer ef = parse(value); + if (ef != null && ef > 0) { + return null; + } + + ValidationException validationException = new ValidationException(); + validationException.addValidationError(METHOD_PARAMETER_EF_SEARCH + " should be greater than 0"); + return validationException; + } + }, + + NPROBE(METHOD_PARAMETER_NPROBES, Version.V_2_16_0, NPROBE_FIELD) { + @Override + public Integer parse(Object value) { + return parseInteger(value, METHOD_PARAMETER_EF_SEARCH); + } + + @Override + public ValidationException validate(Object value) { + final Integer nprobe = parse(value); + if (nprobe != null && nprobe > 0) { + return null; + } + + ValidationException validationException = new ValidationException(); + validationException.addValidationError(METHOD_PARAMETER_NPROBES + " should be greater than 0"); + return validationException; + } + }; + + private final String name; + private final Version version; + private final ParseField parseField; + + private static Map PARAMETERS_DIR; + + public abstract T parse(Object value); + + // These are preliminary validations on rest layer + public abstract ValidationException validate(Object value); + + public static MethodParameter enumOf(final String name) { + if (PARAMETERS_DIR == null) { + PARAMETERS_DIR = new HashMap<>(); + for (final MethodParameter methodParameter : MethodParameter.values()) { + PARAMETERS_DIR.put(methodParameter.name, methodParameter); + } + } + return PARAMETERS_DIR.get(name); + } + + private static Integer parseInteger(Object value, String name) { + try { + return Integer.parseInt(String.valueOf(value)); + } catch (final NumberFormatException e) { + throw new IllegalArgumentException(name + " value must be an integer"); + } + } +} diff --git a/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java b/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java new file mode 100644 index 000000000..09aeb7591 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/query/rescore/RescoreContext.java @@ -0,0 +1,88 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.rescore; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +@EqualsAndHashCode +public final class RescoreContext { + + public static final float DEFAULT_OVERSAMPLE_FACTOR = 1.0f; + public static final float MAX_OVERSAMPLE_FACTOR = 100.0f; + public static final float MIN_OVERSAMPLE_FACTOR = 1.0f; + + public static final int MAX_FIRST_PASS_RESULTS = 10000; + public static final int DIMENSION_THRESHOLD = 1000; + public static final float OVERSAMPLE_FACTOR_BELOW_DIMENSION_THRESHOLD = 5.0f; + + // Dimension thresholds for adjusting oversample factor + public static final int DIMENSION_THRESHOLD_1000 = 1000; + public static final int DIMENSION_THRESHOLD_768 = 768; + + // Oversample factors based on dimension thresholds + public static final float OVERSAMPLE_FACTOR_1000 = 1.0f; // No oversampling for dimensions >= 1000 + public static final float OVERSAMPLE_FACTOR_768 = 2.0f; // 2x oversampling for dimensions >= 768 and < 1000 + public static final float OVERSAMPLE_FACTOR_BELOW_768 = 3.0f; // 3x oversampling for dimensions < 768 + + // Todo:- We will improve this in upcoming releases + public static final int MIN_FIRST_PASS_RESULTS = 100; + + @Builder.Default + private float oversampleFactor = DEFAULT_OVERSAMPLE_FACTOR; + + /** + * Flag to track whether the oversample factor is user-provided or default. The Reason to introduce + * this is to set default when Shard Level rescoring is false, + * else we end up overriding user provided value in NativeEngineKnnVectorQuery + */ + @Builder.Default + private boolean userProvided = true; + + /** + * + * @return default RescoreContext + */ + public static RescoreContext getDefault() { + return RescoreContext.builder().oversampleFactor(DEFAULT_OVERSAMPLE_FACTOR).userProvided(false).build(); + } + + /** + * Calculates the number of results to return for the first pass of rescoring (firstPassK). + * This method considers whether shard-level rescoring is enabled and adjusts the oversample factor + * based on the vector dimension if shard-level rescoring is disabled. + * + * @param finalK The final number of results to return for the entire shard. + * @param isShardLevelRescoringEnabled A boolean flag indicating whether shard-level rescoring is enabled. + * If true, the dimension-based oversampling logic is bypassed. + * @param dimension The dimension of the vector. This is used to determine the oversampling factor when + * shard-level rescoring is disabled. + * @return The number of results to return for the first pass of rescoring, adjusted by the oversample factor. + */ + public int getFirstPassK(int finalK, boolean isShardLevelRescoringEnabled, int dimension) { + // Only apply default dimension-based oversampling logic when: + // 1. Shard-level rescoring is disabled + // 2. The oversample factor was not provided by the user + if (!isShardLevelRescoringEnabled && !userProvided) { + // Apply new dimension-based oversampling logic when shard-level rescoring is disabled + if (dimension >= DIMENSION_THRESHOLD_1000) { + oversampleFactor = OVERSAMPLE_FACTOR_1000; // No oversampling for dimensions >= 1000 + } else if (dimension >= DIMENSION_THRESHOLD_768) { + oversampleFactor = OVERSAMPLE_FACTOR_768; // 2x oversampling for dimensions >= 768 and < 1000 + } else { + oversampleFactor = OVERSAMPLE_FACTOR_BELOW_768; // 3x oversampling for dimensions < 768 + } + } + // The calculation for firstPassK remains the same, applying the oversample factor + return Math.min(MAX_FIRST_PASS_RESULTS, Math.max(MIN_FIRST_PASS_RESULTS, (int) Math.ceil(finalK * oversampleFactor))); + } + +} diff --git a/src/main/java/org/opensearch/knn/index/store/IndexInputWithBuffer.java b/src/main/java/org/opensearch/knn/index/store/IndexInputWithBuffer.java new file mode 100644 index 000000000..0b1c934e5 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/store/IndexInputWithBuffer.java @@ -0,0 +1,52 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.store; + +import lombok.NonNull; +import org.apache.lucene.store.IndexInput; + +import java.io.IOException; + +/** + * This class contains a Lucene's IndexInput with a reader buffer. + * A Java reference of this class will be passed to native engines, then 'copyBytes' method will be + * called by native engine via JNI API. + * Therefore, this class servers as a read layer in native engines to read the bytes it wants. + */ +public class IndexInputWithBuffer { + private IndexInput indexInput; + private long contentLength; + // 64K buffer. + private byte[] buffer = new byte[64 * 1024]; + + public IndexInputWithBuffer(@NonNull IndexInput indexInput) { + this.indexInput = indexInput; + this.contentLength = indexInput.length(); + } + + /** + * This method will be invoked in native engines via JNI API. + * Then it will call IndexInput to read required bytes then copy them into a read buffer. + * + * @param nbytes Desired number of bytes to be read. + * @return The number of read bytes in a buffer. + * @throws IOException + */ + private int copyBytes(long nbytes) throws IOException { + final int readBytes = (int) Math.min(nbytes, buffer.length); + indexInput.readBytes(buffer, 0, readBytes); + return readBytes; + } + + private long remainingBytes() { + return contentLength - indexInput.getFilePointer(); + } + + @Override + public String toString() { + return "{indexInput=" + indexInput + ", len(buffer)=" + buffer.length + "}"; + } +} diff --git a/src/main/java/org/opensearch/knn/index/store/IndexOutputWithBuffer.java b/src/main/java/org/opensearch/knn/index/store/IndexOutputWithBuffer.java new file mode 100644 index 000000000..c1420238a --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/store/IndexOutputWithBuffer.java @@ -0,0 +1,40 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.store; + +import org.apache.lucene.store.IndexOutput; + +import java.io.IOException; + +public class IndexOutputWithBuffer { + // Underlying `IndexOutput` obtained from Lucene's Directory. + private IndexOutput indexOutput; + // Write buffer. Native engine will copy bytes into this buffer. + // Allocating 64KB here since it show better performance in NMSLIB with the size. (We had slightly improvement in FAISS than having 4KB) + // NMSLIB writes an adjacent list size first, then followed by serializing the list. Since we usually have more adjacent lists, having + // 64KB to accumulate bytes as possible to reduce the times of calling `writeBytes`. + private byte[] buffer = new byte[64 * 1024]; + + public IndexOutputWithBuffer(IndexOutput indexOutput) { + this.indexOutput = indexOutput; + } + + // This method will be called in JNI layer which precisely knows + // the amount of bytes need to be written. + public void writeBytes(int length) { + try { + // Delegate Lucene `indexOuptut` to write bytes. + indexOutput.writeBytes(buffer, 0, length); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public String toString() { + return "{indexOutput=" + indexOutput + ", len(buffer)=" + buffer.length + "}"; + } +} diff --git a/src/main/java/org/opensearch/knn/index/util/IndexHyperParametersUtil.java b/src/main/java/org/opensearch/knn/index/util/IndexHyperParametersUtil.java new file mode 100644 index 000000000..c88a28e5c --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/util/IndexHyperParametersUtil.java @@ -0,0 +1,99 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.util; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.extern.log4j.Log4j2; +import org.opensearch.Version; +import org.opensearch.knn.index.KNNSettings; + +/** + * This class acts as an abstraction to get the default hyperparameter values for different parameters used in the + * Nearest Neighbor Algorithm across different version of Index. + */ +@Log4j2 +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class IndexHyperParametersUtil { + + private static final int INDEX_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION_OLD_VALUE = 512; + private static final int INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH_OLD_VALUE = 512; + private static final int INDEX_BINARY_QUANTIZATION_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION = 256; + private static final int INDEX_BINARY_QUANTIZATION_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH = 256; + + /** + * Returns the default value of EF Construction that should be used for the input index version. After version 2.12.0 + * of Opensearch we are have reduced the value of ef_construction in favor of better build times. + * + * @param indexVersion {@code Version} of the index with which it was created. + * @return default value of EF Construction that should be used for the input index version. + */ + public static int getHNSWEFConstructionValue(@NonNull final Version indexVersion) { + if (indexVersion.before(Version.V_2_12_0)) { + log.debug( + "Picking up old values of ef_construction : index version : {}, value: {}", + indexVersion, + INDEX_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION_OLD_VALUE + ); + return INDEX_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION_OLD_VALUE; + } + log.debug( + "Picking up new values of ef_construction : index version : {}, value: {}", + indexVersion, + KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION + ); + return KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION; + } + + /** + * Returns the default value of EF Search that should be used for the input index version. After version 2.12.0 + * of Opensearch we are have reduced the value of ef_search in favor of better latency. + * + * @param indexVersion {@code Version} of the index with which it was created. + * @return default value of EF Search that should be used for the input index version. + */ + public static int getHNSWEFSearchValue(@NonNull final Version indexVersion) { + if (indexVersion.before(Version.V_2_12_0)) { + log.debug( + "Picking up old values of ef_search : index version : {}, value: {}", + indexVersion, + INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH_OLD_VALUE + ); + return INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH_OLD_VALUE; + } + log.debug( + "Picking up new values of ef_search : index version : {}, value: {}", + indexVersion, + KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH + ); + return KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH; + } + + /* + * Returns the default value of EF Construction that should be used with Binary Quantization. + * + * @return default value of EF Construction + */ + public static int getBinaryQuantizationEFConstructionValue() { + return INDEX_BINARY_QUANTIZATION_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION; + } + + /* + * Returns the default value of EF Search that should be used with Binary Quantization. + * + * @return default value of EF Search + */ + public static int getBinaryQuantizationEFSearchValue() { + return INDEX_BINARY_QUANTIZATION_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH; + } +} diff --git a/src/main/java/org/opensearch/knn/index/util/IndexUtil.java b/src/main/java/org/opensearch/knn/index/util/IndexUtil.java new file mode 100644 index 000000000..02f57660b --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/util/IndexUtil.java @@ -0,0 +1,424 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.util; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import org.apache.commons.lang.StringUtils; +import org.opensearch.Version; +import org.opensearch.cluster.metadata.IndexMetadata; +import org.opensearch.cluster.metadata.MappingMetadata; +import org.opensearch.common.ValidationException; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.mapper.KNNVectorFieldMapper; +import org.opensearch.knn.index.query.request.MethodParameter; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.indices.ModelDao; +import org.opensearch.knn.indices.ModelMetadata; +import org.opensearch.knn.indices.ModelUtil; +import org.opensearch.knn.jni.JNIService; + +import java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import static org.opensearch.knn.common.KNNConstants.BYTES_PER_KILOBYTES; +import static org.opensearch.knn.common.KNNConstants.ENCODER_FLAT; +import static org.opensearch.knn.common.KNNConstants.HNSW_ALGO_EF_SEARCH; +import static org.opensearch.knn.common.KNNConstants.SPACE_TYPE; +import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; +import static org.opensearch.knn.index.query.parser.RescoreParser.RESCORE_PARAMETER; + +public class IndexUtil { + + public static final String MODEL_NODE_ASSIGNMENT_KEY = KNNConstants.MODEL_NODE_ASSIGNMENT; + public static final String MODEL_METHOD_COMPONENT_CONTEXT_KEY = KNNConstants.MODEL_METHOD_COMPONENT_CONTEXT; + + private static final Version MINIMAL_SUPPORTED_VERSION_FOR_LUCENE_HNSW_FILTER = Version.V_2_4_0; + private static final Version MINIMAL_SUPPORTED_VERSION_FOR_IGNORE_UNMAPPED = Version.V_2_11_0; + private static final Version MINIMAL_SUPPORTED_VERSION_FOR_MODEL_NODE_ASSIGNMENT = Version.V_2_12_0; + private static final Version MINIMAL_SUPPORTED_VERSION_FOR_MODEL_METHOD_COMPONENT_CONTEXT = Version.V_2_13_0; + private static final Version MINIMAL_SUPPORTED_VERSION_FOR_RADIAL_SEARCH = Version.V_2_14_0; + private static final Version MINIMAL_SUPPORTED_VERSION_FOR_METHOD_PARAMETERS = Version.V_2_16_0; + private static final Version MINIMAL_SUPPORTED_VERSION_FOR_MODEL_VECTOR_DATA_TYPE = Version.V_2_16_0; + private static final Version MINIMAL_RESCORE_FEATURE = Version.V_2_17_0; + private static final Version MINIMAL_MODE_AND_COMPRESSION_FEATURE = Version.V_2_17_0; + private static final Version MINIMAL_TOP_LEVEL_SPACE_TYPE_FEATURE = Version.V_2_17_0; + private static final Version MINIMAL_SUPPORTED_VERSION_FOR_MODEL_VERSION = Version.V_2_17_0; + // public so neural search can access it + public static final Map minimalRequiredVersionMap = initializeMinimalRequiredVersionMap(); + public static final Set VECTOR_DATA_TYPES_NOT_SUPPORTING_ENCODERS = Set.of(VectorDataType.BINARY, VectorDataType.BYTE); + + /** + * Determines the size of a file on disk in kilobytes + * + * @param filePath path to the file + * @return file size in kilobytes + */ + public static int getFileSizeInKB(String filePath) { + if (filePath == null || filePath.isEmpty()) { + return 0; + } + File file = new File(filePath); + if (!file.exists() || !file.isFile()) { + return 0; + } + + return Math.toIntExact((file.length() / BYTES_PER_KILOBYTES) + 1L); // Add one so that integer division rounds up + } + + /** + * Validate that a field is a k-NN vector field and has the expected dimension + * + * @param indexMetadata metadata for index to validate + * @param field field name to validate + * @param expectedDimension expected dimension of the field. If this value is negative, dimension will not be + * checked + * @param modelDao used to look up dimension if field uses a model for initialization. Can be null if + * expectedDimension is negative + * @return ValidationException exception produced by field validation + */ + @SuppressWarnings("unchecked") + public static ValidationException validateKnnField( + IndexMetadata indexMetadata, + String field, + int expectedDimension, + ModelDao modelDao, + VectorDataType trainRequestVectorDataType, + KNNMethodContext trainRequestKnnMethodContext + ) { + // Index metadata should not be null + if (indexMetadata == null) { + throw new IllegalArgumentException("IndexMetadata should not be null"); + } + + ValidationException exception = new ValidationException(); + + // Check the mapping + MappingMetadata mappingMetadata = indexMetadata.mapping(); + if (mappingMetadata == null) { + exception.addValidationError("Invalid index. Index does not contain a mapping"); + return exception; + } + + // The mapping output *should* look like this: + // "{properties={field={type=knn_vector, dimension=8}}}" + Map properties = (Map) mappingMetadata.getSourceAsMap().get("properties"); + + if (properties == null) { + exception.addValidationError("Properties in map does not exists. This is unexpected"); + return exception; + } + + // Check field path is valid + if (StringUtils.isEmpty(field)) { + exception.addValidationError(String.format(Locale.ROOT, "Field path is empty.")); + return exception; + } + + Object fieldMapping = getFieldMapping(properties, field); + + // Check field existence + if (fieldMapping == null) { + exception.addValidationError(String.format("Field \"%s\" does not exist.", field)); + return exception; + } + + // Check if field is a map. If not, that is a problem + if (!(fieldMapping instanceof Map)) { + exception.addValidationError(String.format("Field info for \"%s\" is not a map.", field)); + return exception; + } + + Map fieldMap = (Map) fieldMapping; + + // Check fields type is knn_vector + Object type = fieldMap.get("type"); + + if (!(type instanceof String) || !KNNVectorFieldMapper.CONTENT_TYPE.equals(type)) { + exception.addValidationError(String.format("Field \"%s\" is not of type %s.", field, KNNVectorFieldMapper.CONTENT_TYPE)); + return exception; + } + + if (trainRequestVectorDataType != null) { + VectorDataType trainIndexDataType = getVectorDataTypeFromFieldMapping(fieldMap); + + if (trainIndexDataType != trainRequestVectorDataType) { + exception.addValidationError( + String.format( + Locale.ROOT, + "Field \"%s\" has data type %s, which is different from data type used in the training request: %s", + field, + trainIndexDataType.getValue(), + trainRequestVectorDataType.getValue() + ) + ); + return exception; + } + + // Block binary and byte vector data type for any encoder + if (trainRequestKnnMethodContext != null) { + MethodComponentContext methodComponentContext = trainRequestKnnMethodContext.getMethodComponentContext(); + Map parameters = methodComponentContext.getParameters(); + + if (parameters != null && parameters.containsKey(KNNConstants.METHOD_ENCODER_PARAMETER)) { + MethodComponentContext encoder = (MethodComponentContext) parameters.get(KNNConstants.METHOD_ENCODER_PARAMETER); + if (encoder != null + && VECTOR_DATA_TYPES_NOT_SUPPORTING_ENCODERS.contains(trainRequestVectorDataType) + && ENCODER_FLAT.equals(encoder.getName()) == false) { + exception.addValidationError( + String.format( + Locale.ROOT, + "encoder is not supported for vector data type [%s]", + trainRequestVectorDataType.getValue() + ) + ); + return exception; + } + } + } + } + + // Return if dimension does not need to be checked + if (expectedDimension < 0) { + return null; + } + + // Check that the dimension of the method passed in matches that of the model + Object dimension = fieldMap.get(KNNConstants.DIMENSION); + + // If dimension is null, the training index/field could use a model. In this case, we need to get the model id + // for the index and then fetch its dimension from the models metadata + if (dimension == null) { + + String modelId = (String) fieldMap.get(KNNConstants.MODEL_ID); + + if (modelId == null) { + exception.addValidationError(String.format("Field \"%s\" does not have a dimension set.", field)); + return exception; + } + + if (modelDao == null) { + throw new IllegalArgumentException(String.format("Field \"%s\" uses model. modelDao cannot be null.", field)); + } + + ModelMetadata modelMetadata = modelDao.getMetadata(modelId); + if (!ModelUtil.isModelCreated(modelMetadata)) { + exception.addValidationError(String.format("Model \"%s\" for field \"%s\" is not created.", modelId, field)); + return exception; + } + + dimension = modelMetadata.getDimension(); + if ((Integer) dimension != expectedDimension) { + exception.addValidationError( + String.format( + "Field \"%s\" has dimension %d, which is different from " + "dimension specified in the training request: %d", + field, + dimension, + expectedDimension + ) + ); + return exception; + } + + return null; + } + + // If the dimension was found in training fields mapping, check that it equals the models proposed dimension. + if ((Integer) dimension != expectedDimension) { + exception.addValidationError( + String.format( + "Field \"%s\" has dimension %d, which is different from " + "dimension specified in the training request: %d", + field, + dimension, + expectedDimension + ) + ); + return exception; + } + + return null; + } + + /** + * Gets the load time parameters for a given engine. + * + * @param spaceType Space for this particular segment + * @param knnEngine Engine used for the native library indices being loaded in + * @param indexName Name of OpenSearch index that the segment files belong to + * @param vectorDataType Vector data type for this particular segment + * @return load parameters that will be passed to the JNI. + */ + public static Map getParametersAtLoading( + SpaceType spaceType, + KNNEngine knnEngine, + String indexName, + VectorDataType vectorDataType + ) { + Map loadParameters = Maps.newHashMap(ImmutableMap.of(SPACE_TYPE, spaceType.getValue())); + + // For nmslib, we need to add the dynamic ef_search parameter that needs to be passed in when the + // hnsw graphs are loaded into memory + if (KNNEngine.NMSLIB.equals(knnEngine)) { + loadParameters.put(HNSW_ALGO_EF_SEARCH, KNNSettings.getEfSearchParam(indexName)); + } + loadParameters.put(VECTOR_DATA_TYPE_FIELD, vectorDataType.getValue()); + + return Collections.unmodifiableMap(loadParameters); + } + + public static boolean isClusterOnOrAfterMinRequiredVersion(String key) { + Version minimalRequiredVersion = minimalRequiredVersionMap.get(key); + if (minimalRequiredVersion == null) { + return false; + } + return KNNClusterUtil.instance().getClusterMinVersion().onOrAfter(minimalRequiredVersion); + } + + public static boolean isVersionOnOrAfterMinRequiredVersion(Version version, String key) { + Version minimalRequiredVersion = minimalRequiredVersionMap.get(key); + if (minimalRequiredVersion == null) { + return false; + } + return version.onOrAfter(minimalRequiredVersion); + } + + /** + * Checks if index requires shared state + * + * @param knnEngine The knnEngine associated with the index + * @param modelId The modelId associated with the index + * @param indexAddr Address to check if loaded index requires shared state + * @return true if state can be shared; false otherwise + */ + public static boolean isSharedIndexStateRequired(KNNEngine knnEngine, String modelId, long indexAddr) { + if (StringUtils.isEmpty(modelId)) { + return false; + } + return JNIService.isSharedIndexStateRequired(indexAddr, knnEngine); + } + + /** + * Tell if it is binary index or not + * + * @param knnEngine knn engine associated with an index + * @param parameters parameters associated with an index + * @return true if it is binary index + */ + public static boolean isBinaryIndex(KNNEngine knnEngine, Map parameters) { + return KNNEngine.FAISS == knnEngine + && parameters.get(VECTOR_DATA_TYPE_FIELD) != null + && parameters.get(VECTOR_DATA_TYPE_FIELD).toString().equals(VectorDataType.BINARY.getValue()); + } + + /** + * Update vector data type into parameters + * + * @param parameters parameters associated with an index + * @param vectorDataType vector data type + */ + public static void updateVectorDataTypeToParameters(Map parameters, VectorDataType vectorDataType) { + if (VectorDataType.BINARY == vectorDataType) { + parameters.put(VECTOR_DATA_TYPE_FIELD, vectorDataType.getValue()); + } + if (VectorDataType.BYTE == vectorDataType) { + parameters.put(VECTOR_DATA_TYPE_FIELD, vectorDataType.getValue()); + } + } + + /** + * This method retrieves the field mapping by a given field path from the index metadata. + * + * @param properties Index metadata mapping properties. + * @param fieldPath The field path string that make up the path to the field mapping. e.g. "a.b.field" or "field". + * The field path is applied and checked in OpenSearch, so it is guaranteed to be valid. + * + * @return The field mapping object if found, or null if the field is not found in the index metadata. + */ + private static Object getFieldMapping(final Map properties, final String fieldPath) { + String[] fieldPaths = fieldPath.split("\\."); + Object currentFieldMapping = properties; + + // Iterate through the field path list to retrieve the field mapping. + for (String path : fieldPaths) { + currentFieldMapping = ((Map) currentFieldMapping).get(path); + if (currentFieldMapping == null) { + return null; + } + + if (currentFieldMapping instanceof Map) { + Object possibleProperties = ((Map) currentFieldMapping).get("properties"); + if (possibleProperties instanceof Map) { + currentFieldMapping = possibleProperties; + } + } + } + + return currentFieldMapping; + } + + /** + * This method is used to get the vector data type from field mapping + * @param fieldMap field mapping + * @return vector data type + */ + private static VectorDataType getVectorDataTypeFromFieldMapping(Map fieldMap) { + if (fieldMap.containsKey(VECTOR_DATA_TYPE_FIELD)) { + return VectorDataType.get((String) fieldMap.get(VECTOR_DATA_TYPE_FIELD)); + } + return VectorDataType.DEFAULT; + } + + /** + * Initialize the minimal required version map + * + * @return minimal required version map + */ + private static Map initializeMinimalRequiredVersionMap() { + final Map versionMap = new HashMap<>() { + { + put("filter", MINIMAL_SUPPORTED_VERSION_FOR_LUCENE_HNSW_FILTER); + put("ignore_unmapped", MINIMAL_SUPPORTED_VERSION_FOR_IGNORE_UNMAPPED); + put(MODEL_NODE_ASSIGNMENT_KEY, MINIMAL_SUPPORTED_VERSION_FOR_MODEL_NODE_ASSIGNMENT); + put(MODEL_METHOD_COMPONENT_CONTEXT_KEY, MINIMAL_SUPPORTED_VERSION_FOR_MODEL_METHOD_COMPONENT_CONTEXT); + put(KNNConstants.RADIAL_SEARCH_KEY, MINIMAL_SUPPORTED_VERSION_FOR_RADIAL_SEARCH); + put(KNNConstants.METHOD_PARAMETER, MINIMAL_SUPPORTED_VERSION_FOR_METHOD_PARAMETERS); + put(KNNConstants.MODEL_VECTOR_DATA_TYPE_KEY, MINIMAL_SUPPORTED_VERSION_FOR_MODEL_VECTOR_DATA_TYPE); + put(RESCORE_PARAMETER, MINIMAL_RESCORE_FEATURE); + put(KNNConstants.MINIMAL_MODE_AND_COMPRESSION_FEATURE, MINIMAL_MODE_AND_COMPRESSION_FEATURE); + put(KNNConstants.TOP_LEVEL_SPACE_TYPE_FEATURE, MINIMAL_TOP_LEVEL_SPACE_TYPE_FEATURE); + put(KNNConstants.MODEL_VERSION, MINIMAL_SUPPORTED_VERSION_FOR_MODEL_VERSION); + } + }; + + for (final MethodParameter methodParameter : MethodParameter.values()) { + if (methodParameter.getVersion() != null) { + versionMap.put(methodParameter.getName(), methodParameter.getVersion()); + } + } + return Collections.unmodifiableMap(versionMap); + } + + /** + * Tell if it is byte index or not + * + * @param parameters parameters associated with an index + * @return true if it is binary index + */ + public static boolean isByteIndex(Map parameters) { + return parameters.getOrDefault(VECTOR_DATA_TYPE_FIELD, VectorDataType.DEFAULT.getValue()) + .toString() + .equals(VectorDataType.BYTE.getValue()); + } +} diff --git a/src/main/java/org/opensearch/knn/index/util/KNNClusterUtil.java b/src/main/java/org/opensearch/knn/index/util/KNNClusterUtil.java new file mode 100644 index 000000000..c9c96bea5 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/util/KNNClusterUtil.java @@ -0,0 +1,58 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.util; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.opensearch.Version; +import org.opensearch.cluster.service.ClusterService; + +/** + * Class abstracts information related to underlying OpenSearch cluster + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@Log4j2 +public class KNNClusterUtil { + + private ClusterService clusterService; + private static KNNClusterUtil instance; + + /** + * Return instance of the cluster context, must be initialized first for proper usage + * @return instance of cluster context + */ + public static synchronized KNNClusterUtil instance() { + if (instance == null) { + instance = new KNNClusterUtil(); + } + return instance; + } + + /** + * Initializes instance of cluster context by injecting dependencies + * @param clusterService + */ + public void initialize(final ClusterService clusterService) { + this.clusterService = clusterService; + } + + /** + * Return minimal OpenSearch version based on all nodes currently discoverable in the cluster + * @return minimal installed OpenSearch version, default to Version.CURRENT which is typically the latest version + */ + public Version getClusterMinVersion() { + try { + return this.clusterService.state().getNodes().getMinNodeVersion(); + } catch (Exception exception) { + log.error( + String.format("Failed to get cluster minimum node version, returning current node version %s instead.", Version.CURRENT), + exception + ); + return Version.CURRENT; + } + } +} diff --git a/src/main/java/org/opensearch/knn/index/util/KNNEngine.java b/src/main/java/org/opensearch/knn/index/util/KNNEngine.java deleted file mode 100644 index 365226a01..000000000 --- a/src/main/java/org/opensearch/knn/index/util/KNNEngine.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.knn.index.util; - -import org.opensearch.common.ValidationException; -import org.opensearch.knn.index.KNNMethod; -import org.opensearch.knn.index.KNNMethodContext; -import org.opensearch.knn.index.SpaceType; - -import java.util.Map; - -import static org.opensearch.knn.common.KNNConstants.FAISS_NAME; -import static org.opensearch.knn.common.KNNConstants.NMSLIB_NAME; - -/** - * KNNEngine provides the functionality to validate and transform user defined indices into information that can be - * passed to the respective k-NN library's JNI layer. - */ -public enum KNNEngine implements KNNLibrary { - NMSLIB(NMSLIB_NAME, Nmslib.INSTANCE), - FAISS(FAISS_NAME, Faiss.INSTANCE); - - public static final KNNEngine DEFAULT = NMSLIB; - - /** - * Constructor for KNNEngine - * - * @param name name of engine - * @param knnLibrary library the engine uses - */ - KNNEngine(String name, KNNLibrary knnLibrary) { - this.name = name; - this.knnLibrary = knnLibrary; - } - - private String name; - private KNNLibrary knnLibrary; - - /** - * Get the engine - * - * @param name of engine to be fetched - * @return KNNEngine corresponding to name - */ - public static KNNEngine getEngine(String name) { - if (NMSLIB.getName().equalsIgnoreCase(name)) { - return NMSLIB; - } - - if (FAISS.getName().equals(name)) { - return FAISS; - } - - throw new IllegalArgumentException("Invalid engine type: " + name); - } - - /** - * Get the engine from the path. - * - * @param path to be checked - * @return KNNEngine corresponding to path - */ - public static KNNEngine getEngineNameFromPath(String path) { - if (path.endsWith(KNNEngine.NMSLIB.getExtension()) || path.endsWith(KNNEngine.NMSLIB.getCompoundExtension())) { - return KNNEngine.NMSLIB; - } - - if (path.endsWith(KNNEngine.FAISS.getExtension()) || path.endsWith(KNNEngine.FAISS.getCompoundExtension())) { - return KNNEngine.FAISS; - } - - throw new IllegalArgumentException("No engine matches the path's suffix"); - } - - /** - * Get the name of the engine - * - * @return name of the engine - */ - public String getName() { - return name; - } - - @Override - public String getLatestBuildVersion() { - return knnLibrary.getLatestBuildVersion(); - } - - @Override - public String getLatestLibVersion() { - return knnLibrary.getLatestLibVersion(); - } - - @Override - public String getExtension() { - return knnLibrary.getExtension(); - } - - @Override - public String getCompoundExtension() { - return knnLibrary.getCompoundExtension(); - } - - @Override - public KNNMethod getMethod(String methodName) { - return knnLibrary.getMethod(methodName); - } - - @Override - public float score(float rawScore, SpaceType spaceType) { - return knnLibrary.score(rawScore, spaceType); - } - - @Override - public ValidationException validateMethod(KNNMethodContext knnMethodContext) { - return knnLibrary.validateMethod(knnMethodContext); - } - - @Override - public boolean isTrainingRequired(KNNMethodContext knnMethodContext) { - return knnLibrary.isTrainingRequired(knnMethodContext); - } - - @Override - public Map getMethodAsMap(KNNMethodContext knnMethodContext) { - return knnLibrary.getMethodAsMap(knnMethodContext); - } - - @Override - public int estimateOverheadInKB(KNNMethodContext knnMethodContext, int dimension) { - return knnLibrary.estimateOverheadInKB(knnMethodContext, dimension); - } - - @Override - public Boolean isInitialized() { - return knnLibrary.isInitialized(); - } - - @Override - public void setInitialized(Boolean isInitialized) { - knnLibrary.setInitialized(isInitialized); - } -} diff --git a/src/main/java/org/opensearch/knn/index/util/KNNLibrary.java b/src/main/java/org/opensearch/knn/index/util/KNNLibrary.java deleted file mode 100644 index 767a9ad61..000000000 --- a/src/main/java/org/opensearch/knn/index/util/KNNLibrary.java +++ /dev/null @@ -1,691 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.knn.index.util; - -import org.opensearch.common.ValidationException; -import org.opensearch.knn.common.KNNConstants; -import org.opensearch.knn.index.KNNMethod; -import org.opensearch.knn.index.KNNMethodContext; -import org.opensearch.knn.index.KNNSettings; -import org.opensearch.knn.index.MethodComponent; -import org.opensearch.knn.index.MethodComponentContext; -import org.opensearch.knn.index.Parameter; -import org.opensearch.knn.index.SpaceType; -import com.google.common.collect.ImmutableMap; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Function; - -import static org.opensearch.knn.common.KNNConstants.BYTES_PER_KILOBYTES; -import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_M; -import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_COUNT_DEFAULT; -import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_COUNT_LIMIT; -import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_SIZE; -import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_SIZE_DEFAULT; -import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_SIZE_LIMIT; -import static org.opensearch.knn.common.KNNConstants.FAISS_HNSW_DESCRIPTION; -import static org.opensearch.knn.common.KNNConstants.FAISS_IVF_DESCRIPTION; -import static org.opensearch.knn.common.KNNConstants.FAISS_PQ_DESCRIPTION; -import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; -import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; -import static org.opensearch.knn.common.KNNConstants.METHOD_IVF; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_SEARCH; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_M; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NLIST; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NLIST_DEFAULT; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NLIST_LIMIT; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NPROBES; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NPROBES_DEFAULT; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NPROBES_LIMIT; -import static org.opensearch.knn.common.KNNConstants.NAME; -import static org.opensearch.knn.common.KNNConstants.PARAMETERS; - -/** - * KNNLibrary is an interface that helps the plugin communicate with k-NN libraries - */ -public interface KNNLibrary { - - /** - * Gets the library's latest build version - * - * @return the string representing the library's latest build version - */ - String getLatestBuildVersion(); - - /** - * Gets the library's latest version - * - * @return the string representing the library's latest version - */ - String getLatestLibVersion(); - - /** - * Gets the extension that files written with this library should have - * - * @return extension - */ - String getExtension(); - - /** - * Gets the compound extension that files written with this library should have - * - * @return compound extension - */ - String getCompoundExtension(); - - /** - * Gets a particular KNN method that the library supports. This should throw an exception if the method is not - * supported by the library. - * - * @param methodName name of the method to be looked up - * @return KNNMethod in the library corresponding to the method name - */ - KNNMethod getMethod(String methodName); - - /** - * Generate the Lucene score from the rawScore returned by the library. With k-NN, often times the library - * will return a score where the lower the score, the better the result. This is the opposite of how Lucene scores - * documents. - * - * @param rawScore returned by the library - * @param spaceType spaceType used to compute the score - * @return Lucene score for the rawScore - */ - float score(float rawScore, SpaceType spaceType); - - /** - * Validate the knnMethodContext for the given library. A ValidationException should be thrown if the method is - * deemed invalid. - * - * @param knnMethodContext to be validated - * @return ValidationException produced by validation errors; null if no validations errors. - */ - ValidationException validateMethod(KNNMethodContext knnMethodContext); - - /** - * Returns whether training is required or not from knnMethodContext for the given library. - * - * @param knnMethodContext methodContext - * @return true if training is required; false otherwise - */ - boolean isTrainingRequired(KNNMethodContext knnMethodContext); - - /** - * Estimate overhead of KNNMethodContext in Kilobytes. - * - * @param knnMethodContext to estimate size for - * @param dimension to estimate size for - * @return size overhead estimate in KB - */ - int estimateOverheadInKB(KNNMethodContext knnMethodContext, int dimension); - - /** - * Generate method as map that can be used to configure the knn index from the jni - * - * @param knnMethodContext to generate parameter map from - * @return parameter map - */ - Map getMethodAsMap(KNNMethodContext knnMethodContext); - - /** - * Getter for initialized - * - * @return whether library has been initialized - */ - Boolean isInitialized(); - - /** - * Set initialized to true - * - * @param isInitialized whether library has been initialized - */ - void setInitialized(Boolean isInitialized); - - /** - * Abstract implementation of KNNLibrary. It contains several default methods and fields that - * are common across different underlying libraries. - */ - abstract class NativeLibrary implements KNNLibrary { - protected Map methods; - private Map> scoreTranslation; - private String latestLibraryBuildVersion; - private String latestLibraryVersion; - private String extension; - private AtomicBoolean initialized; - - /** - * Constructor for NativeLibrary - * - * @param methods map of methods the native library supports - * @param scoreTranslation Map of translation of space type to scores returned by the library - * @param latestLibraryBuildVersion String representation of latest build version of the library - * @param latestLibraryVersion String representation of latest version of the library - * @param extension String representing the extension that library files should use - */ - public NativeLibrary( - Map methods, - Map> scoreTranslation, - String latestLibraryBuildVersion, - String latestLibraryVersion, - String extension - ) { - this.methods = methods; - this.scoreTranslation = scoreTranslation; - this.latestLibraryBuildVersion = latestLibraryBuildVersion; - this.latestLibraryVersion = latestLibraryVersion; - this.extension = extension; - this.initialized = new AtomicBoolean(false); - } - - @Override - public String getLatestBuildVersion() { - return this.latestLibraryBuildVersion; - } - - @Override - public String getLatestLibVersion() { - return this.latestLibraryVersion; - } - - @Override - public String getExtension() { - return this.extension; - } - - @Override - public String getCompoundExtension() { - return getExtension() + KNNConstants.COMPOUND_EXTENSION; - } - - @Override - public KNNMethod getMethod(String methodName) { - KNNMethod method = methods.get(methodName); - if (method != null) { - return method; - } - throw new IllegalArgumentException("Invalid method name: " + methodName); - } - - @Override - public float score(float rawScore, SpaceType spaceType) { - if (this.scoreTranslation.containsKey(spaceType)) { - return this.scoreTranslation.get(spaceType).apply(rawScore); - } - - return spaceType.scoreTranslation(rawScore); - } - - @Override - public ValidationException validateMethod(KNNMethodContext knnMethodContext) { - String methodName = knnMethodContext.getMethodComponent().getName(); - return getMethod(methodName).validate(knnMethodContext); - } - - @Override - public boolean isTrainingRequired(KNNMethodContext knnMethodContext) { - String methodName = knnMethodContext.getMethodComponent().getName(); - return getMethod(methodName).isTrainingRequired(knnMethodContext); - } - - @Override - public int estimateOverheadInKB(KNNMethodContext knnMethodContext, int dimension) { - String methodName = knnMethodContext.getMethodComponent().getName(); - return getMethod(methodName).estimateOverheadInKB(knnMethodContext, dimension); - } - - @Override - public Map getMethodAsMap(KNNMethodContext knnMethodContext) { - KNNMethod knnMethod = methods.get(knnMethodContext.getMethodComponent().getName()); - - if (knnMethod == null) { - throw new IllegalArgumentException("Invalid method name: " + knnMethodContext.getMethodComponent().getName()); - } - - return knnMethod.getAsMap(knnMethodContext); - } - - @Override - public Boolean isInitialized() { - return initialized.get(); - } - - @Override - public void setInitialized(Boolean isInitialized) { - initialized.set(isInitialized); - } - } - - /** - * Implements NativeLibrary for the nmslib native library - */ - class Nmslib extends NativeLibrary { - // ====================================== - // Constants pertaining to nmslib library - // ====================================== - public final static String HNSW_LIB_NAME = "hnsw"; - public final static String EXTENSION = ".hnsw"; - - public final static Map METHODS = ImmutableMap.of( - METHOD_HNSW, - KNNMethod.Builder.builder( - MethodComponent.Builder.builder(HNSW_LIB_NAME) - .addParameter( - METHOD_PARAMETER_M, - new Parameter.IntegerParameter(METHOD_PARAMETER_M, KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_M, v -> v > 0) - ) - .addParameter( - METHOD_PARAMETER_EF_CONSTRUCTION, - new Parameter.IntegerParameter( - METHOD_PARAMETER_EF_CONSTRUCTION, - KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION, - v -> v > 0 - ) - ) - .build() - ).addSpaces(SpaceType.L2, SpaceType.L1, SpaceType.LINF, SpaceType.COSINESIMIL, SpaceType.INNER_PRODUCT).build() - ); - - public final static Nmslib INSTANCE = new Nmslib( - METHODS, - Collections.emptyMap(), - Version.LATEST.getBuildVersion(), - Version.LATEST.indexLibraryVersion(), - EXTENSION - ); - - /** - * Constructor for Nmslib - * - * @param methods Set of methods the native library supports - * @param scoreTranslation Map of translation of space type to scores returned by the library - * @param latestLibraryBuildVersion String representation of latest build version of the library - * @param latestLibraryVersion String representation of latest version of the library - * @param extension String representing the extension that library files should use - */ - private Nmslib( - Map methods, - Map> scoreTranslation, - String latestLibraryBuildVersion, - String latestLibraryVersion, - String extension - ) { - super(methods, scoreTranslation, latestLibraryBuildVersion, latestLibraryVersion, extension); - } - - public enum Version { - /** - * Latest available nmslib version - */ - V2011("2011") { - @Override - public String indexLibraryVersion() { - return KNNConstants.NMSLIB_JNI_LIBRARY_NAME; - } - }; - - public static final Version LATEST = V2011; - - private String buildVersion; - - Version(String buildVersion) { - this.buildVersion = buildVersion; - } - - /** - * NMS library version used by the KNN codec - * @return nmslib name - */ - public abstract String indexLibraryVersion(); - - public String getBuildVersion() { - return buildVersion; - } - } - } - - /** - * Implements NativeLibrary for the faiss native library - */ - class Faiss extends NativeLibrary { - - // Map that overrides OpenSearch score translation by space type of scores returned by faiss - public final static Map> SCORE_TRANSLATIONS = ImmutableMap.of( - SpaceType.INNER_PRODUCT, - rawScore -> SpaceType.INNER_PRODUCT.scoreTranslation(-1 * rawScore) - ); - - // Define encoders supported by faiss - public final static MethodComponentContext ENCODER_DEFAULT = new MethodComponentContext( - KNNConstants.ENCODER_FLAT, - Collections.emptyMap() - ); - - // TODO: To think about in future: for PQ, if dimension is not divisible by code count, PQ will fail. Right now, - // we do not have a way to base validation off of dimension. Failure will happen during training in JNI. - public final static Map encoderComponents = ImmutableMap.of( - KNNConstants.ENCODER_FLAT, - MethodComponent.Builder.builder(KNNConstants.ENCODER_FLAT) - .setMapGenerator( - ((methodComponent, methodComponentContext) -> MethodAsMapBuilder.builder( - KNNConstants.FAISS_FLAT_DESCRIPTION, - methodComponent, - methodComponentContext - ).build()) - ) - .build(), - KNNConstants.ENCODER_PQ, - MethodComponent.Builder.builder(KNNConstants.ENCODER_PQ) - .addParameter( - ENCODER_PARAMETER_PQ_M, - new Parameter.IntegerParameter( - ENCODER_PARAMETER_PQ_M, - ENCODER_PARAMETER_PQ_CODE_COUNT_DEFAULT, - v -> v > 0 && v < ENCODER_PARAMETER_PQ_CODE_COUNT_LIMIT - ) - ) - .addParameter( - ENCODER_PARAMETER_PQ_CODE_SIZE, - new Parameter.IntegerParameter( - ENCODER_PARAMETER_PQ_CODE_SIZE, - ENCODER_PARAMETER_PQ_CODE_SIZE_DEFAULT, - v -> v > 0 && v < ENCODER_PARAMETER_PQ_CODE_SIZE_LIMIT - ) - ) - .setRequiresTraining(true) - .setMapGenerator( - ((methodComponent, methodComponentContext) -> MethodAsMapBuilder.builder( - FAISS_PQ_DESCRIPTION, - methodComponent, - methodComponentContext - ).addParameter(ENCODER_PARAMETER_PQ_M, "", "").addParameter(ENCODER_PARAMETER_PQ_CODE_SIZE, "x", "").build()) - ) - .setOverheadInKBEstimator((methodComponent, methodComponentContext, dimension) -> { - // Size estimate formula: (4 * d * 2^code_size) / 1024 + 1 - - // Get value of code size passed in by user - Object codeSizeObject = methodComponentContext.getParameters().get(ENCODER_PARAMETER_PQ_CODE_SIZE); - - // If not specified, get default value of code size - if (codeSizeObject == null) { - Object codeSizeParameter = methodComponent.getParameters().get(ENCODER_PARAMETER_PQ_CODE_SIZE); - if (codeSizeParameter == null) { - throw new IllegalStateException( - ENCODER_PARAMETER_PQ_CODE_SIZE + " is not a valid " + " parameter. This is a bug." - ); - } - - codeSizeObject = ((Parameter) codeSizeParameter).getDefaultValue(); - } - - if (!(codeSizeObject instanceof Integer)) { - throw new IllegalStateException(ENCODER_PARAMETER_PQ_CODE_SIZE + " must be " + "an integer."); - } - - int codeSize = (Integer) codeSizeObject; - return ((4L * (1 << codeSize) * dimension) / BYTES_PER_KILOBYTES) + 1; - }) - .build() - ); - - // Define methods supported by faiss - public final static Map METHODS = ImmutableMap.of( - METHOD_HNSW, - KNNMethod.Builder.builder( - MethodComponent.Builder.builder(METHOD_HNSW) - .addParameter( - METHOD_PARAMETER_M, - new Parameter.IntegerParameter(METHOD_PARAMETER_M, KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_M, v -> v > 0) - ) - .addParameter( - METHOD_PARAMETER_EF_CONSTRUCTION, - new Parameter.IntegerParameter( - METHOD_PARAMETER_EF_CONSTRUCTION, - KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION, - v -> v > 0 - ) - ) - .addParameter( - METHOD_PARAMETER_EF_SEARCH, - new Parameter.IntegerParameter( - METHOD_PARAMETER_EF_SEARCH, - KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH, - v -> v > 0 - ) - ) - .addParameter( - METHOD_ENCODER_PARAMETER, - new Parameter.MethodComponentContextParameter(METHOD_ENCODER_PARAMETER, ENCODER_DEFAULT, encoderComponents) - ) - .setMapGenerator( - ((methodComponent, methodComponentContext) -> MethodAsMapBuilder.builder( - FAISS_HNSW_DESCRIPTION, - methodComponent, - methodComponentContext - ).addParameter(METHOD_PARAMETER_M, "", "").addParameter(METHOD_ENCODER_PARAMETER, ",", "").build()) - ) - .build() - ).addSpaces(SpaceType.L2, SpaceType.INNER_PRODUCT).build(), - METHOD_IVF, - KNNMethod.Builder.builder( - MethodComponent.Builder.builder(METHOD_IVF) - .addParameter( - METHOD_PARAMETER_NPROBES, - new Parameter.IntegerParameter( - METHOD_PARAMETER_NPROBES, - METHOD_PARAMETER_NPROBES_DEFAULT, - v -> v > 0 && v < METHOD_PARAMETER_NPROBES_LIMIT - ) - ) - .addParameter( - METHOD_PARAMETER_NLIST, - new Parameter.IntegerParameter( - METHOD_PARAMETER_NLIST, - METHOD_PARAMETER_NLIST_DEFAULT, - v -> v > 0 && v < METHOD_PARAMETER_NLIST_LIMIT - ) - ) - .addParameter( - METHOD_ENCODER_PARAMETER, - new Parameter.MethodComponentContextParameter(METHOD_ENCODER_PARAMETER, ENCODER_DEFAULT, encoderComponents) - ) - .setRequiresTraining(true) - .setMapGenerator( - ((methodComponent, methodComponentContext) -> MethodAsMapBuilder.builder( - FAISS_IVF_DESCRIPTION, - methodComponent, - methodComponentContext - ).addParameter(METHOD_PARAMETER_NLIST, "", "").addParameter(METHOD_ENCODER_PARAMETER, ",", "").build()) - ) - .setOverheadInKBEstimator((methodComponent, methodComponentContext, dimension) -> { - // Size estimate formula: (4 * nlists * d) / 1024 + 1 - - // Get value of nlists passed in by user - Object nlistObject = methodComponentContext.getParameters().get(METHOD_PARAMETER_NLIST); - - // If not specified, get default value of nlist - if (nlistObject == null) { - Object nlistParameter = methodComponent.getParameters().get(METHOD_PARAMETER_NLIST); - if (nlistParameter == null) { - throw new IllegalStateException(METHOD_PARAMETER_NLIST + " is not a valid " + " parameter. This is a bug."); - } - - nlistObject = ((Parameter) nlistParameter).getDefaultValue(); - } - - if (!(nlistObject instanceof Integer)) { - throw new IllegalStateException(METHOD_PARAMETER_NLIST + " must be " + "an integer."); - } - - int centroids = (Integer) nlistObject; - return ((4L * centroids * dimension) / BYTES_PER_KILOBYTES) + 1; - }) - .build() - ).addSpaces(SpaceType.L2, SpaceType.INNER_PRODUCT).build() - ); - - public final static Faiss INSTANCE = new Faiss( - METHODS, - SCORE_TRANSLATIONS, - Version.LATEST.getBuildVersion(), - Version.LATEST.indexLibraryVersion(), - KNNConstants.FAISS_EXTENSION - ); - - /** - * Constructor for Faiss - * - * @param methods map of methods the native library supports - * @param scoreTranslation Map of translation of space type to scores returned by the library - * @param latestLibraryBuildVersion String representation of latest build version of the library - * @param latestLibraryVersion String representation of latest version of the library - * @param extension String representing the extension that library files should use - */ - private Faiss( - Map methods, - Map> scoreTranslation, - String latestLibraryBuildVersion, - String latestLibraryVersion, - String extension - ) { - super(methods, scoreTranslation, latestLibraryBuildVersion, latestLibraryVersion, extension); - } - - /** - * MethodAsMap builder is used to create the map that will be passed to the jni to create the faiss index. - * Faiss's index factory takes an "index description" that it uses to build the index. In this description, - * some parameters of the index can be configured; others need to be manually set. MethodMap builder creates - * the index description from a set of parameters and removes them from the map. On build, it sets the index - * description in the map and returns the processed map - */ - protected static class MethodAsMapBuilder { - String indexDescription; - MethodComponent methodComponent; - Map methodAsMap; - - /** - * Constructor - * - * @param baseDescription the basic description this component should start with - * @param methodComponent the method component that maps to this builder - * @param initialMap the initial parameter map that will be modified - */ - MethodAsMapBuilder(String baseDescription, MethodComponent methodComponent, Map initialMap) { - this.indexDescription = baseDescription; - this.methodComponent = methodComponent; - this.methodAsMap = initialMap; - } - - /** - * Add a parameter that will be used in the index description for the given method component - * - * @param parameterName name of the parameter - * @param prefix to append to the index description before the parameter - * @param suffix to append to the index description after the parameter - * @return this builder - */ - @SuppressWarnings("unchecked") - MethodAsMapBuilder addParameter(String parameterName, String prefix, String suffix) { - indexDescription += prefix; - - // When we add a parameter, what we are doing is taking it from the methods parameter and building it - // into the index description string faiss uses to create the index. - Map methodParameters = (Map) methodAsMap.get(PARAMETERS); - Parameter parameter = methodComponent.getParameters().get(parameterName); - Object value = methodParameters.containsKey(parameterName) - ? methodParameters.get(parameterName) - : parameter.getDefaultValue(); - - // Recursion is needed if the parameter is a method component context itself. - if (parameter instanceof Parameter.MethodComponentContextParameter) { - MethodComponentContext subMethodComponentContext = (MethodComponentContext) value; - MethodComponent subMethodComponent = ((Parameter.MethodComponentContextParameter) parameter).getMethodComponent( - subMethodComponentContext.getName() - ); - - Map subMethodAsMap = subMethodComponent.getAsMap(subMethodComponentContext); - indexDescription += subMethodAsMap.get(KNNConstants.INDEX_DESCRIPTION_PARAMETER); - subMethodAsMap.remove(KNNConstants.INDEX_DESCRIPTION_PARAMETER); - - // We replace parameterName with the map that contains only parameters that are not included in - // the method description - methodParameters.put(parameterName, subMethodAsMap); - } else { - // Just add the value to the method description and remove from map - indexDescription += value; - methodParameters.remove(parameterName); - } - - indexDescription += suffix; - return this; - } - - /** - * Build - * - * @return Method as a map - */ - Map build() { - methodAsMap.put(KNNConstants.INDEX_DESCRIPTION_PARAMETER, indexDescription); - return methodAsMap; - } - - static MethodAsMapBuilder builder( - String baseDescription, - MethodComponent methodComponent, - MethodComponentContext methodComponentContext - ) { - Map initialMap = new HashMap<>(); - initialMap.put(NAME, methodComponent.getName()); - initialMap.put(PARAMETERS, MethodComponent.getParameterMapWithDefaultsAdded(methodComponentContext, methodComponent)); - return new MethodAsMapBuilder(baseDescription, methodComponent, initialMap); - } - } - - /** - * Enum containing information about faiss versioning - */ - private enum Version { - - /** - * Latest available nmslib version - */ - V165("165") { - @Override - public String indexLibraryVersion() { - return KNNConstants.FAISS_JNI_LIBRARY_NAME; - } - }; - - static final Version LATEST = V165; - - String buildVersion; - - Version(String buildVersion) { - this.buildVersion = buildVersion; - } - - /** - * Faiss version used by the KNN codec - * @return library name - */ - abstract String indexLibraryVersion(); - - String getBuildVersion() { - return buildVersion; - } - } - } -} diff --git a/src/main/java/org/opensearch/knn/index/vectorvalues/KNNBinaryVectorValues.java b/src/main/java/org/opensearch/knn/index/vectorvalues/KNNBinaryVectorValues.java new file mode 100644 index 000000000..5da093fd5 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/vectorvalues/KNNBinaryVectorValues.java @@ -0,0 +1,42 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.vectorvalues; + +import lombok.ToString; +import org.apache.lucene.codecs.KnnFieldVectorsWriter; +import org.apache.lucene.index.BinaryDocValues; +import org.apache.lucene.index.ByteVectorValues; + +import java.io.IOException; +import java.util.Arrays; + +/** + * Concrete implementation of {@link KNNVectorValues} that returns byte[] as vector where binary vector is stored and + * provides an abstraction over {@link BinaryDocValues}, {@link ByteVectorValues}, {@link KnnFieldVectorsWriter} etc. + */ +@ToString(callSuper = true) +public class KNNBinaryVectorValues extends KNNVectorValues { + KNNBinaryVectorValues(KNNVectorValuesIterator vectorValuesIterator) { + super(vectorValuesIterator); + } + + @Override + public byte[] getVector() throws IOException { + final byte[] vector = VectorValueExtractorStrategy.extractBinaryVector(vectorValuesIterator); + this.dimension = vector.length * Byte.SIZE; + this.bytesPerVector = vector.length; + return vector; + } + + @Override + public byte[] conditionalCloneVector() throws IOException { + byte[] vector = getVector(); + if (vectorValuesIterator.getDocIdSetIterator() instanceof ByteVectorValues) { + return Arrays.copyOf(vector, vector.length); + } + return vector; + } +} diff --git a/src/main/java/org/opensearch/knn/index/vectorvalues/KNNByteVectorValues.java b/src/main/java/org/opensearch/knn/index/vectorvalues/KNNByteVectorValues.java new file mode 100644 index 000000000..1ebc50970 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/vectorvalues/KNNByteVectorValues.java @@ -0,0 +1,43 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.vectorvalues; + +import lombok.ToString; +import org.apache.lucene.codecs.KnnFieldVectorsWriter; +import org.apache.lucene.index.BinaryDocValues; +import org.apache.lucene.index.ByteVectorValues; + +import java.io.IOException; +import java.util.Arrays; + +/** + * Concrete implementation of {@link KNNVectorValues} that returns float[] as vector and provides an abstraction over + * {@link BinaryDocValues}, {@link ByteVectorValues}, {@link KnnFieldVectorsWriter} etc. + */ +@ToString(callSuper = true) +public class KNNByteVectorValues extends KNNVectorValues { + KNNByteVectorValues(KNNVectorValuesIterator vectorValuesIterator) { + super(vectorValuesIterator); + } + + @Override + public byte[] getVector() throws IOException { + final byte[] vector = VectorValueExtractorStrategy.extractByteVector(vectorValuesIterator); + this.dimension = vector.length; + this.bytesPerVector = vector.length; + return vector; + } + + @Override + public byte[] conditionalCloneVector() throws IOException { + byte[] vector = getVector(); + if (vectorValuesIterator.getDocIdSetIterator() instanceof ByteVectorValues) { + return Arrays.copyOf(vector, vector.length); + + } + return vector; + } +} diff --git a/src/main/java/org/opensearch/knn/index/vectorvalues/KNNFloatVectorValues.java b/src/main/java/org/opensearch/knn/index/vectorvalues/KNNFloatVectorValues.java new file mode 100644 index 000000000..dffdd8f0d --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/vectorvalues/KNNFloatVectorValues.java @@ -0,0 +1,40 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.vectorvalues; + +import org.apache.lucene.codecs.KnnFieldVectorsWriter; +import org.apache.lucene.index.BinaryDocValues; +import org.apache.lucene.index.FloatVectorValues; + +import java.io.IOException; +import java.util.Arrays; + +/** + * Concrete implementation of {@link KNNVectorValues} that returns float[] as vector and provides an abstraction over + * {@link BinaryDocValues}, {@link FloatVectorValues}, {@link KnnFieldVectorsWriter} etc. + */ +public class KNNFloatVectorValues extends KNNVectorValues { + KNNFloatVectorValues(final KNNVectorValuesIterator vectorValuesIterator) { + super(vectorValuesIterator); + } + + @Override + public float[] getVector() throws IOException { + final float[] vector = VectorValueExtractorStrategy.extractFloatVector(vectorValuesIterator); + this.dimension = vector.length; + this.bytesPerVector = vector.length * 4; + return vector; + } + + @Override + public float[] conditionalCloneVector() throws IOException { + float[] vector = getVector(); + if (vectorValuesIterator.getDocIdSetIterator() instanceof FloatVectorValues) { + return Arrays.copyOf(vector, vector.length); + } + return vector; + } +} diff --git a/src/main/java/org/opensearch/knn/index/vectorvalues/KNNVectorValues.java b/src/main/java/org/opensearch/knn/index/vectorvalues/KNNVectorValues.java new file mode 100644 index 000000000..b12395185 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/vectorvalues/KNNVectorValues.java @@ -0,0 +1,117 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.vectorvalues; + +import lombok.ToString; +import org.apache.lucene.codecs.KnnFieldVectorsWriter; +import org.apache.lucene.index.BinaryDocValues; +import org.apache.lucene.index.ByteVectorValues; +import org.apache.lucene.index.FloatVectorValues; + +import java.io.IOException; + +/** + * An abstract class to iterate over KNNVectors, as KNNVectors are stored as different representation like + * {@link BinaryDocValues}, {@link FloatVectorValues}, {@link ByteVectorValues}, {@link KnnFieldVectorsWriter} etc. + * @param + */ +@ToString +public abstract class KNNVectorValues { + + protected final KNNVectorValuesIterator vectorValuesIterator; + protected int dimension; + protected int bytesPerVector; + + protected KNNVectorValues(final KNNVectorValuesIterator vectorValuesIterator) { + this.vectorValuesIterator = vectorValuesIterator; + } + + /** + * Return a vector reference. If you are adding this address in a List/Map ensure that you are copying the vector first. + * This is to ensure that we keep the heap and latency in check by reducing the copies of vectors. + * + * @return T an array of byte[], float[] + * @throws IOException if we are not able to get the vector + */ + public abstract T getVector() throws IOException; + + /** + * Intended to return a vector reference either after deep copy of the vector obtained from {@code getVector} + * or return the vector itself. + *

+ * This decision to clone depends on the vector returned based on the type of iterator + *

+ * Running this function can incur latency hence should be absolutely used when necessary. + * For most of the cases {@link #getVector()} function should work. + * + * @return T an array of byte[], float[] Or a deep copy of it + * @throws IOException + */ + public abstract T conditionalCloneVector() throws IOException; + + /** + * Dimension of vector is returned. Do call getVector function first before calling this function otherwise you will get 0 value. + * @return int + */ + public int dimension() { + assert docId() != -1 && dimension != 0 : "Cannot get dimension before we retrieve a vector from KNNVectorValues"; + return dimension; + } + + /** + * Size of a vector in bytes is returned. Do call getVector function first before calling this function otherwise you will get 0 value. + * @return int + */ + public int bytesPerVector() { + assert docId() != -1 && bytesPerVector != 0 : "Cannot get bytesPerVector before we retrieve a vector from KNNVectorValues"; + return bytesPerVector; + } + + /** + * Returns the total live docs for KNNVectorValues. This function is broken and doesn't always give the accurate + * live docs count when iterators are {@link FloatVectorValues}, {@link ByteVectorValues}. Avoid using this iterator, + * rather use a simple function like this: + *
+     *     int liveDocs = 0;
+     *     while(vectorValues.nextDoc() != DocIdSetIterator.NO_MORE_DOCS) {
+     *         liveDocs++;
+     *     }
+     * 
+ * @return long + */ + @Deprecated + public long totalLiveDocs() { + return vectorValuesIterator.liveDocs(); + } + + /** + * Returns the current docId where the iterator is pointing to. + * @return int + */ + public int docId() { + return vectorValuesIterator.docId(); + } + + /** + * Advances to a specific docId. Ensure that the passed docId is greater than current docId where Iterator is + * pointing to, otherwise + * {@link IOException} will be thrown + * @return int + * @throws IOException if we are not able to move to the passed docId. + */ + public int advance(int docId) throws IOException { + return vectorValuesIterator.advance(docId); + } + + /** + * Move to nextDocId. + * @return int + * @throws IOException if we cannot move to next docId + */ + public int nextDoc() throws IOException { + return vectorValuesIterator.nextDoc(); + } +} diff --git a/src/main/java/org/opensearch/knn/index/vectorvalues/KNNVectorValuesFactory.java b/src/main/java/org/opensearch/knn/index/vectorvalues/KNNVectorValuesFactory.java new file mode 100644 index 000000000..41408e217 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/vectorvalues/KNNVectorValuesFactory.java @@ -0,0 +1,90 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.vectorvalues; + +import org.apache.lucene.index.DocValues; +import org.apache.lucene.index.DocsWithFieldSet; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.LeafReader; +import org.apache.lucene.index.VectorEncoding; +import org.apache.lucene.search.DocIdSetIterator; +import org.opensearch.knn.common.FieldInfoExtractor; +import org.opensearch.knn.index.VectorDataType; + +import java.io.IOException; +import java.util.Map; + +/** + * A factory class that provides various methods to create the {@link KNNVectorValues}. + */ +public final class KNNVectorValuesFactory { + + /** + * Returns a {@link KNNVectorValues} for the given {@link DocIdSetIterator} and {@link VectorDataType} + * + * @param vectorDataType {@link VectorDataType} + * @param docIdSetIterator {@link DocIdSetIterator} + * @return {@link KNNVectorValues} + */ + public static KNNVectorValues getVectorValues(final VectorDataType vectorDataType, final DocIdSetIterator docIdSetIterator) { + return getVectorValues(vectorDataType, new KNNVectorValuesIterator.DocIdsIteratorValues(docIdSetIterator)); + } + + /** + * Returns a {@link KNNVectorValues} for the given {@link DocIdSetIterator} and a Map of docId and vectors. + * + * @param vectorDataType {@link VectorDataType} + * @param docIdWithFieldSet {@link DocsWithFieldSet} + * @return {@link KNNVectorValues} + */ + public static KNNVectorValues getVectorValues( + final VectorDataType vectorDataType, + final DocsWithFieldSet docIdWithFieldSet, + final Map vectors + ) { + return getVectorValues(vectorDataType, new KNNVectorValuesIterator.FieldWriterIteratorValues(docIdWithFieldSet, vectors)); + } + + /** + * Returns a {@link KNNVectorValues} for the given {@link FieldInfo} and {@link LeafReader} + * + * @param fieldInfo {@link FieldInfo} + * @param leafReader {@link LeafReader} + * @return {@link KNNVectorValues} + */ + public static KNNVectorValues getVectorValues(final FieldInfo fieldInfo, final LeafReader leafReader) throws IOException { + final DocIdSetIterator docIdSetIterator; + if (fieldInfo.hasVectorValues()) { + if (fieldInfo.getVectorEncoding() == VectorEncoding.BYTE) { + docIdSetIterator = leafReader.getByteVectorValues(fieldInfo.getName()); + } else if (fieldInfo.getVectorEncoding() == VectorEncoding.FLOAT32) { + docIdSetIterator = leafReader.getFloatVectorValues(fieldInfo.getName()); + } else { + throw new IllegalArgumentException("Invalid Vector encoding provided, hence cannot return VectorValues"); + } + } else { + docIdSetIterator = DocValues.getBinary(leafReader, fieldInfo.getName()); + } + final KNNVectorValuesIterator vectorValuesIterator = new KNNVectorValuesIterator.DocIdsIteratorValues(docIdSetIterator); + return getVectorValues(FieldInfoExtractor.extractVectorDataType(fieldInfo), vectorValuesIterator); + } + + @SuppressWarnings("unchecked") + private static KNNVectorValues getVectorValues( + final VectorDataType vectorDataType, + final KNNVectorValuesIterator knnVectorValuesIterator + ) { + switch (vectorDataType) { + case FLOAT: + return (KNNVectorValues) new KNNFloatVectorValues(knnVectorValuesIterator); + case BYTE: + return (KNNVectorValues) new KNNByteVectorValues(knnVectorValuesIterator); + case BINARY: + return (KNNVectorValues) new KNNBinaryVectorValues(knnVectorValuesIterator); + } + throw new IllegalArgumentException("Invalid Vector data type provided, hence cannot return VectorValues"); + } +} diff --git a/src/main/java/org/opensearch/knn/index/vectorvalues/KNNVectorValuesIterator.java b/src/main/java/org/opensearch/knn/index/vectorvalues/KNNVectorValuesIterator.java new file mode 100644 index 000000000..4f1445c1c --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/vectorvalues/KNNVectorValuesIterator.java @@ -0,0 +1,188 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.vectorvalues; + +import lombok.NonNull; +import org.apache.lucene.codecs.KnnFieldVectorsWriter; +import org.apache.lucene.index.BinaryDocValues; +import org.apache.lucene.index.ByteVectorValues; +import org.apache.lucene.index.DocsWithFieldSet; +import org.apache.lucene.index.FloatVectorValues; +import org.apache.lucene.search.DocIdSetIterator; +import org.opensearch.knn.index.codec.util.KNNCodecUtil; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +/** + * An abstract class that provides an iterator to iterate over KNNVectors, as KNNVectors are stored as different + * representation like {@link BinaryDocValues}, {@link FloatVectorValues}, FieldWriter etc. How to iterate using this + * iterator please refer {@link DocIdsIteratorValues} java docs. + */ +public interface KNNVectorValuesIterator { + + /** + * Returns the current docId where the iterator is pointing to. + * @return int + */ + int docId(); + + /** + * Advances to a specific docId. Ensure that the passed docId is greater than current docId where Iterator is + * pointing to, otherwise + * {@link IOException} will be thrown + * @return int + * @throws IOException if we are not able to move to the passed docId. + */ + int advance(int docId) throws IOException; + + /** + * Move to nextDocId. If no more docs are present then {@link DocIdSetIterator#NO_MORE_DOCS} will be returned. + * @return int + * @throws IOException if we cannot move to next docId + */ + int nextDoc() throws IOException; + + /** + * Return a {@link DocIdSetIterator} + * @return {@link DocIdSetIterator} + */ + DocIdSetIterator getDocIdSetIterator(); + + /** + * Total number of live doc which will the iterator will iterate upon. + * @return long: total number of live docs + */ + long liveDocs(); + + /** + * Returns the {@link VectorValueExtractorStrategy} to extract the vector from the iterator. + * @return VectorValueExtractorStrategy + */ + VectorValueExtractorStrategy getVectorExtractorStrategy(); + + /** + * A DocIdsIteratorValues provides a common iteration logic for all Values that implements + * {@link DocIdSetIterator} interface. Example: {@link BinaryDocValues}, {@link FloatVectorValues} etc. + */ + class DocIdsIteratorValues implements KNNVectorValuesIterator { + protected DocIdSetIterator docIdSetIterator; + private static final List> VALID_ITERATOR_INSTANCE = List.of( + (itr) -> itr instanceof BinaryDocValues, + (itr) -> itr instanceof FloatVectorValues, + (itr) -> itr instanceof ByteVectorValues + ); + + DocIdsIteratorValues(@NonNull final DocIdSetIterator docIdSetIterator) { + validateIteratorType(docIdSetIterator); + this.docIdSetIterator = docIdSetIterator; + } + + @Override + public int docId() { + return docIdSetIterator.docID(); + } + + @Override + public int advance(int docId) throws IOException { + return docIdSetIterator.advance(docId); + } + + @Override + public int nextDoc() throws IOException { + return docIdSetIterator.nextDoc(); + } + + @Override + public DocIdSetIterator getDocIdSetIterator() { + return docIdSetIterator; + } + + @Override + public long liveDocs() { + if (docIdSetIterator instanceof BinaryDocValues) { + return KNNCodecUtil.getTotalLiveDocsCount((BinaryDocValues) docIdSetIterator); + } else if (docIdSetIterator instanceof FloatVectorValues || docIdSetIterator instanceof ByteVectorValues) { + return docIdSetIterator.cost(); + } + throw new IllegalArgumentException( + "DocIdSetIterator present is not of valid type. Valid types are: BinaryDocValues, FloatVectorValues and ByteVectorValues" + ); + } + + @Override + public VectorValueExtractorStrategy getVectorExtractorStrategy() { + return new VectorValueExtractorStrategy.DISIVectorExtractor(); + } + + private void validateIteratorType(final DocIdSetIterator docIdSetIterator) { + VALID_ITERATOR_INSTANCE.stream() + .map(v -> v.apply(docIdSetIterator)) + .filter(Boolean::booleanValue) + .findFirst() + .orElseThrow( + () -> new IllegalArgumentException( + "DocIdSetIterator present is not of valid type. Valid types are: BinaryDocValues, FloatVectorValues and ByteVectorValues" + ) + ); + } + } + + /** + * A FieldWriterIteratorValues is mainly used when Vectors are stored in {@link KnnFieldVectorsWriter} interface. + */ + class FieldWriterIteratorValues implements KNNVectorValuesIterator { + private final DocIdSetIterator docIdSetIterator; + private final Map vectors; + + FieldWriterIteratorValues(@NonNull final DocsWithFieldSet docsWithFieldSet, @NonNull final Map vectors) { + assert docsWithFieldSet.iterator().cost() == vectors.size(); + this.vectors = vectors; + this.docIdSetIterator = docsWithFieldSet.iterator(); + } + + @Override + public int docId() { + return docIdSetIterator.docID(); + } + + @Override + public int advance(int docId) throws IOException { + return docIdSetIterator.advance(docId); + } + + @Override + public int nextDoc() throws IOException { + return docIdSetIterator.nextDoc(); + } + + /** + * Returns a Map of docId and vector. + * @return {@link Map} + */ + public T vectorsValue() { + return vectors.get(docId()); + } + + @Override + public DocIdSetIterator getDocIdSetIterator() { + return docIdSetIterator; + } + + @Override + public long liveDocs() { + return docIdSetIterator.cost(); + } + + @Override + public VectorValueExtractorStrategy getVectorExtractorStrategy() { + return new VectorValueExtractorStrategy.FieldWriterIteratorVectorExtractor(); + } + } + +} diff --git a/src/main/java/org/opensearch/knn/index/vectorvalues/VectorValueExtractorStrategy.java b/src/main/java/org/opensearch/knn/index/vectorvalues/VectorValueExtractorStrategy.java new file mode 100644 index 000000000..07db4e7f6 --- /dev/null +++ b/src/main/java/org/opensearch/knn/index/vectorvalues/VectorValueExtractorStrategy.java @@ -0,0 +1,126 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.vectorvalues; + +import org.apache.lucene.index.BinaryDocValues; +import org.apache.lucene.index.ByteVectorValues; +import org.apache.lucene.index.FloatVectorValues; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.util.ArrayUtil; +import org.apache.lucene.util.BytesRef; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.codec.util.KNNVectorSerializer; +import org.opensearch.knn.index.codec.util.KNNVectorSerializerFactory; + +import java.io.IOException; + +/** + * Provides different strategies to extract the vectors from different {@link KNNVectorValuesIterator} + */ +interface VectorValueExtractorStrategy { + + /** + * Extract a float vector from KNNVectorValuesIterator. + * @param iterator {@link KNNVectorValuesIterator} + * @return float[] + * @throws IOException exception while retrieving the vectors + */ + static float[] extractFloatVector(final KNNVectorValuesIterator iterator) throws IOException { + return iterator.getVectorExtractorStrategy().extract(VectorDataType.FLOAT, iterator); + } + + /** + * Extract a byte vector from KNNVectorValuesIterator. + * @param iterator {@link KNNVectorValuesIterator} + * @return byte[] + * @throws IOException exception while retrieving the vectors + */ + static byte[] extractByteVector(final KNNVectorValuesIterator iterator) throws IOException { + return iterator.getVectorExtractorStrategy().extract(VectorDataType.BYTE, iterator); + } + + /** + * Extract a binary vector which is represented as byte[] from KNNVectorValuesIterator. + * @param iterator {@link KNNVectorValuesIterator} + * @return byte[] + * @throws IOException exception while retrieving the vectors + */ + static byte[] extractBinaryVector(final KNNVectorValuesIterator iterator) throws IOException { + return iterator.getVectorExtractorStrategy().extract(VectorDataType.BINARY, iterator); + } + + /** + * Extract Vector based on the vector datatype and vector values iterator. + * @param vectorDataType {@link VectorDataType} + * @param vectorValuesIterator {@link KNNVectorValuesIterator} + * @return vector + * @param could be of type float[], byte[] + * @throws IOException exception during extracting the vectors + */ + T extract(VectorDataType vectorDataType, KNNVectorValuesIterator vectorValuesIterator) throws IOException; + + /** + * Strategy to extract the vector from {@link KNNVectorValuesIterator.DocIdsIteratorValues} + */ + class DISIVectorExtractor implements VectorValueExtractorStrategy { + @Override + public T extract(final VectorDataType vectorDataType, final KNNVectorValuesIterator vectorValuesIterator) throws IOException { + final DocIdSetIterator docIdSetIterator = vectorValuesIterator.getDocIdSetIterator(); + switch (vectorDataType) { + case FLOAT: + if (docIdSetIterator instanceof BinaryDocValues) { + final BinaryDocValues values = (BinaryDocValues) docIdSetIterator; + return (T) getFloatVectorFromByteRef(values.binaryValue()); + } else if (docIdSetIterator instanceof FloatVectorValues) { + return (T) ((FloatVectorValues) docIdSetIterator).vectorValue(); + } + throw new IllegalArgumentException( + "VectorValuesIterator is not of a valid type. Valid Types are: BinaryDocValues and FloatVectorValues" + ); + case BYTE: + case BINARY: + if (docIdSetIterator instanceof BinaryDocValues) { + final BinaryDocValues values = (BinaryDocValues) docIdSetIterator; + final BytesRef bytesRef = values.binaryValue(); + return (T) ArrayUtil.copyOfSubArray(bytesRef.bytes, bytesRef.offset, bytesRef.offset + bytesRef.length); + } else if (docIdSetIterator instanceof ByteVectorValues) { + return (T) ((ByteVectorValues) docIdSetIterator).vectorValue(); + } + throw new IllegalArgumentException( + "VectorValuesIterator is not of a valid type. Valid Types are: BinaryDocValues and ByteVectorValues" + ); + } + throw new IllegalArgumentException("Valid Vector data type not passed to extract vector from DISIVectorExtractor strategy"); + } + + private float[] getFloatVectorFromByteRef(final BytesRef bytesRef) { + final KNNVectorSerializer vectorSerializer = KNNVectorSerializerFactory.getSerializerByBytesRef(bytesRef); + return vectorSerializer.byteToFloatArray(bytesRef); + } + } + + /** + * Strategy to extract the vector from {@link KNNVectorValuesIterator.FieldWriterIteratorValues} + */ + class FieldWriterIteratorVectorExtractor implements VectorValueExtractorStrategy { + + @SuppressWarnings("unchecked") + @Override + public T extract(final VectorDataType vectorDataType, final KNNVectorValuesIterator vectorValuesIterator) throws IOException { + switch (vectorDataType) { + case FLOAT: + return (T) ((KNNVectorValuesIterator.FieldWriterIteratorValues) vectorValuesIterator).vectorsValue(); + case BYTE: + case BINARY: + return (T) ((KNNVectorValuesIterator.FieldWriterIteratorValues) vectorValuesIterator).vectorsValue(); + } + throw new IllegalArgumentException( + "Valid Vector data type not passed to extract vector from FieldWriterIteratorVectorExtractor strategy" + ); + } + } + +} diff --git a/src/main/java/org/opensearch/knn/indices/Model.java b/src/main/java/org/opensearch/knn/indices/Model.java index 18e085f5a..5007650dd 100644 --- a/src/main/java/org/opensearch/knn/indices/Model.java +++ b/src/main/java/org/opensearch/knn/indices/Model.java @@ -12,11 +12,11 @@ package org.opensearch.knn.indices; import org.opensearch.common.Nullable; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; -import org.opensearch.common.io.stream.Writeable; -import org.opensearch.common.xcontent.ToXContentObject; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.common.io.stream.Writeable; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.knn.common.KNNConstants; import java.io.IOException; diff --git a/src/main/java/org/opensearch/knn/indices/ModelDao.java b/src/main/java/org/opensearch/knn/indices/ModelDao.java index 937e5f811..d0abe8612 100644 --- a/src/main/java/org/opensearch/knn/indices/ModelDao.java +++ b/src/main/java/org/opensearch/knn/indices/ModelDao.java @@ -13,13 +13,15 @@ import com.google.common.base.Charsets; import com.google.common.io.Resources; +import lombok.SneakyThrows; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.opensearch.OpenSearchException; import org.opensearch.ResourceNotFoundException; -import org.opensearch.action.ActionListener; import org.opensearch.action.DocWriteRequest; import org.opensearch.action.DocWriteResponse; import org.opensearch.action.FailedNodeException; +import org.opensearch.action.StepListener; import org.opensearch.action.admin.indices.create.CreateIndexRequest; import org.opensearch.action.admin.indices.create.CreateIndexResponse; import org.opensearch.action.delete.DeleteAction; @@ -40,13 +42,24 @@ import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.index.IndexNotFoundException; import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.common.exception.DeleteModelException; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; import org.opensearch.knn.plugin.transport.DeleteModelResponse; import org.opensearch.knn.plugin.transport.GetModelResponse; import org.opensearch.knn.plugin.transport.RemoveModelFromCacheAction; import org.opensearch.knn.plugin.transport.RemoveModelFromCacheRequest; import org.opensearch.knn.plugin.transport.RemoveModelFromCacheResponse; +import org.opensearch.knn.plugin.transport.UpdateModelGraveyardAction; +import org.opensearch.knn.plugin.transport.UpdateModelGraveyardRequest; import org.opensearch.knn.plugin.transport.UpdateModelMetadataAction; import org.opensearch.knn.plugin.transport.UpdateModelMetadataRequest; @@ -55,8 +68,12 @@ import java.util.Base64; import java.util.HashMap; import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.concurrent.ExecutionException; +import java.util.function.Supplier; +import static java.util.Objects.isNull; import static org.opensearch.knn.common.KNNConstants.MODEL_INDEX_MAPPING_PATH; import static org.opensearch.knn.common.KNNConstants.MODEL_INDEX_NAME; import static org.opensearch.knn.common.KNNConstants.MODEL_METADATA_FIELD; @@ -69,7 +86,7 @@ public interface ModelDao { /** - * Creates model index. It is possible that the 2 threads call this function simulateously. In this case, one + * Creates model index. It is possible that the 2 threads call this function simultaneously. In this case, one * thread will throw a ResourceAlreadyExistsException. This should be caught and handled. * * @param actionListener CreateIndexResponse listener @@ -122,9 +139,8 @@ public interface ModelDao { * * @param modelId to retrieve * @param listener handles get model response - * @throws IOException thrown on search */ - void get(String modelId, ActionListener listener) throws IOException; + void get(String modelId, ActionListener listener); /** * searches model from the system index. Non-blocking. @@ -151,6 +167,16 @@ public interface ModelDao { */ void delete(String modelId, ActionListener listener); + /** + * Check if modelId is in model graveyard or not. Non-blocking. + * A modelId is added to model graveyard before deleting that + * model and removed from it after deleting the model + * + * @param modelId to retrieve + * @return true if modelId is in model graveyard, otherwise return false + */ + boolean isModelInGraveyard(String modelId); + /** * Implementation of ModelDao for k-NN model index */ @@ -198,14 +224,21 @@ public void create(ActionListener actionListener) throws IO if (isCreated()) { return; } - CreateIndexRequest request = new CreateIndexRequest(MODEL_INDEX_NAME).mapping(getMapping()) - .settings( - Settings.builder() - .put("index.hidden", true) - .put("index.number_of_shards", this.numberOfShards) - .put("index.number_of_replicas", this.numberOfReplicas) - ); - client.admin().indices().create(request, actionListener); + runWithStashedThreadContext(() -> { + CreateIndexRequest request; + try { + request = new CreateIndexRequest(MODEL_INDEX_NAME).mapping(getMapping()) + .settings( + Settings.builder() + .put("index.hidden", true) + .put("index.number_of_shards", this.numberOfShards) + .put("index.number_of_replicas", this.numberOfReplicas) + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + client.admin().indices().create(request, actionListener); + }); } @Override @@ -260,6 +293,21 @@ private void putInternal(Model model, ActionListener listener, Do put(KNNConstants.MODEL_TIMESTAMP, modelMetadata.getTimestamp()); put(KNNConstants.MODEL_DESCRIPTION, modelMetadata.getDescription()); put(KNNConstants.MODEL_ERROR, modelMetadata.getError()); + put(KNNConstants.MODEL_NODE_ASSIGNMENT, modelMetadata.getNodeAssignment()); + put(KNNConstants.VECTOR_DATA_TYPE_FIELD, modelMetadata.getVectorDataType()); + if (Mode.isConfigured(modelMetadata.getMode())) { + put(KNNConstants.MODE_PARAMETER, modelMetadata.getMode().getName()); + } + if (CompressionLevel.isConfigured(modelMetadata.getCompressionLevel())) { + put(KNNConstants.COMPRESSION_LEVEL_PARAMETER, modelMetadata.getCompressionLevel().getName()); + } + put(KNNConstants.MODEL_VERSION, modelMetadata.getModelVersion()); + MethodComponentContext methodComponentContext = modelMetadata.getMethodComponentContext(); + if (!methodComponentContext.getName().isEmpty()) { + XContentBuilder builder = XContentFactory.jsonBuilder().startObject(); + builder = methodComponentContext.toXContent(builder, ToXContent.EMPTY_PARAMS).endObject(); + put(KNNConstants.MODEL_METHOD_COMPONENT_CONTEXT, builder.toString()); + } } }; @@ -275,8 +323,7 @@ private void putInternal(Model model, ActionListener listener, Do parameters.put(KNNConstants.MODEL_BLOB_PARAMETER, base64Model); } - IndexRequestBuilder indexRequestBuilder = client.prepareIndex(MODEL_INDEX_NAME); - + final IndexRequestBuilder indexRequestBuilder = client.prepareIndex(MODEL_INDEX_NAME); indexRequestBuilder.setId(model.getModelID()); indexRequestBuilder.setSource(parameters); @@ -286,8 +333,8 @@ private void putInternal(Model model, ActionListener listener, Do // After metadata update finishes, remove item from every node's cache if necessary. If no model id is // passed then nothing needs to be removed from the cache ActionListener onMetaListener; - onMetaListener = ActionListener.wrap( - indexResponse -> client.execute( + onMetaListener = ActionListener.wrap(indexResponse -> { + client.execute( RemoveModelFromCacheAction.INSTANCE, new RemoveModelFromCacheRequest(model.getModelID()), ActionListener.wrap(removeModelFromCacheResponse -> { @@ -300,27 +347,24 @@ private void putInternal(Model model, ActionListener listener, Do listener.onFailure(new RuntimeException(failureMessage)); }, listener::onFailure) - ), - listener::onFailure - ); + ); + }, listener::onFailure); - // After the model is indexed, update metadata only if the model is in CREATED state - ActionListener onIndexListener; - if (ModelState.CREATED.equals(model.getModelMetadata().getState())) { - onIndexListener = getUpdateModelMetadataListener(model.getModelMetadata(), onMetaListener); - } else { - onIndexListener = onMetaListener; - } + ActionListener onIndexListener = getUpdateModelMetadataListener(model.getModelMetadata(), onMetaListener); // Create the model index if it does not already exist + Runnable indexModelRunnable = () -> indexRequestBuilder.execute(onIndexListener); if (!isCreated()) { create( - ActionListener.wrap(createIndexResponse -> indexRequestBuilder.execute(onIndexListener), onIndexListener::onFailure) + ActionListener.wrap( + createIndexResponse -> ModelDao.runWithStashedThreadContext(indexModelRunnable), + onIndexListener::onFailure + ) ); return; } - indexRequestBuilder.execute(onIndexListener); + ModelDao.runWithStashedThreadContext(indexModelRunnable); } private ActionListener getUpdateModelMetadataListener( @@ -339,16 +383,30 @@ private ActionListener getUpdateModelMetadataListener( ); } + @SneakyThrows @Override - public Model get(String modelId) throws ExecutionException, InterruptedException { + public Model get(String modelId) { /* GET //?_local */ - GetRequestBuilder getRequestBuilder = new GetRequestBuilder(client, GetAction.INSTANCE, MODEL_INDEX_NAME).setId(modelId) - .setPreference("_local"); - GetResponse getResponse = getRequestBuilder.execute().get(); - Map responseMap = getResponse.getSourceAsMap(); - return Model.getModelFromSourceMap(responseMap); + try { + return ModelDao.runWithStashedThreadContext(() -> { + GetRequestBuilder getRequestBuilder = new GetRequestBuilder(client, GetAction.INSTANCE, MODEL_INDEX_NAME).setId(modelId) + .setPreference("_local"); + GetResponse getResponse; + try { + getResponse = getRequestBuilder.execute().get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + Map responseMap = getResponse.getSourceAsMap(); + return Model.getModelFromSourceMap(responseMap); + }); + } catch (RuntimeException runtimeException) { + // we need to use RuntimeException as container for real exception to keep signature + // of runWithStashedThreadContext generic + throw runtimeException.getCause(); + } } /** @@ -356,27 +414,28 @@ public Model get(String modelId) throws ExecutionException, InterruptedException * * @param modelId to retrieve * @param actionListener handles get model response - * @throws IOException thrown on search */ @Override - public void get(String modelId, ActionListener actionListener) throws IOException { + public void get(String modelId, ActionListener actionListener) { /* GET //?_local */ - GetRequestBuilder getRequestBuilder = new GetRequestBuilder(client, GetAction.INSTANCE, MODEL_INDEX_NAME).setId(modelId) - .setPreference("_local"); - - getRequestBuilder.execute(ActionListener.wrap(response -> { - if (response.isSourceEmpty()) { - String errorMessage = String.format("Model \" %s \" does not exist", modelId); - actionListener.onFailure(new ResourceNotFoundException(modelId, errorMessage)); - return; - } - final Map responseMap = response.getSourceAsMap(); - Model model = Model.getModelFromSourceMap(responseMap); - actionListener.onResponse(new GetModelResponse(model)); - - }, actionListener::onFailure)); + ModelDao.runWithStashedThreadContext(() -> { + GetRequestBuilder getRequestBuilder = new GetRequestBuilder(client, GetAction.INSTANCE, MODEL_INDEX_NAME).setId(modelId) + .setPreference("_local"); + + getRequestBuilder.execute(ActionListener.wrap(response -> { + if (response.isSourceEmpty()) { + String errorMessage = String.format("Model \" %s \" does not exist", modelId); + actionListener.onFailure(new ResourceNotFoundException(modelId, errorMessage)); + return; + } + final Map responseMap = response.getSourceAsMap(); + Model model = Model.getModelFromSourceMap(responseMap); + actionListener.onResponse(new GetModelResponse(model)); + + }, actionListener::onFailure)); + }); } /** @@ -387,8 +446,10 @@ public void get(String modelId, ActionListener actionListener) */ @Override public void search(SearchRequest request, ActionListener actionListener) { - request.indices(MODEL_INDEX_NAME); - client.search(request, actionListener); + ModelDao.runWithStashedThreadContext(() -> { + request.indices(MODEL_INDEX_NAME); + client.search(request, actionListener); + }); } @Override @@ -419,6 +480,9 @@ public ModelMetadata getMetadata(String modelId) { } private String getMapping() throws IOException { + if (ModelDao.class.getClassLoader() == null) { + throw new IllegalStateException("ClassLoader of ModelDao Class is null"); + } URL url = ModelDao.class.getClassLoader().getResource(MODEL_INDEX_MAPPING_PATH); if (url == null) { throw new IllegalStateException("Unable to retrieve mapping for \"" + MODEL_INDEX_NAME + "\""); @@ -427,61 +491,197 @@ private String getMapping() throws IOException { return Resources.toString(url, Charsets.UTF_8); } + @Override + public boolean isModelInGraveyard(String modelId) { + // Check if the objects are not null and throw a customized NullPointerException + Objects.requireNonNull(clusterService.state(), "Cluster state must not be null"); + Objects.requireNonNull(clusterService.state().metadata(), "Cluster metadata must not be null"); + ModelGraveyard modelGraveyard = clusterService.state().metadata().custom(ModelGraveyard.TYPE); + + if (isNull(modelGraveyard)) { + return false; + } + + return modelGraveyard.contains(modelId); + } + @Override public void delete(String modelId, ActionListener listener) { // If the index is not created, there is no need to delete the model if (!isCreated()) { - logger.error("Cannot delete model \"" + modelId + "\". Model index " + MODEL_INDEX_NAME + "does not exist."); - String errorMessage = String.format("Cannot delete model \"%s\". Model index does not exist", modelId); - listener.onResponse(new DeleteModelResponse(modelId, "failed", errorMessage)); + String errorMessage = String.format("Cannot delete model [%s]. Model index [%s] does not exist", modelId, MODEL_INDEX_NAME); + listener.onFailure(new ResourceNotFoundException(errorMessage)); return; } - // Setup delete model request - DeleteRequestBuilder deleteRequestBuilder = new DeleteRequestBuilder(client, DeleteAction.INSTANCE, MODEL_INDEX_NAME); - deleteRequestBuilder.setId(modelId); - deleteRequestBuilder.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + StepListener getModelStep = new StepListener<>(); + StepListener blockModelIdStep = new StepListener<>(); + StepListener clearModelMetadataStep = new StepListener<>(); + StepListener deleteModelFromIndexStep = new StepListener<>(); + StepListener clearModelFromCacheStep = new StepListener<>(); + StepListener unblockModelIdStep = new StepListener<>(); + + // Get Model to check if model is in TRAINING + get(modelId, ActionListener.wrap(getModelStep::onResponse, exception -> { + if (exception instanceof ResourceNotFoundException) { + String errorMessage = String.format("Unable to delete model [%s]. Model does not exist", modelId); + ResourceNotFoundException resourceNotFoundException = new ResourceNotFoundException(errorMessage); + removeModelIdFromGraveyardOnFailure(modelId, resourceNotFoundException, getModelStep); + } else { + removeModelIdFromGraveyardOnFailure(modelId, exception, getModelStep); + } + })); - // On model deletion from the index, remove the model from all nodes' model cache - ActionListener onModelDeleteListener = ActionListener.wrap(deleteResponse -> { - // If model is not deleted, return with error message + getModelStep.whenComplete(getModelResponse -> { + // If model is in Training state, fail delete model request + if (ModelState.TRAINING == getModelResponse.getModel().getModelMetadata().getState()) { + String errorMessage = String.format("Cannot delete model [%s]. Model is still in training", modelId); + listener.onFailure(new DeleteModelException(errorMessage)); + return; + } + + // Add modelId to model graveyard until delete model request is processed + updateModelGraveyardToDelete(modelId, false, blockModelIdStep, Optional.empty()); + }, listener::onFailure); + + // Remove the metadata asynchronously + blockModelIdStep.whenComplete( + acknowledgedResponse -> { clearModelMetadata(modelId, clearModelMetadataStep); }, + listener::onFailure + ); + + // Setup delete model request + clearModelMetadataStep.whenComplete(acknowledgedResponse -> { + DeleteRequestBuilder deleteRequestBuilder = new DeleteRequestBuilder(client, DeleteAction.INSTANCE, MODEL_INDEX_NAME); + deleteRequestBuilder.setId(modelId); + deleteRequestBuilder.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + deleteModelFromIndex(modelId, deleteModelFromIndexStep, deleteRequestBuilder); + }, listener::onFailure); + + deleteModelFromIndexStep.whenComplete(deleteResponse -> { + // If model is not deleted, remove modelId from model graveyard and return with error message if (deleteResponse.getResult() != DocWriteResponse.Result.DELETED) { - String errorMessage = String.format("Model \" %s \" does not exist", modelId); - listener.onResponse(new DeleteModelResponse(modelId, deleteResponse.getResult().getLowercase(), errorMessage)); + updateModelGraveyardToDelete(modelId, true, unblockModelIdStep, Optional.empty()); + String errorMessage = String.format("Model [%s] does not exist", modelId); + listener.onFailure(new ResourceNotFoundException(errorMessage)); return; } - // After model is deleted from the index, make sure the model is evicted from every cache in the - // cluster - client.execute( - RemoveModelFromCacheAction.INSTANCE, - new RemoveModelFromCacheRequest(modelId), - ActionListener.wrap(removeModelFromCacheResponse -> { + // After model is deleted from the index, make sure the model is evicted from every cache in the cluster + removeModelFromCache(modelId, clearModelFromCacheStep); + }, e -> listener.onFailure(new OpenSearchException(e))); - if (!removeModelFromCacheResponse.hasFailures()) { - listener.onResponse(new DeleteModelResponse(modelId, deleteResponse.getResult().getLowercase(), null)); - return; - } + clearModelFromCacheStep.whenComplete(removeModelFromCacheResponse -> { - String failureMessage = buildRemoveModelErrorMessage(modelId, removeModelFromCacheResponse); + // If there are any failures while removing model from the cache build the error message + OpenSearchException exception = null; + if (removeModelFromCacheResponse.hasFailures()) { + String failureMessage = buildRemoveModelErrorMessage(modelId, removeModelFromCacheResponse); + exception = new OpenSearchException(failureMessage); + } - listener.onResponse(new DeleteModelResponse(modelId, "failed", failureMessage)); + // Remove modelId from model graveyard + updateModelGraveyardToDelete(modelId, true, unblockModelIdStep, Optional.ofNullable(exception)); - }, e -> listener.onResponse(new DeleteModelResponse(modelId, "failed", e.getMessage()))) - ); - }, e -> listener.onResponse(new DeleteModelResponse(modelId, "failed", e.getMessage()))); + }, e -> listener.onFailure(new OpenSearchException(e))); - // On model metadata removal, delete the model from the index - ActionListener onMetadataUpdateListener = ActionListener.wrap( - acknowledgedResponse -> deleteRequestBuilder.execute(onModelDeleteListener), - listener::onFailure + unblockModelIdStep.whenComplete(acknowledgedResponse -> { + // After clearing the cache, if there are no errors return the response + listener.onResponse(new DeleteModelResponse(modelId)); + + }, listener::onFailure); + + } + + // Remove model from cache in the cluster + private void removeModelFromCache(String modelId, StepListener clearModelFromCacheStep) { + client.execute( + RemoveModelFromCacheAction.INSTANCE, + new RemoveModelFromCacheRequest(modelId), + ActionListener.wrap( + clearModelFromCacheStep::onResponse, + exception -> removeModelIdFromGraveyardOnFailure(modelId, exception, clearModelFromCacheStep) + ) ); + } - // Remove the metadata asynchronously + // Delete model from the system index + private void deleteModelFromIndex( + String modelId, + StepListener deleteModelFromIndexStep, + DeleteRequestBuilder deleteRequestBuilder + ) { + ModelDao.runWithStashedThreadContext( + () -> deleteRequestBuilder.execute( + ActionListener.wrap( + deleteModelFromIndexStep::onResponse, + exception -> removeModelIdFromGraveyardOnFailure(modelId, exception, deleteModelFromIndexStep) + ) + ) + ); + } + + // Update model graveyard to add/remove modelId + private void updateModelGraveyardToDelete( + String modelId, + boolean isRemoveRequest, + StepListener step, + Optional exception + ) { + + client.execute( + UpdateModelGraveyardAction.INSTANCE, + new UpdateModelGraveyardRequest(modelId, isRemoveRequest), + ActionListener.wrap(acknowledgedResponse -> { + if (exception.isEmpty()) { + step.onResponse(acknowledgedResponse); + return; + } + throw exception.get(); + + }, e -> { + // If it fails to remove the modelId from Model Graveyard, then log the error message + String errorMessage = String.format("Failed to remove \" %s \" from Model Graveyard", modelId); + String failureMessage = String.format("%s%s%s", errorMessage, "\n", e.getMessage()); + logger.error(failureMessage); + + if (exception.isEmpty()) { + step.onFailure(e); + return; + } + step.onFailure(exception.get()); + }) + ); + } + + // Clear the metadata of the model for a given modelId + private void clearModelMetadata(String modelId, StepListener clearModelMetadataStep) { client.execute( UpdateModelMetadataAction.INSTANCE, new UpdateModelMetadataRequest(modelId, true, null), - onMetadataUpdateListener + ActionListener.wrap( + clearModelMetadataStep::onResponse, + exception -> removeModelIdFromGraveyardOnFailure(modelId, exception, clearModelMetadataStep) + ) + ); + } + + // This function helps to remove the model from model graveyard and return the exception from previous step + // when the delete request fails while executing after adding modelId to model graveyard + private void removeModelIdFromGraveyardOnFailure(String modelId, Exception exceptionFromPreviousStep, StepListener step) { + client.execute( + UpdateModelGraveyardAction.INSTANCE, + new UpdateModelGraveyardRequest(modelId, true), + ActionListener.wrap(acknowledgedResponse -> { + throw exceptionFromPreviousStep; + }, unblockingFailedException -> { + // If it fails to remove the modelId from Model Graveyard, then log the error message and + // throw the exception that was passed as a parameter from previous step + String errorMessage = String.format("Failed to remove \" %s \" from Model Graveyard", modelId); + String failureMessage = String.format("%s%s%s", errorMessage, "\n", unblockingFailedException.getMessage()); + logger.error(failureMessage); + step.onFailure(exceptionFromPreviousStep); + }) ); } @@ -500,4 +700,26 @@ private String buildRemoveModelErrorMessage(String modelId, RemoveModelFromCache return stringBuilder.toString(); } } + + /** + * Set the thread context to default, this is needed to allow actions on model system index + * when security plugin is enabled + * @param function runnable that needs to be executed after thread context has been stashed, accepts and returns nothing + */ + private static void runWithStashedThreadContext(Runnable function) { + try (ThreadContext.StoredContext context = OpenSearchKNNModelDao.client.threadPool().getThreadContext().stashContext()) { + function.run(); + } + } + + /** + * Set the thread context to default, this is needed to allow actions on model system index + * when security plugin is enabled + * @param function supplier function that needs to be executed after thread context has been stashed, return object + */ + private static T runWithStashedThreadContext(Supplier function) { + try (ThreadContext.StoredContext context = OpenSearchKNNModelDao.client.threadPool().getThreadContext().stashContext()) { + return function.get(); + } + } } diff --git a/src/main/java/org/opensearch/knn/indices/ModelGraveyard.java b/src/main/java/org/opensearch/knn/indices/ModelGraveyard.java new file mode 100644 index 000000000..ead95cf9a --- /dev/null +++ b/src/main/java/org/opensearch/knn/indices/ModelGraveyard.java @@ -0,0 +1,321 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.indices; + +import lombok.AllArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.opensearch.OpenSearchParseException; +import org.opensearch.Version; +import org.opensearch.cluster.Diff; +import org.opensearch.cluster.NamedDiff; +import org.opensearch.cluster.metadata.Metadata; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; + +import java.io.IOException; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Set; +import java.util.Iterator; + +import com.google.common.collect.Sets; + +/** + * This class implements Metadata.Custom Interface to store a set of modelIds in the cluster metadata. + * The modelIds of the models that are under deletion are added to this set and later removed from this set after deletion. + * Also, this class implements the methods to perform operations on this set (like add, remove, contains) + */ + +@AllArgsConstructor +@Log4j2 +public class ModelGraveyard implements Metadata.Custom { + public static final String TYPE = "opensearch-knn-blocked-models"; + private static final String MODEL_IDS = "model_ids"; + private final Set modelIds; + + /** + * Default Constructor to initialize object when it is null + */ + public ModelGraveyard() { + this.modelIds = new HashSet<>(); + } + + /** + * @param in input stream + * @throws IOException if read from stream fails + */ + public ModelGraveyard(StreamInput in) throws IOException { + this.modelIds = new HashSet<>(in.readStringList()); + } + + @Override + public EnumSet context() { + return Metadata.ALL_CONTEXTS; + } + + /** + * @return WriteableName for ModelGraveyard + */ + @Override + public String getWriteableName() { + return TYPE; + } + + @Override + public Version getMinimalSupportedVersion() { + return Version.CURRENT.minimumCompatibilityVersion(); + } + + /** + * @param out output stream + * @throws IOException if write to stream fails + */ + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeStringCollection(modelIds); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + Iterator model_ids = getModelIds().iterator(); + + builder.startArray(MODEL_IDS); + while (model_ids.hasNext()) { + builder.value(model_ids.next()); + } + builder.endArray(); + return builder; + } + + /** + * @param modelId id of the model that needs to be removed from modelIds set + */ + public void remove(String modelId) { + modelIds.remove(modelId); + } + + /** + * @param modelId id of the model that needs to be added to modelIds set + */ + public void add(String modelId) { + modelIds.add(modelId); + } + + /** + * @return Set of modelIds in modelGraveyard + */ + public Set getModelIds() { + return modelIds; + } + + /** + * @return number of modelIds in modelGraveyard + */ + public int size() { + return modelIds.size(); + } + + /** + * @param modelId to check if the id of given model is there in modelIds set + * @return true if the modelId is in the modelIds set, otherwise false + */ + public boolean contains(String modelId) { + return modelIds.contains(modelId); + } + + /** + * @param before The previous custom metadata object + * @return the diff between the current updated object and the previous object + */ + @Override + public Diff diff(Metadata.Custom before) { + return new ModelGraveyardDiff((ModelGraveyard) before, this); + } + + /** + * @param streamInput input stream + * @return ModelGraveyardDiff + * @throws IOException if read from stream fails + */ + public static NamedDiff readDiffFrom(StreamInput streamInput) throws IOException { + return new ModelGraveyardDiff(streamInput); + } + + /** + * @param xContentParser + * @return ModelGraveyard + * @throws IOException + */ + public static ModelGraveyard fromXContent(XContentParser xContentParser) throws IOException { + // Added validation checks to validate all the different possible scenarios + // model_ids:"abcd" - Throws exception as the START_OBJECT token is missing + // {} - Returns an empty ModelGraveyard object (BackwardCompatibility) + // {["abcd", "1234"]} - Throws exception as the FIELD_NAME token is missing + // {"dummy_field_name":} - Throws exception as the FIELD_NAME is not matching with model_ids + // {model_ids:"abcd"} - Throws exception as the START_ARRAY token is missing after field name + // {model_ids:null} - Throws exception as the START_ARRAY token is missing + // {model_ids:[]} - Parses and returns an empty ModelGraveyard object as there are no model ids + // {model_ids: ["abcd", "1234"]} - Parses and returns a ModelGraveyard object which contains the model ids "abcd" and "1234" + // {model_ids:[],dummy_field:[]} - Throws exception as we have FIELD_NAME(dummy_field) instead of END_OBJECT token + + ModelGraveyard modelGraveyard = new ModelGraveyard(); + + // If it is a fresh parser, move to the first token + if (xContentParser.currentToken() == null) { + xContentParser.nextToken(); + } + + // Validate if the first token is START_OBJECT + if (xContentParser.currentToken() != XContentParser.Token.START_OBJECT) { + throw new OpenSearchParseException( + "Unable to parse ModelGraveyard. Expecting token start of an object but got {}", + xContentParser.currentToken() + ); + } + + // Adding Backward Compatibility for the domains that have already parsed the old toXContent logic which has XContent as {} + if (xContentParser.nextToken() == XContentParser.Token.END_OBJECT) { + return modelGraveyard; + } + + // Validate it starts with FIELD_NAME token after START_OBJECT + if (xContentParser.currentToken() != XContentParser.Token.FIELD_NAME) { + throw new OpenSearchParseException( + "Unable to parse ModelGraveyard. Expecting token field name but got {}", + xContentParser.currentToken() + ); + } + + // Validating that FIELD_NAME matches with "model_ids" + if (!MODEL_IDS.equals(xContentParser.currentName())) { + throw new OpenSearchParseException( + "Unable to parse ModelGraveyard. Expecting field {} but got {}", + MODEL_IDS, + xContentParser.currentName() + ); + } + + // Validate it starts with START_ARRAY token after FIELD_NAME + if (xContentParser.nextToken() != XContentParser.Token.START_ARRAY) { + throw new OpenSearchParseException( + "Unable to parse ModelGraveyard. Expecting token start of an array but got {}", + xContentParser.currentToken() + ); + } + + while (xContentParser.nextToken() != XContentParser.Token.END_ARRAY) { + if (xContentParser.currentToken() != XContentParser.Token.VALUE_STRING) { + throw new OpenSearchParseException( + "Unable to parse ModelGraveyard. Expecting token value string but got {}", + xContentParser.currentToken() + ); + } + modelGraveyard.add(xContentParser.text()); + } + + // Validate if the last token is END_OBJECT + if (xContentParser.nextToken() != XContentParser.Token.END_OBJECT) { + throw new OpenSearchParseException( + "Unable to parse ModelGraveyard. Expecting token end of an object but got {}", + xContentParser.currentToken() + ); + } + return modelGraveyard; + } + + /** + * The ModelGraveyardDiff class compares the previous modelGraveyard object with the current updated modelGraveyard object + * and returns only the diff of those 2 objects. So that, whenever there is a change in cluster state, clusterManager node only + * sends the diff to all the data nodes instead of the full cluster state + */ + public static class ModelGraveyardDiff implements NamedDiff { + private final Set added; + private final Set removed; + + /** + * @param inp input stream + * @throws IOException if read from stream fails + */ + public ModelGraveyardDiff(StreamInput inp) throws IOException { + added = Set.copyOf(inp.readStringList()); + removed = Set.copyOf(inp.readStringList()); + } + + /** + * @param previous previous ModelGraveyard object + * @param current current updated ModelGraveyard object + * + * Constructor which compares both the objects to find the entries that are newly added in current object, + * entries that are deleted from previous object and the deleted entries count + */ + public ModelGraveyardDiff(ModelGraveyard previous, ModelGraveyard current) { + final Set previousModelIdsSet = previous.modelIds; + final Set currentModelIdsSet = current.modelIds; + final Set added, removed; + if (previousModelIdsSet.isEmpty()) { + // nothing will have been removed in previous object, and all entries in current object are new + added = new HashSet<>(currentModelIdsSet); + removed = new HashSet<>(); + } else if (currentModelIdsSet.isEmpty()) { + // nothing will have been added to current object, and all entries in previous object are removed + added = new HashSet<>(); + removed = new HashSet<>(previousModelIdsSet); + } else { + // some entries in previous object are removed and few entries are added to current object + removed = Sets.difference(previousModelIdsSet, currentModelIdsSet); + added = Sets.difference(currentModelIdsSet, previousModelIdsSet); + } + this.added = Collections.unmodifiableSet(added); + this.removed = Collections.unmodifiableSet(removed); + } + + /** + * @param previous Previous custom metadata object + * @return ModelGraveyard object after calculating the diff + */ + @Override + public ModelGraveyard apply(Metadata.Custom previous) { + final ModelGraveyard old = (ModelGraveyard) previous; + int removedCount = removed.size(); + if (removedCount > old.size()) { + throw new IllegalStateException( + "ModelGraveyardDiff cannot remove [" + removedCount + "] entries from [" + old.size() + "] modelIds." + ); + } + Set updatedOldGraveyardSet = Sets.difference(old.modelIds, removed); + Set modelGraveyardDiffSet = new HashSet<>(); + modelGraveyardDiffSet.addAll(added); + modelGraveyardDiffSet.addAll(updatedOldGraveyardSet); + return new ModelGraveyard(modelGraveyardDiffSet); + } + + public Set getAdded() { + return added; + } + + public Set getRemoved() { + return removed; + } + + @Override + public String getWriteableName() { + return TYPE; + } + + /** + * @param out output stream + * @throws IOException if write to stream fails + */ + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeStringCollection(added); + out.writeStringCollection(removed); + } + } +} diff --git a/src/main/java/org/opensearch/knn/indices/ModelMetadata.java b/src/main/java/org/opensearch/knn/indices/ModelMetadata.java index 9aa0d133b..17eed833e 100644 --- a/src/main/java/org/opensearch/knn/indices/ModelMetadata.java +++ b/src/main/java/org/opensearch/knn/indices/ModelMetadata.java @@ -11,34 +11,39 @@ package org.opensearch.knn.indices; +import lombok.Getter; +import lombok.extern.log4j.Log4j2; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; -import org.opensearch.common.io.stream.Writeable; -import org.opensearch.common.xcontent.ToXContentObject; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.Version; +import org.opensearch.common.xcontent.json.JsonXContent; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.common.io.stream.Writeable; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; +import org.opensearch.knn.index.util.IndexUtil; +import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; import java.io.IOException; import java.util.Map; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; -import static org.opensearch.knn.common.KNNConstants.DIMENSION; -import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; -import static org.opensearch.knn.common.KNNConstants.MODEL_DESCRIPTION; -import static org.opensearch.knn.common.KNNConstants.MODEL_ERROR; -import static org.opensearch.knn.common.KNNConstants.MODEL_STATE; -import static org.opensearch.knn.common.KNNConstants.MODEL_TIMESTAMP; -import static org.opensearch.knn.index.KNNVectorFieldMapper.MAX_DIMENSION; +import static org.opensearch.core.xcontent.DeprecationHandler.IGNORE_DEPRECATIONS; +@Log4j2 public class ModelMetadata implements Writeable, ToXContentObject { - private static final String DELIMITER = ","; + public static final String DELIMITER = ","; final private KNNEngine knnEngine; final private SpaceType spaceType; @@ -47,7 +52,15 @@ public class ModelMetadata implements Writeable, ToXContentObject { private AtomicReference state; final private String timestamp; final private String description; + final private String trainingNodeAssignment; + final private VectorDataType vectorDataType; + private MethodComponentContext methodComponentContext; + @Getter + private final Mode mode; private String error; + @Getter + private final CompressionLevel compressionLevel; + private final Version version; /** * Constructor @@ -64,7 +77,39 @@ public ModelMetadata(StreamInput in) throws IOException { // Description and error may be empty. However, reading the string will work as long as they are not null // which is checked in constructor and setters this.description = in.readString(); + ModelUtil.blockCommasInModelDescription(this.description); this.error = in.readString(); + + if (IndexUtil.isVersionOnOrAfterMinRequiredVersion(in.getVersion(), IndexUtil.MODEL_NODE_ASSIGNMENT_KEY)) { + this.trainingNodeAssignment = in.readString(); + } else { + this.trainingNodeAssignment = ""; + } + + if (IndexUtil.isVersionOnOrAfterMinRequiredVersion(in.getVersion(), IndexUtil.MODEL_METHOD_COMPONENT_CONTEXT_KEY)) { + this.methodComponentContext = new MethodComponentContext(in); + } else { + this.methodComponentContext = MethodComponentContext.EMPTY; + } + + if (IndexUtil.isVersionOnOrAfterMinRequiredVersion(in.getVersion(), KNNConstants.MODEL_VECTOR_DATA_TYPE_KEY)) { + this.vectorDataType = VectorDataType.get(in.readString()); + } else { + this.vectorDataType = VectorDataType.DEFAULT; + } + if (IndexUtil.isVersionOnOrAfterMinRequiredVersion(in.getVersion(), KNNConstants.MINIMAL_MODE_AND_COMPRESSION_FEATURE)) { + this.mode = Mode.fromName(in.readOptionalString()); + this.compressionLevel = CompressionLevel.fromName(in.readOptionalString()); + } else { + this.mode = Mode.NOT_CONFIGURED; + this.compressionLevel = CompressionLevel.NOT_CONFIGURED; + } + + if (IndexUtil.isVersionOnOrAfterMinRequiredVersion(in.getVersion(), KNNConstants.MODEL_VERSION)) { + this.version = Version.fromString(in.readString()); + } else { + this.version = Version.V_EMPTY; + } } /** @@ -77,6 +122,9 @@ public ModelMetadata(StreamInput in) throws IOException { * @param timestamp timevalue when model was created * @param description information about the model * @param error error message associated with model + * @param trainingNodeAssignment node assignment for the model + * @param methodComponentContext method component context associated with model + * @param vectorDataType vector data type of the model */ public ModelMetadata( KNNEngine knnEngine, @@ -85,13 +133,24 @@ public ModelMetadata( ModelState modelState, String timestamp, String description, - String error + String error, + String trainingNodeAssignment, + MethodComponentContext methodComponentContext, + VectorDataType vectorDataType, + Mode mode, + CompressionLevel compressionLevel, + Version version ) { this.knnEngine = Objects.requireNonNull(knnEngine, "knnEngine must not be null"); this.spaceType = Objects.requireNonNull(spaceType, "spaceType must not be null"); - if (dimension <= 0 || dimension >= MAX_DIMENSION) { + int maxDimensions = KNNEngine.getMaxDimensionByEngine(this.knnEngine); + if (dimension <= 0 || dimension > maxDimensions) { throw new IllegalArgumentException( - "Dimension \"" + dimension + "\" is invalid. Value must be greater " + "than 0 and less than " + MAX_DIMENSION + String.format( + "Dimension \"%s\" is invalid. Value must be greater than 0 and less than or equal to %d", + dimension, + maxDimensions + ) ); } this.dimension = dimension; @@ -99,7 +158,14 @@ public ModelMetadata( this.state = new AtomicReference<>(Objects.requireNonNull(modelState, "modelState must not be null")); this.timestamp = Objects.requireNonNull(timestamp, "timestamp must not be null"); this.description = Objects.requireNonNull(description, "description must not be null"); + ModelUtil.blockCommasInModelDescription(this.description); this.error = Objects.requireNonNull(error, "error must not be null"); + this.trainingNodeAssignment = Objects.requireNonNull(trainingNodeAssignment, "node assignment must not be null"); + this.methodComponentContext = Objects.requireNonNull(methodComponentContext, "method context must not be null"); + this.vectorDataType = Objects.requireNonNull(vectorDataType, "vector data type must not be null"); + this.mode = Objects.requireNonNull(mode, "Mode must not be null"); + this.compressionLevel = Objects.requireNonNull(compressionLevel, "Compression level must not be null"); + this.version = Objects.requireNonNull(version, "model version must not be null"); } /** @@ -165,6 +231,36 @@ public String getError() { return error; } + /** + * getter for model's node assignment + * + * @return trainingNodeAssignment + */ + public String getNodeAssignment() { + return trainingNodeAssignment; + } + + /** + * getter for model's method context + * + * @return knnMethodContext + */ + public MethodComponentContext getMethodComponentContext() { + return methodComponentContext; + } + + public VectorDataType getVectorDataType() { + return vectorDataType; + } + + /** + * Getter for the model version + * @return version + */ + public Version getModelVersion() { + return version; + } + /** * setter for model's state * @@ -193,7 +289,13 @@ public String toString() { getState().toString(), timestamp, description, - error + error, + trainingNodeAssignment, + methodComponentContext.toClusterStateString(), + vectorDataType.getValue(), + mode.getName(), + compressionLevel.getName(), + version.toString() ); } @@ -211,6 +313,9 @@ public boolean equals(Object obj) { equalsBuilder.append(getTimestamp(), other.getTimestamp()); equalsBuilder.append(getDescription(), other.getDescription()); equalsBuilder.append(getError(), other.getError()); + equalsBuilder.append(getVectorDataType(), other.getVectorDataType()); + equalsBuilder.append(getMode(), other.getMode()); + equalsBuilder.append(getCompressionLevel(), other.getCompressionLevel()); return equalsBuilder.isEquals(); } @@ -224,6 +329,11 @@ public int hashCode() { .append(getTimestamp()) .append(getDescription()) .append(getError()) + .append(getMethodComponentContext()) + .append(getVectorDataType()) + .append(getMode()) + .append(getCompressionLevel()) + .append(getModelVersion()) .toHashCode(); } @@ -235,11 +345,16 @@ public int hashCode() { */ public static ModelMetadata fromString(String modelMetadataString) { String[] modelMetadataArray = modelMetadataString.split(DELIMITER, -1); - - if (modelMetadataArray.length != 7) { + int length = modelMetadataArray.length; + if (length < 7 || length > 13) { throw new IllegalArgumentException( "Illegal format for model metadata. Must be of the form " - + "\",,,,,,\"." + + "\",,,,,,\" or " + + "\",,,,,,,\" or " + + "\",,,,,,,,\" or " + + "\",,,,,,,,,\". or " + + "\",,,,,,,,,,,\" or " + + "\",,,,,,,,,,,,\"." ); } @@ -250,8 +365,53 @@ public static ModelMetadata fromString(String modelMetadataString) { String timestamp = modelMetadataArray[4]; String description = modelMetadataArray[5]; String error = modelMetadataArray[6]; + String trainingNodeAssignment = length > 7 ? modelMetadataArray[7] : ""; + MethodComponentContext methodComponentContext = length > 8 + ? MethodComponentContext.fromClusterStateString(modelMetadataArray[8]) + : MethodComponentContext.EMPTY; + VectorDataType vectorDataType = length > 9 ? VectorDataType.get(modelMetadataArray[9]) : VectorDataType.DEFAULT; + Mode mode = length > 10 ? Mode.fromName(modelMetadataArray[10]) : Mode.NOT_CONFIGURED; + CompressionLevel compressionLevel = length > 11 + ? CompressionLevel.fromName(modelMetadataArray[11]) + : CompressionLevel.NOT_CONFIGURED; + Version version = length > 12 ? Version.fromString(modelMetadataArray[12]) : Version.V_EMPTY; + + log.debug(getLogMessage(length)); + + return new ModelMetadata( + knnEngine, + spaceType, + dimension, + modelState, + timestamp, + description, + error, + trainingNodeAssignment, + methodComponentContext, + vectorDataType, + mode, + compressionLevel, + version + ); + } - return new ModelMetadata(knnEngine, spaceType, dimension, modelState, timestamp, description, error); + private static String getLogMessage(int length) { + switch (length) { + case 7: + return "Model metadata array does not contain training node assignment or method component context. Assuming empty string node assignment and empty method component context."; + case 8: + return "Model metadata contains training node assignment. Assuming empty method component context."; + case 9: + return "Model metadata contains training node assignment and method context."; + case 10: + return "Model metadata contains training node assignment, method context and vector data type."; + case 12: + return "Model metadata contains mode and compression level"; + case 13: + return "Model metadata contains training node assignment, method context, vector data type, and version"; + default: + throw new IllegalArgumentException("Unexpected metadata array length: " + length); + } } private static String objectToString(Object value) { @@ -278,6 +438,39 @@ public static ModelMetadata getMetadataFromSourceMap(final Map m Object timestamp = modelSourceMap.get(KNNConstants.MODEL_TIMESTAMP); Object description = modelSourceMap.get(KNNConstants.MODEL_DESCRIPTION); Object error = modelSourceMap.get(KNNConstants.MODEL_ERROR); + Object trainingNodeAssignment = modelSourceMap.get(KNNConstants.MODEL_NODE_ASSIGNMENT); + Object methodComponentContext = modelSourceMap.get(KNNConstants.MODEL_METHOD_COMPONENT_CONTEXT); + Object vectorDataType = modelSourceMap.get(KNNConstants.VECTOR_DATA_TYPE_FIELD); + Object mode = modelSourceMap.get(KNNConstants.MODE_PARAMETER); + Object compressionLevel = modelSourceMap.get(KNNConstants.COMPRESSION_LEVEL_PARAMETER); + Object version = modelSourceMap.get(KNNConstants.MODEL_VERSION); + + if (trainingNodeAssignment == null) { + trainingNodeAssignment = ""; + } + + if (Objects.nonNull(methodComponentContext)) { + try { + XContentParser xContentParser = JsonXContent.jsonXContent.createParser( + NamedXContentRegistry.EMPTY, + IGNORE_DEPRECATIONS, + objectToString(methodComponentContext) + ); + methodComponentContext = MethodComponentContext.fromXContent(xContentParser); + } catch (IOException e) { + throw new IllegalArgumentException("Error parsing method component context"); + } + } else { + methodComponentContext = MethodComponentContext.EMPTY; + } + + if (vectorDataType == null) { + vectorDataType = VectorDataType.DEFAULT.getValue(); + } + + if (version == null) { + version = Version.V_EMPTY; + } ModelMetadata modelMetadata = new ModelMetadata( KNNEngine.getEngine(objectToString(engine)), @@ -286,7 +479,13 @@ public static ModelMetadata getMetadataFromSourceMap(final Map m ModelState.getModelState(objectToString(state)), objectToString(timestamp), objectToString(description), - objectToString(error) + objectToString(error), + objectToString(trainingNodeAssignment), + (MethodComponentContext) methodComponentContext, + VectorDataType.get(objectToString(vectorDataType)), + Mode.fromName(objectToString(mode)), + CompressionLevel.fromName(objectToString(compressionLevel)), + Version.fromString(version.toString()) ); return modelMetadata; } @@ -300,18 +499,60 @@ public void writeTo(StreamOutput out) throws IOException { out.writeString(getTimestamp()); out.writeString(getDescription()); out.writeString(getError()); + if (IndexUtil.isVersionOnOrAfterMinRequiredVersion(out.getVersion(), IndexUtil.MODEL_NODE_ASSIGNMENT_KEY)) { + out.writeString(getNodeAssignment()); + } + if (IndexUtil.isVersionOnOrAfterMinRequiredVersion(out.getVersion(), IndexUtil.MODEL_METHOD_COMPONENT_CONTEXT_KEY)) { + getMethodComponentContext().writeTo(out); + } + if (IndexUtil.isVersionOnOrAfterMinRequiredVersion(out.getVersion(), KNNConstants.MODEL_VECTOR_DATA_TYPE_KEY)) { + out.writeString(vectorDataType.getValue()); + } + if (IndexUtil.isVersionOnOrAfterMinRequiredVersion(out.getVersion(), KNNConstants.MINIMAL_MODE_AND_COMPRESSION_FEATURE)) { + out.writeOptionalString(mode.getName()); + out.writeOptionalString(compressionLevel.getName()); + } + if (IndexUtil.isVersionOnOrAfterMinRequiredVersion(out.getVersion(), KNNConstants.MODEL_VERSION)) { + out.writeString(version.toString()); + } } @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.field(MODEL_STATE, getState().getName()); - builder.field(MODEL_TIMESTAMP, getTimestamp()); - builder.field(MODEL_DESCRIPTION, getDescription()); - builder.field(MODEL_ERROR, getError()); - - builder.field(METHOD_PARAMETER_SPACE_TYPE, getSpaceType().getValue()); - builder.field(DIMENSION, getDimension()); - builder.field(KNN_ENGINE, getKnnEngine().getName()); + builder.field(KNNConstants.MODEL_STATE, getState().getName()); + builder.field(KNNConstants.MODEL_TIMESTAMP, getTimestamp()); + builder.field(KNNConstants.MODEL_DESCRIPTION, getDescription()); + builder.field(KNNConstants.MODEL_ERROR, getError()); + + builder.field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, getSpaceType().getValue()); + builder.field(KNNConstants.DIMENSION, getDimension()); + builder.field(KNNConstants.KNN_ENGINE, getKnnEngine().getName()); + if (IndexUtil.isClusterOnOrAfterMinRequiredVersion(IndexUtil.MODEL_NODE_ASSIGNMENT_KEY)) { + builder.field(KNNConstants.MODEL_NODE_ASSIGNMENT, getNodeAssignment()); + } + if (IndexUtil.isClusterOnOrAfterMinRequiredVersion(IndexUtil.MODEL_METHOD_COMPONENT_CONTEXT_KEY)) { + builder.field(KNNConstants.MODEL_METHOD_COMPONENT_CONTEXT).startObject(); + getMethodComponentContext().toXContent(builder, params); + builder.endObject(); + } + if (IndexUtil.isClusterOnOrAfterMinRequiredVersion(KNNConstants.MODEL_VECTOR_DATA_TYPE_KEY)) { + builder.field(KNNConstants.VECTOR_DATA_TYPE_FIELD, vectorDataType.getValue()); + } + if (IndexUtil.isClusterOnOrAfterMinRequiredVersion(KNNConstants.MINIMAL_MODE_AND_COMPRESSION_FEATURE)) { + if (Mode.isConfigured(mode)) { + builder.field(KNNConstants.MODE_PARAMETER, mode.getName()); + } + if (CompressionLevel.isConfigured(compressionLevel)) { + builder.field(KNNConstants.COMPRESSION_LEVEL_PARAMETER, compressionLevel.getName()); + } + } + if (IndexUtil.isClusterOnOrAfterMinRequiredVersion(KNNConstants.MODEL_VERSION)) { + String versionString = "unknown"; + if (version != Version.V_EMPTY) { + versionString = version.toString(); + } + builder.field(KNNConstants.MODEL_VERSION, versionString); + } return builder; } } diff --git a/src/main/java/org/opensearch/knn/indices/ModelState.java b/src/main/java/org/opensearch/knn/indices/ModelState.java index d78ae06bc..35def5335 100644 --- a/src/main/java/org/opensearch/knn/indices/ModelState.java +++ b/src/main/java/org/opensearch/knn/indices/ModelState.java @@ -11,9 +11,9 @@ package org.opensearch.knn.indices; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; -import org.opensearch.common.io.stream.Writeable; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.common.io.stream.Writeable; import java.io.IOException; diff --git a/src/main/java/org/opensearch/knn/indices/ModelUtil.java b/src/main/java/org/opensearch/knn/indices/ModelUtil.java new file mode 100644 index 000000000..d63f02b2b --- /dev/null +++ b/src/main/java/org/opensearch/knn/indices/ModelUtil.java @@ -0,0 +1,59 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.indices; + +import lombok.experimental.UtilityClass; +import org.apache.commons.lang.StringUtils; + +import java.util.Locale; + +/** + * A utility class for models. + */ +@UtilityClass +public class ModelUtil { + + public static void blockCommasInModelDescription(String description) { + if (description.contains(",")) { + throw new IllegalArgumentException("Model description cannot contain any commas: ','"); + } + } + + public static boolean isModelPresent(ModelMetadata modelMetadata) { + return modelMetadata != null; + } + + public static boolean isModelCreated(ModelMetadata modelMetadata) { + if (!isModelPresent(modelMetadata)) { + return false; + } + return modelMetadata.getState().equals(ModelState.CREATED); + } + + /** + * Gets Model Metadata from a given model id. + * @param modelId {@link String} + * @return {@link ModelMetadata} + */ + public static ModelMetadata getModelMetadata(final String modelId) { + if (StringUtils.isEmpty(modelId)) { + return null; + } + ModelDao modelDao = ModelDao.OpenSearchKNNModelDao.getInstance(); + final ModelMetadata modelMetadata = modelDao.getMetadata(modelId); + if (isModelCreated(modelMetadata) == false) { + throw new IllegalArgumentException(String.format(Locale.ROOT, "Model ID '%s' is not created.", modelId)); + } + return modelMetadata; + } + +} diff --git a/src/main/java/org/opensearch/knn/jni/FaissService.java b/src/main/java/org/opensearch/knn/jni/FaissService.java index 3f90e33d1..dcc7b180d 100644 --- a/src/main/java/org/opensearch/knn/jni/FaissService.java +++ b/src/main/java/org/opensearch/knn/jni/FaissService.java @@ -12,26 +12,43 @@ package org.opensearch.knn.jni; import org.opensearch.knn.common.KNNConstants; -import org.opensearch.knn.index.KNNQueryResult; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.query.KNNQueryResult; +import org.opensearch.knn.index.store.IndexInputWithBuffer; +import org.opensearch.knn.index.store.IndexOutputWithBuffer; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Map; +import static org.opensearch.knn.index.KNNSettings.isFaissAVX2Disabled; +import static org.opensearch.knn.index.KNNSettings.isFaissAVX512Disabled; +import static org.opensearch.knn.jni.PlatformUtils.isAVX2SupportedBySystem; +import static org.opensearch.knn.jni.PlatformUtils.isAVX512SupportedBySystem; + /** * Service to interact with faiss jni layer. Class dependencies should be minimal - * + *

* In order to compile C++ header file, run: * javac -h jni/include src/main/java/org/opensearch/knn/jni/FaissService.java - * src/main/java/org/opensearch/knn/index/KNNQueryResult.java + * src/main/java/org/opensearch/knn/index/query/KNNQueryResult.java * src/main/java/org/opensearch/knn/common/KNNConstants.java */ class FaissService { static { AccessController.doPrivileged((PrivilegedAction) () -> { - System.loadLibrary(KNNConstants.FAISS_JNI_LIBRARY_NAME); + + // Even if the underlying system supports AVX512 and AVX2, users can override and disable it by setting + // 'knn.faiss.avx2.disabled' or 'knn.faiss.avx512.disabled' to true in the opensearch.yml configuration + if (!isFaissAVX512Disabled() && isAVX512SupportedBySystem()) { + System.loadLibrary(KNNConstants.FAISS_AVX512_JNI_LIBRARY_NAME); + } else if (!isFaissAVX2Disabled() && isAVX2SupportedBySystem()) { + System.loadLibrary(KNNConstants.FAISS_AVX2_JNI_LIBRARY_NAME); + } else { + System.loadLibrary(KNNConstants.FAISS_JNI_LIBRARY_NAME); + } + initLibrary(); KNNEngine.FAISS.setInitialized(true); return null; @@ -39,28 +56,157 @@ class FaissService { } /** - * Create an index for the native library + * Initialize an index for the native library. Takes in numDocs to + * allocate the correct amount of memory. * - * @param ids array of ids mapping to the data passed in - * @param data array of float arrays to be indexed - * @param indexPath path to save index file to + * @param numDocs number of documents to be added + * @param dim dimension of the vector to be indexed + * @param parameters parameters to build index + */ + public static native long initIndex(long numDocs, int dim, Map parameters); + + /** + * Initialize an index for the native library. Takes in numDocs to + * allocate the correct amount of memory. + * + * @param numDocs number of documents to be added + * @param dim dimension of the vector to be indexed * @param parameters parameters to build index */ - public static native void createIndex(int[] ids, float[][] data, String indexPath, Map parameters); + public static native long initBinaryIndex(long numDocs, int dim, Map parameters); + + /** + * Initialize a byte index for the native library. Takes in numDocs to + * allocate the correct amount of memory. + * + * @param numDocs number of documents to be added + * @param dim dimension of the vector to be indexed + * @param parameters parameters to build index + */ + public static native long initByteIndex(long numDocs, int dim, Map parameters); + + /** + * Inserts to a faiss index. The memory occupied by the vectorsAddress will be freed up during the + * function call. So Java layer doesn't need to free up the memory. This is not an ideal behavior because Java layer + * created the memory address and that should only free up the memory. + * + * @param ids ids of documents + * @param vectorsAddress address of native memory where vectors are stored + * @param dim dimension of the vector to be indexed + * @param indexAddress address of native memory where index is stored + * @param threadCount number of threads to use for insertion + */ + public static native void insertToIndex(int[] ids, long vectorsAddress, int dim, long indexAddress, int threadCount); + + /** + * Inserts to a faiss index. The memory occupied by the vectorsAddress will be freed up during the + * function call. So Java layer doesn't need to free up the memory. This is not an ideal behavior because Java layer + * created the memory address and that should only free up the memory. + * + * @param ids ids of documents + * @param vectorsAddress address of native memory where vectors are stored + * @param dim dimension of the vector to be indexed + * @param indexAddress address of native memory where index is stored + * @param threadCount number of threads to use for insertion + */ + public static native void insertToBinaryIndex(int[] ids, long vectorsAddress, int dim, long indexAddress, int threadCount); + + /** + * Inserts to a faiss index. The memory occupied by the vectorsAddress will be freed up during the + * function call. So Java layer doesn't need to free up the memory. This is not an ideal behavior because Java layer + * created the memory address and that should only free up the memory. + * + * @param ids ids of documents + * @param vectorsAddress address of native memory where vectors are stored + * @param dim dimension of the vector to be indexed + * @param indexAddress address of native memory where index is stored + * @param threadCount number of threads to use for insertion + */ + public static native void insertToByteIndex(int[] ids, long vectorsAddress, int dim, long indexAddress, int threadCount); + + /** + * Writes a faiss index. + * + * NOTE: This will always free the index. Do not call free after this. + * + * @param indexAddress address of native memory where index is stored + * @param output Index output wrapper having Lucene's IndexOutput to be used to flush bytes in native engines. + */ + public static native void writeIndex(long indexAddress, IndexOutputWithBuffer output); + + /** + * Writes a faiss index. + * + * NOTE: This will always free the index. Do not call free after this. + * + * @param indexAddress address of native memory where index is stored + * @param output Index output wrapper having Lucene's IndexOutput to be used to flush bytes in native engines. + */ + public static native void writeBinaryIndex(long indexAddress, IndexOutputWithBuffer output); + + /** + * Writes a faiss index. + * + * NOTE: This will always free the index. Do not call free after this. + * + * @param indexAddress address of native memory where index is stored + * @param output Index output wrapper having Lucene's IndexOutput to be used to flush bytes in native engines. + */ + public static native void writeByteIndex(long indexAddress, IndexOutputWithBuffer output); /** * Create an index for the native library with a provided template index * * @param ids array of ids mapping to the data passed in - * @param data array of float arrays to be indexed - * @param indexPath path to save index file to + * @param vectorsAddress address of native memory where vectors are stored + * @param dim dimension of the vector to be indexed + * @param output Index output wrapper having Lucene's IndexOutput to be used to flush bytes in native engines. * @param templateIndex empty template index * @param parameters additional build time parameters */ public static native void createIndexFromTemplate( int[] ids, - float[][] data, - String indexPath, + long vectorsAddress, + int dim, + IndexOutputWithBuffer output, + byte[] templateIndex, + Map parameters + ); + + /** + * Create a binary index for the native library with a provided template index + * + * @param ids array of ids mapping to the data passed in + * @param vectorsAddress address of native memory where vectors are stored + * @param dim dimension of the vector to be indexed + * @param output Index output wrapper having Lucene's IndexOutput to be used to flush bytes in native engines. + * @param templateIndex empty template index + * @param parameters additional build time parameters + */ + public static native void createBinaryIndexFromTemplate( + int[] ids, + long vectorsAddress, + int dim, + IndexOutputWithBuffer output, + byte[] templateIndex, + Map parameters + ); + + /** + * Create a byte index for the native library with a provided template index + * + * @param ids array of ids mapping to the data passed in + * @param vectorsAddress address of native memory where vectors are stored + * @param dim dimension of the vector to be indexed + * @param output Index output wrapper having Lucene's IndexOutput to be used to flush bytes in native engines. + * @param templateIndex empty template index + * @param parameters additional build time parameters + */ + public static native void createByteIndexFromTemplate( + int[] ids, + long vectorsAddress, + int dim, + IndexOutputWithBuffer output, byte[] templateIndex, Map parameters ); @@ -74,19 +220,151 @@ public static native void createIndexFromTemplate( public static native long loadIndex(String indexPath); /** - * Query an index + * Load an index into memory via a wrapping having Lucene's IndexInput. + * Instead of directly accessing an index path, this will make Faiss delegate IndexInput to load bytes. + * + * @param readStream IndexInput wrapper having a Lucene's IndexInput reference. + * @return pointer to location in memory the index resides in + */ + public static native long loadIndexWithStream(IndexInputWithBuffer readStream); + + /** + * Load a binary index into memory + * + * @param indexPath path to index file + * @return pointer to location in memory the index resides in + */ + public static native long loadBinaryIndex(String indexPath); + + /** + * Load a binary index into memory with a wrapping having Lucene's IndexInput. + * Instead of directly accessing an index path, this will make Faiss delegate IndexInput to load bytes. + * + * @param readStream IndexInput wrapper having a Lucene's IndexInput reference. + * @return pointer to location in memory the index resides in + */ + public static native long loadBinaryIndexWithStream(IndexInputWithBuffer readStream); + + /** + * Determine if index contains shared state. + * + * @param indexAddr address of index to be checked. + * @return true if index requires shared index state; false otherwise + */ + public static native boolean isSharedIndexStateRequired(long indexAddr); + + /** + * Initialize the shared state for an index + * + * @param indexAddr address of the index to initialize from + * @return Address of shared index state address + */ + public static native long initSharedIndexState(long indexAddr); + + /** + * Set the index state for an index + * + * @param indexAddr address of index to set state for + * @param shareIndexStateAddr address of shared state to be set + */ + public static native void setSharedIndexState(long indexAddr, long shareIndexStateAddr); + + /** + * Query an index without filter + * + * If the "knn" field is a nested field, each vector value within that nested field will be assigned its + * own document ID. In this situation, the term "parent ID" corresponds to the original document ID. + * The arrangement of parent IDs and nested field IDs is assured to have all nested field IDs appearing first, + * followed by the parent ID, in consecutive order without any gaps. Because of this ID pattern, + * we can determine the parent ID of a specific nested field ID using only an array of parent IDs. + * + * @param indexPointer pointer to index in memory + * @param queryVector vector to be used for query + * @param k neighbors to be returned + * @param methodParameters method parameter + * @param parentIds list of parent doc ids when the knn field is a nested field + * @return KNNQueryResult array of k neighbors + */ + public static native KNNQueryResult[] queryIndex( + long indexPointer, + float[] queryVector, + int k, + Map methodParameters, + int[] parentIds + ); + + /** + * Query an index with filter * * @param indexPointer pointer to index in memory * @param queryVector vector to be used for query * @param k neighbors to be returned + * @param methodParameters method parameter + * @param filterIds list of doc ids to include in the query result + * @param parentIds list of parent doc ids when the knn field is a nested field * @return KNNQueryResult array of k neighbors */ - public static native KNNQueryResult[] queryIndex(long indexPointer, float[] queryVector, int k); + public static native KNNQueryResult[] queryIndexWithFilter( + long indexPointer, + float[] queryVector, + int k, + Map methodParameters, + long[] filterIds, + int filterIdsType, + int[] parentIds + ); + + /** + * Query a binary index with filter + * + * @param indexPointer pointer to index in memory + * @param queryVector vector to be used for query + * @param k neighbors to be returned + * @param methodParameters method parameter + * @param filterIds list of doc ids to include in the query result + * @param parentIds list of parent doc ids when the knn field is a nested field + * @return KNNQueryResult array of k neighbors + */ + public static native KNNQueryResult[] queryBinaryIndexWithFilter( + long indexPointer, + byte[] queryVector, + int k, + Map methodParameters, + long[] filterIds, + int filterIdsType, + int[] parentIds + ); + + /** + * Query a binary index with filter + * + * @param indexPointer pointer to index in memory + * @param queryVector vector to be used for query + * @param k neighbors to be returned + * @param filterIds list of doc ids to include in the query result + * @param parentIds list of parent doc ids when the knn field is a nested field + * @return KNNQueryResult array of k neighbors + */ + public static native KNNQueryResult[] queryBinaryIndexWithFilter( + long indexPointer, + byte[] queryVector, + int k, + long[] filterIds, + int filterIdsType, + int[] parentIds + ); /** * Free native memory pointer */ - public static native void free(long indexPointer); + public static native void free(long indexPointer, boolean isBinary); + + /** + * Deallocate memory of the shared index state + * + * @param shareIndexStateAddr address of shared state + */ + public static native void freeSharedIndexState(long shareIndexStateAddr); /** * Initialize library @@ -105,18 +383,79 @@ public static native void createIndexFromTemplate( public static native byte[] trainIndex(Map indexParameters, int dimension, long trainVectorsPointer); /** + * Train an empty binary index + * + * @param indexParameters parameters used to build index + * @param dimension dimension for the index + * @param trainVectorsPointer pointer to where training vectors are stored in native memory + * @return bytes array of trained template index + */ + public static native byte[] trainBinaryIndex(Map indexParameters, int dimension, long trainVectorsPointer); + + /** + * Train an empty byte index + * + * @param indexParameters parameters used to build index + * @param dimension dimension for the index + * @param trainVectorsPointer pointer to where training vectors are stored in native memory + * @return bytes array of trained template index + */ + public static native byte[] trainByteIndex(Map indexParameters, int dimension, long trainVectorsPointer); + + /** + *

+ * The function is deprecated. Use {@link JNICommons#storeVectorData(long, float[][], long)} + *

* Transfer vectors from Java to native * * @param vectorsPointer pointer to vectors in native memory. Should be 0 to create vector as well * @param trainingData data to be transferred * @return pointer to native memory location of training data */ + @Deprecated(since = "2.14.0", forRemoval = true) public static native long transferVectors(long vectorsPointer, float[][] trainingData); /** - * Free vectors from memory + * Range search index with filter + * + * @param indexPointer pointer to index in memory + * @param queryVector vector to be used for query + * @param radius search within radius threshold + * @param methodParameters parameters to be used for the query + * @param indexMaxResultWindow maximum number of results to return + * @param filteredIds list of doc ids to include in the query result + * @param filterIdsType type of filter ids + * @param parentIds list of parent doc ids when the knn field is a nested field + * @return KNNQueryResult array of neighbors within radius + */ + public static native KNNQueryResult[] rangeSearchIndexWithFilter( + long indexPointer, + float[] queryVector, + float radius, + Map methodParameters, + int indexMaxResultWindow, + long[] filteredIds, + int filterIdsType, + int[] parentIds + ); + + /** + * Range search index * - * @param vectorsPointer to be freed + * @param indexPointer pointer to index in memory + * @param queryVector vector to be used for query + * @param radius search within radius threshold + * @param methodParameters parameters to be used for the query + * @param indexMaxResultWindow maximum number of results to return + * @param parentIds list of parent doc ids when the knn field is a nested field + * @return KNNQueryResult array of neighbors within radius */ - public static native void freeVectors(long vectorsPointer); + public static native KNNQueryResult[] rangeSearchIndex( + long indexPointer, + float[] queryVector, + float radius, + Map methodParameters, + int indexMaxResultWindow, + int[] parentIds + ); } diff --git a/src/main/java/org/opensearch/knn/jni/JNICommons.java b/src/main/java/org/opensearch/knn/jni/JNICommons.java new file mode 100644 index 000000000..df3e551cd --- /dev/null +++ b/src/main/java/org/opensearch/knn/jni/JNICommons.java @@ -0,0 +1,192 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.jni; + +import org.opensearch.knn.common.KNNConstants; + +import java.security.AccessController; +import java.security.PrivilegedAction; + +/** + * Common class for providing the JNI related functionality to various JNIServices. + */ +public class JNICommons { + + static { + AccessController.doPrivileged((PrivilegedAction) () -> { + System.loadLibrary(KNNConstants.COMMON_JNI_LIBRARY_NAME); + return null; + }); + } + + /** + * This is utility function that can be used to store data in native memory. This function will allocate memory for + * the data(rows*columns) with initialCapacity and return the memory address where the data is stored. + * If you are using this function for first time use memoryAddress = 0 to ensure that a new memory location is created. + * For subsequent calls you can pass the same memoryAddress. If the data cannot be stored in the memory location + * will throw Exception. + * + *

+ * The function is not threadsafe. If multiple threads are trying to insert on same memory location, then it can + * lead to data corruption. + *

+ * + * @param memoryAddress The address of the memory location where data will be stored. + * @param data 2D float array containing data to be stored in native memory. + * @param initialCapacity The initial capacity of the memory location. + * @return memory address where the data is stored. + */ + public static long storeVectorData(long memoryAddress, float[][] data, long initialCapacity) { + return storeVectorData(memoryAddress, data, initialCapacity, true); + } + + /** + * This is utility function that can be used to store data in native memory. This function will allocate memory for + * the data(rows*columns) with initialCapacity and return the memory address where the data is stored. + * If you are using this function for first time use memoryAddress = 0 to ensure that a new memory location is created. + * For subsequent calls you can pass the same memoryAddress. If the data cannot be stored in the memory location + * will throw Exception. + * + *

+ * The function is not threadsafe. If multiple threads are trying to insert on same memory location, then it can + * lead to data corruption. + *

+ * + * @param memoryAddress The address of the memory location where data will be stored. + * @param data 2D float array containing data to be stored in native memory. + * @param initialCapacity The initial capacity of the memory location. + * @param append append the data or rewrite the memory location + * @return memory address where the data is stored. + */ + public static native long storeVectorData(long memoryAddress, float[][] data, long initialCapacity, boolean append); + + /** + * This is utility function that can be used to store binary data in native memory. This function will allocate memory for + * the data(rows*columns) with initialCapacity and return the memory address where the data is stored. + * If you are using this function for first time use memoryAddress = 0 to ensure that a new memory location is created. + * For subsequent calls you can pass the same memoryAddress. If the data cannot be stored in the memory location + * will throw Exception. + * + *

+ * The function is not threadsafe. If multiple threads are trying to insert on same memory location, then it can + * lead to data corruption. + *

+ * + * @param memoryAddress The address of the memory location where data will be stored. + * @param data 2D byte array containing binary data to be stored in native memory. + * @param initialCapacity The initial capacity of the memory location. + * @return memory address where the data is stored. + */ + public static long storeBinaryVectorData(long memoryAddress, byte[][] data, long initialCapacity) { + return storeBinaryVectorData(memoryAddress, data, initialCapacity, true); + } + + /** + * This is utility function that can be used to store binary data in native memory. This function will allocate memory for + * the data(rows*columns) with initialCapacity and return the memory address where the data is stored. + * If you are using this function for first time use memoryAddress = 0 to ensure that a new memory location is created. + * For subsequent calls you can pass the same memoryAddress. If the data cannot be stored in the memory location + * will throw Exception. + * + *

+ * The function is not threadsafe. If multiple threads are trying to insert on same memory location, then it can + * lead to data corruption. + *

+ * + * @param memoryAddress The address of the memory location where data will be stored. + * @param data 2D byte array containing binary data to be stored in native memory. + * @param initialCapacity The initial capacity of the memory location. + * @param append append the data or rewrite the memory location + * @return memory address where the data is stored. + */ + public static native long storeBinaryVectorData(long memoryAddress, byte[][] data, long initialCapacity, boolean append); + + /** + * This is utility function that can be used to store byte data in native memory. This function will allocate memory for + * the data(rows*columns) with initialCapacity and return the memory address where the data is stored. + * If you are using this function for first time use memoryAddress = 0 to ensure that a new memory location is created. + * For subsequent calls you can pass the same memoryAddress. If the data cannot be stored in the memory location + * will throw Exception. + * + *

+ * The function is not threadsafe. If multiple threads are trying to insert on same memory location, then it can + * lead to data corruption. + *

+ * + * @param memoryAddress The address of the memory location where data will be stored. + * @param data 2D byte array containing byte data to be stored in native memory. + * @param initialCapacity The initial capacity of the memory location. + * @return memory address where the data is stored. + */ + public static long storeByteVectorData(long memoryAddress, byte[][] data, long initialCapacity) { + return storeByteVectorData(memoryAddress, data, initialCapacity, true); + } + + /** + * This is utility function that can be used to store byte data in native memory. This function will allocate memory for + * the data(rows*columns) with initialCapacity and return the memory address where the data is stored. + * If you are using this function for first time use memoryAddress = 0 to ensure that a new memory location is created. + * For subsequent calls you can pass the same memoryAddress. If the data cannot be stored in the memory location + * will throw Exception. + * + *

+ * The function is not threadsafe. If multiple threads are trying to insert on same memory location, then it can + * lead to data corruption. + *

+ * + * @param memoryAddress The address of the memory location where data will be stored. + * @param data 2D byte array containing byte data to be stored in native memory. + * @param initialCapacity The initial capacity of the memory location. + * @param append append the data or rewrite the memory location + * @return memory address where the data is stored. + */ + public static native long storeByteVectorData(long memoryAddress, byte[][] data, long initialCapacity, boolean append); + + /** + * Free up the memory allocated for the data stored in memory address. This function should be used with the memory + * address returned by {@link JNICommons#storeVectorData(long, float[][], long, boolean)} + * + *

+ * The function is not threadsafe. If multiple threads are trying to free up same memory location, then it can + * lead to errors. + *

+ * + * @param memoryAddress address to be freed. + */ + public static native void freeVectorData(long memoryAddress); + + /** + * Free up the memory allocated for the binary data stored in memory address. This function should be used with the memory + * address returned by {@link JNICommons#storeBinaryVectorData(long, byte[][], long)} + * + *

+ * The function is not threadsafe. If multiple threads are trying to free up same memory location, then it can + * lead to errors. + *

+ * + * @param memoryAddress address to be freed. + */ + public static native void freeBinaryVectorData(long memoryAddress); + + /** + * Free up the memory allocated for the byte data stored in memory address. This function should be used with the memory + * address returned by {@link JNICommons#storeByteVectorData(long, byte[][], long)} + * + *

+ * The function is not threadsafe. If multiple threads are trying to free up same memory location, then it can + * lead to errors. + *

+ * + * @param memoryAddress address to be freed. + */ + public static native void freeByteVectorData(long memoryAddress); +} diff --git a/src/main/java/org/opensearch/knn/jni/JNIService.java b/src/main/java/org/opensearch/knn/jni/JNIService.java index 7afc312ac..b490476eb 100644 --- a/src/main/java/org/opensearch/knn/jni/JNIService.java +++ b/src/main/java/org/opensearch/knn/jni/JNIService.java @@ -11,160 +11,469 @@ package org.opensearch.knn.jni; -import org.opensearch.knn.index.KNNQueryResult; -import org.opensearch.knn.index.util.KNNEngine; +import org.apache.commons.lang.ArrayUtils; +import org.opensearch.common.Nullable; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.query.KNNQueryResult; +import org.opensearch.knn.index.store.IndexInputWithBuffer; +import org.opensearch.knn.index.store.IndexOutputWithBuffer; +import org.opensearch.knn.index.util.IndexUtil; +import java.util.Locale; import java.util.Map; /** * Service to distribute requests to the proper engine jni service */ public class JNIService { - /** - * Create an index for the native library + * Initialize an index for the native library. Takes in numDocs to + * allocate the correct amount of memory. * - * @param ids array of ids mapping to the data passed in - * @param data array of float arrays to be indexed - * @param indexPath path to save index file to + * @param numDocs number of documents to be added + * @param dim dimension of the vector to be indexed * @param parameters parameters to build index - * @param engineName name of engine to build index for + * @param knnEngine knn engine + * @return address of the index in memory + */ + public static long initIndex(long numDocs, int dim, Map parameters, KNNEngine knnEngine) { + if (KNNEngine.FAISS == knnEngine) { + if (IndexUtil.isBinaryIndex(knnEngine, parameters)) { + return FaissService.initBinaryIndex(numDocs, dim, parameters); + } + if (IndexUtil.isByteIndex(parameters)) { + return FaissService.initByteIndex(numDocs, dim, parameters); + } + + return FaissService.initIndex(numDocs, dim, parameters); + + } + + throw new IllegalArgumentException( + String.format(Locale.ROOT, "initIndexFromScratch not supported for provided engine : %s", knnEngine.getName()) + ); + } + + /** + * Inserts to a faiss index. + * + * @param docs ids of documents + * @param vectorsAddress address of native memory where vectors are stored + * @param dimension dimension of the vector to be indexed + * @param parameters parameters to build index + * @param indexAddress address of native memory where index is stored + * @param knnEngine knn engine + */ + public static void insertToIndex( + int[] docs, + long vectorsAddress, + int dimension, + Map parameters, + long indexAddress, + KNNEngine knnEngine + ) { + int threadCount = (int) parameters.getOrDefault(KNNConstants.INDEX_THREAD_QTY, 0); + if (KNNEngine.FAISS == knnEngine) { + if (IndexUtil.isBinaryIndex(knnEngine, parameters)) { + FaissService.insertToBinaryIndex(docs, vectorsAddress, dimension, indexAddress, threadCount); + } else if (IndexUtil.isByteIndex(parameters)) { + FaissService.insertToByteIndex(docs, vectorsAddress, dimension, indexAddress, threadCount); + } else { + FaissService.insertToIndex(docs, vectorsAddress, dimension, indexAddress, threadCount); + } + return; + } + + throw new IllegalArgumentException( + String.format(Locale.ROOT, "insertToIndex not supported for provided engine : %s", knnEngine.getName()) + ); + } + + /** + * Writes a faiss index to disk. + * + * @param output Index output wrapper having Lucene's IndexOutput to be used to flush bytes in native engines. + * @param indexAddress address of native memory where index is stored + * @param knnEngine knn engine + * @param parameters parameters to build index */ - public static void createIndex(int[] ids, float[][] data, String indexPath, Map parameters, String engineName) { - if (KNNEngine.NMSLIB.getName().equals(engineName)) { - NmslibService.createIndex(ids, data, indexPath, parameters); + public static void writeIndex(IndexOutputWithBuffer output, long indexAddress, KNNEngine knnEngine, Map parameters) { + if (KNNEngine.FAISS == knnEngine) { + if (IndexUtil.isBinaryIndex(knnEngine, parameters)) { + FaissService.writeBinaryIndex(indexAddress, output); + } else if (IndexUtil.isByteIndex(parameters)) { + FaissService.writeByteIndex(indexAddress, output); + } else { + FaissService.writeIndex(indexAddress, output); + } return; } - if (KNNEngine.FAISS.getName().equals(engineName)) { - FaissService.createIndex(ids, data, indexPath, parameters); + throw new IllegalArgumentException( + String.format(Locale.ROOT, "writeIndex not supported for provided engine : %s", knnEngine.getName()) + ); + } + + /** + * Create an index for the native library. The memory occupied by the vectorsAddress will be freed up during the + * function call. So Java layer doesn't need to free up the memory. This is not an ideal behavior because Java layer + * created the memory address and that should only free up the memory. We are tracking the proper fix for this on this + * issue + * + * @param ids array of ids mapping to the data passed in + * @param vectorsAddress address of native memory where vectors are stored + * @param dim dimension of the vector to be indexed + * @param output Index output wrapper having Lucene's IndexOutput to be used to flush bytes in native engines. + * @param parameters parameters to build index + * @param knnEngine engine to build index for + */ + public static void createIndex( + int[] ids, + long vectorsAddress, + int dim, + IndexOutputWithBuffer output, + Map parameters, + KNNEngine knnEngine + ) { + if (KNNEngine.NMSLIB == knnEngine) { + NmslibService.createIndex(ids, vectorsAddress, dim, output, parameters); return; } - throw new IllegalArgumentException("CreateIndex not supported for provided engine"); + throw new IllegalArgumentException( + String.format(Locale.ROOT, "CreateIndex not supported for provided engine : %s", knnEngine.getName()) + ); } /** * Create an index for the native library with a provided template index * - * @param ids array of ids mapping to the data passed in - * @param data array of float arrays to be indexed - * @param indexPath path to save index file to - * @param templateIndex empty template index - * @param parameters parameters to build index - * @param engineName name of engine to build index for + * @param ids array of ids mapping to the data passed in + * @param vectorsAddress address of native memory where vectors are stored + * @param dim dimension of vectors to be indexed + * @param output Index output wrapper having Lucene's IndexOutput to be used to flush bytes in native engines. + * @param templateIndex empty template index + * @param parameters parameters to build index + * @param knnEngine engine to build index for */ public static void createIndexFromTemplate( int[] ids, - float[][] data, - String indexPath, + long vectorsAddress, + int dim, + IndexOutputWithBuffer output, byte[] templateIndex, Map parameters, - String engineName + KNNEngine knnEngine ) { - if (KNNEngine.FAISS.getName().equals(engineName)) { - FaissService.createIndexFromTemplate(ids, data, indexPath, templateIndex, parameters); + if (KNNEngine.FAISS == knnEngine) { + if (IndexUtil.isBinaryIndex(knnEngine, parameters)) { + FaissService.createBinaryIndexFromTemplate(ids, vectorsAddress, dim, output, templateIndex, parameters); + return; + } + if (IndexUtil.isByteIndex(parameters)) { + FaissService.createByteIndexFromTemplate(ids, vectorsAddress, dim, output, templateIndex, parameters); + return; + } + + FaissService.createIndexFromTemplate(ids, vectorsAddress, dim, output, templateIndex, parameters); return; } - throw new IllegalArgumentException("CreateIndexFromTemplate not supported for provided engine"); + throw new IllegalArgumentException( + String.format(Locale.ROOT, "CreateIndexFromTemplate not supported for provided engine : %s", knnEngine.getName()) + ); + } + + /** + * Load an index via Lucene's IndexInput. + * + * @param readStream A wrapper having Lucene's IndexInput to load bytes from a file. + * @param parameters Parameters to be used when loading index + * @param knnEngine Engine to load index + * @return Pointer to location in memory the index resides in + */ + public static long loadIndex(IndexInputWithBuffer readStream, Map parameters, KNNEngine knnEngine) { + if (KNNEngine.FAISS == knnEngine) { + if (IndexUtil.isBinaryIndex(knnEngine, parameters)) { + return FaissService.loadBinaryIndexWithStream(readStream); + } else { + return FaissService.loadIndexWithStream(readStream); + } + } else if (KNNEngine.NMSLIB == knnEngine) { + return NmslibService.loadIndexWithStream(readStream, parameters); + } + + throw new IllegalArgumentException( + String.format(Locale.ROOT, "LoadIndex not supported for provided engine : %s", knnEngine.getName()) + ); } /** - * Load an index into memory + * Determine if index contains shared state. Currently, we cannot do this in the plugin because we do not store the + * model definition anywhere. Only faiss supports indices that have shared state. So for all other engines it will + * return false. * - * @param indexPath path to index file - * @param parameters parameters to be used when loading index - * @param engineName name of engine to load index - * @return pointer to location in memory the index resides in + * @param indexAddr address of index to be checked. + * @param knnEngine engine + * @return true if index requires shared index state; false otherwise */ - public static long loadIndex(String indexPath, Map parameters, String engineName) { - if (KNNEngine.NMSLIB.getName().equals(engineName)) { - return NmslibService.loadIndex(indexPath, parameters); + public static boolean isSharedIndexStateRequired(long indexAddr, KNNEngine knnEngine) { + if (KNNEngine.FAISS == knnEngine) { + return FaissService.isSharedIndexStateRequired(indexAddr); } - if (KNNEngine.FAISS.getName().equals(engineName)) { - return FaissService.loadIndex(indexPath); + return false; + } + + /** + * Initialize the shared state for an index + * + * @param indexAddr address of the index to initialize from + * @param knnEngine engine + * @return Address of shared index state address + */ + public static long initSharedIndexState(long indexAddr, KNNEngine knnEngine) { + if (KNNEngine.FAISS == knnEngine) { + return FaissService.initSharedIndexState(indexAddr); + } + throw new IllegalArgumentException( + String.format(Locale.ROOT, "InitSharedIndexState not supported for provided engine : %s", knnEngine.getName()) + ); + } + + /** + * Set the index state for an index + * + * @param indexAddr address of index to set state for + * @param shareIndexStateAddr address of shared state to be set + * @param knnEngine engine + */ + public static void setSharedIndexState(long indexAddr, long shareIndexStateAddr, KNNEngine knnEngine) { + if (KNNEngine.FAISS == knnEngine) { + FaissService.setSharedIndexState(indexAddr, shareIndexStateAddr); + return; } - throw new IllegalArgumentException("LoadIndex not supported for provided engine"); + throw new IllegalArgumentException( + String.format(Locale.ROOT, "SetSharedIndexState not supported for provided engine : %s", knnEngine.getName()) + ); } /** * Query an index * - * @param indexPointer pointer to index in memory - * @param queryVector vector to be used for query - * @param k neighbors to be returned - * @param engineName name of engine to query index + * @param indexPointer pointer to index in memory + * @param queryVector vector to be used for query + * @param k neighbors to be returned + * @param methodParameters method parameter + * @param knnEngine engine to query index + * @param filteredIds array of ints on which should be used for search. + * @param filterIdsType how to filter ids: Batch or BitMap * @return KNNQueryResult array of k neighbors */ - public static KNNQueryResult[] queryIndex(long indexPointer, float[] queryVector, int k, String engineName) { - if (KNNEngine.NMSLIB.getName().equals(engineName)) { - return NmslibService.queryIndex(indexPointer, queryVector, k); + public static KNNQueryResult[] queryIndex( + long indexPointer, + float[] queryVector, + int k, + @Nullable Map methodParameters, + KNNEngine knnEngine, + long[] filteredIds, + int filterIdsType, + int[] parentIds + ) { + if (KNNEngine.NMSLIB == knnEngine) { + return NmslibService.queryIndex(indexPointer, queryVector, k, methodParameters); } - if (KNNEngine.FAISS.getName().equals(engineName)) { - return FaissService.queryIndex(indexPointer, queryVector, k); + if (KNNEngine.FAISS == knnEngine) { + // This code assumes that if filteredIds == null / filteredIds.length == 0 if filter is specified then empty + // k-NN results are already returned. Otherwise, it's a filter case and we need to run search with + // filterIds. FilterIds is coming as empty then its the case where we need to do search with Faiss engine + // normally. + if (ArrayUtils.isNotEmpty(filteredIds)) { + return FaissService.queryIndexWithFilter( + indexPointer, + queryVector, + k, + methodParameters, + filteredIds, + filterIdsType, + parentIds + ); + } + return FaissService.queryIndex(indexPointer, queryVector, k, methodParameters, parentIds); } + throw new IllegalArgumentException( + String.format(Locale.ROOT, "QueryIndex not supported for provided engine : %s", knnEngine.getName()) + ); + } - throw new IllegalArgumentException("QueryIndex not supported for provided engine"); + /** + * Query a binary index + * + * @param indexPointer pointer to index in memory + * @param queryVector vector to be used for query + * @param k neighbors to be returned + * @param methodParameters method parameter + * @param knnEngine engine to query index + * @param filteredIds array of ints on which should be used for search. + * @param filterIdsType how to filter ids: Batch or BitMap + * @return KNNQueryResult array of k neighbors + */ + public static KNNQueryResult[] queryBinaryIndex( + long indexPointer, + byte[] queryVector, + int k, + @Nullable Map methodParameters, + KNNEngine knnEngine, + long[] filteredIds, + int filterIdsType, + int[] parentIds + ) { + if (KNNEngine.FAISS == knnEngine) { + return FaissService.queryBinaryIndexWithFilter( + indexPointer, + queryVector, + k, + methodParameters, + ArrayUtils.isEmpty(filteredIds) ? null : filteredIds, + filterIdsType, + parentIds + ); + } + throw new IllegalArgumentException( + String.format(Locale.ROOT, "QueryBinaryIndex not supported for provided engine : %s", knnEngine.getName()) + ); } /** * Free native memory pointer * * @param indexPointer location to be freed - * @param engineName engine to perform free + * @param knnEngine engine to perform free + */ + public static void free(final long indexPointer, final KNNEngine knnEngine) { + free(indexPointer, knnEngine, false); + } + + /** + * Free native memory pointer + * + * @param indexPointer location to be freed + * @param knnEngine engine to perform free + * @param isBinaryIndex indicate if it is binary index or not */ - public static void free(long indexPointer, String engineName) { - if (KNNEngine.NMSLIB.getName().equals(engineName)) { + public static void free(final long indexPointer, final KNNEngine knnEngine, final boolean isBinaryIndex) { + if (KNNEngine.NMSLIB == knnEngine) { NmslibService.free(indexPointer); return; } - if (KNNEngine.FAISS.getName().equals(engineName)) { - FaissService.free(indexPointer); + if (KNNEngine.FAISS == knnEngine) { + FaissService.free(indexPointer, isBinaryIndex); return; } - throw new IllegalArgumentException("Free not supported for provided engine"); + throw new IllegalArgumentException(String.format(Locale.ROOT, "Free not supported for provided engine : %s", knnEngine.getName())); + } + + /** + * Deallocate memory of the shared index state + * + * @param shareIndexStateAddr address of shared state + * @param knnEngine engine + */ + public static void freeSharedIndexState(long shareIndexStateAddr, KNNEngine knnEngine) { + if (KNNEngine.FAISS == knnEngine) { + FaissService.freeSharedIndexState(shareIndexStateAddr); + return; + } + throw new IllegalArgumentException( + String.format(Locale.ROOT, "FreeSharedIndexState not supported for provided engine : %s", knnEngine.getName()) + ); } /** * Train an empty index * - * @param indexParameters parameters used to build index - * @param dimension dimension for the index + * @param indexParameters parameters used to build index + * @param dimension dimension for the index * @param trainVectorsPointer pointer to where training vectors are stored in native memory - * @param engineName engine to perform the training + * @param knnEngine engine to perform the training * @return bytes array of trained template index */ - public static byte[] trainIndex(Map indexParameters, int dimension, long trainVectorsPointer, String engineName) { - if (KNNEngine.FAISS.getName().equals(engineName)) { + public static byte[] trainIndex(Map indexParameters, int dimension, long trainVectorsPointer, KNNEngine knnEngine) { + if (KNNEngine.FAISS == knnEngine) { + if (IndexUtil.isBinaryIndex(knnEngine, indexParameters)) { + return FaissService.trainBinaryIndex(indexParameters, dimension, trainVectorsPointer); + } + if (IndexUtil.isByteIndex(indexParameters)) { + return FaissService.trainByteIndex(indexParameters, dimension, trainVectorsPointer); + } return FaissService.trainIndex(indexParameters, dimension, trainVectorsPointer); } - throw new IllegalArgumentException("TrainIndex not supported for provided engine"); + throw new IllegalArgumentException( + String.format(Locale.ROOT, "TrainIndex not supported for provided engine : %s", knnEngine.getName()) + ); } /** + *

+ * The function is deprecated. Use {@link JNICommons#storeVectorData(long, float[][], long, boolean)} + *

* Transfer vectors from Java to native * * @param vectorsPointer pointer to vectors in native memory. Should be 0 to create vector as well - * @param trainingData data to be transferred + * @param trainingData data to be transferred * @return pointer to native memory location of training data */ + @Deprecated(since = "2.14.0", forRemoval = true) public static long transferVectors(long vectorsPointer, float[][] trainingData) { return FaissService.transferVectors(vectorsPointer, trainingData); } /** - * Free vectors from memory + * Range search index for a given query vector * - * @param vectorsPointer to be freed + * @param indexPointer pointer to index in memory + * @param queryVector vector to be used for query + * @param radius search within radius threshold + * @param methodParameters parameters to be used when loading index + * @param knnEngine engine to query index + * @param indexMaxResultWindow maximum number of results to return + * @param filteredIds list of doc ids to include in the query result + * @param filterIdsType how to filter ids: Batch or BitMap + * @param parentIds parent ids of the vectors + * @return KNNQueryResult array of neighbors within radius */ - public static void freeVectors(long vectorsPointer) { - FaissService.freeVectors(vectorsPointer); + public static KNNQueryResult[] radiusQueryIndex( + long indexPointer, + float[] queryVector, + float radius, + @Nullable Map methodParameters, + KNNEngine knnEngine, + int indexMaxResultWindow, + long[] filteredIds, + int filterIdsType, + int[] parentIds + ) { + if (KNNEngine.FAISS == knnEngine) { + if (ArrayUtils.isNotEmpty(filteredIds)) { + return FaissService.rangeSearchIndexWithFilter( + indexPointer, + queryVector, + radius, + methodParameters, + indexMaxResultWindow, + filteredIds, + filterIdsType, + parentIds + ); + } + return FaissService.rangeSearchIndex(indexPointer, queryVector, radius, methodParameters, indexMaxResultWindow, parentIds); + } + throw new IllegalArgumentException(String.format(Locale.ROOT, "RadiusQueryIndex not supported for provided engine")); } } diff --git a/src/main/java/org/opensearch/knn/jni/NmslibService.java b/src/main/java/org/opensearch/knn/jni/NmslibService.java index b02cdca0f..16cc6bf52 100644 --- a/src/main/java/org/opensearch/knn/jni/NmslibService.java +++ b/src/main/java/org/opensearch/knn/jni/NmslibService.java @@ -12,8 +12,10 @@ package org.opensearch.knn.jni; import org.opensearch.knn.common.KNNConstants; -import org.opensearch.knn.index.KNNQueryResult; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.query.KNNQueryResult; +import org.opensearch.knn.index.store.IndexInputWithBuffer; +import org.opensearch.knn.index.store.IndexOutputWithBuffer; import java.security.AccessController; import java.security.PrivilegedAction; @@ -21,7 +23,7 @@ /** * Service to interact with nmslib jni layer. Class dependencies should be minimal - * + *

* In order to compile C++ header file, run: * javac -h jni/include src/main/java/org/opensearch/knn/jni/NmslibService.java * src/main/java/org/opensearch/knn/index/KNNQueryResult.java @@ -39,23 +41,33 @@ class NmslibService { } /** - * Create an index for the native library + * Create an index for the native library. The memory occupied by the vectorsAddress will be freed up during the + * function call. So Java layer doesn't need to free up the memory. This is not an ideal behavior because Java layer + * created the memory address and that should only free up the memory. We are tracking the proper fix for this on this + * issue * * @param ids array of ids mapping to the data passed in - * @param data array of float arrays to be indexed - * @param indexPath path to save index file to + * @param vectorsAddress address of native memory where vectors are stored + * @param dim dimension of the vector to be indexed + * @param output Index output wrapper having Lucene's IndexOutput to be used to flush bytes in native engines. * @param parameters parameters to build index */ - public static native void createIndex(int[] ids, float[][] data, String indexPath, Map parameters); + public static native void createIndex( + int[] ids, + long vectorsAddress, + int dim, + IndexOutputWithBuffer output, + Map parameters + ); /** - * Load an index into memory + * Load an index into memory through the provided read stream wrapping Lucene's IndexInput. * - * @param indexPath path to index file - * @param parameters parameters to be used when loading index - * @return pointer to location in memory the index resides in + * @param readStream Read stream wrapping Lucene's IndexInput. + * @param parameters Parameters to be used when loading index + * @return Pointer to location in memory the index resides in */ - public static native long loadIndex(String indexPath, Map parameters); + public static native long loadIndexWithStream(IndexInputWithBuffer readStream, Map parameters); /** * Query an index @@ -65,7 +77,7 @@ class NmslibService { * @param k neighbors to be returned * @return KNNQueryResult array of k neighbors */ - public static native KNNQueryResult[] queryIndex(long indexPointer, float[] queryVector, int k); + public static native KNNQueryResult[] queryIndex(long indexPointer, float[] queryVector, int k, Map methodParameters); /** * Free native memory pointer diff --git a/src/main/java/org/opensearch/knn/jni/PlatformUtils.java b/src/main/java/org/opensearch/knn/jni/PlatformUtils.java new file mode 100644 index 000000000..445862f24 --- /dev/null +++ b/src/main/java/org/opensearch/knn/jni/PlatformUtils.java @@ -0,0 +1,119 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.jni; + +import com.sun.jna.Platform; +import org.apache.commons.lang.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import oshi.util.platform.mac.SysctlUtil; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.Arrays; +import java.util.Locale; +import java.util.stream.Stream; + +public class PlatformUtils { + + private static final Logger logger = LogManager.getLogger(PlatformUtils.class); + + /** + * Verify if the underlying system supports AVX2 SIMD Optimization or not + * 1. If the architecture is not x86 return false. + * 2. If the operating system is not Mac or Linux return false(for example Windows). + * 3. If the operating system is macOS, use oshi library to verify if the cpu flags + * contains 'avx2' and return true if it exists else false. + * 4. If the operating system is linux, read the '/proc/cpuinfo' file path and verify if + * the flags contains 'avx2' and return true if it exists else false. + */ + public static boolean isAVX2SupportedBySystem() { + if (!Platform.isIntel() || Platform.isWindows()) { + return false; + } + + if (Platform.isMac()) { + + // sysctl or system control retrieves system info and allows processes with appropriate privileges + // to set system info. This system info contains the machine dependent cpu features that are supported by it. + // On MacOS, if the underlying processor supports AVX2 instruction set, it will be listed under the "leaf7" + // subset of instructions ("sysctl -a | grep machdep.cpu.leaf7_features"). + // https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/sysctl.3.html + try { + return AccessController.doPrivileged((PrivilegedExceptionAction) () -> { + String flags = SysctlUtil.sysctl("machdep.cpu.leaf7_features", "empty"); + return (flags.toLowerCase(Locale.ROOT)).contains("avx2"); + }); + } catch (Exception e) { + logger.error("[KNN] Error fetching cpu flags info. [{}]", e.getMessage(), e); + } + + } else if (Platform.isLinux()) { + // The "/proc/cpuinfo" is a virtual file which identifies and provides the processor details used + // by system. This info contains "flags" for each processor which determines the qualities of that processor + // and it's ability to process different instruction sets like mmx, avx, avx2 and so on. + // https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/s2-proc-cpuinfo + // Here, we are trying to read the details of all processors used by system and find if any of the processor + // supports AVX2 instructions. Pentium and Celeron are a couple of examples which doesn't support AVX2 + // https://ark.intel.com/content/www/us/en/ark/products/199285/intel-pentium-gold-g6600-processor-4m-cache-4-20-ghz.html + String fileName = "/proc/cpuinfo"; + try { + return AccessController.doPrivileged( + (PrivilegedExceptionAction) () -> (Boolean) Files.lines(Paths.get(fileName)) + .filter(s -> s.startsWith("flags")) + .anyMatch(s -> StringUtils.containsIgnoreCase(s, "avx2")) + ); + + } catch (Exception e) { + logger.error("[KNN] Error reading file [{}]. [{}]", fileName, e.getMessage(), e); + } + } + return false; + } + + public static boolean isAVX512SupportedBySystem() { + + if (!Platform.isIntel() || Platform.isMac() || Platform.isWindows()) { + return false; + } + + if (Platform.isLinux()) { + // The "/proc/cpuinfo" is a virtual file which identifies and provides the processor details used + // by system. This info contains "flags" for each processor which determines the qualities of that processor + // and it's ability to process different instruction sets like mmx, avx, avx2, avx512 and so on. + // https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/s2-proc-cpuinfo + // Here, we are trying to read the details of all processors used by system and find if any of the processor + // supports AVX512 instructions supported by faiss. + String fileName = "/proc/cpuinfo"; + + // AVX512 has multiple flags, which control various features. k-nn requires the same set of flags as faiss to compile + // using avx512. Please update these if faiss updates their compilation instructions in the future. + // https://github.com/facebookresearch/faiss/blob/main/faiss/CMakeLists.txt + String[] avx512 = { "avx512f", "avx512cd", "avx512vl", "avx512dq", "avx512bw" }; + + try { + return AccessController.doPrivileged((PrivilegedExceptionAction) () -> { + Stream linestream = Files.lines(Paths.get(fileName)); + String flags = linestream.filter(line -> line.startsWith("flags")).findFirst().orElse(""); + return Arrays.stream(avx512).allMatch(flags::contains); + }); + + } catch (PrivilegedActionException e) { + logger.error("[KNN] Error reading file [{}]. [{}]", fileName, e.getMessage(), e); + } + } + return false; + } +} diff --git a/src/main/java/org/opensearch/knn/plugin/KNNPlugin.java b/src/main/java/org/opensearch/knn/plugin/KNNPlugin.java index 9cf0696f2..d05bbaa05 100644 --- a/src/main/java/org/opensearch/knn/plugin/KNNPlugin.java +++ b/src/main/java/org/opensearch/knn/plugin/KNNPlugin.java @@ -5,16 +5,26 @@ package org.opensearch.knn.plugin; +import org.opensearch.cluster.NamedDiff; +import org.opensearch.cluster.metadata.Metadata; +import org.opensearch.core.ParseField; +import org.opensearch.core.action.ActionResponse; import org.opensearch.index.codec.CodecServiceFactory; import org.opensearch.index.engine.EngineFactory; +import org.opensearch.indices.SystemIndexDescriptor; import org.opensearch.knn.index.KNNCircuitBreaker; -import org.opensearch.knn.index.KNNQueryBuilder; +import org.opensearch.knn.plugin.search.KNNConcurrentSearchRequestDecider; +import org.opensearch.knn.index.util.KNNClusterUtil; +import org.opensearch.knn.index.query.KNNQueryBuilder; import org.opensearch.knn.index.KNNSettings; -import org.opensearch.knn.index.KNNVectorFieldMapper; +import org.opensearch.knn.index.mapper.KNNVectorFieldMapper; -import org.opensearch.knn.index.KNNWeight; +import org.opensearch.knn.index.query.parser.KNNQueryBuilderParser; +import org.opensearch.knn.index.query.KNNWeight; import org.opensearch.knn.index.codec.KNNCodecService; import org.opensearch.knn.index.memory.NativeMemoryLoadStrategy; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.indices.ModelGraveyard; import org.opensearch.knn.indices.ModelCache; import org.opensearch.knn.indices.ModelDao; import org.opensearch.knn.plugin.rest.RestDeleteModelHandler; @@ -23,6 +33,7 @@ import org.opensearch.knn.plugin.rest.RestKNNWarmupHandler; import org.opensearch.knn.plugin.rest.RestSearchModelHandler; import org.opensearch.knn.plugin.rest.RestTrainModelHandler; +import org.opensearch.knn.plugin.rest.RestClearCacheHandler; import org.opensearch.knn.plugin.script.KNNScoringScriptEngine; import org.opensearch.knn.plugin.stats.KNNStats; import org.opensearch.knn.plugin.transport.DeleteModelAction; @@ -33,27 +44,27 @@ import org.opensearch.knn.plugin.transport.KNNStatsTransportAction; import org.opensearch.knn.plugin.transport.KNNWarmupAction; import org.opensearch.knn.plugin.transport.KNNWarmupTransportAction; +import org.opensearch.knn.plugin.transport.ClearCacheAction; +import org.opensearch.knn.plugin.transport.ClearCacheTransportAction; import com.google.common.collect.ImmutableList; import org.opensearch.action.ActionRequest; -import org.opensearch.action.ActionResponse; import org.opensearch.client.Client; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.node.DiscoveryNodes; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.io.stream.NamedWriteableRegistry; +import org.opensearch.core.common.io.stream.NamedWriteableRegistry; import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.IndexScopedSettings; import org.opensearch.common.settings.Setting; import org.opensearch.common.settings.Settings; import org.opensearch.common.settings.SettingsFilter; -import org.opensearch.common.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.env.Environment; import org.opensearch.env.NodeEnvironment; import org.opensearch.index.IndexModule; import org.opensearch.index.IndexSettings; import org.opensearch.index.mapper.Mapper; -import org.opensearch.knn.plugin.stats.KNNStatsConfig; import org.opensearch.knn.plugin.transport.RemoveModelFromCacheAction; import org.opensearch.knn.plugin.transport.RemoveModelFromCacheTransportAction; import org.opensearch.knn.plugin.transport.SearchModelAction; @@ -67,6 +78,9 @@ import org.opensearch.knn.plugin.transport.TrainingModelTransportAction; import org.opensearch.knn.plugin.transport.UpdateModelMetadataAction; import org.opensearch.knn.plugin.transport.UpdateModelMetadataTransportAction; +import org.opensearch.knn.plugin.transport.UpdateModelGraveyardAction; +import org.opensearch.knn.plugin.transport.UpdateModelGraveyardTransportAction; +import org.opensearch.knn.training.TrainingJobClusterStateListener; import org.opensearch.knn.training.TrainingJobRunner; import org.opensearch.knn.training.VectorReader; import org.opensearch.plugins.ActionPlugin; @@ -76,27 +90,33 @@ import org.opensearch.plugins.Plugin; import org.opensearch.plugins.ScriptPlugin; import org.opensearch.plugins.SearchPlugin; +import org.opensearch.plugins.SystemIndexPlugin; import org.opensearch.repositories.RepositoriesService; import org.opensearch.rest.RestController; import org.opensearch.rest.RestHandler; import org.opensearch.script.ScriptContext; import org.opensearch.script.ScriptEngine; import org.opensearch.script.ScriptService; +import org.opensearch.search.deciders.ConcurrentSearchRequestDecider; import org.opensearch.threadpool.ExecutorBuilder; import org.opensearch.threadpool.FixedExecutorBuilder; import org.opensearch.threadpool.ThreadPool; import org.opensearch.watcher.ResourceWatcherService; import java.util.Arrays; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static java.util.Collections.singletonList; import static org.opensearch.knn.common.KNNConstants.KNN_THREAD_POOL_PREFIX; +import static org.opensearch.knn.common.KNNConstants.MODEL_INDEX_NAME; import static org.opensearch.knn.common.KNNConstants.TRAIN_THREAD_POOL; /** @@ -129,7 +149,15 @@ * } * */ -public class KNNPlugin extends Plugin implements MapperPlugin, SearchPlugin, ActionPlugin, EnginePlugin, ScriptPlugin, ExtensiblePlugin { +public class KNNPlugin extends Plugin + implements + MapperPlugin, + SearchPlugin, + ActionPlugin, + EnginePlugin, + ScriptPlugin, + ExtensiblePlugin, + SystemIndexPlugin { public static final String LEGACY_KNN_BASE_URI = "/_opendistro/_knn"; public static final String KNN_BASE_URI = "/_plugins/_knn"; @@ -147,7 +175,7 @@ public Map getMappers() { @Override public List> getQueries() { - return singletonList(new QuerySpec<>(KNNQueryBuilder.NAME, KNNQueryBuilder::new, KNNQueryBuilder::fromXContent)); + return singletonList(new QuerySpec<>(KNNQueryBuilder.NAME, KNNQueryBuilder::new, KNNQueryBuilderParser::fromXContent)); } @Override @@ -167,19 +195,23 @@ public Collection createComponents( this.clusterService = clusterService; // Initialize Native Memory loading strategies - NativeMemoryLoadStrategy.IndexLoadStrategy.initialize(resourceWatcherService); VectorReader vectorReader = new VectorReader(client); NativeMemoryLoadStrategy.TrainingLoadStrategy.initialize(vectorReader); KNNSettings.state().initialize(client, clusterService); + KNNClusterUtil.instance().initialize(clusterService); ModelDao.OpenSearchKNNModelDao.initialize(client, clusterService, environment.settings()); ModelCache.initialize(ModelDao.OpenSearchKNNModelDao.getInstance(), clusterService); TrainingJobRunner.initialize(threadPool, ModelDao.OpenSearchKNNModelDao.getInstance()); + TrainingJobClusterStateListener.initialize(threadPool, ModelDao.OpenSearchKNNModelDao.getInstance(), clusterService); KNNCircuitBreaker.getInstance().initialize(threadPool, clusterService, client); KNNQueryBuilder.initialize(ModelDao.OpenSearchKNNModelDao.getInstance()); KNNWeight.initialize(ModelDao.OpenSearchKNNModelDao.getInstance()); TrainingModelRequest.initialize(ModelDao.OpenSearchKNNModelDao.getInstance(), clusterService); - knnStats = new KNNStats(KNNStatsConfig.KNN_STATS); + + clusterService.addListener(TrainingJobClusterStateListener.getInstance()); + + knnStats = new KNNStats(); return ImmutableList.of(knnStats); } @@ -198,7 +230,7 @@ public List getRestHandlers( Supplier nodesInCluster ) { - RestKNNStatsHandler restKNNStatsHandler = new RestKNNStatsHandler(settings, restController, knnStats); + RestKNNStatsHandler restKNNStatsHandler = new RestKNNStatsHandler(); RestKNNWarmupHandler restKNNWarmupHandler = new RestKNNWarmupHandler( settings, restController, @@ -209,6 +241,7 @@ public List getRestHandlers( RestDeleteModelHandler restDeleteModelHandler = new RestDeleteModelHandler(); RestTrainModelHandler restTrainModelHandler = new RestTrainModelHandler(); RestSearchModelHandler restSearchModelHandler = new RestSearchModelHandler(); + RestClearCacheHandler restClearCacheHandler = new RestClearCacheHandler(clusterService, indexNameExpressionResolver); return ImmutableList.of( restKNNStatsHandler, @@ -216,7 +249,8 @@ public List getRestHandlers( restGetModelHandler, restDeleteModelHandler, restTrainModelHandler, - restSearchModelHandler + restSearchModelHandler, + restClearCacheHandler ); } @@ -235,7 +269,9 @@ public List getRestHandlers( new ActionHandler<>(TrainingJobRouterAction.INSTANCE, TrainingJobRouterTransportAction.class), new ActionHandler<>(TrainingModelAction.INSTANCE, TrainingModelTransportAction.class), new ActionHandler<>(RemoveModelFromCacheAction.INSTANCE, RemoveModelFromCacheTransportAction.class), - new ActionHandler<>(SearchModelAction.INSTANCE, SearchModelTransportAction.class) + new ActionHandler<>(SearchModelAction.INSTANCE, SearchModelTransportAction.class), + new ActionHandler<>(UpdateModelGraveyardAction.INSTANCE, UpdateModelGraveyardTransportAction.class), + new ActionHandler<>(ClearCacheAction.INSTANCE, ClearCacheTransportAction.class) ); } @@ -293,4 +329,54 @@ public ScriptEngine getScriptEngine(Settings settings, Collection> getExecutorBuilders(Settings settings) { return ImmutableList.of(new FixedExecutorBuilder(settings, TRAIN_THREAD_POOL, 1, 1, KNN_THREAD_POOL_PREFIX, false)); } + + @Override + public List getNamedWriteables() { + List entries = new ArrayList<>(); + + entries.add(new NamedWriteableRegistry.Entry(Metadata.Custom.class, ModelGraveyard.TYPE, ModelGraveyard::new)); + entries.add(new NamedWriteableRegistry.Entry(NamedDiff.class, ModelGraveyard.TYPE, ModelGraveyard::readDiffFrom)); + return entries; + } + + @Override + public List getNamedXContent() { + List entries = new ArrayList<>(); + + entries.add( + new NamedXContentRegistry.Entry(Metadata.Custom.class, new ParseField(ModelGraveyard.TYPE), ModelGraveyard::fromXContent) + ); + return entries; + } + + @Override + public Collection getSystemIndexDescriptors(Settings settings) { + return ImmutableList.of(new SystemIndexDescriptor(MODEL_INDEX_NAME, "Index for storing models used for k-NN indices")); + } + + /** + * Plugin can provide additional node settings, that includes new settings or overrides for existing one from core. + * + * @return settings that are set by plugin + */ + @Override + public Settings additionalSettings() { + // We add engine specific extensions to the core list for HybridFS store type. We read existing values + // and append ours because in core setting will be replaced by override. + // Values are set as cluster defaults and are used at index creation time. Index specific overrides will take priority over values + // that are set here. + final List engineSettings = Arrays.stream(KNNEngine.values()) + .flatMap(engine -> engine.mmapFileExtensions().stream()) + .collect(Collectors.toList()); + final List combinedSettings = Stream.concat( + IndexModule.INDEX_STORE_HYBRID_MMAP_EXTENSIONS.getDefault(Settings.EMPTY).stream(), + engineSettings.stream() + ).collect(Collectors.toList()); + return Settings.builder().putList(IndexModule.INDEX_STORE_HYBRID_MMAP_EXTENSIONS.getKey(), combinedSettings).build(); + } + + @Override + public Optional getConcurrentSearchRequestDeciderFactory() { + return Optional.of(new KNNConcurrentSearchRequestDecider.Factory()); + } } diff --git a/src/main/java/org/opensearch/knn/plugin/rest/RestClearCacheHandler.java b/src/main/java/org/opensearch/knn/plugin/rest/RestClearCacheHandler.java new file mode 100644 index 000000000..2cbc9cd76 --- /dev/null +++ b/src/main/java/org/opensearch/knn/plugin/rest/RestClearCacheHandler.java @@ -0,0 +1,98 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.plugin.rest; + +import com.google.common.collect.ImmutableList; +import lombok.AllArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.opensearch.client.node.NodeClient; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.core.common.Strings; +import org.opensearch.core.index.Index; +import org.opensearch.knn.common.exception.KNNInvalidIndicesException; +import org.opensearch.knn.plugin.KNNPlugin; +import org.opensearch.knn.plugin.transport.ClearCacheAction; +import org.opensearch.knn.plugin.transport.ClearCacheRequest; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; + +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; + +import static org.opensearch.action.support.IndicesOptions.strictExpandOpen; +import static org.opensearch.knn.common.KNNConstants.CLEAR_CACHE; +import static org.opensearch.knn.index.KNNSettings.KNN_INDEX; + +/** + * RestHandler for k-NN Clear Cache API. API provides the ability for a user to evict those indices from Cache. + */ +@AllArgsConstructor +@Log4j2 +public class RestClearCacheHandler extends BaseRestHandler { + private static final String INDEX = "index"; + public static String NAME = "knn_clear_cache_action"; + private final ClusterService clusterService; + private final IndexNameExpressionResolver indexNameExpressionResolver; + + /** + * @return name of Clear Cache API action + */ + @Override + public String getName() { + return NAME; + } + + /** + * @return Immutable List of Clear Cache API endpoint + */ + @Override + public List routes() { + return ImmutableList.of( + new Route(RestRequest.Method.POST, String.format(Locale.ROOT, "%s/%s/{%s}", KNNPlugin.KNN_BASE_URI, CLEAR_CACHE, INDEX)) + ); + } + + /** + * @param request RestRequest + * @param client NodeClient + * @return RestChannelConsumer + */ + @Override + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { + ClearCacheRequest clearCacheRequest = createClearCacheRequest(request); + log.info("[KNN] ClearCache started for the following indices: [{}]", String.join(",", clearCacheRequest.indices())); + return channel -> client.execute(ClearCacheAction.INSTANCE, clearCacheRequest, new RestToXContentListener<>(channel)); + } + + // Create a clear cache request by processing the rest request and validating the indices + private ClearCacheRequest createClearCacheRequest(RestRequest request) { + String[] indexNames = Strings.splitStringByCommaToArray(request.param("index")); + Index[] indices = indexNameExpressionResolver.concreteIndices(clusterService.state(), strictExpandOpen(), indexNames); + validateIndices(indices); + + return new ClearCacheRequest(indexNames); + } + + // Validate if the given indices are k-NN indices or not. If there are any invalid indices, + // the request is rejected and an exception is thrown. + private void validateIndices(Index[] indices) { + List invalidIndexNames = Arrays.stream(indices) + .filter(index -> !"true".equals(clusterService.state().metadata().getIndexSafe(index).getSettings().get(KNN_INDEX))) + .map(Index::getName) + .collect(Collectors.toList()); + + if (!invalidIndexNames.isEmpty()) { + throw new KNNInvalidIndicesException( + invalidIndexNames, + "ClearCache request rejected. One or more indices have 'index.knn' set to false." + ); + } + } +} diff --git a/src/main/java/org/opensearch/knn/plugin/rest/RestDeleteModelHandler.java b/src/main/java/org/opensearch/knn/plugin/rest/RestDeleteModelHandler.java index 37074d128..31b589ae1 100644 --- a/src/main/java/org/opensearch/knn/plugin/rest/RestDeleteModelHandler.java +++ b/src/main/java/org/opensearch/knn/plugin/rest/RestDeleteModelHandler.java @@ -11,9 +11,9 @@ package org.opensearch.knn.plugin.rest; +import org.apache.commons.lang.StringUtils; import com.google.common.collect.ImmutableList; import org.opensearch.client.node.NodeClient; -import org.opensearch.common.Strings; import org.opensearch.knn.plugin.KNNPlugin; import org.opensearch.knn.plugin.transport.DeleteModelAction; import org.opensearch.knn.plugin.transport.DeleteModelRequest; @@ -58,7 +58,7 @@ public List routes() { @Override protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { String modelID = request.param(MODEL_ID); - if (!Strings.hasText(modelID)) { + if (StringUtils.isBlank(modelID)) { throw new IllegalArgumentException("model ID cannot be empty"); } DeleteModelRequest deleteModelRequest = new DeleteModelRequest(modelID); diff --git a/src/main/java/org/opensearch/knn/plugin/rest/RestGetModelHandler.java b/src/main/java/org/opensearch/knn/plugin/rest/RestGetModelHandler.java index 09f2daab2..8b1f0676b 100644 --- a/src/main/java/org/opensearch/knn/plugin/rest/RestGetModelHandler.java +++ b/src/main/java/org/opensearch/knn/plugin/rest/RestGetModelHandler.java @@ -12,8 +12,8 @@ package org.opensearch.knn.plugin.rest; import com.google.common.collect.ImmutableList; +import org.apache.commons.lang.StringUtils; import org.opensearch.client.node.NodeClient; -import org.opensearch.common.Strings; import org.opensearch.knn.plugin.KNNPlugin; import org.opensearch.knn.plugin.transport.GetModelAction; import org.opensearch.knn.plugin.transport.GetModelRequest; @@ -50,7 +50,7 @@ public List routes() { @Override protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) throws IOException { String modelID = restRequest.param(MODEL_ID); - if (!Strings.hasText(modelID)) { + if (StringUtils.isBlank(modelID)) { throw new IllegalArgumentException("model ID cannot be empty"); } diff --git a/src/main/java/org/opensearch/knn/plugin/rest/RestKNNStatsHandler.java b/src/main/java/org/opensearch/knn/plugin/rest/RestKNNStatsHandler.java index d5e991858..9049a83db 100644 --- a/src/main/java/org/opensearch/knn/plugin/rest/RestKNNStatsHandler.java +++ b/src/main/java/org/opensearch/knn/plugin/rest/RestKNNStatsHandler.java @@ -5,18 +5,14 @@ package org.opensearch.knn.plugin.rest; +import lombok.AllArgsConstructor; +import org.apache.commons.lang.StringUtils; import org.opensearch.knn.plugin.KNNPlugin; -import org.opensearch.knn.plugin.stats.KNNStats; import org.opensearch.knn.plugin.transport.KNNStatsAction; import org.opensearch.knn.plugin.transport.KNNStatsRequest; import com.google.common.collect.ImmutableList; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.opensearch.client.node.NodeClient; -import org.opensearch.common.Strings; -import org.opensearch.common.settings.Settings; import org.opensearch.rest.BaseRestHandler; -import org.opensearch.rest.RestController; import org.opensearch.rest.RestRequest; import org.opensearch.rest.action.RestActions; @@ -33,22 +29,9 @@ * Resthandler for stats api endpoint. The user has the ability to get all stats from * all nodes or select stats from specific nodes. */ +@AllArgsConstructor public class RestKNNStatsHandler extends BaseRestHandler { - - private static final Logger LOG = LogManager.getLogger(RestKNNStatsHandler.class); private static final String NAME = "knn_stats_action"; - private KNNStats knnStats; - - /** - * Constructor - * - * @param settings Settings - * @param controller Rest Controller - * @param knnStats KNNStats - */ - public RestKNNStatsHandler(Settings settings, RestController controller, KNNStats knnStats) { - this.knnStats = knnStats; - } @Override public String getName() { @@ -100,17 +83,17 @@ private KNNStatsRequest getRequest(RestRequest request) { // parse the nodes the user wants to query String[] nodeIdsArr = null; String nodesIdsStr = request.param("nodeId"); - if (!Strings.isEmpty(nodesIdsStr)) { + if (StringUtils.isNotEmpty(nodesIdsStr)) { nodeIdsArr = nodesIdsStr.split(","); } - KNNStatsRequest knnStatsRequest = new KNNStatsRequest(knnStats.getStats().keySet(), nodeIdsArr); + KNNStatsRequest knnStatsRequest = new KNNStatsRequest(nodeIdsArr); knnStatsRequest.timeout(request.param("timeout")); // parse the stats the customer wants to see Set statsSet = null; String statsStr = request.param("stat"); - if (!Strings.isEmpty(statsStr)) { + if (StringUtils.isNotEmpty(statsStr)) { statsSet = new HashSet<>(Arrays.asList(statsStr.split(","))); } diff --git a/src/main/java/org/opensearch/knn/plugin/rest/RestKNNWarmupHandler.java b/src/main/java/org/opensearch/knn/plugin/rest/RestKNNWarmupHandler.java index f457d6782..42991af13 100644 --- a/src/main/java/org/opensearch/knn/plugin/rest/RestKNNWarmupHandler.java +++ b/src/main/java/org/opensearch/knn/plugin/rest/RestKNNWarmupHandler.java @@ -5,6 +5,7 @@ package org.opensearch.knn.plugin.rest; +import org.apache.commons.lang.StringUtils; import org.opensearch.knn.common.exception.KNNInvalidIndicesException; import org.opensearch.knn.plugin.KNNPlugin; import org.opensearch.knn.plugin.transport.KNNWarmupAction; @@ -15,9 +16,8 @@ import org.opensearch.client.node.NodeClient; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; -import org.opensearch.index.Index; +import org.opensearch.core.index.Index; import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.RestController; import org.opensearch.rest.RestRequest; @@ -81,7 +81,7 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli } private KNNWarmupRequest createKNNWarmupRequest(RestRequest request) { - String[] indexNames = Strings.splitStringByCommaToArray(request.param("index")); + String[] indexNames = StringUtils.split(request.param("index"), ","); Index[] indices = indexNameExpressionResolver.concreteIndices(clusterService.state(), strictExpandOpen(), indexNames); List invalidIndexNames = new ArrayList<>(); diff --git a/src/main/java/org/opensearch/knn/plugin/rest/RestTrainModelHandler.java b/src/main/java/org/opensearch/knn/plugin/rest/RestTrainModelHandler.java index 9ddf7410b..4380310c3 100644 --- a/src/main/java/org/opensearch/knn/plugin/rest/RestTrainModelHandler.java +++ b/src/main/java/org/opensearch/knn/plugin/rest/RestTrainModelHandler.java @@ -13,9 +13,16 @@ import com.google.common.collect.ImmutableList; import org.opensearch.client.node.NodeClient; -import org.opensearch.common.xcontent.XContentParser; +import org.opensearch.core.xcontent.XContentParser; import org.opensearch.index.mapper.NumberFieldMapper; -import org.opensearch.knn.index.KNNMethodContext; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.SpaceTypeResolver; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; +import org.opensearch.knn.indices.ModelUtil; import org.opensearch.knn.plugin.KNNPlugin; import org.opensearch.knn.plugin.transport.TrainingJobRouterAction; import org.opensearch.knn.plugin.transport.TrainingModelRequest; @@ -27,17 +34,20 @@ import java.util.List; import java.util.Locale; -import static org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken; +import static org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken; +import static org.opensearch.knn.common.KNNConstants.COMPRESSION_LEVEL_PARAMETER; import static org.opensearch.knn.common.KNNConstants.DIMENSION; import static org.opensearch.knn.common.KNNConstants.KNN_METHOD; import static org.opensearch.knn.common.KNNConstants.MAX_VECTOR_COUNT_PARAMETER; import static org.opensearch.knn.common.KNNConstants.MODELS; import static org.opensearch.knn.common.KNNConstants.MODEL_DESCRIPTION; import static org.opensearch.knn.common.KNNConstants.MODEL_ID; +import static org.opensearch.knn.common.KNNConstants.MODE_PARAMETER; import static org.opensearch.knn.common.KNNConstants.PREFERENCE_PARAMETER; import static org.opensearch.knn.common.KNNConstants.SEARCH_SIZE_PARAMETER; import static org.opensearch.knn.common.KNNConstants.TRAIN_FIELD_PARAMETER; import static org.opensearch.knn.common.KNNConstants.TRAIN_INDEX_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; /** * Rest Handler for model training api endpoint. @@ -81,10 +91,15 @@ private TrainingModelRequest createTransportRequest(RestRequest restRequest) thr String trainingIndex = (String) DEFAULT_NOT_SET_OBJECT_VALUE; String trainingField = (String) DEFAULT_NOT_SET_OBJECT_VALUE; String description = (String) DEFAULT_NOT_SET_OBJECT_VALUE; + VectorDataType vectorDataType = (VectorDataType) DEFAULT_NOT_SET_OBJECT_VALUE; int dimension = DEFAULT_NOT_SET_INT_VALUE; int maximumVectorCount = DEFAULT_NOT_SET_INT_VALUE; int searchSize = DEFAULT_NOT_SET_INT_VALUE; + SpaceType topLevelSpaceType = SpaceType.UNDEFINED; + + String compressionLevel = null; + String mode = null; while (parser.nextToken() != XContentParser.Token.END_OBJECT) { String fieldName = parser.currentName(); @@ -104,13 +119,25 @@ private TrainingModelRequest createTransportRequest(RestRequest restRequest) thr searchSize = (Integer) NumberFieldMapper.NumberType.INTEGER.parse(parser.objectBytes(), false); } else if (MODEL_DESCRIPTION.equals(fieldName) && ensureNotSet(fieldName, description)) { description = parser.textOrNull(); - } else { - throw new IllegalArgumentException("Unable to parse token. \"" + fieldName + "\" is not a valid " + "parameter."); - } + ModelUtil.blockCommasInModelDescription(description); + } else if (VECTOR_DATA_TYPE_FIELD.equals(fieldName) && ensureNotSet(fieldName, vectorDataType)) { + vectorDataType = VectorDataType.get(parser.text()); + } else if (KNNConstants.MODE_PARAMETER.equals(fieldName) && ensureNotSet(fieldName, mode)) { + mode = parser.text(); + } else if (KNNConstants.COMPRESSION_LEVEL_PARAMETER.equals(fieldName) && ensureNotSet(fieldName, compressionLevel)) { + compressionLevel = parser.text(); + } else if ((KNNConstants.SPACE_TYPE.equals(fieldName) || KNNConstants.TOP_LEVEL_PARAMETER_SPACE_TYPE.equals(fieldName)) + && ensureSpaceTypeNotSet(topLevelSpaceType)) { + topLevelSpaceType = SpaceType.getSpace(parser.text()); + } else { + throw new IllegalArgumentException("Unable to parse token. \"" + fieldName + "\" is not a valid " + "parameter."); + } } - // Check that these parameters get set - ensureSet(KNN_METHOD, knnMethodContext); + ensureAtleastOneSet(KNN_METHOD, knnMethodContext, MODE_PARAMETER, mode, COMPRESSION_LEVEL_PARAMETER, compressionLevel); + ensureMutualExclusion(KNN_METHOD, knnMethodContext, MODE_PARAMETER, mode); + ensureMutualExclusion(KNN_METHOD, knnMethodContext, COMPRESSION_LEVEL_PARAMETER, compressionLevel); + ensureSet(DIMENSION, dimension); ensureSet(TRAIN_INDEX_PARAMETER, trainingIndex); ensureSet(TRAIN_FIELD_PARAMETER, trainingField); @@ -120,6 +147,26 @@ private TrainingModelRequest createTransportRequest(RestRequest restRequest) thr description = ""; } + if (vectorDataType == DEFAULT_NOT_SET_OBJECT_VALUE) { + vectorDataType = VectorDataType.DEFAULT; + } + + ensureIfSetThenEquals( + MODE_PARAMETER, + mode, + COMPRESSION_LEVEL_PARAMETER, + compressionLevel, + VECTOR_DATA_TYPE_FIELD, + VectorDataType.FLOAT, + vectorDataType, + VectorDataType.FLOAT.getValue() + ); + SpaceType resolvedSpaceType = SpaceTypeResolver.INSTANCE.resolveSpaceType( + knnMethodContext, + vectorDataType, + topLevelSpaceType.getValue() + ); + setSpaceType(knnMethodContext, resolvedSpaceType); TrainingModelRequest trainingModelRequest = new TrainingModelRequest( modelId, knnMethodContext, @@ -127,7 +174,11 @@ private TrainingModelRequest createTransportRequest(RestRequest restRequest) thr trainingIndex, trainingField, preferredNodeId, - description + description, + vectorDataType, + Mode.fromName(mode), + CompressionLevel.fromName(compressionLevel), + resolvedSpaceType ); if (maximumVectorCount != DEFAULT_NOT_SET_INT_VALUE) { @@ -153,6 +204,60 @@ private void ensureSet(String fieldName, int value) { } } + private void ensureMutualExclusion(String fieldNameA, Object valueA, String fieldNameB, Object valueB) { + if (valueA != DEFAULT_NOT_SET_OBJECT_VALUE && valueB != DEFAULT_NOT_SET_OBJECT_VALUE) { + throw new IllegalArgumentException( + String.format(Locale.ROOT, "\"[%s]\" and \"[%s]\" cannot both be set", fieldNameA, fieldNameB) + ); + } + } + + private boolean ensureSpaceTypeNotSet(SpaceType spaceType) { + if (spaceType != SpaceType.UNDEFINED) { + throw new IllegalArgumentException("Unable to parse SpaceType as it is duplicated."); + } + return true; + } + + private void setSpaceType(KNNMethodContext knnMethodContext, SpaceType resolvedSpaceType) { + if (knnMethodContext == null) { + return; + } + knnMethodContext.setSpaceType(resolvedSpaceType); + } + + private void ensureIfSetThenEquals( + String fieldNameA, + Object valueA, + String fieldNameB, + Object valueB, + String fieldNameC, + Object expectedValueC, + Object actualValueC, + String expectedValueCName + ) { + if ((valueA != DEFAULT_NOT_SET_OBJECT_VALUE || valueB != DEFAULT_NOT_SET_OBJECT_VALUE) && expectedValueC != actualValueC) { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "When \"[%s]\" or \"[%s]\" is set, \"[%s]\" must be set to \"[%s]\"", + fieldNameA, + fieldNameB, + fieldNameC, + expectedValueCName + ) + ); + } + } + + private void ensureAtleastOneSet(String fieldNameA, Object valueA, String fieldNameB, Object valueB, String fieldNameC, Object valueC) { + if (valueA == DEFAULT_NOT_SET_OBJECT_VALUE && valueB == DEFAULT_NOT_SET_OBJECT_VALUE && valueC == DEFAULT_NOT_SET_OBJECT_VALUE) { + throw new IllegalArgumentException( + String.format(Locale.ROOT, "At least \"[%s]\", \"[%s]\" or \"[%s]\" needs to be set", fieldNameA, fieldNameB, fieldNameC) + ); + } + } + private boolean ensureNotSet(String fieldName, Object value) { if (value != DEFAULT_NOT_SET_OBJECT_VALUE) { throw new IllegalArgumentException("Unable to parse token. \"" + fieldName + "\" is duplicated."); diff --git a/src/main/java/org/opensearch/knn/plugin/script/KNNWhitelistExtension.java b/src/main/java/org/opensearch/knn/plugin/script/KNNAllowlistExtension.java similarity index 83% rename from src/main/java/org/opensearch/knn/plugin/script/KNNWhitelistExtension.java rename to src/main/java/org/opensearch/knn/plugin/script/KNNAllowlistExtension.java index 4c0fe01a7..47b99af17 100644 --- a/src/main/java/org/opensearch/knn/plugin/script/KNNWhitelistExtension.java +++ b/src/main/java/org/opensearch/knn/plugin/script/KNNAllowlistExtension.java @@ -9,6 +9,7 @@ import org.opensearch.painless.spi.PainlessExtension; import org.opensearch.painless.spi.Whitelist; import org.opensearch.painless.spi.WhitelistLoader; +import org.opensearch.script.FieldScript; import org.opensearch.script.ScoreScript; import org.opensearch.script.ScriptContext; import org.opensearch.script.ScriptedMetricAggContexts; @@ -16,9 +17,9 @@ import java.util.List; import java.util.Map; -public class KNNWhitelistExtension implements PainlessExtension { +public class KNNAllowlistExtension implements PainlessExtension { - private static final Whitelist ALLOW_LIST = WhitelistLoader.loadFromResourceFiles(KNNWhitelistExtension.class, "knn_whitelist.txt"); + private static final Whitelist ALLOW_LIST = WhitelistLoader.loadFromResourceFiles(KNNAllowlistExtension.class, "knn_allowlist.txt"); @Override public Map, List> getContextWhitelists() { @@ -33,6 +34,8 @@ public Map, List> getContextWhitelists() { ScriptedMetricAggContexts.CombineScript.CONTEXT, allowLists, ScriptedMetricAggContexts.ReduceScript.CONTEXT, + allowLists, + FieldScript.CONTEXT, allowLists ); } diff --git a/src/main/java/org/opensearch/knn/plugin/script/KNNScoreScript.java b/src/main/java/org/opensearch/knn/plugin/script/KNNScoreScript.java index f190a3e1d..d7a84817b 100644 --- a/src/main/java/org/opensearch/knn/plugin/script/KNNScoreScript.java +++ b/src/main/java/org/opensearch/knn/plugin/script/KNNScoreScript.java @@ -5,6 +5,7 @@ package org.opensearch.knn.plugin.script; +import org.apache.lucene.search.IndexSearcher; import org.opensearch.knn.index.KNNVectorScriptDocValues; import org.apache.lucene.index.LeafReaderContext; import org.opensearch.index.fielddata.ScriptDocValues; @@ -32,9 +33,10 @@ public KNNScoreScript( String field, BiFunction scoringMethod, SearchLookup lookup, - LeafReaderContext leafContext + LeafReaderContext leafContext, + IndexSearcher searcher ) { - super(params, lookup, leafContext); + super(params, lookup, searcher, leafContext); this.queryValue = queryValue; this.field = field; this.scoringMethod = scoringMethod; @@ -51,9 +53,10 @@ public LongType( String field, BiFunction scoringMethod, SearchLookup lookup, - LeafReaderContext leafContext + LeafReaderContext leafContext, + IndexSearcher searcher ) { - super(params, queryValue, field, scoringMethod, lookup, leafContext); + super(params, queryValue, field, scoringMethod, lookup, leafContext, searcher); } /** @@ -84,9 +87,10 @@ public BigIntegerType( String field, BiFunction scoringMethod, SearchLookup lookup, - LeafReaderContext leafContext + LeafReaderContext leafContext, + IndexSearcher searcher ) { - super(params, queryValue, field, scoringMethod, lookup, leafContext); + super(params, queryValue, field, scoringMethod, lookup, leafContext, searcher); } /** @@ -118,9 +122,10 @@ public KNNVectorType( String field, BiFunction scoringMethod, SearchLookup lookup, - LeafReaderContext leafContext + LeafReaderContext leafContext, + IndexSearcher searcher ) throws IOException { - super(params, queryValue, field, scoringMethod, lookup, leafContext); + super(params, queryValue, field, scoringMethod, lookup, leafContext, searcher); } /** diff --git a/src/main/java/org/opensearch/knn/plugin/script/KNNScoreScriptFactory.java b/src/main/java/org/opensearch/knn/plugin/script/KNNScoreScriptFactory.java index b686a20f0..4f6a1a6c4 100644 --- a/src/main/java/org/opensearch/knn/plugin/script/KNNScoreScriptFactory.java +++ b/src/main/java/org/opensearch/knn/plugin/script/KNNScoreScriptFactory.java @@ -5,61 +5,22 @@ package org.opensearch.knn.plugin.script; -import org.opensearch.knn.plugin.stats.KNNCounter; -import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.search.IndexSearcher; import org.opensearch.script.ScoreScript; +import org.opensearch.script.ScriptFactory; import org.opensearch.search.lookup.SearchLookup; -import java.io.IOException; import java.util.Map; -public class KNNScoreScriptFactory implements ScoreScript.LeafFactory { - private final Map params; - private final SearchLookup lookup; - private String similaritySpace; - private String field; - private Object query; - private KNNScoringSpace knnScoringSpace; - - public KNNScoreScriptFactory(Map params, SearchLookup lookup) { - KNNCounter.SCRIPT_QUERY_REQUESTS.increment(); - this.params = params; - this.lookup = lookup; - this.field = getValue(params, "field").toString(); - this.similaritySpace = getValue(params, "space_type").toString(); - this.query = getValue(params, "query_value"); - - this.knnScoringSpace = KNNScoringSpaceFactory.create( - this.similaritySpace, - this.query, - lookup.doc().mapperService().fieldType(this.field) - ); - } - - private Object getValue(Map params, String fieldName) { - final Object value = params.get(fieldName); - if (value != null) return value; - - KNNCounter.SCRIPT_QUERY_ERRORS.increment(); - throw new IllegalArgumentException("Missing parameter [" + fieldName + "]"); - } - +public class KNNScoreScriptFactory implements ScoreScript.Factory, ScriptFactory { @Override - public boolean needs_score() { - return false; + public boolean isResultDeterministic() { + // This implies the results are cacheable + return true; } - /** - * For each segment, supply the KNNScoreScript that should be used to re-score the documents returned from the - * query. Because the method to score the documents was set during factory construction, the scripts are agnostic of - * the similarity space. The KNNScoringSpace will return the correct script, given the query, the field type, and - * the similarity space. - * - * @param ctx LeafReaderContext for the segment - * @return ScoreScript to be executed - */ @Override - public ScoreScript newInstance(LeafReaderContext ctx) throws IOException { - return knnScoringSpace.getScoreScript(params, field, lookup, ctx); + public ScoreScript.LeafFactory newFactory(Map params, SearchLookup lookup, IndexSearcher indexSearcher) { + return new KNNScoreScriptLeafFactory(params, lookup, indexSearcher); } } diff --git a/src/main/java/org/opensearch/knn/plugin/script/KNNScoreScriptLeafFactory.java b/src/main/java/org/opensearch/knn/plugin/script/KNNScoreScriptLeafFactory.java new file mode 100644 index 000000000..1caca0d4b --- /dev/null +++ b/src/main/java/org/opensearch/knn/plugin/script/KNNScoreScriptLeafFactory.java @@ -0,0 +1,73 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.plugin.script; + +import java.io.IOException; +import java.util.Locale; +import java.util.Map; + +import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.search.IndexSearcher; +import org.opensearch.knn.plugin.stats.KNNCounter; +import org.opensearch.script.ScoreScript; +import org.opensearch.search.lookup.SearchLookup; + +/* + * A factory that creates KNNScoreScriptLeafFactory objects. The factory is responsible for parsing the parameters + * passed in the query and creating the KNNScoreScriptLeafFactory object. + */ +public class KNNScoreScriptLeafFactory implements ScoreScript.LeafFactory { + private final Map params; + private final SearchLookup lookup; + private final String similaritySpace; + private final String field; + private final Object query; + private final KNNScoringSpace knnScoringSpace; + private final IndexSearcher searcher; + + public KNNScoreScriptLeafFactory(Map params, SearchLookup lookup, IndexSearcher searcher) { + KNNCounter.SCRIPT_QUERY_REQUESTS.increment(); + this.params = params; + this.lookup = lookup; + this.field = getValue(params, "field").toString(); + this.similaritySpace = getValue(params, "space_type").toString(); + this.query = getValue(params, "query_value"); + this.searcher = searcher; + + this.knnScoringSpace = KNNScoringSpaceFactory.create( + this.similaritySpace, + this.query, + lookup.doc().mapperService().fieldType(this.field) + ); + } + + private Object getValue(Map params, String fieldName) { + final Object value = params.get(fieldName); + if (value != null) return value; + + KNNCounter.SCRIPT_QUERY_ERRORS.increment(); + throw new IllegalArgumentException(String.format(Locale.ROOT, "Missing parameter [%s]", fieldName)); + } + + @Override + public boolean needs_score() { + return false; + } + + /** + * For each segment, supply the KNNScoreScript that should be used to re-score the documents returned from the + * query. Because the method to score the documents was set during factory construction, the scripts are agnostic of + * the similarity space. The KNNScoringSpace will return the correct script, given the query, the field type, and + * the similarity space. + * + * @param ctx LeafReaderContext for the segment + * @return ScoreScript to be executed + */ + @Override + public ScoreScript newInstance(LeafReaderContext ctx) throws IOException { + return knnScoringSpace.getScoreScript(params, field, lookup, ctx, this.searcher); + } +} diff --git a/src/main/java/org/opensearch/knn/plugin/script/KNNScoringScriptEngine.java b/src/main/java/org/opensearch/knn/plugin/script/KNNScoringScriptEngine.java index 2d6b5f69c..61b26c760 100644 --- a/src/main/java/org/opensearch/knn/plugin/script/KNNScoringScriptEngine.java +++ b/src/main/java/org/opensearch/knn/plugin/script/KNNScoringScriptEngine.java @@ -10,6 +10,7 @@ import org.opensearch.script.ScriptContext; import org.opensearch.script.ScriptEngine; +import java.util.Collections; import java.util.Map; import java.util.Set; @@ -38,12 +39,12 @@ public FactoryType compile(String name, String code, ScriptContext KNNCounter.SCRIPT_COMPILATION_ERRORS.increment(); throw new IllegalArgumentException("Unknown script name " + code); } - ScoreScript.Factory factory = KNNScoreScriptFactory::new; + ScoreScript.Factory factory = new KNNScoreScriptFactory(); return context.factoryClazz.cast(factory); } @Override public Set> getSupportedContexts() { - return null; + return Collections.singleton(ScoreScript.CONTEXT); } } diff --git a/src/main/java/org/opensearch/knn/plugin/script/KNNScoringSpace.java b/src/main/java/org/opensearch/knn/plugin/script/KNNScoringSpace.java index 3e066b7c9..71616c9fd 100644 --- a/src/main/java/org/opensearch/knn/plugin/script/KNNScoringSpace.java +++ b/src/main/java/org/opensearch/knn/plugin/script/KNNScoringSpace.java @@ -5,16 +5,23 @@ package org.opensearch.knn.plugin.script; -import org.opensearch.knn.index.KNNVectorFieldMapper; -import org.opensearch.knn.index.KNNWeight; +import lombok.Getter; import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.search.IndexSearcher; import org.opensearch.index.mapper.MappedFieldType; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.mapper.KNNVectorFieldMapperUtil; +import org.opensearch.knn.index.mapper.KNNVectorFieldType; +import org.opensearch.knn.index.query.KNNWeight; import org.opensearch.script.ScoreScript; import org.opensearch.search.lookup.SearchLookup; import java.io.IOException; import java.math.BigInteger; +import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.function.BiFunction; import static org.opensearch.knn.plugin.script.KNNScoringSpaceUtil.getVectorMagnitudeSquared; @@ -29,66 +36,170 @@ public interface KNNScoringSpace { /** * Return the correct scoring script for a given query. The scoring script * - * @param params Map of parameters - * @param field Fieldname - * @param lookup SearchLookup - * @param ctx ctx LeafReaderContext to be used for scoring documents + * @param params Map of parameters + * @param field field name + * @param lookup SearchLookup + * @param ctx ctx LeafReaderContext to be used for scoring documents + * @param searcher IndexSearcher * @return ScoreScript for this query * @throws IOException throws IOException if ScoreScript cannot be constructed */ - ScoreScript getScoreScript(Map params, String field, SearchLookup lookup, LeafReaderContext ctx) throws IOException; + ScoreScript getScoreScript(Map params, String field, SearchLookup lookup, LeafReaderContext ctx, IndexSearcher searcher) + throws IOException; - class L2 implements KNNScoringSpace { + /** + * Base class to represent vector space for knn field + */ + abstract class KNNFieldSpace implements KNNScoringSpace { + public static final Set DATA_TYPES_DEFAULT = Set.of(VectorDataType.FLOAT, VectorDataType.BYTE); - float[] processedQuery; - BiFunction scoringMethod; + private float[] processedQuery; + @Getter + private BiFunction scoringMethod; - /** - * Constructor for L2 scoring space. L2 scoring space expects values to be of type float[]. - * - * @param query Query object that, along with the doc values, will be used to compute L2 score - * @param fieldType FieldType for the doc values that will be used - */ - public L2(Object query, MappedFieldType fieldType) { - if (!isKNNVectorFieldType(fieldType)) { - throw new IllegalArgumentException("Incompatible field_type for l2 space. The field type must " + "be knn_vector."); + public KNNFieldSpace(final Object query, final MappedFieldType fieldType, final String spaceName) { + this(query, fieldType, spaceName, DATA_TYPES_DEFAULT); + } + + public KNNFieldSpace( + final Object query, + final MappedFieldType fieldType, + final String spaceName, + final Set supportingVectorDataTypes + ) { + KNNVectorFieldType knnVectorFieldType = toKNNVectorFieldType(fieldType, spaceName, supportingVectorDataTypes); + this.processedQuery = getProcessedQuery(query, knnVectorFieldType); + this.scoringMethod = getScoringMethod(this.processedQuery); + } + + public ScoreScript getScoreScript( + Map params, + String field, + SearchLookup lookup, + LeafReaderContext ctx, + IndexSearcher searcher + ) throws IOException { + return new KNNScoreScript.KNNVectorType(params, this.processedQuery, field, this.scoringMethod, lookup, ctx, searcher); + } + + private KNNVectorFieldType toKNNVectorFieldType( + final MappedFieldType fieldType, + final String spaceName, + final Set supportingVectorDataTypes + ) { + if (isKNNVectorFieldType(fieldType) == false) { + throw new IllegalArgumentException( + String.format(Locale.ROOT, "Incompatible field_type for %s space. The field type must be knn_vector.", spaceName) + ); } - this.processedQuery = parseToFloatArray(query, ((KNNVectorFieldMapper.KNNVectorFieldType) fieldType).getDimension()); - this.scoringMethod = (float[] q, float[] v) -> 1 / (1 + KNNScoringUtil.l2Squared(q, v)); + KNNVectorFieldType knnVectorFieldType = (KNNVectorFieldType) fieldType; + VectorDataType vectorDataType = knnVectorFieldType.getVectorDataType() == null + ? VectorDataType.FLOAT + : knnVectorFieldType.getVectorDataType(); + if (supportingVectorDataTypes.contains(vectorDataType) == false) { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "Incompatible field_type for %s space. The data type should be %s but got %s", + spaceName, + supportingVectorDataTypes, + vectorDataType + ) + ); + } + + return knnVectorFieldType; } - public ScoreScript getScoreScript(Map params, String field, SearchLookup lookup, LeafReaderContext ctx) - throws IOException { - return new KNNScoreScript.KNNVectorType(params, this.processedQuery, field, this.scoringMethod, lookup, ctx); + protected float[] getProcessedQuery(final Object query, final KNNVectorFieldType knnVectorFieldType) { + return parseToFloatArray( + query, + KNNVectorFieldMapperUtil.getExpectedVectorLength(knnVectorFieldType), + knnVectorFieldType.getVectorDataType() + ); } + + protected abstract BiFunction getScoringMethod(final float[] processedQuery); + } - class CosineSimilarity implements KNNScoringSpace { + class L2 extends KNNFieldSpace { + public L2(final Object query, final MappedFieldType fieldType) { + super(query, fieldType, "l2"); + } - float[] processedQuery; - BiFunction scoringMethod; + @Override + public BiFunction getScoringMethod(final float[] processedQuery) { + return (float[] q, float[] v) -> 1 / (1 + KNNScoringUtil.l2Squared(q, v)); + } + } - /** - * Constructor for CosineSimilarity scoring space. CosineSimilarity scoring space expects values to be of type - * float[]. - * - * @param query Query object that, along with the doc values, will be used to compute CosineSimilarity score - * @param fieldType FieldType for the doc values that will be used - */ + class CosineSimilarity extends KNNFieldSpace { public CosineSimilarity(Object query, MappedFieldType fieldType) { - if (!isKNNVectorFieldType(fieldType)) { - throw new IllegalArgumentException("Incompatible field_type for cosine space. The field type must " + "be knn_vector."); - } + super(query, fieldType, "cosine"); + } + + @Override + protected BiFunction getScoringMethod(final float[] processedQuery) { + SpaceType.COSINESIMIL.validateVector(processedQuery); + float qVectorSquaredMagnitude = getVectorMagnitudeSquared(processedQuery); + return (float[] q, float[] v) -> 1 + KNNScoringUtil.cosinesimilOptimized(q, v, qVectorSquaredMagnitude); + } + } + + class L1 extends KNNFieldSpace { + public L1(Object query, MappedFieldType fieldType) { + super(query, fieldType, "l1"); + } + + @Override + protected BiFunction getScoringMethod(final float[] processedQuery) { + return (float[] q, float[] v) -> 1 / (1 + KNNScoringUtil.l1Norm(q, v)); + } + } + + class LInf extends KNNFieldSpace { + public LInf(Object query, MappedFieldType fieldType) { + super(query, fieldType, "l-inf"); + } + + @Override + protected BiFunction getScoringMethod(final float[] processedQuery) { + return (float[] q, float[] v) -> 1 / (1 + KNNScoringUtil.lInfNorm(q, v)); + } + } + + class InnerProd extends KNNFieldSpace { + public InnerProd(Object query, MappedFieldType fieldType) { + super(query, fieldType, "innerproduct"); + } + + @Override + protected BiFunction getScoringMethod(final float[] processedQuery) { + return (float[] q, float[] v) -> KNNWeight.normalizeScore(-KNNScoringUtil.innerProduct(q, v)); + } + } + + class Hamming extends KNNFieldSpace { + private static final Set DATA_TYPES_HAMMING = Set.of(VectorDataType.BINARY); - this.processedQuery = parseToFloatArray(query, ((KNNVectorFieldMapper.KNNVectorFieldType) fieldType).getDimension()); - float qVectorSquaredMagnitude = getVectorMagnitudeSquared(this.processedQuery); - this.scoringMethod = (float[] q, float[] v) -> 1 + KNNScoringUtil.cosinesimilOptimized(q, v, qVectorSquaredMagnitude); + public Hamming(Object query, MappedFieldType fieldType) { + super(query, fieldType, "hamming", DATA_TYPES_HAMMING); + } + + @Override + protected BiFunction getScoringMethod(final float[] processedQuery) { + // TODO we want to avoid converting back and forth between byte and float + return (float[] q, float[] v) -> 1 / (1 + KNNScoringUtil.calculateHammingBit(toByte(q), toByte(v))); } - public ScoreScript getScoreScript(Map params, String field, SearchLookup lookup, LeafReaderContext ctx) - throws IOException { - return new KNNScoreScript.KNNVectorType(params, this.processedQuery, field, this.scoringMethod, lookup, ctx); + private byte[] toByte(final float[] vector) { + byte[] bytes = new byte[vector.length]; + for (int i = 0; i < vector.length; i++) { + bytes[i] = (byte) vector[i]; + } + return bytes; } } @@ -113,14 +224,19 @@ public HammingBit(Object query, MappedFieldType fieldType) { this.scoringMethod = (BigInteger q, BigInteger v) -> 1.0f / (1 + KNNScoringUtil.calculateHammingBit(q, v)); } else { throw new IllegalArgumentException( - "Incompatible field_type for hamming space. The field type must " + "of type long or binary." + "Incompatible field_type for hammingbit space. The field type must of type long or binary." ); } } @SuppressWarnings("unchecked") - public ScoreScript getScoreScript(Map params, String field, SearchLookup lookup, LeafReaderContext ctx) - throws IOException { + public ScoreScript getScoreScript( + Map params, + String field, + SearchLookup lookup, + LeafReaderContext ctx, + IndexSearcher searcher + ) throws IOException { if (this.processedQuery instanceof Long) { return new KNNScoreScript.LongType( params, @@ -128,7 +244,8 @@ public ScoreScript getScoreScript(Map params, String field, Sear field, (BiFunction) this.scoringMethod, lookup, - ctx + ctx, + searcher ); } @@ -138,89 +255,9 @@ public ScoreScript getScoreScript(Map params, String field, Sear field, (BiFunction) this.scoringMethod, lookup, - ctx + ctx, + searcher ); } } - - class L1 implements KNNScoringSpace { - - float[] processedQuery; - BiFunction scoringMethod; - - /** - * Constructor for L1 scoring space. L1 scoring space expects values to be of type float[]. - * - * @param query Query object that, along with the doc values, will be used to compute L1 score - * @param fieldType FieldType for the doc values that will be used - */ - public L1(Object query, MappedFieldType fieldType) { - if (!isKNNVectorFieldType(fieldType)) { - throw new IllegalArgumentException("Incompatible field_type for l1 space. The field type must " + "be knn_vector."); - } - - this.processedQuery = parseToFloatArray(query, ((KNNVectorFieldMapper.KNNVectorFieldType) fieldType).getDimension()); - this.scoringMethod = (float[] q, float[] v) -> 1 / (1 + KNNScoringUtil.l1Norm(q, v)); - } - - public ScoreScript getScoreScript(Map params, String field, SearchLookup lookup, LeafReaderContext ctx) - throws IOException { - return new KNNScoreScript.KNNVectorType(params, this.processedQuery, field, this.scoringMethod, lookup, ctx); - } - } - - class LInf implements KNNScoringSpace { - - float[] processedQuery; - BiFunction scoringMethod; - - /** - * Constructor for L-inf scoring space. L-inf scoring space expects values to be of type float[]. - * - * @param query Query object that, along with the doc values, will be used to compute L-inf score - * @param fieldType FieldType for the doc values that will be used - */ - public LInf(Object query, MappedFieldType fieldType) { - if (!isKNNVectorFieldType(fieldType)) { - throw new IllegalArgumentException("Incompatible field_type for l-inf space. The field type must " + "be knn_vector."); - } - - this.processedQuery = parseToFloatArray(query, ((KNNVectorFieldMapper.KNNVectorFieldType) fieldType).getDimension()); - this.scoringMethod = (float[] q, float[] v) -> 1 / (1 + KNNScoringUtil.lInfNorm(q, v)); - } - - public ScoreScript getScoreScript(Map params, String field, SearchLookup lookup, LeafReaderContext ctx) - throws IOException { - return new KNNScoreScript.KNNVectorType(params, this.processedQuery, field, this.scoringMethod, lookup, ctx); - } - } - - class InnerProd implements KNNScoringSpace { - - float[] processedQuery; - BiFunction scoringMethod; - - /** - * Constructor for innerproduct scoring space. innerproduct scoring space expects values to be of type float[]. - * - * @param query Query object that, along with the doc values, will be used to compute L-inf score - * @param fieldType FieldType for the doc values that will be used - */ - public InnerProd(Object query, MappedFieldType fieldType) { - if (!isKNNVectorFieldType(fieldType)) { - throw new IllegalArgumentException( - "Incompatible field_type for innerproduct space. The field type must " + "be knn_vector." - ); - } - - this.processedQuery = parseToFloatArray(query, ((KNNVectorFieldMapper.KNNVectorFieldType) fieldType).getDimension()); - this.scoringMethod = (float[] q, float[] v) -> KNNWeight.normalizeScore(-KNNScoringUtil.innerProduct(q, v)); - } - - @Override - public ScoreScript getScoreScript(Map params, String field, SearchLookup lookup, LeafReaderContext ctx) - throws IOException { - return new KNNScoreScript.KNNVectorType(params, this.processedQuery, field, this.scoringMethod, lookup, ctx); - } - } } diff --git a/src/main/java/org/opensearch/knn/plugin/script/KNNScoringSpaceFactory.java b/src/main/java/org/opensearch/knn/plugin/script/KNNScoringSpaceFactory.java index 8d0506f58..2508fc015 100644 --- a/src/main/java/org/opensearch/knn/plugin/script/KNNScoringSpaceFactory.java +++ b/src/main/java/org/opensearch/knn/plugin/script/KNNScoringSpaceFactory.java @@ -13,9 +13,11 @@ * Factory to create correct KNNScoringSpace based on the spaceType passed in. */ public class KNNScoringSpaceFactory { + public static final String HAMMING_BIT = "hammingbit"; + public static KNNScoringSpace create(String spaceType, Object query, MappedFieldType mappedFieldType) { - if (SpaceType.HAMMING_BIT.getValue().equalsIgnoreCase(spaceType)) { - return new KNNScoringSpace.HammingBit(query, mappedFieldType); + if (SpaceType.HAMMING.getValue().equalsIgnoreCase(spaceType)) { + return new KNNScoringSpace.Hamming(query, mappedFieldType); } if (SpaceType.L2.getValue().equalsIgnoreCase(spaceType)) { @@ -36,6 +38,10 @@ public static KNNScoringSpace create(String spaceType, Object query, MappedField return new KNNScoringSpace.CosineSimilarity(query, mappedFieldType); } + if (HAMMING_BIT.equalsIgnoreCase(spaceType)) { + return new KNNScoringSpace.HammingBit(query, mappedFieldType); + } + KNNCounter.SCRIPT_QUERY_ERRORS.increment(); throw new IllegalArgumentException("Invalid space type. Please refer to the available space types."); } diff --git a/src/main/java/org/opensearch/knn/plugin/script/KNNScoringSpaceUtil.java b/src/main/java/org/opensearch/knn/plugin/script/KNNScoringSpaceUtil.java index 4d64b5b96..7a97fdb05 100644 --- a/src/main/java/org/opensearch/knn/plugin/script/KNNScoringSpaceUtil.java +++ b/src/main/java/org/opensearch/knn/plugin/script/KNNScoringSpaceUtil.java @@ -5,18 +5,23 @@ package org.opensearch.knn.plugin.script; -import org.opensearch.knn.index.KNNVectorFieldMapper; +import java.util.List; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.mapper.KNNVectorFieldType; import org.opensearch.knn.plugin.stats.KNNCounter; import org.opensearch.index.mapper.BinaryFieldMapper; import org.opensearch.index.mapper.MappedFieldType; import org.opensearch.index.mapper.NumberFieldMapper; import java.math.BigInteger; -import java.util.ArrayList; import java.util.Base64; import static org.opensearch.index.mapper.NumberFieldMapper.NumberType.LONG; +import static org.opensearch.knn.common.KNNValidationUtil.validateByteVectorValue; +/** + * Utility class for KNNScoringSpace + */ public class KNNScoringSpaceUtil { /** @@ -47,7 +52,17 @@ public static boolean isBinaryFieldType(MappedFieldType fieldType) { * @return true if fieldType is of type KNNVectorFieldType; false otherwise */ public static boolean isKNNVectorFieldType(MappedFieldType fieldType) { - return fieldType instanceof KNNVectorFieldMapper.KNNVectorFieldType; + return fieldType instanceof KNNVectorFieldType; + } + + /** + * Check if the KNN field type is a binary vector data type + * + * @param fieldType KNN vector field type + * @return true if the KNN field type is a binary vector data type + */ + public static boolean isBinaryVectorDataType(final KNNVectorFieldType fieldType) { + return VectorDataType.BINARY == fieldType.getVectorDataType(); } /** @@ -82,15 +97,15 @@ public static BigInteger parseToBigInteger(Object object) { * Convert an Object to a float array. * * @param object Object to be converted to a float array - * @param expectedDimensions int representing the expected dimension of this array. + * @param expectedVectorLength int representing the expected vector length of this array. * @return float[] of the object */ - public static float[] parseToFloatArray(Object object, int expectedDimensions) { - float[] floatArray = convertVectorToPrimitive(object); - if (expectedDimensions != floatArray.length) { + public static float[] parseToFloatArray(Object object, int expectedVectorLength, VectorDataType vectorDataType) { + float[] floatArray = convertVectorToPrimitive(object, vectorDataType); + if (expectedVectorLength != floatArray.length) { KNNCounter.SCRIPT_QUERY_ERRORS.increment(); throw new IllegalStateException( - "Object's dimension=" + floatArray.length + " does not match the " + "expected dimension=" + expectedDimensions + "." + "Object's length=" + floatArray.length + " does not match the " + "expected length=" + expectedVectorLength + "." ); } return floatArray; @@ -103,13 +118,17 @@ public static float[] parseToFloatArray(Object object, int expectedDimensions) { * @return Float array representing the vector */ @SuppressWarnings("unchecked") - public static float[] convertVectorToPrimitive(Object vector) { + public static float[] convertVectorToPrimitive(Object vector, VectorDataType vectorDataType) { float[] primitiveVector = null; if (vector != null) { - final ArrayList tmp = (ArrayList) vector; + final List tmp = (List) vector; primitiveVector = new float[tmp.size()]; for (int i = 0; i < primitiveVector.length; i++) { - primitiveVector[i] = tmp.get(i).floatValue(); + float value = tmp.get(i).floatValue(); + if (VectorDataType.BYTE == vectorDataType || VectorDataType.BINARY == vectorDataType) { + validateByteVectorValue(value, vectorDataType); + } + primitiveVector[i] = value; } } return primitiveVector; diff --git a/src/main/java/org/opensearch/knn/plugin/script/KNNScoringUtil.java b/src/main/java/org/opensearch/knn/plugin/script/KNNScoringUtil.java index 5ec462933..f61ae4349 100644 --- a/src/main/java/org/opensearch/knn/plugin/script/KNNScoringUtil.java +++ b/src/main/java/org/opensearch/knn/plugin/script/KNNScoringUtil.java @@ -5,13 +5,18 @@ package org.opensearch.knn.plugin.script; -import org.opensearch.knn.index.KNNVectorScriptDocValues; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - import java.math.BigInteger; import java.util.List; +import java.util.Locale; import java.util.Objects; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.lucene.util.VectorUtil; +import org.opensearch.knn.index.KNNVectorScriptDocValues; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; + +import static org.opensearch.knn.common.KNNValidationUtil.validateByteVectorValue; public class KNNScoringUtil { private static Logger logger = LogManager.getLogger(KNNScoringUtil.class); @@ -36,6 +41,52 @@ private static void requireEqualDimension(final float[] queryVector, final float } } + /** + * checks both query vector and input vector has equal dimension + * + * @param queryVector query vector + * @param inputVector input vector + * @throws IllegalArgumentException if query vector and input vector has different dimensions + */ + private static void requireEqualDimension(final byte[] queryVector, final byte[] inputVector) { + Objects.requireNonNull(queryVector); + Objects.requireNonNull(inputVector); + if (queryVector.length != inputVector.length) { + String errorMessage = String.format( + "query vector dimension mismatch. Expected: %d, Given: %d", + inputVector.length, + queryVector.length + ); + throw new IllegalArgumentException(errorMessage); + } + } + + private static void requireNonBinaryType(final String spaceName, final VectorDataType vectorDataType) { + if (VectorDataType.BINARY == vectorDataType) { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "Incompatible field_type for %s space. The data type should be either float or byte but got %s", + spaceName, + vectorDataType.getValue() + ) + ); + } + } + + private static void requireBinaryType(final String spaceName, final VectorDataType vectorDataType) { + if (VectorDataType.BINARY != vectorDataType) { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "Incompatible field_type for %s space. The data type should be binary but got %s", + spaceName, + vectorDataType.getValue() + ) + ); + } + } + /** * This method calculates L2 squared distance between query vector * and input vector @@ -45,106 +96,69 @@ private static void requireEqualDimension(final float[] queryVector, final float * @return L2 score */ public static float l2Squared(float[] queryVector, float[] inputVector) { - requireEqualDimension(queryVector, inputVector); - float squaredDistance = 0; - for (int i = 0; i < inputVector.length; i++) { - float diff = queryVector[i] - inputVector[i]; - squaredDistance += diff * diff; - } - return squaredDistance; + return VectorUtil.squareDistance(queryVector, inputVector); } - private static float[] toFloat(List inputVector) { + private static float[] toFloat(final List inputVector, final VectorDataType vectorDataType) { Objects.requireNonNull(inputVector); float[] value = new float[inputVector.size()]; int index = 0; for (final Number val : inputVector) { - value[index++] = val.floatValue(); + float floatValue = val.floatValue(); + if (VectorDataType.BYTE == vectorDataType || VectorDataType.BINARY == vectorDataType) { + validateByteVectorValue(floatValue, vectorDataType); + } + value[index++] = floatValue; } return value; } - /** - * Whitelisted l2Squared method for users to calculate L2 squared distance between query vector - * and document vectors - * Example - * "script": { - * "source": "1/(1 + l2Squared(params.query_vector, doc[params.field]))", - * "params": { - * "query_vector": [1, 2, 3.4], - * "field": "my_dense_vector" - * } - * } - * - * @param queryVector query vector - * @param docValues script doc values - * @return L2 score - */ - public static float l2Squared(List queryVector, KNNVectorScriptDocValues docValues) { - return l2Squared(toFloat(queryVector), docValues.getValue()); + private static byte[] toByte(final List inputVector, final VectorDataType vectorDataType) { + Objects.requireNonNull(inputVector); + byte[] value = new byte[inputVector.size()]; + int index = 0; + for (final Number val : inputVector) { + float floatValue = val.floatValue(); + if (VectorDataType.BYTE == vectorDataType || VectorDataType.BINARY == vectorDataType) { + validateByteVectorValue(floatValue, vectorDataType); + } + value[index++] = val.byteValue(); + } + return value; } /** - * This method can be used script to avoid repeated calculation of normalization - * for query vector for each filtered documents + * This method calculates cosine similarity * - * @param queryVector query vector - * @param inputVector input vector - * @param normQueryVector normalized query vector value. + * @param queryVector query vector + * @param inputVector input vector * @return cosine score */ - public static float cosinesimilOptimized(float[] queryVector, float[] inputVector, float normQueryVector) { + public static float cosinesimil(float[] queryVector, float[] inputVector) { requireEqualDimension(queryVector, inputVector); - float dotProduct = 0.0f; - float normInputVector = 0.0f; - for (int i = 0; i < queryVector.length; i++) { - dotProduct += queryVector[i] * inputVector[i]; - normInputVector += inputVector[i] * inputVector[i]; - } - float normalizedProduct = normQueryVector * normInputVector; - if (normalizedProduct == 0) { + try { + return VectorUtil.cosine(queryVector, inputVector); + } catch (IllegalArgumentException | AssertionError e) { logger.debug("Invalid vectors for cosine. Returning minimum score to put this result to end"); return 0.0f; } - return (float) (dotProduct / (Math.sqrt(normalizedProduct))); } /** - * Whitelisted cosineSimilarity method that can be used in a script to avoid repeated - * calculation of normalization for the query vector. - * Example: - * "script": { - * "source": "cosineSimilarity(params.query_vector, docs[field], 1.0) ", - * "params": { - * "query_vector": [1, 2, 3.4], - * "field": "my_dense_vector" - * } - * } - * - * @param queryVector query vector - * @param docValues script doc values - * @param queryVectorMagnitude the magnitude of the query vector. - * @return cosine score - */ - public static float cosineSimilarity(List queryVector, KNNVectorScriptDocValues docValues, Number queryVectorMagnitude) { - return cosinesimilOptimized(toFloat(queryVector), docValues.getValue(), queryVectorMagnitude.floatValue()); - } - - /** - * This method calculates cosine similarity + * This method can be used script to avoid repeated calculation of normalization + * for query vector for each filtered documents * - * @param queryVector query vector - * @param inputVector input vector + * @param queryVector query vector + * @param inputVector input vector + * @param normQueryVector normalized query vector value. * @return cosine score */ - public static float cosinesimil(float[] queryVector, float[] inputVector) { + public static float cosinesimilOptimized(float[] queryVector, float[] inputVector, float normQueryVector) { requireEqualDimension(queryVector, inputVector); float dotProduct = 0.0f; - float normQueryVector = 0.0f; float normInputVector = 0.0f; for (int i = 0; i < queryVector.length; i++) { dotProduct += queryVector[i] * inputVector[i]; - normQueryVector += queryVector[i] * queryVector[i]; normInputVector += inputVector[i] * inputVector[i]; } float normalizedProduct = normQueryVector * normInputVector; @@ -155,26 +169,6 @@ public static float cosinesimil(float[] queryVector, float[] inputVector) { return (float) (dotProduct / (Math.sqrt(normalizedProduct))); } - /** - * Whitelisted cosineSimilarity method for users to calculate cosine similarity between query vectors and - * document vectors - * Example: - * "script": { - * "source": "cosineSimilarity(params.query_vector, docs[field]) ", - * "params": { - * "query_vector": [1, 2, 3.4], - * "field": "my_dense_vector" - * } - * } - * - * @param queryVector query vector - * @param docValues script doc values - * @return cosine score - */ - public static float cosineSimilarity(List queryVector, KNNVectorScriptDocValues docValues) { - return cosinesimil(toFloat(queryVector), docValues.getValue()); - } - /** * This method calculates hamming distance on 2 BigIntegers * @@ -197,6 +191,19 @@ public static float calculateHammingBit(Long queryLong, Long inputLong) { return Long.bitCount(queryLong ^ inputLong); } + /** + * This method calculates hamming distance between query vector + * and input vector + * + * @param queryVector query vector + * @param inputVector input vector + * @return hamming distance + */ + public static float calculateHammingBit(byte[] queryVector, byte[] inputVector) { + requireEqualDimension(queryVector, inputVector); + return VectorUtil.xorBitCount(queryVector, inputVector); + } + /** * This method calculates L1 distance between query vector * and input vector @@ -215,26 +222,6 @@ public static float l1Norm(float[] queryVector, float[] inputVector) { return distance; } - /** - * Whitelisted l1distance method for users to calculate L1 distance between query vector - * and document vectors - * Example - * "script": { - * "source": "1/(1 + l1Norm(params.query_vector, doc[params.field]))", - * "params": { - * "query_vector": [1, 2, 3.4], - * "field": "my_dense_vector" - * } - * } - * - * @param queryVector query vector - * @param docValues script doc values - * @return L1 score - */ - public static float l1Norm(List queryVector, KNNVectorScriptDocValues docValues) { - return l1Norm(toFloat(queryVector), docValues.getValue()); - } - /** * This method calculates L-inf distance between query vector * and input vector @@ -254,7 +241,47 @@ public static float lInfNorm(float[] queryVector, float[] inputVector) { } /** - * Whitelisted lInfNorm method for users to calculate L-inf distance between query vector + * This method calculates dot product distance between query vector + * and input vector + * + * @param queryVector query vector + * @param inputVector input vector + * @return dot product score + */ + public static float innerProduct(float[] queryVector, float[] inputVector) { + requireEqualDimension(queryVector, inputVector); + return VectorUtil.dotProduct(queryVector, inputVector); + } + + /** + ********************************************************************************************* + * Functions to be used in painless script which is defined in knn_allowlist.txt + ********************************************************************************************* + */ + + /** + * Allowlisted l2Squared method for users to calculate L2 squared distance between query vector + * and document vectors + * Example + * "script": { + * "source": "1/(1 + l2Squared(params.query_vector, doc[params.field]))", + * "params": { + * "query_vector": [1, 2, 3.4], + * "field": "my_dense_vector" + * } + * } + * + * @param queryVector query vector + * @param docValues script doc values + * @return L2 score + */ + public static float l2Squared(List queryVector, KNNVectorScriptDocValues docValues) { + requireNonBinaryType("l2Squared", docValues.getVectorDataType()); + return l2Squared(toFloat(queryVector, docValues.getVectorDataType()), docValues.getValue()); + } + + /** + * Allowlisted lInfNorm method for users to calculate L-inf distance between query vector * and document vectors * Example * "script": { @@ -270,28 +297,33 @@ public static float lInfNorm(float[] queryVector, float[] inputVector) { * @return L-inf score */ public static float lInfNorm(List queryVector, KNNVectorScriptDocValues docValues) { - return lInfNorm(toFloat(queryVector), docValues.getValue()); + requireNonBinaryType("lInfNorm", docValues.getVectorDataType()); + return lInfNorm(toFloat(queryVector, docValues.getVectorDataType()), docValues.getValue()); } /** - * This method calculates dot product distance between query vector - * and input vector + * Allowlisted l1distance method for users to calculate L1 distance between query vector + * and document vectors + * Example + * "script": { + * "source": "1/(1 + l1Norm(params.query_vector, doc[params.field]))", + * "params": { + * "query_vector": [1, 2, 3.4], + * "field": "my_dense_vector" + * } + * } * * @param queryVector query vector - * @param inputVector input vector - * @return dot product score + * @param docValues script doc values + * @return L1 score */ - public static float innerProduct(float[] queryVector, float[] inputVector) { - requireEqualDimension(queryVector, inputVector); - float distance = 0; - for (int i = 0; i < inputVector.length; i++) { - distance += queryVector[i] * inputVector[i]; - } - return distance; + public static float l1Norm(List queryVector, KNNVectorScriptDocValues docValues) { + requireNonBinaryType("l1Norm", docValues.getVectorDataType()); + return l1Norm(toFloat(queryVector, docValues.getVectorDataType()), docValues.getValue()); } /** - * Whitelisted innerProd method for users to calculate inner product distance between query vector + * Allowlisted innerProd method for users to calculate inner product distance between query vector * and document vectors * Example * "script": { @@ -307,6 +339,84 @@ public static float innerProduct(float[] queryVector, float[] inputVector) { * @return inner product score */ public static float innerProduct(List queryVector, KNNVectorScriptDocValues docValues) { - return innerProduct(toFloat(queryVector), docValues.getValue()); + requireNonBinaryType("innerProduct", docValues.getVectorDataType()); + return innerProduct(toFloat(queryVector, docValues.getVectorDataType()), docValues.getValue()); + } + + /** + * Allowlisted cosineSimilarity method for users to calculate cosine similarity between query vectors and + * document vectors + * Example: + * "script": { + * "source": "cosineSimilarity(params.query_vector, docs[field]) ", + * "params": { + * "query_vector": [1, 2, 3.4], + * "field": "my_dense_vector" + * } + * } + * + * @param queryVector query vector + * @param docValues script doc values + * @return cosine score + */ + public static float cosineSimilarity(List queryVector, KNNVectorScriptDocValues docValues) { + requireNonBinaryType("cosineSimilarity", docValues.getVectorDataType()); + float[] inputVector = toFloat(queryVector, docValues.getVectorDataType()); + SpaceType.COSINESIMIL.validateVector(inputVector); + return cosinesimil(inputVector, docValues.getValue()); + } + + /** + * Allowlisted cosineSimilarity method that can be used in a script to avoid repeated + * calculation of normalization for the query vector. + * Example: + * "script": { + * "source": "cosineSimilarity(params.query_vector, docs[field], 1.0) ", + * "params": { + * "query_vector": [1, 2, 3.4], + * "field": "my_dense_vector" + * } + * } + * + * @param queryVector query vector + * @param docValues script doc values + * @param queryVectorMagnitude the magnitude of the query vector. + * @return cosine score + */ + public static float cosineSimilarity(List queryVector, KNNVectorScriptDocValues docValues, Number queryVectorMagnitude) { + requireNonBinaryType("cosineSimilarity", docValues.getVectorDataType()); + float[] inputVector = toFloat(queryVector, docValues.getVectorDataType()); + SpaceType.COSINESIMIL.validateVector(inputVector); + return cosinesimilOptimized(inputVector, docValues.getValue(), queryVectorMagnitude.floatValue()); + } + + /** + * Allowlisted hamming method that can be used in a script to avoid repeated + * calculation of normalization for the query vector. + * Example: + * "script": { + * "source": "hamming(params.query_vector, docs[field]) ", + * "params": { + * "query_vector": [1, 2], + * "field": "my_dense_vector" + * } + * } + * + * @param queryVector query vector + * @param docValues script doc values + * @return hamming score + */ + public static float hamming(List queryVector, KNNVectorScriptDocValues docValues) { + requireBinaryType("hamming", docValues.getVectorDataType()); + byte[] queryVectorInByte = toByte(queryVector, docValues.getVectorDataType()); + + // TODO Optimization need be done for doc value to return byte[] instead of float[] + float[] docVectorInFloat = docValues.getValue(); + byte[] docVectorInByte = new byte[docVectorInFloat.length]; + for (int i = 0; i < docVectorInByte.length; i++) { + docVectorInByte[i] = (byte) docVectorInFloat[i]; + } + + return calculateHammingBit(queryVectorInByte, docVectorInByte); } } diff --git a/src/main/java/org/opensearch/knn/plugin/search/KNNConcurrentSearchRequestDecider.java b/src/main/java/org/opensearch/knn/plugin/search/KNNConcurrentSearchRequestDecider.java new file mode 100644 index 000000000..92b0a3e3c --- /dev/null +++ b/src/main/java/org/opensearch/knn/plugin/search/KNNConcurrentSearchRequestDecider.java @@ -0,0 +1,65 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.plugin.search; + +import lombok.EqualsAndHashCode; +import org.opensearch.index.IndexSettings; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.query.KNNQueryBuilder; +import org.opensearch.search.deciders.ConcurrentSearchDecision; +import org.opensearch.search.deciders.ConcurrentSearchRequestDecider; + +import java.util.Optional; + +/** + * Decides if the knn query uses concurrent segment search + * As of 2.17, this is only used when + * - "index.search.concurrent_segment_search.mode": "auto" or + * - "search.concurrent_segment_search.mode": "auto" + * + * Note: the class is not thread-safe and a new instance needs to be created for each request + */ +@EqualsAndHashCode(callSuper = true) +public class KNNConcurrentSearchRequestDecider extends ConcurrentSearchRequestDecider { + + private static final ConcurrentSearchDecision DEFAULT_KNN_DECISION = new ConcurrentSearchDecision( + ConcurrentSearchDecision.DecisionStatus.NO_OP, + "Default decision" + ); + private static final ConcurrentSearchDecision YES = new ConcurrentSearchDecision( + ConcurrentSearchDecision.DecisionStatus.YES, + "Enable concurrent search for knn as Query has k-NN query in it and index is k-nn index" + ); + + private ConcurrentSearchDecision knnDecision = DEFAULT_KNN_DECISION; + + @Override + public void evaluateForQuery(final QueryBuilder queryBuilder, final IndexSettings indexSettings) { + if (queryBuilder instanceof KNNQueryBuilder && indexSettings.getValue(KNNSettings.IS_KNN_INDEX_SETTING)) { + knnDecision = YES; + } else { + knnDecision = DEFAULT_KNN_DECISION; + } + } + + @Override + public ConcurrentSearchDecision getConcurrentSearchDecision() { + return knnDecision; + } + + /** + * Returns {@link KNNConcurrentSearchRequestDecider} when index.knn is true + */ + public static class Factory implements ConcurrentSearchRequestDecider.Factory { + public Optional create(final IndexSettings indexSettings) { + if (indexSettings.getValue(KNNSettings.IS_KNN_INDEX_SETTING)) { + return Optional.of(new KNNConcurrentSearchRequestDecider()); + } + return Optional.empty(); + } + } +} diff --git a/src/main/java/org/opensearch/knn/plugin/stats/KNNCounter.java b/src/main/java/org/opensearch/knn/plugin/stats/KNNCounter.java index d933ce66d..3bcc3399c 100644 --- a/src/main/java/org/opensearch/knn/plugin/stats/KNNCounter.java +++ b/src/main/java/org/opensearch/knn/plugin/stats/KNNCounter.java @@ -21,7 +21,12 @@ public enum KNNCounter { SCRIPT_QUERY_REQUESTS("script_query_requests"), SCRIPT_QUERY_ERRORS("script_query_errors"), TRAINING_REQUESTS("training_requests"), - TRAINING_ERRORS("training_errors"); + TRAINING_ERRORS("training_errors"), + KNN_QUERY_WITH_FILTER_REQUESTS("knn_query_with_filter_requests"), + MIN_SCORE_QUERY_REQUESTS("min_score_query_requests"), + MIN_SCORE_QUERY_WITH_FILTER_REQUESTS("min_score_query_with_filter_requests"), + MAX_DISTANCE_QUERY_REQUESTS("max_distance_query_requests"), + MAX_DISTANCE_QUERY_WITH_FILTER_REQUESTS("max_distance_query_with_filter_requests"); private String name; private AtomicLong count; diff --git a/src/main/java/org/opensearch/knn/plugin/stats/KNNGraphValue.java b/src/main/java/org/opensearch/knn/plugin/stats/KNNGraphValue.java new file mode 100644 index 000000000..b33b59e36 --- /dev/null +++ b/src/main/java/org/opensearch/knn/plugin/stats/KNNGraphValue.java @@ -0,0 +1,95 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.plugin.stats; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * Contains a map to keep track of different graph values + */ +public enum KNNGraphValue { + + REFRESH_TOTAL_OPERATIONS("total"), + REFRESH_TOTAL_TIME_IN_MILLIS("total_time_in_millis"), + MERGE_CURRENT_OPERATIONS("current"), + MERGE_CURRENT_DOCS("current_docs"), + MERGE_CURRENT_SIZE_IN_BYTES("current_size_in_bytes"), + MERGE_TOTAL_OPERATIONS("total"), + MERGE_TOTAL_TIME_IN_MILLIS("total_time_in_millis"), + MERGE_TOTAL_DOCS("total_docs"), + MERGE_TOTAL_SIZE_IN_BYTES("total_size_in_bytes"); + + private String name; + private AtomicLong value; + + /** + * Constructor + * + * @param name name of the graph value + */ + KNNGraphValue(String name) { + this.name = name; + this.value = new AtomicLong(0); + } + + /** + * Get name of value + * + * @return name + */ + public String getName() { + return name; + } + + /** + * Get the graph value + * + * @return value + */ + public Long getValue() { + return value.get(); + } + + /** + * Increment the graph value + */ + public void increment() { + value.getAndIncrement(); + } + + /** + * Decrement the graph value + */ + public void decrement() { + value.getAndDecrement(); + } + + /** + * Increment the graph value by a specified amount + * + * @param delta The amount to increment + */ + public void incrementBy(long delta) { + value.getAndAdd(delta); + } + + /** + * Decrement the graph value by a specified amount + * + * @param delta The amount to decrement + */ + public void decrementBy(long delta) { + value.set(value.get() - delta); + } + + /** + * @param value graph value + * Set the graph value + */ + public void set(long value) { + this.value.set(value); + } +} diff --git a/src/main/java/org/opensearch/knn/plugin/stats/KNNStats.java b/src/main/java/org/opensearch/knn/plugin/stats/KNNStats.java index a2b35083a..bcd419ea6 100644 --- a/src/main/java/org/opensearch/knn/plugin/stats/KNNStats.java +++ b/src/main/java/org/opensearch/knn/plugin/stats/KNNStats.java @@ -5,23 +5,39 @@ package org.opensearch.knn.plugin.stats; +import com.google.common.cache.CacheStats; +import com.google.common.collect.ImmutableMap; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.memory.NativeMemoryCacheManager; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.indices.ModelCache; +import org.opensearch.knn.indices.ModelDao; +import org.opensearch.knn.plugin.stats.suppliers.EventOccurredWithinThresholdSupplier; +import org.opensearch.knn.plugin.stats.suppliers.KNNCircuitBreakerSupplier; +import org.opensearch.knn.plugin.stats.suppliers.KNNCounterSupplier; +import org.opensearch.knn.plugin.stats.suppliers.KNNInnerCacheStatsSupplier; +import org.opensearch.knn.plugin.stats.suppliers.LibraryInitializedSupplier; +import org.opensearch.knn.plugin.stats.suppliers.ModelIndexStatusSupplier; +import org.opensearch.knn.plugin.stats.suppliers.ModelIndexingDegradingSupplier; +import org.opensearch.knn.plugin.stats.suppliers.NativeMemoryCacheManagerSupplier; + +import java.time.temporal.ChronoUnit; import java.util.HashMap; import java.util.Map; +import java.util.function.Supplier; /** * Class represents all stats the plugin keeps track of */ public class KNNStats { - private Map> knnStats; + private final Map> knnStats; /** * Constructor - * - * @param knnStats Map that maps name of stat to KNNStat object */ - public KNNStats(Map> knnStats) { - this.knnStats = knnStats; + public KNNStats() { + this.knnStats = buildStatsMap(); } /** @@ -33,20 +49,6 @@ public Map> getStats() { return knnStats; } - /** - * Get individual stat by stat name - * - * @param key Name of stat - * @return ADStat - * @throws IllegalArgumentException thrown on illegal statName - */ - public KNNStat getStat(String key) throws IllegalArgumentException { - if (!knnStats.keySet().contains(key)) { - throw new IllegalArgumentException("Stat=\"" + key + "\" does not exist"); - } - return knnStats.get(key); - } - /** * Get a map of the stats that are kept at the node level * @@ -75,4 +77,145 @@ private Map> getClusterOrNodeStats(Boolean getClusterStats) { } return statsMap; } + + private Map> buildStatsMap() { + ImmutableMap.Builder> builder = ImmutableMap.>builder(); + addQueryStats(builder); + addNativeMemoryStats(builder); + addEngineStats(builder); + addScriptStats(builder); + addModelStats(builder); + addGraphStats(builder); + return builder.build(); + } + + private void addQueryStats(ImmutableMap.Builder> builder) { + // KNN Query Stats + builder.put(StatNames.KNN_QUERY_REQUESTS.getName(), new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.KNN_QUERY_REQUESTS))) + .put( + StatNames.KNN_QUERY_WITH_FILTER_REQUESTS.getName(), + new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.KNN_QUERY_WITH_FILTER_REQUESTS)) + ); + + // Min Score Query Stats + builder.put( + StatNames.MIN_SCORE_QUERY_REQUESTS.getName(), + new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.MIN_SCORE_QUERY_REQUESTS)) + ) + .put( + StatNames.MIN_SCORE_QUERY_WITH_FILTER_REQUESTS.getName(), + new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.MIN_SCORE_QUERY_WITH_FILTER_REQUESTS)) + ); + + // Max Distance Query Stats + builder.put( + StatNames.MAX_DISTANCE_QUERY_REQUESTS.getName(), + new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.MAX_DISTANCE_QUERY_REQUESTS)) + ) + .put( + StatNames.MAX_DISTANCE_QUERY_WITH_FILTER_REQUESTS.getName(), + new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.MAX_DISTANCE_QUERY_WITH_FILTER_REQUESTS)) + ); + } + + private void addNativeMemoryStats(ImmutableMap.Builder> builder) { + builder.put(StatNames.HIT_COUNT.getName(), new KNNStat<>(false, new KNNInnerCacheStatsSupplier(CacheStats::hitCount))) + .put(StatNames.MISS_COUNT.getName(), new KNNStat<>(false, new KNNInnerCacheStatsSupplier(CacheStats::missCount))) + .put(StatNames.LOAD_SUCCESS_COUNT.getName(), new KNNStat<>(false, new KNNInnerCacheStatsSupplier(CacheStats::loadSuccessCount))) + .put( + StatNames.LOAD_EXCEPTION_COUNT.getName(), + new KNNStat<>(false, new KNNInnerCacheStatsSupplier(CacheStats::loadExceptionCount)) + ) + .put(StatNames.TOTAL_LOAD_TIME.getName(), new KNNStat<>(false, new KNNInnerCacheStatsSupplier(CacheStats::totalLoadTime))) + .put(StatNames.EVICTION_COUNT.getName(), new KNNStat<>(false, new KNNInnerCacheStatsSupplier(CacheStats::evictionCount))) + .put( + StatNames.GRAPH_MEMORY_USAGE.getName(), + new KNNStat<>(false, new NativeMemoryCacheManagerSupplier<>(NativeMemoryCacheManager::getIndicesSizeInKilobytes)) + ) + .put( + StatNames.GRAPH_MEMORY_USAGE_PERCENTAGE.getName(), + new KNNStat<>(false, new NativeMemoryCacheManagerSupplier<>(NativeMemoryCacheManager::getIndicesSizeAsPercentage)) + ) + .put( + StatNames.INDICES_IN_CACHE.getName(), + new KNNStat<>(false, new NativeMemoryCacheManagerSupplier<>(NativeMemoryCacheManager::getIndicesCacheStats)) + ) + .put( + StatNames.CACHE_CAPACITY_REACHED.getName(), + new KNNStat<>(false, new NativeMemoryCacheManagerSupplier<>(NativeMemoryCacheManager::isCacheCapacityReached)) + ) + .put(StatNames.GRAPH_QUERY_ERRORS.getName(), new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.GRAPH_QUERY_ERRORS))) + .put(StatNames.GRAPH_QUERY_REQUESTS.getName(), new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.GRAPH_QUERY_REQUESTS))) + .put(StatNames.GRAPH_INDEX_ERRORS.getName(), new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.GRAPH_INDEX_ERRORS))) + .put(StatNames.GRAPH_INDEX_REQUESTS.getName(), new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.GRAPH_INDEX_REQUESTS))) + .put(StatNames.CIRCUIT_BREAKER_TRIGGERED.getName(), new KNNStat<>(true, new KNNCircuitBreakerSupplier())); + } + + private void addEngineStats(ImmutableMap.Builder> builder) { + builder.put(StatNames.FAISS_LOADED.getName(), new KNNStat<>(false, new LibraryInitializedSupplier(KNNEngine.FAISS))) + .put(StatNames.NMSLIB_LOADED.getName(), new KNNStat<>(false, new LibraryInitializedSupplier(KNNEngine.NMSLIB))) + .put(StatNames.LUCENE_LOADED.getName(), new KNNStat<>(false, new LibraryInitializedSupplier(KNNEngine.LUCENE))); + } + + private void addScriptStats(ImmutableMap.Builder> builder) { + builder.put(StatNames.SCRIPT_COMPILATIONS.getName(), new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.SCRIPT_COMPILATIONS))) + .put( + StatNames.SCRIPT_COMPILATION_ERRORS.getName(), + new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.SCRIPT_COMPILATION_ERRORS)) + ) + .put(StatNames.SCRIPT_QUERY_REQUESTS.getName(), new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.SCRIPT_QUERY_REQUESTS))) + .put(StatNames.SCRIPT_QUERY_ERRORS.getName(), new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.SCRIPT_QUERY_ERRORS))); + } + + private void addModelStats(ImmutableMap.Builder> builder) { + builder.put( + StatNames.INDEXING_FROM_MODEL_DEGRADED.getName(), + new KNNStat<>( + false, + new EventOccurredWithinThresholdSupplier( + new ModelIndexingDegradingSupplier(ModelCache::getEvictedDueToSizeAt), + KNNConstants.MODEL_CACHE_CAPACITY_ATROPHY_THRESHOLD_IN_MINUTES, + ChronoUnit.MINUTES + ) + ) + ) + .put(StatNames.MODEL_INDEX_STATUS.getName(), new KNNStat<>(true, new ModelIndexStatusSupplier<>(ModelDao::getHealthStatus))) + .put(StatNames.TRAINING_REQUESTS.getName(), new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.TRAINING_REQUESTS))) + .put(StatNames.TRAINING_ERRORS.getName(), new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.TRAINING_ERRORS))) + .put( + StatNames.TRAINING_MEMORY_USAGE.getName(), + new KNNStat<>(false, new NativeMemoryCacheManagerSupplier<>(NativeMemoryCacheManager::getTrainingSizeInKilobytes)) + ) + .put( + StatNames.TRAINING_MEMORY_USAGE_PERCENTAGE.getName(), + new KNNStat<>(false, new NativeMemoryCacheManagerSupplier<>(NativeMemoryCacheManager::getTrainingSizeAsPercentage)) + ); + } + + private void addGraphStats(ImmutableMap.Builder> builder) { + builder.put(StatNames.GRAPH_STATS.getName(), new KNNStat<>(false, new Supplier>>() { + @Override + public Map> get() { + return createGraphStatsMap(); + } + })); + } + + private Map> createGraphStatsMap() { + Map mergeMap = new HashMap<>(); + mergeMap.put(KNNGraphValue.MERGE_CURRENT_OPERATIONS.getName(), KNNGraphValue.MERGE_CURRENT_OPERATIONS.getValue()); + mergeMap.put(KNNGraphValue.MERGE_CURRENT_DOCS.getName(), KNNGraphValue.MERGE_CURRENT_DOCS.getValue()); + mergeMap.put(KNNGraphValue.MERGE_CURRENT_SIZE_IN_BYTES.getName(), KNNGraphValue.MERGE_CURRENT_SIZE_IN_BYTES.getValue()); + mergeMap.put(KNNGraphValue.MERGE_TOTAL_OPERATIONS.getName(), KNNGraphValue.MERGE_TOTAL_OPERATIONS.getValue()); + mergeMap.put(KNNGraphValue.MERGE_TOTAL_TIME_IN_MILLIS.getName(), KNNGraphValue.MERGE_TOTAL_TIME_IN_MILLIS.getValue()); + mergeMap.put(KNNGraphValue.MERGE_TOTAL_DOCS.getName(), KNNGraphValue.MERGE_TOTAL_DOCS.getValue()); + mergeMap.put(KNNGraphValue.MERGE_TOTAL_SIZE_IN_BYTES.getName(), KNNGraphValue.MERGE_TOTAL_SIZE_IN_BYTES.getValue()); + Map refreshMap = new HashMap<>(); + refreshMap.put(KNNGraphValue.REFRESH_TOTAL_OPERATIONS.getName(), KNNGraphValue.REFRESH_TOTAL_OPERATIONS.getValue()); + refreshMap.put(KNNGraphValue.REFRESH_TOTAL_TIME_IN_MILLIS.getName(), KNNGraphValue.REFRESH_TOTAL_TIME_IN_MILLIS.getValue()); + Map> graphStatsMap = new HashMap<>(); + graphStatsMap.put(StatNames.MERGE.getName(), mergeMap); + graphStatsMap.put(StatNames.REFRESH.getName(), refreshMap); + return graphStatsMap; + } } diff --git a/src/main/java/org/opensearch/knn/plugin/stats/KNNStatsConfig.java b/src/main/java/org/opensearch/knn/plugin/stats/KNNStatsConfig.java deleted file mode 100644 index 56089ed84..000000000 --- a/src/main/java/org/opensearch/knn/plugin/stats/KNNStatsConfig.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.opensearch.knn.plugin.stats; - -import com.google.common.cache.CacheStats; -import com.google.common.collect.ImmutableMap; -import org.opensearch.knn.common.KNNConstants; -import org.opensearch.knn.index.memory.NativeMemoryCacheManager; -import org.opensearch.knn.index.util.KNNEngine; -import org.opensearch.knn.indices.ModelCache; -import org.opensearch.knn.indices.ModelDao; -import org.opensearch.knn.plugin.stats.suppliers.LibraryInitializedSupplier; -import org.opensearch.knn.plugin.stats.suppliers.EventOccurredWithinThresholdSupplier; -import org.opensearch.knn.plugin.stats.suppliers.KNNCircuitBreakerSupplier; -import org.opensearch.knn.plugin.stats.suppliers.KNNCounterSupplier; -import org.opensearch.knn.plugin.stats.suppliers.KNNInnerCacheStatsSupplier; -import org.opensearch.knn.plugin.stats.suppliers.ModelIndexStatusSupplier; -import org.opensearch.knn.plugin.stats.suppliers.ModelIndexingDegradingSupplier; -import org.opensearch.knn.plugin.stats.suppliers.NativeMemoryCacheManagerSupplier; - -import java.time.temporal.ChronoUnit; -import java.util.Map; - -public class KNNStatsConfig { - public static Map> KNN_STATS = ImmutableMap.>builder() - .put(StatNames.HIT_COUNT.getName(), new KNNStat<>(false, new KNNInnerCacheStatsSupplier(CacheStats::hitCount))) - .put(StatNames.MISS_COUNT.getName(), new KNNStat<>(false, new KNNInnerCacheStatsSupplier(CacheStats::missCount))) - .put(StatNames.LOAD_SUCCESS_COUNT.getName(), new KNNStat<>(false, new KNNInnerCacheStatsSupplier(CacheStats::loadSuccessCount))) - .put(StatNames.LOAD_EXCEPTION_COUNT.getName(), new KNNStat<>(false, new KNNInnerCacheStatsSupplier(CacheStats::loadExceptionCount))) - .put(StatNames.TOTAL_LOAD_TIME.getName(), new KNNStat<>(false, new KNNInnerCacheStatsSupplier(CacheStats::totalLoadTime))) - .put(StatNames.EVICTION_COUNT.getName(), new KNNStat<>(false, new KNNInnerCacheStatsSupplier(CacheStats::evictionCount))) - .put( - StatNames.GRAPH_MEMORY_USAGE.getName(), - new KNNStat<>(false, new NativeMemoryCacheManagerSupplier<>(NativeMemoryCacheManager::getIndicesSizeInKilobytes)) - ) - .put( - StatNames.GRAPH_MEMORY_USAGE_PERCENTAGE.getName(), - new KNNStat<>(false, new NativeMemoryCacheManagerSupplier<>(NativeMemoryCacheManager::getIndicesSizeAsPercentage)) - ) - .put( - StatNames.INDICES_IN_CACHE.getName(), - new KNNStat<>(false, new NativeMemoryCacheManagerSupplier<>(NativeMemoryCacheManager::getIndicesCacheStats)) - ) - .put( - StatNames.CACHE_CAPACITY_REACHED.getName(), - new KNNStat<>(false, new NativeMemoryCacheManagerSupplier<>(NativeMemoryCacheManager::isCacheCapacityReached)) - ) - .put(StatNames.GRAPH_QUERY_ERRORS.getName(), new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.GRAPH_QUERY_ERRORS))) - .put(StatNames.GRAPH_QUERY_REQUESTS.getName(), new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.GRAPH_QUERY_REQUESTS))) - .put(StatNames.GRAPH_INDEX_ERRORS.getName(), new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.GRAPH_INDEX_ERRORS))) - .put(StatNames.GRAPH_INDEX_REQUESTS.getName(), new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.GRAPH_INDEX_REQUESTS))) - .put(StatNames.CIRCUIT_BREAKER_TRIGGERED.getName(), new KNNStat<>(true, new KNNCircuitBreakerSupplier())) - .put(StatNames.MODEL_INDEX_STATUS.getName(), new KNNStat<>(true, new ModelIndexStatusSupplier<>(ModelDao::getHealthStatus))) - .put(StatNames.KNN_QUERY_REQUESTS.getName(), new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.KNN_QUERY_REQUESTS))) - .put(StatNames.SCRIPT_COMPILATIONS.getName(), new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.SCRIPT_COMPILATIONS))) - .put( - StatNames.SCRIPT_COMPILATION_ERRORS.getName(), - new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.SCRIPT_COMPILATION_ERRORS)) - ) - .put(StatNames.SCRIPT_QUERY_REQUESTS.getName(), new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.SCRIPT_QUERY_REQUESTS))) - .put(StatNames.SCRIPT_QUERY_ERRORS.getName(), new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.SCRIPT_QUERY_ERRORS))) - .put( - StatNames.INDEXING_FROM_MODEL_DEGRADED.getName(), - new KNNStat<>( - false, - new EventOccurredWithinThresholdSupplier( - new ModelIndexingDegradingSupplier(ModelCache::getEvictedDueToSizeAt), - KNNConstants.MODEL_CACHE_CAPACITY_ATROPHY_THRESHOLD_IN_MINUTES, - ChronoUnit.MINUTES - ) - ) - ) - .put(StatNames.FAISS_LOADED.getName(), new KNNStat<>(false, new LibraryInitializedSupplier(KNNEngine.FAISS))) - .put(StatNames.NMSLIB_LOADED.getName(), new KNNStat<>(false, new LibraryInitializedSupplier(KNNEngine.NMSLIB))) - .put(StatNames.TRAINING_REQUESTS.getName(), new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.TRAINING_REQUESTS))) - .put(StatNames.TRAINING_ERRORS.getName(), new KNNStat<>(false, new KNNCounterSupplier(KNNCounter.TRAINING_ERRORS))) - .put( - StatNames.TRAINING_MEMORY_USAGE.getName(), - new KNNStat<>(false, new NativeMemoryCacheManagerSupplier<>(NativeMemoryCacheManager::getTrainingSizeInKilobytes)) - ) - .put( - StatNames.TRAINING_MEMORY_USAGE_PERCENTAGE.getName(), - new KNNStat<>(false, new NativeMemoryCacheManagerSupplier<>(NativeMemoryCacheManager::getTrainingSizeAsPercentage)) - ) - .build(); -} diff --git a/src/main/java/org/opensearch/knn/plugin/stats/StatNames.java b/src/main/java/org/opensearch/knn/plugin/stats/StatNames.java index f807c3eaa..e7f4fd4a2 100644 --- a/src/main/java/org/opensearch/knn/plugin/stats/StatNames.java +++ b/src/main/java/org/opensearch/knn/plugin/stats/StatNames.java @@ -26,6 +26,7 @@ public enum StatNames { MODEL_INDEX_STATUS("model_index_status"), FAISS_LOADED("faiss_initialized"), NMSLIB_LOADED("nmslib_initialized"), + LUCENE_LOADED("lucene_initialized"), INDEXING_FROM_MODEL_DEGRADED("indexing_from_model_degraded"), GRAPH_QUERY_ERRORS(KNNCounter.GRAPH_QUERY_ERRORS.getName()), GRAPH_QUERY_REQUESTS(KNNCounter.GRAPH_QUERY_REQUESTS.getName()), @@ -39,7 +40,15 @@ public enum StatNames { TRAINING_ERRORS(KNNCounter.TRAINING_ERRORS.getName()), TRAINING_MEMORY_USAGE("training_memory_usage"), TRAINING_MEMORY_USAGE_PERCENTAGE("training_memory_usage_percentage"), - SCRIPT_QUERY_ERRORS(KNNCounter.SCRIPT_QUERY_ERRORS.getName()); + SCRIPT_QUERY_ERRORS(KNNCounter.SCRIPT_QUERY_ERRORS.getName()), + KNN_QUERY_WITH_FILTER_REQUESTS(KNNCounter.KNN_QUERY_WITH_FILTER_REQUESTS.getName()), + GRAPH_STATS("graph_stats"), + REFRESH("refresh"), + MERGE("merge"), + MIN_SCORE_QUERY_REQUESTS(KNNCounter.MIN_SCORE_QUERY_REQUESTS.getName()), + MIN_SCORE_QUERY_WITH_FILTER_REQUESTS(KNNCounter.MIN_SCORE_QUERY_WITH_FILTER_REQUESTS.getName()), + MAX_DISTANCE_QUERY_REQUESTS(KNNCounter.MAX_DISTANCE_QUERY_REQUESTS.getName()), + MAX_DISTANCE_QUERY_WITH_FILTER_REQUESTS(KNNCounter.MAX_DISTANCE_QUERY_WITH_FILTER_REQUESTS.getName()); private String name; diff --git a/src/main/java/org/opensearch/knn/plugin/stats/suppliers/LibraryInitializedSupplier.java b/src/main/java/org/opensearch/knn/plugin/stats/suppliers/LibraryInitializedSupplier.java index 800c1a827..efed0ac7c 100644 --- a/src/main/java/org/opensearch/knn/plugin/stats/suppliers/LibraryInitializedSupplier.java +++ b/src/main/java/org/opensearch/knn/plugin/stats/suppliers/LibraryInitializedSupplier.java @@ -11,7 +11,7 @@ package org.opensearch.knn.plugin.stats.suppliers; -import org.opensearch.knn.index.util.KNNLibrary; +import org.opensearch.knn.index.engine.KNNLibrary; import java.util.function.Supplier; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/ClearCacheAction.java b/src/main/java/org/opensearch/knn/plugin/transport/ClearCacheAction.java new file mode 100644 index 000000000..358027a8b --- /dev/null +++ b/src/main/java/org/opensearch/knn/plugin/transport/ClearCacheAction.java @@ -0,0 +1,27 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.plugin.transport; + +import org.opensearch.action.ActionType; +import org.opensearch.core.common.io.stream.Writeable; + +/** + * Action associated with ClearCache + */ +public class ClearCacheAction extends ActionType { + + public static final ClearCacheAction INSTANCE = new ClearCacheAction(); + public static final String NAME = "cluster:admin/clear_cache_action"; + + private ClearCacheAction() { + super(NAME, ClearCacheResponse::new); + } + + @Override + public Writeable.Reader getResponseReader() { + return ClearCacheResponse::new; + } +} diff --git a/src/main/java/org/opensearch/knn/plugin/transport/ClearCacheRequest.java b/src/main/java/org/opensearch/knn/plugin/transport/ClearCacheRequest.java new file mode 100644 index 000000000..029b9babe --- /dev/null +++ b/src/main/java/org/opensearch/knn/plugin/transport/ClearCacheRequest.java @@ -0,0 +1,36 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.plugin.transport; + +import org.opensearch.action.support.broadcast.BroadcastRequest; +import org.opensearch.core.common.io.stream.StreamInput; + +import java.io.IOException; + +/** + * Clear Cache Request. This request contains a list of indices which needs to be evicted from Cache. + */ +public class ClearCacheRequest extends BroadcastRequest { + + /** + * Constructor + * + * @param in input stream + * @throws IOException if read from stream fails + */ + public ClearCacheRequest(StreamInput in) throws IOException { + super(in); + } + + /** + * Constructor + * + * @param indices list of indices which needs to be evicted from cache + */ + public ClearCacheRequest(String... indices) { + super(indices); + } +} diff --git a/src/main/java/org/opensearch/knn/plugin/transport/ClearCacheResponse.java b/src/main/java/org/opensearch/knn/plugin/transport/ClearCacheResponse.java new file mode 100644 index 000000000..3da7b0738 --- /dev/null +++ b/src/main/java/org/opensearch/knn/plugin/transport/ClearCacheResponse.java @@ -0,0 +1,49 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.plugin.transport; + +import org.opensearch.core.action.support.DefaultShardOperationFailedException; +import org.opensearch.action.support.broadcast.BroadcastResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.xcontent.ToXContentObject; + +import java.io.IOException; +import java.util.List; + +/** + * {@link ClearCacheResponse} represents Response returned by {@link ClearCacheRequest}. + * Returns total number of shards on which ClearCache was performed on, as well as + * the number of shards that succeeded and the number of shards that failed. + */ +public class ClearCacheResponse extends BroadcastResponse implements ToXContentObject { + + /** + * Constructor + * + * @param in input stream + * @throws IOException if read from stream fails + */ + public ClearCacheResponse(StreamInput in) throws IOException { + super(in); + } + + /** + * Constructor + * + * @param totalShards total number of shards on which ClearCache was performed + * @param successfulShards number of shards that succeeded + * @param failedShards number of shards that failed + * @param shardFailures list of shard failure exceptions + */ + public ClearCacheResponse( + int totalShards, + int successfulShards, + int failedShards, + List shardFailures + ) { + super(totalShards, successfulShards, failedShards, shardFailures); + } +} diff --git a/src/main/java/org/opensearch/knn/plugin/transport/ClearCacheTransportAction.java b/src/main/java/org/opensearch/knn/plugin/transport/ClearCacheTransportAction.java new file mode 100644 index 000000000..4a294e73b --- /dev/null +++ b/src/main/java/org/opensearch/knn/plugin/transport/ClearCacheTransportAction.java @@ -0,0 +1,164 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.plugin.transport; + +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.broadcast.node.TransportBroadcastByNodeAction; +import org.opensearch.cluster.ClusterState; +import org.opensearch.cluster.block.ClusterBlockException; +import org.opensearch.cluster.block.ClusterBlockLevel; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.routing.ShardRouting; +import org.opensearch.cluster.routing.ShardsIterator; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.inject.Inject; +import org.opensearch.core.action.support.DefaultShardOperationFailedException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.index.Index; +import org.opensearch.index.IndexService; +import org.opensearch.index.shard.IndexShard; +import org.opensearch.indices.IndicesService; +import org.opensearch.knn.index.KNNIndexShard; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.TransportService; + +import java.io.IOException; +import java.util.List; + +/** + * Transport Action to evict k-NN indices from Cache. TransportBroadcastByNodeAction will distribute the request to + * all shards across the cluster for the given indices. For each shard, shardOperation will be called and the + * indices will be cleared from cache. + */ +public class ClearCacheTransportAction extends TransportBroadcastByNodeAction< + ClearCacheRequest, + ClearCacheResponse, + TransportBroadcastByNodeAction.EmptyResult> { + + private IndicesService indicesService; + + /** + * Constructor + * + * @param clusterService ClusterService + * @param transportService TransportService + * @param actionFilters ActionFilters + * @param indexNameExpressionResolver IndexNameExpressionResolver + * @param indicesService IndicesService + */ + @Inject + public ClearCacheTransportAction( + ClusterService clusterService, + TransportService transportService, + ActionFilters actionFilters, + IndexNameExpressionResolver indexNameExpressionResolver, + IndicesService indicesService + ) { + super( + ClearCacheAction.NAME, + clusterService, + transportService, + actionFilters, + indexNameExpressionResolver, + ClearCacheRequest::new, + ThreadPool.Names.SEARCH + ); + this.indicesService = indicesService; + } + + /** + * @param streamInput StreamInput + * @return EmptyResult + * @throws IOException + */ + @Override + protected EmptyResult readShardResult(StreamInput streamInput) throws IOException { + return EmptyResult.readEmptyResultFrom(streamInput); + } + + /** + * @param request ClearCacheRequest + * @param totalShards total number of shards on which ClearCache was performed + * @param successfulShards number of shards that succeeded + * @param failedShards number of shards that failed + * @param emptyResults List of EmptyResult + * @param shardFailures list of shard failure exceptions + * @param clusterState ClusterState + * @return {@link ClearCacheResponse} + */ + @Override + protected ClearCacheResponse newResponse( + ClearCacheRequest request, + int totalShards, + int successfulShards, + int failedShards, + List emptyResults, + List shardFailures, + ClusterState clusterState + ) { + return new ClearCacheResponse(totalShards, successfulShards, failedShards, shardFailures); + } + + /** + * @param streamInput StreamInput + * @return {@link ClearCacheRequest} + * @throws IOException + */ + @Override + protected ClearCacheRequest readRequestFrom(StreamInput streamInput) throws IOException { + return new ClearCacheRequest(streamInput); + } + + /** + * Operation performed at a shard level on all the shards of given index where the index is removed from the cache. + * + * @param request ClearCacheRequest + * @param shardRouting ShardRouting of given shard + * @return EmptyResult + * @throws IOException + */ + @Override + protected EmptyResult shardOperation(ClearCacheRequest request, ShardRouting shardRouting) throws IOException { + Index index = shardRouting.shardId().getIndex(); + IndexService indexService = indicesService.indexServiceSafe(index); + IndexShard indexShard = indexService.getShard(shardRouting.shardId().id()); + KNNIndexShard knnIndexShard = new KNNIndexShard(indexShard); + knnIndexShard.clearCache(); + return EmptyResult.INSTANCE; + } + + /** + * @param clusterState ClusterState + * @param request ClearCacheRequest + * @param concreteIndices Indices in the request + * @return ShardsIterator with all the shards for given concrete indices + */ + @Override + protected ShardsIterator shards(ClusterState clusterState, ClearCacheRequest request, String[] concreteIndices) { + return clusterState.routingTable().allShards(concreteIndices); + } + + /** + * @param clusterState ClusterState + * @param request ClearCacheRequest + * @return ClusterBlockException if there is any global cluster block at a cluster block level of "METADATA_WRITE" + */ + @Override + protected ClusterBlockException checkGlobalBlock(ClusterState clusterState, ClearCacheRequest request) { + return clusterState.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE); + } + + /** + * @param clusterState ClusterState + * @param request ClearCacheRequest + * @param concreteIndices Indices in the request + * @return ClusterBlockException if there is any cluster block on any of the given indices at a cluster block level of "METADATA_WRITE" + */ + @Override + protected ClusterBlockException checkRequestBlock(ClusterState clusterState, ClearCacheRequest request, String[] concreteIndices) { + return clusterState.blocks().indicesBlockedException(ClusterBlockLevel.METADATA_WRITE, concreteIndices); + } +} diff --git a/src/main/java/org/opensearch/knn/plugin/transport/DeleteModelAction.java b/src/main/java/org/opensearch/knn/plugin/transport/DeleteModelAction.java index f58728368..33be3fde3 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/DeleteModelAction.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/DeleteModelAction.java @@ -12,7 +12,7 @@ package org.opensearch.knn.plugin.transport; import org.opensearch.action.ActionType; -import org.opensearch.common.io.stream.Writeable; +import org.opensearch.core.common.io.stream.Writeable; public class DeleteModelAction extends ActionType { diff --git a/src/main/java/org/opensearch/knn/plugin/transport/DeleteModelRequest.java b/src/main/java/org/opensearch/knn/plugin/transport/DeleteModelRequest.java index 792ccc543..dbc274160 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/DeleteModelRequest.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/DeleteModelRequest.java @@ -11,11 +11,11 @@ package org.opensearch.knn.plugin.transport; +import org.apache.commons.lang.StringUtils; import org.opensearch.action.ActionRequest; import org.opensearch.action.ActionRequestValidationException; -import org.opensearch.common.Strings; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; import java.io.IOException; @@ -43,7 +43,7 @@ public void writeTo(StreamOutput out) throws IOException { @Override public ActionRequestValidationException validate() { - if (Strings.hasText(modelID)) { + if (StringUtils.isNotBlank(modelID)) { return null; } return addValidationError("Model id cannot be empty ", null); diff --git a/src/main/java/org/opensearch/knn/plugin/transport/DeleteModelResponse.java b/src/main/java/org/opensearch/knn/plugin/transport/DeleteModelResponse.java index af63fd1e7..35f5f6cf2 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/DeleteModelResponse.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/DeleteModelResponse.java @@ -10,13 +10,12 @@ */ package org.opensearch.knn.plugin.transport; -import org.opensearch.action.ActionResponse; import org.opensearch.common.Nullable; -import org.opensearch.common.Strings; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; -import org.opensearch.common.xcontent.ToXContentObject; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; import java.io.IOException; @@ -29,16 +28,40 @@ public class DeleteModelResponse extends ActionResponse implements ToXContentObj public static final String RESULT = "result"; public static final String ERROR_MSG = "error"; + private static final String DELETED = "deleted"; private final String modelID; private final String result; private final String errorMessage; + /** + * Ctor to build delete model response. + * @deprecated + * Returning errors through {@link DeleteModelResponse} should not be done. Instead, if there is an + * error, throw/return a suitable exception. Use {@link DeleteModelResponse#DeleteModelResponse(String)} to + * construct valid responses instead. + * + * @param modelID ID of the model that is deleted + * @param result Resulting action of the deletion. + * @param errorMessage Error message to be returned to the user + */ + @Deprecated public DeleteModelResponse(String modelID, String result, @Nullable String errorMessage) { this.modelID = modelID; this.result = result; this.errorMessage = errorMessage; } + /** + * Ctor to build delete model response + * + * @param modelID ID of the model that is deleted + */ + public DeleteModelResponse(String modelID) { + this.modelID = modelID; + this.result = DELETED; + this.errorMessage = null; + } + public DeleteModelResponse(StreamInput in) throws IOException { super(in); this.modelID = in.readString(); @@ -63,16 +86,12 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws /* Response should look like below: { "model_id": "my_model_id" - "result": "not_found", - "error": "Model my_model_id doesn't exist" + "result": "deleted" } */ builder.startObject(); builder.field(MODEL_ID, getModelID()); builder.field(RESULT, getResult()); - if (Strings.hasText(errorMessage)) { - builder.field(ERROR_MSG, getErrorMessage()); - } builder.endObject(); return builder; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/DeleteModelTransportAction.java b/src/main/java/org/opensearch/knn/plugin/transport/DeleteModelTransportAction.java index ee7f9e939..9828b5025 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/DeleteModelTransportAction.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/DeleteModelTransportAction.java @@ -11,7 +11,8 @@ package org.opensearch.knn.plugin.transport; -import org.opensearch.action.ActionListener; +import lombok.extern.log4j.Log4j2; +import org.opensearch.core.action.ActionListener; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; import org.opensearch.common.inject.Inject; @@ -19,6 +20,7 @@ import org.opensearch.tasks.Task; import org.opensearch.transport.TransportService; +@Log4j2 public class DeleteModelTransportAction extends HandledTransportAction { private final ModelDao modelDao; @@ -32,6 +34,9 @@ public DeleteModelTransportAction(TransportService transportService, ActionFilte @Override protected void doExecute(Task task, DeleteModelRequest request, ActionListener listener) { String modelID = request.getModelID(); - modelDao.delete(modelID, listener); + modelDao.delete(modelID, ActionListener.wrap(listener::onResponse, e -> { + log.error(e); + listener.onFailure(e); + })); } } diff --git a/src/main/java/org/opensearch/knn/plugin/transport/GetModelAction.java b/src/main/java/org/opensearch/knn/plugin/transport/GetModelAction.java index 74b115830..18c792ec7 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/GetModelAction.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/GetModelAction.java @@ -12,7 +12,7 @@ package org.opensearch.knn.plugin.transport; import org.opensearch.action.ActionType; -import org.opensearch.common.io.stream.Writeable; +import org.opensearch.core.common.io.stream.Writeable; /** * GetModelAction class diff --git a/src/main/java/org/opensearch/knn/plugin/transport/GetModelRequest.java b/src/main/java/org/opensearch/knn/plugin/transport/GetModelRequest.java index 08ba03e07..4c3d4de97 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/GetModelRequest.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/GetModelRequest.java @@ -13,8 +13,8 @@ import org.opensearch.action.ActionRequest; import org.opensearch.action.ActionRequestValidationException; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; import java.io.IOException; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/GetModelResponse.java b/src/main/java/org/opensearch/knn/plugin/transport/GetModelResponse.java index 6c61befef..f47638560 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/GetModelResponse.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/GetModelResponse.java @@ -10,11 +10,11 @@ */ package org.opensearch.knn.plugin.transport; -import org.opensearch.action.ActionResponse; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; -import org.opensearch.common.xcontent.ToXContentObject; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.knn.indices.Model; import java.io.IOException; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/GetModelTransportAction.java b/src/main/java/org/opensearch/knn/plugin/transport/GetModelTransportAction.java index 05cb12742..8f8276746 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/GetModelTransportAction.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/GetModelTransportAction.java @@ -12,7 +12,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.action.ActionListener; +import org.opensearch.core.action.ActionListener; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; import org.opensearch.common.inject.Inject; @@ -20,8 +20,6 @@ import org.opensearch.tasks.Task; import org.opensearch.transport.TransportService; -import java.io.IOException; - /** * Transport Action for {@link GetModelAction} */ @@ -38,10 +36,8 @@ public GetModelTransportAction(TransportService transportService, ActionFilters @Override protected void doExecute(Task task, GetModelRequest request, ActionListener actionListener) { String modelID = request.getModelID(); - try { - modelDao.get(modelID, actionListener); - } catch (IOException e) { - actionListener.onFailure(e); - } + + modelDao.get(modelID, actionListener); + } } diff --git a/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsAction.java b/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsAction.java index ccafb00d5..df7799d1f 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsAction.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsAction.java @@ -6,7 +6,7 @@ package org.opensearch.knn.plugin.transport; import org.opensearch.action.ActionType; -import org.opensearch.common.io.stream.Writeable; +import org.opensearch.core.common.io.stream.Writeable; /** * KNNStatsAction class diff --git a/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsNodeRequest.java b/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsNodeRequest.java index ba66784f8..930039f06 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsNodeRequest.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsNodeRequest.java @@ -6,8 +6,8 @@ package org.opensearch.knn.plugin.transport; import org.opensearch.action.support.nodes.BaseNodeRequest; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; import java.io.IOException; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsNodeResponse.java b/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsNodeResponse.java index 5bdf03ea7..14b78fed4 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsNodeResponse.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsNodeResponse.java @@ -7,10 +7,10 @@ import org.opensearch.action.support.nodes.BaseNodeResponse; import org.opensearch.cluster.node.DiscoveryNode; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; -import org.opensearch.common.xcontent.ToXContentFragment; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentFragment; +import org.opensearch.core.xcontent.XContentBuilder; import java.io.IOException; import java.util.Map; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsRequest.java b/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsRequest.java index 500c36203..2e245e5a3 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsRequest.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsRequest.java @@ -6,8 +6,9 @@ package org.opensearch.knn.plugin.transport; import org.opensearch.action.support.nodes.BaseNodesRequest; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.knn.plugin.stats.StatNames; import java.io.IOException; import java.util.HashSet; @@ -23,15 +24,16 @@ public class KNNStatsRequest extends BaseNodesRequest { * Key indicating all stats should be retrieved */ public static final String ALL_STATS_KEY = "_all"; - - private Set validStats; - private Set statsToBeRetrieved; + private final Set validStats; + private final Set statsToBeRetrieved; /** * Empty constructor needed for KNNStatsTransportAction */ public KNNStatsRequest() { super((String[]) null); + validStats = StatNames.getNames(); + statsToBeRetrieved = new HashSet<>(); } /** @@ -49,12 +51,11 @@ public KNNStatsRequest(StreamInput in) throws IOException { /** * Constructor * - * @param validStats set of stat names that are valid for KNN plugin * @param nodeIds NodeIDs from which to retrieve stats */ - public KNNStatsRequest(Set validStats, String... nodeIds) { + public KNNStatsRequest(String... nodeIds) { super(nodeIds); - this.validStats = validStats; + validStats = StatNames.getNames(); statsToBeRetrieved = new HashSet<>(); } diff --git a/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsResponse.java b/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsResponse.java index 0679eefaa..ec8aa6f6e 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsResponse.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsResponse.java @@ -9,10 +9,10 @@ import org.opensearch.action.support.nodes.BaseNodesResponse; import org.opensearch.cluster.ClusterName; import org.opensearch.cluster.node.DiscoveryNode; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; -import org.opensearch.common.xcontent.ToXContentObject; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; import java.io.IOException; import java.util.List; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsTransportAction.java b/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsTransportAction.java index 0189bf88d..7edc44894 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsTransportAction.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/KNNStatsTransportAction.java @@ -12,7 +12,7 @@ import org.opensearch.action.support.nodes.TransportNodesAction; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; -import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.transport.TransportService; import org.opensearch.threadpool.ThreadPool; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/KNNWarmupAction.java b/src/main/java/org/opensearch/knn/plugin/transport/KNNWarmupAction.java index 32fabe0c1..16b5c3a4d 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/KNNWarmupAction.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/KNNWarmupAction.java @@ -6,7 +6,7 @@ package org.opensearch.knn.plugin.transport; import org.opensearch.action.ActionType; -import org.opensearch.common.io.stream.Writeable; +import org.opensearch.core.common.io.stream.Writeable; /** * Action associated with k-NN warmup diff --git a/src/main/java/org/opensearch/knn/plugin/transport/KNNWarmupRequest.java b/src/main/java/org/opensearch/knn/plugin/transport/KNNWarmupRequest.java index 545f6fc47..30c7b52a0 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/KNNWarmupRequest.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/KNNWarmupRequest.java @@ -6,7 +6,7 @@ package org.opensearch.knn.plugin.transport; import org.opensearch.action.support.broadcast.BroadcastRequest; -import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamInput; import java.io.IOException; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/KNNWarmupResponse.java b/src/main/java/org/opensearch/knn/plugin/transport/KNNWarmupResponse.java index 3409bb395..d9e607db2 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/KNNWarmupResponse.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/KNNWarmupResponse.java @@ -5,10 +5,10 @@ package org.opensearch.knn.plugin.transport; -import org.opensearch.action.support.DefaultShardOperationFailedException; +import org.opensearch.core.action.support.DefaultShardOperationFailedException; import org.opensearch.action.support.broadcast.BroadcastResponse; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.xcontent.ToXContentObject; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.xcontent.ToXContentObject; import java.io.IOException; import java.util.List; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/KNNWarmupTransportAction.java b/src/main/java/org/opensearch/knn/plugin/transport/KNNWarmupTransportAction.java index 79c0315fd..a738527ff 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/KNNWarmupTransportAction.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/KNNWarmupTransportAction.java @@ -9,7 +9,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.action.support.ActionFilters; -import org.opensearch.action.support.DefaultShardOperationFailedException; +import org.opensearch.core.action.support.DefaultShardOperationFailedException; import org.opensearch.action.support.broadcast.node.TransportBroadcastByNodeAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.block.ClusterBlockException; @@ -19,7 +19,7 @@ import org.opensearch.cluster.routing.ShardsIterator; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; -import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.indices.IndicesService; import org.opensearch.transport.TransportService; import org.opensearch.threadpool.ThreadPool; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheAction.java b/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheAction.java index c2ce43069..6a0e61684 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheAction.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheAction.java @@ -12,7 +12,7 @@ package org.opensearch.knn.plugin.transport; import org.opensearch.action.ActionType; -import org.opensearch.common.io.stream.Writeable; +import org.opensearch.core.common.io.stream.Writeable; /** * Action used to remove a model from the cache on some or all nodes diff --git a/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheNodeRequest.java b/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheNodeRequest.java index 09e7e1148..c13a26707 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheNodeRequest.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheNodeRequest.java @@ -12,8 +12,8 @@ package org.opensearch.knn.plugin.transport; import org.opensearch.action.support.nodes.BaseNodeRequest; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; import java.io.IOException; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheNodeResponse.java b/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheNodeResponse.java index 7e1be85d0..d8bf28d40 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheNodeResponse.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheNodeResponse.java @@ -13,7 +13,7 @@ import org.opensearch.action.support.nodes.BaseNodeResponse; import org.opensearch.cluster.node.DiscoveryNode; -import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamInput; import java.io.IOException; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheRequest.java b/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheRequest.java index 5da60c809..3a31f7ada 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheRequest.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheRequest.java @@ -12,8 +12,8 @@ package org.opensearch.knn.plugin.transport; import org.opensearch.action.support.nodes.BaseNodesRequest; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; import java.io.IOException; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheResponse.java b/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheResponse.java index 3293573ce..74a95bf69 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheResponse.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheResponse.java @@ -14,8 +14,8 @@ import org.opensearch.action.FailedNodeException; import org.opensearch.action.support.nodes.BaseNodesResponse; import org.opensearch.cluster.ClusterName; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; import java.io.IOException; import java.util.List; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheTransportAction.java b/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheTransportAction.java index 92938ed3c..042b870ea 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheTransportAction.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheTransportAction.java @@ -18,7 +18,7 @@ import org.opensearch.action.support.nodes.TransportNodesAction; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; -import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.knn.indices.ModelCache; import org.opensearch.threadpool.ThreadPool; import org.opensearch.transport.TransportService; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/SearchModelAction.java b/src/main/java/org/opensearch/knn/plugin/transport/SearchModelAction.java index 219e6d1e3..ff217fdbd 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/SearchModelAction.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/SearchModelAction.java @@ -13,7 +13,7 @@ import org.opensearch.action.ActionType; import org.opensearch.action.search.SearchResponse; -import org.opensearch.common.io.stream.Writeable; +import org.opensearch.core.common.io.stream.Writeable; /** * GetModelAction class diff --git a/src/main/java/org/opensearch/knn/plugin/transport/SearchModelTransportAction.java b/src/main/java/org/opensearch/knn/plugin/transport/SearchModelTransportAction.java index 4d9f67059..89114d763 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/SearchModelTransportAction.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/SearchModelTransportAction.java @@ -11,7 +11,7 @@ package org.opensearch.knn.plugin.transport; -import org.opensearch.action.ActionListener; +import org.opensearch.core.action.ActionListener; import org.opensearch.action.search.SearchRequest; import org.opensearch.action.search.SearchResponse; import org.opensearch.action.support.ActionFilters; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoAction.java b/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoAction.java index 4336cbf62..03d62bfb7 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoAction.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoAction.java @@ -12,7 +12,7 @@ package org.opensearch.knn.plugin.transport; import org.opensearch.action.ActionType; -import org.opensearch.common.io.stream.Writeable; +import org.opensearch.core.common.io.stream.Writeable; /** * Action used to collect information from each node to determine which node would be best to route a particular diff --git a/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoNodeRequest.java b/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoNodeRequest.java index 7bca7e563..051ec0278 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoNodeRequest.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoNodeRequest.java @@ -12,7 +12,7 @@ package org.opensearch.knn.plugin.transport; import org.opensearch.action.support.nodes.BaseNodeRequest; -import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamInput; import java.io.IOException; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoNodeResponse.java b/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoNodeResponse.java index 2782a8374..2469528f2 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoNodeResponse.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoNodeResponse.java @@ -13,11 +13,11 @@ import org.opensearch.action.support.nodes.BaseNodeResponse; import org.opensearch.cluster.node.DiscoveryNode; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; -import org.opensearch.common.xcontent.ToXContent; -import org.opensearch.common.xcontent.ToXContentFragment; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.ToXContentFragment; +import org.opensearch.core.xcontent.XContentBuilder; import java.io.IOException; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoRequest.java b/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoRequest.java index 23a0691b8..316168a1c 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoRequest.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoRequest.java @@ -12,7 +12,7 @@ package org.opensearch.knn.plugin.transport; import org.opensearch.action.support.nodes.BaseNodesRequest; -import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamInput; import java.io.IOException; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoResponse.java b/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoResponse.java index 4fe50410c..71812409c 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoResponse.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoResponse.java @@ -15,10 +15,10 @@ import org.opensearch.action.support.nodes.BaseNodesResponse; import org.opensearch.cluster.ClusterName; import org.opensearch.cluster.node.DiscoveryNode; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; -import org.opensearch.common.xcontent.ToXContentObject; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; import java.io.IOException; import java.util.List; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoTransportAction.java b/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoTransportAction.java index 407036c86..cdee47927 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoTransportAction.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoTransportAction.java @@ -16,7 +16,7 @@ import org.opensearch.action.support.nodes.TransportNodesAction; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; -import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.knn.training.TrainingJobRunner; import org.opensearch.threadpool.ThreadPool; import org.opensearch.transport.TransportService; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouterAction.java b/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouterAction.java index d6a263a4b..ad340a582 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouterAction.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouterAction.java @@ -12,7 +12,7 @@ package org.opensearch.knn.plugin.transport; import org.opensearch.action.ActionType; -import org.opensearch.common.io.stream.Writeable; +import org.opensearch.core.common.io.stream.Writeable; /** * Action to route training request to a particular node in the cluster diff --git a/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouterTransportAction.java b/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouterTransportAction.java index 774029c58..78f3769c5 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouterTransportAction.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/TrainingJobRouterTransportAction.java @@ -11,7 +11,8 @@ package org.opensearch.knn.plugin.transport; -import org.opensearch.action.ActionListener; +import org.apache.commons.lang.StringUtils; +import org.opensearch.core.action.ActionListener; import org.opensearch.action.ActionListenerResponseHandler; import org.opensearch.action.search.SearchRequest; import org.opensearch.action.support.ActionFilters; @@ -19,15 +20,16 @@ import org.opensearch.client.Client; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.Strings; import org.opensearch.common.ValidationException; -import org.opensearch.common.collect.ImmutableOpenMap; import org.opensearch.common.inject.Inject; +import org.opensearch.knn.index.VectorDataType; import org.opensearch.search.builder.SearchSourceBuilder; import org.opensearch.tasks.Task; import org.opensearch.transport.TransportRequestOptions; import org.opensearch.transport.TransportService; +import java.util.Map; + import static org.opensearch.knn.common.KNNConstants.BYTES_PER_KILOBYTES; import static org.opensearch.search.internal.SearchContext.DEFAULT_TERMINATE_AFTER; @@ -94,7 +96,7 @@ protected DiscoveryNode selectNode(String preferredNode, TrainingJobRouteDecisio DiscoveryNode selectedNode = null; - ImmutableOpenMap eligibleNodes = clusterService.state().nodes().getDataNodes(); + Map eligibleNodes = clusterService.state().nodes().getDataNodes(); DiscoveryNode currentNode; for (TrainingJobRouteDecisionInfoNodeResponse response : jobInfo.getNodes()) { @@ -107,7 +109,7 @@ protected DiscoveryNode selectNode(String preferredNode, TrainingJobRouteDecisio if (response.getTrainingJobCount() < 1) { selectedNode = currentNode; // Return right away if the user didnt pass a preferred node or this is the preferred node - if (Strings.isEmpty(preferredNode) || selectedNode.getId().equals(preferredNode)) { + if (StringUtils.isEmpty(preferredNode) || selectedNode.getId().equals(preferredNode)) { return selectedNode; } } @@ -132,7 +134,9 @@ protected void getTrainingIndexSizeInKB(TrainingModelRequest trainingModelReques trainingVectors = trainingModelRequest.getMaximumVectorCount(); } - listener.onResponse(estimateVectorSetSizeInKB(trainingVectors, trainingModelRequest.getDimension())); + listener.onResponse( + estimateVectorSetSizeInKB(trainingVectors, trainingModelRequest.getDimension(), trainingModelRequest.getVectorDataType()) + ); }, listener::onFailure)); } @@ -143,8 +147,14 @@ protected void getTrainingIndexSizeInKB(TrainingModelRequest trainingModelReques * @param dimension dimension of vectors * @return size estimate */ - public static int estimateVectorSetSizeInKB(long vectorCount, int dimension) { - // Ensure we do not overflow the int on estimate - return Math.toIntExact(((Float.BYTES * dimension * vectorCount) / BYTES_PER_KILOBYTES) + 1L); + public static int estimateVectorSetSizeInKB(long vectorCount, int dimension, VectorDataType vectorDataType) { + switch (vectorDataType) { + case BINARY: + return Math.toIntExact(((Byte.BYTES * (dimension / 8) * vectorCount) / BYTES_PER_KILOBYTES) + 1L); + case BYTE: + return Math.toIntExact(((Byte.BYTES * dimension * vectorCount) / BYTES_PER_KILOBYTES) + 1L); + default: + return Math.toIntExact(((Float.BYTES * dimension * vectorCount) / BYTES_PER_KILOBYTES) + 1L); + } } } diff --git a/src/main/java/org/opensearch/knn/plugin/transport/TrainingModelAction.java b/src/main/java/org/opensearch/knn/plugin/transport/TrainingModelAction.java index b82107407..19f1fa366 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/TrainingModelAction.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/TrainingModelAction.java @@ -12,7 +12,7 @@ package org.opensearch.knn.plugin.transport; import org.opensearch.action.ActionType; -import org.opensearch.common.io.stream.Writeable; +import org.opensearch.core.common.io.stream.Writeable; public class TrainingModelAction extends ActionType { diff --git a/src/main/java/org/opensearch/knn/plugin/transport/TrainingModelRequest.java b/src/main/java/org/opensearch/knn/plugin/transport/TrainingModelRequest.java index ce9397905..9906ab490 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/TrainingModelRequest.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/TrainingModelRequest.java @@ -11,16 +11,26 @@ package org.opensearch.knn.plugin.transport; +import lombok.Getter; +import org.opensearch.Version; import org.opensearch.action.ActionRequest; import org.opensearch.action.ActionRequestValidationException; import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.ValidationException; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; import org.opensearch.knn.common.KNNConstants; -import org.opensearch.knn.index.IndexUtil; -import org.opensearch.knn.index.KNNMethodContext; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.ResolvedMethodContext; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; +import org.opensearch.knn.index.engine.EngineResolver; +import org.opensearch.knn.index.util.IndexUtil; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.indices.ModelDao; import java.io.IOException; @@ -28,6 +38,7 @@ /** * Request to train and serialize a model */ +@Getter public class TrainingModelRequest extends ActionRequest { private static ClusterService clusterService; @@ -35,16 +46,45 @@ public class TrainingModelRequest extends ActionRequest { private final String modelId; private final KNNMethodContext knnMethodContext; + private final KNNMethodConfigContext knnMethodConfigContext; private final int dimension; private final String trainingIndex; private final String trainingField; private final String preferredNodeId; private final String description; - + private final VectorDataType vectorDataType; private int maximumVectorCount; private int searchSize; - private int trainingDataSizeInKB; + private final Mode mode; + private final CompressionLevel compressionLevel; + + TrainingModelRequest( + String modelId, + KNNMethodContext knnMethodContext, + int dimension, + String trainingIndex, + String trainingField, + String preferredNodeId, + String description, + VectorDataType vectorDataType, + Mode mode, + CompressionLevel compressionLevel + ) { + this( + modelId, + knnMethodContext, + dimension, + trainingIndex, + trainingField, + preferredNodeId, + description, + vectorDataType, + mode, + compressionLevel, + SpaceType.DEFAULT + ); + } /** * Constructor. @@ -64,16 +104,21 @@ public TrainingModelRequest( String trainingIndex, String trainingField, String preferredNodeId, - String description + String description, + VectorDataType vectorDataType, + Mode mode, + CompressionLevel compressionLevel, + SpaceType spaceType ) { super(); this.modelId = modelId; - this.knnMethodContext = knnMethodContext; this.dimension = dimension; this.trainingIndex = trainingIndex; this.trainingField = trainingField; this.preferredNodeId = preferredNodeId; this.description = description; + this.vectorDataType = vectorDataType; + this.mode = mode; // Set these as defaults initially. If call wants to override them, they can use the setters. this.maximumVectorCount = Integer.MAX_VALUE; // By default, get all vectors in the index @@ -81,7 +126,19 @@ public TrainingModelRequest( // Training data size in kilobytes. By default, this is invalid (it cant have negative kb). It eventually gets // calculated in transit. A user cannot set this value directly. - this.trainingDataSizeInKB = -1; + this.knnMethodConfigContext = KNNMethodConfigContext.builder() + .vectorDataType(vectorDataType) + .dimension(dimension) + .versionCreated(Version.CURRENT) + .compressionLevel(compressionLevel) + .mode(mode) + .build(); + + KNNEngine knnEngine = EngineResolver.INSTANCE.resolveEngine(knnMethodConfigContext, knnMethodContext, true); + ResolvedMethodContext resolvedMethodContext = knnEngine.resolveMethod(knnMethodContext, knnMethodConfigContext, true, spaceType); + this.knnMethodContext = resolvedMethodContext.getKnnMethodContext(); + this.compressionLevel = resolvedMethodContext.getCompressionLevel(); + this.knnMethodConfigContext.setCompressionLevel(resolvedMethodContext.getCompressionLevel()); } /** @@ -102,6 +159,26 @@ public TrainingModelRequest(StreamInput in) throws IOException { this.maximumVectorCount = in.readInt(); this.searchSize = in.readInt(); this.trainingDataSizeInKB = in.readInt(); + if (IndexUtil.isVersionOnOrAfterMinRequiredVersion(in.getVersion(), KNNConstants.MODEL_VECTOR_DATA_TYPE_KEY)) { + this.vectorDataType = VectorDataType.get(in.readString()); + } else { + this.vectorDataType = VectorDataType.DEFAULT; + } + if (IndexUtil.isVersionOnOrAfterMinRequiredVersion(in.getVersion(), KNNConstants.MINIMAL_MODE_AND_COMPRESSION_FEATURE)) { + this.mode = Mode.fromName(in.readOptionalString()); + this.compressionLevel = CompressionLevel.fromName(in.readOptionalString()); + } else { + this.mode = Mode.NOT_CONFIGURED; + this.compressionLevel = CompressionLevel.NOT_CONFIGURED; + } + + this.knnMethodConfigContext = KNNMethodConfigContext.builder() + .vectorDataType(vectorDataType) + .dimension(dimension) + .versionCreated(in.getVersion()) + .compressionLevel(compressionLevel) + .mode(mode) + .build(); } /** @@ -115,79 +192,6 @@ public static void initialize(ModelDao modelDao, ClusterService clusterService) TrainingModelRequest.clusterService = clusterService; } - /** - * Getter for modelId - * - * @return modelId - */ - public String getModelId() { - return modelId; - } - - /** - * Getter for knnMethodContext - * - * @return knnMethodContext - */ - public KNNMethodContext getKnnMethodContext() { - return knnMethodContext; - } - - /** - * Getter for dimension - * - * @return dimension - */ - public int getDimension() { - return dimension; - } - - /** - * Getter for trainingIndex - * - * @return trainingIndex - */ - public String getTrainingIndex() { - return trainingIndex; - } - - /** - * Getter for trainingField - * - * @return trainingField - */ - public String getTrainingField() { - return trainingField; - } - - /** - * Getter for preferredNodeId - * - * @return preferredNodeId - */ - public String getPreferredNodeId() { - return preferredNodeId; - } - - /** - * Getter description of the model - * - * @return description - */ - public String getDescription() { - return description; - } - - /** - * Getter for maximum vector count. This corresponds to the maximum number of vectors from the training index - * a user wants to use for training. - * - * @return maximumVectorCount - */ - public int getMaximumVectorCount() { - return maximumVectorCount; - } - /** * Setter for maximum vector count * @@ -202,16 +206,6 @@ public void setMaximumVectorCount(int maximumVectorCount) { this.maximumVectorCount = maximumVectorCount; } - /** - * Getter for search size. This value corresponds to how many vectors are pulled from the training index per - * search request - * - * @return searchSize - */ - public int getSearchSize() { - return searchSize; - } - /** * Setter for search size. * @@ -226,15 +220,6 @@ public void setSearchSize(int searchSize) { this.searchSize = searchSize; } - /** - * Getter for training data size in kilobytes. - * - * @return trainingDataSizeInKB - */ - public int getTrainingDataSizeInKB() { - return trainingDataSizeInKB; - } - /** * Setter for trainingDataSizeInKB. Package private to prevent users from changing this value directly. * @@ -254,14 +239,28 @@ public ActionRequestValidationException validate() { ActionRequestValidationException exception = null; // Check if model id exists via model metadata - if (modelDao.getMetadata(modelId) != null) { + // Also, check if model is not in model graveyard to make sure it is not being deleted + if (modelDao.getMetadata(modelId) != null && !modelDao.isModelInGraveyard(modelId)) { exception = new ActionRequestValidationException(); exception.addValidationError("Model with id=\"" + modelId + "\" already exists"); return exception; } + // Check if modelId is in model graveyard + // ModelId is added to model graveyard if that model is undergoing deletion + // and will be removed from it after model is deleted + if (modelDao.isModelInGraveyard(modelId)) { + exception = new ActionRequestValidationException(); + String errorMessage = String.format( + "Model with id = \"%s\" is being deleted. Cannot create a model with same modelID until that model is deleted", + modelId + ); + exception.addValidationError(errorMessage); + return exception; + } + // Confirm that the passed in knnMethodContext is valid and requires training - ValidationException validationException = this.knnMethodContext.validate(); + ValidationException validationException = this.knnMethodContext.validate(knnMethodConfigContext); if (validationException != null) { exception = new ActionRequestValidationException(); exception.addValidationErrors(validationException.validationErrors()); @@ -293,7 +292,14 @@ public ActionRequestValidationException validate() { } // Validate the training field - ValidationException fieldValidation = IndexUtil.validateKnnField(indexMetadata, this.trainingField, this.dimension, modelDao); + ValidationException fieldValidation = IndexUtil.validateKnnField( + indexMetadata, + this.trainingField, + this.dimension, + modelDao, + vectorDataType, + knnMethodContext + ); if (fieldValidation != null) { exception = exception == null ? new ActionRequestValidationException() : exception; exception.addValidationErrors(fieldValidation.validationErrors()); @@ -315,5 +321,14 @@ public void writeTo(StreamOutput out) throws IOException { out.writeInt(this.maximumVectorCount); out.writeInt(this.searchSize); out.writeInt(this.trainingDataSizeInKB); + if (IndexUtil.isVersionOnOrAfterMinRequiredVersion(out.getVersion(), KNNConstants.MODEL_VECTOR_DATA_TYPE_KEY)) { + out.writeString(this.vectorDataType.getValue()); + } else { + out.writeString(VectorDataType.DEFAULT.getValue()); + } + if (IndexUtil.isVersionOnOrAfterMinRequiredVersion(out.getVersion(), KNNConstants.MINIMAL_MODE_AND_COMPRESSION_FEATURE)) { + out.writeOptionalString(mode.getName()); + out.writeOptionalString(compressionLevel.getName()); + } } } diff --git a/src/main/java/org/opensearch/knn/plugin/transport/TrainingModelResponse.java b/src/main/java/org/opensearch/knn/plugin/transport/TrainingModelResponse.java index ac38bca8e..6dec1adcf 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/TrainingModelResponse.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/TrainingModelResponse.java @@ -11,11 +11,11 @@ package org.opensearch.knn.plugin.transport; -import org.opensearch.action.ActionResponse; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; -import org.opensearch.common.xcontent.ToXContentObject; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.knn.common.KNNConstants; import java.io.IOException; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/TrainingModelTransportAction.java b/src/main/java/org/opensearch/knn/plugin/transport/TrainingModelTransportAction.java index a3c4be16e..493c5cd2c 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/TrainingModelTransportAction.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/TrainingModelTransportAction.java @@ -11,11 +11,16 @@ package org.opensearch.knn.plugin.transport; -import org.opensearch.action.ActionListener; +import org.opensearch.Version; +import org.opensearch.core.action.ActionListener; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; +import org.opensearch.knn.index.engine.KNNLibraryIndexingContext; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; import org.opensearch.knn.index.memory.NativeMemoryCacheManager; import org.opensearch.knn.index.memory.NativeMemoryEntryContext; import org.opensearch.knn.index.memory.NativeMemoryLoadStrategy; @@ -26,6 +31,7 @@ import org.opensearch.transport.TransportService; import java.io.IOException; +import java.util.concurrent.ExecutionException; /** * Transport action that trains a model and serializes it to model system index @@ -42,6 +48,15 @@ public TrainingModelTransportAction(TransportService transportService, ActionFil @Override protected void doExecute(Task task, TrainingModelRequest request, ActionListener listener) { + KNNMethodContext knnMethodContext = request.getKnnMethodContext(); + KNNMethodConfigContext knnMethodConfigContext = request.getKnnMethodConfigContext(); + QuantizationConfig quantizationConfig = QuantizationConfig.EMPTY; + + if (knnMethodContext != null && request.getKnnMethodConfigContext() != null) { + KNNLibraryIndexingContext knnLibraryIndexingContext = knnMethodContext.getKnnEngine() + .getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext); + quantizationConfig = knnLibraryIndexingContext.getQuantizationConfig(); + } NativeMemoryEntryContext.TrainingDataEntryContext trainingDataEntryContext = new NativeMemoryEntryContext.TrainingDataEntryContext( request.getTrainingDataSizeInKB(), @@ -50,12 +65,21 @@ protected void doExecute(Task task, TrainingModelRequest request, ActionListener NativeMemoryLoadStrategy.TrainingLoadStrategy.getInstance(), clusterService, request.getMaximumVectorCount(), - request.getSearchSize() + request.getSearchSize(), + request.getVectorDataType(), + quantizationConfig ); // Allocation representing size model will occupy in memory during training NativeMemoryEntryContext.AnonymousEntryContext modelAnonymousEntryContext = new NativeMemoryEntryContext.AnonymousEntryContext( - request.getKnnMethodContext().estimateOverheadInKB(request.getDimension()), + request.getKnnMethodContext() + .estimateOverheadInKB( + KNNMethodConfigContext.builder() + .dimension(request.getDimension()) + .vectorDataType(request.getVectorDataType()) + .versionCreated(Version.CURRENT) + .build() + ), NativeMemoryLoadStrategy.AnonymousLoadStrategy.getInstance() ); @@ -65,8 +89,11 @@ protected void doExecute(Task task, TrainingModelRequest request, ActionListener NativeMemoryCacheManager.getInstance(), trainingDataEntryContext, modelAnonymousEntryContext, - request.getDimension(), - request.getDescription() + request.getKnnMethodConfigContext(), + request.getDescription(), + clusterService.localNode().getEphemeralId(), + request.getMode(), + request.getCompressionLevel() ); KNNCounter.TRAINING_REQUESTS.increment(); @@ -84,7 +111,7 @@ protected void doExecute(Task task, TrainingModelRequest request, ActionListener wrappedListener::onFailure ) ); - } catch (IOException e) { + } catch (IOException | ExecutionException | InterruptedException e) { wrappedListener.onFailure(e); } } diff --git a/src/main/java/org/opensearch/knn/plugin/transport/UpdateModelGraveyardAction.java b/src/main/java/org/opensearch/knn/plugin/transport/UpdateModelGraveyardAction.java new file mode 100644 index 000000000..216efa78e --- /dev/null +++ b/src/main/java/org/opensearch/knn/plugin/transport/UpdateModelGraveyardAction.java @@ -0,0 +1,29 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.plugin.transport; + +import org.opensearch.action.ActionType; +import org.opensearch.action.support.master.AcknowledgedResponse; +import org.opensearch.core.common.io.stream.Writeable; + +/** + * Action to update model graveyard + */ +public class UpdateModelGraveyardAction extends ActionType { + + public static final String NAME = "cluster:admin/knn_update_model_graveyard_action"; + public static final UpdateModelGraveyardAction INSTANCE = new UpdateModelGraveyardAction(NAME, AcknowledgedResponse::new); + + /** + * Constructor. + * + * @param name name of action + * @param acknowledgedResponseReader reader for acknowledged response + */ + public UpdateModelGraveyardAction(String name, Writeable.Reader acknowledgedResponseReader) { + super(name, acknowledgedResponseReader); + } +} diff --git a/src/main/java/org/opensearch/knn/plugin/transport/UpdateModelGraveyardRequest.java b/src/main/java/org/opensearch/knn/plugin/transport/UpdateModelGraveyardRequest.java new file mode 100644 index 000000000..887f5d7a2 --- /dev/null +++ b/src/main/java/org/opensearch/knn/plugin/transport/UpdateModelGraveyardRequest.java @@ -0,0 +1,69 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.plugin.transport; + +import lombok.Getter; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.action.support.master.AcknowledgedRequest; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; + +import java.io.IOException; + +import static org.opensearch.action.ValidateActions.addValidationError; + +/** + * Request for updating model graveyard while processing delete model request + */ +public class UpdateModelGraveyardRequest extends AcknowledgedRequest { + + @Getter + private final String modelId; + @Getter + private final boolean isRemoveRequest; + + /** + * Constructor + * + * @param in input stream + * @throws IOException if read from stream fails + */ + public UpdateModelGraveyardRequest(StreamInput in) throws IOException { + super(in); + this.modelId = in.readString(); + this.isRemoveRequest = in.readBoolean(); + } + + /** + * Constructor + * + * @param modelId Id of model + * @param isRemoveRequest should this model id be removed + */ + public UpdateModelGraveyardRequest(String modelId, boolean isRemoveRequest) { + super(); + this.modelId = modelId; + this.isRemoveRequest = isRemoveRequest; + } + + @Override + public ActionRequestValidationException validate() { + ActionRequestValidationException validationException = null; + + if (modelId.isEmpty()) { + validationException = addValidationError("Missing model ID", validationException); + } + + return validationException; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeString(modelId); + out.writeBoolean(isRemoveRequest); + } +} diff --git a/src/main/java/org/opensearch/knn/plugin/transport/UpdateModelGraveyardTransportAction.java b/src/main/java/org/opensearch/knn/plugin/transport/UpdateModelGraveyardTransportAction.java new file mode 100644 index 000000000..7d5750c2b --- /dev/null +++ b/src/main/java/org/opensearch/knn/plugin/transport/UpdateModelGraveyardTransportAction.java @@ -0,0 +1,213 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.plugin.transport; + +import lombok.Value; +import lombok.extern.log4j.Log4j2; +import org.opensearch.cluster.metadata.IndexMetadata; +import org.opensearch.core.action.ActionListener; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.master.AcknowledgedResponse; +import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction; +import org.opensearch.cluster.ClusterState; +import org.opensearch.cluster.ClusterStateTaskConfig; +import org.opensearch.cluster.ClusterStateTaskExecutor; +import org.opensearch.cluster.ClusterStateTaskListener; +import org.opensearch.cluster.block.ClusterBlockException; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.metadata.Metadata; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.Priority; +import org.opensearch.common.inject.Inject; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.indices.IndicesService; +import org.opensearch.knn.common.exception.DeleteModelException; +import org.opensearch.knn.indices.ModelGraveyard; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.TransportService; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import static java.util.stream.Collectors.toList; +import static org.opensearch.knn.common.KNNConstants.MODEL_ID; +import static org.opensearch.knn.common.KNNConstants.PLUGIN_NAME; + +/** + * Transport action used to update model graveyard on the cluster manager node. + */ +@Log4j2 +public class UpdateModelGraveyardTransportAction extends TransportClusterManagerNodeAction< + UpdateModelGraveyardRequest, + AcknowledgedResponse> { + private UpdateModelGraveyardExecutor updateModelGraveyardExecutor; + private final IndicesService indicesService; + + @Inject + public UpdateModelGraveyardTransportAction( + TransportService transportService, + ClusterService clusterService, + ThreadPool threadPool, + ActionFilters actionFilters, + IndexNameExpressionResolver indexNameExpressionResolver, + IndicesService indicesService + ) { + super( + UpdateModelGraveyardAction.NAME, + transportService, + clusterService, + threadPool, + actionFilters, + UpdateModelGraveyardRequest::new, + indexNameExpressionResolver + ); + this.updateModelGraveyardExecutor = new UpdateModelGraveyardExecutor(); + this.indicesService = indicesService; + } + + @Override + protected String executor() { + return ThreadPool.Names.SAME; + } + + @Override + protected AcknowledgedResponse read(StreamInput streamInput) throws IOException { + return new AcknowledgedResponse(streamInput); + } + + @Override + protected void clusterManagerOperation( + UpdateModelGraveyardRequest request, + ClusterState clusterState, + ActionListener actionListener + ) { + // ClusterManager updates model graveyard based on request parameters + clusterService.submitStateUpdateTask( + PLUGIN_NAME, + new UpdateModelGraveyardTask(request.getModelId(), request.isRemoveRequest(), indicesService), + ClusterStateTaskConfig.build(Priority.NORMAL), + updateModelGraveyardExecutor, + new ClusterStateTaskListener() { + @Override + public void onFailure(String s, Exception e) { + actionListener.onFailure(e); + } + + @Override + public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) { + actionListener.onResponse(new AcknowledgedResponse(true)); + } + } + ); + } + + @Override + protected ClusterBlockException checkBlock(UpdateModelGraveyardRequest request, ClusterState clusterState) { + return null; + } + + /** + * UpdateModelGraveyardTask is used to provide the executor with the information it needs to perform its task + */ + @Value + private static class UpdateModelGraveyardTask { + String modelId; + boolean isRemoveRequest; + IndicesService indicesService; + } + + /** + * Updates the cluster state based on the UpdateModelGraveyardTask + */ + private static class UpdateModelGraveyardExecutor implements ClusterStateTaskExecutor { + /** + * @param clusterState ClusterState + * @param taskList contains the list of UpdateModelGraveyardTask request parameters (modelId and isRemoveRequest) + * @return Represents the result of a batched execution of cluster state update tasks (UpdateModelGraveyardTasks) + */ + @Override + public ClusterTasksResult execute(ClusterState clusterState, List taskList) + throws IOException { + + // Check if the objects are not null and throw a customized NullPointerException + Objects.requireNonNull(clusterState, "Cluster state must not be null"); + Objects.requireNonNull(clusterState.metadata(), "Cluster metadata must not be null"); + ModelGraveyard immutableModelGraveyard = clusterState.metadata().custom(ModelGraveyard.TYPE); + ModelGraveyard modelGraveyard; + Set copySet; + + if (immutableModelGraveyard == null) { + modelGraveyard = new ModelGraveyard(); + } else { + // Deep Copy to copy all the modelIds in ModelGraveyard to local object + // to avoid copying the reference + copySet = new HashSet<>(immutableModelGraveyard.getModelIds()); + modelGraveyard = new ModelGraveyard(copySet); + } + + for (UpdateModelGraveyardTask task : taskList) { + if (task.isRemoveRequest()) { + modelGraveyard.remove(task.getModelId()); + continue; + } + List indicesUsingModel = getIndicesUsingModel(clusterState, task); + // Throw exception if any indices are using the model + if (!indicesUsingModel.isEmpty()) { + throw new DeleteModelException( + String.format( + "Cannot delete model [%s]. Model is in use by the following indices %s, which must be deleted first.", + task.getModelId(), + indicesUsingModel + ) + ); + } + modelGraveyard.add(task.getModelId()); + } + + Metadata.Builder metaDataBuilder = Metadata.builder(clusterState.metadata()); + metaDataBuilder.putCustom(ModelGraveyard.TYPE, modelGraveyard); + + ClusterState updatedClusterState = ClusterState.builder(clusterState).metadata(metaDataBuilder).build(); + return new ClusterTasksResult.Builder().successes(taskList).build(updatedClusterState); + } + + private List getIndicesUsingModel(ClusterState clusterState, UpdateModelGraveyardTask task) throws IOException { + Map indices = clusterState.metadata().indices(); + String[] knnIndicesList = indices.values() + .stream() + .filter(metadata -> "true".equals(metadata.getSettings().get("index.knn", "false"))) + .map(metadata -> metadata.getIndex().getName()) + .toArray(String[]::new); + if (knnIndicesList.length == 0) { + return Collections.emptyList(); + } + + return clusterState.metadata() + .findMappings(knnIndicesList, task.getIndicesService().getFieldFilter()) + .entrySet() + .stream() + .filter(entry -> entry.getValue() != null) + .filter(entry -> { + Object properties = entry.getValue().getSourceAsMap().get("properties"); + if ((properties instanceof Map) == false) { + return false; + } + Map propertiesMap = (Map) properties; + return propertiesMap.values() + .stream() + .filter(obj -> obj instanceof Map) + .anyMatch(obj -> task.getModelId().equals(((Map) obj).get(MODEL_ID))); + }) + .map(Map.Entry::getKey) + .collect(toList()); + } + } +} diff --git a/src/main/java/org/opensearch/knn/plugin/transport/UpdateModelMetadataAction.java b/src/main/java/org/opensearch/knn/plugin/transport/UpdateModelMetadataAction.java index 905e05aa0..756a32575 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/UpdateModelMetadataAction.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/UpdateModelMetadataAction.java @@ -13,7 +13,7 @@ import org.opensearch.action.ActionType; import org.opensearch.action.support.master.AcknowledgedResponse; -import org.opensearch.common.io.stream.Writeable; +import org.opensearch.core.common.io.stream.Writeable; /** * Action to update model metadata. diff --git a/src/main/java/org/opensearch/knn/plugin/transport/UpdateModelMetadataRequest.java b/src/main/java/org/opensearch/knn/plugin/transport/UpdateModelMetadataRequest.java index d0628a519..af063ad27 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/UpdateModelMetadataRequest.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/UpdateModelMetadataRequest.java @@ -13,8 +13,8 @@ import org.opensearch.action.ActionRequestValidationException; import org.opensearch.action.support.master.AcknowledgedRequest; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; import org.opensearch.knn.indices.ModelMetadata; import java.io.IOException; diff --git a/src/main/java/org/opensearch/knn/plugin/transport/UpdateModelMetadataTransportAction.java b/src/main/java/org/opensearch/knn/plugin/transport/UpdateModelMetadataTransportAction.java index c3ef866fd..ec909f443 100644 --- a/src/main/java/org/opensearch/knn/plugin/transport/UpdateModelMetadataTransportAction.java +++ b/src/main/java/org/opensearch/knn/plugin/transport/UpdateModelMetadataTransportAction.java @@ -13,10 +13,10 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.action.ActionListener; +import org.opensearch.core.action.ActionListener; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.master.AcknowledgedResponse; -import org.opensearch.action.support.master.TransportMasterNodeAction; +import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.ClusterStateTaskConfig; import org.opensearch.cluster.ClusterStateTaskExecutor; @@ -28,7 +28,7 @@ import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.Priority; import org.opensearch.common.inject.Inject; -import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.knn.indices.ModelMetadata; import org.opensearch.threadpool.ThreadPool; import org.opensearch.transport.TransportService; @@ -45,7 +45,9 @@ /** * Transport action used to update metadata of model's on the cluster manager node. */ -public class UpdateModelMetadataTransportAction extends TransportMasterNodeAction { +public class UpdateModelMetadataTransportAction extends TransportClusterManagerNodeAction< + UpdateModelMetadataRequest, + AcknowledgedResponse> { public static Logger logger = LogManager.getLogger(UpdateModelMetadataTransportAction.class); @@ -82,7 +84,7 @@ protected AcknowledgedResponse read(StreamInput streamInput) throws IOException } @Override - protected void masterOperation( + protected void clusterManagerOperation( UpdateModelMetadataRequest request, ClusterState clusterState, ActionListener actionListener diff --git a/src/main/java/org/opensearch/knn/quantization/enums/ScalarQuantizationType.java b/src/main/java/org/opensearch/knn/quantization/enums/ScalarQuantizationType.java new file mode 100644 index 000000000..40347ad93 --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/enums/ScalarQuantizationType.java @@ -0,0 +1,62 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.enums; + +import lombok.Getter; + +/** + * The ScalarQuantizationType enum defines the various scalar quantization types that can be used + * for vector quantization. Each type corresponds to a different bit-width representation of the quantized values. + * + *

+ * Future Developers: If you change the name of any enum constant, do not change its associated value. + * Serialization and deserialization depend on these values to maintain compatibility. + *

+ */ +@Getter +public enum ScalarQuantizationType { + /** + * ONE_BIT quantization uses a single bit per coordinate. + */ + ONE_BIT(1), + + /** + * TWO_BIT quantization uses two bits per coordinate. + */ + TWO_BIT(2), + + /** + * FOUR_BIT quantization uses four bits per coordinate. + */ + FOUR_BIT(4); + + private final int id; + + /** + * Constructs a ScalarQuantizationType with the specified ID. + * + * @param id the ID representing the quantization type. + */ + ScalarQuantizationType(int id) { + this.id = id; + } + + /** + * Returns the ScalarQuantizationType associated with the given ID. + * + * @param id the ID of the quantization type. + * @return the corresponding ScalarQuantizationType. + * @throws IllegalArgumentException if the ID does not correspond to any ScalarQuantizationType. + */ + public static ScalarQuantizationType fromId(int id) { + for (ScalarQuantizationType type : ScalarQuantizationType.values()) { + if (type.getId() == id) { + return type; + } + } + throw new IllegalArgumentException("Unknown ScalarQuantizationType ID: " + id); + } +} diff --git a/src/main/java/org/opensearch/knn/quantization/factory/QuantizerFactory.java b/src/main/java/org/opensearch/knn/quantization/factory/QuantizerFactory.java new file mode 100644 index 000000000..6705c7688 --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/factory/QuantizerFactory.java @@ -0,0 +1,55 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.factory; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.opensearch.knn.quantization.models.quantizationParams.QuantizationParams; +import org.opensearch.knn.quantization.quantizer.Quantizer; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * The QuantizerFactory class is responsible for creating instances of {@link Quantizer} + * based on the provided {@link QuantizationParams}. It uses a registry to look up the + * appropriate quantizer implementation for the given quantization parameters. + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class QuantizerFactory { + private static final AtomicBoolean isRegistered = new AtomicBoolean(false); + + /** + * Retrieves a quantizer instance based on the provided quantization parameters. + * + * @param params the quantization parameters used to determine the appropriate quantizer + * @param

the type of quantization parameters, extending {@link QuantizationParams} + * @param the type of the input vector to be quantized + * @param the type of the output after quantization + * @return an instance of {@link Quantizer} corresponding to the provided parameters + */ + public static

Quantizer getQuantizer(final P params) { + if (params == null) { + throw new IllegalArgumentException("Quantization parameters must not be null."); + } + // Lazy Registration instead of static block as class level; + ensureRegistered(); + return (Quantizer) QuantizerRegistry.getQuantizer(params); + } + + /** + * Ensures that default quantizers are registered. + */ + private static void ensureRegistered() { + if (!isRegistered.get()) { + synchronized (QuantizerFactory.class) { + if (!isRegistered.get()) { + QuantizerRegistrar.registerDefaultQuantizers(); + isRegistered.set(true); + } + } + } + } +} diff --git a/src/main/java/org/opensearch/knn/quantization/factory/QuantizerRegistrar.java b/src/main/java/org/opensearch/knn/quantization/factory/QuantizerRegistrar.java new file mode 100644 index 000000000..7b542aea0 --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/factory/QuantizerRegistrar.java @@ -0,0 +1,46 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.factory; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; +import org.opensearch.knn.quantization.models.quantizationParams.ScalarQuantizationParams; +import org.opensearch.knn.quantization.quantizer.MultiBitScalarQuantizer; +import org.opensearch.knn.quantization.quantizer.OneBitScalarQuantizer; + +/** + * The QuantizerRegistrar class is responsible for registering default quantizers. + * This class ensures that the registration happens only once in a thread-safe manner. + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +final class QuantizerRegistrar { + + /** + * Registers default quantizers + *

+ * This method is synchronized to ensure that registration occurs only once, + * even in a multi-threaded environment. + *

+ */ + static synchronized void registerDefaultQuantizers() { + // Register OneBitScalarQuantizer for SQParams with VALUE_QUANTIZATION and SQTypes.ONE_BIT + QuantizerRegistry.register( + ScalarQuantizationParams.generateTypeIdentifier(ScalarQuantizationType.ONE_BIT), + new OneBitScalarQuantizer() + ); + // Register MultiBitScalarQuantizer for SQParams with VALUE_QUANTIZATION with bit per co-ordinate = 2 + QuantizerRegistry.register( + ScalarQuantizationParams.generateTypeIdentifier(ScalarQuantizationType.TWO_BIT), + new MultiBitScalarQuantizer(2) + ); + // Register MultiBitScalarQuantizer for SQParams with VALUE_QUANTIZATION with bit per co-ordinate = 4 + QuantizerRegistry.register( + ScalarQuantizationParams.generateTypeIdentifier(ScalarQuantizationType.FOUR_BIT), + new MultiBitScalarQuantizer(4) + ); + } +} diff --git a/src/main/java/org/opensearch/knn/quantization/factory/QuantizerRegistry.java b/src/main/java/org/opensearch/knn/quantization/factory/QuantizerRegistry.java new file mode 100644 index 000000000..2da830f3b --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/factory/QuantizerRegistry.java @@ -0,0 +1,58 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.factory; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.opensearch.knn.quantization.models.quantizationParams.QuantizationParams; +import org.opensearch.knn.quantization.quantizer.Quantizer; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * The QuantizerRegistry class is responsible for managing the registration and retrieval + * of quantizer instances. Quantizers are registered with specific quantization parameters + * and type identifiers, allowing for efficient lookup and instantiation. + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +final class QuantizerRegistry { + // ConcurrentHashMap for thread-safe access + private static final Map> registry = new ConcurrentHashMap<>(); + + /** + * Registers a quantizer with the registry. + * + * @param paramIdentifier the unique identifier for the quantization parameters + * @param quantizer an instance of the quantizer + */ + static void register(final String paramIdentifier, final Quantizer quantizer) { + // Check if the quantizer is already registered for the given identifier + if (registry.putIfAbsent(paramIdentifier, quantizer) != null) { + // Throw an exception if a quantizer is already registered + throw new IllegalArgumentException("Quantizer already registered for identifier: " + paramIdentifier); + } + } + + /** + * Retrieves a quantizer instance based on the provided quantization parameters. + * + * @param params the quantization parameters used to determine the appropriate quantizer + * @param

the type of quantization parameters + * @param the type of the input vector to be quantized + * @param the type of the output after quantization + * @return an instance of {@link Quantizer} corresponding to the provided parameters + * @throws IllegalArgumentException if no quantizer is registered for the given parameters + */ + static

Quantizer getQuantizer(final P params) { + String identifier = params.getTypeIdentifier(); + Quantizer quantizer = registry.get(identifier); + if (quantizer == null) { + throw new IllegalArgumentException("No quantizer registered for type identifier: " + identifier); + } + return (Quantizer) quantizer; + } +} diff --git a/src/main/java/org/opensearch/knn/quantization/models/quantizationOutput/BinaryQuantizationOutput.java b/src/main/java/org/opensearch/knn/quantization/models/quantizationOutput/BinaryQuantizationOutput.java new file mode 100644 index 000000000..dc8634b9d --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/models/quantizationOutput/BinaryQuantizationOutput.java @@ -0,0 +1,78 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.models.quantizationOutput; + +import lombok.Getter; + +import java.util.Arrays; +import lombok.RequiredArgsConstructor; + +/** + * The BinaryQuantizationOutput class represents the output of a quantization process in binary format. + * It implements the QuantizationOutput interface to handle byte arrays specifically. + */ +@Getter +@RequiredArgsConstructor +public class BinaryQuantizationOutput implements QuantizationOutput { + private byte[] quantizedVector; + private final int bitsPerCoordinate; + private int currentVectorLength = -1; // Indicates uninitialized state + + /** + * Prepares the quantized vector based on the vector length. + * This includes initializing or resetting the quantized vector. + * + * @param vectorLength The length of the vector to be quantized. + */ + @Override + public void prepareQuantizedVector(int vectorLength) { + if (vectorLength <= 0) { + throw new IllegalArgumentException("Vector length must be greater than zero."); + } + + if (vectorLength != currentVectorLength) { + int totalBits = bitsPerCoordinate * vectorLength; + int byteLength = (totalBits + 7) >> 3; + this.quantizedVector = new byte[byteLength]; + this.currentVectorLength = vectorLength; + } else { + Arrays.fill(this.quantizedVector, (byte) 0); + } + } + + /** + * Checks if the quantized vector has already been prepared for the given vector length. + * + * @param vectorLength The length of the vector to be quantized. + * @return true if the quantized vector is already prepared, false otherwise. + */ + @Override + public boolean isPrepared(int vectorLength) { + return vectorLength == currentVectorLength && quantizedVector != null; + } + + /** + * Returns the quantized vector. + * + * @return the quantized vector byte array. + */ + @Override + public byte[] getQuantizedVector() { + return quantizedVector; + } + + /** + * Returns a copy of the quantized vector. + * + * @return a copy of the quantized vector byte array. + */ + @Override + public byte[] getQuantizedVectorCopy() { + byte[] clonedByteArray = new byte[quantizedVector.length]; + System.arraycopy(quantizedVector, 0, clonedByteArray, 0, quantizedVector.length); + return clonedByteArray; + } +} diff --git a/src/main/java/org/opensearch/knn/quantization/models/quantizationOutput/QuantizationOutput.java b/src/main/java/org/opensearch/knn/quantization/models/quantizationOutput/QuantizationOutput.java new file mode 100644 index 000000000..29124c268 --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/models/quantizationOutput/QuantizationOutput.java @@ -0,0 +1,68 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.models.quantizationOutput; + +/** + * The QuantizationOutput interface defines the contract for quantization output data. + * + * @param The type of the quantized data. + */ +public interface QuantizationOutput { + /** + * Returns the quantized vector. + * + * This method provides access to the quantized data in its current state. + * It returns the same reference to the internal quantized vector on each call, meaning any modifications + * to the returned array will directly affect the internal state of the object. This design is intentional + * to avoid unnecessary copying of data and improve performance, especially in scenarios where frequent + * access to the quantized vector is required. + * + *

Important: As this method returns a direct reference to the internal array, care must be taken + * when modifying the returned array. If the returned vector is altered, the changes will reflect in the + * quantized vector managed by the object, which could lead to unintended side effects.

+ * + *

Usage Example:

+ *
+     * byte[] quantizedData = quantizationOutput.getQuantizedVector();
+     * // Use or modify quantizedData, but be cautious that changes affect the internal state.
+     * 
+ * + * This method does not create a deep copy of the vector to avoid performance overhead in real-time + * or high-frequency operations. If a separate copy of the vector is needed, the caller should manually + * clone or copy the returned array. + * + *

Example to clone the array:

+ *
+     * byte[] clonedData = Arrays.copyOf(quantizationOutput.getQuantizedVector(), quantizationOutput.getQuantizedVector().length);
+     * 
+ * + * @return the quantized vector (same reference on each invocation). + */ + T getQuantizedVector(); + + /** + * Prepares the quantized vector based on the vector length. + * This includes initializing or resetting the quantized vector. + * + * @param vectorLength The length of the vector to be quantized. + */ + void prepareQuantizedVector(int vectorLength); + + /** + * Checks if the quantized vector has already been prepared for the given vector length. + * + * @param vectorLength The length of the vector to be quantized. + * @return true if the quantized vector is already prepared, false otherwise. + */ + boolean isPrepared(int vectorLength); + + /** + * Returns a copy of the quantized vector. + * + * @return a copy of the quantized data. + */ + T getQuantizedVectorCopy(); +} diff --git a/src/main/java/org/opensearch/knn/quantization/models/quantizationParams/QuantizationParams.java b/src/main/java/org/opensearch/knn/quantization/models/quantizationParams/QuantizationParams.java new file mode 100644 index 000000000..4f2ee36c5 --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/models/quantizationParams/QuantizationParams.java @@ -0,0 +1,27 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.models.quantizationParams; + +import org.opensearch.core.common.io.stream.Writeable; + +/** + * Interface for quantization parameters. + * This interface defines the basic contract for all quantization parameter types. + * It provides methods to retrieve the quantization type and a unique type identifier. + * Implementations of this interface are expected to provide specific configurations + * for various quantization strategies. + */ +public interface QuantizationParams extends Writeable { + /** + * Provides a unique identifier for the quantization parameters. + * This identifier is typically a combination of the quantization type + * and additional specifics, and it serves to distinguish between different + * configurations or modes of quantization. + * + * @return a string representing the unique type identifier. + */ + String getTypeIdentifier(); +} diff --git a/src/main/java/org/opensearch/knn/quantization/models/quantizationParams/ScalarQuantizationParams.java b/src/main/java/org/opensearch/knn/quantization/models/quantizationParams/ScalarQuantizationParams.java new file mode 100644 index 000000000..881c2132d --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/models/quantizationParams/ScalarQuantizationParams.java @@ -0,0 +1,90 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.models.quantizationParams; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; + +import java.io.IOException; + +/** + * The ScalarQuantizationParams class represents the parameters specific to scalar quantization (SQ). + * This class implements the QuantizationParams interface and includes the type of scalar quantization. + */ +@Getter +@AllArgsConstructor +@NoArgsConstructor // No-argument constructor for deserialization +@EqualsAndHashCode +public class ScalarQuantizationParams implements QuantizationParams { + private ScalarQuantizationType sqType; + + /** + * Static method to generate type identifier based on ScalarQuantizationType. + * + * @param sqType the scalar quantization type. + * @return A string representing the unique type identifier. + */ + public static String generateTypeIdentifier(ScalarQuantizationType sqType) { + return generateIdentifier(sqType.getId()); + } + + /** + * Provides a unique type identifier for the ScalarQuantizationParams, combining the SQ type. + * This identifier is useful for distinguishing between different configurations of scalar quantization parameters. + * + * @return A string representing the unique type identifier. + */ + @Override + public String getTypeIdentifier() { + return generateIdentifier(sqType.getId()); + } + + /** + * Writes the object to the output stream. + * This method is part of the Writeable interface and is used to serialize the object. + * + * @param out the output stream to write the object to. + * @throws IOException if an I/O error occurs. + */ + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeVInt(sqType.getId()); + } + + /** + * Reads the object from the input stream. + * This method is part of the Writeable interface and is used to deserialize the object. + * + * @param in the input stream to read the object from. + * @throws IOException if an I/O error occurs. + */ + public ScalarQuantizationParams(StreamInput in, int version) throws IOException { + int typeId = in.readVInt(); + this.sqType = ScalarQuantizationType.fromId(typeId); + } + + /** + * Generates a unique identifier for Scalar Quantization Parameters. + * + *

+ * This method constructs an identifier string by prefixing the given integer ID + * with "ScalarQuantizationParams_". The resulting string can be used to uniquely + * identify specific quantization parameter instances, especially when registering + * or retrieving them in a registry or similar structure. + *

+ * + * @param id the integer ID to be used in generating the unique identifier. + * @return a string representing the unique identifier for the quantization parameters. + */ + private static String generateIdentifier(int id) { + return "ScalarQuantizationParams_" + id; + } +} diff --git a/src/main/java/org/opensearch/knn/quantization/models/quantizationState/DefaultQuantizationState.java b/src/main/java/org/opensearch/knn/quantization/models/quantizationState/DefaultQuantizationState.java new file mode 100644 index 000000000..531a70851 --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/models/quantizationState/DefaultQuantizationState.java @@ -0,0 +1,82 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.models.quantizationState; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.opensearch.Version; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.knn.quantization.models.quantizationParams.QuantizationParams; +import org.opensearch.knn.quantization.models.quantizationParams.ScalarQuantizationParams; + +import java.io.IOException; + +/** + * DefaultQuantizationState is used as a fallback state when no training is required or if training fails. + * It can be utilized by any quantizer to represent a default state. + */ +@Getter +@NoArgsConstructor // No-argument constructor for deserialization +@AllArgsConstructor +public class DefaultQuantizationState implements QuantizationState { + private QuantizationParams params; + + @Override + public QuantizationParams getQuantizationParams() { + return params; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeInt(Version.CURRENT.id); // Write the version + params.writeTo(out); + } + + public DefaultQuantizationState(StreamInput in) throws IOException { + int version = in.readInt(); // Read the version + this.params = new ScalarQuantizationParams(in, version); + } + + /** + * Serializes the quantization state to a byte array. + * + * @return a byte array representing the serialized state. + * @throws IOException if an I/O error occurs during serialization. + */ + @Override + public byte[] toByteArray() throws IOException { + return QuantizationStateSerializer.serialize(this); + } + + /** + * Deserializes a DefaultQuantizationState from a byte array. + * + * @param bytes the byte array containing the serialized state. + * @return the deserialized DefaultQuantizationState. + * @throws IOException if an I/O error occurs during deserialization. + * @throws ClassNotFoundException if the class of the serialized object cannot be found. + */ + public static DefaultQuantizationState fromByteArray(final byte[] bytes) throws IOException, ClassNotFoundException { + return (DefaultQuantizationState) QuantizationStateSerializer.deserialize(bytes, DefaultQuantizationState::new); + } + + @Override + public int getBytesPerVector() { + return 0; + } + + @Override + public int getDimensions() { + return 0; + } + + @Override + public long ramBytesUsed() { + return 0; + } +} diff --git a/src/main/java/org/opensearch/knn/quantization/models/quantizationState/MultiBitScalarQuantizationState.java b/src/main/java/org/opensearch/knn/quantization/models/quantizationState/MultiBitScalarQuantizationState.java new file mode 100644 index 000000000..dbef3a72a --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/models/quantizationState/MultiBitScalarQuantizationState.java @@ -0,0 +1,180 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.models.quantizationState; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.apache.lucene.util.RamUsageEstimator; +import org.opensearch.Version; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.knn.quantization.models.quantizationParams.ScalarQuantizationParams; + +import java.io.IOException; + +/** + * MultiBitScalarQuantizationState represents the state of multi-bit scalar quantization, + * including the thresholds used for quantization. + */ +@Getter +@NoArgsConstructor // No-argument constructor for deserialization +@AllArgsConstructor +public final class MultiBitScalarQuantizationState implements QuantizationState { + private ScalarQuantizationParams quantizationParams; + /** + * The threshold values for multi-bit quantization, organized as a 2D array + * where each row corresponds to a different bit level. + * + * For example: + * - For 2-bit quantization: + * thresholds[0] {0.5f, 1.5f, 2.5f} // Thresholds for the first bit level + * thresholds[1] {1.0f, 2.0f, 3.0f} // Thresholds for the second bit level + * - For 4-bit quantization: + * thresholds[0] {0.1f, 0.2f, 0.3f} + * thresholds[1] {0.4f, 0.5f, 0.6f} + * thresholds[2] {0.7f, 0.8f, 0.9f} + * thresholds[3] {1.0f, 1.1f, 1.2f} + * + * Each column represents the threshold for a specific dimension in the vector space. + */ + private float[][] thresholds; + + @Override + public ScalarQuantizationParams getQuantizationParams() { + return quantizationParams; + } + + /** + * This method is responsible for writing the state of the MultiBitScalarQuantizationState object to an external output. + * It includes versioning information to ensure compatibility between different versions of the serialized object. + * + * @param out the StreamOutput to write the object to. + * @throws IOException if an I/O error occurs during serialization. + */ + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeVInt(Version.CURRENT.id); // Write the version + quantizationParams.writeTo(out); + out.writeVInt(thresholds.length); // Number of rows + for (float[] row : thresholds) { + out.writeFloatArray(row); // Write each row as a float array + } + } + + /** + * This method is responsible for reading the state of the MultiBitScalarQuantizationState object from an external input. + * It includes versioning information to ensure compatibility between different versions of the serialized object. + * + * @param in the StreamInput to read the object from. + * @throws IOException if an I/O error occurs during deserialization. + */ + public MultiBitScalarQuantizationState(StreamInput in) throws IOException { + int version = in.readVInt(); // Read the version + this.quantizationParams = new ScalarQuantizationParams(in, version); + int rows = in.readVInt(); // Read the number of rows + this.thresholds = new float[rows][]; + for (int i = 0; i < rows; i++) { + this.thresholds[i] = in.readFloatArray(); // Read each row as a float array + } + } + + /** + * Serializes the current state of this MultiBitScalarQuantizationState object into a byte array. + * This method uses the QuantizationStateSerializer to handle the serialization process. + * + *

The serialized byte array includes all necessary state information, such as the thresholds + * and quantization parameters, ensuring that the object can be fully reconstructed from the byte array.

+ * + *
+     * {@code
+     * MultiBitScalarQuantizationState state = new MultiBitScalarQuantizationState(params, thresholds);
+     * byte[] serializedState = state.toByteArray();
+     * }
+     * 
+ * + * @return a byte array representing the serialized state of this object. + * @throws IOException if an I/O error occurs during serialization. + */ + @Override + public byte[] toByteArray() throws IOException { + return QuantizationStateSerializer.serialize(this); + } + + /** + * Deserializes a MultiBitScalarQuantizationState object from a byte array. + * This method uses the QuantizationStateSerializer to handle the deserialization process. + * + *

The byte array should contain serialized state information, including the thresholds + * and quantization parameters, which are necessary to reconstruct the MultiBitScalarQuantizationState object.

+ * + *
+     * {@code
+     * byte[] serializedState = ...; // obtain the byte array from some source
+     * MultiBitScalarQuantizationState state = MultiBitScalarQuantizationState.fromByteArray(serializedState);
+     * }
+     * 
+ * + * @param bytes the byte array containing the serialized state. + * @return the deserialized MultiBitScalarQuantizationState object. + * @throws IOException if an I/O error occurs during deserialization. + */ + public static MultiBitScalarQuantizationState fromByteArray(final byte[] bytes) throws IOException { + return (MultiBitScalarQuantizationState) QuantizationStateSerializer.deserialize(bytes, MultiBitScalarQuantizationState::new); + } + + /** + * Calculates and returns the number of bytes stored per vector after quantization. + * + * @return the number of bytes stored per vector. + */ + @Override + public int getBytesPerVector() { + // Check if thresholds are null or have invalid structure + if (thresholds == null || thresholds.length == 0 || thresholds[0] == null) { + throw new IllegalStateException("Error in getBytesStoredPerVector: The thresholds array is not initialized."); + } + + // Calculate the number of bytes required for multi-bit quantization + return thresholds.length * thresholds[0].length; + } + + @Override + public int getDimensions() { + // For multi-bit quantization, the dimension for indexing is the number of rows * columns in the thresholds array. + // Where number of column reprensents Dimesion of Original vector and number of rows equals to number of bits + // Check if thresholds are null or have invalid structure + if (thresholds == null || thresholds.length == 0 || thresholds[0] == null) { + throw new IllegalStateException("Error in getting Dimension: The thresholds array is not initialized."); + } + int originalDimensions = thresholds[0].length; + + // Align the original dimensions to the next multiple of 8 for each bit level + int alignedDimensions = (originalDimensions + 7) & ~7; + + // The final dimension count should consider the bit levels + return thresholds.length * alignedDimensions; + + } + + /** + * Calculates the memory usage of the MultiBitScalarQuantizationState object in bytes. + * This method computes the shallow size of the instance itself, the shallow size of the + * quantization parameters, and the memory usage of the 2D thresholds array. + * + * @return The estimated memory usage of the MultiBitScalarQuantizationState object in bytes. + */ + @Override + public long ramBytesUsed() { + long size = RamUsageEstimator.shallowSizeOfInstance(MultiBitScalarQuantizationState.class); + size += RamUsageEstimator.shallowSizeOf(quantizationParams); + size += RamUsageEstimator.shallowSizeOf(thresholds); // shallow size of the 2D array (array of references to rows) + for (float[] row : thresholds) { + size += RamUsageEstimator.sizeOf(row); // size of each row in the 2D array + } + return size; + } +} diff --git a/src/main/java/org/opensearch/knn/quantization/models/quantizationState/OneBitScalarQuantizationState.java b/src/main/java/org/opensearch/knn/quantization/models/quantizationState/OneBitScalarQuantizationState.java new file mode 100644 index 000000000..0a8c33771 --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/models/quantizationState/OneBitScalarQuantizationState.java @@ -0,0 +1,144 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.models.quantizationState; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.apache.lucene.util.RamUsageEstimator; +import org.opensearch.Version; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.knn.quantization.models.quantizationParams.ScalarQuantizationParams; + +import java.io.IOException; + +/** + * OneBitScalarQuantizationState represents the state of one-bit scalar quantization, + * including the mean values used for quantization. + */ +@Getter +@NoArgsConstructor // No-argument constructor for deserialization +@AllArgsConstructor +public final class OneBitScalarQuantizationState implements QuantizationState { + private ScalarQuantizationParams quantizationParams; + /** + * Mean thresholds used in the quantization process. + * Each threshold value corresponds to a dimension of the vector being quantized. + * + * Example: + * If we have a vector [1.2, 3.4, 5.6] and mean thresholds [2.0, 3.0, 4.0], + * The quantized vector will be [0, 1, 1]. + */ + private float[] meanThresholds; + + @Override + public ScalarQuantizationParams getQuantizationParams() { + return quantizationParams; + } + + /** + * This method is responsible for writing the state of the OneBitScalarQuantizationState object to an external output. + * It includes versioning information to ensure compatibility between different versions of the serialized object. + * @param out the StreamOutput to write the object to. + * @throws IOException if an I/O error occurs during serialization. + */ + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeVInt(Version.CURRENT.id); // Write the version + quantizationParams.writeTo(out); + out.writeFloatArray(meanThresholds); + } + + /** + * This method is responsible for reading the state of the OneBitScalarQuantizationState object from an external input. + * It includes versioning information to ensure compatibility between different versions of the serialized object. + * @param in the StreamInput to read the object from. + * @throws IOException if an I/O error occurs during deserialization. + */ + public OneBitScalarQuantizationState(StreamInput in) throws IOException { + int version = in.readVInt(); // Read the version + this.quantizationParams = new ScalarQuantizationParams(in, version); + this.meanThresholds = in.readFloatArray(); + } + + /** + * Serializes the current state of this OneBitScalarQuantizationState object into a byte array. + * This method uses the QuantizationStateSerializer to handle the serialization process. + * + *

The serialized byte array includes all necessary state information, such as the mean thresholds + * and quantization parameters, ensuring that the object can be fully reconstructed from the byte array.

+ * + *
+     * {@code
+     * OneBitScalarQuantizationState state = new OneBitScalarQuantizationState(params, meanThresholds);
+     * byte[] serializedState = state.toByteArray();
+     * }
+     * 
+ * + * @return a byte array representing the serialized state of this object. + * @throws IOException if an I/O error occurs during serialization. + */ + @Override + public byte[] toByteArray() throws IOException { + return QuantizationStateSerializer.serialize(this); + } + + /** + * Deserializes a OneBitScalarQuantizationState object from a byte array. + * This method uses the QuantizationStateSerializer to handle the deserialization process. + * + *

The byte array should contain serialized state information, including the mean thresholds + * and quantization parameters, which are necessary to reconstruct the OneBitScalarQuantizationState object.

+ * + *
+     * {@code
+     * byte[] serializedState = ...; // obtain the byte array from some source
+     * OneBitScalarQuantizationState state = OneBitScalarQuantizationState.fromByteArray(serializedState);
+     * }
+     * 
+ * + * @param bytes the byte array containing the serialized state. + * @return the deserialized OneBitScalarQuantizationState object. + * @throws IOException if an I/O error occurs during deserialization. + */ + public static OneBitScalarQuantizationState fromByteArray(final byte[] bytes) throws IOException { + return (OneBitScalarQuantizationState) QuantizationStateSerializer.deserialize(bytes, OneBitScalarQuantizationState::new); + } + + /** + * Calculates and returns the number of bytes stored per vector after quantization. + * + * @return the number of bytes stored per vector. + */ + @Override + public int getBytesPerVector() { + // Calculate the number of bytes required for one-bit quantization + return meanThresholds.length; + } + + @Override + public int getDimensions() { + // For one-bit quantization, the dimension for indexing is just the length of the thresholds array. + // Align the original dimensions to the next multiple of 8 + return (meanThresholds.length + 7) & ~7; + } + + /** + * Calculates the memory usage of the OneBitScalarQuantizationState object in bytes. + * This method computes the shallow size of the instance itself, the shallow size of the + * quantization parameters, and the memory usage of the mean thresholds array. + * + * @return The estimated memory usage of the OneBitScalarQuantizationState object in bytes. + */ + @Override + public long ramBytesUsed() { + long size = RamUsageEstimator.shallowSizeOfInstance(OneBitScalarQuantizationState.class); + size += RamUsageEstimator.shallowSizeOf(quantizationParams); + size += RamUsageEstimator.sizeOf(meanThresholds); + return size; + } +} diff --git a/src/main/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationState.java b/src/main/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationState.java new file mode 100644 index 000000000..18ee813fc --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationState.java @@ -0,0 +1,56 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.models.quantizationState; + +import org.opensearch.core.common.io.stream.Writeable; +import org.opensearch.knn.quantization.models.quantizationParams.QuantizationParams; + +import java.io.IOException; + +/** + * QuantizationState interface represents the state of a quantization process, including the parameters used. + * This interface provides methods for serializing and deserializing the state. + */ +public interface QuantizationState extends Writeable { + /** + * Returns the quantization parameters associated with this state. + * + * @return the quantization parameters. + */ + QuantizationParams getQuantizationParams(); + + /** + * Serializes the quantization state to a byte array. + * + * @return a byte array representing the serialized state. + * @throws IOException if an I/O error occurs during serialization. + */ + byte[] toByteArray() throws IOException; + + /** + * Calculates the number of bytes stored per vector after quantization. + * This method can be overridden by implementing classes to provide the specific calculation. + * + * @return the number of bytes stored per vector. + */ + int getBytesPerVector(); + + /** + * Returns the effective dimension used for indexing after quantization. + * For one-bit quantization, this might correspond to the length of thresholds. + * For multi-bit quantization, this might correspond to rows * columns of the thresholds matrix. + * + * @return the effective dimension for indexing. + */ + int getDimensions(); + + /** + * Estimates the memory usage of the quantization state in bytes. + * + * @return the memory usage in bytes. + */ + long ramBytesUsed(); +} diff --git a/src/main/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationStateCache.java b/src/main/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationStateCache.java new file mode 100644 index 000000000..f057026b9 --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationStateCache.java @@ -0,0 +1,132 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.models.quantizationState; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.RemovalCause; +import com.google.common.cache.RemovalNotification; +import lombok.Getter; +import lombok.extern.log4j.Log4j2; +import org.opensearch.common.unit.TimeValue; +import org.opensearch.core.common.unit.ByteSizeValue; +import org.opensearch.knn.index.KNNSettings; + +import java.io.IOException; +import java.time.Instant; +import java.util.concurrent.TimeUnit; + +import static org.opensearch.knn.index.KNNSettings.QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES; +import static org.opensearch.knn.index.KNNSettings.QUANTIZATION_STATE_CACHE_SIZE_LIMIT; + +/** + * A thread-safe singleton cache that contains quantization states. + */ +@Log4j2 +public class QuantizationStateCache { + + private static volatile QuantizationStateCache instance; + private Cache cache; + @Getter + private long maxCacheSizeInKB; + @Getter + private Instant evictedDueToSizeAt; + + @VisibleForTesting + QuantizationStateCache() { + maxCacheSizeInKB = ((ByteSizeValue) KNNSettings.state().getSettingValue(QUANTIZATION_STATE_CACHE_SIZE_LIMIT)).getKb(); + buildCache(); + } + + /** + * Gets the singleton instance of the cache. + * @return QuantizationStateCache + */ + static QuantizationStateCache getInstance() { + if (instance == null) { + synchronized (QuantizationStateCache.class) { + if (instance == null) { + instance = new QuantizationStateCache(); + } + } + } + return instance; + } + + private void buildCache() { + this.cache = CacheBuilder.newBuilder().concurrencyLevel(1).maximumWeight(maxCacheSizeInKB).weigher((k, v) -> { + try { + return ((QuantizationState) v).toByteArray().length; + } catch (IOException e) { + throw new RuntimeException(e); + } + }) + .expireAfterAccess( + ((TimeValue) KNNSettings.state().getSettingValue(QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES)).getMinutes(), + TimeUnit.MINUTES + ) + .removalListener(this::onRemoval) + .build(); + } + + synchronized void rebuildCache() { + clear(); + buildCache(); + } + + /** + * Retrieves the quantization state associated with a given field name. + * @param fieldName The name of the field. + * @return The associated QuantizationState, or null if not present. + */ + QuantizationState getQuantizationState(String fieldName) { + return cache.getIfPresent(fieldName); + } + + /** + * Adds or updates a quantization state in the cache. + * @param fieldName The name of the field. + * @param quantizationState The quantization state to store. + */ + void addQuantizationState(String fieldName, QuantizationState quantizationState) { + cache.put(fieldName, quantizationState); + } + + /** + * Removes the quantization state associated with a given field name. + * @param fieldName The name of the field. + */ + public void evict(String fieldName) { + cache.invalidate(fieldName); + } + + private void onRemoval(RemovalNotification removalNotification) { + if (RemovalCause.SIZE == removalNotification.getCause()) { + updateEvictedDueToSizeAt(); + log.info( + "[KNN] Quantization state evicted from cache. Key {}, Reason: {}", + removalNotification.getKey(), + removalNotification.getCause() + ); + } + } + + void setMaxCacheSizeInKB(long maxCacheSizeInKB) { + this.maxCacheSizeInKB = maxCacheSizeInKB; + } + + private void updateEvictedDueToSizeAt() { + evictedDueToSizeAt = Instant.now(); + } + + /** + * Clears all entries from the cache. + */ + public void clear() { + cache.invalidateAll(); + } +} diff --git a/src/main/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationStateCacheManager.java b/src/main/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationStateCacheManager.java new file mode 100644 index 000000000..932d5cde0 --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationStateCacheManager.java @@ -0,0 +1,82 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.models.quantizationState; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.opensearch.knn.index.codec.KNN990Codec.KNN990QuantizationStateReader; + +import java.io.IOException; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class QuantizationStateCacheManager { + + private static volatile QuantizationStateCacheManager instance; + + /** + * Gets the singleton instance of the cache. + * @return QuantizationStateCache + */ + public static QuantizationStateCacheManager getInstance() { + if (instance == null) { + synchronized (QuantizationStateCacheManager.class) { + if (instance == null) { + instance = new QuantizationStateCacheManager(); + } + } + } + return instance; + } + + public synchronized void rebuildCache() { + QuantizationStateCache.getInstance().rebuildCache(); + } + + /** + * Retrieves the quantization state associated with a given field name. Reads from cache first, then from disk if necessary. + * @param quantizationStateReadConfig information required from reading from off-heap if necessary + * @return The associated QuantizationState + */ + public QuantizationState getQuantizationState(QuantizationStateReadConfig quantizationStateReadConfig) throws IOException { + QuantizationState quantizationState = QuantizationStateCache.getInstance() + .getQuantizationState(quantizationStateReadConfig.getCacheKey()); + if (quantizationState == null) { + quantizationState = KNN990QuantizationStateReader.read(quantizationStateReadConfig); + if (quantizationState != null) { + addQuantizationState(quantizationStateReadConfig.getCacheKey(), quantizationState); + } + } + return quantizationState; + } + + /** + * Adds or updates a quantization state in the cache. + * @param fieldName The name of the field. + * @param quantizationState The quantization state to store. + */ + public void addQuantizationState(String fieldName, QuantizationState quantizationState) { + QuantizationStateCache.getInstance().addQuantizationState(fieldName, quantizationState); + } + + /** + * Removes the quantization state associated with a given field name. + * @param fieldName The name of the field. + */ + public void evict(String fieldName) { + QuantizationStateCache.getInstance().evict(fieldName); + } + + public void setMaxCacheSizeInKB(long maxCacheSizeInKB) { + QuantizationStateCache.getInstance().setMaxCacheSizeInKB(maxCacheSizeInKB); + } + + /** + * Clears all entries from the cache. + */ + public void clear() { + QuantizationStateCache.getInstance().clear(); + } +} diff --git a/src/main/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationStateReadConfig.java b/src/main/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationStateReadConfig.java new file mode 100644 index 000000000..d13e4f3f5 --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationStateReadConfig.java @@ -0,0 +1,20 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.models.quantizationState; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.apache.lucene.index.SegmentReadState; +import org.opensearch.knn.quantization.models.quantizationParams.QuantizationParams; + +@Getter +@AllArgsConstructor +public class QuantizationStateReadConfig { + private SegmentReadState segmentReadState; + private QuantizationParams quantizationParams; + private String field; + private String cacheKey; +} diff --git a/src/main/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationStateSerializer.java b/src/main/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationStateSerializer.java new file mode 100644 index 000000000..1f378e0dc --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationStateSerializer.java @@ -0,0 +1,56 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.models.quantizationState; + +import lombok.experimental.UtilityClass; +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.core.common.io.stream.StreamInput; + +import java.io.IOException; + +/** + * QuantizationStateSerializer is a utility class that provides methods for serializing and deserializing + * QuantizationState objects along with their specific data. + */ +@UtilityClass +class QuantizationStateSerializer { + + /** + * A functional interface for deserializing specific data associated with a QuantizationState. + */ + @FunctionalInterface + interface SerializableDeserializer { + QuantizationState deserialize(StreamInput in) throws IOException; + } + + /** + * Serializes the QuantizationState and specific data into a byte array. + * + * @param state The QuantizationState to serialize. + * @return A byte array representing the serialized state and specific data. + * @throws IOException If an I/O error occurs during serialization. + */ + static byte[] serialize(QuantizationState state) throws IOException { + try (BytesStreamOutput out = new BytesStreamOutput()) { + state.writeTo(out); + return out.bytes().toBytesRef().bytes; + } + } + + /** + * Deserializes a QuantizationState and its specific data from a byte array. + * + * @param bytes The byte array containing the serialized data. + * @param deserializer The deserializer for the specific data associated with the state. + * @return The deserialized QuantizationState including its specific data. + * @throws IOException If an I/O error occurs during deserialization. + */ + static QuantizationState deserialize(byte[] bytes, SerializableDeserializer deserializer) throws IOException { + try (StreamInput in = StreamInput.wrap(bytes)) { + return deserializer.deserialize(in); + } + } +} diff --git a/src/main/java/org/opensearch/knn/quantization/models/requests/TrainingRequest.java b/src/main/java/org/opensearch/knn/quantization/models/requests/TrainingRequest.java new file mode 100644 index 000000000..d8b0eab10 --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/models/requests/TrainingRequest.java @@ -0,0 +1,33 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.models.requests; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.io.IOException; + +/** + * TrainingRequest represents a request for training a quantizer. + * + * @param the type of vectors to be trained. + */ +@Getter +@AllArgsConstructor +public abstract class TrainingRequest { + /** + * The total number of vectors in one segment. + */ + private final int totalNumberOfVectors; + + /** + * Returns the vector corresponding to the specified document ID. + * + * @param position the document position. + * @return the vector corresponding to the specified document ID. + */ + public abstract T getVectorAtThePosition(int position) throws IOException; +} diff --git a/src/main/java/org/opensearch/knn/quantization/quantizer/BitPacker.java b/src/main/java/org/opensearch/knn/quantization/quantizer/BitPacker.java new file mode 100644 index 000000000..fe470ed74 --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/quantizer/BitPacker.java @@ -0,0 +1,143 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.quantizer; + +import lombok.experimental.UtilityClass; + +/** + * The BitPacker class provides utility methods for quantizing floating-point vectors and packing the resulting bits + * into a pre-allocated byte array. This class supports both single-bit and multi-bit quantization scenarios, + * enabling efficient storage and transmission of quantized vectors. + * + *

+ * The methods in this class are designed to be used by quantizers that need to convert floating-point vectors + * into compact binary representations by comparing them against quantization thresholds. + *

+ * + *

+ * This class is marked as a utility class using Lombok's {@link lombok.experimental.UtilityClass} annotation, + * making it a singleton and preventing instantiation. + *

+ */ +@UtilityClass +class BitPacker { + + /** + * Quantizes a given floating-point vector and packs the resulting quantized bits into a provided byte array. + * This method operates by comparing each element of the input vector against corresponding thresholds + * and encoding the results into a compact binary format using the specified number of bits per coordinate. + * + *

+ * The method supports multi-bit quantization where each coordinate of the input vector can be represented + * by multiple bits. For example, with 2-bit quantization, each coordinate is encoded into 2 bits, allowing + * for four distinct levels of quantization per coordinate. + *

+ * + *

+ * Example: + *

+ *

+ * Consider a vector with 3 coordinates: [1.2, 3.4, 5.6] and thresholds: + *

+ *
+     * thresholds = {
+     *     {1.0, 3.0, 5.0},  // First bit thresholds
+     *     {1.5, 3.5, 5.5}   // Second bit thresholds
+     * };
+     * 
+ *

+ * If the number of bits per coordinate is 2, the quantization process will proceed as follows: + *

+ *
    + *
  • First bit comparison: + *
      + *
    • 1.2 > 1.0 -> 1
    • + *
    • 3.4 > 3.0 -> 1
    • + *
    • 5.6 > 5.0 -> 1
    • + *
    + *
  • + *
  • Second bit comparison: + *
      + *
    • 1.2 <= 1.5 -> 0
    • + *
    • 3.4 <= 3.5 -> 0
    • + *
    • 5.6 > 5.5 -> 1
    • + *
    + *
  • + *
+ *

+ * The resulting quantized bits will be 11 10 11, which is packed into the provided byte array. + * If there are fewer than 8 bits, the remaining bits in the byte are set to 0. + *

+ * + *

+ * Packing Process: + * The quantized bits are packed into the byte array. The first coordinate's bits are stored in the most + * significant positions of the first byte, followed by the second coordinate, and so on. In the example + * above, the resulting byte array will have the following binary representation: + *

+ *
+     * packedBits = [11011000] // Only the first 6 bits are used, and the last two are set to 0.
+     * 
+ * + *

Bitwise Operations Explanation:

+ *
    + *
  • byteIndex: This is calculated using byteIndex = bitPosition >> 3, which is equivalent to bitPosition / 8. It determines which byte in the byte array the current bit should be placed in.
  • + *
  • bitIndex: This is calculated using bitIndex = 7 - (bitPosition & 7), which is equivalent to 7 - (bitPosition % 8). It determines the exact bit position within the byte.
  • + *
  • Setting the bit: The bit is set using packedBits[byteIndex] |= (1 << bitIndex). This shifts a 1 into the correct bit position and ORs it with the existing byte value to set the bit.
  • + *
+ * + * @param vector the floating-point vector to be quantized. + * @param thresholds a 2D array representing the quantization thresholds. The first dimension corresponds to the number of bits per coordinate, and the second dimension corresponds to the vector's length. + * @param bitsPerCoordinate the number of bits used per coordinate, determining the granularity of the quantization. + * @param packedBits the byte array where the quantized bits will be packed. + */ + void quantizeAndPackBits(final float[] vector, final float[][] thresholds, final int bitsPerCoordinate, byte[] packedBits) { + int vectorLength = vector.length; + + for (int i = 0; i < bitsPerCoordinate; i++) { + for (int j = 0; j < vectorLength; j++) { + if (vector[j] > thresholds[i][j]) { + int bitPosition = i * vectorLength + j; + // Calculate the index of the byte in the packedBits array. + int byteIndex = bitPosition >> 3; // Equivalent to bitPosition / 8 + // Calculate the bit index within the byte. + int bitIndex = 7 - (bitPosition & 7); // Equivalent to 7 - (bitPosition % 8) + // Set the bit at the calculated position. + packedBits[byteIndex] |= (1 << bitIndex); // Set the bit at bitIndex + } + } + } + } + + /** + * Overloaded method to quantize a vector using single-bit quantization and pack the results into a provided byte array. + * + *

+ * This method is specifically designed for one-bit quantization scenarios, where each coordinate of the + * vector is represented by a single bit indicating whether the value is above or below the threshold. + *

+ * + *

Example:

+ *

+ * If we have a vector [1.2, 3.4, 5.6] and thresholds [2.0, 3.0, 4.0], the quantization process will be: + *

+ *
    + *
  • 1.2 < 2.0 -> 0
  • + *
  • 3.4 > 3.0 -> 1
  • + *
  • 5.6 > 4.0 -> 1
  • + *
+ *

+ * The quantized vector will be [0, 1, 1]. + *

+ * + * @param vector the vector to quantize. + * @param thresholds the thresholds for quantization, where each element represents the threshold for a corresponding coordinate. + * @param packedBits the byte array where the quantized bits will be packed. + */ + void quantizeAndPackBits(final float[] vector, final float[] thresholds, byte[] packedBits) { + quantizeAndPackBits(vector, new float[][] { thresholds }, 1, packedBits); + } +} diff --git a/src/main/java/org/opensearch/knn/quantization/quantizer/MultiBitScalarQuantizer.java b/src/main/java/org/opensearch/knn/quantization/quantizer/MultiBitScalarQuantizer.java new file mode 100644 index 000000000..0bcc252d1 --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/quantizer/MultiBitScalarQuantizer.java @@ -0,0 +1,186 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + * + */ + +package org.opensearch.knn.quantization.quantizer; + +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; +import org.opensearch.knn.quantization.models.quantizationOutput.QuantizationOutput; +import org.opensearch.knn.quantization.models.quantizationParams.ScalarQuantizationParams; +import org.opensearch.knn.quantization.models.quantizationState.MultiBitScalarQuantizationState; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; +import org.opensearch.knn.quantization.models.requests.TrainingRequest; +import org.opensearch.knn.quantization.sampler.Sampler; +import org.opensearch.knn.quantization.sampler.SamplerType; +import org.opensearch.knn.quantization.sampler.SamplingFactory; +import oshi.util.tuples.Pair; + +import java.io.IOException; + +/** + * MultiBitScalarQuantizer is responsible for quantizing vectors into multi-bit representations per dimension. + * Unlike the OneBitScalarQuantizer, which uses a single bit per dimension to represent whether a value is above + * or below a mean threshold, the MultiBitScalarQuantizer allows for multiple bits per dimension, enabling more + * granular and precise quantization. + * + *

+ * In a OneBitScalarQuantizer, each dimension of a vector is compared to a single threshold (the mean), and a single + * bit is used to indicate whether the value is above or below that threshold. This results in a very coarse + * representation where each dimension is either "on" or "off." + *

+ * + *

+ * The MultiBitScalarQuantizer, on the other hand, uses multiple thresholds per dimension. For example, in a 2-bit + * quantization scheme, three thresholds are used to divide each dimension into four possible regions. Each region + * is represented by a unique 2-bit value. This allows for a much finer representation of the data, capturing more + * nuances in the variation of each dimension. + *

+ * + *

+ * The thresholds in MultiBitScalarQuantizer are calculated based on the mean and standard deviation of the sampled + * vectors for each dimension. Here's how it works: + *

+ * + *
    + *
  • First, the mean and standard deviation are computed for each dimension across the sampled vectors.
  • + *
  • For each bit used in the quantization (e.g., 2 bits per coordinate), the thresholds are calculated + * using a linear combination of the mean and the standard deviation. The combination coefficients are + * determined by the number of bits, allowing the thresholds to split the data into equal probability regions. + *
  • + *
  • For example, in a 2-bit quantization (which divides data into four regions), the thresholds might be + * set at points corresponding to -1 standard deviation, 0 standard deviations (mean), and +1 standard deviation. + * This ensures that the data is evenly split into four regions, each represented by a 2-bit value. + *
  • + *
+ * + *

+ * The number of bits per coordinate is determined by the type of scalar quantization being applied, such as 2-bit + * or 4-bit quantization. The increased number of bits per coordinate in MultiBitScalarQuantizer allows for better + * preservation of information during the quantization process, making it more suitable for tasks where precision + * is crucial. However, this comes at the cost of increased storage and computational complexity compared to the + * simpler OneBitScalarQuantizer. + *

+ */ +public class MultiBitScalarQuantizer implements Quantizer { + private final int bitsPerCoordinate; // Number of bits used to quantize each dimension + private final int samplingSize; // Sampling size for training + private final Sampler sampler; // Sampler for training + private static final boolean IS_TRAINING_REQUIRED = true; + // Currently Lucene has sampling size as + // 25000 for segment level training , Keeping same + // to having consistent, Will revisit + // if this requires change + private static final int DEFAULT_SAMPLE_SIZE = 25000; + + /** + * Constructs a MultiBitScalarQuantizer with a specified number of bits per coordinate. + * + * @param bitsPerCoordinate the number of bits used per coordinate for quantization. + */ + public MultiBitScalarQuantizer(final int bitsPerCoordinate) { + this(bitsPerCoordinate, DEFAULT_SAMPLE_SIZE, SamplingFactory.getSampler(SamplerType.RESERVOIR)); + } + + /** + * Constructs a MultiBitScalarQuantizer with a specified number of bits per coordinate and sampling size. + * + * @param bitsPerCoordinate the number of bits used per coordinate for quantization. + * @param samplingSize the number of samples to use for training. + * @param sampler the sampler to use for training. + */ + public MultiBitScalarQuantizer(final int bitsPerCoordinate, final int samplingSize, final Sampler sampler) { + if (bitsPerCoordinate < 2) { + throw new IllegalArgumentException("bitsPerCoordinate must be greater than or equal to 2 for multibit quantizer."); + } + this.bitsPerCoordinate = bitsPerCoordinate; + this.samplingSize = samplingSize; + this.sampler = sampler; + } + + /** + * Trains the quantizer based on the provided training request, which should be of type SamplingTrainingRequest. + * The training process calculates the mean and standard deviation for each dimension and then determines + * threshold values for quantization based on these statistics. + * + * @param trainingRequest the request containing the data and parameters for training. + * @return a MultiBitScalarQuantizationState containing the computed thresholds. + */ + @Override + public QuantizationState train(final TrainingRequest trainingRequest) throws IOException { + int[] sampledIndices = sampler.sample(trainingRequest.getTotalNumberOfVectors(), samplingSize); + // Calculate sum, mean, and standard deviation in one pass + Pair meanAndStdDev = QuantizerHelper.calculateMeanAndStdDev(trainingRequest, sampledIndices); + float[][] thresholds = calculateThresholds(meanAndStdDev.getA(), meanAndStdDev.getB()); + ScalarQuantizationParams params = (bitsPerCoordinate == 2) + ? new ScalarQuantizationParams(ScalarQuantizationType.TWO_BIT) + : new ScalarQuantizationParams(ScalarQuantizationType.FOUR_BIT); + return new MultiBitScalarQuantizationState(params, thresholds); + } + + /** + * Quantizes the provided vector using the provided quantization state, producing a quantized output. + * The vector is quantized based on the thresholds in the quantization state. + * + * @param vector the vector to quantize. + * @param state the quantization state containing threshold information. + * @param output the QuantizationOutput object to store the quantized representation of the vector. + */ + @Override + public void quantize(final float[] vector, final QuantizationState state, final QuantizationOutput output) { + if (vector == null) { + throw new IllegalArgumentException("Vector to quantize must not be null."); + } + validateState(state); + int vectorLength = vector.length; + MultiBitScalarQuantizationState multiBitState = (MultiBitScalarQuantizationState) state; + float[][] thresholds = multiBitState.getThresholds(); + if (thresholds == null || thresholds[0].length != vector.length) { + throw new IllegalArgumentException("Thresholds must not be null and must match the dimension of the vector."); + } + output.prepareQuantizedVector(vectorLength); + BitPacker.quantizeAndPackBits(vector, thresholds, bitsPerCoordinate, output.getQuantizedVector()); + } + + /** + * Calculates the thresholds for quantization based on mean and standard deviation. + * + * @param meanArray the mean for each dimension. + * @param stdDevArray the standard deviation for each dimension. + * @return the thresholds for quantization. + */ + private float[][] calculateThresholds(final float[] meanArray, final float[] stdDevArray) { + int dimension = meanArray.length; + float[][] thresholds = new float[bitsPerCoordinate][dimension]; + float coef = bitsPerCoordinate + 1; + for (int i = 0; i < bitsPerCoordinate; i++) { + float iCoef = -1 + 2 * (i + 1) / coef; + for (int j = 0; j < dimension; j++) { + thresholds[i][j] = meanArray[j] + iCoef * stdDevArray[j]; + } + } + return thresholds; + } + + /** + * Validates the quantization state to ensure it is of the expected type. + * + * @param state the quantization state to validate. + * @throws IllegalArgumentException if the state is not an instance of MultiBitScalarQuantizationState. + */ + private void validateState(final QuantizationState state) { + if (!(state instanceof MultiBitScalarQuantizationState)) { + throw new IllegalArgumentException("Quantization state must be of type MultiBitScalarQuantizationState."); + } + } + + /** + * Returns the number of bits per coordinate used by this quantizer. + * + * @return the number of bits per coordinate. + */ + public int getBitsPerCoordinate() { + return bitsPerCoordinate; + } +} diff --git a/src/main/java/org/opensearch/knn/quantization/quantizer/OneBitScalarQuantizer.java b/src/main/java/org/opensearch/knn/quantization/quantizer/OneBitScalarQuantizer.java new file mode 100644 index 000000000..3cba89c39 --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/quantizer/OneBitScalarQuantizer.java @@ -0,0 +1,102 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.quantizer; + +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; +import org.opensearch.knn.quantization.models.quantizationOutput.QuantizationOutput; +import org.opensearch.knn.quantization.models.quantizationParams.ScalarQuantizationParams; +import org.opensearch.knn.quantization.models.quantizationState.OneBitScalarQuantizationState; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; +import org.opensearch.knn.quantization.models.requests.TrainingRequest; +import org.opensearch.knn.quantization.sampler.Sampler; +import org.opensearch.knn.quantization.sampler.SamplerType; +import org.opensearch.knn.quantization.sampler.SamplingFactory; + +import java.io.IOException; + +/** + * OneBitScalarQuantizer is responsible for quantizing vectors using a single bit per dimension. + * It computes the mean of each dimension during training and then uses these means as thresholds + * for quantizing the vectors. + */ +public class OneBitScalarQuantizer implements Quantizer { + private final int samplingSize; // Sampling size for training + private static final boolean IS_TRAINING_REQUIRED = true; + private final Sampler sampler; // Sampler for training + // Currently Lucene has sampling size as + // 25000 for segment level training , Keeping same + // to having consistent, Will revisit + // if this requires change + private static final int DEFAULT_SAMPLE_SIZE = 25000; + + /** + * Constructs a OneBitScalarQuantizer with a default sampling size of 25000. + */ + public OneBitScalarQuantizer() { + this(DEFAULT_SAMPLE_SIZE, SamplingFactory.getSampler(SamplerType.RESERVOIR)); + } + + /** + * Constructs a OneBitScalarQuantizer with a specified sampling size. + * + * @param samplingSize the number of samples to use for training. + */ + public OneBitScalarQuantizer(final int samplingSize, final Sampler sampler) { + + this.samplingSize = samplingSize; + this.sampler = sampler; + } + + /** + * Trains the quantizer by calculating the mean of each dimension from the sampled vectors. + * These means are used as thresholds in the quantization process. + * + * @param trainingRequest the request containing the data and parameters for training. + * @return a OneBitScalarQuantizationState containing the calculated means. + */ + @Override + public QuantizationState train(final TrainingRequest trainingRequest) throws IOException { + int[] sampledDocIds = sampler.sample(trainingRequest.getTotalNumberOfVectors(), samplingSize); + float[] meanThresholds = QuantizerHelper.calculateMeanThresholds(trainingRequest, sampledDocIds); + return new OneBitScalarQuantizationState(new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT), meanThresholds); + } + + /** + * Quantizes the provided vector using the given quantization state. + * It compares each dimension of the vector against the corresponding mean (threshold) to determine the quantized value. + * + * @param vector the vector to quantize. + * @param state the quantization state containing the means for each dimension. + * @param output the QuantizationOutput object to store the quantized representation of the vector. + */ + @Override + public void quantize(final float[] vector, final QuantizationState state, final QuantizationOutput output) { + if (vector == null) { + throw new IllegalArgumentException("Vector to quantize must not be null."); + } + validateState(state); + int vectorLength = vector.length; + OneBitScalarQuantizationState binaryState = (OneBitScalarQuantizationState) state; + float[] thresholds = binaryState.getMeanThresholds(); + if (thresholds == null || thresholds.length != vectorLength) { + throw new IllegalArgumentException("Thresholds must not be null and must match the dimension of the vector."); + } + output.prepareQuantizedVector(vectorLength); + BitPacker.quantizeAndPackBits(vector, thresholds, output.getQuantizedVector()); + } + + /** + * Validates the quantization state to ensure it is of the expected type. + * + * @param state the quantization state to validate. + * @throws IllegalArgumentException if the state is not an instance of OneBitScalarQuantizationState. + */ + private void validateState(final QuantizationState state) { + if (!(state instanceof OneBitScalarQuantizationState)) { + throw new IllegalArgumentException("Quantization state must be of type OneBitScalarQuantizationState."); + } + } +} diff --git a/src/main/java/org/opensearch/knn/quantization/quantizer/Quantizer.java b/src/main/java/org/opensearch/knn/quantization/quantizer/Quantizer.java new file mode 100644 index 000000000..521863205 --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/quantizer/Quantizer.java @@ -0,0 +1,42 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.quantizer; + +import org.opensearch.knn.quantization.models.quantizationOutput.QuantizationOutput; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; +import org.opensearch.knn.quantization.models.requests.TrainingRequest; + +import java.io.IOException; + +/** + * The Quantizer interface defines the methods required for training and quantizing vectors + * in the context of K-Nearest Neighbors (KNN) and similar machine learning tasks. + * It supports training to determine quantization parameters and quantizing data vectors + * based on these parameters. + * + * @param The type of the vector or data to be quantized. + * @param The type of the quantized output, typically a compressed or encoded representation. + */ +public interface Quantizer { + + /** + * Trains the quantizer based on the provided training request. The training process typically + * involves learning parameters that can be used to quantize vectors. + * + * @param trainingRequest the request containing data and parameters for training. + * @return a QuantizationState containing the learned parameters. + */ + QuantizationState train(TrainingRequest trainingRequest) throws IOException; + + /** + * Quantizes the provided vector using the specified quantization state. + * + * @param vector the vector to quantize. + * @param state the quantization state containing parameters for quantization. + * @param output the QuantizationOutput object to store the quantized representation of the vector. + */ + void quantize(T vector, QuantizationState state, QuantizationOutput output); +} diff --git a/src/main/java/org/opensearch/knn/quantization/quantizer/QuantizerHelper.java b/src/main/java/org/opensearch/knn/quantization/quantizer/QuantizerHelper.java new file mode 100644 index 000000000..bac2067c0 --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/quantizer/QuantizerHelper.java @@ -0,0 +1,108 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.quantizer; + +import org.opensearch.knn.quantization.models.requests.TrainingRequest; +import lombok.experimental.UtilityClass; +import oshi.util.tuples.Pair; + +import java.io.IOException; + +/** + * Utility class providing common methods for quantizer operations, such as parameter validation and + * extraction. This class is designed to be used with various quantizer implementations that require + * consistent handling of training requests and sampled indices. + */ +@UtilityClass +class QuantizerHelper { + /** + * Calculates the mean vector from a set of sampled vectors. + * + * @param samplingRequest The {@link TrainingRequest} containing the dataset and methods to access vectors by their indices. + * @param sampledIndices An array of indices representing the sampled vectors to be used for mean calculation. + * @return A float array representing the mean vector of the sampled vectors. + * @throws IllegalArgumentException If any of the vectors at the sampled indices are null. + * @throws IllegalStateException If the mean array is unexpectedly null after processing the vectors. + */ + static float[] calculateMeanThresholds(TrainingRequest samplingRequest, int[] sampledIndices) throws IOException { + int totalSamples = sampledIndices.length; + float[] mean = null; + int lastIndex = 0; + for (int docId : sampledIndices) { + float[] vector = samplingRequest.getVectorAtThePosition(docId); + if (vector == null) { + throw new IllegalArgumentException("Vector at sampled index " + docId + " is null."); + } + if (mean == null) { + mean = new float[vector.length]; + } + for (int j = 0; j < vector.length; j++) { + mean[j] += vector[j]; + } + } + if (mean == null) { + throw new IllegalStateException("Mean array should not be null after processing vectors."); + } + for (int j = 0; j < mean.length; j++) { + mean[j] /= totalSamples; + } + return mean; + } + + /** + * Calculates the mean and standard deviation for each dimension of the vectors in the training request. + *

+ * This method processes the vectors specified by the sampled indices and calculates both the mean and + * standard deviation in one pass. The results are returned as a pair of arrays: one for the means and + * one for the standard deviations. + * + * @param trainingRequest The request containing the data and parameters for training. + * @param sampledIndices An array of document IDs representing the sampled indices to be processed. + * @return A Pair containing two float arrays: the first array represents the mean of each dimension, + * and the second array represents the standard deviation of each dimension. + * @throws IllegalArgumentException if any of the vectors at the sampled indices are null. + * @throws IllegalStateException if the mean or standard deviation arrays are not initialized after processing. + */ + static Pair calculateMeanAndStdDev(TrainingRequest trainingRequest, int[] sampledIndices) + throws IOException { + float[] meanArray = null; + float[] stdDevArray = null; + int totalSamples = sampledIndices.length; + int lastIndex = 0; + for (int docId : sampledIndices) { + float[] vector = trainingRequest.getVectorAtThePosition(docId); + if (vector == null) { + throw new IllegalArgumentException("Vector at sampled index " + docId + " is null."); + } + int dimension = vector.length; + + // Initialize meanArray and stdDevArray on the first iteration + if (meanArray == null) { + meanArray = new float[dimension]; + } + if (stdDevArray == null) { + stdDevArray = new float[dimension]; + } + + for (int j = 0; j < dimension; j++) { + meanArray[j] += vector[j]; + stdDevArray[j] += vector[j] * vector[j]; + } + } + if (meanArray == null || stdDevArray == null) { + throw new IllegalStateException("Mean and StdDev should not be null after processing vectors."); + } + + // Calculate mean and standard deviation in one pass + for (int j = 0; j < meanArray.length; j++) { + meanArray[j] = meanArray[j] / totalSamples; + stdDevArray[j] = (float) Math.sqrt((stdDevArray[j] / totalSamples) - (meanArray[j] * meanArray[j])); + } + + // Return both arrays as a Pair + return new Pair<>(meanArray, stdDevArray); + } +} diff --git a/src/main/java/org/opensearch/knn/quantization/sampler/ReservoirSampler.java b/src/main/java/org/opensearch/knn/quantization/sampler/ReservoirSampler.java new file mode 100644 index 000000000..020efe54f --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/sampler/ReservoirSampler.java @@ -0,0 +1,90 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.sampler; + +import lombok.NoArgsConstructor; + +import java.util.Arrays; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.IntStream; + +/** + * ReservoirSampler implements the Sampler interface and provides a method for sampling + * a specified number of indices from a total number of vectors using the reservoir sampling algorithm. + * This algorithm is particularly useful for randomly sampling a subset of data from a larger set + * when the total size of the dataset is unknown or very large. + */ +@NoArgsConstructor +final class ReservoirSampler implements Sampler { + /** + * Singleton instance holder. + */ + private static ReservoirSampler instance; + + /** + * Provides the singleton instance of ReservoirSampler. + * + * @return the singleton instance of ReservoirSampler. + */ + public static synchronized ReservoirSampler getInstance() { + if (instance == null) { + instance = new ReservoirSampler(); + } + return instance; + } + + /** + * Samples indices from the range [0, totalNumberOfVectors). + * If the total number of vectors is less than or equal to the sample size, it returns all indices. + * Otherwise, it uses the reservoir sampling algorithm to select a random subset. + * + * @param totalNumberOfVectors the total number of vectors to sample from. + * @param sampleSize the number of indices to sample. + * @return an array of sampled indices. + */ + @Override + public int[] sample(final int totalNumberOfVectors, final int sampleSize) { + if (totalNumberOfVectors <= sampleSize) { + return IntStream.range(0, totalNumberOfVectors).toArray(); + } + return reservoirSampleIndices(totalNumberOfVectors, sampleSize); + } + + /** + * Applies the reservoir sampling algorithm to select a random sample of indices. + * This method ensures that each index in the range [0, numVectors) has an equal probability + * of being included in the sample. + * + * Reservoir sampling is particularly useful for selecting a random sample from a large or unknown-sized dataset. + * For more information on the algorithm, see the following link: + * Reservoir Sampling - Wikipedia + * + * @param numVectors the total number of vectors. + * @param sampleSize the number of indices to sample. + * @return an array of sampled indices. + */ + private int[] reservoirSampleIndices(final int numVectors, final int sampleSize) { + int[] indices = new int[sampleSize]; + + // Initialize the reservoir with the first sampleSize elements + for (int i = 0; i < sampleSize; i++) { + indices[i] = i; + } + + // Replace elements with gradually decreasing probability + for (int i = sampleSize; i < numVectors; i++) { + int j = ThreadLocalRandom.current().nextInt(i + 1); + if (j < sampleSize) { + indices[j] = i; + } + } + + // Sort the sampled indices + Arrays.sort(indices); + + return indices; + } +} diff --git a/src/main/java/org/opensearch/knn/quantization/sampler/Sampler.java b/src/main/java/org/opensearch/knn/quantization/sampler/Sampler.java new file mode 100644 index 000000000..5ff385972 --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/sampler/Sampler.java @@ -0,0 +1,25 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.sampler; + +/** + * The Sampler interface defines the contract for sampling strategies + * used in various quantization processes. Implementations of this + * interface should provide specific strategies for selecting a sample + * from a given set of vectors. + */ +public interface Sampler { + + /** + * Samples a subset of indices from the total number of vectors. + * + * @param totalNumberOfVectors the total number of vectors available. + * @param sampleSize the number of vectors to be sampled. + * @return an array of integers representing the indices of the sampled vectors. + * @throws IllegalArgumentException if the sample size is greater than the total number of vectors. + */ + int[] sample(int totalNumberOfVectors, int sampleSize); +} diff --git a/src/main/java/org/opensearch/knn/quantization/sampler/SamplerType.java b/src/main/java/org/opensearch/knn/quantization/sampler/SamplerType.java new file mode 100644 index 000000000..cd9b301df --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/sampler/SamplerType.java @@ -0,0 +1,14 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.sampler; + +/** + * SamplerType is an enumeration of the different types of samplers that can be created by the factory. + */ +public enum SamplerType { + RESERVOIR, // Represents a reservoir sampling strategy + // Add more enum values here for additional sampler types +} diff --git a/src/main/java/org/opensearch/knn/quantization/sampler/SamplingFactory.java b/src/main/java/org/opensearch/knn/quantization/sampler/SamplingFactory.java new file mode 100644 index 000000000..80fe5bdae --- /dev/null +++ b/src/main/java/org/opensearch/knn/quantization/sampler/SamplingFactory.java @@ -0,0 +1,34 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.sampler; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +/** + * SamplingFactory is a factory class for creating instances of Sampler. + * It uses the factory design pattern to encapsulate the creation logic for different types of samplers. + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class SamplingFactory { + + /** + * Creates and returns a Sampler instance based on the specified SamplerType. + * + * @param samplerType the type of sampler to create. + * @return a Sampler instance. + * @throws IllegalArgumentException if the sampler type is not supported. + */ + public static Sampler getSampler(final SamplerType samplerType) { + switch (samplerType) { + case RESERVOIR: + return ReservoirSampler.getInstance(); + // Add more cases for different samplers here + default: + throw new IllegalArgumentException("Unsupported sampler type: " + samplerType); + } + } +} diff --git a/src/main/java/org/opensearch/knn/training/BinaryTrainingDataConsumer.java b/src/main/java/org/opensearch/knn/training/BinaryTrainingDataConsumer.java new file mode 100644 index 000000000..6760afc9f --- /dev/null +++ b/src/main/java/org/opensearch/knn/training/BinaryTrainingDataConsumer.java @@ -0,0 +1,74 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.training; + +import lombok.extern.log4j.Log4j2; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.knn.index.memory.NativeMemoryAllocation; +import org.opensearch.knn.jni.JNICommons; +import org.opensearch.search.SearchHit; + +import java.util.ArrayList; +import java.util.List; + +/** + * Transfers binary vectors from JVM to native memory. + */ +@Log4j2 +public class BinaryTrainingDataConsumer extends TrainingDataConsumer { + + /** + * Constructor + * + * @param trainingDataAllocation NativeMemoryAllocation that contains information about native memory allocation. + */ + public BinaryTrainingDataConsumer(NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation) { + super(trainingDataAllocation); + } + + @Override + public void accept(List byteVectors) { + long memoryAddress = trainingDataAllocation.getMemoryAddress(); + memoryAddress = JNICommons.storeBinaryVectorData(memoryAddress, byteVectors.toArray(new byte[0][0]), byteVectors.size()); + trainingDataAllocation.setMemoryAddress(memoryAddress); + } + + @Override + public void processTrainingVectors(SearchResponse searchResponse, int vectorsToAdd, String fieldName) { + SearchHit[] hits = searchResponse.getHits().getHits(); + List vectors = new ArrayList<>(); + String[] fieldPath = fieldName.split("\\."); + int nullVectorCount = 0; + + for (int vector = 0; vector < vectorsToAdd; vector++) { + Object fieldValue = extractFieldValue(hits[vector], fieldPath); + if (fieldValue == null) { + nullVectorCount++; + continue; + } + + byte[] byteArray; + if (!(fieldValue instanceof List)) { + continue; + } + List fieldList = (List) fieldValue; + byteArray = new byte[fieldList.size()]; + for (int i = 0; i < fieldList.size(); i++) { + byteArray[i] = fieldList.get(i).byteValue(); + } + + vectors.add(byteArray); + } + + if (nullVectorCount > 0) { + log.warn("Found {} documents with null byte vectors in field {}", nullVectorCount, fieldName); + } + + setTotalVectorsCountAdded(getTotalVectorsCountAdded() + vectors.size()); + + accept(vectors); + } +} diff --git a/src/main/java/org/opensearch/knn/training/ByteTrainingDataConsumer.java b/src/main/java/org/opensearch/knn/training/ByteTrainingDataConsumer.java new file mode 100644 index 000000000..c51a96533 --- /dev/null +++ b/src/main/java/org/opensearch/knn/training/ByteTrainingDataConsumer.java @@ -0,0 +1,69 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.training; + +import org.opensearch.action.search.SearchResponse; +import org.opensearch.knn.index.memory.NativeMemoryAllocation; +import org.opensearch.knn.jni.JNICommons; +import org.opensearch.search.SearchHit; + +import java.util.ArrayList; +import java.util.List; + +/** + * Transfers byte vectors from JVM to native memory. + */ +public class ByteTrainingDataConsumer extends TrainingDataConsumer { + + /** + * Constructor + * + * @param trainingDataAllocation NativeMemoryAllocation that contains information about native memory allocation. + */ + public ByteTrainingDataConsumer(NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation) { + super(trainingDataAllocation); + } + + @Override + public void accept(List byteVectors) { + long memoryAddress = trainingDataAllocation.getMemoryAddress(); + memoryAddress = JNICommons.storeByteVectorData(memoryAddress, byteVectors.toArray(new byte[0][0]), byteVectors.size()); + trainingDataAllocation.setMemoryAddress(memoryAddress); + } + + @Override + public void processTrainingVectors(SearchResponse searchResponse, int vectorsToAdd, String fieldName) { + SearchHit[] hits = searchResponse.getHits().getHits(); + List vectors = new ArrayList<>(); + String[] fieldPath = fieldName.split("\\."); + + for (int vector = 0; vector < vectorsToAdd; vector++) { + Object fieldValue = extractFieldValue(hits[vector], fieldPath); + + byte[] byteArray; + if (!(fieldValue instanceof List)) { + continue; + } + List fieldList = (List) fieldValue; + byteArray = new byte[fieldList.size()]; + for (int i = 0; i < fieldList.size(); i++) { + byteArray[i] = fieldList.get(i).byteValue(); + } + + vectors.add(byteArray); + } + + setTotalVectorsCountAdded(getTotalVectorsCountAdded() + vectors.size()); + + accept(vectors); + } +} diff --git a/src/main/java/org/opensearch/knn/training/FloatTrainingDataConsumer.java b/src/main/java/org/opensearch/knn/training/FloatTrainingDataConsumer.java new file mode 100644 index 000000000..292752945 --- /dev/null +++ b/src/main/java/org/opensearch/knn/training/FloatTrainingDataConsumer.java @@ -0,0 +1,115 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.training; + +import org.apache.commons.lang.ArrayUtils; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.jni.JNICommons; +import org.opensearch.knn.jni.JNIService; +import org.opensearch.knn.index.memory.NativeMemoryAllocation; +import org.opensearch.knn.quantization.factory.QuantizerFactory; +import org.opensearch.knn.quantization.models.quantizationOutput.BinaryQuantizationOutput; +import org.opensearch.knn.quantization.models.quantizationParams.ScalarQuantizationParams; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; +import org.opensearch.knn.quantization.models.requests.TrainingRequest; +import org.opensearch.knn.quantization.quantizer.Quantizer; +import org.opensearch.search.SearchHit; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Transfers float vectors from JVM to native memory. + */ +public class FloatTrainingDataConsumer extends TrainingDataConsumer { + + private final QuantizationConfig quantizationConfig; + + /** + * Constructor + * + * @param trainingDataAllocation NativeMemoryAllocation that contains information about native memory allocation. + */ + public FloatTrainingDataConsumer(NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation) { + super(trainingDataAllocation); + this.quantizationConfig = trainingDataAllocation.getQuantizationConfig(); + } + + @Override + public void accept(List floats) { + if (isValidFloatsAndQuantizationConfig(floats)) { + try { + List byteVectors = quantizeVectors(floats); + long memoryAddress = trainingDataAllocation.getMemoryAddress(); + memoryAddress = JNICommons.storeBinaryVectorData(memoryAddress, byteVectors.toArray(new byte[0][0]), byteVectors.size()); + trainingDataAllocation.setMemoryAddress(memoryAddress); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + trainingDataAllocation.setMemoryAddress( + JNIService.transferVectors( + trainingDataAllocation.getMemoryAddress(), + floats.stream().map(v -> ArrayUtils.toPrimitive((Float[]) v)).toArray(float[][]::new) + ) + ); + } + } + + @Override + public void processTrainingVectors(SearchResponse searchResponse, int vectorsToAdd, String fieldName) { + SearchHit[] hits = searchResponse.getHits().getHits(); + List vectors = new ArrayList<>(); + String[] fieldPath = fieldName.split("\\."); + + for (int vector = 0; vector < vectorsToAdd; vector++) { + Object fieldValue = extractFieldValue(hits[vector], fieldPath); + if (!(fieldValue instanceof List)) { + continue; + } + + List fieldList = (List) fieldValue; + vectors.add(fieldList.stream().map(Number::floatValue).toArray(Float[]::new)); + } + + setTotalVectorsCountAdded(getTotalVectorsCountAdded() + vectors.size()); + + accept(vectors); + } + + private List quantizeVectors(List vectors) throws IOException { + List bytes = new ArrayList<>(); + ScalarQuantizationParams quantizationParams = new ScalarQuantizationParams(quantizationConfig.getQuantizationType()); + Quantizer quantizer = QuantizerFactory.getQuantizer(quantizationParams); + // Create training request + TrainingRequest trainingRequest = new TrainingRequest(vectors.size()) { + @Override + public float[] getVectorAtThePosition(int position) { + return ArrayUtils.toPrimitive((Float[]) vectors.get(position)); + } + }; + QuantizationState quantizationState = quantizer.train(trainingRequest); + BinaryQuantizationOutput binaryQuantizationOutput = new BinaryQuantizationOutput(quantizationConfig.getQuantizationType().getId()); + for (int i = 0; i < vectors.size(); i++) { + quantizer.quantize(ArrayUtils.toPrimitive((Float[]) vectors.get(i)), quantizationState, binaryQuantizationOutput); + bytes.add(binaryQuantizationOutput.getQuantizedVectorCopy()); + } + + return bytes; + } + + private boolean isValidFloatsAndQuantizationConfig(List floats) { + return floats != null && floats.isEmpty() == false && quantizationConfig != null && quantizationConfig != QuantizationConfig.EMPTY; + } +} diff --git a/src/main/java/org/opensearch/knn/training/TrainingDataConsumer.java b/src/main/java/org/opensearch/knn/training/TrainingDataConsumer.java index 6732bd3f4..9d0683fdc 100644 --- a/src/main/java/org/opensearch/knn/training/TrainingDataConsumer.java +++ b/src/main/java/org/opensearch/knn/training/TrainingDataConsumer.java @@ -11,19 +11,25 @@ package org.opensearch.knn.training; -import org.apache.commons.lang.ArrayUtils; -import org.opensearch.knn.jni.JNIService; +import lombok.Getter; +import lombok.Setter; +import org.opensearch.action.search.SearchResponse; import org.opensearch.knn.index.memory.NativeMemoryAllocation; +import org.opensearch.search.SearchHit; import java.util.List; -import java.util.function.Consumer; +import java.util.Map; /** - * Transfers vectors from JVM to native memory. + * TrainingDataConsumer is an abstract class that defines the interface for consuming training data. + * It is used to process training data and add it to the training data allocation. */ -public class TrainingDataConsumer implements Consumer> { +public abstract class TrainingDataConsumer { - private final NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation; + @Setter + @Getter + private int totalVectorsCountAdded = 0; + protected final NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation; /** * Constructor @@ -34,13 +40,25 @@ public TrainingDataConsumer(NativeMemoryAllocation.TrainingDataAllocation traini this.trainingDataAllocation = trainingDataAllocation; } - @Override - public void accept(List floats) { - trainingDataAllocation.setMemoryAddress( - JNIService.transferVectors( - trainingDataAllocation.getMemoryAddress(), - floats.stream().map(ArrayUtils::toPrimitive).toArray(float[][]::new) - ) - ); + protected abstract void accept(List vectors); + + public abstract void processTrainingVectors(SearchResponse searchResponse, int vectorsToAdd, String fieldName); + + /** + * Traverses the hit to the desired field and extracts its value. + * + * @param hit The search hit to extract the field value from + * @param fieldPath The path to the desired field + * @return The extracted field value, or null if the field does not exist + */ + protected Object extractFieldValue(SearchHit hit, String[] fieldPath) { + Map currentMap = hit.getSourceAsMap(); + for (int pathPart = 0; pathPart < fieldPath.length - 1; pathPart++) { + currentMap = (Map) currentMap.get(fieldPath[pathPart]); + if (currentMap == null) { + return null; + } + } + return currentMap.get(fieldPath[fieldPath.length - 1]); } } diff --git a/src/main/java/org/opensearch/knn/training/TrainingJob.java b/src/main/java/org/opensearch/knn/training/TrainingJob.java index c83c69831..b479192e8 100644 --- a/src/main/java/org/opensearch/knn/training/TrainingJob.java +++ b/src/main/java/org/opensearch/knn/training/TrainingJob.java @@ -11,14 +11,21 @@ package org.opensearch.knn.training; +import lombok.Getter; +import org.apache.commons.lang.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.common.Strings; import org.opensearch.common.UUIDs; import org.opensearch.knn.common.KNNConstants; import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNLibraryIndexingContext; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; import org.opensearch.knn.jni.JNIService; -import org.opensearch.knn.index.KNNMethodContext; +import org.opensearch.knn.index.engine.KNNMethodContext; import org.opensearch.knn.index.memory.NativeMemoryAllocation; import org.opensearch.knn.index.memory.NativeMemoryCacheManager; import org.opensearch.knn.index.memory.NativeMemoryEntryContext; @@ -40,12 +47,15 @@ public class TrainingJob implements Runnable { public static Logger logger = LogManager.getLogger(TrainingJob.class); private final KNNMethodContext knnMethodContext; + private final KNNMethodConfigContext knnMethodConfigContext; private final NativeMemoryCacheManager nativeMemoryCacheManager; private final NativeMemoryEntryContext.TrainingDataEntryContext trainingDataEntryContext; private final NativeMemoryEntryContext.AnonymousEntryContext modelAnonymousEntryContext; + @Getter private final Model model; - private String modelId; + @Getter + private final String modelId; /** * Constructor. @@ -55,7 +65,6 @@ public class TrainingJob implements Runnable { * @param nativeMemoryCacheManager Cache manager loads training data into native memory. * @param trainingDataEntryContext Training data configuration * @param modelAnonymousEntryContext Model allocation context - * @param dimension model's dimension * @param description user provided description of the model. */ public TrainingJob( @@ -64,48 +73,40 @@ public TrainingJob( NativeMemoryCacheManager nativeMemoryCacheManager, NativeMemoryEntryContext.TrainingDataEntryContext trainingDataEntryContext, NativeMemoryEntryContext.AnonymousEntryContext modelAnonymousEntryContext, - int dimension, - String description + KNNMethodConfigContext knnMethodConfigContext, + String description, + String nodeAssignment, + Mode mode, + CompressionLevel compressionLevel ) { // Generate random base64 string if one is not provided - this.modelId = Strings.hasText(modelId) ? modelId : UUIDs.randomBase64UUID(); + this.modelId = StringUtils.isNotBlank(modelId) ? modelId : UUIDs.randomBase64UUID(); this.knnMethodContext = Objects.requireNonNull(knnMethodContext, "MethodContext cannot be null."); + this.knnMethodConfigContext = knnMethodConfigContext; this.nativeMemoryCacheManager = Objects.requireNonNull(nativeMemoryCacheManager, "NativeMemoryCacheManager cannot be null."); this.trainingDataEntryContext = Objects.requireNonNull(trainingDataEntryContext, "TrainingDataEntryContext cannot be null."); this.modelAnonymousEntryContext = Objects.requireNonNull(modelAnonymousEntryContext, "AnonymousEntryContext cannot be null."); this.model = new Model( new ModelMetadata( - knnMethodContext.getEngine(), + knnMethodContext.getKnnEngine(), knnMethodContext.getSpaceType(), - dimension, + knnMethodConfigContext.getDimension(), ModelState.TRAINING, ZonedDateTime.now(ZoneOffset.UTC).toString(), description, - "" + "", + nodeAssignment, + knnMethodContext.getMethodComponentContext(), + knnMethodConfigContext.getVectorDataType(), + mode, + compressionLevel, + knnMethodConfigContext.getVersionCreated() ), null, this.modelId ); } - /** - * Getter for model id. - * - * @return modelId - */ - public String getModelId() { - return modelId; - } - - /** - * Getter for model - * - * @return model - */ - public Model getModel() { - return model; - } - @Override public void run() { NativeMemoryAllocation trainingDataAllocation = null; @@ -173,24 +174,34 @@ public void run() { throw new RuntimeException("Unable to load training data into memory: allocation is already closed"); } - Map trainParameters = model.getModelMetadata().getKnnEngine().getMethodAsMap(knnMethodContext); + KNNLibraryIndexingContext libraryIndexingContext = model.getModelMetadata() + .getKnnEngine() + .getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext); + + Map trainParameters = libraryIndexingContext.getLibraryParameters(); trainParameters.put( KNNConstants.INDEX_THREAD_QTY, KNNSettings.state().getSettingValue(KNNSettings.KNN_ALGO_PARAM_INDEX_THREAD_QTY) ); + if (libraryIndexingContext.getQuantizationConfig() != QuantizationConfig.EMPTY) { + trainParameters.put(KNNConstants.VECTOR_DATA_TYPE_FIELD, VectorDataType.BINARY.getValue()); + } else { + trainParameters.put(KNNConstants.VECTOR_DATA_TYPE_FIELD, modelMetadata.getVectorDataType().getValue()); + } + byte[] modelBlob = JNIService.trainIndex( trainParameters, model.getModelMetadata().getDimension(), trainingDataAllocation.getMemoryAddress(), - model.getModelMetadata().getKnnEngine().getName() + model.getModelMetadata().getKnnEngine() ); // Once training finishes, update model model.setModelBlob(modelBlob); modelMetadata.setState(ModelState.CREATED); } catch (Exception e) { - logger.error("Failed to run training job for model \"" + modelId + "\": " + e.getMessage()); + logger.error("Failed to run training job for model \"" + modelId + "\": ", e); modelMetadata.setState(ModelState.FAILED); modelMetadata.setError( "Failed to execute training. May be caused by an invalid method definition or " + "not enough memory to perform training." diff --git a/src/main/java/org/opensearch/knn/training/TrainingJobClusterStateListener.java b/src/main/java/org/opensearch/knn/training/TrainingJobClusterStateListener.java new file mode 100644 index 000000000..7e39ff7b3 --- /dev/null +++ b/src/main/java/org/opensearch/knn/training/TrainingJobClusterStateListener.java @@ -0,0 +1,189 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.training; + +import lombok.extern.log4j.Log4j2; +import org.opensearch.action.index.IndexResponse; +import org.opensearch.action.search.SearchRequest; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.cluster.ClusterChangedEvent; +import org.opensearch.cluster.ClusterStateListener; +import org.opensearch.cluster.node.DiscoveryNode; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.unit.TimeValue; +import org.opensearch.core.action.ActionListener; +import org.opensearch.knn.indices.Model; +import org.opensearch.knn.indices.ModelDao; +import org.opensearch.knn.indices.ModelMetadata; +import org.opensearch.knn.indices.ModelState; +import org.opensearch.search.SearchHit; +import org.opensearch.threadpool.ThreadPool; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; + +/** + * TrainingJobClusterStateListener is a ClusterStateListener that is used to update models that are still training when a node leaves or the cluster crashes. + * This class also sets a flag in TrainingJobRunner to block serialization when a node rejoins a cluster. + */ +@Log4j2 +public class TrainingJobClusterStateListener implements ClusterStateListener { + private static TrainingJobClusterStateListener INSTANCE; + + private static ModelDao modelDao; + private static ThreadPool threadPool; + private static ClusterService clusterService; + private String oldClusterManagerNodeId = ""; + private String currentClusterManagerNodeId = ""; + private boolean clusterManagerNodeRemoved = false; + + /** + * Get singleton instance of TrainingJobRunner + * + * @return singleton instance of TrainingJobRunner + */ + public static synchronized TrainingJobClusterStateListener getInstance() { + if (INSTANCE == null) { + INSTANCE = new TrainingJobClusterStateListener(); + } + return INSTANCE; + } + + /** + * Initializes static components. + * + * @param threadPool threadPool to use to schedule update of models + * @param modelDao modelDao used to get modelIds + * @param clusterService clusterService used to add a listener + */ + public static synchronized void initialize(ThreadPool threadPool, ModelDao modelDao, ClusterService clusterService) { + TrainingJobClusterStateListener.threadPool = threadPool; + TrainingJobClusterStateListener.modelDao = modelDao; + TrainingJobClusterStateListener.clusterService = clusterService; + } + + /** + * This method is called whenever the cluster state changes. + * It is used to update models that are still training when a node leaves or the cluster crashes. + * It is also used to cancel training jobs when a node rejoins the cluster. + * @param event the event that changed the cluster change + */ + @Override + public void clusterChanged(ClusterChangedEvent event) { + if (event.localNodeClusterManager()) { + if (event.isNewCluster()) { + // When the cluster is first created, the cluster manager will update models that are still marked as training. + threadPool.schedule(() -> { + try { + updateModelsNewCluster(); + } catch (IOException | InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + }, TimeValue.timeValueSeconds(1), ThreadPool.Names.GENERIC); + } else if (event.nodesRemoved()) { + List removedNodes = event.nodesDelta().removedNodes(); + threadPool.schedule(() -> { + try { + updateModelsNodesRemoved(removedNodes); + } catch (IOException | InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + }, TimeValue.timeValueSeconds(0), ThreadPool.Names.GENERIC); + } + } + } + + protected void updateModelsNewCluster() throws IOException, InterruptedException, ExecutionException { + if (modelDao.isCreated()) { + List modelIds = searchModelIds(); + for (String modelId : modelIds) { + ModelMetadata modelMetadata = getModelMetadata(modelId); + if (modelMetadata.getState().equals(ModelState.TRAINING)) { + updateModelStateAsFailed(modelId, modelMetadata, "Training failed to complete as cluster crashed"); + } + } + } + } + + protected void updateModelsNodesRemoved(List removedNodes) throws IOException, InterruptedException, ExecutionException { + if (modelDao.isCreated()) { + List modelIds = searchModelIds(); + for (DiscoveryNode removedNode : removedNodes) { + for (String modelId : modelIds) { + ModelMetadata modelMetadata = getModelMetadata(modelId); + if (modelMetadata.getNodeAssignment().equals(removedNode.getEphemeralId()) + && modelMetadata.getState().equals(ModelState.TRAINING)) { + updateModelStateAsFailed(modelId, modelMetadata, "Training failed to complete as node dropped"); + } + } + } + } + } + + private List searchModelIds() throws IOException, InterruptedException { + List modelIds = new ArrayList(); + CountDownLatch latch = new CountDownLatch(1); + modelDao.search(new SearchRequest(), new ActionListener() { + @Override + public void onResponse(SearchResponse searchResponse) { + try { + for (SearchHit searchHit : searchResponse.getHits().getHits()) { + modelIds.add(searchHit.getId()); + } + } finally { + latch.countDown(); + } + } + + @Override + public void onFailure(Exception e) { + latch.countDown(); + } + }); + latch.await(); + return modelIds; + } + + private void updateModelStateAsFailed(String modelId, ModelMetadata modelMetadata, String msg) throws IOException, ExecutionException, + InterruptedException { + modelMetadata.setState(ModelState.FAILED); + modelMetadata.setError(msg); + Model model = new Model(modelMetadata, null, modelId); + modelDao.update(model, new ActionListener() { + @Override + public void onResponse(IndexResponse indexResponse) { + log.info("Model {} marked as {}", model.getModelID(), model.getModelMetadata().getState()); + } + + @Override + public void onFailure(Exception e) { + log.error("Failed to update model state", e); + } + }); + } + + private ModelMetadata getModelMetadata(String modelId) throws ExecutionException, InterruptedException { + ModelMetadata modelMetadata = modelDao.getMetadata(modelId); + // On versions prior to 2.14, only models in created state are present in model metadata. + if (modelMetadata == null) { + log.info( + "Model metadata is null in cluster metadata. This can happen for models training on nodes prior to OpenSearch version 2.14.0. Fetching model information from system index." + ); + Model model = modelDao.get(modelId); + return model.getModelMetadata(); + } + return modelMetadata; + } +} diff --git a/src/main/java/org/opensearch/knn/training/TrainingJobRunner.java b/src/main/java/org/opensearch/knn/training/TrainingJobRunner.java index 774500311..5b2bb26b6 100644 --- a/src/main/java/org/opensearch/knn/training/TrainingJobRunner.java +++ b/src/main/java/org/opensearch/knn/training/TrainingJobRunner.java @@ -13,7 +13,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.action.ActionListener; +import org.opensearch.core.action.ActionListener; import org.opensearch.action.index.IndexResponse; import org.opensearch.common.ValidationException; import org.opensearch.knn.indices.ModelDao; @@ -23,6 +23,7 @@ import org.opensearch.threadpool.ThreadPool; import java.io.IOException; +import java.util.concurrent.ExecutionException; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicInteger; @@ -79,7 +80,8 @@ public static void initialize(ThreadPool threadPool, ModelDao modelDao) { * @param trainingJob training job to be executed * @param listener listener to handle final model serialization response (or exception) */ - public void execute(TrainingJob trainingJob, ActionListener listener) throws IOException { + public void execute(TrainingJob trainingJob, ActionListener listener) throws IOException, ExecutionException, + InterruptedException { // If the semaphore cannot be acquired, the node is unable to execute this job. This allows us to limit // the number of training jobs that enter this function. Although the training threadpool size will also prevent // this, we want to prevent this before we perform any serialization. @@ -106,10 +108,10 @@ public void execute(TrainingJob trainingJob, ActionListener liste logger.error("Unable to initialize model serialization: " + exception.getMessage()); listener.onFailure(exception); }), false); - } catch (IOException ioe) { + } catch (IOException | ExecutionException | InterruptedException e) { jobCount.decrementAndGet(); semaphore.release(); - throw ioe; + throw e; } } @@ -130,7 +132,7 @@ private void train(TrainingJob trainingJob) { try { trainingJob.run(); serializeModel(trainingJob, loggingListener, true); - } catch (IOException e) { + } catch (IOException | ExecutionException | InterruptedException e) { logger.error("Unable to serialize model \"" + trainingJob.getModelId() + "\": " + e.getMessage()); KNNCounter.TRAINING_ERRORS.increment(); } catch (Exception e) { @@ -150,8 +152,8 @@ private void train(TrainingJob trainingJob) { try { serializeModel(trainingJob, loggingListener, true); - } catch (IOException ioe) { - logger.error("Unable to serialize the failure for model \"" + trainingJob.getModelId() + "\": " + ioe); + } catch (IOException | ExecutionException | InterruptedException e) { + logger.error("Unable to serialize the failure for model \"{}\": ", trainingJob.getModelId(), e); } finally { jobCount.decrementAndGet(); semaphore.release(); @@ -160,9 +162,15 @@ private void train(TrainingJob trainingJob) { } } - private void serializeModel(TrainingJob trainingJob, ActionListener listener, boolean update) throws IOException { + private void serializeModel(TrainingJob trainingJob, ActionListener listener, boolean update) throws IOException, + ExecutionException, InterruptedException { if (update) { - modelDao.update(trainingJob.getModel(), listener); + ModelMetadata modelMetadata = modelDao.getMetadata(trainingJob.getModelId()); + if (modelMetadata.getState().equals(ModelState.TRAINING)) { + modelDao.update(trainingJob.getModel(), listener); + } else { + logger.info("Model state is {}. Skipping serialization of trained data", modelMetadata.getState()); + } } else { modelDao.put(trainingJob.getModel(), listener); } diff --git a/src/main/java/org/opensearch/knn/training/VectorReader.java b/src/main/java/org/opensearch/knn/training/VectorReader.java index 9b7db6d99..3935ee956 100644 --- a/src/main/java/org/opensearch/knn/training/VectorReader.java +++ b/src/main/java/org/opensearch/knn/training/VectorReader.java @@ -13,7 +13,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.action.ActionListener; +import org.opensearch.core.action.ActionListener; import org.opensearch.action.search.SearchRequestBuilder; import org.opensearch.action.search.SearchResponse; import org.opensearch.action.search.SearchScrollRequestBuilder; @@ -23,13 +23,13 @@ import org.opensearch.common.ValidationException; import org.opensearch.common.unit.TimeValue; import org.opensearch.index.query.ExistsQueryBuilder; -import org.opensearch.knn.index.IndexUtil; +import org.opensearch.knn.index.util.IndexUtil; import org.opensearch.search.SearchHit; import org.opensearch.search.sort.SortOrder; import java.util.ArrayList; import java.util.List; -import java.util.function.Consumer; +import java.util.Map; public class VectorReader { @@ -58,13 +58,13 @@ public VectorReader(Client client) { * @param vectorConsumer consumer used to do something with the collected vectors after each search * @param listener ActionListener that should be called once all search operations complete */ - public void read( + public void read( ClusterService clusterService, String indexName, String fieldName, int maxVectorCount, int searchSize, - Consumer> vectorConsumer, + TrainingDataConsumer vectorConsumer, ActionListener listener ) { @@ -88,7 +88,7 @@ public void read( throw validationException; } - ValidationException fieldValidationException = IndexUtil.validateKnnField(indexMetadata, fieldName, -1, null); + ValidationException fieldValidationException = IndexUtil.validateKnnField(indexMetadata, fieldName, -1, null, null, null); if (fieldValidationException != null) { validationException = validationException == null ? new ValidationException() : validationException; validationException.addValidationErrors(validationException.validationErrors()); @@ -135,14 +135,14 @@ private SearchScrollRequestBuilder createSearchScrollRequestBuilder() { return searchScrollRequestBuilder; } - private static class VectorReaderListener implements ActionListener { + private static class VectorReaderListener implements ActionListener { final Client client; final String fieldName; final int maxVectorCount; int collectedVectorCount; final ActionListener listener; - final Consumer> vectorConsumer; + final TrainingDataConsumer vectorConsumer; SearchScrollRequestBuilder searchScrollRequestBuilder; /** @@ -161,7 +161,7 @@ public VectorReaderListener( int maxVectorCount, int collectedVectorCount, ActionListener listener, - Consumer> vectorConsumer, + TrainingDataConsumer vectorConsumer, SearchScrollRequestBuilder searchScrollRequestBuilder ) { this.client = client; @@ -180,18 +180,9 @@ public void onResponse(SearchResponse searchResponse) { // Either add the entire set of returned hits, or maxVectorCount - collectedVectorCount hits SearchHit[] hits = searchResponse.getHits().getHits(); int vectorsToAdd = Integer.min(maxVectorCount - collectedVectorCount, hits.length); - List trainingData = new ArrayList<>(); - - for (int i = 0; i < vectorsToAdd; i++) { - trainingData.add( - ((List) hits[i].getSourceAsMap().get(fieldName)).stream().map(Number::floatValue).toArray(Float[]::new) - ); - } - this.collectedVectorCount += trainingData.size(); - - // Do something with the vectors - vectorConsumer.accept(trainingData); + vectorConsumer.processTrainingVectors(searchResponse, vectorsToAdd, fieldName); + this.collectedVectorCount = vectorConsumer.getTotalVectorsCountAdded(); if (vectorsToAdd <= 0 || this.collectedVectorCount >= maxVectorCount) { // Clear scroll context @@ -225,5 +216,42 @@ public void onFailure(Exception e) { listener.onFailure(e); } } + + /** + * Extracts vectors from the hits in a search response + * + * @param searchResponse Search response to extract vectors from + * @param vectorsToAdd number of vectors to extract + * @return list of vectors + */ + private List extractVectorsFromHits(SearchResponse searchResponse, int vectorsToAdd) { + SearchHit[] hits = searchResponse.getHits().getHits(); + List trainingData = new ArrayList<>(); + String[] fieldPath = fieldName.split("\\."); + int nullVectorCount = 0; + + for (int vector = 0; vector < vectorsToAdd; vector++) { + Map currentMap = hits[vector].getSourceAsMap(); + // The field name may be a nested field, so we need to split it and traverse the map. + // Example fieldName: "my_field" or "my_field.nested_field.nested_nested_field" + + for (int pathPart = 0; pathPart < fieldPath.length - 1; pathPart++) { + currentMap = (Map) currentMap.get(fieldPath[pathPart]); + } + + if (currentMap.get(fieldPath[fieldPath.length - 1]) instanceof List == false) { + nullVectorCount++; + continue; + } + + List fieldList = (List) currentMap.get(fieldPath[fieldPath.length - 1]); + + trainingData.add(fieldList.stream().map(Number::floatValue).toArray(Float[]::new)); + } + if (nullVectorCount > 0) { + logger.warn("Found {} documents with null vectors in field {}", nullVectorCount, fieldName); + } + return trainingData; + } } } diff --git a/src/main/plugin-metadata/plugin-security.policy b/src/main/plugin-metadata/plugin-security.policy index c7345edcd..ed329740f 100644 --- a/src/main/plugin-metadata/plugin-security.policy +++ b/src/main/plugin-metadata/plugin-security.policy @@ -1,5 +1,10 @@ grant { permission java.lang.RuntimePermission "loadLibrary.opensearchknn_nmslib"; permission java.lang.RuntimePermission "loadLibrary.opensearchknn_faiss"; + permission java.lang.RuntimePermission "loadLibrary.opensearchknn_common"; + permission java.lang.RuntimePermission "loadLibrary.opensearchknn_faiss_avx2"; + permission java.lang.RuntimePermission "loadLibrary.opensearchknn_faiss_avx512"; permission java.net.SocketPermission "*", "connect,resolve"; + permission java.lang.RuntimePermission "accessDeclaredMembers"; + permission java.io.FilePermission "/proc/cpuinfo", "read"; }; diff --git a/src/main/resources/META-INF/services/org.apache.lucene.codecs.Codec b/src/main/resources/META-INF/services/org.apache.lucene.codecs.Codec index b897dc36a..7a8916981 100644 --- a/src/main/resources/META-INF/services/org.apache.lucene.codecs.Codec +++ b/src/main/resources/META-INF/services/org.apache.lucene.codecs.Codec @@ -2,4 +2,10 @@ org.opensearch.knn.index.codec.KNN80Codec.KNN80Codec org.opensearch.knn.index.codec.KNN84Codec.KNN84Codec org.opensearch.knn.index.codec.KNN86Codec.KNN86Codec org.opensearch.knn.index.codec.KNN87Codec.KNN87Codec -org.opensearch.knn.index.codec.KNN910Codec.KNN910Codec \ No newline at end of file +org.opensearch.knn.index.codec.KNN910Codec.KNN910Codec +org.opensearch.knn.index.codec.KNN920Codec.KNN920Codec +org.opensearch.knn.index.codec.KNN940Codec.KNN940Codec +org.opensearch.knn.index.codec.KNN950Codec.KNN950Codec +org.opensearch.knn.index.codec.KNN990Codec.KNN990Codec +org.opensearch.knn.index.codec.KNN9120Codec.KNN9120Codec +org.opensearch.knn.index.codec.KNN990Codec.UnitTestCodec diff --git a/benchmarks/perf-tool/okpt/__init__.py b/src/main/resources/META-INF/services/org.apache.lucene.codecs.KnnVectorsFormat similarity index 53% rename from benchmarks/perf-tool/okpt/__init__.py rename to src/main/resources/META-INF/services/org.apache.lucene.codecs.KnnVectorsFormat index c3bffc54c..d799c3869 100644 --- a/benchmarks/perf-tool/okpt/__init__.py +++ b/src/main/resources/META-INF/services/org.apache.lucene.codecs.KnnVectorsFormat @@ -1,6 +1,12 @@ +# # SPDX-License-Identifier: Apache-2.0 # # The OpenSearch Contributors require contributions made to # this file be licensed under the Apache-2.0 license or a # compatible open source license. +# +# Modifications Copyright OpenSearch Contributors. See +# GitHub history for details. +# +org.opensearch.knn.index.codec.KNN990Codec.NativeEngines990KnnVectorsFormat diff --git a/src/main/resources/META-INF/services/org.opensearch.painless.spi.PainlessExtension b/src/main/resources/META-INF/services/org.opensearch.painless.spi.PainlessExtension index 5fdf5f649..530c96349 100644 --- a/src/main/resources/META-INF/services/org.opensearch.painless.spi.PainlessExtension +++ b/src/main/resources/META-INF/services/org.opensearch.painless.spi.PainlessExtension @@ -1,4 +1,4 @@ # Copyright OpenSearch Contributors # SPDX-License-Identifier: Apache-2.0 -org.opensearch.knn.plugin.script.KNNWhitelistExtension +org.opensearch.knn.plugin.script.KNNAllowlistExtension diff --git a/src/main/resources/mappings/model-index.json b/src/main/resources/mappings/model-index.json index a8c7d6528..8d16b98ab 100644 --- a/src/main/resources/mappings/model-index.json +++ b/src/main/resources/mappings/model-index.json @@ -26,6 +26,21 @@ }, "model_blob": { "type": "binary" + }, + "node_assignment": { + "type": "keyword" + }, + "method_component_context": { + "type": "keyword" + }, + "mode": { + "type": "keyword" + }, + "compression_level": { + "type": "keyword" + }, + "model_version": { + "type": "keyword" } } } diff --git a/src/main/resources/org/opensearch/knn/plugin/script/knn_whitelist.txt b/src/main/resources/org/opensearch/knn/plugin/script/knn_allowlist.txt similarity index 88% rename from src/main/resources/org/opensearch/knn/plugin/script/knn_whitelist.txt rename to src/main/resources/org/opensearch/knn/plugin/script/knn_allowlist.txt index 6b6e6434e..388cdda8a 100644 --- a/src/main/resources/org/opensearch/knn/plugin/script/knn_whitelist.txt +++ b/src/main/resources/org/opensearch/knn/plugin/script/knn_allowlist.txt @@ -13,4 +13,5 @@ static_import { float innerProduct(List, org.opensearch.knn.index.KNNVectorScriptDocValues) from_class org.opensearch.knn.plugin.script.KNNScoringUtil float cosineSimilarity(List, org.opensearch.knn.index.KNNVectorScriptDocValues) from_class org.opensearch.knn.plugin.script.KNNScoringUtil float cosineSimilarity(List, org.opensearch.knn.index.KNNVectorScriptDocValues, Number) from_class org.opensearch.knn.plugin.script.KNNScoringUtil + float hamming(List, org.opensearch.knn.index.KNNVectorScriptDocValues) from_class org.opensearch.knn.plugin.script.KNNScoringUtil } diff --git a/src/test/java/org/opensearch/knn/KNNSingleNodeTestCase.java b/src/test/java/org/opensearch/knn/KNNSingleNodeTestCase.java index b4edcf304..7bfce5b94 100644 --- a/src/test/java/org/opensearch/knn/KNNSingleNodeTestCase.java +++ b/src/test/java/org/opensearch/knn/KNNSingleNodeTestCase.java @@ -5,11 +5,22 @@ package org.opensearch.knn; -import org.opensearch.common.bytes.BytesReference; +import org.opensearch.action.admin.indices.settings.put.UpdateSettingsRequest; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.common.bytes.BytesReference; +import org.opensearch.cluster.ClusterName; +import org.opensearch.cluster.ClusterState; +import org.opensearch.cluster.block.ClusterBlock; +import org.opensearch.cluster.block.ClusterBlockLevel; +import org.opensearch.cluster.block.ClusterBlocks; +import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.xcontent.XContentHelper; -import org.opensearch.knn.index.KNNQueryBuilder; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.query.KNNQueryBuilder; import org.opensearch.knn.index.memory.NativeMemoryCacheManager; import org.opensearch.knn.index.memory.NativeMemoryLoadStrategy; +import org.opensearch.knn.indices.Model; import org.opensearch.knn.indices.ModelDao; import org.opensearch.knn.indices.ModelMetadata; import org.opensearch.knn.indices.ModelState; @@ -21,20 +32,36 @@ import org.opensearch.action.search.SearchResponse; import org.opensearch.action.support.WriteRequest; import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.index.IndexService; import org.opensearch.plugins.Plugin; -import org.opensearch.rest.RestStatus; +import org.opensearch.core.rest.RestStatus; import org.opensearch.test.OpenSearchSingleNodeTestCase; import org.opensearch.test.hamcrest.OpenSearchAssertions; import java.io.IOException; +import java.util.Base64; import java.util.Collection; import java.util.Collections; +import java.util.EnumSet; import java.util.Map; import java.util.concurrent.ExecutionException; +import static org.mockito.Mockito.when; +import static org.opensearch.knn.common.KNNConstants.DIMENSION; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; +import static org.opensearch.knn.common.KNNConstants.MODEL_BLOB_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.MODEL_DESCRIPTION; +import static org.opensearch.knn.common.KNNConstants.MODEL_ERROR; +import static org.opensearch.knn.common.KNNConstants.MODEL_ID; +import static org.opensearch.knn.common.KNNConstants.MODEL_INDEX_NAME; +import static org.opensearch.knn.common.KNNConstants.MODEL_STATE; +import static org.opensearch.knn.common.KNNConstants.MODEL_TIMESTAMP; +import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; + public class KNNSingleNodeTestCase extends OpenSearchSingleNodeTestCase { @Override public void setUp() throws Exception { @@ -81,6 +108,59 @@ protected void createKnnIndexMapping(String indexName, String fieldName, Integer OpenSearchAssertions.assertAcked(client().admin().indices().putMapping(request).actionGet()); } + /** + * Create simple k-NN mapping with engine + */ + protected void createKnnIndexMapping(String indexName, String fieldName, Integer dimensions, KNNEngine engine) throws IOException { + PutMappingRequest request = new PutMappingRequest(indexName); + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("properties"); + xContentBuilder.startObject(fieldName); + xContentBuilder.field("type", "knn_vector").field("dimension", dimensions.toString()); + xContentBuilder.startObject("method"); + xContentBuilder.field("name", METHOD_HNSW); + xContentBuilder.field(KNN_ENGINE, engine.getName()); + xContentBuilder.endObject(); + xContentBuilder.endObject(); + xContentBuilder.endObject(); + xContentBuilder.endObject(); + request.source(xContentBuilder); + OpenSearchAssertions.assertAcked(client().admin().indices().putMapping(request).actionGet()); + } + + protected void updateIndexSetting(String indexName, Settings setting) { + UpdateSettingsRequest request = new UpdateSettingsRequest(setting, indexName); + OpenSearchAssertions.assertAcked(client().admin().indices().updateSettings(request).actionGet()); + } + + /** + * Create simple k-NN mapping which can be nested. + * e.g. fieldPath = "a.b.c" will create mapping for "c" as knn_vector + */ + protected void createKnnNestedIndexMapping(String indexName, String fieldPath, Integer dimensions) throws IOException { + PutMappingRequest request = new PutMappingRequest(indexName); + String[] path = fieldPath.split("\\."); + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("properties"); + for (int i = 0; i < path.length; i++) { + xContentBuilder.startObject(path[i]); + if (i == path.length - 1) { + xContentBuilder.field("type", "knn_vector").field("dimension", dimensions.toString()); + } else { + xContentBuilder.startObject("properties"); + } + } + for (int i = path.length - 1; i >= 0; i--) { + if (i != path.length - 1) { + xContentBuilder.endObject(); + } + xContentBuilder.endObject(); + } + xContentBuilder.endObject().endObject(); + + request.source(xContentBuilder); + + OpenSearchAssertions.assertAcked(client().admin().indices().putMapping(request).actionGet()); + } + /** * Get default k-NN settings for test cases */ @@ -88,6 +168,18 @@ protected Settings getKNNDefaultIndexSettings() { return Settings.builder().put("number_of_shards", 1).put("number_of_replicas", 0).put("index.knn", true).build(); } + /** + * Get default k-NN settings for test cases with build graph always + */ + protected Settings getKNNDefaultIndexSettingsBuildsGraphAlways() { + return Settings.builder() + .put("number_of_shards", 1) + .put("number_of_replicas", 0) + .put("index.knn", true) + .put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0) + .build(); + } + /** * Add a k-NN doc to an index */ @@ -103,6 +195,31 @@ protected void addKnnDoc(String index, String docId, String fieldName, Object[] assertEquals(response.status(), RestStatus.CREATED); } + /** + * Add a k-NN doc to an index with nested knn_vector field + */ + protected void addKnnNestedDoc(String index, String docId, String fieldPath, Object[] vector) throws IOException, InterruptedException, + ExecutionException { + XContentBuilder builder = XContentFactory.jsonBuilder().startObject(); + String[] fieldParts = fieldPath.split("\\."); + + for (int i = 0; i < fieldParts.length - 1; i++) { + builder.startObject(fieldParts[i]); + } + builder.field(fieldParts[fieldParts.length - 1], vector); + for (int i = fieldParts.length - 2; i >= 0; i--) { + builder.endObject(); + } + builder.endObject(); + IndexRequest indexRequest = new IndexRequest().index(index) + .id(docId) + .source(builder) + .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + + IndexResponse response = client().index(indexRequest).get(); + assertEquals(response.status(), RestStatus.CREATED); + } + /** * Add any document to index */ @@ -118,6 +235,55 @@ protected void addDoc(String index, String docId, String fieldName, String dummy assertEquals(response.status(), RestStatus.CREATED); } + /** + * Index a new model + */ + protected void writeModelToModelSystemIndex(Model model) throws IOException, ExecutionException, InterruptedException { + ModelMetadata modelMetadata = model.getModelMetadata(); + + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .field(MODEL_ID, model.getModelID()) + .field(KNN_ENGINE, modelMetadata.getKnnEngine().getName()) + .field(METHOD_PARAMETER_SPACE_TYPE, modelMetadata.getSpaceType().getValue()) + .field(DIMENSION, modelMetadata.getDimension()) + .field(MODEL_STATE, modelMetadata.getState().getName()) + .field(MODEL_TIMESTAMP, modelMetadata.getTimestamp().toString()) + .field(MODEL_DESCRIPTION, modelMetadata.getDescription()) + .field(MODEL_ERROR, modelMetadata.getError()) + .field(VECTOR_DATA_TYPE_FIELD, modelMetadata.getVectorDataType().getValue()); + + if (model.getModelBlob() != null) { + builder.field(MODEL_BLOB_PARAMETER, Base64.getEncoder().encodeToString(model.getModelBlob())); + } + + builder.endObject(); + + IndexRequest indexRequest = new IndexRequest().index(MODEL_INDEX_NAME) + .id(model.getModelID()) + .source(builder) + .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + + IndexResponse response = client().index(indexRequest).get(); + assertTrue(response.status() == RestStatus.CREATED || response.status() == RestStatus.OK); + } + + // Add a new model to ModelDao + protected void addModel(Model model) throws IOException { + ModelDao modelDao = ModelDao.OpenSearchKNNModelDao.getInstance(); + modelDao.put(model, new ActionListener() { + @Override + public void onResponse(IndexResponse indexResponse) { + assertTrue(indexResponse.status() == RestStatus.CREATED || indexResponse.status() == RestStatus.OK); + } + + @Override + public void onFailure(Exception e) { + fail("Failed to add model: " + e); + } + }); + } + /** * Run a search against a k-NN index */ @@ -154,4 +320,25 @@ public void assertTrainingSucceeds(ModelDao modelDao, String modelId, int attemp fail("Training did not succeed after " + attempts + " attempts with a delay of " + delayInMillis + " ms."); } + + // Add Global Cluster Block with the given ClusterBlockLevel + protected void addGlobalClusterBlock(ClusterService clusterService, String description, EnumSet clusterBlockLevels) { + ClusterBlock block = new ClusterBlock(randomInt(), description, false, false, false, RestStatus.FORBIDDEN, clusterBlockLevels); + ClusterBlocks clusterBlocks = ClusterBlocks.builder().addGlobalBlock(block).build(); + ClusterState state = ClusterState.builder(ClusterName.DEFAULT).blocks(clusterBlocks).build(); + when(clusterService.state()).thenReturn(state); + } + + // Add Cluster Block for an Index with given ClusterBlockLevel + protected void addIndexClusterBlock( + ClusterService clusterService, + String description, + EnumSet clusterBlockLevels, + String testIndex + ) { + ClusterBlock block = new ClusterBlock(randomInt(), description, false, false, false, RestStatus.FORBIDDEN, clusterBlockLevels); + ClusterBlocks clusterBlocks = ClusterBlocks.builder().addIndexBlock(testIndex, block).build(); + ClusterState state = ClusterState.builder(ClusterName.DEFAULT).blocks(clusterBlocks).build(); + when(clusterService.state()).thenReturn(state); + } } diff --git a/src/test/java/org/opensearch/knn/KNNTestCase.java b/src/test/java/org/opensearch/knn/KNNTestCase.java index d5ac75287..21b3298be 100644 --- a/src/test/java/org/opensearch/knn/KNNTestCase.java +++ b/src/test/java/org/opensearch/knn/KNNTestCase.java @@ -5,37 +5,173 @@ package org.opensearch.knn; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.settings.ClusterSettings; +import org.opensearch.common.settings.Setting; +import org.opensearch.common.settings.Settings; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNLibrarySearchContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.mapper.KNNMappingConfig; import org.opensearch.knn.index.memory.NativeMemoryCacheManager; import org.opensearch.knn.plugin.stats.KNNCounter; -import org.opensearch.common.bytes.BytesReference; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.common.bytes.BytesReference; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentHelper; import org.opensearch.test.OpenSearchTestCase; +import java.util.Collections; +import java.util.HashSet; import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.mockito.Mockito.when; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_IVF; /** * Base class for integration tests for KNN plugin. Contains several methods for testing KNN ES functionality. */ public class KNNTestCase extends OpenSearchTestCase { + + protected static final KNNLibrarySearchContext EMPTY_ENGINE_SPECIFIC_CONTEXT = ctx -> Map.of(); + + @Mock + protected ClusterService clusterService; + private AutoCloseable openMocks; + + @Override + public void setUp() throws Exception { + super.setUp(); + openMocks = MockitoAnnotations.openMocks(this); + // This is required to make sure that before every test we are initializing the KNNSettings. Not doing this + // leads to failures of unit tests cases when a unit test is run separately. Try running this test: + // ./gradlew ':test' --tests "org.opensearch.knn.training.TrainingJobTests.testRun_success" and see it fails + // but if run along with other tests this test passes. + initKNNSettings(); + } + @Override public void tearDown() throws Exception { super.tearDown(); resetState(); + openMocks.close(); + } + + @Override + protected boolean enableWarningsCheck() { + // Disable warnings check to avoid flaky tests, more details at: + // https://github.com/opensearch-project/k-NN/issues/1392 + return false; } - public static void resetState() { + public void resetState() { // Reset all of the counters for (KNNCounter knnCounter : KNNCounter.values()) { knnCounter.set(0L); } + initKNNSettings(); // Clean up the cache NativeMemoryCacheManager.getInstance().invalidateAll(); NativeMemoryCacheManager.getInstance().close(); } + private void initKNNSettings() { + Set> defaultClusterSettings = new HashSet<>(ClusterSettings.BUILT_IN_CLUSTER_SETTINGS); + defaultClusterSettings.addAll( + KNNSettings.state() + .getSettings() + .stream() + .filter(s -> s.getProperties().contains(Setting.Property.NodeScope)) + .collect(Collectors.toList()) + ); + when(clusterService.getClusterSettings()).thenReturn(new ClusterSettings(Settings.EMPTY, defaultClusterSettings)); + KNNSettings.state().setClusterService(clusterService); + } + public Map xContentBuilderToMap(XContentBuilder xContentBuilder) { return XContentHelper.convertToMap(BytesReference.bytes(xContentBuilder), true, xContentBuilder.contentType()).v2(); } + + public static KNNMethodContext getDefaultKNNMethodContext() { + MethodComponentContext methodComponentContext = new MethodComponentContext(METHOD_HNSW, Collections.emptyMap()); + return new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.DEFAULT, methodComponentContext); + } + + public static KNNMethodContext getDefaultKNNMethodContextForModel() { + MethodComponentContext methodComponentContext = new MethodComponentContext(METHOD_IVF, Collections.emptyMap()); + return new KNNMethodContext(KNNEngine.FAISS, SpaceType.DEFAULT, methodComponentContext); + } + + public static KNNMethodContext getDefaultByteKNNMethodContext() { + MethodComponentContext methodComponentContext = new MethodComponentContext(METHOD_HNSW, Collections.emptyMap()); + return new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.DEFAULT, methodComponentContext); + } + + public static KNNMethodContext getDefaultBinaryKNNMethodContext() { + MethodComponentContext methodComponentContext = new MethodComponentContext(METHOD_HNSW, Collections.emptyMap()); + return new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.DEFAULT_BINARY, methodComponentContext); + } + + public static KNNMappingConfig getMappingConfigForMethodMapping(KNNMethodContext knnMethodContext, int dimension) { + return new KNNMappingConfig() { + @Override + public Optional getKnnMethodContext() { + return Optional.of(knnMethodContext); + } + + @Override + public int getDimension() { + return dimension; + } + }; + } + + public static KNNMappingConfig getMappingConfigForFlatMapping(int dimension) { + return () -> dimension; + } + + public static KNNMappingConfig getMappingConfigForModelMapping(String modelId, int dimension) { + return new KNNMappingConfig() { + @Override + public Optional getModelId() { + return Optional.of(modelId); + } + + @Override + public int getDimension() { + return dimension; + } + }; + } + + /** + * Adjust the provided dimension based on {@link VectorDataType} during ingestion. + * @param dimension int + * @param vectorDataType {@link VectorDataType} + * @return int + */ + protected int adjustDimensionForIndexing(final int dimension, final VectorDataType vectorDataType) { + return VectorDataType.BINARY == vectorDataType ? dimension * Byte.SIZE : dimension; + } + + /** + * Adjust the provided dimension based on {@link VectorDataType} for search. + * + * @param dimension int + * @param vectorDataType {@link VectorDataType} + * @return int + */ + protected int adjustDimensionForSearch(final int dimension, final VectorDataType vectorDataType) { + return VectorDataType.BINARY == vectorDataType ? dimension / Byte.SIZE : dimension; + } } diff --git a/src/test/java/org/opensearch/knn/common/Constants.java b/src/test/java/org/opensearch/knn/common/Constants.java new file mode 100644 index 000000000..2580d2c9c --- /dev/null +++ b/src/test/java/org/opensearch/knn/common/Constants.java @@ -0,0 +1,11 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.common; + +public class Constants { + public static final String FIELD_FILTER = "filter"; + public static final String FIELD_TERM = "term"; +} diff --git a/src/test/java/org/opensearch/knn/common/FieldInfoExtractorTests.java b/src/test/java/org/opensearch/knn/common/FieldInfoExtractorTests.java new file mode 100644 index 000000000..dd3721071 --- /dev/null +++ b/src/test/java/org/opensearch/knn/common/FieldInfoExtractorTests.java @@ -0,0 +1,79 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.common; + +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.FieldInfos; +import org.apache.lucene.index.LeafReader; +import org.junit.Assert; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.indices.ModelMetadata; +import org.opensearch.knn.indices.ModelUtil; + +import static org.mockito.Mockito.when; + +public class FieldInfoExtractorTests extends KNNTestCase { + + private static final String MODEL_ID = "model_id"; + + public void testExtractVectorDataType_whenDifferentConditions_thenSuccess() { + FieldInfo fieldInfo = Mockito.mock(FieldInfo.class); + try (MockedStatic modelUtilMockedStatic = Mockito.mockStatic(ModelUtil.class)) { + // default case + Mockito.when(fieldInfo.getAttribute(KNNConstants.VECTOR_DATA_TYPE_FIELD)).thenReturn(null); + Mockito.when(fieldInfo.getAttribute(KNNConstants.MODEL_ID)).thenReturn(MODEL_ID); + modelUtilMockedStatic.when(() -> ModelUtil.getModelMetadata(MODEL_ID)).thenReturn(null); + Assert.assertEquals(VectorDataType.DEFAULT, FieldInfoExtractor.extractVectorDataType(fieldInfo)); + + // VectorDataType present in fieldInfo + Mockito.when(fieldInfo.getAttribute(KNNConstants.VECTOR_DATA_TYPE_FIELD)).thenReturn(VectorDataType.BINARY.getValue()); + Assert.assertEquals(VectorDataType.BINARY, FieldInfoExtractor.extractVectorDataType(fieldInfo)); + + // VectorDataType present in ModelMetadata + ModelMetadata modelMetadata = Mockito.mock(ModelMetadata.class); + Mockito.when(fieldInfo.getAttribute(KNNConstants.VECTOR_DATA_TYPE_FIELD)).thenReturn(null); + modelUtilMockedStatic.when(() -> ModelUtil.getModelMetadata(MODEL_ID)).thenReturn(modelMetadata); + Mockito.when(modelMetadata.getVectorDataType()).thenReturn(VectorDataType.BYTE); + Assert.assertEquals(VectorDataType.BYTE, FieldInfoExtractor.extractVectorDataType(fieldInfo)); + } + } + + public void testExtractVectorDataType() { + FieldInfo fieldInfo = Mockito.mock(FieldInfo.class); + when(fieldInfo.getAttribute("data_type")).thenReturn(VectorDataType.BINARY.getValue()); + + assertEquals(VectorDataType.BINARY, FieldInfoExtractor.extractVectorDataType(fieldInfo)); + when(fieldInfo.getAttribute("data_type")).thenReturn(null); + + when(fieldInfo.getAttribute("model_id")).thenReturn(MODEL_ID); + try (MockedStatic modelUtilMockedStatic = Mockito.mockStatic(ModelUtil.class)) { + ModelMetadata modelMetadata = Mockito.mock(ModelMetadata.class); + modelUtilMockedStatic.when(() -> ModelUtil.getModelMetadata(MODEL_ID)).thenReturn(modelMetadata); + when(modelMetadata.getVectorDataType()).thenReturn(VectorDataType.BYTE); + + assertEquals(VectorDataType.BYTE, FieldInfoExtractor.extractVectorDataType(fieldInfo)); + when(modelMetadata.getVectorDataType()).thenReturn(null); + when(modelMetadata.getVectorDataType()).thenReturn(VectorDataType.DEFAULT); + } + + when(fieldInfo.getAttribute("model_id")).thenReturn(null); + assertEquals(VectorDataType.DEFAULT, FieldInfoExtractor.extractVectorDataType(fieldInfo)); + } + + public void testGetFieldInfo_whenDifferentInput_thenSuccess() { + LeafReader leafReader = Mockito.mock(LeafReader.class); + FieldInfos fieldInfos = Mockito.mock(FieldInfos.class); + FieldInfo fieldInfo = Mockito.mock(FieldInfo.class); + Mockito.when(leafReader.getFieldInfos()).thenReturn(fieldInfos); + Mockito.when(fieldInfos.fieldInfo("invalid")).thenReturn(null); + Mockito.when(fieldInfos.fieldInfo("valid")).thenReturn(fieldInfo); + Assert.assertNull(FieldInfoExtractor.getFieldInfo(leafReader, "invalid")); + Assert.assertEquals(fieldInfo, FieldInfoExtractor.getFieldInfo(leafReader, "valid")); + } +} diff --git a/src/test/java/org/opensearch/knn/common/KNNValidationUtilTests.java b/src/test/java/org/opensearch/knn/common/KNNValidationUtilTests.java new file mode 100644 index 000000000..4b4337880 --- /dev/null +++ b/src/test/java/org/opensearch/knn/common/KNNValidationUtilTests.java @@ -0,0 +1,41 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.common; + +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.VectorDataType; + +import static org.hamcrest.Matchers.containsString; + +public class KNNValidationUtilTests extends KNNTestCase { + public void testValidateVectorDimension_whenBinary_thenVectorSizeShouldBeEightTimesLarger() { + int vectorLength = randomInt(100) + 1; + Exception ex = expectThrows( + IllegalArgumentException.class, + () -> KNNValidationUtil.validateVectorDimension(vectorLength, vectorLength, VectorDataType.BINARY) + ); + assertThat( + ex.getMessage(), + containsString("The dimension of the binary vector must be 8 times the length of the provided vector.") + ); + + // Expect no exception + KNNValidationUtil.validateVectorDimension(vectorLength * Byte.SIZE, vectorLength, VectorDataType.BINARY); + } + + public void testValidateVectorDimension_whenNonBinary_thenVectorSizeShouldBeSameAsDimension() { + int dimension = randomInt(100); + VectorDataType vectorDataType = randomInt(1) == 0 ? VectorDataType.FLOAT : VectorDataType.BYTE; + Exception ex = expectThrows( + IllegalArgumentException.class, + () -> KNNValidationUtil.validateVectorDimension(dimension, dimension + 1, vectorDataType) + ); + assertThat(ex.getMessage(), containsString("Vector dimension mismatch")); + + // Expect no exception + KNNValidationUtil.validateVectorDimension(dimension, dimension, vectorDataType); + } +} diff --git a/src/test/java/org/opensearch/knn/common/KNNVectorUtilTests.java b/src/test/java/org/opensearch/knn/common/KNNVectorUtilTests.java new file mode 100644 index 000000000..d64b73c9a --- /dev/null +++ b/src/test/java/org/opensearch/knn/common/KNNVectorUtilTests.java @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.common; + +import lombok.SneakyThrows; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.vectorvalues.KNNVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNVectorValuesFactory; +import org.opensearch.knn.index.vectorvalues.TestVectorValues; + +import java.util.List; + +import static org.opensearch.knn.common.KNNVectorUtil.iterateVectorValuesOnce; + +public class KNNVectorUtilTests extends KNNTestCase { + public void testByteZeroVector() { + assertTrue(KNNVectorUtil.isZeroVector(new byte[] { 0, 0, 0 })); + assertFalse(KNNVectorUtil.isZeroVector(new byte[] { 1, 1, 1 })); + } + + public void testFloatZeroVector() { + assertTrue(KNNVectorUtil.isZeroVector(new float[] { 0.0f, 0.0f, 0.0f })); + assertFalse(KNNVectorUtil.isZeroVector(new float[] { 1.0f, 1.0f, 1.0f })); + } + + public void testIntListToArray() { + assertArrayEquals(new int[] { 1, 2, 3 }, KNNVectorUtil.intListToArray(List.of(1, 2, 3))); + assertNull(KNNVectorUtil.intListToArray(List.of())); + assertNull(KNNVectorUtil.intListToArray(null)); + } + + @SneakyThrows + public void testInit() { + // Give + final List floatArray = List.of(new float[] { 1, 2 }, new float[] { 2, 3 }); + final int dimension = floatArray.get(0).length; + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + floatArray + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, randomVectorValues); + + // When + iterateVectorValuesOnce(knnVectorValues); + + // Then + assertNotEquals(-1, knnVectorValues.docId()); + assertArrayEquals(floatArray.get(0), knnVectorValues.getVector(), 0.001f); + assertEquals(dimension, knnVectorValues.dimension()); + } +} diff --git a/src/test/java/org/opensearch/knn/common/RaisingIOExceptionIndexInput.java b/src/test/java/org/opensearch/knn/common/RaisingIOExceptionIndexInput.java new file mode 100644 index 000000000..8882f7d2f --- /dev/null +++ b/src/test/java/org/opensearch/knn/common/RaisingIOExceptionIndexInput.java @@ -0,0 +1,51 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.common; + +import org.apache.lucene.store.IndexInput; + +import java.io.IOException; + +public class RaisingIOExceptionIndexInput extends IndexInput { + public RaisingIOExceptionIndexInput() { + super(RaisingIOExceptionIndexInput.class.getSimpleName()); + } + + @Override + public void close() throws IOException { + throw new IOException("RaisingIOExceptionIndexInput::readBytes failed."); + } + + @Override + public long getFilePointer() { + throw new RuntimeException("RaisingIOExceptionIndexInput::readBytes failed."); + } + + @Override + public void seek(long l) throws IOException { + throw new IOException("RaisingIOExceptionIndexInput::readBytes failed."); + } + + @Override + public long length() { + return 0; + } + + @Override + public IndexInput slice(String s, long l, long l1) throws IOException { + throw new IOException("RaisingIOExceptionIndexInput::readBytes failed."); + } + + @Override + public byte readByte() throws IOException { + throw new IOException("RaisingIOExceptionIndexInput::readBytes failed."); + } + + @Override + public void readBytes(byte[] bytes, int i, int i1) throws IOException { + throw new IOException("RaisingIOExceptionIndexInput::readBytes failed."); + } +} diff --git a/src/test/java/org/opensearch/knn/common/RasingIOExceptionIndexOutput.java b/src/test/java/org/opensearch/knn/common/RasingIOExceptionIndexOutput.java new file mode 100644 index 000000000..7334bf201 --- /dev/null +++ b/src/test/java/org/opensearch/knn/common/RasingIOExceptionIndexOutput.java @@ -0,0 +1,41 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.common; + +import org.apache.lucene.store.IndexOutput; + +import java.io.IOException; + +public class RasingIOExceptionIndexOutput extends IndexOutput { + public RasingIOExceptionIndexOutput() { + super("Always throws IOException", RasingIOExceptionIndexOutput.class.getSimpleName()); + } + + @Override + public void close() throws IOException { + throw new IOException("RaiseIOExceptionIndexInput::close failed."); + } + + @Override + public long getFilePointer() { + throw new RuntimeException("RaiseIOExceptionIndexInput::getFilePointer failed."); + } + + @Override + public long getChecksum() throws IOException { + throw new IOException("RaiseIOExceptionIndexInput::getChecksum failed."); + } + + @Override + public void writeByte(byte b) throws IOException { + throw new IOException("RaiseIOExceptionIndexInput::writeByte failed."); + } + + @Override + public void writeBytes(byte[] bytes, int i, int i1) throws IOException { + throw new IOException("RaiseIOExceptionIndexInput::writeBytes failed."); + } +} diff --git a/src/test/java/org/opensearch/knn/common/featureflags/KNNFeatureFlagsTests.java b/src/test/java/org/opensearch/knn/common/featureflags/KNNFeatureFlagsTests.java new file mode 100644 index 000000000..f2f74944e --- /dev/null +++ b/src/test/java/org/opensearch/knn/common/featureflags/KNNFeatureFlagsTests.java @@ -0,0 +1,34 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.common.featureflags; + +import org.mockito.Mock; +import org.opensearch.common.settings.ClusterSettings; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.KNNSettings; + +import static org.mockito.Mockito.when; +import static org.opensearch.knn.common.featureflags.KNNFeatureFlags.KNN_FORCE_EVICT_CACHE_ENABLED_SETTING; +import static org.opensearch.knn.common.featureflags.KNNFeatureFlags.isForceEvictCacheEnabled; + +public class KNNFeatureFlagsTests extends KNNTestCase { + + @Mock + ClusterSettings clusterSettings; + + public void setUp() throws Exception { + super.setUp(); + when(clusterService.getClusterSettings()).thenReturn(clusterSettings); + KNNSettings.state().setClusterService(clusterService); + } + + public void testIsForceEvictCacheEnabled() throws Exception { + when(clusterSettings.get(KNN_FORCE_EVICT_CACHE_ENABLED_SETTING)).thenReturn(false); + assertFalse(isForceEvictCacheEnabled()); + when(clusterSettings.get(KNN_FORCE_EVICT_CACHE_ENABLED_SETTING)).thenReturn(true); + assertTrue(isForceEvictCacheEnabled()); + } +} diff --git a/src/test/java/org/opensearch/knn/index/AdvancedFilteringUseCasesIT.java b/src/test/java/org/opensearch/knn/index/AdvancedFilteringUseCasesIT.java new file mode 100644 index 000000000..c32b179f9 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/AdvancedFilteringUseCasesIT.java @@ -0,0 +1,549 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index; + +import com.google.common.collect.ImmutableMap; +import lombok.SneakyThrows; +import org.apache.http.util.EntityUtils; +import org.junit.Assert; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.NestedKnnDocBuilder; +import org.opensearch.knn.index.engine.KNNEngine; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +import static org.opensearch.knn.common.KNNConstants.DIMENSION; +import static org.opensearch.knn.common.KNNConstants.K; +import static org.opensearch.knn.common.KNNConstants.KNN; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.KNN_METHOD; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; +import static org.opensearch.knn.common.KNNConstants.NAME; +import static org.opensearch.knn.common.KNNConstants.PATH; +import static org.opensearch.knn.common.KNNConstants.QUERY; +import static org.opensearch.knn.common.KNNConstants.TYPE; +import static org.opensearch.knn.common.KNNConstants.TYPE_KNN_VECTOR; +import static org.opensearch.knn.common.KNNConstants.TYPE_NESTED; +import static org.opensearch.knn.common.KNNConstants.VECTOR; + +/** + * This class contains the IT for some advanced and tricky use-case of filters. + * Github issue + */ +public class AdvancedFilteringUseCasesIT extends KNNRestTestCase { + + private static final String INDEX_NAME = "advanced_filtering_test_index"; + + private static final String FIELD_NAME_NESTED = "test_nested"; + + private static final String FIELD_NAME_VECTOR = "test_vector"; + + private static final String FILTER_FIELD = "filter"; + + private static final String TERM_FIELD = "term"; + + private static final int k = 20; + + private static final String FIELD_NAME_METADATA = "parking"; + + private static final int NUM_DOCS = 50; + + private static final int DOCUMENT_IN_RESPONSE = 10; + + private static final Float[] QUERY_VECTOR = { 5f }; + + private static final List enginesToTest = KNNEngine.getEnginesThatSupportsFilters() + .stream() + .map(KNNEngine::getName) + .collect(Collectors.toList()); + + /** + * { + * "query": { + * "nested": { + * "path": "test_nested", + * "query": { + * "knn": { + * "test_nested.test_vector": { + * "vector": [ + * 3 + * ], + * "k": 20, + * "filter": { + * "nested": { + * "path": "test_nested", + * "query": { + * "term": { + * "test_nested.parking": "false" + * } + * } + * } + * } + * } + * } + * } + * } + * } + * } + */ + @SneakyThrows + public void testFiltering_whenNestedKNNAndFilterFieldWithNestedQueries_thenSuccess() { + for (final String engine : enginesToTest) { + // Set up the index with nested k-nn and metadata fields + createKnnIndex(INDEX_NAME, createNestedMappings(1, engine)); + for (int i = 1; i <= NUM_DOCS; i++) { + // making sure that only 2 documents have valid filters + final String metadataFieldValue = i % 2 == 0 ? "false" : "true"; + String doc = NestedKnnDocBuilder.create(FIELD_NAME_NESTED) + .addVectors(FIELD_NAME_VECTOR, new Float[] { (float) i + 1 }) + .addVectorWithMetadata(FIELD_NAME_VECTOR, new Float[] { (float) i }, FIELD_NAME_METADATA, metadataFieldValue) + .build(); + addKnnDoc(INDEX_NAME, String.valueOf(i), doc); + } + refreshIndex(INDEX_NAME); + forceMergeKnnIndex(INDEX_NAME); + + // Build the query with both k-nn and filters as nested fields. The filter should also have a nested context + final XContentBuilder builder = XContentFactory.jsonBuilder().startObject().startObject(QUERY); + builder.startObject(TYPE_NESTED); + builder.field(PATH, FIELD_NAME_NESTED); + builder.startObject(QUERY).startObject(KNN).startObject(FIELD_NAME_NESTED + "." + FIELD_NAME_VECTOR); + builder.field(VECTOR, QUERY_VECTOR); + builder.field(K, k); + builder.startObject(FILTER_FIELD); + builder.startObject(TYPE_NESTED); + builder.field(PATH, FIELD_NAME_NESTED); + builder.startObject(QUERY); + builder.startObject(TERM_FIELD); + builder.field(FIELD_NAME_NESTED + "." + FIELD_NAME_METADATA, "false"); + builder.endObject(); + builder.endObject(); + builder.endObject(); + builder.endObject(); + builder.endObject().endObject().endObject().endObject().endObject().endObject(); + + validateFilterSearch(builder.toString(), engine); + // cleanup + deleteKNNIndex(INDEX_NAME); + } + } + + /** + * { + * "query": { + * "nested": { + * "path": "test_nested", + * "query": { + * "knn": { + * "test_nested.test_vector": { + * "vector": [ + * 3 + * ], + * "k": 20, + * "filter": { + * "term": { + * "test_nested.parking": "false" + * } + * } + * } + * } + * } + * } + * } + * } + */ + @SneakyThrows + public void testFiltering_whenNestedKNNAndFilterFieldWithNoNestedContextInFilterQuery_thenSuccess() { + for (final String engine : enginesToTest) { + // Set up the index with nested k-nn and metadata fields + createKnnIndex(INDEX_NAME, createNestedMappings(1, engine)); + for (int i = 1; i <= NUM_DOCS; i++) { + // making sure that only 2 documents have valid filters + final String metadataFieldValue = i % 2 == 0 ? "false" : "true"; + String doc = NestedKnnDocBuilder.create(FIELD_NAME_NESTED) + .addVectors(FIELD_NAME_VECTOR, new Float[] { (float) i + 1 }) + .addVectorWithMetadata(FIELD_NAME_VECTOR, new Float[] { (float) i }, FIELD_NAME_METADATA, metadataFieldValue) + .build(); + addKnnDoc(INDEX_NAME, String.valueOf(i), doc); + } + refreshIndex(INDEX_NAME); + forceMergeKnnIndex(INDEX_NAME); + + // Build the query with both k-nn and filters as nested fields but a single nested context + final XContentBuilder builder = XContentFactory.jsonBuilder().startObject().startObject(QUERY); + builder.startObject(TYPE_NESTED); + builder.field(PATH, FIELD_NAME_NESTED); + builder.startObject(QUERY).startObject(KNN).startObject(FIELD_NAME_NESTED + "." + FIELD_NAME_VECTOR); + builder.field(VECTOR, QUERY_VECTOR); + builder.field(K, k); + builder.startObject(FILTER_FIELD); + builder.startObject(TERM_FIELD); + builder.field(FIELD_NAME_NESTED + "." + FIELD_NAME_METADATA, "false"); + builder.endObject(); + builder.endObject(); + builder.endObject().endObject().endObject().endObject().endObject().endObject(); + + validateFilterSearch(builder.toString(), engine); + + // cleanup + deleteKNNIndex(INDEX_NAME); + } + } + + /** + * { + * "query": { + * "nested": { + * "path": "test_nested", + * "query": { + * "knn": { + * "test_nested.test_vector": { + * "vector": [ + * 3 + * ], + * "k": 20, + * "filter": { + * "term": { + * "parking": "false" + * } + * } + * } + * } + * } + * } + * } + * } + * + */ + @SneakyThrows + public void testFiltering_whenNestedKNNAndNonNestedFilterFieldWithNonNestedFilterQuery_thenSuccess() { + for (final String engine : enginesToTest) { + // Set up the index with nested k-nn and metadata fields + createKnnIndex(INDEX_NAME, createNestedMappings(1, engine)); + for (int i = 1; i <= NUM_DOCS; i++) { + final String metadataFieldValue = i % 2 == 0 ? "false" : "true"; + String doc = NestedKnnDocBuilder.create(FIELD_NAME_NESTED) + .addVectors(FIELD_NAME_VECTOR, new Float[] { (float) i + 1 }, new Float[] { (float) i }) + .addTopLevelField(FIELD_NAME_METADATA, metadataFieldValue) + .build(); + addKnnDoc(INDEX_NAME, String.valueOf(i), doc); + } + refreshIndex(INDEX_NAME); + forceMergeKnnIndex(INDEX_NAME); + + // Build the query with k-nn field as nested query and filter on the top level fields + final XContentBuilder builder = XContentFactory.jsonBuilder().startObject().startObject(QUERY); + builder.startObject(TYPE_NESTED); + builder.field(PATH, FIELD_NAME_NESTED); + builder.startObject(QUERY).startObject(KNN).startObject(FIELD_NAME_NESTED + "." + FIELD_NAME_VECTOR); + builder.field(VECTOR, QUERY_VECTOR); + builder.field(K, k); + builder.startObject(FILTER_FIELD); + builder.startObject(TERM_FIELD); + builder.field(FIELD_NAME_METADATA, "false"); + builder.endObject(); + builder.endObject(); + builder.endObject().endObject().endObject().endObject().endObject().endObject(); + + validateFilterSearch(builder.toString(), engine); + + // cleanup + deleteKNNIndex(INDEX_NAME); + } + } + + /** + * { + * "query": { + * "knn": { + * "test_vector": { + * "vector": [ + * 3 + * ], + * "k": 20, + * "filter": { + * "bool": { + * "should": [ + * { + * "nested": { + * "path": "test_nested", + * "query": { + * "term": { + * "test_nested.parking": "false" + * } + * } + * } + * } + * ] + * } + * } + * } + * } + * } + * } + */ + @SneakyThrows + public void testFiltering_whenNonNestedKNNAndNestedFilterFieldWithNestedFilterQuery_thenSuccess() { + for (final String engine : enginesToTest) { + // Set up the index with nested k-nn and metadata fields + createKnnIndex(INDEX_NAME, createVectorNonNestedMappings(1, engine)); + for (int i = 1; i <= NUM_DOCS; i++) { + final String metadataFieldValue = i % 2 == 0 ? "false" : "true"; + String doc = NestedKnnDocBuilder.create(FIELD_NAME_NESTED) + .addMetadata(ImmutableMap.of(FIELD_NAME_METADATA, metadataFieldValue)) + .addTopLevelField(FIELD_NAME_VECTOR, new Float[] { (float) i }) + .build(); + addKnnDoc(INDEX_NAME, String.valueOf(i), doc); + } + refreshIndex(INDEX_NAME); + forceMergeKnnIndex(INDEX_NAME); + + // Build the query when filters are nested with nested path and k-NN field is non nested. + final XContentBuilder builder = XContentFactory.jsonBuilder().startObject().startObject(QUERY); + builder.startObject(KNN).startObject(FIELD_NAME_VECTOR); + builder.field(VECTOR, QUERY_VECTOR); + builder.field(K, k); + builder.startObject(FILTER_FIELD); + builder.startObject("bool"); + builder.startArray("should"); + builder.startObject(); + + builder.startObject(TYPE_NESTED); + builder.field(PATH, FIELD_NAME_NESTED); + + builder.startObject(QUERY); + builder.startObject(TERM_FIELD); + builder.field(FIELD_NAME_NESTED + "." + FIELD_NAME_METADATA, "false"); + builder.endObject(); + builder.endObject(); + + builder.endObject(); + + builder.endObject(); + builder.endArray(); + builder.endObject(); + builder.endObject(); + builder.endObject(); + builder.endObject(); + builder.endObject(); + builder.endObject(); + + validateFilterSearch(builder.toString(), engine); + // cleanup + deleteKNNIndex(INDEX_NAME); + } + } + + /** + * { + * "query": { + * "knn": { + * "test_vector": { + * "vector": [ + * 5 + * ], + * "k": 20, + * "filter": { + * "bool": { + * "must": [ + * { + * "nested": { + * "path": "test_nested", + * "query": { + * "term": { + * "test_nested.parking": "false" + * } + * } + * } + * }, + * { + * "term": { + * "parking": "false" + * } + * } + * ] + * } + * } + * } + * } + * } + * } + */ + @SneakyThrows + public void testFiltering_whenNonNestedKNNAndNestedFilterAndNonNestedFieldWithNestedAndNonNestedFilterQuery_thenSuccess() { + for (final String engine : enginesToTest) { + // Set up the index with nested k-nn and metadata fields + createKnnIndex(INDEX_NAME, createVectorNonNestedMappings(1, engine)); + for (int i = 1; i <= NUM_DOCS; i++) { + final String metadataFieldValue = i % 2 == 0 ? "false" : "true"; + String doc = NestedKnnDocBuilder.create(FIELD_NAME_NESTED) + .addMetadata(ImmutableMap.of(FIELD_NAME_METADATA, metadataFieldValue)) + .addTopLevelField(FIELD_NAME_VECTOR, new Float[] { (float) i }) + .addTopLevelField(FIELD_NAME_METADATA, metadataFieldValue) + .build(); + addKnnDoc(INDEX_NAME, String.valueOf(i), doc); + } + refreshIndex(INDEX_NAME); + forceMergeKnnIndex(INDEX_NAME); + + // Build the query when filters are nested with nested path and k-NN field is non nested. + final XContentBuilder builder = XContentFactory.jsonBuilder().startObject().startObject(QUERY); + builder.startObject(KNN).startObject(FIELD_NAME_VECTOR); + builder.field(VECTOR, QUERY_VECTOR); + builder.field(K, k); + builder.startObject(FILTER_FIELD); + builder.startObject("bool"); + builder.startArray("must"); + builder.startObject(); + builder.startObject(TERM_FIELD); + builder.field(FIELD_NAME_METADATA, "false"); + builder.endObject(); + builder.endObject(); + + builder.startObject(); + + builder.startObject(TYPE_NESTED); + builder.field(PATH, FIELD_NAME_NESTED); + + builder.startObject(QUERY); + builder.startObject(TERM_FIELD); + builder.field(FIELD_NAME_NESTED + "." + FIELD_NAME_METADATA, "false"); + builder.endObject(); + builder.endObject(); + + builder.endObject(); + + builder.endObject(); + builder.endArray(); + builder.endObject(); + builder.endObject(); + builder.endObject(); + builder.endObject(); + builder.endObject(); + builder.endObject(); + + validateFilterSearch(builder.toString(), engine); + // cleanup + deleteKNNIndex(INDEX_NAME); + } + } + + private void validateFilterSearch(final String query, final String engine) throws IOException { + String response = EntityUtils.toString(performSearch(INDEX_NAME, query).getEntity()); + // Validate number of documents returned as the expected number of documents + Assert.assertEquals("For engine " + engine + ", hits: ", DOCUMENT_IN_RESPONSE, parseHits(response)); + Assert.assertEquals("For engine " + engine + ", totalSearchHits: ", k, parseTotalSearchHits(response)); + if (KNNEngine.getEngine(engine) == KNNEngine.FAISS) { + // Update the filter threshold to 0 to ensure that we are hitting ANN Search use case for FAISS + updateIndexSettings(INDEX_NAME, Settings.builder().put(KNNSettings.ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD, 0)); + response = EntityUtils.toString(performSearch(INDEX_NAME, query).getEntity()); + + // Validate number of documents returned as the expected number of documents + Assert.assertEquals("For engine " + engine + ", hits with ANN search :", DOCUMENT_IN_RESPONSE, parseHits(response)); + Assert.assertEquals("For engine " + engine + ", totalSearchHits with ANN search :", k, parseTotalSearchHits(response)); + } + } + + /** + * Sample return + * { + * "properties": { + * "test_nested": { + * "type": "nested", + * "properties": { + * "test_vector": { + * "type": "knn_vector", + * "dimension": 1, + * "method": { + * "name": "hnsw", + * "space_type": "l2", + * "engine": "lucene" + * } + * } + * } + * } + * } + * } + */ + @SneakyThrows + private String createNestedMappings(final int dimension, final String engine) { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES_FIELD) + .startObject(FIELD_NAME_NESTED) + .field(TYPE, TYPE_NESTED) + .startObject(PROPERTIES_FIELD) + .startObject(FIELD_NAME_VECTOR) + .field(TYPE, TYPE_KNN_VECTOR) + .field(DIMENSION, dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2.getValue()) + .field(KNN_ENGINE, engine) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + return builder.toString(); + } + + /** + * Sample return + * { + * "properties": { + * "test_vector": { + * "type": "knn_vector", + * "dimension": 1, + * "method": { + * "name": "hnsw", + * "space_type": "l2", + * "engine": "lucene" + * } + * }, + * "test_nested": { + * "type": "nested" + * } + * } + * } + */ + @SneakyThrows + private String createVectorNonNestedMappings(final int dimension, final String engine) { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES_FIELD) + .startObject(FIELD_NAME_NESTED) + .field(TYPE, TYPE_NESTED) + .endObject() + .startObject(FIELD_NAME_VECTOR) + .field(TYPE, TYPE_KNN_VECTOR) + .field(DIMENSION, dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2.getValue()) + .field(KNN_ENGINE, engine) + .endObject() + .endObject() + .endObject() + .endObject(); + + return builder.toString(); + } +} diff --git a/src/test/java/org/opensearch/knn/index/FaissHNSWFlatE2EIT.java b/src/test/java/org/opensearch/knn/index/FaissHNSWFlatE2EIT.java new file mode 100644 index 000000000..db982566c --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/FaissHNSWFlatE2EIT.java @@ -0,0 +1,192 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index; + +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; +import com.google.common.collect.ImmutableList; +import com.google.common.primitives.Floats; +import lombok.AllArgsConstructor; +import lombok.SneakyThrows; +import org.apache.http.util.EntityUtils; +import org.junit.BeforeClass; +import org.opensearch.client.Response; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.KNNResult; +import org.opensearch.knn.TestUtils; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.query.KNNQueryBuilder; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.plugin.script.KNNScoringUtil; + +import java.io.IOException; +import java.net.URL; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import static com.carrotsearch.randomizedtesting.RandomizedTest.$; +import static com.carrotsearch.randomizedtesting.RandomizedTest.$$; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_SEARCH; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; +import static org.opensearch.knn.common.KNNConstants.NAME; +import static org.opensearch.knn.common.KNNConstants.PARAMETERS; + +@AllArgsConstructor +public class FaissHNSWFlatE2EIT extends KNNRestTestCase { + + private String description; + private int k; + private Map methodParameters; + private boolean deleteRandomDocs; + + static TestUtils.TestData testData; + + @BeforeClass + public static void setUpClass() throws IOException { + if (FaissHNSWFlatE2EIT.class.getClassLoader() == null) { + throw new IllegalStateException("ClassLoader of FaissIT Class is null"); + } + URL testIndexVectors = FaissHNSWFlatE2EIT.class.getClassLoader().getResource("data/test_vectors_1000x128.json"); + URL testQueries = FaissHNSWFlatE2EIT.class.getClassLoader().getResource("data/test_queries_100x128.csv"); + assert testIndexVectors != null; + assert testQueries != null; + testData = new TestUtils.TestData(testIndexVectors.getPath(), testQueries.getPath()); + } + + @ParametersFactory(argumentFormatting = "description:%1$s; k:%2$s; efSearch:%3$s, deleteDocs:%4$s") + public static Collection parameters() { + return Arrays.asList( + $$( + $("Valid k, valid efSearch efSearch value", 10, Map.of(METHOD_PARAMETER_EF_SEARCH, 300), false), + $("Valid k, efsearch absent", 10, null, false), + $("Has delete docs, ef_search", 10, Map.of(METHOD_PARAMETER_EF_SEARCH, 300), true), + $("Has delete docs", 10, null, true) + ) + ); + } + + @SneakyThrows + public void testEndToEnd_whenMethodIsHNSWFlat_thenSucceed() { + String indexName = "test-index-1"; + String fieldName = "test-field-1"; + SpaceType spaceType = SpaceType.L2; + + List mValues = ImmutableList.of(16, 32, 64, 128); + List efConstructionValues = ImmutableList.of(16, 32, 64, 128); + List efSearchValues = ImmutableList.of(16, 32, 64, 128); + + Integer dimension = testData.indexData.vectors[0].length; + + // Create an index + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNNConstants.KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(PARAMETERS) + .field(KNNConstants.METHOD_PARAMETER_M, mValues.get(random().nextInt(mValues.size()))) + .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, efConstructionValues.get(random().nextInt(efConstructionValues.size()))) + .field(KNNConstants.METHOD_PARAMETER_EF_SEARCH, efSearchValues.get(random().nextInt(efSearchValues.size()))) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + Map mappingMap = xContentBuilderToMap(builder); + String mapping = builder.toString(); + + createKnnIndex(indexName, mapping); + assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(indexName))); + + // Index the test data + for (int i = 0; i < testData.indexData.docs.length; i++) { + addKnnDoc( + indexName, + Integer.toString(testData.indexData.docs[i]), + fieldName, + Floats.asList(testData.indexData.vectors[i]).toArray() + ); + } + + // Assert we have the right number of documents in the index + refreshAllNonSystemIndices(); + assertEquals(testData.indexData.docs.length, getDocCount(indexName)); + + // Delete few Docs + if (deleteRandomDocs) { + final Set docIdsToBeDeleted = new HashSet<>(); + while (docIdsToBeDeleted.size() < 10) { + docIdsToBeDeleted.add(randomInt(testData.indexData.docs.length - 1)); + } + + for (Integer id : docIdsToBeDeleted) { + deleteKnnDoc(indexName, Integer.toString(testData.indexData.docs[id])); + } + refreshAllNonSystemIndices(); + forceMergeKnnIndex(indexName, 3); + + assertEquals(testData.indexData.docs.length - 10, getDocCount(indexName)); + } + + // Test search queries + for (int i = 0; i < testData.queries.length; i++) { + final KNNQueryBuilder queryBuilder = KNNQueryBuilder.builder() + .fieldName(fieldName) + .vector(testData.queries[i]) + .k(k) + .methodParameters(methodParameters) + .build(); + Response response = searchKNNIndex(indexName, queryBuilder, k); + String responseBody = EntityUtils.toString(response.getEntity()); + List knnResults = parseSearchResponse(responseBody, fieldName); + assertEquals(k, knnResults.size()); + + List actualScores = parseSearchResponseScore(responseBody, fieldName); + for (int j = 0; j < k; j++) { + float[] primitiveArray = knnResults.get(j).getVector(); + assertEquals( + KNNEngine.FAISS.score(KNNScoringUtil.l2Squared(testData.queries[i], primitiveArray), spaceType), + actualScores.get(j), + 0.0001 + ); + } + } + + // Delete index + deleteKNNIndex(indexName); + + // Search every 5 seconds 14 times to confirm graph gets evicted + int intervals = 14; + for (int i = 0; i < intervals; i++) { + if (getTotalGraphsInCache() == 0) { + return; + } + Thread.sleep(5 * 1000); + } + + fail("Graphs are not getting evicted"); + } +} diff --git a/src/test/java/org/opensearch/knn/index/FaissIT.java b/src/test/java/org/opensearch/knn/index/FaissIT.java index 157eb0ce8..317cff4d4 100644 --- a/src/test/java/org/opensearch/knn/index/FaissIT.java +++ b/src/test/java/org/opensearch/knn/index/FaissIT.java @@ -12,41 +12,95 @@ package org.opensearch.knn.index; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.primitives.Floats; +import lombok.SneakyThrows; +import org.apache.http.ParseException; import org.apache.http.util.EntityUtils; import org.junit.BeforeClass; import org.opensearch.client.Response; -import org.opensearch.common.xcontent.XContentBuilder; -import org.opensearch.knn.KNNRestTestCase; -import org.opensearch.common.Strings; import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.common.settings.Settings; +import org.opensearch.client.ResponseException; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.QueryBuilders; +import org.opensearch.index.query.TermQueryBuilder; +import org.opensearch.knn.KNNRestTestCase; import org.opensearch.knn.KNNResult; import org.opensearch.knn.TestUtils; import org.opensearch.knn.common.KNNConstants; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.query.KNNQueryBuilder; +import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.plugin.script.KNNScoringUtil; import java.io.IOException; import java.net.URL; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.Random; import java.util.TreeMap; import java.util.stream.Collectors; +import static org.opensearch.knn.common.KNNConstants.DIMENSION; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_SIZE; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_M; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PQ; +import static org.opensearch.knn.common.KNNConstants.ENCODER_SQ; +import static org.opensearch.knn.common.KNNConstants.FAISS_NAME; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_CLIP; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_ENCODER_FP16; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_TYPE; +import static org.opensearch.knn.common.KNNConstants.FP16_MAX_VALUE; +import static org.opensearch.knn.common.KNNConstants.FP16_MIN_VALUE; import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.KNN_METHOD; +import static org.opensearch.knn.common.KNNConstants.MAX_DISTANCE; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_IVF; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_M; import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NLIST; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NPROBES; import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; +import static org.opensearch.knn.common.KNNConstants.MIN_SCORE; +import static org.opensearch.knn.common.KNNConstants.MODEL_DESCRIPTION; import static org.opensearch.knn.common.KNNConstants.MODEL_ID; import static org.opensearch.knn.common.KNNConstants.NAME; import static org.opensearch.knn.common.KNNConstants.PARAMETERS; +import static org.opensearch.knn.common.KNNConstants.TRAIN_FIELD_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.TRAIN_INDEX_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; public class FaissIT extends KNNRestTestCase { + private static final String DOC_ID_1 = "doc1"; + private static final String DOC_ID_2 = "doc2"; + private static final String DOC_ID_3 = "doc3"; + private static final String COLOR_FIELD_NAME = "color"; + private static final String TASTE_FIELD_NAME = "taste"; + + private static final String DIMENSION_FIELD_NAME = "dimension"; + private static final int VECTOR_DIMENSION = 3; + private static final String KNN_VECTOR_TYPE = "knn_vector"; + private static final String PROPERTIES_FIELD_NAME = "properties"; + private static final String TYPE_FIELD_NAME = "type"; + private static final String INTEGER_FIELD_NAME = "int_field"; + private static final String FILED_TYPE_INTEGER = "integer"; + private static final String NON_EXISTENT_INTEGER_FIELD_NAME = "nonexistent_int_field"; + public static final int NEVER_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD = -1; static TestUtils.TestData testData; @BeforeClass public static void setUpClass() throws IOException { + if (FaissIT.class.getClassLoader() == null) { + throw new IllegalStateException("ClassLoader of FaissIT Class is null"); + } URL testIndexVectors = FaissIT.class.getClassLoader().getResource("data/test_vectors_1000x128.json"); URL testQueries = FaissIT.class.getClassLoader().getResource("data/test_queries_100x128.csv"); assert testIndexVectors != null; @@ -54,11 +108,66 @@ public static void setUpClass() throws IOException { testData = new TestUtils.TestData(testIndexVectors.getPath(), testQueries.getPath()); } - public void testEndToEnd_fromMethod() throws IOException, InterruptedException { - String indexName = "test-index-1"; - String fieldName = "test-field-1"; + @SneakyThrows + public void testEndToEnd_whenDoRadiusSearch_whenDistanceThreshold_whenMethodIsHNSWFlat_thenSucceed() { + SpaceType spaceType = SpaceType.L2; + + List mValues = ImmutableList.of(16, 32, 64, 128); + List efConstructionValues = ImmutableList.of(16, 32, 64, 128); + List efSearchValues = ImmutableList.of(16, 32, 64, 128); + + Integer dimension = testData.indexData.vectors[0].length; + + // Create an index + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_M, mValues.get(random().nextInt(mValues.size()))) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, efConstructionValues.get(random().nextInt(efConstructionValues.size()))) + .field(KNNConstants.METHOD_PARAMETER_EF_SEARCH, efSearchValues.get(random().nextInt(efSearchValues.size()))) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + Map mappingMap = xContentBuilderToMap(builder); + String mapping = builder.toString(); + + createKnnIndex(INDEX_NAME, mapping); + assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(INDEX_NAME))); + + // Index the test data + for (int i = 0; i < testData.indexData.docs.length; i++) { + addKnnDoc( + INDEX_NAME, + Integer.toString(testData.indexData.docs[i]), + FIELD_NAME, + Floats.asList(testData.indexData.vectors[i]).toArray() + ); + } + + // Assert we have the right number of documents + refreshAllNonSystemIndices(); + assertEquals(testData.indexData.docs.length, getDocCount(INDEX_NAME)); + + float distance = 300000000000f; + validateRadiusSearchResults(INDEX_NAME, FIELD_NAME, testData.queries, distance, null, spaceType, null, null); - KNNMethod hnswMethod = KNNEngine.FAISS.getMethod(KNNConstants.METHOD_HNSW); + // Delete index + deleteKNNIndex(INDEX_NAME); + } + + @SneakyThrows + public void testEndToEnd_whenDoRadiusSearch_whenScoreThreshold_whenMethodIsHNSWFlat_thenSucceed() { SpaceType spaceType = SpaceType.L2; List mValues = ImmutableList.of(16, 32, 64, 128); @@ -71,25 +180,296 @@ public void testEndToEnd_fromMethod() throws IOException, InterruptedException { XContentBuilder builder = XContentFactory.jsonBuilder() .startObject() .startObject("properties") - .startObject(fieldName) + .startObject(FIELD_NAME) .field("type", "knn_vector") .field("dimension", dimension) - .startObject(KNNConstants.KNN_METHOD) - .field(KNNConstants.NAME, hnswMethod.getMethodComponent().getName()) - .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) - .field(KNNConstants.KNN_ENGINE, KNNEngine.FAISS.getName()) - .startObject(KNNConstants.PARAMETERS) - .field(KNNConstants.METHOD_PARAMETER_M, mValues.get(random().nextInt(mValues.size()))) - .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, efConstructionValues.get(random().nextInt(efConstructionValues.size()))) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_M, mValues.get(random().nextInt(mValues.size()))) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, efConstructionValues.get(random().nextInt(efConstructionValues.size()))) + .field(KNNConstants.METHOD_PARAMETER_EF_SEARCH, efSearchValues.get(random().nextInt(efSearchValues.size()))) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + Map mappingMap = xContentBuilderToMap(builder); + String mapping = builder.toString(); + + createKnnIndex(INDEX_NAME, mapping); + assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(INDEX_NAME))); + + // Index the test data + for (int i = 0; i < testData.indexData.docs.length; i++) { + addKnnDoc( + INDEX_NAME, + Integer.toString(testData.indexData.docs[i]), + FIELD_NAME, + Floats.asList(testData.indexData.vectors[i]).toArray() + ); + } + + // Assert we have the right number of documents + refreshAllNonSystemIndices(); + assertEquals(testData.indexData.docs.length, getDocCount(INDEX_NAME)); + + float score = 0.00001f; + + validateRadiusSearchResults(INDEX_NAME, FIELD_NAME, testData.queries, null, score, spaceType, null, null); + + // Delete index + deleteKNNIndex(INDEX_NAME); + } + + @SneakyThrows + public void testEndToEnd_whenDoRadiusSearch_whenMoreThanOneScoreThreshold_whenMethodIsHNSWFlat_thenSucceed() { + SpaceType spaceType = SpaceType.INNER_PRODUCT; + + List mValues = ImmutableList.of(16, 32, 64, 128); + List efConstructionValues = ImmutableList.of(16, 32, 64, 128); + List efSearchValues = ImmutableList.of(16, 32, 64, 128); + + Integer dimension = testData.indexData.vectors[0].length; + + // Create an index + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_M, mValues.get(random().nextInt(mValues.size()))) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, efConstructionValues.get(random().nextInt(efConstructionValues.size()))) + .field(KNNConstants.METHOD_PARAMETER_EF_SEARCH, efSearchValues.get(random().nextInt(efSearchValues.size()))) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + Map mappingMap = xContentBuilderToMap(builder); + String mapping = builder.toString(); + + createKnnIndex(INDEX_NAME, mapping); + assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(INDEX_NAME))); + + // Index the test data + for (int i = 0; i < testData.indexData.docs.length; i++) { + addKnnDoc( + INDEX_NAME, + Integer.toString(testData.indexData.docs[i]), + FIELD_NAME, + Floats.asList(testData.indexData.vectors[i]).toArray() + ); + } + + // Assert we have the right number of documents + refreshAllNonSystemIndices(); + assertEquals(testData.indexData.docs.length, getDocCount(INDEX_NAME)); + + float score = 5f; + + validateRadiusSearchResults(INDEX_NAME, FIELD_NAME, testData.queries, null, score, spaceType, null, null); + + // Delete index + deleteKNNIndex(INDEX_NAME); + } + + @SneakyThrows + public void testEndToEnd_whenDoRadiusSearch_whenDistanceThreshold_whenMethodIsHNSWPQ_thenSucceed() { + String indexName = "test-index"; + String fieldName = "test-field"; + String trainingIndexName = "training-index"; + String trainingFieldName = "training-field"; + + String modelId = "test-model"; + String modelDescription = "test model"; + + List mValues = ImmutableList.of(16, 32, 64, 128); + List efConstructionValues = ImmutableList.of(16, 32, 64, 128); + List efSearchValues = ImmutableList.of(16, 32, 64, 128); + List pqMValues = ImmutableList.of(2, 4, 8); + + // training data needs to be at least equal to the number of centroids for PQ + // which is 2^8 = 256. 8 because that's the only valid code_size for HNSWPQ + int trainingDataCount = 256; + + SpaceType spaceType = SpaceType.L2; + + int dimension = testData.indexData.vectors[0].length; + + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, FAISS_NAME) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_M, mValues.get(random().nextInt(mValues.size()))) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, efConstructionValues.get(random().nextInt(efConstructionValues.size()))) + .field(KNNConstants.METHOD_PARAMETER_EF_SEARCH, efSearchValues.get(random().nextInt(efSearchValues.size()))) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_PQ) + .startObject(PARAMETERS) + .field(ENCODER_PARAMETER_PQ_M, pqMValues.get(random().nextInt(pqMValues.size()))) + .endObject() + .endObject() + .endObject() + .endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + + createBasicKnnIndex(trainingIndexName, trainingFieldName, dimension); + ingestDataAndTrainModel(modelId, trainingIndexName, trainingFieldName, dimension, modelDescription, in, trainingDataCount); + assertTrainingSucceeds(modelId, 360, 1000); + + // Create an index + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field("model_id", modelId) + .endObject() + .endObject() + .endObject(); + + Map mappingMap = xContentBuilderToMap(builder); + String mapping = builder.toString(); + + createKnnIndex(indexName, mapping); + + assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(indexName))); + + // Index the test data + for (int i = 0; i < testData.indexData.docs.length; i++) { + addKnnDoc( + indexName, + Integer.toString(testData.indexData.docs[i]), + fieldName, + Floats.asList(testData.indexData.vectors[i]).toArray() + ); + } + + // Assert we have the right number of documents in the index + refreshAllNonSystemIndices(); + assertEquals(testData.indexData.docs.length, getDocCount(indexName)); + + float distance = 300000000000f; + // create method parameter wih ef_search + Map methodParameters = new ImmutableMap.Builder().put(KNNConstants.METHOD_PARAMETER_EF_SEARCH, 150) + .build(); + + validateRadiusSearchResults(indexName, fieldName, testData.queries, distance, null, spaceType, null, methodParameters); + + // Delete index + deleteKNNIndex(indexName); + deleteModel(modelId); + + // Search every 5 seconds 14 times to confirm graph gets evicted + int intervals = 14; + for (int i = 0; i < intervals; i++) { + if (getTotalGraphsInCache() == 0) { + return; + } + + Thread.sleep(5 * 1000); + } + + fail("Graphs are not getting evicted"); + } + + @SneakyThrows + public void testRadialQuery_withFilter_thenSuccess() { + setupKNNIndexForFilterQuery(); + + final float[][] searchVector = new float[][] { { 3.3f, 3.0f, 5.0f } }; + TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("color", "red"); + List expectedDocIds = Arrays.asList(DOC_ID_3); + + float distance = 15f; + List> queryResult = validateRadiusSearchResults( + INDEX_NAME, + FIELD_NAME, + searchVector, + distance, + null, + SpaceType.L2, + termQueryBuilder, + null + ); + + assertEquals(1, queryResult.get(0).size()); + assertEquals(expectedDocIds.get(0), queryResult.get(0).get(0).getDocId()); + + // Delete index + deleteKNNIndex(INDEX_NAME); + } + + @SneakyThrows + public void testEndToEnd_whenMethodIsHNSWPQ_thenSucceed() { + String indexName = "test-index"; + String fieldName = "test-field"; + String trainingIndexName = "training-index"; + String trainingFieldName = "training-field"; + + String modelId = "test-model"; + String modelDescription = "test model"; + + List mValues = ImmutableList.of(16, 32, 64, 128); + List efConstructionValues = ImmutableList.of(16, 32, 64, 128); + List efSearchValues = ImmutableList.of(16, 32, 64, 128); + List pqMValues = ImmutableList.of(2, 4, 8); + + // training data needs to be at least equal to the number of centroids for PQ + // which is 2^8 = 256. 8 because thats the only valid code_size for HNSWPQ + int trainingDataCount = 256; + + SpaceType spaceType = SpaceType.L2; + + Integer dimension = testData.indexData.vectors[0].length; + + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, FAISS_NAME) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_M, mValues.get(random().nextInt(mValues.size()))) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, efConstructionValues.get(random().nextInt(efConstructionValues.size()))) .field(KNNConstants.METHOD_PARAMETER_EF_SEARCH, efSearchValues.get(random().nextInt(efSearchValues.size()))) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_PQ) + .startObject(PARAMETERS) + .field(ENCODER_PARAMETER_PQ_M, pqMValues.get(random().nextInt(pqMValues.size()))) .endObject() .endObject() .endObject() + .endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + + createBasicKnnIndex(trainingIndexName, trainingFieldName, dimension); + ingestDataAndTrainModel(modelId, trainingIndexName, trainingFieldName, dimension, modelDescription, in, trainingDataCount); + assertTrainingSucceeds(modelId, 360, 1000); + + // Create an index + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field("model_id", modelId) + .endObject() .endObject() .endObject(); Map mappingMap = xContentBuilderToMap(builder); - String mapping = Strings.toString(builder); + String mapping = builder.toString(); createKnnIndex(indexName, mapping); assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(indexName))); @@ -105,7 +485,7 @@ public void testEndToEnd_fromMethod() throws IOException, InterruptedException { } // Assert we have the right number of documents in the index - refreshAllIndices(); + refreshAllNonSystemIndices(); assertEquals(testData.indexData.docs.length, getDocCount(indexName)); int k = 10; @@ -117,7 +497,7 @@ public void testEndToEnd_fromMethod() throws IOException, InterruptedException { List actualScores = parseSearchResponseScore(responseBody, fieldName); for (int j = 0; j < k; j++) { - float[] primitiveArray = Floats.toArray(Arrays.stream(knnResults.get(j).getVector()).collect(Collectors.toList())); + float[] primitiveArray = knnResults.get(j).getVector(); assertEquals( KNNEngine.FAISS.score(KNNScoringUtil.l2Squared(testData.queries[i], primitiveArray), spaceType), actualScores.get(j), @@ -128,6 +508,7 @@ public void testEndToEnd_fromMethod() throws IOException, InterruptedException { // Delete index deleteKNNIndex(indexName); + deleteModel(modelId); // Search every 5 seconds 14 times to confirm graph gets evicted int intervals = 14; @@ -142,13 +523,20 @@ public void testEndToEnd_fromMethod() throws IOException, InterruptedException { fail("Graphs are not getting evicted"); } - public void testDocUpdate() throws IOException { - String indexName = "test-index-1"; - String fieldName = "test-field-1"; - Integer dimension = 2; + @SneakyThrows + public void testHNSWSQFP16_whenIndexedAndQueried_thenSucceed() { + String indexName = "test-index-hnsw-sqfp16"; + String fieldName = "test-field-hnsw-sqfp16"; + SpaceType[] spaceTypes = { SpaceType.L2, SpaceType.INNER_PRODUCT }; + Random random = new Random(); + SpaceType spaceType = spaceTypes[random.nextInt(spaceTypes.length)]; - KNNMethod hnswMethod = KNNEngine.FAISS.getMethod(KNNConstants.METHOD_HNSW); - SpaceType spaceType = SpaceType.L2; + List mValues = ImmutableList.of(16, 32, 64, 128); + List efConstructionValues = ImmutableList.of(16, 32, 64, 128); + List efSearchValues = ImmutableList.of(16, 32, 64, 128); + + int dimension = 128; + int numDocs = 100; // Create an index XContentBuilder builder = XContentFactory.jsonBuilder() @@ -157,126 +545,1602 @@ public void testDocUpdate() throws IOException { .startObject(fieldName) .field("type", "knn_vector") .field("dimension", dimension) - .startObject(KNNConstants.KNN_METHOD) - .field(KNNConstants.NAME, hnswMethod.getMethodComponent().getName()) - .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) - .field(KNNConstants.KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_M, mValues.get(random().nextInt(mValues.size()))) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, efConstructionValues.get(random().nextInt(efConstructionValues.size()))) + .field(KNNConstants.METHOD_PARAMETER_EF_SEARCH, efSearchValues.get(random().nextInt(efSearchValues.size()))) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_SQ) + .startObject(PARAMETERS) + .field(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16) + .endObject() + .endObject() + .endObject() .endObject() .endObject() .endObject() .endObject(); - String mapping = Strings.toString(builder); - createKnnIndex(indexName, mapping); - - Float[] vector = { 6.0f, 6.0f }; - addKnnDoc(INDEX_NAME, "1", FIELD_NAME, vector); - - // update - Float[] updatedVector = { 8.0f, 8.0f }; - updateKnnDoc(INDEX_NAME, "1", FIELD_NAME, updatedVector); + Map mappingMap = xContentBuilderToMap(builder); + String mapping = builder.toString(); + createKnnIndex(indexName, mapping); + assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(indexName))); + indexTestData(indexName, fieldName, dimension, numDocs); + queryTestData(indexName, fieldName, dimension, numDocs); + deleteKNNIndex(indexName); + validateGraphEviction(); } - public void testDocDeletion() throws IOException { - String indexName = "test-index-1"; - String fieldName = "test-field-1"; - Integer dimension = 2; + @SneakyThrows + public void testHNSWSQFP16_whenGraphThresholdIsNegative_whenIndexed_thenSkipCreatingGraph() { + final String indexName = "test-index-hnsw-sqfp16"; + final String fieldName = "test-field-hnsw-sqfp16"; + final SpaceType[] spaceTypes = { SpaceType.L2, SpaceType.INNER_PRODUCT }; + final Random random = new Random(); + final SpaceType spaceType = spaceTypes[random.nextInt(spaceTypes.length)]; - KNNMethod hnswMethod = KNNEngine.FAISS.getMethod(KNNConstants.METHOD_HNSW); - SpaceType spaceType = SpaceType.L2; + final int dimension = 128; + final int numDocs = 100; // Create an index - XContentBuilder builder = XContentFactory.jsonBuilder() + final XContentBuilder builder = XContentFactory.jsonBuilder() .startObject() .startObject("properties") .startObject(fieldName) .field("type", "knn_vector") .field("dimension", dimension) - .startObject(KNNConstants.KNN_METHOD) - .field(KNNConstants.NAME, hnswMethod.getMethodComponent().getName()) - .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) - .field(KNNConstants.KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(PARAMETERS) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_SQ) + .startObject(PARAMETERS) + .field(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16) + .endObject() + .endObject() + .endObject() .endObject() .endObject() .endObject() .endObject(); - String mapping = Strings.toString(builder); - createKnnIndex(indexName, mapping); + final Map mappingMap = xContentBuilderToMap(builder); + final String mapping = builder.toString(); + final Settings knnIndexSettings = buildKNNIndexSettings(-1); + createKnnIndex(indexName, knnIndexSettings, mapping); + assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(indexName))); + indexTestData(indexName, fieldName, dimension, numDocs); - Float[] vector = { 6.0f, 6.0f }; - addKnnDoc(INDEX_NAME, "1", FIELD_NAME, vector); + final float[] queryVector = new float[dimension]; + Arrays.fill(queryVector, (float) numDocs); - // delete knn doc - deleteKnnDoc(INDEX_NAME, "1"); + // Assert we have the right number of documents in the index + assertEquals(numDocs, getDocCount(indexName)); + + final Response searchResponse = searchKNNIndex(indexName, buildSearchQuery(fieldName, 1, queryVector, null), 1); + final List results = parseSearchResponse(EntityUtils.toString(searchResponse.getEntity()), fieldName); + // expect result due to exact search + assertEquals(1, results.size()); + + deleteKNNIndex(indexName); + validateGraphEviction(); } - public void testEndToEnd_fromModel() throws IOException, InterruptedException { - String modelId = "test-model"; + @SneakyThrows + public void testHNSWSQFP16_whenGraphThresholdIsMetDuringMerge_thenCreateGraph() { + final String indexName = "test-index-hnsw-sqfp16"; + final String fieldName = "test-field-hnsw-sqfp16"; + final SpaceType[] spaceTypes = { SpaceType.L2, SpaceType.INNER_PRODUCT }; + final Random random = new Random(); + final SpaceType spaceType = spaceTypes[random.nextInt(spaceTypes.length)]; + final int dimension = 128; + final int numDocs = 100; + + // Create an index + final XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(PARAMETERS) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_SQ) + .startObject(PARAMETERS) + .field(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + final Map mappingMap = xContentBuilderToMap(builder); + final String mapping = builder.toString(); + final Settings knnIndexSettings = buildKNNIndexSettings(numDocs); + createKnnIndex(indexName, knnIndexSettings, mapping); + assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(indexName))); + indexTestData(indexName, fieldName, dimension, numDocs); + + final float[] queryVector = new float[dimension]; + Arrays.fill(queryVector, (float) numDocs); + + // Assert we have the right number of documents in the index + assertEquals(numDocs, getDocCount(indexName)); + + // KNN Query should return empty result + final Response searchResponse = searchKNNIndex(indexName, buildSearchQuery(fieldName, 1, queryVector, null), 1); + final List results = parseSearchResponse(EntityUtils.toString(searchResponse.getEntity()), fieldName); + assertEquals(1, results.size()); + + // update index setting to build graph and do force merge + // update build vector data structure setting + forceMergeKnnIndex(indexName, 1); + + queryTestData(indexName, fieldName, dimension, numDocs); + + deleteKNNIndex(indexName); + validateGraphEviction(); + } + + @SneakyThrows + public void testIVFSQFP16_whenIndexedAndQueried_thenSucceed() { + + String modelId = "test-model-ivf-sqfp16"; int dimension = 128; + int numDocs = 100; - String trainingIndexName = "train-index"; - String trainingFieldName = "train-field"; + String trainingIndexName = "train-index-ivf-sqfp16"; + String trainingFieldName = "train-field-ivf-sqfp16"; // Add training data createBasicKnnIndex(trainingIndexName, trainingFieldName, dimension); int trainingDataCount = 200; bulkIngestRandomVectors(trainingIndexName, trainingFieldName, trainingDataCount, dimension); - // Call train API - IVF with nlists = 1 is brute force, but will require training XContentBuilder builder = XContentFactory.jsonBuilder() .startObject() - .field(NAME, "ivf") - .field(KNN_ENGINE, "faiss") + .field(NAME, METHOD_IVF) + .field(KNN_ENGINE, FAISS_NAME) .field(METHOD_PARAMETER_SPACE_TYPE, "l2") .startObject(PARAMETERS) - .field(METHOD_PARAMETER_NLIST, 1) + .field(METHOD_PARAMETER_NPROBES, 4) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_SQ) + .startObject(PARAMETERS) + .field(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16) + .endObject() + .endObject() .endObject() .endObject(); Map method = xContentBuilderToMap(builder); - trainModel(modelId, trainingIndexName, trainingFieldName, dimension, method, "faiss test description"); + trainModel(modelId, trainingIndexName, trainingFieldName, dimension, method, "faiss ivf sqfp16 test description"); // Make sure training succeeds after 30 seconds assertTrainingSucceeds(modelId, 30, 1000); // Create knn index from model - String fieldName = "test-field-name"; - String indexName = "test-index-name"; - String indexMapping = Strings.toString( - XContentFactory.jsonBuilder() - .startObject() - .startObject("properties") - .startObject(fieldName) - .field("type", "knn_vector") - .field(MODEL_ID, modelId) - .endObject() - .endObject() - .endObject() - ); + String fieldName = "test-field-name-ivf-sqfp16"; + String indexName = "test-index-name-ivf-sqfp16"; + String indexMapping = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field(MODEL_ID, modelId) + .endObject() + .endObject() + .endObject() + .toString(); createKnnIndex(indexName, getKNNDefaultIndexSettings(), indexMapping); - // Index some documents - int numDocs = 100; - for (int i = 0; i < numDocs; i++) { - Float[] indexVector = new Float[dimension]; - Arrays.fill(indexVector, (float) i); + indexTestData(indexName, fieldName, dimension, numDocs); + queryTestData(indexName, fieldName, dimension, numDocs); + queryTestData(indexName, fieldName, dimension, numDocs, Map.of("nprobes", 100)); + deleteKNNIndex(indexName); + validateGraphEviction(); + } - addKnnDoc(indexName, Integer.toString(i), fieldName, indexVector); - } + @SneakyThrows + public void testHNSWSQFP16_whenIndexedWithOutOfFP16Range_thenThrowException() { + String indexName = "test-index-sqfp16"; + String fieldName = "test-field-sqfp16"; + SpaceType[] spaceTypes = { SpaceType.L2, SpaceType.INNER_PRODUCT }; + Random random = new Random(); + SpaceType spaceType = spaceTypes[random.nextInt(spaceTypes.length)]; - // Run search and ensure that the values returned are expected - float[] queryVector = new float[dimension]; - Arrays.fill(queryVector, (float) numDocs); - int k = 10; + List mValues = ImmutableList.of(16, 32, 64, 128); + List efConstructionValues = ImmutableList.of(16, 32, 64, 128); + List efSearchValues = ImmutableList.of(16, 32, 64, 128); + + int dimension = 2; + + // Create an index + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_M, mValues.get(random().nextInt(mValues.size()))) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, efConstructionValues.get(random().nextInt(efConstructionValues.size()))) + .field(KNNConstants.METHOD_PARAMETER_EF_SEARCH, efSearchValues.get(random().nextInt(efSearchValues.size()))) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_SQ) + .startObject(PARAMETERS) + .field(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + Map mappingMap = xContentBuilderToMap(builder); + String mapping = builder.toString(); + + createKnnIndex(indexName, mapping); + assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(indexName))); + Float[] vector = { -10.76f, 65504.2f }; + + ResponseException ex = expectThrows(ResponseException.class, () -> addKnnDoc(indexName, "1", fieldName, vector)); + assertTrue( + ex.getMessage() + .contains( + String.format( + Locale.ROOT, + "encoder name is set as [%s] and type is set as [%s] in index mapping. But, KNN vector values are not within in the FP16 range [%f, %f]", + ENCODER_SQ, + FAISS_SQ_ENCODER_FP16, + FP16_MIN_VALUE, + FP16_MAX_VALUE + ) + ) + ); + + Float[] vector1 = { -65506.84f, 12.56f }; + + ResponseException ex1 = expectThrows(ResponseException.class, () -> addKnnDoc(indexName, "2", fieldName, vector1)); + assertTrue( + ex1.getMessage() + .contains( + String.format( + Locale.ROOT, + "encoder name is set as [%s] and type is set as [%s] in index mapping. But, KNN vector values are not within in the FP16 range [%f, %f]", + ENCODER_SQ, + FAISS_SQ_ENCODER_FP16, + FP16_MIN_VALUE, + FP16_MAX_VALUE + ) + ) + ); + + Float[] vector2 = { -65526.4567f, 65526.4567f }; + + ResponseException ex2 = expectThrows(ResponseException.class, () -> addKnnDoc(indexName, "3", fieldName, vector2)); + assertTrue( + ex2.getMessage() + .contains( + String.format( + Locale.ROOT, + "encoder name is set as [%s] and type is set as [%s] in index mapping. But, KNN vector values are not within in the FP16 range [%f, %f]", + ENCODER_SQ, + FAISS_SQ_ENCODER_FP16, + FP16_MIN_VALUE, + FP16_MAX_VALUE + ) + ) + ); + deleteKNNIndex(indexName); + validateGraphEviction(); + } + + @SneakyThrows + public void testHNSWSQFP16_whenClipToFp16isTrueAndIndexedWithOutOfFP16Range_thenSucceed() { + String indexName = "test-index-sqfp16-clip-fp16"; + String fieldName = "test-field-sqfp16"; + + List mValues = ImmutableList.of(16, 32, 64, 128); + List efConstructionValues = ImmutableList.of(16, 32, 64, 128); + List efSearchValues = ImmutableList.of(16, 32, 64, 128); + + int dimension = 2; + + // Create an index + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2.getValue()) + .field(KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_M, mValues.get(random().nextInt(mValues.size()))) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, efConstructionValues.get(random().nextInt(efConstructionValues.size()))) + .field(KNNConstants.METHOD_PARAMETER_EF_SEARCH, efSearchValues.get(random().nextInt(efSearchValues.size()))) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_SQ) + .startObject(PARAMETERS) + .field(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16) + .field(FAISS_SQ_CLIP, true) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + Map mappingMap = xContentBuilderToMap(builder); + String mapping = builder.toString(); + + createKnnIndex(indexName, mapping); + assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(indexName))); + Float[] vector1 = { -65523.76f, 65504.2f }; + Float[] vector2 = { -270.85f, 65514.2f }; + Float[] vector3 = { -150.9f, 65504.0f }; + Float[] vector4 = { -20.89f, 100000000.0f }; + addKnnDoc(indexName, "1", fieldName, vector1); + addKnnDoc(indexName, "2", fieldName, vector2); + addKnnDoc(indexName, "3", fieldName, vector3); + addKnnDoc(indexName, "4", fieldName, vector4); + float[] queryVector = { -10.5f, 25.48f }; + int k = 4; Response searchResponse = searchKNNIndex(indexName, new KNNQueryBuilder(fieldName, queryVector, k), k); List results = parseSearchResponse(EntityUtils.toString(searchResponse.getEntity()), fieldName); + assertEquals(k, results.size()); + for (int i = 0; i < k; i++) { + assertEquals(k - i, Integer.parseInt(results.get(i).getDocId())); + } + + deleteKNNIndex(indexName); + validateGraphEviction(); + } + + @SneakyThrows + public void testIVFSQFP16_whenIndexedWithOutOfFP16Range_thenThrowException() { + String modelId = "test-model-ivf-sqfp16"; + int dimension = 128; + + String trainingIndexName = "train-index-ivf-sqfp16"; + String trainingFieldName = "train-field-ivf-sqfp16"; + + // Add training data + createBasicKnnIndex(trainingIndexName, trainingFieldName, dimension); + int trainingDataCount = 200; + bulkIngestRandomVectors(trainingIndexName, trainingFieldName, trainingDataCount, dimension); + + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_IVF) + .field(KNN_ENGINE, FAISS_NAME) + .field(METHOD_PARAMETER_SPACE_TYPE, "l2") + .startObject(PARAMETERS) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_SQ) + .startObject(PARAMETERS) + .field(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16) + .endObject() + .endObject() + .endObject() + .endObject(); + Map method = xContentBuilderToMap(builder); + + trainModel(modelId, trainingIndexName, trainingFieldName, dimension, method, "faiss ivf sqfp16 test description"); + + // Make sure training succeeds after 30 seconds + assertTrainingSucceeds(modelId, 30, 1000); + + // Create knn index from model + String fieldName = "test-field-name-ivf-sqfp16"; + String indexName = "test-index-name-ivf-sqfp16"; + String indexMapping = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field(MODEL_ID, modelId) + .endObject() + .endObject() + .endObject() + .toString(); + + createKnnIndex(indexName, getKNNDefaultIndexSettings(), indexMapping); + Float[] vector = { -10.76f, 65504.2f }; + + ResponseException ex = expectThrows(ResponseException.class, () -> addKnnDoc(indexName, "1", fieldName, vector)); + assertTrue( + ex.getMessage() + .contains( + String.format( + Locale.ROOT, + "encoder name is set as [%s] and type is set as [%s] in index mapping. But, KNN vector values are not within in the FP16 range [%f, %f]", + ENCODER_SQ, + FAISS_SQ_ENCODER_FP16, + FP16_MIN_VALUE, + FP16_MAX_VALUE + ) + ) + ); + + Float[] vector1 = { -65506.84f, 12.56f }; + + ResponseException ex1 = expectThrows(ResponseException.class, () -> addKnnDoc(indexName, "2", fieldName, vector1)); + assertTrue( + ex1.getMessage() + .contains( + String.format( + Locale.ROOT, + "encoder name is set as [%s] and type is set as [%s] in index mapping. But, KNN vector values are not within in the FP16 range [%f, %f]", + ENCODER_SQ, + FAISS_SQ_ENCODER_FP16, + FP16_MIN_VALUE, + FP16_MAX_VALUE + ) + ) + ); + + Float[] vector2 = { -65526.4567f, 65526.4567f }; + + ResponseException ex2 = expectThrows(ResponseException.class, () -> addKnnDoc(indexName, "3", fieldName, vector2)); + assertTrue( + ex2.getMessage() + .contains( + String.format( + Locale.ROOT, + "encoder name is set as [%s] and type is set as [%s] in index mapping. But, KNN vector values are not within in the FP16 range [%f, %f]", + ENCODER_SQ, + FAISS_SQ_ENCODER_FP16, + FP16_MIN_VALUE, + FP16_MAX_VALUE + ) + ) + ); + deleteKNNIndex(indexName); + deleteKNNIndex(trainingIndexName); + deleteModel(modelId); + } + + @SneakyThrows + public void testIVFSQFP16_whenClipToFp16isTrueAndIndexedWithOutOfFP16Range_thenSucceed() { + String modelId = "test-model-ivf-sqfp16"; + int dimension = 2; + + String trainingIndexName = "train-index-ivf-sqfp16"; + String trainingFieldName = "train-field-ivf-sqfp16"; + + // Add training data + createBasicKnnIndex(trainingIndexName, trainingFieldName, dimension); + int trainingDataCount = 200; + bulkIngestRandomVectors(trainingIndexName, trainingFieldName, trainingDataCount, dimension); + + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_IVF) + .field(KNN_ENGINE, FAISS_NAME) + .field(METHOD_PARAMETER_SPACE_TYPE, "l2") + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_NLIST, 1) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_SQ) + .startObject(PARAMETERS) + .field(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16) + .field(FAISS_SQ_CLIP, true) + .endObject() + .endObject() + .endObject() + .endObject(); + Map method = xContentBuilderToMap(builder); + + trainModel(modelId, trainingIndexName, trainingFieldName, dimension, method, "faiss ivf sqfp16 test description"); + + // Make sure training succeeds after 30 seconds + assertTrainingSucceeds(modelId, 30, 1000); + + // Create knn index from model + String fieldName = "test-field-name-ivf-sqfp16"; + String indexName = "test-index-name-ivf-sqfp16"; + String indexMapping = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field(MODEL_ID, modelId) + .endObject() + .endObject() + .endObject() + .toString(); + + createKnnIndex(indexName, getKNNDefaultIndexSettings(), indexMapping); + Float[] vector1 = { -65523.76f, 65504.2f }; + Float[] vector2 = { -270.85f, 65514.2f }; + Float[] vector3 = { -150.9f, 65504.0f }; + Float[] vector4 = { -20.89f, 100000000.0f }; + addKnnDoc(indexName, "1", fieldName, vector1); + addKnnDoc(indexName, "2", fieldName, vector2); + addKnnDoc(indexName, "3", fieldName, vector3); + addKnnDoc(indexName, "4", fieldName, vector4); + float[] queryVector = { -10.5f, 25.48f }; + int k = 4; + Response searchResponse = searchKNNIndex(indexName, new KNNQueryBuilder(fieldName, queryVector, k), k); + List results = parseSearchResponse(EntityUtils.toString(searchResponse.getEntity()), fieldName); + assertEquals(k, results.size()); for (int i = 0; i < k; i++) { - assertEquals(numDocs - i - 1, Integer.parseInt(results.get(i).getDocId())); + assertEquals(k - i, Integer.parseInt(results.get(i).getDocId())); + } + + deleteKNNIndex(indexName); + deleteKNNIndex(trainingIndexName); + deleteModel(modelId); + validateGraphEviction(); + } + + @SneakyThrows + public void testEndToEnd_whenMethodIsHNSWPQAndHyperParametersNotSet_thenSucceed() { + String indexName = "test-index"; + String fieldName = "test-field"; + String trainingIndexName = "training-index"; + String trainingFieldName = "training-field"; + + String modelId = "test-model"; + String modelDescription = "test model"; + + List mValues = ImmutableList.of(16, 32, 64, 128); + List pqMValues = ImmutableList.of(2, 4, 8); + + // training data needs to be at least equal to the number of centroids for PQ + // which is 2^8 = 256. 8 because thats the only valid code_size for HNSWPQ + int trainingDataCount = 256; + + SpaceType spaceType = SpaceType.L2; + + Integer dimension = testData.indexData.vectors[0].length; + + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, FAISS_NAME) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_M, mValues.get(random().nextInt(mValues.size()))) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_PQ) + .startObject(PARAMETERS) + .field(ENCODER_PARAMETER_PQ_M, pqMValues.get(random().nextInt(pqMValues.size()))) + .endObject() + .endObject() + .endObject() + .endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + + createBasicKnnIndex(trainingIndexName, trainingFieldName, dimension); + ingestDataAndTrainModel(modelId, trainingIndexName, trainingFieldName, dimension, modelDescription, in, trainingDataCount); + assertTrainingSucceeds(modelId, 360, 1000); + + // Create an index + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field("model_id", modelId) + .endObject() + .endObject() + .endObject(); + + Map mappingMap = xContentBuilderToMap(builder); + String mapping = builder.toString(); + + createKnnIndex(indexName, mapping); + assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(indexName))); + + // Index the test data + for (int i = 0; i < testData.indexData.docs.length; i++) { + addKnnDoc( + indexName, + Integer.toString(testData.indexData.docs[i]), + fieldName, + Floats.asList(testData.indexData.vectors[i]).toArray() + ); + } + + // Assert we have the right number of documents in the index + refreshAllNonSystemIndices(); + assertEquals(testData.indexData.docs.length, getDocCount(indexName)); + + int k = 10; + for (int i = 0; i < testData.queries.length; i++) { + Response response = searchKNNIndex(indexName, new KNNQueryBuilder(fieldName, testData.queries[i], k), k); + String responseBody = EntityUtils.toString(response.getEntity()); + List knnResults = parseSearchResponse(responseBody, fieldName); + assertEquals(k, knnResults.size()); + + List actualScores = parseSearchResponseScore(responseBody, fieldName); + for (int j = 0; j < k; j++) { + float[] primitiveArray = knnResults.get(j).getVector(); + assertEquals( + KNNEngine.FAISS.score(KNNScoringUtil.l2Squared(testData.queries[i], primitiveArray), spaceType), + actualScores.get(j), + 0.0001 + ); + } + } + + // Delete index + deleteKNNIndex(indexName); + deleteModel(modelId); + + // Search every 5 seconds 14 times to confirm graph gets evicted + int intervals = 14; + for (int i = 0; i < intervals; i++) { + if (getTotalGraphsInCache() == 0) { + return; + } + + Thread.sleep(5 * 1000); + } + + fail("Graphs are not getting evicted"); + } + + /** + * This test confirms that sharing index state for IVFPQ-l2 indices functions properly. The main functionality that + * needs to be confirmed is that once an index gets deleted, it will not cause a failure for the non-deleted index. + * + * The workflow will be: + * 1. Create a model + * 2. Create two indices index from the model + * 3. Load the native index files from the first index + * 4. Assert search works + * 5. Load the native index files (which will reuse the shared state from the initial index) + * 6. Assert search works on the second index + * 7. Delete the first index and wait + * 8. Assert search works on the second index + */ + @SneakyThrows + public void testSharedIndexState_whenOneIndexDeleted_thenSecondIndexIsStillSearchable() { + String firstIndexName = "test-index-1"; + String secondIndexName = "test-index-2"; + String trainingIndexName = "training-index"; + + String modelId = "test-model"; + String modelDescription = "ivfpql2 model for testing shared state"; + + int dimension = testData.indexData.vectors[0].length; + SpaceType spaceType = SpaceType.L2; + int ivfNlist = 4; + int ivfNprobes = 4; + int pqCodeSize = 8; + int pqM = 1; + int docCount = 100; + + // training data needs to be at least equal to the number of centroids for PQ + // which is 2^8 = 256. 8 because thats the only valid code_size for HNSWPQ + int trainingDataCount = 256; + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_IVF) + .field(KNN_ENGINE, FAISS_NAME) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_NPROBES, ivfNprobes) + .field(METHOD_PARAMETER_NLIST, ivfNlist) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_PQ) + .startObject(PARAMETERS) + .field(ENCODER_PARAMETER_PQ_M, pqM) + .field(ENCODER_PARAMETER_PQ_CODE_SIZE, pqCodeSize) + .endObject() + .endObject() + .endObject() + .endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + createBasicKnnIndex(trainingIndexName, FIELD_NAME, dimension); + ingestDataAndTrainModel(modelId, trainingIndexName, FIELD_NAME, dimension, modelDescription, in, trainingDataCount); + assertTrainingSucceeds(modelId, 360, 1000); + + createIndexFromModelAndIngestDocuments(firstIndexName, modelId, docCount); + createIndexFromModelAndIngestDocuments(secondIndexName, modelId, docCount); + + doKnnWarmup(List.of(firstIndexName)); + validateSearchWorkflow(firstIndexName, testData.queries, 10); + doKnnWarmup(List.of(secondIndexName)); + validateSearchWorkflow(secondIndexName, testData.queries, 10); + deleteKNNIndex(firstIndexName); + // wait for all index files to be cleaned up from original index. empirically determined to take 25 seconds. + // will give 15 second buffer from that + Thread.sleep(1000 * 45); + validateSearchWorkflow(secondIndexName, testData.queries, 10); + deleteKNNIndex(secondIndexName); + deleteModel(modelId); + } + + @SneakyThrows + private void createIndexFromModelAndIngestDocuments(String indexName, String modelId, int docCount) { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("model_id", modelId) + .endObject() + .endObject() + .endObject(); + + Map mappingMap = xContentBuilderToMap(builder); + String mapping = builder.toString(); + createKnnIndex(indexName, mapping); + assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(indexName))); + + for (int i = 0; i < Math.min(testData.indexData.docs.length, docCount); i++) { + addKnnDoc( + indexName, + Integer.toString(testData.indexData.docs[i]), + FIELD_NAME, + Floats.asList(testData.indexData.vectors[i]).toArray() + ); + } + refreshAllNonSystemIndices(); + assertEquals(Math.min(testData.indexData.docs.length, docCount), getDocCount(indexName)); + } + + @SneakyThrows + private void validateSearchWorkflow(String indexName, float[][] queries, int k) { + for (float[] query : queries) { + Response response = searchKNNIndex(indexName, new KNNQueryBuilder(FIELD_NAME, query, k), k); + String responseBody = EntityUtils.toString(response.getEntity()); + List knnResults = parseSearchResponse(responseBody, FIELD_NAME); + assertEquals(k, knnResults.size()); + } + } + + public void testDocUpdate() throws IOException { + String indexName = "test-index-1"; + String fieldName = "test-field-1"; + Integer dimension = 2; + SpaceType spaceType = SpaceType.L2; + + // Create an index + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNN_ENGINE, KNNEngine.FAISS.getName()) + .endObject() + .endObject() + .endObject() + .endObject(); + + String mapping = builder.toString(); + createKnnIndex(indexName, mapping); + + Float[] vector = { 6.0f, 6.0f }; + addKnnDoc(INDEX_NAME, "1", FIELD_NAME, vector); + + // update + Float[] updatedVector = { 8.0f, 8.0f }; + updateKnnDoc(INDEX_NAME, "1", FIELD_NAME, updatedVector); + + } + + public void testDocDeletion() throws IOException { + String indexName = "test-index-1"; + String fieldName = "test-field-1"; + Integer dimension = 2; + SpaceType spaceType = SpaceType.L2; + + // Create an index + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNN_ENGINE, KNNEngine.FAISS.getName()) + .endObject() + .endObject() + .endObject() + .endObject(); + + String mapping = builder.toString(); + createKnnIndex(indexName, mapping); + + Float[] vector = { 6.0f, 6.0f }; + addKnnDoc(INDEX_NAME, "1", FIELD_NAME, vector); + + // delete knn doc + deleteKnnDoc(INDEX_NAME, "1"); + } + + public void testKNNQuery_withModelDifferentCombination_thenSuccess() throws Exception { + String modelId = "test-model"; + int dimension = 128; + + String trainingIndexName = "train-index"; + String trainingFieldName = "train-field"; + + // Add training data + createBasicKnnIndex(trainingIndexName, trainingFieldName, dimension); + int trainingDataCount = 200; + bulkIngestRandomVectors(trainingIndexName, trainingFieldName, trainingDataCount, dimension); + + // Call train API - IVF with nlists = 1 is brute force, but will require training + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, "ivf") + .field(KNN_ENGINE, "faiss") + .field(METHOD_PARAMETER_SPACE_TYPE, "l2") + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_NLIST, 1) + .endObject() + .endObject(); + Map method = xContentBuilderToMap(builder); + + trainModel(modelId, trainingIndexName, trainingFieldName, dimension, method, "faiss test description"); + + // Make sure training succeeds after 30 seconds + assertTrainingSucceeds(modelId, 30, 1000); + + // Create knn index from model + String fieldName = "test-field-name"; + String indexName = "test-index-name"; + String indexMapping = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field(MODEL_ID, modelId) + .endObject() + .endObject() + .endObject() + .toString(); + + createKnnIndex(indexName, getKNNDefaultIndexSettings(), indexMapping); + + // Index some documents + int numDocs = 100; + for (int i = 0; i < numDocs; i++) { + float[] indexVector = new float[dimension]; + Arrays.fill(indexVector, (float) i); + addKnnDocWithAttributes(indexName, Integer.toString(i), fieldName, indexVector, ImmutableMap.of("rating", String.valueOf(i))); + } + + // Run search and ensure that the values returned are expected + float[] queryVector = new float[dimension]; + Arrays.fill(queryVector, (float) numDocs); + int k = 10; + + Response searchResponse = searchKNNIndex(indexName, new KNNQueryBuilder(fieldName, queryVector, k), k); + List results = parseSearchResponse(EntityUtils.toString(searchResponse.getEntity()), fieldName); + + for (int i = 0; i < k; i++) { + assertEquals(numDocs - i - 1, Integer.parseInt(results.get(i).getDocId())); + } + + // doing exact search with filters + Response exactSearchFilteredResponse = searchKNNIndex( + indexName, + new KNNQueryBuilder(fieldName, queryVector, k, QueryBuilders.rangeQuery("rating").gte("90").lte("99")), + k + ); + List exactSearchFilteredResults = parseSearchResponse( + EntityUtils.toString(exactSearchFilteredResponse.getEntity()), + fieldName + ); + for (int i = 0; i < k; i++) { + assertEquals(numDocs - i - 1, Integer.parseInt(exactSearchFilteredResults.get(i).getDocId())); + } + + // doing exact search with filters + Response aNNSearchFilteredResponse = searchKNNIndex( + indexName, + new KNNQueryBuilder(fieldName, queryVector, k, QueryBuilders.rangeQuery("rating").gte("80").lte("99")), + k + ); + List aNNSearchFilteredResults = parseSearchResponse( + EntityUtils.toString(aNNSearchFilteredResponse.getEntity()), + fieldName + ); + for (int i = 0; i < k; i++) { + assertEquals(numDocs - i - 1, Integer.parseInt(aNNSearchFilteredResults.get(i).getDocId())); + } + } + + @SneakyThrows + public void testQueryWithFilter_withDifferentCombination_thenSuccess() { + setupKNNIndexForFilterQuery(); + final float[] searchVector = { 6.0f, 6.0f, 4.1f }; + // K > filteredResults + int kGreaterThanFilterResult = 5; + List expectedDocIds = Arrays.asList(DOC_ID_1, DOC_ID_3); + final Response response = searchKNNIndex( + INDEX_NAME, + new KNNQueryBuilder(FIELD_NAME, searchVector, kGreaterThanFilterResult, QueryBuilders.termQuery(COLOR_FIELD_NAME, "red")), + kGreaterThanFilterResult + ); + final String responseBody = EntityUtils.toString(response.getEntity()); + final List knnResults = parseSearchResponse(responseBody, FIELD_NAME); + + assertEquals(expectedDocIds.size(), knnResults.size()); + assertTrue(knnResults.stream().map(KNNResult::getDocId).collect(Collectors.toList()).containsAll(expectedDocIds)); + + // K Limits Filter results + int kLimitsFilterResult = 1; + List expectedDocIdsKLimitsFilterResult = List.of(DOC_ID_1); + final Response responseKLimitsFilterResult = searchKNNIndex( + INDEX_NAME, + new KNNQueryBuilder(FIELD_NAME, searchVector, kLimitsFilterResult, QueryBuilders.termQuery(COLOR_FIELD_NAME, "red")), + kLimitsFilterResult + ); + final String responseBodyKLimitsFilterResult = EntityUtils.toString(responseKLimitsFilterResult.getEntity()); + final List knnResultsKLimitsFilterResult = parseSearchResponse(responseBodyKLimitsFilterResult, FIELD_NAME); + + assertEquals(expectedDocIdsKLimitsFilterResult.size(), knnResultsKLimitsFilterResult.size()); + assertTrue( + knnResultsKLimitsFilterResult.stream() + .map(KNNResult::getDocId) + .collect(Collectors.toList()) + .containsAll(expectedDocIdsKLimitsFilterResult) + ); + + // Empty filter docIds + int k = 10; + final Response emptyFilterResponse = searchKNNIndex( + INDEX_NAME, + new KNNQueryBuilder( + FIELD_NAME, + searchVector, + kLimitsFilterResult, + QueryBuilders.termQuery(COLOR_FIELD_NAME, "color_not_present") + ), + k + ); + final String responseBodyForEmptyDocIds = EntityUtils.toString(emptyFilterResponse.getEntity()); + final List emptyKNNFilteredResultsFromResponse = parseSearchResponse(responseBodyForEmptyDocIds, FIELD_NAME); + + assertEquals(0, emptyKNNFilteredResultsFromResponse.size()); + } + + @SneakyThrows + public void testFiltering_whenUsingFaissExactSearchWithIP_thenMatchExpectedScore() { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", 2) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.INNER_PRODUCT.getValue()) + .field(KNN_ENGINE, KNNEngine.FAISS.getName()) + .endObject() + .endObject() + .endObject() + .endObject(); + final String mapping = builder.toString(); + createKnnIndex(INDEX_NAME, mapping); + + final List dataVectors = Arrays.asList(new Float[] { -2.0f, 2.0f }, new Float[] { 2.0f, -2.0f }); + final List ids = Arrays.asList(DOC_ID_1, DOC_ID_2); + + // Ingest all of the documents + for (int i = 0; i < dataVectors.size(); i++) { + addKnnDoc(INDEX_NAME, ids.get(i), FIELD_NAME, dataVectors.get(i)); + } + refreshIndex(INDEX_NAME); + + // Execute the search request with a match all query to ensure exact logic gets called + updateIndexSettings(INDEX_NAME, Settings.builder().put(KNNSettings.ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD, 1000)); + float[] queryVector = new float[] { -2.0f, 2.0f }; + int k = 2; + final Response response = searchKNNIndex( + INDEX_NAME, + new KNNQueryBuilder(FIELD_NAME, queryVector, k, QueryBuilders.matchAllQuery()), + k + ); + final String responseBody = EntityUtils.toString(response.getEntity()); + final List knnResults = parseSearchResponseScore(responseBody, FIELD_NAME); + + // Check that the expected scores are returned + final List expectedScores = Arrays.asList( + KNNEngine.FAISS.score(8.0f, SpaceType.INNER_PRODUCT), + KNNEngine.FAISS.score(-8.0f, SpaceType.INNER_PRODUCT) + ); + assertEquals(expectedScores.size(), knnResults.size()); + for (int i = 0; i < expectedScores.size(); i++) { + assertEquals(expectedScores.get(i), knnResults.get(i), 0.0000001); + } + } + + @SneakyThrows + public void testHNSW_InvalidPQM_thenFail() { + String trainingIndexName = "training-index"; + String trainingFieldName = "training-field"; + + String modelId = "test-model"; + String modelDescription = "test model"; + + List mValues = ImmutableList.of(16, 32, 64, 128); + int invalidPQM = 3; + + // training data needs to be at least equal to the number of centroids for PQ + // which is 2^8 = 256. 8 because thats the only valid code_size for HNSWPQ + int trainingDataCount = 256; + + SpaceType spaceType = SpaceType.L2; + + Integer dimension = testData.indexData.vectors[0].length; + + /* + * Builds the below json: + * { + * "name": "hnsw", + * "engine": "faiss", + * "space_type": "l2", + * "parameters": { + * "encoder": { + * "name": "pq", + * "parameters": { + * "m": 3 + * } + * } + * } + * } + */ + + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, FAISS_NAME) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .startObject(PARAMETERS) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_PQ) + .startObject(PARAMETERS) + .field(ENCODER_PARAMETER_PQ_M, invalidPQM) + .endObject() + .endObject() + .endObject() + .endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + + createBasicKnnIndex(trainingIndexName, trainingFieldName, dimension); + ResponseException re = expectThrows( + ResponseException.class, + () -> ingestDataAndTrainModel(modelId, trainingIndexName, trainingFieldName, dimension, modelDescription, in, trainingDataCount) + ); + assertTrue( + re.getMessage().contains("Validation Failed: 1: parameter validation failed for MethodComponentContext parameter [encoder].;") + ); + } + + @SneakyThrows + public void testIVF_InvalidPQM_thenFail() { + String trainingIndexName = "training-index"; + String trainingFieldName = "training-field"; + + String modelId = "test-model"; + String modelDescription = "test model"; + + List mValues = ImmutableList.of(16, 32, 64, 128); + int invalidPQM = 3; + + // training data needs to be at least equal to the number of centroids for PQ + // which is 2^8 = 256. + int trainingDataCount = 256; + + int dimension = testData.indexData.vectors[0].length; + SpaceType spaceType = SpaceType.L2; + int ivfNlist = 4; + int ivfNprobes = 4; + int pqCodeSize = 8; + + /* + * Builds the below json: + * { + * "name": "ivf", + * "engine": "faiss", + * "space_type": "l2", + * "parameters": { + * "nprobes": 8, + * "nlist": 4, + * "encoder": { + * "name": "pq", + * "parameters": { + * "m": 3, + * "code_size": 8 + * } + * } + * } + * } + */ + + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_IVF) + .field(KNN_ENGINE, FAISS_NAME) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_NPROBES, ivfNprobes) + .field(METHOD_PARAMETER_NLIST, ivfNlist) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_PQ) + .startObject(PARAMETERS) + .field(ENCODER_PARAMETER_PQ_M, invalidPQM) + .field(ENCODER_PARAMETER_PQ_CODE_SIZE, pqCodeSize) + .endObject() + .endObject() + .endObject() + .endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + + createBasicKnnIndex(trainingIndexName, trainingFieldName, dimension); + ResponseException re = expectThrows( + ResponseException.class, + () -> ingestDataAndTrainModel(modelId, trainingIndexName, trainingFieldName, dimension, modelDescription, in, trainingDataCount) + ); + assertTrue( + re.getMessage(), + re.getMessage().contains("Validation Failed: 1: parameter validation failed for Integer parameter [m].;") + ); + } + + @SneakyThrows + public void testIVF_whenBinaryFormat_whenIVF_thenSuccess() { + String modelId = "test-model-ivf-binary"; + int dimension = 8; + + String trainingIndexName = "train-index-ivf-binary"; + String trainingFieldName = "train-field-ivf-binary"; + + String trainIndexMapping = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(trainingFieldName) + .field("type", "knn_vector") + .field("dimension", dimension) + .field("data_type", VectorDataType.BINARY.getValue()) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.HAMMING.getValue()) + .field(KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_M, 24) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, 128) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .toString(); + + createKnnIndex(trainingIndexName, trainIndexMapping); + + int trainingDataCount = 40; + bulkIngestRandomBinaryVectors(trainingIndexName, trainingFieldName, trainingDataCount, dimension); + + XContentBuilder trainModelXContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TRAIN_INDEX_PARAMETER, trainingIndexName) + .field(TRAIN_FIELD_PARAMETER, trainingFieldName) + .field(DIMENSION, dimension) + .field(MODEL_DESCRIPTION, "My model description") + .field(VECTOR_DATA_TYPE_FIELD, VectorDataType.BINARY.getValue()) + .field( + KNN_METHOD, + Map.of( + NAME, + METHOD_IVF, + KNN_ENGINE, + FAISS_NAME, + METHOD_PARAMETER_SPACE_TYPE, + SpaceType.HAMMING.getValue(), + PARAMETERS, + Map.of(METHOD_PARAMETER_NLIST, 1, METHOD_PARAMETER_NPROBES, 1) + ) + ) + .endObject(); + + trainModel(modelId, trainModelXContentBuilder); + + // Make sure training succeeds after 30 seconds + assertTrainingSucceeds(modelId, 30, 1000); + + // Create knn index from model + String fieldName = "test-field-name-ivf-binary"; + String indexName = "test-index-name-ivf-binary"; + String indexMapping = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field(MODEL_ID, modelId) + .endObject() + .endObject() + .endObject() + .toString(); + + createKnnIndex(indexName, getKNNDefaultIndexSettings(), indexMapping); + Integer[] vector1 = { 11 }; + Integer[] vector2 = { 22 }; + Integer[] vector3 = { 33 }; + Integer[] vector4 = { 44 }; + addKnnDoc(indexName, "1", fieldName, vector1); + addKnnDoc(indexName, "2", fieldName, vector2); + addKnnDoc(indexName, "3", fieldName, vector3); + addKnnDoc(indexName, "4", fieldName, vector4); + + Integer[] queryVector = { 15 }; + int k = 2; + + XContentBuilder queryBuilder = XContentFactory.jsonBuilder() + .startObject() + .startObject("query") + .startObject("knn") + .startObject(fieldName) + .field("vector", queryVector) + .field("k", k) + .endObject() + .endObject() + .endObject() + .endObject(); + Response searchResponse = searchKNNIndex(indexName, queryBuilder, k); + List results = parseSearchResponse(EntityUtils.toString(searchResponse.getEntity()), fieldName); + assertEquals(k, results.size()); + + deleteKNNIndex(indexName); + Thread.sleep(45 * 1000); + deleteModel(modelId); + deleteKNNIndex(trainingIndexName); + validateGraphEviction(); + } + + @SneakyThrows + public void testEndToEnd_whenDoRadiusSearch_whenNoGraphFileIsCreated_whenDistanceThreshold_thenSucceed() { + final SpaceType spaceType = SpaceType.L2; + + final List mValues = ImmutableList.of(16, 32, 64, 128); + final List efConstructionValues = ImmutableList.of(16, 32, 64, 128); + final List efSearchValues = ImmutableList.of(16, 32, 64, 128); + + final Integer dimension = testData.indexData.vectors[0].length; + final Settings knnIndexSettings = buildKNNIndexSettings(NEVER_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD); + + // Create an index + final XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_M, mValues.get(random().nextInt(mValues.size()))) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, efConstructionValues.get(random().nextInt(efConstructionValues.size()))) + .field(KNNConstants.METHOD_PARAMETER_EF_SEARCH, efSearchValues.get(random().nextInt(efSearchValues.size()))) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + createKnnIndex(INDEX_NAME, knnIndexSettings, builder.toString()); + + // Index the test data + for (int i = 0; i < testData.indexData.docs.length; i++) { + addKnnDoc( + INDEX_NAME, + Integer.toString(testData.indexData.docs[i]), + FIELD_NAME, + Floats.asList(testData.indexData.vectors[i]).toArray() + ); + } + + // Assert we have the right number of documents + refreshAllNonSystemIndices(); + assertEquals(testData.indexData.docs.length, getDocCount(INDEX_NAME)); + + final float distance = 300000000000f; + final List> resultsFromDistance = validateRadiusSearchResults( + INDEX_NAME, + FIELD_NAME, + testData.queries, + distance, + null, + spaceType, + null, + null + ); + assertFalse(resultsFromDistance.isEmpty()); + resultsFromDistance.forEach(result -> { assertFalse(result.isEmpty()); }); + final float score = spaceType.scoreTranslation(distance); + final List> resultsFromScore = validateRadiusSearchResults( + INDEX_NAME, + FIELD_NAME, + testData.queries, + null, + score, + spaceType, + null, + null + ); + assertFalse(resultsFromScore.isEmpty()); + resultsFromScore.forEach(result -> { assertFalse(result.isEmpty()); }); + + // Delete index + deleteKNNIndex(INDEX_NAME); + } + + @SneakyThrows + public void testRadialQueryWithFilter_whenNoGraphIsCreated_thenSuccess() { + setupKNNIndexForFilterQuery(buildKNNIndexSettings(NEVER_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD)); + + final float[][] searchVector = new float[][] { { 3.3f, 3.0f, 5.0f } }; + TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("color", "red"); + List expectedDocIds = Arrays.asList(DOC_ID_3); + + float distance = 15f; + List> queryResult = validateRadiusSearchResults( + INDEX_NAME, + FIELD_NAME, + searchVector, + distance, + null, + SpaceType.L2, + termQueryBuilder, + null + ); + + assertEquals(1, queryResult.get(0).size()); + assertEquals(expectedDocIds.get(0), queryResult.get(0).get(0).getDocId()); + + // Delete index + deleteKNNIndex(INDEX_NAME); + } + + @SneakyThrows + public void testQueryWithFilter_whenNonExistingFieldUsedInFilter_thenSuccessful() { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES_FIELD_NAME) + .startObject(FIELD_NAME) + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, VECTOR_DIMENSION) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, METHOD_HNSW) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2.getValue()) + .field(KNNConstants.KNN_ENGINE, KNNEngine.LUCENE.getName()) + .endObject() + .endObject() + .startObject(INTEGER_FIELD_NAME) + .field(TYPE_FIELD_NAME, FILED_TYPE_INTEGER) + .endObject() + .endObject() + .endObject(); + Map mappingMap = xContentBuilderToMap(builder); + String mapping = builder.toString(); + + createKnnIndex(INDEX_NAME, mapping); + + Float[] vector = new Float[] { 2.0f, 4.5f, 6.5f }; + + String documentAsString = XContentFactory.jsonBuilder() + .startObject() + .field(INTEGER_FIELD_NAME, 5) + .field(FIELD_NAME, vector) + .endObject() + .toString(); + + addKnnDoc(INDEX_NAME, DOC_ID_1, documentAsString); + + refreshIndex(INDEX_NAME); + assertEquals(1, getDocCount(INDEX_NAME)); + + float[] searchVector = new float[] { 1.0f, 2.1f, 3.9f }; + int k = 10; + + // use filter where nonexistent field is must, we should have no results + QueryBuilder filterWithRequiredNonExistentField = QueryBuilders.boolQuery() + .must(QueryBuilders.rangeQuery(NON_EXISTENT_INTEGER_FIELD_NAME).gte(1)); + Response searchWithRequiredNonExistentFiledInFilterResponse = searchKNNIndex( + INDEX_NAME, + new KNNQueryBuilder(FIELD_NAME, searchVector, k, filterWithRequiredNonExistentField), + k + ); + List resultsQuery1 = parseSearchResponse( + EntityUtils.toString(searchWithRequiredNonExistentFiledInFilterResponse.getEntity()), + FIELD_NAME + ); + assertTrue(resultsQuery1.isEmpty()); + + // use filter with non existent field as optional, we should have some results + QueryBuilder filterWithOptionalNonExistentField = QueryBuilders.boolQuery() + .should(QueryBuilders.rangeQuery(NON_EXISTENT_INTEGER_FIELD_NAME).gte(1)) + .must(QueryBuilders.rangeQuery(INTEGER_FIELD_NAME).gte(1)); + Response searchWithOptionalNonExistentFiledInFilterResponse = searchKNNIndex( + INDEX_NAME, + new KNNQueryBuilder(FIELD_NAME, searchVector, k, filterWithOptionalNonExistentField), + k + ); + List resultsQuery2 = parseSearchResponse( + EntityUtils.toString(searchWithOptionalNonExistentFiledInFilterResponse.getEntity()), + FIELD_NAME + ); + assertEquals(1, resultsQuery2.size()); + } + + protected void setupKNNIndexForFilterQuery() throws Exception { + setupKNNIndexForFilterQuery(getKNNDefaultIndexSettings()); + } + + protected void setupKNNIndexForFilterQuery(Settings settings) throws Exception { + // Create Mappings + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", 3) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2) + .field(KNN_ENGINE, KNNEngine.FAISS.getName()) + .endObject() + .endObject() + .endObject() + .endObject(); + final String mapping = builder.toString(); + + createKnnIndex(INDEX_NAME, settings, mapping); + + addKnnDocWithAttributes( + DOC_ID_1, + new float[] { 6.0f, 7.9f, 3.1f }, + ImmutableMap.of(COLOR_FIELD_NAME, "red", TASTE_FIELD_NAME, "sweet") + ); + addKnnDocWithAttributes(DOC_ID_2, new float[] { 3.2f, 2.1f, 4.8f }, ImmutableMap.of(COLOR_FIELD_NAME, "green")); + addKnnDocWithAttributes(DOC_ID_3, new float[] { 4.1f, 5.0f, 7.1f }, ImmutableMap.of(COLOR_FIELD_NAME, "red")); + + refreshIndex(INDEX_NAME); + } + + @SneakyThrows + private void queryTestData(final String indexName, final String fieldName, final int dimension, final int numDocs) { + queryTestData(indexName, fieldName, dimension, numDocs, null); + } + + private void queryTestData( + final String indexName, + final String fieldName, + final int dimension, + final int numDocs, + Map methodParams + ) throws IOException, ParseException { + float[] queryVector = new float[dimension]; + Arrays.fill(queryVector, (float) numDocs); + int k = 10; + + Response searchResponse = searchKNNIndex(indexName, buildSearchQuery(fieldName, k, queryVector, methodParams), k); + List results = parseSearchResponse(EntityUtils.toString(searchResponse.getEntity()), fieldName); + assertEquals(k, results.size()); + for (int i = 0; i < k; i++) { + assertEquals(numDocs - i - 1, Integer.parseInt(results.get(i).getDocId())); + } + } + + private void indexTestData(final String indexName, final String fieldName, final int dimension, final int numDocs) throws Exception { + for (int i = 0; i < numDocs; i++) { + float[] indexVector = new float[dimension]; + Arrays.fill(indexVector, (float) i); + addKnnDocWithAttributes(indexName, Integer.toString(i), fieldName, indexVector, ImmutableMap.of("rating", String.valueOf(i))); + } + + // Assert that all docs are ingested + refreshAllNonSystemIndices(); + assertEquals(numDocs, getDocCount(indexName)); + } + + private void validateGraphEviction() throws Exception { + // Search every 5 seconds 14 times to confirm graph gets evicted + int intervals = 14; + for (int i = 0; i < intervals; i++) { + if (getTotalGraphsInCache() == 0) { + return; + } + + Thread.sleep(5 * 1000); + } + + fail("Graphs are not getting evicted"); + } + + private List> validateRadiusSearchResults( + String indexName, + String fieldName, + float[][] queryVectors, + Float distanceThreshold, + Float scoreThreshold, + final SpaceType spaceType, + TermQueryBuilder filterQuery, + Map methodParameters + ) throws IOException, ParseException { + List> queryResults = new ArrayList<>(); + for (float[] queryVector : queryVectors) { + XContentBuilder queryBuilder = XContentFactory.jsonBuilder().startObject().startObject("query"); + queryBuilder.startObject("knn"); + queryBuilder.startObject(fieldName); + queryBuilder.field("vector", queryVector); + if (distanceThreshold != null) { + queryBuilder.field(MAX_DISTANCE, distanceThreshold); + } else if (scoreThreshold != null) { + queryBuilder.field(MIN_SCORE, scoreThreshold); + } else { + throw new IllegalArgumentException("Invalid threshold"); + } + if (filterQuery != null) { + queryBuilder.field("filter", filterQuery); + } + if (methodParameters != null) { + queryBuilder.startObject(METHOD_PARAMETER); + for (Map.Entry entry : methodParameters.entrySet()) { + queryBuilder.field(entry.getKey(), entry.getValue()); + } + queryBuilder.endObject(); + } + queryBuilder.endObject(); + queryBuilder.endObject(); + queryBuilder.endObject().endObject(); + final String responseBody = EntityUtils.toString(searchKNNIndex(indexName, queryBuilder, 10).getEntity()); + + List knnResults = parseSearchResponse(responseBody, fieldName); + + for (KNNResult knnResult : knnResults) { + float[] vector = knnResult.getVector(); + float distance = TestUtils.computeDistFromSpaceType(spaceType, vector, queryVector); + if (spaceType == SpaceType.L2) { + assertTrue(KNNScoringUtil.l2Squared(queryVector, vector) <= distance); + } else if (spaceType == SpaceType.INNER_PRODUCT) { + assertTrue(KNNScoringUtil.innerProduct(queryVector, vector) >= distance); + } else { + throw new IllegalArgumentException("Invalid space type"); + } + } + queryResults.add(knnResults); } + return queryResults; } } diff --git a/src/test/java/org/opensearch/knn/index/IndexUtilTests.java b/src/test/java/org/opensearch/knn/index/IndexUtilTests.java deleted file mode 100644 index 7013ef261..000000000 --- a/src/test/java/org/opensearch/knn/index/IndexUtilTests.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.knn.index; - -import com.google.common.collect.ImmutableMap; -import org.opensearch.cluster.ClusterState; -import org.opensearch.cluster.metadata.IndexMetadata; -import org.opensearch.cluster.metadata.Metadata; -import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.settings.Settings; -import org.opensearch.knn.KNNTestCase; -import org.opensearch.knn.index.util.KNNEngine; - -import java.util.Map; - -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.opensearch.knn.common.KNNConstants.HNSW_ALGO_EF_SEARCH; -import static org.opensearch.knn.common.KNNConstants.SPACE_TYPE; -import static org.opensearch.knn.index.IndexUtil.getParametersAtLoading; -import static org.opensearch.knn.index.KNNSettings.KNN_ALGO_PARAM_EF_SEARCH; - -public class IndexUtilTests extends KNNTestCase { - public void testGetLoadParameters() { - // Test faiss to ensure that space type gets set properly - SpaceType spaceType1 = SpaceType.COSINESIMIL; - KNNEngine knnEngine1 = KNNEngine.FAISS; - String indexName = "my-test-index"; - - Map loadParameters = getParametersAtLoading(spaceType1, knnEngine1, indexName); - assertEquals(1, loadParameters.size()); - assertEquals(spaceType1.getValue(), loadParameters.get(SPACE_TYPE)); - - // Test nmslib to ensure both space type and ef search are properly set - SpaceType spaceType2 = SpaceType.L1; - KNNEngine knnEngine2 = KNNEngine.NMSLIB; - int efSearchValue = 413; - - // We use the constant for the setting here as opposed to the identifier of efSearch in nmslib jni - Map indexSettings = ImmutableMap.of(KNN_ALGO_PARAM_EF_SEARCH, efSearchValue); - - // Because ef search comes from an index setting, we need to mock the long line of calls to get those - // index settings - Settings settings = Settings.builder().loadFromMap(indexSettings).build(); - IndexMetadata indexMetadata = mock(IndexMetadata.class); - when(indexMetadata.getSettings()).thenReturn(settings); - Metadata metadata = mock(Metadata.class); - when(metadata.index(anyString())).thenReturn(indexMetadata); - ClusterState clusterState = mock(ClusterState.class); - when(clusterState.getMetadata()).thenReturn(metadata); - ClusterService clusterService = mock(ClusterService.class); - when(clusterService.state()).thenReturn(clusterState); - KNNSettings.state().setClusterService(clusterService); - - loadParameters = getParametersAtLoading(spaceType2, knnEngine2, indexName); - assertEquals(2, loadParameters.size()); - assertEquals(spaceType2.getValue(), loadParameters.get(SPACE_TYPE)); - assertEquals(efSearchValue, loadParameters.get(HNSW_ALGO_EF_SEARCH)); - } -} diff --git a/src/test/java/org/opensearch/knn/index/KNNCircuitBreakerIT.java b/src/test/java/org/opensearch/knn/index/KNNCircuitBreakerIT.java index 86b728ab9..fe0c880e4 100644 --- a/src/test/java/org/opensearch/knn/index/KNNCircuitBreakerIT.java +++ b/src/test/java/org/opensearch/knn/index/KNNCircuitBreakerIT.java @@ -9,6 +9,7 @@ import org.apache.http.util.EntityUtils; import org.opensearch.client.Response; import org.opensearch.common.settings.Settings; +import org.opensearch.knn.index.query.KNNQueryBuilder; import java.util.Collections; import java.util.Map; @@ -19,6 +20,8 @@ * Integration tests to test Circuit Breaker functionality */ public class KNNCircuitBreakerIT extends KNNRestTestCase { + private static final Integer ALWAYS_BUILD_GRAPH = 0; + /** * To trip the circuit breaker, we will create two indices and index documents. Each index will be small enough so * that individually they fit into the cache, but together they do not. To prevent Lucene conditions where @@ -38,6 +41,7 @@ private void tripCb() throws Exception { .put("number_of_shards", 1) .put("number_of_replicas", numNodes - 1) .put("index.knn", true) + .put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, ALWAYS_BUILD_GRAPH) .build(); String indexName1 = INDEX_NAME + "1"; @@ -47,7 +51,7 @@ private void tripCb() throws Exception { createKnnIndex(indexName2, settings, createKnnIndexMapping(FIELD_NAME, 2)); Float[] vector = { 1.3f, 2.2f }; - int docsInIndex = 5; // through testing, 7 is minimum number of docs to trip circuit breaker at 1kb + int docsInIndex = 7; // through testing, 7 is minimum number of docs to trip circuit breaker at 1kb for (int i = 0; i < docsInIndex; i++) { addKnnDoc(indexName1, Integer.toString(i), FIELD_NAME, vector); diff --git a/src/test/java/org/opensearch/knn/index/KNNClusterTestUtils.java b/src/test/java/org/opensearch/knn/index/KNNClusterTestUtils.java new file mode 100644 index 000000000..6ded05d17 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/KNNClusterTestUtils.java @@ -0,0 +1,35 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index; + +import org.opensearch.Version; +import org.opensearch.cluster.ClusterState; +import org.opensearch.cluster.node.DiscoveryNodes; +import org.opensearch.cluster.service.ClusterService; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Collection of util methods required for testing and related to OpenSearch cluster setup and functionality + */ +public class KNNClusterTestUtils { + + /** + * Create new mock for ClusterService + * @param version min version for cluster nodes + * @return + */ + public static ClusterService mockClusterService(final Version version) { + ClusterService clusterService = mock(ClusterService.class); + ClusterState clusterState = mock(ClusterState.class); + when(clusterService.state()).thenReturn(clusterState); + DiscoveryNodes discoveryNodes = mock(DiscoveryNodes.class); + when(clusterState.getNodes()).thenReturn(discoveryNodes); + when(discoveryNodes.getMinNodeVersion()).thenReturn(version); + return clusterService; + } +} diff --git a/src/test/java/org/opensearch/knn/index/KNNCreateIndexFromModelTests.java b/src/test/java/org/opensearch/knn/index/KNNCreateIndexFromModelTests.java index e0b2c05cf..d6ee2d7fd 100644 --- a/src/test/java/org/opensearch/knn/index/KNNCreateIndexFromModelTests.java +++ b/src/test/java/org/opensearch/knn/index/KNNCreateIndexFromModelTests.java @@ -12,14 +12,17 @@ package org.opensearch.knn.index; import com.google.common.collect.ImmutableMap; -import org.opensearch.action.ActionListener; +import org.opensearch.Version; +import org.opensearch.core.action.ActionListener; import org.opensearch.action.admin.indices.create.CreateIndexRequestBuilder; -import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.knn.KNNSingleNodeTestCase; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; import org.opensearch.knn.jni.JNIService; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.indices.Model; import org.opensearch.knn.indices.ModelDao; import org.opensearch.knn.indices.ModelMetadata; @@ -51,7 +54,7 @@ public void testCreateIndexFromModel() throws IOException, InterruptedException ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, "Flat", SPACE_TYPE, spaceType.getValue()), dimension, vectorsPointer, - KNNEngine.FAISS.getName() + KNNEngine.FAISS ); // Setup model @@ -62,7 +65,13 @@ public void testCreateIndexFromModel() throws IOException, InterruptedException ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "test-node", + MethodComponentContext.EMPTY, + VectorDataType.FLOAT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.V_EMPTY ); Model model = new Model(modelMetadata, modelBlob, modelId); @@ -75,17 +84,16 @@ public void testCreateIndexFromModel() throws IOException, InterruptedException String indexName = "test-index"; String fieldName = "test-field"; - final String mapping = Strings.toString( - XContentFactory.jsonBuilder() - .startObject() - .startObject("properties") - .startObject(fieldName) - .field("type", "knn_vector") - .field("model_id", modelId) - .endObject() - .endObject() - .endObject() - ); + final String mapping = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field("model_id", modelId) + .endObject() + .endObject() + .endObject() + .toString(); modelDao.put(model, ActionListener.wrap(indexResponse -> { CreateIndexRequestBuilder createIndexRequestBuilder = client().admin() diff --git a/src/test/java/org/opensearch/knn/index/KNNESSettingsTestIT.java b/src/test/java/org/opensearch/knn/index/KNNESSettingsTestIT.java index 1319448c8..19a57f0ac 100644 --- a/src/test/java/org/opensearch/knn/index/KNNESSettingsTestIT.java +++ b/src/test/java/org/opensearch/knn/index/KNNESSettingsTestIT.java @@ -6,12 +6,13 @@ package org.opensearch.knn.index; import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.index.query.KNNQueryBuilder; import org.opensearch.knn.plugin.stats.StatNames; import org.apache.http.util.EntityUtils; import org.opensearch.client.Response; import org.opensearch.client.ResponseException; import org.opensearch.common.settings.Settings; -import org.opensearch.rest.RestStatus; +import org.opensearch.core.rest.RestStatus; import java.io.IOException; import java.util.Collections; @@ -20,6 +21,9 @@ import static org.hamcrest.Matchers.containsString; public class KNNESSettingsTestIT extends KNNRestTestCase { + + public static final int ALWAYS_BUILD_GRAPH = 0; + /** * KNN Index writes should be blocked when the plugin disabled * @throws Exception Exception from test @@ -71,7 +75,7 @@ public void testQueriesPluginDisabled() throws Exception { } public void testItemRemovedFromCache_expiration() throws Exception { - createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); + createKnnIndex(INDEX_NAME, buildKNNIndexSettings(ALWAYS_BUILD_GRAPH), createKnnIndexMapping(FIELD_NAME, 2)); updateClusterSettings(KNNSettings.KNN_CACHE_ITEM_EXPIRY_ENABLED, true); updateClusterSettings(KNNSettings.KNN_CACHE_ITEM_EXPIRY_TIME_MINUTES, "1m"); @@ -117,8 +121,8 @@ public void testUpdateIndexSetting() throws IOException { } @SuppressWarnings("unchecked") - public void testCacheRebuiltAfterUpdateIndexSettings() throws IOException { - createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); + public void testCacheRebuiltAfterUpdateIndexSettings() throws Exception { + createKnnIndex(INDEX_NAME, buildKNNIndexSettings(ALWAYS_BUILD_GRAPH), createKnnIndexMapping(FIELD_NAME, 2)); Float[] vector = { 6.0f, 6.0f }; addKnnDoc(INDEX_NAME, "1", FIELD_NAME, vector); diff --git a/src/test/java/org/opensearch/knn/index/KNNIndexShardTests.java b/src/test/java/org/opensearch/knn/index/KNNIndexShardTests.java index fc88a8ea6..d01c3a0b4 100644 --- a/src/test/java/org/opensearch/knn/index/KNNIndexShardTests.java +++ b/src/test/java/org/opensearch/knn/index/KNNIndexShardTests.java @@ -7,18 +7,26 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import lombok.SneakyThrows; +import org.apache.lucene.index.SegmentCommitInfo; +import org.apache.lucene.index.SegmentInfo; +import org.apache.lucene.store.Directory; +import org.apache.lucene.util.StringHelper; +import org.apache.lucene.util.Version; +import org.mockito.Mockito; +import org.opensearch.common.settings.Settings; import org.opensearch.knn.KNNSingleNodeTestCase; import org.opensearch.index.IndexService; import org.opensearch.index.engine.Engine; import org.opensearch.index.shard.IndexShard; +import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.index.memory.NativeMemoryCacheManager; import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; @@ -65,6 +73,7 @@ public void testWarmup_emptyIndex() throws IOException { public void testWarmup_shardPresentInCache() throws InterruptedException, ExecutionException, IOException { IndexService indexService = createKNNIndex(testIndexName); createKnnIndexMapping(testIndexName, testFieldName, dimensions); + updateIndexSetting(testIndexName, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0).build()); addKnnDoc(testIndexName, "1", testFieldName, new Float[] { 2.5F, 3.5F }); searchKNNIndex(testIndexName, testFieldName, new float[] { 1.0f, 2.0f }, 1); @@ -79,6 +88,7 @@ public void testWarmup_shardPresentInCache() throws InterruptedException, Execut public void testWarmup_shardNotPresentInCache() throws InterruptedException, ExecutionException, IOException { IndexService indexService = createKNNIndex(testIndexName); createKnnIndexMapping(testIndexName, testFieldName, dimensions); + updateIndexSetting(testIndexName, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0).build()); IndexShard indexShard; KNNIndexShard knnIndexShard; @@ -97,38 +107,40 @@ public void testWarmup_shardNotPresentInCache() throws InterruptedException, Exe assertEquals(2, NativeMemoryCacheManager.getInstance().getIndicesCacheStats().get(testIndexName).get(GRAPH_COUNT)); } - public void testGetHNSWPaths() throws IOException, ExecutionException, InterruptedException { + public void testGetAllEngineFileContexts() throws IOException, ExecutionException, InterruptedException { IndexService indexService = createKNNIndex(testIndexName); - createKnnIndexMapping(testIndexName, testFieldName, dimensions); - IndexShard indexShard; - KNNIndexShard knnIndexShard; - Engine.Searcher searcher; - Map hnswPaths; + createKnnIndexMapping(testIndexName, testFieldName, dimensions, KNNEngine.NMSLIB); + updateIndexSetting(testIndexName, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0).build()); - indexShard = indexService.iterator().next(); - knnIndexShard = new KNNIndexShard(indexShard); + IndexShard indexShard = indexService.iterator().next(); + KNNIndexShard knnIndexShard = new KNNIndexShard(indexShard); - searcher = indexShard.acquireSearcher("test-hnsw-paths-1"); - hnswPaths = knnIndexShard.getAllEnginePaths(searcher.getIndexReader()); - assertEquals(0, hnswPaths.size()); + Engine.Searcher searcher = indexShard.acquireSearcher("test-hnsw-paths-1"); + List engineFileContexts = knnIndexShard.getAllEngineFileContexts(searcher.getIndexReader()); + assertEquals(0, engineFileContexts.size()); searcher.close(); addKnnDoc(testIndexName, "1", testFieldName, new Float[] { 2.5F, 3.5F }); searcher = indexShard.acquireSearcher("test-hnsw-paths-2"); - hnswPaths = knnIndexShard.getAllEnginePaths(searcher.getIndexReader()); - assertEquals(1, hnswPaths.size()); - List paths = new ArrayList<>(hnswPaths.keySet()); + engineFileContexts = knnIndexShard.getAllEngineFileContexts(searcher.getIndexReader()); + assertEquals(1, engineFileContexts.size()); + List paths = engineFileContexts.stream() + .map(KNNIndexShard.EngineFileContext::getVectorFileName) + .collect(Collectors.toList()); assertTrue(paths.get(0).contains("hnsw") || paths.get(0).contains("hnswc")); searcher.close(); } - public void testGetEnginePaths() { + @SneakyThrows + public void testGetEngineFileContexts() { // Check that the correct engine paths are being returned by the KNNIndexShard String segmentName = "_0"; String fieldName = "test_field"; String fileExt = ".test"; SpaceType spaceType = SpaceType.L2; + String modelId = "test-model"; + VectorDataType vectorDataType = VectorDataType.FLOAT; Set includedFileNames = ImmutableSet.of( String.format("%s_111_%s%s", segmentName, fieldName, fileExt), @@ -146,10 +158,66 @@ public void testGetEnginePaths() { KNNIndexShard knnIndexShard = new KNNIndexShard(null); - Path path = Paths.get(""); - Map included = knnIndexShard.getEnginePaths(files, segmentName, fieldName, fileExt, path, spaceType); + final Directory dummyDirectory = Mockito.mock(Directory.class); + final SegmentInfo segmentInfo = new SegmentInfo( + dummyDirectory, + Version.LATEST, + null, + segmentName, + 0, + false, + false, + null, + Collections.emptyMap(), + new byte[StringHelper.ID_LENGTH], + Collections.emptyMap(), + null + ); + // Inject 'files' into the segment info instance. + // Since SegmentInfo class does trim out its given file list, for example removing segment name from a file name etc, + // we can't just use 'setFiles' api to assign the file list. Which will lead this unit test to be fail. + final Field setFilesPrivateField = SegmentInfo.class.getDeclaredField("setFiles"); + setFilesPrivateField.setAccessible(true); + setFilesPrivateField.set(segmentInfo, new HashSet<>(files)); + + final SegmentCommitInfo segmentCommitInfo = new SegmentCommitInfo(segmentInfo, 0, 0, -1, 0, 0, null); + List included = knnIndexShard.getEngineFileContexts( + segmentCommitInfo, + fieldName, + fileExt, + spaceType, + modelId, + vectorDataType + ); assertEquals(includedFileNames.size(), included.size()); - included.keySet().forEach(o -> assertTrue(includedFileNames.contains(o))); + included.stream().map(KNNIndexShard.EngineFileContext::getVectorFileName).forEach(o -> assertTrue(includedFileNames.contains(o))); + } + + @SneakyThrows + public void testClearCache_emptyIndex() { + IndexService indexService = createKNNIndex(testIndexName); + createKnnIndexMapping(testIndexName, testFieldName, dimensions); + + IndexShard indexShard = indexService.iterator().next(); + KNNIndexShard knnIndexShard = new KNNIndexShard(indexShard); + knnIndexShard.clearCache(); + assertNull(NativeMemoryCacheManager.getInstance().getIndicesCacheStats().get(testIndexName)); + } + + @SneakyThrows + public void testClearCache_shardPresentInCache() { + IndexService indexService = createKNNIndex(testIndexName); + createKnnIndexMapping(testIndexName, testFieldName, dimensions); + updateIndexSetting(testIndexName, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0).build()); + addKnnDoc(testIndexName, String.valueOf(randomInt()), testFieldName, new Float[] { randomFloat(), randomFloat() }); + + IndexShard indexShard = indexService.iterator().next(); + KNNIndexShard knnIndexShard = new KNNIndexShard(indexShard); + knnIndexShard.warmup(); + assertEquals(1, NativeMemoryCacheManager.getInstance().getIndicesCacheStats().get(testIndexName).get(GRAPH_COUNT)); + + knnIndexShard.clearCache(); + assertNull(NativeMemoryCacheManager.getInstance().getIndicesCacheStats().get(testIndexName)); } } diff --git a/src/test/java/org/opensearch/knn/index/KNNMapperSearcherIT.java b/src/test/java/org/opensearch/knn/index/KNNMapperSearcherIT.java index b98b081ab..62bd15721 100644 --- a/src/test/java/org/opensearch/knn/index/KNNMapperSearcherIT.java +++ b/src/test/java/org/opensearch/knn/index/KNNMapperSearcherIT.java @@ -5,19 +5,34 @@ package org.opensearch.knn.index; +import lombok.SneakyThrows; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.knn.KNNRestTestCase; import org.opensearch.knn.KNNResult; import org.apache.http.util.EntityUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.opensearch.client.Response; +import org.opensearch.knn.index.query.KNNQueryBuilder; +import org.opensearch.knn.index.engine.KNNEngine; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import static org.opensearch.knn.common.KNNConstants.DIMENSION; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.KNN_METHOD; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.NAME; +import static org.opensearch.knn.common.KNNConstants.QUERY; +import static org.opensearch.knn.common.KNNConstants.TYPE; +import static org.opensearch.knn.common.KNNConstants.TYPE_KNN_VECTOR; +import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; + public class KNNMapperSearcherIT extends KNNRestTestCase { - private static final Logger logger = LogManager.getLogger(KNNMapperSearcherIT.class); + + private static final String INDEX_NAME = "test_index"; + private static final String FIELD_NAME = "test_vector"; /** * Test Data set @@ -238,4 +253,166 @@ public void testLargeK() throws Exception { List results = parseSearchResponse(EntityUtils.toString(response.getEntity()), FIELD_NAME); assertEquals(results.size(), 4); } + + /** + * Request: + * { + * "stored_fields": ["test_vector"], + * "query": { + * "match_all": {} + * } + * } + * + * Example Response: + * { + * "took":248, + * "timed_out":false, + * "_shards":{ + * "total":1, + * "successful":1, + * "skipped":0, + * "failed":0 + * }, + * "hits":{ + * "total":{ + * "value":1, + * "relation":"eq" + * }, + * "max_score":1.0, + * "hits":[ + * { + * "_index":"test_index", + * "_id":"1", + * "_score":1.0, + * "fields":{"test_vector":[[-128,0,1,127]]} + * } + * ] + * } + * } + */ + @SneakyThrows + public void testStoredFields_whenByteDataType_thenSucceed() { + // Create index with stored field and confirm that we can properly retrieve it + int[] testVector = new int[] { -128, 0, 1, 127 }; + String expectedResponse = String.format("\"fields\":{\"%s\":[[-128,0,1,127]]}}", FIELD_NAME); + createKnnIndex( + INDEX_NAME, + createVectorMapping(testVector.length, KNNEngine.LUCENE.getName(), VectorDataType.BYTE.getValue(), true) + ); + addKnnDoc(INDEX_NAME, "1", FIELD_NAME, testVector); + + final XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.field(STORED_QUERY_FIELD, List.of(FIELD_NAME)); + builder.startObject(QUERY); + builder.startObject(MATCH_ALL_QUERY_FIELD); + builder.endObject(); + builder.endObject(); + builder.endObject(); + + String response = EntityUtils.toString(performSearch(INDEX_NAME, builder.toString()).getEntity()); + assertTrue(response.contains(expectedResponse)); + + deleteKNNIndex(INDEX_NAME); + } + + /** + * Request: + * { + * "stored_fields": ["test_vector"], + * "query": { + * "match_all": {} + * } + * } + * + * Example Response: + * { + * "took":248, + * "timed_out":false, + * "_shards":{ + * "total":1, + * "successful":1, + * "skipped":0, + * "failed":0 + * }, + * "hits":{ + * "total":{ + * "value":1, + * "relation":"eq" + * }, + * "max_score":1.0, + * "hits":[ + * { + * "_index":"test_index", + * "_id":"1", + * "_score":1.0, + * "fields":{"test_vector":[[-100.0,100.0,0.0,1.0]]} + * } + * ] + * } + * } + */ + @SneakyThrows + public void testStoredFields_whenFloatDataType_thenSucceed() { + List enginesToTest = List.of(KNNEngine.NMSLIB, KNNEngine.FAISS, KNNEngine.LUCENE); + float[] testVector = new float[] { -100.0f, 100.0f, 0f, 1f }; + String expectedResponse = String.format("\"fields\":{\"%s\":[[-100.0,100.0,0.0,1.0]]}}", FIELD_NAME); + for (KNNEngine knnEngine : enginesToTest) { + createKnnIndex(INDEX_NAME, createVectorMapping(testVector.length, knnEngine.getName(), VectorDataType.FLOAT.getValue(), true)); + addKnnDoc(INDEX_NAME, "1", FIELD_NAME, testVector); + + final XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.field(STORED_QUERY_FIELD, List.of(FIELD_NAME)); + builder.startObject(QUERY); + builder.startObject(MATCH_ALL_QUERY_FIELD); + builder.endObject(); + builder.endObject(); + builder.endObject(); + + String response = EntityUtils.toString(performSearch(INDEX_NAME, builder.toString()).getEntity()); + assertTrue(response.contains(expectedResponse)); + + deleteKNNIndex(INDEX_NAME); + } + } + + /** + * Mapping + * { + * "properties": { + * "test_vector": { + * "type": "knn_vector", + * "dimension": {dimension}, + * "data_type": "{type}", + * "stored": true + * "method": { + * "name": "hnsw", + * "engine": "{engine}" + * } + * } + * } + * } + */ + @SneakyThrows + private String createVectorMapping(final int dimension, final String engine, final String dataType, final boolean isStored) { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES_FIELD) + .startObject(FIELD_NAME) + .field(TYPE, TYPE_KNN_VECTOR) + .field(DIMENSION, dimension) + .field(VECTOR_DATA_TYPE_FIELD, dataType) + .field(STORE_FIELD, isStored) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, engine) + .endObject() + .endObject() + .endObject() + .endObject(); + + return builder.toString(); + } + } diff --git a/src/test/java/org/opensearch/knn/index/KNNMethodTests.java b/src/test/java/org/opensearch/knn/index/KNNMethodTests.java deleted file mode 100644 index 18aa90b46..000000000 --- a/src/test/java/org/opensearch/knn/index/KNNMethodTests.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.knn.index; - -import com.google.common.collect.ImmutableMap; -import org.opensearch.knn.KNNTestCase; -import org.opensearch.common.xcontent.XContentBuilder; -import org.opensearch.common.xcontent.XContentFactory; -import org.opensearch.knn.common.KNNConstants; -import org.opensearch.knn.index.util.KNNEngine; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -import static org.opensearch.knn.common.KNNConstants.NAME; -import static org.opensearch.knn.common.KNNConstants.PARAMETERS; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; - -public class KNNMethodTests extends KNNTestCase { - /** - * Test KNNMethod method component getter - */ - public void testGetMethodComponent() { - String name = "test"; - KNNMethod knnMethod = KNNMethod.Builder.builder(MethodComponent.Builder.builder(name).build()).build(); - assertEquals(name, knnMethod.getMethodComponent().getName()); - } - - /** - * Test KNNMethod has space - */ - public void testHasSpace() { - String name = "test"; - KNNMethod knnMethod = KNNMethod.Builder.builder(MethodComponent.Builder.builder(name).build()) - .addSpaces(SpaceType.L2, SpaceType.COSINESIMIL) - .build(); - assertTrue(knnMethod.containsSpace(SpaceType.L2)); - assertTrue(knnMethod.containsSpace(SpaceType.COSINESIMIL)); - assertFalse(knnMethod.containsSpace(SpaceType.INNER_PRODUCT)); - } - - /** - * Test KNNMethod validate - */ - public void testValidate() throws IOException { - String methodName = "test-method"; - KNNMethod knnMethod = KNNMethod.Builder.builder(MethodComponent.Builder.builder(methodName).build()) - .addSpaces(SpaceType.L2) - .build(); - - // Invalid space - XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() - .startObject() - .field(NAME, methodName) - .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.INNER_PRODUCT.getValue()) - .endObject(); - Map in = xContentBuilderToMap(xContentBuilder); - KNNMethodContext knnMethodContext1 = KNNMethodContext.parse(in); - assertNotNull(knnMethod.validate(knnMethodContext1)); - - // Invalid methodComponent - xContentBuilder = XContentFactory.jsonBuilder() - .startObject() - .field(NAME, methodName) - .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2.getValue()) - .startObject(PARAMETERS) - .field("invalid", "invalid") - .endObject() - .endObject(); - in = xContentBuilderToMap(xContentBuilder); - KNNMethodContext knnMethodContext2 = KNNMethodContext.parse(in); - - assertNotNull(knnMethod.validate(knnMethodContext2)); - - // Valid everything - xContentBuilder = XContentFactory.jsonBuilder() - .startObject() - .field(NAME, methodName) - .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2.getValue()) - .endObject(); - in = xContentBuilderToMap(xContentBuilder); - KNNMethodContext knnMethodContext3 = KNNMethodContext.parse(in); - assertNull(knnMethod.validate(knnMethodContext3)); - } - - public void testGetAsMap() { - SpaceType spaceType = SpaceType.DEFAULT; - String methodName = "test-method"; - Map generatedMap = ImmutableMap.of("test-key", "test-value"); - MethodComponent methodComponent = MethodComponent.Builder.builder(methodName) - .setMapGenerator(((methodComponent1, methodComponentContext) -> generatedMap)) - .build(); - - KNNMethod knnMethod = KNNMethod.Builder.builder(methodComponent).build(); - - Map expectedMap = new HashMap<>(generatedMap); - expectedMap.put(KNNConstants.SPACE_TYPE, spaceType.getValue()); - - assertEquals(expectedMap, knnMethod.getAsMap(new KNNMethodContext(KNNEngine.DEFAULT, spaceType, null))); - } - - public void testBuilder() { - String name = "test"; - KNNMethod.Builder builder = KNNMethod.Builder.builder(MethodComponent.Builder.builder(name).build()); - KNNMethod knnMethod = builder.build(); - - assertEquals(name, knnMethod.getMethodComponent().getName()); - - builder.addSpaces(SpaceType.L2); - knnMethod = builder.build(); - - assertTrue(knnMethod.containsSpace(SpaceType.L2)); - } -} diff --git a/src/test/java/org/opensearch/knn/index/KNNQueryBuilderTests.java b/src/test/java/org/opensearch/knn/index/KNNQueryBuilderTests.java deleted file mode 100644 index ccf6bcfca..000000000 --- a/src/test/java/org/opensearch/knn/index/KNNQueryBuilderTests.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.opensearch.knn.index; - -import org.opensearch.knn.KNNTestCase; -import org.opensearch.common.xcontent.XContentBuilder; -import org.opensearch.common.xcontent.XContentFactory; -import org.opensearch.common.xcontent.XContentParser; -import org.opensearch.index.Index; -import org.opensearch.index.mapper.NumberFieldMapper; -import org.opensearch.index.query.QueryShardContext; -import org.opensearch.knn.indices.ModelDao; -import org.opensearch.knn.indices.ModelMetadata; - -import java.io.IOException; - -import static org.mockito.Mockito.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class KNNQueryBuilderTests extends KNNTestCase { - - public void testInvalidK() { - float[] queryVector = { 1.0f, 1.0f }; - - /** - * -ve k - */ - expectThrows(IllegalArgumentException.class, () -> new KNNQueryBuilder("myvector", queryVector, -1)); - - /** - * zero k - */ - expectThrows(IllegalArgumentException.class, () -> new KNNQueryBuilder("myvector", queryVector, 0)); - - /** - * k > KNNQueryBuilder.K_MAX - */ - expectThrows(IllegalArgumentException.class, () -> new KNNQueryBuilder("myvector", queryVector, KNNQueryBuilder.K_MAX + 1)); - } - - public void testEmptyVector() { - /** - * null query vector - */ - float[] queryVector = null; - expectThrows(IllegalArgumentException.class, () -> new KNNQueryBuilder("myvector", queryVector, 1)); - - /** - * empty query vector - */ - float[] queryVector1 = {}; - expectThrows(IllegalArgumentException.class, () -> new KNNQueryBuilder("myvector", queryVector1, 1)); - } - - public void testFromXcontent() throws Exception { - float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; - KNNQueryBuilder knnQueryBuilder = new KNNQueryBuilder("myvector", queryVector, 1); - XContentBuilder builder = XContentFactory.jsonBuilder(); - builder.startObject(); - builder.startObject(knnQueryBuilder.fieldName()); - builder.field(KNNQueryBuilder.VECTOR_FIELD.getPreferredName(), knnQueryBuilder.vector()); - builder.field(KNNQueryBuilder.K_FIELD.getPreferredName(), knnQueryBuilder.getK()); - builder.endObject(); - builder.endObject(); - XContentParser contentParser = createParser(builder); - contentParser.nextToken(); - KNNQueryBuilder actualBuilder = KNNQueryBuilder.fromXContent(contentParser); - actualBuilder.equals(knnQueryBuilder); - } - - public void testDoToQuery_Normal() throws Exception { - float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; - KNNQueryBuilder knnQueryBuilder = new KNNQueryBuilder("myvector", queryVector, 1); - Index dummyIndex = new Index("dummy", "dummy"); - QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); - KNNVectorFieldMapper.KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldMapper.KNNVectorFieldType.class); - when(mockQueryShardContext.index()).thenReturn(dummyIndex); - when(mockKNNVectorField.getDimension()).thenReturn(4); - when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); - KNNQuery query = (KNNQuery) knnQueryBuilder.doToQuery(mockQueryShardContext); - assertEquals(knnQueryBuilder.getK(), query.getK()); - assertEquals(knnQueryBuilder.fieldName(), query.getField()); - assertEquals(knnQueryBuilder.vector(), query.getQueryVector()); - } - - public void testDoToQuery_FromModel() throws Exception { - float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; - KNNQueryBuilder knnQueryBuilder = new KNNQueryBuilder("myvector", queryVector, 1); - Index dummyIndex = new Index("dummy", "dummy"); - QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); - KNNVectorFieldMapper.KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldMapper.KNNVectorFieldType.class); - when(mockQueryShardContext.index()).thenReturn(dummyIndex); - - // Dimension is -1. In this case, model metadata will need to provide dimension - when(mockKNNVectorField.getDimension()).thenReturn(-1); - String modelId = "test-model-id"; - when(mockKNNVectorField.getModelId()).thenReturn(modelId); - - // Mock the modelDao to return mocked modelMetadata - ModelMetadata modelMetadata = mock(ModelMetadata.class); - when(modelMetadata.getDimension()).thenReturn(4); - ModelDao modelDao = mock(ModelDao.class); - when(modelDao.getMetadata(modelId)).thenReturn(modelMetadata); - KNNQueryBuilder.initialize(modelDao); - - when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); - KNNQuery query = (KNNQuery) knnQueryBuilder.doToQuery(mockQueryShardContext); - assertEquals(knnQueryBuilder.getK(), query.getK()); - assertEquals(knnQueryBuilder.fieldName(), query.getField()); - assertEquals(knnQueryBuilder.vector(), query.getQueryVector()); - } - - public void testDoToQuery_InvalidDimensions() { - float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; - KNNQueryBuilder knnQueryBuilder = new KNNQueryBuilder("myvector", queryVector, 1); - Index dummyIndex = new Index("dummy", "dummy"); - QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); - KNNVectorFieldMapper.KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldMapper.KNNVectorFieldType.class); - when(mockQueryShardContext.index()).thenReturn(dummyIndex); - when(mockKNNVectorField.getDimension()).thenReturn(400); - when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); - expectThrows(IllegalArgumentException.class, () -> knnQueryBuilder.doToQuery(mockQueryShardContext)); - when(mockKNNVectorField.getDimension()).thenReturn(1); - expectThrows(IllegalArgumentException.class, () -> knnQueryBuilder.doToQuery(mockQueryShardContext)); - } - - public void testDoToQuery_InvalidFieldType() throws IOException { - float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; - KNNQueryBuilder knnQueryBuilder = new KNNQueryBuilder("mynumber", queryVector, 1); - Index dummyIndex = new Index("dummy", "dummy"); - QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); - NumberFieldMapper.NumberFieldType mockNumberField = mock(NumberFieldMapper.NumberFieldType.class); - when(mockQueryShardContext.index()).thenReturn(dummyIndex); - when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockNumberField); - expectThrows(IllegalArgumentException.class, () -> knnQueryBuilder.doToQuery(mockQueryShardContext)); - } -} diff --git a/src/test/java/org/opensearch/knn/index/KNNSettingsTests.java b/src/test/java/org/opensearch/knn/index/KNNSettingsTests.java new file mode 100644 index 000000000..60be2ee19 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/KNNSettingsTests.java @@ -0,0 +1,240 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index; + +import lombok.SneakyThrows; +import org.opensearch.action.admin.cluster.state.ClusterStateRequest; +import org.opensearch.action.admin.indices.create.CreateIndexRequest; +import org.opensearch.action.admin.indices.settings.put.UpdateSettingsRequest; +import org.opensearch.cluster.ClusterName; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.network.NetworkModule; +import org.opensearch.common.settings.Settings; +import org.opensearch.core.common.unit.ByteSizeValue; +import org.opensearch.env.Environment; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.plugin.KNNPlugin; +import org.opensearch.node.MockNode; +import org.opensearch.node.Node; +import org.opensearch.plugins.Plugin; +import org.opensearch.test.InternalTestCluster; +import org.opensearch.test.MockHttpTransport; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.opensearch.test.NodeRoles.dataNode; + +public class KNNSettingsTests extends KNNTestCase { + + private static final String INDEX_NAME = "myindex"; + + @SneakyThrows + public void testGetSettingValueFromConfig() { + long expectedKNNCircuitBreakerLimit = 13; + Node mockNode = createMockNode( + Map.of(KNNSettings.KNN_MEMORY_CIRCUIT_BREAKER_LIMIT, "\"" + expectedKNNCircuitBreakerLimit + "kb\"") + ); + mockNode.start(); + ClusterService clusterService = mockNode.injector().getInstance(ClusterService.class); + KNNSettings.state().setClusterService(clusterService); + long actualKNNCircuitBreakerLimit = ((ByteSizeValue) KNNSettings.state() + .getSettingValue(KNNSettings.KNN_MEMORY_CIRCUIT_BREAKER_LIMIT)).getKb(); + mockNode.close(); + assertEquals(expectedKNNCircuitBreakerLimit, actualKNNCircuitBreakerLimit); + } + + @SneakyThrows + public void testGetSettingValueDefault() { + Node mockNode = createMockNode(Collections.emptyMap()); + mockNode.start(); + ClusterService clusterService = mockNode.injector().getInstance(ClusterService.class); + KNNSettings.state().setClusterService(clusterService); + long actualKNNCircuitBreakerLimit = ((ByteSizeValue) KNNSettings.state() + .getSettingValue(KNNSettings.KNN_MEMORY_CIRCUIT_BREAKER_LIMIT)).getKb(); + mockNode.close(); + assertEquals( + ((ByteSizeValue) KNNSettings.dynamicCacheSettings.get(KNNSettings.KNN_MEMORY_CIRCUIT_BREAKER_LIMIT).getDefault(Settings.EMPTY)) + .getKb(), + actualKNNCircuitBreakerLimit + + ); + } + + @SneakyThrows + public void testFilteredSearchAdvanceSetting_whenNoValuesProvidedByUsers_thenDefaultSettingsUsed() { + Node mockNode = createMockNode(Collections.emptyMap()); + mockNode.start(); + ClusterService clusterService = mockNode.injector().getInstance(ClusterService.class); + mockNode.client().admin().cluster().state(new ClusterStateRequest()).actionGet(); + mockNode.client().admin().indices().create(new CreateIndexRequest(INDEX_NAME)).actionGet(); + KNNSettings.state().setClusterService(clusterService); + + Integer filteredSearchThreshold = KNNSettings.getFilteredExactSearchThreshold(INDEX_NAME); + mockNode.close(); + assertEquals(KNNSettings.ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD_DEFAULT_VALUE, filteredSearchThreshold); + } + + @SneakyThrows + public void testFilteredSearchAdvanceSetting_whenValuesProvidedByUsers_thenValidateSameValues() { + int userDefinedThreshold = 1000; + int userDefinedThresholdMinValue = 0; + Node mockNode = createMockNode(Collections.emptyMap()); + mockNode.start(); + ClusterService clusterService = mockNode.injector().getInstance(ClusterService.class); + mockNode.client().admin().cluster().state(new ClusterStateRequest()).actionGet(); + mockNode.client().admin().indices().create(new CreateIndexRequest(INDEX_NAME)).actionGet(); + KNNSettings.state().setClusterService(clusterService); + + final Settings filteredSearchAdvanceSettings = Settings.builder() + .put(KNNSettings.ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD, userDefinedThreshold) + .build(); + + mockNode.client() + .admin() + .indices() + .updateSettings(new UpdateSettingsRequest(filteredSearchAdvanceSettings, INDEX_NAME)) + .actionGet(); + + int filteredSearchThreshold = KNNSettings.getFilteredExactSearchThreshold(INDEX_NAME); + + // validate if we are able to set MinValues for the setting + final Settings filteredSearchAdvanceSettingsWithMinValues = Settings.builder() + .put(KNNSettings.ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD, userDefinedThresholdMinValue) + .build(); + + mockNode.client() + .admin() + .indices() + .updateSettings(new UpdateSettingsRequest(filteredSearchAdvanceSettingsWithMinValues, INDEX_NAME)) + .actionGet(); + + int filteredSearchThresholdMinValue = KNNSettings.getFilteredExactSearchThreshold(INDEX_NAME); + + // Validate if less than MinValues are set then Exception Happens + final Settings filteredSearchAdvanceSettingsWithLessThanMinValues = Settings.builder() + .put(KNNSettings.ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD, -1) + .build(); + mockNode.close(); + assertEquals(userDefinedThreshold, filteredSearchThreshold); + assertEquals(userDefinedThresholdMinValue, filteredSearchThresholdMinValue); + } + + @SneakyThrows + public void testGetEfSearch_whenNoValuesProvidedByUsers_thenDefaultSettingsUsed() { + Node mockNode = createMockNode(Collections.emptyMap()); + mockNode.start(); + ClusterService clusterService = mockNode.injector().getInstance(ClusterService.class); + mockNode.client().admin().cluster().state(new ClusterStateRequest()).actionGet(); + mockNode.client().admin().indices().create(new CreateIndexRequest(INDEX_NAME)).actionGet(); + KNNSettings.state().setClusterService(clusterService); + + Integer efSearchValue = KNNSettings.getEfSearchParam(INDEX_NAME); + mockNode.close(); + assertEquals(KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH, efSearchValue); + } + + @SneakyThrows + public void testGetEfSearch_whenEFSearchValueSetByUser_thenReturnValue() { + int userProvidedEfSearch = 300; + Node mockNode = createMockNode(Collections.emptyMap()); + mockNode.start(); + ClusterService clusterService = mockNode.injector().getInstance(ClusterService.class); + mockNode.client().admin().cluster().state(new ClusterStateRequest()).actionGet(); + final Settings settings = Settings.builder() + .put(KNNSettings.KNN_ALGO_PARAM_EF_SEARCH, userProvidedEfSearch) + .put(KNNSettings.KNN_INDEX, true) + .build(); + mockNode.client().admin().indices().create(new CreateIndexRequest(INDEX_NAME, settings)).actionGet(); + KNNSettings.state().setClusterService(clusterService); + + int efSearchValue = KNNSettings.getEfSearchParam(INDEX_NAME); + mockNode.close(); + assertEquals(userProvidedEfSearch, efSearchValue); + } + + @SneakyThrows + public void testShardLevelRescoringEnabled_whenNoValuesProvidedByUser_thenDefaultSettingsUsed() { + Node mockNode = createMockNode(Collections.emptyMap()); + mockNode.start(); + ClusterService clusterService = mockNode.injector().getInstance(ClusterService.class); + mockNode.client().admin().cluster().state(new ClusterStateRequest()).actionGet(); + mockNode.client().admin().indices().create(new CreateIndexRequest(INDEX_NAME)).actionGet(); + KNNSettings.state().setClusterService(clusterService); + + boolean shardLevelRescoringDisabled = KNNSettings.isShardLevelRescoringEnabledForDiskBasedVector(INDEX_NAME); + mockNode.close(); + assertFalse(shardLevelRescoringDisabled); + } + + @SneakyThrows + public void testShardLevelRescoringDisabled_whenValueProvidedByUser_thenSettingApplied() { + boolean userDefinedRescoringDisabled = true; + Node mockNode = createMockNode(Collections.emptyMap()); + mockNode.start(); + ClusterService clusterService = mockNode.injector().getInstance(ClusterService.class); + mockNode.client().admin().cluster().state(new ClusterStateRequest()).actionGet(); + mockNode.client().admin().indices().create(new CreateIndexRequest(INDEX_NAME)).actionGet(); + KNNSettings.state().setClusterService(clusterService); + + final Settings rescoringDisabledSetting = Settings.builder() + .put(KNNSettings.KNN_DISK_VECTOR_SHARD_LEVEL_RESCORING_DISABLED, userDefinedRescoringDisabled) + .build(); + + mockNode.client().admin().indices().updateSettings(new UpdateSettingsRequest(rescoringDisabledSetting, INDEX_NAME)).actionGet(); + + boolean shardLevelRescoringDisabled = KNNSettings.isShardLevelRescoringEnabledForDiskBasedVector(INDEX_NAME); + mockNode.close(); + assertEquals(userDefinedRescoringDisabled, shardLevelRescoringDisabled); + } + + @SneakyThrows + public void testGetFaissAVX2DisabledSettingValueFromConfig_enableSetting_thenValidateAndSucceed() { + boolean expectedKNNFaissAVX2Disabled = true; + Node mockNode = createMockNode(Map.of(KNNSettings.KNN_FAISS_AVX2_DISABLED, expectedKNNFaissAVX2Disabled)); + mockNode.start(); + ClusterService clusterService = mockNode.injector().getInstance(ClusterService.class); + KNNSettings.state().setClusterService(clusterService); + boolean actualKNNFaissAVX2Disabled = KNNSettings.state().getSettingValue(KNNSettings.KNN_FAISS_AVX2_DISABLED); + mockNode.close(); + assertEquals(expectedKNNFaissAVX2Disabled, actualKNNFaissAVX2Disabled); + } + + private Node createMockNode(Map configSettings) throws IOException { + Path configDir = createTempDir(); + File configFile = configDir.resolve("opensearch.yml").toFile(); + FileWriter configFileWriter = new FileWriter(configFile); + + for (Map.Entry setting : configSettings.entrySet()) { + configFileWriter.write("\"" + setting.getKey() + "\": " + setting.getValue()); + } + configFileWriter.close(); + return new MockNode(baseSettings().build(), basePlugins(), configDir, true); + } + + private List> basePlugins() { + List> plugins = new ArrayList<>(); + plugins.add(getTestTransportPlugin()); + plugins.add(MockHttpTransport.TestPlugin.class); + plugins.add(KNNPlugin.class); + return plugins; + } + + private static Settings.Builder baseSettings() { + final Path tempDir = createTempDir(); + return Settings.builder() + .put(ClusterName.CLUSTER_NAME_SETTING.getKey(), InternalTestCluster.clusterName("single-node-cluster", randomLong())) + .put(Environment.PATH_HOME_SETTING.getKey(), tempDir) + .put(NetworkModule.TRANSPORT_TYPE_KEY, getTestTransportType()) + .put(dataNode()); + } +} diff --git a/src/test/java/org/opensearch/knn/index/KNNVectorDVLeafFieldDataTests.java b/src/test/java/org/opensearch/knn/index/KNNVectorDVLeafFieldDataTests.java index 8bda1aefc..cbe11dd6b 100644 --- a/src/test/java/org/opensearch/knn/index/KNNVectorDVLeafFieldDataTests.java +++ b/src/test/java/org/opensearch/knn/index/KNNVectorDVLeafFieldDataTests.java @@ -62,30 +62,38 @@ public void tearDown() throws Exception { } public void testGetScriptValues() { - KNNVectorDVLeafFieldData leafFieldData = new KNNVectorDVLeafFieldData(leafReaderContext.reader(), MOCK_INDEX_FIELD_NAME); + KNNVectorDVLeafFieldData leafFieldData = new KNNVectorDVLeafFieldData( + leafReaderContext.reader(), + MOCK_INDEX_FIELD_NAME, + VectorDataType.FLOAT + ); ScriptDocValues scriptValues = leafFieldData.getScriptValues(); assertNotNull(scriptValues); assertTrue(scriptValues instanceof KNNVectorScriptDocValues); } public void testGetScriptValuesWrongFieldName() { - KNNVectorDVLeafFieldData leafFieldData = new KNNVectorDVLeafFieldData(leafReaderContext.reader(), "invalid"); + KNNVectorDVLeafFieldData leafFieldData = new KNNVectorDVLeafFieldData(leafReaderContext.reader(), "invalid", VectorDataType.FLOAT); ScriptDocValues scriptValues = leafFieldData.getScriptValues(); assertNotNull(scriptValues); } public void testGetScriptValuesWrongFieldType() { - KNNVectorDVLeafFieldData leafFieldData = new KNNVectorDVLeafFieldData(leafReaderContext.reader(), MOCK_NUMERIC_INDEX_FIELD_NAME); + KNNVectorDVLeafFieldData leafFieldData = new KNNVectorDVLeafFieldData( + leafReaderContext.reader(), + MOCK_NUMERIC_INDEX_FIELD_NAME, + VectorDataType.FLOAT + ); expectThrows(IllegalStateException.class, () -> leafFieldData.getScriptValues()); } public void testRamBytesUsed() { - KNNVectorDVLeafFieldData leafFieldData = new KNNVectorDVLeafFieldData(leafReaderContext.reader(), ""); + KNNVectorDVLeafFieldData leafFieldData = new KNNVectorDVLeafFieldData(leafReaderContext.reader(), "", VectorDataType.FLOAT); assertEquals(0, leafFieldData.ramBytesUsed()); } public void testGetBytesValues() { - KNNVectorDVLeafFieldData leafFieldData = new KNNVectorDVLeafFieldData(leafReaderContext.reader(), ""); + KNNVectorDVLeafFieldData leafFieldData = new KNNVectorDVLeafFieldData(leafReaderContext.reader(), "", VectorDataType.FLOAT); expectThrows(UnsupportedOperationException.class, () -> leafFieldData.getBytesValues()); } } diff --git a/src/test/java/org/opensearch/knn/index/KNNVectorFieldMapperTests.java b/src/test/java/org/opensearch/knn/index/KNNVectorFieldMapperTests.java deleted file mode 100644 index b6a65cce9..000000000 --- a/src/test/java/org/opensearch/knn/index/KNNVectorFieldMapperTests.java +++ /dev/null @@ -1,469 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.opensearch.knn.index; - -import com.google.common.collect.ImmutableMap; -import org.opensearch.knn.KNNTestCase; -import org.opensearch.cluster.metadata.IndexMetadata; -import org.opensearch.common.ValidationException; -import org.opensearch.common.settings.IndexScopedSettings; -import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.XContentBuilder; -import org.opensearch.common.xcontent.XContentFactory; -import org.opensearch.index.IndexSettings; -import org.opensearch.index.mapper.ContentPath; -import org.opensearch.index.mapper.Mapper; -import org.opensearch.index.mapper.MapperService; -import org.opensearch.knn.index.util.KNNEngine; -import org.opensearch.knn.indices.ModelDao; -import org.opensearch.knn.indices.ModelMetadata; -import org.opensearch.knn.indices.ModelState; - -import java.io.IOException; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.util.HashSet; - -import static org.opensearch.knn.common.KNNConstants.KNN_METHOD; -import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_M; -import static org.opensearch.knn.common.KNNConstants.MODEL_ID; -import static org.opensearch.knn.common.KNNConstants.NAME; -import static org.opensearch.knn.common.KNNConstants.PARAMETERS; -import static org.opensearch.Version.CURRENT; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class KNNVectorFieldMapperTests extends KNNTestCase { - - public void testBuilder_getParameters() { - String fieldName = "test-field-name"; - ModelDao modelDao = mock(ModelDao.class); - KNNVectorFieldMapper.Builder builder = new KNNVectorFieldMapper.Builder(fieldName, modelDao); - assertEquals(6, builder.getParameters().size()); - } - - public void testBuilder_build_fromKnnMethodContext() { - // Check that knnMethodContext takes precedent over both model and legacy - ModelDao modelDao = mock(ModelDao.class); - KNNVectorFieldMapper.Builder builder = new KNNVectorFieldMapper.Builder("test-field-name-1", modelDao); - - SpaceType spaceType = SpaceType.COSINESIMIL; - int m = 17; - int efConstruction = 17; - - // Setup settings - Settings settings = Settings.builder() - .put(settings(CURRENT).build()) - .put(KNNSettings.KNN_SPACE_TYPE, spaceType.getValue()) - .put(KNNSettings.KNN_ALGO_PARAM_M, m) - .put(KNNSettings.KNN_ALGO_PARAM_EF_CONSTRUCTION, efConstruction) - .build(); - - builder.knnMethodContext.setValue( - new KNNMethodContext( - KNNEngine.DEFAULT, - spaceType, - new MethodComponentContext( - METHOD_HNSW, - ImmutableMap.of(METHOD_PARAMETER_M, m, METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction) - ) - ) - ); - - builder.modelId.setValue("Random modelId"); - - Mapper.BuilderContext builderContext = new Mapper.BuilderContext(settings, new ContentPath()); - KNNVectorFieldMapper knnVectorFieldMapper = builder.build(builderContext); - assertTrue(knnVectorFieldMapper instanceof KNNVectorFieldMapper.MethodFieldMapper); - assertNotNull(knnVectorFieldMapper.knnMethod); - assertNull(knnVectorFieldMapper.modelId); - } - - public void testBuilder_build_fromModel() { - // Check that modelContext takes precedent over legacy - ModelDao modelDao = mock(ModelDao.class); - KNNVectorFieldMapper.Builder builder = new KNNVectorFieldMapper.Builder("test-field-name-1", modelDao); - - SpaceType spaceType = SpaceType.COSINESIMIL; - int m = 17; - int efConstruction = 17; - - // Setup settings - Settings settings = Settings.builder() - .put(settings(CURRENT).build()) - .put(KNNSettings.KNN_SPACE_TYPE, spaceType.getValue()) - .put(KNNSettings.KNN_ALGO_PARAM_M, m) - .put(KNNSettings.KNN_ALGO_PARAM_EF_CONSTRUCTION, efConstruction) - .build(); - - String modelId = "Random modelId"; - ModelMetadata mockedModelMetadata = new ModelMetadata( - KNNEngine.FAISS, - SpaceType.L2, - 129, - ModelState.CREATED, - ZonedDateTime.now(ZoneOffset.UTC).toString(), - "", - "" - ); - builder.modelId.setValue(modelId); - Mapper.BuilderContext builderContext = new Mapper.BuilderContext(settings, new ContentPath()); - - when(modelDao.getMetadata(modelId)).thenReturn(mockedModelMetadata); - KNNVectorFieldMapper knnVectorFieldMapper = builder.build(builderContext); - assertTrue(knnVectorFieldMapper instanceof KNNVectorFieldMapper.ModelFieldMapper); - assertNotNull(knnVectorFieldMapper.modelId); - assertNull(knnVectorFieldMapper.knnMethod); - } - - public void testBuilder_build_fromLegacy() { - // Check legacy is picked up if model context and method context are not set - ModelDao modelDao = mock(ModelDao.class); - KNNVectorFieldMapper.Builder builder = new KNNVectorFieldMapper.Builder("test-field-name-1", modelDao); - - SpaceType spaceType = SpaceType.COSINESIMIL; - int m = 17; - int efConstruction = 17; - - // Setup settings - Settings settings = Settings.builder() - .put(settings(CURRENT).build()) - .put(KNNSettings.KNN_SPACE_TYPE, spaceType.getValue()) - .put(KNNSettings.KNN_ALGO_PARAM_M, m) - .put(KNNSettings.KNN_ALGO_PARAM_EF_CONSTRUCTION, efConstruction) - .build(); - - Mapper.BuilderContext builderContext = new Mapper.BuilderContext(settings, new ContentPath()); - KNNVectorFieldMapper knnVectorFieldMapper = builder.build(builderContext); - assertTrue(knnVectorFieldMapper instanceof KNNVectorFieldMapper.LegacyFieldMapper); - - assertNull(knnVectorFieldMapper.modelId); - assertNull(knnVectorFieldMapper.knnMethod); - } - - public void testTypeParser_parse_fromKnnMethodContext() throws IOException { - // Check that knnMethodContext is set - String fieldName = "test-field-name"; - String indexName = "test-index-name"; - - Settings settings = Settings.builder().put(settings(CURRENT).build()).build(); - - ModelDao modelDao = mock(ModelDao.class); - KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); - - int efConstruction = 321; - int dimension = 133; - XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() - .startObject() - .field("type", "knn_vector") - .field("dimension", dimension) - .startObject(KNN_METHOD) - .field(NAME, METHOD_HNSW) - .startObject(PARAMETERS) - .field(METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction) - .endObject() - .endObject() - .endObject(); - - KNNVectorFieldMapper.Builder builder = (KNNVectorFieldMapper.Builder) typeParser.parse( - fieldName, - xContentBuilderToMap(xContentBuilder), - buildParserContext(indexName, settings) - ); - - assertEquals(METHOD_HNSW, builder.knnMethodContext.get().getMethodComponent().getName()); - assertEquals( - efConstruction, - builder.knnMethodContext.get().getMethodComponent().getParameters().get(METHOD_PARAMETER_EF_CONSTRUCTION) - ); - - // Test invalid parameter - XContentBuilder xContentBuilder2 = XContentFactory.jsonBuilder() - .startObject() - .field("type", "knn_vector") - .field("dimension", dimension) - .startObject(KNN_METHOD) - .field(NAME, METHOD_HNSW) - .startObject(PARAMETERS) - .field("invalid", "invalid") - .endObject() - .endObject() - .endObject(); - - expectThrows( - ValidationException.class, - () -> typeParser.parse(fieldName, xContentBuilderToMap(xContentBuilder2), buildParserContext(indexName, settings)) - ); - - // Test invalid method - XContentBuilder xContentBuilder3 = XContentFactory.jsonBuilder() - .startObject() - .field("type", "knn_vector") - .field("dimension", dimension) - .startObject(KNN_METHOD) - .field(NAME, "invalid") - .endObject() - .endObject(); - - expectThrows( - IllegalArgumentException.class, - () -> typeParser.parse(fieldName, xContentBuilderToMap(xContentBuilder3), buildParserContext(indexName, settings)) - ); - - // Test missing required parameter: dimension - XContentBuilder xContentBuilder4 = XContentFactory.jsonBuilder().startObject().field("type", "knn_vector").endObject(); - - expectThrows( - IllegalArgumentException.class, - () -> typeParser.parse(fieldName, xContentBuilderToMap(xContentBuilder4), buildParserContext(indexName, settings)) - ); - - // Check that this fails if model id is also set - XContentBuilder xContentBuilder5 = XContentFactory.jsonBuilder() - .startObject() - .field("type", "knn_vector") - .field("dimension", dimension) - .field(MODEL_ID, "test-id") - .startObject(KNN_METHOD) - .field(NAME, METHOD_HNSW) - .startObject(PARAMETERS) - .field("invalid", "invalid") - .endObject() - .endObject() - .endObject(); - - expectThrows( - IllegalArgumentException.class, - () -> typeParser.parse(fieldName, xContentBuilderToMap(xContentBuilder5), buildParserContext(indexName, settings)) - ); - } - - public void testTypeParser_parse_fromModel() throws IOException { - // Check that modelContext is set for the builder - String fieldName = "test-field-name"; - String indexName = "test-index-name"; - - Settings settings = Settings.builder().put(settings(CURRENT).build()).build(); - - ModelDao modelDao = mock(ModelDao.class); - KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); - - String modelId = "test-id"; - XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() - .startObject() - .field("type", "knn_vector") - .field(MODEL_ID, modelId) - .endObject(); - - KNNVectorFieldMapper.Builder builder = (KNNVectorFieldMapper.Builder) typeParser.parse( - fieldName, - xContentBuilderToMap(xContentBuilder), - buildParserContext(indexName, settings) - ); - - assertEquals(modelId, builder.modelId.get()); - } - - public void testTypeParser_parse_fromLegacy() throws IOException { - // Check that the particular values are set in builder - String fieldName = "test-field-name"; - String indexName = "test-index-name"; - - int m = 144; - int efConstruction = 123; - SpaceType spaceType = SpaceType.L2; - Settings settings = Settings.builder() - .put(settings(CURRENT).build()) - .put(KNNSettings.KNN_SPACE_TYPE, spaceType.getValue()) - .put(KNNSettings.KNN_ALGO_PARAM_M, m) - .put(KNNSettings.KNN_ALGO_PARAM_EF_CONSTRUCTION, efConstruction) - .build(); - - ModelDao modelDao = mock(ModelDao.class); - KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); - - int dimension = 122; - XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() - .startObject() - .field("type", "knn_vector") - .field("dimension", dimension) - .endObject(); - - KNNVectorFieldMapper.Builder builder = (KNNVectorFieldMapper.Builder) typeParser.parse( - fieldName, - xContentBuilderToMap(xContentBuilder), - buildParserContext(indexName, settings) - ); - - assertNull(builder.modelId.get()); - assertNull(builder.knnMethodContext.get()); - } - - public void testKNNVectorFieldMapper_merge_fromKnnMethodContext() throws IOException { - String fieldName = "test-field-name"; - String indexName = "test-index-name"; - - Settings settings = Settings.builder().put(settings(CURRENT).build()).build(); - - ModelDao modelDao = mock(ModelDao.class); - KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); - - int dimension = 133; - int efConstruction = 321; - XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() - .startObject() - .field("type", "knn_vector") - .field("dimension", dimension) - .startObject(KNN_METHOD) - .field(NAME, METHOD_HNSW) - .startObject(PARAMETERS) - .field(METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction) - .endObject() - .endObject() - .endObject(); - - KNNVectorFieldMapper.Builder builder = (KNNVectorFieldMapper.Builder) typeParser.parse( - fieldName, - xContentBuilderToMap(xContentBuilder), - buildParserContext(indexName, settings) - ); - - Mapper.BuilderContext builderContext = new Mapper.BuilderContext(settings, new ContentPath()); - KNNVectorFieldMapper knnVectorFieldMapper1 = builder.build(builderContext); - - // merge with itself - should be successful - KNNVectorFieldMapper knnVectorFieldMapperMerge1 = (KNNVectorFieldMapper) knnVectorFieldMapper1.merge(knnVectorFieldMapper1); - assertEquals(knnVectorFieldMapper1.knnMethod, knnVectorFieldMapperMerge1.knnMethod); - - // merge with another mapper of the same field with same context - KNNVectorFieldMapper knnVectorFieldMapper2 = builder.build(builderContext); - KNNVectorFieldMapper knnVectorFieldMapperMerge2 = (KNNVectorFieldMapper) knnVectorFieldMapper1.merge(knnVectorFieldMapper2); - assertEquals(knnVectorFieldMapper1.knnMethod, knnVectorFieldMapperMerge2.knnMethod); - - // merge with another mapper of the same field with different context - xContentBuilder = XContentFactory.jsonBuilder() - .startObject() - .field("type", "knn_vector") - .field("dimension", dimension) - .startObject(KNN_METHOD) - .field(NAME, METHOD_HNSW) - .endObject() - .endObject(); - - builder = (KNNVectorFieldMapper.Builder) typeParser.parse( - fieldName, - xContentBuilderToMap(xContentBuilder), - buildParserContext(indexName, settings) - ); - KNNVectorFieldMapper knnVectorFieldMapper3 = builder.build(builderContext); - expectThrows(IllegalArgumentException.class, () -> knnVectorFieldMapper1.merge(knnVectorFieldMapper3)); - } - - public void testKNNVectorFieldMapper_merge_fromModel() throws IOException { - String fieldName = "test-field-name"; - String indexName = "test-index-name"; - - Settings settings = Settings.builder().put(settings(CURRENT).build()).build(); - - String modelId = "test-id"; - int dimension = 133; - - ModelDao mockModelDao = mock(ModelDao.class); - ModelMetadata mockModelMetadata = new ModelMetadata( - KNNEngine.DEFAULT, - SpaceType.DEFAULT, - dimension, - ModelState.CREATED, - ZonedDateTime.now(ZoneOffset.UTC).toString(), - "", - "" - ); - when(mockModelDao.getMetadata(modelId)).thenReturn(mockModelMetadata); - - KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> mockModelDao); - - XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() - .startObject() - .field("type", "knn_vector") - .field(MODEL_ID, modelId) - .endObject(); - - KNNVectorFieldMapper.Builder builder = (KNNVectorFieldMapper.Builder) typeParser.parse( - fieldName, - xContentBuilderToMap(xContentBuilder), - buildParserContext(indexName, settings) - ); - - Mapper.BuilderContext builderContext = new Mapper.BuilderContext(settings, new ContentPath()); - KNNVectorFieldMapper knnVectorFieldMapper1 = builder.build(builderContext); - - // merge with itself - should be successful - KNNVectorFieldMapper knnVectorFieldMapperMerge1 = (KNNVectorFieldMapper) knnVectorFieldMapper1.merge(knnVectorFieldMapper1); - assertEquals(knnVectorFieldMapper1.modelId, knnVectorFieldMapperMerge1.modelId); - - // merge with another mapper of the same field with same context - KNNVectorFieldMapper knnVectorFieldMapper2 = builder.build(builderContext); - KNNVectorFieldMapper knnVectorFieldMapperMerge2 = (KNNVectorFieldMapper) knnVectorFieldMapper1.merge(knnVectorFieldMapper2); - assertEquals(knnVectorFieldMapper1.modelId, knnVectorFieldMapperMerge2.modelId); - - // merge with another mapper of the same field with different context - xContentBuilder = XContentFactory.jsonBuilder() - .startObject() - .field("type", "knn_vector") - .field("dimension", dimension) - .startObject(KNN_METHOD) - .field(NAME, METHOD_HNSW) - .endObject() - .endObject(); - - builder = (KNNVectorFieldMapper.Builder) typeParser.parse( - fieldName, - xContentBuilderToMap(xContentBuilder), - buildParserContext(indexName, settings) - ); - - KNNVectorFieldMapper knnVectorFieldMapper3 = builder.build(builderContext); - expectThrows(IllegalArgumentException.class, () -> knnVectorFieldMapper1.merge(knnVectorFieldMapper3)); - } - - public IndexMetadata buildIndexMetaData(String indexName, Settings settings) { - return IndexMetadata.builder(indexName) - .settings(settings) - .numberOfShards(1) - .numberOfReplicas(0) - .version(7) - .mappingVersion(0) - .settingsVersion(0) - .aliasesVersion(0) - .creationDate(0) - .build(); - } - - public Mapper.TypeParser.ParserContext buildParserContext(String indexName, Settings settings) { - IndexSettings indexSettings = new IndexSettings( - buildIndexMetaData(indexName, settings), - Settings.EMPTY, - new IndexScopedSettings(Settings.EMPTY, new HashSet<>(IndexScopedSettings.BUILT_IN_INDEX_SETTINGS)) - ); - MapperService mapperService = mock(MapperService.class); - when(mapperService.getIndexSettings()).thenReturn(indexSettings); - - // Setup blank - ModelDao mockModelDao = mock(ModelDao.class); - return new Mapper.TypeParser.ParserContext( - null, - mapperService, - type -> new KNNVectorFieldMapper.TypeParser(() -> mockModelDao), - CURRENT, - null, - null, - null - ); - - } -} diff --git a/src/test/java/org/opensearch/knn/index/KNNVectorIndexFieldDataTests.java b/src/test/java/org/opensearch/knn/index/KNNVectorIndexFieldDataTests.java index 8523c4146..ee57cb190 100644 --- a/src/test/java/org/opensearch/knn/index/KNNVectorIndexFieldDataTests.java +++ b/src/test/java/org/opensearch/knn/index/KNNVectorIndexFieldDataTests.java @@ -27,7 +27,7 @@ public class KNNVectorIndexFieldDataTests extends KNNTestCase { @Before public void setUp() throws Exception { super.setUp(); - indexFieldData = new KNNVectorIndexFieldData(MOCK_INDEX_FIELD_NAME, CoreValuesSourceType.BYTES); + indexFieldData = new KNNVectorIndexFieldData(MOCK_INDEX_FIELD_NAME, CoreValuesSourceType.BYTES, VectorDataType.FLOAT); directory = newDirectory(); createEmptyDocument(directory); } diff --git a/src/test/java/org/opensearch/knn/index/KNNVectorScriptDocValuesTests.java b/src/test/java/org/opensearch/knn/index/KNNVectorScriptDocValuesTests.java index 876117940..66e2893c0 100644 --- a/src/test/java/org/opensearch/knn/index/KNNVectorScriptDocValuesTests.java +++ b/src/test/java/org/opensearch/knn/index/KNNVectorScriptDocValuesTests.java @@ -5,6 +5,15 @@ package org.opensearch.knn.index; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.KnnByteVectorField; +import org.apache.lucene.document.KnnFloatVectorField; +import org.apache.lucene.index.BinaryDocValues; +import org.apache.lucene.index.ByteVectorValues; +import org.apache.lucene.index.DocValues; +import org.apache.lucene.index.FloatVectorValues; +import org.apache.lucene.index.LeafReader; +import org.apache.lucene.search.DocIdSetIterator; import org.opensearch.knn.KNNTestCase; import org.apache.lucene.tests.analysis.MockAnalyzer; import org.apache.lucene.document.BinaryDocValuesField; @@ -13,7 +22,6 @@ import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; -import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.store.Directory; import org.junit.Assert; import org.junit.Before; @@ -24,6 +32,7 @@ public class KNNVectorScriptDocValuesTests extends KNNTestCase { private static final String MOCK_INDEX_FIELD_NAME = "test-index-field-name"; private static final float[] SAMPLE_VECTOR_DATA = new float[] { 1.0f, 2.0f }; + private static final byte[] SAMPLE_BYTE_VECTOR_DATA = new byte[] { 1, 2 }; private KNNVectorScriptDocValues scriptDocValues; private Directory directory; private DirectoryReader reader; @@ -32,25 +41,39 @@ public class KNNVectorScriptDocValuesTests extends KNNTestCase { public void setUp() throws Exception { super.setUp(); directory = newDirectory(); - createKNNVectorDocument(directory); + Class valuesClass = randomFrom(BinaryDocValues.class, ByteVectorValues.class, FloatVectorValues.class); + createKNNVectorDocument(directory, valuesClass); reader = DirectoryReader.open(directory); - LeafReaderContext leafReaderContext = reader.getContext().leaves().get(0); - scriptDocValues = new KNNVectorScriptDocValues( - leafReaderContext.reader().getBinaryDocValues(MOCK_INDEX_FIELD_NAME), - MOCK_INDEX_FIELD_NAME - ); + LeafReader leafReader = reader.getContext().leaves().get(0).reader(); + DocIdSetIterator vectorValues; + if (BinaryDocValues.class.equals(valuesClass)) { + vectorValues = DocValues.getBinary(leafReader, MOCK_INDEX_FIELD_NAME); + } else if (ByteVectorValues.class.equals(valuesClass)) { + vectorValues = leafReader.getByteVectorValues(MOCK_INDEX_FIELD_NAME); + } else { + vectorValues = leafReader.getFloatVectorValues(MOCK_INDEX_FIELD_NAME); + } + + scriptDocValues = KNNVectorScriptDocValues.create(vectorValues, MOCK_INDEX_FIELD_NAME, VectorDataType.FLOAT); } - private void createKNNVectorDocument(Directory directory) throws IOException { + private void createKNNVectorDocument(Directory directory, Class valuesClass) throws IOException { IndexWriterConfig conf = newIndexWriterConfig(new MockAnalyzer(random())); IndexWriter writer = new IndexWriter(directory, conf); Document knnDocument = new Document(); - knnDocument.add( - new BinaryDocValuesField( + Field field; + if (BinaryDocValues.class.equals(valuesClass)) { + field = new BinaryDocValuesField( MOCK_INDEX_FIELD_NAME, new VectorField(MOCK_INDEX_FIELD_NAME, SAMPLE_VECTOR_DATA, new FieldType()).binaryValue() - ) - ); + ); + } else if (ByteVectorValues.class.equals(valuesClass)) { + field = new KnnByteVectorField(MOCK_INDEX_FIELD_NAME, SAMPLE_BYTE_VECTOR_DATA); + } else { + field = new KnnFloatVectorField(MOCK_INDEX_FIELD_NAME, SAMPLE_VECTOR_DATA); + } + + knnDocument.add(field); writer.addDocument(knnDocument); writer.commit(); writer.close(); @@ -82,4 +105,18 @@ public void testSize() throws IOException { public void testGet() throws IOException { expectThrows(UnsupportedOperationException.class, () -> scriptDocValues.get(0)); } + + public void testUnsupportedValues() throws IOException { + expectThrows( + IllegalArgumentException.class, + () -> KNNVectorScriptDocValues.create(DocValues.emptyNumeric(), MOCK_INDEX_FIELD_NAME, VectorDataType.FLOAT) + ); + } + + public void testEmptyValues() throws IOException { + KNNVectorScriptDocValues values = KNNVectorScriptDocValues.emptyValues(MOCK_INDEX_FIELD_NAME, VectorDataType.FLOAT); + assertEquals(0, values.size()); + scriptDocValues.setNextDocId(0); + assertEquals(0, values.size()); + } } diff --git a/src/test/java/org/opensearch/knn/index/KNNVectorSimilarityFunctionTests.java b/src/test/java/org/opensearch/knn/index/KNNVectorSimilarityFunctionTests.java new file mode 100644 index 000000000..691941dc3 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/KNNVectorSimilarityFunctionTests.java @@ -0,0 +1,57 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index; + +import junit.framework.TestCase; +import org.apache.lucene.index.VectorSimilarityFunction; +import org.opensearch.knn.plugin.script.KNNScoringUtil; + +import java.util.Set; + +import static org.apache.lucene.tests.util.LuceneTestCase.expectThrows; +import static org.opensearch.knn.index.KNNVectorSimilarityFunction.COSINE; +import static org.opensearch.knn.index.KNNVectorSimilarityFunction.DOT_PRODUCT; +import static org.opensearch.knn.index.KNNVectorSimilarityFunction.EUCLIDEAN; +import static org.opensearch.knn.index.KNNVectorSimilarityFunction.HAMMING; +import static org.opensearch.knn.index.KNNVectorSimilarityFunction.MAXIMUM_INNER_PRODUCT; + +public class KNNVectorSimilarityFunctionTests extends TestCase { + private static final Set FUNCTION_SET_BACKED_BY_LUCENE = Set.of( + EUCLIDEAN, + DOT_PRODUCT, + COSINE, + MAXIMUM_INNER_PRODUCT + ); + + public void testFunctions_whenBackedByLucene_thenSameAsLucene() { + float[] f1 = new float[] { 1.5f, 2.5f, 3.5f, 4.5f, 5.5f }; + float[] f2 = new float[] { 6.5f, 7.5f, 8.5f, 09.5f, 10.5f }; + byte[] b1 = new byte[] { 1, 2, 3 }; + byte[] b2 = new byte[] { 4, 5, 6 }; + for (KNNVectorSimilarityFunction function : KNNVectorSimilarityFunction.values()) { + if (FUNCTION_SET_BACKED_BY_LUCENE.contains(function) == false) { + continue; + } + assertEquals(VectorSimilarityFunction.valueOf(function.name()), function.getVectorSimilarityFunction()); + assertEquals(function.getVectorSimilarityFunction().compare(f1, f2), function.compare(f1, f2)); + assertEquals(function.getVectorSimilarityFunction().compare(b1, b2), function.compare(b1, b2)); + } + } + + public void testFunctions_whenHamming_thenFloatVectorIsNotSupported() { + float[] f1 = new float[] { 1.5f, 2.5f, 3.5f, 4.5f, 5.5f }; + float[] f2 = new float[] { 6.5f, 7.5f, 8.5f, 09.5f, 10.5f }; + + Exception ex = expectThrows(IllegalStateException.class, () -> HAMMING.compare(f1, f2)); + assertTrue(ex.getMessage().contains("not supported")); + } + + public void testFunctions_whenHamming_thenReturnCorrectScore() { + byte[] b1 = new byte[] { 1, 2, 3 }; + byte[] b2 = new byte[] { 4, 5, 6 }; + assertEquals(1.0f / (1 + KNNScoringUtil.calculateHammingBit(b1, b2)), HAMMING.compare(b1, b2)); + } +} diff --git a/src/test/java/org/opensearch/knn/index/LuceneEngineIT.java b/src/test/java/org/opensearch/knn/index/LuceneEngineIT.java new file mode 100644 index 000000000..64e5af1e7 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/LuceneEngineIT.java @@ -0,0 +1,980 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import lombok.SneakyThrows; +import org.apache.commons.lang.math.RandomUtils; +import org.apache.http.util.EntityUtils; +import org.apache.lucene.util.VectorUtil; +import org.junit.After; +import org.opensearch.client.Response; +import org.opensearch.client.ResponseException; +import org.opensearch.common.Nullable; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.QueryBuilders; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.KNNResult; +import org.opensearch.knn.TestUtils; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.query.KNNQueryBuilder; +import org.opensearch.knn.index.engine.KNNEngine; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.opensearch.knn.common.KNNConstants.ENCODER_SQ; +import static org.opensearch.knn.common.KNNConstants.LUCENE_SQ_BITS; +import static org.opensearch.knn.common.KNNConstants.LUCENE_SQ_CONFIDENCE_INTERVAL; +import static org.opensearch.knn.common.KNNConstants.LUCENE_SQ_DEFAULT_BITS; +import static org.opensearch.knn.common.KNNConstants.MAXIMUM_CONFIDENCE_INTERVAL; +import static org.opensearch.knn.common.KNNConstants.MAX_DISTANCE; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.MINIMUM_CONFIDENCE_INTERVAL; +import static org.opensearch.knn.common.KNNConstants.MIN_SCORE; +import static org.opensearch.knn.common.KNNConstants.NAME; +import static org.opensearch.knn.common.KNNConstants.NMSLIB_NAME; +import static org.opensearch.knn.common.KNNConstants.PARAMETERS; +import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; + +public class LuceneEngineIT extends KNNRestTestCase { + + private static final int DIMENSION = 3; + private static final String DOC_ID = "doc1"; + private static final String DOC_ID_2 = "doc2"; + private static final String DOC_ID_3 = "doc3"; + private static final int EF_CONSTRUCTION = 128; + private static final String COLOR_FIELD_NAME = "color"; + private static final String TASTE_FIELD_NAME = "taste"; + private static final int M = 16; + + private static final Float[][] TEST_INDEX_VECTORS = { { 1.0f, 1.0f, 1.0f }, { 2.0f, 2.0f, 2.0f }, { 3.0f, 3.0f, 3.0f } }; + private static final Float[][] TEST_COSINESIMIL_INDEX_VECTORS = { { 6.0f, 7.0f, 3.0f }, { 3.0f, 2.0f, 5.0f }, { 4.0f, 5.0f, 7.0f } }; + private static final Float[][] TEST_INNER_PRODUCT_INDEX_VECTORS = { + { 1.0f, 1.0f, 1.0f }, + { 2.0f, 2.0f, 2.0f }, + { 3.0f, 3.0f, 3.0f }, + { -1.0f, -1.0f, -1.0f }, + { -2.0f, -2.0f, -2.0f }, + { -3.0f, -3.0f, -3.0f } }; + + private static final float[][] TEST_QUERY_VECTORS = { { 1.0f, 1.0f, 1.0f }, { 2.0f, 2.0f, 2.0f }, { 3.0f, 3.0f, 3.0f } }; + + private static final Map> VECTOR_SIMILARITY_TO_SCORE = ImmutableMap.of( + KNNVectorSimilarityFunction.EUCLIDEAN, + (similarity) -> 1 / (1 + similarity), + KNNVectorSimilarityFunction.DOT_PRODUCT, + (similarity) -> (1 + similarity) / 2, + KNNVectorSimilarityFunction.COSINE, + (similarity) -> (1 + similarity) / 2, + KNNVectorSimilarityFunction.MAXIMUM_INNER_PRODUCT, + (similarity) -> similarity <= 0 ? 1 / (1 - similarity) : similarity + 1 + ); + private static final String DIMENSION_FIELD_NAME = "dimension"; + private static final String KNN_VECTOR_TYPE = "knn_vector"; + private static final String PROPERTIES_FIELD_NAME = "properties"; + private static final String TYPE_FIELD_NAME = "type"; + private static final String INTEGER_FIELD_NAME = "int_field"; + private static final String FILED_TYPE_INTEGER = "integer"; + private static final String NON_EXISTENT_INTEGER_FIELD_NAME = "nonexistent_int_field"; + + @After + public final void cleanUp() throws IOException { + deleteKNNIndex(INDEX_NAME); + } + + public void testQuery_l2() throws Exception { + baseQueryTest(SpaceType.L2); + } + + public void testQuery_cosine() throws Exception { + baseQueryTest(SpaceType.COSINESIMIL); + } + + public void testQuery_invalidVectorDimensionInQuery() throws Exception { + + createKnnIndexMappingWithLuceneEngine(DIMENSION, SpaceType.L2, VectorDataType.FLOAT); + for (int j = 0; j < TEST_INDEX_VECTORS.length; j++) { + addKnnDoc(INDEX_NAME, Integer.toString(j + 1), FIELD_NAME, TEST_INDEX_VECTORS[j]); + } + + float[] invalidQuery = new float[DIMENSION - 1]; + int validK = 1; + expectThrows( + ResponseException.class, + () -> searchKNNIndex(INDEX_NAME, new KNNQueryBuilder(FIELD_NAME, invalidQuery, validK), validK) + ); + } + + public void testQuery_documentsMissingField() throws Exception { + + SpaceType spaceType = SpaceType.L2; + + createKnnIndexMappingWithLuceneEngine(DIMENSION, spaceType, VectorDataType.FLOAT); + for (int j = 0; j < TEST_INDEX_VECTORS.length; j++) { + addKnnDoc(INDEX_NAME, Integer.toString(j + 1), FIELD_NAME, TEST_INDEX_VECTORS[j]); + } + + // Add a doc without the lucene field set + String secondField = "field-2"; + addDocWithNumericField(INDEX_NAME, Integer.toString(TEST_INDEX_VECTORS.length + 1), secondField, 0L); + + validateQueries(spaceType, FIELD_NAME); + } + + public void testQuery_multipleEngines() throws Exception { + String luceneField = "lucene-field"; + SpaceType luceneSpaceType = SpaceType.COSINESIMIL; + String nmslibField = "nmslib-field"; + SpaceType nmslibSpaceType = SpaceType.L2; + + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES_FIELD_NAME) + .startObject(luceneField) + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, DIMENSION) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, METHOD_HNSW) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, luceneSpaceType.getValue()) + .field(KNNConstants.KNN_ENGINE, KNNEngine.LUCENE.getName()) + .startObject(KNNConstants.PARAMETERS) + .field(KNNConstants.METHOD_PARAMETER_M, M) + .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, EF_CONSTRUCTION) + .endObject() + .endObject() + .endObject() + .startObject(nmslibField) + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, DIMENSION) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, METHOD_HNSW) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, nmslibSpaceType.getValue()) + .field(KNNConstants.KNN_ENGINE, KNNEngine.NMSLIB.getName()) + .startObject(KNNConstants.PARAMETERS) + .field(KNNConstants.METHOD_PARAMETER_M, M) + .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, EF_CONSTRUCTION) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + String mapping = builder.toString(); + createKnnIndex(INDEX_NAME, mapping); + + for (int i = 0; i < TEST_INDEX_VECTORS.length; i++) { + addKnnDoc( + INDEX_NAME, + Integer.toString(i + 1), + ImmutableList.of(luceneField, nmslibField), + ImmutableList.of(TEST_INDEX_VECTORS[i], TEST_INDEX_VECTORS[i]) + ); + } + + validateQueries(luceneSpaceType, luceneField); + validateQueries(nmslibSpaceType, nmslibField); + } + + public void testAddDoc() throws Exception { + List mValues = ImmutableList.of(16, 32, 64, 128); + List efConstructionValues = ImmutableList.of(16, 32, 64, 128); + + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES_FIELD_NAME) + .startObject(FIELD_NAME) + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, DIMENSION) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, METHOD_HNSW) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2.getValue()) + .field(KNNConstants.KNN_ENGINE, KNNEngine.LUCENE.getName()) + .startObject(KNNConstants.PARAMETERS) + .field(KNNConstants.METHOD_PARAMETER_M, mValues.get(random().nextInt(mValues.size()))) + .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, efConstructionValues.get(random().nextInt(efConstructionValues.size()))) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + Map mappingMap = xContentBuilderToMap(builder); + String mapping = builder.toString(); + + createKnnIndex(INDEX_NAME, mapping); + assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(INDEX_NAME))); + + Float[] vector = new Float[] { 2.0f, 4.5f, 6.5f }; + addKnnDoc(INDEX_NAME, DOC_ID, FIELD_NAME, vector); + + refreshIndex(INDEX_NAME); + assertEquals(1, getDocCount(INDEX_NAME)); + } + + public void testUpdateDoc() throws Exception { + createKnnIndexMappingWithLuceneEngine(2, SpaceType.L2, VectorDataType.FLOAT); + Float[] vector = { 6.0f, 6.0f }; + addKnnDoc(INDEX_NAME, DOC_ID, FIELD_NAME, vector); + + Float[] updatedVector = { 8.0f, 8.0f }; + updateKnnDoc(INDEX_NAME, DOC_ID, FIELD_NAME, updatedVector); + + refreshIndex(INDEX_NAME); + assertEquals(1, getDocCount(INDEX_NAME)); + } + + public void testDeleteDoc() throws Exception { + createKnnIndexMappingWithLuceneEngine(2, SpaceType.L2, VectorDataType.FLOAT); + Float[] vector = { 6.0f, 6.0f }; + addKnnDoc(INDEX_NAME, DOC_ID, FIELD_NAME, vector); + + deleteKnnDoc(INDEX_NAME, DOC_ID); + + refreshIndex(INDEX_NAME); + assertEquals(0, getDocCount(INDEX_NAME)); + } + + public void testQueryWithFilterUsingFloatVectorDataType() throws Exception { + createKnnIndexMappingWithLuceneEngine(DIMENSION, SpaceType.L2, VectorDataType.FLOAT); + + addKnnDocWithAttributes( + DOC_ID, + new float[] { 6.0f, 7.9f, 3.1f }, + ImmutableMap.of(COLOR_FIELD_NAME, "red", TASTE_FIELD_NAME, "sweet") + ); + addKnnDocWithAttributes(DOC_ID_2, new float[] { 3.2f, 2.1f, 4.8f }, ImmutableMap.of(COLOR_FIELD_NAME, "green")); + addKnnDocWithAttributes(DOC_ID_3, new float[] { 4.1f, 5.0f, 7.1f }, ImmutableMap.of(COLOR_FIELD_NAME, "red")); + + refreshIndex(INDEX_NAME); + + final float[] searchVector = { 6.0f, 6.0f, 4.1f }; + List expectedDocIdsKGreaterThanFilterResult = Arrays.asList(DOC_ID, DOC_ID_3); + List expectedDocIdsKLimitsFilterResult = Arrays.asList(DOC_ID); + validateQueryResultsWithFilters(searchVector, 5, 1, expectedDocIdsKGreaterThanFilterResult, expectedDocIdsKLimitsFilterResult); + } + + @SneakyThrows + public void testQueryWithFilterUsingByteVectorDataType() { + createKnnIndexMappingWithLuceneEngine(3, SpaceType.L2, VectorDataType.BYTE); + + addKnnDocWithAttributes(DOC_ID, new float[] { 6.0f, 7.0f, 3.0f }, ImmutableMap.of(COLOR_FIELD_NAME, "red")); + addKnnDocWithAttributes(DOC_ID_2, new float[] { 3.0f, 2.0f, 4.0f }, ImmutableMap.of(COLOR_FIELD_NAME, "green")); + addKnnDocWithAttributes(DOC_ID_3, new float[] { 4.0f, 5.0f, 7.0f }, ImmutableMap.of(COLOR_FIELD_NAME, "red")); + + refreshIndex(INDEX_NAME); + + final float[] searchVector = { 6.0f, 6.0f, 4.0f }; + List expectedDocIdsKGreaterThanFilterResult = Arrays.asList(DOC_ID, DOC_ID_3); + List expectedDocIdsKLimitsFilterResult = Arrays.asList(DOC_ID); + validateQueryResultsWithFilters(searchVector, 5, 1, expectedDocIdsKGreaterThanFilterResult, expectedDocIdsKLimitsFilterResult); + } + + @SneakyThrows + public void testQueryWithFilter_whenNonExistingFieldUsedInFilter_thenSuccessful() { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES_FIELD_NAME) + .startObject(FIELD_NAME) + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, DIMENSION) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, METHOD_HNSW) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2.getValue()) + .field(KNNConstants.KNN_ENGINE, KNNEngine.LUCENE.getName()) + .endObject() + .endObject() + .startObject(INTEGER_FIELD_NAME) + .field(TYPE_FIELD_NAME, FILED_TYPE_INTEGER) + .endObject() + .endObject() + .endObject(); + Map mappingMap = xContentBuilderToMap(builder); + String mapping = builder.toString(); + + createKnnIndex(INDEX_NAME, mapping); + + Float[] vector = new Float[] { 2.0f, 4.5f, 6.5f }; + + String documentAsString = XContentFactory.jsonBuilder() + .startObject() + .field(INTEGER_FIELD_NAME, 5) + .field(FIELD_NAME, vector) + .endObject() + .toString(); + + addKnnDoc(INDEX_NAME, DOC_ID, documentAsString); + + refreshIndex(INDEX_NAME); + assertEquals(1, getDocCount(INDEX_NAME)); + + float[] searchVector = new float[] { 1.0f, 2.1f, 3.9f }; + int k = 10; + + // use filter where nonexistent field is must, we should have no results + QueryBuilder filterWithRequiredNonExistentField = QueryBuilders.boolQuery() + .must(QueryBuilders.rangeQuery(NON_EXISTENT_INTEGER_FIELD_NAME).gte(1)); + Response searchWithRequiredNonExistentFiledInFilterResponse = searchKNNIndex( + INDEX_NAME, + new KNNQueryBuilder(FIELD_NAME, searchVector, k, filterWithRequiredNonExistentField), + k + ); + List resultsQuery1 = parseSearchResponse( + EntityUtils.toString(searchWithRequiredNonExistentFiledInFilterResponse.getEntity()), + FIELD_NAME + ); + assertTrue(resultsQuery1.isEmpty()); + + // use filter with non existent field as optional, we should have some results + QueryBuilder filterWithOptionalNonExistentField = QueryBuilders.boolQuery() + .should(QueryBuilders.rangeQuery(NON_EXISTENT_INTEGER_FIELD_NAME).gte(1)) + .must(QueryBuilders.rangeQuery(INTEGER_FIELD_NAME).gte(1)); + Response searchWithOptionalNonExistentFiledInFilterResponse = searchKNNIndex( + INDEX_NAME, + new KNNQueryBuilder(FIELD_NAME, searchVector, k, filterWithOptionalNonExistentField), + k + ); + List resultsQuery2 = parseSearchResponse( + EntityUtils.toString(searchWithOptionalNonExistentFiledInFilterResponse.getEntity()), + FIELD_NAME + ); + assertEquals(1, resultsQuery2.size()); + } + + public void testQuery_filterWithNonLuceneEngine() throws Exception { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES_FIELD_NAME) + .startObject(FIELD_NAME) + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, DIMENSION) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, METHOD_HNSW) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2.getValue()) + .field(KNNConstants.KNN_ENGINE, NMSLIB_NAME) + .endObject() + .endObject() + .endObject() + .endObject(); + + String mapping = builder.toString(); + createKnnIndex(INDEX_NAME, mapping); + + addKnnDocWithAttributes( + DOC_ID, + new float[] { 6.0f, 7.9f, 3.1f }, + ImmutableMap.of(COLOR_FIELD_NAME, "red", TASTE_FIELD_NAME, "sweet") + ); + addKnnDocWithAttributes(DOC_ID_2, new float[] { 3.2f, 2.1f, 4.8f }, ImmutableMap.of(COLOR_FIELD_NAME, "green")); + addKnnDocWithAttributes(DOC_ID_3, new float[] { 4.1f, 5.0f, 7.1f }, ImmutableMap.of(COLOR_FIELD_NAME, "red")); + + final float[] searchVector = { 6.0f, 6.0f, 5.6f }; + int k = 5; + expectThrows( + ResponseException.class, + () -> searchKNNIndex( + INDEX_NAME, + new KNNQueryBuilder(FIELD_NAME, searchVector, k, QueryBuilders.termQuery(COLOR_FIELD_NAME, "red")), + k + ) + ); + } + + public void testIndexReopening() throws Exception { + createKnnIndexMappingWithLuceneEngine(DIMENSION, SpaceType.L2, VectorDataType.FLOAT); + + for (int j = 0; j < TEST_INDEX_VECTORS.length; j++) { + addKnnDoc(INDEX_NAME, Integer.toString(j + 1), FIELD_NAME, TEST_INDEX_VECTORS[j]); + } + + final float[] searchVector = TEST_QUERY_VECTORS[0]; + final int k = 1 + RandomUtils.nextInt(TEST_INDEX_VECTORS.length); + + final List knnResultsBeforeIndexClosure = queryResults(searchVector, k); + + closeIndex(INDEX_NAME); + openIndex(INDEX_NAME); + + ensureGreen(INDEX_NAME); + + final List knnResultsAfterIndexClosure = queryResults(searchVector, k); + + assertArrayEquals(knnResultsBeforeIndexClosure.toArray(), knnResultsAfterIndexClosure.toArray()); + } + + public void testRadiusSearch_usingDistanceThreshold_usingL2Metrics_usingFloatType() throws Exception { + createKnnIndexMappingWithLuceneEngine(DIMENSION, SpaceType.L2, VectorDataType.FLOAT); + for (int j = 0; j < TEST_INDEX_VECTORS.length; j++) { + addKnnDoc(INDEX_NAME, Integer.toString(j + 1), FIELD_NAME, TEST_INDEX_VECTORS[j]); + } + + final float distance = 3.5f; + final int[] expectedResults = { 2, 3, 2 }; + + validateRadiusSearchResults(TEST_QUERY_VECTORS, distance, null, SpaceType.L2, expectedResults, null, null, null); + } + + public void testRadiusSearch_usingScoreThreshold_usingL2Metrics_usingFloatType() throws Exception { + createKnnIndexMappingWithLuceneEngine(DIMENSION, SpaceType.L2, VectorDataType.FLOAT); + for (int j = 0; j < TEST_INDEX_VECTORS.length; j++) { + addKnnDoc(INDEX_NAME, Integer.toString(j + 1), FIELD_NAME, TEST_INDEX_VECTORS[j]); + } + + final float score = 0.23f; + final int[] expectedResults = { 2, 3, 2 }; + + validateRadiusSearchResults(TEST_QUERY_VECTORS, null, score, SpaceType.L2, expectedResults, null, null, null); + } + + public void testRadiusSearch_usingDistanceThreshold_usingCosineMetrics_usingFloatType() throws Exception { + createKnnIndexMappingWithLuceneEngine(DIMENSION, SpaceType.COSINESIMIL, VectorDataType.FLOAT); + for (int j = 0; j < TEST_COSINESIMIL_INDEX_VECTORS.length; j++) { + addKnnDoc(INDEX_NAME, Integer.toString(j + 1), FIELD_NAME, TEST_COSINESIMIL_INDEX_VECTORS[j]); + } + + final float distance = 0.03f; + final int[] expectedResults = { 1, 1, 1 }; + + validateRadiusSearchResults(TEST_QUERY_VECTORS, distance, null, SpaceType.COSINESIMIL, expectedResults, null, null, null); + } + + public void testRadiusSearch_usingScoreThreshold_usingCosineMetrics_usingFloatType() throws Exception { + createKnnIndexMappingWithLuceneEngine(DIMENSION, SpaceType.COSINESIMIL, VectorDataType.FLOAT); + for (int j = 0; j < TEST_COSINESIMIL_INDEX_VECTORS.length; j++) { + addKnnDoc(INDEX_NAME, Integer.toString(j + 1), FIELD_NAME, TEST_COSINESIMIL_INDEX_VECTORS[j]); + } + + final float score = 0.97f; + final int[] expectedResults = { 1, 1, 1 }; + + validateRadiusSearchResults(TEST_QUERY_VECTORS, null, score, SpaceType.COSINESIMIL, expectedResults, null, null, null); + } + + public void testRadiusSearch_usingScoreThreshold_usingInnerProductMetrics_usingFloatType() throws Exception { + createKnnIndexMappingWithLuceneEngine(DIMENSION, SpaceType.INNER_PRODUCT, VectorDataType.FLOAT); + for (int j = 0; j < TEST_INNER_PRODUCT_INDEX_VECTORS.length; j++) { + addKnnDoc(INDEX_NAME, Integer.toString(j + 1), FIELD_NAME, TEST_INNER_PRODUCT_INDEX_VECTORS[j]); + } + + final float score = 2f; + final int[] expectedResults = { 1, 1, 1 }; + + validateRadiusSearchResults(TEST_QUERY_VECTORS, null, score, SpaceType.INNER_PRODUCT, expectedResults, null, null, null); + } + + public void testRadiusSearch_usingDistanceThreshold_usingL2Metrics_usingByteType() throws Exception { + createKnnIndexMappingWithLuceneEngine(DIMENSION, SpaceType.L2, VectorDataType.BYTE); + for (int j = 0; j < TEST_INDEX_VECTORS.length; j++) { + addKnnDoc(INDEX_NAME, Integer.toString(j + 1), FIELD_NAME, TEST_INDEX_VECTORS[j]); + } + + final float distance = 3.5f; + final int[] expectedResults = { 2, 2, 2 }; + + validateRadiusSearchResults(TEST_QUERY_VECTORS, distance, null, SpaceType.L2, expectedResults, null, null, null); + } + + public void testRadiusSearch_usingScoreThreshold_usingL2Metrics_usingByteType() throws Exception { + createKnnIndexMappingWithLuceneEngine(DIMENSION, SpaceType.L2, VectorDataType.BYTE); + for (int j = 0; j < TEST_INDEX_VECTORS.length; j++) { + addKnnDoc(INDEX_NAME, Integer.toString(j + 1), FIELD_NAME, TEST_INDEX_VECTORS[j]); + } + + final float score = 0.23f; + final int[] expectedResults = { 2, 2, 2 }; + + validateRadiusSearchResults(TEST_QUERY_VECTORS, null, score, SpaceType.L2, expectedResults, null, null, null); + } + + public void testRadiusSearch_usingDistanceThreshold_usingCosineMetrics_usingByteType() throws Exception { + createKnnIndexMappingWithLuceneEngine(DIMENSION, SpaceType.COSINESIMIL, VectorDataType.BYTE); + for (int j = 0; j < TEST_COSINESIMIL_INDEX_VECTORS.length; j++) { + addKnnDoc(INDEX_NAME, Integer.toString(j + 1), FIELD_NAME, TEST_COSINESIMIL_INDEX_VECTORS[j]); + } + + final float distance = 0.05f; + final int[] expectedResults = { 2, 2, 2 }; + + validateRadiusSearchResults(TEST_QUERY_VECTORS, distance, null, SpaceType.COSINESIMIL, expectedResults, null, null, null); + } + + public void testRadiusSearch_usingScoreThreshold_usingCosineMetrics_usingByteType() throws Exception { + createKnnIndexMappingWithLuceneEngine(DIMENSION, SpaceType.COSINESIMIL, VectorDataType.BYTE); + for (int j = 0; j < TEST_COSINESIMIL_INDEX_VECTORS.length; j++) { + addKnnDoc(INDEX_NAME, Integer.toString(j + 1), FIELD_NAME, TEST_COSINESIMIL_INDEX_VECTORS[j]); + } + + final float score = 0.97f; + final int[] expectedResults = { 2, 2, 2 }; + + validateRadiusSearchResults(TEST_QUERY_VECTORS, null, score, SpaceType.COSINESIMIL, expectedResults, null, null, null); + } + + public void testRadiusSearch_usingDistanceThreshold_withFilter_usingL2Metrics_usingFloatType() throws Exception { + createKnnIndexMappingWithLuceneEngine(DIMENSION, SpaceType.L2, VectorDataType.FLOAT); + addKnnDocWithAttributes(DOC_ID, new float[] { 6.0f, 7.9f, 3.1f }, ImmutableMap.of(COLOR_FIELD_NAME, "red")); + addKnnDocWithAttributes(DOC_ID_2, new float[] { 3.2f, 2.1f, 4.8f }, ImmutableMap.of(COLOR_FIELD_NAME, "red")); + addKnnDocWithAttributes(DOC_ID_3, new float[] { 4.1f, 5.0f, 7.1f }, ImmutableMap.of(COLOR_FIELD_NAME, "green")); + + refreshIndex(INDEX_NAME); + + final float distance = 45.0f; + final int[] expectedResults = { 1, 1, 1 }; + + validateRadiusSearchResults(TEST_QUERY_VECTORS, distance, null, SpaceType.L2, expectedResults, COLOR_FIELD_NAME, "red", null); + } + + public void testRadiusSearch_usingScoreThreshold_withFilter_usingCosineMetrics_usingFloatType() throws Exception { + createKnnIndexMappingWithLuceneEngine(DIMENSION, SpaceType.COSINESIMIL, VectorDataType.FLOAT); + addKnnDocWithAttributes(DOC_ID, new float[] { 6.0f, 7.9f, 3.1f }, ImmutableMap.of(COLOR_FIELD_NAME, "red")); + addKnnDocWithAttributes(DOC_ID_2, new float[] { 3.2f, 2.1f, 4.8f }, ImmutableMap.of(COLOR_FIELD_NAME, "red")); + addKnnDocWithAttributes(DOC_ID_3, new float[] { 4.1f, 5.0f, 7.1f }, ImmutableMap.of(COLOR_FIELD_NAME, "green")); + + refreshIndex(INDEX_NAME); + + final float score = 0.02f; + final int[] expectedResults = { 1, 1, 1 }; + + validateRadiusSearchResults(TEST_QUERY_VECTORS, null, score, SpaceType.COSINESIMIL, expectedResults, COLOR_FIELD_NAME, "red", null); + } + + @SneakyThrows + public void testSQ_withInvalidParams_thenThrowException() { + + // Use invalid number of bits for the bits param which throws an exception + int bits = -1; + expectThrows( + ResponseException.class, + () -> createKnnIndexMappingWithLuceneEngineAndSQEncoder( + DIMENSION, + SpaceType.L2, + VectorDataType.FLOAT, + bits, + MINIMUM_CONFIDENCE_INTERVAL + ) + ); + + // Use invalid value for confidence_interval param which throws an exception + double confidenceInterval = -2.5; + expectThrows( + ResponseException.class, + () -> createKnnIndexMappingWithLuceneEngineAndSQEncoder( + DIMENSION, + SpaceType.L2, + VectorDataType.FLOAT, + LUCENE_SQ_DEFAULT_BITS, + confidenceInterval + ) + ); + + // Use "byte" data_type with sq encoder which throws an exception + expectThrows( + ResponseException.class, + () -> createKnnIndexMappingWithLuceneEngineAndSQEncoder( + DIMENSION, + SpaceType.L2, + VectorDataType.BYTE, + LUCENE_SQ_DEFAULT_BITS, + MINIMUM_CONFIDENCE_INTERVAL + ) + ); + } + + @SneakyThrows + public void testAddDocWithSQEncoder() { + createKnnIndexMappingWithLuceneEngineAndSQEncoder( + DIMENSION, + SpaceType.L2, + VectorDataType.FLOAT, + LUCENE_SQ_DEFAULT_BITS, + MAXIMUM_CONFIDENCE_INTERVAL + ); + Float[] vector = new Float[] { 2.0f, 4.5f, 6.5f }; + addKnnDoc(INDEX_NAME, DOC_ID, FIELD_NAME, vector); + + refreshIndex(INDEX_NAME); + assertEquals(1, getDocCount(INDEX_NAME)); + } + + @SneakyThrows + public void testUpdateDocWithSQEncoder() { + createKnnIndexMappingWithLuceneEngineAndSQEncoder( + DIMENSION, + SpaceType.INNER_PRODUCT, + VectorDataType.FLOAT, + LUCENE_SQ_DEFAULT_BITS, + MAXIMUM_CONFIDENCE_INTERVAL + ); + Float[] vector = { 6.0f, 6.0f, 7.0f }; + addKnnDoc(INDEX_NAME, DOC_ID, FIELD_NAME, vector); + + Float[] updatedVector = { 8.0f, 8.0f, 8.0f }; + updateKnnDoc(INDEX_NAME, DOC_ID, FIELD_NAME, updatedVector); + + refreshIndex(INDEX_NAME); + assertEquals(1, getDocCount(INDEX_NAME)); + } + + @SneakyThrows + public void testDeleteDocWithSQEncoder() { + createKnnIndexMappingWithLuceneEngineAndSQEncoder( + DIMENSION, + SpaceType.INNER_PRODUCT, + VectorDataType.FLOAT, + LUCENE_SQ_DEFAULT_BITS, + MAXIMUM_CONFIDENCE_INTERVAL + ); + Float[] vector = { 6.0f, 6.0f, 7.0f }; + addKnnDoc(INDEX_NAME, DOC_ID, FIELD_NAME, vector); + + deleteKnnDoc(INDEX_NAME, DOC_ID); + + refreshIndex(INDEX_NAME); + assertEquals(0, getDocCount(INDEX_NAME)); + } + + @SneakyThrows + public void testIndexingAndQueryingWithSQEncoder() { + createKnnIndexMappingWithLuceneEngineAndSQEncoder( + DIMENSION, + SpaceType.INNER_PRODUCT, + VectorDataType.FLOAT, + LUCENE_SQ_DEFAULT_BITS, + MAXIMUM_CONFIDENCE_INTERVAL + ); + + int numDocs = 10; + for (int i = 0; i < numDocs; i++) { + float[] indexVector = new float[DIMENSION]; + Arrays.fill(indexVector, (float) i); + addKnnDocWithAttributes(INDEX_NAME, Integer.toString(i), FIELD_NAME, indexVector, ImmutableMap.of("rating", String.valueOf(i))); + } + + // Assert that all docs are ingested + refreshAllNonSystemIndices(); + assertEquals(numDocs, getDocCount(INDEX_NAME)); + + float[] queryVector = new float[DIMENSION]; + Arrays.fill(queryVector, (float) numDocs); + int k = 10; + + Response searchResponse = searchKNNIndex(INDEX_NAME, new KNNQueryBuilder(FIELD_NAME, queryVector, k), k); + List results = parseSearchResponse(EntityUtils.toString(searchResponse.getEntity()), FIELD_NAME); + assertEquals(k, results.size()); + for (int i = 0; i < k; i++) { + assertEquals(numDocs - i - 1, Integer.parseInt(results.get(i).getDocId())); + } + } + + public void testQueryWithFilterUsingSQEncoder() throws Exception { + createKnnIndexMappingWithLuceneEngineAndSQEncoder( + DIMENSION, + SpaceType.INNER_PRODUCT, + VectorDataType.FLOAT, + LUCENE_SQ_DEFAULT_BITS, + MAXIMUM_CONFIDENCE_INTERVAL + ); + + addKnnDocWithAttributes( + DOC_ID, + new float[] { 6.0f, 7.9f, 3.1f }, + ImmutableMap.of(COLOR_FIELD_NAME, "red", TASTE_FIELD_NAME, "sweet") + ); + addKnnDocWithAttributes(DOC_ID_2, new float[] { 3.2f, 2.1f, 4.8f }, ImmutableMap.of(COLOR_FIELD_NAME, "green")); + addKnnDocWithAttributes(DOC_ID_3, new float[] { 4.1f, 5.0f, 7.1f }, ImmutableMap.of(COLOR_FIELD_NAME, "red")); + + refreshIndex(INDEX_NAME); + + final float[] searchVector = { 6.0f, 6.0f, 4.1f }; + List expectedDocIdsKGreaterThanFilterResult = Arrays.asList(DOC_ID, DOC_ID_3); + List expectedDocIdsKLimitsFilterResult = Arrays.asList(DOC_ID); + validateQueryResultsWithFilters(searchVector, 5, 1, expectedDocIdsKGreaterThanFilterResult, expectedDocIdsKLimitsFilterResult); + } + + private void createKnnIndexMappingWithLuceneEngineAndSQEncoder( + int dimension, + SpaceType spaceType, + VectorDataType vectorDataType, + int bits, + double confidenceInterval + ) throws Exception { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES_FIELD_NAME) + .startObject(FIELD_NAME) + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(VECTOR_DATA_TYPE_FIELD, vectorDataType) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, METHOD_HNSW) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNNConstants.KNN_ENGINE, KNNEngine.LUCENE.getName()) + .startObject(KNNConstants.PARAMETERS) + .field(KNNConstants.METHOD_PARAMETER_M, M) + .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, EF_CONSTRUCTION) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_SQ) + .startObject(PARAMETERS) + .field(LUCENE_SQ_BITS, bits) + .field(LUCENE_SQ_CONFIDENCE_INTERVAL, confidenceInterval) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + String mapping = builder.toString(); + createKnnIndex(INDEX_NAME, mapping); + } + + private void createKnnIndexMappingWithLuceneEngine(int dimension, SpaceType spaceType, VectorDataType vectorDataType) throws Exception { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES_FIELD_NAME) + .startObject(FIELD_NAME) + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(VECTOR_DATA_TYPE_FIELD, vectorDataType) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, METHOD_HNSW) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNNConstants.KNN_ENGINE, KNNEngine.LUCENE.getName()) + .startObject(KNNConstants.PARAMETERS) + .field(KNNConstants.METHOD_PARAMETER_M, M) + .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, EF_CONSTRUCTION) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + String mapping = builder.toString(); + createKnnIndex(INDEX_NAME, mapping); + } + + private void baseQueryTest(SpaceType spaceType) throws Exception { + + createKnnIndexMappingWithLuceneEngine(DIMENSION, spaceType, VectorDataType.FLOAT); + for (int j = 0; j < TEST_INDEX_VECTORS.length; j++) { + addKnnDoc(INDEX_NAME, Integer.toString(j + 1), FIELD_NAME, TEST_INDEX_VECTORS[j]); + } + + validateQueries(spaceType, FIELD_NAME); + validateQueries(spaceType, FIELD_NAME, Map.of("ef_search", 100)); + } + + private void validateQueries(SpaceType spaceType, String fieldName) throws Exception { + validateQueries(spaceType, fieldName, null); + } + + private void validateQueries(SpaceType spaceType, String fieldName, Map methodParameters) throws Exception { + + int k = LuceneEngineIT.TEST_INDEX_VECTORS.length; + for (float[] queryVector : TEST_QUERY_VECTORS) { + Response response = searchKNNIndex(INDEX_NAME, buildSearchQuery(fieldName, k, queryVector, methodParameters), k); + String responseBody = EntityUtils.toString(response.getEntity()); + List knnResults = parseSearchResponse(responseBody, fieldName); + assertEquals(k, knnResults.size()); + + List actualScores = parseSearchResponseScore(responseBody, fieldName); + for (int j = 0; j < k; j++) { + float[] primitiveArray = knnResults.get(j).getVector(); + float distance = TestUtils.computeDistFromSpaceType(spaceType, primitiveArray, queryVector); + float rawScore = VECTOR_SIMILARITY_TO_SCORE.get(spaceType.getKnnVectorSimilarityFunction()).apply(distance); + assertEquals(KNNEngine.LUCENE.score(rawScore, spaceType), actualScores.get(j), 0.0001); + } + } + } + + private List queryResults(final float[] searchVector, final int k) throws Exception { + final String responseBody = EntityUtils.toString( + searchKNNIndex(INDEX_NAME, new KNNQueryBuilder(FIELD_NAME, searchVector, k), k).getEntity() + ); + final List knnResults = parseSearchResponse(responseBody, FIELD_NAME); + assertNotNull(knnResults); + return knnResults.stream().map(KNNResult::getVector).collect(Collectors.toUnmodifiableList()); + } + + @SneakyThrows + private void validateQueryResultsWithFilters( + float[] searchVector, + int kGreaterThanFilterResult, + int kLimitsFilterResult, + List expectedDocIdsKGreaterThanFilterResult, + List expectedDocIdsKLimitsFilterResult + ) { + final Response response = searchKNNIndex( + INDEX_NAME, + new KNNQueryBuilder(FIELD_NAME, searchVector, kGreaterThanFilterResult, QueryBuilders.termQuery(COLOR_FIELD_NAME, "red")), + kGreaterThanFilterResult + ); + final String responseBody = EntityUtils.toString(response.getEntity()); + final List knnResults = parseSearchResponse(responseBody, FIELD_NAME); + + assertEquals(expectedDocIdsKGreaterThanFilterResult.size(), knnResults.size()); + assertTrue( + knnResults.stream().map(KNNResult::getDocId).collect(Collectors.toList()).containsAll(expectedDocIdsKGreaterThanFilterResult) + ); + + final Response responseKLimitsFilterResult = searchKNNIndex( + INDEX_NAME, + new KNNQueryBuilder(FIELD_NAME, searchVector, kLimitsFilterResult, QueryBuilders.termQuery(COLOR_FIELD_NAME, "red")), + kLimitsFilterResult + ); + final String responseBodyKLimitsFilterResult = EntityUtils.toString(responseKLimitsFilterResult.getEntity()); + final List knnResultsKLimitsFilterResult = parseSearchResponse(responseBodyKLimitsFilterResult, FIELD_NAME); + + assertEquals(expectedDocIdsKLimitsFilterResult.size(), knnResultsKLimitsFilterResult.size()); + assertTrue( + knnResultsKLimitsFilterResult.stream() + .map(KNNResult::getDocId) + .collect(Collectors.toList()) + .containsAll(expectedDocIdsKLimitsFilterResult) + ); + } + + @SneakyThrows + public void test_whenUsingIP_thenSuccess() { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", 2) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, METHOD_HNSW) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, SpaceType.INNER_PRODUCT.getValue()) + .field(KNNConstants.KNN_ENGINE, KNNEngine.LUCENE) + .endObject() + .endObject() + .endObject() + .endObject(); + final String mapping = builder.toString(); + createKnnIndex(INDEX_NAME, mapping); + + final List dataVectors = Arrays.asList(new Float[] { -2.0f, 2.0f }, new Float[] { 2.0f, -2.0f }); + final List ids = Arrays.asList(DOC_ID, DOC_ID_2); + + // Ingest all the documents + for (int i = 0; i < dataVectors.size(); i++) { + addKnnDoc(INDEX_NAME, ids.get(i), FIELD_NAME, dataVectors.get(i)); + } + refreshIndex(INDEX_NAME); + + float[] queryVector = new float[] { -2.0f, 2.0f }; + int k = 2; + final Response response = searchKNNIndex( + INDEX_NAME, + new KNNQueryBuilder(FIELD_NAME, queryVector, k, QueryBuilders.matchAllQuery()), + k + ); + final String responseBody = EntityUtils.toString(response.getEntity()); + final List knnResults = parseSearchResponseScore(responseBody, FIELD_NAME); + + // Check that the expected scores are returned + final List expectedScores = Arrays.asList( + VectorUtil.scaleMaxInnerProductScore(8.0f), + VectorUtil.scaleMaxInnerProductScore(-8.0f) + ); + assertEquals(expectedScores.size(), knnResults.size()); + for (int i = 0; i < expectedScores.size(); i++) { + assertEquals(expectedScores.get(i), knnResults.get(i), 0.0000001); + } + } + + @SneakyThrows + public void testRadialSearch_whenEfSearchIsSet_thenThrowException() { + createKnnIndexMappingWithLuceneEngine(DIMENSION, SpaceType.L2, VectorDataType.FLOAT); + for (int j = 0; j < TEST_INDEX_VECTORS.length; j++) { + addKnnDoc(INDEX_NAME, Integer.toString(j + 1), FIELD_NAME, TEST_INDEX_VECTORS[j]); + } + + final float score = 0.23f; + final int[] expectedResults = { 2, 3, 2 }; + + Map methodParameters = new ImmutableMap.Builder().put(KNNConstants.METHOD_PARAMETER_EF_SEARCH, 150) + .build(); + + expectThrows( + ResponseException.class, + () -> validateRadiusSearchResults(TEST_QUERY_VECTORS, null, score, SpaceType.L2, expectedResults, null, null, methodParameters) + ); + } + + private void validateRadiusSearchResults( + final float[][] searchVectors, + final Float distanceThreshold, + final Float scoreThreshold, + final SpaceType spaceType, + final int[] expectedResults, + @Nullable final String filterField, + @Nullable final String filterValue, + @Nullable final Map methodParameters + ) throws Exception { + for (int i = 0; i < searchVectors.length; i++) { + XContentBuilder builder = XContentFactory.jsonBuilder().startObject().startObject("query"); + builder.startObject("knn"); + builder.startObject(FIELD_NAME); + builder.field("vector", searchVectors[i]); + if (distanceThreshold != null) { + builder.field(MAX_DISTANCE, distanceThreshold); + } else if (scoreThreshold != null) { + builder.field(MIN_SCORE, scoreThreshold); + } else { + throw new IllegalArgumentException("Either distance or score must be provided"); + } + if (filterField != null && filterValue != null) { + builder.startObject("filter"); + builder.startObject("term"); + builder.field(filterField, filterValue); + builder.endObject(); + builder.endObject(); + } + if (methodParameters != null) { + builder.startObject(METHOD_PARAMETER); + for (Map.Entry entry : methodParameters.entrySet()) { + builder.field(entry.getKey(), entry.getValue()); + } + builder.endObject(); + } + builder.endObject(); + builder.endObject(); + builder.endObject().endObject(); + + final String responseBody = EntityUtils.toString(searchKNNIndex(INDEX_NAME, builder, expectedResults[i]).getEntity()); + final List radiusResults = parseSearchResponse(responseBody, FIELD_NAME); + + assertEquals(expectedResults[i], radiusResults.size()); + + List actualScores = parseSearchResponseScore(responseBody, FIELD_NAME); + for (KNNResult result : radiusResults) { + float[] vector = result.getVector(); + float distance = TestUtils.computeDistFromSpaceType(spaceType, vector, searchVectors[i]); + float rawScore = VECTOR_SIMILARITY_TO_SCORE.get(spaceType.getKnnVectorSimilarityFunction()).apply(distance); + if (spaceType == SpaceType.COSINESIMIL) { + distance = 1 - distance; + } + if (distanceThreshold != null) { + assertTrue(distance <= distanceThreshold); + } else { + assertTrue(rawScore >= scoreThreshold); + } + assertEquals(KNNEngine.LUCENE.score(rawScore, spaceType), actualScores.get(radiusResults.indexOf(result)), 0.0001); + } + } + } +} diff --git a/src/test/java/org/opensearch/knn/index/MethodComponentContextTests.java b/src/test/java/org/opensearch/knn/index/MethodComponentContextTests.java index 00c87503c..719c32610 100644 --- a/src/test/java/org/opensearch/knn/index/MethodComponentContextTests.java +++ b/src/test/java/org/opensearch/knn/index/MethodComponentContextTests.java @@ -14,10 +14,11 @@ import com.google.common.collect.ImmutableMap; import org.opensearch.common.io.stream.BytesStreamOutput; import org.opensearch.knn.KNNTestCase; -import org.opensearch.common.xcontent.ToXContent; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.index.mapper.MapperParsingException; +import org.opensearch.knn.index.engine.MethodComponentContext; import java.io.IOException; import java.util.Collections; @@ -281,4 +282,73 @@ public void testHashCode() { assertNotEquals(methodContext1.hashCode(), methodContext4.hashCode()); assertEquals(methodContext4.hashCode(), methodContext5.hashCode()); } + + public void testToStringFromString() { + HashMap parameters3 = new HashMap() { + { + put("nlist", 4); + put("nprobes", 2); + } + }; + + HashMap parameters4 = new HashMap() { + { + put("nlist", 4); + put("type", "fp16"); + } + }; + + HashMap nestedParameters = new HashMap() { + { + put("nprobes", 2); + put("clip", false); + } + }; + HashMap parameters5 = new HashMap() { + { + put("nlist", 4); + put("type", "fp16"); + put("encoder", new MethodComponentContext("sq", nestedParameters)); + } + }; + + HashMap parameters6 = new HashMap() { + { + put("nlist", 4); + put("encoder", new MethodComponentContext("sq", nestedParameters)); + put("type", "fp16"); + } + }; + + MethodComponentContext methodComponentContext1 = MethodComponentContext.EMPTY; + MethodComponentContext methodComponentContext2 = new MethodComponentContext("ivf", null); + MethodComponentContext methodComponentContext3 = new MethodComponentContext("ivf", parameters3); + MethodComponentContext methodComponentContext4 = new MethodComponentContext("ivf", parameters4); + MethodComponentContext methodComponentContext5 = new MethodComponentContext("ivf", parameters5); + MethodComponentContext methodComponentContext6 = new MethodComponentContext("ivf", parameters6); + + String contextString1 = methodComponentContext1.toClusterStateString(); + String contextString2 = methodComponentContext2.toClusterStateString(); + String contextString3 = methodComponentContext3.toClusterStateString(); + String contextString4 = methodComponentContext4.toClusterStateString(); + String contextString5 = methodComponentContext5.toClusterStateString(); + String contextString6 = methodComponentContext6.toClusterStateString(); + + assertEquals("{name=;parameters=[]}", contextString1); + assertEquals("{name=ivf;parameters=[]}", contextString2); + + MethodComponentContext methodComponentContextFromString1 = MethodComponentContext.fromClusterStateString(contextString1); + MethodComponentContext methodComponentContextFromString2 = MethodComponentContext.fromClusterStateString(contextString2); + MethodComponentContext methodComponentContextFromString3 = MethodComponentContext.fromClusterStateString(contextString3); + MethodComponentContext methodComponentContextFromString4 = MethodComponentContext.fromClusterStateString(contextString4); + MethodComponentContext methodComponentContextFromString5 = MethodComponentContext.fromClusterStateString(contextString5); + MethodComponentContext methodComponentContextFromString6 = MethodComponentContext.fromClusterStateString(contextString6); + + assertEquals(methodComponentContext1, methodComponentContextFromString1); + assertEquals(new MethodComponentContext("ivf", Collections.emptyMap()), methodComponentContextFromString2); + assertEquals(methodComponentContext3, methodComponentContextFromString3); + assertEquals(methodComponentContext4, methodComponentContextFromString4); + assertEquals(methodComponentContext5, methodComponentContextFromString5); + assertEquals(methodComponentContext6, methodComponentContextFromString6); + } } diff --git a/src/test/java/org/opensearch/knn/index/NmslibIT.java b/src/test/java/org/opensearch/knn/index/NmslibIT.java index 1e1b1e3b5..23d35d39e 100644 --- a/src/test/java/org/opensearch/knn/index/NmslibIT.java +++ b/src/test/java/org/opensearch/knn/index/NmslibIT.java @@ -13,30 +13,30 @@ import com.google.common.collect.ImmutableList; import com.google.common.primitives.Floats; +import lombok.SneakyThrows; import org.apache.http.util.EntityUtils; import org.junit.BeforeClass; import org.opensearch.client.Response; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.knn.KNNRestTestCase; import org.opensearch.client.ResponseException; -import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.knn.KNNResult; import org.opensearch.knn.TestUtils; import org.opensearch.knn.common.KNNConstants; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.query.KNNQueryBuilder; +import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.plugin.script.KNNScoringUtil; import java.io.IOException; import java.net.URL; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.TreeMap; -import java.util.stream.Collectors; import static org.hamcrest.Matchers.containsString; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; public class NmslibIT extends KNNRestTestCase { @@ -44,18 +44,88 @@ public class NmslibIT extends KNNRestTestCase { @BeforeClass public static void setUpClass() throws IOException { - URL testIndexVectors = FaissIT.class.getClassLoader().getResource("data/test_vectors_1000x128.json"); - URL testQueries = FaissIT.class.getClassLoader().getResource("data/test_queries_100x128.csv"); + if (NmslibIT.class.getClassLoader() == null) { + throw new IllegalStateException("ClassLoader of NmslibIT Class is null"); + } + URL testIndexVectors = NmslibIT.class.getClassLoader().getResource("data/test_vectors_1000x128.json"); + URL testQueries = NmslibIT.class.getClassLoader().getResource("data/test_queries_100x128.csv"); assert testIndexVectors != null; assert testQueries != null; testData = new TestUtils.TestData(testIndexVectors.getPath(), testQueries.getPath()); } - public void testEndToEnd() throws IOException, InterruptedException { + public void testInvalidMethodParameters() throws Exception { String indexName = "test-index-1"; String fieldName = "test-field-1"; + Integer dimension = testData.indexData.vectors[0].length; + SpaceType spaceType = SpaceType.L1; + + // Create an index + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNNConstants.KNN_ENGINE, KNNEngine.NMSLIB.getName()) + .startObject(KNNConstants.PARAMETERS) + .field(KNNConstants.METHOD_PARAMETER_M, 32) + .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, 100) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); - KNNMethod hnswMethod = KNNEngine.NMSLIB.getMethod(KNNConstants.METHOD_HNSW); + final Map mappingMap = xContentBuilderToMap(builder); + String mapping = builder.toString(); + + createKnnIndex(indexName, mapping); + assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(indexName))); + + // Index the test data + // Adding only doc to cut on integ test time + addKnnDoc( + indexName, + Integer.toString(testData.indexData.docs[0]), + fieldName, + Floats.asList(testData.indexData.vectors[0]).toArray() + ); + + expectThrows( + IllegalArgumentException.class, + () -> searchKNNIndex( + indexName, + KNNQueryBuilder.builder() + .k(10) + .methodParameters(Map.of("foo", "bar")) + .vector(testData.queries[0]) + .fieldName(fieldName) + .build(), + 10 + ) + ); + expectThrows( + IllegalArgumentException.class, + () -> searchKNNIndex( + indexName, + KNNQueryBuilder.builder() + .k(10) + .methodParameters(Map.of("ef_search", "bar")) + .vector(testData.queries[0]) + .fieldName(fieldName) + .build(), + 10 + ) + ); + } + + public void testEndToEnd() throws Exception { + String indexName = "test-index-1"; + String fieldName = "test-field-1"; SpaceType spaceType = SpaceType.L1; List mValues = ImmutableList.of(16, 32, 64, 128); @@ -71,7 +141,7 @@ public void testEndToEnd() throws IOException, InterruptedException { .field("type", "knn_vector") .field("dimension", dimension) .startObject(KNNConstants.KNN_METHOD) - .field(KNNConstants.NAME, hnswMethod.getMethodComponent().getName()) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) .field(KNNConstants.KNN_ENGINE, KNNEngine.NMSLIB.getName()) .startObject(KNNConstants.PARAMETERS) @@ -84,9 +154,9 @@ public void testEndToEnd() throws IOException, InterruptedException { .endObject(); Map mappingMap = xContentBuilderToMap(builder); - String mapping = Strings.toString(builder); + String mapping = builder.toString(); - createKnnIndex(indexName, mapping); + createKnnIndex(indexName, buildKNNIndexSettings(0), mapping); assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(indexName))); // Index the test data @@ -103,23 +173,11 @@ public void testEndToEnd() throws IOException, InterruptedException { refreshAllIndices(); assertEquals(testData.indexData.docs.length, getDocCount(indexName)); - int k = 10; - for (int i = 0; i < testData.queries.length; i++) { - Response response = searchKNNIndex(indexName, new KNNQueryBuilder(fieldName, testData.queries[i], k), k); - String responseBody = EntityUtils.toString(response.getEntity()); - List knnResults = parseSearchResponse(responseBody, fieldName); - assertEquals(k, knnResults.size()); - - List actualScores = parseSearchResponseScore(responseBody, fieldName); - for (int j = 0; j < k; j++) { - float[] primitiveArray = Floats.toArray(Arrays.stream(knnResults.get(j).getVector()).collect(Collectors.toList())); - assertEquals( - KNNEngine.NMSLIB.score(KNNScoringUtil.l1Norm(testData.queries[i], primitiveArray), spaceType), - actualScores.get(j), - 0.0001 - ); - } - } + // search index + // without method parameters + validateSearch(indexName, fieldName, spaceType, null); + // With valid method params + validateSearch(indexName, fieldName, spaceType, Map.of("ef_search", 50)); // Delete index deleteKNNIndex(indexName); @@ -137,6 +195,36 @@ public void testEndToEnd() throws IOException, InterruptedException { fail("Graphs are not getting evicted"); } + @SneakyThrows + private void validateSearch( + final String indexName, + final String fieldName, + SpaceType spaceType, + final Map methodParams + ) { + int k = 10; + for (int i = 0; i < testData.queries.length; i++) { + Response response = searchKNNIndex( + indexName, + KNNQueryBuilder.builder().fieldName(fieldName).vector(testData.queries[i]).k(k).methodParameters(methodParams).build(), + k + ); + String responseBody = EntityUtils.toString(response.getEntity()); + List knnResults = parseSearchResponse(responseBody, fieldName); + assertEquals(k, knnResults.size()); + + List actualScores = parseSearchResponseScore(responseBody, fieldName); + for (int j = 0; j < k; j++) { + float[] primitiveArray = knnResults.get(j).getVector(); + assertEquals( + KNNEngine.NMSLIB.score(KNNScoringUtil.l1Norm(testData.queries[i], primitiveArray), spaceType), + actualScores.get(j), + 0.0001 + ); + } + } + } + public void testAddDoc() throws Exception { createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); Float[] vector = { 6.0f, 6.0f }; @@ -186,25 +274,25 @@ public void testCreateIndexWithValidAlgoParams_mapping() { int efConstruction = 14; int m = 13; - String mapping = Strings.toString( - XContentFactory.jsonBuilder() - .startObject() - .startObject("properties") - .startObject(FIELD_NAME) - .field("type", "knn_vector") - .field("dimension", 2) - .startObject(KNNConstants.KNN_METHOD) - .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType) - .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) - .startObject(KNNConstants.PARAMETERS) - .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction) - .field(KNNConstants.METHOD_PARAMETER_M, m) - .endObject() - .endObject() - .endObject() - .endObject() - .endObject() - ); + String mapping = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", 2) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType) + .field(KNN_ENGINE, KNNEngine.NMSLIB.getName()) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .startObject(KNNConstants.PARAMETERS) + .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction) + .field(KNNConstants.METHOD_PARAMETER_M, m) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .toString(); createKnnIndex(INDEX_NAME, settings, mapping); @@ -242,25 +330,25 @@ public void testCreateIndexWithValidAlgoParams_mappingAndSettings() { .put("index.knn.algo_param.ef_construction", efConstruction1) .build(); - String mapping = Strings.toString( - XContentFactory.jsonBuilder() - .startObject() - .startObject("properties") - .startObject(FIELD_NAME) - .field("type", "knn_vector") - .field("dimension", 2) - .startObject(KNNConstants.KNN_METHOD) - .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType1) - .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) - .startObject(KNNConstants.PARAMETERS) - .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction1) - .field(KNNConstants.METHOD_PARAMETER_M, m1) - .endObject() - .endObject() - .endObject() - .endObject() - .endObject() - ); + String mapping = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", 2) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType1) + .field(KNN_ENGINE, KNNEngine.NMSLIB.getName()) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .startObject(KNNConstants.PARAMETERS) + .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction1) + .field(KNNConstants.METHOD_PARAMETER_M, m1) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .toString(); createKnnIndex(INDEX_NAME + "1", settings, mapping); Float[] vector = { 6.0f, 6.0f }; @@ -270,37 +358,38 @@ public void testCreateIndexWithValidAlgoParams_mappingAndSettings() { int efConstruction2 = 114; int m2 = 113; - mapping = Strings.toString( - XContentFactory.jsonBuilder() - .startObject() - .startObject("properties") - .startObject(FIELD_NAME + "1") - .field("type", "knn_vector") - .field("dimension", 2) - .startObject(KNNConstants.KNN_METHOD) - .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType1) - .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) - .startObject(KNNConstants.PARAMETERS) - .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction1) - .field(KNNConstants.METHOD_PARAMETER_M, m1) - .endObject() - .endObject() - .endObject() - .startObject(FIELD_NAME + "2") - .field("type", "knn_vector") - .field("dimension", 2) - .startObject(KNNConstants.KNN_METHOD) - .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType2) - .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) - .startObject(KNNConstants.PARAMETERS) - .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction2) - .field(KNNConstants.METHOD_PARAMETER_M, m2) - .endObject() - .endObject() - .endObject() - .endObject() - .endObject() - ); + mapping = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME + "1") + .field("type", "knn_vector") + .field("dimension", 2) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType1) + .field(KNN_ENGINE, KNNEngine.NMSLIB.getName()) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .startObject(KNNConstants.PARAMETERS) + .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction1) + .field(KNNConstants.METHOD_PARAMETER_M, m1) + .endObject() + .endObject() + .endObject() + .startObject(FIELD_NAME + "2") + .field("type", "knn_vector") + .field("dimension", 2) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType2) + .field(KNN_ENGINE, KNNEngine.NMSLIB.getName()) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .startObject(KNNConstants.PARAMETERS) + .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction2) + .field(KNNConstants.METHOD_PARAMETER_M, m2) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .toString(); createKnnIndex(INDEX_NAME + "2", settings, mapping); addKnnDoc(INDEX_NAME + "2", "1", FIELD_NAME, vector); @@ -329,23 +418,22 @@ public void testInvalidIndexHnswAlgoParams_settings() { public void testInvalidIndexHnswAlgoParams_mapping() throws IOException { Settings settings = Settings.builder().put(getKNNDefaultIndexSettings()).build(); - String mapping = Strings.toString( - XContentFactory.jsonBuilder() - .startObject() - .startObject("properties") - .startObject(FIELD_NAME) - .field("type", "knn_vector") - .field("dimension", 2) - .startObject(KNNConstants.KNN_METHOD) - .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) - .startObject(KNNConstants.PARAMETERS) - .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, "-1") - .endObject() - .endObject() - .endObject() - .endObject() - .endObject() - ); + String mapping = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", 2) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .startObject(KNNConstants.PARAMETERS) + .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, "-1") + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .toString(); expectThrows(ResponseException.class, () -> createKnnIndex(INDEX_NAME, settings, mapping)); } @@ -353,23 +441,22 @@ public void testInvalidIndexHnswAlgoParams_mapping() throws IOException { public void testInvalidIndexHnswAlgoParams_mappingAndSettings() throws IOException { Settings settings = Settings.builder().put(getKNNDefaultIndexSettings()).put("index.knn.algo_param.m", "-1").build(); - String mapping = Strings.toString( - XContentFactory.jsonBuilder() - .startObject() - .startObject("properties") - .startObject(FIELD_NAME) - .field("type", "knn_vector") - .field("dimension", 2) - .startObject(KNNConstants.KNN_METHOD) - .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) - .startObject(KNNConstants.PARAMETERS) - .field(KNNConstants.METHOD_PARAMETER_M, "-1") - .endObject() - .endObject() - .endObject() - .endObject() - .endObject() - ); + String mapping = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", 2) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .startObject(KNNConstants.PARAMETERS) + .field(KNNConstants.METHOD_PARAMETER_M, "-1") + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .toString(); expectThrows(ResponseException.class, () -> createKnnIndex(INDEX_NAME, settings, mapping)); } @@ -382,4 +469,9 @@ public void testInvalidQueryHnswAlgoParams() { ); assertThat(ex.getMessage(), containsString("Failed to parse value [-1] for setting [index.knn.algo_param.ef_search]")); } + + @Override + protected Settings restClientSettings() { + return noStrictDeprecationModeSettingsBuilder().build(); + } } diff --git a/src/test/java/org/opensearch/knn/index/OpenSearchIT.java b/src/test/java/org/opensearch/knn/index/OpenSearchIT.java index 6a06de14a..3d6137db9 100644 --- a/src/test/java/org/opensearch/knn/index/OpenSearchIT.java +++ b/src/test/java/org/opensearch/knn/index/OpenSearchIT.java @@ -13,23 +13,27 @@ import com.google.common.collect.ImmutableList; import com.google.common.primitives.Floats; +import java.util.Locale; +import lombok.SneakyThrows; +import org.apache.http.ParseException; import org.junit.BeforeClass; +import org.junit.Ignore; import org.opensearch.knn.KNNRestTestCase; import org.opensearch.knn.KNNResult; import org.apache.http.util.EntityUtils; import org.opensearch.client.Request; import org.opensearch.client.Response; import org.opensearch.client.ResponseException; -import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.index.query.ExistsQueryBuilder; import org.opensearch.knn.TestUtils; import org.opensearch.knn.common.KNNConstants; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.query.KNNQueryBuilder; +import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.plugin.script.KNNScoringUtil; -import org.opensearch.rest.RestStatus; +import org.opensearch.core.rest.RestStatus; import java.io.IOException; import java.net.URL; @@ -37,9 +41,10 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; -import java.util.stream.Collectors; import static org.hamcrest.Matchers.containsString; +import static org.opensearch.knn.index.KNNSettings.INDEX_KNN_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD_MAX; +import static org.opensearch.knn.index.KNNSettings.INDEX_KNN_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD_MIN; public class OpenSearchIT extends KNNRestTestCase { @@ -47,22 +52,22 @@ public class OpenSearchIT extends KNNRestTestCase { @BeforeClass public static void setUpClass() throws IOException { - URL testIndexVectors = FaissIT.class.getClassLoader().getResource("data/test_vectors_1000x128.json"); - URL testQueries = FaissIT.class.getClassLoader().getResource("data/test_queries_100x128.csv"); + if (OpenSearchIT.class.getClassLoader() == null) { + throw new IllegalStateException("ClassLoader of OpenSearchIT Class is null"); + } + URL testIndexVectors = OpenSearchIT.class.getClassLoader().getResource("data/test_vectors_1000x128.json"); + URL testQueries = OpenSearchIT.class.getClassLoader().getResource("data/test_queries_100x128.csv"); assert testIndexVectors != null; assert testQueries != null; testData = new TestUtils.TestData(testIndexVectors.getPath(), testQueries.getPath()); } - public void testEndToEnd() throws IOException, InterruptedException { + public void testEndToEnd() throws Exception { String indexName = "test-index-1"; KNNEngine knnEngine1 = KNNEngine.NMSLIB; KNNEngine knnEngine2 = KNNEngine.FAISS; String fieldName1 = "test-field-1"; String fieldName2 = "test-field-2"; - - KNNMethod method1 = knnEngine1.getMethod(KNNConstants.METHOD_HNSW); - KNNMethod method2 = knnEngine2.getMethod(KNNConstants.METHOD_HNSW); SpaceType spaceType1 = SpaceType.COSINESIMIL; SpaceType spaceType2 = SpaceType.L2; @@ -80,7 +85,7 @@ public void testEndToEnd() throws IOException, InterruptedException { .field("type", "knn_vector") .field("dimension", dimension) .startObject(KNNConstants.KNN_METHOD) - .field(KNNConstants.NAME, method1.getMethodComponent().getName()) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType1.getValue()) .field(KNNConstants.KNN_ENGINE, knnEngine1.getName()) .startObject(KNNConstants.PARAMETERS) @@ -93,7 +98,7 @@ public void testEndToEnd() throws IOException, InterruptedException { .field("type", "knn_vector") .field("dimension", dimension) .startObject(KNNConstants.KNN_METHOD) - .field(KNNConstants.NAME, method2.getMethodComponent().getName()) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType2.getValue()) .field(KNNConstants.KNN_ENGINE, knnEngine2.getName()) .startObject(KNNConstants.PARAMETERS) @@ -107,8 +112,8 @@ public void testEndToEnd() throws IOException, InterruptedException { .endObject(); Map mappingMap = xContentBuilderToMap(builder); - String mapping = Strings.toString(builder); - createKnnIndex(indexName, mapping); + String mapping = builder.toString(); + createKnnIndex(indexName, buildKNNIndexSettings(0), mapping); assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(indexName))); // Index the test data @@ -138,7 +143,7 @@ public void testEndToEnd() throws IOException, InterruptedException { List actualScores = parseSearchResponseScore(responseBody, fieldName1); for (int j = 0; j < k; j++) { - float[] primitiveArray = Floats.toArray(Arrays.stream(knnResults.get(j).getVector()).collect(Collectors.toList())); + float[] primitiveArray = knnResults.get(j).getVector(); assertEquals( knnEngine1.score(1 - KNNScoringUtil.cosinesimil(testData.queries[i], primitiveArray), spaceType1), actualScores.get(j), @@ -154,7 +159,7 @@ public void testEndToEnd() throws IOException, InterruptedException { actualScores = parseSearchResponseScore(responseBody, fieldName2); for (int j = 0; j < k; j++) { - float[] primitiveArray = Floats.toArray(Arrays.stream(knnResults.get(j).getVector()).collect(Collectors.toList())); + float[] primitiveArray = knnResults.get(j).getVector(); assertEquals( knnEngine2.score(KNNScoringUtil.l2Squared(testData.queries[i], primitiveArray), spaceType2), actualScores.get(j), @@ -185,8 +190,8 @@ public void testAddDoc_blockedWhenCbTrips() throws Exception { Float[] vector = { 6.0f, 6.0f }; ResponseException ex = expectThrows(ResponseException.class, () -> addKnnDoc(INDEX_NAME, "1", FIELD_NAME, vector)); - String expMessage = "Indexing knn vector fields is rejected as circuit breaker triggered." - + " Check _opendistro/_knn/stats for detailed state"; + String expMessage = + "Parsing the created knn vector fields prior to indexing has failed as the circuit breaker triggered. This indicates that the cluster is low on memory resources and cannot index more documents at the moment. Check _plugins/_knn/stats for the circuit breaker status."; assertThat(EntityUtils.toString(ex.getResponse().getEntity()), containsString(expMessage)); // reset @@ -203,8 +208,8 @@ public void testUpdateDoc_blockedWhenCbTrips() throws Exception { updateClusterSettings("knn.circuit_breaker.triggered", "true"); Float[] updatedVector = { 8.0f, 8.0f }; ResponseException ex = expectThrows(ResponseException.class, () -> updateKnnDoc(INDEX_NAME, "1", FIELD_NAME, vector)); - String expMessage = "Indexing knn vector fields is rejected as circuit breaker triggered." - + " Check _opendistro/_knn/stats for detailed state"; + String expMessage = + "Parsing the created knn vector fields prior to indexing has failed as the circuit breaker triggered. This indicates that the cluster is low on memory resources and cannot index more documents at the moment. Check _plugins/_knn/stats for the circuit breaker status."; assertThat(EntityUtils.toString(ex.getResponse().getEntity()), containsString(expMessage)); // reset @@ -263,19 +268,47 @@ public void testIndexingVectorValidation_differentSizes() throws Exception { assertThat(EntityUtils.toString(ex.getResponse().getEntity()), containsString("Vector dimension mismatch. Expected: 4, Given: 5")); } + @SneakyThrows + public void testIndexingVectorValidation_zeroVector() { + Settings settings = Settings.builder().put(getKNNDefaultIndexSettings()).build(); + final boolean valid = randomBoolean(); + final String method = KNNConstants.METHOD_HNSW; + String engine; + String spaceType; + if (valid) { + engine = randomFrom(KNNEngine.values()).getName(); + spaceType = SpaceType.L2.getValue(); + } else { + engine = randomFrom(KNNConstants.LUCENE_NAME, KNNConstants.NMSLIB_NAME); + spaceType = SpaceType.COSINESIMIL.getValue(); + } + createKnnIndex(INDEX_NAME, settings, createKnnIndexMapping(FIELD_NAME, 4, method, engine, spaceType)); + Float[] zeroVector = { 0.0f, 0.0f, 0.0f, 0.0f }; + if (valid) { + addKnnDoc(INDEX_NAME, "1", FIELD_NAME, zeroVector); + } else { + ResponseException ex = expectThrows(ResponseException.class, () -> addKnnDoc(INDEX_NAME, "1", FIELD_NAME, zeroVector)); + assertTrue( + EntityUtils.toString(ex.getResponse().getEntity()) + .contains( + String.format(Locale.ROOT, "zero vector is not supported when space type is [%s]", SpaceType.COSINESIMIL.getValue()) + ) + ); + } + } + public void testVectorMappingValidation_noDimension() throws Exception { Settings settings = Settings.builder().put(getKNNDefaultIndexSettings()).build(); - String mapping = Strings.toString( - XContentFactory.jsonBuilder() - .startObject() - .startObject("properties") - .startObject(FIELD_NAME) - .field("type", "knn_vector") - .endObject() - .endObject() - .endObject() - ); + String mapping = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .endObject() + .endObject() + .endObject() + .toString(); Exception ex = expectThrows(ResponseException.class, () -> createKnnIndex(INDEX_NAME, settings, mapping)); assertThat(ex.getMessage(), containsString("Dimension value missing for vector: " + FIELD_NAME)); @@ -286,11 +319,20 @@ public void testVectorMappingValidation_invalidDimension() { Exception ex = expectThrows( ResponseException.class, - () -> createKnnIndex(INDEX_NAME, settings, createKnnIndexMapping(FIELD_NAME, KNNVectorFieldMapper.MAX_DIMENSION + 1)) + () -> createKnnIndex( + INDEX_NAME, + settings, + createKnnIndexMapping(FIELD_NAME, KNNEngine.getMaxDimensionByEngine(KNNEngine.DEFAULT) + 1) + ) ); assertThat( ex.getMessage(), - containsString("Dimension value cannot be greater than " + KNNVectorFieldMapper.MAX_DIMENSION + " for vector: " + FIELD_NAME) + containsString( + "Dimension value cannot be greater than " + + KNNEngine.getMaxDimensionByEngine(KNNEngine.DEFAULT) + + " for vector with engine: " + + KNNEngine.DEFAULT.getName() + ) ); } @@ -328,21 +370,20 @@ public void testVectorMappingValidation_multiFieldsDifferentDimension() throws E String f4 = FIELD_NAME + "-4"; String f5 = FIELD_NAME + "-5"; - String mapping = Strings.toString( - XContentFactory.jsonBuilder() - .startObject() - .startObject("properties") - .startObject(f4) - .field("type", "knn_vector") - .field("dimension", "4") - .endObject() - .startObject(f5) - .field("type", "knn_vector") - .field("dimension", "5") - .endObject() - .endObject() - .endObject() - ); + String mapping = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(f4) + .field("type", "knn_vector") + .field("dimension", "4") + .endObject() + .startObject(f5) + .field("type", "knn_vector") + .field("dimension", "5") + .endObject() + .endObject() + .endObject() + .toString(); createKnnIndex(INDEX_NAME, settings, mapping); @@ -374,7 +415,7 @@ public void testExistsQuery() throws IOException { Request request = new Request("POST", "/" + INDEX_NAME + "/_doc/7?refresh=true"); XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field("non-knn-field", "test").endObject(); - request.setJsonEntity(Strings.toString(builder)); + request.setJsonEntity(builder.toString()); Response response = client().performRequest(request); assertEquals(request.getEndpoint() + ": failed", RestStatus.CREATED, RestStatus.fromCode(response.getStatusLine().getStatusCode())); @@ -446,4 +487,424 @@ public void testIndexingVectorValidation_updateVectorWithNull() throws Exception assertArrayEquals(vectorForDocumentOne, vectorRestoreInitialValue); } + // This doesn't work since indices that are created post 2.17 don't evict by default when indices are closed or deleted. + // Enable this PR once https://github.com/opensearch-project/k-NN/issues/2148 is resolved. + @Ignore + public void testCacheClear_whenCloseIndex() throws Exception { + String indexName = "test-index-1"; + KNNEngine knnEngine1 = KNNEngine.NMSLIB; + KNNEngine knnEngine2 = KNNEngine.FAISS; + String fieldName1 = "test-field-1"; + String fieldName2 = "test-field-2"; + SpaceType spaceType1 = SpaceType.COSINESIMIL; + SpaceType spaceType2 = SpaceType.L2; + + List mValues = ImmutableList.of(16, 32, 64, 128); + List efConstructionValues = ImmutableList.of(16, 32, 64, 128); + List efSearchValues = ImmutableList.of(16, 32, 64, 128); + + Integer dimension = testData.indexData.vectors[0].length; + + // Create an index + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName1) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType1.getValue()) + .field(KNNConstants.KNN_ENGINE, knnEngine1.getName()) + .startObject(KNNConstants.PARAMETERS) + .field(KNNConstants.METHOD_PARAMETER_M, mValues.get(random().nextInt(mValues.size()))) + .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, efConstructionValues.get(random().nextInt(efConstructionValues.size()))) + .endObject() + .endObject() + .endObject() + .startObject(fieldName2) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType2.getValue()) + .field(KNNConstants.KNN_ENGINE, knnEngine2.getName()) + .startObject(KNNConstants.PARAMETERS) + .field(KNNConstants.METHOD_PARAMETER_M, mValues.get(random().nextInt(mValues.size()))) + .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, efConstructionValues.get(random().nextInt(efConstructionValues.size()))) + .field(KNNConstants.METHOD_PARAMETER_EF_SEARCH, efSearchValues.get(random().nextInt(efSearchValues.size()))) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + Map mappingMap = xContentBuilderToMap(builder); + String mapping = builder.toString(); + createKnnIndex(indexName, mapping); + assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(indexName))); + + // Index the test data + for (int i = 0; i < testData.indexData.docs.length; i++) { + addKnnDoc( + indexName, + Integer.toString(testData.indexData.docs[i]), + ImmutableList.of(fieldName1, fieldName2), + ImmutableList.of( + Floats.asList(testData.indexData.vectors[i]).toArray(), + Floats.asList(testData.indexData.vectors[i]).toArray() + ) + ); + } + + // Assert we have the right number of documents in the index + refreshAllIndices(); + assertEquals(testData.indexData.docs.length, getDocCount(indexName)); + + int k = 10; + for (int i = 0; i < testData.queries.length; i++) { + // Search the first field + Response response = searchKNNIndex(indexName, new KNNQueryBuilder(fieldName1, testData.queries[i], k), k); + String responseBody = EntityUtils.toString(response.getEntity()); + List knnResults = parseSearchResponse(responseBody, fieldName1); + assertEquals(k, knnResults.size()); + + List actualScores = parseSearchResponseScore(responseBody, fieldName1); + for (int j = 0; j < k; j++) { + float[] primitiveArray = knnResults.get(j).getVector(); + assertEquals( + knnEngine1.score(1 - KNNScoringUtil.cosinesimil(testData.queries[i], primitiveArray), spaceType1), + actualScores.get(j), + 0.0001 + ); + } + + // Search the second field + response = searchKNNIndex(indexName, new KNNQueryBuilder(fieldName2, testData.queries[i], k), k); + responseBody = EntityUtils.toString(response.getEntity()); + knnResults = parseSearchResponse(responseBody, fieldName2); + assertEquals(k, knnResults.size()); + + actualScores = parseSearchResponseScore(responseBody, fieldName2); + for (int j = 0; j < k; j++) { + float[] primitiveArray = knnResults.get(j).getVector(); + assertEquals( + knnEngine2.score(KNNScoringUtil.l2Squared(testData.queries[i], primitiveArray), spaceType2), + actualScores.get(j), + 0.0001 + ); + } + } + + // Get Stats + int graphCount = getTotalGraphsInCache(); + assertTrue(graphCount > 0); + // Close index + closeKNNIndex(indexName); + + // Search every 5 seconds 14 times to confirm graph gets evicted + int intervals = 14; + for (int i = 0; i < intervals; i++) { + if (getTotalGraphsInCache() == 0) { + return; + } + + Thread.sleep(5 * 1000); + } + + fail("Graphs are not getting evicted"); + } + + public void testKNNIndex_whenBuildGraphThresholdIsPresent_thenGetThresholdValue() throws Exception { + final Integer buildVectorDataStructureThreshold = randomIntBetween( + INDEX_KNN_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD_MIN, + INDEX_KNN_BUILD_VECTOR_DATA_STRUCTURE_THRESHOLD_MAX + ); + final Settings settings = Settings.builder().put(buildKNNIndexSettings(buildVectorDataStructureThreshold)).build(); + final String knnIndexMapping = createKnnIndexMapping(FIELD_NAME, KNNEngine.getMaxDimensionByEngine(KNNEngine.DEFAULT)); + final String indexName = "test-index-with-build-graph-settings"; + createKnnIndex(indexName, settings, knnIndexMapping); + final String buildVectorDataStructureThresholdSetting = getIndexSettingByName( + indexName, + KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD + ); + assertNotNull("build_vector_data_structure_threshold index setting is not found", buildVectorDataStructureThresholdSetting); + assertEquals( + "incorrect setting for build_vector_data_structure_threshold", + buildVectorDataStructureThreshold, + Integer.valueOf(buildVectorDataStructureThresholdSetting) + ); + deleteKNNIndex(indexName); + } + + public void testKNNIndex_whenBuildThresholdIsNotProvided_thenShouldNotReturnSetting() throws Exception { + final String knnIndexMapping = createKnnIndexMapping(FIELD_NAME, KNNEngine.getMaxDimensionByEngine(KNNEngine.DEFAULT)); + final String indexName = "test-index-with-build-graph-settings"; + createKnnIndex(indexName, knnIndexMapping); + final String buildVectorDataStructureThresholdSetting = getIndexSettingByName( + indexName, + KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD + ); + assertNull( + "build_vector_data_structure_threshold index setting should not be added in index setting", + buildVectorDataStructureThresholdSetting + ); + deleteKNNIndex(indexName); + } + + public void testKNNIndex_whenGetIndexSettingWithDefaultIsCalled_thenReturnDefaultBuildGraphThresholdValue() throws Exception { + final String knnIndexMapping = createKnnIndexMapping(FIELD_NAME, KNNEngine.getMaxDimensionByEngine(KNNEngine.DEFAULT)); + final String indexName = "test-index-with-build-vector-graph-settings"; + createKnnIndex(indexName, knnIndexMapping); + final String buildVectorDataStructureThresholdSetting = getIndexSettingByName( + indexName, + KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, + true + ); + assertNotNull("build_vector_data_structure index setting is not found", buildVectorDataStructureThresholdSetting); + assertEquals( + "incorrect default setting for build_vector_data_structure_threshold", + KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD_DEFAULT_VALUE, + Integer.valueOf(buildVectorDataStructureThresholdSetting) + ); + deleteKNNIndex(indexName); + } + + /* + For this testcase, we will create index with setting build_vector_data_structure_threshold as -1, then index few documents, perform knn search, + then, confirm hits because of exact search though there are no graph. In next step, update setting to 0, force merge segment to 1, perform knn search and confirm expected + hits are returned. + */ + public void testKNNIndex_whenBuildVectorGraphThresholdIsProvidedEndToEnd_thenBuildGraphBasedOnSetting() throws Exception { + final String indexName = "test-index-1"; + final String fieldName1 = "test-field-1"; + final String fieldName2 = "test-field-2"; + + final Integer dimension = testData.indexData.vectors[0].length; + final Settings knnIndexSettings = buildKNNIndexSettings(-1); + + // Create an index + final XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName1) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .field(KNNConstants.KNN_ENGINE, KNNEngine.NMSLIB.getName()) + .startObject(KNNConstants.PARAMETERS) + .endObject() + .endObject() + .endObject() + .startObject(fieldName2) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .field(KNNConstants.KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(KNNConstants.PARAMETERS) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + createKnnIndex(indexName, knnIndexSettings, builder.toString()); + + // Index the test data + for (int i = 0; i < testData.indexData.docs.length; i++) { + addKnnDoc( + indexName, + Integer.toString(testData.indexData.docs[i]), + ImmutableList.of(fieldName1, fieldName2), + ImmutableList.of( + Floats.asList(testData.indexData.vectors[i]).toArray(), + Floats.asList(testData.indexData.vectors[i]).toArray() + ) + ); + } + + refreshAllIndices(); + // Assert we have the right number of documents in the index + assertEquals(testData.indexData.docs.length, getDocCount(indexName)); + + final List nmslibNeighbors = getResults(indexName, fieldName1, testData.queries[0], 1); + assertEquals("unexpected neighbors are returned", nmslibNeighbors.size(), nmslibNeighbors.size()); + + final List faissNeighbors = getResults(indexName, fieldName2, testData.queries[0], 1); + assertEquals("unexpected neighbors are returned", faissNeighbors.size(), faissNeighbors.size()); + + // update build vector data structure setting + updateIndexSettings(indexName, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0)); + forceMergeKnnIndex(indexName, 1); + + final int k = 10; + for (int i = 0; i < testData.queries.length; i++) { + // Search nmslib field + final Response response = searchKNNIndex(indexName, new KNNQueryBuilder(fieldName1, testData.queries[i], k), k); + final String responseBody = EntityUtils.toString(response.getEntity()); + final List nmslibValidNeighbors = parseSearchResponse(responseBody, fieldName1); + assertEquals(k, nmslibValidNeighbors.size()); + // Search faiss field + final List faissValidNeighbors = getResults(indexName, fieldName2, testData.queries[i], k); + assertEquals(k, faissValidNeighbors.size()); + } + + // Delete index + deleteKNNIndex(indexName); + } + + /* + For this testcase, we will create index with setting build_vector_data_structure_threshold number of documents to ingest, then index x documents, perform knn search, + then, confirm expected hits are returned. Here, we don't need force merge to build graph, since, threshold is less than + actual number of documents in segments + */ + public void testKNNIndex_whenBuildVectorDataStructureIsLessThanDocCount_thenBuildGraphBasedSuccessfully() throws Exception { + final String indexName = "test-index-1"; + final String fieldName = "test-field-1"; + + final Integer dimension = testData.indexData.vectors[0].length; + final Settings knnIndexSettings = buildKNNIndexSettings(testData.indexData.docs.length); + + // Create an index + final XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .field(KNNConstants.KNN_ENGINE, KNNEngine.NMSLIB.getName()) + .startObject(KNNConstants.PARAMETERS) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + createKnnIndex(indexName, knnIndexSettings, builder.toString()); + // Disable refresh + updateIndexSettings(indexName, Settings.builder().put("index.refresh_interval", -1)); + + // Index the test data without refresh on every document + for (int i = 0; i < testData.indexData.docs.length; i++) { + addKnnDoc( + indexName, + Integer.toString(testData.indexData.docs[i]), + ImmutableList.of(fieldName), + ImmutableList.of(Floats.asList(testData.indexData.vectors[i]).toArray()), + false + ); + } + + refreshAllIndices(); + // Assert we have the right number of documents in the index + assertEquals(testData.indexData.docs.length, getDocCount(indexName)); + + final int k = 10; + for (int i = 0; i < testData.queries.length; i++) { + final Response response = searchKNNIndex(indexName, new KNNQueryBuilder(fieldName, testData.queries[i], k), k); + final String responseBody = EntityUtils.toString(response.getEntity()); + final List nmslibValidNeighbors = parseSearchResponse(responseBody, fieldName); + assertEquals(k, nmslibValidNeighbors.size()); + } + // Delete index + deleteKNNIndex(indexName); + } + + /* + For this testcase, we will create index with setting build_vector_data_structure_threshold as -1, then index few documents, perform knn search, + then, confirm hits because of exact search though there are no graph. In next step, update setting to 0, force merge segment to 1, perform knn search and confirm expected + hits are returned. + */ + public void testKNNIndex_whenBuildVectorGraphThresholdIsProvidedEndToEnd_thenBuildGraphBasedOnSettingUsingRadialSearch() + throws Exception { + final String indexName = "test-index-1"; + final String fieldName1 = "test-field-1"; + final String fieldName2 = "test-field-2"; + + final Integer dimension = testData.indexData.vectors[0].length; + final Settings knnIndexSettings = buildKNNIndexSettings(-1); + + // Create an index + final XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName1) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .field(KNNConstants.KNN_ENGINE, KNNEngine.NMSLIB.getName()) + .startObject(KNNConstants.PARAMETERS) + .endObject() + .endObject() + .endObject() + .startObject(fieldName2) + .field("type", "knn_vector") + .field("dimension", dimension) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, KNNConstants.METHOD_HNSW) + .field(KNNConstants.KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(KNNConstants.PARAMETERS) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + createKnnIndex(indexName, knnIndexSettings, builder.toString()); + + // Index the test data + for (int i = 0; i < testData.indexData.docs.length; i++) { + addKnnDoc( + indexName, + Integer.toString(testData.indexData.docs[i]), + ImmutableList.of(fieldName1, fieldName2), + ImmutableList.of( + Floats.asList(testData.indexData.vectors[i]).toArray(), + Floats.asList(testData.indexData.vectors[i]).toArray() + ) + ); + } + + refreshAllIndices(); + // Assert we have the right number of documents in the index + assertEquals(testData.indexData.docs.length, getDocCount(indexName)); + + final List nmslibNeighbors = getResults(indexName, fieldName1, testData.queries[0], 1); + assertEquals("unexpected neighbors are returned", nmslibNeighbors.size(), nmslibNeighbors.size()); + + final List faissNeighbors = getResults(indexName, fieldName2, testData.queries[0], 1); + assertEquals("unexpected neighbors are returned", faissNeighbors.size(), faissNeighbors.size()); + + // update build vector data structure setting + updateIndexSettings(indexName, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, 0)); + forceMergeKnnIndex(indexName, 1); + + final int k = 10; + for (int i = 0; i < testData.queries.length; i++) { + // Search nmslib field + final Response response = searchKNNIndex(indexName, new KNNQueryBuilder(fieldName1, testData.queries[i], k), k); + final String responseBody = EntityUtils.toString(response.getEntity()); + final List nmslibValidNeighbors = parseSearchResponse(responseBody, fieldName1); + assertEquals(k, nmslibValidNeighbors.size()); + // Search faiss field + final List faissValidNeighbors = getResults(indexName, fieldName2, testData.queries[i], k); + assertEquals(k, faissValidNeighbors.size()); + } + + // Delete index + deleteKNNIndex(indexName); + } + + private List getResults(final String indexName, final String fieldName, final float[] vector, final int k) + throws IOException, ParseException { + final Response searchResponseField = searchKNNIndex(indexName, new KNNQueryBuilder(fieldName, vector, k), k); + final String searchResponseBody = EntityUtils.toString(searchResponseField.getEntity()); + return parseSearchResponse(searchResponseBody, fieldName); + } + } diff --git a/src/test/java/org/opensearch/knn/index/ParameterTests.java b/src/test/java/org/opensearch/knn/index/ParameterTests.java deleted file mode 100644 index 4e7adfc8c..000000000 --- a/src/test/java/org/opensearch/knn/index/ParameterTests.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.knn.index; - -import com.google.common.collect.ImmutableMap; -import org.opensearch.knn.KNNTestCase; -import org.opensearch.common.ValidationException; -import org.opensearch.knn.index.Parameter.IntegerParameter; -import org.opensearch.knn.index.Parameter.MethodComponentContextParameter; - -import java.util.Map; - -public class ParameterTests extends KNNTestCase { - /** - * Test default default value getter - */ - public void testGetDefaultValue() { - String defaultValue = "test-default"; - Parameter parameter = new Parameter("test", defaultValue, v -> true) { - @Override - public ValidationException validate(Object value) { - return null; - } - }; - - assertEquals(defaultValue, parameter.getDefaultValue()); - } - - /** - * Test integer parameter validate - */ - public void testIntegerParameter_validate() { - final IntegerParameter parameter = new IntegerParameter("test", 1, v -> v > 0); - - // Invalid type - assertNotNull(parameter.validate("String")); - - // Invalid value - assertNotNull(parameter.validate(-1)); - - // valid value - assertNull(parameter.validate(12)); - } - - public void testMethodComponentContextParameter_validate() { - String methodComponentName1 = "method-1"; - String parameterKey1 = "parameter_key_1"; - Integer parameterValue1 = 12; - - Map defaultParameterMap = ImmutableMap.of(parameterKey1, parameterValue1); - MethodComponentContext methodComponentContext = new MethodComponentContext(methodComponentName1, defaultParameterMap); - - Map methodComponentMap = ImmutableMap.of( - methodComponentName1, - MethodComponent.Builder.builder(parameterKey1) - .addParameter(parameterKey1, new IntegerParameter(parameterKey1, 1, v -> v > 0)) - .build() - ); - - final MethodComponentContextParameter parameter = new MethodComponentContextParameter( - "test", - methodComponentContext, - methodComponentMap - ); - - // Invalid type - assertNotNull(parameter.validate(17)); - assertNotNull(parameter.validate("invalid-value")); - - // Invalid value - String invalidMethodComponentName = "invalid-method"; - MethodComponentContext invalidMethodComponentContext1 = new MethodComponentContext(invalidMethodComponentName, defaultParameterMap); - assertNotNull(parameter.validate(invalidMethodComponentContext1)); - - String invalidParameterKey = "invalid-parameter"; - Map invalidParameterMap1 = ImmutableMap.of(invalidParameterKey, parameterValue1); - MethodComponentContext invalidMethodComponentContext2 = new MethodComponentContext(methodComponentName1, invalidParameterMap1); - assertNotNull(parameter.validate(invalidMethodComponentContext2)); - - String invalidParameterValue = "invalid-value"; - Map invalidParameterMap2 = ImmutableMap.of(parameterKey1, invalidParameterValue); - MethodComponentContext invalidMethodComponentContext3 = new MethodComponentContext(methodComponentName1, invalidParameterMap2); - assertNotNull(parameter.validate(invalidMethodComponentContext3)); - - // valid value - assertNull(parameter.validate(methodComponentContext)); - } - - public void testMethodComponentContextParameter_getMethodComponent() { - String methodComponentName1 = "method-1"; - String parameterKey1 = "parameter_key_1"; - Integer parameterValue1 = 12; - - Map defaultParameterMap = ImmutableMap.of(parameterKey1, parameterValue1); - MethodComponentContext methodComponentContext = new MethodComponentContext(methodComponentName1, defaultParameterMap); - - Map methodComponentMap = ImmutableMap.of( - methodComponentName1, - MethodComponent.Builder.builder(parameterKey1) - .addParameter(parameterKey1, new IntegerParameter(parameterKey1, 1, v -> v > 0)) - .build() - ); - - final MethodComponentContextParameter parameter = new MethodComponentContextParameter( - "test", - methodComponentContext, - methodComponentMap - ); - - // Test when method component is available - assertEquals(methodComponentMap.get(methodComponentName1), parameter.getMethodComponent(methodComponentName1)); - - // test when method component is not available - String invalidMethod = "invalid-method"; - assertNull(parameter.getMethodComponent(invalidMethod)); - } -} diff --git a/src/test/java/org/opensearch/knn/index/SegmentReplicationIT.java b/src/test/java/org/opensearch/knn/index/SegmentReplicationIT.java new file mode 100644 index 000000000..02b0fcf71 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/SegmentReplicationIT.java @@ -0,0 +1,94 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index; + +import lombok.SneakyThrows; +import lombok.extern.log4j.Log4j2; +import org.apache.http.util.EntityUtils; +import org.junit.Assert; +import org.opensearch.client.Response; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.KNNResult; + +import java.util.List; + +/** + * This IT class contains will contain special cases of IT for segment replication behavior. + * All the index created in this test will have replication type SEGMENT, number of replicas: 1 and should be run on + * at-least 2 node configuration. + */ +@Log4j2 +public class SegmentReplicationIT extends KNNRestTestCase { + private static final String INDEX_NAME = "segment-replicated-knn-index"; + + @SneakyThrows + public void testSearchOnReplicas_whenIndexHasDeletedDocs_thenSuccess() { + createKnnIndex(INDEX_NAME, getKNNSegmentReplicatedIndexSettings(), createKNNIndexMethodFieldMapping(FIELD_NAME, 2)); + + Float[] vector = { 1.3f, 2.2f }; + int docsInIndex = 10; + + for (int i = 0; i < docsInIndex; i++) { + addKnnDoc(INDEX_NAME, Integer.toString(i), FIELD_NAME, vector); + } + refreshIndex(INDEX_NAME); + int deleteDocs = 5; + for (int i = 0; i < deleteDocs; i++) { + deleteKnnDoc(INDEX_NAME, Integer.toString(i)); + } + refreshIndex(INDEX_NAME); + // sleep for 5sec to ensure data is replicated. I don't have a better way here to know if segments has been + // replicated. + Thread.sleep(5000); + // validate warmup is successful or not. + doKnnWarmup(List.of(INDEX_NAME)); + + XContentBuilder queryBuilder = XContentFactory.jsonBuilder().startObject().startObject("query"); + queryBuilder.startObject("knn"); + queryBuilder.startObject(FIELD_NAME); + queryBuilder.field("vector", vector); + queryBuilder.field("k", docsInIndex); + queryBuilder.endObject().endObject().endObject().endObject(); + + // validate primaries are working + Response searchResponse = performSearch(INDEX_NAME, queryBuilder.toString(), "preference=_primary"); + String responseBody = EntityUtils.toString(searchResponse.getEntity()); + List knnResults = parseSearchResponse(responseBody, FIELD_NAME); + assertEquals(docsInIndex - deleteDocs, knnResults.size()); + + if (ensureMinDataNodesCountForTestingQueriesOnReplica()) { + // validate replicas are working + searchResponse = performSearch(INDEX_NAME, queryBuilder.toString(), "preference=_replica"); + responseBody = EntityUtils.toString(searchResponse.getEntity()); + knnResults = parseSearchResponse(responseBody, FIELD_NAME); + assertEquals(docsInIndex - deleteDocs, knnResults.size()); + } + } + + private boolean ensureMinDataNodesCountForTestingQueriesOnReplica() { + int dataNodeCount = getDataNodeCount(); + if (dataNodeCount <= 1) { + log.warn( + "Not running segment replication tests named: " + + "testSearchOnReplicas_whenIndexHasDeletedDocs_thenSuccess, as data nodes count is not atleast 2. " + + "Actual datanode count : {}", + dataNodeCount + ); + Assert.assertTrue(true); + // making the test successful because we don't want to break already running tests. + return false; + } + return true; + } +} diff --git a/src/test/java/org/opensearch/knn/index/SpaceTypeTests.java b/src/test/java/org/opensearch/knn/index/SpaceTypeTests.java new file mode 100644 index 000000000..b0a6c1375 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/SpaceTypeTests.java @@ -0,0 +1,103 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index; + +import org.apache.lucene.index.VectorSimilarityFunction; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.engine.KNNEngine; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class SpaceTypeTests extends KNNTestCase { + + public void testGetVectorSimilarityFunction_l2() { + assertEquals(VectorSimilarityFunction.EUCLIDEAN, SpaceType.L2.getKnnVectorSimilarityFunction().getVectorSimilarityFunction()); + } + + public void testGetVectorSimilarityFunction_invalid() { + expectThrows(UnsupportedOperationException.class, SpaceType.L1::getKnnVectorSimilarityFunction); + } + + public void testGetVectorSimilarityFunction_whenInnerproduct_thenConsistentWithScoreTranslation() { + /* + For the innerproduct space type, we expect that negative dot product scores will be transformed as follows: + if (negativeDotProduct >= 0) { + return 1 / (1 + negativeDotProduct); + } + return -negativeDotProduct + 1; + + Internally, Lucene uses scaleMaxInnerProductScore to scale the raw dot product into a proper lucene score. + See: + 1. https://github.com/apache/lucene/blob/releases/lucene/9.10.0/lucene/core/src/java/org/apache/lucene/util/VectorUtil.java#L195-L200 + 2. https://github.com/apache/lucene/blob/releases/lucene/9.10.0/lucene/core/src/java/org/apache/lucene/index/VectorSimilarityFunction.java#L90 + */ + final List dataVectors = Arrays.asList( + new float[] { 0.0f, 0.0f }, + new float[] { 0.25f, -0.25f }, + new float[] { 0.125f, -0.125f }, + new float[] { 25.0f, -25.0f }, + new float[] { -0.125f, 0.125f }, + new float[] { -0.25f, 0.25f }, + new float[] { -25.0f, 25.0f } + ); + float[] queryVector = new float[] { -2.0f, 2.0f }; + List dotProducts = List.of(0.0f, -1.0f, -0.5f, -100.0f, 0.5f, 1.0f, 100.0f); + + for (int i = 0; i < dataVectors.size(); i++) { + assertEquals( + KNNEngine.FAISS.score(dotProducts.get(i), SpaceType.INNER_PRODUCT), + SpaceType.INNER_PRODUCT.getKnnVectorSimilarityFunction().compare(queryVector, dataVectors.get(i)), + 0.0000001 + ); + } + } + + public void testValidateVectorDataType_whenCalled_thenReturn() { + Map> expected = Map.of( + SpaceType.UNDEFINED, + Collections.emptySet(), + SpaceType.L2, + Set.of(VectorDataType.FLOAT, VectorDataType.BYTE), + SpaceType.COSINESIMIL, + Set.of(VectorDataType.FLOAT, VectorDataType.BYTE), + SpaceType.L1, + Set.of(VectorDataType.FLOAT, VectorDataType.BYTE), + SpaceType.LINF, + Set.of(VectorDataType.FLOAT, VectorDataType.BYTE), + SpaceType.INNER_PRODUCT, + Set.of(VectorDataType.FLOAT, VectorDataType.BYTE), + SpaceType.HAMMING, + Set.of(VectorDataType.BINARY) + ); + + for (SpaceType spaceType : SpaceType.values()) { + for (VectorDataType vectorDataType : VectorDataType.values()) { + if (expected.get(spaceType).isEmpty()) { + Exception ex = expectThrows(IllegalStateException.class, () -> spaceType.validateVectorDataType(vectorDataType)); + assertTrue(ex.getMessage().contains("Unsupported method")); + continue; + } + + if (expected.get(spaceType).contains(vectorDataType)) { + spaceType.validateVectorDataType(vectorDataType); + } else { + Exception ex = expectThrows(IllegalArgumentException.class, () -> spaceType.validateVectorDataType(vectorDataType)); + assertTrue(ex.getMessage().contains("is not supported")); + } + } + } + } +} diff --git a/src/test/java/org/opensearch/knn/index/VectorDataTypeIT.java b/src/test/java/org/opensearch/knn/index/VectorDataTypeIT.java new file mode 100644 index 000000000..2e68339d4 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/VectorDataTypeIT.java @@ -0,0 +1,791 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index; + +import lombok.SneakyThrows; +import org.apache.http.util.EntityUtils; +import org.junit.After; +import org.opensearch.client.Request; +import org.opensearch.client.Response; +import org.opensearch.client.ResponseException; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.rest.RestStatus; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.index.query.MatchAllQueryBuilder; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.KNNResult; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.query.KNNQueryBuilder; +import org.opensearch.script.Script; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.DIMENSION; +import static org.opensearch.knn.common.KNNConstants.ENCODER_SQ; +import static org.opensearch.knn.common.KNNConstants.FAISS_NAME; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_ENCODER_FP16; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_TYPE; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.KNN_METHOD; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_IVF; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NLIST; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NPROBES; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; +import static org.opensearch.knn.common.KNNConstants.MODEL_DESCRIPTION; +import static org.opensearch.knn.common.KNNConstants.MODEL_ID; +import static org.opensearch.knn.common.KNNConstants.NAME; +import static org.opensearch.knn.common.KNNConstants.PARAMETERS; +import static org.opensearch.knn.common.KNNConstants.TRAIN_FIELD_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.TRAIN_INDEX_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; +import static org.opensearch.knn.index.VectorDataType.SUPPORTED_VECTOR_DATA_TYPES; + +public class VectorDataTypeIT extends KNNRestTestCase { + private static final String INDEX_NAME = "test-index-vec-dt"; + private static final String FIELD_NAME = "test-field-vec-dt"; + private static final String PROPERTIES_FIELD = "properties"; + private static final String DOC_ID = "doc1"; + private static final String TYPE_FIELD_NAME = "type"; + private static final String KNN_VECTOR_TYPE = "knn_vector"; + private static final int EF_CONSTRUCTION = 128; + private static final int M = 16; + private static final QueryBuilder MATCH_ALL_QUERY_BUILDER = new MatchAllQueryBuilder(); + + @After + @SneakyThrows + public final void cleanUp() { + deleteKNNIndex(INDEX_NAME); + } + + // Validate if we are able to create an index by setting data_type field as byte and add a doc to it + @SneakyThrows + public void testAddDocWithByteVector() { + createKnnIndexMappingWithLuceneEngine(2, SpaceType.L2, VectorDataType.BYTE.getValue()); + Byte[] vector = { 6, 6 }; + addKnnDoc(INDEX_NAME, DOC_ID, FIELD_NAME, vector); + + refreshAllIndices(); + assertEquals(1, getDocCount(INDEX_NAME)); + } + + // Validate by creating an index by setting data_type field as byte, add a doc to it and update it later. + @SneakyThrows + public void testUpdateDocWithByteVector() { + createKnnIndexMappingWithLuceneEngine(2, SpaceType.L2, VectorDataType.BYTE.getValue()); + Byte[] vector = { -36, 78 }; + addKnnDoc(INDEX_NAME, DOC_ID, FIELD_NAME, vector); + + Byte[] updatedVector = { 89, -8 }; + updateKnnDoc(INDEX_NAME, DOC_ID, FIELD_NAME, updatedVector); + + refreshAllIndices(); + assertEquals(1, getDocCount(INDEX_NAME)); + } + + // Validate by creating an index by setting data_type field as byte, add a doc to it and delete it later. + @SneakyThrows + public void testDeleteDocWithByteVector() { + createKnnIndexMappingWithLuceneEngine(2, SpaceType.L2, VectorDataType.BYTE.getValue()); + Byte[] vector = { 35, -46 }; + addKnnDoc(INDEX_NAME, DOC_ID, FIELD_NAME, vector); + + deleteKnnDoc(INDEX_NAME, DOC_ID); + refreshAllIndices(); + + assertEquals(0, getDocCount(INDEX_NAME)); + } + + @SneakyThrows + public void testSearchWithByteVector() { + createKnnIndexMappingWithLuceneEngine(2, SpaceType.L2, VectorDataType.BYTE.getValue()); + ingestL2ByteTestData(); + + Byte[] queryVector = { 1, 1 }; + Response response = searchKNNIndex(INDEX_NAME, new KNNQueryBuilder(FIELD_NAME, convertByteToFloatArray(queryVector), 4), 4); + + validateL2SearchResults(response); + } + + @SneakyThrows + public void testSearchWithInvalidByteVector() { + createKnnIndexMappingWithLuceneEngine(2, SpaceType.L2, VectorDataType.BYTE.getValue()); + ingestL2ByteTestData(); + + // Validate search with floats instead of byte vectors + float[] queryVector = { -10.76f, 15.89f }; + ResponseException ex = expectThrows( + ResponseException.class, + () -> searchKNNIndex(INDEX_NAME, new KNNQueryBuilder(FIELD_NAME, queryVector, 4), 4) + ); + assertTrue( + ex.getMessage() + .contains( + String.format( + Locale.ROOT, + "[%s] field was set as [%s] in index mapping. But, KNN vector values are floats instead of byte integers", + VECTOR_DATA_TYPE_FIELD, + VectorDataType.BYTE.getValue() + ) + ) + ); + + // validate search with search vectors outside of byte range + float[] queryVector1 = { -1000.0f, 200.0f }; + ResponseException ex1 = expectThrows( + ResponseException.class, + () -> searchKNNIndex(INDEX_NAME, new KNNQueryBuilder(FIELD_NAME, queryVector1, 4), 4) + ); + + assertTrue( + ex1.getMessage() + .contains( + String.format( + Locale.ROOT, + "[%s] field was set as [%s] in index mapping. But, KNN vector values are not within in the byte range [%d, %d]", + VECTOR_DATA_TYPE_FIELD, + VectorDataType.BYTE.getValue(), + Byte.MIN_VALUE, + Byte.MAX_VALUE + ) + ) + ); + } + + @SneakyThrows + public void testSearchWithFloatVectorDataType() { + createKnnIndexMappingWithLuceneEngine(2, SpaceType.L2, VectorDataType.FLOAT.getValue()); + ingestL2FloatTestData(); + + float[] queryVector = { 1.0f, 1.0f }; + Response response = searchKNNIndex(INDEX_NAME, new KNNQueryBuilder(FIELD_NAME, queryVector, 4), 4); + + validateL2SearchResults(response); + } + + // Set an invalid value for data_type field while creating the index which should throw an exception + public void testInvalidVectorDataType() { + String vectorDataType = "invalidVectorType"; + ResponseException ex = expectThrows( + ResponseException.class, + () -> createKnnIndexMappingWithLuceneEngine(2, SpaceType.L2, vectorDataType) + ); + assertTrue( + ex.getMessage() + .contains( + String.format( + Locale.ROOT, + "Invalid value provided for [%s] field. Supported values are [%s]", + VECTOR_DATA_TYPE_FIELD, + SUPPORTED_VECTOR_DATA_TYPES + ) + ) + ); + } + + // Set null value for data_type field while creating the index which should throw an exception + public void testVectorDataTypeAsNull() { + ResponseException ex = expectThrows(ResponseException.class, () -> createKnnIndexMappingWithLuceneEngine(2, SpaceType.L2, null)); + assertTrue( + ex.getMessage() + .contains( + String.format( + Locale.ROOT, + "[%s] on mapper [%s] of type [%s] must not have a [null] value", + VECTOR_DATA_TYPE_FIELD, + FIELD_NAME, + KNN_VECTOR_TYPE + ) + ) + ); + } + + // Create an index with byte vector data_type and add a doc with decimal values which should throw exception + @SneakyThrows + public void testInvalidVectorData() { + createKnnIndexMappingWithLuceneEngine(2, SpaceType.L2, VectorDataType.BYTE.getValue()); + Float[] vector = { -10.76f, 15.89f }; + + ResponseException ex = expectThrows(ResponseException.class, () -> addKnnDoc(INDEX_NAME, DOC_ID, FIELD_NAME, vector)); + assertTrue( + ex.getMessage() + .contains( + String.format( + Locale.ROOT, + "[%s] field was set as [%s] in index mapping. But, KNN vector values are floats instead of byte integers", + VECTOR_DATA_TYPE_FIELD, + VectorDataType.BYTE.getValue() + ) + ) + ); + } + + // Create an index with byte vector data_type and add a doc with values out of byte range which should throw exception + @SneakyThrows + public void testInvalidByteVectorRange() { + createKnnIndexMappingWithLuceneEngine(2, SpaceType.L2, VectorDataType.BYTE.getValue()); + Float[] vector = { -1000f, 155f }; + + ResponseException ex = expectThrows(ResponseException.class, () -> addKnnDoc(INDEX_NAME, DOC_ID, FIELD_NAME, vector)); + assertTrue( + ex.getMessage() + .contains( + String.format( + Locale.ROOT, + "[%s] field was set as [%s] in index mapping. But, KNN vector values are not within in the byte range [%d, %d]", + VECTOR_DATA_TYPE_FIELD, + VectorDataType.BYTE.getValue(), + Byte.MIN_VALUE, + Byte.MAX_VALUE + ) + ) + ); + } + + // Create an index with byte vector data_type using nmslib engine which should throw an exception + public void testByteVectorDataTypeWithNmslibEngine() { + ResponseException ex = expectThrows( + ResponseException.class, + () -> createKnnIndexMappingWithNmslibEngine(2, SpaceType.L2, VectorDataType.BYTE.getValue()) + ); + assertTrue(ex.getMessage().contains("is not supported for vector data type")); + } + + public void testDocValuesWithByteVectorDataTypeLuceneEngine() throws Exception { + createKnnIndexMappingWithLuceneEngine(2, SpaceType.L2, VectorDataType.BYTE.getValue()); + ingestL2ByteTestData(); + + Byte[] queryVector = { 1, 1 }; + Request request = createScriptQueryRequest(queryVector, SpaceType.L2.getValue(), MATCH_ALL_QUERY_BUILDER); + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + + validateL2SearchResults(response); + } + + public void testDocValuesWithFloatVectorDataTypeLuceneEngine() throws Exception { + createKnnIndexMappingWithLuceneEngine(2, SpaceType.L2, VectorDataType.FLOAT.getValue()); + ingestL2FloatTestData(); + + Byte[] queryVector = { 1, 1 }; + Request request = createScriptQueryRequest(queryVector, SpaceType.L2.getValue(), MATCH_ALL_QUERY_BUILDER); + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + + validateL2SearchResults(response); + } + + public void testL2ScriptScoreWithByteVectorDataType() throws Exception { + createKnnIndexMappingForScripting(2, VectorDataType.BYTE.getValue()); + ingestL2ByteTestData(); + + Byte[] queryVector = { 1, 1 }; + Request request = createScriptQueryRequest(queryVector, SpaceType.L2.getValue(), MATCH_ALL_QUERY_BUILDER); + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + + validateL2SearchResults(response); + } + + public void testL2ScriptScoreWithFloatVectorDataType() throws Exception { + createKnnIndexMappingForScripting(2, VectorDataType.FLOAT.getValue()); + ingestL2FloatTestData(); + + Float[] queryVector = { 1.0f, 1.0f }; + Request request = createScriptQueryRequest(queryVector, SpaceType.L2.getValue(), MATCH_ALL_QUERY_BUILDER); + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + + validateL2SearchResults(response); + } + + public void testL2PainlessScriptingWithByteVectorDataType() throws Exception { + createKnnIndexMappingForScripting(2, VectorDataType.BYTE.getValue()); + ingestL2ByteTestData(); + + String source = String.format("1/(1 + l2Squared([1, 1], doc['%s']))", FIELD_NAME); + Request request = constructScriptScoreContextSearchRequest( + INDEX_NAME, + MATCH_ALL_QUERY_BUILDER, + Collections.emptyMap(), + Script.DEFAULT_SCRIPT_LANG, + source, + 4, + Collections.emptyMap() + ); + + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + + validateL2SearchResults(response); + } + + public void testL2PainlessScriptingWithFloatVectorDataType() throws Exception { + createKnnIndexMappingForScripting(2, VectorDataType.FLOAT.getValue()); + ingestL2FloatTestData(); + + String source = String.format("1/(1 + l2Squared([1.0f, 1.0f], doc['%s']))", FIELD_NAME); + Request request = constructScriptScoreContextSearchRequest( + INDEX_NAME, + MATCH_ALL_QUERY_BUILDER, + Collections.emptyMap(), + Script.DEFAULT_SCRIPT_LANG, + source, + 4, + Collections.emptyMap() + ); + + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + + validateL2SearchResults(response); + } + + public void testKNNScriptScoreWithInvalidVectorDataType() { + // Set an invalid value for data_type field while creating the index for script scoring which should throw an exception + ResponseException ex = expectThrows(ResponseException.class, () -> createKnnIndexMappingForScripting(2, "invalid_data_type")); + assertTrue( + ex.getMessage() + .contains( + String.format( + Locale.ROOT, + "Invalid value provided for [%s] field. Supported values are [%s]", + VECTOR_DATA_TYPE_FIELD, + SUPPORTED_VECTOR_DATA_TYPES + ) + ) + ); + } + + public void testKNNScriptScoreWithInvalidByteQueryVector() throws Exception { + // Create an index with byte vector data_type, add docs and run a scoring script query with decimal values + // which should throw exception + createKnnIndexMappingForScripting(2, VectorDataType.BYTE.getValue()); + + Byte[] f1 = { 6, 6 }; + addKnnDoc(INDEX_NAME, "1", FIELD_NAME, f1); + + Byte[] f2 = { 2, 2 }; + addKnnDoc(INDEX_NAME, "2", FIELD_NAME, f2); + + // Construct Search Request with query vector having decimal values + Float[] queryVector = { 10.67f, 19.78f }; + Request request = createScriptQueryRequest(queryVector, SpaceType.L2.getValue(), MATCH_ALL_QUERY_BUILDER); + ResponseException ex = expectThrows(ResponseException.class, () -> client().performRequest(request)); + assertTrue( + ex.getMessage() + .contains( + String.format( + Locale.ROOT, + "[%s] field was set as [%s] in index mapping. But, KNN vector values are floats instead of byte integers", + VECTOR_DATA_TYPE_FIELD, + VectorDataType.BYTE.getValue() + ) + ) + ); + } + + @SneakyThrows + public void testSearchWithInvalidSearchVectorType() { + createKnnIndexMappingWithLuceneEngine(2, SpaceType.L2, VectorDataType.FLOAT.getValue()); + ingestL2FloatTestData(); + Request request = new Request("POST", String.format("/%s/_search", INDEX_NAME)); + List invalidTypeQueryVector = new ArrayList<>(); + invalidTypeQueryVector.add(1.5); + invalidTypeQueryVector.add(2.5); + invalidTypeQueryVector.add("a"); + invalidTypeQueryVector.add(null); + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("query") + .startObject("knn") + .startObject(FIELD_NAME) + .field("vector", invalidTypeQueryVector) + .field("k", 4) + .endObject() + .endObject() + .endObject() + .endObject(); + request.setJsonEntity(builder.toString()); + + ResponseException ex = expectThrows(ResponseException.class, () -> client().performRequest(request)); + assertEquals(400, ex.getResponse().getStatusLine().getStatusCode()); + assertTrue(ex.getMessage(), ex.getMessage().contains("[knn] failed to parse field [vector]")); + } + + @SneakyThrows + public void testSearchWithMissingQueryVector() { + createKnnIndexMappingWithLuceneEngine(2, SpaceType.L2, VectorDataType.FLOAT.getValue()); + ingestL2FloatTestData(); + Request request = new Request("POST", String.format("/%s/_search", INDEX_NAME)); + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("query") + .startObject("knn") + .startObject(FIELD_NAME) + .field("k", 4) + .endObject() + .endObject() + .endObject() + .endObject(); + request.setJsonEntity(builder.toString()); + + ResponseException ex = expectThrows(ResponseException.class, () -> client().performRequest(request)); + assertEquals(400, ex.getResponse().getStatusLine().getStatusCode()); + assertTrue(ex.getMessage().contains("[knn] requires query vector")); + } + + @SneakyThrows + public void testAddDocWithByteVectorUsingFaissEngine() { + createKnnIndexMappingWithFaissEngine(2, SpaceType.L2, VectorDataType.BYTE.getValue()); + Byte[] vector = { 6, 6 }; + addKnnDoc(INDEX_NAME, DOC_ID, FIELD_NAME, vector); + + refreshAllIndices(); + assertEquals(1, getDocCount(INDEX_NAME)); + } + + @SneakyThrows + public void testUpdateDocWithByteVectorUsingFaissEngine() { + createKnnIndexMappingWithFaissEngine(2, SpaceType.L2, VectorDataType.BYTE.getValue()); + Byte[] vector = { -36, 78 }; + addKnnDoc(INDEX_NAME, DOC_ID, FIELD_NAME, vector); + + Byte[] updatedVector = { 89, -8 }; + updateKnnDoc(INDEX_NAME, DOC_ID, FIELD_NAME, updatedVector); + + refreshAllIndices(); + assertEquals(1, getDocCount(INDEX_NAME)); + } + + @SneakyThrows + public void testDeleteDocWithByteVectorUsingFaissEngine() { + createKnnIndexMappingWithFaissEngine(2, SpaceType.L2, VectorDataType.BYTE.getValue()); + Byte[] vector = { 35, -46 }; + addKnnDoc(INDEX_NAME, DOC_ID, FIELD_NAME, vector); + + deleteKnnDoc(INDEX_NAME, DOC_ID); + refreshAllIndices(); + + assertEquals(0, getDocCount(INDEX_NAME)); + } + + @SneakyThrows + public void testSearchWithByteVectorUsingFaissEngine() { + createKnnIndexMappingWithFaissEngine(2, SpaceType.L2, VectorDataType.BYTE.getValue()); + ingestL2ByteTestData(); + + Byte[] queryVector = { 1, 1 }; + Response response = searchKNNIndex(INDEX_NAME, new KNNQueryBuilder(FIELD_NAME, convertByteToFloatArray(queryVector), 4), 4); + + validateL2SearchResults(response); + } + + @SneakyThrows + public void testInvalidVectorDataUsingFaissEngine() { + createKnnIndexMappingWithFaissEngine(2, SpaceType.L2, VectorDataType.BYTE.getValue()); + Float[] vector = { -10.76f, 15.89f }; + + ResponseException ex = expectThrows(ResponseException.class, () -> addKnnDoc(INDEX_NAME, DOC_ID, FIELD_NAME, vector)); + assertTrue( + ex.getMessage() + .contains( + String.format( + Locale.ROOT, + "[%s] field was set as [%s] in index mapping. But, KNN vector values are floats instead of byte integers", + VECTOR_DATA_TYPE_FIELD, + VectorDataType.BYTE.getValue() + ) + ) + ); + } + + // Create an index with byte vector data_type and add a doc with values out of byte range which should throw exception + @SneakyThrows + public void testInvalidByteVectorRangeUsingFaissEngine() { + createKnnIndexMappingWithFaissEngine(2, SpaceType.L2, VectorDataType.BYTE.getValue()); + Float[] vector = { -1000f, 155f }; + + ResponseException ex = expectThrows(ResponseException.class, () -> addKnnDoc(INDEX_NAME, DOC_ID, FIELD_NAME, vector)); + assertTrue( + ex.getMessage() + .contains( + String.format( + Locale.ROOT, + "[%s] field was set as [%s] in index mapping. But, KNN vector values are not within in the byte range [%d, %d]", + VECTOR_DATA_TYPE_FIELD, + VectorDataType.BYTE.getValue(), + Byte.MIN_VALUE, + Byte.MAX_VALUE + ) + ) + ); + } + + // Create an index with byte vector data_type using faiss engine with an encoder which should throw an exception + @SneakyThrows + public void testByteVectorDataTypeWithFaissEngineUsingEncoderThrowsException() { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES_FIELD) + .startObject(FIELD_NAME) + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION, 2) + .field(VECTOR_DATA_TYPE_FIELD, VectorDataType.BYTE.getValue()) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, METHOD_HNSW) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2) + .field(KNNConstants.KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(PARAMETERS) + .field(KNNConstants.METHOD_PARAMETER_M, M) + .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, EF_CONSTRUCTION) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_SQ) + .startObject(PARAMETERS) + .field(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + String mapping = builder.toString(); + expectThrows(ResponseException.class, () -> createKnnIndex(INDEX_NAME, mapping)); + } + + public void testDocValuesWithByteVectorDataTypeFaissEngine() throws Exception { + createKnnIndexMappingWithFaissEngine(2, SpaceType.L2, VectorDataType.BYTE.getValue()); + ingestL2ByteTestData(); + + Byte[] queryVector = { 1, 1 }; + Request request = createScriptQueryRequest(queryVector, SpaceType.L2.getValue(), MATCH_ALL_QUERY_BUILDER); + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + + validateL2SearchResults(response); + } + + @SneakyThrows + public void testIVFByteVector_whenIndexedAndQueried_thenSucceed() { + + String modelId = "test-model-ivf-byte"; + int dimension = 2; + + // Add training data + String trainIndexMapping = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", dimension) + .field("data_type", VectorDataType.BYTE.getValue()) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, KNNEngine.FAISS.getName()) + .endObject() + .endObject() + .endObject() + .endObject() + .toString(); + createKnnIndex(INDEX_NAME, trainIndexMapping); + + int trainingDataCount = 100; + bulkIngestRandomByteVectors(INDEX_NAME, FIELD_NAME, trainingDataCount, dimension); + + XContentBuilder trainModelXContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TRAIN_INDEX_PARAMETER, INDEX_NAME) + .field(TRAIN_FIELD_PARAMETER, FIELD_NAME) + .field(DIMENSION, dimension) + .field(MODEL_DESCRIPTION, "My model description") + .field(VECTOR_DATA_TYPE_FIELD, VectorDataType.BYTE.getValue()) + .field( + KNN_METHOD, + Map.of( + NAME, + METHOD_IVF, + KNN_ENGINE, + FAISS_NAME, + METHOD_PARAMETER_SPACE_TYPE, + SpaceType.L2.getValue(), + PARAMETERS, + Map.of(METHOD_PARAMETER_NLIST, 4, METHOD_PARAMETER_NPROBES, 4) + ) + ) + .endObject(); + + trainModel(modelId, trainModelXContentBuilder); + + // Make sure training succeeds after 30 seconds + assertTrainingSucceeds(modelId, 30, 1000); + + // Create knn index from model + String indexName = "test-index-name-ivf-byte"; + String indexMapping = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field(MODEL_ID, modelId) + .endObject() + .endObject() + .endObject() + .toString(); + + createKnnIndex(indexName, getKNNDefaultIndexSettings(), indexMapping); + + Byte[] b1 = { 6, 6 }; + addKnnDoc(indexName, "1", FIELD_NAME, b1); + Byte[] b2 = { 2, 2 }; + addKnnDoc(indexName, "2", FIELD_NAME, b2); + Byte[] b3 = { 4, 4 }; + addKnnDoc(indexName, "3", FIELD_NAME, b3); + Byte[] b4 = { 3, 3 }; + addKnnDoc(indexName, "4", FIELD_NAME, b4); + + Byte[] queryVector = { 1, 1 }; + Response response = searchKNNIndex(indexName, new KNNQueryBuilder(FIELD_NAME, convertByteToFloatArray(queryVector), 4), 4); + + validateL2SearchResults(response); + deleteKNNIndex(indexName); + Thread.sleep(45 * 1000); + deleteModel(modelId); + } + + @SneakyThrows + private void ingestL2ByteTestData() { + Byte[] b1 = { 6, 6 }; + addKnnDoc(INDEX_NAME, "1", FIELD_NAME, b1); + + Byte[] b2 = { 2, 2 }; + addKnnDoc(INDEX_NAME, "2", FIELD_NAME, b2); + + Byte[] b3 = { 4, 4 }; + addKnnDoc(INDEX_NAME, "3", FIELD_NAME, b3); + + Byte[] b4 = { 3, 3 }; + addKnnDoc(INDEX_NAME, "4", FIELD_NAME, b4); + } + + @SneakyThrows + private void ingestL2FloatTestData() { + Float[] f1 = { 6.0f, 6.0f }; + addKnnDoc(INDEX_NAME, "1", FIELD_NAME, f1); + + Float[] f2 = { 2.0f, 2.0f }; + addKnnDoc(INDEX_NAME, "2", FIELD_NAME, f2); + + Float[] f3 = { 4.0f, 4.0f }; + addKnnDoc(INDEX_NAME, "3", FIELD_NAME, f3); + + Float[] f4 = { 3.0f, 3.0f }; + addKnnDoc(INDEX_NAME, "4", FIELD_NAME, f4); + } + + private void createKnnIndexMappingWithNmslibEngine(int dimension, SpaceType spaceType, String vectorDataType) throws Exception { + createKnnIndexMappingWithCustomEngine(dimension, spaceType, vectorDataType, KNNEngine.NMSLIB.getName()); + } + + private void createKnnIndexMappingWithLuceneEngine(int dimension, SpaceType spaceType, String vectorDataType) throws Exception { + createKnnIndexMappingWithCustomEngine(dimension, spaceType, vectorDataType, KNNEngine.LUCENE.getName()); + } + + private void createKnnIndexMappingWithFaissEngine(int dimension, SpaceType spaceType, String vectorDataType) throws Exception { + createKnnIndexMappingWithCustomEngine(dimension, spaceType, vectorDataType, KNNEngine.FAISS.getName()); + } + + private void createKnnIndexMappingWithCustomEngine(int dimension, SpaceType spaceType, String vectorDataType, String engine) + throws Exception { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES_FIELD) + .startObject(FIELD_NAME) + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION, dimension) + .field(VECTOR_DATA_TYPE_FIELD, vectorDataType) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, METHOD_HNSW) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNNConstants.KNN_ENGINE, engine) + .startObject(PARAMETERS) + .field(KNNConstants.METHOD_PARAMETER_M, M) + .field(KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION, EF_CONSTRUCTION) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + String mapping = builder.toString(); + createKnnIndex(INDEX_NAME, mapping); + } + + private void createKnnIndexMappingForScripting(int dimension, String vectorDataType) throws Exception { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES_FIELD) + .startObject(FIELD_NAME) + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION, dimension) + .field(VECTOR_DATA_TYPE_FIELD, vectorDataType) + .endObject() + .endObject() + .endObject(); + + String mapping = builder.toString(); + createKnnIndex(INDEX_NAME, Settings.EMPTY, mapping); + } + + @SneakyThrows + private Request createScriptQueryRequest(Byte[] queryVector, String spaceType, QueryBuilder qb) { + Map params = new HashMap<>(); + params.put("field", FIELD_NAME); + params.put("query_value", queryVector); + params.put("space_type", spaceType); + return constructKNNScriptQueryRequest(INDEX_NAME, qb, params); + } + + @SneakyThrows + private Request createScriptQueryRequest(Float[] queryVector, String spaceType, QueryBuilder qb) { + Map params = new HashMap<>(); + params.put("field", FIELD_NAME); + params.put("query_value", queryVector); + params.put("space_type", spaceType); + return constructKNNScriptQueryRequest(INDEX_NAME, qb, params); + } + + @SneakyThrows + private void validateL2SearchResults(Response response) { + + List results = parseSearchResponse(EntityUtils.toString(response.getEntity()), FIELD_NAME); + + assertEquals(4, results.size()); + + String[] expectedDocIDs = { "2", "4", "3", "1" }; + for (int i = 0; i < results.size(); i++) { + assertEquals(expectedDocIDs[i], results.get(i).getDocId()); + } + } + + private float[] convertByteToFloatArray(Byte[] arr) { + float[] floatArray = new float[arr.length]; + for (int i = 0; i < arr.length; i++) { + floatArray[i] = arr[i]; + } + return floatArray; + } +} diff --git a/src/test/java/org/opensearch/knn/index/VectorDataTypeTests.java b/src/test/java/org/opensearch/knn/index/VectorDataTypeTests.java new file mode 100644 index 000000000..f760a6e88 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/VectorDataTypeTests.java @@ -0,0 +1,126 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index; + +import lombok.SneakyThrows; +import org.apache.lucene.document.BinaryDocValuesField; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.FieldType; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.index.VectorSimilarityFunction; +import org.apache.lucene.store.Directory; +import org.apache.lucene.tests.analysis.MockAnalyzer; +import org.apache.lucene.util.BytesRef; +import org.junit.Assert; +import org.opensearch.knn.KNNTestCase; + +import java.io.IOException; + +public class VectorDataTypeTests extends KNNTestCase { + + private static final String MOCK_FLOAT_INDEX_FIELD_NAME = "test-float-index-field-name"; + private static final String MOCK_BYTE_INDEX_FIELD_NAME = "test-byte-index-field-name"; + private static final float[] SAMPLE_FLOAT_VECTOR_DATA = new float[] { 10.0f, 25.0f }; + private static final byte[] SAMPLE_BYTE_VECTOR_DATA = new byte[] { 10, 25 }; + private Directory directory; + private DirectoryReader reader; + + @SneakyThrows + public void testGetDocValuesWithFloatVectorDataType() { + KNNVectorScriptDocValues scriptDocValues = getKNNFloatVectorScriptDocValues(); + + scriptDocValues.setNextDocId(0); + Assert.assertArrayEquals(SAMPLE_FLOAT_VECTOR_DATA, scriptDocValues.getValue(), 0.1f); + + reader.close(); + directory.close(); + } + + @SneakyThrows + public void testGetDocValuesWithByteVectorDataType() { + KNNVectorScriptDocValues scriptDocValues = getKNNByteVectorScriptDocValues(); + + scriptDocValues.setNextDocId(0); + Assert.assertArrayEquals(SAMPLE_FLOAT_VECTOR_DATA, scriptDocValues.getValue(), 0.1f); + + reader.close(); + directory.close(); + } + + @SneakyThrows + private KNNVectorScriptDocValues getKNNFloatVectorScriptDocValues() { + directory = newDirectory(); + createKNNFloatVectorDocument(directory); + reader = DirectoryReader.open(directory); + LeafReaderContext leafReaderContext = reader.getContext().leaves().get(0); + return KNNVectorScriptDocValues.create( + leafReaderContext.reader().getBinaryDocValues(VectorDataTypeTests.MOCK_FLOAT_INDEX_FIELD_NAME), + VectorDataTypeTests.MOCK_FLOAT_INDEX_FIELD_NAME, + VectorDataType.FLOAT + ); + } + + @SneakyThrows + private KNNVectorScriptDocValues getKNNByteVectorScriptDocValues() { + directory = newDirectory(); + createKNNByteVectorDocument(directory); + reader = DirectoryReader.open(directory); + LeafReaderContext leafReaderContext = reader.getContext().leaves().get(0); + return KNNVectorScriptDocValues.create( + leafReaderContext.reader().getBinaryDocValues(VectorDataTypeTests.MOCK_BYTE_INDEX_FIELD_NAME), + VectorDataTypeTests.MOCK_BYTE_INDEX_FIELD_NAME, + VectorDataType.BYTE + ); + } + + private void createKNNFloatVectorDocument(Directory directory) throws IOException { + IndexWriterConfig conf = newIndexWriterConfig(new MockAnalyzer(random())); + IndexWriter writer = new IndexWriter(directory, conf); + Document knnDocument = new Document(); + knnDocument.add( + new BinaryDocValuesField( + MOCK_FLOAT_INDEX_FIELD_NAME, + new VectorField(MOCK_FLOAT_INDEX_FIELD_NAME, SAMPLE_FLOAT_VECTOR_DATA, new FieldType()).binaryValue() + ) + ); + writer.addDocument(knnDocument); + writer.commit(); + writer.close(); + } + + private void createKNNByteVectorDocument(Directory directory) throws IOException { + IndexWriterConfig conf = newIndexWriterConfig(new MockAnalyzer(random())); + IndexWriter writer = new IndexWriter(directory, conf); + Document knnDocument = new Document(); + knnDocument.add( + new BinaryDocValuesField( + MOCK_BYTE_INDEX_FIELD_NAME, + new VectorField(MOCK_BYTE_INDEX_FIELD_NAME, SAMPLE_BYTE_VECTOR_DATA, new FieldType()).binaryValue() + ) + ); + writer.addDocument(knnDocument); + writer.commit(); + writer.close(); + } + + public void testCreateKnnVectorFieldType_whenBinary_thenException() { + Exception ex = expectThrows( + IllegalStateException.class, + () -> VectorDataType.BINARY.createKnnVectorFieldType(1, VectorSimilarityFunction.EUCLIDEAN) + ); + assertTrue(ex.getMessage().contains("Unsupported method")); + } + + public void testGetVectorFromBytesRef_whenBinary_thenException() { + byte[] vector = { 1, 2, 3 }; + float[] expected = { 1, 2, 3 }; + BytesRef bytesRef = new BytesRef(vector); + assertArrayEquals(expected, VectorDataType.BINARY.getVectorFromBytesRef(bytesRef), 0.01f); + } +} diff --git a/src/test/java/org/opensearch/knn/index/VectorQueryTypeTests.java b/src/test/java/org/opensearch/knn/index/VectorQueryTypeTests.java new file mode 100644 index 000000000..d0fac3f59 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/VectorQueryTypeTests.java @@ -0,0 +1,30 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index; + +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.plugin.stats.KNNCounter; + +public class VectorQueryTypeTests extends KNNTestCase { + + public void testGetQueryStatCounter() { + assertEquals(KNNCounter.KNN_QUERY_REQUESTS, VectorQueryType.K.getQueryStatCounter()); + assertEquals(KNNCounter.MIN_SCORE_QUERY_REQUESTS, VectorQueryType.MIN_SCORE.getQueryStatCounter()); + assertEquals(KNNCounter.MAX_DISTANCE_QUERY_REQUESTS, VectorQueryType.MAX_DISTANCE.getQueryStatCounter()); + } + + public void testGetQueryWithFilterStatCounter() { + assertEquals(KNNCounter.KNN_QUERY_WITH_FILTER_REQUESTS, VectorQueryType.K.getQueryWithFilterStatCounter()); + assertEquals(KNNCounter.MIN_SCORE_QUERY_WITH_FILTER_REQUESTS, VectorQueryType.MIN_SCORE.getQueryWithFilterStatCounter()); + assertEquals(KNNCounter.MAX_DISTANCE_QUERY_WITH_FILTER_REQUESTS, VectorQueryType.MAX_DISTANCE.getQueryWithFilterStatCounter()); + } +} diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80BinaryDocValuesTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80BinaryDocValuesTests.java index 620559867..727cb8e6e 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80BinaryDocValuesTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80BinaryDocValuesTests.java @@ -10,7 +10,7 @@ import org.apache.lucene.index.DocIDMerger; import org.apache.lucene.index.MergeState; import org.opensearch.knn.KNNTestCase; -import org.opensearch.knn.index.codec.KNNCodecTestUtil; +import org.opensearch.knn.index.vectorvalues.TestVectorValues; import org.opensearch.knn.index.codec.util.BinaryDocValuesSub; import java.io.IOException; @@ -30,7 +30,7 @@ public void testNextDoc() throws IOException { public int get(int docID) { return expectedDoc; } - }, new KNNCodecTestUtil.ConstantVectorBinaryDocValues(10, 128, 1.0f)); + }, new TestVectorValues.ConstantVectorBinaryDocValues(10, 128, 1.0f)); DocIDMerger docIDMerger = DocIDMerger.of(ImmutableList.of(sub), false); KNN80BinaryDocValues knn80BinaryDocValues = new KNN80BinaryDocValues(docIDMerger); @@ -53,7 +53,7 @@ public void testCost() { } public void testBinaryValue() throws IOException { - BinaryDocValues binaryDocValues = new KNNCodecTestUtil.ConstantVectorBinaryDocValues(10, 128, 1.0f); + BinaryDocValues binaryDocValues = new TestVectorValues.ConstantVectorBinaryDocValues(10, 128, 1.0f); BinaryDocValuesSub sub = new BinaryDocValuesSub(new MergeState.DocMap() { @Override public int get(int docID) { diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundFormatTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundFormatTests.java index c370755ac..6001a9729 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundFormatTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80CompoundFormatTests.java @@ -18,7 +18,7 @@ import org.opensearch.knn.KNNTestCase; import org.opensearch.knn.index.codec.KNN87Codec.KNN87Codec; import org.opensearch.knn.index.codec.KNNCodecTestUtil; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.engine.KNNEngine; import java.io.IOException; import java.util.Arrays; @@ -49,7 +49,9 @@ public void testGetCompoundReader() throws IOException { CompoundFormat delegate = mock(CompoundFormat.class); when(delegate.getCompoundReader(null, null, null)).thenReturn(dir); KNN80CompoundFormat knn80CompoundFormat = new KNN80CompoundFormat(delegate); - assertEquals(dir, knn80CompoundFormat.getCompoundReader(null, null, null)); + CompoundDirectory knnDir = knn80CompoundFormat.getCompoundReader(null, null, null); + assertTrue(knnDir instanceof KNN80CompoundDirectory); + assertEquals(dir, ((KNN80CompoundDirectory) knnDir).getDelegate()); } public void testWrite() throws IOException { diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesConsumerTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesConsumerTests.java index adbaf74d7..c11bc765f 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesConsumerTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesConsumerTests.java @@ -18,52 +18,69 @@ import org.apache.lucene.store.IOContext; import org.junit.AfterClass; import org.junit.BeforeClass; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.opensearch.Version; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.Strings; import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.knn.KNNTestCase; import org.opensearch.knn.common.KNNConstants; -import org.opensearch.knn.index.KNNMethodContext; -import org.opensearch.knn.index.KNNVectorFieldMapper; -import org.opensearch.knn.index.MethodComponentContext; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; +import org.opensearch.knn.index.vectorvalues.TestVectorValues; +import org.opensearch.knn.index.mapper.KNNVectorFieldMapper; +import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.SpaceType; import org.opensearch.knn.index.codec.KNN87Codec.KNN87Codec; import org.opensearch.knn.index.codec.KNNCodecTestUtil; import org.opensearch.knn.index.codec.util.KNNCodecUtil; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.indices.Model; import org.opensearch.knn.indices.ModelCache; import org.opensearch.knn.indices.ModelDao; import org.opensearch.knn.indices.ModelMetadata; import org.opensearch.knn.indices.ModelState; +import org.opensearch.knn.jni.JNICommons; import org.opensearch.knn.jni.JNIService; import org.opensearch.knn.plugin.stats.KNNCounter; +import org.opensearch.knn.plugin.stats.KNNGraphValue; import java.io.IOException; import java.util.Map; import java.util.concurrent.ExecutionException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.opensearch.knn.common.KNNConstants.INDEX_DESCRIPTION_PARAMETER; import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_SEARCH; import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_M; import static org.opensearch.knn.common.KNNConstants.MODEL_ID; +import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; import static org.opensearch.knn.index.KNNSettings.MODEL_CACHE_SIZE_LIMIT_SETTING; +import static org.opensearch.knn.index.codec.KNNCodecTestUtil.assertBinaryIndexLoadableByEngine; import static org.opensearch.knn.index.codec.KNNCodecTestUtil.assertFileInCorrectLocation; import static org.opensearch.knn.index.codec.KNNCodecTestUtil.assertLoadableByEngine; import static org.opensearch.knn.index.codec.KNNCodecTestUtil.assertValidFooter; -import static org.opensearch.knn.index.codec.KNNCodecTestUtil.getRandomVectors; -import static org.opensearch.knn.index.codec.KNNCodecTestUtil.RandomVectorDocValuesProducer; public class KNN80DocValuesConsumerTests extends KNNTestCase { + private static final int EF_SEARCH = 10; + private static final Map HNSW_METHODPARAMETERS = Map.of(METHOD_PARAMETER_EF_SEARCH, EF_SEARCH); + private static Directory directory; private static Codec codec; @@ -92,7 +109,7 @@ public void testAddBinaryField_withKNN() throws IOException { KNN80DocValuesConsumer knn80DocValuesConsumer = new KNN80DocValuesConsumer(delegate, null) { @Override - public void addKNNBinaryField(FieldInfo field, DocValuesProducer valuesProducer) { + public void addKNNBinaryField(FieldInfo field, DocValuesProducer valuesProducer, boolean isMerge) { called[0] = true; } }; @@ -112,11 +129,24 @@ public void testAddBinaryField_withoutKNN() throws IOException { DocValuesConsumer delegate = mock(DocValuesConsumer.class); doNothing().when(delegate).addBinaryField(fieldInfo, docValuesProducer); + String segmentName = String.format("test_segment%s", randomAlphaOfLength(4)); + int docsInSegment = 100; + + SegmentInfo segmentInfo = KNNCodecTestUtil.segmentInfoBuilder() + .directory(directory) + .segmentName(segmentName) + .docsInSegment(docsInSegment) + .codec(codec) + .build(); + + FieldInfos fieldInfos = mock(FieldInfos.class); + SegmentWriteState state = new SegmentWriteState(null, directory, segmentInfo, fieldInfos, null, IOContext.DEFAULT); + final boolean[] called = { false }; - KNN80DocValuesConsumer knn80DocValuesConsumer = new KNN80DocValuesConsumer(delegate, null) { + KNN80DocValuesConsumer knn80DocValuesConsumer = new KNN80DocValuesConsumer(delegate, state) { @Override - public void addKNNBinaryField(FieldInfo field, DocValuesProducer valuesProducer) { + public void addKNNBinaryField(FieldInfo field, DocValuesProducer valuesProducer, boolean isMerge) { called[0] = true; } }; @@ -129,11 +159,33 @@ public void addKNNBinaryField(FieldInfo field, DocValuesProducer valuesProducer) public void testAddKNNBinaryField_noVectors() throws IOException { // When there are no new vectors, no more graph index requests should be added - RandomVectorDocValuesProducer randomVectorDocValuesProducer = new RandomVectorDocValuesProducer(0, 128); + TestVectorValues.RandomVectorDocValuesProducer randomVectorDocValuesProducer = new TestVectorValues.RandomVectorDocValuesProducer( + 0, + 128 + ); Long initialGraphIndexRequests = KNNCounter.GRAPH_INDEX_REQUESTS.getCount(); - KNN80DocValuesConsumer knn80DocValuesConsumer = new KNN80DocValuesConsumer(null, null); - knn80DocValuesConsumer.addKNNBinaryField(null, randomVectorDocValuesProducer); + Long initialMergeOperations = KNNGraphValue.MERGE_TOTAL_OPERATIONS.getValue(); + Long initialMergeSize = KNNGraphValue.MERGE_TOTAL_SIZE_IN_BYTES.getValue(); + Long initialMergeDocs = KNNGraphValue.MERGE_TOTAL_DOCS.getValue(); + String segmentName = String.format("test_segment%s", randomAlphaOfLength(4)); + int docsInSegment = 100; + + SegmentInfo segmentInfo = KNNCodecTestUtil.segmentInfoBuilder() + .directory(directory) + .segmentName(segmentName) + .docsInSegment(docsInSegment) + .codec(codec) + .build(); + + FieldInfos fieldInfos = mock(FieldInfos.class); + SegmentWriteState state = new SegmentWriteState(null, directory, segmentInfo, fieldInfos, null, IOContext.DEFAULT); + KNN80DocValuesConsumer knn80DocValuesConsumer = new KNN80DocValuesConsumer(null, state); + FieldInfo fieldInfo = KNNCodecTestUtil.FieldInfoBuilder.builder("test-field").build(); + knn80DocValuesConsumer.addKNNBinaryField(fieldInfo, randomVectorDocValuesProducer, true); assertEquals(initialGraphIndexRequests, KNNCounter.GRAPH_INDEX_REQUESTS.getCount()); + assertEquals(initialMergeOperations, KNNGraphValue.MERGE_TOTAL_OPERATIONS.getValue()); + assertEquals(initialMergeSize, KNNGraphValue.MERGE_TOTAL_SIZE_IN_BYTES.getValue()); + assertEquals(initialMergeDocs, KNNGraphValue.MERGE_TOTAL_DOCS.getValue()); } public void testAddKNNBinaryField_fromScratch_nmslibCurrent() throws IOException { @@ -153,13 +205,19 @@ public void testAddKNNBinaryField_fromScratch_nmslibCurrent() throws IOException .codec(codec) .build(); + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .versionCreated(Version.CURRENT) + .build(); KNNMethodContext knnMethodContext = new KNNMethodContext( knnEngine, spaceType, new MethodComponentContext(METHOD_HNSW, ImmutableMap.of(METHOD_PARAMETER_M, 16, METHOD_PARAMETER_EF_CONSTRUCTION, 512)) ); - String parameterString = Strings.toString(XContentFactory.jsonBuilder().map(knnEngine.getMethodAsMap(knnMethodContext))); + String parameterString = XContentFactory.jsonBuilder() + .map(knnEngine.getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext).getLibraryParameters()) + .toString(); FieldInfo[] fieldInfoArray = new FieldInfo[] { KNNCodecTestUtil.FieldInfoBuilder.builder(fieldName) @@ -172,25 +230,30 @@ public void testAddKNNBinaryField_fromScratch_nmslibCurrent() throws IOException FieldInfos fieldInfos = new FieldInfos(fieldInfoArray); SegmentWriteState state = new SegmentWriteState(null, directory, segmentInfo, fieldInfos, null, IOContext.DEFAULT); + long initialMergeOperations = KNNGraphValue.MERGE_TOTAL_OPERATIONS.getValue(); + // Add documents to the field KNN80DocValuesConsumer knn80DocValuesConsumer = new KNN80DocValuesConsumer(null, state); - RandomVectorDocValuesProducer randomVectorDocValuesProducer = new RandomVectorDocValuesProducer(docsInSegment, dimension); - knn80DocValuesConsumer.addKNNBinaryField(fieldInfoArray[0], randomVectorDocValuesProducer); + TestVectorValues.RandomVectorDocValuesProducer randomVectorDocValuesProducer = new TestVectorValues.RandomVectorDocValuesProducer( + docsInSegment, + dimension + ); + knn80DocValuesConsumer.addKNNBinaryField(fieldInfoArray[0], randomVectorDocValuesProducer, true); // The document should be created in the correct location - String expectedFile = KNNCodecUtil.buildEngineFileName( - segmentName, - knnEngine.getLatestBuildVersion(), - fieldName, - knnEngine.getExtension() - ); + String expectedFile = KNNCodecUtil.buildEngineFileName(segmentName, knnEngine.getVersion(), fieldName, knnEngine.getExtension()); assertFileInCorrectLocation(state, expectedFile); // The footer should be valid assertValidFooter(state.directory, expectedFile); // The document should be readable by nmslib - assertLoadableByEngine(state, expectedFile, knnEngine, spaceType, dimension); + assertLoadableByEngine(null, state, expectedFile, knnEngine, spaceType, dimension); + + // The graph creation statistics should be updated + assertEquals(1 + initialMergeOperations, (long) KNNGraphValue.MERGE_TOTAL_OPERATIONS.getValue()); + assertNotEquals(0, (long) KNNGraphValue.MERGE_TOTAL_DOCS.getValue()); + assertNotEquals(0, (long) KNNGraphValue.MERGE_TOTAL_SIZE_IN_BYTES.getValue()); } public void testAddKNNBinaryField_fromScratch_nmslibLegacy() throws IOException { @@ -216,30 +279,36 @@ public void testAddKNNBinaryField_fromScratch_nmslibLegacy() throws IOException .addAttribute(KNNConstants.HNSW_ALGO_EF_CONSTRUCTION, "512") .addAttribute(KNNConstants.HNSW_ALGO_M, "16") .addAttribute(KNNConstants.SPACE_TYPE, spaceType.getValue()) + .addAttribute(KNNConstants.KNN_ENGINE, knnEngine.getName()) .build() }; FieldInfos fieldInfos = new FieldInfos(fieldInfoArray); SegmentWriteState state = new SegmentWriteState(null, directory, segmentInfo, fieldInfos, null, IOContext.DEFAULT); + long initialMergeOperations = KNNGraphValue.MERGE_TOTAL_OPERATIONS.getValue(); + // Add documents to the field KNN80DocValuesConsumer knn80DocValuesConsumer = new KNN80DocValuesConsumer(null, state); - RandomVectorDocValuesProducer randomVectorDocValuesProducer = new RandomVectorDocValuesProducer(docsInSegment, dimension); - knn80DocValuesConsumer.addKNNBinaryField(fieldInfoArray[0], randomVectorDocValuesProducer); + TestVectorValues.RandomVectorDocValuesProducer randomVectorDocValuesProducer = new TestVectorValues.RandomVectorDocValuesProducer( + docsInSegment, + dimension + ); + knn80DocValuesConsumer.addKNNBinaryField(fieldInfoArray[0], randomVectorDocValuesProducer, true); // The document should be created in the correct location - String expectedFile = KNNCodecUtil.buildEngineFileName( - segmentName, - knnEngine.getLatestBuildVersion(), - fieldName, - knnEngine.getExtension() - ); + String expectedFile = KNNCodecUtil.buildEngineFileName(segmentName, knnEngine.getVersion(), fieldName, knnEngine.getExtension()); assertFileInCorrectLocation(state, expectedFile); // The footer should be valid assertValidFooter(state.directory, expectedFile); // The document should be readable by nmslib - assertLoadableByEngine(state, expectedFile, knnEngine, spaceType, dimension); + assertLoadableByEngine(null, state, expectedFile, knnEngine, spaceType, dimension); + + // The graph creation statistics should be updated + assertEquals(1 + initialMergeOperations, (long) KNNGraphValue.MERGE_TOTAL_OPERATIONS.getValue()); + assertNotEquals(0, (long) KNNGraphValue.MERGE_TOTAL_DOCS.getValue()); + assertNotEquals(0, (long) KNNGraphValue.MERGE_TOTAL_SIZE_IN_BYTES.getValue()); } public void testAddKNNBinaryField_fromScratch_faissCurrent() throws IOException { @@ -258,13 +327,19 @@ public void testAddKNNBinaryField_fromScratch_faissCurrent() throws IOException .codec(codec) .build(); + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .versionCreated(Version.CURRENT) + .build(); KNNMethodContext knnMethodContext = new KNNMethodContext( knnEngine, spaceType, new MethodComponentContext(METHOD_HNSW, ImmutableMap.of(METHOD_PARAMETER_M, 16, METHOD_PARAMETER_EF_CONSTRUCTION, 512)) ); - String parameterString = Strings.toString(XContentFactory.jsonBuilder().map(knnEngine.getMethodAsMap(knnMethodContext))); + String parameterString = XContentFactory.jsonBuilder() + .map(knnEngine.getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext).getLibraryParameters()) + .toString(); FieldInfo[] fieldInfoArray = new FieldInfo[] { KNNCodecTestUtil.FieldInfoBuilder.builder(fieldName) @@ -277,71 +352,40 @@ public void testAddKNNBinaryField_fromScratch_faissCurrent() throws IOException FieldInfos fieldInfos = new FieldInfos(fieldInfoArray); SegmentWriteState state = new SegmentWriteState(null, directory, segmentInfo, fieldInfos, null, IOContext.DEFAULT); + long initialRefreshOperations = KNNGraphValue.REFRESH_TOTAL_OPERATIONS.getValue(); + // Add documents to the field KNN80DocValuesConsumer knn80DocValuesConsumer = new KNN80DocValuesConsumer(null, state); - RandomVectorDocValuesProducer randomVectorDocValuesProducer = new RandomVectorDocValuesProducer(docsInSegment, dimension); - knn80DocValuesConsumer.addKNNBinaryField(fieldInfoArray[0], randomVectorDocValuesProducer); + TestVectorValues.RandomVectorDocValuesProducer randomVectorDocValuesProducer = new TestVectorValues.RandomVectorDocValuesProducer( + docsInSegment, + dimension + ); + knn80DocValuesConsumer.addKNNBinaryField(fieldInfoArray[0], randomVectorDocValuesProducer, false); // The document should be created in the correct location - String expectedFile = KNNCodecUtil.buildEngineFileName( - segmentName, - knnEngine.getLatestBuildVersion(), - fieldName, - knnEngine.getExtension() - ); + String expectedFile = KNNCodecUtil.buildEngineFileName(segmentName, knnEngine.getVersion(), fieldName, knnEngine.getExtension()); assertFileInCorrectLocation(state, expectedFile); // The footer should be valid assertValidFooter(state.directory, expectedFile); // The document should be readable by faiss - assertLoadableByEngine(state, expectedFile, knnEngine, spaceType, dimension); - } + assertLoadableByEngine(HNSW_METHODPARAMETERS, state, expectedFile, knnEngine, spaceType, dimension); - public void testAddKNNBinaryField_fromModel_faiss() throws IOException, ExecutionException, InterruptedException { - // Generate a trained faiss model - KNNEngine knnEngine = KNNEngine.FAISS; - SpaceType spaceType = SpaceType.INNER_PRODUCT; - int dimension = 16; - String modelId = "test-model-id"; - - float[][] trainingData = getRandomVectors(200, dimension); - long trainingPtr = JNIService.transferVectors(0, trainingData); - - Map parameters = ImmutableMap.of( - INDEX_DESCRIPTION_PARAMETER, - "IVF4,Flat", - KNNConstants.SPACE_TYPE, - SpaceType.L2.getValue() - ); - - byte[] modelBytes = JNIService.trainIndex(parameters, dimension, trainingPtr, knnEngine.getName()); - Model model = new Model( - new ModelMetadata(knnEngine, spaceType, dimension, ModelState.CREATED, "timestamp", "Empty description", ""), - modelBytes, - modelId - ); - JNIService.freeVectors(trainingPtr); - - // Setup the model cache to return the correct model - ModelDao modelDao = mock(ModelDao.class); - when(modelDao.get(modelId)).thenReturn(model); - ClusterService clusterService = mock(ClusterService.class); - when(clusterService.getSettings()).thenReturn(Settings.EMPTY); - - ClusterSettings clusterSettings = new ClusterSettings( - Settings.builder().put(MODEL_CACHE_SIZE_LIMIT_SETTING.getKey(), "10kb").build(), - ImmutableSet.of(MODEL_CACHE_SIZE_LIMIT_SETTING) - ); - - when(clusterService.getClusterSettings()).thenReturn(clusterSettings); - ModelCache.initialize(modelDao, clusterService); + // The graph creation statistics should be updated + assertEquals(1 + initialRefreshOperations, (long) KNNGraphValue.REFRESH_TOTAL_OPERATIONS.getValue()); + } - // Build the segment and field info + public void testAddKNNBinaryField_whenFaissBinary_thenAdded() throws IOException { String segmentName = String.format("test_segment%s", randomAlphaOfLength(4)); int docsInSegment = 100; String fieldName = String.format("test_field%s", randomAlphaOfLength(4)); + KNNEngine knnEngine = KNNEngine.FAISS; + SpaceType spaceType = SpaceType.HAMMING; + VectorDataType dataType = VectorDataType.BINARY; + int dimension = 16; + SegmentInfo segmentInfo = KNNCodecTestUtil.segmentInfoBuilder() .directory(directory) .segmentName(segmentName) @@ -349,34 +393,163 @@ public void testAddKNNBinaryField_fromModel_faiss() throws IOException, Executio .codec(codec) .build(); + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.BINARY) + .versionCreated(Version.CURRENT) + .build(); + KNNMethodContext knnMethodContext = new KNNMethodContext( + knnEngine, + spaceType, + new MethodComponentContext(METHOD_HNSW, ImmutableMap.of(METHOD_PARAMETER_M, 16, METHOD_PARAMETER_EF_CONSTRUCTION, 512)) + ); + + String parameterString = XContentFactory.jsonBuilder() + .map(knnEngine.getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext).getLibraryParameters()) + .toString(); + FieldInfo[] fieldInfoArray = new FieldInfo[] { KNNCodecTestUtil.FieldInfoBuilder.builder(fieldName) .addAttribute(KNNVectorFieldMapper.KNN_FIELD, "true") - .addAttribute(MODEL_ID, modelId) + .addAttribute(KNNConstants.KNN_ENGINE, knnEngine.getName()) + .addAttribute(KNNConstants.SPACE_TYPE, spaceType.getValue()) + .addAttribute(VECTOR_DATA_TYPE_FIELD, dataType.getValue()) + .addAttribute(KNNConstants.PARAMETERS, parameterString) .build() }; FieldInfos fieldInfos = new FieldInfos(fieldInfoArray); SegmentWriteState state = new SegmentWriteState(null, directory, segmentInfo, fieldInfos, null, IOContext.DEFAULT); + long initialMergeOperations = KNNGraphValue.MERGE_TOTAL_OPERATIONS.getValue(); + // Add documents to the field KNN80DocValuesConsumer knn80DocValuesConsumer = new KNN80DocValuesConsumer(null, state); - RandomVectorDocValuesProducer randomVectorDocValuesProducer = new RandomVectorDocValuesProducer(docsInSegment, dimension); - knn80DocValuesConsumer.addKNNBinaryField(fieldInfoArray[0], randomVectorDocValuesProducer); + TestVectorValues.RandomVectorDocValuesProducer randomVectorDocValuesProducer = new TestVectorValues.RandomVectorDocValuesProducer( + docsInSegment, + dimension + ); + knn80DocValuesConsumer.addKNNBinaryField(fieldInfoArray[0], randomVectorDocValuesProducer, true); // The document should be created in the correct location - String expectedFile = KNNCodecUtil.buildEngineFileName( - segmentName, - knnEngine.getLatestBuildVersion(), - fieldName, - knnEngine.getExtension() - ); + String expectedFile = KNNCodecUtil.buildEngineFileName(segmentName, knnEngine.getVersion(), fieldName, knnEngine.getExtension()); assertFileInCorrectLocation(state, expectedFile); // The footer should be valid assertValidFooter(state.directory, expectedFile); // The document should be readable by faiss - assertLoadableByEngine(state, expectedFile, knnEngine, spaceType, dimension); + assertBinaryIndexLoadableByEngine(state, expectedFile, knnEngine, spaceType, dimension, dataType); + + // The graph creation statistics should be updated + assertEquals(1 + initialMergeOperations, (long) KNNGraphValue.MERGE_TOTAL_OPERATIONS.getValue()); + assertNotEquals(0, (long) KNNGraphValue.MERGE_TOTAL_DOCS.getValue()); + assertNotEquals(0, (long) KNNGraphValue.MERGE_TOTAL_SIZE_IN_BYTES.getValue()); + } + + public void testAddKNNBinaryField_fromModel_faiss() throws IOException, ExecutionException, InterruptedException { + // Generate a trained faiss model + KNNEngine knnEngine = KNNEngine.FAISS; + SpaceType spaceType = SpaceType.INNER_PRODUCT; + int dimension = 16; + String modelId = "test-model-id"; + + float[][] trainingData = TestVectorValues.getRandomVectors(200, dimension); + long trainingPtr = JNIService.transferVectors(0, trainingData); + + Map parameters = ImmutableMap.of( + INDEX_DESCRIPTION_PARAMETER, + "IVF4,Flat", + KNNConstants.SPACE_TYPE, + SpaceType.L2.getValue() + ); + + byte[] modelBytes = JNIService.trainIndex(parameters, dimension, trainingPtr, knnEngine); + ModelMetadata modelMetadata = new ModelMetadata( + knnEngine, + spaceType, + dimension, + ModelState.CREATED, + "timestamp", + "Empty description", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.FLOAT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.V_EMPTY + ); + Model model = new Model(modelMetadata, modelBytes, modelId); + JNICommons.freeVectorData(trainingPtr); + + try (MockedStatic modelDaoMockedStatic = Mockito.mockStatic(ModelDao.OpenSearchKNNModelDao.class)) { + // Setup the model cache to return the correct model + ModelDao.OpenSearchKNNModelDao modelDao = mock(ModelDao.OpenSearchKNNModelDao.class); + when(modelDao.get(modelId)).thenReturn(model); + when(modelDao.getMetadata(modelId)).thenReturn(modelMetadata); + + modelDaoMockedStatic.when(ModelDao.OpenSearchKNNModelDao::getInstance).thenReturn(modelDao); + + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.getSettings()).thenReturn(Settings.EMPTY); + + ClusterSettings clusterSettings = new ClusterSettings( + Settings.builder().put(MODEL_CACHE_SIZE_LIMIT_SETTING.getKey(), "10kb").build(), + ImmutableSet.of(MODEL_CACHE_SIZE_LIMIT_SETTING) + ); + + when(clusterService.getClusterSettings()).thenReturn(clusterSettings); + ModelCache.initialize(modelDao, clusterService); + + // Build the segment and field info + String segmentName = String.format("test_segment%s", randomAlphaOfLength(4)); + int docsInSegment = 100; + String fieldName = String.format("test_field%s", randomAlphaOfLength(4)); + + SegmentInfo segmentInfo = KNNCodecTestUtil.segmentInfoBuilder() + .directory(directory) + .segmentName(segmentName) + .docsInSegment(docsInSegment) + .codec(codec) + .build(); + + FieldInfo[] fieldInfoArray = new FieldInfo[] { + KNNCodecTestUtil.FieldInfoBuilder.builder(fieldName) + .addAttribute(KNNVectorFieldMapper.KNN_FIELD, "true") + .addAttribute(MODEL_ID, modelId) + .build() }; + + FieldInfos fieldInfos = new FieldInfos(fieldInfoArray); + SegmentWriteState state = new SegmentWriteState(null, directory, segmentInfo, fieldInfos, null, IOContext.DEFAULT); + + long initialMergeOperations = KNNGraphValue.MERGE_TOTAL_OPERATIONS.getValue(); + + // Add documents to the field + KNN80DocValuesConsumer knn80DocValuesConsumer = new KNN80DocValuesConsumer(null, state); + TestVectorValues.RandomVectorDocValuesProducer randomVectorDocValuesProducer = + new TestVectorValues.RandomVectorDocValuesProducer(docsInSegment, dimension); + knn80DocValuesConsumer.addKNNBinaryField(fieldInfoArray[0], randomVectorDocValuesProducer, true); + + // The document should be created in the correct location + String expectedFile = KNNCodecUtil.buildEngineFileName( + segmentName, + knnEngine.getVersion(), + fieldName, + knnEngine.getExtension() + ); + assertFileInCorrectLocation(state, expectedFile); + + // The footer should be valid + assertValidFooter(state.directory, expectedFile); + + // The document should be readable by faiss + assertLoadableByEngine(HNSW_METHODPARAMETERS, state, expectedFile, knnEngine, spaceType, dimension); + + // The graph creation statistics should be updated + assertEquals(1 + initialMergeOperations, (long) KNNGraphValue.MERGE_TOTAL_OPERATIONS.getValue()); + assertNotEquals(0, (long) KNNGraphValue.MERGE_TOTAL_DOCS.getValue()); + assertNotEquals(0, (long) KNNGraphValue.MERGE_TOTAL_SIZE_IN_BYTES.getValue()); + } + } public void testMerge_exception() throws IOException { @@ -429,4 +602,21 @@ public void testClose() throws IOException { verify(delegate, times(1)).close(); } + public void testAddBinaryField_luceneEngine_noInvocations_addKNNBinary() throws IOException { + var fieldInfo = KNNCodecTestUtil.FieldInfoBuilder.builder("test-field") + .addAttribute(KNNVectorFieldMapper.KNN_FIELD, "true") + .addAttribute(KNNConstants.KNN_ENGINE, KNNEngine.LUCENE.getName()) + .build(); + DocValuesProducer docValuesProducer = mock(DocValuesProducer.class); + + var delegate = mock(DocValuesConsumer.class); + doNothing().when(delegate).addBinaryField(fieldInfo, docValuesProducer); + + var knn80DocValuesConsumer = spy(new KNN80DocValuesConsumer(delegate, null)); + + knn80DocValuesConsumer.addBinaryField(fieldInfo, docValuesProducer); + + verify(delegate, times(1)).addBinaryField(fieldInfo, docValuesProducer); + verify(knn80DocValuesConsumer, never()).addKNNBinaryField(any(), any(), eq(false)); + } } diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducerTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducerTests.java new file mode 100644 index 000000000..6b18c51c3 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/codec/KNN80Codec/KNN80DocValuesProducerTests.java @@ -0,0 +1,184 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN80Codec; + +import com.google.common.collect.ImmutableMap; +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.DocValuesFormat; +import org.apache.lucene.codecs.DocValuesProducer; +import org.apache.lucene.index.DocValuesType; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.FieldInfos; +import org.apache.lucene.index.SegmentInfo; +import org.apache.lucene.index.SegmentReadState; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexOutput; +import org.junit.Before; +import org.opensearch.Version; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.codec.KNN87Codec.KNN87Codec; +import org.opensearch.knn.index.codec.KNNCodecTestUtil; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.mapper.KNNVectorFieldMapper; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_M; + +public class KNN80DocValuesProducerTests extends KNNTestCase { + + private static Directory directory; + + @Before + public void setUp() throws Exception { + super.setUp(); + directory = newFSDirectory(createTempDir()); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + directory.close(); + } + + public void testProduceKNNBinaryField_fromCodec_nmslibCurrent() throws IOException { + // Set information about the segment and the fields + DocValuesFormat mockDocValuesFormat = mock(DocValuesFormat.class); + Codec mockDelegateCodec = mock(Codec.class); + DocValuesProducer mockDocValuesProducer = mock(DocValuesProducer.class); + when(mockDelegateCodec.docValuesFormat()).thenReturn(mockDocValuesFormat); + when(mockDocValuesFormat.fieldsProducer(any())).thenReturn(mockDocValuesProducer); + when(mockDocValuesFormat.getName()).thenReturn("mockDocValuesFormat"); + Codec codec = new KNN87Codec(mockDelegateCodec); + + String segmentName = "_test"; + int docsInSegment = 100; + String fieldName1 = String.format("test_field1%s", randomAlphaOfLength(4)); + String fieldName2 = String.format("test_field2%s", randomAlphaOfLength(4)); + List segmentFiles = Arrays.asList( + String.format("%s_2011_%s%s", segmentName, fieldName1, KNNEngine.NMSLIB.getExtension()), + String.format("%s_165_%s%s", segmentName, fieldName2, KNNEngine.FAISS.getExtension()) + ); + + KNNEngine knnEngine = KNNEngine.NMSLIB; + SpaceType spaceType = SpaceType.COSINESIMIL; + SegmentInfo segmentInfo = KNNCodecTestUtil.segmentInfoBuilder() + .directory(directory) + .segmentName(segmentName) + .docsInSegment(docsInSegment) + .codec(codec) + .build(); + + for (String name : segmentFiles) { + IndexOutput indexOutput = directory.createOutput(name, IOContext.DEFAULT); + indexOutput.close(); + } + segmentInfo.setFiles(segmentFiles); + + KNNMethodContext knnMethodContext = new KNNMethodContext( + knnEngine, + spaceType, + new MethodComponentContext(METHOD_HNSW, ImmutableMap.of(METHOD_PARAMETER_M, 16, METHOD_PARAMETER_EF_CONSTRUCTION, 512)) + ); + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .versionCreated(Version.CURRENT) + .build(); + String parameterString = XContentFactory.jsonBuilder() + .map(knnEngine.getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext).getLibraryParameters()) + .toString(); + + FieldInfo[] fieldInfoArray = new FieldInfo[] { + KNNCodecTestUtil.FieldInfoBuilder.builder(fieldName1) + .addAttribute(KNNVectorFieldMapper.KNN_FIELD, "true") + .addAttribute(KNNConstants.KNN_ENGINE, knnEngine.getName()) + .addAttribute(KNNConstants.SPACE_TYPE, spaceType.getValue()) + .addAttribute(KNNConstants.PARAMETERS, parameterString) + .build() }; + + FieldInfos fieldInfos = new FieldInfos(fieldInfoArray); + SegmentReadState state = new SegmentReadState(directory, segmentInfo, fieldInfos, IOContext.DEFAULT); + + DocValuesFormat docValuesFormat = codec.docValuesFormat(); + assertTrue(docValuesFormat instanceof KNN80DocValuesFormat); + DocValuesProducer producer = docValuesFormat.fieldsProducer(state); + assertTrue(producer instanceof KNN80DocValuesProducer); + int cacheKeySize = ((KNN80DocValuesProducer) producer).getCacheKeys().size(); + assertEquals(cacheKeySize, 1); + + String cacheKey = ((KNN80DocValuesProducer) producer).getCacheKeys().get(0); + assertTrue(cacheKey.contains(segmentFiles.get(0))); + } + + public void testProduceKNNBinaryField_whenFieldHasNonBinaryDocValues_thenSkipThoseField() throws IOException { + // Set information about the segment and the fields + DocValuesFormat mockDocValuesFormat = mock(DocValuesFormat.class); + Codec mockDelegateCodec = mock(Codec.class); + DocValuesProducer mockDocValuesProducer = mock(DocValuesProducer.class); + when(mockDelegateCodec.docValuesFormat()).thenReturn(mockDocValuesFormat); + when(mockDocValuesFormat.fieldsProducer(any())).thenReturn(mockDocValuesProducer); + when(mockDocValuesFormat.getName()).thenReturn("mockDocValuesFormat"); + Codec codec = new KNN87Codec(mockDelegateCodec); + + String segmentName = "_test"; + int docsInSegment = 100; + String fieldName1 = String.format("test_field1%s", randomAlphaOfLength(4)); + String fieldName2 = String.format("test_field2%s", randomAlphaOfLength(4)); + List segmentFiles = Arrays.asList( + String.format("%s_2011_%s%s", segmentName, fieldName1, KNNEngine.NMSLIB.getExtension()), + String.format("%s_165_%s%s", segmentName, fieldName2, KNNEngine.FAISS.getExtension()) + ); + + KNNEngine knnEngine = KNNEngine.NMSLIB; + SpaceType spaceType = SpaceType.COSINESIMIL; + SegmentInfo segmentInfo = KNNCodecTestUtil.segmentInfoBuilder() + .directory(directory) + .segmentName(segmentName) + .docsInSegment(docsInSegment) + .codec(codec) + .build(); + + for (String name : segmentFiles) { + IndexOutput indexOutput = directory.createOutput(name, IOContext.DEFAULT); + indexOutput.close(); + } + segmentInfo.setFiles(segmentFiles); + + FieldInfo[] fieldInfoArray = new FieldInfo[] { + KNNCodecTestUtil.FieldInfoBuilder.builder(fieldName1) + .addAttribute(KNNVectorFieldMapper.KNN_FIELD, "true") + .addAttribute(KNNConstants.KNN_ENGINE, knnEngine.getName()) + .addAttribute(KNNConstants.SPACE_TYPE, spaceType.getValue()) + .docValuesType(DocValuesType.NONE) + .dvGen(-1) + .build() }; + + FieldInfos fieldInfos = new FieldInfos(fieldInfoArray); + SegmentReadState state = new SegmentReadState(directory, segmentInfo, fieldInfos, IOContext.DEFAULT); + + DocValuesFormat docValuesFormat = codec.docValuesFormat(); + assertTrue(docValuesFormat instanceof KNN80DocValuesFormat); + DocValuesProducer producer = docValuesFormat.fieldsProducer(state); + assertTrue(producer instanceof KNN80DocValuesProducer); + assertEquals(0, ((KNN80DocValuesProducer) producer).getCacheKeys().size()); + } + +} diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN910Codec/KNN910CodecTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN910Codec/KNN910CodecTests.java deleted file mode 100644 index 1ec28d6a4..000000000 --- a/src/test/java/org/opensearch/knn/index/codec/KNN910Codec/KNN910CodecTests.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.opensearch.knn.index.codec.KNN910Codec; - -import org.opensearch.knn.index.codec.KNNCodecTestCase; - -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -public class KNN910CodecTests extends KNNCodecTestCase { - - public void testMultiFieldsKnnIndex() throws Exception { - testMultiFieldsKnnIndex(new KNN910Codec()); - } - - public void testBuildFromModelTemplate() throws InterruptedException, ExecutionException, IOException { - testBuildFromModelTemplate(new KNN910Codec()); - } -} diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/KNN990CodecTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/KNN990CodecTests.java new file mode 100644 index 000000000..307ebbb24 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/KNN990CodecTests.java @@ -0,0 +1,51 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN990Codec; + +import lombok.SneakyThrows; +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.perfield.PerFieldKnnVectorsFormat; +import org.opensearch.index.mapper.MapperService; +import org.opensearch.knn.index.codec.KNNCodecTestCase; + +import java.util.Optional; +import java.util.function.Function; + +import static org.opensearch.knn.index.codec.KNNCodecVersion.V_9_9_0; + +public class KNN990CodecTests extends KNNCodecTestCase { + + @SneakyThrows + public void testMultiFieldsKnnIndex() { + testMultiFieldsKnnIndex(KNN990Codec.builder().delegate(V_9_9_0.getDefaultCodecDelegate()).build()); + } + + @SneakyThrows + public void testBuildFromModelTemplate() { + testBuildFromModelTemplate((KNN990Codec.builder().delegate(V_9_9_0.getDefaultCodecDelegate()).build())); + } + + // Ensure that the codec is able to return the correct per field knn vectors format for codec + public void testCodecSetsCustomPerFieldKnnVectorsFormat() { + final Codec codec = new KNN990Codec(); + assertTrue(codec.knnVectorsFormat() instanceof KNN990PerFieldKnnVectorsFormat); + } + + // IMPORTANT: When this Codec is moved to a backwards Codec, this test needs to be removed, because it attempts to + // write with a read only codec, which will fail + @SneakyThrows + public void testKnnVectorIndex() { + Function perFieldKnnVectorsFormatProvider = ( + mapperService) -> new KNN990PerFieldKnnVectorsFormat(Optional.of(mapperService)); + + Function knnCodecProvider = (knnVectorFormat) -> KNN990Codec.builder() + .delegate(V_9_9_0.getDefaultCodecDelegate()) + .knnVectorsFormat(knnVectorFormat) + .build(); + + testKnnVectorIndex(knnCodecProvider, perFieldKnnVectorsFormatProvider); + } +} diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/KNN990QuantizationStateReaderTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/KNN990QuantizationStateReaderTests.java new file mode 100644 index 000000000..da790e947 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/KNN990QuantizationStateReaderTests.java @@ -0,0 +1,181 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN990Codec; + +import lombok.SneakyThrows; +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.CodecUtil; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.FieldInfos; +import org.apache.lucene.index.IndexFileNames; +import org.apache.lucene.index.SegmentInfo; +import org.apache.lucene.index.SegmentReadState; +import org.apache.lucene.search.Sort; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexInput; +import org.apache.lucene.util.Version; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; +import org.opensearch.knn.quantization.models.quantizationParams.ScalarQuantizationParams; +import org.opensearch.knn.quantization.models.quantizationState.MultiBitScalarQuantizationState; +import org.opensearch.knn.quantization.models.quantizationState.OneBitScalarQuantizationState; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationStateReadConfig; + +import java.util.Map; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; + +public class KNN990QuantizationStateReaderTests extends KNNTestCase { + + @SneakyThrows + public void testReadFromQuantizationStateReadConfig() { + String fieldName = "test-field"; + int fieldNumber = 4; + FieldInfos fieldInfos = Mockito.mock(FieldInfos.class); + FieldInfo fieldInfo = Mockito.mock(FieldInfo.class); + Mockito.when(fieldInfo.getFieldNumber()).thenReturn(fieldNumber); + Mockito.when(fieldInfos.fieldInfo(fieldName)).thenReturn(fieldInfo); + + final String segmentName = "test-segment-name"; + final String segmentSuffix = "test-segment-suffix"; + + final SegmentInfo segmentInfo = new SegmentInfo( + Mockito.mock(Directory.class), + Mockito.mock(Version.class), + Mockito.mock(Version.class), + segmentName, + 0, + false, + false, + Mockito.mock(Codec.class), + Mockito.mock(Map.class), + new byte[16], + Mockito.mock(Map.class), + Mockito.mock(Sort.class) + ); + + Directory directory = Mockito.mock(Directory.class); + IndexInput input = Mockito.mock(IndexInput.class); + Mockito.when(directory.openInput(any(), any())).thenReturn(input); + + final SegmentReadState segmentReadState = new SegmentReadState( + directory, + segmentInfo, + fieldInfos, + Mockito.mock(IOContext.class), + segmentSuffix + ); + ScalarQuantizationParams scalarQuantizationParams1 = new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); + ScalarQuantizationParams scalarQuantizationParams2 = new ScalarQuantizationParams(ScalarQuantizationType.TWO_BIT); + ScalarQuantizationParams scalarQuantizationParams4 = new ScalarQuantizationParams(ScalarQuantizationType.FOUR_BIT); + QuantizationStateReadConfig quantizationStateReadConfig = Mockito.mock(QuantizationStateReadConfig.class); + Mockito.when(quantizationStateReadConfig.getSegmentReadState()).thenReturn(segmentReadState); + Mockito.when(quantizationStateReadConfig.getField()).thenReturn(fieldName); + + try (MockedStatic mockedStaticReader = Mockito.mockStatic(KNN990QuantizationStateReader.class)) { + mockedStaticReader.when(() -> KNN990QuantizationStateReader.getNumFields(input)).thenReturn(2); + mockedStaticReader.when(() -> KNN990QuantizationStateReader.read(quantizationStateReadConfig)).thenCallRealMethod(); + mockedStaticReader.when(() -> KNN990QuantizationStateReader.readStateBytes(any(IndexInput.class), anyLong(), anyInt())) + .thenReturn(new byte[8]); + try (MockedStatic mockedStaticCodecUtil = mockStatic(CodecUtil.class)) { + + Mockito.when(input.readInt()).thenReturn(fieldNumber); + + try (MockedStatic mockedStaticOneBit = mockStatic(OneBitScalarQuantizationState.class)) { + Mockito.when(quantizationStateReadConfig.getQuantizationParams()).thenReturn(scalarQuantizationParams1); + OneBitScalarQuantizationState oneBitScalarQuantizationState = Mockito.mock(OneBitScalarQuantizationState.class); + mockedStaticOneBit.when(() -> OneBitScalarQuantizationState.fromByteArray(any(byte[].class))) + .thenReturn(oneBitScalarQuantizationState); + QuantizationState quantizationState = KNN990QuantizationStateReader.read(quantizationStateReadConfig); + assertEquals(oneBitScalarQuantizationState, quantizationState); + mockedStaticCodecUtil.verify(() -> CodecUtil.retrieveChecksum(input)); + } + + try (MockedStatic mockedStaticOneBit = mockStatic(MultiBitScalarQuantizationState.class)) { + MultiBitScalarQuantizationState multiBitScalarQuantizationState = Mockito.mock(MultiBitScalarQuantizationState.class); + mockedStaticOneBit.when(() -> MultiBitScalarQuantizationState.fromByteArray(any(byte[].class))) + .thenReturn(multiBitScalarQuantizationState); + + Mockito.when(quantizationStateReadConfig.getQuantizationParams()).thenReturn(scalarQuantizationParams2); + Mockito.when(quantizationStateReadConfig.getQuantizationParams()).thenReturn(scalarQuantizationParams2); + QuantizationState quantizationState = KNN990QuantizationStateReader.read(quantizationStateReadConfig); + assertEquals(multiBitScalarQuantizationState, quantizationState); + + Mockito.when(quantizationStateReadConfig.getQuantizationParams()).thenReturn(scalarQuantizationParams4); + Mockito.when(quantizationStateReadConfig.getQuantizationParams()).thenReturn(scalarQuantizationParams4); + quantizationState = KNN990QuantizationStateReader.read(quantizationStateReadConfig); + assertEquals(multiBitScalarQuantizationState, quantizationState); + } + } + } + } + + @SneakyThrows + public void testGetNumFields() { + IndexInput input = Mockito.mock(IndexInput.class); + KNN990QuantizationStateReader.getNumFields(input); + + Mockito.verify(input, times(1)).readInt(); + Mockito.verify(input, times(1)).readLong(); + Mockito.verify(input, times(2)).seek(anyLong()); + Mockito.verify(input, times(1)).length(); + } + + @SneakyThrows + public void testReadStateBytes() { + IndexInput input = Mockito.mock(IndexInput.class); + long position = 1; + int length = 2; + byte[] stateBytes = new byte[length]; + KNN990QuantizationStateReader.readStateBytes(input, position, length); + + Mockito.verify(input, times(1)).seek(position); + Mockito.verify(input, times(1)).readBytes(stateBytes, 0, length); + + } + + @SneakyThrows + public void testGetQuantizationStateFileName() { + String segmentName = "test-segment"; + String segmentSuffix = "test-suffix"; + String expectedName = IndexFileNames.segmentFileName(segmentName, segmentSuffix, KNNConstants.QUANTIZATION_STATE_FILE_SUFFIX); + + final SegmentInfo segmentInfo = new SegmentInfo( + Mockito.mock(Directory.class), + Mockito.mock(Version.class), + Mockito.mock(Version.class), + segmentName, + 0, + false, + false, + Mockito.mock(Codec.class), + Mockito.mock(Map.class), + new byte[16], + Mockito.mock(Map.class), + Mockito.mock(Sort.class) + ); + + final SegmentReadState segmentReadState = new SegmentReadState( + Mockito.mock(Directory.class), + segmentInfo, + Mockito.mock(FieldInfos.class), + Mockito.mock(IOContext.class), + segmentSuffix + ); + + assertEquals(expectedName, KNN990QuantizationStateReader.getQuantizationStateFileName(segmentReadState)); + + } +} diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/KNN990QuantizationStateWriterTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/KNN990QuantizationStateWriterTests.java new file mode 100644 index 000000000..2423a6827 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/KNN990QuantizationStateWriterTests.java @@ -0,0 +1,187 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN990Codec; + +import lombok.SneakyThrows; +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.CodecUtil; +import org.apache.lucene.index.FieldInfos; +import org.apache.lucene.index.SegmentInfo; +import org.apache.lucene.index.SegmentWriteState; +import org.apache.lucene.search.Sort; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexOutput; +import org.apache.lucene.util.InfoStream; +import org.apache.lucene.util.Version; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.stubbing.Answer; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; +import org.opensearch.knn.quantization.models.quantizationParams.ScalarQuantizationParams; +import org.opensearch.knn.quantization.models.quantizationState.OneBitScalarQuantizationState; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; + +import java.util.Map; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; + +public class KNN990QuantizationStateWriterTests extends KNNTestCase { + + @SneakyThrows + public void testWriteHeader() { + final String segmentName = "test-segment-name"; + + final SegmentInfo segmentInfo = new SegmentInfo( + Mockito.mock(Directory.class), + Mockito.mock(Version.class), + Mockito.mock(Version.class), + segmentName, + 0, + false, + false, + Mockito.mock(Codec.class), + Mockito.mock(Map.class), + new byte[16], + Mockito.mock(Map.class), + Mockito.mock(Sort.class) + ); + + Directory directory = Mockito.mock(Directory.class); + IndexOutput output = Mockito.mock(IndexOutput.class); + Mockito.when(directory.createOutput(any(), any())).thenReturn(output); + + final SegmentWriteState segmentWriteState = new SegmentWriteState( + Mockito.mock(InfoStream.class), + directory, + segmentInfo, + Mockito.mock(FieldInfos.class), + null, + Mockito.mock(IOContext.class) + ); + KNN990QuantizationStateWriter quantizationStateWriter = new KNN990QuantizationStateWriter(segmentWriteState); + try (MockedStatic mockedStaticCodecUtil = Mockito.mockStatic(CodecUtil.class)) { + mockedStaticCodecUtil.when( + () -> CodecUtil.writeIndexHeader(any(IndexOutput.class), anyString(), anyInt(), any(byte[].class), anyString()) + ).thenAnswer((Answer) invocation -> null); + quantizationStateWriter.writeHeader(segmentWriteState); + mockedStaticCodecUtil.verify( + () -> CodecUtil.writeIndexHeader( + output, + KNN990QuantizationStateWriter.NATIVE_ENGINES_990_KNN_VECTORS_FORMAT_QS_DATA, + 0, + segmentWriteState.segmentInfo.getId(), + segmentWriteState.segmentSuffix + ) + ); + } + } + + @SneakyThrows + public void testWriteState() { + final String segmentName = "test-segment-name"; + + final SegmentInfo segmentInfo = new SegmentInfo( + Mockito.mock(Directory.class), + Mockito.mock(Version.class), + Mockito.mock(Version.class), + segmentName, + 0, + false, + false, + Mockito.mock(Codec.class), + Mockito.mock(Map.class), + new byte[16], + Mockito.mock(Map.class), + Mockito.mock(Sort.class) + ); + + Directory directory = Mockito.mock(Directory.class); + IndexOutput output = Mockito.mock(IndexOutput.class); + Mockito.when(directory.createOutput(any(), any())).thenReturn(output); + + final SegmentWriteState segmentWriteState = new SegmentWriteState( + Mockito.mock(InfoStream.class), + directory, + segmentInfo, + Mockito.mock(FieldInfos.class), + null, + Mockito.mock(IOContext.class) + ); + KNN990QuantizationStateWriter quantizationStateWriter = new KNN990QuantizationStateWriter(segmentWriteState); + + int fieldNumber = 0; + QuantizationState quantizationState = new OneBitScalarQuantizationState( + new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT), + new float[] { 1.2f, 2.3f, 3.4f, 4.5f } + ); + quantizationStateWriter.writeState(fieldNumber, quantizationState); + byte[] stateBytes = quantizationState.toByteArray(); + Mockito.verify(output, times(1)).writeBytes(stateBytes, stateBytes.length); + } + + @SneakyThrows + public void testWriteFooter() { + final String segmentName = "test-segment-name"; + + final SegmentInfo segmentInfo = new SegmentInfo( + Mockito.mock(Directory.class), + Mockito.mock(Version.class), + Mockito.mock(Version.class), + segmentName, + 0, + false, + false, + Mockito.mock(Codec.class), + Mockito.mock(Map.class), + new byte[16], + Mockito.mock(Map.class), + Mockito.mock(Sort.class) + ); + + Directory directory = Mockito.mock(Directory.class); + IndexOutput output = Mockito.mock(IndexOutput.class); + Mockito.when(directory.createOutput(any(), any())).thenReturn(output); + + final SegmentWriteState segmentWriteState = new SegmentWriteState( + Mockito.mock(InfoStream.class), + directory, + segmentInfo, + Mockito.mock(FieldInfos.class), + null, + Mockito.mock(IOContext.class) + ); + KNN990QuantizationStateWriter quantizationStateWriter = new KNN990QuantizationStateWriter(segmentWriteState); + + int fieldNumber1 = 1; + int fieldNumber2 = 2; + QuantizationState quantizationState1 = new OneBitScalarQuantizationState( + new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT), + new float[] { 1.2f, 2.3f, 3.4f, 4.5f } + ); + QuantizationState quantizationState2 = new OneBitScalarQuantizationState( + new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT), + new float[] { 2.3f, 3.4f, 4.5f, 5.6f } + ); + quantizationStateWriter.writeState(fieldNumber1, quantizationState1); + quantizationStateWriter.writeState(fieldNumber2, quantizationState2); + + try (MockedStatic mockedStaticCodecUtil = mockStatic(CodecUtil.class)) { + quantizationStateWriter.writeFooter(); + + Mockito.verify(output, times(6)).writeInt(anyInt()); + Mockito.verify(output, times(2)).writeVLong(anyLong()); + Mockito.verify(output, times(1)).writeLong(anyLong()); + mockedStaticCodecUtil.verify(() -> CodecUtil.writeFooter(output)); + } + } +} diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngineFieldVectorsWriterTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngineFieldVectorsWriterTests.java new file mode 100644 index 000000000..4f68a360e --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngineFieldVectorsWriterTests.java @@ -0,0 +1,130 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.codec.KNN990Codec; + +import lombok.SneakyThrows; +import org.apache.lucene.codecs.hnsw.FlatFieldVectorsWriter; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.VectorEncoding; +import org.apache.lucene.util.InfoStream; +import org.junit.Assert; +import org.mockito.Mockito; +import org.opensearch.knn.index.codec.KNNCodecTestCase; + +public class NativeEngineFieldVectorsWriterTests extends KNNCodecTestCase { + + @SuppressWarnings("unchecked") + @SneakyThrows + public void testCreate_ForDifferentInputs_thenSuccess() { + final FieldInfo fieldInfo = Mockito.mock(FieldInfo.class); + Mockito.when(fieldInfo.getVectorEncoding()).thenReturn(VectorEncoding.FLOAT32); + final FlatFieldVectorsWriter mockedFlatFieldVectorsWriter = Mockito.mock(FlatFieldVectorsWriter.class); + NativeEngineFieldVectorsWriter floatWriter = (NativeEngineFieldVectorsWriter) NativeEngineFieldVectorsWriter + .create(fieldInfo, mockedFlatFieldVectorsWriter, InfoStream.getDefault()); + final float[] floatVector = new float[] { 1.0f, 2.0f }; + floatWriter.addValue(1, floatVector); + Mockito.doNothing().when(mockedFlatFieldVectorsWriter).addValue(1, floatVector); + + Mockito.verify(fieldInfo).getVectorEncoding(); + Mockito.verify(mockedFlatFieldVectorsWriter).addValue(1, floatVector); + + final byte[] byteVector = new byte[] { 1, 2 }; + final FlatFieldVectorsWriter mockedFlatFieldByteVectorsWriter = Mockito.mock(FlatFieldVectorsWriter.class); + Mockito.doNothing().when(mockedFlatFieldByteVectorsWriter).addValue(1, byteVector); + Mockito.when(fieldInfo.getVectorEncoding()).thenReturn(VectorEncoding.BYTE); + NativeEngineFieldVectorsWriter byteWriter = (NativeEngineFieldVectorsWriter) NativeEngineFieldVectorsWriter.create( + fieldInfo, + mockedFlatFieldByteVectorsWriter, + InfoStream.getDefault() + ); + Assert.assertNotNull(byteWriter); + Mockito.verify(fieldInfo, Mockito.times(2)).getVectorEncoding(); + byteWriter.addValue(1, byteVector); + Mockito.verify(mockedFlatFieldByteVectorsWriter).addValue(1, byteVector); + } + + @SuppressWarnings("unchecked") + @SneakyThrows + public void testAddValue_ForDifferentInputs_thenSuccess() { + final FieldInfo fieldInfo = Mockito.mock(FieldInfo.class); + Mockito.when(fieldInfo.getVectorEncoding()).thenReturn(VectorEncoding.FLOAT32); + final FlatFieldVectorsWriter mockedFlatFieldVectorsWriter = Mockito.mock(FlatFieldVectorsWriter.class); + final float[] vec1 = new float[] { 1.0f, 2.0f }; + final float[] vec2 = new float[] { 2.0f, 2.0f }; + Mockito.doNothing().when(mockedFlatFieldVectorsWriter).addValue(1, vec1); + Mockito.doNothing().when(mockedFlatFieldVectorsWriter).addValue(2, vec2); + final NativeEngineFieldVectorsWriter floatWriter = (NativeEngineFieldVectorsWriter) NativeEngineFieldVectorsWriter + .create(fieldInfo, mockedFlatFieldVectorsWriter, InfoStream.getDefault()); + floatWriter.addValue(1, vec1); + floatWriter.addValue(2, vec2); + Mockito.verify(mockedFlatFieldVectorsWriter).addValue(1, vec1); + Mockito.verify(mockedFlatFieldVectorsWriter).addValue(2, vec2); + + Assert.assertEquals(vec1, floatWriter.getVectors().get(1)); + Assert.assertEquals(vec2, floatWriter.getVectors().get(2)); + Mockito.verify(fieldInfo).getVectorEncoding(); + + Mockito.when(fieldInfo.getVectorEncoding()).thenReturn(VectorEncoding.BYTE); + final FlatFieldVectorsWriter mockedFlatFieldByteVectorsWriter = Mockito.mock(FlatFieldVectorsWriter.class); + final byte[] bvec1 = new byte[] { 1, 2 }; + final byte[] bvec2 = new byte[] { 2, 2 }; + Mockito.doNothing().when(mockedFlatFieldByteVectorsWriter).addValue(1, bvec1); + Mockito.doNothing().when(mockedFlatFieldByteVectorsWriter).addValue(2, bvec2); + final NativeEngineFieldVectorsWriter byteWriter = (NativeEngineFieldVectorsWriter) NativeEngineFieldVectorsWriter + .create(fieldInfo, mockedFlatFieldByteVectorsWriter, InfoStream.getDefault()); + byteWriter.addValue(1, bvec1); + byteWriter.addValue(2, bvec2); + + Assert.assertEquals(bvec1, byteWriter.getVectors().get(1)); + Assert.assertEquals(bvec2, byteWriter.getVectors().get(2)); + Mockito.verify(fieldInfo, Mockito.times(2)).getVectorEncoding(); + Mockito.verify(mockedFlatFieldByteVectorsWriter).addValue(1, bvec1); + Mockito.verify(mockedFlatFieldByteVectorsWriter).addValue(2, bvec2); + } + + @SuppressWarnings("unchecked") + @SneakyThrows + public void testCopyValue_whenValidInput_thenException() { + final FieldInfo fieldInfo = Mockito.mock(FieldInfo.class); + FlatFieldVectorsWriter mockedFlatFieldVectorsWriter = Mockito.mock(FlatFieldVectorsWriter.class); + Mockito.when(fieldInfo.getVectorEncoding()).thenReturn(VectorEncoding.FLOAT32); + final NativeEngineFieldVectorsWriter floatWriter = (NativeEngineFieldVectorsWriter) NativeEngineFieldVectorsWriter + .create(fieldInfo, mockedFlatFieldVectorsWriter, InfoStream.getDefault()); + expectThrows(UnsupportedOperationException.class, () -> floatWriter.copyValue(new float[3])); + + Mockito.when(fieldInfo.getVectorEncoding()).thenReturn(VectorEncoding.BYTE); + final NativeEngineFieldVectorsWriter byteWriter = (NativeEngineFieldVectorsWriter) NativeEngineFieldVectorsWriter + .create(fieldInfo, mockedFlatFieldVectorsWriter, InfoStream.getDefault()); + expectThrows(UnsupportedOperationException.class, () -> byteWriter.copyValue(new byte[3])); + } + + @SuppressWarnings("unchecked") + @SneakyThrows + public void testRamByteUsed_whenValidInput_thenSuccess() { + final FieldInfo fieldInfo = Mockito.mock(FieldInfo.class); + Mockito.when(fieldInfo.getVectorEncoding()).thenReturn(VectorEncoding.FLOAT32); + Mockito.when(fieldInfo.getVectorDimension()).thenReturn(2); + FlatFieldVectorsWriter mockedFlatFieldVectorsWriter = Mockito.mock(FlatFieldVectorsWriter.class); + Mockito.when(mockedFlatFieldVectorsWriter.ramBytesUsed()).thenReturn(1L); + final NativeEngineFieldVectorsWriter floatWriter = (NativeEngineFieldVectorsWriter) NativeEngineFieldVectorsWriter + .create(fieldInfo, mockedFlatFieldVectorsWriter, InfoStream.getDefault()); + // testing for value > 0 as we don't have a concrete way to find out expected bytes. This can OS dependent too. + Assert.assertTrue(floatWriter.ramBytesUsed() > 0); + + Mockito.when(fieldInfo.getVectorEncoding()).thenReturn(VectorEncoding.BYTE); + final NativeEngineFieldVectorsWriter byteWriter = (NativeEngineFieldVectorsWriter) NativeEngineFieldVectorsWriter + .create(fieldInfo, mockedFlatFieldVectorsWriter, InfoStream.getDefault()); + // testing for value > 0 as we don't have a concrete way to find out expected bytes. This can OS dependent too. + Assert.assertTrue(byteWriter.ramBytesUsed() > 0); + Mockito.verify(mockedFlatFieldVectorsWriter, Mockito.times(2)).ramBytesUsed(); + } +} diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsFormatTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsFormatTests.java new file mode 100644 index 000000000..f30484586 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsFormatTests.java @@ -0,0 +1,371 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.codec.KNN990Codec; + +import lombok.SneakyThrows; +import lombok.extern.log4j.Log4j2; +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.CodecUtil; +import org.apache.lucene.codecs.hnsw.FlatVectorScorerUtil; +import org.apache.lucene.codecs.hnsw.FlatVectorsReader; +import org.apache.lucene.codecs.hnsw.FlatVectorsWriter; +import org.apache.lucene.codecs.lucene99.Lucene99FlatVectorsFormat; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.FieldType; +import org.apache.lucene.document.KnnByteVectorField; +import org.apache.lucene.document.KnnFloatVectorField; +import org.apache.lucene.index.ByteVectorValues; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.FieldInfos; +import org.apache.lucene.index.FloatVectorValues; +import org.apache.lucene.index.IndexOptions; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.LeafReader; +import org.apache.lucene.index.NoMergePolicy; +import org.apache.lucene.index.SegmentInfo; +import org.apache.lucene.index.SegmentReadState; +import org.apache.lucene.index.SegmentReader; +import org.apache.lucene.index.SegmentWriteState; +import org.apache.lucene.index.SerialMergeScheduler; +import org.apache.lucene.index.VectorEncoding; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.Sort; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexInput; +import org.apache.lucene.store.IndexOutput; +import org.apache.lucene.tests.index.RandomIndexWriter; +import org.apache.lucene.tests.store.BaseDirectoryWrapper; +import org.apache.lucene.util.Bits; +import org.apache.lucene.util.InfoStream; +import org.apache.lucene.util.Version; +import org.junit.After; +import org.junit.Assert; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.stubbing.Answer; +import org.opensearch.common.lucene.Lucene; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.index.engine.qframe.QuantizationConfigParser; +import org.opensearch.knn.index.mapper.KNNVectorFieldMapper; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; + +@Log4j2 +public class NativeEngines990KnnVectorsFormatTests extends KNNTestCase { + private static final Codec TESTING_CODEC = new UnitTestCodec(); + private static final String FLAT_VECTOR_FILE_EXT = ".vec"; + private static final String FAISS_ENGINE_FILE_EXT = ".faiss"; + private static final String FLOAT_VECTOR_FIELD = "float_field"; + private static final String FLOAT_VECTOR_FIELD_BINARY = "float_binary_field"; + private static final String BYTE_VECTOR_FIELD = "byte_field"; + private Directory dir; + private RandomIndexWriter indexWriter; + + @After + public void tearDown() throws Exception { + if (dir != null) { + dir.close(); + } + super.tearDown(); + } + + @SneakyThrows + public void testReaderAndWriter_whenValidInput_thenSuccess() { + final Lucene99FlatVectorsFormat mockedFlatVectorsFormat = Mockito.mock(Lucene99FlatVectorsFormat.class); + + final String segmentName = "test-segment-name"; + + final SegmentInfo mockedSegmentInfo = new SegmentInfo( + Mockito.mock(Directory.class), + Mockito.mock(Version.class), + Mockito.mock(Version.class), + segmentName, + 0, + false, + false, + Mockito.mock(Codec.class), + Mockito.mock(Map.class), + new byte[16], + Mockito.mock(Map.class), + Mockito.mock(Sort.class) + ); + + final String segmentSuffix = "test-segment-suffix"; + + Directory directory = Mockito.mock(Directory.class); + IndexInput input = Mockito.mock(IndexInput.class); + Mockito.when(directory.openInput(any(), any())).thenReturn(input); + + String fieldName = "test-field"; + FieldInfos fieldInfos = Mockito.mock(FieldInfos.class); + FieldInfo fieldInfo = Mockito.mock(FieldInfo.class); + Mockito.when(fieldInfo.getName()).thenReturn(fieldName); + Mockito.when(fieldInfos.fieldInfo(anyInt())).thenReturn(fieldInfo); + Mockito.when(fieldInfos.iterator()).thenReturn(new Iterator() { + @Override + public boolean hasNext() { + return false; + } + + @Override + public FieldInfo next() { + return null; + } + }); + + final SegmentReadState mockedSegmentReadState = new SegmentReadState( + directory, + mockedSegmentInfo, + fieldInfos, + Mockito.mock(IOContext.class), + segmentSuffix + ); + + final SegmentWriteState mockedSegmentWriteState = new SegmentWriteState( + Mockito.mock(InfoStream.class), + Mockito.mock(Directory.class), + mockedSegmentInfo, + Mockito.mock(FieldInfos.class), + null, + Mockito.mock(IOContext.class) + ); + Mockito.when(mockedFlatVectorsFormat.fieldsReader(mockedSegmentReadState)).thenReturn(Mockito.mock(FlatVectorsReader.class)); + Mockito.when(mockedFlatVectorsFormat.fieldsWriter(mockedSegmentWriteState)).thenReturn(Mockito.mock(FlatVectorsWriter.class)); + + final NativeEngines990KnnVectorsFormat nativeEngines990KnnVectorsFormat = new NativeEngines990KnnVectorsFormat( + mockedFlatVectorsFormat + ); + try (MockedStatic mockedStaticCodecUtil = Mockito.mockStatic(CodecUtil.class)) { + mockedStaticCodecUtil.when( + () -> CodecUtil.writeIndexHeader(any(IndexOutput.class), anyString(), anyInt(), any(byte[].class), anyString()) + ).thenAnswer((Answer) invocation -> null); + mockedStaticCodecUtil.when(() -> CodecUtil.retrieveChecksum(any(IndexInput.class))) + .thenAnswer((Answer) invocation -> null); + Assert.assertTrue( + nativeEngines990KnnVectorsFormat.fieldsReader(mockedSegmentReadState) instanceof NativeEngines990KnnVectorsReader + ); + + Assert.assertTrue( + nativeEngines990KnnVectorsFormat.fieldsWriter(mockedSegmentWriteState) instanceof NativeEngines990KnnVectorsWriter + ); + } + } + + @SneakyThrows + public void testNativeEngineVectorFormat_whenMultipleVectorFieldIndexed_thenSuccess() { + setup(); + float[] floatVector = { 1.0f, 3.0f, 4.0f }; + byte[] byteVector = { 6, 14 }; + + FieldType fieldTypeForFloat = createVectorField(3, VectorEncoding.FLOAT32, VectorDataType.FLOAT); + fieldTypeForFloat.putAttribute(KNNConstants.PARAMETERS, "{ \"index_description\":\"HNSW16,Flat\", \"spaceType\": \"l2\"}"); + fieldTypeForFloat.freeze(); + addFieldToIndex(new KnnFloatVectorField(FLOAT_VECTOR_FIELD, floatVector, fieldTypeForFloat), indexWriter); + FieldType fieldTypeForByte = createVectorField(2, VectorEncoding.BYTE, VectorDataType.BINARY); + fieldTypeForByte.putAttribute(KNNConstants.PARAMETERS, "{ \"index_description\":\"HNSW16,Flat\", \"spaceType\": \"l2\"}"); + fieldTypeForByte.freeze(); + addFieldToIndex(new KnnByteVectorField(BYTE_VECTOR_FIELD, byteVector, fieldTypeForByte), indexWriter); + + float[] floatVectorForBinaryQuantization_1 = { 1.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f }; + float[] floatVectorForBinaryQuantization_2 = { 1.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f }; + FieldType fieldTypeForBinaryQuantization = createVectorField(8, VectorEncoding.FLOAT32, VectorDataType.FLOAT); + fieldTypeForBinaryQuantization.putAttribute(KNNConstants.PARAMETERS, "{ \"index_description\":\"BHNSW32\", \"spaceType\": \"l2\"}"); + QuantizationConfig quantizationConfig = QuantizationConfig.builder().quantizationType(ScalarQuantizationType.ONE_BIT).build(); + fieldTypeForBinaryQuantization.putAttribute(KNNConstants.QFRAMEWORK_CONFIG, QuantizationConfigParser.toCsv(quantizationConfig)); + fieldTypeForBinaryQuantization.freeze(); + + addFieldToIndex( + new KnnFloatVectorField(FLOAT_VECTOR_FIELD_BINARY, floatVectorForBinaryQuantization_1, fieldTypeForBinaryQuantization), + indexWriter + ); + addFieldToIndex( + new KnnFloatVectorField(FLOAT_VECTOR_FIELD_BINARY, floatVectorForBinaryQuantization_2, fieldTypeForBinaryQuantization), + indexWriter + ); + + final IndexReader indexReader = indexWriter.getReader(); + // ensuring segments are created + indexWriter.flush(); + indexWriter.commit(); + indexWriter.close(); + + // Validate to see if correct values are returned, assumption here is only 1 segment is getting created + IndexSearcher searcher = new IndexSearcher(indexReader); + final LeafReader leafReader = searcher.getLeafContexts().get(0).reader(); + SegmentReader segmentReader = Lucene.segmentReader(leafReader); + final List hnswfiles = getFilesFromSegment(dir, FAISS_ENGINE_FILE_EXT); + assertEquals(3, hnswfiles.size()); + assertEquals(hnswfiles.stream().filter(x -> x.contains(FLOAT_VECTOR_FIELD)).count(), 1); + assertEquals(hnswfiles.stream().filter(x -> x.contains(BYTE_VECTOR_FIELD)).count(), 1); + assertEquals(hnswfiles.stream().filter(x -> x.contains(FLOAT_VECTOR_FIELD_BINARY)).count(), 1); + + // Even setting IWC to not use compound file it still uses compound file, hence ensuring we don't check .vec + // file in case segment uses compound format. use this seed once we fix this to validate everything is + // working or not. -Dtests.seed=CAAE1B8D573EEB7E + if (segmentReader.getSegmentInfo().info.getUseCompoundFile() == false) { + final List vecfiles = getFilesFromSegment(dir, FLAT_VECTOR_FILE_EXT); + // 2 .vec files will be created as we are using per field vectors format. + assertEquals(3, vecfiles.size()); + } + + final FloatVectorValues floatVectorValues = leafReader.getFloatVectorValues(FLOAT_VECTOR_FIELD); + floatVectorValues.nextDoc(); + assertArrayEquals(floatVector, floatVectorValues.vectorValue(), 0.0f); + assertEquals(1, floatVectorValues.size()); + assertEquals(3, floatVectorValues.dimension()); + + final ByteVectorValues byteVectorValues = leafReader.getByteVectorValues(BYTE_VECTOR_FIELD); + byteVectorValues.nextDoc(); + assertArrayEquals(byteVector, byteVectorValues.vectorValue()); + assertEquals(1, byteVectorValues.size()); + assertEquals(2, byteVectorValues.dimension()); + + final FloatVectorValues floatVectorValuesForBinaryQuantization = leafReader.getFloatVectorValues(FLOAT_VECTOR_FIELD_BINARY); + floatVectorValuesForBinaryQuantization.nextDoc(); + assertArrayEquals(floatVectorForBinaryQuantization_1, floatVectorValuesForBinaryQuantization.vectorValue(), 0.0f); + assertEquals(2, floatVectorValuesForBinaryQuantization.size()); + assertEquals(8, floatVectorValuesForBinaryQuantization.dimension()); + + Assert.assertThrows( + UnsupportedOperationException.class, + () -> leafReader.searchNearestVectors(FLOAT_VECTOR_FIELD, floatVector, 10, new Bits.MatchAllBits(1), 10) + ); + + Assert.assertThrows( + UnsupportedOperationException.class, + () -> leafReader.searchNearestVectors(BYTE_VECTOR_FIELD, byteVector, 10, new Bits.MatchAllBits(1), 10) + ); + // do it at the end so that all search is completed + indexReader.close(); + } + + @SneakyThrows + public void testNativeEngineVectorFormat_whenBinaryQuantizationApplied_thenSuccess() { + setup(); + float[] floatVectorForBinaryQuantization = { 1.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f }; + FieldType fieldTypeForBinaryQuantization = createVectorField(8, VectorEncoding.FLOAT32, VectorDataType.FLOAT); + fieldTypeForBinaryQuantization.putAttribute(KNNConstants.PARAMETERS, "{ \"index_description\":\"BHNSW32\", \"spaceType\": \"l2\"}"); + QuantizationConfig quantizationConfig = QuantizationConfig.builder().quantizationType(ScalarQuantizationType.ONE_BIT).build(); + fieldTypeForBinaryQuantization.putAttribute(KNNConstants.QFRAMEWORK_CONFIG, QuantizationConfigParser.toCsv(quantizationConfig)); + + addFieldToIndex( + new KnnFloatVectorField(FLOAT_VECTOR_FIELD_BINARY, floatVectorForBinaryQuantization, fieldTypeForBinaryQuantization), + indexWriter + ); + + final IndexReader indexReader = indexWriter.getReader(); + // ensuring segments are created + indexWriter.flush(); + indexWriter.commit(); + indexWriter.close(); + + IndexSearcher searcher = new IndexSearcher(indexReader); + final LeafReader leafReader = searcher.getLeafContexts().get(0).reader(); + SegmentReader segmentReader = Lucene.segmentReader(leafReader); + if (segmentReader.getSegmentInfo().info.getUseCompoundFile() == false) { + final List vecfiles = getFilesFromSegment(dir, FLAT_VECTOR_FILE_EXT); + // 2 .vec files will be created as we are using per field vectors format. + assertEquals(1, vecfiles.size()); + } + + final FloatVectorValues floatVectorValues = leafReader.getFloatVectorValues(FLOAT_VECTOR_FIELD_BINARY); + floatVectorValues.nextDoc(); + assertArrayEquals(floatVectorForBinaryQuantization, floatVectorValues.vectorValue(), 0.0f); + assertEquals(1, floatVectorValues.size()); + assertEquals(8, floatVectorValues.dimension()); + indexReader.close(); + } + + public void testFormatName_withValidInput_thenSuccess() { + final String validFormatName = "NativeEngines990KnnVectorsFormat"; + Assert.assertEquals(validFormatName, new NativeEngines990KnnVectorsFormat().getName()); + Assert.assertEquals( + validFormatName, + new NativeEngines990KnnVectorsFormat(new Lucene99FlatVectorsFormat(FlatVectorScorerUtil.getLucene99FlatVectorsScorer())) + .getName() + ); + } + + private List getFilesFromSegment(Directory dir, String fileFormat) throws IOException { + return Arrays.stream(dir.listAll()).filter(x -> x.contains(fileFormat)).collect(Collectors.toList()); + } + + /** + * This should have been annotated with @Before, but somehow when I annotate with @Before apart from running + * before tests, it is also running independently and failing. Need to figure this out. + * @throws IOException + */ + private void setup() throws IOException { + dir = newFSDirectory(createTempDir()); + // on the mock directory Lucene goes ahead and does a search on different fields. We want to avoid that as of + // now. Given we have not implemented search for the native engine format using codec, the dir.close fails + // with exception. Hence, marking this as false. + ((BaseDirectoryWrapper) dir).setCheckIndexOnClose(false); + indexWriter = createIndexWriter(dir); + } + + private RandomIndexWriter createIndexWriter(final Directory dir) throws IOException { + final IndexWriterConfig iwc = newIndexWriterConfig(); + iwc.setMergeScheduler(new SerialMergeScheduler()); + iwc.setCodec(TESTING_CODEC); + iwc.setUseCompoundFile(false); + // Set merge policy to no merges so that we create a predictable number of segments. + iwc.setMergePolicy(NoMergePolicy.INSTANCE); + iwc.setMaxBufferedDocs(Integer.MAX_VALUE); + iwc.setRAMBufferSizeMB(1024); + return new RandomIndexWriter(random(), dir, iwc); + } + + private void addFieldToIndex(final Field vectorField, final RandomIndexWriter indexWriter) throws IOException { + final Document doc1 = new Document(); + doc1.add(vectorField); + indexWriter.addDocument(doc1); + } + + private FieldType createVectorField(int dimension, VectorEncoding vectorEncoding, VectorDataType vectorDataType) { + FieldType nativeVectorField = new FieldType(); + // TODO: Replace this with the default field which will be created in mapper for Native Engines with KNNVectorsFormat + nativeVectorField.setTokenized(false); + nativeVectorField.setIndexOptions(IndexOptions.NONE); + nativeVectorField.putAttribute(KNNVectorFieldMapper.KNN_FIELD, "true"); + nativeVectorField.putAttribute(KNNConstants.KNN_METHOD, KNNConstants.METHOD_HNSW); + nativeVectorField.putAttribute(KNNConstants.KNN_ENGINE, KNNEngine.FAISS.getName()); + nativeVectorField.putAttribute(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()); + nativeVectorField.putAttribute(KNNConstants.HNSW_ALGO_M, "32"); + nativeVectorField.putAttribute(KNNConstants.HNSW_ALGO_EF_CONSTRUCTION, "512"); + nativeVectorField.putAttribute(KNNConstants.VECTOR_DATA_TYPE_FIELD, vectorDataType.getValue()); + nativeVectorField.setVectorAttributes( + dimension, + vectorEncoding, + SpaceType.L2.getKnnVectorSimilarityFunction().getVectorSimilarityFunction() + ); + return nativeVectorField; + } +} diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java new file mode 100644 index 000000000..03d0f6160 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterFlushTests.java @@ -0,0 +1,858 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN990Codec; + +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.apache.lucene.codecs.hnsw.FlatFieldVectorsWriter; +import org.apache.lucene.codecs.hnsw.FlatVectorsWriter; +import org.apache.lucene.index.DocsWithFieldSet; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.SegmentWriteState; +import org.apache.lucene.index.VectorEncoding; +import org.mockito.Mock; +import org.mockito.MockedConstruction; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.codec.nativeindex.NativeIndexWriter; +import org.opensearch.knn.index.quantizationservice.QuantizationService; +import org.opensearch.knn.index.vectorvalues.KNNVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNVectorValuesFactory; +import org.opensearch.knn.index.vectorvalues.TestVectorValues; +import org.opensearch.knn.plugin.stats.KNNGraphValue; +import org.opensearch.knn.quantization.models.quantizationParams.QuantizationParams; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; +import org.opensearch.test.OpenSearchTestCase; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static com.carrotsearch.randomizedtesting.RandomizedTest.$; +import static com.carrotsearch.randomizedtesting.RandomizedTest.$$; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockConstruction; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@RequiredArgsConstructor +public class NativeEngines990KnnVectorsWriterFlushTests extends OpenSearchTestCase { + + @Mock + private FlatVectorsWriter flatVectorsWriter; + @Mock + private SegmentWriteState segmentWriteState; + @Mock + private QuantizationParams quantizationParams; + @Mock + private QuantizationState quantizationState; + @Mock + private QuantizationService quantizationService; + @Mock + private NativeIndexWriter nativeIndexWriter; + + private FlatFieldVectorsWriter mockedFlatFieldVectorsWriter; + + private NativeEngines990KnnVectorsWriter objectUnderTest; + + private final String description; + private final List> vectorsPerField; + private static final Integer BUILD_GRAPH_ALWAYS_THRESHOLD = 0; + private static final Integer BUILD_GRAPH_NEVER_THRESHOLD = -1; + + @Override + public void setUp() throws Exception { + super.setUp(); + MockitoAnnotations.openMocks(this); + objectUnderTest = new NativeEngines990KnnVectorsWriter(segmentWriteState, flatVectorsWriter, BUILD_GRAPH_ALWAYS_THRESHOLD); + mockedFlatFieldVectorsWriter = Mockito.mock(FlatFieldVectorsWriter.class); + Mockito.doNothing().when(mockedFlatFieldVectorsWriter).addValue(Mockito.anyInt(), Mockito.any()); + Mockito.when(flatVectorsWriter.addField(Mockito.any())).thenReturn(mockedFlatFieldVectorsWriter); + } + + @ParametersFactory + public static Collection data() { + return Arrays.asList( + $$( + $("Single field", List.of(Map.of(0, new float[] { 1, 2, 3 }, 1, new float[] { 2, 3, 4 }, 2, new float[] { 3, 4, 5 }))), + $("Single field, no total live docs", List.of()), + $( + "Multi Field", + List.of( + Map.of(0, new float[] { 1, 2, 3 }, 1, new float[] { 2, 3, 4 }, 2, new float[] { 3, 4, 5 }), + Collections.emptyMap(), + Map.of( + 0, + new float[] { 1, 2, 3, 4 }, + 1, + new float[] { 2, 3, 4, 5 }, + 2, + new float[] { 3, 4, 5, 6 }, + 3, + new float[] { 4, 5, 6, 7 } + ) + ) + ) + ) + ); + } + + @SneakyThrows + public void testFlush() { + // Given + final List> expectedVectorValues = vectorsPerField.stream().map(vectors -> { + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(vectors.values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.FLOAT, + randomVectorValues + ); + return knnVectorValues; + }).collect(Collectors.toList()); + + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final FieldInfo fieldInfo = fieldInfo( + i, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, vectorsPerField.get(i)); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); + + try { + objectUnderTest.addField(fieldInfo); + } catch (Exception e) { + throw new RuntimeException(e); + } + + DocsWithFieldSet docsWithFieldSet = field.getDocsWithField(); + knnVectorValuesFactoryMockedStatic.when( + () -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, docsWithFieldSet, vectorsPerField.get(i)) + ).thenReturn(expectedVectorValues.get(i)); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(null); + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, null)) + .thenReturn(nativeIndexWriter); + }); + + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).flushIndex(any(), anyInt()); + + // When + objectUnderTest.flush(5, null); + + // Then + verify(flatVectorsWriter).flush(5, null); + if (vectorsPerField.size() > 0) { + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + assertNotEquals(0L, (long) KNNGraphValue.REFRESH_TOTAL_TIME_IN_MILLIS.getValue()); + } + + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + try { + if (vectorsPerField.get(i).isEmpty()) { + verify(nativeIndexWriter, never()).flushIndex(expectedVectorValues.get(i), vectorsPerField.get(i).size()); + } else { + verify(nativeIndexWriter).flushIndex(expectedVectorValues.get(i), vectorsPerField.get(i).size()); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + final Long expectedTimesGetVectorValuesIsCalled = vectorsPerField.stream().filter(Predicate.not(Map::isEmpty)).count(); + knnVectorValuesFactoryMockedStatic.verify( + () -> KNNVectorValuesFactory.getVectorValues(any(VectorDataType.class), any(DocsWithFieldSet.class), any()), + times(Math.toIntExact(expectedTimesGetVectorValuesIsCalled)) + ); + } + } + + @SneakyThrows + public void testFlush_WithQuantization() { + // Given + List> expectedVectorValues = new ArrayList<>(); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(vectorsPerField.get(i).values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.FLOAT, + randomVectorValues + ); + expectedVectorValues.add(knnVectorValues); + + }); + + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final FieldInfo fieldInfo = fieldInfo( + i, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, vectorsPerField.get(i)); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); + + try { + objectUnderTest.addField(fieldInfo); + } catch (Exception e) { + throw new RuntimeException(e); + } + + DocsWithFieldSet docsWithFieldSet = field.getDocsWithField(); + knnVectorValuesFactoryMockedStatic.when( + () -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, docsWithFieldSet, vectorsPerField.get(i)) + ).thenReturn(expectedVectorValues.get(i)); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(quantizationParams); + try { + when(quantizationService.train(quantizationParams, expectedVectorValues.get(i), vectorsPerField.get(i).size())) + .thenReturn(quantizationState); + } catch (Exception e) { + throw new RuntimeException(e); + } + + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, quantizationState)) + .thenReturn(nativeIndexWriter); + }); + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).flushIndex(any(), anyInt()); + + // When + objectUnderTest.flush(5, null); + + // Then + verify(flatVectorsWriter).flush(5, null); + if (vectorsPerField.size() > 0) { + verify(knn990QuantWriterMockedConstruction.constructed().get(0)).writeHeader(segmentWriteState); + assertTrue(KNNGraphValue.REFRESH_TOTAL_TIME_IN_MILLIS.getValue() > 0L); + } else { + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + } + + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + try { + if (vectorsPerField.get(i).isEmpty()) { + verify(knn990QuantWriterMockedConstruction.constructed().get(0), never()).writeState(i, quantizationState); + verify(nativeIndexWriter, never()).flushIndex(expectedVectorValues.get(i), vectorsPerField.get(i).size()); + } else { + verify(knn990QuantWriterMockedConstruction.constructed().get(0)).writeState(i, quantizationState); + verify(nativeIndexWriter).flushIndex(expectedVectorValues.get(i), vectorsPerField.get(i).size()); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + final Long expectedTimesGetVectorValuesIsCalled = vectorsPerField.stream().filter(Predicate.not(Map::isEmpty)).count(); + knnVectorValuesFactoryMockedStatic.verify( + () -> KNNVectorValuesFactory.getVectorValues(any(VectorDataType.class), any(DocsWithFieldSet.class), any()), + times(Math.toIntExact(expectedTimesGetVectorValuesIsCalled) * 2) + ); + } + } + + public void testFlush_whenThresholdIsNegative_thenNativeIndexWriterIsNeverCalled() throws IOException { + // Given + List> expectedVectorValues = new ArrayList<>(); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(vectorsPerField.get(i).values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.FLOAT, + randomVectorValues + ); + expectedVectorValues.add(knnVectorValues); + + }); + + final NativeEngines990KnnVectorsWriter nativeEngineWriter = new NativeEngines990KnnVectorsWriter( + segmentWriteState, + flatVectorsWriter, + BUILD_GRAPH_NEVER_THRESHOLD + ); + + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final FieldInfo fieldInfo = fieldInfo( + i, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, vectorsPerField.get(i)); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); + try { + nativeEngineWriter.addField(fieldInfo); + } catch (Exception e) { + throw new RuntimeException(e); + } + + DocsWithFieldSet docsWithFieldSet = field.getDocsWithField(); + knnVectorValuesFactoryMockedStatic.when( + () -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, docsWithFieldSet, vectorsPerField.get(i)) + ).thenReturn(expectedVectorValues.get(i)); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(null); + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, null)) + .thenReturn(nativeIndexWriter); + }); + + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).flushIndex(any(), anyInt()); + + // When + nativeEngineWriter.flush(5, null); + + // Then + verify(flatVectorsWriter).flush(5, null); + if (vectorsPerField.size() > 0) { + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + } + verifyNoInteractions(nativeIndexWriter); + } + } + + public void testFlush_whenThresholdIsGreaterThanVectorSize_thenNativeIndexWriterIsNeverCalled() throws IOException { + // Given + List> expectedVectorValues = new ArrayList<>(); + final Map sizeMap = new HashMap<>(); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(vectorsPerField.get(i).values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.FLOAT, + randomVectorValues + ); + sizeMap.put(i, randomVectorValues.size()); + expectedVectorValues.add(knnVectorValues); + + }); + final int maxThreshold = sizeMap.values().stream().filter(count -> count != 0).max(Integer::compareTo).orElse(0); + final NativeEngines990KnnVectorsWriter nativeEngineWriter = new NativeEngines990KnnVectorsWriter( + segmentWriteState, + flatVectorsWriter, + maxThreshold + 1 + ); + + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final FieldInfo fieldInfo = fieldInfo( + i, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, vectorsPerField.get(i)); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); + try { + nativeEngineWriter.addField(fieldInfo); + } catch (Exception e) { + throw new RuntimeException(e); + } + + DocsWithFieldSet docsWithFieldSet = field.getDocsWithField(); + knnVectorValuesFactoryMockedStatic.when( + () -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, docsWithFieldSet, vectorsPerField.get(i)) + ).thenReturn(expectedVectorValues.get(i)); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(null); + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, null)) + .thenReturn(nativeIndexWriter); + }); + + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).flushIndex(any(), anyInt()); + + // When + nativeEngineWriter.flush(5, null); + + // Then + verify(flatVectorsWriter).flush(5, null); + if (vectorsPerField.size() > 0) { + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + } + verifyNoInteractions(nativeIndexWriter); + } + } + + public void testFlush_whenThresholdIsEqualToMinNumberOfVectors_thenNativeIndexWriterIsCalled() throws IOException { + // Given + List> expectedVectorValues = new ArrayList<>(); + final Map sizeMap = new HashMap<>(); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(vectorsPerField.get(i).values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.FLOAT, + randomVectorValues + ); + sizeMap.put(i, randomVectorValues.size()); + expectedVectorValues.add(knnVectorValues); + + }); + + final int minThreshold = sizeMap.values().stream().filter(count -> count != 0).min(Integer::compareTo).orElse(0); + final NativeEngines990KnnVectorsWriter nativeEngineWriter = new NativeEngines990KnnVectorsWriter( + segmentWriteState, + flatVectorsWriter, + minThreshold + ); + + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final FieldInfo fieldInfo = fieldInfo( + i, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, vectorsPerField.get(i)); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); + try { + nativeEngineWriter.addField(fieldInfo); + } catch (Exception e) { + throw new RuntimeException(e); + } + + DocsWithFieldSet docsWithFieldSet = field.getDocsWithField(); + knnVectorValuesFactoryMockedStatic.when( + () -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, docsWithFieldSet, vectorsPerField.get(i)) + ).thenReturn(expectedVectorValues.get(i)); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(null); + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, null)) + .thenReturn(nativeIndexWriter); + }); + + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).flushIndex(any(), anyInt()); + + // When + nativeEngineWriter.flush(5, null); + + // Then + verify(flatVectorsWriter).flush(5, null); + if (vectorsPerField.size() > 0) { + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + assertTrue((long) KNNGraphValue.REFRESH_TOTAL_TIME_IN_MILLIS.getValue() > 0); + } + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + try { + if (vectorsPerField.get(i).size() > 0) { + verify(nativeIndexWriter).flushIndex(expectedVectorValues.get(i), vectorsPerField.get(i).size()); + } else { + verify(nativeIndexWriter, never()).flushIndex(expectedVectorValues.get(i), vectorsPerField.get(i).size()); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + } + + public void testFlush_whenThresholdIsEqualToFixedValue_thenRelevantNativeIndexWriterIsCalled() throws IOException { + // Given + List> expectedVectorValues = new ArrayList<>(); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(vectorsPerField.get(i).values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.FLOAT, + randomVectorValues + ); + expectedVectorValues.add(knnVectorValues); + + }); + final int threshold = 4; + final NativeEngines990KnnVectorsWriter nativeEngineWriter = new NativeEngines990KnnVectorsWriter( + segmentWriteState, + flatVectorsWriter, + threshold + ); + + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final FieldInfo fieldInfo = fieldInfo( + i, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, vectorsPerField.get(i)); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); + try { + nativeEngineWriter.addField(fieldInfo); + } catch (Exception e) { + throw new RuntimeException(e); + } + + DocsWithFieldSet docsWithFieldSet = field.getDocsWithField(); + knnVectorValuesFactoryMockedStatic.when( + () -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, docsWithFieldSet, vectorsPerField.get(i)) + ).thenReturn(expectedVectorValues.get(i)); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(null); + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, null)) + .thenReturn(nativeIndexWriter); + }); + + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).flushIndex(any(), anyInt()); + + // When + nativeEngineWriter.flush(5, null); + + // Then + verify(flatVectorsWriter).flush(5, null); + if (vectorsPerField.size() > 0) { + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + } + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + try { + if (vectorsPerField.get(i).size() >= threshold) { + verify(nativeIndexWriter).flushIndex(expectedVectorValues.get(i), vectorsPerField.get(i).size()); + } else { + verify(nativeIndexWriter, never()).flushIndex(expectedVectorValues.get(i), vectorsPerField.get(i).size()); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + } + + public void testFlush_whenQuantizationIsProvided_whenBuildGraphDatStructureThresholdIsNotMet_thenSkipBuildingGraph() + throws IOException { + // Given + List> expectedVectorValues = new ArrayList<>(); + final Map sizeMap = new HashMap<>(); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(vectorsPerField.get(i).values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.FLOAT, + randomVectorValues + ); + sizeMap.put(i, randomVectorValues.size()); + expectedVectorValues.add(knnVectorValues); + + }); + final int maxThreshold = sizeMap.values().stream().filter(count -> count != 0).max(Integer::compareTo).orElse(0); + final NativeEngines990KnnVectorsWriter nativeEngineWriter = new NativeEngines990KnnVectorsWriter( + segmentWriteState, + flatVectorsWriter, + maxThreshold + 1 // to avoid building graph using max doc threshold, the same can be achieved by -1 too + ); + + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final FieldInfo fieldInfo = fieldInfo( + i, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, vectorsPerField.get(i)); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); + + try { + nativeEngineWriter.addField(fieldInfo); + } catch (Exception e) { + throw new RuntimeException(e); + } + + DocsWithFieldSet docsWithFieldSet = field.getDocsWithField(); + knnVectorValuesFactoryMockedStatic.when( + () -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, docsWithFieldSet, vectorsPerField.get(i)) + ).thenReturn(expectedVectorValues.get(i)); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(quantizationParams); + try { + when(quantizationService.train(quantizationParams, expectedVectorValues.get(i), vectorsPerField.get(i).size())) + .thenReturn(quantizationState); + } catch (Exception e) { + throw new RuntimeException(e); + } + + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, quantizationState)) + .thenReturn(nativeIndexWriter); + }); + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).flushIndex(any(), anyInt()); + + // When + nativeEngineWriter.flush(5, null); + + // Then + verify(flatVectorsWriter).flush(5, null); + if (vectorsPerField.size() > 0) { + verify(knn990QuantWriterMockedConstruction.constructed().get(0)).writeHeader(segmentWriteState); + } else { + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + } + verifyNoInteractions(nativeIndexWriter); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + try { + if (vectorsPerField.get(i).isEmpty()) { + verify(knn990QuantWriterMockedConstruction.constructed().get(0), never()).writeState(i, quantizationState); + } else { + verify(knn990QuantWriterMockedConstruction.constructed().get(0)).writeState(i, quantizationState); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + final Long expectedTimesGetVectorValuesIsCalled = vectorsPerField.stream().filter(Predicate.not(Map::isEmpty)).count(); + knnVectorValuesFactoryMockedStatic.verify( + () -> KNNVectorValuesFactory.getVectorValues(any(VectorDataType.class), any(DocsWithFieldSet.class), any()), + times(Math.toIntExact(expectedTimesGetVectorValuesIsCalled)) + ); + } + } + + public void testFlush_whenQuantizationIsProvided_whenBuildGraphDatStructureThresholdIsNegative_thenSkipBuildingGraph() + throws IOException { + // Given + List> expectedVectorValues = new ArrayList<>(); + final Map sizeMap = new HashMap<>(); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(vectorsPerField.get(i).values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.FLOAT, + randomVectorValues + ); + sizeMap.put(i, randomVectorValues.size()); + expectedVectorValues.add(knnVectorValues); + + }); + final NativeEngines990KnnVectorsWriter nativeEngineWriter = new NativeEngines990KnnVectorsWriter( + segmentWriteState, + flatVectorsWriter, + BUILD_GRAPH_NEVER_THRESHOLD + ); + + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + final FieldInfo fieldInfo = fieldInfo( + i, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, vectorsPerField.get(i)); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); + + try { + nativeEngineWriter.addField(fieldInfo); + } catch (Exception e) { + throw new RuntimeException(e); + } + + DocsWithFieldSet docsWithFieldSet = field.getDocsWithField(); + knnVectorValuesFactoryMockedStatic.when( + () -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, docsWithFieldSet, vectorsPerField.get(i)) + ).thenReturn(expectedVectorValues.get(i)); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(quantizationParams); + try { + when(quantizationService.train(quantizationParams, expectedVectorValues.get(i), vectorsPerField.get(i).size())) + .thenReturn(quantizationState); + } catch (Exception e) { + throw new RuntimeException(e); + } + + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, quantizationState)) + .thenReturn(nativeIndexWriter); + }); + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).flushIndex(any(), anyInt()); + + // When + nativeEngineWriter.flush(5, null); + + // Then + verify(flatVectorsWriter).flush(5, null); + if (vectorsPerField.size() > 0) { + verify(knn990QuantWriterMockedConstruction.constructed().get(0)).writeHeader(segmentWriteState); + } else { + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + } + verifyNoInteractions(nativeIndexWriter); + IntStream.range(0, vectorsPerField.size()).forEach(i -> { + try { + if (vectorsPerField.get(i).isEmpty()) { + verify(knn990QuantWriterMockedConstruction.constructed().get(0), never()).writeState(i, quantizationState); + } else { + verify(knn990QuantWriterMockedConstruction.constructed().get(0)).writeState(i, quantizationState); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + final Long expectedTimesGetVectorValuesIsCalled = vectorsPerField.stream().filter(Predicate.not(Map::isEmpty)).count(); + knnVectorValuesFactoryMockedStatic.verify( + () -> KNNVectorValuesFactory.getVectorValues(any(VectorDataType.class), any(DocsWithFieldSet.class), any()), + times(Math.toIntExact(expectedTimesGetVectorValuesIsCalled)) + ); + } + } + + private FieldInfo fieldInfo(int fieldNumber, VectorEncoding vectorEncoding, Map attributes) { + FieldInfo fieldInfo = mock(FieldInfo.class); + when(fieldInfo.getFieldNumber()).thenReturn(fieldNumber); + when(fieldInfo.getVectorEncoding()).thenReturn(vectorEncoding); + when(fieldInfo.attributes()).thenReturn(attributes); + attributes.forEach((key, value) -> when(fieldInfo.getAttribute(key)).thenReturn(value)); + return fieldInfo; + } + + private NativeEngineFieldVectorsWriter nativeEngineFieldVectorsWriter(FieldInfo fieldInfo, Map vectors) { + NativeEngineFieldVectorsWriter fieldVectorsWriter = mock(NativeEngineFieldVectorsWriter.class); + DocsWithFieldSet docsWithFieldSet = new DocsWithFieldSet(); + vectors.keySet().stream().sorted().forEach(docsWithFieldSet::add); + when(fieldVectorsWriter.getFieldInfo()).thenReturn(fieldInfo); + when(fieldVectorsWriter.getVectors()).thenReturn(vectors); + when(fieldVectorsWriter.getDocsWithField()).thenReturn(docsWithFieldSet); + return fieldVectorsWriter; + } +} diff --git a/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterMergeTests.java b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterMergeTests.java new file mode 100644 index 000000000..77f3fd8ed --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/codec/KNN990Codec/NativeEngines990KnnVectorsWriterMergeTests.java @@ -0,0 +1,380 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.KNN990Codec; + +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.apache.lucene.codecs.KnnVectorsWriter; +import org.apache.lucene.codecs.hnsw.FlatFieldVectorsWriter; +import org.apache.lucene.codecs.hnsw.FlatVectorsWriter; +import org.apache.lucene.index.DocsWithFieldSet; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.FloatVectorValues; +import org.apache.lucene.index.MergeState; +import org.apache.lucene.index.SegmentWriteState; +import org.apache.lucene.index.VectorEncoding; +import org.mockito.Mock; +import org.mockito.MockedConstruction; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.codec.nativeindex.NativeIndexWriter; +import org.opensearch.knn.index.quantizationservice.QuantizationService; +import org.opensearch.knn.index.vectorvalues.KNNVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNVectorValuesFactory; +import org.opensearch.knn.index.vectorvalues.TestVectorValues; +import org.opensearch.knn.plugin.stats.KNNGraphValue; +import org.opensearch.knn.quantization.models.quantizationParams.QuantizationParams; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; +import org.opensearch.test.OpenSearchTestCase; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; + +import static com.carrotsearch.randomizedtesting.RandomizedTest.$; +import static com.carrotsearch.randomizedtesting.RandomizedTest.$$; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockConstruction; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@RequiredArgsConstructor +public class NativeEngines990KnnVectorsWriterMergeTests extends OpenSearchTestCase { + + @Mock + private FlatVectorsWriter flatVectorsWriter; + @Mock + private SegmentWriteState segmentWriteState; + @Mock + private QuantizationParams quantizationParams; + @Mock + private QuantizationState quantizationState; + @Mock + private QuantizationService quantizationService; + @Mock + private NativeIndexWriter nativeIndexWriter; + @Mock + private FloatVectorValues floatVectorValues; + @Mock + private MergeState mergeState; + + private NativeEngines990KnnVectorsWriter objectUnderTest; + + private final String description; + private final Map mergedVectors; + private FlatFieldVectorsWriter mockedFlatFieldVectorsWriter; + private static final Integer BUILD_GRAPH_ALWAYS_THRESHOLD = 0; + private static final Integer BUILD_GRAPH_NEVER_THRESHOLD = -1; + + @Override + public void setUp() throws Exception { + super.setUp(); + MockitoAnnotations.openMocks(this); + objectUnderTest = new NativeEngines990KnnVectorsWriter(segmentWriteState, flatVectorsWriter, BUILD_GRAPH_ALWAYS_THRESHOLD); + mockedFlatFieldVectorsWriter = Mockito.mock(FlatFieldVectorsWriter.class); + Mockito.doNothing().when(mockedFlatFieldVectorsWriter).addValue(Mockito.anyInt(), Mockito.any()); + Mockito.when(flatVectorsWriter.addField(Mockito.any())).thenReturn(mockedFlatFieldVectorsWriter); + } + + @ParametersFactory + public static Collection data() { + return Arrays.asList( + $$( + $("Merge one field", Map.of(0, new float[] { 1, 2, 3 }, 1, new float[] { 2, 3, 4 }, 2, new float[] { 3, 4, 5 })), + $("Merge, no live docs", Map.of()) + ) + ); + } + + @SneakyThrows + public void testMerge() { + // Given + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(mergedVectors.values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, randomVectorValues); + + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedStatic mergedVectorValuesMockedStatic = mockStatic( + KnnVectorsWriter.MergedVectorValues.class + ); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + final FieldInfo fieldInfo = fieldInfo( + 0, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, mergedVectors); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); + + mergedVectorValuesMockedStatic.when(() -> KnnVectorsWriter.MergedVectorValues.mergeFloatVectorValues(fieldInfo, mergeState)) + .thenReturn(floatVectorValues); + knnVectorValuesFactoryMockedStatic.when(() -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, floatVectorValues)) + .thenReturn(knnVectorValues); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(null); + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, null)) + .thenReturn(nativeIndexWriter); + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).mergeIndex(any(), anyInt()); + + // When + objectUnderTest.mergeOneField(fieldInfo, mergeState); + + // Then + verify(flatVectorsWriter).mergeOneField(fieldInfo, mergeState); + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + if (!mergedVectors.isEmpty()) { + verify(nativeIndexWriter).mergeIndex(knnVectorValues, mergedVectors.size()); + assertTrue(KNNGraphValue.MERGE_TOTAL_TIME_IN_MILLIS.getValue() > 0L); + knnVectorValuesFactoryMockedStatic.verify( + () -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, floatVectorValues), + times(2) + ); + } else { + verifyNoInteractions(nativeIndexWriter); + } + } + } + + public void testMerge_whenThresholdIsNegative_thenNativeIndexWriterIsNeverCalled() throws IOException { + // Given + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(mergedVectors.values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, randomVectorValues); + final NativeEngines990KnnVectorsWriter nativeEngineWriter = new NativeEngines990KnnVectorsWriter( + segmentWriteState, + flatVectorsWriter, + BUILD_GRAPH_NEVER_THRESHOLD + ); + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedStatic mergedVectorValuesMockedStatic = mockStatic( + KnnVectorsWriter.MergedVectorValues.class + ); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + final FieldInfo fieldInfo = fieldInfo( + 0, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, mergedVectors); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); + + mergedVectorValuesMockedStatic.when(() -> KnnVectorsWriter.MergedVectorValues.mergeFloatVectorValues(fieldInfo, mergeState)) + .thenReturn(floatVectorValues); + knnVectorValuesFactoryMockedStatic.when(() -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, floatVectorValues)) + .thenReturn(knnVectorValues); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(null); + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, null)) + .thenReturn(nativeIndexWriter); + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).mergeIndex(any(), anyInt()); + + // When + nativeEngineWriter.mergeOneField(fieldInfo, mergeState); + + // Then + verify(flatVectorsWriter).mergeOneField(fieldInfo, mergeState); + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + verifyNoInteractions(nativeIndexWriter); + } + } + + public void testMerge_whenThresholdIsEqualToNumberOfVectors_thenNativeIndexWriterIsCalled() throws IOException { + // Given + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(mergedVectors.values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, randomVectorValues); + final NativeEngines990KnnVectorsWriter nativeEngineWriter = new NativeEngines990KnnVectorsWriter( + segmentWriteState, + flatVectorsWriter, + mergedVectors.size() + ); + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedStatic mergedVectorValuesMockedStatic = mockStatic( + KnnVectorsWriter.MergedVectorValues.class + ); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + final FieldInfo fieldInfo = fieldInfo( + 0, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, mergedVectors); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); + + mergedVectorValuesMockedStatic.when(() -> KnnVectorsWriter.MergedVectorValues.mergeFloatVectorValues(fieldInfo, mergeState)) + .thenReturn(floatVectorValues); + knnVectorValuesFactoryMockedStatic.when(() -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, floatVectorValues)) + .thenReturn(knnVectorValues); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(null); + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, null)) + .thenReturn(nativeIndexWriter); + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).mergeIndex(any(), anyInt()); + + // When + nativeEngineWriter.mergeOneField(fieldInfo, mergeState); + + // Then + verify(flatVectorsWriter).mergeOneField(fieldInfo, mergeState); + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + if (!mergedVectors.isEmpty()) { + verify(nativeIndexWriter).mergeIndex(knnVectorValues, mergedVectors.size()); + } else { + verifyNoInteractions(nativeIndexWriter); + } + } + } + + @SneakyThrows + public void testMerge_WithQuantization() { + // Given + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + new ArrayList<>(mergedVectors.values()) + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, randomVectorValues); + + try ( + MockedStatic fieldWriterMockedStatic = mockStatic(NativeEngineFieldVectorsWriter.class); + MockedStatic knnVectorValuesFactoryMockedStatic = mockStatic(KNNVectorValuesFactory.class); + MockedStatic quantizationServiceMockedStatic = mockStatic(QuantizationService.class); + MockedStatic nativeIndexWriterMockedStatic = mockStatic(NativeIndexWriter.class); + MockedConstruction knn990QuantWriterMockedConstruction = mockConstruction( + KNN990QuantizationStateWriter.class + ); + MockedStatic mergedVectorValuesMockedStatic = mockStatic( + KnnVectorsWriter.MergedVectorValues.class + ); + ) { + quantizationServiceMockedStatic.when(() -> QuantizationService.getInstance()).thenReturn(quantizationService); + + final FieldInfo fieldInfo = fieldInfo( + 0, + VectorEncoding.FLOAT32, + Map.of(KNNConstants.VECTOR_DATA_TYPE_FIELD, "float", KNNConstants.KNN_ENGINE, "faiss") + ); + + NativeEngineFieldVectorsWriter field = nativeEngineFieldVectorsWriter(fieldInfo, mergedVectors); + fieldWriterMockedStatic.when( + () -> NativeEngineFieldVectorsWriter.create(fieldInfo, mockedFlatFieldVectorsWriter, segmentWriteState.infoStream) + ).thenReturn(field); + + mergedVectorValuesMockedStatic.when(() -> KnnVectorsWriter.MergedVectorValues.mergeFloatVectorValues(fieldInfo, mergeState)) + .thenReturn(floatVectorValues); + knnVectorValuesFactoryMockedStatic.when(() -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, floatVectorValues)) + .thenReturn(knnVectorValues); + + when(quantizationService.getQuantizationParams(fieldInfo)).thenReturn(quantizationParams); + try { + when(quantizationService.train(quantizationParams, knnVectorValues, mergedVectors.size())).thenReturn(quantizationState); + } catch (Exception e) { + throw new RuntimeException(e); + } + + nativeIndexWriterMockedStatic.when(() -> NativeIndexWriter.getWriter(fieldInfo, segmentWriteState, quantizationState)) + .thenReturn(nativeIndexWriter); + doAnswer(answer -> { + Thread.sleep(2); // Need this for KNNGraph value assertion, removing this will fail the assertion + return null; + }).when(nativeIndexWriter).mergeIndex(any(), anyInt()); + + // When + objectUnderTest.mergeOneField(fieldInfo, mergeState); + + // Then + verify(flatVectorsWriter).mergeOneField(fieldInfo, mergeState); + if (!mergedVectors.isEmpty()) { + verify(knn990QuantWriterMockedConstruction.constructed().get(0)).writeHeader(segmentWriteState); + verify(knn990QuantWriterMockedConstruction.constructed().get(0)).writeState(0, quantizationState); + verify(nativeIndexWriter).mergeIndex(knnVectorValues, mergedVectors.size()); + assertTrue(KNNGraphValue.MERGE_TOTAL_TIME_IN_MILLIS.getValue() > 0L); + knnVectorValuesFactoryMockedStatic.verify( + () -> KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, floatVectorValues), + times(3) + ); + } else { + assertEquals(0, knn990QuantWriterMockedConstruction.constructed().size()); + verifyNoInteractions(nativeIndexWriter); + } + + } + } + + private FieldInfo fieldInfo(int fieldNumber, VectorEncoding vectorEncoding, Map attributes) { + FieldInfo fieldInfo = mock(FieldInfo.class); + when(fieldInfo.getFieldNumber()).thenReturn(fieldNumber); + when(fieldInfo.getVectorEncoding()).thenReturn(vectorEncoding); + when(fieldInfo.attributes()).thenReturn(attributes); + attributes.forEach((key, value) -> when(fieldInfo.getAttribute(key)).thenReturn(value)); + return fieldInfo; + } + + private NativeEngineFieldVectorsWriter nativeEngineFieldVectorsWriter(FieldInfo fieldInfo, Map vectors) { + NativeEngineFieldVectorsWriter fieldVectorsWriter = mock(NativeEngineFieldVectorsWriter.class); + DocsWithFieldSet docsWithFieldSet = new DocsWithFieldSet(); + vectors.keySet().stream().sorted().forEach(docsWithFieldSet::add); + when(fieldVectorsWriter.getFieldInfo()).thenReturn(fieldInfo); + when(fieldVectorsWriter.getVectors()).thenReturn(vectors); + when(fieldVectorsWriter.getDocsWithField()).thenReturn(docsWithFieldSet); + return fieldVectorsWriter; + } +} diff --git a/src/test/java/org/opensearch/knn/index/codec/KNNCodecFactoryTests.java b/src/test/java/org/opensearch/knn/index/codec/KNNCodecFactoryTests.java index c2ab1b5d9..29dae6085 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNNCodecFactoryTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNNCodecFactoryTests.java @@ -5,30 +5,47 @@ package org.opensearch.knn.index.codec; +import org.apache.lucene.backward_codecs.lucene92.Lucene92Codec; import org.apache.lucene.codecs.Codec; import org.apache.lucene.backward_codecs.lucene91.Lucene91Codec; +import org.apache.lucene.backward_codecs.lucene94.Lucene94Codec; +import org.apache.lucene.backward_codecs.lucene95.Lucene95Codec; import org.opensearch.knn.KNNTestCase; -import org.opensearch.knn.index.codec.KNN910Codec.KNN910Codec; + +import static org.opensearch.knn.index.codec.KNNCodecVersion.V_9_1_0; +import static org.opensearch.knn.index.codec.KNNCodecVersion.V_9_2_0; +import static org.opensearch.knn.index.codec.KNNCodecVersion.V_9_4_0; +import static org.opensearch.knn.index.codec.KNNCodecVersion.V_9_5_0; public class KNNCodecFactoryTests extends KNNTestCase { - public void testKNN91DefaultDelegate() { - Codec knn91DefaultDelegate = KNNCodecFactory.CodecDelegateFactory.createKNN91DefaultDelegate(); - assertNotNull(knn91DefaultDelegate); - assertTrue(knn91DefaultDelegate instanceof Lucene91Codec); + public void testKNN910Codec() { + assertDelegateForVersion(V_9_1_0, Lucene91Codec.class); + assertNull(V_9_1_0.getPerFieldKnnVectorsFormat()); + assertNotNull(V_9_1_0.getKnnFormatFacadeSupplier().apply(V_9_1_0.getDefaultCodecDelegate())); + } + + public void testKNN920Codec() { + assertDelegateForVersion(V_9_2_0, Lucene92Codec.class); + assertNotNull(V_9_2_0.getPerFieldKnnVectorsFormat()); + assertNotNull(V_9_2_0.getKnnFormatFacadeSupplier().apply(V_9_2_0.getDefaultCodecDelegate())); + } + + public void testKNN940Codec() { + assertDelegateForVersion(V_9_4_0, Lucene94Codec.class); + assertNotNull(V_9_4_0.getPerFieldKnnVectorsFormat()); + assertNotNull(V_9_4_0.getKnnFormatFacadeSupplier().apply(V_9_4_0.getDefaultCodecDelegate())); } - public void testKNN91DefaultCodec() { - Lucene91Codec lucene91CodecDelegate = new Lucene91Codec(); - Codec knnCodec = KNNCodecFactory.createKNNCodec(lucene91CodecDelegate); - assertNotNull(knnCodec); - assertTrue(knnCodec instanceof KNN910Codec); + public void testKNN950Codec() { + assertDelegateForVersion(V_9_5_0, Lucene95Codec.class); + assertNotNull(V_9_5_0.getPerFieldKnnVectorsFormat()); + assertNotNull(V_9_5_0.getKnnFormatFacadeSupplier().apply(V_9_5_0.getDefaultCodecDelegate())); } - public void testKNN91CodecByVersion() { - Lucene91Codec lucene91CodecDelegate = new Lucene91Codec(); - Codec knnCodec = KNNCodecFactory.createKNNCodec(KNNCodecFactory.KNNCodecVersion.KNN910, lucene91CodecDelegate); - assertNotNull(knnCodec); - assertTrue(knnCodec instanceof KNN910Codec); + private void assertDelegateForVersion(final KNNCodecVersion codecVersion, final Class expectedCodecClass) { + final Codec defaultDelegate = codecVersion.getDefaultCodecDelegate(); + assertNotNull(defaultDelegate); + assertTrue(defaultDelegate.getClass().isAssignableFrom(expectedCodecClass)); } } diff --git a/src/test/java/org/opensearch/knn/index/codec/KNNCodecServiceTests.java b/src/test/java/org/opensearch/knn/index/codec/KNNCodecServiceTests.java new file mode 100644 index 000000000..dfe4e7f22 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/codec/KNNCodecServiceTests.java @@ -0,0 +1,68 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec; + +import org.apache.lucene.codecs.Codec; +import org.opensearch.cluster.metadata.IndexMetadata; +import org.opensearch.common.settings.Settings; +import org.opensearch.core.index.Index; +import org.opensearch.index.IndexSettings; +import org.opensearch.index.codec.CodecServiceConfig; +import org.opensearch.index.mapper.MapperService; +import org.opensearch.knn.KNNTestCase; + +import org.apache.logging.log4j.Logger; + +import java.util.UUID; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Test for KNNCodecService class with focus on codec by name lookup + */ +public class KNNCodecServiceTests extends KNNTestCase { + private static final String TEST_INDEX = "test-index"; + private static final int NUM_OF_SHARDS = 1; + private static final UUID INDEX_UUID = UUID.randomUUID(); + + private IndexSettings indexSettings; + + @Override + public void setUp() throws Exception { + super.setUp(); + IndexMetadata indexMetadata = mock(IndexMetadata.class); + when(indexMetadata.getIndex()).thenReturn(new Index(TEST_INDEX, INDEX_UUID.toString())); + when(indexMetadata.getCustomData(IndexMetadata.REMOTE_STORE_CUSTOM_KEY)).thenReturn(null); + when(indexMetadata.getSettings()).thenReturn(Settings.EMPTY); + Settings settings = Settings.builder().put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, Integer.toString(NUM_OF_SHARDS)).build(); + indexSettings = new IndexSettings(indexMetadata, settings); + } + + public void testGetCodecByName() { + MapperService mapperService = mock(MapperService.class); + Logger loggerMock = mock(Logger.class); + CodecServiceConfig codecServiceConfig = new CodecServiceConfig(indexSettings, mapperService, loggerMock); + KNNCodecService knnCodecService = new KNNCodecService(codecServiceConfig); + Codec codec = knnCodecService.codec(KNNCodecVersion.current().getCodecName()); + assertNotNull(codec); + } + + /** + * This test case covers scenarios when MapperService is null, for instance this may happen during index.close operation. + * In such scenario codec is not really required but is created as part of engine initialization, please check code references below: + * @see EngineConfig.java + * @see IndexShard.java + * @see Engine.java + */ + public void testGetCodecByNameWithNoMapperService() { + Logger loggerMock = mock(Logger.class); + CodecServiceConfig codecServiceConfig = new CodecServiceConfig(indexSettings, null, loggerMock); + KNNCodecService knnCodecService = new KNNCodecService(codecServiceConfig); + Codec codec = knnCodecService.codec(KNNCodecVersion.current().getCodecName()); + assertNotNull(codec); + } +} diff --git a/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestCase.java b/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestCase.java index 2251308a0..315693a65 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestCase.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestCase.java @@ -7,18 +7,39 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import org.apache.lucene.codecs.perfield.PerFieldKnnVectorsFormat; +import org.apache.lucene.document.KnnFloatVectorField; +import org.apache.lucene.document.KnnVectorField; +import org.apache.lucene.index.DocValuesType; +import org.apache.lucene.index.NoMergePolicy; +import org.apache.lucene.index.VectorSimilarityFunction; +import org.apache.lucene.search.Query; import org.apache.lucene.search.TopDocs; +import org.apache.lucene.search.join.BitSetProducer; +import org.mockito.MockedStatic; +import org.opensearch.Version; import org.opensearch.common.settings.ClusterSettings; +import org.opensearch.common.settings.Setting; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.index.mapper.MapperService; import org.opensearch.knn.KNNTestCase; import org.opensearch.knn.common.KNNConstants; -import org.opensearch.knn.index.codec.KNN910Codec.KNN910Codec; -import org.opensearch.knn.jni.JNIService; -import org.opensearch.knn.index.KNNQuery; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; import org.opensearch.knn.index.KNNSettings; -import org.opensearch.knn.index.KNNVectorFieldMapper; -import org.opensearch.knn.index.KNNWeight; +import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.VectorField; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.KNNVectorFieldType; +import org.opensearch.knn.index.mapper.Mode; +import org.opensearch.knn.index.query.BaseQueryFactory; +import org.opensearch.knn.index.query.KNNQueryFactory; +import org.opensearch.knn.jni.JNIService; +import org.opensearch.knn.index.query.KNNQuery; +import org.opensearch.knn.index.mapper.KNNVectorFieldMapper; +import org.opensearch.knn.index.query.KNNWeight; import org.apache.lucene.codecs.Codec; import org.apache.lucene.document.Document; import org.apache.lucene.document.FieldType; @@ -31,7 +52,7 @@ import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.settings.Settings; import org.opensearch.knn.index.memory.NativeMemoryLoadStrategy; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.indices.Model; import org.opensearch.knn.indices.ModelCache; import org.opensearch.knn.indices.ModelDao; @@ -44,15 +65,30 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.ExecutionException; +import java.util.function.Function; import java.util.stream.Collectors; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.opensearch.Version.CURRENT; +import static org.opensearch.knn.common.KNNConstants.DEFAULT_VECTOR_DATA_TYPE_FIELD; +import static org.opensearch.knn.common.KNNConstants.HNSW_ALGO_EF_CONSTRUCTION; +import static org.opensearch.knn.common.KNNConstants.HNSW_ALGO_M; import static org.opensearch.knn.common.KNNConstants.INDEX_DESCRIPTION_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_M; import static org.opensearch.knn.common.KNNConstants.SPACE_TYPE; import static org.opensearch.knn.index.KNNSettings.MODEL_CACHE_SIZE_LIMIT_SETTING; @@ -60,23 +96,54 @@ * Test used for testing Codecs */ public class KNNCodecTestCase extends KNNTestCase { - - private static final KNN910Codec ACTUAL_CODEC = new KNN910Codec(); - private static FieldType sampleFieldType; + private static final FieldType sampleFieldType; static { + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(CURRENT) + .vectorDataType(VectorDataType.DEFAULT) + .build(); + KNNMethodContext knnMethodContext = new KNNMethodContext( + KNNEngine.NMSLIB, + SpaceType.DEFAULT, + new MethodComponentContext(METHOD_HNSW, ImmutableMap.of(METHOD_PARAMETER_M, 16, METHOD_PARAMETER_EF_CONSTRUCTION, 512)) + ); + String parameterString; + try { + parameterString = XContentFactory.jsonBuilder() + .map( + knnMethodContext.getKnnEngine() + .getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext) + .getLibraryParameters() + ) + .toString(); + } catch (IOException e) { + throw new RuntimeException(e); + } + sampleFieldType = new FieldType(KNNVectorFieldMapper.Defaults.FIELD_TYPE); - sampleFieldType.putAttribute(KNNConstants.KNN_METHOD, KNNConstants.METHOD_HNSW); - sampleFieldType.putAttribute(KNNConstants.KNN_ENGINE, KNNEngine.NMSLIB.getName()); - sampleFieldType.putAttribute(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()); - sampleFieldType.putAttribute(KNNConstants.HNSW_ALGO_M, "32"); - sampleFieldType.putAttribute(KNNConstants.HNSW_ALGO_EF_CONSTRUCTION, "512"); + sampleFieldType.setDocValuesType(DocValuesType.BINARY); + sampleFieldType.putAttribute(KNNVectorFieldMapper.KNN_FIELD, "true"); + sampleFieldType.putAttribute(KNNConstants.KNN_ENGINE, knnMethodContext.getKnnEngine().getName()); + sampleFieldType.putAttribute(KNNConstants.SPACE_TYPE, knnMethodContext.getSpaceType().getValue()); + sampleFieldType.putAttribute(KNNConstants.PARAMETERS, parameterString); sampleFieldType.freeze(); } + private static final String FIELD_NAME_ONE = "test_vector_one"; + private static final String FIELD_NAME_TWO = "test_vector_two"; protected void setUpMockClusterService() { ClusterService clusterService = mock(ClusterService.class, RETURNS_DEEP_STUBS); Settings settings = Settings.Builder.EMPTY_SETTINGS; when(clusterService.state().getMetadata().index(Mockito.anyString()).getSettings()).thenReturn(settings); + Set> defaultClusterSettings = new HashSet<>(ClusterSettings.BUILT_IN_CLUSTER_SETTINGS); + defaultClusterSettings.addAll( + KNNSettings.state() + .getSettings() + .stream() + .filter(s -> s.getProperties().contains(Setting.Property.NodeScope)) + .collect(Collectors.toList()) + ); + when(clusterService.getClusterSettings()).thenReturn(new ClusterSettings(Settings.EMPTY, defaultClusterSettings)); KNNSettings.state().setClusterService(clusterService); } @@ -87,60 +154,67 @@ protected ResourceWatcherService createDisabledResourceWatcherService() { public void testMultiFieldsKnnIndex(Codec codec) throws Exception { setUpMockClusterService(); - Directory dir = newFSDirectory(createTempDir()); - IndexWriterConfig iwc = newIndexWriterConfig(); - iwc.setMergeScheduler(new SerialMergeScheduler()); - iwc.setCodec(codec); - - /** - * Add doc with field "test_vector" - */ - float[] array = { 1.0f, 3.0f, 4.0f }; - VectorField vectorField = new VectorField("test_vector", array, sampleFieldType); - RandomIndexWriter writer = new RandomIndexWriter(random(), dir, iwc); - Document doc = new Document(); - doc.add(vectorField); - writer.addDocument(doc); - writer.close(); - - /** - * Add doc with field "my_vector" - */ - IndexWriterConfig iwc1 = newIndexWriterConfig(); - iwc1.setMergeScheduler(new SerialMergeScheduler()); - iwc1.setCodec(ACTUAL_CODEC); - writer = new RandomIndexWriter(random(), dir, iwc1); - float[] array1 = { 6.0f, 14.0f }; - VectorField vectorField1 = new VectorField("my_vector", array1, sampleFieldType); - Document doc1 = new Document(); - doc1.add(vectorField1); - writer.addDocument(doc1); - IndexReader reader = writer.getReader(); - writer.close(); - ResourceWatcherService resourceWatcherService = createDisabledResourceWatcherService(); - NativeMemoryLoadStrategy.IndexLoadStrategy.initialize(resourceWatcherService); - List hnswfiles = Arrays.stream(dir.listAll()).filter(x -> x.contains("hnsw")).collect(Collectors.toList()); - - // there should be 2 hnsw index files created. one for test_vector and one for my_vector - assertEquals(hnswfiles.size(), 2); - assertEquals(hnswfiles.stream().filter(x -> x.contains("test_vector")).collect(Collectors.toList()).size(), 1); - assertEquals(hnswfiles.stream().filter(x -> x.contains("my_vector")).collect(Collectors.toList()).size(), 1); - - // query to verify distance for each of the field - IndexSearcher searcher = new IndexSearcher(reader); - float score = searcher.search(new KNNQuery("test_vector", new float[] { 1.0f, 0.0f, 0.0f }, 1, "dummy"), 10).scoreDocs[0].score; - float score1 = searcher.search(new KNNQuery("my_vector", new float[] { 1.0f, 2.0f }, 1, "dummy"), 10).scoreDocs[0].score; - assertEquals(1.0f / (1 + 25), score, 0.01f); - assertEquals(1.0f / (1 + 169), score1, 0.01f); - - // query to determine the hits - assertEquals(1, searcher.count(new KNNQuery("test_vector", new float[] { 1.0f, 0.0f, 0.0f }, 1, "dummy"))); - assertEquals(1, searcher.count(new KNNQuery("my_vector", new float[] { 1.0f, 1.0f }, 1, "dummy"))); - - reader.close(); - dir.close(); - resourceWatcherService.close(); - NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance().close(); + try (Directory dir = newFSDirectory(createTempDir())) { + IndexWriterConfig iwc = newIndexWriterConfig(); + iwc.setMergeScheduler(new SerialMergeScheduler()); + iwc.setCodec(codec); + // Set merge policy to no merges so that we create a predictable number of segments. + iwc.setMergePolicy(NoMergePolicy.INSTANCE); + + /** + * Add doc with field "test_vector" + */ + float[] array = { 1.0f, 3.0f, 4.0f }; + VectorField vectorField = new VectorField("test_vector", array, sampleFieldType); + RandomIndexWriter writer = new RandomIndexWriter(random(), dir, iwc); + Document doc = new Document(); + doc.add(vectorField); + writer.addDocument(doc); + // ensuring the refresh happens, to create the segment and hnsw file + writer.flush(); + + /** + * Add doc with field "my_vector" + */ + float[] array1 = { 6.0f, 14.0f }; + VectorField vectorField1 = new VectorField("my_vector", array1, sampleFieldType); + Document doc1 = new Document(); + doc1.add(vectorField1); + writer.addDocument(doc1); + // ensuring the refresh happens, to create the segment and hnsw file + writer.flush(); + IndexReader reader = writer.getReader(); + writer.close(); + List hnswfiles = Arrays.stream(dir.listAll()).filter(x -> x.contains("hnsw")).collect(Collectors.toList()); + + // there should be 2 hnsw index files created. one for test_vector and one for my_vector + assertEquals(2, hnswfiles.size()); + assertEquals(hnswfiles.stream().filter(x -> x.contains("test_vector")).collect(Collectors.toList()).size(), 1); + assertEquals(hnswfiles.stream().filter(x -> x.contains("my_vector")).collect(Collectors.toList()).size(), 1); + + // query to verify distance for each of the field + IndexSearcher searcher = new IndexSearcher(reader); + float score = searcher.search( + new KNNQuery("test_vector", new float[] { 1.0f, 0.0f, 0.0f }, 1, "dummy", (BitSetProducer) null), + 10 + ).scoreDocs[0].score; + float score1 = searcher.search( + new KNNQuery("my_vector", new float[] { 1.0f, 2.0f }, 1, "dummy", (BitSetProducer) null), + 10 + ).scoreDocs[0].score; + assertEquals(1.0f / (1 + 25), score, 0.01f); + assertEquals(1.0f / (1 + 169), score1, 0.01f); + + // query to determine the hits + assertEquals( + 1, + searcher.count(new KNNQuery("test_vector", new float[] { 1.0f, 0.0f, 0.0f }, 1, "dummy", (BitSetProducer) null)) + ); + assertEquals(1, searcher.count(new KNNQuery("my_vector", new float[] { 1.0f, 1.0f }, 1, "dummy", (BitSetProducer) null))); + + reader.close(); + NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance().close(); + } } public void testBuildFromModelTemplate(Codec codec) throws IOException, ExecutionException, InterruptedException { @@ -156,83 +230,140 @@ public void testBuildFromModelTemplate(Codec codec) throws IOException, Executio ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, "Flat", SPACE_TYPE, spaceType.getValue()), dimension, vectorsPointer, - KNNEngine.FAISS.getName() + KNNEngine.FAISS ); // Setup model cache - ModelDao modelDao = mock(ModelDao.class); - - // Set model state to created - ModelMetadata modelMetadata1 = new ModelMetadata( - knnEngine, - spaceType, - dimension, - ModelState.CREATED, - ZonedDateTime.now(ZoneOffset.UTC).toString(), - "", - "" - ); - - Model mockModel = new Model(modelMetadata1, modelBlob, modelId); - when(modelDao.get(modelId)).thenReturn(mockModel); - when(modelDao.getMetadata(modelId)).thenReturn(modelMetadata1); - - Settings settings = settings(CURRENT).put(MODEL_CACHE_SIZE_LIMIT_SETTING.getKey(), "10%").build(); - ClusterSettings clusterSettings = new ClusterSettings(settings, ImmutableSet.of(MODEL_CACHE_SIZE_LIMIT_SETTING)); - - ClusterService clusterService = mock(ClusterService.class); - when(clusterService.getSettings()).thenReturn(settings); - when(clusterService.getClusterSettings()).thenReturn(clusterSettings); - - ModelCache.initialize(modelDao, clusterService); - ModelCache.getInstance().removeAll(); + try (MockedStatic modelDaoMockedStatic = Mockito.mockStatic(ModelDao.OpenSearchKNNModelDao.class)) { + ModelDao.OpenSearchKNNModelDao modelDao = mock(ModelDao.OpenSearchKNNModelDao.class); + modelDaoMockedStatic.when(ModelDao.OpenSearchKNNModelDao::getInstance).thenReturn(modelDao); + + // Set model state to created + ModelMetadata modelMetadata1 = new ModelMetadata( + knnEngine, + spaceType, + dimension, + ModelState.CREATED, + ZonedDateTime.now(ZoneOffset.UTC).toString(), + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.FLOAT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.V_EMPTY + ); + + Model mockModel = new Model(modelMetadata1, modelBlob, modelId); + when(modelDao.get(modelId)).thenReturn(mockModel); + when(modelDao.getMetadata(modelId)).thenReturn(modelMetadata1); + + Settings settings = settings(CURRENT).put(MODEL_CACHE_SIZE_LIMIT_SETTING.getKey(), "10%").build(); + ClusterSettings clusterSettings = new ClusterSettings(settings, ImmutableSet.of(MODEL_CACHE_SIZE_LIMIT_SETTING)); + + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.getSettings()).thenReturn(settings); + when(clusterService.getClusterSettings()).thenReturn(clusterSettings); + + ModelCache.initialize(modelDao, clusterService); + ModelCache.getInstance().removeAll(); + + // Setup Lucene + setUpMockClusterService(); + Directory dir = newFSDirectory(createTempDir()); + IndexWriterConfig iwc = newIndexWriterConfig(); + iwc.setMergeScheduler(new SerialMergeScheduler()); + iwc.setCodec(codec); + + FieldType fieldType = new FieldType(KNNVectorFieldMapper.Defaults.FIELD_TYPE); + fieldType.setDocValuesType(DocValuesType.BINARY); + fieldType.putAttribute(KNNConstants.MODEL_ID, modelId); + fieldType.freeze(); + + // Add the documents to the index + float[][] arrays = { { 1.0f, 3.0f, 4.0f }, { 2.0f, 5.0f, 8.0f }, { 3.0f, 6.0f, 9.0f }, { 4.0f, 7.0f, 10.0f } }; + + RandomIndexWriter writer = new RandomIndexWriter(random(), dir, iwc); + String fieldName = "test_vector"; + for (float[] array : arrays) { + VectorField vectorField = new VectorField(fieldName, array, fieldType); + Document doc = new Document(); + doc.add(vectorField); + writer.addDocument(doc); + } + + IndexReader reader = writer.getReader(); + writer.close(); + + // Make sure that search returns the correct results + KNNWeight.initialize(modelDao); + float[] query = { 10.0f, 10.0f, 10.0f }; + IndexSearcher searcher = new IndexSearcher(reader); + TopDocs topDocs = searcher.search(new KNNQuery(fieldName, query, 4, "dummy", (BitSetProducer) null), 10); + + assertEquals(3, topDocs.scoreDocs[0].doc); + assertEquals(2, topDocs.scoreDocs[1].doc); + assertEquals(1, topDocs.scoreDocs[2].doc); + assertEquals(0, topDocs.scoreDocs[3].doc); + + reader.close(); + dir.close(); + NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance().close(); + } + } - // Setup Lucene + public void testWriteByOldCodec(Codec codec) throws IOException { setUpMockClusterService(); Directory dir = newFSDirectory(createTempDir()); IndexWriterConfig iwc = newIndexWriterConfig(); iwc.setMergeScheduler(new SerialMergeScheduler()); iwc.setCodec(codec); - FieldType fieldType = new FieldType(KNNVectorFieldMapper.Defaults.FIELD_TYPE); - fieldType.putAttribute(KNNConstants.MODEL_ID, modelId); - fieldType.freeze(); - - // Add the documents to the index - float[][] arrays = { { 1.0f, 3.0f, 4.0f }, { 2.0f, 5.0f, 8.0f }, { 3.0f, 6.0f, 9.0f }, { 4.0f, 7.0f, 10.0f } }; - - RandomIndexWriter writer = new RandomIndexWriter(random(), dir, iwc); - String fieldName = "test_vector"; - for (float[] array : arrays) { - VectorField vectorField = new VectorField(fieldName, array, fieldType); + /** + * Add doc with field "test_vector", expect it to fail + */ + float[] array = { 1.0f, 3.0f, 4.0f }; + VectorField vectorField = new VectorField("test_vector", array, sampleFieldType); + try (RandomIndexWriter writer = new RandomIndexWriter(random(), dir, iwc)) { Document doc = new Document(); doc.add(vectorField); - writer.addDocument(doc); + expectThrows(UnsupportedOperationException.class, () -> writer.addDocument(doc)); } - IndexReader reader = writer.getReader(); - writer.close(); - - // Make sure that search returns the correct results - KNNWeight.initialize(modelDao); - ResourceWatcherService resourceWatcherService = createDisabledResourceWatcherService(); - NativeMemoryLoadStrategy.IndexLoadStrategy.initialize(resourceWatcherService); - float[] query = { 10.0f, 10.0f, 10.0f }; - IndexSearcher searcher = new IndexSearcher(reader); - TopDocs topDocs = searcher.search(new KNNQuery(fieldName, query, 4, "dummy"), 10); - - assertEquals(3, topDocs.scoreDocs[0].doc); - assertEquals(2, topDocs.scoreDocs[1].doc); - assertEquals(1, topDocs.scoreDocs[2].doc); - assertEquals(0, topDocs.scoreDocs[3].doc); - - reader.close(); dir.close(); - resourceWatcherService.close(); NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance().close(); } - public void testWriteByOldCodec(Codec codec) throws IOException { + public void testKnnVectorIndex( + final Function codecProvider, + final Function perFieldKnnVectorsFormatProvider + ) throws Exception { + final MapperService mapperService = mock(MapperService.class); + final KNNMethodContext knnMethodContext = new KNNMethodContext( + KNNEngine.LUCENE, + SpaceType.L2, + new MethodComponentContext(METHOD_HNSW, Map.of(HNSW_ALGO_M, 16, HNSW_ALGO_EF_CONSTRUCTION, 256)) + ); + + final KNNVectorFieldType mappedFieldType1 = new KNNVectorFieldType( + "test", + Collections.emptyMap(), + VectorDataType.FLOAT, + getMappingConfigForMethodMapping(knnMethodContext, 3) + ); + final KNNVectorFieldType mappedFieldType2 = new KNNVectorFieldType( + "test", + Collections.emptyMap(), + VectorDataType.FLOAT, + getMappingConfigForMethodMapping(knnMethodContext, 2) + ); + when(mapperService.fieldType(eq(FIELD_NAME_ONE))).thenReturn(mappedFieldType1); + when(mapperService.fieldType(eq(FIELD_NAME_TWO))).thenReturn(mappedFieldType2); + + var perFieldKnnVectorsFormatSpy = spy(perFieldKnnVectorsFormatProvider.apply(mapperService)); + final Codec codec = codecProvider.apply(perFieldKnnVectorsFormatSpy); + setUpMockClusterService(); Directory dir = newFSDirectory(createTempDir()); IndexWriterConfig iwc = newIndexWriterConfig(); @@ -240,16 +371,73 @@ public void testWriteByOldCodec(Codec codec) throws IOException { iwc.setCodec(codec); /** - * Add doc with field "test_vector", expect it to fail + * Add doc with field "test_vector_one" */ + final FieldType luceneFieldType = KnnFloatVectorField.createFieldType(3, VectorSimilarityFunction.EUCLIDEAN); float[] array = { 1.0f, 3.0f, 4.0f }; - VectorField vectorField = new VectorField("test_vector", array, sampleFieldType); - try (RandomIndexWriter writer = new RandomIndexWriter(random(), dir, iwc)) { - Document doc = new Document(); - doc.add(vectorField); - expectThrows(UnsupportedOperationException.class, () -> writer.addDocument(doc)); - } + KnnFloatVectorField vectorField = new KnnFloatVectorField(FIELD_NAME_ONE, array, luceneFieldType); + RandomIndexWriter writer = new RandomIndexWriter(random(), dir, iwc); + Document doc = new Document(); + doc.add(vectorField); + writer.addDocument(doc); + writer.commit(); + IndexReader reader = writer.getReader(); + writer.close(); + + verify(perFieldKnnVectorsFormatSpy, atLeastOnce()).getKnnVectorsFormatForField(eq(FIELD_NAME_ONE)); + verify(perFieldKnnVectorsFormatSpy, atLeastOnce()).getMaxDimensions(eq(FIELD_NAME_ONE)); + + IndexSearcher searcher = new IndexSearcher(reader); + + Query query = KNNQueryFactory.create( + BaseQueryFactory.CreateQueryRequest.builder() + .knnEngine(KNNEngine.LUCENE) + .indexName("dummy") + .fieldName(FIELD_NAME_ONE) + .vector(new float[] { 1.0f, 0.0f, 0.0f }) + .k(1) + .vectorDataType(DEFAULT_VECTOR_DATA_TYPE_FIELD) + .build() + ); + + assertEquals(1, searcher.count(query)); + + reader.close(); + + /** + * Add doc with field "test_vector_two" + */ + IndexWriterConfig iwc1 = newIndexWriterConfig(); + iwc1.setMergeScheduler(new SerialMergeScheduler()); + iwc1.setCodec(codec); + writer = new RandomIndexWriter(random(), dir, iwc1); + final FieldType luceneFieldType1 = KnnVectorField.createFieldType(2, VectorSimilarityFunction.EUCLIDEAN); + float[] array1 = { 6.0f, 14.0f }; + KnnVectorField vectorField1 = new KnnVectorField(FIELD_NAME_TWO, array1, luceneFieldType1); + Document doc1 = new Document(); + doc1.add(vectorField1); + writer.addDocument(doc1); + IndexReader reader1 = writer.getReader(); + writer.close(); + + verify(perFieldKnnVectorsFormatSpy, atLeastOnce()).getKnnVectorsFormatForField(eq(FIELD_NAME_TWO)); + verify(perFieldKnnVectorsFormatSpy, atLeastOnce()).getMaxDimensions(eq(FIELD_NAME_TWO)); + + IndexSearcher searcher1 = new IndexSearcher(reader1); + Query query1 = KNNQueryFactory.create( + BaseQueryFactory.CreateQueryRequest.builder() + .knnEngine(KNNEngine.LUCENE) + .indexName("dummy") + .fieldName(FIELD_NAME_TWO) + .vector(new float[] { 1.0f, 0.0f }) + .k(1) + .vectorDataType(DEFAULT_VECTOR_DATA_TYPE_FIELD) + .build() + ); + + assertEquals(1, searcher1.count(query1)); + reader1.close(); dir.close(); NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance().close(); } diff --git a/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestUtil.java b/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestUtil.java index 9b64815ac..d6f22ca7f 100644 --- a/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestUtil.java +++ b/src/test/java/org/opensearch/knn/index/codec/KNNCodecTestUtil.java @@ -10,45 +10,38 @@ import lombok.Builder; import org.apache.lucene.codecs.Codec; import org.apache.lucene.codecs.CodecUtil; -import org.apache.lucene.codecs.DocValuesProducer; -import org.apache.lucene.index.BinaryDocValues; import org.apache.lucene.index.DocValuesType; import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.IndexOptions; -import org.apache.lucene.index.NumericDocValues; import org.apache.lucene.index.SegmentInfo; import org.apache.lucene.index.SegmentWriteState; -import org.apache.lucene.index.SortedDocValues; -import org.apache.lucene.index.SortedNumericDocValues; -import org.apache.lucene.index.SortedSetDocValues; +import org.apache.lucene.index.VectorEncoding; import org.apache.lucene.index.VectorSimilarityFunction; import org.apache.lucene.search.Sort; import org.apache.lucene.store.ChecksumIndexInput; import org.apache.lucene.store.Directory; -import org.apache.lucene.store.FSDirectory; -import org.apache.lucene.store.FilterDirectory; import org.apache.lucene.store.IOContext; -import org.apache.lucene.util.BytesRef; +import org.apache.lucene.store.IndexInput; import org.apache.lucene.util.StringHelper; import org.apache.lucene.util.Version; -import org.opensearch.common.collect.Set; -import org.opensearch.knn.index.KNNQueryResult; +import java.util.Set; + +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.query.KNNQueryResult; import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.codec.util.KNNVectorSerializer; -import org.opensearch.knn.index.codec.util.KNNVectorSerializerFactory; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.store.IndexInputWithBuffer; import org.opensearch.knn.jni.JNIService; import java.io.IOException; -import java.nio.file.Paths; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; -import static com.carrotsearch.randomizedtesting.RandomizedTest.randomFloat; import static org.junit.Assert.assertTrue; +import static org.opensearch.knn.common.KNNConstants.INDEX_DESCRIPTION_PARAMETER; import static org.opensearch.knn.common.KNNConstants.SPACE_TYPE; +import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; import static org.opensearch.test.OpenSearchTestCase.randomByteArrayOfLength; public class KNNCodecTestUtil { @@ -70,6 +63,7 @@ public static class FieldInfoBuilder { private int vectorDimension; private VectorSimilarityFunction vectorSimilarityFunction; private boolean softDeletes; + private boolean isParentField; public static FieldInfoBuilder builder(String fieldName) { return new FieldInfoBuilder(fieldName); @@ -91,6 +85,7 @@ private FieldInfoBuilder(String fieldName) { this.vectorDimension = 0; this.vectorSimilarityFunction = VectorSimilarityFunction.EUCLIDEAN; this.softDeletes = false; + this.isParentField = false; } public FieldInfoBuilder fieldNumber(int fieldNumber) { @@ -163,6 +158,11 @@ public FieldInfoBuilder softDeletes(boolean softDeletes) { return this; } + public FieldInfoBuilder isParentField(boolean isParentField) { + this.isParentField = isParentField; + return this; + } + public FieldInfo build() { return new FieldInfo( fieldName, @@ -178,132 +178,14 @@ public FieldInfo build() { pointIndexDimensionCount, pointNumBytes, vectorDimension, + VectorEncoding.FLOAT32, vectorSimilarityFunction, - softDeletes + softDeletes, + isParentField ); } } - public static abstract class VectorDocValues extends BinaryDocValues { - - final int count; - final int dimension; - int current; - KNNVectorSerializer knnVectorSerializer; - - public VectorDocValues(int count, int dimension) { - this.count = count; - this.dimension = dimension; - this.current = -1; - this.knnVectorSerializer = KNNVectorSerializerFactory.getDefaultSerializer(); - } - - @Override - public boolean advanceExact(int target) throws IOException { - return false; - } - - @Override - public int docID() { - if (this.current > this.count) { - return BinaryDocValues.NO_MORE_DOCS; - } - return this.current; - } - - @Override - public int nextDoc() throws IOException { - return advance(current + 1); - } - - @Override - public int advance(int target) throws IOException { - current = target; - if (current >= count) { - current = NO_MORE_DOCS; - } - return current; - } - - @Override - public long cost() { - return 0; - } - } - - public static class ConstantVectorBinaryDocValues extends VectorDocValues { - - private final BytesRef value; - - public ConstantVectorBinaryDocValues(int count, int dimension, float value) { - super(count, dimension); - float[] array = new float[dimension]; - Arrays.fill(array, value); - this.value = new BytesRef(knnVectorSerializer.floatToByteArray(array)); - } - - @Override - public BytesRef binaryValue() throws IOException { - return value; - } - } - - public static class RandomVectorBinaryDocValues extends VectorDocValues { - - public RandomVectorBinaryDocValues(int count, int dimension) { - super(count, dimension); - } - - @Override - public BytesRef binaryValue() throws IOException { - return new BytesRef(knnVectorSerializer.floatToByteArray(getRandomVector(dimension))); - } - } - - public static class RandomVectorDocValuesProducer extends DocValuesProducer { - - final RandomVectorBinaryDocValues randomBinaryDocValues; - - public RandomVectorDocValuesProducer(int count, int dimension) { - this.randomBinaryDocValues = new RandomVectorBinaryDocValues(count, dimension); - } - - @Override - public NumericDocValues getNumeric(FieldInfo field) { - return null; - } - - @Override - public BinaryDocValues getBinary(FieldInfo field) throws IOException { - return randomBinaryDocValues; - } - - @Override - public SortedDocValues getSorted(FieldInfo field) { - return null; - } - - @Override - public SortedNumericDocValues getSortedNumeric(FieldInfo field) { - return null; - } - - @Override - public SortedSetDocValues getSortedSet(FieldInfo field) { - return null; - } - - @Override - public void checkIntegrity() { - - } - - @Override - public void close() throws IOException { - - } - } - public static void assertFileInCorrectLocation(SegmentWriteState state, String expectedFile) throws IOException { assertTrue(Set.of(state.directory.listAll()).contains(expectedFile)); } @@ -316,40 +198,62 @@ public static void assertValidFooter(Directory dir, String filename) throws IOEx } public static void assertLoadableByEngine( + Map methodParameters, SegmentWriteState state, String fileName, KNNEngine knnEngine, SpaceType spaceType, int dimension ) { - String filePath = Paths.get(((FSDirectory) (FilterDirectory.unwrap(state.directory))).getDirectory().toString(), fileName) - .toString(); - long indexPtr = JNIService.loadIndex( - filePath, - Maps.newHashMap(ImmutableMap.of(SPACE_TYPE, spaceType.getValue())), - knnEngine.getName() - ); - int k = 2; - float[] queryVector = new float[dimension]; - KNNQueryResult[] results = JNIService.queryIndex(indexPtr, queryVector, k, knnEngine.getName()); - assertTrue(results.length > 0); - JNIService.free(indexPtr, knnEngine.getName()); - } - - public static float[][] getRandomVectors(int count, int dimension) { - float[][] data = new float[count][dimension]; - for (int i = 0; i < count; i++) { - data[i] = getRandomVector(dimension); + try (final IndexInput indexInput = state.directory.openInput(fileName, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + long indexPtr = JNIService.loadIndex( + indexInputWithBuffer, + Maps.newHashMap(ImmutableMap.of(SPACE_TYPE, spaceType.getValue())), + knnEngine + ); + int k = 2; + float[] queryVector = new float[dimension]; + KNNQueryResult[] results = JNIService.queryIndex(indexPtr, queryVector, k, methodParameters, knnEngine, null, 0, null); + assertTrue(results.length > 0); + JNIService.free(indexPtr, knnEngine); + } catch (IOException e) { + throw new RuntimeException(e); } - return data; } - public static float[] getRandomVector(int dimension) { - float[] data = new float[dimension]; - for (int i = 0; i < dimension; i++) { - data[i] = randomFloat(); + public static void assertBinaryIndexLoadableByEngine( + SegmentWriteState state, + String fileName, + KNNEngine knnEngine, + SpaceType spaceType, + int dimension, + VectorDataType vectorDataType + ) { + try (final IndexInput indexInput = state.directory.openInput(fileName, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + long indexPtr = JNIService.loadIndex( + indexInputWithBuffer, + Maps.newHashMap( + ImmutableMap.of( + SPACE_TYPE, + spaceType.getValue(), + INDEX_DESCRIPTION_PARAMETER, + "BHNSW32", + VECTOR_DATA_TYPE_FIELD, + vectorDataType.getValue() + ) + ), + knnEngine + ); + int k = 2; + byte[] queryVector = new byte[dimension]; + KNNQueryResult[] results = JNIService.queryBinaryIndex(indexPtr, queryVector, k, null, knnEngine, null, 0, null); + assertTrue(results.length > 0); + JNIService.free(indexPtr, knnEngine); + } catch (IOException e) { + throw new RuntimeException(e); } - return data; } @Builder(builderMethodName = "segmentInfoBuilder") @@ -361,6 +265,7 @@ public static SegmentInfo newSegmentInfo(final Directory directory, final String segmentName, docsInSegment, false, + false, codec, Collections.emptyMap(), randomByteArrayOfLength(StringHelper.ID_LENGTH), diff --git a/src/test/java/org/opensearch/knn/index/codec/KNNFormatFactoryTests.java b/src/test/java/org/opensearch/knn/index/codec/KNNFormatFactoryTests.java deleted file mode 100644 index 545e2a141..000000000 --- a/src/test/java/org/opensearch/knn/index/codec/KNNFormatFactoryTests.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.opensearch.knn.index.codec; - -import org.apache.lucene.codecs.Codec; -import org.opensearch.knn.KNNTestCase; - -public class KNNFormatFactoryTests extends KNNTestCase { - - public void testKNN91Format() { - final Codec lucene91CodecDelegate = KNNCodecFactory.CodecDelegateFactory.createKNN91DefaultDelegate(); - final Codec knnCodec = KNNCodecFactory.createKNNCodec(lucene91CodecDelegate); - KNNFormatFacade knnFormatFacade = KNNFormatFactory.createKNN910Format(knnCodec); - - assertNotNull(knnFormatFacade); - assertNotNull(knnFormatFacade.compoundFormat()); - assertNotNull(knnFormatFacade.docValuesFormat()); - } -} diff --git a/src/test/java/org/opensearch/knn/index/codec/nativeindex/DefaultIndexBuildStrategyTests.java b/src/test/java/org/opensearch/knn/index/codec/nativeindex/DefaultIndexBuildStrategyTests.java new file mode 100644 index 000000000..35c54f3b3 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/codec/nativeindex/DefaultIndexBuildStrategyTests.java @@ -0,0 +1,289 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.nativeindex; + +import lombok.SneakyThrows; +import org.apache.lucene.index.DocsWithFieldSet; +import org.junit.Before; +import org.mockito.ArgumentCaptor; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.opensearch.core.common.unit.ByteSizeValue; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.codec.nativeindex.model.BuildIndexParams; +import org.opensearch.knn.index.codec.transfer.OffHeapVectorTransfer; +import org.opensearch.knn.index.codec.transfer.OffHeapVectorTransferFactory; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.quantizationservice.QuantizationService; +import org.opensearch.knn.index.store.IndexOutputWithBuffer; +import org.opensearch.knn.index.vectorvalues.KNNFloatVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNVectorValuesFactory; +import org.opensearch.knn.index.vectorvalues.TestVectorValues; +import org.opensearch.knn.jni.JNIService; +import org.opensearch.knn.quantization.models.quantizationOutput.QuantizationOutput; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; +import org.opensearch.test.OpenSearchTestCase; + +import java.util.List; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class DefaultIndexBuildStrategyTests extends OpenSearchTestCase { + + ArgumentCaptor vectorTransferCapture = ArgumentCaptor.forClass(float[].class); + + @Before + public void init() { + vectorTransferCapture = ArgumentCaptor.forClass(float[].class); + } + + @SneakyThrows + public void testBuildAndWrite() { + // Given + List vectorValues = List.of(new float[] { 1, 2 }, new float[] { 2, 3 }, new float[] { 3, 4 }); + + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + vectorValues + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, randomVectorValues); + + try ( + MockedStatic mockedJNIService = mockStatic(JNIService.class); + MockedStatic mockedOffHeapVectorTransferFactory = mockStatic(OffHeapVectorTransferFactory.class) + ) { + OffHeapVectorTransfer offHeapVectorTransfer = mock(OffHeapVectorTransfer.class); + mockedOffHeapVectorTransferFactory.when(() -> OffHeapVectorTransferFactory.getVectorTransfer(VectorDataType.FLOAT, 8, 3)) + .thenReturn(offHeapVectorTransfer); + + when(offHeapVectorTransfer.getVectorAddress()).thenReturn(200L); + + IndexOutputWithBuffer indexOutputWithBuffer = Mockito.mock(IndexOutputWithBuffer.class); + BuildIndexParams buildIndexParams = BuildIndexParams.builder() + .indexOutputWithBuffer(indexOutputWithBuffer) + .knnEngine(KNNEngine.NMSLIB) + .vectorDataType(VectorDataType.FLOAT) + .parameters(Map.of("index", "param")) + .vectorValues(knnVectorValues) + .totalLiveDocs((int) knnVectorValues.totalLiveDocs()) + .build(); + + // When + DefaultIndexBuildStrategy.getInstance().buildAndWriteIndex(buildIndexParams); + + // Then + mockedJNIService.verify( + () -> JNIService.createIndex( + eq(new int[] { 0, 1, 2 }), + eq(200L), + eq(knnVectorValues.dimension()), + eq(indexOutputWithBuffer), + eq(Map.of("index", "param")), + eq(KNNEngine.NMSLIB) + ) + ); + mockedJNIService.verifyNoMoreInteractions(); + verify(offHeapVectorTransfer).flush(true); + verify(offHeapVectorTransfer, times(3)).transfer(vectorTransferCapture.capture(), eq(true)); + verify(offHeapVectorTransfer).reset(); + + float[] prev = null; + for (float[] vector : vectorTransferCapture.getAllValues()) { + if (prev != null) { + assertNotSame(prev, vector); + } + prev = vector; + } + } + } + + @SneakyThrows + public void testBuildAndWrite_withQuantization() { + // Given + ArgumentCaptor vectorAddressCaptor = ArgumentCaptor.forClass(Long.class); + ArgumentCaptor vectorTransferCapture = ArgumentCaptor.forClass(Object.class); + + List vectorValues = List.of(new float[] { 1, 2 }, new float[] { 2, 3 }, new float[] { 3, 4 }); + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + vectorValues + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, randomVectorValues); + + try ( + MockedStatic mockedKNNSettings = mockStatic(KNNSettings.class); + MockedStatic mockedJNIService = mockStatic(JNIService.class); + MockedStatic mockedOffHeapVectorTransferFactory = mockStatic(OffHeapVectorTransferFactory.class); + MockedStatic mockedQuantizationIntegration = mockStatic(QuantizationService.class) + ) { + + // Limits transfer to 2 vectors + mockedKNNSettings.when(KNNSettings::getVectorStreamingMemoryLimit).thenReturn(new ByteSizeValue(16)); + mockedJNIService.when(() -> JNIService.initIndex(3, 2, Map.of("index", "param"), KNNEngine.FAISS)).thenReturn(100L); + + OffHeapVectorTransfer offHeapVectorTransfer = mock(OffHeapVectorTransfer.class); + mockedOffHeapVectorTransferFactory.when(() -> OffHeapVectorTransferFactory.getVectorTransfer(VectorDataType.FLOAT, 8, 3)) + .thenReturn(offHeapVectorTransfer); + + QuantizationService quantizationService = mock(QuantizationService.class); + mockedQuantizationIntegration.when(QuantizationService::getInstance).thenReturn(quantizationService); + + QuantizationState quantizationState = mock(QuantizationState.class); + ArgumentCaptor vectorCaptor = ArgumentCaptor.forClass(float[].class); + // New: Create QuantizationOutput and mock the quantization process + QuantizationOutput quantizationOutput = mock(QuantizationOutput.class); + when(quantizationOutput.getQuantizedVectorCopy()).thenReturn(new byte[] { 1, 2 }); + when(quantizationService.createQuantizationOutput(eq(quantizationState.getQuantizationParams()))).thenReturn( + quantizationOutput + ); + + // Quantize the vector with the quantization output + when(quantizationService.quantize(eq(quantizationState), vectorCaptor.capture(), eq(quantizationOutput))).thenAnswer( + invocation -> { + quantizationOutput.getQuantizedVectorCopy(); + return quantizationOutput.getQuantizedVectorCopy(); + } + ); + when(quantizationState.getDimensions()).thenReturn(2); + when(quantizationState.getBytesPerVector()).thenReturn(8); + + when(offHeapVectorTransfer.transfer(vectorTransferCapture.capture(), eq(false))).thenReturn(false) + .thenReturn(true) + .thenReturn(false); + when(offHeapVectorTransfer.flush(false)).thenReturn(true); + when(offHeapVectorTransfer.getVectorAddress()).thenReturn(200L); + + IndexOutputWithBuffer indexOutputWithBuffer = Mockito.mock(IndexOutputWithBuffer.class); + BuildIndexParams buildIndexParams = BuildIndexParams.builder() + .indexOutputWithBuffer(indexOutputWithBuffer) + .knnEngine(KNNEngine.FAISS) + .vectorDataType(VectorDataType.FLOAT) + .parameters(Map.of("index", "param")) + .quantizationState(quantizationState) + .vectorValues(knnVectorValues) + .totalLiveDocs((int) knnVectorValues.totalLiveDocs()) + .build(); + + // When + MemOptimizedNativeIndexBuildStrategy.getInstance().buildAndWriteIndex(buildIndexParams); + + // Then + mockedJNIService.verify( + () -> JNIService.initIndex( + knnVectorValues.totalLiveDocs(), + knnVectorValues.dimension(), + Map.of("index", "param"), + KNNEngine.FAISS + ) + ); + + mockedJNIService.verify( + () -> JNIService.insertToIndex( + eq(new int[] { 0, 1 }), + vectorAddressCaptor.capture(), + eq(knnVectorValues.dimension()), + eq(Map.of("index", "param")), + eq(100L), + eq(KNNEngine.FAISS) + ) + ); + + // For the flush + mockedJNIService.verify( + () -> JNIService.insertToIndex( + eq(new int[] { 2 }), + vectorAddressCaptor.capture(), + eq(knnVectorValues.dimension()), + eq(Map.of("index", "param")), + eq(100L), + eq(KNNEngine.FAISS) + ) + ); + + mockedJNIService.verify( + () -> JNIService.writeIndex(eq(indexOutputWithBuffer), eq(100L), eq(KNNEngine.FAISS), eq(Map.of("index", "param"))) + ); + assertEquals(200L, vectorAddressCaptor.getValue().longValue()); + assertEquals(vectorAddressCaptor.getValue().longValue(), vectorAddressCaptor.getAllValues().get(0).longValue()); + verify(offHeapVectorTransfer, times(0)).reset(); + + for (Object vector : vectorTransferCapture.getAllValues()) { + // Assert that the vector is in byte[] format due to quantization + assertTrue(vector instanceof byte[]); + } + } + } + + @SneakyThrows + public void testBuildAndWriteWithModel() { + // Given + final Map docs = Map.of(0, new float[] { 1, 2 }, 1, new float[] { 2, 3 }, 2, new float[] { 3, 4 }); + DocsWithFieldSet docsWithFieldSet = new DocsWithFieldSet(); + docs.keySet().stream().sorted().forEach(docsWithFieldSet::add); + + byte[] modelBlob = new byte[] { 1 }; + + KNNFloatVectorValues knnVectorValues = (KNNFloatVectorValues) KNNVectorValuesFactory.getVectorValues( + VectorDataType.FLOAT, + docsWithFieldSet, + docs + ); + try ( + MockedStatic mockedJNIService = mockStatic(JNIService.class); + MockedStatic mockedOffHeapVectorTransferFactory = mockStatic(OffHeapVectorTransferFactory.class) + ) { + + OffHeapVectorTransfer offHeapVectorTransfer = mock(OffHeapVectorTransfer.class); + mockedOffHeapVectorTransferFactory.when(() -> OffHeapVectorTransferFactory.getVectorTransfer(VectorDataType.FLOAT, 8, 3)) + .thenReturn(offHeapVectorTransfer); + + when(offHeapVectorTransfer.getVectorAddress()).thenReturn(200L); + + IndexOutputWithBuffer indexOutputWithBuffer = Mockito.mock(IndexOutputWithBuffer.class); + BuildIndexParams buildIndexParams = BuildIndexParams.builder() + .indexOutputWithBuffer(indexOutputWithBuffer) + .knnEngine(KNNEngine.NMSLIB) + .vectorDataType(VectorDataType.FLOAT) + .parameters(Map.of("model_id", "id", "model_blob", modelBlob)) + .vectorValues(knnVectorValues) + .totalLiveDocs((int) knnVectorValues.totalLiveDocs()) + .build(); + + // When + DefaultIndexBuildStrategy.getInstance().buildAndWriteIndex(buildIndexParams); + + // Then + mockedJNIService.verify( + () -> JNIService.createIndexFromTemplate( + eq(new int[] { 0, 1, 2 }), + eq(200L), + eq(2), + eq(indexOutputWithBuffer), + eq(modelBlob), + eq(Map.of("model_id", "id", "model_blob", modelBlob)), + eq(KNNEngine.NMSLIB) + ) + ); + mockedJNIService.verifyNoMoreInteractions(); + verify(offHeapVectorTransfer).flush(true); + verify(offHeapVectorTransfer, times(3)).transfer(vectorTransferCapture.capture(), eq(true)); + + float[] prev = null; + for (float[] vector : vectorTransferCapture.getAllValues()) { + if (prev != null) { + assertNotSame(prev, vector); + } + prev = vector; + } + } + } +} diff --git a/src/test/java/org/opensearch/knn/index/codec/nativeindex/MemOptimizedNativeIndexBuildStrategyTests.java b/src/test/java/org/opensearch/knn/index/codec/nativeindex/MemOptimizedNativeIndexBuildStrategyTests.java new file mode 100644 index 000000000..08942fe7f --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/codec/nativeindex/MemOptimizedNativeIndexBuildStrategyTests.java @@ -0,0 +1,249 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.nativeindex; + +import lombok.SneakyThrows; +import org.mockito.ArgumentCaptor; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.codec.nativeindex.model.BuildIndexParams; +import org.opensearch.knn.index.codec.transfer.OffHeapVectorTransfer; +import org.opensearch.knn.index.codec.transfer.OffHeapVectorTransferFactory; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.quantizationservice.QuantizationService; +import org.opensearch.knn.index.store.IndexOutputWithBuffer; +import org.opensearch.knn.index.vectorvalues.KNNVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNVectorValuesFactory; +import org.opensearch.knn.index.vectorvalues.TestVectorValues; +import org.opensearch.knn.jni.JNIService; +import org.opensearch.knn.quantization.models.quantizationOutput.QuantizationOutput; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; +import org.opensearch.test.OpenSearchTestCase; + +import java.util.List; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class MemOptimizedNativeIndexBuildStrategyTests extends OpenSearchTestCase { + + @SneakyThrows + public void testBuildAndWrite() { + // Given + ArgumentCaptor vectorAddressCaptor = ArgumentCaptor.forClass(Long.class); + ArgumentCaptor vectorTransferCapture = ArgumentCaptor.forClass(float[].class); + + List vectorValues = List.of(new float[] { 1, 2 }, new float[] { 2, 3 }, new float[] { 3, 4 }); + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + vectorValues + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, randomVectorValues); + + try ( + MockedStatic mockedJNIService = Mockito.mockStatic(JNIService.class); + MockedStatic mockedOffHeapVectorTransferFactory = Mockito.mockStatic( + OffHeapVectorTransferFactory.class + ) + ) { + // Limits transfer to 2 vectors + mockedJNIService.when(() -> JNIService.initIndex(3, 2, Map.of("index", "param"), KNNEngine.FAISS)).thenReturn(100L); + + OffHeapVectorTransfer offHeapVectorTransfer = mock(OffHeapVectorTransfer.class); + mockedOffHeapVectorTransferFactory.when(() -> OffHeapVectorTransferFactory.getVectorTransfer(VectorDataType.FLOAT, 8, 3)) + .thenReturn(offHeapVectorTransfer); + IndexOutputWithBuffer indexOutputWithBuffer = Mockito.mock(IndexOutputWithBuffer.class); + + when(offHeapVectorTransfer.getTransferLimit()).thenReturn(2); + when(offHeapVectorTransfer.transfer(vectorTransferCapture.capture(), eq(false))).thenReturn(false) + .thenReturn(true) + .thenReturn(false); + when(offHeapVectorTransfer.flush(false)).thenReturn(true); + when(offHeapVectorTransfer.getVectorAddress()).thenReturn(200L); + + BuildIndexParams buildIndexParams = BuildIndexParams.builder() + .indexOutputWithBuffer(indexOutputWithBuffer) + .knnEngine(KNNEngine.FAISS) + .vectorDataType(VectorDataType.FLOAT) + .parameters(Map.of("index", "param")) + .vectorValues(knnVectorValues) + .totalLiveDocs((int) knnVectorValues.totalLiveDocs()) + .build(); + + // When + MemOptimizedNativeIndexBuildStrategy.getInstance().buildAndWriteIndex(buildIndexParams); + + // Then + mockedJNIService.verify( + () -> JNIService.initIndex( + knnVectorValues.totalLiveDocs(), + knnVectorValues.dimension(), + Map.of("index", "param"), + KNNEngine.FAISS + ) + ); + + mockedJNIService.verify( + () -> JNIService.insertToIndex( + eq(new int[] { 0, 1 }), + vectorAddressCaptor.capture(), + eq(knnVectorValues.dimension()), + eq(Map.of("index", "param")), + eq(100L), + eq(KNNEngine.FAISS) + ) + ); + + // For the flush + mockedJNIService.verify( + () -> JNIService.insertToIndex( + eq(new int[] { 2 }), + vectorAddressCaptor.capture(), + eq(knnVectorValues.dimension()), + eq(Map.of("index", "param")), + eq(100L), + eq(KNNEngine.FAISS) + ) + ); + + mockedJNIService.verify( + () -> JNIService.writeIndex(eq(indexOutputWithBuffer), eq(100L), eq(KNNEngine.FAISS), eq(Map.of("index", "param"))) + ); + assertEquals(200L, vectorAddressCaptor.getValue().longValue()); + assertEquals(vectorAddressCaptor.getValue().longValue(), vectorAddressCaptor.getAllValues().get(0).longValue()); + verify(offHeapVectorTransfer, times(0)).reset(); + + float[] prev = null; + for (float[] vector : vectorTransferCapture.getAllValues()) { + if (prev != null) { + assertNotSame(prev, vector); + } + prev = vector; + } + } + } + + @SneakyThrows + public void testBuildAndWrite_withQuantization() { + // Given + ArgumentCaptor vectorAddressCaptor = ArgumentCaptor.forClass(Long.class); + ArgumentCaptor vectorTransferCapture = ArgumentCaptor.forClass(Object.class); + + List vectorValues = List.of(new float[] { 1, 2 }, new float[] { 2, 3 }, new float[] { 3, 4 }); + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + vectorValues + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, randomVectorValues); + + try ( + MockedStatic mockedJNIService = Mockito.mockStatic(JNIService.class); + MockedStatic mockedOffHeapVectorTransferFactory = Mockito.mockStatic( + OffHeapVectorTransferFactory.class + ); + MockedStatic mockedQuantizationIntegration = Mockito.mockStatic(QuantizationService.class) + ) { + + // Limits transfer to 2 vectors + mockedJNIService.when(() -> JNIService.initIndex(3, 2, Map.of("index", "param"), KNNEngine.FAISS)).thenReturn(100L); + + OffHeapVectorTransfer offHeapVectorTransfer = mock(OffHeapVectorTransfer.class); + when(offHeapVectorTransfer.getTransferLimit()).thenReturn(2); + mockedOffHeapVectorTransferFactory.when(() -> OffHeapVectorTransferFactory.getVectorTransfer(VectorDataType.FLOAT, 8, 3)) + .thenReturn(offHeapVectorTransfer); + + QuantizationService quantizationService = mock(QuantizationService.class); + mockedQuantizationIntegration.when(QuantizationService::getInstance).thenReturn(quantizationService); + + QuantizationState quantizationState = mock(QuantizationState.class); + ArgumentCaptor vectorCaptor = ArgumentCaptor.forClass(float[].class); + // New: Create QuantizationOutput and mock the quantization process + QuantizationOutput quantizationOutput = mock(QuantizationOutput.class); + when(quantizationOutput.getQuantizedVectorCopy()).thenReturn(new byte[] { 1, 2 }); + when(quantizationService.createQuantizationOutput(eq(quantizationState.getQuantizationParams()))).thenReturn( + quantizationOutput + ); + + // Quantize the vector with the quantization output + when(quantizationService.quantize(eq(quantizationState), vectorCaptor.capture(), eq(quantizationOutput))).thenAnswer( + invocation -> { + quantizationOutput.getQuantizedVectorCopy(); + return quantizationOutput.getQuantizedVectorCopy(); + } + ); + when(quantizationState.getDimensions()).thenReturn(2); + when(quantizationState.getBytesPerVector()).thenReturn(8); + + when(offHeapVectorTransfer.transfer(vectorTransferCapture.capture(), eq(false))).thenReturn(false) + .thenReturn(true) + .thenReturn(false); + when(offHeapVectorTransfer.flush(false)).thenReturn(true); + when(offHeapVectorTransfer.getVectorAddress()).thenReturn(200L); + + IndexOutputWithBuffer indexOutputWithBuffer = Mockito.mock(IndexOutputWithBuffer.class); + BuildIndexParams buildIndexParams = BuildIndexParams.builder() + .indexOutputWithBuffer(indexOutputWithBuffer) + .knnEngine(KNNEngine.FAISS) + .vectorDataType(VectorDataType.FLOAT) + .parameters(Map.of("index", "param")) + .quantizationState(quantizationState) + .vectorValues(knnVectorValues) + .totalLiveDocs((int) knnVectorValues.totalLiveDocs()) + .build(); + + // When + MemOptimizedNativeIndexBuildStrategy.getInstance().buildAndWriteIndex(buildIndexParams); + + // Then + mockedJNIService.verify( + () -> JNIService.initIndex( + knnVectorValues.totalLiveDocs(), + knnVectorValues.dimension(), + Map.of("index", "param"), + KNNEngine.FAISS + ) + ); + + mockedJNIService.verify( + () -> JNIService.insertToIndex( + eq(new int[] { 0, 1 }), + vectorAddressCaptor.capture(), + eq(knnVectorValues.dimension()), + eq(Map.of("index", "param")), + eq(100L), + eq(KNNEngine.FAISS) + ) + ); + + // For the flush + mockedJNIService.verify( + () -> JNIService.insertToIndex( + eq(new int[] { 2 }), + vectorAddressCaptor.capture(), + eq(knnVectorValues.dimension()), + eq(Map.of("index", "param")), + eq(100L), + eq(KNNEngine.FAISS) + ) + ); + + mockedJNIService.verify( + () -> JNIService.writeIndex(eq(indexOutputWithBuffer), eq(100L), eq(KNNEngine.FAISS), eq(Map.of("index", "param"))) + ); + assertEquals(200L, vectorAddressCaptor.getValue().longValue()); + assertEquals(vectorAddressCaptor.getValue().longValue(), vectorAddressCaptor.getAllValues().get(0).longValue()); + verify(offHeapVectorTransfer, times(0)).reset(); + + for (Object vector : vectorTransferCapture.getAllValues()) { + // Assert that the vector is in byte[] format due to quantization + assertTrue(vector instanceof byte[]); + } + } + } +} diff --git a/src/test/java/org/opensearch/knn/index/codec/nativeindex/QuantizationIndexUtilsTests.java b/src/test/java/org/opensearch/knn/index/codec/nativeindex/QuantizationIndexUtilsTests.java new file mode 100644 index 000000000..61d3d7589 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/codec/nativeindex/QuantizationIndexUtilsTests.java @@ -0,0 +1,109 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.nativeindex; + +import org.junit.Before; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.codec.nativeindex.model.BuildIndexParams; +import org.opensearch.knn.index.quantizationservice.QuantizationService; +import org.opensearch.knn.index.vectorvalues.KNNVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNVectorValuesFactory; +import org.opensearch.knn.index.vectorvalues.TestVectorValues; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; +import org.opensearch.knn.quantization.models.quantizationOutput.QuantizationOutput; +import org.opensearch.knn.quantization.models.quantizationParams.ScalarQuantizationParams; +import org.opensearch.knn.quantization.models.quantizationState.OneBitScalarQuantizationState; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; + +import java.io.IOException; +import java.util.List; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class QuantizationIndexUtilsTests extends KNNTestCase { + + private KNNVectorValues knnVectorValues; + private BuildIndexParams buildIndexParams; + private QuantizationService quantizationService; + + @Before + public void setUp() throws Exception { + super.setUp(); + quantizationService = mock(QuantizationService.class); + + // Predefined float vectors for testing + List floatVectors = List.of( + new float[] { 1.0f, 2.0f, 3.0f }, + new float[] { 4.0f, 5.0f, 6.0f }, + new float[] { 7.0f, 8.0f, 9.0f } + ); + + // Use the predefined vectors to create KNNVectorValues + knnVectorValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.FLOAT, + new TestVectorValues.PreDefinedFloatVectorValues(floatVectors) + ); + + // Mocking BuildIndexParams + buildIndexParams = mock(BuildIndexParams.class); + } + + public void testPrepareIndexBuild_withQuantization_success() { + QuantizationState quantizationState = mock(OneBitScalarQuantizationState.class); + QuantizationOutput quantizationOutput = mock(QuantizationOutput.class); + + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); + when(quantizationOutput.getQuantizedVector()).thenReturn(new byte[] { 0x01 }); + when(quantizationState.getDimensions()).thenReturn(2); + when(quantizationState.getBytesPerVector()).thenReturn(8); + when(quantizationState.getQuantizationParams()).thenReturn(params); + + when(buildIndexParams.getQuantizationState()).thenReturn(quantizationState); + + IndexBuildSetup setup = QuantizationIndexUtils.prepareIndexBuild(knnVectorValues, buildIndexParams); + + assertNotNull(setup.getQuantizationState()); + assertEquals(8, setup.getBytesPerVector()); + assertEquals(2, setup.getDimensions()); + } + + public void testPrepareIndexBuild_withoutQuantization_success() throws IOException { + when(buildIndexParams.getQuantizationState()).thenReturn(null); + knnVectorValues.nextDoc(); + knnVectorValues.getVector(); + IndexBuildSetup setup = QuantizationIndexUtils.prepareIndexBuild(knnVectorValues, buildIndexParams); + assertNull(setup.getQuantizationState()); + assertEquals(knnVectorValues.bytesPerVector(), setup.getBytesPerVector()); + assertEquals(knnVectorValues.dimension(), setup.getDimensions()); + } + + public void testProcessAndReturnVector_withoutQuantization_success() throws IOException { + // Set up the BuildIndexParams to return no quantization + when(buildIndexParams.getQuantizationState()).thenReturn(null); + knnVectorValues.nextDoc(); + knnVectorValues.getVector(); + IndexBuildSetup setup = QuantizationIndexUtils.prepareIndexBuild(knnVectorValues, buildIndexParams); + // Process and return the vector + assertNotNull(QuantizationIndexUtils.processAndReturnVector(knnVectorValues, setup)); + } + + public void testProcessAndReturnVector_withQuantization_success() throws IOException { + // Set up quantization state and output + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); + float[] mean = { 1.0f, 2.0f, 3.0f }; + knnVectorValues.nextDoc(); + OneBitScalarQuantizationState state = new OneBitScalarQuantizationState(params, mean); + QuantizationOutput quantizationOutput = mock(QuantizationOutput.class); + when(buildIndexParams.getQuantizationState()).thenReturn(state); + IndexBuildSetup setup = QuantizationIndexUtils.prepareIndexBuild(knnVectorValues, buildIndexParams); + // Process and return the vector + Object result = QuantizationIndexUtils.processAndReturnVector(knnVectorValues, setup); + assertTrue(result instanceof byte[]); + assertArrayEquals(new byte[] { 0x00 }, (byte[]) result); + } +} diff --git a/src/test/java/org/opensearch/knn/index/codec/params/KNNScalarQuantizedVectorsFormatParamsTests.java b/src/test/java/org/opensearch/knn/index/codec/params/KNNScalarQuantizedVectorsFormatParamsTests.java new file mode 100644 index 000000000..b7394b06a --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/codec/params/KNNScalarQuantizedVectorsFormatParamsTests.java @@ -0,0 +1,159 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.codec.params; + +import junit.framework.TestCase; +import org.junit.Assert; +import org.opensearch.knn.index.engine.MethodComponentContext; + +import java.util.HashMap; +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.ENCODER_SQ; +import static org.opensearch.knn.common.KNNConstants.LUCENE_SQ_BITS; +import static org.opensearch.knn.common.KNNConstants.LUCENE_SQ_CONFIDENCE_INTERVAL; +import static org.opensearch.knn.common.KNNConstants.LUCENE_SQ_DEFAULT_BITS; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_M; +import static org.opensearch.knn.common.KNNConstants.MINIMUM_CONFIDENCE_INTERVAL; + +public class KNNScalarQuantizedVectorsFormatParamsTests extends TestCase { + private static final int DEFAULT_MAX_CONNECTIONS = 16; + private static final int DEFAULT_BEAM_WIDTH = 100; + + public void testInitParams_whenCalled_thenReturnDefaultParams() { + KNNScalarQuantizedVectorsFormatParams knnScalarQuantizedVectorsFormatParams = new KNNScalarQuantizedVectorsFormatParams( + getDefaultParamsForConstructor(), + DEFAULT_MAX_CONNECTIONS, + DEFAULT_BEAM_WIDTH + ); + + assertEquals(DEFAULT_MAX_CONNECTIONS, knnScalarQuantizedVectorsFormatParams.getMaxConnections()); + assertEquals(DEFAULT_BEAM_WIDTH, knnScalarQuantizedVectorsFormatParams.getBeamWidth()); + assertNull(knnScalarQuantizedVectorsFormatParams.getConfidenceInterval()); + assertFalse(knnScalarQuantizedVectorsFormatParams.isCompressFlag()); + assertEquals(LUCENE_SQ_DEFAULT_BITS, knnScalarQuantizedVectorsFormatParams.getBits()); + } + + public void testInitParams_whenCalled_thenReturnParams() { + int m = 64; + int efConstruction = 128; + + Map encoderParams = new HashMap<>(); + encoderParams.put(LUCENE_SQ_CONFIDENCE_INTERVAL, MINIMUM_CONFIDENCE_INTERVAL); + MethodComponentContext encoderComponentContext = new MethodComponentContext(ENCODER_SQ, encoderParams); + + Map params = new HashMap<>(); + params.put(METHOD_ENCODER_PARAMETER, encoderComponentContext); + params.put(METHOD_PARAMETER_M, m); + params.put(METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction); + + KNNScalarQuantizedVectorsFormatParams knnScalarQuantizedVectorsFormatParams = new KNNScalarQuantizedVectorsFormatParams( + params, + DEFAULT_MAX_CONNECTIONS, + DEFAULT_BEAM_WIDTH + ); + + assertEquals(m, knnScalarQuantizedVectorsFormatParams.getMaxConnections()); + assertEquals(efConstruction, knnScalarQuantizedVectorsFormatParams.getBeamWidth()); + assertEquals((float) MINIMUM_CONFIDENCE_INTERVAL, knnScalarQuantizedVectorsFormatParams.getConfidenceInterval()); + assertFalse(knnScalarQuantizedVectorsFormatParams.isCompressFlag()); + assertEquals(LUCENE_SQ_DEFAULT_BITS, knnScalarQuantizedVectorsFormatParams.getBits()); + } + + public void testInitParams_whenBitsIs4_thenReturnParams() { + int m = 64; + int efConstruction = 128; + + Map encoderParams = new HashMap<>(); + encoderParams.put(LUCENE_SQ_CONFIDENCE_INTERVAL, MINIMUM_CONFIDENCE_INTERVAL); + encoderParams.put(LUCENE_SQ_BITS, 4); + MethodComponentContext encoderComponentContext = new MethodComponentContext(ENCODER_SQ, encoderParams); + + Map params = new HashMap<>(); + params.put(METHOD_ENCODER_PARAMETER, encoderComponentContext); + params.put(METHOD_PARAMETER_M, m); + params.put(METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction); + + KNNScalarQuantizedVectorsFormatParams knnScalarQuantizedVectorsFormatParams = new KNNScalarQuantizedVectorsFormatParams( + params, + DEFAULT_MAX_CONNECTIONS, + DEFAULT_BEAM_WIDTH + ); + + assertEquals(m, knnScalarQuantizedVectorsFormatParams.getMaxConnections()); + assertEquals(efConstruction, knnScalarQuantizedVectorsFormatParams.getBeamWidth()); + assertEquals((float) MINIMUM_CONFIDENCE_INTERVAL, knnScalarQuantizedVectorsFormatParams.getConfidenceInterval()); + assertTrue(knnScalarQuantizedVectorsFormatParams.isCompressFlag()); + assertEquals(4, knnScalarQuantizedVectorsFormatParams.getBits()); + } + + public void testInitParams_whenBitsIs0_thenThrowException() { + int m = 64; + int efConstruction = 128; + + Map encoderParams = new HashMap<>(); + encoderParams.put(LUCENE_SQ_CONFIDENCE_INTERVAL, MINIMUM_CONFIDENCE_INTERVAL); + encoderParams.put(LUCENE_SQ_BITS, 0); + MethodComponentContext encoderComponentContext = new MethodComponentContext(ENCODER_SQ, encoderParams); + + Map params = new HashMap<>(); + params.put(METHOD_ENCODER_PARAMETER, encoderComponentContext); + params.put(METHOD_PARAMETER_M, m); + params.put(METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction); + + Assert.assertThrows( + IllegalArgumentException.class, + () -> new KNNScalarQuantizedVectorsFormatParams(params, DEFAULT_MAX_CONNECTIONS, DEFAULT_BEAM_WIDTH) + ); + } + + public void testValidate_whenCalled_thenReturnTrue() { + Map params = getDefaultParamsForConstructor(); + KNNScalarQuantizedVectorsFormatParams knnScalarQuantizedVectorsFormatParams = new KNNScalarQuantizedVectorsFormatParams( + params, + DEFAULT_MAX_CONNECTIONS, + DEFAULT_BEAM_WIDTH + ); + assertTrue(knnScalarQuantizedVectorsFormatParams.validate(params)); + } + + public void testValidate_whenCalled_thenReturnFalse() { + KNNScalarQuantizedVectorsFormatParams knnScalarQuantizedVectorsFormatParams = new KNNScalarQuantizedVectorsFormatParams( + getDefaultParamsForConstructor(), + DEFAULT_MAX_CONNECTIONS, + DEFAULT_BEAM_WIDTH + ); + Map params = new HashMap<>(); + + // Return false if encoder value is null + params.put(METHOD_ENCODER_PARAMETER, null); + assertFalse(knnScalarQuantizedVectorsFormatParams.validate(params)); + + // Return false if encoder value is not an instance of MethodComponentContext + params.replace(METHOD_ENCODER_PARAMETER, "dummy string"); + assertFalse(knnScalarQuantizedVectorsFormatParams.validate(params)); + + // Return false if encoder name is not "sq" + MethodComponentContext encoderComponentContext = new MethodComponentContext("invalid encoder name", new HashMap<>()); + params.replace(METHOD_ENCODER_PARAMETER, encoderComponentContext); + assertFalse(knnScalarQuantizedVectorsFormatParams.validate(params)); + } + + private Map getDefaultParamsForConstructor() { + MethodComponentContext encoderComponentContext = new MethodComponentContext(ENCODER_SQ, new HashMap<>()); + Map params = new HashMap<>(); + params.put(METHOD_ENCODER_PARAMETER, encoderComponentContext); + return params; + } +} diff --git a/src/test/java/org/opensearch/knn/index/codec/params/KNNVectorsFormatParamsTests.java b/src/test/java/org/opensearch/knn/index/codec/params/KNNVectorsFormatParamsTests.java new file mode 100644 index 000000000..dca054046 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/codec/params/KNNVectorsFormatParamsTests.java @@ -0,0 +1,56 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.codec.params; + +import junit.framework.TestCase; + +import java.util.HashMap; +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_M; + +public class KNNVectorsFormatParamsTests extends TestCase { + private static final int DEFAULT_MAX_CONNECTIONS = 16; + private static final int DEFAULT_BEAM_WIDTH = 100; + + public void testInitParams_whenCalled_thenReturnDefaultParams() { + KNNVectorsFormatParams knnVectorsFormatParams = new KNNVectorsFormatParams( + new HashMap<>(), + DEFAULT_MAX_CONNECTIONS, + DEFAULT_BEAM_WIDTH + ); + assertEquals(DEFAULT_MAX_CONNECTIONS, knnVectorsFormatParams.getMaxConnections()); + assertEquals(DEFAULT_BEAM_WIDTH, knnVectorsFormatParams.getBeamWidth()); + } + + public void testInitParams_whenCalled_thenReturnParams() { + int m = 64; + int efConstruction = 128; + Map params = new HashMap<>(); + params.put(METHOD_PARAMETER_M, m); + params.put(METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction); + + KNNVectorsFormatParams knnVectorsFormatParams = new KNNVectorsFormatParams(params, DEFAULT_MAX_CONNECTIONS, DEFAULT_BEAM_WIDTH); + assertEquals(m, knnVectorsFormatParams.getMaxConnections()); + assertEquals(efConstruction, knnVectorsFormatParams.getBeamWidth()); + } + + public void testValidate_whenCalled_thenReturnTrue() { + KNNVectorsFormatParams knnVectorsFormatParams = new KNNVectorsFormatParams( + new HashMap<>(), + DEFAULT_MAX_CONNECTIONS, + DEFAULT_BEAM_WIDTH + ); + assertTrue(knnVectorsFormatParams.validate(new HashMap<>())); + } +} diff --git a/src/test/java/org/opensearch/knn/index/codec/transfer/OffHeapVectorTransferFactoryTests.java b/src/test/java/org/opensearch/knn/index/codec/transfer/OffHeapVectorTransferFactoryTests.java new file mode 100644 index 000000000..09984ba46 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/codec/transfer/OffHeapVectorTransferFactoryTests.java @@ -0,0 +1,34 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.transfer; + +import org.mockito.MockedStatic; +import org.opensearch.core.common.unit.ByteSizeValue; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.test.OpenSearchTestCase; + +import static org.mockito.Mockito.mockStatic; + +public class OffHeapVectorTransferFactoryTests extends OpenSearchTestCase { + + public void testOffHeapVectorTransferFactory() { + try (MockedStatic mockedKNNSettings = mockStatic(KNNSettings.class)) { + mockedKNNSettings.when(KNNSettings::getVectorStreamingMemoryLimit).thenReturn(new ByteSizeValue(16)); + var floatVectorTransfer = OffHeapVectorTransferFactory.getVectorTransfer(VectorDataType.FLOAT, 10, 10); + assertEquals(OffHeapFloatVectorTransfer.class, floatVectorTransfer.getClass()); + assertNotSame(floatVectorTransfer, OffHeapVectorTransferFactory.getVectorTransfer(VectorDataType.FLOAT, 10, 10)); + + var byteVectorTransfer = OffHeapVectorTransferFactory.getVectorTransfer(VectorDataType.BYTE, 10, 10); + assertEquals(OffHeapByteVectorTransfer.class, byteVectorTransfer.getClass()); + assertNotSame(byteVectorTransfer, OffHeapVectorTransferFactory.getVectorTransfer(VectorDataType.BYTE, 10, 10)); + + var binaryVectorTransfer = OffHeapVectorTransferFactory.getVectorTransfer(VectorDataType.BINARY, 10, 10); + assertEquals(OffHeapBinaryVectorTransfer.class, binaryVectorTransfer.getClass()); + assertNotSame(binaryVectorTransfer, OffHeapVectorTransferFactory.getVectorTransfer(VectorDataType.BINARY, 10, 10)); + } + } +} diff --git a/src/test/java/org/opensearch/knn/index/codec/transfer/OffHeapVectorTransferTests.java b/src/test/java/org/opensearch/knn/index/codec/transfer/OffHeapVectorTransferTests.java new file mode 100644 index 000000000..fb2ef274e --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/codec/transfer/OffHeapVectorTransferTests.java @@ -0,0 +1,109 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.transfer; + +import lombok.SneakyThrows; +import org.mockito.MockedStatic; +import org.opensearch.core.common.unit.ByteSizeValue; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.KNNSettings; + +import java.util.List; + +import static org.mockito.Mockito.mockStatic; + +public class OffHeapVectorTransferTests extends KNNTestCase { + + @SneakyThrows + public void testFloatTransfer() { + List vectors = List.of( + new float[] { 0.1f, 0.2f }, + new float[] { 0.2f, 0.3f }, + new float[] { 0.3f, 0.4f }, + new float[] { 0.3f, 0.4f }, + new float[] { 0.3f, 0.4f } + ); + + try (MockedStatic mockedKNNSettings = mockStatic(KNNSettings.class)) { + mockedKNNSettings.when(KNNSettings::getVectorStreamingMemoryLimit).thenReturn(new ByteSizeValue(16)); + + OffHeapFloatVectorTransfer vectorTransfer = new OffHeapFloatVectorTransfer(8, 5); + long vectorAddress = 0; + assertFalse(vectorTransfer.transfer(vectors.get(0), false)); + assertEquals(0, vectorTransfer.getVectorAddress()); + assertTrue(vectorTransfer.transfer(vectors.get(1), false)); + vectorAddress = vectorTransfer.getVectorAddress(); + assertFalse(vectorTransfer.transfer(vectors.get(2), false)); + assertEquals(vectorAddress, vectorTransfer.getVectorAddress()); + assertTrue(vectorTransfer.transfer(vectors.get(3), false)); + assertEquals(vectorAddress, vectorTransfer.getVectorAddress()); + assertFalse(vectorTransfer.transfer(vectors.get(4), false)); + assertTrue(vectorTransfer.flush(false)); + vectorTransfer.reset(); + assertEquals(0, vectorTransfer.getVectorAddress()); + vectorTransfer.close(); + + } + + } + + @SneakyThrows + public void testByteTransfer() { + List vectors = List.of( + new byte[] { 0, 1 }, + new byte[] { 2, 3 }, + new byte[] { 4, 5 }, + new byte[] { 6, 7 }, + new byte[] { 8, 9 } + ); + + try (MockedStatic mockedKNNSettings = mockStatic(KNNSettings.class)) { + mockedKNNSettings.when(KNNSettings::getVectorStreamingMemoryLimit).thenReturn(new ByteSizeValue(4)); + OffHeapByteVectorTransfer vectorTransfer = new OffHeapByteVectorTransfer(2, 5); + long vectorAddress = 0; + assertFalse(vectorTransfer.transfer(vectors.get(0), false)); + assertEquals(0, vectorTransfer.getVectorAddress()); + assertTrue(vectorTransfer.transfer(vectors.get(1), false)); + vectorAddress = vectorTransfer.getVectorAddress(); + assertFalse(vectorTransfer.transfer(vectors.get(2), false)); + assertEquals(vectorAddress, vectorTransfer.getVectorAddress()); + assertTrue(vectorTransfer.transfer(vectors.get(3), false)); + assertEquals(vectorAddress, vectorTransfer.getVectorAddress()); + assertFalse(vectorTransfer.transfer(vectors.get(4), false)); + assertTrue(vectorTransfer.flush(false)); + vectorTransfer.close(); + assertEquals(0, vectorTransfer.getVectorAddress()); + } + } + + @SneakyThrows + public void testBinaryTransfer() { + List vectors = List.of( + new byte[] { 0, 1 }, + new byte[] { 2, 3 }, + new byte[] { 4, 5 }, + new byte[] { 6, 7 }, + new byte[] { 8, 9 } + ); + + try (MockedStatic mockedKNNSettings = mockStatic(KNNSettings.class)) { + mockedKNNSettings.when(KNNSettings::getVectorStreamingMemoryLimit).thenReturn(new ByteSizeValue(4)); + OffHeapBinaryVectorTransfer vectorTransfer = new OffHeapBinaryVectorTransfer(2, 5); + long vectorAddress = 0; + assertFalse(vectorTransfer.transfer(vectors.get(0), false)); + assertEquals(0, vectorTransfer.getVectorAddress()); + assertTrue(vectorTransfer.transfer(vectors.get(1), false)); + vectorAddress = vectorTransfer.getVectorAddress(); + assertFalse(vectorTransfer.transfer(vectors.get(2), false)); + assertEquals(vectorAddress, vectorTransfer.getVectorAddress()); + assertTrue(vectorTransfer.transfer(vectors.get(3), false)); + assertEquals(vectorAddress, vectorTransfer.getVectorAddress()); + assertFalse(vectorTransfer.transfer(vectors.get(4), false)); + assertTrue(vectorTransfer.flush(false)); + vectorTransfer.close(); + } + } +} diff --git a/src/test/java/org/opensearch/knn/index/codec/util/BinaryDocValuesSubTests.java b/src/test/java/org/opensearch/knn/index/codec/util/BinaryDocValuesSubTests.java index a2105af3a..757930dcd 100644 --- a/src/test/java/org/opensearch/knn/index/codec/util/BinaryDocValuesSubTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/util/BinaryDocValuesSubTests.java @@ -7,14 +7,14 @@ import org.apache.lucene.index.BinaryDocValues; import org.apache.lucene.index.MergeState; import org.opensearch.knn.KNNTestCase; -import org.opensearch.knn.index.codec.KNNCodecTestUtil; +import org.opensearch.knn.index.vectorvalues.TestVectorValues; import java.io.IOException; public class BinaryDocValuesSubTests extends KNNTestCase { public void testNextDoc() throws IOException { - BinaryDocValues binaryDocValues = new KNNCodecTestUtil.ConstantVectorBinaryDocValues(10, 128, 2.0f); + BinaryDocValues binaryDocValues = new TestVectorValues.ConstantVectorBinaryDocValues(10, 128, 2.0f); MergeState.DocMap docMap = new MergeState.DocMap() { @Override public int get(int docID) { @@ -28,7 +28,7 @@ public int get(int docID) { } public void testGetValues() { - BinaryDocValues binaryDocValues = new KNNCodecTestUtil.ConstantVectorBinaryDocValues(10, 128, 2.0f); + BinaryDocValues binaryDocValues = new TestVectorValues.ConstantVectorBinaryDocValues(10, 128, 2.0f); MergeState.DocMap docMap = new MergeState.DocMap() { @Override public int get(int docID) { diff --git a/src/test/java/org/opensearch/knn/index/codec/util/KNNCodecUtilTests.java b/src/test/java/org/opensearch/knn/index/codec/util/KNNCodecUtilTests.java new file mode 100644 index 000000000..86e22cd88 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/codec/util/KNNCodecUtilTests.java @@ -0,0 +1,49 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.codec.util; + +import junit.framework.TestCase; +import org.apache.lucene.index.SegmentInfo; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; + +import java.util.List; +import java.util.Set; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.opensearch.knn.index.codec.util.KNNCodecUtil.calculateArraySize; + +public class KNNCodecUtilTests extends TestCase { + + public void testCalculateArraySize() { + int numVectors = 4; + int vectorLength = 10; + + // Float data type + VectorDataType vectorDataType = VectorDataType.FLOAT; + assertEquals(160, calculateArraySize(numVectors, vectorLength, vectorDataType)); + + // Byte data type + vectorDataType = VectorDataType.BYTE; + assertEquals(40, calculateArraySize(numVectors, vectorLength, vectorDataType)); + + // Binary data type + vectorDataType = VectorDataType.BINARY; + assertEquals(40, calculateArraySize(numVectors, vectorLength, vectorDataType)); + } + + public void testGetKNNEngines() { + SegmentInfo segmentInfo = mock(SegmentInfo.class); + KNNEngine knnEngine = KNNEngine.FAISS; + Set SEGMENT_MULTI_FIELD_FILES_FAISS = Set.of("_0.cfe", "_0_2011_long_target_field.faissc", "_0_2011_target_field.faissc"); + when(segmentInfo.getUseCompoundFile()).thenReturn(true); + when(segmentInfo.files()).thenReturn(SEGMENT_MULTI_FIELD_FILES_FAISS); + List engineFiles = KNNCodecUtil.getEngineFiles(knnEngine.getExtension(), "target_field", segmentInfo); + assertEquals(engineFiles.size(), 2); + assertTrue(engineFiles.get(0).equals("_0_2011_target_field.faissc")); + } +} diff --git a/src/test/java/org/opensearch/knn/index/codec/util/KNNVectorSerializerTests.java b/src/test/java/org/opensearch/knn/index/codec/util/KNNVectorSerializerTests.java index 1d08df6a0..9ec6d937a 100644 --- a/src/test/java/org/opensearch/knn/index/codec/util/KNNVectorSerializerTests.java +++ b/src/test/java/org/opensearch/knn/index/codec/util/KNNVectorSerializerTests.java @@ -5,9 +5,9 @@ package org.opensearch.knn.index.codec.util; +import org.apache.lucene.util.BytesRef; import org.opensearch.knn.KNNTestCase; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.ObjectOutputStream; @@ -26,14 +26,11 @@ public void testVectorSerializerFactory() throws Exception { final DataOutputStream ds = new DataOutputStream(bas); for (float f : vector) ds.writeFloat(f); - final byte[] vectorAsCollectionOfFloats = bas.toByteArray(); - final ByteArrayInputStream bais = new ByteArrayInputStream(vectorAsCollectionOfFloats); - bais.reset(); - + final BytesRef vectorAsCollectionOfFloats = new BytesRef(bas.toByteArray()); final KNNVectorSerializer defaultSerializer = KNNVectorSerializerFactory.getDefaultSerializer(); assertNotNull(defaultSerializer); - final float[] actualDeserializedVector = defaultSerializer.byteToFloatArray(bais); + final float[] actualDeserializedVector = defaultSerializer.byteToFloatArray(vectorAsCollectionOfFloats); assertNotNull(actualDeserializedVector); assertArrayEquals(vector, actualDeserializedVector, 0.1f); @@ -46,18 +43,16 @@ public void testVectorSerializerFactory() throws Exception { assertNotNull(collectionOfFloatsSerializer); } - public void testVectorSerializerFactory_throwExceptionForStreamWithUnsupportedDataType() throws Exception { + public void testVectorSerializerFactory_throwExceptionForBytesWithUnsupportedDataType() throws Exception { // prepare array of chars that is not supported by serializer factory. expected behavior is to fail final char[] arrayOfChars = new char[] { 'a', 'b', 'c' }; final ByteArrayOutputStream bas = new ByteArrayOutputStream(); final DataOutputStream ds = new DataOutputStream(bas); for (char ch : arrayOfChars) ds.writeChar(ch); - final byte[] vectorAsCollectionOfChars = bas.toByteArray(); - final ByteArrayInputStream bais = new ByteArrayInputStream(vectorAsCollectionOfChars); - bais.reset(); + final BytesRef vectorAsCollectionOfChars = new BytesRef(bas.toByteArray()); - expectThrows(RuntimeException.class, () -> KNNVectorSerializerFactory.getSerializerByStreamContent(bais)); + expectThrows(RuntimeException.class, () -> KNNVectorSerializerFactory.getSerializerByBytesRef(vectorAsCollectionOfChars)); } public void testVectorAsArraySerializer() throws Exception { @@ -66,21 +61,17 @@ public void testVectorAsArraySerializer() throws Exception { final ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); final ObjectOutputStream objectStream = new ObjectOutputStream(byteStream); objectStream.writeObject(vector); - final byte[] serializedVector = byteStream.toByteArray(); - final ByteArrayInputStream bais = new ByteArrayInputStream(serializedVector); - - final KNNVectorSerializer vectorSerializer = KNNVectorSerializerFactory.getSerializerByStreamContent(bais); + final BytesRef serializedVector = new BytesRef(byteStream.toByteArray()); + final KNNVectorSerializer vectorSerializer = KNNVectorSerializerFactory.getSerializerByBytesRef(serializedVector); // testing serialization - bais.reset(); final byte[] actualSerializedVector = vectorSerializer.floatToByteArray(vector); assertNotNull(actualSerializedVector); - assertArrayEquals(serializedVector, actualSerializedVector); + assertArrayEquals(serializedVector.bytes, actualSerializedVector); // testing deserialization - bais.reset(); - final float[] actualDeserializedVector = vectorSerializer.byteToFloatArray(bais); + final float[] actualDeserializedVector = vectorSerializer.byteToFloatArray(serializedVector); assertNotNull(actualDeserializedVector); assertArrayEquals(vector, actualDeserializedVector, 0.1f); @@ -94,26 +85,37 @@ public void testVectorAsCollectionOfFloatsSerializer() throws Exception { final DataOutputStream ds = new DataOutputStream(bas); for (float f : vector) ds.writeFloat(f); - final byte[] vectorAsCollectionOfFloats = bas.toByteArray(); - final ByteArrayInputStream bais = new ByteArrayInputStream(vectorAsCollectionOfFloats); - - final KNNVectorSerializer vectorSerializer = KNNVectorSerializerFactory.getSerializerByStreamContent(bais); + final BytesRef vectorAsCollectionOfFloats = new BytesRef(bas.toByteArray()); + final KNNVectorSerializer vectorSerializer = KNNVectorSerializerFactory.getSerializerByBytesRef(vectorAsCollectionOfFloats); // testing serialization - bais.reset(); final byte[] actualSerializedVector = vectorSerializer.floatToByteArray(vector); assertNotNull(actualSerializedVector); - assertArrayEquals(vectorAsCollectionOfFloats, actualSerializedVector); + assertArrayEquals(vectorAsCollectionOfFloats.bytes, actualSerializedVector); // testing deserialization - bais.reset(); - final float[] actualDeserializedVector = vectorSerializer.byteToFloatArray(bais); + final float[] actualDeserializedVector = vectorSerializer.byteToFloatArray(vectorAsCollectionOfFloats); assertNotNull(actualDeserializedVector); assertArrayEquals(vector, actualDeserializedVector, 0.1f); } + public void testVectorSerializer_whenVectorBytesOffset_thenSuccess() { + final float[] vector = getArrayOfRandomFloats(20); + int offset = randomInt(4); + for (SerializationMode serializationMode : SerializationMode.values()) { + final KNNVectorSerializer vectorSerializer = KNNVectorSerializerFactory.getSerializerBySerializationMode(serializationMode); + assertNotNull(vectorSerializer); + byte[] bytes = vectorSerializer.floatToByteArray(vector); + byte[] bytesWithOffset = new byte[bytes.length + 2 * offset]; + System.arraycopy(bytes, 0, bytesWithOffset, offset, bytes.length); + BytesRef serializedVector = new BytesRef(bytesWithOffset, offset, bytes.length); + float[] deserializedVector = vectorSerializer.byteToFloatArray(serializedVector); + assertArrayEquals(vector, deserializedVector, 0.1f); + } + } + private float[] getArrayOfRandomFloats(int arrayLength) { float[] vector = new float[arrayLength]; IntStream.range(0, arrayLength).forEach(index -> vector[index] = random.nextFloat()); diff --git a/src/test/java/org/opensearch/knn/index/engine/AbstractKNNLibraryTests.java b/src/test/java/org/opensearch/knn/index/engine/AbstractKNNLibraryTests.java new file mode 100644 index 000000000..95d4b68a5 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/AbstractKNNLibraryTests.java @@ -0,0 +1,182 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import com.google.common.collect.ImmutableMap; +import org.opensearch.common.ValidationException; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.*; +import org.opensearch.knn.index.engine.model.QueryContext; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static org.opensearch.knn.common.KNNConstants.NAME; + +public class AbstractKNNLibraryTests extends KNNTestCase { + + private final static String CURRENT_VERSION = "test-version"; + private final static String INVALID_METHOD_THROWS_VALIDATION_NAME = "test-method-1"; + private final static KNNMethod INVALID_METHOD_THROWS_VALIDATION = new AbstractKNNMethod( + MethodComponent.Builder.builder(INVALID_METHOD_THROWS_VALIDATION_NAME).addSupportedDataTypes(Set.of(VectorDataType.FLOAT)).build(), + Set.of(SpaceType.DEFAULT), + new DefaultHnswSearchContext() + ) { + @Override + public ValidationException validate(KNNMethodContext knnMethodContext, KNNMethodConfigContext knnMethodConfigContext) { + return new ValidationException(); + } + }; + private final static String VALID_METHOD_NAME = "test-method-2"; + private final static KNNLibrarySearchContext VALID_METHOD_CONTEXT = ctx -> ImmutableMap.of( + "myparameter", + new Parameter.BooleanParameter("myparameter", null, (v, context) -> true) + ); + private final static Map VALID_EXPECTED_MAP = ImmutableMap.of("test-key", "test-param"); + private final static KNNMethod VALID_METHOD = new AbstractKNNMethod( + MethodComponent.Builder.builder(VALID_METHOD_NAME) + .setKnnLibraryIndexingContextGenerator( + (methodComponent, methodComponentContext, knnMethodConfigContext) -> KNNLibraryIndexingContextImpl.builder() + .parameters(new HashMap<>(VALID_EXPECTED_MAP)) + .build() + ) + .addSupportedDataTypes(Set.of(VectorDataType.FLOAT)) + .build(), + Set.of(SpaceType.DEFAULT), + VALID_METHOD_CONTEXT + ) { + }; + private final static AbstractKNNLibrary TEST_LIBRARY = new TestAbstractKNNLibrary( + ImmutableMap.of(INVALID_METHOD_THROWS_VALIDATION_NAME, INVALID_METHOD_THROWS_VALIDATION, VALID_METHOD_NAME, VALID_METHOD), + CURRENT_VERSION + ); + + public void testGetVersion() { + assertEquals(CURRENT_VERSION, TEST_LIBRARY.getVersion()); + } + + public void testValidateMethod() throws IOException { + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(org.opensearch.Version.CURRENT) + .dimension(10) + .vectorDataType(VectorDataType.FLOAT) + .build(); + // Invalid - method not supported + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().field(NAME, "invalid").endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext1 = KNNMethodContext.parse(in); + assertNotNull(TEST_LIBRARY.validateMethod(knnMethodContext1, knnMethodConfigContext)); + + // Invalid - method validation + xContentBuilder = XContentFactory.jsonBuilder().startObject().field(NAME, INVALID_METHOD_THROWS_VALIDATION_NAME).endObject(); + in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext2 = KNNMethodContext.parse(in); + expectThrows(IllegalStateException.class, () -> TEST_LIBRARY.validateMethod(knnMethodContext2, knnMethodConfigContext)); + } + + public void testEngineSpecificMethods() { + QueryContext engineSpecificMethodContext = new QueryContext(VectorQueryType.K); + assertNotNull(TEST_LIBRARY.getKNNLibrarySearchContext(VALID_METHOD_NAME)); + assertTrue( + TEST_LIBRARY.getKNNLibrarySearchContext(VALID_METHOD_NAME) + .supportedMethodParameters(engineSpecificMethodContext) + .containsKey("myparameter") + ); + } + + public void testGetKNNLibraryIndexingContext() { + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(org.opensearch.Version.CURRENT) + .dimension(10) + .vectorDataType(VectorDataType.FLOAT) + .build(); + // Check that map is expected + Map expectedMap = new HashMap<>(VALID_EXPECTED_MAP); + expectedMap.put(KNNConstants.SPACE_TYPE, SpaceType.DEFAULT.getValue()); + expectedMap.put(KNNConstants.VECTOR_DATA_TYPE_FIELD, VectorDataType.FLOAT.getValue()); + KNNMethodContext knnMethodContext = new KNNMethodContext( + KNNEngine.DEFAULT, + SpaceType.DEFAULT, + new MethodComponentContext(VALID_METHOD_NAME, Collections.emptyMap()) + ); + assertEquals( + expectedMap, + TEST_LIBRARY.getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext).getLibraryParameters() + ); + + // Check when invalid method is passed in + KNNMethodContext invalidKnnMethodContext = new KNNMethodContext( + KNNEngine.DEFAULT, + SpaceType.DEFAULT, + new MethodComponentContext("invalid", Collections.emptyMap()) + ); + expectThrows( + IllegalArgumentException.class, + () -> TEST_LIBRARY.getKNNLibraryIndexingContext(invalidKnnMethodContext, knnMethodConfigContext) + ); + } + + private static class TestAbstractKNNLibrary extends AbstractKNNLibrary { + public TestAbstractKNNLibrary(Map methods, String currentVersion) { + super(methods, currentVersion); + } + + @Override + public String getExtension() { + return null; + } + + @Override + public String getCompoundExtension() { + return null; + } + + @Override + public float score(float rawScore, SpaceType spaceType) { + return 0; + } + + @Override + public Float distanceToRadialThreshold(Float distance, SpaceType spaceType) { + return 0f; + } + + public Float scoreToRadialThreshold(Float score, SpaceType spaceType) { + return 0f; + } + + @Override + public int estimateOverheadInKB(KNNMethodContext knnMethodContext, KNNMethodConfigContext knnMethodConfigContext) { + return 0; + } + + @Override + public Boolean isInitialized() { + return null; + } + + @Override + public void setInitialized(Boolean isInitialized) { + + } + + @Override + public ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + SpaceType spaceType + ) { + return null; + } + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/AbstractKNNMethodTests.java b/src/test/java/org/opensearch/knn/index/engine/AbstractKNNMethodTests.java new file mode 100644 index 000000000..241703d8b --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/AbstractKNNMethodTests.java @@ -0,0 +1,191 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import com.google.common.collect.ImmutableMap; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static org.opensearch.knn.common.KNNConstants.NAME; +import static org.opensearch.knn.common.KNNConstants.PARAMETERS; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; + +public class AbstractKNNMethodTests extends KNNTestCase { + + private static class TestKNNMethod extends AbstractKNNMethod { + public TestKNNMethod(MethodComponent methodComponent, Set spaces, KNNLibrarySearchContext engineSpecificMethodContext) { + super(methodComponent, spaces, engineSpecificMethodContext); + } + } + + /** + * Test KNNMethod has space + */ + public void testHasSpace() { + String name = "test"; + KNNMethod knnMethod = new TestKNNMethod( + MethodComponent.Builder.builder(name).build(), + Set.of(SpaceType.L2, SpaceType.COSINESIMIL), + EMPTY_ENGINE_SPECIFIC_CONTEXT + ); + assertTrue(knnMethod.isSpaceTypeSupported(SpaceType.L2)); + assertTrue(knnMethod.isSpaceTypeSupported(SpaceType.COSINESIMIL)); + assertFalse(knnMethod.isSpaceTypeSupported(SpaceType.INNER_PRODUCT)); + } + + /** + * Test KNNMethod validate + */ + public void testValidate() throws IOException { + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(org.opensearch.Version.CURRENT) + .dimension(10) + .vectorDataType(VectorDataType.FLOAT) + .build(); + String methodName = "test-method"; + KNNMethod knnMethod = new TestKNNMethod( + MethodComponent.Builder.builder(methodName).addSupportedDataTypes(Set.of(VectorDataType.FLOAT)).build(), + Set.of(SpaceType.L2), + EMPTY_ENGINE_SPECIFIC_CONTEXT + ); + + // Invalid space + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, methodName) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.INNER_PRODUCT.getValue()) + .endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext1 = KNNMethodContext.parse(in); + assertNotNull(knnMethod.validate(knnMethodContext1, knnMethodConfigContext)); + + // Invalid methodComponent + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, methodName) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2.getValue()) + .startObject(PARAMETERS) + .field("invalid", "invalid") + .endObject() + .endObject(); + in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext2 = KNNMethodContext.parse(in); + + assertNotNull(knnMethod.validate(knnMethodContext2, knnMethodConfigContext)); + + // Valid everything + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, methodName) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2.getValue()) + .endObject(); + in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext3 = KNNMethodContext.parse(in); + assertNull(knnMethod.validate(knnMethodContext3, knnMethodConfigContext)); + } + + /** + * Test KNNMethod validateWithData + */ + public void testValidateWithContext() throws IOException { + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(org.opensearch.Version.CURRENT) + .dimension(4) + .vectorDataType(VectorDataType.FLOAT) + .build(); + String methodName = "test-method"; + KNNMethod knnMethod = new TestKNNMethod( + MethodComponent.Builder.builder(methodName).addSupportedDataTypes(Set.of(VectorDataType.FLOAT)).build(), + Set.of(SpaceType.L2), + EMPTY_ENGINE_SPECIFIC_CONTEXT + ); + + // Invalid space + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, methodName) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.INNER_PRODUCT.getValue()) + .endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext1 = KNNMethodContext.parse(in); + assertNotNull(knnMethod.validate(knnMethodContext1, knnMethodConfigContext)); + + // Invalid methodComponent + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, methodName) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2.getValue()) + .startObject(PARAMETERS) + .field("invalid", "invalid") + .endObject() + .endObject(); + in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext2 = KNNMethodContext.parse(in); + assertNotNull(knnMethod.validate(knnMethodContext2, knnMethodConfigContext)); + + // Valid everything + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, methodName) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2.getValue()) + .endObject(); + in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext3 = KNNMethodContext.parse(in); + assertNull(knnMethod.validate(knnMethodContext3, knnMethodConfigContext)); + } + + public void testGetKNNLibraryIndexingContext() { + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(org.opensearch.Version.CURRENT) + .dimension(4) + .vectorDataType(VectorDataType.FLOAT) + .build(); + SpaceType spaceType = SpaceType.DEFAULT; + String methodName = "test-method"; + Map generatedMap = new HashMap<>(ImmutableMap.of("test-key", "test-value")); + MethodComponent methodComponent = MethodComponent.Builder.builder(methodName) + .setKnnLibraryIndexingContextGenerator( + ((methodComponent1, methodComponentContext, methodConfigContext) -> KNNLibraryIndexingContextImpl.builder() + .parameters(methodComponentContext.getParameters()) + .build()) + ) + .build(); + + KNNMethod knnMethod = new TestKNNMethod(methodComponent, Set.of(SpaceType.L2), EMPTY_ENGINE_SPECIFIC_CONTEXT); + + Map expectedMap = new HashMap<>(generatedMap); + expectedMap.put(KNNConstants.SPACE_TYPE, spaceType.getValue()); + expectedMap.put(KNNConstants.VECTOR_DATA_TYPE_FIELD, VectorDataType.FLOAT.getValue()); + + assertEquals( + expectedMap, + knnMethod.getKNNLibraryIndexingContext( + new KNNMethodContext(KNNEngine.DEFAULT, spaceType, new MethodComponentContext(methodName, generatedMap)), + knnMethodConfigContext + ).getLibraryParameters() + ); + } + + public void testGetKNNLibrarySearchContext() { + String methodName = "test-method"; + KNNLibrarySearchContext knnLibrarySearchContext = new DefaultHnswSearchContext(); + KNNMethod knnMethod = new TestKNNMethod( + MethodComponent.Builder.builder(methodName).build(), + Set.of(SpaceType.L2), + knnLibrarySearchContext + ); + assertEquals(knnLibrarySearchContext, knnMethod.getKNNLibrarySearchContext()); + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/AbstractMethodResolverTests.java b/src/test/java/org/opensearch/knn/index/engine/AbstractMethodResolverTests.java new file mode 100644 index 000000000..f21459246 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/AbstractMethodResolverTests.java @@ -0,0 +1,158 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; + +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; + +public class AbstractMethodResolverTests extends KNNTestCase { + + private final static String ENCODER_NAME = "test"; + private final static CompressionLevel DEFAULT_COMPRESSION = CompressionLevel.x8; + + private final static AbstractMethodResolver TEST_RESOLVER = new AbstractMethodResolver() { + @Override + public ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + SpaceType spaceType + ) { + return null; + } + }; + + private final static Encoder TEST_ENCODER = new Encoder() { + + @Override + public MethodComponent getMethodComponent() { + return MethodComponent.Builder.builder(ENCODER_NAME).build(); + } + + @Override + public CompressionLevel calculateCompressionLevel( + MethodComponentContext encoderContext, + KNNMethodConfigContext knnMethodConfigContext + ) { + return DEFAULT_COMPRESSION; + } + }; + + private final static Map ENCODER_MAP = Map.of(ENCODER_NAME, TEST_ENCODER); + + public void testResolveCompressionLevelFromMethodContext() { + assertEquals( + CompressionLevel.x1, + TEST_RESOLVER.resolveCompressionLevelFromMethodContext( + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.DEFAULT, MethodComponentContext.EMPTY), + KNNMethodConfigContext.builder().build(), + ENCODER_MAP + ) + ); + assertEquals( + DEFAULT_COMPRESSION, + TEST_RESOLVER.resolveCompressionLevelFromMethodContext( + new KNNMethodContext( + KNNEngine.DEFAULT, + SpaceType.DEFAULT, + new MethodComponentContext( + METHOD_HNSW, + Map.of(METHOD_ENCODER_PARAMETER, new MethodComponentContext(ENCODER_NAME, Map.of())) + ) + ), + KNNMethodConfigContext.builder().build(), + ENCODER_MAP + ) + ); + } + + public void testIsEncoderSpecified() { + assertFalse(TEST_RESOLVER.isEncoderSpecified(null)); + assertFalse( + TEST_RESOLVER.isEncoderSpecified(new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.DEFAULT, MethodComponentContext.EMPTY)) + ); + assertFalse( + TEST_RESOLVER.isEncoderSpecified( + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.DEFAULT, new MethodComponentContext(METHOD_HNSW, Map.of())) + ) + ); + assertTrue( + TEST_RESOLVER.isEncoderSpecified( + new KNNMethodContext( + KNNEngine.DEFAULT, + SpaceType.DEFAULT, + new MethodComponentContext(METHOD_HNSW, Map.of(METHOD_ENCODER_PARAMETER, "test")) + ) + ) + ); + } + + public void testShouldEncoderBeResolved() { + assertFalse( + TEST_RESOLVER.shouldEncoderBeResolved( + new KNNMethodContext( + KNNEngine.DEFAULT, + SpaceType.DEFAULT, + new MethodComponentContext(METHOD_HNSW, Map.of(METHOD_ENCODER_PARAMETER, "test")) + ), + KNNMethodConfigContext.builder().build() + ) + ); + assertFalse( + TEST_RESOLVER.shouldEncoderBeResolved(null, KNNMethodConfigContext.builder().compressionLevel(CompressionLevel.x1).build()) + ); + assertFalse( + TEST_RESOLVER.shouldEncoderBeResolved( + null, + KNNMethodConfigContext.builder().compressionLevel(CompressionLevel.x1).mode(Mode.ON_DISK).build() + ) + ); + assertFalse( + TEST_RESOLVER.shouldEncoderBeResolved( + null, + KNNMethodConfigContext.builder().compressionLevel(CompressionLevel.NOT_CONFIGURED).mode(Mode.IN_MEMORY).build() + ) + ); + assertFalse( + TEST_RESOLVER.shouldEncoderBeResolved( + null, + KNNMethodConfigContext.builder() + .compressionLevel(CompressionLevel.NOT_CONFIGURED) + .mode(Mode.ON_DISK) + .vectorDataType(VectorDataType.BINARY) + .build() + ) + ); + assertTrue( + TEST_RESOLVER.shouldEncoderBeResolved( + null, + KNNMethodConfigContext.builder() + .compressionLevel(CompressionLevel.NOT_CONFIGURED) + .mode(Mode.ON_DISK) + .vectorDataType(VectorDataType.FLOAT) + .build() + ) + ); + assertTrue( + TEST_RESOLVER.shouldEncoderBeResolved( + null, + KNNMethodConfigContext.builder() + .compressionLevel(CompressionLevel.x32) + .mode(Mode.ON_DISK) + .vectorDataType(VectorDataType.FLOAT) + .build() + ) + ); + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/EngineResolverTests.java b/src/test/java/org/opensearch/knn/index/engine/EngineResolverTests.java new file mode 100644 index 000000000..291f0c671 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/EngineResolverTests.java @@ -0,0 +1,152 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; + +public class EngineResolverTests extends KNNTestCase { + + private static final EngineResolver ENGINE_RESOLVER = EngineResolver.INSTANCE; + + public void testResolveEngine_whenEngineSpecifiedInMethod_thenThatEngine() { + assertEquals( + KNNEngine.LUCENE, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().build(), + new KNNMethodContext(KNNEngine.LUCENE, SpaceType.DEFAULT, MethodComponentContext.EMPTY), + false + ) + ); + } + + public void testResolveEngine_whenRequiresTraining_thenFaiss() { + assertEquals(KNNEngine.FAISS, ENGINE_RESOLVER.resolveEngine(KNNMethodConfigContext.builder().build(), null, true)); + } + + public void testResolveEngine_whenModeAndCompressionAreFalse_thenDefault() { + assertEquals(KNNEngine.DEFAULT, ENGINE_RESOLVER.resolveEngine(KNNMethodConfigContext.builder().build(), null, false)); + assertEquals( + KNNEngine.DEFAULT, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().build(), + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.UNDEFINED, MethodComponentContext.EMPTY, false), + false + ) + ); + } + + public void testResolveEngine_whenModeSpecifiedAndCompressionIsNotSpecified_thenNMSLIB() { + assertEquals(KNNEngine.DEFAULT, ENGINE_RESOLVER.resolveEngine(KNNMethodConfigContext.builder().build(), null, false)); + assertEquals( + KNNEngine.NMSLIB, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().mode(Mode.IN_MEMORY).build(), + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.UNDEFINED, MethodComponentContext.EMPTY, false), + false + ) + ); + } + + public void testResolveEngine_whenCompressionIs1x_thenEngineBasedOnMode() { + assertEquals( + KNNEngine.FAISS, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().mode(Mode.ON_DISK).compressionLevel(CompressionLevel.x1).build(), + null, + false + ) + ); + assertEquals( + KNNEngine.NMSLIB, + ENGINE_RESOLVER.resolveEngine(KNNMethodConfigContext.builder().compressionLevel(CompressionLevel.x1).build(), null, false) + ); + } + + public void testResolveEngine_whenCompressionIs4x_thenEngineIsLucene() { + assertEquals( + KNNEngine.LUCENE, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().mode(Mode.ON_DISK).compressionLevel(CompressionLevel.x4).build(), + null, + false + ) + ); + assertEquals( + KNNEngine.LUCENE, + ENGINE_RESOLVER.resolveEngine(KNNMethodConfigContext.builder().compressionLevel(CompressionLevel.x4).build(), null, false) + ); + } + + public void testResolveEngine_whenConfiguredForBQ_thenEngineIsFaiss() { + assertEquals( + KNNEngine.FAISS, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().mode(Mode.ON_DISK).compressionLevel(CompressionLevel.x2).build(), + null, + false + ) + ); + assertEquals( + KNNEngine.FAISS, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().mode(Mode.IN_MEMORY).compressionLevel(CompressionLevel.x2).build(), + null, + false + ) + ); + assertEquals( + KNNEngine.FAISS, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().mode(Mode.ON_DISK).compressionLevel(CompressionLevel.x8).build(), + null, + false + ) + ); + assertEquals( + KNNEngine.FAISS, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().mode(Mode.IN_MEMORY).compressionLevel(CompressionLevel.x8).build(), + null, + false + ) + ); + assertEquals( + KNNEngine.FAISS, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().mode(Mode.ON_DISK).compressionLevel(CompressionLevel.x16).build(), + null, + false + ) + ); + assertEquals( + KNNEngine.FAISS, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().mode(Mode.IN_MEMORY).compressionLevel(CompressionLevel.x16).build(), + null, + false + ) + ); + assertEquals( + KNNEngine.FAISS, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().mode(Mode.ON_DISK).compressionLevel(CompressionLevel.x32).build(), + null, + false + ) + ); + assertEquals( + KNNEngine.FAISS, + ENGINE_RESOLVER.resolveEngine( + KNNMethodConfigContext.builder().mode(Mode.IN_MEMORY).compressionLevel(CompressionLevel.x32).build(), + null, + false + ) + ); + } +} diff --git a/src/test/java/org/opensearch/knn/index/util/KNNEngineTests.java b/src/test/java/org/opensearch/knn/index/engine/KNNEngineTests.java similarity index 50% rename from src/test/java/org/opensearch/knn/index/util/KNNEngineTests.java rename to src/test/java/org/opensearch/knn/index/engine/KNNEngineTests.java index a62a556e7..3f356999c 100644 --- a/src/test/java/org/opensearch/knn/index/util/KNNEngineTests.java +++ b/src/test/java/org/opensearch/knn/index/engine/KNNEngineTests.java @@ -1,25 +1,32 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. */ -package org.opensearch.knn.index.util; +package org.opensearch.knn.index.engine; import org.opensearch.knn.KNNTestCase; import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.engine.faiss.Faiss; +import org.opensearch.knn.index.engine.lucene.Lucene; +import org.opensearch.knn.index.engine.nmslib.Nmslib; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; public class KNNEngineTests extends KNNTestCase { /** - * Get latest build version from library + * Check that version from engine and library match */ public void testDelegateLibraryFunctions() { - assertEquals(KNNLibrary.Nmslib.INSTANCE.getLatestLibVersion(), KNNEngine.NMSLIB.getLatestLibVersion()); + assertEquals(Nmslib.INSTANCE.getVersion(), KNNEngine.NMSLIB.getVersion()); + assertEquals(Faiss.INSTANCE.getVersion(), KNNEngine.FAISS.getVersion()); + assertEquals(Lucene.INSTANCE.getVersion(), KNNEngine.LUCENE.getVersion()); + } + + public void testGetDefaultEngine_thenReturnFAISS() { + assertEquals(KNNEngine.FAISS, KNNEngine.DEFAULT); } /** @@ -38,9 +45,9 @@ public void testGetEngine() { } public void testGetEngineFromPath() { - String hnswPath1 = "test" + KNNLibrary.Nmslib.EXTENSION; + String hnswPath1 = "test" + Nmslib.EXTENSION; assertEquals(KNNEngine.NMSLIB, KNNEngine.getEngineNameFromPath(hnswPath1)); - String hnswPath2 = "test" + KNNLibrary.Nmslib.EXTENSION + KNNConstants.COMPOUND_EXTENSION; + String hnswPath2 = "test" + Nmslib.EXTENSION + KNNConstants.COMPOUND_EXTENSION; assertEquals(KNNEngine.NMSLIB, KNNEngine.getEngineNameFromPath(hnswPath2)); String faissPath1 = "test" + KNNConstants.FAISS_EXTENSION; @@ -51,4 +58,14 @@ public void testGetEngineFromPath() { String invalidPath = "test.invalid"; expectThrows(IllegalArgumentException.class, () -> KNNEngine.getEngineNameFromPath(invalidPath)); } + + public void testMmapFileExtensions() { + final List mmapExtensions = Arrays.stream(KNNEngine.values()) + .flatMap(engine -> engine.mmapFileExtensions().stream()) + .collect(Collectors.toList()); + assertNotNull(mmapExtensions); + final List expectedSettings = List.of("vex", "vec"); + assertTrue(expectedSettings.containsAll(mmapExtensions)); + assertTrue(mmapExtensions.containsAll(expectedSettings)); + } } diff --git a/src/test/java/org/opensearch/knn/index/KNNMethodContextTests.java b/src/test/java/org/opensearch/knn/index/engine/KNNMethodContextTests.java similarity index 63% rename from src/test/java/org/opensearch/knn/index/KNNMethodContextTests.java rename to src/test/java/org/opensearch/knn/index/engine/KNNMethodContextTests.java index 176029ad6..c5979e576 100644 --- a/src/test/java/org/opensearch/knn/index/KNNMethodContextTests.java +++ b/src/test/java/org/opensearch/knn/index/engine/KNNMethodContextTests.java @@ -1,22 +1,19 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. */ -package org.opensearch.knn.index; +package org.opensearch.knn.index.engine; +import org.opensearch.Version; import org.opensearch.common.io.stream.BytesStreamOutput; import org.opensearch.knn.KNNTestCase; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; import com.google.common.collect.ImmutableMap; -import org.opensearch.common.xcontent.ToXContent; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.index.mapper.MapperParsingException; @@ -24,14 +21,7 @@ import java.util.Collections; import java.util.Map; -import static org.opensearch.knn.common.KNNConstants.BYTES_PER_KILOBYTES; -import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_SIZE; -import static org.opensearch.knn.common.KNNConstants.ENCODER_PQ; import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; -import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; -import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; -import static org.opensearch.knn.common.KNNConstants.METHOD_IVF; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NLIST; import static org.opensearch.knn.common.KNNConstants.NAME; import static org.opensearch.knn.common.KNNConstants.PARAMETERS; import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; @@ -65,7 +55,7 @@ public void testStreams() throws IOException { public void testGetMethodComponent() { MethodComponentContext methodComponent = new MethodComponentContext("test-method", Collections.emptyMap()); KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.DEFAULT, methodComponent); - assertEquals(methodComponent, knnMethodContext.getMethodComponent()); + assertEquals(methodComponent, knnMethodContext.getMethodComponentContext()); } /** @@ -74,7 +64,7 @@ public void testGetMethodComponent() { public void testGetEngine() { MethodComponentContext methodComponent = new MethodComponentContext("test-method", Collections.emptyMap()); KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.DEFAULT, methodComponent); - assertEquals(KNNEngine.DEFAULT, knnMethodContext.getEngine()); + assertEquals(KNNEngine.DEFAULT, knnMethodContext.getKnnEngine()); } /** @@ -86,104 +76,6 @@ public void testGetSpaceType() { assertEquals(SpaceType.L1, knnMethodContext.getSpaceType()); } - /** - * Test KNNMethodContext validation - */ - public void testValidate() { - // Check valid default - this should not throw any exception - assertNull(KNNMethodContext.getDefault().validate()); - - // Check a valid nmslib method - MethodComponentContext hnswMethod = new MethodComponentContext(METHOD_HNSW, Collections.emptyMap()); - KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.NMSLIB, SpaceType.L2, hnswMethod); - assertNull(knnMethodContext.validate()); - - // Check invalid parameter nmslib - hnswMethod = new MethodComponentContext(METHOD_HNSW, ImmutableMap.of("invalid", 111)); - KNNMethodContext knnMethodContext1 = new KNNMethodContext(KNNEngine.NMSLIB, SpaceType.L2, hnswMethod); - assertNotNull(knnMethodContext1.validate()); - - // Check invalid method nmslib - MethodComponentContext invalidMethod = new MethodComponentContext("invalid", Collections.emptyMap()); - KNNMethodContext knnMethodContext2 = new KNNMethodContext(KNNEngine.NMSLIB, SpaceType.L2, invalidMethod); - expectThrows(IllegalArgumentException.class, knnMethodContext2::validate); - } - - /** - * Test KNNMethodContext requires training method - */ - public void testRequiresTraining() { - - // Check for NMSLIB - MethodComponentContext hnswMethod = new MethodComponentContext(METHOD_HNSW, Collections.emptyMap()); - KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.NMSLIB, SpaceType.L2, hnswMethod); - assertFalse(knnMethodContext.isTrainingRequired()); - - // Check for FAISS not required - hnswMethod = new MethodComponentContext(METHOD_HNSW, Collections.emptyMap()); - knnMethodContext = new KNNMethodContext(KNNEngine.FAISS, SpaceType.L2, hnswMethod); - assertFalse(knnMethodContext.isTrainingRequired()); - - // Check FAISS required - MethodComponentContext pq = new MethodComponentContext(ENCODER_PQ, Collections.emptyMap()); - - MethodComponentContext hnswMethodPq = new MethodComponentContext(METHOD_HNSW, ImmutableMap.of(METHOD_ENCODER_PARAMETER, pq)); - knnMethodContext = new KNNMethodContext(KNNEngine.FAISS, SpaceType.L2, hnswMethodPq); - assertTrue(knnMethodContext.isTrainingRequired()); - - MethodComponentContext ivfMethod = new MethodComponentContext(METHOD_IVF, Collections.emptyMap()); - knnMethodContext = new KNNMethodContext(KNNEngine.FAISS, SpaceType.L2, ivfMethod); - assertTrue(knnMethodContext.isTrainingRequired()); - - MethodComponentContext ivfMethodPq = new MethodComponentContext(METHOD_IVF, ImmutableMap.of(METHOD_ENCODER_PARAMETER, pq)); - knnMethodContext = new KNNMethodContext(KNNEngine.FAISS, SpaceType.L2, ivfMethodPq); - assertTrue(knnMethodContext.isTrainingRequired()); - } - - public void testEstimateOverheadInKB() { - // For HNSW no encoding we expect 0 - MethodComponentContext hnswMethod = new MethodComponentContext(METHOD_HNSW, Collections.emptyMap()); - KNNMethodContext knnMethodContextNmslib = new KNNMethodContext(KNNEngine.NMSLIB, SpaceType.L2, hnswMethod); - KNNMethodContext knnMethodContextFaiss = new KNNMethodContext(KNNEngine.FAISS, SpaceType.INNER_PRODUCT, hnswMethod); - assertEquals(0, knnMethodContextNmslib.estimateOverheadInKB(1000)); - assertEquals(0, knnMethodContextFaiss.estimateOverheadInKB(168)); - - // For IVF, we expect 4 * nlist * d / 1024 + 1 - int dimension = 768; - int nlists = 1024; - int expectedIvf = 4 * nlists * dimension / BYTES_PER_KILOBYTES + 1; - - MethodComponentContext ivfMethod = new MethodComponentContext(METHOD_IVF, ImmutableMap.of(METHOD_PARAMETER_NLIST, nlists)); - knnMethodContextFaiss = new KNNMethodContext(KNNEngine.FAISS, SpaceType.L2, ivfMethod); - assertEquals(expectedIvf, knnMethodContextFaiss.estimateOverheadInKB(dimension)); - - // For IVFPQ twe expect 4 * nlist * d / 1024 + 1 + 4 * d * 2^code_size / 1024 + 1 - int codeSize = 16; - int expectedFromPq = 4 * dimension * (1 << codeSize) / BYTES_PER_KILOBYTES + 1; - int expectedIvfPq = expectedIvf + expectedFromPq; - - MethodComponentContext pqMethodContext = new MethodComponentContext( - ENCODER_PQ, - ImmutableMap.of(ENCODER_PARAMETER_PQ_CODE_SIZE, codeSize) - ); - - MethodComponentContext ivfMethodPq = new MethodComponentContext( - METHOD_IVF, - ImmutableMap.of(METHOD_PARAMETER_NLIST, nlists, METHOD_ENCODER_PARAMETER, pqMethodContext) - ); - knnMethodContextFaiss = new KNNMethodContext(KNNEngine.FAISS, SpaceType.L2, ivfMethodPq); - assertEquals(expectedIvfPq, knnMethodContextFaiss.estimateOverheadInKB(dimension)); - - // For HNSWPQ, we expect 4 * d * 2^code_size / 1024 + 1 - int expectedHnswPq = expectedFromPq; - MethodComponentContext hnswMethodPq = new MethodComponentContext( - METHOD_HNSW, - ImmutableMap.of(METHOD_ENCODER_PARAMETER, pqMethodContext) - ); - knnMethodContextFaiss = new KNNMethodContext(KNNEngine.FAISS, SpaceType.L2, hnswMethodPq); - assertEquals(expectedHnswPq, knnMethodContextFaiss.estimateOverheadInKB(dimension)); - } - /** * Test context method parsing when input is invalid */ @@ -251,7 +143,7 @@ public void testParse_nullParameters() throws IOException { .endObject(); Map in = xContentBuilderToMap(xContentBuilder); KNNMethodContext knnMethodContext = KNNMethodContext.parse(in); - assertTrue(knnMethodContext.getMethodComponent().getParameters().isEmpty()); + assertTrue(knnMethodContext.getMethodComponentContext().getParameters().isEmpty()); } /** @@ -265,10 +157,10 @@ public void testParse_valid() throws IOException { Map in = xContentBuilderToMap(xContentBuilder); KNNMethodContext knnMethodContext = KNNMethodContext.parse(in); - assertEquals(KNNEngine.DEFAULT, knnMethodContext.getEngine()); - assertEquals(SpaceType.DEFAULT, knnMethodContext.getSpaceType()); - assertEquals(methodName, knnMethodContext.getMethodComponent().getName()); - assertTrue(knnMethodContext.getMethodComponent().getParameters().isEmpty()); + assertEquals(KNNEngine.DEFAULT, knnMethodContext.getKnnEngine()); + assertEquals(SpaceType.UNDEFINED, knnMethodContext.getSpaceType()); + assertEquals(methodName, knnMethodContext.getMethodComponentContext().getName()); + assertTrue(knnMethodContext.getMethodComponentContext().getParameters().isEmpty()); // Method with parameters String methodParameterKey1 = "p-1"; @@ -287,8 +179,8 @@ public void testParse_valid() throws IOException { in = xContentBuilderToMap(xContentBuilder); knnMethodContext = KNNMethodContext.parse(in); - assertEquals(methodParameterValue1, knnMethodContext.getMethodComponent().getParameters().get(methodParameterKey1)); - assertEquals(methodParameterValue2, knnMethodContext.getMethodComponent().getParameters().get(methodParameterKey2)); + assertEquals(methodParameterValue1, knnMethodContext.getMethodComponentContext().getParameters().get(methodParameterKey1)); + assertEquals(methodParameterValue2, knnMethodContext.getMethodComponentContext().getParameters().get(methodParameterKey2)); // Method with parameter that is a method context paramet @@ -306,12 +198,12 @@ public void testParse_valid() throws IOException { in = xContentBuilderToMap(xContentBuilder); knnMethodContext = KNNMethodContext.parse(in); - assertTrue(knnMethodContext.getMethodComponent().getParameters().get(methodParameterKey1) instanceof MethodComponentContext); + assertTrue(knnMethodContext.getMethodComponentContext().getParameters().get(methodParameterKey1) instanceof MethodComponentContext); assertEquals( methodParameterValue1, - ((MethodComponentContext) knnMethodContext.getMethodComponent().getParameters().get(methodParameterKey1)).getName() + ((MethodComponentContext) knnMethodContext.getMethodComponentContext().getParameters().get(methodParameterKey1)).getName() ); - assertEquals(methodParameterValue2, knnMethodContext.getMethodComponent().getParameters().get(methodParameterKey2)); + assertEquals(methodParameterValue2, knnMethodContext.getMethodComponentContext().getParameters().get(methodParameterKey2)); } /** @@ -385,4 +277,62 @@ public void testHashCode() { assertNotEquals(methodContext1.hashCode(), methodContext4.hashCode()); assertNotEquals(methodContext1.hashCode(), methodContext5.hashCode()); } + + public void testValidateVectorDataType_whenBinaryFaissHNSW_thenValid() { + validateValidateVectorDataType(KNNEngine.FAISS, KNNConstants.METHOD_HNSW, VectorDataType.BINARY, SpaceType.HAMMING, null); + } + + public void testValidateVectorDataType_whenBinaryNonFaiss_thenException() { + validateValidateVectorDataType( + KNNEngine.LUCENE, + KNNConstants.METHOD_HNSW, + VectorDataType.BINARY, + SpaceType.HAMMING, + "UnsupportedMethod" + ); + validateValidateVectorDataType( + KNNEngine.NMSLIB, + KNNConstants.METHOD_HNSW, + VectorDataType.BINARY, + SpaceType.HAMMING, + "UnsupportedMethod" + ); + } + + public void testValidateVectorDataType_whenByte_thenValid() { + validateValidateVectorDataType(KNNEngine.LUCENE, KNNConstants.METHOD_HNSW, VectorDataType.BYTE, SpaceType.L2, null); + validateValidateVectorDataType(KNNEngine.FAISS, KNNConstants.METHOD_HNSW, VectorDataType.BYTE, SpaceType.L2, null); + } + + public void testValidateVectorDataType_whenByte_thenException() { + validateValidateVectorDataType(KNNEngine.NMSLIB, KNNConstants.METHOD_IVF, VectorDataType.BYTE, SpaceType.L2, "UnsupportedMethod"); + } + + public void testValidateVectorDataType_whenFloat_thenValid() { + validateValidateVectorDataType(KNNEngine.FAISS, KNNConstants.METHOD_HNSW, VectorDataType.FLOAT, SpaceType.L2, null); + validateValidateVectorDataType(KNNEngine.LUCENE, KNNConstants.METHOD_HNSW, VectorDataType.FLOAT, SpaceType.L2, null); + validateValidateVectorDataType(KNNEngine.NMSLIB, KNNConstants.METHOD_HNSW, VectorDataType.FLOAT, SpaceType.L2, null); + } + + private void validateValidateVectorDataType( + final KNNEngine knnEngine, + final String methodName, + final VectorDataType vectorDataType, + final SpaceType spaceType, + final String expectedErrMsg + ) { + MethodComponentContext methodComponentContext = new MethodComponentContext(methodName, Collections.emptyMap()); + KNNMethodContext methodContext = new KNNMethodContext(knnEngine, spaceType, methodComponentContext); + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .vectorDataType(vectorDataType) + .dimension(8) + .versionCreated(Version.CURRENT) + .build(); + if (expectedErrMsg == null) { + assertNull(knnEngine.validateMethod(methodContext, knnMethodConfigContext)); + } else { + assertNotNull(knnEngine.validateMethod(methodContext, knnMethodConfigContext)); + } + } + } diff --git a/src/test/java/org/opensearch/knn/index/MethodComponentTests.java b/src/test/java/org/opensearch/knn/index/engine/MethodComponentTests.java similarity index 55% rename from src/test/java/org/opensearch/knn/index/MethodComponentTests.java rename to src/test/java/org/opensearch/knn/index/engine/MethodComponentTests.java index b752764c3..e247fb70a 100644 --- a/src/test/java/org/opensearch/knn/index/MethodComponentTests.java +++ b/src/test/java/org/opensearch/knn/index/engine/MethodComponentTests.java @@ -1,23 +1,23 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. */ -package org.opensearch.knn.index; +package org.opensearch.knn.index.engine; import com.google.common.collect.ImmutableMap; +import org.opensearch.Version; import org.opensearch.knn.KNNTestCase; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; +import org.opensearch.knn.index.util.IndexHyperParametersUtil; import java.io.IOException; import java.util.Map; +import java.util.Set; import static org.opensearch.knn.common.KNNConstants.NAME; import static org.opensearch.knn.common.KNNConstants.PARAMETERS; @@ -39,7 +39,7 @@ public void testGetParameters() { String name = "test"; String paramKey = "key"; MethodComponent methodComponent = MethodComponent.Builder.builder(name) - .addParameter(paramKey, new Parameter.IntegerParameter(paramKey, 1, v -> v > 0)) + .addParameter(paramKey, new Parameter.IntegerParameter(paramKey, 1, (v, context) -> v > 0)) .build(); assertEquals(1, methodComponent.getParameters().size()); assertTrue(methodComponent.getParameters().containsKey(paramKey)); @@ -58,11 +58,18 @@ public void testValidate() throws IOException { .field("invalid", "invalid") .endObject() .endObject(); + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .dimension(1) + .versionCreated(Version.CURRENT) + .vectorDataType(VectorDataType.FLOAT) + .build(); Map in = xContentBuilderToMap(xContentBuilder); MethodComponentContext componentContext1 = MethodComponentContext.parse(in); - MethodComponent methodComponent1 = MethodComponent.Builder.builder(methodName).build(); - assertNotNull(methodComponent1.validate(componentContext1)); + MethodComponent methodComponent1 = MethodComponent.Builder.builder(methodName) + .addSupportedDataTypes(Set.of(VectorDataType.FLOAT)) + .build(); + assertNotNull(methodComponent1.validate(componentContext1, knnMethodConfigContext)); // Invalid parameter type xContentBuilder = XContentFactory.jsonBuilder() @@ -76,9 +83,10 @@ public void testValidate() throws IOException { MethodComponentContext componentContext2 = MethodComponentContext.parse(in); MethodComponent methodComponent2 = MethodComponent.Builder.builder(methodName) - .addParameter("valid", new Parameter.IntegerParameter("valid", 1, v -> v > 0)) + .addSupportedDataTypes(Set.of(VectorDataType.FLOAT)) + .addParameter("valid", new Parameter.IntegerParameter("valid", 1, (v, context) -> v > 0)) .build(); - assertNotNull(methodComponent2.validate(componentContext2)); + assertNotNull(methodComponent2.validate(componentContext2, knnMethodConfigContext)); // valid configuration xContentBuilder = XContentFactory.jsonBuilder() @@ -93,10 +101,11 @@ public void testValidate() throws IOException { MethodComponentContext componentContext3 = MethodComponentContext.parse(in); MethodComponent methodComponent3 = MethodComponent.Builder.builder(methodName) - .addParameter("valid1", new Parameter.IntegerParameter("valid1", 1, v -> v > 0)) - .addParameter("valid2", new Parameter.IntegerParameter("valid2", 1, v -> v > 0)) + .addSupportedDataTypes(Set.of(VectorDataType.FLOAT)) + .addParameter("valid1", new Parameter.IntegerParameter("valid1", 1, (v, context) -> v > 0)) + .addParameter("valid2", new Parameter.IntegerParameter("valid2", 1, (v, context) -> v > 0)) .build(); - assertNull(methodComponent3.validate(componentContext3)); + assertNull(methodComponent3.validate(componentContext3, knnMethodConfigContext)); // valid configuration - empty parameters xContentBuilder = XContentFactory.jsonBuilder().startObject().field(NAME, methodName).endObject(); @@ -104,10 +113,11 @@ public void testValidate() throws IOException { MethodComponentContext componentContext4 = MethodComponentContext.parse(in); MethodComponent methodComponent4 = MethodComponent.Builder.builder(methodName) - .addParameter("valid1", new Parameter.IntegerParameter("valid1", 1, v -> v > 0)) - .addParameter("valid2", new Parameter.IntegerParameter("valid2", 1, v -> v > 0)) + .addSupportedDataTypes(Set.of(VectorDataType.FLOAT)) + .addParameter("valid1", new Parameter.IntegerParameter("valid1", 1, (v, context) -> v > 0)) + .addParameter("valid2", new Parameter.IntegerParameter("valid2", 1, (v, context) -> v > 0)) .build(); - assertNull(methodComponent4.validate(componentContext4)); + assertNull(methodComponent4.validate(componentContext4, knnMethodConfigContext)); } @SuppressWarnings("unchecked") @@ -119,8 +129,8 @@ public void testGetAsMap_withoutGenerator() throws IOException { int default2 = 5; MethodComponent methodComponent = MethodComponent.Builder.builder(methodName) - .addParameter(parameterName1, new Parameter.IntegerParameter(parameterName1, default1, v -> v > 0)) - .addParameter(parameterName2, new Parameter.IntegerParameter(parameterName2, default2, v -> v > 0)) + .addParameter(parameterName1, new Parameter.IntegerParameter(parameterName1, default1, (v, context) -> v > 0)) + .addParameter(parameterName2, new Parameter.IntegerParameter(parameterName2, default2, (v, context) -> v > 0)) .build(); XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() @@ -134,31 +144,50 @@ public void testGetAsMap_withoutGenerator() throws IOException { Map in = xContentBuilderToMap(xContentBuilder); MethodComponentContext methodComponentContext = MethodComponentContext.parse(in); - assertEquals(in, methodComponent.getAsMap(methodComponentContext)); + assertEquals( + in, + methodComponent.getKNNLibraryIndexingContext( + methodComponentContext, + KNNMethodConfigContext.builder().versionCreated(Version.CURRENT).build() + ).getLibraryParameters() + ); xContentBuilder = XContentFactory.jsonBuilder().startObject().field(NAME, methodName).endObject(); in = xContentBuilderToMap(xContentBuilder); methodComponentContext = MethodComponentContext.parse(in); - Map methodAsMap = methodComponent.getAsMap(methodComponentContext); - assertEquals(default1, ((Map) methodAsMap.get(PARAMETERS)).get(parameterName1)); - assertEquals(default2, ((Map) methodAsMap.get(PARAMETERS)).get(parameterName2)); + KNNLibraryIndexingContext methodAsMap = methodComponent.getKNNLibraryIndexingContext( + methodComponentContext, + KNNMethodConfigContext.builder().versionCreated(Version.CURRENT).build() + ); + assertEquals(default1, ((Map) methodAsMap.getLibraryParameters().get(PARAMETERS)).get(parameterName1)); + assertEquals(default2, ((Map) methodAsMap.getLibraryParameters().get(PARAMETERS)).get(parameterName2)); } public void testGetAsMap_withGenerator() throws IOException { String methodName = "test-method"; Map generatedMap = ImmutableMap.of("test-key", "test-value"); MethodComponent methodComponent = MethodComponent.Builder.builder(methodName) - .addParameter("valid1", new Parameter.IntegerParameter("valid1", 1, v -> v > 0)) - .addParameter("valid2", new Parameter.IntegerParameter("valid2", 1, v -> v > 0)) - .setMapGenerator((methodComponent1, methodComponentContext) -> generatedMap) + .addParameter("valid1", new Parameter.IntegerParameter("valid1", 1, (v, context) -> v > 0)) + .addParameter("valid2", new Parameter.IntegerParameter("valid2", 1, (v, context) -> v > 0)) + .setKnnLibraryIndexingContextGenerator( + (methodComponent1, methodComponentContext, knnMethodConfigContext) -> KNNLibraryIndexingContextImpl.builder() + .parameters(generatedMap) + .build() + ) .build(); XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().field(NAME, methodName).endObject(); Map in = xContentBuilderToMap(xContentBuilder); MethodComponentContext methodComponentContext = MethodComponentContext.parse(in); - assertEquals(generatedMap, methodComponent.getAsMap(methodComponentContext)); + assertEquals( + generatedMap, + methodComponent.getKNNLibraryIndexingContext( + methodComponentContext, + KNNMethodConfigContext.builder().versionCreated(Version.CURRENT).build() + ).getLibraryParameters() + ); } public void testBuilder() { @@ -169,15 +198,60 @@ public void testBuilder() { assertEquals(0, methodComponent.getParameters().size()); assertEquals(name, methodComponent.getName()); - builder.addParameter("test", new Parameter.IntegerParameter("test", 1, v -> v > 0)); + builder.addParameter("test", new Parameter.IntegerParameter("test", 1, (v, context) -> v > 0)); methodComponent = builder.build(); assertEquals(1, methodComponent.getParameters().size()); Map generatedMap = ImmutableMap.of("test-key", "test-value"); - builder.setMapGenerator((methodComponent1, methodComponentContext) -> generatedMap); + builder.setKnnLibraryIndexingContextGenerator( + (methodComponent1, methodComponentContext, knnMethodConfigContext) -> KNNLibraryIndexingContextImpl.builder() + .parameters(generatedMap) + .build() + ); methodComponent = builder.build(); - assertEquals(generatedMap, methodComponent.getAsMap(null)); + assertEquals( + generatedMap, + methodComponent.getKNNLibraryIndexingContext(null, KNNMethodConfigContext.builder().versionCreated(Version.CURRENT).build()) + .getLibraryParameters() + ); + } + + /** + * Test the new flow where EF_SEARCH and EF_CONSTRUCTION are set for ON_DISK mode + * with binary quantization compression levels. + */ + public void testGetParameterMapWithDefaultsAdded_forOnDiskWithBinaryQuantization() { + // Set up MethodComponent and context + String methodName = "test-method"; + String parameterEFSearch = "ef_search"; + String parameterEFConstruction = "ef_construction"; + + MethodComponent methodComponent = MethodComponent.Builder.builder(methodName) + .addParameter(parameterEFSearch, new Parameter.IntegerParameter(parameterEFSearch, 512, (v, context) -> v > 0)) + .addParameter(parameterEFConstruction, new Parameter.IntegerParameter(parameterEFConstruction, 512, (v, context) -> v > 0)) + .build(); + + // Simulate ON_DISK mode and binary quantization compression levels + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(Version.CURRENT) + .mode(Mode.ON_DISK) // ON_DISK mode + .compressionLevel(CompressionLevel.x32) // Binary quantization compression level + .build(); + + MethodComponentContext methodComponentContext = new MethodComponentContext(methodName, Map.of()); + + // Retrieve parameter map with defaults added + Map resultMap = MethodComponent.getParameterMapWithDefaultsAdded( + methodComponentContext, + methodComponent, + knnMethodConfigContext + ); + + // Check that binary quantization values are used + assertEquals(IndexHyperParametersUtil.getBinaryQuantizationEFSearchValue(), resultMap.get(parameterEFSearch)); + assertEquals(IndexHyperParametersUtil.getBinaryQuantizationEFConstructionValue(), resultMap.get(parameterEFConstruction)); } + } diff --git a/src/test/java/org/opensearch/knn/index/engine/NativeLibraryTests.java b/src/test/java/org/opensearch/knn/index/engine/NativeLibraryTests.java new file mode 100644 index 000000000..c1fbe4aa5 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/NativeLibraryTests.java @@ -0,0 +1,87 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import com.google.common.collect.ImmutableMap; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.SpaceType; + +import java.util.Collections; +import java.util.Map; +import java.util.function.Function; + +public class NativeLibraryTests extends KNNTestCase { + + /** + * Test native library extension getter + */ + public void testGetExtension() { + String extension = ".extension"; + TestNativeLibrary testNativeLibrary = new TestNativeLibrary(Collections.emptyMap(), Collections.emptyMap(), "", extension); + assertEquals(extension, testNativeLibrary.getExtension()); + } + + /** + * Test native library compound extension getter + */ + public void testGetCompoundExtension() { + String extension = ".extension"; + TestNativeLibrary testNativeLibrary = new TestNativeLibrary(Collections.emptyMap(), Collections.emptyMap(), "", extension); + assertEquals(extension + "c", testNativeLibrary.getCompoundExtension()); + } + + /** + * Test native library scoring override + */ + public void testScore() { + Map> translationMap = ImmutableMap.of(SpaceType.L2, s -> s * 2); + TestNativeLibrary testNativeLibrary = new TestNativeLibrary(Collections.emptyMap(), translationMap, "", ""); + // Test override + assertEquals(2f, testNativeLibrary.score(1f, SpaceType.L2), 0.0001); + + // Test non-override + assertEquals(SpaceType.L1.scoreTranslation(1f), testNativeLibrary.score(1f, SpaceType.L1), 0.0001); + } + + static class TestNativeLibrary extends NativeLibrary { + /** + * Constructor for TestNativeLibrary + * + * @param methods map of methods the native library supports + * @param scoreTranslation Map of translation of space type to scores returned by the library + * @param currentVersion String representation of current version of the library + * @param extension String representing the extension that library files should use + */ + public TestNativeLibrary( + Map methods, + Map> scoreTranslation, + String currentVersion, + String extension + ) { + super(methods, scoreTranslation, currentVersion, extension); + } + + @Override + public Float distanceToRadialThreshold(Float distance, SpaceType spaceType) { + return 0.0f; + } + + @Override + public Float scoreToRadialThreshold(Float score, SpaceType spaceType) { + return 0.0f; + } + + @Override + public ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + SpaceType spaceType + ) { + return null; + } + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/ParameterTests.java b/src/test/java/org/opensearch/knn/index/engine/ParameterTests.java new file mode 100644 index 000000000..9f3979314 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/ParameterTests.java @@ -0,0 +1,282 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import com.google.common.collect.ImmutableMap; +import org.opensearch.Version; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.common.ValidationException; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.Parameter.IntegerParameter; +import org.opensearch.knn.index.engine.Parameter.StringParameter; +import org.opensearch.knn.index.engine.Parameter.MethodComponentContextParameter; + +import java.util.Map; +import java.util.Set; + +public class ParameterTests extends KNNTestCase { + /** + * Test default default value getter + */ + public void testGetDefaultValue() { + String defaultValue = "test-default"; + Parameter parameter = new Parameter("test", defaultValue, (v, context) -> true) { + @Override + public ValidationException validate(Object value, KNNMethodConfigContext context) { + return null; + } + }; + + assertEquals(defaultValue, parameter.getDefaultValue()); + } + + /** + * Test integer parameter validate + */ + public void testIntegerParameter_validate() { + final IntegerParameter parameter = new IntegerParameter("test", 1, (v, context) -> v > 0); + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .dimension(1) + .versionCreated(Version.CURRENT) + .vectorDataType(VectorDataType.FLOAT) + .build(); + // Invalid type + assertNotNull(parameter.validate("String", knnMethodConfigContext)); + + // Invalid value + assertNotNull(parameter.validate(-1, knnMethodConfigContext)); + + // valid value + assertNull(parameter.validate(12, knnMethodConfigContext)); + } + + /** + * Test integer parameter validate + */ + public void testIntegerParameter_validateWithContext() { + final IntegerParameter parameter = new IntegerParameter("test", 1, (v, context) -> v > 0 && v > context.getDimension()); + + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder().dimension(0).build(); + + // Invalid type + assertNotNull(parameter.validate("String", knnMethodConfigContext)); + + // Invalid value + assertNotNull(parameter.validate(-1, knnMethodConfigContext)); + + // valid value + assertNull(parameter.validate(12, knnMethodConfigContext)); + } + + public void testStringParameter_validate() { + final StringParameter parameter = new StringParameter("test_parameter", "default_value", (v, context) -> "test".equals(v)); + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .dimension(1) + .versionCreated(Version.CURRENT) + .vectorDataType(VectorDataType.FLOAT) + .build(); + // Invalid type + assertNotNull(parameter.validate(5, knnMethodConfigContext)); + + // null + assertNotNull(parameter.validate(null, knnMethodConfigContext)); + + // valid value + assertNull(parameter.validate("test", knnMethodConfigContext)); + } + + public void testStringParameter_validateWithData() { + final StringParameter parameter = new StringParameter("test_parameter", "default_value", (v, context) -> { + if (context.getDimension() > 0) { + return "test".equals(v); + } + return false; + }); + + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder().dimension(1).build(); + + // Invalid type + assertNotNull(parameter.validate(5, knnMethodConfigContext)); + + // null + assertNotNull(parameter.validate(null, knnMethodConfigContext)); + + // valid value + assertNull(parameter.validate("test", knnMethodConfigContext)); + + knnMethodConfigContext.setDimension(0); + + // invalid value + assertNotNull(parameter.validate("test", knnMethodConfigContext)); + } + + public void testDoubleParameter_validate() { + final Parameter.DoubleParameter parameter = new Parameter.DoubleParameter("test_parameter", 1.0, (v, context) -> v >= 0); + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .dimension(1) + .versionCreated(Version.CURRENT) + .vectorDataType(VectorDataType.FLOAT) + .build(); + // valid value + assertNull(parameter.validate(0.9, knnMethodConfigContext)); + + // Invalid type + assertNotNull(parameter.validate(true, knnMethodConfigContext)); + + // Invalid type + assertNotNull(parameter.validate(-1, knnMethodConfigContext)); + + } + + public void testDoubleParameter_validateWithData() { + final Parameter.DoubleParameter parameter = new Parameter.DoubleParameter( + "test", + 1.0, + (v, context) -> v > 0 && v > context.getDimension() + ); + + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder().dimension(0).build(); + + // Invalid type + assertNotNull(parameter.validate("String", knnMethodConfigContext)); + + // Invalid value + assertNotNull(parameter.validate(-1, knnMethodConfigContext)); + + // valid value + assertNull(parameter.validate(1.2, knnMethodConfigContext)); + } + + public void testMethodComponentContextParameter_validate() { + String methodComponentName1 = "method-1"; + String parameterKey1 = "parameter_key_1"; + Integer parameterValue1 = 12; + + Map defaultParameterMap = ImmutableMap.of(parameterKey1, parameterValue1); + MethodComponentContext methodComponentContext = new MethodComponentContext(methodComponentName1, defaultParameterMap); + + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .dimension(1) + .versionCreated(Version.CURRENT) + .vectorDataType(VectorDataType.FLOAT) + .build(); + + Map methodComponentMap = ImmutableMap.of( + methodComponentName1, + MethodComponent.Builder.builder(parameterKey1) + .addSupportedDataTypes(Set.of(VectorDataType.FLOAT)) + .addParameter(parameterKey1, new IntegerParameter(parameterKey1, 1, (v, context) -> v > 0)) + .build() + ); + + final MethodComponentContextParameter parameter = new MethodComponentContextParameter( + "test", + methodComponentContext, + methodComponentMap + ); + + // Invalid type + assertNotNull(parameter.validate(17, knnMethodConfigContext)); + assertNotNull(parameter.validate("invalid-value", knnMethodConfigContext)); + + // Invalid value + String invalidMethodComponentName = "invalid-method"; + MethodComponentContext invalidMethodComponentContext1 = new MethodComponentContext(invalidMethodComponentName, defaultParameterMap); + assertNotNull(parameter.validate(invalidMethodComponentContext1, knnMethodConfigContext)); + + String invalidParameterKey = "invalid-parameter"; + Map invalidParameterMap1 = ImmutableMap.of(invalidParameterKey, parameterValue1); + MethodComponentContext invalidMethodComponentContext2 = new MethodComponentContext(methodComponentName1, invalidParameterMap1); + assertNotNull(parameter.validate(invalidMethodComponentContext2, knnMethodConfigContext)); + + String invalidParameterValue = "invalid-value"; + Map invalidParameterMap2 = ImmutableMap.of(parameterKey1, invalidParameterValue); + MethodComponentContext invalidMethodComponentContext3 = new MethodComponentContext(methodComponentName1, invalidParameterMap2); + assertNotNull(parameter.validate(invalidMethodComponentContext3, knnMethodConfigContext)); + + // valid value + assertNull(parameter.validate(methodComponentContext, knnMethodConfigContext)); + } + + public void testMethodComponentContextParameter_validateWithData() { + String methodComponentName1 = "method-1"; + String parameterKey1 = "parameter_key_1"; + Integer parameterValue1 = 12; + + Map defaultParameterMap = ImmutableMap.of(parameterKey1, parameterValue1); + MethodComponentContext methodComponentContext = new MethodComponentContext(methodComponentName1, defaultParameterMap); + + Map methodComponentMap = ImmutableMap.of( + methodComponentName1, + MethodComponent.Builder.builder(parameterKey1) + .addSupportedDataTypes(Set.of(VectorDataType.FLOAT)) + .addParameter(parameterKey1, new IntegerParameter(parameterKey1, 1, (v, context) -> v > 0 && v > context.getDimension())) + .build() + ); + + final MethodComponentContextParameter parameter = new MethodComponentContextParameter( + "test", + methodComponentContext, + methodComponentMap + ); + + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .dimension(0) + .vectorDataType(VectorDataType.FLOAT) + .versionCreated(Version.CURRENT) + .build(); + + // Invalid type + assertNotNull(parameter.validate("invalid-value", knnMethodConfigContext)); + + // Invalid value + String invalidMethodComponentName = "invalid-method"; + MethodComponentContext invalidMethodComponentContext1 = new MethodComponentContext(invalidMethodComponentName, defaultParameterMap); + assertNotNull(parameter.validate(invalidMethodComponentContext1, knnMethodConfigContext)); + + String invalidParameterKey = "invalid-parameter"; + Map invalidParameterMap1 = ImmutableMap.of(invalidParameterKey, parameterValue1); + MethodComponentContext invalidMethodComponentContext2 = new MethodComponentContext(methodComponentName1, invalidParameterMap1); + assertNotNull(parameter.validate(invalidMethodComponentContext2, knnMethodConfigContext)); + + String invalidParameterValue = "invalid-value"; + Map invalidParameterMap2 = ImmutableMap.of(parameterKey1, invalidParameterValue); + MethodComponentContext invalidMethodComponentContext3 = new MethodComponentContext(methodComponentName1, invalidParameterMap2); + assertNotNull(parameter.validate(invalidMethodComponentContext3, knnMethodConfigContext)); + + // valid value + assertNull(parameter.validate(methodComponentContext, knnMethodConfigContext)); + } + + public void testMethodComponentContextParameter_getMethodComponent() { + String methodComponentName1 = "method-1"; + String parameterKey1 = "parameter_key_1"; + Integer parameterValue1 = 12; + + Map defaultParameterMap = ImmutableMap.of(parameterKey1, parameterValue1); + MethodComponentContext methodComponentContext = new MethodComponentContext(methodComponentName1, defaultParameterMap); + + Map methodComponentMap = ImmutableMap.of( + methodComponentName1, + MethodComponent.Builder.builder(parameterKey1) + .addParameter(parameterKey1, new IntegerParameter(parameterKey1, 1, (v, context) -> v > 0)) + .build() + ); + + final MethodComponentContextParameter parameter = new MethodComponentContextParameter( + "test", + methodComponentContext, + methodComponentMap + ); + + // Test when method component is available + assertEquals(methodComponentMap.get(methodComponentName1), parameter.getMethodComponent(methodComponentName1)); + + // test when method component is not available + String invalidMethod = "invalid-method"; + assertNull(parameter.getMethodComponent(invalidMethod)); + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/SpaceTypeResolverTests.java b/src/test/java/org/opensearch/knn/index/engine/SpaceTypeResolverTests.java new file mode 100644 index 000000000..99fc98c9e --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/SpaceTypeResolverTests.java @@ -0,0 +1,99 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine; + +import lombok.SneakyThrows; +import org.opensearch.index.mapper.MapperParsingException; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; + +public class SpaceTypeResolverTests extends KNNTestCase { + + private static final SpaceTypeResolver SPACE_TYPE_RESOLVER = SpaceTypeResolver.INSTANCE; + + public void testResolveSpaceType_whenNoConfigProvided_thenFallbackToVectorDataType() { + assertEquals(SpaceType.DEFAULT, SPACE_TYPE_RESOLVER.resolveSpaceType(null, VectorDataType.FLOAT, "")); + assertEquals(SpaceType.DEFAULT, SPACE_TYPE_RESOLVER.resolveSpaceType(null, VectorDataType.BYTE, "")); + assertEquals( + SpaceType.DEFAULT, + SPACE_TYPE_RESOLVER.resolveSpaceType( + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.UNDEFINED, MethodComponentContext.EMPTY), + VectorDataType.FLOAT, + "" + ) + ); + assertEquals(SpaceType.DEFAULT_BINARY, SPACE_TYPE_RESOLVER.resolveSpaceType(null, VectorDataType.BINARY, "")); + assertEquals( + SpaceType.DEFAULT_BINARY, + SPACE_TYPE_RESOLVER.resolveSpaceType( + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.UNDEFINED, MethodComponentContext.EMPTY), + VectorDataType.BINARY, + "" + ) + ); + } + + @SneakyThrows + public void testResolveSpaceType_whenMethodSpaceTypeAndTopLevelSpecified_thenThrowIfConflict() { + expectThrows( + MapperParsingException.class, + () -> SPACE_TYPE_RESOLVER.resolveSpaceType( + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.L2, MethodComponentContext.EMPTY), + VectorDataType.FLOAT, + SpaceType.INNER_PRODUCT.getValue() + ) + ); + assertEquals( + SpaceType.DEFAULT, + SPACE_TYPE_RESOLVER.resolveSpaceType( + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.DEFAULT, MethodComponentContext.EMPTY), + VectorDataType.FLOAT, + SpaceType.DEFAULT.getValue() + ) + ); + assertEquals( + SpaceType.DEFAULT, + SPACE_TYPE_RESOLVER.resolveSpaceType( + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.DEFAULT, MethodComponentContext.EMPTY), + VectorDataType.FLOAT, + SpaceType.UNDEFINED.getValue() + ) + ); + assertEquals( + SpaceType.DEFAULT, + SPACE_TYPE_RESOLVER.resolveSpaceType( + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.UNDEFINED, MethodComponentContext.EMPTY), + VectorDataType.FLOAT, + SpaceType.DEFAULT.getValue() + ) + ); + assertEquals( + SpaceType.DEFAULT, + SPACE_TYPE_RESOLVER.resolveSpaceType( + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.UNDEFINED, MethodComponentContext.EMPTY), + VectorDataType.FLOAT, + SpaceType.UNDEFINED.getValue() + ) + ); + } + + @SneakyThrows + public void testResolveSpaceType_whenSpaceTypeSpecifiedOnce_thenReturnValue() { + assertEquals( + SpaceType.L1, + SPACE_TYPE_RESOLVER.resolveSpaceType( + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.L1, MethodComponentContext.EMPTY), + VectorDataType.FLOAT, + "" + ) + ); + assertEquals( + SpaceType.INNER_PRODUCT, + SPACE_TYPE_RESOLVER.resolveSpaceType(null, VectorDataType.FLOAT, SpaceType.INNER_PRODUCT.getValue()) + ); + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/faiss/AbstractFaissPQEncoderTests.java b/src/test/java/org/opensearch/knn/index/engine/faiss/AbstractFaissPQEncoderTests.java new file mode 100644 index 000000000..704657c11 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/faiss/AbstractFaissPQEncoderTests.java @@ -0,0 +1,79 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import lombok.SneakyThrows; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.engine.Encoder; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.mapper.CompressionLevel; + +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_SIZE; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_M; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PQ; + +public class AbstractFaissPQEncoderTests extends KNNTestCase { + + @SneakyThrows + public void testCalculateCompressionLevel() { + AbstractFaissPQEncoder encoder = new AbstractFaissPQEncoder() { + @Override + public MethodComponent getMethodComponent() { + return FaissIVFPQEncoder.METHOD_COMPONENT; + } + }; + + // Compression formula is: + // actual_compression = (d*32)/(m*code_size) and then round down to nearest: 1x, 2x, 4x, 8x, 16x, 32x + + // d=768 + // m=2 + // code_size=8 + // actual_compression = (768*32)/(2*8) = 1,536x + // expected_compression = Max compression level + assertCompressionLevel(2, 8, 768, CompressionLevel.MAX_COMPRESSION_LEVEL, encoder); + + // d=32 + // m=4 + // code_size=16 + // actual_compression = (32*32)/(4*16) = 16x + // expected_compression = Max compression level + assertCompressionLevel(4, 16, 32, CompressionLevel.x16, encoder); + + // d=1536 + // m=768 + // code_size=8 + // actual_compression = (1536*32)/(768*8) = 8x + // expected_compression = Max compression level + assertCompressionLevel(768, 8, 1536, CompressionLevel.x8, encoder); + + // d=128 + // m=128 + // code_size=8 + // actual_compression = (128*32)/(128*8) = 4x + // expected_compression = Max compression level + assertCompressionLevel(128, 8, 128, CompressionLevel.x4, encoder); + } + + private void assertCompressionLevel(int m, int codeSize, int d, CompressionLevel expectedCompression, Encoder encoder) { + assertEquals( + expectedCompression, + encoder.calculateCompressionLevel(generateMethodComponentContext(m, codeSize), generateKNNMethodConfigContext(d)) + ); + } + + private MethodComponentContext generateMethodComponentContext(int m, int codeSize) { + return new MethodComponentContext(ENCODER_PQ, Map.of(ENCODER_PARAMETER_PQ_M, m, ENCODER_PARAMETER_PQ_CODE_SIZE, codeSize)); + } + + private KNNMethodConfigContext generateKNNMethodConfigContext(int dimension) { + return KNNMethodConfigContext.builder().dimension(dimension).build(); + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/faiss/FaissFP16UtilTests.java b/src/test/java/org/opensearch/knn/index/engine/faiss/FaissFP16UtilTests.java new file mode 100644 index 000000000..81afef877 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/faiss/FaissFP16UtilTests.java @@ -0,0 +1,60 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import org.opensearch.knn.KNNTestCase; + +import java.util.Locale; + +import static org.opensearch.knn.common.KNNConstants.ENCODER_SQ; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_ENCODER_FP16; +import static org.opensearch.knn.common.KNNConstants.FP16_MAX_VALUE; +import static org.opensearch.knn.common.KNNConstants.FP16_MIN_VALUE; +import static org.opensearch.knn.index.engine.faiss.FaissFP16Util.clipVectorValueToFP16Range; +import static org.opensearch.knn.index.engine.faiss.FaissFP16Util.validateFP16VectorValue; + +public class FaissFP16UtilTests extends KNNTestCase { + + public void testValidateFp16VectorValue_outOfRange_throwsException() { + IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> validateFP16VectorValue(65505.25f)); + assertTrue( + ex.getMessage() + .contains( + String.format( + Locale.ROOT, + "encoder name is set as [%s] and type is set as [%s] in index mapping. But, KNN vector values are not within in the FP16 range [%f, %f]", + ENCODER_SQ, + FAISS_SQ_ENCODER_FP16, + FP16_MIN_VALUE, + FP16_MAX_VALUE + ) + ) + ); + + IllegalArgumentException ex1 = expectThrows(IllegalArgumentException.class, () -> validateFP16VectorValue(-65525.65f)); + assertTrue( + ex1.getMessage() + .contains( + String.format( + Locale.ROOT, + "encoder name is set as [%s] and type is set as [%s] in index mapping. But, KNN vector values are not within in the FP16 range [%f, %f]", + ENCODER_SQ, + FAISS_SQ_ENCODER_FP16, + FP16_MIN_VALUE, + FP16_MAX_VALUE + ) + ) + ); + } + + public void testClipVectorValuetoFP16Range_succeed() { + assertEquals(65504.0f, clipVectorValueToFP16Range(65504.10f), 0.0f); + assertEquals(65504.0f, clipVectorValueToFP16Range(1000000.89f), 0.0f); + assertEquals(-65504.0f, clipVectorValueToFP16Range(-65504.10f), 0.0f); + assertEquals(-65504.0f, clipVectorValueToFP16Range(-1000000.89f), 0.0f); + } + +} diff --git a/src/test/java/org/opensearch/knn/index/engine/faiss/FaissMethodResolverTests.java b/src/test/java/org/opensearch/knn/index/engine/faiss/FaissMethodResolverTests.java new file mode 100644 index 000000000..3a33736fa --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/faiss/FaissMethodResolverTests.java @@ -0,0 +1,273 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import org.opensearch.Version; +import org.opensearch.common.ValidationException; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.engine.MethodResolver; +import org.opensearch.knn.index.engine.ResolvedMethodContext; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; + +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.ENCODER_FLAT; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; + +public class FaissMethodResolverTests extends KNNTestCase { + + MethodResolver TEST_RESOLVER = new FaissMethodResolver(); + + public void testResolveMethod_whenValid_thenResolve() { + ResolvedMethodContext resolvedMethodContext = TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.FLOAT).versionCreated(Version.CURRENT).build(), + false, + SpaceType.INNER_PRODUCT + ); + validateResolveMethodContext(resolvedMethodContext, CompressionLevel.x1, SpaceType.INNER_PRODUCT, ENCODER_FLAT, false); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .mode(Mode.ON_DISK) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.INNER_PRODUCT + ); + validateResolveMethodContext(resolvedMethodContext, CompressionLevel.x32, SpaceType.INNER_PRODUCT, QFrameBitEncoder.NAME, true); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .mode(Mode.ON_DISK) + .compressionLevel(CompressionLevel.x16) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.INNER_PRODUCT + ); + validateResolveMethodContext(resolvedMethodContext, CompressionLevel.x16, SpaceType.INNER_PRODUCT, QFrameBitEncoder.NAME, true); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .compressionLevel(CompressionLevel.x16) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.INNER_PRODUCT + ); + validateResolveMethodContext(resolvedMethodContext, CompressionLevel.x16, SpaceType.INNER_PRODUCT, QFrameBitEncoder.NAME, true); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + new KNNMethodContext( + KNNEngine.FAISS, + SpaceType.L2, + new MethodComponentContext( + METHOD_HNSW, + Map.of( + METHOD_ENCODER_PARAMETER, + new MethodComponentContext( + QFrameBitEncoder.NAME, + Map.of(QFrameBitEncoder.BITCOUNT_PARAM, CompressionLevel.x8.numBitsForFloat32()) + ) + ) + ) + ), + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .mode(Mode.ON_DISK) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.L2 + ); + validateResolveMethodContext(resolvedMethodContext, CompressionLevel.x8, SpaceType.L2, QFrameBitEncoder.NAME, true); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + new KNNMethodContext( + KNNEngine.FAISS, + SpaceType.L2, + new MethodComponentContext( + METHOD_HNSW, + Map.of( + METHOD_ENCODER_PARAMETER, + new MethodComponentContext( + QFrameBitEncoder.NAME, + Map.of(QFrameBitEncoder.BITCOUNT_PARAM, CompressionLevel.x8.numBitsForFloat32()) + ) + ) + ) + ), + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.FLOAT).versionCreated(Version.CURRENT).build(), + false, + SpaceType.L2 + ); + validateResolveMethodContext(resolvedMethodContext, CompressionLevel.x8, SpaceType.L2, QFrameBitEncoder.NAME, true); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + new KNNMethodContext(KNNEngine.FAISS, SpaceType.L2, new MethodComponentContext(METHOD_HNSW, Map.of())), + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.FLOAT).versionCreated(Version.CURRENT).build(), + false, + SpaceType.L2 + ); + validateResolveMethodContext(resolvedMethodContext, CompressionLevel.x1, SpaceType.L2, ENCODER_FLAT, false); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + new KNNMethodContext(KNNEngine.FAISS, SpaceType.L2, new MethodComponentContext(METHOD_HNSW, Map.of())), + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.BINARY).versionCreated(Version.CURRENT).build(), + false, + SpaceType.L2 + ); + validateResolveMethodContext(resolvedMethodContext, CompressionLevel.x1, SpaceType.L2, ENCODER_FLAT, false); + + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .versionCreated(Version.CURRENT) + .build(); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + new KNNMethodContext( + KNNEngine.FAISS, + SpaceType.L2, + new MethodComponentContext( + METHOD_HNSW, + Map.of( + METHOD_ENCODER_PARAMETER, + new MethodComponentContext( + QFrameBitEncoder.NAME, + Map.of(QFrameBitEncoder.BITCOUNT_PARAM, CompressionLevel.x8.numBitsForFloat32()) + ) + ) + ) + ), + knnMethodConfigContext, + false, + SpaceType.L2 + ); + assertEquals(knnMethodConfigContext.getCompressionLevel(), CompressionLevel.x8); + validateResolveMethodContext(resolvedMethodContext, CompressionLevel.x8, SpaceType.L2, QFrameBitEncoder.NAME, true); + } + + private void validateResolveMethodContext( + ResolvedMethodContext resolvedMethodContext, + CompressionLevel expectedCompression, + SpaceType expectedSpaceType, + String expectedEncoderName, + boolean checkBitsEncoderParam + ) { + assertEquals(expectedCompression, resolvedMethodContext.getCompressionLevel()); + assertEquals(KNNEngine.FAISS, resolvedMethodContext.getKnnMethodContext().getKnnEngine()); + assertEquals(expectedSpaceType, resolvedMethodContext.getKnnMethodContext().getSpaceType()); + assertEquals( + expectedEncoderName, + ((MethodComponentContext) resolvedMethodContext.getKnnMethodContext() + .getMethodComponentContext() + .getParameters() + .get(METHOD_ENCODER_PARAMETER)).getName() + ); + if (checkBitsEncoderParam) { + assertEquals( + expectedCompression.numBitsForFloat32(), + ((MethodComponentContext) resolvedMethodContext.getKnnMethodContext() + .getMethodComponentContext() + .getParameters() + .get(METHOD_ENCODER_PARAMETER)).getParameters().get(QFrameBitEncoder.BITCOUNT_PARAM) + ); + } + + } + + public void testResolveMethod_whenInvalid_thenThrow() { + // Invalid compression + expectThrows( + ValidationException.class, + () -> TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .compressionLevel(CompressionLevel.x4) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.L2 + ) + ); + + expectThrows( + ValidationException.class, + () -> TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.BINARY) + .compressionLevel(CompressionLevel.x4) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.L2 + ) + ); + + // Invalid spec ondisk and compression is 1 + expectThrows( + ValidationException.class, + () -> TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .mode(Mode.ON_DISK) + .compressionLevel(CompressionLevel.x1) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.L2 + ) + ); + + // Invalid compression conflict + expectThrows( + ValidationException.class, + () -> TEST_RESOLVER.resolveMethod( + new KNNMethodContext( + KNNEngine.FAISS, + SpaceType.INNER_PRODUCT, + new MethodComponentContext( + METHOD_HNSW, + Map.of( + METHOD_ENCODER_PARAMETER, + new MethodComponentContext( + QFrameBitEncoder.NAME, + Map.of(QFrameBitEncoder.BITCOUNT_PARAM, CompressionLevel.x32.numBitsForFloat32()) + ) + ) + ) + ), + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .mode(Mode.ON_DISK) + .compressionLevel(CompressionLevel.x8) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.INNER_PRODUCT + ) + + ); + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/faiss/FaissSQEncoderTests.java b/src/test/java/org/opensearch/knn/index/engine/faiss/FaissSQEncoderTests.java new file mode 100644 index 000000000..3905158a2 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/faiss/FaissSQEncoderTests.java @@ -0,0 +1,16 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.mapper.CompressionLevel; + +public class FaissSQEncoderTests extends KNNTestCase { + public void testCalculateCompressionLevel() { + FaissSQEncoder encoder = new FaissSQEncoder(); + assertEquals(CompressionLevel.x2, encoder.calculateCompressionLevel(null, null)); + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/faiss/FaissTests.java b/src/test/java/org/opensearch/knn/index/engine/faiss/FaissTests.java new file mode 100644 index 000000000..75da6811e --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/faiss/FaissTests.java @@ -0,0 +1,370 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import lombok.SneakyThrows; +import org.opensearch.Version; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNLibraryIndexingContext; +import org.opensearch.knn.index.engine.KNNLibraryIndexingContextImpl; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.engine.Parameter; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_SIZE; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_M; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PQ; +import static org.opensearch.knn.common.KNNConstants.ENCODER_SQ; +import static org.opensearch.knn.common.KNNConstants.FAISS_NAME; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_ENCODER_FP16; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_TYPE; +import static org.opensearch.knn.common.KNNConstants.INDEX_DESCRIPTION_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_IVF; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_M; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NLIST; +import static org.opensearch.knn.common.KNNConstants.NAME; +import static org.opensearch.knn.common.KNNConstants.PARAMETERS; + +public class FaissTests extends KNNTestCase { + + public void testGetKNNLibraryIndexingContext_whenMethodIsHNSWFlat_thenCreateCorrectIndexDescription() throws IOException { + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(org.opensearch.Version.CURRENT) + .dimension(4) + .vectorDataType(VectorDataType.FLOAT) + .build(); + + int mParam = 65; + String expectedIndexDescription = String.format(Locale.ROOT, "HNSW%d,Flat", mParam); + + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, FAISS_NAME) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_M, mParam) + .endObject() + .endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext = KNNMethodContext.parse(in); + + Map map = Faiss.INSTANCE.getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext) + .getLibraryParameters(); + + assertTrue(map.containsKey(INDEX_DESCRIPTION_PARAMETER)); + assertEquals(expectedIndexDescription, map.get(INDEX_DESCRIPTION_PARAMETER)); + } + + public void testGetKNNLibraryIndexingContext_whenMethodIsHNSWPQ_thenCreateCorrectIndexDescription() throws IOException { + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(org.opensearch.Version.CURRENT) + .dimension(4) + .vectorDataType(VectorDataType.FLOAT) + .build(); + int hnswMParam = 65; + int pqMParam = 17; + String expectedIndexDescription = String.format(Locale.ROOT, "HNSW%d,PQ%d", hnswMParam, pqMParam); + + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, FAISS_NAME) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_M, hnswMParam) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_PQ) + .startObject(PARAMETERS) + .field(ENCODER_PARAMETER_PQ_M, pqMParam) + .endObject() + .endObject() + .endObject() + .endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext = KNNMethodContext.parse(in); + + Map map = Faiss.INSTANCE.getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext) + .getLibraryParameters(); + + assertTrue(map.containsKey(INDEX_DESCRIPTION_PARAMETER)); + assertEquals(expectedIndexDescription, map.get(INDEX_DESCRIPTION_PARAMETER)); + } + + @SneakyThrows + public void testGetKNNLibraryIndexingContext_whenMethodIsHNSWSQFP16_thenCreateCorrectIndexDescription() { + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(org.opensearch.Version.CURRENT) + .dimension(4) + .vectorDataType(VectorDataType.FLOAT) + .build(); + int hnswMParam = 65; + String expectedIndexDescription = String.format(Locale.ROOT, "HNSW%d,SQfp16", hnswMParam); + + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, FAISS_NAME) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_M, hnswMParam) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_SQ) + .startObject(PARAMETERS) + .field(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16) + .endObject() + .endObject() + .endObject() + .endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext = KNNMethodContext.parse(in); + + Map map = Faiss.INSTANCE.getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext) + .getLibraryParameters(); + + assertTrue(map.containsKey(INDEX_DESCRIPTION_PARAMETER)); + assertEquals(expectedIndexDescription, map.get(INDEX_DESCRIPTION_PARAMETER)); + } + + public void testGetKNNLibraryIndexingContext_whenMethodIsIVFFlat_thenCreateCorrectIndexDescription() throws IOException { + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(org.opensearch.Version.CURRENT) + .dimension(4) + .vectorDataType(VectorDataType.FLOAT) + .build(); + int nlists = 88; + String expectedIndexDescription = String.format(Locale.ROOT, "IVF%d,Flat", nlists); + + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_IVF) + .field(KNN_ENGINE, FAISS_NAME) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_NLIST, nlists) + .endObject() + .endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext = KNNMethodContext.parse(in); + + Map map = Faiss.INSTANCE.getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext) + .getLibraryParameters(); + + assertTrue(map.containsKey(INDEX_DESCRIPTION_PARAMETER)); + assertEquals(expectedIndexDescription, map.get(INDEX_DESCRIPTION_PARAMETER)); + } + + public void testGetKNNLibraryIndexingContext_whenMethodIsIVFPQ_thenCreateCorrectIndexDescription() throws IOException { + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(org.opensearch.Version.CURRENT) + .dimension(4) + .vectorDataType(VectorDataType.FLOAT) + .build(); + int ivfNlistsParam = 88; + int pqMParam = 17; + int pqCodeSizeParam = 53; + String expectedIndexDescription = String.format(Locale.ROOT, "IVF%d,PQ%dx%d", ivfNlistsParam, pqMParam, pqCodeSizeParam); + + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_IVF) + .field(KNN_ENGINE, FAISS_NAME) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_NLIST, ivfNlistsParam) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_PQ) + .startObject(PARAMETERS) + .field(ENCODER_PARAMETER_PQ_M, pqMParam) + .field(ENCODER_PARAMETER_PQ_CODE_SIZE, pqCodeSizeParam) + .endObject() + .endObject() + .endObject() + .endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext = KNNMethodContext.parse(in); + + Map map = Faiss.INSTANCE.getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext) + .getLibraryParameters(); + + assertTrue(map.containsKey(INDEX_DESCRIPTION_PARAMETER)); + assertEquals(expectedIndexDescription, map.get(INDEX_DESCRIPTION_PARAMETER)); + } + + @SneakyThrows + public void testGetKNNLibraryIndexingContext_whenMethodIsIVFSQFP16_thenCreateCorrectIndexDescription() { + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(org.opensearch.Version.CURRENT) + .dimension(4) + .vectorDataType(VectorDataType.FLOAT) + .build(); + int nlists = 88; + String expectedIndexDescription = String.format(Locale.ROOT, "IVF%d,SQfp16", nlists); + + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_IVF) + .field(KNN_ENGINE, FAISS_NAME) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_NLIST, nlists) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_SQ) + .startObject(PARAMETERS) + .field(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16) + .endObject() + .endObject() + .endObject() + .endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext = KNNMethodContext.parse(in); + + Map map = Faiss.INSTANCE.getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext) + .getLibraryParameters(); + + assertTrue(map.containsKey(INDEX_DESCRIPTION_PARAMETER)); + assertEquals(expectedIndexDescription, map.get(INDEX_DESCRIPTION_PARAMETER)); + } + + @SneakyThrows + public void testGetKNNLibraryIndexingContext_whenMethodIsHNSWWithQFrame_thenCreateCorrectConfig() { + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(org.opensearch.Version.CURRENT) + .dimension(4) + .vectorDataType(VectorDataType.FLOAT) + .build(); + int m = 88; + String expectedIndexDescription = "BHNSW" + m + ",Flat"; + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, FAISS_NAME) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_M, m) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, QFrameBitEncoder.NAME) + .startObject(PARAMETERS) + .field(QFrameBitEncoder.BITCOUNT_PARAM, 4) + .endObject() + .endObject() + .endObject() + .endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext = KNNMethodContext.parse(in); + KNNLibraryIndexingContext knnLibraryIndexingContext = Faiss.INSTANCE.getKNNLibraryIndexingContext( + knnMethodContext, + knnMethodConfigContext + ); + Map map = knnLibraryIndexingContext.getLibraryParameters(); + + assertTrue(map.containsKey(INDEX_DESCRIPTION_PARAMETER)); + assertEquals(expectedIndexDescription, map.get(INDEX_DESCRIPTION_PARAMETER)); + assertEquals( + QuantizationConfig.builder().quantizationType(ScalarQuantizationType.FOUR_BIT).build(), + knnLibraryIndexingContext.getQuantizationConfig() + ); + } + + @SneakyThrows + public void testGetKNNLibraryIndexingContext_whenMethodIsIVFWithQFrame_thenCreateCorrectConfig() { + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(org.opensearch.Version.CURRENT) + .dimension(4) + .vectorDataType(VectorDataType.FLOAT) + .build(); + int nlist = 88; + String expectedIndexDescription = "BIVF" + nlist + ",Flat"; + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_IVF) + .field(KNN_ENGINE, FAISS_NAME) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_NLIST, nlist) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, QFrameBitEncoder.NAME) + .startObject(PARAMETERS) + .field(QFrameBitEncoder.BITCOUNT_PARAM, 2) + .endObject() + .endObject() + .endObject() + .endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext = KNNMethodContext.parse(in); + KNNLibraryIndexingContext knnLibraryIndexingContext = Faiss.INSTANCE.getKNNLibraryIndexingContext( + knnMethodContext, + knnMethodConfigContext + ); + Map map = knnLibraryIndexingContext.getLibraryParameters(); + + assertTrue(map.containsKey(INDEX_DESCRIPTION_PARAMETER)); + assertEquals(expectedIndexDescription, map.get(INDEX_DESCRIPTION_PARAMETER)); + assertEquals( + QuantizationConfig.builder().quantizationType(ScalarQuantizationType.TWO_BIT).build(), + knnLibraryIndexingContext.getQuantizationConfig() + ); + } + + public void testMethodAsMapBuilder() throws IOException { + String methodName = "test-method"; + String methodDescription = "test-description"; + String parameter1 = "test-parameter-1"; + Integer value1 = 10; + Integer defaultValue1 = 1; + String parameter2 = "test-parameter-2"; + Integer value2 = 15; + Integer defaultValue2 = 2; + String parameter3 = "test-parameter-3"; + Integer defaultValue3 = 3; + MethodComponent methodComponent = MethodComponent.Builder.builder(methodName) + .addParameter(parameter1, new Parameter.IntegerParameter(parameter1, defaultValue1, (value, context) -> value > 0)) + .addParameter(parameter2, new Parameter.IntegerParameter(parameter2, defaultValue2, (value, context) -> value > 0)) + .addParameter(parameter3, new Parameter.IntegerParameter(parameter3, defaultValue3, (value, context) -> value > 0)) + .build(); + + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, methodName) + .startObject(PARAMETERS) + .field(parameter1, value1) + .field(parameter2, value2) + .endObject() + .endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + MethodComponentContext methodComponentContext = MethodComponentContext.parse(in); + + Map expectedParametersMap = new HashMap<>(methodComponentContext.getParameters()); + expectedParametersMap.put(parameter3, defaultValue3); + expectedParametersMap.remove(parameter1); + Map expectedMap = new HashMap<>(); + expectedMap.put(PARAMETERS, expectedParametersMap); + expectedMap.put(NAME, methodName); + expectedMap.put(INDEX_DESCRIPTION_PARAMETER, methodDescription + value1); + KNNLibraryIndexingContext expectedKNNMethodContext = KNNLibraryIndexingContextImpl.builder().parameters(expectedMap).build(); + + KNNLibraryIndexingContext actualKNNLibraryIndexingContext = MethodAsMapBuilder.builder( + methodDescription, + methodComponent, + methodComponentContext, + KNNMethodConfigContext.builder().versionCreated(Version.CURRENT).build() + ).addParameter(parameter1, "", "").build(); + + assertEquals(expectedKNNMethodContext.getQuantizationConfig(), actualKNNLibraryIndexingContext.getQuantizationConfig()); + assertEquals(expectedKNNMethodContext.getLibraryParameters(), actualKNNLibraryIndexingContext.getLibraryParameters()); + assertEquals(expectedKNNMethodContext.getPerDimensionProcessor(), actualKNNLibraryIndexingContext.getPerDimensionProcessor()); + assertEquals(expectedKNNMethodContext.getPerDimensionValidator(), actualKNNLibraryIndexingContext.getPerDimensionValidator()); + assertEquals(expectedKNNMethodContext.getVectorValidator(), actualKNNLibraryIndexingContext.getVectorValidator()); + } + +} diff --git a/src/test/java/org/opensearch/knn/index/engine/faiss/QFrameBitEncoderTests.java b/src/test/java/org/opensearch/knn/index/engine/faiss/QFrameBitEncoderTests.java new file mode 100644 index 000000000..e926916af --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/faiss/QFrameBitEncoderTests.java @@ -0,0 +1,167 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.faiss; + +import com.google.common.collect.ImmutableMap; +import org.opensearch.Version; +import org.opensearch.common.ValidationException; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNLibraryIndexingContext; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.MethodComponent; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; + +import java.util.HashMap; +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.FAISS_FLAT_DESCRIPTION; +import static org.opensearch.knn.common.KNNConstants.INDEX_DESCRIPTION_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.index.engine.faiss.QFrameBitEncoder.BITCOUNT_PARAM; + +public class QFrameBitEncoderTests extends KNNTestCase { + public void testGetLibraryIndexingContext() { + QFrameBitEncoder qFrameBitEncoder = new QFrameBitEncoder(); + MethodComponent methodComponent = qFrameBitEncoder.getMethodComponent(); + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(Version.CURRENT) + .vectorDataType(VectorDataType.FLOAT) + .dimension(10) + .build(); + + MethodComponentContext methodComponentContext = new MethodComponentContext( + QFrameBitEncoder.NAME, + ImmutableMap.of(BITCOUNT_PARAM, 4) + ); + + KNNLibraryIndexingContext knnLibraryIndexingContext = methodComponent.getKNNLibraryIndexingContext( + methodComponentContext, + knnMethodConfigContext + ); + assertEquals( + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, FAISS_FLAT_DESCRIPTION), + knnLibraryIndexingContext.getLibraryParameters() + ); + assertEquals( + QuantizationConfig.builder().quantizationType(ScalarQuantizationType.FOUR_BIT).build(), + knnLibraryIndexingContext.getQuantizationConfig() + ); + + methodComponentContext = new MethodComponentContext(QFrameBitEncoder.NAME, ImmutableMap.of(BITCOUNT_PARAM, 2)); + knnLibraryIndexingContext = methodComponent.getKNNLibraryIndexingContext(methodComponentContext, knnMethodConfigContext); + assertEquals( + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, FAISS_FLAT_DESCRIPTION), + knnLibraryIndexingContext.getLibraryParameters() + ); + assertEquals( + QuantizationConfig.builder().quantizationType(ScalarQuantizationType.TWO_BIT).build(), + knnLibraryIndexingContext.getQuantizationConfig() + ); + } + + public void testValidate() { + QFrameBitEncoder qFrameBitEncoder = new QFrameBitEncoder(); + MethodComponent methodComponent = qFrameBitEncoder.getMethodComponent(); + + // Invalid data type + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(Version.CURRENT) + .vectorDataType(VectorDataType.BYTE) + .dimension(10) + .build(); + MethodComponentContext methodComponentContext = new MethodComponentContext( + QFrameBitEncoder.NAME, + ImmutableMap.of(BITCOUNT_PARAM, 4) + ); + + assertNotNull(methodComponent.validate(methodComponentContext, knnMethodConfigContext)); + + // Invalid param + knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(Version.CURRENT) + .vectorDataType(VectorDataType.FLOAT) + .dimension(10) + .build(); + methodComponentContext = new MethodComponentContext(QFrameBitEncoder.NAME, ImmutableMap.of(BITCOUNT_PARAM, 4, "invalid", 4)); + assertNotNull(methodComponent.validate(methodComponentContext, knnMethodConfigContext)); + + // Invalid param type + knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(Version.CURRENT) + .vectorDataType(VectorDataType.FLOAT) + .dimension(10) + .build(); + methodComponentContext = new MethodComponentContext(QFrameBitEncoder.NAME, ImmutableMap.of(BITCOUNT_PARAM, "invalid")); + assertNotNull(methodComponent.validate(methodComponentContext, knnMethodConfigContext)); + + // Invalid param value + knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(Version.CURRENT) + .vectorDataType(VectorDataType.FLOAT) + .dimension(10) + .build(); + methodComponentContext = new MethodComponentContext(QFrameBitEncoder.NAME, ImmutableMap.of(BITCOUNT_PARAM, 5)); + assertNotNull(methodComponent.validate(methodComponentContext, knnMethodConfigContext)); + } + + public void testIsTrainingRequired() { + QFrameBitEncoder qFrameBitEncoder = new QFrameBitEncoder(); + assertFalse( + qFrameBitEncoder.getMethodComponent() + .isTrainingRequired(new MethodComponentContext(QFrameBitEncoder.NAME, ImmutableMap.of(BITCOUNT_PARAM, 4))) + ); + } + + public void testEstimateOverheadInKB() { + QFrameBitEncoder qFrameBitEncoder = new QFrameBitEncoder(); + assertEquals( + 0, + qFrameBitEncoder.getMethodComponent() + .estimateOverheadInKB(new MethodComponentContext(QFrameBitEncoder.NAME, ImmutableMap.of(BITCOUNT_PARAM, 4)), 8) + ); + } + + public void testCalculateCompressionLevel() { + QFrameBitEncoder encoder = new QFrameBitEncoder(); + assertEquals( + CompressionLevel.x32, + encoder.calculateCompressionLevel(generateMethodComponentContext(CompressionLevel.x32.numBitsForFloat32()), null) + ); + assertEquals( + CompressionLevel.x16, + encoder.calculateCompressionLevel(generateMethodComponentContext(CompressionLevel.x16.numBitsForFloat32()), null) + ); + assertEquals( + CompressionLevel.x8, + encoder.calculateCompressionLevel(generateMethodComponentContext(CompressionLevel.x8.numBitsForFloat32()), null) + ); + assertEquals( + CompressionLevel.NOT_CONFIGURED, + encoder.calculateCompressionLevel( + new MethodComponentContext( + METHOD_HNSW, + new HashMap<>(Map.of(METHOD_ENCODER_PARAMETER, new MethodComponentContext(QFrameBitEncoder.NAME, Map.of()))) + ), + null + ) + ); + + expectThrows( + ValidationException.class, + () -> encoder.calculateCompressionLevel(generateMethodComponentContext(CompressionLevel.x4.numBitsForFloat32()), null) + ); + expectThrows(ValidationException.class, () -> encoder.calculateCompressionLevel(generateMethodComponentContext(-1), null)); + } + + private MethodComponentContext generateMethodComponentContext(int bitCount) { + return new MethodComponentContext(QFrameBitEncoder.NAME, Map.of(BITCOUNT_PARAM, bitCount)); + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/lucene/LuceneMethodResolverTests.java b/src/test/java/org/opensearch/knn/index/engine/lucene/LuceneMethodResolverTests.java new file mode 100644 index 000000000..833d83135 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/lucene/LuceneMethodResolverTests.java @@ -0,0 +1,212 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.lucene; + +import org.opensearch.Version; +import org.opensearch.common.ValidationException; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.engine.MethodResolver; +import org.opensearch.knn.index.engine.ResolvedMethodContext; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; + +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.ENCODER_SQ; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; + +public class LuceneMethodResolverTests extends KNNTestCase { + MethodResolver TEST_RESOLVER = new LuceneMethodResolver(); + + public void testResolveMethod_whenValid_thenResolve() { + ResolvedMethodContext resolvedMethodContext = TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.FLOAT).versionCreated(Version.CURRENT).build(), + false, + SpaceType.INNER_PRODUCT + ); + assertEquals(METHOD_HNSW, resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getName()); + assertFalse(resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().isEmpty()); + assertEquals(KNNEngine.LUCENE, resolvedMethodContext.getKnnMethodContext().getKnnEngine()); + assertEquals(SpaceType.INNER_PRODUCT, resolvedMethodContext.getKnnMethodContext().getSpaceType()); + assertEquals(CompressionLevel.x1, resolvedMethodContext.getCompressionLevel()); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .versionCreated(Version.CURRENT) + .mode(Mode.ON_DISK) + .build(), + false, + SpaceType.INNER_PRODUCT + ); + assertEquals(METHOD_HNSW, resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getName()); + assertFalse(resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().isEmpty()); + assertTrue( + resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().containsKey(METHOD_ENCODER_PARAMETER) + ); + assertEquals(KNNEngine.LUCENE, resolvedMethodContext.getKnnMethodContext().getKnnEngine()); + assertEquals(SpaceType.INNER_PRODUCT, resolvedMethodContext.getKnnMethodContext().getSpaceType()); + assertEquals(CompressionLevel.x4, resolvedMethodContext.getCompressionLevel()); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .versionCreated(Version.CURRENT) + .compressionLevel(CompressionLevel.x4) + .build(), + false, + SpaceType.INNER_PRODUCT + ); + assertEquals(METHOD_HNSW, resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getName()); + assertFalse(resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().isEmpty()); + assertTrue( + resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().containsKey(METHOD_ENCODER_PARAMETER) + ); + assertEquals(KNNEngine.LUCENE, resolvedMethodContext.getKnnMethodContext().getKnnEngine()); + assertEquals(SpaceType.INNER_PRODUCT, resolvedMethodContext.getKnnMethodContext().getSpaceType()); + assertEquals(CompressionLevel.x4, resolvedMethodContext.getCompressionLevel()); + + KNNMethodContext knnMethodContext = new KNNMethodContext( + KNNEngine.LUCENE, + SpaceType.INNER_PRODUCT, + new MethodComponentContext(METHOD_HNSW, Map.of()) + ); + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + knnMethodContext, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .versionCreated(Version.CURRENT) + .mode(Mode.ON_DISK) + .build(), + false, + SpaceType.INNER_PRODUCT + ); + assertEquals(METHOD_HNSW, resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getName()); + assertFalse(resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().isEmpty()); + assertTrue( + resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().containsKey(METHOD_ENCODER_PARAMETER) + ); + assertEquals(KNNEngine.LUCENE, resolvedMethodContext.getKnnMethodContext().getKnnEngine()); + assertEquals(SpaceType.INNER_PRODUCT, resolvedMethodContext.getKnnMethodContext().getSpaceType()); + assertEquals(CompressionLevel.x4, resolvedMethodContext.getCompressionLevel()); + assertNotEquals(knnMethodContext, resolvedMethodContext.getKnnMethodContext()); + + knnMethodContext = new KNNMethodContext( + KNNEngine.LUCENE, + SpaceType.INNER_PRODUCT, + new MethodComponentContext(METHOD_HNSW, Map.of()) + ); + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + knnMethodContext, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .versionCreated(Version.CURRENT) + .compressionLevel(CompressionLevel.x4) + .build(), + false, + SpaceType.INNER_PRODUCT + ); + assertEquals(METHOD_HNSW, resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getName()); + assertFalse(resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().isEmpty()); + assertTrue( + resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().containsKey(METHOD_ENCODER_PARAMETER) + ); + assertEquals(KNNEngine.LUCENE, resolvedMethodContext.getKnnMethodContext().getKnnEngine()); + assertEquals(SpaceType.INNER_PRODUCT, resolvedMethodContext.getKnnMethodContext().getSpaceType()); + assertEquals(CompressionLevel.x4, resolvedMethodContext.getCompressionLevel()); + assertNotEquals(knnMethodContext, resolvedMethodContext.getKnnMethodContext()); + + knnMethodContext = new KNNMethodContext( + KNNEngine.LUCENE, + SpaceType.INNER_PRODUCT, + new MethodComponentContext(METHOD_HNSW, Map.of(METHOD_ENCODER_PARAMETER, new MethodComponentContext(ENCODER_SQ, Map.of()))) + ); + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + knnMethodContext, + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.FLOAT).versionCreated(Version.CURRENT).build(), + false, + SpaceType.INNER_PRODUCT + ); + assertEquals(METHOD_HNSW, resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getName()); + assertFalse(resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().isEmpty()); + assertTrue( + resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().containsKey(METHOD_ENCODER_PARAMETER) + ); + assertEquals(KNNEngine.LUCENE, resolvedMethodContext.getKnnMethodContext().getKnnEngine()); + assertEquals(SpaceType.INNER_PRODUCT, resolvedMethodContext.getKnnMethodContext().getSpaceType()); + assertEquals(CompressionLevel.x4, resolvedMethodContext.getCompressionLevel()); + assertNotEquals(knnMethodContext, resolvedMethodContext.getKnnMethodContext()); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.BYTE).versionCreated(Version.CURRENT).build(), + false, + SpaceType.INNER_PRODUCT + ); + assertEquals(METHOD_HNSW, resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getName()); + assertFalse(resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().isEmpty()); + assertFalse( + resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().containsKey(METHOD_ENCODER_PARAMETER) + ); + assertEquals(KNNEngine.LUCENE, resolvedMethodContext.getKnnMethodContext().getKnnEngine()); + assertEquals(SpaceType.INNER_PRODUCT, resolvedMethodContext.getKnnMethodContext().getSpaceType()); + assertEquals(CompressionLevel.x1, resolvedMethodContext.getCompressionLevel()); + } + + public void testResolveMethod_whenInvalid_thenThrow() { + // Invalid training context + expectThrows( + ValidationException.class, + () -> TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.FLOAT).versionCreated(Version.CURRENT).build(), + true, + SpaceType.L2 + ) + ); + + // Invalid compression + expectThrows( + ValidationException.class, + () -> TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .compressionLevel(CompressionLevel.x32) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.L2 + ) + ); + + // Invalid spec ondisk and compression is 1 + expectThrows( + ValidationException.class, + () -> TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .mode(Mode.ON_DISK) + .compressionLevel(CompressionLevel.x1) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.L2 + ) + ); + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/lucene/LuceneSQEncoderTests.java b/src/test/java/org/opensearch/knn/index/engine/lucene/LuceneSQEncoderTests.java new file mode 100644 index 000000000..139f96e8b --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/lucene/LuceneSQEncoderTests.java @@ -0,0 +1,16 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.lucene; + +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.mapper.CompressionLevel; + +public class LuceneSQEncoderTests extends KNNTestCase { + public void testCalculateCompressionLevel() { + LuceneSQEncoder encoder = new LuceneSQEncoder(); + assertEquals(CompressionLevel.x4, encoder.calculateCompressionLevel(null, null)); + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/lucene/LuceneTests.java b/src/test/java/org/opensearch/knn/index/engine/lucene/LuceneTests.java new file mode 100644 index 000000000..2d2025d49 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/lucene/LuceneTests.java @@ -0,0 +1,146 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.lucene; + +import org.apache.lucene.util.Version; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.SpaceType; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_M; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; +import static org.opensearch.knn.common.KNNConstants.NAME; +import static org.opensearch.knn.common.KNNConstants.PARAMETERS; + +public class LuceneTests extends KNNTestCase { + + public void testLucenHNSWMethod() throws IOException { + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(org.opensearch.Version.CURRENT) + .dimension(10) + .vectorDataType(VectorDataType.FLOAT) + .build(); + int efConstruction = 100; + int m = 17; + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2.getValue()) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction) + .field(METHOD_PARAMETER_M, m) + .endObject() + .endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext1 = KNNMethodContext.parse(in); + assertNull(KNNEngine.LUCENE.validateMethod(knnMethodContext1, knnMethodConfigContext)); + + // Invalid parameter + String invalidParameter = "invalid"; + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_HNSW) + .startObject(PARAMETERS) + .field(invalidParameter, 10) + .endObject() + .endObject(); + in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext2 = KNNMethodContext.parse(in); + knnMethodContext2.setSpaceType(SpaceType.L2); + assertNotNull(KNNEngine.LUCENE.validateMethod(knnMethodContext2, knnMethodConfigContext)); + + // Valid parameter, invalid value + int invalidEfConstruction = -1; + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_HNSW) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, invalidEfConstruction) + .endObject() + .endObject(); + in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext3 = KNNMethodContext.parse(in); + knnMethodContext3.setSpaceType(SpaceType.L2); + assertNotNull(KNNEngine.LUCENE.validateMethod(knnMethodContext3, knnMethodConfigContext)); + + // Unsupported space type + SpaceType invalidSpaceType = SpaceType.LINF; // Not currently supported + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, invalidSpaceType.getValue()) + .endObject(); + in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext4 = KNNMethodContext.parse(in); + assertNotNull(KNNEngine.LUCENE.validateMethod(knnMethodContext4, knnMethodConfigContext)); + + // Check INNER_PRODUCT is supported with Lucene Engine + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.INNER_PRODUCT.getValue()) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction) + .field(METHOD_PARAMETER_M, m) + .endObject() + .endObject(); + in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext5 = KNNMethodContext.parse(in); + assertNull(KNNEngine.LUCENE.validateMethod(knnMethodContext5, knnMethodConfigContext)); + } + + public void testGetExtension() { + Lucene luceneLibrary = new Lucene(Collections.emptyMap(), "", Collections.emptyMap()); + expectThrows(UnsupportedOperationException.class, luceneLibrary::getExtension); + } + + public void testGetCompundExtension() { + Lucene luceneLibrary = new Lucene(Collections.emptyMap(), "", Collections.emptyMap()); + expectThrows(UnsupportedOperationException.class, luceneLibrary::getCompoundExtension); + } + + public void testScore() { + Lucene luceneLibrary = new Lucene(Collections.emptyMap(), "", Collections.emptyMap()); + float rawScore = 10.0f; + assertEquals(rawScore, luceneLibrary.score(rawScore, SpaceType.DEFAULT), 0.001); + } + + public void testIsInitialized() { + Lucene luceneLibrary = new Lucene(Collections.emptyMap(), "", Collections.emptyMap()); + assertFalse(luceneLibrary.isInitialized()); + } + + public void testSetInitialized() { + Lucene luceneLibrary = new Lucene(Collections.emptyMap(), "", Collections.emptyMap()); + luceneLibrary.setInitialized(true); + assertTrue(luceneLibrary.isInitialized()); + } + + public void testVersion() { + Lucene luceneInstance = Lucene.INSTANCE; + assertEquals(Version.LATEST.toString(), luceneInstance.getVersion()); + } + + public void testMmapFileExtensions() { + final List luceneMmapExtensions = Lucene.INSTANCE.mmapFileExtensions(); + assertNotNull(luceneMmapExtensions); + final List expectedSettings = List.of("vex", "vec"); + assertTrue(expectedSettings.containsAll(luceneMmapExtensions)); + assertTrue(luceneMmapExtensions.containsAll(expectedSettings)); + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/nmslib/NmslibMethodResolverTests.java b/src/test/java/org/opensearch/knn/index/engine/nmslib/NmslibMethodResolverTests.java new file mode 100644 index 000000000..065e0e378 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/nmslib/NmslibMethodResolverTests.java @@ -0,0 +1,106 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.nmslib; + +import org.opensearch.Version; +import org.opensearch.common.ValidationException; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.engine.MethodResolver; +import org.opensearch.knn.index.engine.ResolvedMethodContext; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; + +import java.util.Map; + +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; + +public class NmslibMethodResolverTests extends KNNTestCase { + + MethodResolver TEST_RESOLVER = new NmslibMethodResolver(); + + public void testResolveMethod_whenValid_thenResolve() { + // No configuration passed in + ResolvedMethodContext resolvedMethodContext = TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.FLOAT).versionCreated(Version.CURRENT).build(), + false, + SpaceType.INNER_PRODUCT + ); + assertEquals(METHOD_HNSW, resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getName()); + assertFalse(resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().isEmpty()); + assertEquals(KNNEngine.NMSLIB, resolvedMethodContext.getKnnMethodContext().getKnnEngine()); + assertEquals(SpaceType.INNER_PRODUCT, resolvedMethodContext.getKnnMethodContext().getSpaceType()); + assertEquals(CompressionLevel.x1, resolvedMethodContext.getCompressionLevel()); + + KNNMethodContext knnMethodContext = new KNNMethodContext( + KNNEngine.NMSLIB, + SpaceType.INNER_PRODUCT, + new MethodComponentContext(METHOD_HNSW, Map.of()) + ); + + resolvedMethodContext = TEST_RESOLVER.resolveMethod( + knnMethodContext, + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.FLOAT).versionCreated(Version.CURRENT).build(), + false, + SpaceType.INNER_PRODUCT + ); + assertEquals(METHOD_HNSW, resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getName()); + assertFalse(resolvedMethodContext.getKnnMethodContext().getMethodComponentContext().getParameters().isEmpty()); + assertEquals(KNNEngine.NMSLIB, resolvedMethodContext.getKnnMethodContext().getKnnEngine()); + assertEquals(SpaceType.INNER_PRODUCT, resolvedMethodContext.getKnnMethodContext().getSpaceType()); + assertEquals(CompressionLevel.x1, resolvedMethodContext.getCompressionLevel()); + assertNotEquals(knnMethodContext, resolvedMethodContext.getKnnMethodContext()); + } + + public void testResolveMethod_whenInvalid_thenThrow() { + // Invalid training context + expectThrows( + ValidationException.class, + () -> TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.FLOAT).versionCreated(Version.CURRENT).build(), + true, + SpaceType.L2 + ) + ); + + // Invalid compression + expectThrows( + ValidationException.class, + () -> TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .compressionLevel(CompressionLevel.x8) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.L2 + ) + ); + + // Invalid mode + expectThrows( + ValidationException.class, + () -> TEST_RESOLVER.resolveMethod( + null, + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .mode(Mode.ON_DISK) + .versionCreated(Version.CURRENT) + .build(), + false, + SpaceType.L2 + ) + ); + } +} diff --git a/src/test/java/org/opensearch/knn/index/engine/qframe/QuantizationConfigParserTests.java b/src/test/java/org/opensearch/knn/index/engine/qframe/QuantizationConfigParserTests.java new file mode 100644 index 000000000..317d40e27 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/engine/qframe/QuantizationConfigParserTests.java @@ -0,0 +1,74 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.engine.qframe; + +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; + +public class QuantizationConfigParserTests extends KNNTestCase { + + public void testFromCsv() { + assertEquals(QuantizationConfig.EMPTY, QuantizationConfigParser.fromCsv("")); + assertEquals(QuantizationConfig.EMPTY, QuantizationConfigParser.fromCsv(null)); + + expectThrows( + IllegalArgumentException.class, + () -> QuantizationConfigParser.fromCsv(QuantizationConfigParser.TYPE_NAME + "=" + QuantizationConfigParser.BINARY_TYPE) + ); + + expectThrows( + IllegalArgumentException.class, + () -> QuantizationConfigParser.fromCsv( + QuantizationConfigParser.TYPE_NAME + "=invalid," + QuantizationConfigParser.BIT_COUNT_NAME + "=4" + ) + ); + + expectThrows( + IllegalArgumentException.class, + () -> QuantizationConfigParser.fromCsv( + QuantizationConfigParser.TYPE_NAME + + "=" + + QuantizationConfigParser.BINARY_TYPE + + ",invalid=4" + + QuantizationConfigParser.BIT_COUNT_NAME + + "=4" + ) + ); + + expectThrows( + IllegalArgumentException.class, + () -> QuantizationConfigParser.fromCsv( + QuantizationConfigParser.TYPE_NAME + + "=" + + QuantizationConfigParser.BINARY_TYPE + + "," + + QuantizationConfigParser.BIT_COUNT_NAME + + "=invalid" + ) + ); + + assertEquals( + QuantizationConfig.builder().quantizationType(ScalarQuantizationType.FOUR_BIT).build(), + QuantizationConfigParser.fromCsv( + QuantizationConfigParser.TYPE_NAME + + "=" + + QuantizationConfigParser.BINARY_TYPE + + "," + + QuantizationConfigParser.BIT_COUNT_NAME + + "=4" + ) + ); + } + + public void testToCsv() { + assertEquals("", QuantizationConfigParser.toCsv(null)); + assertEquals("", QuantizationConfigParser.toCsv(QuantizationConfig.EMPTY)); + assertEquals( + "type=binary,bits=2", + QuantizationConfigParser.toCsv(QuantizationConfig.builder().quantizationType(ScalarQuantizationType.TWO_BIT).build()) + ); + } +} diff --git a/src/test/java/org/opensearch/knn/index/mapper/CompressionLevelTests.java b/src/test/java/org/opensearch/knn/index/mapper/CompressionLevelTests.java new file mode 100644 index 000000000..e882d6697 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/mapper/CompressionLevelTests.java @@ -0,0 +1,101 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.mapper; + +import org.opensearch.core.common.Strings; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.query.rescore.RescoreContext; + +public class CompressionLevelTests extends KNNTestCase { + + public void testFromName() { + assertEquals(CompressionLevel.NOT_CONFIGURED, CompressionLevel.fromName(null)); + assertEquals(CompressionLevel.NOT_CONFIGURED, CompressionLevel.fromName("")); + assertEquals(CompressionLevel.x1, CompressionLevel.fromName("1x")); + assertEquals(CompressionLevel.x32, CompressionLevel.fromName("32x")); + expectThrows(IllegalArgumentException.class, () -> CompressionLevel.fromName("x1")); + } + + public void testGetName() { + assertTrue(Strings.isEmpty(CompressionLevel.NOT_CONFIGURED.getName())); + assertEquals("4x", CompressionLevel.x4.getName()); + assertEquals("16x", CompressionLevel.x16.getName()); + } + + public void testNumBitsForFloat32() { + assertEquals(1, CompressionLevel.x32.numBitsForFloat32()); + assertEquals(2, CompressionLevel.x16.numBitsForFloat32()); + assertEquals(4, CompressionLevel.x8.numBitsForFloat32()); + assertEquals(8, CompressionLevel.x4.numBitsForFloat32()); + assertEquals(16, CompressionLevel.x2.numBitsForFloat32()); + assertEquals(32, CompressionLevel.x1.numBitsForFloat32()); + assertEquals(32, CompressionLevel.NOT_CONFIGURED.numBitsForFloat32()); + } + + public void testIsConfigured() { + assertFalse(CompressionLevel.isConfigured(CompressionLevel.NOT_CONFIGURED)); + assertFalse(CompressionLevel.isConfigured(null)); + assertTrue(CompressionLevel.isConfigured(CompressionLevel.x1)); + } + + public void testGetDefaultRescoreContext() { + // Test rescore context for ON_DISK mode + Mode mode = Mode.ON_DISK; + + int belowThresholdDimension = 500; // A dimension below the threshold + int aboveThresholdDimension = 1500; // A dimension above the threshold + + // x32 with dimension <= 1000 should have an oversample factor of 5.0f + RescoreContext rescoreContext = CompressionLevel.x32.getDefaultRescoreContext(mode, belowThresholdDimension); + assertNotNull(rescoreContext); + assertEquals(5.0f, rescoreContext.getOversampleFactor(), 0.0f); + + // x32 with dimension > 1000 should have an oversample factor of 3.0f + rescoreContext = CompressionLevel.x32.getDefaultRescoreContext(mode, aboveThresholdDimension); + assertNotNull(rescoreContext); + assertEquals(3.0f, rescoreContext.getOversampleFactor(), 0.0f); + + // x16 with dimension <= 1000 should have an oversample factor of 5.0f + rescoreContext = CompressionLevel.x16.getDefaultRescoreContext(mode, belowThresholdDimension); + assertNotNull(rescoreContext); + assertEquals(5.0f, rescoreContext.getOversampleFactor(), 0.0f); + + // x16 with dimension > 1000 should have an oversample factor of 3.0f + rescoreContext = CompressionLevel.x16.getDefaultRescoreContext(mode, aboveThresholdDimension); + assertNotNull(rescoreContext); + assertEquals(3.0f, rescoreContext.getOversampleFactor(), 0.0f); + + // x8 with dimension <= 1000 should have an oversample factor of 5.0f + rescoreContext = CompressionLevel.x8.getDefaultRescoreContext(mode, belowThresholdDimension); + assertNotNull(rescoreContext); + assertEquals(5.0f, rescoreContext.getOversampleFactor(), 0.0f); + // x8 with dimension > 1000 should have an oversample factor of 2.0f + rescoreContext = CompressionLevel.x8.getDefaultRescoreContext(mode, aboveThresholdDimension); + assertNotNull(rescoreContext); + assertEquals(2.0f, rescoreContext.getOversampleFactor(), 0.0f); + + // x4 with dimension <= 1000 should have an oversample factor of 5.0f (though it doesn't have its own RescoreContext) + rescoreContext = CompressionLevel.x4.getDefaultRescoreContext(mode, belowThresholdDimension); + assertNull(rescoreContext); + // x4 with dimension > 1000 should return null (no RescoreContext is configured for x4) + rescoreContext = CompressionLevel.x4.getDefaultRescoreContext(mode, aboveThresholdDimension); + assertNull(rescoreContext); + // Other compression levels should behave similarly with respect to dimension + rescoreContext = CompressionLevel.x2.getDefaultRescoreContext(mode, belowThresholdDimension); + assertNull(rescoreContext); + // x2 with dimension > 1000 should return null + rescoreContext = CompressionLevel.x2.getDefaultRescoreContext(mode, aboveThresholdDimension); + assertNull(rescoreContext); + rescoreContext = CompressionLevel.x1.getDefaultRescoreContext(mode, belowThresholdDimension); + assertNull(rescoreContext); + // x1 with dimension > 1000 should return null + rescoreContext = CompressionLevel.x1.getDefaultRescoreContext(mode, aboveThresholdDimension); + assertNull(rescoreContext); + // NOT_CONFIGURED with dimension <= 1000 should return a RescoreContext with an oversample factor of 5.0f + rescoreContext = CompressionLevel.NOT_CONFIGURED.getDefaultRescoreContext(mode, belowThresholdDimension); + assertNull(rescoreContext); + } +} diff --git a/src/test/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapperTests.java b/src/test/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapperTests.java new file mode 100644 index 000000000..714723a8e --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapperTests.java @@ -0,0 +1,2114 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.mapper; + +import lombok.SneakyThrows; +import lombok.extern.log4j.Log4j2; +import org.apache.lucene.document.KnnByteVectorField; +import org.apache.lucene.document.KnnFloatVectorField; +import org.apache.lucene.index.IndexableField; +import org.apache.lucene.index.VectorEncoding; +import org.apache.lucene.util.BytesRef; +import org.junit.Assert; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.opensearch.Version; +import org.opensearch.cluster.metadata.IndexMetadata; +import org.opensearch.common.Explicit; +import org.opensearch.common.ValidationException; +import org.opensearch.common.settings.IndexScopedSettings; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.xcontent.LoggingDeprecationHandler; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.common.xcontent.XContentHelper; +import org.opensearch.core.common.Strings; +import org.opensearch.core.common.bytes.BytesArray; +import org.opensearch.core.xcontent.MediaTypeRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.index.IndexSettings; +import org.opensearch.index.mapper.ContentPath; +import org.opensearch.index.mapper.FieldMapper; +import org.opensearch.index.mapper.Mapper; +import org.opensearch.index.mapper.MapperParsingException; +import org.opensearch.index.mapper.MapperService; +import org.opensearch.index.mapper.ParseContext; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.VectorField; +import org.opensearch.knn.index.codec.util.KNNVectorSerializerFactory; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.engine.faiss.QFrameBitEncoder; +import org.opensearch.knn.indices.ModelDao; +import org.opensearch.knn.indices.ModelMetadata; +import org.opensearch.knn.indices.ModelState; +import org.opensearch.knn.indices.ModelUtil; + +import java.io.IOException; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.stream.Collectors; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.opensearch.Version.CURRENT; +import static org.opensearch.knn.common.KNNConstants.COMPRESSION_LEVEL_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.DIMENSION; +import static org.opensearch.knn.common.KNNConstants.ENCODER_SQ; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.KNN_METHOD; +import static org.opensearch.knn.common.KNNConstants.LUCENE_NAME; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_IVF; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_M; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; +import static org.opensearch.knn.common.KNNConstants.MODEL_ID; +import static org.opensearch.knn.common.KNNConstants.MODE_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.NAME; +import static org.opensearch.knn.common.KNNConstants.NMSLIB_NAME; +import static org.opensearch.knn.common.KNNConstants.PARAMETERS; +import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; +import static org.opensearch.knn.index.KNNSettings.KNN_INDEX; +import static org.opensearch.knn.index.VectorDataType.SUPPORTED_VECTOR_DATA_TYPES; + +@Log4j2 +public class KNNVectorFieldMapperTests extends KNNTestCase { + + private static final String TEST_INDEX_NAME = "test-index-name"; + private static final String TEST_FIELD_NAME = "test-field-name"; + + private static final int TEST_DIMENSION = 17; + + private static final float TEST_VECTOR_VALUE = 1.5f; + + private static final float[] TEST_VECTOR = createInitializedFloatArray(TEST_DIMENSION, TEST_VECTOR_VALUE); + + private static final byte TEST_BYTE_VECTOR_VALUE = 10; + private static final byte[] TEST_BYTE_VECTOR = createInitializedByteArray(TEST_DIMENSION, TEST_BYTE_VECTOR_VALUE); + + private static final BytesRef TEST_VECTOR_BYTES_REF = new BytesRef( + KNNVectorSerializerFactory.getDefaultSerializer().floatToByteArray(TEST_VECTOR) + ); + private static final BytesRef TEST_BYTE_VECTOR_BYTES_REF = new BytesRef(TEST_BYTE_VECTOR); + private static final String DIMENSION_FIELD_NAME = "dimension"; + private static final String KNN_VECTOR_TYPE = "knn_vector"; + private static final String TYPE_FIELD_NAME = "type"; + + public void testBuilder_getParameters() { + String fieldName = "test-field-name"; + ModelDao modelDao = mock(ModelDao.class); + KNNVectorFieldMapper.Builder builder = new KNNVectorFieldMapper.Builder( + fieldName, + modelDao, + CURRENT, + null, + new OriginalMappingParameters(VectorDataType.DEFAULT, TEST_DIMENSION, null, null, null, null, SpaceType.UNDEFINED.getValue()) + ); + + assertEquals(10, builder.getParameters().size()); + List actualParams = builder.getParameters().stream().map(a -> a.name).collect(Collectors.toList()); + List expectedParams = Arrays.asList( + "store", + "doc_values", + DIMENSION, + VECTOR_DATA_TYPE_FIELD, + "meta", + KNN_METHOD, + MODEL_ID, + MODE_PARAMETER, + COMPRESSION_LEVEL_PARAMETER, + KNNConstants.TOP_LEVEL_PARAMETER_SPACE_TYPE + ); + assertEquals(expectedParams, actualParams); + } + + public void testTypeParser_build_fromKnnMethodContext() throws IOException { + // Check that knnMethodContext takes precedent over both model and legacy + ModelDao modelDao = mock(ModelDao.class); + + SpaceType spaceType = SpaceType.DEFAULT; + int mRight = 17; + int mWrong = 71; + + KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); + + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, TEST_DIMENSION) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_M, mRight) + .endObject() + .endObject() + .endObject(); + + // Setup settings + Settings settings = Settings.builder() + .put(settings(CURRENT).build()) + .put(KNNSettings.KNN_ALGO_PARAM_M, mWrong) + .put(KNN_INDEX, true) + .build(); + + KNNVectorFieldMapper.Builder builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + "test-field-name-1", + xContentBuilderToMap(xContentBuilder), + buildParserContext("test", settings) + ); + + Mapper.BuilderContext builderContext = new Mapper.BuilderContext(settings, new ContentPath()); + KNNVectorFieldMapper knnVectorFieldMapper = builder.build(builderContext); + assertTrue(knnVectorFieldMapper instanceof MethodFieldMapper); + assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getKnnMethodContext().isPresent()); + assertEquals(spaceType, knnVectorFieldMapper.fieldType().getKnnMappingConfig().getKnnMethodContext().get().getSpaceType()); + assertEquals( + mRight, + knnVectorFieldMapper.fieldType() + .getKnnMappingConfig() + .getKnnMethodContext() + .get() + .getMethodComponentContext() + .getParameters() + .get(METHOD_PARAMETER_M) + ); + assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getModelId().isEmpty()); + } + + public void testTypeParser_withDifferentSpaceTypeCombinations_thenSuccess() throws IOException { + // Check that knnMethodContext takes precedent over both model and legacy + ModelDao modelDao = mock(ModelDao.class); + int mForSetting = 71; + // Setup settings + Settings settings = Settings.builder() + .put(settings(CURRENT).build()) + .put(KNNSettings.KNN_ALGO_PARAM_M, mForSetting) + .put(KNN_INDEX, true) + .build(); + SpaceType methodSpaceType = SpaceType.COSINESIMIL; + SpaceType topLevelSpaceType = SpaceType.INNER_PRODUCT; + KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); + + // space type provided at top level but not in the method + XContentBuilder xContentBuilder = createXContentForFieldMapping(topLevelSpaceType, null, null, null, TEST_DIMENSION); + + KNNVectorFieldMapper.Builder builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + "test-field-name-1", + xContentBuilderToMap(xContentBuilder), + buildParserContext("test", settings) + ); + + Mapper.BuilderContext builderContext = new Mapper.BuilderContext(settings, new ContentPath()); + KNNVectorFieldMapper knnVectorFieldMapper = builder.build(builderContext); + assertTrue(knnVectorFieldMapper instanceof MethodFieldMapper); + assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getKnnMethodContext().isPresent()); + assertEquals(topLevelSpaceType, knnVectorFieldMapper.fieldType().getKnnMappingConfig().getKnnMethodContext().get().getSpaceType()); + assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getModelId().isEmpty()); + + // not setting any space type + xContentBuilder = createXContentForFieldMapping(null, null, null, null, TEST_DIMENSION); + + builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + "test-field-name-1", + xContentBuilderToMap(xContentBuilder), + buildParserContext("test", settings) + ); + + builderContext = new Mapper.BuilderContext(settings, new ContentPath()); + knnVectorFieldMapper = builder.build(builderContext); + assertTrue(knnVectorFieldMapper instanceof MethodFieldMapper); + assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getKnnMethodContext().isPresent()); + assertEquals(SpaceType.DEFAULT, knnVectorFieldMapper.fieldType().getKnnMappingConfig().getKnnMethodContext().get().getSpaceType()); + assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getModelId().isEmpty()); + + // if space types are same + xContentBuilder = createXContentForFieldMapping(topLevelSpaceType, topLevelSpaceType, null, null, TEST_DIMENSION); + builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + "test-field-name-1", + xContentBuilderToMap(xContentBuilder), + buildParserContext("test", settings) + ); + + builderContext = new Mapper.BuilderContext(settings, new ContentPath()); + knnVectorFieldMapper = builder.build(builderContext); + assertTrue(knnVectorFieldMapper instanceof MethodFieldMapper); + assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getKnnMethodContext().isPresent()); + assertEquals(topLevelSpaceType, knnVectorFieldMapper.fieldType().getKnnMappingConfig().getKnnMethodContext().get().getSpaceType()); + assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getModelId().isEmpty()); + + // if space types are not same + xContentBuilder = createXContentForFieldMapping(topLevelSpaceType, methodSpaceType, null, null, TEST_DIMENSION); + + XContentBuilder finalXContentBuilder = xContentBuilder; + Assert.assertThrows( + MapperParsingException.class, + () -> typeParser.parse("test-field-name-1", xContentBuilderToMap(finalXContentBuilder), buildParserContext("test", settings)) + ); + + // if space types not provided and field is binary + xContentBuilder = createXContentForFieldMapping(null, null, KNNEngine.FAISS, VectorDataType.BINARY, 8); + builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + "test-field-name-1", + xContentBuilderToMap(xContentBuilder), + buildParserContext("test", settings) + ); + + builderContext = new Mapper.BuilderContext(settings, new ContentPath()); + knnVectorFieldMapper = builder.build(builderContext); + assertTrue(knnVectorFieldMapper instanceof MethodFieldMapper); + assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getKnnMethodContext().isPresent()); + assertEquals( + SpaceType.DEFAULT_BINARY, + knnVectorFieldMapper.fieldType().getKnnMappingConfig().getKnnMethodContext().get().getSpaceType() + ); + assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getModelId().isEmpty()); + + // mock useKNNMethodContextFromLegacy to simulate index ix created before 2_18 + try (MockedStatic utilMockedStatic = Mockito.mockStatic(KNNVectorFieldMapper.class)) { + utilMockedStatic.when(() -> KNNVectorFieldMapper.useKNNMethodContextFromLegacy(any(), any())).thenReturn(true); + // if space type is provided and legacy mappings is hit + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, TEST_DIMENSION) + .field(KNNConstants.TOP_LEVEL_PARAMETER_SPACE_TYPE, topLevelSpaceType.getValue()) + .endObject(); + builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + "test-field-name-1", + xContentBuilderToMap(xContentBuilder), + buildParserContext("test", settings) + ); + + builderContext = new Mapper.BuilderContext(settings, new ContentPath()); + knnVectorFieldMapper = builder.build(builderContext); + assertTrue(knnVectorFieldMapper instanceof MethodFieldMapper); + assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getKnnMethodContext().isPresent()); + assertEquals( + topLevelSpaceType, + knnVectorFieldMapper.fieldType().getKnnMappingConfig().getKnnMethodContext().get().getSpaceType() + ); + // this check ensures that legacy mapping is hit, as in legacy mapping we pick M from index settings + assertEquals( + mForSetting, + knnVectorFieldMapper.fieldType() + .getKnnMappingConfig() + .getKnnMethodContext() + .get() + .getMethodComponentContext() + .getParameters() + .get(METHOD_PARAMETER_M) + ); + assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getModelId().isEmpty()); + } + } + + public void testTypeParser_withSpaceTypeAndMode_thenSuccess() throws IOException { + // Check that knnMethodContext takes precedent over both model and legacy + ModelDao modelDao = mock(ModelDao.class); + // Setup settings + Settings settings = Settings.builder().put(settings(CURRENT).build()).put(KNN_INDEX, true).build(); + + SpaceType topLevelSpaceType = SpaceType.INNER_PRODUCT; + KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION, TEST_DIMENSION) + .field(MODE_PARAMETER, Mode.ON_DISK.getName()) + .field(COMPRESSION_LEVEL_PARAMETER, CompressionLevel.x16.getName()) + .field(KNNConstants.TOP_LEVEL_PARAMETER_SPACE_TYPE, topLevelSpaceType.getValue()) + .endObject(); + KNNVectorFieldMapper.Builder builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + "test-field-name-1", + xContentBuilderToMap(xContentBuilder), + buildParserContext("test", settings) + ); + + Mapper.BuilderContext builderContext = new Mapper.BuilderContext(settings, new ContentPath()); + KNNVectorFieldMapper knnVectorFieldMapper = builder.build(builderContext); + assertTrue(knnVectorFieldMapper instanceof MethodFieldMapper); + assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getKnnMethodContext().isPresent()); + assertEquals(topLevelSpaceType, knnVectorFieldMapper.fieldType().getKnnMappingConfig().getKnnMethodContext().get().getSpaceType()); + assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getModelId().isEmpty()); + } + + public void testBuilder_build_fromModel() { + // Check that modelContext takes precedent over legacy + ModelDao modelDao = mock(ModelDao.class); + KNNVectorFieldMapper.Builder builder = new KNNVectorFieldMapper.Builder("test-field-name-1", modelDao, CURRENT, null, null); + + SpaceType spaceType = SpaceType.COSINESIMIL; + int m = 17; + int efConstruction = 17; + + // Setup settings + Settings settings = Settings.builder() + .put(settings(CURRENT).build()) + .put(KNNSettings.KNN_SPACE_TYPE, spaceType.getValue()) + .put(KNNSettings.KNN_ALGO_PARAM_M, m) + .put(KNNSettings.KNN_ALGO_PARAM_EF_CONSTRUCTION, efConstruction) + .put(KNN_INDEX, true) + .build(); + + String modelId = "Random modelId"; + ModelMetadata mockedModelMetadata = new ModelMetadata( + KNNEngine.FAISS, + SpaceType.L2, + 129, + ModelState.CREATED, + ZonedDateTime.now(ZoneOffset.UTC).toString(), + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.FLOAT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.V_EMPTY + ); + builder.modelId.setValue(modelId); + Mapper.BuilderContext builderContext = new Mapper.BuilderContext(settings, new ContentPath()); + + when(modelDao.getMetadata(modelId)).thenReturn(mockedModelMetadata); + builder.setOriginalParameters(new OriginalMappingParameters(builder)); + KNNVectorFieldMapper knnVectorFieldMapper = builder.build(builderContext); + assertTrue(knnVectorFieldMapper instanceof ModelFieldMapper); + assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getModelId().isPresent()); + assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getKnnMethodContext().isEmpty()); + } + + public void testBuilder_build_fromLegacy() throws IOException { + // Check legacy is picked up if model context and method context are not set + ModelDao modelDao = mock(ModelDao.class); + int m = 17; + int efConstruction = 17; + KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); + + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, 12) + .endObject(); + + Settings settings = Settings.builder() + .put(settings(CURRENT).build()) + .put(KNNSettings.KNN_ALGO_PARAM_M, m) + .put(KNNSettings.KNN_ALGO_PARAM_EF_CONSTRUCTION, efConstruction) + .put(KNN_INDEX, true) + .build(); + + KNNVectorFieldMapper.Builder builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + "test-field-name-1", + xContentBuilderToMap(xContentBuilder), + buildParserContext("test", settings) + ); + + // Setup settings + Mapper.BuilderContext builderContext = new Mapper.BuilderContext(settings, new ContentPath()); + KNNVectorFieldMapper knnVectorFieldMapper = builder.build(builderContext); + assertTrue(knnVectorFieldMapper instanceof MethodFieldMapper); + assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getKnnMethodContext().isPresent()); + assertTrue(knnVectorFieldMapper.fieldType().getKnnMappingConfig().getModelId().isEmpty()); + assertEquals(SpaceType.L2, knnVectorFieldMapper.fieldType().getKnnMappingConfig().getKnnMethodContext().get().getSpaceType()); + } + + public void testBuilder_parse_fromKnnMethodContext_luceneEngine() throws IOException { + String fieldName = "test-field-name"; + String indexName = "test-index-name"; + + Settings settings = Settings.builder().put(settings(CURRENT).build()).put(KNN_INDEX, true).build(); + + ModelDao modelDao = mock(ModelDao.class); + KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); + + KNNEngine.LUCENE.setInitialized(false); + + int efConstruction = 321; + int m = 12; + int dimension = 133; + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2) + .field(KNN_ENGINE, LUCENE_NAME) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction) + .field(METHOD_PARAMETER_M, m) + .endObject() + .endObject() + .endObject(); + KNNVectorFieldMapper.Builder builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + fieldName, + xContentBuilderToMap(xContentBuilder), + buildParserContext(indexName, settings) + ); + Mapper.BuilderContext builderContext = new Mapper.BuilderContext(settings, new ContentPath()); + builder.build(builderContext); + + assertEquals(METHOD_HNSW, builder.knnMethodContext.get().getMethodComponentContext().getName()); + assertEquals( + efConstruction, + builder.knnMethodContext.get().getMethodComponentContext().getParameters().get(METHOD_PARAMETER_EF_CONSTRUCTION) + ); + assertTrue(KNNEngine.LUCENE.isInitialized()); + + XContentBuilder xContentBuilderEmptyParams = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2) + .field(KNN_ENGINE, LUCENE_NAME) + .endObject() + .endObject(); + KNNVectorFieldMapper.Builder builderEmptyParams = (KNNVectorFieldMapper.Builder) typeParser.parse( + fieldName, + xContentBuilderToMap(xContentBuilderEmptyParams), + buildParserContext(indexName, settings) + ); + + assertEquals(METHOD_HNSW, builder.knnMethodContext.get().getMethodComponentContext().getName()); + assertTrue(builderEmptyParams.knnMethodContext.get().getMethodComponentContext().getParameters().isEmpty()); + + XContentBuilder xContentBuilderUnsupportedParam = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2) + .field(KNN_ENGINE, LUCENE_NAME) + .startObject(PARAMETERS) + .field("RANDOM_PARAM", 0) + .endObject() + .endObject() + .endObject(); + + expectThrows( + ValidationException.class, + () -> typeParser.parse( + fieldName, + xContentBuilderToMap(xContentBuilderUnsupportedParam), + buildParserContext(indexName, settings) + ) + ); + } + + public void testTypeParser_parse_fromKnnMethodContext_invalidDimension() throws IOException { + String fieldName = "test-field-name"; + String indexName = "test-index-name"; + + Settings settings = Settings.builder().put(settings(CURRENT).put(KNN_INDEX, true).build()).build(); + + ModelDao modelDao = mock(ModelDao.class); + KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); + + int efConstruction = 321; + + XContentBuilder xContentBuilderOverMaxDimension = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, 20000) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2) + .field(KNN_ENGINE, LUCENE_NAME) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction) + .endObject() + .endObject() + .endObject(); + + IllegalArgumentException ex = expectThrows( + IllegalArgumentException.class, + () -> typeParser.parse( + fieldName, + xContentBuilderToMap(xContentBuilderOverMaxDimension), + buildParserContext(indexName, settings) + ) + ); + assertTrue(ex.getMessage().contains("Dimension value cannot be greater than 16000 for vector with engine: lucene")); + + XContentBuilder xContentBuilderInvalidDimension = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, "2147483648") + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2) + .field(KNN_ENGINE, NMSLIB_NAME) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction) + .endObject() + .endObject() + .endObject(); + + IllegalArgumentException exInvalidDimension = expectThrows( + IllegalArgumentException.class, + () -> typeParser.parse( + fieldName, + xContentBuilderToMap(xContentBuilderInvalidDimension), + buildParserContext(indexName, settings) + ) + ); + assertEquals( + "Unable to parse [dimension] from provided value [2147483648] for vector [test-field-name]", + exInvalidDimension.getMessage() + ); + } + + @SneakyThrows + public void testTypeParser_parse_compressionAndModeParameter() { + String fieldName = "test-field-name-vec"; + String indexName = "test-index-name-vec"; + + Settings settings = Settings.builder().put(settings(CURRENT).build()).put(KNN_INDEX, true).build(); + + ModelDao modelDao = mock(ModelDao.class); + KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); + + XContentBuilder xContentBuilder1 = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION, 10) + .field(VECTOR_DATA_TYPE_FIELD, VectorDataType.DEFAULT.getValue()) + .field(MODE_PARAMETER, Mode.ON_DISK.getName()) + .field(COMPRESSION_LEVEL_PARAMETER, CompressionLevel.x16.getName()) + .endObject(); + + Mapper.Builder builder = typeParser.parse( + fieldName, + xContentBuilderToMap(xContentBuilder1), + buildParserContext(indexName, settings) + ); + + assertTrue(builder instanceof KNNVectorFieldMapper.Builder); + assertEquals(Mode.ON_DISK.getName(), ((KNNVectorFieldMapper.Builder) builder).mode.get()); + assertEquals(CompressionLevel.x16.getName(), ((KNNVectorFieldMapper.Builder) builder).compressionLevel.get()); + + XContentBuilder xContentBuilder2 = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION, 10) + .field(VECTOR_DATA_TYPE_FIELD, VectorDataType.DEFAULT.getValue()) + .field(MODE_PARAMETER, "invalid") + .field(COMPRESSION_LEVEL_PARAMETER, CompressionLevel.x16.getName()) + .endObject(); + + expectThrows( + MapperParsingException.class, + () -> typeParser.parse(fieldName, xContentBuilderToMap(xContentBuilder2), buildParserContext(indexName, settings)) + ); + + XContentBuilder xContentBuilder3 = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION, 10) + .field(VECTOR_DATA_TYPE_FIELD, VectorDataType.DEFAULT.getValue()) + .field(COMPRESSION_LEVEL_PARAMETER, "invalid") + .endObject(); + + expectThrows( + MapperParsingException.class, + () -> typeParser.parse(fieldName, xContentBuilderToMap(xContentBuilder3), buildParserContext(indexName, settings)) + ); + + XContentBuilder xContentBuilder4 = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION, 10) + .field(VECTOR_DATA_TYPE_FIELD, VectorDataType.DEFAULT.getValue()) + .field(MODEL_ID, "test") + .field(MODE_PARAMETER, Mode.ON_DISK.getName()) + .field(COMPRESSION_LEVEL_PARAMETER, CompressionLevel.x16.getName()) + .endObject(); + + expectThrows( + MapperParsingException.class, + () -> typeParser.parse(fieldName, xContentBuilderToMap(xContentBuilder4), buildParserContext(indexName, settings)) + ); + } + + // Validate TypeParser parsing invalid vector data_type which throws exception + @SneakyThrows + public void testTypeParser_parse_invalidVectorDataType() { + String fieldName = "test-field-name-vec"; + String indexName = "test-index-name-vec"; + String vectorDataType = "invalid"; + + Settings settings = Settings.builder().put(settings(CURRENT).build()).build(); + + ModelDao modelDao = mock(ModelDao.class); + KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); + + XContentBuilder xContentBuilderOverInvalidVectorType = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION, 10) + .field(VECTOR_DATA_TYPE_FIELD, vectorDataType) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2) + .field(KNN_ENGINE, LUCENE_NAME) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, 128) + .endObject() + .endObject() + .endObject(); + + IllegalArgumentException ex = expectThrows( + IllegalArgumentException.class, + () -> typeParser.parse( + fieldName, + xContentBuilderToMap(xContentBuilderOverInvalidVectorType), + buildParserContext(indexName, settings) + ) + ); + assertEquals( + String.format( + Locale.ROOT, + "Invalid value provided for [%s] field. Supported values are [%s]", + VECTOR_DATA_TYPE_FIELD, + SUPPORTED_VECTOR_DATA_TYPES + ), + ex.getMessage() + ); + } + + public void testTypeParser_parse_fromKnnMethodContext_invalidSpaceType() throws IOException { + String fieldName = "test-field-name"; + String indexName = "test-index-name"; + + Settings settings = Settings.builder().put(settings(CURRENT).build()).put(KNN_INDEX, true).build(); + + ModelDao modelDao = mock(ModelDao.class); + KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); + + int efConstruction = 321; + int dimension = 133; + XContentBuilder xContentBuilderL1SpaceType = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.L1.getValue()) + .field(KNN_ENGINE, LUCENE_NAME) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction) + .endObject() + .endObject() + .endObject(); + + expectThrows( + ValidationException.class, + () -> typeParser.parse(fieldName, xContentBuilderToMap(xContentBuilderL1SpaceType), buildParserContext(indexName, settings)) + ); + } + + public void testTypeParser_parse_fromKnnMethodContext() throws IOException { + // Check that knnMethodContext is set + String fieldName = "test-field-name"; + String indexName = "test-index-name"; + + Settings settings = Settings.builder().put(settings(CURRENT).build()).put(KNN_INDEX, true).build(); + + ModelDao modelDao = mock(ModelDao.class); + KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); + + int efConstruction = 321; + int dimension = 133; + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction) + .endObject() + .endObject() + .endObject(); + + KNNVectorFieldMapper.Builder builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + fieldName, + xContentBuilderToMap(xContentBuilder), + buildParserContext(indexName, settings) + ); + + assertEquals(METHOD_HNSW, builder.knnMethodContext.get().getMethodComponentContext().getName()); + assertEquals( + efConstruction, + builder.knnMethodContext.get().getMethodComponentContext().getParameters().get(METHOD_PARAMETER_EF_CONSTRUCTION) + ); + + // Test invalid parameter + XContentBuilder xContentBuilder2 = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .startObject(PARAMETERS) + .field("invalid", "invalid") + .endObject() + .endObject() + .endObject(); + + expectThrows( + ValidationException.class, + () -> typeParser.parse(fieldName, xContentBuilderToMap(xContentBuilder2), buildParserContext(indexName, settings)) + ); + + // Test invalid method + XContentBuilder xContentBuilder3 = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .startObject(KNN_METHOD) + .field(NAME, "invalid") + .endObject() + .endObject(); + + expectThrows( + IllegalArgumentException.class, + () -> typeParser.parse(fieldName, xContentBuilderToMap(xContentBuilder3), buildParserContext(indexName, settings)) + ); + + // Test missing required parameter: dimension + XContentBuilder xContentBuilder4 = XContentFactory.jsonBuilder().startObject().field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE).endObject(); + + expectThrows( + IllegalArgumentException.class, + () -> typeParser.parse(fieldName, xContentBuilderToMap(xContentBuilder4), buildParserContext(indexName, settings)) + ); + + // Check that this fails if model id is also set + XContentBuilder xContentBuilder5 = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(MODEL_ID, "test-id") + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .startObject(PARAMETERS) + .field("invalid", "invalid") + .endObject() + .endObject() + .endObject(); + + expectThrows( + IllegalArgumentException.class, + () -> typeParser.parse(fieldName, xContentBuilderToMap(xContentBuilder5), buildParserContext(indexName, settings)) + ); + } + + public void testTypeParser_parse_fromModel() throws IOException { + // Check that modelContext is set for the builder + String fieldName = "test-field-name"; + String indexName = "test-index-name"; + + Settings settings = Settings.builder().put(settings(CURRENT).build()).put(KNN_INDEX, true).build(); + + ModelDao modelDao = mock(ModelDao.class); + KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); + + String modelId = "test-id"; + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(MODEL_ID, modelId) + .endObject(); + + KNNVectorFieldMapper.Builder builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + fieldName, + xContentBuilderToMap(xContentBuilder), + buildParserContext(indexName, settings) + ); + + assertEquals(modelId, builder.modelId.get()); + } + + public void testTypeParser_parse_fromLegacy() throws IOException { + // Check that the particular values are set in builder + String fieldName = "test-field-name"; + String indexName = "test-index-name"; + + int m = 144; + int efConstruction = 123; + SpaceType spaceType = SpaceType.L2; + Settings settings = Settings.builder() + .put(settings(CURRENT).build()) + .put(KNNSettings.KNN_SPACE_TYPE, spaceType.getValue()) + .put(KNNSettings.KNN_ALGO_PARAM_M, m) + .put(KNNSettings.KNN_ALGO_PARAM_EF_CONSTRUCTION, efConstruction) + .build(); + + ModelDao modelDao = mock(ModelDao.class); + KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); + + int dimension = 122; + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .endObject(); + + KNNVectorFieldMapper.Builder builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + fieldName, + xContentBuilderToMap(xContentBuilder), + buildParserContext(indexName, settings) + ); + + assertNull(builder.modelId.get()); + assertNull(builder.knnMethodContext.get()); + } + + public void testKNNVectorFieldMapperMerge_whenModeAndCompressionIsPresent_thenSuccess() throws IOException { + String fieldName = "test-field-name"; + String indexName = "test-index-name"; + + Settings settings = Settings.builder().put(settings(CURRENT).build()).put(KNN_INDEX, true).build(); + ModelDao modelDao = mock(ModelDao.class); + KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); + + int dimension = 133; + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(MODE_PARAMETER, Mode.ON_DISK.getName()) + .field(COMPRESSION_LEVEL_PARAMETER, CompressionLevel.x32.getName()) + .endObject(); + + KNNVectorFieldMapper.Builder builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + fieldName, + xContentBuilderToMap(xContentBuilder), + buildParserContext(indexName, settings) + ); + Mapper.BuilderContext builderContext = new Mapper.BuilderContext(settings, new ContentPath()); + KNNVectorFieldMapper knnVectorFieldMapper1 = builder.build(builderContext); + + // merge with itself - should be successful + KNNVectorFieldMapper knnVectorFieldMapperMerge1 = (KNNVectorFieldMapper) knnVectorFieldMapper1.merge(knnVectorFieldMapper1); + assertEquals( + knnVectorFieldMapper1.fieldType().getKnnMappingConfig().getKnnMethodContext().get(), + knnVectorFieldMapperMerge1.fieldType().getKnnMappingConfig().getKnnMethodContext().get() + ); + + assertEquals( + knnVectorFieldMapper1.fieldType().getKnnMappingConfig().getCompressionLevel(), + knnVectorFieldMapperMerge1.fieldType().getKnnMappingConfig().getCompressionLevel() + ); + assertEquals( + knnVectorFieldMapper1.fieldType().getKnnMappingConfig().getMode(), + knnVectorFieldMapperMerge1.fieldType().getKnnMappingConfig().getMode() + ); + + // merge with another mapper of the same field with same context + KNNVectorFieldMapper knnVectorFieldMapper2 = builder.build(builderContext); + KNNVectorFieldMapper knnVectorFieldMapperMerge2 = (KNNVectorFieldMapper) knnVectorFieldMapper1.merge(knnVectorFieldMapper2); + assertEquals( + knnVectorFieldMapper1.fieldType().getKnnMappingConfig().getKnnMethodContext().get(), + knnVectorFieldMapperMerge2.fieldType().getKnnMappingConfig().getKnnMethodContext().get() + ); + + assertEquals( + knnVectorFieldMapper1.fieldType().getKnnMappingConfig().getCompressionLevel(), + knnVectorFieldMapperMerge2.fieldType().getKnnMappingConfig().getCompressionLevel() + ); + assertEquals( + knnVectorFieldMapper1.fieldType().getKnnMappingConfig().getMode(), + knnVectorFieldMapperMerge2.fieldType().getKnnMappingConfig().getMode() + ); + } + + public void testKNNVectorFieldMapper_merge_fromKnnMethodContext() throws IOException { + String fieldName = "test-field-name"; + String indexName = "test-index-name"; + + Settings settings = Settings.builder().put(settings(CURRENT).build()).put(KNN_INDEX, true).build(); + + ModelDao modelDao = mock(ModelDao.class); + KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); + + int dimension = 133; + int efConstruction = 321; + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, efConstruction) + .endObject() + .endObject() + .endObject(); + + KNNVectorFieldMapper.Builder builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + fieldName, + xContentBuilderToMap(xContentBuilder), + buildParserContext(indexName, settings) + ); + + Mapper.BuilderContext builderContext = new Mapper.BuilderContext(settings, new ContentPath()); + KNNVectorFieldMapper knnVectorFieldMapper1 = builder.build(builderContext); + + // merge with itself - should be successful + KNNVectorFieldMapper knnVectorFieldMapperMerge1 = (KNNVectorFieldMapper) knnVectorFieldMapper1.merge(knnVectorFieldMapper1); + assertEquals( + knnVectorFieldMapper1.fieldType().getKnnMappingConfig().getKnnMethodContext().get(), + knnVectorFieldMapperMerge1.fieldType().getKnnMappingConfig().getKnnMethodContext().get() + ); + + // merge with another mapper of the same field with same context + KNNVectorFieldMapper knnVectorFieldMapper2 = builder.build(builderContext); + KNNVectorFieldMapper knnVectorFieldMapperMerge2 = (KNNVectorFieldMapper) knnVectorFieldMapper1.merge(knnVectorFieldMapper2); + assertEquals( + knnVectorFieldMapper1.fieldType().getKnnMappingConfig().getKnnMethodContext().get(), + knnVectorFieldMapperMerge2.fieldType().getKnnMappingConfig().getKnnMethodContext().get() + ); + + // merge with another mapper of the same field with different context + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .endObject() + .endObject(); + + builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + fieldName, + xContentBuilderToMap(xContentBuilder), + buildParserContext(indexName, settings) + ); + KNNVectorFieldMapper knnVectorFieldMapper3 = builder.build(builderContext); + expectThrows(IllegalArgumentException.class, () -> knnVectorFieldMapper1.merge(knnVectorFieldMapper3)); + } + + public void testKNNVectorFieldMapper_merge_fromModel() throws IOException { + String fieldName = "test-field-name"; + String indexName = "test-index-name"; + + Settings settings = Settings.builder().put(settings(CURRENT).build()).put(KNN_INDEX, true).build(); + + String modelId = "test-id"; + int dimension = 133; + + ModelDao mockModelDao = mock(ModelDao.class); + ModelMetadata mockModelMetadata = new ModelMetadata( + KNNEngine.DEFAULT, + SpaceType.DEFAULT, + dimension, + ModelState.CREATED, + ZonedDateTime.now(ZoneOffset.UTC).toString(), + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.FLOAT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.V_EMPTY + ); + when(mockModelDao.getMetadata(modelId)).thenReturn(mockModelMetadata); + + KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> mockModelDao); + + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(MODEL_ID, modelId) + .endObject(); + + KNNVectorFieldMapper.Builder builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + fieldName, + xContentBuilderToMap(xContentBuilder), + buildParserContext(indexName, settings) + ); + + Mapper.BuilderContext builderContext = new Mapper.BuilderContext(settings, new ContentPath()); + KNNVectorFieldMapper knnVectorFieldMapper1 = builder.build(builderContext); + + // merge with itself - should be successful + KNNVectorFieldMapper knnVectorFieldMapperMerge1 = (KNNVectorFieldMapper) knnVectorFieldMapper1.merge(knnVectorFieldMapper1); + assertEquals( + knnVectorFieldMapper1.fieldType().getKnnMappingConfig().getModelId().get(), + knnVectorFieldMapperMerge1.fieldType().getKnnMappingConfig().getModelId().get() + ); + + // merge with another mapper of the same field with same context + KNNVectorFieldMapper knnVectorFieldMapper2 = builder.build(builderContext); + KNNVectorFieldMapper knnVectorFieldMapperMerge2 = (KNNVectorFieldMapper) knnVectorFieldMapper1.merge(knnVectorFieldMapper2); + assertEquals( + knnVectorFieldMapper1.fieldType().getKnnMappingConfig().getModelId().get(), + knnVectorFieldMapperMerge2.fieldType().getKnnMappingConfig().getModelId().get() + ); + + // merge with another mapper of the same field with different context + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .endObject() + .endObject(); + + builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + fieldName, + xContentBuilderToMap(xContentBuilder), + buildParserContext(indexName, settings) + ); + + KNNVectorFieldMapper knnVectorFieldMapper3 = builder.build(builderContext); + expectThrows(IllegalArgumentException.class, () -> knnVectorFieldMapper1.merge(knnVectorFieldMapper3)); + } + + @SneakyThrows + public void testMethodFieldMapperParseCreateField_validInput_thenDifferentFieldTypes() { + try (MockedStatic utilMockedStatic = Mockito.mockStatic(KNNVectorFieldMapperUtil.class)) { + for (VectorDataType dataType : VectorDataType.values()) { + log.info("Vector Data Type is : {}", dataType); + int dimension = adjustDimensionForIndexing(TEST_DIMENSION, dataType); + final MethodComponentContext methodComponentContext = new MethodComponentContext(METHOD_HNSW, Collections.emptyMap()); + SpaceType spaceType = VectorDataType.BINARY == dataType ? SpaceType.DEFAULT_BINARY : SpaceType.INNER_PRODUCT; + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .vectorDataType(dataType) + .versionCreated(CURRENT) + .dimension(dimension) + .build(); + final KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.FAISS, spaceType, methodComponentContext); + + ParseContext.Document document = new ParseContext.Document(); + ContentPath contentPath = new ContentPath(); + ParseContext parseContext = mock(ParseContext.class); + when(parseContext.doc()).thenReturn(document); + when(parseContext.path()).thenReturn(contentPath); + when(parseContext.parser()).thenReturn(createXContentParser(dataType)); + + utilMockedStatic.when(() -> KNNVectorFieldMapperUtil.useLuceneKNNVectorsFormat(Mockito.any())).thenReturn(true); + + OriginalMappingParameters originalMappingParameters = new OriginalMappingParameters( + dataType, + dimension, + knnMethodContext, + Mode.NOT_CONFIGURED.getName(), + CompressionLevel.NOT_CONFIGURED.getName(), + null, + SpaceType.UNDEFINED.getValue() + ); + originalMappingParameters.setResolvedKnnMethodContext(knnMethodContext); + MethodFieldMapper methodFieldMapper = MethodFieldMapper.createFieldMapper( + TEST_FIELD_NAME, + TEST_FIELD_NAME, + Collections.emptyMap(), + knnMethodConfigContext, + FieldMapper.MultiFields.empty(), + FieldMapper.CopyTo.empty(), + new Explicit<>(true, true), + false, + false, + originalMappingParameters + ); + methodFieldMapper.parseCreateField(parseContext, dimension, dataType); + + List fields = document.getFields(); + assertEquals(1, fields.size()); + IndexableField field1 = fields.get(0); + if (dataType == VectorDataType.FLOAT) { + assertTrue(field1 instanceof KnnFloatVectorField); + assertEquals(field1.fieldType().vectorEncoding(), VectorEncoding.FLOAT32); + } else { + assertTrue(field1 instanceof KnnByteVectorField); + assertEquals(field1.fieldType().vectorEncoding(), VectorEncoding.BYTE); + } + + assertEquals(field1.fieldType().vectorDimension(), adjustDimensionForSearch(dimension, dataType)); + assertEquals(Integer.parseInt(field1.fieldType().getAttributes().get(DIMENSION_FIELD_NAME)), dimension); + assertEquals( + field1.fieldType().vectorSimilarityFunction(), + SpaceType.DEFAULT.getKnnVectorSimilarityFunction().getVectorSimilarityFunction() + ); + + utilMockedStatic.when(() -> KNNVectorFieldMapperUtil.useLuceneKNNVectorsFormat(Mockito.any())).thenReturn(false); + + document = new ParseContext.Document(); + contentPath = new ContentPath(); + when(parseContext.doc()).thenReturn(document); + when(parseContext.path()).thenReturn(contentPath); + when(parseContext.parser()).thenReturn(createXContentParser(dataType)); + methodFieldMapper = MethodFieldMapper.createFieldMapper( + TEST_FIELD_NAME, + TEST_FIELD_NAME, + Collections.emptyMap(), + knnMethodConfigContext, + FieldMapper.MultiFields.empty(), + FieldMapper.CopyTo.empty(), + new Explicit<>(true, true), + false, + false, + originalMappingParameters + ); + + methodFieldMapper.parseCreateField(parseContext, dimension, dataType); + fields = document.getFields(); + assertEquals(1, fields.size()); + field1 = fields.get(0); + assertTrue(field1 instanceof VectorField); + assertEquals(Integer.parseInt(field1.fieldType().getAttributes().get(DIMENSION_FIELD_NAME)), dimension); + } + } + } + + @SneakyThrows + public void testModelFieldMapperParseCreateField_validInput_thenDifferentFieldTypes() { + ModelDao modelDao = mock(ModelDao.class); + ModelMetadata modelMetadata = mock(ModelMetadata.class); + final MethodComponentContext methodComponentContext = new MethodComponentContext(METHOD_IVF, Collections.emptyMap()); + try ( + MockedStatic utilMockedStatic = Mockito.mockStatic(KNNVectorFieldMapperUtil.class); + MockedStatic modelUtilMockedStatic = Mockito.mockStatic(ModelUtil.class) + ) { + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .versionCreated(CURRENT) + .dimension(TEST_DIMENSION) + .build(); + + for (VectorDataType dataType : VectorDataType.values()) { + log.info("Vector Data Type is : {}", dataType); + SpaceType spaceType = VectorDataType.BINARY == dataType ? SpaceType.DEFAULT_BINARY : SpaceType.INNER_PRODUCT; + int dimension = adjustDimensionForIndexing(TEST_DIMENSION, dataType); + when(modelDao.getMetadata(MODEL_ID)).thenReturn(modelMetadata); + modelUtilMockedStatic.when(() -> ModelUtil.isModelCreated(modelMetadata)).thenReturn(true); + when(modelMetadata.getDimension()).thenReturn(dimension); + when(modelMetadata.getVectorDataType()).thenReturn(dataType); + when(modelMetadata.getSpaceType()).thenReturn(spaceType); + when(modelMetadata.getKnnEngine()).thenReturn(KNNEngine.FAISS); + when(modelMetadata.getMethodComponentContext()).thenReturn(methodComponentContext); + + ParseContext.Document document = new ParseContext.Document(); + ContentPath contentPath = new ContentPath(); + ParseContext parseContext = mock(ParseContext.class); + when(parseContext.doc()).thenReturn(document); + when(parseContext.path()).thenReturn(contentPath); + when(parseContext.parser()).thenReturn(createXContentParser(dataType)); + + utilMockedStatic.when(() -> KNNVectorFieldMapperUtil.useLuceneKNNVectorsFormat(Mockito.any())).thenReturn(true); + + OriginalMappingParameters originalMappingParameters = new OriginalMappingParameters( + VectorDataType.DEFAULT, + -1, + null, + Mode.NOT_CONFIGURED.getName(), + CompressionLevel.NOT_CONFIGURED.getName(), + MODEL_ID, + SpaceType.UNDEFINED.getValue() + ); + + ModelFieldMapper modelFieldMapper = ModelFieldMapper.createFieldMapper( + TEST_FIELD_NAME, + TEST_FIELD_NAME, + Collections.emptyMap(), + dataType, + FieldMapper.MultiFields.empty(), + FieldMapper.CopyTo.empty(), + new Explicit<>(true, true), + false, + false, + modelDao, + CURRENT, + originalMappingParameters, + knnMethodConfigContext + ); + + modelFieldMapper.parseCreateField(parseContext); + + List fields = document.getFields(); + assertEquals(1, fields.size()); + IndexableField field1 = fields.get(0); + if (dataType == VectorDataType.FLOAT) { + assertTrue(field1 instanceof KnnFloatVectorField); + assertEquals(field1.fieldType().vectorEncoding(), VectorEncoding.FLOAT32); + } else { + assertTrue(field1 instanceof KnnByteVectorField); + assertEquals(field1.fieldType().vectorEncoding(), VectorEncoding.BYTE); + } + + assertEquals(field1.fieldType().vectorDimension(), adjustDimensionForSearch(dimension, dataType)); + assertEquals( + field1.fieldType().vectorSimilarityFunction(), + SpaceType.DEFAULT.getKnnVectorSimilarityFunction().getVectorSimilarityFunction() + ); + + utilMockedStatic.when(() -> KNNVectorFieldMapperUtil.useLuceneKNNVectorsFormat(Mockito.any())).thenReturn(false); + + document = new ParseContext.Document(); + contentPath = new ContentPath(); + when(parseContext.doc()).thenReturn(document); + when(parseContext.path()).thenReturn(contentPath); + when(parseContext.parser()).thenReturn(createXContentParser(dataType)); + modelFieldMapper = ModelFieldMapper.createFieldMapper( + TEST_FIELD_NAME, + TEST_FIELD_NAME, + Collections.emptyMap(), + dataType, + FieldMapper.MultiFields.empty(), + FieldMapper.CopyTo.empty(), + new Explicit<>(true, true), + false, + false, + modelDao, + CURRENT, + originalMappingParameters, + knnMethodConfigContext + ); + + modelFieldMapper.parseCreateField(parseContext); + fields = document.getFields(); + assertEquals(1, fields.size()); + field1 = fields.get(0); + assertTrue(field1 instanceof VectorField); + } + } + } + + @SneakyThrows + public void testLuceneFieldMapper_parseCreateField_docValues_withFloats() { + // Create a lucene field mapper that creates a binary doc values field as well as KnnVectorField + LuceneFieldMapper.CreateLuceneFieldMapperInput.CreateLuceneFieldMapperInputBuilder inputBuilder = + createLuceneFieldMapperInputBuilder(); + + ParseContext.Document document = new ParseContext.Document(); + ContentPath contentPath = new ContentPath(); + ParseContext parseContext = mock(ParseContext.class); + when(parseContext.doc()).thenReturn(document); + when(parseContext.path()).thenReturn(contentPath); + when(parseContext.parser()).thenReturn(createXContentParser(VectorDataType.FLOAT)); + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .versionCreated(CURRENT) + .dimension(TEST_DIMENSION) + .build(); + + OriginalMappingParameters originalMappingParameters = new OriginalMappingParameters( + VectorDataType.FLOAT, + TEST_DIMENSION, + getDefaultKNNMethodContext(), + Mode.NOT_CONFIGURED.getName(), + CompressionLevel.NOT_CONFIGURED.getName(), + null, + SpaceType.UNDEFINED.getValue() + ); + originalMappingParameters.setResolvedKnnMethodContext(originalMappingParameters.getKnnMethodContext()); + + LuceneFieldMapper luceneFieldMapper = LuceneFieldMapper.createFieldMapper( + TEST_FIELD_NAME, + Collections.emptyMap(), + knnMethodConfigContext, + inputBuilder.build(), + originalMappingParameters + ); + luceneFieldMapper.parseCreateField(parseContext, TEST_DIMENSION, VectorDataType.FLOAT); + + // Document should have 2 fields: one for VectorField (binary doc values) and one for KnnFloatVectorField + List fields = document.getFields(); + assertEquals(2, fields.size()); + IndexableField field1 = fields.get(0); + IndexableField field2 = fields.get(1); + + VectorField vectorField; + KnnFloatVectorField knnVectorField; + if (field1 instanceof VectorField) { + assertTrue(field2 instanceof KnnFloatVectorField); + vectorField = (VectorField) field1; + knnVectorField = (KnnFloatVectorField) field2; + } else { + assertTrue(field1 instanceof KnnFloatVectorField); + assertTrue(field2 instanceof VectorField); + knnVectorField = (KnnFloatVectorField) field1; + vectorField = (VectorField) field2; + } + + assertEquals(TEST_VECTOR_BYTES_REF, vectorField.binaryValue()); + assertEquals(VectorEncoding.FLOAT32, vectorField.fieldType().vectorEncoding()); + assertArrayEquals(TEST_VECTOR, knnVectorField.vectorValue(), 0.001f); + + // Test when doc values are disabled + document = new ParseContext.Document(); + contentPath = new ContentPath(); + parseContext = mock(ParseContext.class); + when(parseContext.doc()).thenReturn(document); + when(parseContext.path()).thenReturn(contentPath); + when(parseContext.parser()).thenReturn(createXContentParser(VectorDataType.FLOAT)); + + inputBuilder.hasDocValues(false); + + knnMethodConfigContext = KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .versionCreated(CURRENT) + .dimension(TEST_DIMENSION) + .build(); + MethodComponentContext methodComponentContext = new MethodComponentContext(METHOD_HNSW, Collections.emptyMap()); + KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.LUCENE, SpaceType.DEFAULT, methodComponentContext); + originalMappingParameters = new OriginalMappingParameters( + VectorDataType.FLOAT, + TEST_DIMENSION, + knnMethodContext, + Mode.NOT_CONFIGURED.getName(), + CompressionLevel.NOT_CONFIGURED.getName(), + null, + SpaceType.UNDEFINED.getValue() + ); + originalMappingParameters.setResolvedKnnMethodContext(originalMappingParameters.getKnnMethodContext()); + luceneFieldMapper = LuceneFieldMapper.createFieldMapper( + TEST_FIELD_NAME, + Collections.emptyMap(), + knnMethodConfigContext, + inputBuilder.build(), + originalMappingParameters + ); + luceneFieldMapper.parseCreateField(parseContext, TEST_DIMENSION, VectorDataType.FLOAT); + + // Document should have 1 field: one for KnnVectorField + fields = document.getFields(); + assertEquals(1, fields.size()); + IndexableField field = fields.get(0); + assertTrue(field instanceof KnnFloatVectorField); + knnVectorField = (KnnFloatVectorField) field; + assertArrayEquals(TEST_VECTOR, knnVectorField.vectorValue(), 0.001f); + } + + @SneakyThrows + public void testLuceneFieldMapper_parseCreateField_docValues_withBytes() { + // Create a lucene field mapper that creates a binary doc values field as well as KnnByteVectorField + + LuceneFieldMapper.CreateLuceneFieldMapperInput.CreateLuceneFieldMapperInputBuilder inputBuilder = + createLuceneFieldMapperInputBuilder(); + + ParseContext.Document document = new ParseContext.Document(); + ContentPath contentPath = new ContentPath(); + ParseContext parseContext = mock(ParseContext.class); + when(parseContext.doc()).thenReturn(document); + when(parseContext.path()).thenReturn(contentPath); + + OriginalMappingParameters originalMappingParameters = new OriginalMappingParameters( + VectorDataType.BYTE, + TEST_DIMENSION, + getDefaultByteKNNMethodContext(), + Mode.NOT_CONFIGURED.getName(), + CompressionLevel.NOT_CONFIGURED.getName(), + null, + SpaceType.UNDEFINED.getValue() + ); + originalMappingParameters.setResolvedKnnMethodContext(originalMappingParameters.getKnnMethodContext()); + + LuceneFieldMapper luceneFieldMapper = Mockito.spy( + LuceneFieldMapper.createFieldMapper( + TEST_FIELD_NAME, + Collections.emptyMap(), + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.BYTE) + .versionCreated(CURRENT) + .dimension(TEST_DIMENSION) + .build(), + inputBuilder.build(), + originalMappingParameters + ) + ); + doReturn(Optional.of(TEST_BYTE_VECTOR)).when(luceneFieldMapper) + .getBytesFromContext(parseContext, TEST_DIMENSION, VectorDataType.BYTE); + doNothing().when(luceneFieldMapper).validatePreparse(); + + luceneFieldMapper.parseCreateField(parseContext, TEST_DIMENSION, VectorDataType.BYTE); + + // Document should have 2 fields: one for VectorField (binary doc values) and one for KnnByteVectorField + List fields = document.getFields(); + assertEquals(2, fields.size()); + IndexableField field1 = fields.get(0); + IndexableField field2 = fields.get(1); + + VectorField vectorField; + KnnByteVectorField knnByteVectorField; + if (field1 instanceof VectorField) { + assertTrue(field2 instanceof KnnByteVectorField); + vectorField = (VectorField) field1; + knnByteVectorField = (KnnByteVectorField) field2; + } else { + assertTrue(field1 instanceof KnnByteVectorField); + assertTrue(field2 instanceof VectorField); + knnByteVectorField = (KnnByteVectorField) field1; + vectorField = (VectorField) field2; + } + + assertEquals(TEST_BYTE_VECTOR_BYTES_REF, vectorField.binaryValue()); + assertArrayEquals(TEST_BYTE_VECTOR, knnByteVectorField.vectorValue()); + + // Test when doc values are disabled + document = new ParseContext.Document(); + contentPath = new ContentPath(); + parseContext = mock(ParseContext.class); + when(parseContext.doc()).thenReturn(document); + when(parseContext.path()).thenReturn(contentPath); + + inputBuilder.hasDocValues(false); + + luceneFieldMapper = Mockito.spy( + LuceneFieldMapper.createFieldMapper( + TEST_FIELD_NAME, + Collections.emptyMap(), + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.BYTE) + .versionCreated(CURRENT) + .dimension(TEST_DIMENSION) + .build(), + inputBuilder.build(), + originalMappingParameters + ) + ); + doReturn(Optional.of(TEST_BYTE_VECTOR)).when(luceneFieldMapper) + .getBytesFromContext(parseContext, TEST_DIMENSION, VectorDataType.BYTE); + doNothing().when(luceneFieldMapper).validatePreparse(); + + luceneFieldMapper.parseCreateField(parseContext, TEST_DIMENSION, VectorDataType.BYTE); + + // Document should have 1 field: one for KnnByteVectorField + fields = document.getFields(); + assertEquals(1, fields.size()); + IndexableField field = fields.get(0); + assertTrue(field instanceof KnnByteVectorField); + knnByteVectorField = (KnnByteVectorField) field; + assertArrayEquals(TEST_BYTE_VECTOR, knnByteVectorField.vectorValue()); + } + + public void testTypeParser_whenBinaryFaissHNSW_thenValid() throws IOException { + testTypeParserWithBinaryDataType(KNNEngine.FAISS, SpaceType.HAMMING, METHOD_HNSW, 8, null); + } + + public void testTypeParser_whenBinaryWithInvalidDimension_thenException() throws IOException { + testTypeParserWithBinaryDataType(KNNEngine.FAISS, SpaceType.HAMMING, METHOD_HNSW, 4, "should be multiply of 8"); + } + + public void testTypeParser_whenBinaryFaissHNSWWithInvalidSpaceType_thenException() throws IOException { + for (SpaceType spaceType : SpaceType.values()) { + if (SpaceType.UNDEFINED == spaceType || SpaceType.HAMMING == spaceType) { + continue; + } + testTypeParserWithBinaryDataType(KNNEngine.FAISS, spaceType, METHOD_HNSW, 8, "is not supported with"); + } + } + + public void testTypeParser_whenBinaryNonFaiss_thenException() throws IOException { + testTypeParserWithBinaryDataType(KNNEngine.LUCENE, SpaceType.HAMMING, METHOD_HNSW, 8, "is not supported for vector data type"); + testTypeParserWithBinaryDataType(KNNEngine.NMSLIB, SpaceType.HAMMING, METHOD_HNSW, 8, "is not supported for vector data type"); + } + + private void testTypeParserWithBinaryDataType( + KNNEngine knnEngine, + SpaceType spaceType, + String method, + int dimension, + String expectedErrMsg + ) throws IOException { + // Check legacy is picked up if model context and method context are not set + ModelDao modelDao = mock(ModelDao.class); + KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); + String fieldName = "test-field-name-1"; + String indexName = "test-index"; + + // Setup settings + Settings settings = Settings.builder().put(settings(CURRENT).build()).put(KNN_INDEX, true).build(); + + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(VECTOR_DATA_TYPE_FIELD, VectorDataType.BINARY.getValue()) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNN_ENGINE, knnEngine.getName()) + .endObject() + .endObject(); + + if (expectedErrMsg == null) { + KNNVectorFieldMapper.Builder builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + fieldName, + xContentBuilderToMap(xContentBuilder), + buildParserContext(indexName, settings) + ); + + assertEquals(spaceType, builder.getOriginalParameters().getResolvedKnnMethodContext().getSpaceType()); + } else { + Exception ex = expectThrows(Exception.class, () -> { + typeParser.parse(fieldName, xContentBuilderToMap(xContentBuilder), buildParserContext(indexName, settings)); + }); + assertTrue(ex.getMessage(), ex.getMessage().contains(expectedErrMsg)); + } + } + + public void testTypeParser_whenBinaryFaissHNSWWithSQ_thenException() throws IOException { + ModelDao modelDao = mock(ModelDao.class); + KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); + // Setup settings + Settings settings = Settings.builder().put(settings(CURRENT).build()).put(KNN_INDEX, true).build(); + + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, 8) + .field(VECTOR_DATA_TYPE_FIELD, VectorDataType.BINARY.getValue()) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, KNNEngine.FAISS.getName()) + .startObject(PARAMETERS) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_SQ) + .endObject() + .endObject() + .endObject() + .endObject(); + + Exception ex = expectThrows( + Exception.class, + () -> typeParser.parse("test", xContentBuilderToMap(xContentBuilder), buildParserContext("test", settings)) + ); + assertTrue(ex.getMessage(), ex.getMessage().contains("parameter validation failed for MethodComponentContext parameter [encoder]")); + } + + public void testBuilder_whenBinaryWithLegacyKNNDisabled_thenValid() { + // Check legacy is picked up if model context and method context are not set + ModelDao modelDao = mock(ModelDao.class); + KNNVectorFieldMapper.Builder builder = new KNNVectorFieldMapper.Builder("test-field-name-1", modelDao, CURRENT, null, null); + builder.vectorDataType.setValue(VectorDataType.BINARY); + builder.dimension.setValue(8); + + // Setup settings + Settings settings = Settings.builder().put(settings(CURRENT).build()).put(KNN_INDEX, false).build(); + + builder.setOriginalParameters(new OriginalMappingParameters(builder)); + Mapper.BuilderContext builderContext = new Mapper.BuilderContext(settings, new ContentPath()); + KNNVectorFieldMapper knnVectorFieldMapper = builder.build(builderContext); + assertTrue(knnVectorFieldMapper instanceof FlatVectorFieldMapper); + } + + public void testTypeParser_whenBinaryWithLegacyKNNEnabled_thenValid() throws IOException { + // Check legacy is picked up if model context and method context are not set + ModelDao modelDao = mock(ModelDao.class); + KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); + String fieldName = "test-field-name-1"; + String indexName = "test-index"; + + // Setup settings + Settings settings = Settings.builder().put(settings(CURRENT).build()).put(KNN_INDEX, true).build(); + + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, 8) + .field(VECTOR_DATA_TYPE_FIELD, VectorDataType.BINARY.getValue()) + .endObject(); + + final KNNVectorFieldMapper.Builder builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + fieldName, + xContentBuilderToMap(xContentBuilder), + buildParserContext(indexName, settings) + ); + assertEquals(SpaceType.HAMMING, builder.getOriginalParameters().getResolvedKnnMethodContext().getSpaceType()); + } + + public void testBuild_whenInvalidCharsInFieldName_thenThrowException() { + for (char disallowChar : Strings.INVALID_FILENAME_CHARS) { + // When an invalid vector field name was given. + final String invalidVectorFieldName = "fieldname" + disallowChar; + + // Prepare context. + Settings settings = Settings.builder().put(settings(CURRENT).build()).put(KNN_INDEX, true).build(); + Mapper.BuilderContext builderContext = new Mapper.BuilderContext(settings, new ContentPath()); + + // IllegalArgumentException should be thrown. + Exception e = assertThrows(IllegalArgumentException.class, () -> { + new KNNVectorFieldMapper.Builder(invalidVectorFieldName, null, CURRENT, null, null).build(builderContext); + }); + assertTrue(e.getMessage(), e.getMessage().contains("Vector field name must not include")); + } + } + + public void testTypeParser_whenModeAndCompressionAreSet_thenHandle() throws IOException { + int dimension = 16; + Settings settings = Settings.builder().put(settings(CURRENT).build()).put(KNN_INDEX, true).build(); + ModelDao modelDao = mock(ModelDao.class); + KNNVectorFieldMapper.TypeParser typeParser = new KNNVectorFieldMapper.TypeParser(() -> modelDao); + + // Default to faiss and ensure legacy is in use + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .endObject(); + KNNVectorFieldMapper.Builder builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + TEST_FIELD_NAME, + xContentBuilderToMap(xContentBuilder), + buildParserContext(TEST_INDEX_NAME, settings) + ); + assertNull(builder.getOriginalParameters().getKnnMethodContext()); + assertTrue(builder.getOriginalParameters().isLegacyMapping()); + validateBuilderAfterParsing( + builder, + KNNEngine.DEFAULT, + SpaceType.L2, + VectorDataType.FLOAT, + CompressionLevel.x1, + CompressionLevel.NOT_CONFIGURED, + Mode.NOT_CONFIGURED, + false + ); + + // If mode is in memory and 1x compression, again use default legacy + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(COMPRESSION_LEVEL_PARAMETER, CompressionLevel.x1.getName()) + .field(MODE_PARAMETER, Mode.IN_MEMORY.getName()) + .endObject(); + builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + TEST_FIELD_NAME, + xContentBuilderToMap(xContentBuilder), + buildParserContext(TEST_INDEX_NAME, settings) + ); + assertNull(builder.getOriginalParameters().getKnnMethodContext()); + assertFalse(builder.getOriginalParameters().isLegacyMapping()); + validateBuilderAfterParsing( + builder, + KNNEngine.NMSLIB, + SpaceType.L2, + VectorDataType.FLOAT, + CompressionLevel.x1, + CompressionLevel.x1, + Mode.IN_MEMORY, + false + ); + + // Default on disk is faiss with 32x binary quant + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(MODE_PARAMETER, Mode.ON_DISK.getName()) + .endObject(); + + builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + TEST_FIELD_NAME, + xContentBuilderToMap(xContentBuilder), + buildParserContext(TEST_INDEX_NAME, settings) + ); + validateBuilderAfterParsing( + builder, + KNNEngine.FAISS, + SpaceType.L2, + VectorDataType.FLOAT, + CompressionLevel.x32, + CompressionLevel.NOT_CONFIGURED, + Mode.ON_DISK, + true + ); + + // Ensure 2x does not use binary quantization + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(COMPRESSION_LEVEL_PARAMETER, CompressionLevel.x2.getName()) + .endObject(); + + builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + TEST_FIELD_NAME, + xContentBuilderToMap(xContentBuilder), + buildParserContext(TEST_INDEX_NAME, settings) + ); + validateBuilderAfterParsing( + builder, + KNNEngine.FAISS, + SpaceType.L2, + VectorDataType.FLOAT, + CompressionLevel.x2, + CompressionLevel.x2, + Mode.NOT_CONFIGURED, + false + ); + + // For 8x ensure that it does use binary quantization + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(MODE_PARAMETER, Mode.ON_DISK.getName()) + .field(COMPRESSION_LEVEL_PARAMETER, CompressionLevel.x8.getName()) + .endObject(); + + builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + TEST_FIELD_NAME, + xContentBuilderToMap(xContentBuilder), + buildParserContext(TEST_INDEX_NAME, settings) + ); + validateBuilderAfterParsing( + builder, + KNNEngine.FAISS, + SpaceType.L2, + VectorDataType.FLOAT, + CompressionLevel.x8, + CompressionLevel.x8, + Mode.ON_DISK, + true + ); + + // For 4x compression on disk, use Lucene + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(MODE_PARAMETER, Mode.ON_DISK.getName()) + .field(COMPRESSION_LEVEL_PARAMETER, CompressionLevel.x4.getName()) + .endObject(); + + builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + TEST_FIELD_NAME, + xContentBuilderToMap(xContentBuilder), + buildParserContext(TEST_INDEX_NAME, settings) + ); + validateBuilderAfterParsing( + builder, + KNNEngine.LUCENE, + SpaceType.L2, + VectorDataType.FLOAT, + CompressionLevel.x4, + CompressionLevel.x4, + Mode.ON_DISK, + false + ); + + // For 4x compression in memory, use Lucene + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(MODE_PARAMETER, Mode.IN_MEMORY.getName()) + .field(COMPRESSION_LEVEL_PARAMETER, CompressionLevel.x4.getName()) + .endObject(); + + builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + TEST_FIELD_NAME, + xContentBuilderToMap(xContentBuilder), + buildParserContext(TEST_INDEX_NAME, settings) + ); + validateBuilderAfterParsing( + builder, + KNNEngine.LUCENE, + SpaceType.L2, + VectorDataType.FLOAT, + CompressionLevel.x4, + CompressionLevel.x4, + Mode.IN_MEMORY, + false + ); + + // For override, ensure compression is correct + xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, KNNEngine.FAISS) + .startObject(PARAMETERS) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, QFrameBitEncoder.NAME) + .startObject(PARAMETERS) + .field(QFrameBitEncoder.BITCOUNT_PARAM, CompressionLevel.x16.numBitsForFloat32()) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + builder = (KNNVectorFieldMapper.Builder) typeParser.parse( + TEST_FIELD_NAME, + xContentBuilderToMap(xContentBuilder), + buildParserContext(TEST_INDEX_NAME, settings) + ); + validateBuilderAfterParsing( + builder, + KNNEngine.FAISS, + SpaceType.L2, + VectorDataType.FLOAT, + CompressionLevel.x16, + CompressionLevel.NOT_CONFIGURED, + Mode.NOT_CONFIGURED, + true + ); + + // Override with conflicting compression levels should fail + XContentBuilder invalidXContentBuilder1 = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(COMPRESSION_LEVEL_PARAMETER, CompressionLevel.x4.getName()) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, KNNEngine.FAISS) + .startObject(PARAMETERS) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, QFrameBitEncoder.NAME) + .startObject(PARAMETERS) + .field(QFrameBitEncoder.BITCOUNT_PARAM, CompressionLevel.x16.numBitsForFloat32()) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + expectThrows( + ValidationException.class, + () -> typeParser.parse( + TEST_FIELD_NAME, + xContentBuilderToMap(invalidXContentBuilder1), + buildParserContext(TEST_INDEX_NAME, settings) + ) + ); + + // Invalid if vector data type is binary + XContentBuilder invalidXContentBuilder2 = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(VECTOR_DATA_TYPE_FIELD, VectorDataType.BINARY.getValue()) + .field(MODE_PARAMETER, Mode.IN_MEMORY.getName()) + .endObject(); + + expectThrows( + MapperParsingException.class, + () -> typeParser.parse( + TEST_FIELD_NAME, + xContentBuilderToMap(invalidXContentBuilder2), + buildParserContext(TEST_INDEX_NAME, settings) + ) + ); + + // Invalid if engine doesnt support the compression + XContentBuilder invalidXContentBuilder3 = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension) + .field(COMPRESSION_LEVEL_PARAMETER, CompressionLevel.x4.getName()) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, KNNEngine.FAISS) + .endObject() + .endObject(); + + expectThrows( + ValidationException.class, + () -> typeParser.parse( + TEST_FIELD_NAME, + xContentBuilderToMap(invalidXContentBuilder3), + buildParserContext(TEST_INDEX_NAME, settings) + ) + ); + } + + private void validateBuilderAfterParsing( + KNNVectorFieldMapper.Builder builder, + KNNEngine expectedEngine, + SpaceType expectedSpaceType, + VectorDataType expectedVectorDataType, + CompressionLevel expectedResolvedCompressionLevel, + CompressionLevel expectedOriginalCompressionLevel, + Mode expectedMode, + boolean shouldUsesBinaryQFramework + ) { + assertEquals(expectedEngine, builder.getOriginalParameters().getResolvedKnnMethodContext().getKnnEngine()); + assertEquals(expectedSpaceType, builder.getOriginalParameters().getResolvedKnnMethodContext().getSpaceType()); + assertEquals(expectedVectorDataType, builder.getKnnMethodConfigContext().getVectorDataType()); + + assertEquals(expectedResolvedCompressionLevel, builder.getKnnMethodConfigContext().getCompressionLevel()); + assertEquals(expectedOriginalCompressionLevel, CompressionLevel.fromName(builder.getOriginalParameters().getCompressionLevel())); + assertEquals(expectedMode, Mode.fromName(builder.getOriginalParameters().getMode())); + assertEquals(expectedMode, builder.getKnnMethodConfigContext().getMode()); + assertFalse(builder.getOriginalParameters().getResolvedKnnMethodContext().getMethodComponentContext().getParameters().isEmpty()); + + if (shouldUsesBinaryQFramework) { + assertEquals( + QFrameBitEncoder.NAME, + ((MethodComponentContext) builder.getOriginalParameters() + .getResolvedKnnMethodContext() + .getMethodComponentContext() + .getParameters() + .get(METHOD_ENCODER_PARAMETER)).getName() + ); + assertEquals( + expectedResolvedCompressionLevel.numBitsForFloat32(), + (int) ((MethodComponentContext) builder.getOriginalParameters() + .getResolvedKnnMethodContext() + .getMethodComponentContext() + .getParameters() + .get(METHOD_ENCODER_PARAMETER)).getParameters().get(QFrameBitEncoder.BITCOUNT_PARAM) + ); + } else { + assertTrue( + builder.getOriginalParameters().getResolvedKnnMethodContext().getMethodComponentContext().getParameters().isEmpty() + || builder.getOriginalParameters() + .getResolvedKnnMethodContext() + .getMethodComponentContext() + .getParameters() + .containsKey(METHOD_ENCODER_PARAMETER) == false + || QFrameBitEncoder.NAME.equals( + ((MethodComponentContext) builder.getOriginalParameters() + .getResolvedKnnMethodContext() + .getMethodComponentContext() + .getParameters() + .get(METHOD_ENCODER_PARAMETER)).getName() + ) == false + ); + } + } + + private LuceneFieldMapper.CreateLuceneFieldMapperInput.CreateLuceneFieldMapperInputBuilder createLuceneFieldMapperInputBuilder() { + return LuceneFieldMapper.CreateLuceneFieldMapperInput.builder() + .name(TEST_FIELD_NAME) + .multiFields(FieldMapper.MultiFields.empty()) + .copyTo(FieldMapper.CopyTo.empty()) + .hasDocValues(true) + .ignoreMalformed(new Explicit<>(true, true)) + .originalKnnMethodContext(getDefaultKNNMethodContext()); + } + + private XContentBuilder createXContentForFieldMapping( + SpaceType topLevelSpaceType, + SpaceType methodSpaceType, + KNNEngine knnEngine, + VectorDataType vectorDataType, + int dimension + ) throws IOException { + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(TYPE_FIELD_NAME, KNN_VECTOR_TYPE) + .field(DIMENSION_FIELD_NAME, dimension); + + if (topLevelSpaceType != null && topLevelSpaceType != SpaceType.UNDEFINED) { + xContentBuilder.field(KNNConstants.TOP_LEVEL_PARAMETER_SPACE_TYPE, topLevelSpaceType.getValue()); + } + if (vectorDataType != null) { + xContentBuilder.field(VECTOR_DATA_TYPE_FIELD, vectorDataType.getValue()); + } + xContentBuilder.startObject(KNN_METHOD).field(NAME, METHOD_HNSW); + if (knnEngine != null) { + xContentBuilder.field(KNN_ENGINE, knnEngine.getName()); + } + if (methodSpaceType != null && methodSpaceType != SpaceType.UNDEFINED) { + xContentBuilder.field(METHOD_PARAMETER_SPACE_TYPE, methodSpaceType.getValue()); + } + xContentBuilder.endObject().endObject(); + return xContentBuilder; + } + + private static float[] createInitializedFloatArray(int dimension, float value) { + float[] array = new float[dimension]; + Arrays.fill(array, value); + return array; + } + + private static byte[] createInitializedByteArray(int dimension, byte value) { + byte[] array = new byte[dimension]; + Arrays.fill(array, value); + return array; + } + + private XContentParser createXContentParser(final VectorDataType dataType) throws IOException { + final String vectorString; + if (dataType == VectorDataType.FLOAT) { + vectorString = Arrays.toString(TEST_VECTOR); + } else { + vectorString = Arrays.toString(TEST_BYTE_VECTOR); + } + + XContentParser parser = XContentHelper.createParser( + NamedXContentRegistry.EMPTY, + LoggingDeprecationHandler.INSTANCE, + new BytesArray(new BytesArray("{\"" + TEST_FIELD_NAME + "\":" + vectorString + "}").toBytesRef()), + MediaTypeRegistry.JSON + ); + // We need to move to 3rd token, as at start XContentParser is at null, first nextToken call will move the + // parser to { aka START_OBJECT, the next call will move it to FIELD_NAME and after that it will move to [ + // aka START_ARRAY which is what we get when we parse the vectors. + parser.nextToken(); + parser.nextToken(); + parser.nextToken(); + return parser; + } + + public IndexMetadata buildIndexMetaData(String indexName, Settings settings) { + return IndexMetadata.builder(indexName) + .settings(settings) + .numberOfShards(1) + .numberOfReplicas(0) + .version(7) + .mappingVersion(0) + .settingsVersion(0) + .aliasesVersion(0) + .creationDate(0) + .build(); + } + + public Mapper.TypeParser.ParserContext buildParserContext(String indexName, Settings settings) { + IndexSettings indexSettings = new IndexSettings( + buildIndexMetaData(indexName, settings), + Settings.EMPTY, + new IndexScopedSettings(Settings.EMPTY, new HashSet<>(IndexScopedSettings.BUILT_IN_INDEX_SETTINGS)) + ); + MapperService mapperService = mock(MapperService.class); + when(mapperService.getIndexSettings()).thenReturn(indexSettings); + + // Setup blank + ModelDao mockModelDao = mock(ModelDao.class); + return new Mapper.TypeParser.ParserContext( + null, + mapperService, + type -> new KNNVectorFieldMapper.TypeParser(() -> mockModelDao), + CURRENT, + null, + null, + null + ); + } +} diff --git a/src/test/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapperUtilTests.java b/src/test/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapperUtilTests.java new file mode 100644 index 000000000..3025c9bd1 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/mapper/KNNVectorFieldMapperUtilTests.java @@ -0,0 +1,79 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.mapper; + +import org.apache.lucene.document.StoredField; +import org.apache.lucene.util.BytesRef; +import org.junit.Assert; +import org.opensearch.Version; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.codec.util.KNNVectorSerializerFactory; + +import java.util.Arrays; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class KNNVectorFieldMapperUtilTests extends KNNTestCase { + + private static final String TEST_FIELD_NAME = "test_field_name"; + private static final byte[] TEST_BYTE_VECTOR = new byte[] { -128, 0, 1, 127 }; + private static final float[] TEST_FLOAT_VECTOR = new float[] { -100.0f, 100.0f, 0f, 1f }; + + public void testStoredFields_whenVectorIsByteType_thenSucceed() { + StoredField storedField = KNNVectorFieldMapperUtil.createStoredFieldForByteVector(TEST_FIELD_NAME, TEST_BYTE_VECTOR); + assertEquals(TEST_FIELD_NAME, storedField.name()); + assertEquals(TEST_BYTE_VECTOR, storedField.binaryValue().bytes); + Object vector = KNNVectorFieldMapperUtil.deserializeStoredVector(storedField.binaryValue(), VectorDataType.BYTE); + assertTrue(vector instanceof int[]); + int[] byteAsIntArray = new int[TEST_BYTE_VECTOR.length]; + Arrays.setAll(byteAsIntArray, i -> TEST_BYTE_VECTOR[i]); + assertArrayEquals(byteAsIntArray, (int[]) vector); + } + + public void testStoredFields_whenVectorIsFloatType_thenSucceed() { + StoredField storedField = KNNVectorFieldMapperUtil.createStoredFieldForFloatVector(TEST_FIELD_NAME, TEST_FLOAT_VECTOR); + assertEquals(TEST_FIELD_NAME, storedField.name()); + BytesRef bytes = new BytesRef(storedField.binaryValue().bytes); + assertArrayEquals(TEST_FLOAT_VECTOR, KNNVectorSerializerFactory.getDefaultSerializer().byteToFloatArray(bytes), 0.001f); + + Object vector = KNNVectorFieldMapperUtil.deserializeStoredVector(storedField.binaryValue(), VectorDataType.FLOAT); + assertTrue(vector instanceof float[]); + assertArrayEquals(TEST_FLOAT_VECTOR, (float[]) vector, 0.001f); + } + + public void testGetExpectedVectorLengthSuccess() { + KNNVectorFieldType knnVectorFieldType = mock(KNNVectorFieldType.class); + when(knnVectorFieldType.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(getDefaultKNNMethodContext(), 3)); + KNNVectorFieldType knnVectorFieldTypeBinary = mock(KNNVectorFieldType.class); + when(knnVectorFieldTypeBinary.getKnnMappingConfig()).thenReturn( + getMappingConfigForMethodMapping(getDefaultBinaryKNNMethodContext(), 8) + ); + when(knnVectorFieldTypeBinary.getVectorDataType()).thenReturn(VectorDataType.BINARY); + + KNNVectorFieldType knnVectorFieldTypeModelBased = mock(KNNVectorFieldType.class); + when(knnVectorFieldTypeModelBased.getKnnMappingConfig()).thenReturn( + getMappingConfigForMethodMapping(getDefaultBinaryKNNMethodContext(), 8) + ); + String modelId = "test-model"; + when(knnVectorFieldTypeModelBased.getKnnMappingConfig()).thenReturn(getMappingConfigForModelMapping(modelId, 4)); + assertEquals(3, KNNVectorFieldMapperUtil.getExpectedVectorLength(knnVectorFieldType)); + assertEquals(1, KNNVectorFieldMapperUtil.getExpectedVectorLength(knnVectorFieldTypeBinary)); + assertEquals(4, KNNVectorFieldMapperUtil.getExpectedVectorLength(knnVectorFieldTypeModelBased)); + } + + public void testUseLuceneKNNVectorsFormat_withDifferentInputs_thenSuccess() { + Assert.assertFalse(KNNVectorFieldMapperUtil.useLuceneKNNVectorsFormat(Version.V_2_16_0)); + Assert.assertTrue(KNNVectorFieldMapperUtil.useLuceneKNNVectorsFormat(Version.V_2_17_0)); + } +} diff --git a/src/test/java/org/opensearch/knn/index/mapper/ModeTests.java b/src/test/java/org/opensearch/knn/index/mapper/ModeTests.java new file mode 100644 index 000000000..2035bba80 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/mapper/ModeTests.java @@ -0,0 +1,33 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.mapper; + +import org.opensearch.core.common.Strings; +import org.opensearch.knn.KNNTestCase; + +public class ModeTests extends KNNTestCase { + + public void testFromName() { + assertEquals(Mode.NOT_CONFIGURED, Mode.fromName(null)); + assertEquals(Mode.NOT_CONFIGURED, Mode.fromName("")); + assertEquals(Mode.ON_DISK, Mode.fromName("on_disk")); + assertEquals(Mode.IN_MEMORY, Mode.fromName("in_memory")); + expectThrows(IllegalArgumentException.class, () -> Mode.fromName("on_disk2")); + } + + public void testGetName() { + assertTrue(Strings.isEmpty(Mode.NOT_CONFIGURED.getName())); + assertEquals("on_disk", Mode.ON_DISK.getName()); + assertEquals("in_memory", Mode.IN_MEMORY.getName()); + } + + public void testIsConfigured() { + assertFalse(Mode.isConfigured(Mode.NOT_CONFIGURED)); + assertFalse(Mode.isConfigured(null)); + assertTrue(Mode.isConfigured(Mode.ON_DISK)); + } + +} diff --git a/src/test/java/org/opensearch/knn/index/mapper/OriginalMappingParametersTests.java b/src/test/java/org/opensearch/knn/index/mapper/OriginalMappingParametersTests.java new file mode 100644 index 000000000..4b089b149 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/mapper/OriginalMappingParametersTests.java @@ -0,0 +1,63 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.mapper; + +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponentContext; + +import java.util.Collections; + +public class OriginalMappingParametersTests extends KNNTestCase { + + public void testIsLegacy() { + assertTrue( + new OriginalMappingParameters(VectorDataType.DEFAULT, 123, null, null, null, null, SpaceType.UNDEFINED.getValue()) + .isLegacyMapping() + ); + assertFalse( + new OriginalMappingParameters(VectorDataType.DEFAULT, 123, null, null, null, "model-id", SpaceType.UNDEFINED.getValue()) + .isLegacyMapping() + ); + assertFalse( + new OriginalMappingParameters( + VectorDataType.DEFAULT, + 123, + null, + Mode.ON_DISK.getName(), + null, + null, + SpaceType.UNDEFINED.getValue() + ).isLegacyMapping() + ); + assertFalse( + new OriginalMappingParameters( + VectorDataType.DEFAULT, + 123, + null, + null, + CompressionLevel.x2.getName(), + null, + SpaceType.UNDEFINED.getValue() + ).isLegacyMapping() + ); + assertFalse( + new OriginalMappingParameters( + VectorDataType.DEFAULT, + 123, + new KNNMethodContext(KNNEngine.DEFAULT, SpaceType.L2, new MethodComponentContext(null, Collections.emptyMap())), + null, + null, + null, + SpaceType.UNDEFINED.getValue() + ).isLegacyMapping() + ); + } + +} diff --git a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryAllocationTests.java b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryAllocationTests.java index fbd519163..be20150bc 100644 --- a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryAllocationTests.java +++ b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryAllocationTests.java @@ -12,23 +12,38 @@ package org.opensearch.knn.index.memory; import com.google.common.collect.ImmutableMap; +import lombok.SneakyThrows; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexInput; +import org.junit.Before; +import org.mockito.Mock; +import org.opensearch.common.settings.ClusterSettings; import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.TestUtils; import org.opensearch.knn.common.KNNConstants; -import org.opensearch.knn.index.IndexUtil; -import org.opensearch.knn.jni.JNIService; +import org.opensearch.knn.index.KNNSettings; import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.util.KNNEngine; -import org.opensearch.watcher.FileWatcher; -import org.opensearch.watcher.WatcherHandle; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.store.IndexInputWithBuffer; +import org.opensearch.knn.jni.JNICommons; +import org.opensearch.knn.jni.JNIService; import java.nio.file.Path; import java.util.Arrays; import java.util.Map; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.opensearch.knn.common.featureflags.KNNFeatureFlags.KNN_FORCE_EVICT_CACHE_ENABLED_SETTING; public class NativeMemoryAllocationTests extends KNNTestCase { @@ -37,56 +52,134 @@ public class NativeMemoryAllocationTests extends KNNTestCase { private int testLockValue3; private int testLockValue4; - public void testIndexAllocation_close() throws InterruptedException { - // Create basic nmslib HNSW index - Path dir = createTempDir(); - KNNEngine knnEngine = KNNEngine.NMSLIB; - String indexName = "test1" + knnEngine.getExtension(); - String path = dir.resolve(indexName).toAbsolutePath().toString(); - int numVectors = 10; - int dimension = 10; - int[] ids = new int[numVectors]; - float[][] vectors = new float[numVectors][dimension]; - for (int i = 0; i < numVectors; i++) { - ids[i] = i; - Arrays.fill(vectors[i], 1f); - } - Map parameters = ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.DEFAULT.getValue()); - JNIService.createIndex(ids, vectors, path, parameters, knnEngine.getName()); + @Mock + ClusterSettings clusterSettings; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + clusterSettings = mock(ClusterSettings.class); + when(clusterService.getClusterSettings()).thenReturn(clusterSettings); + when(clusterSettings.get(KNN_FORCE_EVICT_CACHE_ENABLED_SETTING)).thenReturn(false); + KNNSettings.state().setClusterService(clusterService); + } - // Load index into memory - long memoryAddress = JNIService.loadIndex(path, parameters, knnEngine.getName()); + @SneakyThrows + public void testIndexAllocation_close() { + // Create basic nmslib HNSW index + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + KNNEngine knnEngine = KNNEngine.NMSLIB; + String indexFileName = "test1" + knnEngine.getExtension(); + int numVectors = 10; + int dimension = 10; + int[] ids = new int[numVectors]; + float[][] vectors = new float[numVectors][dimension]; + for (int i = 0; i < numVectors; i++) { + ids[i] = i; + Arrays.fill(vectors[i], 1f); + } + Map parameters = ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.DEFAULT.getValue()); + long vectorMemoryAddress = JNICommons.storeVectorData(0, vectors, numVectors * dimension); + TestUtils.createIndex(ids, vectorMemoryAddress, dimension, directory, indexFileName, parameters, knnEngine); + + // Load index into memory + final long memoryAddress; + try (IndexInput indexInput = directory.openInput(indexFileName, IOContext.DEFAULT)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + memoryAddress = JNIService.loadIndex(indexInputWithBuffer, parameters, knnEngine); + } + + ExecutorService executorService = Executors.newSingleThreadExecutor(); + NativeMemoryAllocation.IndexAllocation indexAllocation = new NativeMemoryAllocation.IndexAllocation( + executorService, + memoryAddress, + (int) directory.fileLength(indexFileName) / 1024, + knnEngine, + indexFileName, + "test" + ); + + indexAllocation.close(); + + Thread.sleep(1000 * 2); + indexAllocation.writeLock(); + assertTrue(indexAllocation.isClosed()); + indexAllocation.writeUnlock(); - @SuppressWarnings("unchecked") - WatcherHandle watcherHandle = (WatcherHandle) mock(WatcherHandle.class); - doNothing().when(watcherHandle).stop(); + indexAllocation.close(); - ExecutorService executorService = Executors.newSingleThreadExecutor(); - NativeMemoryAllocation.IndexAllocation indexAllocation = new NativeMemoryAllocation.IndexAllocation( - executorService, - memoryAddress, - IndexUtil.getFileSizeInKB(path), - knnEngine, - path, - "test", - watcherHandle - ); + Thread.sleep(1000 * 2); + indexAllocation.writeLock(); + assertTrue(indexAllocation.isClosed()); + indexAllocation.writeUnlock(); - indexAllocation.close(); + executorService.shutdown(); + } + } - Thread.sleep(1000 * 2); - indexAllocation.writeLock(); - assertTrue(indexAllocation.isClosed()); - indexAllocation.writeUnlock(); + @SneakyThrows + public void testClose_whenBinaryFiass_thenSuccess() { + Path tempDirPath = createTempDir(); + KNNEngine knnEngine = KNNEngine.FAISS; + String indexFileName = "test1" + knnEngine.getExtension(); + try (Directory directory = newFSDirectory(tempDirPath)) { + int numVectors = 10; + int dimension = 8; + int dataLength = dimension / 8; + int[] ids = new int[numVectors]; + byte[][] vectors = new byte[numVectors][dataLength]; + for (int i = 0; i < numVectors; i++) { + ids[i] = i; + vectors[i][0] = 1; + } + Map parameters = ImmutableMap.of( + KNNConstants.SPACE_TYPE, + SpaceType.HAMMING.getValue(), + KNNConstants.INDEX_DESCRIPTION_PARAMETER, + "BHNSW32", + KNNConstants.VECTOR_DATA_TYPE_FIELD, + VectorDataType.BINARY.getValue() + ); + long vectorMemoryAddress = JNICommons.storeBinaryVectorData(0, vectors, numVectors * dataLength); + TestUtils.createIndex(ids, vectorMemoryAddress, dimension, directory, indexFileName, parameters, knnEngine); + + // Load index into memory + final long memoryAddress; + try (IndexInput indexInput = directory.openInput(indexFileName, IOContext.DEFAULT)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + memoryAddress = JNIService.loadIndex(indexInputWithBuffer, parameters, knnEngine); + } + + ExecutorService executorService = Executors.newSingleThreadExecutor(); + NativeMemoryAllocation.IndexAllocation indexAllocation = new NativeMemoryAllocation.IndexAllocation( + executorService, + memoryAddress, + (int) directory.fileLength(indexFileName) / 1024, + knnEngine, + indexFileName, + "test", + null, + true + ); + + indexAllocation.close(); + + Thread.sleep(1000 * 2); + indexAllocation.writeLock(); + assertTrue(indexAllocation.isClosed()); + indexAllocation.writeUnlock(); - indexAllocation.close(); + indexAllocation.close(); - Thread.sleep(1000 * 2); - indexAllocation.writeLock(); - assertTrue(indexAllocation.isClosed()); - indexAllocation.writeUnlock(); + Thread.sleep(1000 * 2); + indexAllocation.writeLock(); + assertTrue(indexAllocation.isClosed()); + indexAllocation.writeUnlock(); - executorService.shutdown(); + executorService.shutdown(); + } } public void testIndexAllocation_getMemoryAddress() { @@ -97,8 +190,7 @@ public void testIndexAllocation_getMemoryAddress() { 0, null, "test", - "test", - null + "test" ); assertEquals(memoryAddress, indexAllocation.getMemoryAddress()); @@ -113,8 +205,7 @@ public void testIndexAllocation_readLock() throws InterruptedException { 0, null, "test", - "test", - null + "test" ); int initialValue = 10; @@ -139,6 +230,70 @@ public void testIndexAllocation_readLock() throws InterruptedException { assertEquals(finalValue, testLockValue1); } + public void testIndexAllocation_closeDefault() { + ExecutorService executorService = Executors.newFixedThreadPool(2); + AtomicReference expectedException = new AtomicReference<>(); + + // Executor based non-blocking close + NativeMemoryAllocation.IndexAllocation nonBlockingIndexAllocation = new NativeMemoryAllocation.IndexAllocation( + mock(ExecutorService.class), + 0, + 0, + null, + "test", + "test" + ); + + executorService.submit(nonBlockingIndexAllocation::readLock); + Future closingThread = executorService.submit(nonBlockingIndexAllocation::close); + try { + closingThread.get(); + } catch (Exception ex) { + expectedException.set(ex); + } + assertNull(expectedException.get()); + expectedException.set(null); + executorService.shutdown(); + } + + public void testIndexAllocation_closeBlocking() throws InterruptedException, ExecutionException { + // Prepare mocking and a thread pool. + ExecutorService executorService = Executors.newSingleThreadExecutor(); + + // Enable `KNN_FORCE_EVICT_CACHE_ENABLED_SETTING` to force it to block other threads. + // Having it false will make `IndexAllocation` to run close logic in a different thread. + when(clusterSettings.get(KNN_FORCE_EVICT_CACHE_ENABLED_SETTING)).thenReturn(true); + NativeMemoryAllocation.IndexAllocation blockingIndexAllocation = new NativeMemoryAllocation.IndexAllocation( + mock(ExecutorService.class), + 0, + 0, + null, + "test", + "test" + ); + + // Acquire a read lock + blockingIndexAllocation.readLock(); + + // This should be blocked as a read lock is still being held. + Future closingThread = executorService.submit(blockingIndexAllocation::close); + + // Check if thread is currently blocked + try { + closingThread.get(5, TimeUnit.SECONDS); + fail("Closing should be blocked. We are still holding a read lock."); + } catch (TimeoutException ignored) {} + + // Now, we unlock a read lock. + blockingIndexAllocation.readUnlock(); + // As we don't hold any locking, the closing thread can now good to acquire a write lock. + closingThread.get(); + + // Waits until close + assertTrue(blockingIndexAllocation.isClosed()); + executorService.shutdown(); + } + public void testIndexAllocation_writeLock() throws InterruptedException { // To test the writeLock, we first grab the writeLock in the main thread. Then we start another thread that // grabs the readLock and asserts testLockValue2 has been updated. Next in the main thread, we update the value @@ -149,8 +304,7 @@ public void testIndexAllocation_writeLock() throws InterruptedException { 0, null, "test", - "test", - null + "test" ); int initialValue = 10; @@ -182,8 +336,7 @@ public void testIndexAllocation_getSize() { size, null, "test", - "test", - null + "test" ); assertEquals(size, indexAllocation.getSizeInKB()); @@ -197,8 +350,7 @@ public void testIndexAllocation_getKnnEngine() { 0, knnEngine, "test", - "test", - null + "test" ); assertEquals(knnEngine, indexAllocation.getKnnEngine()); @@ -212,11 +364,10 @@ public void testIndexAllocation_getIndexPath() { 0, null, indexPath, - "test", - null + "test" ); - assertEquals(indexPath, indexAllocation.getIndexPath()); + assertEquals(indexPath, indexAllocation.getVectorFileName()); } public void testIndexAllocation_getOsIndexName() { @@ -227,8 +378,7 @@ public void testIndexAllocation_getOsIndexName() { 0, null, "test", - osIndexName, - null + osIndexName ); assertEquals(osIndexName, indexAllocation.getOpenSearchIndexName()); @@ -248,7 +398,8 @@ public void testTrainingDataAllocation_close() throws InterruptedException { NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation = new NativeMemoryAllocation.TrainingDataAllocation( executorService, memoryAddress, - 0 + 0, + VectorDataType.FLOAT ); trainingDataAllocation.close(); @@ -274,7 +425,8 @@ public void testTrainingDataAllocation_getMemoryAddress() { NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation = new NativeMemoryAllocation.TrainingDataAllocation( null, memoryAddress, - 0 + 0, + VectorDataType.FLOAT ); assertEquals(memoryAddress, trainingDataAllocation.getMemoryAddress()); @@ -287,7 +439,8 @@ public void testTrainingDataAllocation_readLock() throws InterruptedException { NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation = new NativeMemoryAllocation.TrainingDataAllocation( null, 0, - 0 + 0, + VectorDataType.FLOAT ); int initialValue = 10; @@ -320,7 +473,8 @@ public void testTrainingDataAllocation_writeLock() throws InterruptedException { NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation = new NativeMemoryAllocation.TrainingDataAllocation( null, 0, - 0 + 0, + VectorDataType.FLOAT ); int initialValue = 10; @@ -355,7 +509,8 @@ public void testTrainingDataAllocation_getSize() { NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation = new NativeMemoryAllocation.TrainingDataAllocation( null, 0, - size + size, + VectorDataType.FLOAT ); assertEquals(size, trainingDataAllocation.getSizeInKB()); @@ -367,7 +522,8 @@ public void testTrainingDataAllocation_setMemoryAddress() { NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation = new NativeMemoryAllocation.TrainingDataAllocation( null, pointer, - 0 + 0, + VectorDataType.FLOAT ); assertEquals(pointer, trainingDataAllocation.getMemoryAddress()); diff --git a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryCacheManagerTests.java b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryCacheManagerTests.java index 718df0b1f..5fe41c88c 100644 --- a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryCacheManagerTests.java +++ b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryCacheManagerTests.java @@ -16,6 +16,7 @@ import org.opensearch.common.settings.Settings; import org.opensearch.knn.common.exception.OutOfNativeMemoryException; import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.plugin.KNNPlugin; import org.opensearch.plugins.Plugin; import org.opensearch.test.OpenSearchSingleNodeTestCase; @@ -117,8 +118,7 @@ public void testGetIndexSizeInKilobytes() throws ExecutionException, IOException indexEntryWeight, null, key, - indexName, - null + indexName ); NativeMemoryEntryContext.IndexEntryContext indexEntryContext = mock(NativeMemoryEntryContext.IndexEntryContext.class); @@ -151,8 +151,7 @@ public void testGetIndexSizeAsPercentage() throws ExecutionException, IOExceptio indexEntryWeight, null, key, - indexName, - null + indexName ); NativeMemoryEntryContext.IndexEntryContext indexEntryContext = mock(NativeMemoryEntryContext.IndexEntryContext.class); @@ -186,7 +185,8 @@ public void testGetTrainingSize() throws ExecutionException { NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation = new NativeMemoryAllocation.TrainingDataAllocation( null, 0, - allocationEntryWeight + allocationEntryWeight, + VectorDataType.FLOAT ); NativeMemoryEntryContext.TrainingDataEntryContext trainingDataEntryContext = mock( @@ -197,7 +197,7 @@ public void testGetTrainingSize() throws ExecutionException { nativeMemoryCacheManager.get(trainingDataEntryContext, true); - assertEquals((float) allocationEntryWeight, nativeMemoryCacheManager.getTrainingSizeInKilobytes(), 0.001); + assertEquals(allocationEntryWeight, nativeMemoryCacheManager.getTrainingSizeInKilobytes(), 1e-3); assertEquals( 100 * (float) allocationEntryWeight / (float) maxWeight, nativeMemoryCacheManager.getTrainingSizeAsPercentage(), @@ -229,8 +229,7 @@ public void testGetIndexGraphCount() throws ExecutionException, IOException { indexEntryWeight, null, key1, - indexName1, - null + indexName1 ); NativeMemoryEntryContext.IndexEntryContext indexEntryContext = mock(NativeMemoryEntryContext.IndexEntryContext.class); @@ -245,8 +244,7 @@ public void testGetIndexGraphCount() throws ExecutionException, IOException { indexEntryWeight, null, key2, - indexName1, - null + indexName1 ); indexEntryContext = mock(NativeMemoryEntryContext.IndexEntryContext.class); @@ -261,8 +259,7 @@ public void testGetIndexGraphCount() throws ExecutionException, IOException { indexEntryWeight, null, key3, - indexName2, - null + indexName2 ); indexEntryContext = mock(NativeMemoryEntryContext.IndexEntryContext.class); @@ -406,8 +403,7 @@ public void testGetIndicesCacheStats() throws IOException, ExecutionException { size1, null, testKey1, - indexName1, - null + indexName1 ); NativeMemoryAllocation.IndexAllocation indexAllocation2 = new NativeMemoryAllocation.IndexAllocation( @@ -416,8 +412,7 @@ public void testGetIndicesCacheStats() throws IOException, ExecutionException { size2, null, testKey2, - indexName1, - null + indexName1 ); NativeMemoryAllocation.IndexAllocation indexAllocation3 = new NativeMemoryAllocation.IndexAllocation( @@ -426,8 +421,7 @@ public void testGetIndicesCacheStats() throws IOException, ExecutionException { size1, null, testKey3, - indexName2, - null + indexName2 ); NativeMemoryAllocation.IndexAllocation indexAllocation4 = new NativeMemoryAllocation.IndexAllocation( @@ -436,8 +430,7 @@ public void testGetIndicesCacheStats() throws IOException, ExecutionException { size2, null, testKey4, - indexName2, - null + indexName2 ); NativeMemoryEntryContext.IndexEntryContext indexEntryContext1 = mock(NativeMemoryEntryContext.IndexEntryContext.class); diff --git a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryEntryContextTests.java b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryEntryContextTests.java index 495f20347..5379abc74 100644 --- a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryEntryContextTests.java +++ b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryEntryContextTests.java @@ -12,21 +12,22 @@ package org.opensearch.knn.index.memory; import com.google.common.collect.ImmutableMap; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexOutput; +import org.apache.lucene.store.MMapDirectory; import org.opensearch.cluster.service.ClusterService; import org.opensearch.knn.KNNTestCase; -import org.opensearch.knn.index.IndexUtil; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.TestUtils; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; -import java.io.BufferedOutputStream; import java.io.IOException; -import java.io.OutputStream; -import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.Map; -import static java.nio.file.StandardOpenOption.APPEND; -import static java.nio.file.StandardOpenOption.CREATE; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -42,7 +43,8 @@ public void testAbstract_getKey() { public void testIndexEntryContext_load() throws IOException { NativeMemoryLoadStrategy.IndexLoadStrategy indexLoadStrategy = mock(NativeMemoryLoadStrategy.IndexLoadStrategy.class); NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( - "test", + (Directory) null, + TestUtils.createFakeNativeMamoryCacheKey("test"), indexLoadStrategy, null, "test" @@ -54,8 +56,7 @@ public void testIndexEntryContext_load() throws IOException { 10, KNNEngine.DEFAULT, "test-path", - "test-name", - null + "test-name" ); when(indexLoadStrategy.load(indexEntryContext)).thenReturn(indexAllocation); @@ -65,34 +66,37 @@ public void testIndexEntryContext_load() throws IOException { public void testIndexEntryContext_calculateSize() throws IOException { // Create a file and write random bytes to it - Path tmpFile = createTempFile(); + final Path tmpDirectory = createTempDir(); + final Directory directory = new MMapDirectory(tmpDirectory); + final String indexFileName = "test.faiss"; byte[] data = new byte[1024 * 3]; Arrays.fill(data, (byte) 'c'); - try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(tmpFile, CREATE, APPEND))) { - out.write(data, 0, data.length); - } catch (IOException x) { - fail("Failed to write to file"); + try (IndexOutput output = directory.createOutput(indexFileName, IOContext.DEFAULT)) { + output.writeBytes(data, data.length); } // Get the expected size of this function - int expectedSize = IndexUtil.getFileSizeInKB(tmpFile.toAbsolutePath().toString()); + final long expectedSizeBytes = directory.fileLength(indexFileName); + final long expectedSizeKb = expectedSizeBytes / 1024L; // Check that the indexEntryContext will return the same thing NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( - tmpFile.toAbsolutePath().toString(), + directory, + TestUtils.createFakeNativeMamoryCacheKey(indexFileName), null, null, "test" ); - assertEquals(expectedSize, indexEntryContext.calculateSizeInKB().longValue()); + assertEquals(expectedSizeKb, indexEntryContext.calculateSizeInKB().longValue()); } public void testIndexEntryContext_getOpenSearchIndexName() { String openSearchIndexName = "test-index"; NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( - "test", + (Directory) null, + TestUtils.createFakeNativeMamoryCacheKey("test"), null, null, openSearchIndexName @@ -104,7 +108,8 @@ public void testIndexEntryContext_getOpenSearchIndexName() { public void testIndexEntryContext_getParameters() { Map parameters = ImmutableMap.of("test-1", 10); NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( - "test", + (Directory) null, + TestUtils.createFakeNativeMamoryCacheKey("test"), null, parameters, "test" @@ -122,13 +127,16 @@ public void testTrainingDataEntryContext_load() { trainingLoadStrategy, null, 0, - 0 + 0, + VectorDataType.DEFAULT, + QuantizationConfig.EMPTY ); NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation = new NativeMemoryAllocation.TrainingDataAllocation( null, 0, - 0 + 0, + VectorDataType.DEFAULT ); when(trainingLoadStrategy.load(trainingDataEntryContext)).thenReturn(trainingDataAllocation); @@ -145,7 +153,9 @@ public void testTrainingDataEntryContext_getTrainIndexName() { null, null, 0, - 0 + 0, + VectorDataType.DEFAULT, + QuantizationConfig.EMPTY ); assertEquals(trainIndexName, trainingDataEntryContext.getTrainIndexName()); @@ -160,7 +170,9 @@ public void testTrainingDataEntryContext_getTrainFieldName() { null, null, 0, - 0 + 0, + VectorDataType.DEFAULT, + QuantizationConfig.EMPTY ); assertEquals(trainFieldName, trainingDataEntryContext.getTrainFieldName()); @@ -175,7 +187,9 @@ public void testTrainingDataEntryContext_getMaxVectorCount() { null, null, maxVectorCount, - 0 + 0, + VectorDataType.DEFAULT, + QuantizationConfig.EMPTY ); assertEquals(maxVectorCount, trainingDataEntryContext.getMaxVectorCount()); @@ -190,7 +204,9 @@ public void testTrainingDataEntryContext_getSearchSize() { null, null, 0, - searchSize + searchSize, + VectorDataType.DEFAULT, + QuantizationConfig.EMPTY ); assertEquals(searchSize, trainingDataEntryContext.getSearchSize()); @@ -205,7 +221,9 @@ public void testTrainingDataEntryContext_getIndicesService() { null, clusterService, 0, - 0 + 0, + VectorDataType.DEFAULT, + QuantizationConfig.EMPTY ); assertEquals(clusterService, trainingDataEntryContext.getClusterService()); diff --git a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategyTests.java b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategyTests.java index 1b531e29e..735974bd1 100644 --- a/src/test/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategyTests.java +++ b/src/test/java/org/opensearch/knn/index/memory/NativeMemoryLoadStrategyTests.java @@ -12,17 +12,21 @@ package org.opensearch.knn.index.memory; import com.google.common.collect.ImmutableMap; -import org.opensearch.action.ActionListener; +import org.apache.lucene.store.Directory; +import org.opensearch.core.action.ActionListener; import org.opensearch.action.search.SearchResponse; import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.TestUtils; import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.jni.JNICommons; import org.opensearch.knn.jni.JNIService; -import org.opensearch.knn.index.KNNQueryResult; +import org.opensearch.knn.index.query.KNNQueryResult; import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.util.KNNEngine; -import org.opensearch.knn.training.TrainingDataConsumer; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.training.FloatTrainingDataConsumer; import org.opensearch.knn.training.VectorReader; -import org.opensearch.watcher.ResourceWatcherService; import java.io.IOException; import java.nio.file.Path; @@ -30,52 +34,107 @@ import java.util.Arrays; import java.util.Map; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; public class NativeMemoryLoadStrategyTests extends KNNTestCase { public void testIndexLoadStrategy_load() throws IOException { // Create basic nmslib HNSW index - Path dir = createTempDir(); - KNNEngine knnEngine = KNNEngine.NMSLIB; - String indexName = "test1" + knnEngine.getExtension(); - String path = dir.resolve(indexName).toAbsolutePath().toString(); - int numVectors = 10; - int dimension = 10; - int[] ids = new int[numVectors]; - float[][] vectors = new float[numVectors][dimension]; - for (int i = 0; i < numVectors; i++) { - ids[i] = i; - Arrays.fill(vectors[i], 1f); + Path tempDirPath = createTempDir(); + try (Directory luceneDirectory = newFSDirectory(tempDirPath)) { + KNNEngine knnEngine = KNNEngine.NMSLIB; + String indexFileName = "test1" + knnEngine.getExtension(); + int numVectors = 10; + int dimension = 10; + int[] ids = new int[numVectors]; + float[][] vectors = new float[numVectors][dimension]; + for (int i = 0; i < numVectors; i++) { + ids[i] = i; + Arrays.fill(vectors[i], 1f); + } + Map parameters = ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.DEFAULT.getValue()); + long memoryAddress = JNICommons.storeVectorData(0, vectors, numVectors * dimension); + TestUtils.createIndex(ids, memoryAddress, dimension, luceneDirectory, indexFileName, parameters, knnEngine); + + // Setup mock resource manager + NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( + luceneDirectory, + TestUtils.createFakeNativeMamoryCacheKey(indexFileName), + NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance(), + parameters, + "test" + ); + + // Load + NativeMemoryAllocation.IndexAllocation indexAllocation = NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance() + .load(indexEntryContext); + + // Confirm that the file was loaded by querying + float[] query = new float[dimension]; + Arrays.fill(query, numVectors + 1); + KNNQueryResult[] results = JNIService.queryIndex(indexAllocation.getMemoryAddress(), query, 2, null, knnEngine, null, 0, null); + assertTrue(results.length > 0); } - Map parameters = ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.DEFAULT.getValue()); - JNIService.createIndex(ids, vectors, path, parameters, knnEngine.getName()); - - // Setup mock resource manager - ResourceWatcherService resourceWatcherService = mock(ResourceWatcherService.class); - doReturn(null).when(resourceWatcherService).add(any()); - NativeMemoryLoadStrategy.IndexLoadStrategy.initialize(resourceWatcherService); - - NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( - path, - NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance(), - parameters, - "test" - ); + } - // Load - NativeMemoryAllocation.IndexAllocation indexAllocation = NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance() - .load(indexEntryContext); + public void testLoad_whenFaissBinary_thenSuccess() throws IOException { + Path tempDirPath = createTempDir(); + try (Directory luceneDirectory = newFSDirectory(tempDirPath)) { + KNNEngine knnEngine = KNNEngine.FAISS; + String indexFileName = "test1" + knnEngine.getExtension(); + int numVectors = 10; + int dimension = 8; + int dataLength = dimension / 8; + int[] ids = new int[numVectors]; + byte[][] vectors = new byte[numVectors][dataLength]; + for (int i = 0; i < numVectors; i++) { + ids[i] = i; + vectors[i][0] = 1; + } + Map parameters = ImmutableMap.of( + KNNConstants.SPACE_TYPE, + SpaceType.HAMMING.getValue(), + KNNConstants.INDEX_DESCRIPTION_PARAMETER, + "BHNSW32", + KNNConstants.VECTOR_DATA_TYPE_FIELD, + VectorDataType.BINARY.getValue() + ); + long memoryAddress = JNICommons.storeBinaryVectorData(0, vectors, numVectors); + TestUtils.createIndex(ids, memoryAddress, dimension, luceneDirectory, indexFileName, parameters, knnEngine); - // Confirm that the file was loaded by querying - float[] query = new float[dimension]; - Arrays.fill(query, numVectors + 1); - KNNQueryResult[] results = JNIService.queryIndex(indexAllocation.getMemoryAddress(), query, 2, knnEngine.getName()); - assertTrue(results.length > 0); + // Setup mock resource manager + NativeMemoryEntryContext.IndexEntryContext indexEntryContext = new NativeMemoryEntryContext.IndexEntryContext( + luceneDirectory, + TestUtils.createFakeNativeMamoryCacheKey(indexFileName), + NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance(), + parameters, + "test" + ); + + // Load + NativeMemoryAllocation.IndexAllocation indexAllocation = NativeMemoryLoadStrategy.IndexLoadStrategy.getInstance() + .load(indexEntryContext); + + // Verify + assertTrue(indexAllocation.isBinaryIndex()); + + // Confirm that the file was loaded by querying + byte[] query = { 1 }; + KNNQueryResult[] results = JNIService.queryBinaryIndex( + indexAllocation.getMemoryAddress(), + query, + 2, + null, + knnEngine, + null, + 0, + null + ); + assertTrue(results.length > 0); + } } @SuppressWarnings("unchecked") @@ -88,12 +147,12 @@ public void testTrainingLoadStrategy_load() { logger.info("J0"); doAnswer(invocationOnMock -> { logger.info("J1"); - TrainingDataConsumer trainingDataConsumer = (TrainingDataConsumer) invocationOnMock.getArguments()[5]; + FloatTrainingDataConsumer floatTrainingDataConsumer = (FloatTrainingDataConsumer) invocationOnMock.getArguments()[5]; ActionListener listener = (ActionListener) invocationOnMock.getArguments()[6]; Thread thread = new Thread(() -> { try { Thread.sleep(2000); - trainingDataConsumer.accept(vectors); // Transfer some floats + floatTrainingDataConsumer.accept(vectors); // Transfer some floats listener.onResponse(null); } catch (InterruptedException e) { listener.onFailure(null); @@ -114,7 +173,9 @@ public void testTrainingLoadStrategy_load() { NativeMemoryLoadStrategy.TrainingLoadStrategy.getInstance(), null, 0, - 0 + 0, + VectorDataType.FLOAT, + QuantizationConfig.EMPTY ); // Load the allocation. Initially, the memory address should be 0. However, after the readlock is obtained, diff --git a/src/test/java/org/opensearch/knn/index/memory/SharedIndexStateManagerTests.java b/src/test/java/org/opensearch/knn/index/memory/SharedIndexStateManagerTests.java new file mode 100644 index 000000000..7e4b41730 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/memory/SharedIndexStateManagerTests.java @@ -0,0 +1,62 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.memory; + +import org.junit.BeforeClass; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.jni.JNIService; + +import static org.mockito.Mockito.mockStatic; + +public class SharedIndexStateManagerTests extends KNNTestCase { + private static MockedStatic jniServiceMockedStatic; + private final static long TEST_SHARED_TABLE_ADDRESS = 123; + private final static long TEST_INDEX_ADDRESS = 1234; + private final static String TEST_MODEL_ID = "test-model-id"; + private final static KNNEngine TEST_KNN_ENGINE = KNNEngine.DEFAULT; + + @BeforeClass + public static void setUpClass() { + jniServiceMockedStatic = mockStatic(JNIService.class); + jniServiceMockedStatic.when(() -> JNIService.freeSharedIndexState(TEST_SHARED_TABLE_ADDRESS, TEST_KNN_ENGINE)) + .then(invocation -> null); + jniServiceMockedStatic.when(() -> JNIService.initSharedIndexState(TEST_INDEX_ADDRESS, TEST_KNN_ENGINE)) + .thenReturn(TEST_SHARED_TABLE_ADDRESS); + } + + public void testGet_whenNormalWorkfloatApplied_thenSucceed() { + SharedIndexStateManager sharedIndexStateManager = new SharedIndexStateManager(); + SharedIndexState firstSharedIndexStateRetrieved = sharedIndexStateManager.get(TEST_INDEX_ADDRESS, TEST_MODEL_ID, TEST_KNN_ENGINE); + assertEquals(TEST_SHARED_TABLE_ADDRESS, firstSharedIndexStateRetrieved.getSharedIndexStateAddress()); + assertEquals(TEST_MODEL_ID, firstSharedIndexStateRetrieved.getModelId()); + assertEquals(TEST_KNN_ENGINE, firstSharedIndexStateRetrieved.getKnnEngine()); + + SharedIndexState secondSharedIndexStateRetrieved = sharedIndexStateManager.get(TEST_INDEX_ADDRESS, TEST_MODEL_ID, TEST_KNN_ENGINE); + assertEquals(TEST_SHARED_TABLE_ADDRESS, secondSharedIndexStateRetrieved.getSharedIndexStateAddress()); + assertEquals(TEST_MODEL_ID, secondSharedIndexStateRetrieved.getModelId()); + assertEquals(TEST_KNN_ENGINE, secondSharedIndexStateRetrieved.getKnnEngine()); + } + + public void testRelease_whenNormalWorkflowApplied_thenSucceed() { + SharedIndexStateManager sharedIndexStateManager = new SharedIndexStateManager(); + SharedIndexState firstSharedIndexStateRetrieved = sharedIndexStateManager.get(TEST_INDEX_ADDRESS, TEST_MODEL_ID, TEST_KNN_ENGINE); + SharedIndexState secondSharedIndexStateRetrieved = sharedIndexStateManager.get(TEST_INDEX_ADDRESS, TEST_MODEL_ID, TEST_KNN_ENGINE); + + sharedIndexStateManager.release(firstSharedIndexStateRetrieved); + jniServiceMockedStatic.verify(() -> JNIService.freeSharedIndexState(TEST_SHARED_TABLE_ADDRESS, TEST_KNN_ENGINE), Mockito.times(0)); + sharedIndexStateManager.release(secondSharedIndexStateRetrieved); + jniServiceMockedStatic.verify(() -> JNIService.freeSharedIndexState(TEST_SHARED_TABLE_ADDRESS, TEST_KNN_ENGINE), Mockito.times(1)); + } +} diff --git a/src/test/java/org/opensearch/knn/index/memory/SharedIndexStateTests.java b/src/test/java/org/opensearch/knn/index/memory/SharedIndexStateTests.java new file mode 100644 index 000000000..40c19f0b2 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/memory/SharedIndexStateTests.java @@ -0,0 +1,29 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.memory; + +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.engine.KNNEngine; + +public class SharedIndexStateTests extends KNNTestCase { + + private static final String TEST_MODEL_ID = "test-model"; + private static final long TEST_SHARED_INDEX_STATE_ADDRESS = 22L; + private static final KNNEngine TEST_KNN_ENGINE = KNNEngine.DEFAULT; + + public void testSharedIndexState() { + SharedIndexState sharedIndexState = new SharedIndexState(TEST_SHARED_INDEX_STATE_ADDRESS, TEST_MODEL_ID, TEST_KNN_ENGINE); + assertEquals(TEST_MODEL_ID, sharedIndexState.getModelId()); + assertEquals(TEST_SHARED_INDEX_STATE_ADDRESS, sharedIndexState.getSharedIndexStateAddress()); + assertEquals(TEST_KNN_ENGINE, sharedIndexState.getKnnEngine()); + } +} diff --git a/src/test/java/org/opensearch/knn/index/quantizationservice/QuantizationServiceTests.java b/src/test/java/org/opensearch/knn/index/quantizationservice/QuantizationServiceTests.java new file mode 100644 index 000000000..690391dbd --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/quantizationservice/QuantizationServiceTests.java @@ -0,0 +1,159 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.quantizationservice; + +import org.opensearch.knn.KNNTestCase; +import org.junit.Before; + +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.vectorvalues.KNNVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNVectorValuesFactory; +import org.opensearch.knn.index.vectorvalues.TestVectorValues; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; +import org.opensearch.knn.quantization.models.quantizationOutput.QuantizationOutput; +import org.opensearch.knn.quantization.models.quantizationParams.ScalarQuantizationParams; +import org.opensearch.knn.quantization.models.quantizationState.MultiBitScalarQuantizationState; +import org.opensearch.knn.quantization.models.quantizationState.OneBitScalarQuantizationState; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; +import java.io.IOException; +import java.util.List; + +public class QuantizationServiceTests extends KNNTestCase { + private QuantizationService quantizationService; + private KNNVectorValues knnVectorValues; + + @Before + public void setUp() throws Exception { + super.setUp(); + quantizationService = QuantizationService.getInstance(); + + // Predefined float vectors for testing + List floatVectors = List.of( + new float[] { 1.0f, 2.0f, 3.0f }, + new float[] { 4.0f, 5.0f, 6.0f }, + new float[] { 7.0f, 8.0f, 9.0f } + ); + + // Use the predefined vectors to create KNNVectorValues + knnVectorValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.FLOAT, + new TestVectorValues.PreDefinedFloatVectorValues(floatVectors) + ); + } + + public void testTrain_oneBitQuantizer_success() throws IOException { + ScalarQuantizationParams oneBitParams = new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); + QuantizationState quantizationState = quantizationService.train(oneBitParams, knnVectorValues, knnVectorValues.totalLiveDocs()); + + assertTrue(quantizationState instanceof OneBitScalarQuantizationState); + OneBitScalarQuantizationState oneBitState = (OneBitScalarQuantizationState) quantizationState; + + // Validate the mean thresholds obtained from the training + float[] thresholds = oneBitState.getMeanThresholds(); + assertNotNull("Thresholds should not be null", thresholds); + assertEquals("Thresholds array length should match the dimension", 3, thresholds.length); + + // Example expected thresholds based on the provided vectors + assertArrayEquals(new float[] { 4.0f, 5.0f, 6.0f }, thresholds, 0.1f); + } + + public void testTrain_twoBitQuantizer_success() throws IOException { + ScalarQuantizationParams twoBitParams = new ScalarQuantizationParams(ScalarQuantizationType.TWO_BIT); + QuantizationState quantizationState = quantizationService.train(twoBitParams, knnVectorValues, knnVectorValues.totalLiveDocs()); + + assertTrue(quantizationState instanceof MultiBitScalarQuantizationState); + MultiBitScalarQuantizationState multiBitState = (MultiBitScalarQuantizationState) quantizationState; + + // Validate the thresholds obtained from the training + float[][] thresholds = multiBitState.getThresholds(); + assertNotNull("Thresholds should not be null", thresholds); + assertEquals("Number of bits should match the number of rows", 2, thresholds.length); + assertEquals("Thresholds array length should match the dimension", 3, thresholds[0].length); + + // // Example expected thresholds for two-bit quantization + float[][] expectedThresholds = { + { 3.1835034f, 4.1835036f, 5.1835036f }, // First bit level + { 4.816497f, 5.816497f, 6.816497f } // Second bit level + }; + for (int i = 0; i < thresholds.length; i++) { + assertArrayEquals(expectedThresholds[i], thresholds[i], 0.1f); + } + } + + public void testTrain_fourBitQuantizer_success() throws IOException { + ScalarQuantizationParams fourBitParams = new ScalarQuantizationParams(ScalarQuantizationType.FOUR_BIT); + QuantizationState quantizationState = quantizationService.train(fourBitParams, knnVectorValues, knnVectorValues.totalLiveDocs()); + + assertTrue(quantizationState instanceof MultiBitScalarQuantizationState); + MultiBitScalarQuantizationState multiBitState = (MultiBitScalarQuantizationState) quantizationState; + + // Validate the thresholds obtained from the training + float[][] thresholds = multiBitState.getThresholds(); + assertNotNull("Thresholds should not be null", thresholds); + assertEquals("Number of bits should match the number of rows", 4, thresholds.length); + assertEquals("Thresholds array length should match the dimension", 3, thresholds[0].length); + + // // Example expected thresholds for four-bit quantization + float[][] expectedThresholds = { + { 2.530306f, 3.530306f, 4.530306f }, // First bit level + { 3.510102f, 4.5101023f, 5.5101023f }, // Second bit level + { 4.489898f, 5.489898f, 6.489898f }, // Third bit level + { 5.469694f, 6.469694f, 7.469694f } // Fourth bit level + }; + for (int i = 0; i < thresholds.length; i++) { + assertArrayEquals(expectedThresholds[i], thresholds[i], 0.1f); + } + } + + public void testQuantize_oneBitQuantizer_success() throws IOException { + ScalarQuantizationParams oneBitParams = new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); + QuantizationState quantizationState = quantizationService.train(oneBitParams, knnVectorValues, knnVectorValues.totalLiveDocs()); + + QuantizationOutput quantizationOutput = quantizationService.createQuantizationOutput(oneBitParams); + + byte[] quantizedVector = quantizationService.quantize(quantizationState, new float[] { 1.0f, 2.0f, 3.0f }, quantizationOutput); + + assertNotNull("Quantized vector should not be null", quantizedVector); + + // Expected quantized vector values for one-bit quantization (packed bits) + byte[] expectedQuantizedVector = new byte[] { 0 }; // 00000000 (all bits are 0) + assertArrayEquals(expectedQuantizedVector, quantizedVector); + } + + public void testQuantize_twoBitQuantizer_success() throws IOException { + ScalarQuantizationParams twoBitParams = new ScalarQuantizationParams(ScalarQuantizationType.TWO_BIT); + QuantizationState quantizationState = quantizationService.train(twoBitParams, knnVectorValues, knnVectorValues.totalLiveDocs()); + QuantizationOutput quantizationOutput = quantizationService.createQuantizationOutput(twoBitParams); + byte[] quantizedVector = quantizationService.quantize(quantizationState, new float[] { 4.0f, 5.0f, 6.0f }, quantizationOutput); + + assertNotNull("Quantized vector should not be null", quantizedVector); + + // Expected quantized vector values for two-bit quantization (packed bits) + byte[] expectedQuantizedVector = new byte[] { (byte) 0b11100000 }; + assertArrayEquals(expectedQuantizedVector, quantizedVector); + } + + public void testQuantize_fourBitQuantizer_success() throws IOException { + ScalarQuantizationParams fourBitParams = new ScalarQuantizationParams(ScalarQuantizationType.FOUR_BIT); + QuantizationState quantizationState = quantizationService.train(fourBitParams, knnVectorValues, knnVectorValues.totalLiveDocs()); + QuantizationOutput quantizationOutput = quantizationService.createQuantizationOutput(fourBitParams); + + byte[] quantizedVector = quantizationService.quantize(quantizationState, new float[] { 7.0f, 8.0f, 9.0f }, quantizationOutput); + + assertNotNull("Quantized vector should not be null", quantizedVector); + + // Expected quantized vector values for four-bit quantization (packed bits) + byte[] expectedQuantizedVector = new byte[] { (byte) 0xFF, (byte) 0xF0 }; + assertArrayEquals(expectedQuantizedVector, quantizedVector); + } + + public void testQuantize_whenInvalidInput_thenThrows() throws IOException { + ScalarQuantizationParams oneBitParams = new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); + QuantizationState quantizationState = quantizationService.train(oneBitParams, knnVectorValues, knnVectorValues.totalLiveDocs()); + QuantizationOutput quantizationOutput = quantizationService.createQuantizationOutput(oneBitParams); + assertThrows(IllegalArgumentException.class, () -> quantizationService.quantize(quantizationState, null, quantizationOutput)); + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/ExactSearcherTests.java b/src/test/java/org/opensearch/knn/index/query/ExactSearcherTests.java new file mode 100644 index 000000000..a4b853560 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/ExactSearcherTests.java @@ -0,0 +1,194 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query; + +import lombok.SneakyThrows; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.FieldInfos; +import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.index.SegmentCommitInfo; +import org.apache.lucene.index.SegmentInfo; +import org.apache.lucene.index.SegmentReader; +import org.apache.lucene.search.Sort; +import org.apache.lucene.store.FSDirectory; +import org.apache.lucene.util.StringHelper; +import org.apache.lucene.util.Version; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.codec.KNNCodecVersion; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.vectorvalues.KNNFloatVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNVectorValuesFactory; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.opensearch.knn.KNNRestTestCase.FIELD_NAME; +import static org.opensearch.knn.KNNRestTestCase.INDEX_NAME; +import static org.opensearch.knn.common.KNNConstants.INDEX_DESCRIPTION_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.PARAMETERS; +import static org.opensearch.knn.common.KNNConstants.SPACE_TYPE; + +public class ExactSearcherTests extends KNNTestCase { + + private static final String SEGMENT_NAME = "0"; + + @SneakyThrows + public void testExactSearch_whenSegmentHasNoVectorField_thenNoDocsReturned() { + final float[] queryVector = new float[] { 0.1f, 2.0f, 3.0f }; + final KNNQuery query = KNNQuery.builder().field(FIELD_NAME).queryVector(queryVector).k(10).indexName(INDEX_NAME).build(); + + final ExactSearcher.ExactSearcherContext.ExactSearcherContextBuilder exactSearcherContextBuilder = + ExactSearcher.ExactSearcherContext.builder().knnQuery(query); + + ExactSearcher exactSearcher = new ExactSearcher(null); + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + final SegmentReader reader = mock(SegmentReader.class); + when(leafReaderContext.reader()).thenReturn(reader); + + final FieldInfos fieldInfos = mock(FieldInfos.class); + when(reader.getFieldInfos()).thenReturn(fieldInfos); + when(fieldInfos.fieldInfo(query.getField())).thenReturn(null); + Map docIds = exactSearcher.searchLeaf(leafReaderContext, exactSearcherContextBuilder.build()); + Mockito.verify(fieldInfos).fieldInfo(query.getField()); + Mockito.verify(reader).getFieldInfos(); + Mockito.verify(leafReaderContext).reader(); + assertEquals(0, docIds.size()); + } + + @SneakyThrows + public void testRadialSearchExactSearch_whenSegmentHasNoVectorField_thenNoDocsReturned() { + final float[] queryVector = new float[] { 0.1f, 2.0f, 3.0f }; + KNNQuery.Context context = new KNNQuery.Context(10); + final KNNQuery query = KNNQuery.builder() + .field(FIELD_NAME) + .queryVector(queryVector) + .context(context) + .radius(1.0f) + .indexName(INDEX_NAME) + .build(); + + final ExactSearcher.ExactSearcherContext.ExactSearcherContextBuilder exactSearcherContextBuilder = + ExactSearcher.ExactSearcherContext.builder().knnQuery(query); + + ExactSearcher exactSearcher = new ExactSearcher(null); + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + final SegmentReader reader = mock(SegmentReader.class); + when(leafReaderContext.reader()).thenReturn(reader); + + final FieldInfos fieldInfos = mock(FieldInfos.class); + when(reader.getFieldInfos()).thenReturn(fieldInfos); + when(fieldInfos.fieldInfo(query.getField())).thenReturn(null); + Map docIds = exactSearcher.searchLeaf(leafReaderContext, exactSearcherContextBuilder.build()); + Mockito.verify(fieldInfos).fieldInfo(query.getField()); + Mockito.verify(reader).getFieldInfos(); + Mockito.verify(leafReaderContext).reader(); + assertEquals(0, docIds.size()); + } + + @SneakyThrows + public void testRadialSearch_whenNoEngineFiles_thenSuccess() { + try (MockedStatic valuesFactoryMockedStatic = Mockito.mockStatic(KNNVectorValuesFactory.class)) { + final float[] queryVector = new float[] { 0.1f, 2.0f, 3.0f }; + final SpaceType spaceType = randomFrom(SpaceType.L2, SpaceType.INNER_PRODUCT); + final List dataVectors = Arrays.asList( + new float[] { 11.0f, 12.0f, 13.0f }, + new float[] { 14.0f, 15.0f, 16.0f }, + new float[] { 17.0f, 18.0f, 19.0f } + ); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .collect(Collectors.toList()); + final Float score = Collections.min(expectedScores); + final float radius = KNNEngine.FAISS.scoreToRadialThreshold(score, spaceType); + final int maxResults = 1000; + final KNNQuery.Context context = mock(KNNQuery.Context.class); + when(context.getMaxResultWindow()).thenReturn(maxResults); + KNNWeight.initialize(null); + + final KNNQuery query = KNNQuery.builder() + .field(FIELD_NAME) + .queryVector(queryVector) + .radius(radius) + .indexName(INDEX_NAME) + .vectorDataType(VectorDataType.FLOAT) + .context(context) + .build(); + + final ExactSearcher.ExactSearcherContext.ExactSearcherContextBuilder exactSearcherContextBuilder = + ExactSearcher.ExactSearcherContext.builder() + // setting to true, so that if quantization details are present we want to do search on the quantized + // vectors as this flow is used in first pass of search. + .useQuantizedVectorsForSearch(false) + .knnQuery(query); + + ExactSearcher exactSearcher = new ExactSearcher(null); + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + final SegmentReader reader = mock(SegmentReader.class); + when(leafReaderContext.reader()).thenReturn(reader); + + final FSDirectory directory = mock(FSDirectory.class); + when(reader.directory()).thenReturn(directory); + final SegmentInfo segmentInfo = new SegmentInfo( + directory, + Version.LATEST, + Version.LATEST, + SEGMENT_NAME, + 100, + false, + false, + KNNCodecVersion.current().getDefaultCodecDelegate(), + Map.of(), + new byte[StringHelper.ID_LENGTH], + Map.of(), + Sort.RELEVANCE + ); + segmentInfo.setFiles(Set.of()); + final SegmentCommitInfo segmentCommitInfo = new SegmentCommitInfo(segmentInfo, 0, 0, 0, 0, 0, new byte[StringHelper.ID_LENGTH]); + when(reader.getSegmentInfo()).thenReturn(segmentCommitInfo); + + final Path path = mock(Path.class); + when(directory.getDirectory()).thenReturn(path); + final FieldInfos fieldInfos = mock(FieldInfos.class); + final FieldInfo fieldInfo = mock(FieldInfo.class); + when(reader.getFieldInfos()).thenReturn(fieldInfos); + when(fieldInfos.fieldInfo(any())).thenReturn(fieldInfo); + when(fieldInfo.attributes()).thenReturn( + Map.of( + SPACE_TYPE, + spaceType.getValue(), + KNN_ENGINE, + KNNEngine.FAISS.getName(), + PARAMETERS, + String.format(Locale.ROOT, "{\"%s\":\"%s\"}", INDEX_DESCRIPTION_PARAMETER, "HNSW32") + ) + ); + when(fieldInfo.getAttribute(SPACE_TYPE)).thenReturn(spaceType.getValue()); + KNNFloatVectorValues floatVectorValues = mock(KNNFloatVectorValues.class); + valuesFactoryMockedStatic.when(() -> KNNVectorValuesFactory.getVectorValues(fieldInfo, reader)).thenReturn(floatVectorValues); + when(floatVectorValues.nextDoc()).thenReturn(0, 1, 2, NO_MORE_DOCS); + when(floatVectorValues.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); + final Map integerFloatMap = exactSearcher.searchLeaf(leafReaderContext, exactSearcherContextBuilder.build()); + assertEquals(integerFloatMap.size(), dataVectors.size()); + assertEquals(expectedScores, new ArrayList<>(integerFloatMap.values())); + } + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/FilterIdsSelectorTests.java b/src/test/java/org/opensearch/knn/index/query/FilterIdsSelectorTests.java new file mode 100644 index 000000000..02b1553a3 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/FilterIdsSelectorTests.java @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.query; + +import lombok.SneakyThrows; +import org.apache.lucene.util.BitSetIterator; +import org.apache.lucene.util.FixedBitSet; +import org.apache.lucene.util.SparseFixedBitSet; +import org.opensearch.knn.KNNTestCase; + +public class FilterIdsSelectorTests extends KNNTestCase { + + @SneakyThrows + public void testGetIdSelectorTypeWithFixedBitSet() { + FixedBitSet bits = new FixedBitSet(101); + for (int i = 1; i <= 100; i++) { + bits.set(i); + } + FilterIdsSelector idsSelector = FilterIdsSelector.getFilterIdSelector(bits, bits.cardinality()); + assertEquals(idsSelector.getFilterType(), FilterIdsSelector.FilterIdsSelectorType.BITMAP); + assertArrayEquals(bits.getBits(), idsSelector.filterIds); + } + + @SneakyThrows + public void testGetIdSelectorTypeWithSparseBitSetHigh() { + SparseFixedBitSet bits = new SparseFixedBitSet(101); + for (int i = 1; i <= 100; i++) { + bits.set(i); + } + FilterIdsSelector idsSelector = FilterIdsSelector.getFilterIdSelector(bits, bits.cardinality()); + assertEquals(idsSelector.getFilterType(), FilterIdsSelector.FilterIdsSelectorType.BITMAP); + FixedBitSet fixedBitSet = new FixedBitSet(bits.length()); + BitSetIterator sparseBitSetIterator = new BitSetIterator(bits, 101); + fixedBitSet.or(sparseBitSetIterator); + assertArrayEquals(fixedBitSet.getBits(), idsSelector.filterIds); + } + + @SneakyThrows + public void testGetIdSelectorTypeWithSparseBitSetLow() { + int maxDoc = (Integer.MAX_VALUE) / 2; + SparseFixedBitSet bits = new SparseFixedBitSet(maxDoc); + long array[] = new long[100]; + for (int i = maxDoc - 100, idx = 0; i < maxDoc; i++) { + bits.set(i); + array[idx++] = i; + } + FilterIdsSelector idsSelector = FilterIdsSelector.getFilterIdSelector(bits, bits.cardinality()); + assertEquals(idsSelector.getFilterType(), FilterIdsSelector.FilterIdsSelectorType.BATCH); + assertArrayEquals(array, idsSelector.filterIds); + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/InvalidSearchQueryIT.java b/src/test/java/org/opensearch/knn/index/query/InvalidSearchQueryIT.java new file mode 100644 index 000000000..071162789 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/InvalidSearchQueryIT.java @@ -0,0 +1,156 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.query; + +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; +import lombok.AllArgsConstructor; +import lombok.SneakyThrows; +import org.opensearch.client.Request; +import org.opensearch.client.ResponseException; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.knn.KNNRestTestCase; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; + +import static com.carrotsearch.randomizedtesting.RandomizedTest.$; +import static com.carrotsearch.randomizedtesting.RandomizedTest.$$; + +@AllArgsConstructor +public class InvalidSearchQueryIT extends KNNRestTestCase { + + private String description; + private XContentBuilder xContentBuilder; + + @ParametersFactory(argumentFormatting = "description:%1$s; request:%2$s, expectedexception:%3$s") + public static Collection parameters() throws IOException { + /** + * Valid query: + * { + * query: { + * knn: { + * test_field: { + * vector: [1.0, 2.0], + * k: 1, + * method_parameter: { + * ef_search: 10 + * } + * } + * } + * } + * } + */ + + return Arrays.asList( + $$( + $( + "Empty method_parameter", + XContentFactory.jsonBuilder() + .startObject() + .startObject("query") + .startObject("knn") + .startObject(FIELD_NAME) + .field("vector", new float[] { 1.0f, 2.0f }) + .field("k", 1) + .startObject("method_parameter") + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + ), + $( + "ef_search string", + XContentFactory.jsonBuilder() + .startObject() + .startObject("query") + .startObject("knn") + .startObject(FIELD_NAME) + .field("vector", new float[] { 1.0f, 2.0f }) + .field("k", 1) + .startObject("method_parameter") + .field("ef_search", "string value") + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + ), + $( + "ef_search less than 0", + XContentFactory.jsonBuilder() + .startObject() + .startObject("query") + .startObject("knn") + .startObject(FIELD_NAME) + .field("vector", new float[] { 1.0f, 2.0f }) + .field("k", 1) + .startObject("method_parameter") + .field("ef_search", -1) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + ), + $( + "nprobes string", + XContentFactory.jsonBuilder() + .startObject() + .startObject("query") + .startObject("knn") + .startObject(FIELD_NAME) + .field("vector", new float[] { 1.0f, 2.0f }) + .field("k", 1) + .startObject("method_parameter") + .field("nprobes", "string value") + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + ), + $( + "nprobes less than 0", + XContentFactory.jsonBuilder() + .startObject() + .startObject("query") + .startObject("knn") + .startObject(FIELD_NAME) + .field("vector", new float[] { 1.0f, 2.0f }) + .field("k", 1) + .startObject("method_parameter") + .field("nprobes", -10) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + ) + ) + ); + } + + @SneakyThrows + public void testEndToEnd_whenMethodIsHNSWFlat_thenSucceed() { + Request request = new Request("POST", "/dummy_index/_search"); + request.setJsonEntity(xContentBuilder.toString()); + + request.addParameter("size", Integer.toString(10)); + request.addParameter("explain", Boolean.toString(true)); + request.addParameter("search_type", "query_then_fetch"); + + expectThrows(ResponseException.class, () -> client().performRequest(request)); + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/KNNQueryBuilderInvalidParamsTests.java b/src/test/java/org/opensearch/knn/index/query/KNNQueryBuilderInvalidParamsTests.java new file mode 100644 index 000000000..29f2d2368 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/KNNQueryBuilderInvalidParamsTests.java @@ -0,0 +1,108 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query; + +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; +import lombok.AllArgsConstructor; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.query.rescore.RescoreContext; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; + +import static com.carrotsearch.randomizedtesting.RandomizedTest.$; +import static com.carrotsearch.randomizedtesting.RandomizedTest.$$; + +@AllArgsConstructor +public class KNNQueryBuilderInvalidParamsTests extends KNNTestCase { + + private static final float[] QUERY_VECTOR = new float[] { 1.2f, 2.3f, 4.5f }; + private static final String FIELD_NAME = "test_vector"; + + private String description; + private String expectedMessage; + private KNNQueryBuilder.Builder knnQueryBuilderBuilder; + + @ParametersFactory(argumentFormatting = "description:%1$s; expectedMessage:%2$s; querybuilder:%3$s") + public static Collection invalidParameters() { + return Arrays.asList( + $$( + $("fieldName absent", "[knn] requires fieldName", KNNQueryBuilder.builder().k(1).vector(QUERY_VECTOR)), + $("vector absent", "[knn] requires query vector", KNNQueryBuilder.builder().k(1).fieldName(FIELD_NAME)), + $( + "vector empty", + "[knn] query vector is empty", + KNNQueryBuilder.builder().k(1).fieldName(FIELD_NAME).vector(new float[] {}) + ), + $( + "Neither knn nor radial search", + "[knn] requires exactly one of k, distance or score to be set", + KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(QUERY_VECTOR) + ), + $( + "max distance and k present", + "[knn] requires exactly one of k, distance or score to be set", + KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(QUERY_VECTOR).k(1).maxDistance(10f) + ), + $( + "min_score and k present", + "[knn] requires exactly one of k, distance or score to be set", + KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(QUERY_VECTOR).k(1).minScore(1.0f) + ), + $( + "max_dist and min_score present", + "[knn] requires exactly one of k, distance or score to be set", + KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(QUERY_VECTOR).maxDistance(1.0f).minScore(1.0f) + ), + $( + "max_dist, k and min_score present", + "[knn] requires exactly one of k, distance or score to be set", + KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(QUERY_VECTOR).k(1).maxDistance(1.0f).minScore(1.0f) + ), + $( + "-ve k value", + "[knn] requires k to be in the range (0, 10000]", + KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(QUERY_VECTOR).k(-1) + ), + $( + "k value greater than max", + "[knn] requires k to be in the range (0, 10000]", + KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(QUERY_VECTOR).k(10001) + ), + $( + "efSearch 0", + "[knn] errors in method parameter [Validation Failed: 1: Validation Failed: 1: ef_search should be greater than 0;;]", + KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(QUERY_VECTOR).methodParameters(Map.of("ef_search", 0)).k(10) + ), + $( + "efSearch -ve", + "[knn] errors in method parameter [Validation Failed: 1: Validation Failed: 1: ef_search should be greater than 0;;]", + KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(QUERY_VECTOR).methodParameters(Map.of("ef_search", -10)).k(10) + ), + $( + "min score less than 0", + "[knn] requires minScore to be greater than 0", + KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(QUERY_VECTOR).minScore(-1f) + ), + $( + "Rescore context", + " cannot be less than", + KNNQueryBuilder.builder() + .rescoreContext(RescoreContext.builder().oversampleFactor(RescoreContext.MIN_OVERSAMPLE_FACTOR - 1).build()) + .fieldName(FIELD_NAME) + .vector(QUERY_VECTOR) + .k(1) + ) + ) + ); + } + + public void testInvalidBuilder() { + Throwable exception = expectThrows(IllegalArgumentException.class, () -> knnQueryBuilderBuilder.build()); + assertTrue(exception.getMessage(), exception.getMessage().contains(expectedMessage)); + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/KNNQueryBuilderTests.java b/src/test/java/org/opensearch/knn/index/query/KNNQueryBuilderTests.java new file mode 100644 index 000000000..b28b790d1 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/KNNQueryBuilderTests.java @@ -0,0 +1,1076 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query; + +import com.google.common.collect.ImmutableMap; +import lombok.SneakyThrows; +import org.apache.lucene.search.FloatVectorSimilarityQuery; +import org.apache.lucene.search.KnnFloatVectorQuery; +import org.apache.lucene.search.MatchNoDocsQuery; +import org.apache.lucene.search.Query; +import org.junit.Before; +import org.opensearch.Version; +import org.opensearch.cluster.ClusterModule; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.common.settings.ClusterSettings; +import org.opensearch.core.common.io.stream.NamedWriteableAwareStreamInput; +import org.opensearch.core.common.io.stream.NamedWriteableRegistry; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.index.Index; +import org.opensearch.index.IndexSettings; +import org.opensearch.index.mapper.NumberFieldMapper; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.QueryBuilders; +import org.opensearch.index.query.QueryRewriteContext; +import org.opensearch.index.query.QueryShardContext; +import org.opensearch.index.query.TermQueryBuilder; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.index.mapper.KNNMappingConfig; +import org.opensearch.knn.index.mapper.KNNVectorFieldType; +import org.opensearch.knn.index.mapper.Mode; +import org.opensearch.knn.index.query.rescore.RescoreContext; +import org.opensearch.knn.index.util.KNNClusterUtil; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.indices.ModelDao; +import org.opensearch.knn.indices.ModelMetadata; +import org.opensearch.knn.indices.ModelState; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import static java.util.Collections.emptyMap; +import static org.hamcrest.Matchers.instanceOf; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.opensearch.knn.index.KNNClusterTestUtils.mockClusterService; +import static org.opensearch.knn.index.engine.KNNEngine.ENGINES_SUPPORTING_RADIAL_SEARCH; + +public class KNNQueryBuilderTests extends KNNTestCase { + + private static final String FIELD_NAME = "myvector"; + private static final int K = 1; + private static final int EF_SEARCH = 10; + private static final Map HNSW_METHOD_PARAMS = Map.of("ef_search", EF_SEARCH); + private static final Float MAX_DISTANCE = 1.0f; + private static final Float MIN_SCORE = 0.5f; + private static final TermQueryBuilder TERM_QUERY = QueryBuilders.termQuery("field", "value"); + private static final float[] QUERY_VECTOR = new float[] { 1.0f, 2.0f, 3.0f, 4.0f }; + protected static final String TEXT_FIELD_NAME = "some_field"; + protected static final String TEXT_VALUE = "some_value"; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + ClusterSettings clusterSettings = mock(ClusterSettings.class); + when(clusterService.getClusterSettings()).thenReturn(clusterSettings); + KNNSettings.state().setClusterService(clusterService); + } + + public void testInvalidK() { + float[] queryVector = { 1.0f, 1.0f }; + + /** + * -ve k + */ + expectThrows(IllegalArgumentException.class, () -> new KNNQueryBuilder(FIELD_NAME, queryVector, -K)); + + /** + * zero k + */ + expectThrows(IllegalArgumentException.class, () -> new KNNQueryBuilder(FIELD_NAME, queryVector, 0)); + + /** + * k > KNNQueryBuilder.K_MAX + */ + expectThrows(IllegalArgumentException.class, () -> new KNNQueryBuilder(FIELD_NAME, queryVector, KNNQueryBuilder.K_MAX + K)); + } + + public void testInvalidDistance() { + float[] queryVector = { 1.0f, 1.0f }; + /** + * null distance + */ + expectThrows( + IllegalArgumentException.class, + () -> KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(queryVector).maxDistance(null).build() + ); + } + + public void testInvalidScore() { + float[] queryVector = { 1.0f, 1.0f }; + /** + * null min_score + */ + expectThrows( + IllegalArgumentException.class, + () -> KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(queryVector).minScore(null).build() + ); + + /** + * negative min_score + */ + expectThrows( + IllegalArgumentException.class, + () -> KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(queryVector).minScore(-1.0f).build() + ); + + /** + * min_score = 0 + */ + expectThrows( + IllegalArgumentException.class, + () -> KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(queryVector).minScore(0.0f).build() + ); + } + + public void testEmptyVector() { + /** + * null query vector + */ + float[] queryVector = null; + expectThrows(IllegalArgumentException.class, () -> new KNNQueryBuilder(FIELD_NAME, queryVector, K)); + + /** + * empty query vector + */ + float[] queryVector1 = {}; + expectThrows(IllegalArgumentException.class, () -> new KNNQueryBuilder(FIELD_NAME, queryVector1, K)); + + /** + * null query vector with distance + */ + float[] queryVector2 = null; + expectThrows( + IllegalArgumentException.class, + () -> KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(queryVector2).maxDistance(MAX_DISTANCE).build() + ); + + /** + * empty query vector with distance + */ + float[] queryVector3 = {}; + expectThrows( + IllegalArgumentException.class, + () -> KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(queryVector3).maxDistance(MAX_DISTANCE).build() + ); + } + + @Override + protected NamedWriteableRegistry writableRegistry() { + final List entries = ClusterModule.getNamedWriteables(); + entries.add(new NamedWriteableRegistry.Entry(QueryBuilder.class, KNNQueryBuilder.NAME, KNNQueryBuilder::new)); + entries.add(new NamedWriteableRegistry.Entry(QueryBuilder.class, TermQueryBuilder.NAME, TermQueryBuilder::new)); + return new NamedWriteableRegistry(entries); + } + + public void testDoToQuery_Normal() throws Exception { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + KNNQueryBuilder knnQueryBuilder = new KNNQueryBuilder(FIELD_NAME, queryVector, K); + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.FLOAT); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(getDefaultKNNMethodContext(), 4)); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + KNNQuery query = (KNNQuery) knnQueryBuilder.doToQuery(mockQueryShardContext); + assertEquals(knnQueryBuilder.getK(), query.getK()); + assertEquals(knnQueryBuilder.fieldName(), query.getField()); + assertEquals(knnQueryBuilder.vector(), query.getQueryVector()); + } + + @SneakyThrows + public void testDoToQuery_whenNormal_whenDoRadiusSearch_whenDistanceThreshold_thenSucceed() { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .maxDistance(MAX_DISTANCE) + .build(); + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.FLOAT); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + MethodComponentContext methodComponentContext = new MethodComponentContext( + org.opensearch.knn.common.KNNConstants.METHOD_HNSW, + ImmutableMap.of() + ); + KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.LUCENE, SpaceType.L2, methodComponentContext); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(knnMethodContext, 4)); + FloatVectorSimilarityQuery query = (FloatVectorSimilarityQuery) knnQueryBuilder.doToQuery(mockQueryShardContext); + float resultSimilarity = KNNEngine.LUCENE.distanceToRadialThreshold(MAX_DISTANCE, SpaceType.L2); + + assertTrue(query.toString().contains("resultSimilarity=" + resultSimilarity)); + assertTrue( + query.toString() + .contains( + "traversalSimilarity=" + + org.opensearch.knn.common.KNNConstants.DEFAULT_LUCENE_RADIAL_SEARCH_TRAVERSAL_SIMILARITY_RATIO * resultSimilarity + ) + ); + } + + @SneakyThrows + public void testDoToQuery_whenNormal_whenDoRadiusSearch_whenScoreThreshold_thenSucceed() { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(queryVector).minScore(MIN_SCORE).build(); + + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.FLOAT); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + MethodComponentContext methodComponentContext = new MethodComponentContext( + org.opensearch.knn.common.KNNConstants.METHOD_HNSW, + ImmutableMap.of() + ); + KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.LUCENE, SpaceType.L2, methodComponentContext); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(knnMethodContext, 4)); + FloatVectorSimilarityQuery query = (FloatVectorSimilarityQuery) knnQueryBuilder.doToQuery(mockQueryShardContext); + assertTrue(query.toString().contains("resultSimilarity=" + 0.5f)); + } + + @SneakyThrows + public void testDoToQuery_whenDoRadiusSearch_whenPassNegativeDistance_whenSupportedSpaceType_thenSucceed() { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + float negativeDistance = -1.0f; + + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .maxDistance(negativeDistance) + .build(); + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.FLOAT); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + MethodComponentContext methodComponentContext = new MethodComponentContext( + org.opensearch.knn.common.KNNConstants.METHOD_HNSW, + ImmutableMap.of() + ); + KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.FAISS, SpaceType.INNER_PRODUCT, methodComponentContext); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(knnMethodContext, 4)); + IndexSettings indexSettings = mock(IndexSettings.class); + when(mockQueryShardContext.getIndexSettings()).thenReturn(indexSettings); + when(indexSettings.getMaxResultWindow()).thenReturn(1000); + + KNNQuery query = (KNNQuery) knnQueryBuilder.doToQuery(mockQueryShardContext); + + assertEquals(negativeDistance, query.getRadius(), 0); + } + + public void testDoToQuery_whenDoRadiusSearch_whenPassNegativeDistance_whenUnSupportedSpaceType_thenException() { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + float negativeDistance = -1.0f; + + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .maxDistance(negativeDistance) + .build(); + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.FLOAT); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + MethodComponentContext methodComponentContext = new MethodComponentContext( + org.opensearch.knn.common.KNNConstants.METHOD_HNSW, + ImmutableMap.of() + ); + KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.FAISS, SpaceType.L2, methodComponentContext); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(knnMethodContext, 4)); + IndexSettings indexSettings = mock(IndexSettings.class); + when(mockQueryShardContext.getIndexSettings()).thenReturn(indexSettings); + when(indexSettings.getMaxResultWindow()).thenReturn(1000); + + expectThrows(IllegalArgumentException.class, () -> knnQueryBuilder.doToQuery(mockQueryShardContext)); + } + + @SneakyThrows + public void testDoToQuery_whenDoRadiusSearch_whenPassScoreMoreThanOne_whenSupportedSpaceType_thenSucceed() { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + float score = 5f; + + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(queryVector).minScore(score).build(); + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.FLOAT); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + MethodComponentContext methodComponentContext = new MethodComponentContext( + org.opensearch.knn.common.KNNConstants.METHOD_HNSW, + ImmutableMap.of() + ); + KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.FAISS, SpaceType.INNER_PRODUCT, methodComponentContext); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(knnMethodContext, 4)); + IndexSettings indexSettings = mock(IndexSettings.class); + when(mockQueryShardContext.getIndexSettings()).thenReturn(indexSettings); + when(indexSettings.getMaxResultWindow()).thenReturn(1000); + + KNNQuery query = (KNNQuery) knnQueryBuilder.doToQuery(mockQueryShardContext); + + assertEquals(1 - score, query.getRadius(), 0); + } + + public void testDoToQuery_whenDoRadiusSearch_whenPassScoreMoreThanOne_whenUnsupportedSpaceType_thenException() { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + float score = 5f; + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(queryVector).minScore(score).build(); + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.FLOAT); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + MethodComponentContext methodComponentContext = new MethodComponentContext( + org.opensearch.knn.common.KNNConstants.METHOD_HNSW, + ImmutableMap.of() + ); + KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.FAISS, SpaceType.L2, methodComponentContext); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(knnMethodContext, 4)); + IndexSettings indexSettings = mock(IndexSettings.class); + when(mockQueryShardContext.getIndexSettings()).thenReturn(indexSettings); + when(indexSettings.getMaxResultWindow()).thenReturn(1000); + + expectThrows(IllegalArgumentException.class, () -> knnQueryBuilder.doToQuery(mockQueryShardContext)); + } + + @SneakyThrows + public void testDoToQuery_whenPassNegativeDistance_whenSupportedSpaceType_thenSucceed() { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + float negativeDistance = -1.0f; + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .maxDistance(negativeDistance) + .build(); + + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.FLOAT); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + MethodComponentContext methodComponentContext = new MethodComponentContext( + org.opensearch.knn.common.KNNConstants.METHOD_HNSW, + ImmutableMap.of() + ); + KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.FAISS, SpaceType.INNER_PRODUCT, methodComponentContext); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(knnMethodContext, 4)); + IndexSettings indexSettings = mock(IndexSettings.class); + when(mockQueryShardContext.getIndexSettings()).thenReturn(indexSettings); + when(indexSettings.getMaxResultWindow()).thenReturn(1000); + + KNNQuery query = (KNNQuery) knnQueryBuilder.doToQuery(mockQueryShardContext); + + assertEquals(negativeDistance, query.getRadius(), 0); + } + + public void testDoToQuery_whenPassNegativeDistance_whenUnSupportedSpaceType_thenException() { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + float negativeDistance = -1.0f; + + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .maxDistance(negativeDistance) + .build(); + + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.FLOAT); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + MethodComponentContext methodComponentContext = new MethodComponentContext( + org.opensearch.knn.common.KNNConstants.METHOD_HNSW, + ImmutableMap.of() + ); + KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.FAISS, SpaceType.L2, methodComponentContext); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(knnMethodContext, 4)); + IndexSettings indexSettings = mock(IndexSettings.class); + when(mockQueryShardContext.getIndexSettings()).thenReturn(indexSettings); + when(indexSettings.getMaxResultWindow()).thenReturn(1000); + + expectThrows(IllegalArgumentException.class, () -> knnQueryBuilder.doToQuery(mockQueryShardContext)); + } + + public void testDoToQuery_whenRadialSearchOnBinaryIndex_thenException() { + float[] queryVector = { 1.0f }; + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .maxDistance(MAX_DISTANCE) + .build(); + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.BINARY); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + MethodComponentContext methodComponentContext = new MethodComponentContext( + org.opensearch.knn.common.KNNConstants.METHOD_HNSW, + ImmutableMap.of() + ); + KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.FAISS, SpaceType.HAMMING, methodComponentContext); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(knnMethodContext, 8)); + Exception e = expectThrows(UnsupportedOperationException.class, () -> knnQueryBuilder.doToQuery(mockQueryShardContext)); + assertTrue(e.getMessage().contains("Binary data type does not support radial search")); + } + + public void testDoToQuery_whenRadialSearchOnDiskMode_thenException() { + float[] queryVector = { 1.0f }; + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .maxDistance(MAX_DISTANCE) + .build(); + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.FLOAT); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + MethodComponentContext methodComponentContext = new MethodComponentContext( + org.opensearch.knn.common.KNNConstants.METHOD_HNSW, + ImmutableMap.of() + ); + KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.FAISS, SpaceType.L2, methodComponentContext); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(new KNNMappingConfig() { + @Override + public Optional getKnnMethodContext() { + return Optional.of(knnMethodContext); + } + + @Override + public int getDimension() { + return 1; + } + + public Mode getMode() { + return Mode.ON_DISK; + } + + public QuantizationConfig getQuantizationConfig() { + return QuantizationConfig.builder().quantizationType(ScalarQuantizationType.ONE_BIT).build(); + } + }); + Exception e = expectThrows(UnsupportedOperationException.class, () -> knnQueryBuilder.doToQuery(mockQueryShardContext)); + assertEquals("Radial search is not supported for indices which have quantization enabled", e.getMessage()); + } + + public void testDoToQuery_KnnQueryWithFilter_Lucene() throws Exception { + // Given + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .k(K) + .filter(TERM_QUERY) + .build(); + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.FLOAT); + MethodComponentContext methodComponentContext = new MethodComponentContext( + org.opensearch.knn.common.KNNConstants.METHOD_HNSW, + ImmutableMap.of() + ); + KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.LUCENE, SpaceType.L2, methodComponentContext); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(knnMethodContext, 4)); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + + // When + Query query = knnQueryBuilder.doToQuery(mockQueryShardContext); + + // Then + assertNotNull(query); + assertTrue(query.getClass().isAssignableFrom(KnnFloatVectorQuery.class)); + } + + @SneakyThrows + public void testDoToQuery_whenDoRadiusSearch_whenDistanceThreshold_whenFilter_thenSucceed() { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .maxDistance(MAX_DISTANCE) + .filter(TERM_QUERY) + .build(); + + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.FLOAT); + MethodComponentContext methodComponentContext = new MethodComponentContext( + org.opensearch.knn.common.KNNConstants.METHOD_HNSW, + ImmutableMap.of() + ); + KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.LUCENE, SpaceType.L2, methodComponentContext); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(knnMethodContext, 4)); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + Query query = knnQueryBuilder.doToQuery(mockQueryShardContext); + assertNotNull(query); + assertTrue(query.getClass().isAssignableFrom(FloatVectorSimilarityQuery.class)); + } + + @SneakyThrows + public void testDoToQuery_whenDoRadiusSearch_whenScoreThreshold_whenFilter_thenSucceed() { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .maxDistance(MAX_DISTANCE) + .filter(TERM_QUERY) + .build(); + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.FLOAT); + MethodComponentContext methodComponentContext = new MethodComponentContext( + org.opensearch.knn.common.KNNConstants.METHOD_HNSW, + ImmutableMap.of() + ); + KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.LUCENE, SpaceType.L2, methodComponentContext); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(knnMethodContext, 4)); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + Query query = knnQueryBuilder.doToQuery(mockQueryShardContext); + assertNotNull(query); + assertTrue(query.getClass().isAssignableFrom(FloatVectorSimilarityQuery.class)); + } + + @SneakyThrows + public void testDoToQuery_WhenknnQueryWithFilterAndFaissEngine_thenSuccess() { + // Given + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + MethodComponentContext methodComponentContext = new MethodComponentContext( + org.opensearch.knn.common.KNNConstants.METHOD_HNSW, + ImmutableMap.of() + ); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.FLOAT); + KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.FAISS, SpaceType.L2, methodComponentContext); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(knnMethodContext, 4)); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + + // When + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .k(K) + .filter(TERM_QUERY) + .methodParameters(HNSW_METHOD_PARAMS) + .build(); + + Query query = knnQueryBuilder.doToQuery(mockQueryShardContext); + + // Then + assertNotNull(query); + assertTrue(query.getClass().isAssignableFrom(KNNQuery.class)); + assertEquals(HNSW_METHOD_PARAMS, ((KNNQuery) query).getMethodParameters()); + } + + public void testDoToQuery_ThrowsIllegalArgumentExceptionForUnknownMethodParameter() { + + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + KNNMethodContext knnMethodContext = new KNNMethodContext( + KNNEngine.LUCENE, + SpaceType.COSINESIMIL, + new MethodComponentContext("hnsw", Map.of()) + ); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(knnMethodContext, 4)); + + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .k(K) + .methodParameters(Map.of("nprobes", 10)) + .build(); + + expectThrows(IllegalArgumentException.class, () -> knnQueryBuilder.doToQuery(mockQueryShardContext)); + } + + public void testDoToQuery_whenknnQueryWithFilterAndNmsLibEngine_thenException() { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + KNNQueryBuilder knnQueryBuilder = new KNNQueryBuilder(FIELD_NAME, queryVector, K, TERM_QUERY); + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + MethodComponentContext methodComponentContext = new MethodComponentContext( + org.opensearch.knn.common.KNNConstants.METHOD_HNSW, + ImmutableMap.of() + ); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.FLOAT); + KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.NMSLIB, SpaceType.L2, methodComponentContext); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(knnMethodContext, 4)); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + expectThrows(IllegalArgumentException.class, () -> knnQueryBuilder.doToQuery(mockQueryShardContext)); + } + + @SneakyThrows + public void testDoToQuery_FromModel() { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + KNNQueryBuilder knnQueryBuilder = new KNNQueryBuilder(FIELD_NAME, queryVector, K); + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + + // Dimension is -1. In this case, model metadata will need to provide dimension + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.FLOAT); + String modelId = "test-model-id"; + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForModelMapping(modelId, 4)); + + // Mock the modelDao to return mocked modelMetadata + ModelMetadata modelMetadata = mock(ModelMetadata.class); + when(modelMetadata.getKnnEngine()).thenReturn(KNNEngine.FAISS); + when(modelMetadata.getSpaceType()).thenReturn(SpaceType.COSINESIMIL); + when(modelMetadata.getState()).thenReturn(ModelState.CREATED); + when(modelMetadata.getMethodComponentContext()).thenReturn(new MethodComponentContext("ivf", emptyMap())); + when(modelMetadata.getVectorDataType()).thenReturn(VectorDataType.DEFAULT); + ModelDao modelDao = mock(ModelDao.class); + when(modelDao.getMetadata(modelId)).thenReturn(modelMetadata); + KNNQueryBuilder.initialize(modelDao); + + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + KNNQuery query = (KNNQuery) knnQueryBuilder.doToQuery(mockQueryShardContext); + assertEquals(knnQueryBuilder.getK(), query.getK()); + assertEquals(knnQueryBuilder.fieldName(), query.getField()); + assertEquals(knnQueryBuilder.vector(), query.getQueryVector()); + } + + @SneakyThrows + public void testDoToQuery_whenFromModel_whenDoRadiusSearch_whenDistanceThreshold_thenSucceed() { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .maxDistance(MAX_DISTANCE) + .build(); + + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.FLOAT); + String modelId = "test-model-id"; + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForModelMapping(modelId, 4)); + + ModelMetadata modelMetadata = mock(ModelMetadata.class); + when(modelMetadata.getKnnEngine()).thenReturn(KNNEngine.FAISS); + when(modelMetadata.getSpaceType()).thenReturn(SpaceType.L2); + when(modelMetadata.getState()).thenReturn(ModelState.CREATED); + when(modelMetadata.getMethodComponentContext()).thenReturn(new MethodComponentContext("ivf", emptyMap())); + when(modelMetadata.getVectorDataType()).thenReturn(VectorDataType.DEFAULT); + ModelDao modelDao = mock(ModelDao.class); + when(modelDao.getMetadata(modelId)).thenReturn(modelMetadata); + KNNQueryBuilder.initialize(modelDao); + IndexSettings indexSettings = mock(IndexSettings.class); + when(mockQueryShardContext.getIndexSettings()).thenReturn(indexSettings); + when(indexSettings.getMaxResultWindow()).thenReturn(1000); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + + KNNQuery query = (KNNQuery) knnQueryBuilder.doToQuery(mockQueryShardContext); + assertEquals(knnQueryBuilder.getMaxDistance(), query.getRadius(), 0); + assertEquals(knnQueryBuilder.fieldName(), query.getField()); + assertEquals(knnQueryBuilder.vector(), query.getQueryVector()); + } + + @SneakyThrows + public void testDoToQuery_whenFromModel_whenDoRadiusSearch_whenScoreThreshold_thenSucceed() { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(queryVector).minScore(MIN_SCORE).build(); + + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.FLOAT); + String modelId = "test-model-id"; + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForModelMapping(modelId, 4)); + + ModelMetadata modelMetadata = mock(ModelMetadata.class); + when(modelMetadata.getKnnEngine()).thenReturn(KNNEngine.FAISS); + when(modelMetadata.getSpaceType()).thenReturn(SpaceType.L2); + when(modelMetadata.getState()).thenReturn(ModelState.CREATED); + when(modelMetadata.getVectorDataType()).thenReturn(VectorDataType.DEFAULT); + when(modelMetadata.getMethodComponentContext()).thenReturn(new MethodComponentContext("ivf", emptyMap())); + ModelDao modelDao = mock(ModelDao.class); + when(modelDao.getMetadata(modelId)).thenReturn(modelMetadata); + KNNQueryBuilder.initialize(modelDao); + IndexSettings indexSettings = mock(IndexSettings.class); + when(mockQueryShardContext.getIndexSettings()).thenReturn(indexSettings); + when(indexSettings.getMaxResultWindow()).thenReturn(1000); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + KNNQuery query = (KNNQuery) knnQueryBuilder.doToQuery(mockQueryShardContext); + + assertEquals(1 / knnQueryBuilder.getMinScore() - 1, query.getRadius(), 0); + assertEquals(knnQueryBuilder.fieldName(), query.getField()); + assertEquals(knnQueryBuilder.vector(), query.getQueryVector()); + } + + public void testDoToQuery_InvalidDimensions() { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + KNNQueryBuilder knnQueryBuilder = new KNNQueryBuilder(FIELD_NAME, queryVector, K); + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(getDefaultKNNMethodContext(), 400)); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + expectThrows(IllegalArgumentException.class, () -> knnQueryBuilder.doToQuery(mockQueryShardContext)); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(getDefaultKNNMethodContext(), K)); + expectThrows(IllegalArgumentException.class, () -> knnQueryBuilder.doToQuery(mockQueryShardContext)); + } + + public void testDoToQuery_InvalidFieldType() throws IOException { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + KNNQueryBuilder knnQueryBuilder = new KNNQueryBuilder("mynumber", queryVector, K); + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + NumberFieldMapper.NumberFieldType mockNumberField = mock(NumberFieldMapper.NumberFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockNumberField); + expectThrows(IllegalArgumentException.class, () -> knnQueryBuilder.doToQuery(mockQueryShardContext)); + } + + public void testDoToQuery_InvalidZeroFloatVector() { + float[] queryVector = { 0.0f, 0.0f, 0.0f, 0.0f }; + KNNQueryBuilder knnQueryBuilder = new KNNQueryBuilder(FIELD_NAME, queryVector, K); + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.FLOAT); + KNNMethodContext knnMethodContext = mock(KNNMethodContext.class); + when(knnMethodContext.getSpaceType()).thenReturn(SpaceType.COSINESIMIL); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(knnMethodContext, 4)); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + IllegalArgumentException exception = expectThrows( + IllegalArgumentException.class, + () -> knnQueryBuilder.doToQuery(mockQueryShardContext) + ); + assertEquals( + String.format(Locale.ROOT, "zero vector is not supported when space type is [%s]", SpaceType.COSINESIMIL.getValue()), + exception.getMessage() + ); + } + + public void testDoToQuery_InvalidZeroByteVector() { + float[] queryVector = { 0.0f, 0.0f, 0.0f, 0.0f }; + KNNQueryBuilder knnQueryBuilder = new KNNQueryBuilder(FIELD_NAME, queryVector, K); + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.BYTE); + KNNMethodContext knnMethodContext = mock(KNNMethodContext.class); + when(knnMethodContext.getSpaceType()).thenReturn(SpaceType.COSINESIMIL); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(knnMethodContext, 4)); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + IllegalArgumentException exception = expectThrows( + IllegalArgumentException.class, + () -> knnQueryBuilder.doToQuery(mockQueryShardContext) + ); + assertEquals( + String.format(Locale.ROOT, "zero vector is not supported when space type is [%s]", SpaceType.COSINESIMIL.getValue()), + exception.getMessage() + ); + } + + public void testSerialization() throws Exception { + // For k-NN search + assertSerialization(Version.CURRENT, Optional.empty(), K, null, null, null, null); + assertSerialization(Version.CURRENT, Optional.empty(), K, Map.of("ef_search", EF_SEARCH), null, null, null); + assertSerialization(Version.CURRENT, Optional.of(TERM_QUERY), K, Map.of("ef_search", EF_SEARCH), null, null, null); + assertSerialization(Version.V_2_3_0, Optional.empty(), K, Map.of("ef_search", EF_SEARCH), null, null, null); + assertSerialization(Version.V_2_3_0, Optional.empty(), K, null, null, null, null); + + // For distance threshold search + assertSerialization(Version.CURRENT, Optional.empty(), null, null, null, MAX_DISTANCE, null); + assertSerialization(Version.CURRENT, Optional.of(TERM_QUERY), null, null, null, MAX_DISTANCE, null); + + // For score threshold search + assertSerialization(Version.CURRENT, Optional.empty(), null, null, null, MIN_SCORE, null); + assertSerialization(Version.CURRENT, Optional.of(TERM_QUERY), null, null, null, MIN_SCORE, null); + + // Test rescore + assertSerialization(Version.V_2_3_0, Optional.empty(), K, null, null, null, RescoreContext.getDefault()); + assertSerialization(Version.CURRENT, Optional.empty(), K, null, null, null, RescoreContext.getDefault()); + } + + private void assertSerialization( + final Version version, + final Optional queryBuilderOptional, + Integer k, + Map methodParameters, + Float distance, + Float score, + RescoreContext rescoreContext + ) throws Exception { + final KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(QUERY_VECTOR) + .maxDistance(distance) + .minScore(score) + .k(k) + .methodParameters(methodParameters) + .filter(queryBuilderOptional.orElse(null)) + .rescoreContext(rescoreContext) + .build(); + + final ClusterService clusterService = mockClusterService(version); + + final KNNClusterUtil knnClusterUtil = KNNClusterUtil.instance(); + knnClusterUtil.initialize(clusterService); + try (BytesStreamOutput output = new BytesStreamOutput()) { + output.setVersion(version); + output.writeNamedWriteable(knnQueryBuilder); + + try (StreamInput in = new NamedWriteableAwareStreamInput(output.bytes().streamInput(), writableRegistry())) { + in.setVersion(version); + final QueryBuilder deserializedQuery = in.readNamedWriteable(QueryBuilder.class); + + assertNotNull(deserializedQuery); + assertTrue(deserializedQuery instanceof KNNQueryBuilder); + final KNNQueryBuilder deserializedKnnQueryBuilder = (KNNQueryBuilder) deserializedQuery; + assertEquals(FIELD_NAME, deserializedKnnQueryBuilder.fieldName()); + assertArrayEquals(QUERY_VECTOR, (float[]) deserializedKnnQueryBuilder.vector(), 0.0f); + if (k != null) { + assertEquals(k.intValue(), deserializedKnnQueryBuilder.getK()); + } else if (distance != null) { + assertEquals(distance.floatValue(), deserializedKnnQueryBuilder.getMaxDistance(), 0.0f); + } else { + assertEquals(score.floatValue(), deserializedKnnQueryBuilder.getMinScore(), 0.0f); + } + if (queryBuilderOptional.isPresent()) { + assertNotNull(deserializedKnnQueryBuilder.getFilter()); + assertEquals(queryBuilderOptional.get(), deserializedKnnQueryBuilder.getFilter()); + } else { + assertNull(deserializedKnnQueryBuilder.getFilter()); + } + assertMethodParameters(version, methodParameters, deserializedKnnQueryBuilder.getMethodParameters()); + assertRescore(version, rescoreContext, deserializedKnnQueryBuilder.getRescoreContext()); + } + } + } + + private void assertMethodParameters(Version version, Map expectedMethodParameters, Map actualMethodParameters) { + if (!version.onOrAfter(Version.V_2_16_0)) { + assertNull(actualMethodParameters); + } else if (expectedMethodParameters != null) { + if (version.onOrAfter(Version.V_2_16_0)) { + assertEquals(expectedMethodParameters.get("ef_search"), actualMethodParameters.get("ef_search")); + } + } + } + + private void assertRescore(Version version, RescoreContext expectedRescoreContext, RescoreContext actualRescoreContext) { + if (!version.onOrAfter(Version.V_2_17_0)) { + assertNull(actualRescoreContext); + return; + } + + if (expectedRescoreContext != null) { + assertNotNull(actualRescoreContext); + assertEquals(expectedRescoreContext.getOversampleFactor(), actualRescoreContext.getOversampleFactor(), 0.0f); + } + } + + public void testIgnoreUnmapped() throws IOException { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + KNNQueryBuilder.Builder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .k(K) + .ignoreUnmapped(true); + assertTrue(knnQueryBuilder.build().isIgnoreUnmapped()); + Query query = knnQueryBuilder.build().doToQuery(mock(QueryShardContext.class)); + assertNotNull(query); + assertThat(query, instanceOf(MatchNoDocsQuery.class)); + knnQueryBuilder.ignoreUnmapped(false); + expectThrows(IllegalArgumentException.class, () -> knnQueryBuilder.build().doToQuery(mock(QueryShardContext.class))); + } + + public void testRadialSearch_whenUnsupportedEngine_thenThrowException() { + List unsupportedEngines = Arrays.stream(KNNEngine.values()) + .filter(knnEngine -> !ENGINES_SUPPORTING_RADIAL_SEARCH.contains(knnEngine)) + .collect(Collectors.toList()); + for (KNNEngine knnEngine : unsupportedEngines) { + KNNMethodContext knnMethodContext = new KNNMethodContext( + knnEngine, + SpaceType.L2, + new MethodComponentContext(org.opensearch.knn.common.KNNConstants.METHOD_HNSW, ImmutableMap.of()) + ); + + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(QUERY_VECTOR) + .maxDistance(MAX_DISTANCE) + .build(); + + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + Index dummyIndex = new Index("dummy", "dummy"); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(knnMethodContext, 4)); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.FLOAT); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + + expectThrows(UnsupportedOperationException.class, () -> knnQueryBuilder.doToQuery(mockQueryShardContext)); + } + } + + public void testRadialSearch_whenEfSearchIsSet_whenLuceneEngine_thenThrowException() { + KNNMethodContext knnMethodContext = new KNNMethodContext( + KNNEngine.LUCENE, + SpaceType.L2, + new MethodComponentContext(org.opensearch.knn.common.KNNConstants.METHOD_HNSW, ImmutableMap.of()) + ); + + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(QUERY_VECTOR) + .maxDistance(MAX_DISTANCE) + .methodParameters(Map.of("ef_search", EF_SEARCH)) + .build(); + + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + Index dummyIndex = new Index("dummy", "dummy"); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(knnMethodContext, 4)); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + + expectThrows(IllegalArgumentException.class, () -> knnQueryBuilder.doToQuery(mockQueryShardContext)); + } + + @SneakyThrows + public void testRadialSearch_whenEfSearchIsSet_whenFaissEngine_thenSuccess() { + KNNMethodContext knnMethodContext = new KNNMethodContext( + KNNEngine.FAISS, + SpaceType.L2, + new MethodComponentContext(org.opensearch.knn.common.KNNConstants.METHOD_HNSW, ImmutableMap.of()) + ); + + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(QUERY_VECTOR) + .minScore(MIN_SCORE) + .methodParameters(Map.of("ef_search", EF_SEARCH)) + .build(); + + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + Index dummyIndex = new Index("dummy", "dummy"); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(knnMethodContext, 4)); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.FLOAT); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + IndexSettings indexSettings = mock(IndexSettings.class); + when(mockQueryShardContext.getIndexSettings()).thenReturn(indexSettings); + when(indexSettings.getMaxResultWindow()).thenReturn(1000); + + KNNQuery query = (KNNQuery) knnQueryBuilder.doToQuery(mockQueryShardContext); + assertEquals(1 / MIN_SCORE - 1, query.getRadius(), 0); + } + + public void testDoToQuery_whenBinary_thenValid() throws Exception { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + byte[] expectedQueryVector = { 1, 2, 3, 4 }; + KNNQueryBuilder knnQueryBuilder = new KNNQueryBuilder(FIELD_NAME, queryVector, K); + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.BINARY); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(getDefaultBinaryKNNMethodContext(), 32)); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + KNNQuery query = (KNNQuery) knnQueryBuilder.doToQuery(mockQueryShardContext); + assertArrayEquals(expectedQueryVector, query.getByteQueryVector()); + assertNull(query.getQueryVector()); + } + + public void testDoToQuery_whenBinaryWithInvalidDimension_thenException() throws Exception { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + KNNQueryBuilder knnQueryBuilder = new KNNQueryBuilder(FIELD_NAME, queryVector, K); + Index dummyIndex = new Index("dummy", "dummy"); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + KNNVectorFieldType mockKNNVectorField = mock(KNNVectorFieldType.class); + when(mockQueryShardContext.index()).thenReturn(dummyIndex); + when(mockKNNVectorField.getVectorDataType()).thenReturn(VectorDataType.BINARY); + when(mockKNNVectorField.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(getDefaultBinaryKNNMethodContext(), 8)); + when(mockQueryShardContext.fieldMapper(anyString())).thenReturn(mockKNNVectorField); + Exception ex = expectThrows(IllegalArgumentException.class, () -> knnQueryBuilder.doToQuery(mockQueryShardContext)); + assertTrue(ex.getMessage(), ex.getMessage().contains("invalid dimension")); + } + + @SneakyThrows + public void testDoRewrite_whenNoFilter_thenSuccessful() { + KNNQueryBuilder knnQueryBuilder = new KNNQueryBuilder(FIELD_NAME, QUERY_VECTOR, K); + QueryBuilder rewritten = knnQueryBuilder.rewrite(mock(QueryRewriteContext.class)); + assertEquals(knnQueryBuilder, rewritten); + } + + @SneakyThrows + public void testDoRewrite_whenFilterSet_thenSuccessful() { + // Given + QueryBuilder filter = mock(QueryBuilder.class); + QueryBuilder rewrittenFilter = mock(QueryBuilder.class); + QueryRewriteContext context = mock(QueryRewriteContext.class); + when(filter.rewrite(context)).thenReturn(rewrittenFilter); + KNNQueryBuilder expected = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(QUERY_VECTOR) + .filter(rewrittenFilter) + .k(K) + .build(); + // When + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(QUERY_VECTOR).filter(filter).k(K).build(); + + QueryBuilder actual = knnQueryBuilder.rewrite(context); + + // Then + assertEquals(expected, actual); + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/KNNQueryBuilderValidParamsTests.java b/src/test/java/org/opensearch/knn/index/query/KNNQueryBuilderValidParamsTests.java new file mode 100644 index 000000000..23278d28a --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/KNNQueryBuilderValidParamsTests.java @@ -0,0 +1,111 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query; + +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; +import lombok.AllArgsConstructor; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.query.rescore.RescoreContext; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; + +import static com.carrotsearch.randomizedtesting.RandomizedTest.$; +import static com.carrotsearch.randomizedtesting.RandomizedTest.$$; + +@AllArgsConstructor +public class KNNQueryBuilderValidParamsTests extends KNNTestCase { + + private static final float[] QUERY_VECTOR = new float[] { 1.2f, 2.3f, 4.5f }; + private static final String FIELD_NAME = "test_vector"; + + private String description; + private KNNQueryBuilder expected; + private Integer k; + private Map methodParameters; + private Float maxDistance; + private Float minScore; + private RescoreContext rescoreContext; + + @ParametersFactory(argumentFormatting = "description:%1$s; k:%3$s, efSearch:%4$s, maxDist:%5$s, minScore:%6$s, rescoreContext:%6$s") + public static Collection validParameters() { + return Arrays.asList( + $$( + $( + "valid knn with k", + KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(QUERY_VECTOR).k(10).build(), + 10, + null, + null, + null, + null + ), + $( + "valid knn with k and efSearch", + KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(QUERY_VECTOR) + .k(10) + .methodParameters(Map.of("ef_search", 12)) + .build(), + 10, + Map.of("ef_search", 12), + null, + null, + null + ), + $( + "valid knn with maxDis", + KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(QUERY_VECTOR).maxDistance(10.0f).build(), + null, + null, + 10.0f, + null, + null + ), + $( + "valid knn with minScore", + KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(QUERY_VECTOR).minScore(10.0f).build(), + null, + null, + null, + 10.0f, + null + ), + $( + "valid knn with rescore", + KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(QUERY_VECTOR) + .rescoreContext(RescoreContext.getDefault()) + .minScore(10.0f) + .build(), + null, + null, + null, + 10.0f, + RescoreContext.getDefault() + ) + ) + ); + } + + public void testValidBuilder() { + assertEquals( + expected, + KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(QUERY_VECTOR) + .k(k) + .methodParameters(methodParameters) + .maxDistance(maxDistance) + .minScore(minScore) + .rescoreContext(rescoreContext) + .build() + ); + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/KNNQueryFactoryTests.java b/src/test/java/org/opensearch/knn/index/query/KNNQueryFactoryTests.java new file mode 100644 index 000000000..96493acec --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/KNNQueryFactoryTests.java @@ -0,0 +1,485 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query; + +import org.apache.lucene.index.Term; +import org.apache.lucene.search.KnnByteVectorQuery; +import org.apache.lucene.search.KnnFloatVectorQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.join.BitSetProducer; +import org.apache.lucene.search.join.DiversifyingChildrenByteKnnVectorQuery; +import org.apache.lucene.search.join.DiversifyingChildrenFloatKnnVectorQuery; +import org.apache.lucene.search.join.ToChildBlockJoinQuery; +import org.junit.Before; +import org.mockito.Mock; +import org.mockito.MockedConstruction; +import org.mockito.Mockito; +import org.opensearch.common.settings.ClusterSettings; +import org.opensearch.index.mapper.MappedFieldType; +import org.opensearch.index.mapper.MapperService; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.QueryShardContext; +import org.opensearch.index.query.TermQueryBuilder; +import org.opensearch.index.search.NestedHelper; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.query.nativelib.NativeEngineKnnVectorQuery; +import org.opensearch.knn.index.query.rescore.RescoreContext; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.opensearch.knn.common.KNNConstants.DEFAULT_VECTOR_DATA_TYPE_FIELD; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_SEARCH; + +public class KNNQueryFactoryTests extends KNNTestCase { + private static final String FILTER_FILED_NAME = "foo"; + private static final String FILTER_FILED_VALUE = "fooval"; + private static final QueryBuilder FILTER_QUERY_BUILDER = new TermQueryBuilder(FILTER_FILED_NAME, FILTER_FILED_VALUE); + private static final Query FILTER_QUERY = new TermQuery(new Term(FILTER_FILED_NAME, FILTER_FILED_VALUE)); + private final int testQueryDimension = 17; + private final float[] testQueryVector = new float[testQueryDimension]; + private final byte[] testByteQueryVector = new byte[testQueryDimension]; + private final String testIndexName = "test-index"; + private final String testFieldName = "test-field"; + private final int testK = 10; + private final Map methodParameters = Map.of(METHOD_PARAMETER_EF_SEARCH, 100); + + @Mock + ClusterSettings clusterSettings; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + when(clusterService.getClusterSettings()).thenReturn(clusterSettings); + KNNSettings.state().setClusterService(clusterService); + } + + public void testCreateCustomKNNQuery() { + for (KNNEngine knnEngine : KNNEngine.getEnginesThatCreateCustomSegmentFiles()) { + Query query = KNNQueryFactory.create( + BaseQueryFactory.CreateQueryRequest.builder() + .knnEngine(knnEngine) + .indexName(testIndexName) + .fieldName(testFieldName) + .vector(testQueryVector) + .k(testK) + .vectorDataType(DEFAULT_VECTOR_DATA_TYPE_FIELD) + .build() + ); + assertTrue(query instanceof KNNQuery); + assertEquals(testIndexName, ((KNNQuery) query).getIndexName()); + assertEquals(testFieldName, ((KNNQuery) query).getField()); + assertEquals(testQueryVector, ((KNNQuery) query).getQueryVector()); + assertEquals(testK, ((KNNQuery) query).getK()); + + query = KNNQueryFactory.create( + BaseQueryFactory.CreateQueryRequest.builder() + .knnEngine(knnEngine) + .indexName(testIndexName) + .fieldName(testFieldName) + .vector(testQueryVector) + .k(testK) + .vectorDataType(DEFAULT_VECTOR_DATA_TYPE_FIELD) + .build() + ); + + assertTrue(query instanceof KNNQuery); + assertEquals(testIndexName, ((KNNQuery) query).getIndexName()); + assertEquals(testFieldName, ((KNNQuery) query).getField()); + assertEquals(testQueryVector, ((KNNQuery) query).getQueryVector()); + assertEquals(testK, ((KNNQuery) query).getK()); + } + } + + public void testCreateLuceneDefaultQuery() { + List luceneDefaultQueryEngineList = Arrays.stream(KNNEngine.values()) + .filter(knnEngine -> !KNNEngine.getEnginesThatCreateCustomSegmentFiles().contains(knnEngine)) + .collect(Collectors.toList()); + for (KNNEngine knnEngine : luceneDefaultQueryEngineList) { + Query query = KNNQueryFactory.create( + BaseQueryFactory.CreateQueryRequest.builder() + .knnEngine(knnEngine) + .indexName(testIndexName) + .fieldName(testFieldName) + .vector(testQueryVector) + .k(testK) + .vectorDataType(DEFAULT_VECTOR_DATA_TYPE_FIELD) + .build() + ); + assertEquals(KnnFloatVectorQuery.class, query.getClass()); + } + } + + public void testLuceneFloatVectorQuery() { + Query actualQuery1 = KNNQueryFactory.create( + BaseQueryFactory.CreateQueryRequest.builder() + .knnEngine(KNNEngine.LUCENE) + .vector(testQueryVector) + .k(testK) + .indexName(testIndexName) + .fieldName(testFieldName) + .methodParameters(methodParameters) + .vectorDataType(VectorDataType.FLOAT) + .build() + ); + + // efsearch > k + Query expectedQuery1 = new KnnFloatVectorQuery(testFieldName, testQueryVector, 100, null); + assertEquals(expectedQuery1, actualQuery1); + + // efsearch < k + actualQuery1 = KNNQueryFactory.create( + BaseQueryFactory.CreateQueryRequest.builder() + .knnEngine(KNNEngine.LUCENE) + .vector(testQueryVector) + .k(testK) + .indexName(testIndexName) + .fieldName(testFieldName) + .methodParameters(Map.of("ef_search", 1)) + .vectorDataType(VectorDataType.FLOAT) + .build() + ); + expectedQuery1 = new KnnFloatVectorQuery(testFieldName, testQueryVector, testK, null); + assertEquals(expectedQuery1, actualQuery1); + + actualQuery1 = KNNQueryFactory.create( + BaseQueryFactory.CreateQueryRequest.builder() + .knnEngine(KNNEngine.LUCENE) + .vector(testQueryVector) + .k(testK) + .indexName(testIndexName) + .fieldName(testFieldName) + .vectorDataType(VectorDataType.FLOAT) + .build() + ); + expectedQuery1 = new KnnFloatVectorQuery(testFieldName, testQueryVector, testK, null); + assertEquals(expectedQuery1, actualQuery1); + } + + public void testLuceneByteVectorQuery() { + Query actualQuery1 = KNNQueryFactory.create( + BaseQueryFactory.CreateQueryRequest.builder() + .knnEngine(KNNEngine.LUCENE) + .byteVector(testByteQueryVector) + .k(testK) + .indexName(testIndexName) + .fieldName(testFieldName) + .methodParameters(methodParameters) + .vectorDataType(VectorDataType.BYTE) + .build() + ); + + // efsearch > k + Query expectedQuery1 = new KnnByteVectorQuery(testFieldName, testByteQueryVector, 100, null); + assertEquals(expectedQuery1, actualQuery1); + + // efsearch < k + actualQuery1 = KNNQueryFactory.create( + BaseQueryFactory.CreateQueryRequest.builder() + .knnEngine(KNNEngine.LUCENE) + .byteVector(testByteQueryVector) + .k(testK) + .indexName(testIndexName) + .fieldName(testFieldName) + .methodParameters(Map.of("ef_search", 1)) + .vectorDataType(VectorDataType.BYTE) + .build() + ); + expectedQuery1 = new KnnByteVectorQuery(testFieldName, testByteQueryVector, testK, null); + assertEquals(expectedQuery1, actualQuery1); + + actualQuery1 = KNNQueryFactory.create( + BaseQueryFactory.CreateQueryRequest.builder() + .knnEngine(KNNEngine.LUCENE) + .byteVector(testByteQueryVector) + .k(testK) + .indexName(testIndexName) + .fieldName(testFieldName) + .vectorDataType(VectorDataType.BYTE) + .build() + ); + expectedQuery1 = new KnnByteVectorQuery(testFieldName, testByteQueryVector, testK, null); + assertEquals(expectedQuery1, actualQuery1); + } + + public void testCreateLuceneQueryWithFilter() { + List luceneDefaultQueryEngineList = Arrays.stream(KNNEngine.values()) + .filter(knnEngine -> !KNNEngine.getEnginesThatCreateCustomSegmentFiles().contains(knnEngine)) + .collect(Collectors.toList()); + for (KNNEngine knnEngine : luceneDefaultQueryEngineList) { + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + MappedFieldType testMapper = mock(MappedFieldType.class); + when(mockQueryShardContext.fieldMapper(any())).thenReturn(testMapper); + final KNNQueryFactory.CreateQueryRequest createQueryRequest = KNNQueryFactory.CreateQueryRequest.builder() + .knnEngine(knnEngine) + .indexName(testIndexName) + .fieldName(testFieldName) + .vector(testQueryVector) + .vectorDataType(DEFAULT_VECTOR_DATA_TYPE_FIELD) + .k(testK) + .context(mockQueryShardContext) + .filter(FILTER_QUERY_BUILDER) + .build(); + Query query = KNNQueryFactory.create(createQueryRequest); + assertEquals(KnnFloatVectorQuery.class, query.getClass()); + } + } + + public void testCreateFaissQueryWithFilter_withValidValues_thenSuccess() { + // Given + final KNNEngine knnEngine = KNNEngine.FAISS; + final QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + MappedFieldType testMapper = mock(MappedFieldType.class); + when(mockQueryShardContext.fieldMapper(any())).thenReturn(testMapper); + when(testMapper.termQuery(Mockito.any(), Mockito.eq(mockQueryShardContext))).thenReturn(FILTER_QUERY); + + final KNNQuery expectedQuery = KNNQuery.builder() + .indexName(testIndexName) + .filterQuery(FILTER_QUERY) + .field(testFieldName) + .queryVector(testQueryVector) + .k(testK) + .methodParameters(methodParameters) + .vectorDataType(VectorDataType.FLOAT) + .build(); + + // When + final KNNQueryFactory.CreateQueryRequest createQueryRequest = KNNQueryFactory.CreateQueryRequest.builder() + .knnEngine(knnEngine) + .indexName(testIndexName) + .fieldName(testFieldName) + .vector(testQueryVector) + .k(testK) + .methodParameters(methodParameters) + .vectorDataType(VectorDataType.FLOAT) + .context(mockQueryShardContext) + .filter(FILTER_QUERY_BUILDER) + .build(); + + final Query actual = KNNQueryFactory.create(createQueryRequest); + + // Then + assertEquals(expectedQuery, actual); + } + + public void testCreateFaissQueryWithFilter_withValidValues_nullEfSearch_thenSuccess() { + // Given + final KNNEngine knnEngine = KNNEngine.FAISS; + final QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + MappedFieldType testMapper = mock(MappedFieldType.class); + when(mockQueryShardContext.fieldMapper(any())).thenReturn(testMapper); + when(testMapper.termQuery(Mockito.any(), Mockito.eq(mockQueryShardContext))).thenReturn(FILTER_QUERY); + + final KNNQuery expectedQuery = KNNQuery.builder() + .indexName(testIndexName) + .filterQuery(FILTER_QUERY) + .field(testFieldName) + .queryVector(testQueryVector) + .k(testK) + .build(); + + // When + final KNNQueryFactory.CreateQueryRequest createQueryRequest = KNNQueryFactory.CreateQueryRequest.builder() + .knnEngine(knnEngine) + .indexName(testIndexName) + .fieldName(testFieldName) + .vector(testQueryVector) + .k(testK) + .vectorDataType(VectorDataType.FLOAT) + .context(mockQueryShardContext) + .filter(FILTER_QUERY_BUILDER) + .build(); + + final Query actual = KNNQueryFactory.create(createQueryRequest); + + // Then + assertEquals(expectedQuery, actual); + } + + public void testCreate_whenLuceneWithParentFilter_thenReturnDiversifyingQuery() { + validateDiversifyingQueryWithParentFilter(VectorDataType.BYTE, DiversifyingChildrenByteKnnVectorQuery.class); + validateDiversifyingQueryWithParentFilter(VectorDataType.FLOAT, DiversifyingChildrenFloatKnnVectorQuery.class); + } + + public void testCreate_whenNestedVectorFiledAndNonNestedFilterField_thenReturnToChildBlockJoinQueryForFilters() { + MapperService mockMapperService = mock(MapperService.class); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + when(mockQueryShardContext.getMapperService()).thenReturn(mockMapperService); + MappedFieldType testMapper = mock(MappedFieldType.class); + when(mockQueryShardContext.fieldMapper(any())).thenReturn(testMapper); + when(testMapper.termQuery(Mockito.any(), Mockito.eq(mockQueryShardContext))).thenReturn(FILTER_QUERY); + BitSetProducer parentFilter = mock(BitSetProducer.class); + when(mockQueryShardContext.getParentFilter()).thenReturn(parentFilter); + MockedConstruction mockedNestedHelper = Mockito.mockConstruction( + NestedHelper.class, + (mock, context) -> when(mock.mightMatchNestedDocs(FILTER_QUERY)).thenReturn(false) + ); + + final KNNQueryFactory.CreateQueryRequest createQueryRequest = KNNQueryFactory.CreateQueryRequest.builder() + .knnEngine(KNNEngine.FAISS) + .indexName(testIndexName) + .fieldName(testFieldName) + .vector(testQueryVector) + .k(testK) + .vectorDataType(VectorDataType.FLOAT) + .context(mockQueryShardContext) + .filter(FILTER_QUERY_BUILDER) + .build(); + KNNQuery query = (KNNQuery) KNNQueryFactory.create(createQueryRequest); + mockedNestedHelper.close(); + assertEquals(ToChildBlockJoinQuery.class, query.getFilterQuery().getClass()); + } + + public void testCreate_whenNestedVectorAndFilterField_thenReturnSameFilterQuery() { + MapperService mockMapperService = mock(MapperService.class); + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + when(mockQueryShardContext.getMapperService()).thenReturn(mockMapperService); + MappedFieldType testMapper = mock(MappedFieldType.class); + when(mockQueryShardContext.fieldMapper(any())).thenReturn(testMapper); + when(testMapper.termQuery(Mockito.any(), Mockito.eq(mockQueryShardContext))).thenReturn(FILTER_QUERY); + BitSetProducer parentFilter = mock(BitSetProducer.class); + when(mockQueryShardContext.getParentFilter()).thenReturn(parentFilter); + MockedConstruction mockedNestedHelper = Mockito.mockConstruction( + NestedHelper.class, + (mock, context) -> when(mock.mightMatchNestedDocs(FILTER_QUERY)).thenReturn(true) + ); + + final KNNQueryFactory.CreateQueryRequest createQueryRequest = KNNQueryFactory.CreateQueryRequest.builder() + .knnEngine(KNNEngine.FAISS) + .indexName(testIndexName) + .fieldName(testFieldName) + .vector(testQueryVector) + .k(testK) + .vectorDataType(VectorDataType.FLOAT) + .context(mockQueryShardContext) + .filter(FILTER_QUERY_BUILDER) + .build(); + KNNQuery query = (KNNQuery) KNNQueryFactory.create(createQueryRequest); + mockedNestedHelper.close(); + assertEquals(FILTER_QUERY.getClass(), query.getFilterQuery().getClass()); + } + + public void testCreate_whenFaissWithParentFilter_thenSuccess() { + final KNNEngine knnEngine = KNNEngine.FAISS; + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + MappedFieldType testMapper = mock(MappedFieldType.class); + when(mockQueryShardContext.fieldMapper(any())).thenReturn(testMapper); + BitSetProducer parentFilter = mock(BitSetProducer.class); + when(mockQueryShardContext.getParentFilter()).thenReturn(parentFilter); + final KNNQueryFactory.CreateQueryRequest createQueryRequest = KNNQueryFactory.CreateQueryRequest.builder() + .knnEngine(knnEngine) + .indexName(testIndexName) + .fieldName(testFieldName) + .vector(testQueryVector) + .k(testK) + .vectorDataType(VectorDataType.FLOAT) + .context(mockQueryShardContext) + .build(); + final Query query = KNNQueryFactory.create(createQueryRequest); + assertTrue(query instanceof KNNQuery); + assertEquals(testIndexName, ((KNNQuery) query).getIndexName()); + assertEquals(testFieldName, ((KNNQuery) query).getField()); + assertEquals(testQueryVector, ((KNNQuery) query).getQueryVector()); + assertEquals(testK, ((KNNQuery) query).getK()); + assertEquals(parentFilter, ((KNNQuery) query).getParentsFilter()); + } + + private void validateDiversifyingQueryWithParentFilter(final VectorDataType type, final Class expectedQueryClass) { + List luceneDefaultQueryEngineList = Arrays.stream(KNNEngine.values()) + .filter(knnEngine -> !KNNEngine.getEnginesThatCreateCustomSegmentFiles().contains(knnEngine)) + .collect(Collectors.toList()); + for (KNNEngine knnEngine : luceneDefaultQueryEngineList) { + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + MappedFieldType testMapper = mock(MappedFieldType.class); + when(mockQueryShardContext.fieldMapper(any())).thenReturn(testMapper); + BitSetProducer parentFilter = mock(BitSetProducer.class); + when(mockQueryShardContext.getParentFilter()).thenReturn(parentFilter); + final KNNQueryFactory.CreateQueryRequest createQueryRequest = KNNQueryFactory.CreateQueryRequest.builder() + .knnEngine(knnEngine) + .indexName(testIndexName) + .fieldName(testFieldName) + .vector(testQueryVector) + .byteVector(testByteQueryVector) + .vectorDataType(type) + .k(testK) + .context(mockQueryShardContext) + .filter(FILTER_QUERY_BUILDER) + .build(); + Query query = KNNQueryFactory.create(createQueryRequest); + assertEquals(expectedQueryClass, query.getClass()); + } + } + + public void testCreate_whenBinary_thenSuccess() { + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + MappedFieldType testMapper = mock(MappedFieldType.class); + when(mockQueryShardContext.fieldMapper(any())).thenReturn(testMapper); + BitSetProducer parentFilter = mock(BitSetProducer.class); + when(mockQueryShardContext.getParentFilter()).thenReturn(parentFilter); + + final KNNQueryFactory.CreateQueryRequest createQueryRequest = KNNQueryFactory.CreateQueryRequest.builder() + .knnEngine(KNNEngine.FAISS) + .indexName(testIndexName) + .fieldName(testFieldName) + .vector(testQueryVector) + .byteVector(testByteQueryVector) + .vectorDataType(VectorDataType.BINARY) + .k(testK) + .context(mockQueryShardContext) + .filter(FILTER_QUERY_BUILDER) + .build(); + Query query = KNNQueryFactory.create(createQueryRequest); + assertTrue(query instanceof KNNQuery); + assertNotNull(((KNNQuery) query).getByteQueryVector()); + assertNull(((KNNQuery) query).getQueryVector()); + } + + public void testCreate_whenRescoreContextPassed_thenSuccess() { + // Given + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + MappedFieldType testMapper = mock(MappedFieldType.class); + when(mockQueryShardContext.fieldMapper(any())).thenReturn(testMapper); + BitSetProducer parentFilter = mock(BitSetProducer.class); + when(mockQueryShardContext.getParentFilter()).thenReturn(parentFilter); + + final KNNQuery expected = KNNQuery.builder() + .field(testFieldName) + .indexName(testIndexName) + .byteQueryVector(testByteQueryVector) + .k(testK) + .parentsFilter(parentFilter) + .vectorDataType(VectorDataType.BINARY) + .rescoreContext(RescoreContext.getDefault()) + .build(); + + // When + final KNNQueryFactory.CreateQueryRequest createQueryRequest = KNNQueryFactory.CreateQueryRequest.builder() + .knnEngine(KNNEngine.FAISS) + .indexName(testIndexName) + .fieldName(testFieldName) + .vector(testQueryVector) + .byteVector(testByteQueryVector) + .vectorDataType(VectorDataType.BINARY) + .k(testK) + .context(mockQueryShardContext) + .rescoreContext(RescoreContext.getDefault()) + .build(); + Query query = KNNQueryFactory.create(createQueryRequest); + + // Then + assertEquals(expected, ((NativeEngineKnnVectorQuery) query).getKnnQuery()); + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java b/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java new file mode 100644 index 000000000..511895026 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/KNNWeightTests.java @@ -0,0 +1,1707 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query; + +import com.google.common.collect.Comparators; +import com.google.common.collect.ImmutableMap; +import lombok.SneakyThrows; +import org.apache.lucene.index.BinaryDocValues; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.FieldInfos; +import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.index.SegmentCommitInfo; +import org.apache.lucene.index.SegmentInfo; +import org.apache.lucene.index.SegmentReader; +import org.apache.lucene.index.Term; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.Scorer; +import org.apache.lucene.search.Sort; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.Weight; +import org.apache.lucene.search.join.BitSetProducer; +import org.apache.lucene.store.FSDirectory; +import org.apache.lucene.util.Bits; +import org.apache.lucene.util.BytesRef; +import org.apache.lucene.util.FixedBitSet; +import org.apache.lucene.util.StringHelper; +import org.apache.lucene.util.Version; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.mockito.MockedConstruction; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.opensearch.common.io.PathUtils; +import org.opensearch.common.unit.TimeValue; +import org.opensearch.core.common.unit.ByteSizeValue; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.codec.KNN990Codec.QuantizationConfigKNNCollector; +import org.opensearch.knn.index.codec.util.KNNCodecUtil; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.codec.KNNCodecVersion; +import org.opensearch.knn.index.codec.util.KNNVectorAsArraySerializer; +import org.opensearch.knn.index.memory.NativeMemoryAllocation; +import org.opensearch.knn.index.memory.NativeMemoryCacheManager; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.quantizationservice.QuantizationService; +import org.opensearch.knn.index.vectorvalues.KNNBinaryVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNFloatVectorValues; +import org.opensearch.knn.index.vectorvalues.KNNVectorValuesFactory; +import org.opensearch.knn.indices.ModelDao; +import org.opensearch.knn.indices.ModelMetadata; +import org.opensearch.knn.indices.ModelState; +import org.opensearch.knn.jni.JNIService; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; +import org.opensearch.knn.quantization.models.quantizationParams.QuantizationParams; +import org.opensearch.knn.quantization.models.quantizationParams.ScalarQuantizationParams; +import org.opensearch.knn.quantization.models.quantizationState.OneBitScalarQuantizationState; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static java.util.Collections.emptyMap; +import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.opensearch.knn.KNNRestTestCase.INDEX_NAME; +import static org.opensearch.knn.common.KNNConstants.INDEX_DESCRIPTION_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_SEARCH; +import static org.opensearch.knn.common.KNNConstants.MODEL_ID; +import static org.opensearch.knn.common.KNNConstants.PARAMETERS; +import static org.opensearch.knn.common.KNNConstants.SPACE_TYPE; + +public class KNNWeightTests extends KNNTestCase { + private static final String FIELD_NAME = "target_field"; + private static final float[] QUERY_VECTOR = new float[] { 1.8f, 2.4f }; + private static final byte[] BYTE_QUERY_VECTOR = new byte[] { 1, 2 }; + private static final String SEGMENT_NAME = "0"; + private static final int K = 5; + private static final Set SEGMENT_FILES_NMSLIB = Set.of("_0.cfe", "_0_2011_target_field.hnswc"); + private static final Set SEGMENT_FILES_FAISS = Set.of("_0.cfe", "_0_2011_target_field.faissc"); + private static final Set SEGMENT_FILES_DEFAULT = SEGMENT_FILES_FAISS; + private static final Set SEGMENT_MULTI_FIELD_FILES_FAISS = Set.of( + "_0.cfe", + "_0_2011_target_field.faissc", + "_0_2011_long_target_field.faissc" + ); + private static final String CIRCUIT_BREAKER_LIMIT_100KB = "100Kb"; + private static final Integer EF_SEARCH = 10; + private static final Map HNSW_METHOD_PARAMETERS = Map.of(METHOD_PARAMETER_EF_SEARCH, EF_SEARCH); + + private static final Map DOC_ID_TO_SCORES = Map.of(10, 0.4f, 101, 0.05f, 100, 0.8f, 50, 0.52f); + private static final Map FILTERED_DOC_ID_TO_SCORES = Map.of(101, 0.05f, 100, 0.8f, 50, 0.52f); + private static final Map EXACT_SEARCH_DOC_ID_TO_SCORES = Map.of(0, 0.12048191f); + private static final Map BINARY_EXACT_SEARCH_DOC_ID_TO_SCORES = Map.of(0, 0.5f); + + private static final Query FILTER_QUERY = new TermQuery(new Term("foo", "fooValue")); + + private static MockedStatic nativeMemoryCacheManagerMockedStatic; + private static MockedStatic jniServiceMockedStatic; + + private static MockedStatic knnSettingsMockedStatic; + + @BeforeClass + public static void setUpClass() throws Exception { + final KNNSettings knnSettings = mock(KNNSettings.class); + knnSettingsMockedStatic = mockStatic(KNNSettings.class); + when(knnSettings.getSettingValue(eq(KNNSettings.KNN_MEMORY_CIRCUIT_BREAKER_ENABLED))).thenReturn(true); + when(knnSettings.getSettingValue(eq(KNNSettings.KNN_MEMORY_CIRCUIT_BREAKER_LIMIT))).thenReturn(CIRCUIT_BREAKER_LIMIT_100KB); + when(knnSettings.getSettingValue(eq(KNNSettings.KNN_CACHE_ITEM_EXPIRY_ENABLED))).thenReturn(false); + when(knnSettings.getSettingValue(eq(KNNSettings.KNN_CACHE_ITEM_EXPIRY_TIME_MINUTES))).thenReturn(TimeValue.timeValueMinutes(10)); + + final ByteSizeValue v = ByteSizeValue.parseBytesSizeValue( + CIRCUIT_BREAKER_LIMIT_100KB, + KNNSettings.KNN_MEMORY_CIRCUIT_BREAKER_LIMIT + ); + knnSettingsMockedStatic.when(KNNSettings::getCircuitBreakerLimit).thenReturn(v); + knnSettingsMockedStatic.when(KNNSettings::state).thenReturn(knnSettings); + knnSettingsMockedStatic.when(KNNSettings::isKNNPluginEnabled).thenReturn(true); + + nativeMemoryCacheManagerMockedStatic = mockStatic(NativeMemoryCacheManager.class); + + final NativeMemoryCacheManager nativeMemoryCacheManager = mock(NativeMemoryCacheManager.class); + final NativeMemoryAllocation nativeMemoryAllocation = mock(NativeMemoryAllocation.class); + when(nativeMemoryCacheManager.get(any(), anyBoolean())).thenReturn(nativeMemoryAllocation); + + nativeMemoryCacheManagerMockedStatic.when(NativeMemoryCacheManager::getInstance).thenReturn(nativeMemoryCacheManager); + + final MockedStatic pathUtilsMockedStatic = mockStatic(PathUtils.class); + final Path indexPath = mock(Path.class); + when(indexPath.toString()).thenReturn("/mydrive/myfolder"); + pathUtilsMockedStatic.when(() -> PathUtils.get(anyString(), anyString())).thenReturn(indexPath); + } + + @Before + public void setupBeforeTest() { + knnSettingsMockedStatic.when(() -> KNNSettings.getFilteredExactSearchThreshold(INDEX_NAME)).thenReturn(0); + jniServiceMockedStatic = mockStatic(JNIService.class); + } + + @After + public void tearDownAfterTest() { + jniServiceMockedStatic.close(); + } + + @SneakyThrows + public void testQueryResultScoreNmslib() { + for (SpaceType space : List.of(SpaceType.L2, SpaceType.L1, SpaceType.COSINESIMIL, SpaceType.INNER_PRODUCT, SpaceType.LINF)) { + testQueryScore( + space::scoreTranslation, + SEGMENT_FILES_NMSLIB, + Map.of(SPACE_TYPE, space.getValue(), KNN_ENGINE, KNNEngine.NMSLIB.getName()) + ); + } + } + + @SneakyThrows + public void testQueryResultScoreFaiss() { + testQueryScore( + SpaceType.L2::scoreTranslation, + SEGMENT_FILES_FAISS, + Map.of( + SPACE_TYPE, + SpaceType.L2.getValue(), + KNN_ENGINE, + KNNEngine.FAISS.getName(), + PARAMETERS, + String.format(Locale.ROOT, "{\"%s\":\"%s\"}", INDEX_DESCRIPTION_PARAMETER, "HNSW32") + ) + ); + // score translation for Faiss and inner product is different from default defined in Space enum + testQueryScore( + rawScore -> SpaceType.INNER_PRODUCT.scoreTranslation(-1 * rawScore), + SEGMENT_FILES_FAISS, + Map.of( + SPACE_TYPE, + SpaceType.INNER_PRODUCT.getValue(), + KNN_ENGINE, + KNNEngine.FAISS.getName(), + PARAMETERS, + String.format(Locale.ROOT, "{\"%s\":\"%s\"}", INDEX_DESCRIPTION_PARAMETER, "HNSW32") + ) + ); + + // multi field + testQueryScore( + rawScore -> SpaceType.INNER_PRODUCT.scoreTranslation(-1 * rawScore), + SEGMENT_MULTI_FIELD_FILES_FAISS, + Map.of( + SPACE_TYPE, + SpaceType.INNER_PRODUCT.getValue(), + KNN_ENGINE, + KNNEngine.FAISS.getName(), + PARAMETERS, + String.format(Locale.ROOT, "{\"%s\":\"%s\"}", INDEX_DESCRIPTION_PARAMETER, "HNSW32") + ) + ); + } + + @SneakyThrows + public void testQueryScoreForFaissWithModel() { + SpaceType spaceType = SpaceType.L2; + final Function scoreTranslator = spaceType::scoreTranslation; + final String modelId = "modelId"; + jniServiceMockedStatic.when(() -> JNIService.queryIndex(anyLong(), any(), eq(K), isNull(), any(), any(), anyInt(), any())) + .thenReturn(getKNNQueryResults()); + + final KNNQuery query = new KNNQuery(FIELD_NAME, QUERY_VECTOR, K, INDEX_NAME, (BitSetProducer) null); + + ModelDao modelDao = mock(ModelDao.class); + ModelMetadata modelMetadata = mock(ModelMetadata.class); + when(modelMetadata.getKnnEngine()).thenReturn(KNNEngine.FAISS); + when(modelMetadata.getSpaceType()).thenReturn(spaceType); + when(modelMetadata.getState()).thenReturn(ModelState.CREATED); + when(modelMetadata.getVectorDataType()).thenReturn(VectorDataType.DEFAULT); + when(modelMetadata.getMethodComponentContext()).thenReturn(new MethodComponentContext("ivf", emptyMap())); + when(modelDao.getMetadata(eq("modelId"))).thenReturn(modelMetadata); + + KNNWeight.initialize(modelDao); + final float boost = (float) randomDoubleBetween(0, 10, true); + final KNNWeight knnWeight = new KNNWeight(query, boost); + + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + final SegmentReader reader = mock(SegmentReader.class); + when(leafReaderContext.reader()).thenReturn(reader); + + final FSDirectory directory = mock(FSDirectory.class); + when(reader.directory()).thenReturn(directory); + final SegmentInfo segmentInfo = new SegmentInfo( + directory, + Version.LATEST, + Version.LATEST, + SEGMENT_NAME, + 100, + true, + false, + KNNCodecVersion.current().getDefaultCodecDelegate(), + Map.of(), + new byte[StringHelper.ID_LENGTH], + Map.of(), + Sort.RELEVANCE + ); + segmentInfo.setFiles(SEGMENT_FILES_FAISS); + final SegmentCommitInfo segmentCommitInfo = new SegmentCommitInfo(segmentInfo, 0, 0, 0, 0, 0, new byte[StringHelper.ID_LENGTH]); + when(reader.getSegmentInfo()).thenReturn(segmentCommitInfo); + + final Path path = mock(Path.class); + when(directory.getDirectory()).thenReturn(path); + final FieldInfos fieldInfos = mock(FieldInfos.class); + final FieldInfo fieldInfo = mock(FieldInfo.class); + when(reader.getFieldInfos()).thenReturn(fieldInfos); + when(fieldInfos.fieldInfo(any())).thenReturn(fieldInfo); + when(fieldInfo.attributes()).thenReturn(Map.of()); + when(fieldInfo.getAttribute(eq(MODEL_ID))).thenReturn(modelId); + + final KNNScorer knnScorer = (KNNScorer) knnWeight.scorer(leafReaderContext); + assertNotNull(knnScorer); + final DocIdSetIterator docIdSetIterator = knnScorer.iterator(); + assertNotNull(docIdSetIterator); + assertEquals(DOC_ID_TO_SCORES.size(), docIdSetIterator.cost()); + + final List actualDocIds = new ArrayList(); + final Map translatedScores = getTranslatedScores(scoreTranslator); + for (int docId = docIdSetIterator.nextDoc(); docId != NO_MORE_DOCS; docId = docIdSetIterator.nextDoc()) { + actualDocIds.add(docId); + assertEquals(translatedScores.get(docId) * boost, knnScorer.score(), 0.01f); + } + assertEquals(docIdSetIterator.cost(), actualDocIds.size()); + assertTrue(Comparators.isInOrder(actualDocIds, Comparator.naturalOrder())); + } + + @SneakyThrows + public void testQueryScoreForFaissWithNonExistingModel() throws IOException { + SpaceType spaceType = SpaceType.L2; + final String modelId = "modelId"; + + final KNNQuery query = new KNNQuery(FIELD_NAME, QUERY_VECTOR, K, INDEX_NAME, (BitSetProducer) null); + + ModelDao modelDao = mock(ModelDao.class); + ModelMetadata modelMetadata = mock(ModelMetadata.class); + when(modelMetadata.getKnnEngine()).thenReturn(KNNEngine.FAISS); + when(modelMetadata.getSpaceType()).thenReturn(spaceType); + + KNNWeight.initialize(modelDao); + final KNNWeight knnWeight = new KNNWeight(query, 0.0f); + + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + final SegmentReader reader = mock(SegmentReader.class); + when(leafReaderContext.reader()).thenReturn(reader); + + final FSDirectory directory = mock(FSDirectory.class); + when(reader.directory()).thenReturn(directory); + + final Path path = mock(Path.class); + when(directory.getDirectory()).thenReturn(path); + final FieldInfos fieldInfos = mock(FieldInfos.class); + final FieldInfo fieldInfo = mock(FieldInfo.class); + when(reader.getFieldInfos()).thenReturn(fieldInfos); + when(fieldInfos.fieldInfo(any())).thenReturn(fieldInfo); + when(fieldInfo.attributes()).thenReturn(Map.of()); + when(fieldInfo.getAttribute(eq(MODEL_ID))).thenReturn(modelId); + + RuntimeException ex = expectThrows(RuntimeException.class, () -> knnWeight.scorer(leafReaderContext)); + assertEquals(String.format("Model \"%s\" is not created.", modelId), ex.getMessage()); + } + + @SneakyThrows + public void testScorer_whenNoVectorFieldsInDocument_thenEmptyScorerIsReturned() { + final KNNQuery query = new KNNQuery(FIELD_NAME, QUERY_VECTOR, K, INDEX_NAME, (BitSetProducer) null); + KNNWeight.initialize(null); + final KNNWeight knnWeight = new KNNWeight(query, 0.0f); + + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + final SegmentReader reader = mock(SegmentReader.class); + when(leafReaderContext.reader()).thenReturn(reader); + + final FSDirectory directory = mock(FSDirectory.class); + when(reader.directory()).thenReturn(directory); + + final SegmentInfo segmentInfo = new SegmentInfo( + directory, + Version.LATEST, + Version.LATEST, + SEGMENT_NAME, + 100, + false, + false, + KNNCodecVersion.current().getDefaultCodecDelegate(), + Map.of(), + new byte[StringHelper.ID_LENGTH], + Map.of(), + Sort.RELEVANCE + ); + segmentInfo.setFiles(Set.of()); + final SegmentCommitInfo segmentCommitInfo = new SegmentCommitInfo(segmentInfo, 0, 0, 0, 0, 0, new byte[StringHelper.ID_LENGTH]); + when(reader.getSegmentInfo()).thenReturn(segmentCommitInfo); + + final Path path = mock(Path.class); + when(directory.getDirectory()).thenReturn(path); + final FieldInfos fieldInfos = mock(FieldInfos.class); + when(reader.getFieldInfos()).thenReturn(fieldInfos); + // When no knn fields are available , field info for vector field will be null + when(fieldInfos.fieldInfo(FIELD_NAME)).thenReturn(null); + final Scorer knnScorer = knnWeight.scorer(leafReaderContext); + assertEquals(KNNScorer.emptyScorer(knnWeight), knnScorer); + } + + @SneakyThrows + public void testEmptyQueryResults() { + final KNNQueryResult[] knnQueryResults = new KNNQueryResult[] {}; + jniServiceMockedStatic.when(() -> JNIService.queryIndex(anyLong(), any(), eq(K), isNull(), any(), any(), anyInt(), any())) + .thenReturn(knnQueryResults); + + final KNNQuery query = new KNNQuery(FIELD_NAME, QUERY_VECTOR, K, INDEX_NAME, (BitSetProducer) null); + final KNNWeight knnWeight = new KNNWeight(query, 0.0f); + + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + final SegmentReader reader = mock(SegmentReader.class); + when(leafReaderContext.reader()).thenReturn(reader); + + final FSDirectory directory = mock(FSDirectory.class); + when(reader.directory()).thenReturn(directory); + final SegmentInfo segmentInfo = new SegmentInfo( + directory, + Version.LATEST, + Version.LATEST, + SEGMENT_NAME, + 100, + true, + false, + KNNCodecVersion.current().getDefaultCodecDelegate(), + Map.of(), + new byte[StringHelper.ID_LENGTH], + Map.of(), + Sort.RELEVANCE + ); + segmentInfo.setFiles(SEGMENT_FILES_DEFAULT); + final SegmentCommitInfo segmentCommitInfo = new SegmentCommitInfo(segmentInfo, 0, 0, 0, 0, 0, new byte[StringHelper.ID_LENGTH]); + when(reader.getSegmentInfo()).thenReturn(segmentCommitInfo); + + final Path path = mock(Path.class); + when(directory.getDirectory()).thenReturn(path); + final FieldInfos fieldInfos = mock(FieldInfos.class); + final FieldInfo fieldInfo = mock(FieldInfo.class); + when(reader.getFieldInfos()).thenReturn(fieldInfos); + when(fieldInfos.fieldInfo(any())).thenReturn(fieldInfo); + + final Scorer knnScorer = knnWeight.scorer(leafReaderContext); + assertEquals(KNNScorer.emptyScorer(knnWeight), knnScorer); + } + + @SneakyThrows + public void testScorer_whenNoFilterBinary_thenSuccess() { + validateScorer_whenNoFilter_thenSuccess(true); + } + + @SneakyThrows + public void testScorer_whenNoFilter_thenSuccess() { + validateScorer_whenNoFilter_thenSuccess(false); + } + + private void validateScorer_whenNoFilter_thenSuccess(final boolean isBinary) throws IOException { + // Given + int k = 3; + jniServiceMockedStatic.when( + () -> JNIService.queryIndex(anyLong(), eq(QUERY_VECTOR), eq(k), eq(HNSW_METHOD_PARAMETERS), any(), any(), anyInt(), any()) + ).thenReturn(getFilteredKNNQueryResults()); + + jniServiceMockedStatic.when( + () -> JNIService.queryBinaryIndex( + anyLong(), + eq(BYTE_QUERY_VECTOR), + eq(k), + eq(HNSW_METHOD_PARAMETERS), + any(), + any(), + anyInt(), + any() + ) + ).thenReturn(getFilteredKNNQueryResults()); + final SegmentReader reader = mockSegmentReader(); + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + when(leafReaderContext.reader()).thenReturn(reader); + + final KNNQuery query = isBinary + ? KNNQuery.builder() + .field(FIELD_NAME) + .byteQueryVector(BYTE_QUERY_VECTOR) + .k(k) + .indexName(INDEX_NAME) + .filterQuery(FILTER_QUERY) + .methodParameters(HNSW_METHOD_PARAMETERS) + .vectorDataType(VectorDataType.BINARY) + .build() + : KNNQuery.builder() + .field(FIELD_NAME) + .queryVector(QUERY_VECTOR) + .k(k) + .indexName(INDEX_NAME) + .filterQuery(FILTER_QUERY) + .methodParameters(HNSW_METHOD_PARAMETERS) + .vectorDataType(VectorDataType.FLOAT) + .build(); + + final float boost = (float) randomDoubleBetween(0, 10, true); + final KNNWeight knnWeight = new KNNWeight(query, boost); + final FieldInfos fieldInfos = mock(FieldInfos.class); + final FieldInfo fieldInfo = mock(FieldInfo.class); + final Map attributesMap = ImmutableMap.of( + KNN_ENGINE, + KNNEngine.FAISS.getName(), + PARAMETERS, + String.format(Locale.ROOT, "{\"%s\":\"%s\"}", INDEX_DESCRIPTION_PARAMETER, "HNSW32") + ); + + when(reader.getFieldInfos()).thenReturn(fieldInfos); + when(fieldInfos.fieldInfo(any())).thenReturn(fieldInfo); + when(fieldInfo.attributes()).thenReturn(attributesMap); + + // When + final KNNScorer knnScorer = (KNNScorer) knnWeight.scorer(leafReaderContext); + + // Then + assertNotNull(knnScorer); + if (isBinary) { + jniServiceMockedStatic.verify( + () -> JNIService.queryBinaryIndex( + anyLong(), + eq(BYTE_QUERY_VECTOR), + eq(k), + eq(HNSW_METHOD_PARAMETERS), + any(), + any(), + anyInt(), + any() + ), + times(1) + ); + } else { + jniServiceMockedStatic.verify( + () -> JNIService.queryIndex(anyLong(), eq(QUERY_VECTOR), eq(k), eq(HNSW_METHOD_PARAMETERS), any(), any(), anyInt(), any()), + times(1) + ); + } + } + + @SneakyThrows + public void testANNWithFilterQuery_whenDoingANN_thenSuccess() { + validateANNWithFilterQuery_whenDoingANN_thenSuccess(false); + } + + @SneakyThrows + public void testANNWithFilterQuery_whenDoingANNBinary_thenSuccess() { + validateANNWithFilterQuery_whenDoingANN_thenSuccess(true); + } + + @SneakyThrows + public void testScorerWithQuantizedVector() { + // Given + int k = 3; + byte[] quantizedVector = new byte[] { 1, 2, 3 }; // Mocked quantized vector + float[] queryVector = new float[] { 0.1f, 0.3f }; + + // Mock the JNI service to return KNNQueryResults + KNNQueryResult[] knnQueryResults = new KNNQueryResult[] { + new KNNQueryResult(1, 10.0f), // Mock result with id 1 and score 10 + new KNNQueryResult(2, 20.0f) // Mock result with id 2 and score 20 + }; + jniServiceMockedStatic.when( + () -> JNIService.queryBinaryIndex(anyLong(), eq(quantizedVector), eq(k), any(), any(), any(), anyInt(), any()) + ).thenReturn(knnQueryResults); + + KNNEngine knnEngine = mock(KNNEngine.class); + when(knnEngine.score(anyFloat(), eq(SpaceType.HAMMING))).thenAnswer(invocation -> { + Float score = invocation.getArgument(0); + return 1 / (1 + score); + }); + + // Build the KNNQuery object + final KNNQuery query = KNNQuery.builder() + .field(FIELD_NAME) + .queryVector(queryVector) + .k(k) + .indexName(INDEX_NAME) + .vectorDataType(VectorDataType.BINARY) // Simulate binary vector type for quantization + .build(); + + final float boost = 1.0F; + final KNNWeight knnWeight = new KNNWeight(query, boost); + + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + final SegmentReader reader = mock(SegmentReader.class); + when(leafReaderContext.reader()).thenReturn(reader); + + final FieldInfos fieldInfos = mock(FieldInfos.class); + final FieldInfo fieldInfo = mock(FieldInfo.class); + when(reader.getFieldInfos()).thenReturn(fieldInfos); + when(fieldInfos.fieldInfo(FIELD_NAME)).thenReturn(fieldInfo); + + when(fieldInfo.attributes()).thenReturn(Map.of(KNN_ENGINE, KNNEngine.FAISS.getName(), SPACE_TYPE, SpaceType.HAMMING.getValue())); + + FSDirectory directory = mock(FSDirectory.class); + when(reader.directory()).thenReturn(directory); + Path path = mock(Path.class); + when(directory.getDirectory()).thenReturn(path); + when(path.toString()).thenReturn("/fake/directory"); + + SegmentInfo segmentInfo = new SegmentInfo( + directory, // The directory where the segment is stored + Version.LATEST, // Lucene version + Version.LATEST, // Version of the segment info + "0", // Segment name + 100, // Max document count for this segment + false, // Is this a compound file segment + false, // Is this a merged segment + KNNCodecVersion.current().getDefaultCodecDelegate(), // Codec delegate for KNN + Map.of(), // Diagnostics map + new byte[StringHelper.ID_LENGTH], // Segment ID + Map.of(), // Attributes + Sort.RELEVANCE // Default sort order + ); + + final SegmentCommitInfo segmentCommitInfo = new SegmentCommitInfo(segmentInfo, 0, 0, 0, 0, 0, new byte[StringHelper.ID_LENGTH]); + + when(reader.getSegmentInfo()).thenReturn(segmentCommitInfo); + + try (MockedStatic knnCodecUtilMockedStatic = mockStatic(KNNCodecUtil.class)) { + List engineFiles = List.of("_0_1_target_field.faiss"); + knnCodecUtilMockedStatic.when(() -> KNNCodecUtil.getEngineFiles(anyString(), anyString(), eq(segmentInfo))) + .thenReturn(engineFiles); + + try (MockedStatic quantizationUtilMockedStatic = mockStatic(SegmentLevelQuantizationUtil.class)) { + quantizationUtilMockedStatic.when(() -> SegmentLevelQuantizationUtil.quantizeVector(any(), any())) + .thenReturn(quantizedVector); + + // When: Call the scorer method + final KNNScorer knnScorer = (KNNScorer) knnWeight.scorer(leafReaderContext); + + // Then: Ensure scorer is not null + assertNotNull(knnScorer); + + // Verify that JNIService.queryBinaryIndex is called with the quantized vector + jniServiceMockedStatic.verify( + () -> JNIService.queryBinaryIndex(anyLong(), eq(quantizedVector), eq(k), any(), any(), any(), anyInt(), any()), + times(1) + ); + + // Iterate over the results and ensure they are scored with SpaceType.HAMMING + final DocIdSetIterator docIdSetIterator = knnScorer.iterator(); + assertNotNull(docIdSetIterator); + while (docIdSetIterator.nextDoc() != DocIdSetIterator.NO_MORE_DOCS) { + int docId = docIdSetIterator.docID(); + float expectedScore = knnEngine.score(knnQueryResults[docId - 1].getScore(), SpaceType.HAMMING); + float actualScore = knnScorer.score(); + // Check if the score is calculated using HAMMING + assertEquals(expectedScore, actualScore, 0.01f); // Tolerance for floating-point comparison + } + } + } + } + + public void validateANNWithFilterQuery_whenDoingANN_thenSuccess(final boolean isBinary) throws IOException { + // Given + int k = 3; + final int[] filterDocIds = new int[] { 0, 1, 2, 3, 4, 5 }; + FixedBitSet filterBitSet = new FixedBitSet(filterDocIds.length); + for (int docId : filterDocIds) { + filterBitSet.set(docId); + } + if (isBinary) { + jniServiceMockedStatic.when( + () -> JNIService.queryBinaryIndex( + anyLong(), + eq(BYTE_QUERY_VECTOR), + eq(k), + eq(HNSW_METHOD_PARAMETERS), + any(), + eq(filterBitSet.getBits()), + anyInt(), + any() + ) + ).thenReturn(getFilteredKNNQueryResults()); + } else { + jniServiceMockedStatic.when( + () -> JNIService.queryIndex( + anyLong(), + eq(QUERY_VECTOR), + eq(k), + eq(HNSW_METHOD_PARAMETERS), + any(), + eq(filterBitSet.getBits()), + anyInt(), + any() + ) + ).thenReturn(getFilteredKNNQueryResults()); + } + + final Bits liveDocsBits = mock(Bits.class); + for (int filterDocId : filterDocIds) { + when(liveDocsBits.get(filterDocId)).thenReturn(true); + } + when(liveDocsBits.length()).thenReturn(1000); + + final SegmentReader reader = mockSegmentReader(); + when(reader.maxDoc()).thenReturn(filterDocIds.length); + when(reader.getLiveDocs()).thenReturn(liveDocsBits); + + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + when(leafReaderContext.reader()).thenReturn(reader); + + final KNNQuery query = isBinary + ? KNNQuery.builder() + .field(FIELD_NAME) + .byteQueryVector(BYTE_QUERY_VECTOR) + .vectorDataType(VectorDataType.BINARY) + .k(k) + .indexName(INDEX_NAME) + .filterQuery(FILTER_QUERY) + .methodParameters(HNSW_METHOD_PARAMETERS) + .build() + : KNNQuery.builder() + .field(FIELD_NAME) + .queryVector(QUERY_VECTOR) + .k(k) + .indexName(INDEX_NAME) + .filterQuery(FILTER_QUERY) + .methodParameters(HNSW_METHOD_PARAMETERS) + .build(); + + final Weight filterQueryWeight = mock(Weight.class); + final Scorer filterScorer = mock(Scorer.class); + when(filterQueryWeight.scorer(leafReaderContext)).thenReturn(filterScorer); + // Just to make sure that we are not hitting the exact search condition + when(filterScorer.iterator()).thenReturn(DocIdSetIterator.all(filterDocIds.length + 1)); + + final float boost = (float) randomDoubleBetween(0, 10, true); + final KNNWeight knnWeight = new KNNWeight(query, boost, filterQueryWeight); + + final FieldInfos fieldInfos = mock(FieldInfos.class); + final FieldInfo fieldInfo = mock(FieldInfo.class); + final Map attributesMap = ImmutableMap.of( + KNN_ENGINE, + KNNEngine.FAISS.getName(), + SPACE_TYPE, + isBinary ? SpaceType.HAMMING.getValue() : SpaceType.L2.getValue() + ); + + when(reader.getFieldInfos()).thenReturn(fieldInfos); + when(fieldInfos.fieldInfo(any())).thenReturn(fieldInfo); + when(fieldInfo.attributes()).thenReturn(attributesMap); + + // When + final KNNScorer knnScorer = (KNNScorer) knnWeight.scorer(leafReaderContext); + + // Then + assertNotNull(knnScorer); + final DocIdSetIterator docIdSetIterator = knnScorer.iterator(); + assertNotNull(docIdSetIterator); + assertEquals(FILTERED_DOC_ID_TO_SCORES.size(), docIdSetIterator.cost()); + + if (isBinary) { + jniServiceMockedStatic.verify( + () -> JNIService.queryBinaryIndex( + anyLong(), + eq(BYTE_QUERY_VECTOR), + eq(k), + eq(HNSW_METHOD_PARAMETERS), + any(), + any(), + anyInt(), + any() + ), + times(1) + ); + } else { + jniServiceMockedStatic.verify( + () -> JNIService.queryIndex(anyLong(), eq(QUERY_VECTOR), eq(k), eq(HNSW_METHOD_PARAMETERS), any(), any(), anyInt(), any()), + times(1) + ); + } + + final List actualDocIds = new ArrayList<>(); + final Map translatedScores = getTranslatedScores(SpaceType.L2::scoreTranslation); + for (int docId = docIdSetIterator.nextDoc(); docId != NO_MORE_DOCS; docId = docIdSetIterator.nextDoc()) { + actualDocIds.add(docId); + assertEquals(translatedScores.get(docId) * boost, knnScorer.score(), 0.01f); + } + assertEquals(docIdSetIterator.cost(), actualDocIds.size()); + assertTrue(Comparators.isInOrder(actualDocIds, Comparator.naturalOrder())); + } + + private SegmentReader mockSegmentReader() { + Path path = mock(Path.class); + + FSDirectory directory = mock(FSDirectory.class); + when(directory.getDirectory()).thenReturn(path); + + SegmentInfo segmentInfo = new SegmentInfo( + directory, + Version.LATEST, + Version.LATEST, + SEGMENT_NAME, + 100, + true, + false, + KNNCodecVersion.current().getDefaultCodecDelegate(), + Map.of(), + new byte[StringHelper.ID_LENGTH], + Map.of(), + Sort.RELEVANCE + ); + segmentInfo.setFiles(SEGMENT_FILES_FAISS); + SegmentCommitInfo segmentCommitInfo = new SegmentCommitInfo(segmentInfo, 0, 0, 0, 0, 0, new byte[StringHelper.ID_LENGTH]); + + SegmentReader reader = mock(SegmentReader.class); + when(reader.directory()).thenReturn(directory); + when(reader.getSegmentInfo()).thenReturn(segmentCommitInfo); + return reader; + } + + @SneakyThrows + public void testANNWithFilterQuery_whenExactSearch_thenSuccess() { + validateANNWithFilterQuery_whenExactSearch_thenSuccess(false); + } + + @SneakyThrows + public void testANNWithFilterQuery_whenExactSearchBinary_thenSuccess() { + validateANNWithFilterQuery_whenExactSearch_thenSuccess(true); + } + + public void validateANNWithFilterQuery_whenExactSearch_thenSuccess(final boolean isBinary) throws IOException { + try (MockedStatic valuesFactoryMockedStatic = Mockito.mockStatic(KNNVectorValuesFactory.class)) { + KNNWeight.initialize(null); + float[] vector = new float[] { 0.1f, 0.3f }; + byte[] byteVector = new byte[] { 1, 3 }; + int filterDocId = 0; + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + final SegmentReader reader = mock(SegmentReader.class); + when(leafReaderContext.reader()).thenReturn(reader); + + final KNNQuery query = isBinary + ? new KNNQuery(FIELD_NAME, BYTE_QUERY_VECTOR, K, INDEX_NAME, FILTER_QUERY, null, VectorDataType.BINARY, null) + : new KNNQuery(FIELD_NAME, QUERY_VECTOR, K, INDEX_NAME, FILTER_QUERY, null, null); + final Weight filterQueryWeight = mock(Weight.class); + final Scorer filterScorer = mock(Scorer.class); + when(filterQueryWeight.scorer(leafReaderContext)).thenReturn(filterScorer); + // scorer will return 2 documents + when(filterScorer.iterator()).thenReturn(DocIdSetIterator.all(1)); + when(reader.maxDoc()).thenReturn(1); + final Bits liveDocsBits = mock(Bits.class); + when(reader.getLiveDocs()).thenReturn(liveDocsBits); + when(liveDocsBits.get(filterDocId)).thenReturn(true); + + final float boost = (float) randomDoubleBetween(0, 10, true); + final KNNWeight knnWeight = new KNNWeight(query, boost, filterQueryWeight); + final Map attributesMap = ImmutableMap.of( + KNN_ENGINE, + KNNEngine.FAISS.getName(), + SPACE_TYPE, + isBinary ? SpaceType.HAMMING.getValue() : SpaceType.L2.getValue() + ); + final FieldInfos fieldInfos = mock(FieldInfos.class); + final FieldInfo fieldInfo = mock(FieldInfo.class); + final KNNFloatVectorValues floatVectorValues = mock(KNNFloatVectorValues.class); + final KNNBinaryVectorValues binaryVectorValues = mock(KNNBinaryVectorValues.class); + when(reader.getFieldInfos()).thenReturn(fieldInfos); + when(fieldInfos.fieldInfo(any())).thenReturn(fieldInfo); + when(fieldInfo.attributes()).thenReturn(attributesMap); + if (isBinary) { + when(fieldInfo.getAttribute(SPACE_TYPE)).thenReturn(SpaceType.HAMMING.getValue()); + } else { + when(fieldInfo.getAttribute(SPACE_TYPE)).thenReturn(SpaceType.L2.getValue()); + } + when(fieldInfo.getName()).thenReturn(FIELD_NAME); + + if (isBinary) { + valuesFactoryMockedStatic.when(() -> KNNVectorValuesFactory.getVectorValues(fieldInfo, reader)) + .thenReturn(binaryVectorValues); + when(binaryVectorValues.advance(filterDocId)).thenReturn(filterDocId); + Mockito.when(binaryVectorValues.getVector()).thenReturn(byteVector); + } else { + valuesFactoryMockedStatic.when(() -> KNNVectorValuesFactory.getVectorValues(fieldInfo, reader)) + .thenReturn(floatVectorValues); + when(floatVectorValues.advance(filterDocId)).thenReturn(filterDocId); + Mockito.when(floatVectorValues.getVector()).thenReturn(vector); + } + + final KNNScorer knnScorer = (KNNScorer) knnWeight.scorer(leafReaderContext); + assertNotNull(knnScorer); + final DocIdSetIterator docIdSetIterator = knnScorer.iterator(); + assertNotNull(docIdSetIterator); + assertEquals(1, docIdSetIterator.cost()); + + final List actualDocIds = new ArrayList<>(); + for (int docId = docIdSetIterator.nextDoc(); docId != NO_MORE_DOCS; docId = docIdSetIterator.nextDoc()) { + actualDocIds.add(docId); + if (isBinary) { + assertEquals(BINARY_EXACT_SEARCH_DOC_ID_TO_SCORES.get(docId) * boost, knnScorer.score(), 0.01f); + } else { + assertEquals(EXACT_SEARCH_DOC_ID_TO_SCORES.get(docId) * boost, knnScorer.score(), 0.01f); + } + } + assertEquals(docIdSetIterator.cost(), actualDocIds.size()); + assertTrue(Comparators.isInOrder(actualDocIds, Comparator.naturalOrder())); + } + } + + @SneakyThrows + public void testRadialSearch_whenNoEngineFiles_thenPerformExactSearch() { + ExactSearcher mockedExactSearcher = mock(ExactSearcher.class); + final float[] queryVector = new float[] { 0.1f, 2.0f, 3.0f }; + final SpaceType spaceType = randomFrom(SpaceType.L2, SpaceType.INNER_PRODUCT); + KNNWeight.initialize(null, mockedExactSearcher); + final KNNQuery query = KNNQuery.builder() + .field(FIELD_NAME) + .queryVector(queryVector) + .indexName(INDEX_NAME) + .methodParameters(HNSW_METHOD_PARAMETERS) + .build(); + final KNNWeight knnWeight = new KNNWeight(query, 1.0f); + + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + final SegmentReader reader = mock(SegmentReader.class); + when(leafReaderContext.reader()).thenReturn(reader); + + final FSDirectory directory = mock(FSDirectory.class); + when(reader.directory()).thenReturn(directory); + final SegmentInfo segmentInfo = new SegmentInfo( + directory, + Version.LATEST, + Version.LATEST, + SEGMENT_NAME, + 100, + false, + false, + KNNCodecVersion.current().getDefaultCodecDelegate(), + Map.of(), + new byte[StringHelper.ID_LENGTH], + Map.of(), + Sort.RELEVANCE + ); + segmentInfo.setFiles(Set.of()); + final SegmentCommitInfo segmentCommitInfo = new SegmentCommitInfo(segmentInfo, 0, 0, 0, 0, 0, new byte[StringHelper.ID_LENGTH]); + when(reader.getSegmentInfo()).thenReturn(segmentCommitInfo); + + final Path path = mock(Path.class); + when(directory.getDirectory()).thenReturn(path); + final FieldInfos fieldInfos = mock(FieldInfos.class); + final FieldInfo fieldInfo = mock(FieldInfo.class); + when(reader.getFieldInfos()).thenReturn(fieldInfos); + when(fieldInfos.fieldInfo(FIELD_NAME)).thenReturn(fieldInfo); + when(fieldInfo.attributes()).thenReturn( + Map.of( + SPACE_TYPE, + spaceType.getValue(), + KNN_ENGINE, + KNNEngine.FAISS.getName(), + PARAMETERS, + String.format(Locale.ROOT, "{\"%s\":\"%s\"}", INDEX_DESCRIPTION_PARAMETER, "HNSW32") + ) + ); + final ExactSearcher.ExactSearcherContext exactSearchContext = ExactSearcher.ExactSearcherContext.builder() + .isParentHits(true) + // setting to true, so that if quantization details are present we want to do search on the quantized + // vectors as this flow is used in first pass of search. + .useQuantizedVectorsForSearch(true) + .knnQuery(query) + .build(); + when(mockedExactSearcher.searchLeaf(leafReaderContext, exactSearchContext)).thenReturn(DOC_ID_TO_SCORES); + final KNNScorer knnScorer = (KNNScorer) knnWeight.scorer(leafReaderContext); + assertNotNull(knnScorer); + final DocIdSetIterator docIdSetIterator = knnScorer.iterator(); + final List actualDocIds = new ArrayList<>(); + for (int docId = docIdSetIterator.nextDoc(); docId != NO_MORE_DOCS; docId = docIdSetIterator.nextDoc()) { + actualDocIds.add(docId); + assertEquals(DOC_ID_TO_SCORES.get(docId), knnScorer.score(), 0.00000001f); + } + assertEquals(docIdSetIterator.cost(), actualDocIds.size()); + assertTrue(Comparators.isInOrder(actualDocIds, Comparator.naturalOrder())); + // verify JNI Service is not called + jniServiceMockedStatic.verifyNoInteractions(); + verify(mockedExactSearcher).searchLeaf(leafReaderContext, exactSearchContext); + } + + @SneakyThrows + public void testANNWithFilterQuery_whenExactSearchAndThresholdComputations_thenSuccess() { + ModelDao modelDao = mock(ModelDao.class); + KNNWeight.initialize(modelDao); + knnSettingsMockedStatic.when(() -> KNNSettings.getFilteredExactSearchThreshold(INDEX_NAME)).thenReturn(-1); + float[] vector = new float[] { 0.1f, 0.3f }; + int filterDocId = 0; + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + final SegmentReader reader = mock(SegmentReader.class); + when(leafReaderContext.reader()).thenReturn(reader); + + final KNNQuery query = new KNNQuery(FIELD_NAME, QUERY_VECTOR, K, INDEX_NAME, FILTER_QUERY, null, null); + final Weight filterQueryWeight = mock(Weight.class); + final Scorer filterScorer = mock(Scorer.class); + when(filterQueryWeight.scorer(leafReaderContext)).thenReturn(filterScorer); + // scorer will return 2 documents + when(filterScorer.iterator()).thenReturn(DocIdSetIterator.all(1)); + when(reader.maxDoc()).thenReturn(1); + final Bits liveDocsBits = mock(Bits.class); + when(reader.getLiveDocs()).thenReturn(liveDocsBits); + when(liveDocsBits.get(filterDocId)).thenReturn(true); + + final float boost = (float) randomDoubleBetween(0, 10, true); + final KNNWeight knnWeight = new KNNWeight(query, boost, filterQueryWeight); + final Map attributesMap = ImmutableMap.of( + KNN_ENGINE, + KNNEngine.FAISS.getName(), + SPACE_TYPE, + SpaceType.L2.name(), + PARAMETERS, + String.format(Locale.ROOT, "{\"%s\":\"%s\"}", INDEX_DESCRIPTION_PARAMETER, "HNSW32") + ); + final FieldInfos fieldInfos = mock(FieldInfos.class); + final FieldInfo fieldInfo = mock(FieldInfo.class); + final BinaryDocValues binaryDocValues = mock(BinaryDocValues.class); + when(reader.getFieldInfos()).thenReturn(fieldInfos); + when(fieldInfos.fieldInfo(any())).thenReturn(fieldInfo); + when(fieldInfo.attributes()).thenReturn(attributesMap); + when(fieldInfo.getAttribute(SPACE_TYPE)).thenReturn(SpaceType.L2.name()); + when(fieldInfo.getName()).thenReturn(FIELD_NAME); + when(reader.getBinaryDocValues(FIELD_NAME)).thenReturn(binaryDocValues); + when(binaryDocValues.advance(filterDocId)).thenReturn(filterDocId); + BytesRef vectorByteRef = new BytesRef(new KNNVectorAsArraySerializer().floatToByteArray(vector)); + when(binaryDocValues.binaryValue()).thenReturn(vectorByteRef); + + final KNNScorer knnScorer = (KNNScorer) knnWeight.scorer(leafReaderContext); + assertNotNull(knnScorer); + final DocIdSetIterator docIdSetIterator = knnScorer.iterator(); + assertNotNull(docIdSetIterator); + assertEquals(EXACT_SEARCH_DOC_ID_TO_SCORES.size(), docIdSetIterator.cost()); + + final List actualDocIds = new ArrayList<>(); + for (int docId = docIdSetIterator.nextDoc(); docId != NO_MORE_DOCS; docId = docIdSetIterator.nextDoc()) { + actualDocIds.add(docId); + assertEquals(EXACT_SEARCH_DOC_ID_TO_SCORES.get(docId) * boost, knnScorer.score(), 0.01f); + } + assertEquals(docIdSetIterator.cost(), actualDocIds.size()); + assertTrue(Comparators.isInOrder(actualDocIds, Comparator.naturalOrder())); + } + + /** + * This test ensure that we do the exact search when threshold settings are correct and not using filteredIds<=K + * condition to do exact search. + * FilteredIdThreshold: 10 + * FilteredIdThresholdPct: 10% + * FilteredIdsCount: 6 + * liveDocs : null, as there is no deleted documents + * MaxDoc: 100 + * K : 1 + */ + @SneakyThrows + public void testANNWithFilterQuery_whenExactSearchViaThresholdSetting_thenSuccess() { + ModelDao modelDao = mock(ModelDao.class); + KNNWeight.initialize(modelDao); + knnSettingsMockedStatic.when(() -> KNNSettings.getFilteredExactSearchThreshold(INDEX_NAME)).thenReturn(10); + float[] vector = new float[] { 0.1f, 0.3f }; + int k = 1; + final int[] filterDocIds = new int[] { 0, 1, 2, 3, 4, 5 }; + + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + final SegmentReader reader = mock(SegmentReader.class); + when(leafReaderContext.reader()).thenReturn(reader); + when(reader.maxDoc()).thenReturn(100); + when(reader.getLiveDocs()).thenReturn(null); + final Weight filterQueryWeight = mock(Weight.class); + final Scorer filterScorer = mock(Scorer.class); + when(filterQueryWeight.scorer(leafReaderContext)).thenReturn(filterScorer); + + when(filterScorer.iterator()).thenReturn(DocIdSetIterator.all(filterDocIds.length)); + + final KNNQuery query = new KNNQuery(FIELD_NAME, QUERY_VECTOR, k, INDEX_NAME, FILTER_QUERY, null, null); + + final float boost = (float) randomDoubleBetween(0, 10, true); + final KNNWeight knnWeight = new KNNWeight(query, boost, filterQueryWeight); + final Map attributesMap = ImmutableMap.of( + KNN_ENGINE, + KNNEngine.FAISS.getName(), + SPACE_TYPE, + SpaceType.L2.name(), + PARAMETERS, + String.format(Locale.ROOT, "{\"%s\":\"%s\"}", INDEX_DESCRIPTION_PARAMETER, "HNSW32") + ); + final FieldInfos fieldInfos = mock(FieldInfos.class); + final FieldInfo fieldInfo = mock(FieldInfo.class); + final BinaryDocValues binaryDocValues = mock(BinaryDocValues.class); + when(reader.getFieldInfos()).thenReturn(fieldInfos); + when(fieldInfos.fieldInfo(any())).thenReturn(fieldInfo); + when(fieldInfo.attributes()).thenReturn(attributesMap); + when(fieldInfo.getAttribute(SPACE_TYPE)).thenReturn(SpaceType.L2.name()); + when(fieldInfo.getName()).thenReturn(FIELD_NAME); + when(reader.getBinaryDocValues(FIELD_NAME)).thenReturn(binaryDocValues); + when(binaryDocValues.advance(0)).thenReturn(0); + BytesRef vectorByteRef = new BytesRef(new KNNVectorAsArraySerializer().floatToByteArray(vector)); + when(binaryDocValues.binaryValue()).thenReturn(vectorByteRef); + + final KNNScorer knnScorer = (KNNScorer) knnWeight.scorer(leafReaderContext); + assertNotNull(knnScorer); + final DocIdSetIterator docIdSetIterator = knnScorer.iterator(); + assertNotNull(docIdSetIterator); + assertEquals(EXACT_SEARCH_DOC_ID_TO_SCORES.size(), docIdSetIterator.cost()); + + final List actualDocIds = new ArrayList<>(); + for (int docId = docIdSetIterator.nextDoc(); docId != NO_MORE_DOCS; docId = docIdSetIterator.nextDoc()) { + actualDocIds.add(docId); + assertEquals(EXACT_SEARCH_DOC_ID_TO_SCORES.get(docId) * boost, knnScorer.score(), 0.01f); + } + assertEquals(docIdSetIterator.cost(), actualDocIds.size()); + assertTrue(Comparators.isInOrder(actualDocIds, Comparator.naturalOrder())); + } + + /** + * This test ensure that we do the exact search when threshold settings are correct and not using filteredIds<=K + * condition to do exact search on binary index + * FilteredIdThreshold: 10 + * FilteredIdThresholdPct: 10% + * FilteredIdsCount: 6 + * liveDocs : null, as there is no deleted documents + * MaxDoc: 100 + * K : 1 + */ + @SneakyThrows + public void testANNWithFilterQuery_whenExactSearchViaThresholdSettingOnBinaryIndex_thenSuccess() { + try (MockedStatic vectorValuesFactoryMockedStatic = Mockito.mockStatic(KNNVectorValuesFactory.class)) { + KNNWeight.initialize(null); + knnSettingsMockedStatic.when(() -> KNNSettings.getFilteredExactSearchThreshold(INDEX_NAME)).thenReturn(10); + byte[] vector = new byte[] { 1, 3 }; + int k = 1; + final int[] filterDocIds = new int[] { 0, 1, 2, 3, 4, 5 }; + + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + final SegmentReader reader = mock(SegmentReader.class); + when(leafReaderContext.reader()).thenReturn(reader); + when(reader.maxDoc()).thenReturn(100); + when(reader.getLiveDocs()).thenReturn(null); + final Weight filterQueryWeight = mock(Weight.class); + final Scorer filterScorer = mock(Scorer.class); + when(filterQueryWeight.scorer(leafReaderContext)).thenReturn(filterScorer); + + when(filterScorer.iterator()).thenReturn(DocIdSetIterator.all(filterDocIds.length)); + + final KNNQuery query = new KNNQuery( + FIELD_NAME, + BYTE_QUERY_VECTOR, + k, + INDEX_NAME, + FILTER_QUERY, + null, + VectorDataType.BINARY, + null + ); + + final float boost = (float) randomDoubleBetween(0, 10, true); + final KNNWeight knnWeight = new KNNWeight(query, boost, filterQueryWeight); + final Map attributesMap = ImmutableMap.of( + KNN_ENGINE, + KNNEngine.FAISS.getName(), + SPACE_TYPE, + SpaceType.HAMMING.name(), + PARAMETERS, + String.format(Locale.ROOT, "{\"%s\":\"%s\"}", INDEX_DESCRIPTION_PARAMETER, "BHNSW32") + ); + final FieldInfos fieldInfos = mock(FieldInfos.class); + final FieldInfo fieldInfo = mock(FieldInfo.class); + when(reader.getFieldInfos()).thenReturn(fieldInfos); + when(fieldInfos.fieldInfo(any())).thenReturn(fieldInfo); + when(fieldInfo.attributes()).thenReturn(attributesMap); + when(fieldInfo.getAttribute(SPACE_TYPE)).thenReturn(SpaceType.HAMMING.getValue()); + when(fieldInfo.getName()).thenReturn(FIELD_NAME); + + KNNBinaryVectorValues knnBinaryVectorValues = mock(KNNBinaryVectorValues.class); + + vectorValuesFactoryMockedStatic.when(() -> KNNVectorValuesFactory.getVectorValues(fieldInfo, reader)) + .thenReturn(knnBinaryVectorValues); + when(knnBinaryVectorValues.advance(0)).thenReturn(0); + when(knnBinaryVectorValues.getVector()).thenReturn(vector); + + final KNNScorer knnScorer = (KNNScorer) knnWeight.scorer(leafReaderContext); + assertNotNull(knnScorer); + final DocIdSetIterator docIdSetIterator = knnScorer.iterator(); + assertNotNull(docIdSetIterator); + assertEquals(EXACT_SEARCH_DOC_ID_TO_SCORES.size(), docIdSetIterator.cost()); + + final List actualDocIds = new ArrayList<>(); + for (int docId = docIdSetIterator.nextDoc(); docId != NO_MORE_DOCS; docId = docIdSetIterator.nextDoc()) { + actualDocIds.add(docId); + assertEquals(BINARY_EXACT_SEARCH_DOC_ID_TO_SCORES.get(docId) * boost, knnScorer.score(), 0.01f); + } + assertEquals(docIdSetIterator.cost(), actualDocIds.size()); + assertTrue(Comparators.isInOrder(actualDocIds, Comparator.naturalOrder())); + } + } + + @SneakyThrows + public void testANNWithFilterQuery_whenEmptyFilterIds_thenReturnEarly() { + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + final SegmentReader reader = mock(SegmentReader.class); + when(leafReaderContext.reader()).thenReturn(reader); + + final Weight filterQueryWeight = mock(Weight.class); + final Scorer filterScorer = mock(Scorer.class); + when(filterQueryWeight.scorer(leafReaderContext)).thenReturn(filterScorer); + when(filterScorer.iterator()).thenReturn(DocIdSetIterator.empty()); + + final KNNQuery query = new KNNQuery(FIELD_NAME, QUERY_VECTOR, K, INDEX_NAME, FILTER_QUERY, null, null); + final KNNWeight knnWeight = new KNNWeight(query, 0.0f, filterQueryWeight); + + final FieldInfos fieldInfos = mock(FieldInfos.class); + final FieldInfo fieldInfo = mock(FieldInfo.class); + when(reader.getFieldInfos()).thenReturn(fieldInfos); + when(fieldInfos.fieldInfo(any())).thenReturn(fieldInfo); + + final Scorer knnScorer = knnWeight.scorer(leafReaderContext); + assertNotNull(knnScorer); + final DocIdSetIterator docIdSetIterator = knnScorer.iterator(); + assertNotNull(docIdSetIterator); + assertEquals(0, docIdSetIterator.cost()); + assertEquals(0, docIdSetIterator.cost()); + } + + @SneakyThrows + public void testANNWithParentsFilter_whenExactSearch_thenSuccess() { + ModelDao modelDao = mock(ModelDao.class); + KNNWeight.initialize(modelDao); + SegmentReader reader = getMockedSegmentReader(); + + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + when(leafReaderContext.reader()).thenReturn(reader); + + // We will have 0, 1 for filteredIds and 2 will be the parent id for both of them + final Scorer filterScorer = mock(Scorer.class); + when(filterScorer.iterator()).thenReturn(DocIdSetIterator.all(2)); + when(reader.maxDoc()).thenReturn(2); + + // Query vector is {1.8f, 2.4f}, therefore, second vector {1.9f, 2.5f} should be returned in a result + final List vectors = Arrays.asList(new float[] { 0.1f, 0.3f }, new float[] { 1.9f, 2.5f }); + final List byteRefs = vectors.stream() + .map(vector -> new BytesRef(new KNNVectorAsArraySerializer().floatToByteArray(vector))) + .collect(Collectors.toList()); + final BinaryDocValues binaryDocValues = mock(BinaryDocValues.class); + when(binaryDocValues.binaryValue()).thenReturn(byteRefs.get(0), byteRefs.get(1)); + when(binaryDocValues.advance(anyInt())).thenReturn(0, 1); + when(reader.getBinaryDocValues(FIELD_NAME)).thenReturn(binaryDocValues); + + // Parent ID 2 in bitset is 100 which is 4 + FixedBitSet parentIds = new FixedBitSet(new long[] { 4 }, 3); + BitSetProducer parentFilter = mock(BitSetProducer.class); + when(parentFilter.getBitSet(leafReaderContext)).thenReturn(parentIds); + + final Weight filterQueryWeight = mock(Weight.class); + when(filterQueryWeight.scorer(leafReaderContext)).thenReturn(filterScorer); + + final KNNQuery query = new KNNQuery(FIELD_NAME, QUERY_VECTOR, K, INDEX_NAME, FILTER_QUERY, parentFilter, null); + final float boost = (float) randomDoubleBetween(0, 10, true); + final KNNWeight knnWeight = new KNNWeight(query, boost, filterQueryWeight); + + // Execute + final KNNScorer knnScorer = (KNNScorer) knnWeight.scorer(leafReaderContext); + + // Verify + final List expectedScores = vectors.stream() + .map(vector -> SpaceType.L2.getKnnVectorSimilarityFunction().compare(QUERY_VECTOR, vector)) + .collect(Collectors.toList()); + final DocIdSetIterator docIdSetIterator = knnScorer.iterator(); + assertEquals(1, docIdSetIterator.nextDoc()); + assertEquals(expectedScores.get(1) * boost, knnScorer.score(), 0.01f); + assertEquals(NO_MORE_DOCS, docIdSetIterator.nextDoc()); + } + + @SneakyThrows + public void testANNWithParentsFilter_whenDoingANN_thenBitSetIsPassedToJNI() { + SegmentReader reader = getMockedSegmentReader(); + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + when(leafReaderContext.reader()).thenReturn(reader); + + // Prepare parentFilter + final int[] parentsFilter = { 10, 64 }; + final FixedBitSet bitset = new FixedBitSet(65); + Arrays.stream(parentsFilter).forEach(i -> bitset.set(i)); + final BitSetProducer bitSetProducer = mock(BitSetProducer.class); + + // Prepare query and weight + when(bitSetProducer.getBitSet(leafReaderContext)).thenReturn(bitset); + + final KNNQuery query = KNNQuery.builder() + .field(FIELD_NAME) + .queryVector(QUERY_VECTOR) + .k(1) + .indexName(INDEX_NAME) + .methodParameters(HNSW_METHOD_PARAMETERS) + .parentsFilter(bitSetProducer) + .build(); + + final KNNWeight knnWeight = new KNNWeight(query, 0.0f); + + jniServiceMockedStatic.when( + () -> JNIService.queryIndex( + anyLong(), + eq(QUERY_VECTOR), + eq(1), + eq(HNSW_METHOD_PARAMETERS), + any(), + any(), + anyInt(), + eq(parentsFilter) + ) + ).thenReturn(getKNNQueryResults()); + + // Execute + Scorer knnScorer = knnWeight.scorer(leafReaderContext); + + // Verify + jniServiceMockedStatic.verify( + () -> JNIService.queryIndex( + anyLong(), + eq(QUERY_VECTOR), + eq(1), + eq(HNSW_METHOD_PARAMETERS), + any(), + any(), + anyInt(), + eq(parentsFilter) + ) + ); + assertNotNull(knnScorer); + final DocIdSetIterator docIdSetIterator = knnScorer.iterator(); + assertNotNull(docIdSetIterator); + assertEquals(DOC_ID_TO_SCORES.size(), docIdSetIterator.cost()); + } + + @SneakyThrows + public void testDoANNSearch_whenRadialIsDefined_thenCallJniRadiusQueryIndex() { + final float[] queryVector = new float[] { 0.1f, 0.3f }; + final float radius = 0.5f; + final int maxResults = 1000; + jniServiceMockedStatic.when( + () -> JNIService.radiusQueryIndex( + anyLong(), + eq(queryVector), + eq(radius), + eq(HNSW_METHOD_PARAMETERS), + any(), + eq(maxResults), + any(), + anyInt(), + any() + ) + ).thenReturn(getKNNQueryResults()); + KNNQuery.Context context = mock(KNNQuery.Context.class); + when(context.getMaxResultWindow()).thenReturn(maxResults); + + final KNNQuery query = KNNQuery.builder() + .field(FIELD_NAME) + .queryVector(queryVector) + .radius(radius) + .indexName(INDEX_NAME) + .context(context) + .methodParameters(HNSW_METHOD_PARAMETERS) + .build(); + final float boost = (float) randomDoubleBetween(0, 10, true); + final KNNWeight knnWeight = new KNNWeight(query, boost); + + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + final SegmentReader reader = mock(SegmentReader.class); + when(leafReaderContext.reader()).thenReturn(reader); + + final FSDirectory directory = mock(FSDirectory.class); + when(reader.directory()).thenReturn(directory); + final SegmentInfo segmentInfo = new SegmentInfo( + directory, + Version.LATEST, + Version.LATEST, + SEGMENT_NAME, + 100, + true, + false, + KNNCodecVersion.current().getDefaultCodecDelegate(), + Map.of(), + new byte[StringHelper.ID_LENGTH], + Map.of(), + Sort.RELEVANCE + ); + segmentInfo.setFiles(SEGMENT_FILES_FAISS); + final SegmentCommitInfo segmentCommitInfo = new SegmentCommitInfo(segmentInfo, 0, 0, 0, 0, 0, new byte[StringHelper.ID_LENGTH]); + when(reader.getSegmentInfo()).thenReturn(segmentCommitInfo); + + final Path path = mock(Path.class); + when(directory.getDirectory()).thenReturn(path); + final FieldInfos fieldInfos = mock(FieldInfos.class); + final FieldInfo fieldInfo = mock(FieldInfo.class); + when(reader.getFieldInfos()).thenReturn(fieldInfos); + when(fieldInfos.fieldInfo(any())).thenReturn(fieldInfo); + when(fieldInfo.attributes()).thenReturn( + Map.of( + SPACE_TYPE, + SpaceType.L2.getValue(), + KNN_ENGINE, + KNNEngine.FAISS.getName(), + PARAMETERS, + String.format(Locale.ROOT, "{\"%s\":\"%s\"}", INDEX_DESCRIPTION_PARAMETER, "HNSW32") + ) + ); + + final KNNScorer knnScorer = (KNNScorer) knnWeight.scorer(leafReaderContext); + assertNotNull(knnScorer); + jniServiceMockedStatic.verify( + () -> JNIService.radiusQueryIndex( + anyLong(), + eq(queryVector), + eq(radius), + eq(HNSW_METHOD_PARAMETERS), + any(), + eq(maxResults), + any(), + anyInt(), + any() + ) + ); + + final DocIdSetIterator docIdSetIterator = knnScorer.iterator(); + + final List actualDocIds = new ArrayList<>(); + final Map translatedScores = getTranslatedScores(SpaceType.L2::scoreTranslation); + for (int docId = docIdSetIterator.nextDoc(); docId != NO_MORE_DOCS; docId = docIdSetIterator.nextDoc()) { + actualDocIds.add(docId); + assertEquals(translatedScores.get(docId) * boost, knnScorer.score(), 0.01f); + } + assertEquals(docIdSetIterator.cost(), actualDocIds.size()); + assertTrue(Comparators.isInOrder(actualDocIds, Comparator.naturalOrder())); + } + + private SegmentReader getMockedSegmentReader() { + final SegmentReader reader = mock(SegmentReader.class); + when(reader.maxDoc()).thenReturn(1); + + // Prepare live docs + when(reader.getLiveDocs()).thenReturn(null); + + // Prepare directory + final Path path = mock(Path.class); + final FSDirectory directory = mock(FSDirectory.class); + when(directory.getDirectory()).thenReturn(path); + when(reader.directory()).thenReturn(directory); + + // Prepare segment + final SegmentInfo segmentInfo = new SegmentInfo( + directory, + Version.LATEST, + Version.LATEST, + SEGMENT_NAME, + 100, + true, + false, + KNNCodecVersion.current().getDefaultCodecDelegate(), + Map.of(), + new byte[StringHelper.ID_LENGTH], + Map.of(), + Sort.RELEVANCE + ); + segmentInfo.setFiles(SEGMENT_FILES_FAISS); + final SegmentCommitInfo segmentCommitInfo = new SegmentCommitInfo(segmentInfo, 0, 0, 0, 0, 0, new byte[StringHelper.ID_LENGTH]); + when(reader.getSegmentInfo()).thenReturn(segmentCommitInfo); + + // Prepare fieldInfo + final Map attributesMap = ImmutableMap.of( + KNN_ENGINE, + KNNEngine.FAISS.getName(), + SPACE_TYPE, + SpaceType.L2.name(), + PARAMETERS, + String.format(Locale.ROOT, "{\"%s\":\"%s\"}", INDEX_DESCRIPTION_PARAMETER, "HNSW32") + ); + final FieldInfo fieldInfo = mock(FieldInfo.class); + when(fieldInfo.attributes()).thenReturn(attributesMap); + when(fieldInfo.getAttribute(SPACE_TYPE)).thenReturn(SpaceType.L2.name()); + when(fieldInfo.getName()).thenReturn(FIELD_NAME); + + // Prepare fieldInfos + final FieldInfos fieldInfos = mock(FieldInfos.class); + when(fieldInfos.fieldInfo(any())).thenReturn(fieldInfo); + when(reader.getFieldInfos()).thenReturn(fieldInfos); + + return reader; + } + + private void testQueryScore( + final Function scoreTranslator, + final Set segmentFiles, + final Map fileAttributes + ) throws IOException { + jniServiceMockedStatic.when( + () -> JNIService.queryIndex(anyLong(), eq(QUERY_VECTOR), eq(K), eq(HNSW_METHOD_PARAMETERS), any(), any(), anyInt(), any()) + ).thenReturn(getKNNQueryResults()); + + final KNNQuery query = KNNQuery.builder() + .field(FIELD_NAME) + .queryVector(QUERY_VECTOR) + .k(K) + .indexName(INDEX_NAME) + .methodParameters(HNSW_METHOD_PARAMETERS) + .build(); + final float boost = (float) randomDoubleBetween(0, 10, true); + final KNNWeight knnWeight = new KNNWeight(query, boost); + + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + final SegmentReader reader = mock(SegmentReader.class); + when(leafReaderContext.reader()).thenReturn(reader); + + final FSDirectory directory = mock(FSDirectory.class); + when(reader.directory()).thenReturn(directory); + final SegmentInfo segmentInfo = new SegmentInfo( + directory, + Version.LATEST, + Version.LATEST, + SEGMENT_NAME, + 100, + true, + false, + KNNCodecVersion.current().getDefaultCodecDelegate(), + Map.of(), + new byte[StringHelper.ID_LENGTH], + Map.of(), + Sort.RELEVANCE + ); + segmentInfo.setFiles(segmentFiles); + final SegmentCommitInfo segmentCommitInfo = new SegmentCommitInfo(segmentInfo, 0, 0, 0, 0, 0, new byte[StringHelper.ID_LENGTH]); + when(reader.getSegmentInfo()).thenReturn(segmentCommitInfo); + + final Path path = mock(Path.class); + when(directory.getDirectory()).thenReturn(path); + final FieldInfos fieldInfos = mock(FieldInfos.class); + final FieldInfo fieldInfo = mock(FieldInfo.class); + when(reader.getFieldInfos()).thenReturn(fieldInfos); + when(fieldInfos.fieldInfo(any())).thenReturn(fieldInfo); + when(fieldInfo.attributes()).thenReturn(fileAttributes); + + String engineName = fieldInfo.attributes().getOrDefault(KNN_ENGINE, KNNEngine.NMSLIB.getName()); + KNNEngine knnEngine = KNNEngine.getEngine(engineName); + List engineFiles = KNNCodecUtil.getEngineFiles(knnEngine.getExtension(), query.getField(), reader.getSegmentInfo().info); + String expectIndexPath = String.format("%s_%s_%s%s%s", SEGMENT_NAME, 2011, FIELD_NAME, knnEngine.getExtension(), "c"); + assertEquals(engineFiles.get(0), expectIndexPath); + + final KNNScorer knnScorer = (KNNScorer) knnWeight.scorer(leafReaderContext); + assertNotNull(knnScorer); + final DocIdSetIterator docIdSetIterator = knnScorer.iterator(); + assertNotNull(docIdSetIterator); + assertEquals(DOC_ID_TO_SCORES.size(), docIdSetIterator.cost()); + + final List actualDocIds = new ArrayList(); + final Map translatedScores = getTranslatedScores(scoreTranslator); + for (int docId = docIdSetIterator.nextDoc(); docId != NO_MORE_DOCS; docId = docIdSetIterator.nextDoc()) { + actualDocIds.add(docId); + assertEquals(translatedScores.get(docId) * boost, knnScorer.score(), 0.01f); + } + assertEquals(docIdSetIterator.cost(), actualDocIds.size()); + assertTrue(Comparators.isInOrder(actualDocIds, Comparator.naturalOrder())); + } + + private Map getTranslatedScores(Function scoreTranslator) { + return DOC_ID_TO_SCORES.entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, entry -> scoreTranslator.apply(entry.getValue()))); + } + + private KNNQueryResult[] getKNNQueryResults() { + return DOC_ID_TO_SCORES.entrySet() + .stream() + .map(entry -> new KNNQueryResult(entry.getKey(), entry.getValue())) + .collect(Collectors.toList()) + .toArray(new KNNQueryResult[0]); + } + + private KNNQueryResult[] getFilteredKNNQueryResults() { + return FILTERED_DOC_ID_TO_SCORES.entrySet() + .stream() + .map(entry -> new KNNQueryResult(entry.getKey(), entry.getValue())) + .collect(Collectors.toList()) + .toArray(new KNNQueryResult[0]); + } + + @SneakyThrows + public void testANNWithQuantizationParams_whenStateNotFound_thenFail() { + try (MockedStatic quantizationServiceMockedStatic = Mockito.mockStatic(QuantizationService.class)) { + QuantizationService quantizationService = Mockito.mock(QuantizationService.class); + quantizationServiceMockedStatic.when(QuantizationService::getInstance).thenReturn(quantizationService); + QuantizationParams quantizationParams = new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); + Mockito.when(quantizationService.getQuantizationParams(any(FieldInfo.class))).thenReturn(quantizationParams); + + // Given + int k = 3; + jniServiceMockedStatic.when( + () -> JNIService.queryIndex(anyLong(), eq(QUERY_VECTOR), eq(k), eq(HNSW_METHOD_PARAMETERS), any(), any(), anyInt(), any()) + ).thenReturn(getFilteredKNNQueryResults()); + + jniServiceMockedStatic.when( + () -> JNIService.queryBinaryIndex( + anyLong(), + eq(BYTE_QUERY_VECTOR), + eq(k), + eq(HNSW_METHOD_PARAMETERS), + any(), + any(), + anyInt(), + any() + ) + ).thenReturn(getFilteredKNNQueryResults()); + final SegmentReader reader = mockSegmentReader(); + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + when(leafReaderContext.reader()).thenReturn(reader); + + final KNNQuery query = KNNQuery.builder() + .field(FIELD_NAME) + .queryVector(QUERY_VECTOR) + .k(k) + .indexName(INDEX_NAME) + .filterQuery(FILTER_QUERY) + .methodParameters(HNSW_METHOD_PARAMETERS) + .vectorDataType(VectorDataType.FLOAT) + .build(); + + final float boost = (float) randomDoubleBetween(0, 10, true); + final KNNWeight knnWeight = new KNNWeight(query, boost); + final FieldInfos fieldInfos = mock(FieldInfos.class); + final FieldInfo fieldInfo = mock(FieldInfo.class); + final Map attributesMap = ImmutableMap.of( + KNN_ENGINE, + KNNEngine.FAISS.getName(), + PARAMETERS, + String.format(Locale.ROOT, "{\"%s\":\"%s\"}", INDEX_DESCRIPTION_PARAMETER, "HNSW32") + ); + + when(reader.getFieldInfos()).thenReturn(fieldInfos); + when(fieldInfos.fieldInfo(any())).thenReturn(fieldInfo); + when(fieldInfo.attributes()).thenReturn(attributesMap); + // fieldName, new float[0], tempCollector, null) + doNothing().when(reader).searchNearestVectors(any(), eq(new float[0]), any(), any()); + + expectThrows(IllegalStateException.class, () -> knnWeight.scorer(leafReaderContext)); + } + } + + @SneakyThrows + public void testANNWithQuantizationParams_thenSuccess() { + try (MockedStatic quantizationServiceMockedStatic = Mockito.mockStatic(QuantizationService.class)) { + QuantizationService quantizationService = Mockito.mock(QuantizationService.class); + ScalarQuantizationParams quantizationParams = new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); + Mockito.when(quantizationService.getQuantizationParams(any(FieldInfo.class))).thenReturn(quantizationParams); + quantizationServiceMockedStatic.when(QuantizationService::getInstance).thenReturn(quantizationService); + + float[] meanThresholds = new float[] { 1.2f, 2.3f, 3.4f, 4.5f }; + QuantizationState quantizationState = new OneBitScalarQuantizationState(quantizationParams, meanThresholds); + + try ( + MockedConstruction quantizationCollectorMockedConstruction = Mockito.mockConstruction( + QuantizationConfigKNNCollector.class, + (mock, context) -> Mockito.when(mock.getQuantizationState()).thenReturn(quantizationState) + ) + ) { + + // Given + int k = 3; + jniServiceMockedStatic.when( + () -> JNIService.queryIndex( + anyLong(), + eq(QUERY_VECTOR), + eq(k), + eq(HNSW_METHOD_PARAMETERS), + any(), + any(), + anyInt(), + any() + ) + ).thenReturn(getFilteredKNNQueryResults()); + + jniServiceMockedStatic.when( + () -> JNIService.queryBinaryIndex( + anyLong(), + eq(BYTE_QUERY_VECTOR), + eq(k), + eq(HNSW_METHOD_PARAMETERS), + any(), + any(), + anyInt(), + any() + ) + ).thenReturn(getFilteredKNNQueryResults()); + final SegmentReader reader = mockSegmentReader(); + final LeafReaderContext leafReaderContext = mock(LeafReaderContext.class); + when(leafReaderContext.reader()).thenReturn(reader); + + final KNNQuery query = KNNQuery.builder() + .field(FIELD_NAME) + .queryVector(QUERY_VECTOR) + .k(k) + .indexName(INDEX_NAME) + .filterQuery(FILTER_QUERY) + .methodParameters(HNSW_METHOD_PARAMETERS) + .vectorDataType(VectorDataType.FLOAT) + .build(); + + final float boost = (float) randomDoubleBetween(0, 10, true); + final KNNWeight knnWeight = new KNNWeight(query, boost); + final FieldInfos fieldInfos = mock(FieldInfos.class); + final FieldInfo fieldInfo = mock(FieldInfo.class); + final Map attributesMap = ImmutableMap.of( + KNN_ENGINE, + KNNEngine.FAISS.getName(), + PARAMETERS, + String.format(Locale.ROOT, "{\"%s\":\"%s\"}", INDEX_DESCRIPTION_PARAMETER, "HNSW32") + ); + + when(reader.getFieldInfos()).thenReturn(fieldInfos); + when(fieldInfos.fieldInfo(any())).thenReturn(fieldInfo); + when(fieldInfo.attributes()).thenReturn(attributesMap); + + KNNScorer knnScorer = (KNNScorer) knnWeight.scorer(leafReaderContext); + + assertNotNull(knnScorer); + jniServiceMockedStatic.verify( + () -> JNIService.queryIndex( + anyLong(), + eq(QUERY_VECTOR), + eq(k), + eq(HNSW_METHOD_PARAMETERS), + any(), + any(), + anyInt(), + any() + ), + times(1) + ); + } + } + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/RNNQueryFactoryTests.java b/src/test/java/org/opensearch/knn/index/query/RNNQueryFactoryTests.java new file mode 100644 index 000000000..c41b8fbab --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/RNNQueryFactoryTests.java @@ -0,0 +1,146 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.opensearch.knn.common.KNNConstants.DEFAULT_VECTOR_DATA_TYPE_FIELD; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_SEARCH; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.lucene.search.ByteVectorSimilarityQuery; +import org.apache.lucene.search.FloatVectorSimilarityQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.join.BitSetProducer; +import org.opensearch.index.IndexSettings; +import org.opensearch.index.mapper.MappedFieldType; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.QueryShardContext; +import org.opensearch.index.query.TermQueryBuilder; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; + +public class RNNQueryFactoryTests extends KNNTestCase { + private static final String FILTER_FILED_NAME = "foo"; + private static final String FILTER_FILED_VALUE = "fooval"; + private static final QueryBuilder FILTER_QUERY_BUILDER = new TermQueryBuilder(FILTER_FILED_NAME, FILTER_FILED_VALUE); + private final int testQueryDimension = 17; + private final float[] testQueryVector = new float[testQueryDimension]; + private final byte[] testByteQueryVector = new byte[testQueryDimension]; + private final String testIndexName = "test-index"; + private final String testFieldName = "test-field"; + private final Float testRadius = 0.5f; + private final int maxResultWindow = 20000; + private final Map methodParameters = Map.of(METHOD_PARAMETER_EF_SEARCH, 100); + + public void testCreate_whenLucene_withRadiusQuery_withFloatVector() { + List luceneDefaultQueryEngineList = Arrays.stream(KNNEngine.values()) + .filter(knnEngine -> !KNNEngine.getEnginesThatCreateCustomSegmentFiles().contains(knnEngine)) + .collect(Collectors.toList()); + for (KNNEngine knnEngine : luceneDefaultQueryEngineList) { + Query query = RNNQueryFactory.create( + knnEngine, + testIndexName, + testFieldName, + testQueryVector, + testRadius, + DEFAULT_VECTOR_DATA_TYPE_FIELD + ); + assertEquals(FloatVectorSimilarityQuery.class, query.getClass()); + } + } + + public void testCreate_whenLucene_withRadiusQuery_withByteVector() { + List luceneDefaultQueryEngineList = Arrays.stream(KNNEngine.values()) + .filter(knnEngine -> !KNNEngine.getEnginesThatCreateCustomSegmentFiles().contains(knnEngine)) + .collect(Collectors.toList()); + for (KNNEngine knnEngine : luceneDefaultQueryEngineList) { + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + MappedFieldType testMapper = mock(MappedFieldType.class); + when(mockQueryShardContext.fieldMapper(any())).thenReturn(testMapper); + BitSetProducer parentFilter = mock(BitSetProducer.class); + when(mockQueryShardContext.getParentFilter()).thenReturn(parentFilter); + final RNNQueryFactory.CreateQueryRequest createQueryRequest = RNNQueryFactory.CreateQueryRequest.builder() + .knnEngine(knnEngine) + .indexName(testIndexName) + .fieldName(testFieldName) + .vector(testQueryVector) + .radius(testRadius) + .byteVector(testByteQueryVector) + .vectorDataType(VectorDataType.BYTE) + .context(mockQueryShardContext) + .filter(FILTER_QUERY_BUILDER) + .build(); + Query query = RNNQueryFactory.create(createQueryRequest); + assertEquals(ByteVectorSimilarityQuery.class, query.getClass()); + } + } + + public void testCreate_whenLucene_withFilter_thenSucceed() { + List luceneDefaultQueryEngineList = Arrays.stream(KNNEngine.values()) + .filter(knnEngine -> !KNNEngine.getEnginesThatCreateCustomSegmentFiles().contains(knnEngine)) + .collect(Collectors.toList()); + for (KNNEngine knnEngine : luceneDefaultQueryEngineList) { + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + MappedFieldType testMapper = mock(MappedFieldType.class); + when(mockQueryShardContext.fieldMapper(any())).thenReturn(testMapper); + final RNNQueryFactory.CreateQueryRequest createQueryRequest = RNNQueryFactory.CreateQueryRequest.builder() + .knnEngine(knnEngine) + .indexName(testIndexName) + .fieldName(testFieldName) + .vector(testQueryVector) + .vectorDataType(DEFAULT_VECTOR_DATA_TYPE_FIELD) + .context(mockQueryShardContext) + .filter(FILTER_QUERY_BUILDER) + .radius(testRadius) + .build(); + Query query = RNNQueryFactory.create(createQueryRequest); + assertEquals(FloatVectorSimilarityQuery.class, query.getClass()); + } + } + + public void testCreate_whenFaiss_thenSucceed() { + // Given + QueryShardContext mockQueryShardContext = mock(QueryShardContext.class); + MappedFieldType testMapper = mock(MappedFieldType.class); + IndexSettings indexSettings = mock(IndexSettings.class); + when(mockQueryShardContext.getIndexSettings()).thenReturn(indexSettings); + when(mockQueryShardContext.fieldMapper(any())).thenReturn(testMapper); + when(mockQueryShardContext.getIndexSettings().getMaxResultWindow()).thenReturn(maxResultWindow); + + final KNNQuery expectedQuery = KNNQuery.builder() + .field(testFieldName) + .queryVector(testQueryVector) + .indexName(testIndexName) + .radius(testRadius) + .methodParameters(methodParameters) + .context(new KNNQuery.Context(maxResultWindow)) + .build(); + + // When + final RNNQueryFactory.CreateQueryRequest createQueryRequest = RNNQueryFactory.CreateQueryRequest.builder() + .knnEngine(KNNEngine.FAISS) + .indexName(testIndexName) + .fieldName(testFieldName) + .vector(testQueryVector) + .radius(testRadius) + .vectorDataType(DEFAULT_VECTOR_DATA_TYPE_FIELD) + .context(mockQueryShardContext) + .methodParameters(methodParameters) + .build(); + + Query query = RNNQueryFactory.create(createQueryRequest); + + // Then + assertEquals(expectedQuery, query); + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/ResultUtilTests.java b/src/test/java/org/opensearch/knn/index/query/ResultUtilTests.java new file mode 100644 index 000000000..7cda1ed79 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/ResultUtilTests.java @@ -0,0 +1,149 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query; + +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.TopDocs; +import org.apache.lucene.util.BitSet; +import org.junit.Assert; +import org.opensearch.knn.KNNTestCase; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class ResultUtilTests extends KNNTestCase { + + public void testReduceToTopK() { + int firstPassK = 20; + int finalK = 10; + int segmentCount = 5; + + List> initialLeafResults = getRandomListOfResults(firstPassK, segmentCount); + List> reducedLeafResults = initialLeafResults.stream().map(HashMap::new).collect(Collectors.toList()); + ResultUtil.reduceToTopK(reducedLeafResults, finalK); + assertTopK(initialLeafResults, reducedLeafResults, finalK); + + firstPassK = 5; + finalK = 20; + segmentCount = 1; + + initialLeafResults = getRandomListOfResults(firstPassK, segmentCount); + reducedLeafResults = initialLeafResults.stream().map(HashMap::new).collect(Collectors.toList()); + ResultUtil.reduceToTopK(reducedLeafResults, finalK); + assertTopK(initialLeafResults, reducedLeafResults, firstPassK); + } + + public void testResultMapToMatchBitSet() throws IOException { + int firstPassK = 35; + Map perLeafResults = getRandomResults(firstPassK); + BitSet resultBitset = ResultUtil.resultMapToMatchBitSet(perLeafResults); + assertResultMapToMatchBitSet(perLeafResults, resultBitset); + } + + public void testResultMapToMatchBitSet_whenResultMapEmpty_thenReturnEmptyOptional() throws IOException { + BitSet resultBitset = ResultUtil.resultMapToMatchBitSet(Collections.emptyMap()); + Assert.assertNull(resultBitset); + + BitSet resultBitset2 = ResultUtil.resultMapToMatchBitSet(null); + Assert.assertNull(resultBitset2); + } + + public void testResultMapToDocIds() throws IOException { + int firstPassK = 42; + Map perLeafResults = getRandomResults(firstPassK); + final int maxDoc = Collections.max(perLeafResults.keySet()) + 1; + DocIdSetIterator resultDocIdSetIterator = ResultUtil.resultMapToDocIds(perLeafResults, maxDoc); + assertResultMapToDocIdSetIterator(perLeafResults, resultDocIdSetIterator); + } + + public void testResultMapToTopDocs() { + int k = 18; + int offset = 121; + Map perLeafResults = getRandomResults(k); + TopDocs topDocs = ResultUtil.resultMapToTopDocs(perLeafResults, offset); + assertResultMapToTopDocs(perLeafResults, topDocs, k, offset); + } + + private void assertResultMapToTopDocs(Map perLeafResults, TopDocs topDocs, int k, int offset) { + assertEquals(k, topDocs.totalHits.value); + float previousScore = Float.MAX_VALUE; + for (ScoreDoc scoreDoc : topDocs.scoreDocs) { + assertTrue(perLeafResults.containsKey(scoreDoc.doc - offset)); + assertEquals(perLeafResults.get(scoreDoc.doc - offset), scoreDoc.score, 0.0001); + assertTrue(previousScore > scoreDoc.score); + previousScore = scoreDoc.score; + } + } + + private void assertTopK(List> beforeResults, List> reducedResults, int expectedK) { + assertEquals(beforeResults.size(), reducedResults.size()); + assertEquals(expectedK, reducedResults.stream().map(Map::size).reduce(Integer::sum).orElse(-1).intValue()); + float minScore = getMinScore(reducedResults); + int count = 0; + for (Map result : beforeResults) { + for (float score : result.values()) { + if (score >= minScore) { + count++; + } + } + } + assertEquals(expectedK, count); + } + + private void assertResultMapToMatchBitSet(Map resultsMap, BitSet resultBitset) { + assertEquals(resultsMap.size(), resultBitset.cardinality()); + for (Integer docId : resultsMap.keySet()) { + assertTrue(resultBitset.get(docId)); + } + } + + private void assertResultMapToDocIdSetIterator(Map resultsMap, DocIdSetIterator resultDocIdSetIterator) + throws IOException { + int count = 0; + int docId = resultDocIdSetIterator.nextDoc(); + while (docId != DocIdSetIterator.NO_MORE_DOCS) { + assertTrue(resultsMap.containsKey(docId)); + count++; + docId = resultDocIdSetIterator.nextDoc(); + } + assertEquals(resultsMap.size(), count); + } + + private List> getRandomListOfResults(int k, int segments) { + List> perLeafResults = new ArrayList<>(); + for (int i = 0; i < segments; i++) { + perLeafResults.add(getRandomResults(k)); + } + return perLeafResults; + } + + private Map getRandomResults(int k) { + Map results = new HashMap<>(); + for (int i = 0; i < k; i++) { + results.put(i, random().nextFloat()); + } + + return results; + } + + private float getMinScore(List> perLeafResults) { + float minScore = Float.MAX_VALUE; + for (Map result : perLeafResults) { + for (float score : result.values()) { + if (score < minScore) { + minScore = score; + } + } + } + return minScore; + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/iterators/BinaryVectorIdsKNNIteratorTests.java b/src/test/java/org/opensearch/knn/index/query/iterators/BinaryVectorIdsKNNIteratorTests.java new file mode 100644 index 000000000..6d5dffa98 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/iterators/BinaryVectorIdsKNNIteratorTests.java @@ -0,0 +1,97 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.iterators; + +import junit.framework.TestCase; +import lombok.SneakyThrows; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.util.FixedBitSet; +import org.mockito.stubbing.OngoingStubbing; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.vectorvalues.KNNBinaryVectorValues; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class BinaryVectorIdsKNNIteratorTests extends TestCase { + @SneakyThrows + public void testNextDoc_whenCalled_IterateAllDocs() { + final SpaceType spaceType = SpaceType.HAMMING; + final byte[] queryVector = { 1, 2, 3 }; + final int[] filterIds = { 1, 2, 3 }; + final List dataVectors = Arrays.asList(new byte[] { 11, 12, 13 }, new byte[] { 14, 15, 16 }, new byte[] { 17, 18, 19 }); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .collect(Collectors.toList()); + + KNNBinaryVectorValues values = mock(KNNBinaryVectorValues.class); + when(values.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); + + FixedBitSet filterBitSet = new FixedBitSet(4); + for (int id : filterIds) { + when(values.advance(id)).thenReturn(id); + filterBitSet.set(id); + } + + // Execute and verify + BinaryVectorIdsKNNIterator iterator = new BinaryVectorIdsKNNIterator(filterBitSet, queryVector, values, spaceType); + for (int i = 0; i < filterIds.length; i++) { + assertEquals(filterIds[i], iterator.nextDoc()); + assertEquals(expectedScores.get(i), (Float) iterator.score()); + } + assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); + } + + @SneakyThrows + public void testNextDoc_whenCalled_thenIterateAllDocsWithoutFilter() throws IOException { + final SpaceType spaceType = SpaceType.HAMMING; + final byte[] queryVector = { 1, 2, 3 }; + final List dataVectors = Arrays.asList( + new byte[] { 11, 12, 13 }, + new byte[] { 14, 15, 16 }, + new byte[] { 17, 18, 19 }, + new byte[] { 20, 21, 22 }, + new byte[] { 23, 24, 25 } + ); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .collect(Collectors.toList()); + + KNNBinaryVectorValues values = mock(KNNBinaryVectorValues.class); + when(values.getVector()).thenReturn( + dataVectors.get(0), + dataVectors.get(1), + dataVectors.get(2), + dataVectors.get(3), + dataVectors.get(4) + ); + + // stub return value when nextDoc is called + OngoingStubbing stubbing = when(values.nextDoc()); + for (int i = 0; i < dataVectors.size(); i++) { + stubbing = stubbing.thenReturn(i); + } + // set last return to be Integer.MAX_VALUE to represent no more docs + stubbing.thenReturn(Integer.MAX_VALUE); + + // Execute and verify + BinaryVectorIdsKNNIterator iterator = new BinaryVectorIdsKNNIterator(queryVector, values, spaceType); + for (int i = 0; i < dataVectors.size(); i++) { + assertEquals(i, iterator.nextDoc()); + assertEquals(expectedScores.get(i), iterator.score()); + } + assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); + verify(values, never()).advance(anyInt()); + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/iterators/ByteVectorIdsKNNIteratorTests.java b/src/test/java/org/opensearch/knn/index/query/iterators/ByteVectorIdsKNNIteratorTests.java new file mode 100644 index 000000000..60169b95f --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/iterators/ByteVectorIdsKNNIteratorTests.java @@ -0,0 +1,99 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.iterators; + +import junit.framework.TestCase; +import lombok.SneakyThrows; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.util.FixedBitSet; +import org.mockito.stubbing.OngoingStubbing; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.vectorvalues.KNNByteVectorValues; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class ByteVectorIdsKNNIteratorTests extends TestCase { + @SneakyThrows + public void testNextDoc_whenCalled_IterateAllDocs() { + final SpaceType spaceType = SpaceType.L2; + final byte[] byteQueryVector = { 1, 2, 3 }; + final float[] queryVector = { 1f, 2f, 3f }; + final int[] filterIds = { 1, 2, 3 }; + final List dataVectors = Arrays.asList(new byte[] { 11, 12, 13 }, new byte[] { 14, 15, 16 }, new byte[] { 17, 18, 19 }); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(byteQueryVector, vector)) + .collect(Collectors.toList()); + + KNNByteVectorValues values = mock(KNNByteVectorValues.class); + when(values.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); + + FixedBitSet filterBitSet = new FixedBitSet(4); + for (int id : filterIds) { + when(values.advance(id)).thenReturn(id); + filterBitSet.set(id); + } + + // Execute and verify + ByteVectorIdsKNNIterator iterator = new ByteVectorIdsKNNIterator(filterBitSet, queryVector, values, spaceType); + for (int i = 0; i < filterIds.length; i++) { + assertEquals(filterIds[i], iterator.nextDoc()); + assertEquals(expectedScores.get(i), (Float) iterator.score()); + } + assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); + } + + @SneakyThrows + public void testNextDoc_whenCalled_thenIterateAllDocsWithoutFilter() throws IOException { + final SpaceType spaceType = SpaceType.L2; + final byte[] byteQueryVector = { 1, 2, 3 }; + final float[] queryVector = { 1.0f, 2.0f, 3.0f }; + final List dataVectors = Arrays.asList( + new byte[] { 11, 12, 13 }, + new byte[] { 14, 15, 16 }, + new byte[] { 17, 18, 19 }, + new byte[] { 20, 21, 22 }, + new byte[] { 23, 24, 25 } + ); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(byteQueryVector, vector)) + .collect(Collectors.toList()); + + KNNByteVectorValues values = mock(KNNByteVectorValues.class); + when(values.getVector()).thenReturn( + dataVectors.get(0), + dataVectors.get(1), + dataVectors.get(2), + dataVectors.get(3), + dataVectors.get(4) + ); + + // stub return value when nextDoc is called + OngoingStubbing stubbing = when(values.nextDoc()); + for (int i = 0; i < dataVectors.size(); i++) { + stubbing = stubbing.thenReturn(i); + } + // set last return to be Integer.MAX_VALUE to represent no more docs + stubbing.thenReturn(Integer.MAX_VALUE); + + // Execute and verify + ByteVectorIdsKNNIterator iterator = new ByteVectorIdsKNNIterator(queryVector, values, spaceType); + for (int i = 0; i < dataVectors.size(); i++) { + assertEquals(i, iterator.nextDoc()); + assertEquals(expectedScores.get(i), iterator.score()); + } + assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); + verify(values, never()).advance(anyInt()); + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/iterators/NestedBinaryVectorIdsKNNIteratorTests.java b/src/test/java/org/opensearch/knn/index/query/iterators/NestedBinaryVectorIdsKNNIteratorTests.java new file mode 100644 index 000000000..a39a3b2e9 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/iterators/NestedBinaryVectorIdsKNNIteratorTests.java @@ -0,0 +1,91 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.iterators; + +import junit.framework.TestCase; +import lombok.SneakyThrows; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.util.BitSet; +import org.apache.lucene.util.FixedBitSet; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.vectorvalues.KNNBinaryVectorValues; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class NestedBinaryVectorIdsKNNIteratorTests extends TestCase { + @SneakyThrows + public void testNextDoc_whenIterate_ReturnBestChildDocsPerParent() { + final SpaceType spaceType = SpaceType.HAMMING; + final byte[] queryVector = { 1, 2, 3 }; + final int[] filterIds = { 0, 2, 3 }; + // Parent id for 0 -> 1 + // Parent id for 2, 3 -> 4 + // In bit representation, it is 10010. In long, it is 18. + final BitSet parentBitSet = new FixedBitSet(new long[] { 18 }, 5); + final List dataVectors = Arrays.asList(new byte[] { 11, 12, 13 }, new byte[] { 14, 15, 16 }, new byte[] { 17, 18, 19 }); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .collect(Collectors.toList()); + + KNNBinaryVectorValues values = mock(KNNBinaryVectorValues.class); + when(values.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); + + FixedBitSet filterBitSet = new FixedBitSet(4); + for (int id : filterIds) { + when(values.advance(id)).thenReturn(id); + filterBitSet.set(id); + } + + // Execute and verify + NestedBinaryVectorIdsKNNIterator iterator = new NestedBinaryVectorIdsKNNIterator( + filterBitSet, + queryVector, + values, + spaceType, + parentBitSet + ); + assertEquals(filterIds[0], iterator.nextDoc()); + assertEquals(expectedScores.get(0), iterator.score()); + assertEquals(filterIds[2], iterator.nextDoc()); + assertEquals(expectedScores.get(2), iterator.score()); + assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); + } + + @SneakyThrows + public void testNextDoc_whenIterateWithoutFilters_thenReturnBestChildDocsPerParent() { + final SpaceType spaceType = SpaceType.HAMMING; + final byte[] queryVector = { 1, 2, 3 }; + // Parent id for 0 -> 1 + // Parent id for 2, 3 -> 4 + // In bit representation, it is 10010. In long, it is 18. + final BitSet parentBitSet = new FixedBitSet(new long[] { 18 }, 5); + final List dataVectors = Arrays.asList(new byte[] { 11, 12, 13 }, new byte[] { 14, 15, 16 }, new byte[] { 17, 18, 19 }); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .collect(Collectors.toList()); + + KNNBinaryVectorValues values = mock(KNNBinaryVectorValues.class); + when(values.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); + when(values.nextDoc()).thenReturn(0, 2, 3, Integer.MAX_VALUE); + + // Execute and verify + NestedBinaryVectorIdsKNNIterator iterator = new NestedBinaryVectorIdsKNNIterator(queryVector, values, spaceType, parentBitSet); + assertEquals(0, iterator.nextDoc()); + assertEquals(expectedScores.get(0), iterator.score()); + assertEquals(3, iterator.nextDoc()); + assertEquals(expectedScores.get(2), iterator.score()); + assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); + verify(values, never()).advance(anyInt()); + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/iterators/NestedByteVectorIdsKNNIteratorTests.java b/src/test/java/org/opensearch/knn/index/query/iterators/NestedByteVectorIdsKNNIteratorTests.java new file mode 100644 index 000000000..08c859779 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/iterators/NestedByteVectorIdsKNNIteratorTests.java @@ -0,0 +1,93 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.iterators; + +import junit.framework.TestCase; +import lombok.SneakyThrows; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.util.BitSet; +import org.apache.lucene.util.FixedBitSet; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.vectorvalues.KNNByteVectorValues; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class NestedByteVectorIdsKNNIteratorTests extends TestCase { + @SneakyThrows + public void testNextDoc_whenIterate_ReturnBestChildDocsPerParent() { + final SpaceType spaceType = SpaceType.L2; + final byte[] byteQueryVector = { 1, 2, 3 }; + final float[] queryVector = { 1.0f, 2.0f, 3.0f }; + final int[] filterIds = { 0, 2, 3 }; + // Parent id for 0 -> 1 + // Parent id for 2, 3 -> 4 + // In bit representation, it is 10010. In long, it is 18. + final BitSet parentBitSet = new FixedBitSet(new long[] { 18 }, 5); + final List dataVectors = Arrays.asList(new byte[] { 11, 12, 13 }, new byte[] { 17, 18, 19 }, new byte[] { 14, 15, 16 }); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(byteQueryVector, vector)) + .collect(Collectors.toList()); + + KNNByteVectorValues values = mock(KNNByteVectorValues.class); + when(values.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); + + FixedBitSet filterBitSet = new FixedBitSet(4); + for (int id : filterIds) { + when(values.advance(id)).thenReturn(id); + filterBitSet.set(id); + } + + // Execute and verify + NestedByteVectorIdsKNNIterator iterator = new NestedByteVectorIdsKNNIterator( + filterBitSet, + queryVector, + values, + spaceType, + parentBitSet + ); + assertEquals(filterIds[0], iterator.nextDoc()); + assertEquals(expectedScores.get(0), iterator.score()); + assertEquals(filterIds[2], iterator.nextDoc()); + assertEquals(expectedScores.get(2), iterator.score()); + assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); + } + + @SneakyThrows + public void testNextDoc_whenIterateWithoutFilters_thenReturnBestChildDocsPerParent() { + final SpaceType spaceType = SpaceType.L2; + final byte[] byteQueryVector = { 1, 2, 3 }; + final float[] queryVector = { 1.0f, 2.0f, 3.0f }; + // Parent id for 0 -> 1 + // Parent id for 2, 3 -> 4 + // In bit representation, it is 10010. In long, it is 18. + final BitSet parentBitSet = new FixedBitSet(new long[] { 18 }, 5); + final List dataVectors = Arrays.asList(new byte[] { 11, 12, 13 }, new byte[] { 17, 18, 19 }, new byte[] { 14, 15, 16 }); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(byteQueryVector, vector)) + .collect(Collectors.toList()); + + KNNByteVectorValues values = mock(KNNByteVectorValues.class); + when(values.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); + when(values.nextDoc()).thenReturn(0, 2, 3, Integer.MAX_VALUE); + + // Execute and verify + NestedByteVectorIdsKNNIterator iterator = new NestedByteVectorIdsKNNIterator(queryVector, values, spaceType, parentBitSet); + assertEquals(0, iterator.nextDoc()); + assertEquals(expectedScores.get(0), iterator.score()); + assertEquals(3, iterator.nextDoc()); + assertEquals(expectedScores.get(2), iterator.score()); + assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); + verify(values, never()).advance(anyInt()); + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/iterators/NestedVectorIdsKNNIteratorTests.java b/src/test/java/org/opensearch/knn/index/query/iterators/NestedVectorIdsKNNIteratorTests.java new file mode 100644 index 000000000..f94ddb4e1 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/iterators/NestedVectorIdsKNNIteratorTests.java @@ -0,0 +1,97 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.iterators; + +import junit.framework.TestCase; +import lombok.SneakyThrows; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.util.BitSet; +import org.apache.lucene.util.FixedBitSet; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.vectorvalues.KNNFloatVectorValues; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class NestedVectorIdsKNNIteratorTests extends TestCase { + @SneakyThrows + public void testNextDoc_whenIterate_ReturnBestChildDocsPerParent() { + final SpaceType spaceType = SpaceType.L2; + final float[] queryVector = { 1.0f, 2.0f, 3.0f }; + final int[] filterIds = { 0, 2, 3 }; + // Parent id for 0 -> 1 + // Parent id for 2, 3 -> 4 + // In bit representation, it is 10010. In long, it is 18. + final BitSet parentBitSet = new FixedBitSet(new long[] { 18 }, 5); + final List dataVectors = Arrays.asList( + new float[] { 11.0f, 12.0f, 13.0f }, + new float[] { 17.0f, 18.0f, 19.0f }, + new float[] { 14.0f, 15.0f, 16.0f } + ); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .collect(Collectors.toList()); + + KNNFloatVectorValues values = mock(KNNFloatVectorValues.class); + when(values.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); + // final List byteRefs = dataVectors.stream() + // .map(vector -> new BytesRef(new KNNVectorAsArraySerializer().floatToByteArray(vector))) + // .collect(Collectors.toList()); + // when(values.binaryValue()).thenReturn(byteRefs.get(0), byteRefs.get(1), byteRefs.get(2)); + + FixedBitSet filterBitSet = new FixedBitSet(4); + for (int id : filterIds) { + when(values.advance(id)).thenReturn(id); + filterBitSet.set(id); + } + + // Execute and verify + NestedVectorIdsKNNIterator iterator = new NestedVectorIdsKNNIterator(filterBitSet, queryVector, values, spaceType, parentBitSet); + assertEquals(filterIds[0], iterator.nextDoc()); + assertEquals(expectedScores.get(0), iterator.score()); + assertEquals(filterIds[2], iterator.nextDoc()); + assertEquals(expectedScores.get(2), iterator.score()); + assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); + } + + @SneakyThrows + public void testNextDoc_whenIterateWithoutFilters_thenReturnBestChildDocsPerParent() { + final SpaceType spaceType = SpaceType.L2; + final float[] queryVector = { 1.0f, 2.0f, 3.0f }; + // Parent id for 0 -> 1 + // Parent id for 2, 3 -> 4 + // In bit representation, it is 10010. In long, it is 18. + final BitSet parentBitSet = new FixedBitSet(new long[] { 18 }, 5); + final List dataVectors = Arrays.asList( + new float[] { 11.0f, 12.0f, 13.0f }, + new float[] { 17.0f, 18.0f, 19.0f }, + new float[] { 14.0f, 15.0f, 16.0f } + ); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .collect(Collectors.toList()); + + KNNFloatVectorValues values = mock(KNNFloatVectorValues.class); + when(values.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); + when(values.nextDoc()).thenReturn(0, 2, 3, Integer.MAX_VALUE); + + // Execute and verify + NestedVectorIdsKNNIterator iterator = new NestedVectorIdsKNNIterator(queryVector, values, spaceType, parentBitSet); + assertEquals(0, iterator.nextDoc()); + assertEquals(expectedScores.get(0), iterator.score()); + assertEquals(3, iterator.nextDoc()); + assertEquals(expectedScores.get(2), iterator.score()); + assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); + verify(values, never()).advance(anyInt()); + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/iterators/VectorIdsKNNIteratorTests.java b/src/test/java/org/opensearch/knn/index/query/iterators/VectorIdsKNNIteratorTests.java new file mode 100644 index 000000000..96932d0f1 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/iterators/VectorIdsKNNIteratorTests.java @@ -0,0 +1,98 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.iterators; + +import lombok.SneakyThrows; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.util.FixedBitSet; +import org.mockito.stubbing.OngoingStubbing; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.vectorvalues.KNNFloatVectorValues; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class VectorIdsKNNIteratorTests extends KNNTestCase { + @SneakyThrows + public void testNextDoc_whenCalledWithFilters_thenIterateAllDocs() { + final SpaceType spaceType = SpaceType.L2; + final float[] queryVector = { 1.0f, 2.0f, 3.0f }; + final int[] filterIds = { 1, 2, 3 }; + final List dataVectors = Arrays.asList( + new float[] { 11.0f, 12.0f, 13.0f }, + new float[] { 14.0f, 15.0f, 16.0f }, + new float[] { 17.0f, 18.0f, 19.0f } + ); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .collect(Collectors.toList()); + + KNNFloatVectorValues values = mock(KNNFloatVectorValues.class); + when(values.getVector()).thenReturn(dataVectors.get(0), dataVectors.get(1), dataVectors.get(2)); + + FixedBitSet filterBitSet = new FixedBitSet(4); + for (int id : filterIds) { + when(values.advance(id)).thenReturn(id); + filterBitSet.set(id); + } + + // Execute and verify + VectorIdsKNNIterator iterator = new VectorIdsKNNIterator(filterBitSet, queryVector, values, spaceType); + for (int i = 0; i < filterIds.length; i++) { + assertEquals(filterIds[i], iterator.nextDoc()); + assertEquals(expectedScores.get(i), (Float) iterator.score()); + } + assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); + } + + @SneakyThrows + public void testNextDoc_whenCalledWithoutFilters_thenIterateAllDocs() { + final SpaceType spaceType = SpaceType.L2; + final float[] queryVector = { 1.0f, 2.0f, 3.0f }; + final List dataVectors = Arrays.asList( + new float[] { 11.0f, 12.0f, 13.0f }, + new float[] { 14.0f, 15.0f, 16.0f }, + new float[] { 17.0f, 18.0f, 19.0f }, + new float[] { 20.0f, 21.0f, 22.0f }, + new float[] { 23.0f, 24.0f, 25.0f } + ); + final List expectedScores = dataVectors.stream() + .map(vector -> spaceType.getKnnVectorSimilarityFunction().compare(queryVector, vector)) + .collect(Collectors.toList()); + + KNNFloatVectorValues values = mock(KNNFloatVectorValues.class); + when(values.getVector()).thenReturn( + dataVectors.get(0), + dataVectors.get(1), + dataVectors.get(2), + dataVectors.get(3), + dataVectors.get(4) + ); + // stub return value when nextDoc is called + OngoingStubbing stubbing = when(values.nextDoc()); + for (int i = 0; i < dataVectors.size(); i++) { + stubbing = stubbing.thenReturn(i); + } + // set last return to be Integer.MAX_VALUE to represent no more docs + stubbing.thenReturn(Integer.MAX_VALUE); + // Execute and verify + VectorIdsKNNIterator iterator = new VectorIdsKNNIterator(queryVector, values, spaceType); + for (int i = 0; i < dataVectors.size(); i++) { + assertEquals(i, iterator.nextDoc()); + assertEquals(expectedScores.get(i), (Float) iterator.score()); + } + assertEquals(DocIdSetIterator.NO_MORE_DOCS, iterator.nextDoc()); + verify(values, never()).advance(anyInt()); + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/nativelib/DocAndScoreQueryTests.java b/src/test/java/org/opensearch/knn/index/query/nativelib/DocAndScoreQueryTests.java new file mode 100644 index 000000000..185cb5d47 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/nativelib/DocAndScoreQueryTests.java @@ -0,0 +1,99 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.nativelib; + +import lombok.SneakyThrows; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexReaderContext; +import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.search.Explanation; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.ScoreMode; +import org.apache.lucene.search.Scorer; +import org.apache.lucene.search.Weight; +import org.mockito.Mock; +import org.opensearch.test.OpenSearchTestCase; + +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; + +public class DocAndScoreQueryTests extends OpenSearchTestCase { + + @Mock + private LeafReaderContext leaf1; + @Mock + private IndexSearcher indexSearcher; + @Mock + private IndexReader reader; + @Mock + private IndexReaderContext readerContext; + + private DocAndScoreQuery objectUnderTest; + + @Override + public void setUp() throws Exception { + super.setUp(); + openMocks(this); + + when(indexSearcher.getIndexReader()).thenReturn(reader); + when(reader.getContext()).thenReturn(readerContext); + when(readerContext.id()).thenReturn(1); + } + + // Note: cannot test with multi leaf as there LeafReaderContext is readonly with no getters for some fields to mock + public void testScorer() throws Exception { + // Given + int[] expectedDocs = { 0, 1, 2, 3, 4 }; + float[] expectedScores = { 0.1f, 1.2f, 2.3f, 5.1f, 3.4f }; + int[] findSegments = { 0, 2, 5 }; + objectUnderTest = new DocAndScoreQuery(4, expectedDocs, expectedScores, findSegments, 1); + + // When + Scorer scorer1 = objectUnderTest.createWeight(indexSearcher, ScoreMode.COMPLETE, 1).scorer(leaf1); + DocIdSetIterator iterator1 = scorer1.iterator(); + Scorer scorer2 = objectUnderTest.createWeight(indexSearcher, ScoreMode.COMPLETE, 1).scorer(leaf1); + DocIdSetIterator iterator2 = scorer2.iterator(); + + int[] actualDocs = new int[2]; + float[] actualScores = new float[2]; + int index = 0; + while (iterator1.nextDoc() != DocIdSetIterator.NO_MORE_DOCS) { + actualDocs[index] = iterator1.docID(); + actualScores[index] = scorer1.score(); + ++index; + } + + // Then + assertEquals(2, iterator1.cost()); + assertArrayEquals(new int[] { 0, 1 }, actualDocs); + assertArrayEquals(new float[] { 0.1f, 1.2f }, actualScores, 0.0001f); + + assertEquals(1.2f, scorer2.getMaxScore(1), 0.0001f); + assertEquals(iterator2.advance(1), 1); + } + + @SneakyThrows + public void testWeight() { + // Given + int[] expectedDocs = { 0, 1, 2, 3, 4 }; + float[] expectedScores = { 0.1f, 1.2f, 2.3f, 5.1f, 3.4f }; + int[] findSegments = { 0, 2, 5 }; + Explanation expectedExplanation = Explanation.match(1.2f, "within top 4"); + + // When + objectUnderTest = new DocAndScoreQuery(4, expectedDocs, expectedScores, findSegments, 1); + Weight weight = objectUnderTest.createWeight(indexSearcher, ScoreMode.COMPLETE, 1); + Explanation explanation = weight.explain(leaf1, 1); + + // Then + assertEquals(objectUnderTest, weight.getQuery()); + assertTrue(weight.isCacheable(leaf1)); + assertEquals(2, weight.count(leaf1)); + assertEquals(expectedExplanation, explanation); + assertEquals(Explanation.noMatch("not in top 4"), weight.explain(leaf1, 9)); + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java b/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java new file mode 100644 index 000000000..fab67189d --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/nativelib/NativeEngineKNNVectorQueryTests.java @@ -0,0 +1,271 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.nativelib; + +import lombok.SneakyThrows; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexReaderContext; +import org.apache.lucene.index.LeafReader; +import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.MatchNoDocsQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.ScoreMode; +import org.apache.lucene.search.TaskExecutor; +import org.apache.lucene.search.TopDocs; +import org.apache.lucene.search.Weight; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.TotalHits; +import org.apache.lucene.util.Bits; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.invocation.InvocationOnMock; +import org.opensearch.cluster.ClusterState; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.query.KNNQuery; +import org.opensearch.knn.index.query.KNNWeight; +import org.opensearch.knn.index.query.ResultUtil; +import org.opensearch.knn.index.query.rescore.RescoreContext; +import org.opensearch.test.OpenSearchTestCase; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.times; +import static org.mockito.MockitoAnnotations.openMocks; + +public class NativeEngineKNNVectorQueryTests extends OpenSearchTestCase { + + @Mock + private IndexSearcher searcher; + @Mock + private IndexReader reader; + @Mock + private KNNQuery knnQuery; + @Mock + private KNNWeight knnWeight; + @Mock + private TaskExecutor taskExecutor; + @Mock + private IndexReaderContext indexReaderContext; + @Mock + private LeafReaderContext leaf1; + @Mock + private LeafReaderContext leaf2; + @Mock + private LeafReader leafReader1; + @Mock + private LeafReader leafReader2; + + @Mock + private ClusterService clusterService; + + @InjectMocks + private NativeEngineKnnVectorQuery objectUnderTest; + + @Override + public void setUp() throws Exception { + super.setUp(); + openMocks(this); + + when(leaf1.reader()).thenReturn(leafReader1); + when(leaf2.reader()).thenReturn(leafReader2); + + when(searcher.getIndexReader()).thenReturn(reader); + when(knnQuery.createWeight(searcher, ScoreMode.COMPLETE, 1)).thenReturn(knnWeight); + + when(searcher.getTaskExecutor()).thenReturn(taskExecutor); + when(taskExecutor.invokeAll(any())).thenAnswer(invocationOnMock -> { + List>> callables = invocationOnMock.getArgument(0); + List> results = new ArrayList<>(); + for (Callable> callable : callables) { + results.add(callable.call()); + } + return results; + }); + + when(reader.getContext()).thenReturn(indexReaderContext); + + when(clusterService.state()).thenReturn(mock(ClusterState.class)); // Mock ClusterState + + // Set ClusterService in KNNSettings + KNNSettings.state().setClusterService(clusterService); + when(knnQuery.getQueryVector()).thenReturn(new float[] { 1.0f, 2.0f, 3.0f }); // Example vector + + } + + @SneakyThrows + public void testMultiLeaf() { + // Given + List leaves = List.of(leaf1, leaf2); + when(reader.leaves()).thenReturn(leaves); + + when(knnWeight.searchLeaf(leaf1, 4)).thenReturn(new HashMap<>(Map.of(0, 1.2f, 1, 5.1f, 2, 2.2f))); + when(knnWeight.searchLeaf(leaf2, 4)).thenReturn(new HashMap<>(Map.of(4, 3.4f, 3, 5.1f))); + + // Making sure there is deleted docs in one of the segments + Bits liveDocs = mock(Bits.class); + when(leafReader1.getLiveDocs()).thenReturn(liveDocs); + when(leafReader2.getLiveDocs()).thenReturn(null); + + when(liveDocs.get(anyInt())).thenReturn(true); + when(liveDocs.get(2)).thenReturn(false); + when(liveDocs.get(1)).thenReturn(false); + + // k=4 to make sure we get topk results even if docs are deleted/less in one of the leaves + when(knnQuery.getK()).thenReturn(4); + + when(indexReaderContext.id()).thenReturn(1); + int[] expectedDocs = { 0, 3, 4 }; + float[] expectedScores = { 1.2f, 5.1f, 3.4f }; + int[] findSegments = { 0, 1, 3 }; + Query expected = new DocAndScoreQuery(4, expectedDocs, expectedScores, findSegments, 1); + + // When + Weight actual = objectUnderTest.createWeight(searcher, ScoreMode.COMPLETE, 1); + + // Then + assertEquals(expected, actual.getQuery()); + } + + @SneakyThrows + public void testRescoreWhenShardLevelRescoringEnabled() { + // Given + List leaves = List.of(leaf1, leaf2); + when(reader.leaves()).thenReturn(leaves); + + int k = 2; + int firstPassK = 3; + Map initialLeaf1Results = new HashMap<>(Map.of(0, 21f, 1, 19f, 2, 17f)); + Map initialLeaf2Results = new HashMap<>(Map.of(0, 20f, 1, 18f, 2, 16f)); + Map rescoredLeaf1Results = new HashMap<>(Map.of(0, 18f, 1, 20f)); + Map rescoredLeaf2Results = new HashMap<>(Map.of(0, 21f)); + + when(knnQuery.getRescoreContext()).thenReturn(RescoreContext.builder().oversampleFactor(1.5f).build()); + when(knnQuery.getK()).thenReturn(k); + when(knnWeight.getQuery()).thenReturn(knnQuery); + when(knnWeight.searchLeaf(leaf1, firstPassK)).thenReturn(initialLeaf1Results); + when(knnWeight.searchLeaf(leaf2, firstPassK)).thenReturn(initialLeaf2Results); + when(knnWeight.exactSearch(eq(leaf1), any())).thenReturn(rescoredLeaf1Results); + when(knnWeight.exactSearch(eq(leaf2), any())).thenReturn(rescoredLeaf2Results); + + try ( + MockedStatic mockedKnnSettings = mockStatic(KNNSettings.class); + MockedStatic mockedResultUtil = mockStatic(ResultUtil.class) + ) { + + // When shard-level re-scoring is enabled + mockedKnnSettings.when(() -> KNNSettings.isShardLevelRescoringEnabledForDiskBasedVector(any())).thenReturn(true); + + // Mock ResultUtil to return valid TopDocs + mockedResultUtil.when(() -> ResultUtil.resultMapToTopDocs(any(), anyInt())) + .thenReturn(new TopDocs(new TotalHits(0, TotalHits.Relation.EQUAL_TO), new ScoreDoc[0])); + mockedResultUtil.when(() -> ResultUtil.reduceToTopK(any(), anyInt())).thenCallRealMethod(); + + // When + Weight actual = objectUnderTest.createWeight(searcher, ScoreMode.COMPLETE, 1); + + // Then + mockedResultUtil.verify(() -> ResultUtil.reduceToTopK(any(), anyInt()), times(2)); + assertNotNull(actual); + } + } + + @SneakyThrows + public void testSingleLeaf() { + // Given + List leaves = List.of(leaf1); + when(reader.leaves()).thenReturn(leaves); + when(knnWeight.searchLeaf(leaf1, 4)).thenReturn(new HashMap<>(Map.of(0, 1.2f, 1, 5.1f, 2, 2.2f))); + when(knnQuery.getK()).thenReturn(4); + + when(indexReaderContext.id()).thenReturn(1); + int[] expectedDocs = { 0, 1, 2 }; + float[] expectedScores = { 1.2f, 5.1f, 2.2f }; + int[] findSegments = { 0, 3 }; + Query expected = new DocAndScoreQuery(4, expectedDocs, expectedScores, findSegments, 1); + + // When + Weight actual = objectUnderTest.createWeight(searcher, ScoreMode.COMPLETE, 1); + + // Then + assertEquals(expected, actual.getQuery()); + } + + @SneakyThrows + public void testNoMatch() { + // Given + List leaves = List.of(leaf1); + when(reader.leaves()).thenReturn(leaves); + when(knnWeight.searchLeaf(leaf1, 4)).thenReturn(Collections.emptyMap()); + when(knnQuery.getK()).thenReturn(4); + + // When + Weight actual = objectUnderTest.createWeight(searcher, ScoreMode.COMPLETE, 1); + + // Then + assertEquals(new MatchNoDocsQuery(), actual.getQuery()); + } + + @SneakyThrows + public void testRescore() { + // Given + List leaves = List.of(leaf1, leaf2); + when(reader.leaves()).thenReturn(leaves); + + int k = 2; + int firstPassK = 100; + Map initialLeaf1Results = new HashMap<>(Map.of(0, 21f, 1, 19f, 2, 17f, 3, 15f)); + Map initialLeaf2Results = new HashMap<>(Map.of(0, 20f, 1, 18f, 2, 16f, 3, 14f)); + Map rescoredLeaf1Results = new HashMap<>(Map.of(0, 18f, 1, 20f)); + Map rescoredLeaf2Results = new HashMap<>(Map.of(0, 21f)); + TopDocs topDocs1 = ResultUtil.resultMapToTopDocs(Map.of(1, 20f), 0); + TopDocs topDocs2 = ResultUtil.resultMapToTopDocs(Map.of(0, 21f), 4); + Query expected = new DocAndScoreQuery(2, new int[] { 1, 4 }, new float[] { 20f, 21f }, new int[] { 0, 4, 2 }, 1); + + when(indexReaderContext.id()).thenReturn(1); + when(knnQuery.getRescoreContext()).thenReturn(RescoreContext.builder().oversampleFactor(1.5f).build()); + when(knnQuery.getK()).thenReturn(k); + when(knnWeight.getQuery()).thenReturn(knnQuery); + when(knnWeight.searchLeaf(leaf1, firstPassK)).thenReturn(initialLeaf1Results); + when(knnWeight.searchLeaf(leaf2, firstPassK)).thenReturn(initialLeaf2Results); + + when(knnWeight.exactSearch(eq(leaf1), any())).thenReturn(rescoredLeaf1Results); + when(knnWeight.exactSearch(eq(leaf2), any())).thenReturn(rescoredLeaf2Results); + + try ( + MockedStatic mockedKnnSettings = mockStatic(KNNSettings.class); + MockedStatic mockedResultUtil = mockStatic(ResultUtil.class) + ) { + + // When shard-level re-scoring is enabled + mockedKnnSettings.when(() -> KNNSettings.isShardLevelRescoringEnabledForDiskBasedVector(any())).thenReturn(true); + + mockedResultUtil.when(() -> ResultUtil.reduceToTopK(any(), anyInt())).thenAnswer(InvocationOnMock::callRealMethod); + mockedResultUtil.when(() -> ResultUtil.resultMapToMatchBitSet(any())).thenAnswer(InvocationOnMock::callRealMethod); + mockedResultUtil.when(() -> ResultUtil.resultMapToDocIds(any(), anyInt())).thenAnswer(InvocationOnMock::callRealMethod); + + mockedResultUtil.when(() -> ResultUtil.resultMapToTopDocs(eq(rescoredLeaf1Results), anyInt())).thenAnswer(t -> topDocs1); + mockedResultUtil.when(() -> ResultUtil.resultMapToTopDocs(eq(rescoredLeaf2Results), anyInt())).thenAnswer(t -> topDocs2); + try (MockedStatic mockedStaticNativeKnnVectorQuery = mockStatic(NativeEngineKnnVectorQuery.class)) { + mockedStaticNativeKnnVectorQuery.when(() -> NativeEngineKnnVectorQuery.findSegmentStarts(any(), any())) + .thenReturn(new int[] { 0, 4, 2 }); + Weight actual = objectUnderTest.createWeight(searcher, ScoreMode.COMPLETE, 1); + assertEquals(expected, actual.getQuery()); + } + } + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/parser/KNNQueryBuilderParserTests.java b/src/test/java/org/opensearch/knn/index/query/parser/KNNQueryBuilderParserTests.java new file mode 100644 index 000000000..6cac5580b --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/parser/KNNQueryBuilderParserTests.java @@ -0,0 +1,520 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.parser; + +import org.opensearch.Version; +import org.opensearch.cluster.ClusterModule; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.common.ParsingException; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.QueryBuilders; +import org.opensearch.index.query.TermQueryBuilder; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.query.rescore.RescoreContext; +import org.opensearch.knn.index.util.KNNClusterUtil; +import org.opensearch.knn.index.query.KNNQueryBuilder; +import org.opensearch.plugins.SearchPlugin; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.opensearch.core.xcontent.ToXContent.EMPTY_PARAMS; +import static org.opensearch.index.query.AbstractQueryBuilder.BOOST_FIELD; +import static org.opensearch.knn.index.KNNClusterTestUtils.mockClusterService; +import static org.opensearch.knn.index.query.KNNQueryBuilder.NAME; +import static org.opensearch.knn.index.query.KNNQueryBuilder.EF_SEARCH_FIELD; +import static org.opensearch.knn.index.query.parser.RescoreParser.RESCORE_OVERSAMPLE_PARAMETER; +import static org.opensearch.knn.index.query.parser.RescoreParser.RESCORE_PARAMETER; + +public class KNNQueryBuilderParserTests extends KNNTestCase { + + private static final String FIELD_NAME = "myvector"; + private static final int K = 1; + private static final int EF_SEARCH = 10; + private static final Map HNSW_METHOD_PARAMS = Map.of("ef_search", EF_SEARCH); + private static final Float MAX_DISTANCE = 1.0f; + private static final Float MIN_SCORE = 0.5f; + private static final Float BOOST = 10.5f; + private static final TermQueryBuilder TERM_QUERY = QueryBuilders.termQuery("field", "value"); + + public void testFromXContent() throws Exception { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(queryVector).k(K).build(); + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.startObject(knnQueryBuilder.fieldName()); + builder.field(KNNQueryBuilder.VECTOR_FIELD.getPreferredName(), knnQueryBuilder.vector()); + builder.field(KNNQueryBuilder.K_FIELD.getPreferredName(), knnQueryBuilder.getK()); + builder.endObject(); + builder.endObject(); + XContentParser contentParser = createParser(builder); + contentParser.nextToken(); + KNNQueryBuilder actualBuilder = KNNQueryBuilderParser.fromXContent(contentParser); + assertEquals(knnQueryBuilder, actualBuilder); + } + + public void testFromXContent_KnnWithMethodParameters() throws Exception { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .k(K) + .methodParameters(HNSW_METHOD_PARAMS) + .build(); + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.startObject(knnQueryBuilder.fieldName()); + builder.field(KNNQueryBuilder.VECTOR_FIELD.getPreferredName(), knnQueryBuilder.vector()); + builder.field(KNNQueryBuilder.K_FIELD.getPreferredName(), knnQueryBuilder.getK()); + builder.startObject(org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER); + builder.field(EF_SEARCH_FIELD.getPreferredName(), EF_SEARCH); + builder.endObject(); + builder.endObject(); + builder.endObject(); + XContentParser contentParser = createParser(builder); + contentParser.nextToken(); + KNNQueryBuilder actualBuilder = KNNQueryBuilderParser.fromXContent(contentParser); + assertEquals(knnQueryBuilder, actualBuilder); + } + + public void testFromXContent_whenDoRadiusSearch_whenDistanceThreshold_whenMethodParameter_thenSucceed() throws Exception { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .maxDistance(MAX_DISTANCE) + .methodParameters(HNSW_METHOD_PARAMS) + .build(); + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.startObject(knnQueryBuilder.fieldName()); + builder.field(KNNQueryBuilder.VECTOR_FIELD.getPreferredName(), knnQueryBuilder.vector()); + builder.field(KNNQueryBuilder.MAX_DISTANCE_FIELD.getPreferredName(), knnQueryBuilder.getMaxDistance()); + builder.startObject(org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER); + builder.field(EF_SEARCH_FIELD.getPreferredName(), EF_SEARCH); + builder.endObject(); + builder.endObject(); + builder.endObject(); + XContentParser contentParser = createParser(builder); + contentParser.nextToken(); + KNNQueryBuilder actualBuilder = KNNQueryBuilderParser.fromXContent(contentParser); + assertEquals(knnQueryBuilder, actualBuilder); + } + + public void testFromXContent_whenDoRadiusSearch_whenScoreThreshold_whenMethodParameter_thenSucceed() throws Exception { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .minScore(MAX_DISTANCE) + .methodParameters(HNSW_METHOD_PARAMS) + .build(); + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.startObject(knnQueryBuilder.fieldName()); + builder.field(KNNQueryBuilder.VECTOR_FIELD.getPreferredName(), knnQueryBuilder.vector()); + builder.field(KNNQueryBuilder.MIN_SCORE_FIELD.getPreferredName(), knnQueryBuilder.getMinScore()); + builder.startObject(org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER); + builder.field(EF_SEARCH_FIELD.getPreferredName(), EF_SEARCH); + builder.endObject(); + builder.endObject(); + builder.endObject(); + XContentParser contentParser = createParser(builder); + contentParser.nextToken(); + KNNQueryBuilder actualBuilder = KNNQueryBuilderParser.fromXContent(contentParser); + assertEquals(knnQueryBuilder, actualBuilder); + } + + public void testFromXContent_withFilter() throws Exception { + final ClusterService clusterService = mockClusterService(Version.CURRENT); + + final KNNClusterUtil knnClusterUtil = KNNClusterUtil.instance(); + knnClusterUtil.initialize(clusterService); + + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .k(K) + .filter(TERM_QUERY) + .build(); + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.startObject(knnQueryBuilder.fieldName()); + builder.field(KNNQueryBuilder.VECTOR_FIELD.getPreferredName(), knnQueryBuilder.vector()); + builder.field(KNNQueryBuilder.K_FIELD.getPreferredName(), knnQueryBuilder.getK()); + builder.field(KNNQueryBuilder.FILTER_FIELD.getPreferredName(), knnQueryBuilder.getFilter()); + builder.endObject(); + builder.endObject(); + XContentParser contentParser = createParser(builder); + contentParser.nextToken(); + KNNQueryBuilder actualBuilder = KNNQueryBuilderParser.fromXContent(contentParser); + assertEquals(knnQueryBuilder, actualBuilder); + } + + public void testFromXContent_KnnWithEfSearch_withFilter() throws Exception { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .k(K) + .filter(TERM_QUERY) + .methodParameters(HNSW_METHOD_PARAMS) + .build(); + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.startObject(knnQueryBuilder.fieldName()); + builder.field(KNNQueryBuilder.VECTOR_FIELD.getPreferredName(), knnQueryBuilder.vector()); + builder.field(KNNQueryBuilder.K_FIELD.getPreferredName(), knnQueryBuilder.getK()); + builder.startObject(org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER); + builder.field(EF_SEARCH_FIELD.getPreferredName(), EF_SEARCH); + builder.endObject(); + builder.field(KNNQueryBuilder.FILTER_FIELD.getPreferredName(), knnQueryBuilder.getFilter()); + builder.endObject(); + builder.endObject(); + XContentParser contentParser = createParser(builder); + contentParser.nextToken(); + KNNQueryBuilder actualBuilder = KNNQueryBuilderParser.fromXContent(contentParser); + assertEquals(knnQueryBuilder, actualBuilder); + } + + public void testFromXContent_whenDoRadiusSearch_whenDistanceThreshold_whenFilter_thenSucceed() throws Exception { + final ClusterService clusterService = mockClusterService(Version.CURRENT); + + final KNNClusterUtil knnClusterUtil = KNNClusterUtil.instance(); + knnClusterUtil.initialize(clusterService); + + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .maxDistance(MAX_DISTANCE) + .filter(TERM_QUERY) + .build(); + + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.startObject(knnQueryBuilder.fieldName()); + builder.field(KNNQueryBuilder.VECTOR_FIELD.getPreferredName(), knnQueryBuilder.vector()); + builder.field(KNNQueryBuilder.MAX_DISTANCE_FIELD.getPreferredName(), knnQueryBuilder.getMaxDistance()); + builder.field(KNNQueryBuilder.FILTER_FIELD.getPreferredName(), knnQueryBuilder.getFilter()); + builder.endObject(); + builder.endObject(); + XContentParser contentParser = createParser(builder); + contentParser.nextToken(); + KNNQueryBuilder actualBuilder = KNNQueryBuilderParser.fromXContent(contentParser); + assertEquals(knnQueryBuilder, actualBuilder); + } + + public void testFromXContent_whenDoRadiusSearch_whenScoreThreshold_whenFilter_thenSucceed() throws Exception { + final ClusterService clusterService = mockClusterService(Version.CURRENT); + + final KNNClusterUtil knnClusterUtil = KNNClusterUtil.instance(); + knnClusterUtil.initialize(clusterService); + + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .minScore(MIN_SCORE) + .filter(TERM_QUERY) + .build(); + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.startObject(knnQueryBuilder.fieldName()); + builder.field(KNNQueryBuilder.VECTOR_FIELD.getPreferredName(), knnQueryBuilder.vector()); + builder.field(KNNQueryBuilder.MIN_SCORE_FIELD.getPreferredName(), knnQueryBuilder.getMinScore()); + builder.field(KNNQueryBuilder.FILTER_FIELD.getPreferredName(), knnQueryBuilder.getFilter()); + builder.endObject(); + builder.endObject(); + XContentParser contentParser = createParser(builder); + contentParser.nextToken(); + KNNQueryBuilder actualBuilder = KNNQueryBuilderParser.fromXContent(contentParser); + assertEquals(knnQueryBuilder, actualBuilder); + } + + public void testFromXContent_InvalidQueryVectorType() throws Exception { + final ClusterService clusterService = mockClusterService(Version.CURRENT); + + final KNNClusterUtil knnClusterUtil = KNNClusterUtil.instance(); + knnClusterUtil.initialize(clusterService); + + List invalidTypeQueryVector = new ArrayList<>(); + invalidTypeQueryVector.add(1.5); + invalidTypeQueryVector.add(2.5); + invalidTypeQueryVector.add("a"); + invalidTypeQueryVector.add(null); + + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.startObject(FIELD_NAME); + builder.field(KNNQueryBuilder.VECTOR_FIELD.getPreferredName(), invalidTypeQueryVector); + builder.field(KNNQueryBuilder.K_FIELD.getPreferredName(), K); + builder.endObject(); + builder.endObject(); + XContentParser contentParser = createParser(builder); + contentParser.nextToken(); + IllegalArgumentException exception = expectThrows( + IllegalArgumentException.class, + () -> KNNQueryBuilderParser.fromXContent(contentParser) + ); + assertTrue(exception.getMessage(), exception.getMessage().contains("[knn] failed to parse field [vector]")); + } + + public void testFromXContent_whenDoRadiusSearch_whenInputInvalidQueryVectorType_thenException() throws Exception { + final ClusterService clusterService = mockClusterService(Version.CURRENT); + + final KNNClusterUtil knnClusterUtil = KNNClusterUtil.instance(); + knnClusterUtil.initialize(clusterService); + + List invalidTypeQueryVector = new ArrayList<>(); + invalidTypeQueryVector.add(1.5); + invalidTypeQueryVector.add(2.5); + invalidTypeQueryVector.add("a"); + invalidTypeQueryVector.add(null); + + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.startObject(FIELD_NAME); + builder.field(KNNQueryBuilder.VECTOR_FIELD.getPreferredName(), invalidTypeQueryVector); + builder.field(KNNQueryBuilder.MAX_DISTANCE_FIELD.getPreferredName(), MAX_DISTANCE); + builder.endObject(); + builder.endObject(); + XContentParser contentParser = createParser(builder); + contentParser.nextToken(); + IllegalArgumentException exception = expectThrows( + IllegalArgumentException.class, + () -> KNNQueryBuilderParser.fromXContent(contentParser) + ); + assertTrue(exception.getMessage(), exception.getMessage().contains("[knn] failed to parse field [vector]")); + } + + public void testFromXContent_missingQueryVector() throws Exception { + final ClusterService clusterService = mockClusterService(Version.CURRENT); + + final KNNClusterUtil knnClusterUtil = KNNClusterUtil.instance(); + knnClusterUtil.initialize(clusterService); + + // Test without vector field + XContentBuilder builderWithoutVectorField = XContentFactory.jsonBuilder(); + builderWithoutVectorField.startObject(); + builderWithoutVectorField.startObject(FIELD_NAME); + builderWithoutVectorField.field(KNNQueryBuilder.K_FIELD.getPreferredName(), K); + builderWithoutVectorField.endObject(); + builderWithoutVectorField.endObject(); + XContentParser contentParserWithoutVectorField = createParser(builderWithoutVectorField); + contentParserWithoutVectorField.nextToken(); + IllegalArgumentException exception = expectThrows( + IllegalArgumentException.class, + () -> KNNQueryBuilderParser.fromXContent(contentParserWithoutVectorField) + ); + assertTrue(exception.getMessage(), exception.getMessage().contains("[knn] requires query vector")); + + // Test empty vector field + List emptyQueryVector = new ArrayList<>(); + XContentBuilder builderWithEmptyVector = XContentFactory.jsonBuilder(); + builderWithEmptyVector.startObject(); + builderWithEmptyVector.startObject(FIELD_NAME); + builderWithEmptyVector.field(KNNQueryBuilder.VECTOR_FIELD.getPreferredName(), emptyQueryVector); + builderWithEmptyVector.field(KNNQueryBuilder.K_FIELD.getPreferredName(), K); + builderWithEmptyVector.endObject(); + builderWithEmptyVector.endObject(); + XContentParser contentParserWithEmptyVector = createParser(builderWithEmptyVector); + contentParserWithEmptyVector.nextToken(); + exception = expectThrows(IllegalArgumentException.class, () -> KNNQueryBuilderParser.fromXContent(contentParserWithEmptyVector)); + assertTrue(exception.getMessage(), exception.getMessage().contains("[knn] failed to parse field [vector]")); + } + + public void testFromXContent_whenFlat_thenException() throws Exception { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.field(FIELD_NAME, queryVector); + builder.endObject(); + XContentParser contentParser = createParser(builder); + contentParser.nextToken(); + Exception exception = expectThrows(IllegalArgumentException.class, () -> KNNQueryBuilderParser.fromXContent(contentParser)); + assertTrue(exception.getMessage(), exception.getMessage().contains("[knn] requires exactly one of k, distance or score to be set")); + } + + public void testFromXContent_whenMultiFields_thenException() throws Exception { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.startObject(FIELD_NAME + "1"); + builder.field(KNNQueryBuilder.VECTOR_FIELD.getPreferredName(), queryVector); + builder.field(KNNQueryBuilder.K_FIELD.getPreferredName(), K); + builder.endObject(); + builder.startObject(FIELD_NAME + "2"); + builder.field(KNNQueryBuilder.VECTOR_FIELD.getPreferredName(), queryVector); + builder.field(KNNQueryBuilder.K_FIELD.getPreferredName(), K); + builder.endObject(); + builder.endObject(); + XContentParser contentParser = createParser(builder); + contentParser.nextToken(); + Exception exception = expectThrows(ParsingException.class, () -> KNNQueryBuilderParser.fromXContent(contentParser)); + assertTrue(exception.getMessage(), exception.getMessage().contains("[knn] query doesn't support multiple fields")); + } + + public void testToXContent_whenParamsVectorBoostK_thenSucceed() throws IOException { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.startObject(NAME); + builder.startObject(FIELD_NAME); + builder.field(KNNQueryBuilder.VECTOR_FIELD.getPreferredName(), queryVector); + builder.field(KNNQueryBuilder.K_FIELD.getPreferredName(), K); + builder.field(BOOST_FIELD.getPreferredName(), BOOST); + builder.endObject(); + builder.endObject(); + builder.endObject(); + + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder().fieldName(FIELD_NAME).vector(queryVector).k(K).boost(BOOST).build(); + XContentBuilder testBuilder = XContentFactory.jsonBuilder(); + testBuilder.startObject(); + KNNQueryBuilderParser.toXContent(testBuilder, EMPTY_PARAMS, knnQueryBuilder); + testBuilder.endObject(); + assertEquals(builder.toString(), testBuilder.toString()); + } + + public void testToXContent_whenFilter_thenSucceed() throws IOException { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.startObject(NAME); + builder.startObject(FIELD_NAME); + builder.field(KNNQueryBuilder.VECTOR_FIELD.getPreferredName(), queryVector); + builder.field(KNNQueryBuilder.K_FIELD.getPreferredName(), K); + builder.field(KNNQueryBuilder.FILTER_FIELD.getPreferredName(), TERM_QUERY); + builder.field(BOOST_FIELD.getPreferredName(), BOOST); + builder.endObject(); + builder.endObject(); + builder.endObject(); + + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .k(K) + .boost(BOOST) + .filter(TERM_QUERY) + .build(); + XContentBuilder testBuilder = XContentFactory.jsonBuilder(); + testBuilder.startObject(); + KNNQueryBuilderParser.toXContent(testBuilder, EMPTY_PARAMS, knnQueryBuilder); + testBuilder.endObject(); + assertEquals(builder.toString(), testBuilder.toString()); + } + + public void testToXContent_whenMaxDistance_thenSucceed() throws IOException { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.startObject(NAME); + builder.startObject(FIELD_NAME); + builder.field(KNNQueryBuilder.VECTOR_FIELD.getPreferredName(), queryVector); + builder.field(KNNQueryBuilder.K_FIELD.getPreferredName(), 0); + builder.field(KNNQueryBuilder.MAX_DISTANCE_FIELD.getPreferredName(), MAX_DISTANCE); + builder.field(BOOST_FIELD.getPreferredName(), BOOST); + builder.endObject(); + builder.endObject(); + builder.endObject(); + + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .boost(BOOST) + .maxDistance(MAX_DISTANCE) + .build(); + XContentBuilder testBuilder = XContentFactory.jsonBuilder(); + testBuilder.startObject(); + KNNQueryBuilderParser.toXContent(testBuilder, EMPTY_PARAMS, knnQueryBuilder); + testBuilder.endObject(); + assertEquals(builder.toString(), testBuilder.toString()); + } + + public void testToXContent_whenMethodParams_thenSucceed() throws IOException { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.startObject(NAME); + builder.startObject(FIELD_NAME); + builder.field(KNNQueryBuilder.VECTOR_FIELD.getPreferredName(), queryVector); + builder.field(KNNQueryBuilder.K_FIELD.getPreferredName(), K); + builder.startObject(org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER); + builder.field(EF_SEARCH_FIELD.getPreferredName(), EF_SEARCH); + builder.endObject(); + builder.field(BOOST_FIELD.getPreferredName(), BOOST); + builder.endObject(); + builder.endObject(); + builder.endObject(); + + KNNQueryBuilder knnQueryBuilder = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .boost(BOOST) + .k(K) + .methodParameters(HNSW_METHOD_PARAMS) + .build(); + XContentBuilder testBuilder = XContentFactory.jsonBuilder(); + testBuilder.startObject(); + KNNQueryBuilderParser.toXContent(testBuilder, EMPTY_PARAMS, knnQueryBuilder); + testBuilder.endObject(); + logger.info(builder.toString()); + logger.info(testBuilder.toString()); + assertEquals(builder.toString(), testBuilder.toString()); + } + + public void testToXContent_whenRescore_thenSucceed() throws IOException { + float[] queryVector = { 1.0f, 2.0f, 3.0f, 4.0f }; + float oversample = 1.0f; + XContentBuilder builderFromObject = XContentFactory.jsonBuilder() + .startObject() + .startObject(NAME) + .startObject(FIELD_NAME) + .field(KNNQueryBuilder.VECTOR_FIELD.getPreferredName(), queryVector) + .field(KNNQueryBuilder.K_FIELD.getPreferredName(), K) + .startObject(RESCORE_PARAMETER) + .field(RESCORE_OVERSAMPLE_PARAMETER, oversample) + .endObject() + .field(BOOST_FIELD.getPreferredName(), BOOST) + .endObject() + .endObject() + .endObject(); + + KNNQueryBuilder knnQueryBuilderFromObject = KNNQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .boost(BOOST) + .k(K) + .rescoreContext(RescoreContext.builder().oversampleFactor(oversample).build()) + .build(); + + XContentBuilder testBuilder = XContentFactory.jsonBuilder(); + testBuilder.startObject(); + KNNQueryBuilderParser.toXContent(testBuilder, EMPTY_PARAMS, knnQueryBuilderFromObject); + testBuilder.endObject(); + assertEquals(builderFromObject.toString(), testBuilder.toString()); + } + + @Override + protected NamedXContentRegistry xContentRegistry() { + List list = ClusterModule.getNamedXWriteables(); + SearchPlugin.QuerySpec spec = new SearchPlugin.QuerySpec<>( + TermQueryBuilder.NAME, + TermQueryBuilder::new, + TermQueryBuilder::fromXContent + ); + list.add(new NamedXContentRegistry.Entry(QueryBuilder.class, spec.getName(), (p, c) -> spec.getParser().fromXContent(p))); + NamedXContentRegistry registry = new NamedXContentRegistry(list); + return registry; + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/parser/MethodParametersParserTests.java b/src/test/java/org/opensearch/knn/index/query/parser/MethodParametersParserTests.java new file mode 100644 index 000000000..984c72b89 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/parser/MethodParametersParserTests.java @@ -0,0 +1,97 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.query.parser; + +import lombok.SneakyThrows; +import org.opensearch.common.ValidationException; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.common.ParsingException; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.knn.KNNTestCase; + +import java.util.Map; + +import static org.opensearch.knn.index.query.parser.MethodParametersParser.doXContent; +import static org.opensearch.knn.index.query.parser.MethodParametersParser.validateMethodParameters; + +public class MethodParametersParserTests extends KNNTestCase { + + public void testValidateMethodParameters() { + ValidationException validationException = validateMethodParameters(Map.of("dummy", 0)); + assertEquals("Validation Failed: 1: dummy is not a valid method parameter;", validationException.getMessage()); + + ValidationException validationException2 = validateMethodParameters(Map.of("ef_search", 0)); + assertTrue(validationException2.getMessage().contains("Validation Failed: 1: ef_search should be greater than 0")); + + ValidationException validationException3 = validateMethodParameters(Map.of("ef_search", 10)); + assertNull(validationException3); + + ValidationException validationException4 = validateMethodParameters(Map.of("nprobes", 0)); + assertTrue(validationException4.getMessage().contains("Validation Failed: 1: nprobes should be greater than 0")); + } + + @SneakyThrows + public void testDoXContent() { + Map params = Map.of("ef_search", 10); + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("method_parameters") + .field("ef_search", 10) + .endObject() + .endObject(); + + XContentBuilder builder2 = XContentFactory.jsonBuilder().startObject(); + doXContent(builder2, params); + builder2.endObject(); + assertEquals(builder.toString(), builder2.toString()); + + XContentBuilder b3 = XContentFactory.jsonBuilder(); + XContentBuilder b4 = XContentFactory.jsonBuilder(); + + doXContent(b4, null); + assertEquals(b3.toString(), b4.toString()); + } + + @SneakyThrows + public void testFromXContent() { + // efsearch string + XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field("ef_search", "string").endObject(); + XContentParser parser1 = createParser(builder); + expectThrows(ParsingException.class, () -> MethodParametersParser.fromXContent(parser1)); + + // unknown method parameter + builder = XContentFactory.jsonBuilder().startObject().field("unknown", "10").endObject(); + XContentParser parser2 = createParser(builder); + expectThrows(ParsingException.class, () -> MethodParametersParser.fromXContent(parser2)); + + // Valid + builder = XContentFactory.jsonBuilder().startObject().field("ef_search", 10).endObject(); + XContentParser parser3 = createParser(builder); + assertEquals(Map.of("ef_search", 10), MethodParametersParser.fromXContent(parser3)); + + // empty map + builder = XContentFactory.jsonBuilder().startObject().endObject(); + XContentParser parser4 = createParser(builder); + expectThrows(ParsingException.class, () -> MethodParametersParser.fromXContent(parser4)); + + // nprobes string + builder = XContentFactory.jsonBuilder().startObject().field("nprobes", "string").endObject(); + XContentParser parser5 = createParser(builder); + expectThrows(ParsingException.class, () -> MethodParametersParser.fromXContent(parser5)); + + // nprobes Valid + builder = XContentFactory.jsonBuilder().startObject().field("nprobes", 10).endObject(); + XContentParser parser6 = createParser(builder); + assertEquals(Map.of("nprobes", 10), MethodParametersParser.fromXContent(parser6)); + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/parser/RescoreParserTests.java b/src/test/java/org/opensearch/knn/index/query/parser/RescoreParserTests.java new file mode 100644 index 000000000..2bb1f89fc --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/parser/RescoreParserTests.java @@ -0,0 +1,97 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.parser; + +import lombok.SneakyThrows; +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.common.io.stream.NamedWriteableAwareStreamInput; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.query.rescore.RescoreContext; + +import java.io.IOException; + +import static org.opensearch.knn.index.query.parser.RescoreParser.RESCORE_OVERSAMPLE_PARAMETER; +import static org.opensearch.knn.index.query.parser.RescoreParser.RESCORE_PARAMETER; + +public class RescoreParserTests extends KNNTestCase { + + @SneakyThrows + public void testStreams() { + RescoreContext rescoreContext = RescoreContext.builder().oversampleFactor(RescoreContext.DEFAULT_OVERSAMPLE_FACTOR).build(); + validateStreams(rescoreContext); + validateStreams(null); + } + + private void validateStreams(RescoreContext rescoreContext) throws IOException { + try (BytesStreamOutput output = new BytesStreamOutput()) { + RescoreParser.streamOutput(output, rescoreContext); + + try (StreamInput in = new NamedWriteableAwareStreamInput(output.bytes().streamInput(), writableRegistry())) { + RescoreContext parsedRescoreContext = RescoreParser.streamInput(in); + assertEquals(rescoreContext, parsedRescoreContext); + } + } + } + + @SneakyThrows + public void testDoXContent() { + float oversample = RescoreContext.MAX_OVERSAMPLE_FACTOR - 1; + XContentBuilder expectedBuilder = XContentFactory.jsonBuilder() + .startObject() + .startObject(RESCORE_PARAMETER) + .field(RESCORE_OVERSAMPLE_PARAMETER, oversample) + .endObject() + .endObject(); + + XContentBuilder builder = XContentFactory.jsonBuilder().startObject(); + RescoreParser.doXContent(builder, RescoreContext.builder().oversampleFactor(oversample).build()); + builder.endObject(); + assertEquals(expectedBuilder.toString(), builder.toString()); + } + + @SneakyThrows + public void testFromXContent_whenValid_thenSucceed() { + float oversample1 = RescoreContext.MAX_OVERSAMPLE_FACTOR - 1; + XContentBuilder builder1 = XContentFactory.jsonBuilder().startObject().field(RESCORE_OVERSAMPLE_PARAMETER, oversample1).endObject(); + validateOversample(oversample1, builder1); + XContentBuilder builder2 = XContentFactory.jsonBuilder().startObject().endObject(); + validateOversample(RescoreContext.DEFAULT_OVERSAMPLE_FACTOR, builder2); + } + + @SneakyThrows + public void testFromXContent_whenInvalid_thenFail() { + XContentBuilder invalidParamBuilder = XContentFactory.jsonBuilder().startObject().field("invalid", 0).endObject(); + expectValidationException(invalidParamBuilder); + + XContentBuilder invalidParamValueBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(RESCORE_OVERSAMPLE_PARAMETER, "c") + .endObject(); + expectValidationException(invalidParamValueBuilder); + + XContentBuilder extraParamBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(RESCORE_OVERSAMPLE_PARAMETER, RescoreContext.MAX_OVERSAMPLE_FACTOR - 1) + .field("invalid", 0) + .endObject(); + expectValidationException(extraParamBuilder); + } + + private void validateOversample(float expectedOversample, XContentBuilder builder) throws IOException { + XContentParser parser = createParser(builder); + RescoreContext rescoreContext = RescoreParser.fromXContent(parser); + assertEquals(expectedOversample, rescoreContext.getOversampleFactor(), 0.0001); + } + + private void expectValidationException(XContentBuilder builder) throws IOException { + XContentParser parser = createParser(builder); + expectThrows(IllegalArgumentException.class, () -> RescoreParser.fromXContent(parser)); + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/parser/RescoreValidationTests.java b/src/test/java/org/opensearch/knn/index/query/parser/RescoreValidationTests.java new file mode 100644 index 000000000..a23a5c757 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/parser/RescoreValidationTests.java @@ -0,0 +1,45 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.parser; + +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; +import lombok.AllArgsConstructor; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.query.rescore.RescoreContext; + +import java.util.Arrays; +import java.util.Collection; + +import static com.carrotsearch.randomizedtesting.RandomizedTest.$; +import static com.carrotsearch.randomizedtesting.RandomizedTest.$$; + +@AllArgsConstructor +public class RescoreValidationTests extends KNNTestCase { + + private boolean isValid; + private RescoreContext rescoreContext; + + @ParametersFactory(argumentFormatting = "isValid:%1$s; rescoreContext:%2$s") + public static Collection validParams() { + return Arrays.asList( + $$( + $(true, RescoreContext.builder().build()), + $(true, RescoreContext.getDefault()), + $(true, RescoreContext.builder().oversampleFactor(RescoreContext.MAX_OVERSAMPLE_FACTOR - 1).build()), + $(false, RescoreContext.builder().oversampleFactor(RescoreContext.MAX_OVERSAMPLE_FACTOR + 1).build()), + $(false, RescoreContext.builder().oversampleFactor(RescoreContext.MIN_OVERSAMPLE_FACTOR - 1).build()) + ) + ); + } + + public void testValidate() { + if (isValid) { + assertNull(RescoreParser.validate(rescoreContext)); + } else { + assertNotNull(RescoreParser.validate(rescoreContext)); + } + } +} diff --git a/src/test/java/org/opensearch/knn/index/query/rescore/RescoreContextTests.java b/src/test/java/org/opensearch/knn/index/query/rescore/RescoreContextTests.java new file mode 100644 index 000000000..2b309e4ab --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/query/rescore/RescoreContextTests.java @@ -0,0 +1,85 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.query.rescore; + +import org.opensearch.knn.KNNTestCase; + +import static org.opensearch.knn.index.query.rescore.RescoreContext.MAX_FIRST_PASS_RESULTS; +import static org.opensearch.knn.index.query.rescore.RescoreContext.MIN_FIRST_PASS_RESULTS; + +public class RescoreContextTests extends KNNTestCase { + + public void testGetFirstPassK() { + float oversample = 2.6f; + RescoreContext rescoreContext = RescoreContext.builder().oversampleFactor(oversample).userProvided(true).build(); + int finalK = 100; + boolean isShardLevelRescoringEnabled = true; + int dimension = 500; + + // Case 1: Test with standard oversample factor when shard-level rescoring is enabled + assertEquals(260, rescoreContext.getFirstPassK(finalK, isShardLevelRescoringEnabled, dimension)); + + // Case 2: Test with a very small finalK that should result in a value less than MIN_FIRST_PASS_RESULTS + finalK = 1; + assertEquals(MIN_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK, isShardLevelRescoringEnabled, dimension)); + + // Case 3: Test with finalK = 0, should return MIN_FIRST_PASS_RESULTS + finalK = 0; + assertEquals(MIN_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK, isShardLevelRescoringEnabled, dimension)); + + // Case 4: Test with finalK = MAX_FIRST_PASS_RESULTS, should cap at MAX_FIRST_PASS_RESULTS + finalK = MAX_FIRST_PASS_RESULTS; + assertEquals(MAX_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK, isShardLevelRescoringEnabled, dimension)); + } + + public void testGetFirstPassKWithDimensionBasedOversampling() { + int finalK = 100; + int dimension; + + // Case 1: Test no oversampling for dimensions >= 1000 when shard-level rescoring is disabled + dimension = 1000; + RescoreContext rescoreContext = RescoreContext.builder().userProvided(false).build(); // Ensuring dimension-based logic applies + assertEquals(100, rescoreContext.getFirstPassK(finalK, false, dimension)); // No oversampling + + // Case 2: Test 2x oversampling for dimensions >= 768 but < 1000 when shard-level rescoring is disabled + dimension = 800; + rescoreContext = RescoreContext.builder().userProvided(false).build(); // Ensure previous values don't carry over + assertEquals(200, rescoreContext.getFirstPassK(finalK, false, dimension)); // 2x oversampling + + // Case 3: Test 3x oversampling for dimensions < 768 when shard-level rescoring is disabled + dimension = 700; + rescoreContext = RescoreContext.builder().userProvided(false).build(); // Ensure previous values don't carry over + assertEquals(300, rescoreContext.getFirstPassK(finalK, false, dimension)); // 3x oversampling + + // Case 4: Shard-level rescoring enabled, oversample factor should be used as provided by the user (ignore dimension) + rescoreContext = RescoreContext.builder().oversampleFactor(5.0f).userProvided(true).build(); // Provided by user + dimension = 500; + assertEquals(500, rescoreContext.getFirstPassK(finalK, true, dimension)); // User-defined oversample factor should be used + + // Case 5: Test finalK where oversampling factor results in a value less than MIN_FIRST_PASS_RESULTS + finalK = 10; + dimension = 700; + rescoreContext = RescoreContext.builder().userProvided(false).build(); // Ensure dimension-based logic applies + assertEquals(100, rescoreContext.getFirstPassK(finalK, false, dimension)); // 3x oversampling results in 30 + } + + public void testGetFirstPassKWithMinPassK() { + float oversample = 0.5f; + RescoreContext rescoreContext = RescoreContext.builder().oversampleFactor(oversample).userProvided(true).build(); // User provided + boolean isShardLevelRescoringEnabled = false; + + // Case 1: Test where finalK * oversample is smaller than MIN_FIRST_PASS_RESULTS + int finalK = 10; + int dimension = 700; + assertEquals(MIN_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK, isShardLevelRescoringEnabled, dimension)); + + // Case 2: Test where finalK * oversample results in exactly MIN_FIRST_PASS_RESULTS + finalK = 100; + oversample = 1.0f; // This will result in exactly 100 (MIN_FIRST_PASS_RESULTS) + rescoreContext = RescoreContext.builder().oversampleFactor(oversample).userProvided(true).build(); // User provided + assertEquals(MIN_FIRST_PASS_RESULTS, rescoreContext.getFirstPassK(finalK, isShardLevelRescoringEnabled, dimension)); + } +} diff --git a/src/test/java/org/opensearch/knn/index/util/IndexHyperParametersUtilTests.java b/src/test/java/org/opensearch/knn/index/util/IndexHyperParametersUtilTests.java new file mode 100644 index 000000000..96eb41476 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/util/IndexHyperParametersUtilTests.java @@ -0,0 +1,52 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.index.util; + +import junit.framework.TestCase; +import org.junit.Assert; +import org.opensearch.Version; +import org.opensearch.knn.index.KNNSettings; + +public class IndexHyperParametersUtilTests extends TestCase { + + public void testLombokNonNull() { + Assert.assertThrows(NullPointerException.class, () -> IndexHyperParametersUtil.getHNSWEFConstructionValue(null)); + Assert.assertThrows(NullPointerException.class, () -> IndexHyperParametersUtil.getHNSWEFSearchValue(null)); + } + + public void testGetHNSWEFConstructionValue_withDifferentValues_thenSuccess() { + Assert.assertEquals(512, IndexHyperParametersUtil.getHNSWEFConstructionValue(Version.V_2_11_0)); + Assert.assertEquals(512, IndexHyperParametersUtil.getHNSWEFConstructionValue(Version.V_2_3_0)); + Assert.assertEquals( + KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_CONSTRUCTION.intValue(), + IndexHyperParametersUtil.getHNSWEFConstructionValue(Version.CURRENT) + ); + + } + + public void testGetHNSWEFSearchValue_withDifferentValues_thenSuccess() { + Assert.assertEquals(512, IndexHyperParametersUtil.getHNSWEFConstructionValue(Version.V_2_11_0)); + Assert.assertEquals(512, IndexHyperParametersUtil.getHNSWEFConstructionValue(Version.V_2_3_0)); + Assert.assertEquals( + KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH.intValue(), + IndexHyperParametersUtil.getHNSWEFConstructionValue(Version.CURRENT) + ); + } + + public void testGetBinaryQuantizationEFValues_thenSuccess() { + // Test for Binary Quantization EF Construction value + Assert.assertEquals(256, IndexHyperParametersUtil.getBinaryQuantizationEFConstructionValue()); + + // Test for Binary Quantization EF Search value + Assert.assertEquals(256, IndexHyperParametersUtil.getBinaryQuantizationEFSearchValue()); + } +} diff --git a/src/test/java/org/opensearch/knn/index/util/IndexUtilTests.java b/src/test/java/org/opensearch/knn/index/util/IndexUtilTests.java new file mode 100644 index 000000000..867028dd8 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/util/IndexUtilTests.java @@ -0,0 +1,345 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.util; + +import com.google.common.collect.ImmutableMap; +import org.junit.BeforeClass; +import org.mockito.MockedStatic; +import org.opensearch.Version; +import org.opensearch.cluster.ClusterState; +import org.opensearch.cluster.metadata.IndexMetadata; +import org.opensearch.cluster.metadata.MappingMetadata; +import org.opensearch.cluster.metadata.Metadata; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.ValidationException; +import org.opensearch.common.settings.Settings; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.indices.ModelDao; +import org.opensearch.knn.indices.ModelMetadata; +import org.opensearch.knn.jni.JNIService; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PQ; +import static org.opensearch.knn.common.KNNConstants.ENCODER_SQ; +import static org.opensearch.knn.common.KNNConstants.HNSW_ALGO_EF_SEARCH; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_IVF; +import static org.opensearch.knn.common.KNNConstants.SPACE_TYPE; +import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; +import static org.opensearch.knn.index.util.IndexUtil.getParametersAtLoading; +import static org.opensearch.knn.index.KNNSettings.KNN_ALGO_PARAM_EF_SEARCH; + +public class IndexUtilTests extends KNNTestCase { + + private static MockedStatic jniServiceMockedStatic; + private static final long TEST_INDEX_ADDRESS = 0; + + @BeforeClass + public static void setUpClass() { + jniServiceMockedStatic = mockStatic(JNIService.class); + } + + public void testGetLoadParameters() { + // Test faiss to ensure that space type gets set properly + SpaceType spaceType1 = SpaceType.COSINESIMIL; + KNNEngine knnEngine1 = KNNEngine.FAISS; + String indexName = "my-test-index"; + VectorDataType vectorDataType1 = VectorDataType.FLOAT; + + Map loadParameters = getParametersAtLoading(spaceType1, knnEngine1, indexName, vectorDataType1); + assertEquals(2, loadParameters.size()); + assertEquals(spaceType1.getValue(), loadParameters.get(SPACE_TYPE)); + assertEquals(vectorDataType1.getValue(), loadParameters.get(VECTOR_DATA_TYPE_FIELD)); + + // Test nmslib to ensure both space type and ef search are properly set + SpaceType spaceType2 = SpaceType.L1; + KNNEngine knnEngine2 = KNNEngine.NMSLIB; + VectorDataType vectorDataType2 = VectorDataType.BINARY; + int efSearchValue = 413; + + // We use the constant for the setting here as opposed to the identifier of efSearch in nmslib jni + Map indexSettings = ImmutableMap.of(KNN_ALGO_PARAM_EF_SEARCH, efSearchValue); + + // Because ef search comes from an index setting, we need to mock the long line of calls to get those + // index settings + Settings settings = Settings.builder().loadFromMap(indexSettings).build(); + IndexMetadata indexMetadata = mock(IndexMetadata.class); + when(indexMetadata.getSettings()).thenReturn(settings); + when(indexMetadata.getCreationVersion()).thenReturn(Version.CURRENT); + Metadata metadata = mock(Metadata.class); + when(metadata.index(anyString())).thenReturn(indexMetadata); + ClusterState clusterState = mock(ClusterState.class); + when(clusterState.getMetadata()).thenReturn(metadata); + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.state()).thenReturn(clusterState); + KNNSettings.state().setClusterService(clusterService); + + loadParameters = getParametersAtLoading(spaceType2, knnEngine2, indexName, vectorDataType2); + assertEquals(3, loadParameters.size()); + assertEquals(spaceType2.getValue(), loadParameters.get(SPACE_TYPE)); + assertEquals(efSearchValue, loadParameters.get(HNSW_ALGO_EF_SEARCH)); + assertEquals(vectorDataType2.getValue(), loadParameters.get(VECTOR_DATA_TYPE_FIELD)); + } + + public void testValidateKnnField_NestedField() { + Map deepFieldValues = Map.of("type", "knn_vector", "dimension", 8); + Map deepField = Map.of("train-field", deepFieldValues); + Map deepFieldProperties = Map.of("properties", deepField); + Map nest_b = Map.of("b", deepFieldProperties); + Map nest_b_properties = Map.of("properties", nest_b); + Map nest_a = Map.of("a", nest_b_properties); + Map properties = Map.of("properties", nest_a); + + String field = "a.b.train-field"; + int dimension = 8; + + MappingMetadata mappingMetadata = mock(MappingMetadata.class); + when(mappingMetadata.getSourceAsMap()).thenReturn(properties); + IndexMetadata indexMetadata = mock(IndexMetadata.class); + when(indexMetadata.mapping()).thenReturn(mappingMetadata); + ModelDao modelDao = mock(ModelDao.class); + ModelMetadata trainingFieldModelMetadata = mock(ModelMetadata.class); + when(trainingFieldModelMetadata.getDimension()).thenReturn(dimension); + when(modelDao.getMetadata(anyString())).thenReturn(trainingFieldModelMetadata); + + ValidationException e = IndexUtil.validateKnnField(indexMetadata, field, dimension, modelDao, null, null); + + assertNull(e); + } + + public void testValidateKnnField_NonNestedField() { + Map fieldValues = Map.of("type", "knn_vector", "dimension", 8); + Map top_level_field = Map.of("top_level_field", fieldValues); + Map properties = Map.of("properties", top_level_field); + String field = "top_level_field"; + int dimension = 8; + + MappingMetadata mappingMetadata = mock(MappingMetadata.class); + when(mappingMetadata.getSourceAsMap()).thenReturn(properties); + IndexMetadata indexMetadata = mock(IndexMetadata.class); + when(indexMetadata.mapping()).thenReturn(mappingMetadata); + ModelDao modelDao = mock(ModelDao.class); + ModelMetadata trainingFieldModelMetadata = mock(ModelMetadata.class); + when(trainingFieldModelMetadata.getDimension()).thenReturn(dimension); + when(modelDao.getMetadata(anyString())).thenReturn(trainingFieldModelMetadata); + + ValidationException e = IndexUtil.validateKnnField(indexMetadata, field, dimension, modelDao, null, null); + + assertNull(e); + } + + public void testValidateKnnField_NonKnnField() { + Map fieldValues = Map.of("type", "text"); + Map top_level_field = Map.of("top_level_field", fieldValues); + Map properties = Map.of("properties", top_level_field); + String field = "top_level_field"; + int dimension = 8; + MappingMetadata mappingMetadata = mock(MappingMetadata.class); + when(mappingMetadata.getSourceAsMap()).thenReturn(properties); + IndexMetadata indexMetadata = mock(IndexMetadata.class); + when(indexMetadata.mapping()).thenReturn(mappingMetadata); + ModelDao modelDao = mock(ModelDao.class); + ModelMetadata trainingFieldModelMetadata = mock(ModelMetadata.class); + when(trainingFieldModelMetadata.getDimension()).thenReturn(dimension); + when(modelDao.getMetadata(anyString())).thenReturn(trainingFieldModelMetadata); + + ValidationException e = IndexUtil.validateKnnField(indexMetadata, field, dimension, modelDao, null, null); + + assert Objects.requireNonNull(e).getMessage().matches("Validation Failed: 1: Field \"" + field + "\" is not of type knn_vector.;"); + } + + public void testValidateKnnField_WrongFieldPath() { + Map deepFieldValues = Map.of("type", "knn_vector", "dimension", 8); + Map deepField = Map.of("train-field", deepFieldValues); + Map deepFieldProperties = Map.of("properties", deepField); + Map nest_b = Map.of("b", deepFieldProperties); + Map nest_b_properties = Map.of("properties", nest_b); + Map nest_a = Map.of("a", nest_b_properties); + Map properties = Map.of("properties", nest_a); + String field = "a.train-field"; + int dimension = 8; + MappingMetadata mappingMetadata = mock(MappingMetadata.class); + when(mappingMetadata.getSourceAsMap()).thenReturn(properties); + IndexMetadata indexMetadata = mock(IndexMetadata.class); + when(indexMetadata.mapping()).thenReturn(mappingMetadata); + ModelDao modelDao = mock(ModelDao.class); + ModelMetadata trainingFieldModelMetadata = mock(ModelMetadata.class); + when(trainingFieldModelMetadata.getDimension()).thenReturn(dimension); + when(modelDao.getMetadata(anyString())).thenReturn(trainingFieldModelMetadata); + + ValidationException e = IndexUtil.validateKnnField(indexMetadata, field, dimension, modelDao, null, null); + + assert (Objects.requireNonNull(e).getMessage().matches("Validation Failed: 1: Field \"" + field + "\" does not exist.;")); + } + + public void testValidateKnnField_EmptyField() { + Map deepFieldValues = Map.of("type", "knn_vector", "dimension", 8); + Map deepField = Map.of("train-field", deepFieldValues); + Map deepFieldProperties = Map.of("properties", deepField); + Map nest_b = Map.of("b", deepFieldProperties); + Map nest_b_properties = Map.of("properties", nest_b); + Map nest_a = Map.of("a", nest_b_properties); + Map properties = Map.of("properties", nest_a); + String field = ""; + int dimension = 8; + MappingMetadata mappingMetadata = mock(MappingMetadata.class); + when(mappingMetadata.getSourceAsMap()).thenReturn(properties); + IndexMetadata indexMetadata = mock(IndexMetadata.class); + when(indexMetadata.mapping()).thenReturn(mappingMetadata); + ModelDao modelDao = mock(ModelDao.class); + ModelMetadata trainingFieldModelMetadata = mock(ModelMetadata.class); + when(trainingFieldModelMetadata.getDimension()).thenReturn(dimension); + when(modelDao.getMetadata(anyString())).thenReturn(trainingFieldModelMetadata); + + ValidationException e = IndexUtil.validateKnnField(indexMetadata, field, dimension, modelDao, null, null); + + System.out.println(Objects.requireNonNull(e).getMessage()); + + assert (Objects.requireNonNull(e).getMessage().matches("Validation Failed: 1: Field path is empty.;")); + } + + public void testValidateKnnField_EmptyIndexMetadata() { + String field = "a.b.train-field"; + int dimension = 8; + IndexMetadata indexMetadata = mock(IndexMetadata.class); + when(indexMetadata.mapping()).thenReturn(null); + ModelDao modelDao = mock(ModelDao.class); + ModelMetadata trainingFieldModelMetadata = mock(ModelMetadata.class); + when(trainingFieldModelMetadata.getDimension()).thenReturn(dimension); + when(modelDao.getMetadata(anyString())).thenReturn(trainingFieldModelMetadata); + + ValidationException e = IndexUtil.validateKnnField(indexMetadata, field, dimension, modelDao, null, null); + + assert (Objects.requireNonNull(e).getMessage().matches("Validation Failed: 1: Invalid index. Index does not contain a mapping;")); + } + + public void testIsShareableStateContainedInIndex_whenIndexNotModelBased_thenReturnFalse() { + String modelId = null; + KNNEngine knnEngine = KNNEngine.FAISS; + assertFalse(IndexUtil.isSharedIndexStateRequired(knnEngine, modelId, TEST_INDEX_ADDRESS)); + } + + public void testIsShareableStateContainedInIndex_whenFaissHNSWIsUsed_thenReturnFalse() { + jniServiceMockedStatic.when(() -> JNIService.isSharedIndexStateRequired(anyLong(), any())).thenReturn(false); + String modelId = "test-model"; + KNNEngine knnEngine = KNNEngine.FAISS; + assertFalse(IndexUtil.isSharedIndexStateRequired(knnEngine, modelId, TEST_INDEX_ADDRESS)); + } + + public void testIsShareableStateContainedInIndex_whenJNIIsSharedIndexStateRequiredIsTrue_thenReturnTrue() { + jniServiceMockedStatic.when(() -> JNIService.isSharedIndexStateRequired(anyLong(), any())).thenReturn(true); + String modelId = "test-model"; + KNNEngine knnEngine = KNNEngine.FAISS; + assertTrue(IndexUtil.isSharedIndexStateRequired(knnEngine, modelId, TEST_INDEX_ADDRESS)); + } + + public void testIsBinaryIndex_whenBinary_thenTrue() { + Map binaryIndexParams = new HashMap<>(); + binaryIndexParams.put(VECTOR_DATA_TYPE_FIELD, "binary"); + assertTrue(IndexUtil.isBinaryIndex(KNNEngine.FAISS, binaryIndexParams)); + } + + public void testIsBinaryIndex_whenNonBinary_thenFalse() { + Map nonBinaryIndexParams = new HashMap<>(); + nonBinaryIndexParams.put(VECTOR_DATA_TYPE_FIELD, "byte"); + assertFalse(IndexUtil.isBinaryIndex(KNNEngine.FAISS, nonBinaryIndexParams)); + } + + public void testValidateKnnField_whenTrainModelUseDifferentVectorDataTypeFromTrainIndex_thenThrowException() { + Map fieldValues = Map.of("type", "knn_vector", "dimension", 8, "data_type", "float"); + Map top_level_field = Map.of("top_level_field", fieldValues); + Map properties = Map.of("properties", top_level_field); + String field = "top_level_field"; + int dimension = 8; + + MappingMetadata mappingMetadata = mock(MappingMetadata.class); + when(mappingMetadata.getSourceAsMap()).thenReturn(properties); + IndexMetadata indexMetadata = mock(IndexMetadata.class); + when(indexMetadata.mapping()).thenReturn(mappingMetadata); + ModelDao modelDao = mock(ModelDao.class); + + ValidationException e = IndexUtil.validateKnnField(indexMetadata, field, dimension, modelDao, VectorDataType.BINARY, null); + System.out.println(Objects.requireNonNull(e).getMessage()); + + assert Objects.requireNonNull(e) + .getMessage() + .matches( + "Validation Failed: 1: Field \"" + + field + + "\" has data type float, which is different from data type used in the training request: binary;" + ); + } + + public void testUpdateVectorDataTypeToParameters_whenVectorDataTypeIsBinary() { + Map indexParams = new HashMap<>(); + IndexUtil.updateVectorDataTypeToParameters(indexParams, VectorDataType.BINARY); + assertEquals(VectorDataType.BINARY.getValue(), indexParams.get(VECTOR_DATA_TYPE_FIELD)); + } + + public void testValidateKnnField_whenPassBinaryVectorDataTypeAndEncoder_thenThrowException() { + validateKnnField_whenPassVectorDataTypeAndEncoder_thenThrowException(ENCODER_SQ, VectorDataType.BINARY); + validateKnnField_whenPassVectorDataTypeAndEncoder_thenThrowException(ENCODER_PQ, VectorDataType.BINARY); + } + + public void testValidateKnnField_whenPassByteVectorDataTypeAndEncoder_thenThrowException() { + validateKnnField_whenPassVectorDataTypeAndEncoder_thenThrowException(ENCODER_SQ, VectorDataType.BYTE); + validateKnnField_whenPassVectorDataTypeAndEncoder_thenThrowException(ENCODER_PQ, VectorDataType.BYTE); + } + + public void validateKnnField_whenPassVectorDataTypeAndEncoder_thenThrowException(String encoder, VectorDataType vectorDataType) { + Map fieldValues = Map.of( + "type", + "knn_vector", + "dimension", + 8, + "data_type", + vectorDataType.getValue(), + "encoder", + encoder + ); + Map top_level_field = Map.of("top_level_field", fieldValues); + Map properties = Map.of("properties", top_level_field); + String field = "top_level_field"; + int dimension = 8; + + MappingMetadata mappingMetadata = mock(MappingMetadata.class); + when(mappingMetadata.getSourceAsMap()).thenReturn(properties); + IndexMetadata indexMetadata = mock(IndexMetadata.class); + when(indexMetadata.mapping()).thenReturn(mappingMetadata); + ModelDao modelDao = mock(ModelDao.class); + KNNMethodContext knnMethodContext = new KNNMethodContext( + KNNEngine.FAISS, + SpaceType.INNER_PRODUCT, + new MethodComponentContext( + METHOD_IVF, + ImmutableMap.of(METHOD_ENCODER_PARAMETER, new MethodComponentContext(encoder, Collections.emptyMap())) + ) + ); + + ValidationException e = IndexUtil.validateKnnField(indexMetadata, field, dimension, modelDao, vectorDataType, knnMethodContext); + + assert Objects.requireNonNull(e) + .getMessage() + .contains(String.format(Locale.ROOT, "encoder is not supported for vector data type [%s]", vectorDataType.getValue())); + } +} diff --git a/src/test/java/org/opensearch/knn/index/util/KNNClusterUtilTests.java b/src/test/java/org/opensearch/knn/index/util/KNNClusterUtilTests.java new file mode 100644 index 000000000..f04db5c70 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/util/KNNClusterUtilTests.java @@ -0,0 +1,51 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.util; + +import org.opensearch.Version; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.knn.KNNTestCase; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.opensearch.knn.index.KNNClusterTestUtils.mockClusterService; + +public class KNNClusterUtilTests extends KNNTestCase { + + public void testSingleNodeCluster() { + ClusterService clusterService = mockClusterService(Version.V_2_4_0); + + final KNNClusterUtil knnClusterUtil = KNNClusterUtil.instance(); + knnClusterUtil.initialize(clusterService); + + final Version minVersion = knnClusterUtil.getClusterMinVersion(); + + assertTrue(Version.V_2_4_0.equals(minVersion)); + } + + public void testMultipleNodesCluster() { + ClusterService clusterService = mockClusterService(Version.V_2_3_0); + + final KNNClusterUtil knnClusterUtil = KNNClusterUtil.instance(); + knnClusterUtil.initialize(clusterService); + + final Version minVersion = knnClusterUtil.getClusterMinVersion(); + + assertTrue(Version.V_2_3_0.equals(minVersion)); + } + + public void testWhenErrorOnClusterStateDiscover() { + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.state()).thenThrow(new RuntimeException("Cluster state is not ready")); + + final KNNClusterUtil knnClusterUtil = KNNClusterUtil.instance(); + knnClusterUtil.initialize(clusterService); + + final Version minVersion = knnClusterUtil.getClusterMinVersion(); + + assertTrue(Version.CURRENT.equals(minVersion)); + } +} diff --git a/src/test/java/org/opensearch/knn/index/util/KNNLibraryTests.java b/src/test/java/org/opensearch/knn/index/util/KNNLibraryTests.java deleted file mode 100644 index 4f99c6833..000000000 --- a/src/test/java/org/opensearch/knn/index/util/KNNLibraryTests.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.knn.index.util; - -import org.opensearch.knn.KNNTestCase; -import org.opensearch.knn.common.KNNConstants; -import org.opensearch.knn.index.KNNMethod; -import org.opensearch.knn.index.KNNMethodContext; -import org.opensearch.knn.index.MethodComponent; -import org.opensearch.knn.index.MethodComponentContext; -import org.opensearch.knn.index.Parameter; -import org.opensearch.knn.index.SpaceType; -import com.google.common.collect.ImmutableMap; -import org.opensearch.common.ValidationException; -import org.opensearch.common.xcontent.XContentBuilder; -import org.opensearch.common.xcontent.XContentFactory; -import org.opensearch.knn.index.util.KNNLibrary.Faiss.MethodAsMapBuilder; - -import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; - -import static org.opensearch.knn.common.KNNConstants.INDEX_DESCRIPTION_PARAMETER; -import static org.opensearch.knn.common.KNNConstants.NAME; -import static org.opensearch.knn.common.KNNConstants.PARAMETERS; - -public class KNNLibraryTests extends KNNTestCase { - /** - * Test native library build version getter - */ - public void testNativeLibrary_getLatestBuildVersion() { - String latestBuildVersion = "test-build-version"; - TestNativeLibrary testNativeLibrary = new TestNativeLibrary( - Collections.emptyMap(), - Collections.emptyMap(), - latestBuildVersion, - "", - "" - ); - assertEquals(latestBuildVersion, testNativeLibrary.getLatestBuildVersion()); - } - - /** - * Test native library version getter - */ - public void testNativeLibrary_getLatestLibVersion() { - String latestVersion = "test-lib-version"; - TestNativeLibrary testNativeLibrary = new TestNativeLibrary(Collections.emptyMap(), Collections.emptyMap(), "", latestVersion, ""); - assertEquals(latestVersion, testNativeLibrary.getLatestLibVersion()); - } - - /** - * Test native library extension getter - */ - public void testNativeLibrary_getExtension() { - String extension = ".extension"; - TestNativeLibrary testNativeLibrary = new TestNativeLibrary(Collections.emptyMap(), Collections.emptyMap(), "", "", extension); - assertEquals(extension, testNativeLibrary.getExtension()); - } - - /** - * Test native library compound extension getter - */ - public void testNativeLibrary_getCompoundExtension() { - String extension = ".extension"; - TestNativeLibrary testNativeLibrary = new TestNativeLibrary(Collections.emptyMap(), Collections.emptyMap(), "", "", extension); - assertEquals(extension + "c", testNativeLibrary.getCompoundExtension()); - } - - /** - * Test native library compound extension getter - */ - public void testNativeLibrary_getMethod() { - String methodName1 = "test-method-1"; - KNNMethod knnMethod1 = KNNMethod.Builder.builder(MethodComponent.Builder.builder(methodName1).build()).build(); - - String methodName2 = "test-method-2"; - KNNMethod knnMethod2 = KNNMethod.Builder.builder(MethodComponent.Builder.builder(methodName2).build()).build(); - - Map knnMethodMap = ImmutableMap.of(methodName1, knnMethod1, methodName2, knnMethod2); - - TestNativeLibrary testNativeLibrary = new TestNativeLibrary(knnMethodMap, Collections.emptyMap(), "", "", ""); - assertEquals(knnMethod1, testNativeLibrary.getMethod(methodName1)); - assertEquals(knnMethod2, testNativeLibrary.getMethod(methodName2)); - expectThrows(IllegalArgumentException.class, () -> testNativeLibrary.getMethod("invalid")); - } - - /** - * Test native library scoring override - */ - public void testNativeLibrary_score() { - Map> translationMap = ImmutableMap.of(SpaceType.L2, s -> s * 2); - TestNativeLibrary testNativeLibrary = new TestNativeLibrary(Collections.emptyMap(), translationMap, "", "", ""); - // Test override - assertEquals(2f, testNativeLibrary.score(1f, SpaceType.L2), 0.0001); - - // Test non-override - assertEquals(SpaceType.L1.scoreTranslation(1f), testNativeLibrary.score(1f, SpaceType.L1), 0.0001); - } - - /** - * Test native library method validation - */ - public void testNativeLibrary_validateMethod() throws IOException { - // Invalid - method not supported - String methodName1 = "test-method-1"; - KNNMethod knnMethod1 = KNNMethod.Builder.builder(MethodComponent.Builder.builder(methodName1).build()).build(); - - Map methodMap = ImmutableMap.of(methodName1, knnMethod1); - TestNativeLibrary testNativeLibrary1 = new TestNativeLibrary(methodMap, Collections.emptyMap(), "", "", ""); - - XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().field(NAME, "invalid").endObject(); - Map in = xContentBuilderToMap(xContentBuilder); - KNNMethodContext knnMethodContext1 = KNNMethodContext.parse(in); - expectThrows(IllegalArgumentException.class, () -> testNativeLibrary1.validateMethod(knnMethodContext1)); - - // Invalid - method validation - String methodName2 = "test-method-2"; - KNNMethod knnMethod2 = new KNNMethod(MethodComponent.Builder.builder(methodName2).build(), Collections.emptySet()) { - @Override - public ValidationException validate(KNNMethodContext knnMethodContext) { - return new ValidationException(); - } - }; - - methodMap = ImmutableMap.of(methodName2, knnMethod2); - TestNativeLibrary testNativeLibrary2 = new TestNativeLibrary(methodMap, Collections.emptyMap(), "", "", ""); - xContentBuilder = XContentFactory.jsonBuilder().startObject().field(NAME, methodName2).endObject(); - in = xContentBuilderToMap(xContentBuilder); - KNNMethodContext knnMethodContext2 = KNNMethodContext.parse(in); - assertNotNull(testNativeLibrary2.validateMethod(knnMethodContext2)); - } - - public void testNativeLibrary_getMethodAsMap() { - String methodName = "test-method-1"; - SpaceType spaceType = SpaceType.DEFAULT; - Map generatedMap = ImmutableMap.of("test-key", "test-param"); - MethodComponent methodComponent = MethodComponent.Builder.builder(methodName) - .setMapGenerator(((methodComponent1, methodComponentContext) -> generatedMap)) - .build(); - KNNMethod knnMethod = KNNMethod.Builder.builder(methodComponent).build(); - - TestNativeLibrary testNativeLibrary = new TestNativeLibrary( - ImmutableMap.of(methodName, knnMethod), - Collections.emptyMap(), - "", - "", - "" - ); - - // Check that map is expected - Map expectedMap = new HashMap<>(generatedMap); - expectedMap.put(KNNConstants.SPACE_TYPE, spaceType.getValue()); - KNNMethodContext knnMethodContext = new KNNMethodContext( - KNNEngine.DEFAULT, - spaceType, - new MethodComponentContext(methodName, Collections.emptyMap()) - ); - assertEquals(expectedMap, testNativeLibrary.getMethodAsMap(knnMethodContext)); - - // Check when invalid method is passed in - KNNMethodContext invalidKnnMethodContext = new KNNMethodContext( - KNNEngine.DEFAULT, - spaceType, - new MethodComponentContext("invalid", Collections.emptyMap()) - ); - expectThrows(IllegalArgumentException.class, () -> testNativeLibrary.getMethodAsMap(invalidKnnMethodContext)); - } - - public void testFaiss_methodAsMapBuilder() throws IOException { - String methodName = "test-method"; - String methodDescription = "test-description"; - String parameter1 = "test-parameter-1"; - Integer value1 = 10; - Integer defaultValue1 = 1; - String parameter2 = "test-parameter-2"; - Integer value2 = 15; - Integer defaultValue2 = 2; - String parameter3 = "test-parameter-3"; - Integer defaultValue3 = 3; - MethodComponent methodComponent = MethodComponent.Builder.builder(methodName) - .addParameter(parameter1, new Parameter.IntegerParameter(parameter1, defaultValue1, value -> value > 0)) - .addParameter(parameter2, new Parameter.IntegerParameter(parameter2, defaultValue2, value -> value > 0)) - .addParameter(parameter3, new Parameter.IntegerParameter(parameter3, defaultValue3, value -> value > 0)) - .build(); - - XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() - .startObject() - .field(NAME, methodName) - .startObject(PARAMETERS) - .field(parameter1, value1) - .field(parameter2, value2) - .endObject() - .endObject(); - Map in = xContentBuilderToMap(xContentBuilder); - MethodComponentContext methodComponentContext = MethodComponentContext.parse(in); - - Map expectedParametersMap = new HashMap<>(methodComponentContext.getParameters()); - expectedParametersMap.put(parameter3, defaultValue3); - expectedParametersMap.remove(parameter1); - Map expectedMap = new HashMap<>(); - expectedMap.put(PARAMETERS, expectedParametersMap); - expectedMap.put(NAME, methodName); - expectedMap.put(INDEX_DESCRIPTION_PARAMETER, methodDescription + value1); - - Map methodAsMap = MethodAsMapBuilder.builder(methodDescription, methodComponent, methodComponentContext) - .addParameter(parameter1, "", "") - .build(); - - assertEquals(expectedMap, methodAsMap); - } - - static class TestNativeLibrary extends KNNLibrary.NativeLibrary { - /** - * Constructor for TestNativeLibrary - * - * @param methods map of methods the native library supports - * @param scoreTranslation Map of translation of space type to scores returned by the library - * @param latestLibraryBuildVersion String representation of latest build version of the library - * @param latestLibraryVersion String representation of latest version of the library - * @param extension String representing the extension that library files should use - */ - public TestNativeLibrary( - Map methods, - Map> scoreTranslation, - String latestLibraryBuildVersion, - String latestLibraryVersion, - String extension - ) { - super(methods, scoreTranslation, latestLibraryBuildVersion, latestLibraryVersion, extension); - } - } -} diff --git a/src/test/java/org/opensearch/knn/index/vectorvalues/KNNVectorValuesFactoryTests.java b/src/test/java/org/opensearch/knn/index/vectorvalues/KNNVectorValuesFactoryTests.java new file mode 100644 index 000000000..a717aa2c2 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/vectorvalues/KNNVectorValuesFactoryTests.java @@ -0,0 +1,152 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.vectorvalues; + +import lombok.SneakyThrows; +import org.apache.lucene.index.BinaryDocValues; +import org.apache.lucene.index.DocsWithFieldSet; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.SegmentReader; +import org.apache.lucene.index.VectorEncoding; +import org.junit.Assert; +import org.mockito.Mockito; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.VectorDataType; + +import java.util.List; +import java.util.Map; + +public class KNNVectorValuesFactoryTests extends KNNTestCase { + private static final int COUNT = 10; + private static final int DIMENSION = 10; + + public void testGetVectorValuesFromDISI_whenValidInput_thenSuccess() { + final BinaryDocValues binaryDocValues = new TestVectorValues.RandomVectorBinaryDocValues(COUNT, DIMENSION); + final KNNVectorValues floatVectorValues = KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, binaryDocValues); + Assert.assertNotNull(floatVectorValues); + + final KNNVectorValues byteVectorValues = KNNVectorValuesFactory.getVectorValues(VectorDataType.BYTE, binaryDocValues); + Assert.assertNotNull(byteVectorValues); + + final KNNVectorValues binaryVectorValues = KNNVectorValuesFactory.getVectorValues(VectorDataType.BINARY, binaryDocValues); + Assert.assertNotNull(binaryVectorValues); + } + + public void testGetVectorValuesUsingDocWithFieldSet_whenValidInput_thenSuccess() { + final DocsWithFieldSet docsWithFieldSet = new DocsWithFieldSet(); + docsWithFieldSet.add(0); + docsWithFieldSet.add(1); + final Map floatVectorMap = Map.of(0, new float[] { 1, 2 }, 1, new float[] { 2, 3 }); + final KNNVectorValues floatVectorValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.FLOAT, + docsWithFieldSet, + floatVectorMap + ); + Assert.assertNotNull(floatVectorValues); + + final Map byteVectorMap = Map.of(0, new byte[] { 4, 5 }, 1, new byte[] { 6, 7 }); + + final KNNVectorValues byteVectorValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.BYTE, + docsWithFieldSet, + byteVectorMap + ); + Assert.assertNotNull(byteVectorValues); + + final KNNVectorValues binaryVectorValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.BINARY, + docsWithFieldSet, + byteVectorMap + ); + Assert.assertNotNull(binaryVectorValues); + } + + @SneakyThrows + public void testGetVectorValuesFromFieldInfo_whenVectorDimIsNotZero_thenSuccess() { + final List byteArrayList = List.of(new byte[] { 1, 2, 3 }); + final List floatArrayList = List.of(new float[] { 1.3f, 2.2f, 3.2f }); + final List binaryArrayList = List.of(new byte[] { 3, 2, 3 }); + final FieldInfo fieldInfo = Mockito.mock(FieldInfo.class); + final SegmentReader reader = Mockito.mock(SegmentReader.class); + Mockito.when(fieldInfo.hasVectorValues()).thenReturn(true); + Mockito.when(fieldInfo.getName()).thenReturn("test_field"); + + // Checking for ByteVectorValues + Mockito.when(fieldInfo.getVectorEncoding()).thenReturn(VectorEncoding.BYTE); + Mockito.when(fieldInfo.getAttribute(KNNConstants.VECTOR_DATA_TYPE_FIELD)).thenReturn(VectorDataType.BYTE.getValue()); + Mockito.when(reader.getByteVectorValues("test_field")).thenReturn(new TestVectorValues.PreDefinedByteVectorValues(byteArrayList)); + final KNNVectorValues byteVectorValues = KNNVectorValuesFactory.getVectorValues(fieldInfo, reader); + byteVectorValues.nextDoc(); + Assert.assertArrayEquals(byteArrayList.get(0), byteVectorValues.getVector()); + Assert.assertNotNull(byteVectorValues); + + // Checking for FloatVectorValues + Mockito.when(fieldInfo.getVectorEncoding()).thenReturn(VectorEncoding.FLOAT32); + Mockito.when(fieldInfo.getAttribute(KNNConstants.VECTOR_DATA_TYPE_FIELD)).thenReturn(VectorDataType.FLOAT.getValue()); + Mockito.when(reader.getFloatVectorValues("test_field")) + .thenReturn(new TestVectorValues.PreDefinedFloatVectorValues(floatArrayList)); + final KNNVectorValues floatVectorValues = KNNVectorValuesFactory.getVectorValues(fieldInfo, reader); + floatVectorValues.nextDoc(); + Assert.assertArrayEquals(floatArrayList.get(0), floatVectorValues.getVector(), 0.0f); + Assert.assertNotNull(floatVectorValues); + + // Checking for BinaryVectorValues + Mockito.when(fieldInfo.getVectorEncoding()).thenReturn(VectorEncoding.BYTE); + Mockito.when(fieldInfo.getAttribute(KNNConstants.VECTOR_DATA_TYPE_FIELD)).thenReturn(VectorDataType.BINARY.getValue()); + Mockito.when(reader.getByteVectorValues("test_field")) + .thenReturn(new TestVectorValues.PreDefinedBinaryVectorValues(binaryArrayList)); + final KNNVectorValues binaryVectorValues = KNNVectorValuesFactory.getVectorValues(fieldInfo, reader); + binaryVectorValues.nextDoc(); + Assert.assertArrayEquals(binaryArrayList.get(0), binaryVectorValues.getVector()); + Assert.assertNotNull(binaryVectorValues); + + } + + @SneakyThrows + public void testGetVectorValuesFromFieldInfo_whenVectorDimIsZero_thenSuccess() { + final List byteArrayList = List.of(new byte[] { 1, 2, 3 }); + final List floatArrayList = List.of(new float[] { 1.3f, 2.2f, 3.2f }); + final List binaryArrayList = List.of(new byte[] { 3, 2, 3 }); + final FieldInfo fieldInfo = Mockito.mock(FieldInfo.class); + final SegmentReader reader = Mockito.mock(SegmentReader.class); + Mockito.when(fieldInfo.hasVectorValues()).thenReturn(false); + Mockito.when(fieldInfo.getName()).thenReturn("test_field"); + + // Checking for ByteVectorValues + Mockito.when(fieldInfo.getAttribute(KNNConstants.VECTOR_DATA_TYPE_FIELD)).thenReturn(VectorDataType.BYTE.getValue()); + Mockito.when(reader.getBinaryDocValues("test_field")) + .thenReturn(new TestVectorValues.PredefinedByteVectorBinaryDocValues(byteArrayList)); + + final KNNVectorValues byteVectorValues = KNNVectorValuesFactory.getVectorValues(fieldInfo, reader); + byteVectorValues.nextDoc(); + Assert.assertArrayEquals(byteArrayList.get(0), byteVectorValues.getVector()); + Assert.assertNotNull(byteVectorValues); + + // Checking for Floats with BinaryDocValues + Mockito.when(fieldInfo.getAttribute(KNNConstants.VECTOR_DATA_TYPE_FIELD)).thenReturn(VectorDataType.FLOAT.getValue()); + Mockito.when(reader.getBinaryDocValues("test_field")) + .thenReturn(new TestVectorValues.PredefinedFloatVectorBinaryDocValues(floatArrayList)); + + final KNNVectorValues floatVectorValues = KNNVectorValuesFactory.getVectorValues(fieldInfo, reader); + floatVectorValues.nextDoc(); + Assert.assertArrayEquals(floatArrayList.get(0), floatVectorValues.getVector(), 0.0f); + Assert.assertNotNull(floatVectorValues); + + // Checking for BinaryVectorValues + Mockito.when(fieldInfo.getAttribute(KNNConstants.VECTOR_DATA_TYPE_FIELD)).thenReturn(VectorDataType.BINARY.getValue()); + Mockito.when(reader.getBinaryDocValues("test_field")) + .thenReturn(new TestVectorValues.PredefinedByteVectorBinaryDocValues(binaryArrayList)); + + final KNNVectorValues binaryVectorValues = KNNVectorValuesFactory.getVectorValues(fieldInfo, reader); + binaryVectorValues.nextDoc(); + Assert.assertArrayEquals(binaryArrayList.get(0), binaryVectorValues.getVector()); + Assert.assertNotNull(binaryVectorValues); + + Mockito.verify(fieldInfo, Mockito.times(0)).getVectorEncoding(); + } + +} diff --git a/src/test/java/org/opensearch/knn/index/vectorvalues/KNNVectorValuesTests.java b/src/test/java/org/opensearch/knn/index/vectorvalues/KNNVectorValuesTests.java new file mode 100644 index 000000000..0b631ab41 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/vectorvalues/KNNVectorValuesTests.java @@ -0,0 +1,155 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.vectorvalues; + +import lombok.SneakyThrows; +import org.apache.lucene.index.DocsWithFieldSet; +import org.apache.lucene.search.DocIdSetIterator; +import org.junit.Assert; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.VectorDataType; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class KNNVectorValuesTests extends KNNTestCase { + + @SneakyThrows + public void testFloatVectorValues_whenValidInput_thenSuccess() { + final List floatArray = List.of(new float[] { 1, 2 }, new float[] { 2, 3 }); + final int dimension = floatArray.get(0).length; + final TestVectorValues.PreDefinedFloatVectorValues randomVectorValues = new TestVectorValues.PreDefinedFloatVectorValues( + floatArray + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues(VectorDataType.FLOAT, randomVectorValues); + new CompareVectorValues().validateVectorValues(knnVectorValues, floatArray, 8, dimension, true); + + final DocsWithFieldSet docsWithFieldSet = getDocIdSetIterator(floatArray.size()); + + final Map vectorsMap = Map.of(0, floatArray.get(0), 1, floatArray.get(1)); + final KNNVectorValues knnVectorValuesForFieldWriter = KNNVectorValuesFactory.getVectorValues( + VectorDataType.FLOAT, + docsWithFieldSet, + vectorsMap + ); + new CompareVectorValues().validateVectorValues(knnVectorValuesForFieldWriter, floatArray, 8, dimension, false); + + final TestVectorValues.PredefinedFloatVectorBinaryDocValues preDefinedFloatVectorValues = + new TestVectorValues.PredefinedFloatVectorBinaryDocValues(floatArray); + final KNNVectorValues knnFloatVectorValuesBinaryDocValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.FLOAT, + preDefinedFloatVectorValues + ); + new CompareVectorValues().validateVectorValues(knnFloatVectorValuesBinaryDocValues, floatArray, 8, dimension, false); + } + + @SneakyThrows + public void testByteVectorValues_whenValidInput_thenSuccess() { + final List byteArray = List.of(new byte[] { 4, 5 }, new byte[] { 6, 7 }); + final int dimension = byteArray.get(0).length; + final TestVectorValues.PreDefinedByteVectorValues randomVectorValues = new TestVectorValues.PreDefinedByteVectorValues(byteArray); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues(VectorDataType.BYTE, randomVectorValues); + new CompareVectorValues().validateVectorValues(knnVectorValues, byteArray, 2, dimension, true); + + final DocsWithFieldSet docsWithFieldSet = getDocIdSetIterator(byteArray.size()); + final Map vectorsMap = Map.of(0, byteArray.get(0), 1, byteArray.get(1)); + final KNNVectorValues knnVectorValuesForFieldWriter = KNNVectorValuesFactory.getVectorValues( + VectorDataType.BYTE, + docsWithFieldSet, + vectorsMap + ); + new CompareVectorValues().validateVectorValues(knnVectorValuesForFieldWriter, byteArray, 2, dimension, false); + + final TestVectorValues.PredefinedByteVectorBinaryDocValues preDefinedByteVectorValues = + new TestVectorValues.PredefinedByteVectorBinaryDocValues(byteArray); + final KNNVectorValues knnBinaryVectorValuesBinaryDocValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.BYTE, + preDefinedByteVectorValues + ); + new CompareVectorValues().validateVectorValues(knnBinaryVectorValuesBinaryDocValues, byteArray, 2, dimension, false); + } + + @SneakyThrows + public void testBinaryVectorValues_whenValidInput_thenSuccess() { + final List byteArray = List.of(new byte[] { 1, 5, 8 }, new byte[] { 6, 7, 9 }); + int dimension = byteArray.get(0).length * 8; + final TestVectorValues.PreDefinedBinaryVectorValues randomVectorValues = new TestVectorValues.PreDefinedBinaryVectorValues( + byteArray + ); + final KNNVectorValues knnVectorValues = KNNVectorValuesFactory.getVectorValues(VectorDataType.BINARY, randomVectorValues); + new CompareVectorValues().validateVectorValues(knnVectorValues, byteArray, 3, dimension, true); + + final DocsWithFieldSet docsWithFieldSet = getDocIdSetIterator(byteArray.size()); + final Map vectorsMap = Map.of(0, byteArray.get(0), 1, byteArray.get(1)); + final KNNBinaryVectorValues knnVectorValuesForFieldWriter = (KNNBinaryVectorValues) KNNVectorValuesFactory.getVectorValues( + VectorDataType.BINARY, + docsWithFieldSet, + vectorsMap + ); + new CompareVectorValues().validateVectorValues(knnVectorValuesForFieldWriter, byteArray, 3, dimension, false); + + final TestVectorValues.PredefinedByteVectorBinaryDocValues preDefinedByteVectorValues = + new TestVectorValues.PredefinedByteVectorBinaryDocValues(byteArray); + final KNNVectorValues knnBinaryVectorValuesBinaryDocValues = KNNVectorValuesFactory.getVectorValues( + VectorDataType.BINARY, + preDefinedByteVectorValues + ); + new CompareVectorValues().validateVectorValues(knnBinaryVectorValuesBinaryDocValues, byteArray, 3, dimension, false); + } + + public void testDocIdsIteratorValues_whenInvalidDisi_thenThrowException() { + Assert.assertThrows( + IllegalArgumentException.class, + () -> new KNNVectorValuesIterator.DocIdsIteratorValues(new TestVectorValues.NotBinaryDocValues()) + ); + } + + private DocsWithFieldSet getDocIdSetIterator(int numberOfDocIds) { + final DocsWithFieldSet docsWithFieldSet = new DocsWithFieldSet(); + for (int i = 0; i < numberOfDocIds; i++) { + docsWithFieldSet.add(i); + } + return docsWithFieldSet; + } + + private class CompareVectorValues { + void validateVectorValues( + KNNVectorValues vectorValues, + List vectors, + int bytesPerVector, + int dimension, + boolean validateAddress + ) throws IOException { + assertEquals(vectorValues.totalLiveDocs(), vectors.size()); + int docId, i = 0; + T oldActual = null; + int oldDocId = -1; + final KNNVectorValuesIterator iterator = vectorValues.vectorValuesIterator; + for (docId = iterator.nextDoc(); docId != DocIdSetIterator.NO_MORE_DOCS && i < vectors.size(); docId = iterator.nextDoc()) { + T actual = vectorValues.getVector(); + T clone = vectorValues.conditionalCloneVector(); + T expected = vectors.get(i); + assertNotEquals(oldDocId, docId); + assertEquals(dimension, vectorValues.dimension()); + // this will check if reference is correct for the vectors. This is mainly required because for + // VectorValues of Lucene when reading vectors put the vector at same reference + if (oldActual != null && validateAddress) { + assertSame(actual, oldActual); + assertNotSame(clone, oldActual); + } + + oldActual = actual; + // this will do the deep equals + assertArrayEquals(new Object[] { actual }, new Object[] { expected }); + assertArrayEquals(new Object[] { clone }, new Object[] { expected }); + i++; + } + assertEquals(bytesPerVector, vectorValues.bytesPerVector); + } + } + +} diff --git a/src/test/java/org/opensearch/knn/index/vectorvalues/TestVectorValues.java b/src/test/java/org/opensearch/knn/index/vectorvalues/TestVectorValues.java new file mode 100644 index 000000000..0f15d5240 --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/vectorvalues/TestVectorValues.java @@ -0,0 +1,367 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.opensearch.knn.index.vectorvalues; + +import org.apache.lucene.codecs.DocValuesProducer; +import org.apache.lucene.index.BinaryDocValues; +import org.apache.lucene.index.ByteVectorValues; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.FloatVectorValues; +import org.apache.lucene.index.NumericDocValues; +import org.apache.lucene.index.SortedDocValues; +import org.apache.lucene.index.SortedNumericDocValues; +import org.apache.lucene.index.SortedSetDocValues; +import org.apache.lucene.search.VectorScorer; +import org.apache.lucene.util.BytesRef; +import org.opensearch.knn.index.codec.util.KNNVectorSerializer; +import org.opensearch.knn.index.codec.util.KNNVectorSerializerFactory; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import static com.carrotsearch.randomizedtesting.RandomizedTest.randomFloat; + +public class TestVectorValues { + + public static class RandomVectorBinaryDocValues extends VectorDocValues { + + public RandomVectorBinaryDocValues(int count, int dimension) { + super(count, dimension); + } + + @Override + public BytesRef binaryValue() throws IOException { + return new BytesRef(knnVectorSerializer.floatToByteArray(getRandomVector(dimension))); + } + } + + public static class ConstantVectorBinaryDocValues extends VectorDocValues { + + private final BytesRef value; + + public ConstantVectorBinaryDocValues(int count, int dimension, float value) { + super(count, dimension); + float[] array = new float[dimension]; + Arrays.fill(array, value); + this.value = new BytesRef(knnVectorSerializer.floatToByteArray(array)); + } + + @Override + public BytesRef binaryValue() throws IOException { + return value; + } + } + + public static class PredefinedFloatVectorBinaryDocValues extends VectorDocValues { + private final List vectors; + + public PredefinedFloatVectorBinaryDocValues(final List vectors) { + super(vectors.size(), vectors.get(0).length); + this.vectors = vectors; + } + + @Override + public BytesRef binaryValue() throws IOException { + return new BytesRef(knnVectorSerializer.floatToByteArray(vectors.get(docID()))); + } + } + + public static class PredefinedByteVectorBinaryDocValues extends VectorDocValues { + private final List vectors; + + public PredefinedByteVectorBinaryDocValues(final List vectors) { + super(vectors.size(), vectors.get(0).length); + this.vectors = vectors; + } + + @Override + public BytesRef binaryValue() throws IOException { + return new BytesRef(vectors.get(docID())); + } + } + + public static class RandomVectorDocValuesProducer extends DocValuesProducer { + + final RandomVectorBinaryDocValues randomBinaryDocValues; + + public RandomVectorDocValuesProducer(int count, int dimension) { + this.randomBinaryDocValues = new RandomVectorBinaryDocValues(count, dimension); + } + + @Override + public NumericDocValues getNumeric(FieldInfo field) { + return null; + } + + @Override + public BinaryDocValues getBinary(FieldInfo field) throws IOException { + return randomBinaryDocValues; + } + + @Override + public SortedDocValues getSorted(FieldInfo field) { + return null; + } + + @Override + public SortedNumericDocValues getSortedNumeric(FieldInfo field) { + return null; + } + + @Override + public SortedSetDocValues getSortedSet(FieldInfo field) { + return null; + } + + @Override + public void checkIntegrity() { + + } + + @Override + public void close() throws IOException { + + } + } + + static abstract class VectorDocValues extends BinaryDocValues { + + final int count; + final int dimension; + int current; + KNNVectorSerializer knnVectorSerializer; + + public VectorDocValues(int count, int dimension) { + this.count = count; + this.dimension = dimension; + this.current = -1; + this.knnVectorSerializer = KNNVectorSerializerFactory.getDefaultSerializer(); + } + + @Override + public boolean advanceExact(int target) throws IOException { + return false; + } + + @Override + public int docID() { + if (this.current > this.count) { + return BinaryDocValues.NO_MORE_DOCS; + } + return this.current; + } + + @Override + public int nextDoc() throws IOException { + return advance(current + 1); + } + + @Override + public int advance(int target) throws IOException { + current = target; + if (current >= count) { + current = NO_MORE_DOCS; + } + return current; + } + + @Override + public long cost() { + return count; + } + } + + public static class PreDefinedFloatVectorValues extends FloatVectorValues { + final int count; + final int dimension; + final List vectors; + int current; + float[] vector; + + public PreDefinedFloatVectorValues(final List vectors) { + super(); + this.count = vectors.size(); + if (!vectors.isEmpty()) { + this.dimension = vectors.get(0).length; + } else { + this.dimension = 0; + } + this.vectors = vectors; + this.current = -1; + vector = new float[dimension]; + } + + @Override + public int dimension() { + return dimension; + } + + @Override + public int size() { + return count; + } + + @Override + public float[] vectorValue() throws IOException { + // since in FloatVectorValues the reference to returned vector doesn't change. This code ensure that we + // are replicating the behavior so that if someone uses this RandomFloatVectorValues they get an + // experience similar to what we get in prod. + System.arraycopy(vectors.get(docID()), 0, vector, 0, dimension); + return vector; + } + + @Override + public VectorScorer scorer(float[] query) throws IOException { + throw new UnsupportedOperationException("scorer not supported with PreDefinedFloatVectorValues"); + } + + @Override + public int docID() { + if (this.current > this.count) { + return FloatVectorValues.NO_MORE_DOCS; + } + return this.current; + } + + @Override + public int nextDoc() throws IOException { + return advance(current + 1); + } + + @Override + public int advance(int target) throws IOException { + current = target; + if (current >= count) { + current = NO_MORE_DOCS; + } + return current; + } + } + + public static class PreDefinedByteVectorValues extends ByteVectorValues { + private final int count; + private final int dimension; + private final List vectors; + private int current; + private final byte[] vector; + + public PreDefinedByteVectorValues(final List vectors) { + super(); + this.count = vectors.size(); + this.dimension = vectors.get(0).length; + this.vectors = vectors; + this.current = -1; + vector = new byte[dimension]; + } + + @Override + public int dimension() { + return dimension; + } + + @Override + public int size() { + return count; + } + + @Override + public byte[] vectorValue() throws IOException { + // since in FloatVectorValues the reference to returned vector doesn't change. This code ensure that we + // are replicating the behavior so that if someone uses this RandomFloatVectorValues they get an + // experience similar to what we get in prod. + System.arraycopy(vectors.get(docID()), 0, vector, 0, dimension); + return vector; + } + + @Override + public VectorScorer scorer(byte[] query) throws IOException { + throw new UnsupportedOperationException("scorer not supported with PreDefinedFloatVectorValues"); + } + + @Override + public int docID() { + if (this.current > this.count) { + return FloatVectorValues.NO_MORE_DOCS; + } + return this.current; + } + + @Override + public int nextDoc() throws IOException { + return advance(current + 1); + } + + @Override + public int advance(int target) throws IOException { + current = target; + if (current >= count) { + current = NO_MORE_DOCS; + } + return current; + } + } + + public static class PreDefinedBinaryVectorValues extends PreDefinedByteVectorValues { + + public PreDefinedBinaryVectorValues(List vectors) { + super(vectors); + } + + @Override + public int dimension() { + return super.dimension() * Byte.SIZE; + } + } + + public static class NotBinaryDocValues extends NumericDocValues { + + @Override + public long longValue() throws IOException { + return 0; + } + + @Override + public boolean advanceExact(int target) throws IOException { + return false; + } + + @Override + public int docID() { + return 0; + } + + @Override + public int nextDoc() throws IOException { + return 0; + } + + @Override + public int advance(int target) throws IOException { + return 0; + } + + @Override + public long cost() { + return 0; + } + } + + public static float[][] getRandomVectors(int count, int dimension) { + float[][] data = new float[count][dimension]; + for (int i = 0; i < count; i++) { + data[i] = getRandomVector(dimension); + } + return data; + } + + public static float[] getRandomVector(int dimension) { + float[] data = new float[dimension]; + for (int i = 0; i < dimension; i++) { + data[i] = randomFloat(); + } + return data; + } +} diff --git a/src/test/java/org/opensearch/knn/index/vectorvalues/VectorValueExtractorStrategyTests.java b/src/test/java/org/opensearch/knn/index/vectorvalues/VectorValueExtractorStrategyTests.java new file mode 100644 index 000000000..68a49a54c --- /dev/null +++ b/src/test/java/org/opensearch/knn/index/vectorvalues/VectorValueExtractorStrategyTests.java @@ -0,0 +1,29 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.index.vectorvalues; + +import lombok.SneakyThrows; +import org.junit.Assert; +import org.mockito.Mockito; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.VectorDataType; + +/** + * To avoid unit test duplication, tests for exception is added here. For non exception cases tests are present in + * {@link KNNVectorValuesTests} + */ +public class VectorValueExtractorStrategyTests extends KNNTestCase { + + @SneakyThrows + public void testExtractWithDISI_whenInvalidIterator_thenException() { + final VectorValueExtractorStrategy disiStrategy = new VectorValueExtractorStrategy.DISIVectorExtractor(); + final KNNVectorValuesIterator vectorValuesIterator = Mockito.mock(KNNVectorValuesIterator.DocIdsIteratorValues.class); + Mockito.when(vectorValuesIterator.getDocIdSetIterator()).thenReturn(new TestVectorValues.NotBinaryDocValues()); + Assert.assertThrows(IllegalArgumentException.class, () -> disiStrategy.extract(VectorDataType.FLOAT, vectorValuesIterator)); + Assert.assertThrows(IllegalArgumentException.class, () -> disiStrategy.extract(VectorDataType.BINARY, vectorValuesIterator)); + Assert.assertThrows(IllegalArgumentException.class, () -> disiStrategy.extract(VectorDataType.BYTE, vectorValuesIterator)); + } +} diff --git a/src/test/java/org/opensearch/knn/indices/ModelCacheTests.java b/src/test/java/org/opensearch/knn/indices/ModelCacheTests.java index fb810d969..a31fe4a7d 100644 --- a/src/test/java/org/opensearch/knn/indices/ModelCacheTests.java +++ b/src/test/java/org/opensearch/knn/indices/ModelCacheTests.java @@ -13,12 +13,17 @@ import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.UncheckedExecutionException; +import org.opensearch.Version; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.Settings; import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; import java.time.ZoneOffset; import java.time.ZonedDateTime; @@ -42,7 +47,13 @@ public void testGet_normal() throws ExecutionException, InterruptedException { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.V_EMPTY ), "hello".getBytes(), modelId @@ -77,7 +88,13 @@ public void testGet_modelDoesNotFitInCache() throws ExecutionException, Interrup ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.V_EMPTY ), new byte[BYTES_PER_KILOBYTES + 1], modelId @@ -133,7 +150,13 @@ public void testGetTotalWeight() throws ExecutionException, InterruptedException ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.V_EMPTY ), new byte[size1], modelId1 @@ -147,7 +170,13 @@ public void testGetTotalWeight() throws ExecutionException, InterruptedException ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.V_EMPTY ), new byte[size2], modelId2 @@ -189,7 +218,13 @@ public void testRemove_normal() throws ExecutionException, InterruptedException ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.V_EMPTY ), new byte[size1], modelId1 @@ -203,7 +238,13 @@ public void testRemove_normal() throws ExecutionException, InterruptedException ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.V_EMPTY ), new byte[size2], modelId2 @@ -250,7 +291,13 @@ public void testRebuild_normal() throws ExecutionException, InterruptedException ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.V_EMPTY ), "hello".getBytes(), modelId @@ -294,7 +341,13 @@ public void testRebuild_afterSettingUpdate() throws ExecutionException, Interrup ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.V_EMPTY ), new byte[modelSize], modelId @@ -361,7 +414,13 @@ public void testContains() throws ExecutionException, InterruptedException { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.V_EMPTY ), new byte[modelSize1], modelId1 @@ -401,7 +460,13 @@ public void testRemoveAll() throws ExecutionException, InterruptedException { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.V_EMPTY ), new byte[modelSize1], modelId1 @@ -417,7 +482,13 @@ public void testRemoveAll() throws ExecutionException, InterruptedException { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.V_EMPTY ), new byte[modelSize2], modelId2 @@ -461,7 +532,13 @@ public void testModelCacheEvictionDueToSize() throws ExecutionException, Interru ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.V_EMPTY ), new byte[BYTES_PER_KILOBYTES * 2], modelId diff --git a/src/test/java/org/opensearch/knn/indices/ModelDaoTests.java b/src/test/java/org/opensearch/knn/indices/ModelDaoTests.java index 51f8240f3..1edb5cff2 100644 --- a/src/test/java/org/opensearch/knn/indices/ModelDaoTests.java +++ b/src/test/java/org/opensearch/knn/indices/ModelDaoTests.java @@ -12,58 +12,81 @@ package org.opensearch.knn.indices; import org.junit.AfterClass; +import org.junit.Assert; import org.junit.BeforeClass; +import org.mockito.MockedStatic; import org.opensearch.ExceptionsHelper; import org.opensearch.ResourceAlreadyExistsException; -import org.opensearch.action.ActionListener; +import org.opensearch.ResourceNotFoundException; +import org.opensearch.Version; +import org.opensearch.cluster.ClusterChangedEvent; +import org.opensearch.core.action.ActionListener; import org.opensearch.action.DocWriteResponse; +import org.opensearch.action.StepListener; import org.opensearch.action.admin.indices.create.CreateIndexResponse; -import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.delete.DeleteAction; +import org.opensearch.action.delete.DeleteRequestBuilder; +import org.opensearch.action.delete.DeleteResponse; import org.opensearch.action.index.IndexResponse; import org.opensearch.action.support.WriteRequest; -import org.opensearch.common.xcontent.XContentBuilder; -import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.action.support.master.AcknowledgedResponse; import org.opensearch.index.IndexNotFoundException; import org.opensearch.index.engine.VersionConflictEngineException; import org.opensearch.knn.KNNSingleNodeTestCase; +import org.opensearch.knn.common.exception.DeleteModelException; +import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; import org.opensearch.knn.plugin.transport.DeleteModelResponse; -import org.opensearch.rest.RestStatus; +import org.opensearch.knn.plugin.transport.GetModelResponse; +import org.opensearch.knn.plugin.transport.RemoveModelFromCacheAction; +import org.opensearch.knn.plugin.transport.RemoveModelFromCacheRequest; +import org.opensearch.knn.plugin.transport.RemoveModelFromCacheResponse; +import org.opensearch.knn.plugin.transport.UpdateModelMetadataAction; +import org.opensearch.knn.plugin.transport.UpdateModelMetadataRequest; +import org.opensearch.knn.plugin.transport.UpdateModelGraveyardAction; +import org.opensearch.knn.plugin.transport.UpdateModelGraveyardRequest; +import org.opensearch.knn.training.TrainingJobClusterStateListener; import java.io.IOException; import java.time.ZoneOffset; import java.time.ZonedDateTime; -import java.util.Base64; +import java.util.Collections; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import static org.opensearch.knn.common.KNNConstants.DIMENSION; -import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; -import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; -import static org.opensearch.knn.common.KNNConstants.MODEL_BLOB_PARAMETER; -import static org.opensearch.knn.common.KNNConstants.MODEL_DESCRIPTION; -import static org.opensearch.knn.common.KNNConstants.MODEL_ERROR; -import static org.opensearch.knn.common.KNNConstants.MODEL_ID; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; import static org.opensearch.knn.common.KNNConstants.MODEL_INDEX_NAME; -import static org.opensearch.knn.common.KNNConstants.MODEL_STATE; -import static org.opensearch.knn.common.KNNConstants.MODEL_TIMESTAMP; public class ModelDaoTests extends KNNSingleNodeTestCase { private static ExecutorService modelGetterExecutor; + private static final String FAILED = "failed"; + private static MockedStatic trainingJobClusterStateListenerMockedStatic; @BeforeClass public static void setup() { modelGetterExecutor = Executors.newSingleThreadExecutor(); + trainingJobClusterStateListenerMockedStatic = mockStatic(TrainingJobClusterStateListener.class); + final TrainingJobClusterStateListener trainingJobClusterStateListener = mock(TrainingJobClusterStateListener.class); + doNothing().when(trainingJobClusterStateListener).clusterChanged(any(ClusterChangedEvent.class)); + trainingJobClusterStateListenerMockedStatic.when(TrainingJobClusterStateListener::getInstance) + .thenReturn(trainingJobClusterStateListener); } @AfterClass public static void teardown() { modelGetterExecutor.shutdown(); + trainingJobClusterStateListenerMockedStatic.close(); } public void testCreate() throws IOException, InterruptedException { @@ -118,12 +141,18 @@ public void testModelIndexHealth() throws InterruptedException, ExecutionExcepti ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ), modelBlob, modelId ); - addDoc(model); + writeModelToModelSystemIndex(model); assertEquals(model, modelDao.get(modelId)); assertNotNull(modelDao.getHealthStatus()); @@ -136,12 +165,18 @@ public void testModelIndexHealth() throws InterruptedException, ExecutionExcepti ModelState.FAILED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ), modelBlob, modelId ); - addDoc(model); + writeModelToModelSystemIndex(model); assertEquals(model, modelDao.get(modelId)); assertNotNull(modelDao.getHealthStatus()); } @@ -162,7 +197,13 @@ public void testPut_withId() throws InterruptedException, IOException { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + new MethodComponentContext("test", Collections.emptyMap()), + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ), modelBlob, modelId @@ -221,7 +262,13 @@ public void testPut_withoutModel() throws InterruptedException, IOException { ModelState.TRAINING, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ), modelBlob, modelId @@ -281,7 +328,13 @@ public void testPut_invalid_badState() { ModelState.TRAINING, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ), modelBlob, "any-id" @@ -316,7 +369,13 @@ public void testUpdate() throws IOException, InterruptedException { ModelState.TRAINING, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ), null, modelId @@ -353,7 +412,13 @@ public void testUpdate() throws IOException, InterruptedException { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ), modelBlob, modelId @@ -402,12 +467,18 @@ public void testGet() throws IOException, InterruptedException, ExecutionExcepti ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ), modelBlob, modelId ); - addDoc(model); + writeModelToModelSystemIndex(model); assertEquals(model, modelDao.get(modelId)); // Get model during training @@ -419,12 +490,18 @@ public void testGet() throws IOException, InterruptedException, ExecutionExcepti ModelState.TRAINING, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ), null, modelId ); - addDoc(model); + writeModelToModelSystemIndex(model); assertEquals(model, modelDao.get(modelId)); } @@ -454,7 +531,13 @@ public void testGetMetadata() throws IOException, InterruptedException { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ); Model model = new Model(modelMetadata, modelBlob, modelId); @@ -463,7 +546,6 @@ public void testGetMetadata() throws IOException, InterruptedException { final CountDownLatch inProgressLatch1 = new CountDownLatch(1); ActionListener docCreationListener = ActionListener.wrap(response -> { assertEquals(modelId, response.getId()); - ModelMetadata modelMetadata1 = modelDao.getMetadata(modelId); assertEquals(modelMetadata, modelMetadata1); @@ -479,39 +561,88 @@ public void testGetMetadata() throws IOException, InterruptedException { public void testDelete() throws IOException, InterruptedException { ModelDao modelDao = ModelDao.OpenSearchKNNModelDao.getInstance(); String modelId = "testDeleteModelID"; + String modelId1 = "testDeleteModelID1"; byte[] modelBlob = "hello".getBytes(); int dimension = 2; final CountDownLatch inProgressLatch = new CountDownLatch(1); - ActionListener deleteModelIndexDoesNotExistListener = ActionListener.wrap(response -> { - assertEquals("failed", response.getResult()); - inProgressLatch.countDown(); - }, exception -> fail("Unable to delete the model: " + exception)); + ActionListener deleteModelIndexDoesNotExistListener = ActionListener.wrap( + response -> fail("Deleting model when model index does not exist should throw ResourceNotFoundException"), + exception -> { + assertTrue(exception instanceof ResourceNotFoundException); + inProgressLatch.countDown(); + } + ); // model index doesnt exist modelDao.delete(modelId, deleteModelIndexDoesNotExistListener); assertTrue(inProgressLatch.await(100, TimeUnit.SECONDS)); createIndex(MODEL_INDEX_NAME); + // Model does not exist final CountDownLatch inProgressLatch1 = new CountDownLatch(1); - ActionListener deleteModelDoesNotExistListener = ActionListener.wrap(response -> { - assertEquals(DocWriteResponse.Result.NOT_FOUND.getLowercase(), response.getResult()); - inProgressLatch1.countDown(); - }, exception -> fail("Unable to delete the model: " + exception)); + ActionListener deleteModelDoesNotExistListener = ActionListener.wrap( + response -> fail("Deleting model when model does not exist should throw ResourceNotFoundException"), + exception -> { + assertTrue(exception instanceof ResourceNotFoundException); + assertFalse(modelDao.isModelInGraveyard(modelId)); + inProgressLatch1.countDown(); + } + ); modelDao.delete(modelId, deleteModelDoesNotExistListener); - assertTrue(inProgressLatch1.await(100, TimeUnit.SECONDS)); + assertTrue(inProgressLatch1.await(60, TimeUnit.SECONDS)); final CountDownLatch inProgressLatch2 = new CountDownLatch(1); + ActionListener deleteModelTrainingListener = ActionListener.wrap( + response -> fail("Deleting model when model does not exist should throw ResourceNotFoundException"), + exception -> { + assertTrue(exception instanceof DeleteModelException); + assertFalse(modelDao.isModelInGraveyard(modelId)); + inProgressLatch2.countDown(); + } + ); + + // model id exists and model is still in Training + Model model = new Model( + new ModelMetadata( + KNNEngine.DEFAULT, + SpaceType.DEFAULT, + dimension, + ModelState.TRAINING, + ZonedDateTime.now(ZoneOffset.UTC).toString(), + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ), + modelBlob, + modelId + ); + + ActionListener docCreationListener = ActionListener.wrap(response -> { + assertEquals(modelId, response.getId()); + modelDao.delete(modelId, deleteModelTrainingListener); + }, exception -> fail("Unable to put the model: " + exception)); + + modelDao.put(model, docCreationListener); + + assertTrue(inProgressLatch2.await(100, TimeUnit.SECONDS)); + + final CountDownLatch inProgressLatch3 = new CountDownLatch(1); ActionListener deleteModelExistsListener = ActionListener.wrap(response -> { - assertEquals(modelId, response.getModelID()); + assertEquals(modelId1, response.getModelID()); assertEquals(DocWriteResponse.Result.DELETED.getLowercase(), response.getResult()); assertNull(response.getErrorMessage()); - inProgressLatch2.countDown(); + inProgressLatch3.countDown(); }, exception -> fail("Unable to delete model: " + exception)); // model id exists - Model model = new Model( + Model model1 = new Model( new ModelMetadata( KNNEngine.DEFAULT, SpaceType.DEFAULT, @@ -519,49 +650,310 @@ public void testDelete() throws IOException, InterruptedException { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ), modelBlob, - modelId + modelId1 ); - ActionListener docCreationListener = ActionListener.wrap(response -> { - assertEquals(modelId, response.getId()); - modelDao.delete(modelId, deleteModelExistsListener); + ActionListener docCreationListener1 = ActionListener.wrap(response -> { + assertEquals(modelId1, response.getId()); + modelDao.delete(modelId1, deleteModelExistsListener); }, exception -> fail("Unable to put the model: " + exception)); // We use put so that we can confirm cluster metadata gets added - modelDao.put(model, docCreationListener); + modelDao.put(model1, docCreationListener1); - assertTrue(inProgressLatch2.await(100, TimeUnit.SECONDS)); + assertTrue(inProgressLatch3.await(100, TimeUnit.SECONDS)); } - public void addDoc(Model model) throws IOException, ExecutionException, InterruptedException { - ModelMetadata modelMetadata = model.getModelMetadata(); - - XContentBuilder builder = XContentFactory.jsonBuilder() - .startObject() - .field(MODEL_ID, model.getModelID()) - .field(KNN_ENGINE, modelMetadata.getKnnEngine().getName()) - .field(METHOD_PARAMETER_SPACE_TYPE, modelMetadata.getSpaceType().getValue()) - .field(DIMENSION, modelMetadata.getDimension()) - .field(MODEL_STATE, modelMetadata.getState().getName()) - .field(MODEL_TIMESTAMP, modelMetadata.getTimestamp().toString()) - .field(MODEL_DESCRIPTION, modelMetadata.getDescription()) - .field(MODEL_ERROR, modelMetadata.getError()); - - if (model.getModelBlob() != null) { - builder.field(MODEL_BLOB_PARAMETER, Base64.getEncoder().encodeToString(model.getModelBlob())); - } + // Test Delete Model when modelId is in Model Graveyard (previous delete model request which failed to + // remove modelId from model graveyard). But, the model does not exist + public void testDeleteModelWithModelInGraveyardModelDoesNotExist() throws InterruptedException { + ModelDao modelDao = ModelDao.OpenSearchKNNModelDao.getInstance(); + String modelId = "test-model-in-graveyard"; + createIndex(MODEL_INDEX_NAME); + + // Model does not exist + final CountDownLatch inProgressLatch = new CountDownLatch(1); + StepListener blockModelIdStep = new StepListener<>(); + ActionListener deleteModelDoesNotExistListener1 = ActionListener.wrap(Assert::assertNull, exception -> { + assertNotNull(exception); + assertTrue(exception.getMessage().contains(modelId)); + assertTrue(exception.getMessage().contains("Model does not exist")); + // Assert that modelId is removed from graveyard even when the model does not exist + assertFalse(modelDao.isModelInGraveyard(modelId)); + inProgressLatch.countDown(); + }); + + // Adding the modelId to model graveyard + client().execute( + UpdateModelGraveyardAction.INSTANCE, + new UpdateModelGraveyardRequest(modelId, false), + ActionListener.wrap(blockModelIdStep::onResponse, blockModelIdStep::onFailure) + ); + + blockModelIdStep.whenComplete(acknowledgedResponse -> { + // Assert that model is in graveyard + assertTrue(modelDao.isModelInGraveyard(modelId)); + modelDao.delete(modelId, deleteModelDoesNotExistListener1); + }, exception -> fail(exception.getMessage())); + assertTrue(inProgressLatch.await(60, TimeUnit.SECONDS)); + } + + public void testDeleteModelInTrainingWithStepListeners() throws IOException, ExecutionException, InterruptedException { + String modelId = "test-model-id-training"; + ModelDao modelDao = ModelDao.OpenSearchKNNModelDao.getInstance(); + byte[] modelBlob = "deleteModel".getBytes(); + int dimension = 2; + createIndex(MODEL_INDEX_NAME); + + Model model = new Model( + new ModelMetadata( + KNNEngine.DEFAULT, + SpaceType.DEFAULT, + dimension, + ModelState.TRAINING, + ZonedDateTime.now(ZoneOffset.UTC).toString(), + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ), + modelBlob, + modelId + ); + + // created model and added it to index + writeModelToModelSystemIndex(model); + + final CountDownLatch inProgressLatch = new CountDownLatch(1); + + StepListener getModelStep = new StepListener<>(); + + modelDao.get(modelId, ActionListener.wrap(getModelStep::onResponse, getModelStep::onFailure)); + + // Asserting that model is in TRAINING state + getModelStep.whenComplete(getModelResponse -> { + assertEquals(model.getModelMetadata().getState(), getModelResponse.getModel().getModelMetadata().getState()); + assertEquals(ModelState.TRAINING, getModelResponse.getModel().getModelMetadata().getState()); + + inProgressLatch.countDown(); + }, exception -> fail(exception.getMessage())); + assertTrue(inProgressLatch.await(100, TimeUnit.SECONDS)); + } + + public void testDeleteWithStepListeners() throws IOException, InterruptedException, ExecutionException { + String modelId = "test-model-id-delete"; + ModelDao modelDao = ModelDao.OpenSearchKNNModelDao.getInstance(); + byte[] modelBlob = "deleteModel".getBytes(); + int dimension = 2; + createIndex(MODEL_INDEX_NAME); + + Model model = new Model( + new ModelMetadata( + KNNEngine.DEFAULT, + SpaceType.DEFAULT, + dimension, + ModelState.CREATED, + ZonedDateTime.now(ZoneOffset.UTC).toString(), + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ), + modelBlob, + modelId + ); + + // created model and added it to index + writeModelToModelSystemIndex(model); + + final CountDownLatch inProgressLatch = new CountDownLatch(1); + + StepListener getModelStep = new StepListener<>(); + StepListener blockModelIdStep = new StepListener<>(); + StepListener clearModelMetadataStep = new StepListener<>(); + StepListener deleteModelFromIndexStep = new StepListener<>(); + StepListener clearModelFromCacheStep = new StepListener<>(); + StepListener unblockModelIdStep = new StepListener<>(); + + modelDao.get(modelId, ActionListener.wrap(getModelStep::onResponse, getModelStep::onFailure)); + + // Asserting that model is in CREATED state + getModelStep.whenComplete(getModelResponse -> { + assertEquals(model.getModelMetadata().getState(), getModelResponse.getModel().getModelMetadata().getState()); + assertNotEquals(ModelState.TRAINING.getName(), getModelResponse.getModel().getModelMetadata().getState().toString()); + + client().execute( + UpdateModelGraveyardAction.INSTANCE, + new UpdateModelGraveyardRequest(modelId, false), + ActionListener.wrap(blockModelIdStep::onResponse, blockModelIdStep::onFailure) + ); + }, exception -> fail(exception.getMessage())); + + blockModelIdStep.whenComplete(acknowledgedResponse -> { + // Asserting that modelId is in blocked list + assertTrue(modelDao.isModelInGraveyard(modelId)); + + client().execute( + UpdateModelMetadataAction.INSTANCE, + new UpdateModelMetadataRequest(modelId, true, null), + ActionListener.wrap(clearModelMetadataStep::onResponse, clearModelMetadataStep::onFailure) + ); + + }, exception -> fail(exception.getMessage())); + + DeleteRequestBuilder deleteRequestBuilder = new DeleteRequestBuilder(client(), DeleteAction.INSTANCE, MODEL_INDEX_NAME); + deleteRequestBuilder.setId(modelId); + deleteRequestBuilder.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + + clearModelMetadataStep.whenComplete(acknowledgedResponse -> { + // Asserting that metadata is cleared + assertNull(modelDao.getMetadata(modelId)); + + deleteRequestBuilder.execute(ActionListener.wrap(deleteModelFromIndexStep::onResponse, deleteModelFromIndexStep::onFailure)); + + }, exception -> fail(exception.getMessage())); + + deleteModelFromIndexStep.whenComplete(deleteResponse -> { + // Asserting that model is deleted from index + assertEquals(DocWriteResponse.Result.DELETED, deleteResponse.getResult()); + client().execute( + RemoveModelFromCacheAction.INSTANCE, + new RemoveModelFromCacheRequest(modelId), + ActionListener.wrap(clearModelFromCacheStep::onResponse, clearModelFromCacheStep::onFailure) + ); + + }, exception -> fail(exception.getMessage())); + + clearModelFromCacheStep.whenComplete(removeModelFromCacheResponse -> { + assertFalse(removeModelFromCacheResponse.hasFailures()); + + client().execute( + UpdateModelGraveyardAction.INSTANCE, + new UpdateModelGraveyardRequest(modelId, true), + ActionListener.wrap(unblockModelIdStep::onResponse, unblockModelIdStep::onFailure) + ); + + unblockModelIdStep.whenComplete(acknowledgedResponse -> { + // Asserting that model is unblocked + assertFalse(modelDao.isModelInGraveyard(modelId)); + inProgressLatch.countDown(); + }, exception -> fail(exception.getMessage())); + }, exception -> fail(exception.getMessage())); + + assertTrue(inProgressLatch.await(100, TimeUnit.SECONDS)); + } + + // Some exception occurs during the process of deletion and validate that the model is unblocked + public void testDeleteWithStepListenersOnFailureModelUnblocked() throws InterruptedException { + String modelId = "test-model-id-delete1"; + ModelDao modelDao = ModelDao.OpenSearchKNNModelDao.getInstance(); + + // We will validate if the modelId gets unblocked when some exception occurs + // during the process of deletion after adding that modelId to blocked list + final CountDownLatch inProgressLatch = new CountDownLatch(1); - builder.endObject(); + StepListener blockModelIdStep = new StepListener<>(); + StepListener clearModelMetadataStep = new StepListener<>(); - IndexRequest indexRequest = new IndexRequest().index(MODEL_INDEX_NAME) - .id(model.getModelID()) - .source(builder) - .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + // Add modelId to blocked list + client().execute( + UpdateModelGraveyardAction.INSTANCE, + new UpdateModelGraveyardRequest(modelId, false), + ActionListener.wrap(blockModelIdStep::onResponse, blockModelIdStep::onFailure) + ); + + // Asserting that the modelId is blocked + blockModelIdStep.whenComplete(acknowledgedResponse -> { + assertTrue(modelDao.isModelInGraveyard(modelId)); + + // Sending empty string for modelId to fail the clear model metadata request + client().execute( + UpdateModelMetadataAction.INSTANCE, + new UpdateModelMetadataRequest("", true, null), + ActionListener.wrap(clearModelMetadataStep::onResponse, exp -> { + // Asserting that modelId is still blocked and clearModelMetadata throws an exception + assertNotNull(exp.getMessage()); + assertTrue(modelDao.isModelInGraveyard(modelId)); + client().execute( + // OnFailure sending request to unblock modelId + UpdateModelGraveyardAction.INSTANCE, + new UpdateModelGraveyardRequest(modelId, true), + ActionListener.wrap(ackResponse -> { + // Asserting that model is unblocked + assertFalse(modelDao.isModelInGraveyard(modelId)); + assertNotNull(exp.getMessage()); + inProgressLatch.countDown(); + }, exception -> fail(exception.getMessage())) + ); + }) + ); + }, exception -> fail(exception.getMessage())); + + assertTrue(inProgressLatch.await(100, TimeUnit.SECONDS)); + } - IndexResponse response = client().index(indexRequest).get(); - assertTrue(response.status() == RestStatus.CREATED || response.status() == RestStatus.OK); + // Some exception occurs during the process of deletion and unblocking model request also fails + public void testDeleteWithStepListenersOnFailureModelBlocked() throws InterruptedException { + String modelId = "test-model-id-delete2"; + ModelDao modelDao = ModelDao.OpenSearchKNNModelDao.getInstance(); + + final CountDownLatch inProgressLatch1 = new CountDownLatch(1); + + StepListener blockModelIdStep1 = new StepListener<>(); + StepListener clearModelMetadataStep1 = new StepListener<>(); + + // Add modelId to blocked list + client().execute( + UpdateModelGraveyardAction.INSTANCE, + new UpdateModelGraveyardRequest(modelId, false), + ActionListener.wrap(blockModelIdStep1::onResponse, blockModelIdStep1::onFailure) + ); + + // Asserting that the modelId is blocked + blockModelIdStep1.whenComplete(acknowledgedResponse -> { + assertTrue(modelDao.isModelInGraveyard(modelId)); + + // Sending empty string for modelId to fail the clear model metadata request + client().execute( + UpdateModelMetadataAction.INSTANCE, + new UpdateModelMetadataRequest("", true, null), + ActionListener.wrap(clearModelMetadataStep1::onResponse, exp -> { + assertNotNull(exp.getMessage()); + assertTrue(modelDao.isModelInGraveyard(modelId)); + + // Failing unblock modelId request by sending modelId as an empty string + client().execute( + UpdateModelGraveyardAction.INSTANCE, + new UpdateModelGraveyardRequest("", true), + ActionListener.wrap(ackResponse -> {}, unblockingFailedException -> { + // Asserting that model is still blocked and returns both exceptions in response + assertTrue(modelDao.isModelInGraveyard(modelId)); + assertNotNull(exp.getMessage()); + assertNotNull(unblockingFailedException.getMessage()); + inProgressLatch1.countDown(); + }) + ); + }) + ); + }, exception -> fail(exception.getMessage())); + + assertTrue(inProgressLatch1.await(100, TimeUnit.SECONDS)); } } diff --git a/src/test/java/org/opensearch/knn/indices/ModelGraveyardTests.java b/src/test/java/org/opensearch/knn/indices/ModelGraveyardTests.java new file mode 100644 index 000000000..aeb7f6f10 --- /dev/null +++ b/src/test/java/org/opensearch/knn/indices/ModelGraveyardTests.java @@ -0,0 +1,248 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.indices; + +import lombok.SneakyThrows; +import org.opensearch.OpenSearchParseException; +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.test.OpenSearchTestCase; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +public class ModelGraveyardTests extends OpenSearchTestCase { + + public void testAdd() { + ModelGraveyard testModelGraveyard = new ModelGraveyard(); + String testModelId = "test-model-id"; + testModelGraveyard.add(testModelId); + assertTrue(testModelGraveyard.contains(testModelId)); + } + + public void testRemove() { + Set modelIds = new HashSet<>(); + String testModelId = "test-model-id"; + modelIds.add(testModelId); + ModelGraveyard testModelGraveyard = new ModelGraveyard(modelIds); + + assertTrue(testModelGraveyard.contains(testModelId)); + testModelGraveyard.remove(testModelId); + assertFalse(testModelGraveyard.contains(testModelId)); + } + + public void testContains() { + Set modelIds = new HashSet<>(); + String testModelId = "test-model-id"; + modelIds.add(testModelId); + + ModelGraveyard testModelGraveyard = new ModelGraveyard(modelIds); + assertTrue(testModelGraveyard.contains(testModelId)); + } + + public void testStreams() throws IOException { + Set modelIds = new HashSet<>(); + String testModelId = "test-model-id"; + modelIds.add(testModelId); + ModelGraveyard testModelGraveyard = new ModelGraveyard(modelIds); + + BytesStreamOutput streamOutput = new BytesStreamOutput(); + testModelGraveyard.writeTo(streamOutput); + + ModelGraveyard testModelGraveyardCopy = new ModelGraveyard(streamOutput.bytes().streamInput()); + + assertEquals(testModelGraveyard.size(), testModelGraveyardCopy.size()); + assertTrue(testModelGraveyard.contains(testModelId)); + assertTrue(testModelGraveyardCopy.contains(testModelId)); + } + + // Validating {model_ids: ["test-model-id1", "test-model-id2"]} + @SneakyThrows + public void testXContentBuilder_withModelIds_returnsModelGraveyardWithModelIds() { + Set modelIds = new HashSet<>(); + String testModelId1 = "test-model-id1"; + String testModelId2 = "test-model-id2"; + modelIds.add(testModelId1); + modelIds.add(testModelId2); + ModelGraveyard testModelGraveyard = new ModelGraveyard(modelIds); + + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder(); + xContentBuilder.startObject(); + XContentBuilder builder = testModelGraveyard.toXContent(xContentBuilder, ToXContent.EMPTY_PARAMS); + builder.endObject(); + + ModelGraveyard testModelGraveyard2 = ModelGraveyard.fromXContent(createParser(builder)); + assertEquals(2, testModelGraveyard2.size()); + assertTrue(testModelGraveyard2.contains(testModelId1)); + assertTrue(testModelGraveyard2.contains(testModelId2)); + } + + // Validating {model_ids:[]} + @SneakyThrows + public void testXContentBuilder_withoutModelIds_returnsModelGraveyardWithoutModelIds() { + ModelGraveyard testModelGraveyard = new ModelGraveyard(); + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder(); + xContentBuilder.startObject(); + XContentBuilder builder = testModelGraveyard.toXContent(xContentBuilder, ToXContent.EMPTY_PARAMS); + builder.endObject(); + + ModelGraveyard testModelGraveyard2 = ModelGraveyard.fromXContent(createParser(builder)); + assertEquals(0, testModelGraveyard2.size()); + } + + // Validating {test-model:"abcd"} + @SneakyThrows + public void testXContentBuilder_withWrongFieldName_throwsException() { + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder(); + xContentBuilder.startObject(); + xContentBuilder.field("test-model"); + xContentBuilder.value("abcd"); + xContentBuilder.endObject(); + + OpenSearchParseException ex = expectThrows( + OpenSearchParseException.class, + () -> ModelGraveyard.fromXContent(createParser(xContentBuilder)) + ); + assertTrue(ex.getMessage().contains("Expecting field model_ids but got test-model")); + } + + // Validating {} + @SneakyThrows + public void testXContentBuilder_validateBackwardCompatibility_returnsEmptyModelGraveyardObject() { + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder(); + xContentBuilder.startObject(); + xContentBuilder.endObject(); + + ModelGraveyard testModelGraveyard = ModelGraveyard.fromXContent(createParser(xContentBuilder)); + assertEquals(0, testModelGraveyard.size()); + } + + // Validating null + @SneakyThrows + public void testXContentBuilder_withNull_throwsExceptionExpectingStartObject() { + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder(); + + OpenSearchParseException ex = expectThrows( + OpenSearchParseException.class, + () -> ModelGraveyard.fromXContent(createParser(xContentBuilder)) + ); + assertTrue(ex.getMessage().contains("Expecting token start of an object but got null")); + } + + // Validating {model_ids:"abcd"} + @SneakyThrows + public void testXContentBuilder_withMissingStartArray_throwsExceptionExpectingStartArray() { + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder(); + xContentBuilder.startObject(); + xContentBuilder.field("model_ids"); + xContentBuilder.value("abcd"); + xContentBuilder.endObject(); + + OpenSearchParseException ex = expectThrows( + OpenSearchParseException.class, + () -> ModelGraveyard.fromXContent(createParser(xContentBuilder)) + ); + assertTrue(ex.getMessage().contains("Expecting token start of an array but got VALUE_STRING")); + } + + // Validating {model_ids:["abcd"],model_ids_2:[]} + @SneakyThrows + public void testXContentBuilder_validateEndObject_throwsExceptionGotFieldName() { + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder(); + xContentBuilder.startObject(); + xContentBuilder.startArray("model_ids"); + xContentBuilder.value("abcd"); + xContentBuilder.endArray(); + xContentBuilder.startArray("model_ids_2"); + xContentBuilder.endArray(); + xContentBuilder.endObject(); + + OpenSearchParseException ex = expectThrows( + OpenSearchParseException.class, + () -> ModelGraveyard.fromXContent(createParser(xContentBuilder)) + ); + assertTrue(ex.getMessage().contains("Expecting token end of an object but got FIELD_NAME")); + } + + public void testDiffStreams() throws IOException { + Set added = new HashSet<>(); + Set removed = new HashSet<>(); + String testModelId = "test-model-id"; + String testModelId1 = "test-model-id-1"; + added.add(testModelId); + removed.add(testModelId1); + + ModelGraveyard modelGraveyardCurrent = new ModelGraveyard(added); + ModelGraveyard modelGraveyardPrevious = new ModelGraveyard(removed); + + ModelGraveyard.ModelGraveyardDiff modelGraveyardDiff = new ModelGraveyard.ModelGraveyardDiff( + modelGraveyardPrevious, + modelGraveyardCurrent + ); + assertEquals(added, modelGraveyardDiff.getAdded()); + assertEquals(removed, modelGraveyardDiff.getRemoved()); + + BytesStreamOutput streamOutput = new BytesStreamOutput(); + modelGraveyardDiff.writeTo(streamOutput); + + ModelGraveyard.ModelGraveyardDiff modelGraveyardDiffCopy = new ModelGraveyard.ModelGraveyardDiff( + streamOutput.bytes().streamInput() + ); + assertEquals(added, modelGraveyardDiffCopy.getAdded()); + assertEquals(removed, modelGraveyardDiffCopy.getRemoved()); + } + + public void testDiff() { + + // nothing will have been removed in previous object, and all entries in current object are new + ModelGraveyard modelGraveyard1 = new ModelGraveyard(); + + Set modelIds = new HashSet<>(); + modelIds.add("1"); + modelIds.add("2"); + ModelGraveyard modelGraveyard2 = new ModelGraveyard(modelIds); + + ModelGraveyard.ModelGraveyardDiff diff1 = new ModelGraveyard.ModelGraveyardDiff(modelGraveyard1, modelGraveyard2); + assertEquals(0, diff1.getRemoved().size()); + assertEquals(2, diff1.getAdded().size()); + + ModelGraveyard updatedGraveyard1 = diff1.apply(modelGraveyard1); + assertEquals(2, updatedGraveyard1.size()); + assertTrue(updatedGraveyard1.contains("1")); + assertTrue(updatedGraveyard1.contains("2")); + + // nothing will have been added to current object, and all entries in previous object are removed + ModelGraveyard modelGraveyard3 = new ModelGraveyard(); + ModelGraveyard.ModelGraveyardDiff diff2 = new ModelGraveyard.ModelGraveyardDiff(modelGraveyard2, modelGraveyard3); + assertEquals(2, diff2.getRemoved().size()); + assertEquals(0, diff2.getAdded().size()); + + ModelGraveyard updatedGraveyard2 = diff2.apply(modelGraveyard2); + assertEquals(0, updatedGraveyard2.size()); + + // some entries in previous object are removed and few entries are added to current object + modelIds = new HashSet<>(); + modelIds.add("1"); + modelIds.add("3"); + modelIds.add("4"); + ModelGraveyard modelGraveyard4 = new ModelGraveyard(modelIds); + + ModelGraveyard.ModelGraveyardDiff diff3 = new ModelGraveyard.ModelGraveyardDiff(modelGraveyard2, modelGraveyard4); + assertEquals(1, diff3.getRemoved().size()); + assertEquals(2, diff3.getAdded().size()); + + ModelGraveyard updatedGraveyard3 = diff3.apply(modelGraveyard2); + assertEquals(3, updatedGraveyard3.size()); + assertTrue(updatedGraveyard3.contains("1")); + assertTrue(updatedGraveyard3.contains("3")); + assertTrue(updatedGraveyard3.contains("4")); + assertFalse(updatedGraveyard3.contains("2")); + } + +} diff --git a/src/test/java/org/opensearch/knn/indices/ModelMetadataTests.java b/src/test/java/org/opensearch/knn/indices/ModelMetadataTests.java index a2e5c6bbe..79340d331 100644 --- a/src/test/java/org/opensearch/knn/indices/ModelMetadataTests.java +++ b/src/test/java/org/opensearch/knn/indices/ModelMetadataTests.java @@ -11,16 +11,25 @@ package org.opensearch.knn.indices; +import org.opensearch.Version; import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.knn.KNNTestCase; import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; import java.io.IOException; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -38,14 +47,38 @@ public void testStreams() throws IOException { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ); BytesStreamOutput streamOutput = new BytesStreamOutput(); modelMetadata.writeTo(streamOutput); - ModelMetadata modelMetadataCopy = new ModelMetadata(streamOutput.bytes().streamInput()); + assertEquals(modelMetadata, modelMetadataCopy); + modelMetadata = new ModelMetadata( + knnEngine, + spaceType, + dimension, + ModelState.CREATED, + ZonedDateTime.now(ZoneOffset.UTC).toString(), + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.ON_DISK, + CompressionLevel.x16, + Version.CURRENT + ); + streamOutput = new BytesStreamOutput(); + modelMetadata.writeTo(streamOutput); + modelMetadataCopy = new ModelMetadata(streamOutput.bytes().streamInput()); assertEquals(modelMetadata, modelMetadataCopy); } @@ -58,7 +91,13 @@ public void testGetKnnEngine() { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ); assertEquals(knnEngine, modelMetadata.getKnnEngine()); @@ -73,7 +112,13 @@ public void testGetSpaceType() { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ); assertEquals(spaceType, modelMetadata.getSpaceType()); @@ -88,7 +133,13 @@ public void testGetDimension() { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ); assertEquals(dimension, modelMetadata.getDimension()); @@ -103,7 +154,13 @@ public void testGetState() { modelState, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ); assertEquals(modelState, modelMetadata.getState()); @@ -111,7 +168,21 @@ public void testGetState() { public void testGetTimestamp() { String timeValue = ZonedDateTime.now(ZoneOffset.UTC).toString(); - ModelMetadata modelMetadata = new ModelMetadata(KNNEngine.DEFAULT, SpaceType.L2, 12, ModelState.CREATED, timeValue, "", ""); + ModelMetadata modelMetadata = new ModelMetadata( + KNNEngine.DEFAULT, + SpaceType.L2, + 12, + ModelState.CREATED, + timeValue, + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); assertEquals(timeValue, modelMetadata.getTimestamp()); } @@ -125,7 +196,13 @@ public void testDescription() { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), description, - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ); assertEquals(description, modelMetadata.getDescription()); @@ -140,12 +217,60 @@ public void testGetError() { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - error + error, + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ); assertEquals(error, modelMetadata.getError()); } + public void testGetVectorDataType() { + VectorDataType vectorDataType = VectorDataType.BINARY; + ModelMetadata modelMetadata = new ModelMetadata( + KNNEngine.DEFAULT, + SpaceType.L2, + 12, + ModelState.CREATED, + ZonedDateTime.now(ZoneOffset.UTC).toString(), + "", + "", + "", + MethodComponentContext.EMPTY, + vectorDataType, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); + + assertEquals(vectorDataType, modelMetadata.getVectorDataType()); + } + + public void testGetModelVersion() { + Version version = Version.CURRENT; + ModelMetadata modelMetadata = new ModelMetadata( + KNNEngine.DEFAULT, + SpaceType.L2, + 12, + ModelState.CREATED, + ZonedDateTime.now(ZoneOffset.UTC).toString(), + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + version + ); + + assertEquals(version, modelMetadata.getModelVersion()); + } + public void testSetState() { ModelState modelState = ModelState.FAILED; ModelMetadata modelMetadata = new ModelMetadata( @@ -155,7 +280,13 @@ public void testSetState() { modelState, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ); assertEquals(modelState, modelMetadata.getState()); @@ -174,7 +305,13 @@ public void testSetError() { ModelState.TRAINING, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - error + error, + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ); assertEquals(error, modelMetadata.getError()); @@ -192,6 +329,9 @@ public void testToString() { String timestamp = ZonedDateTime.now(ZoneOffset.UTC).toString(); String description = "test-description"; String error = "test-error"; + String nodeAssignment = ""; + MethodComponentContext methodComponentContext = MethodComponentContext.EMPTY; + Version version = Version.CURRENT; String expected = knnEngine.getName() + "," @@ -205,9 +345,35 @@ public void testToString() { + "," + description + "," - + error; + + error + + "," + + nodeAssignment + + "," + + methodComponentContext.toClusterStateString() + + "," + + VectorDataType.DEFAULT.getValue() + + "," + + Mode.ON_DISK.getName() + + "," + + CompressionLevel.x32.getName() + + "," + + version; - ModelMetadata modelMetadata = new ModelMetadata(knnEngine, spaceType, dimension, modelState, timestamp, description, error); + ModelMetadata modelMetadata = new ModelMetadata( + knnEngine, + spaceType, + dimension, + modelState, + timestamp, + description, + error, + nodeAssignment, + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.ON_DISK, + CompressionLevel.x32, + Version.CURRENT + ); assertEquals(expected, modelMetadata.toString()); } @@ -217,14 +383,112 @@ public void testEquals() { String time1 = ZonedDateTime.now(ZoneOffset.UTC).toString(); String time2 = ZonedDateTime.of(2021, 9, 30, 12, 20, 45, 1, ZoneId.systemDefault()).toString(); - ModelMetadata modelMetadata1 = new ModelMetadata(KNNEngine.FAISS, SpaceType.L2, 128, ModelState.CREATED, time1, "", ""); - ModelMetadata modelMetadata2 = new ModelMetadata(KNNEngine.FAISS, SpaceType.L2, 128, ModelState.CREATED, time1, "", ""); + ModelMetadata modelMetadata1 = new ModelMetadata( + KNNEngine.FAISS, + SpaceType.L2, + 128, + ModelState.CREATED, + time1, + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); + ModelMetadata modelMetadata2 = new ModelMetadata( + KNNEngine.FAISS, + SpaceType.L2, + 128, + ModelState.CREATED, + time1, + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); - ModelMetadata modelMetadata3 = new ModelMetadata(KNNEngine.NMSLIB, SpaceType.L2, 128, ModelState.CREATED, time1, "", ""); - ModelMetadata modelMetadata4 = new ModelMetadata(KNNEngine.FAISS, SpaceType.L1, 128, ModelState.CREATED, time1, "", ""); - ModelMetadata modelMetadata5 = new ModelMetadata(KNNEngine.FAISS, SpaceType.L2, 129, ModelState.CREATED, time1, "", ""); - ModelMetadata modelMetadata6 = new ModelMetadata(KNNEngine.FAISS, SpaceType.L2, 128, ModelState.TRAINING, time1, "", ""); - ModelMetadata modelMetadata7 = new ModelMetadata(KNNEngine.FAISS, SpaceType.L2, 128, ModelState.CREATED, time2, "", ""); + ModelMetadata modelMetadata3 = new ModelMetadata( + KNNEngine.NMSLIB, + SpaceType.L2, + 128, + ModelState.CREATED, + time1, + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); + ModelMetadata modelMetadata4 = new ModelMetadata( + KNNEngine.FAISS, + SpaceType.L1, + 128, + ModelState.CREATED, + time1, + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); + ModelMetadata modelMetadata5 = new ModelMetadata( + KNNEngine.FAISS, + SpaceType.L2, + 129, + ModelState.CREATED, + time1, + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); + ModelMetadata modelMetadata6 = new ModelMetadata( + KNNEngine.FAISS, + SpaceType.L2, + 128, + ModelState.TRAINING, + time1, + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); + ModelMetadata modelMetadata7 = new ModelMetadata( + KNNEngine.FAISS, + SpaceType.L2, + 128, + ModelState.CREATED, + time2, + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); ModelMetadata modelMetadata8 = new ModelMetadata( KNNEngine.FAISS, SpaceType.L2, @@ -232,9 +496,45 @@ public void testEquals() { ModelState.CREATED, time1, "diff descript", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); + ModelMetadata modelMetadata9 = new ModelMetadata( + KNNEngine.FAISS, + SpaceType.L2, + 128, + ModelState.CREATED, + time1, + "", + "diff error", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); + + ModelMetadata modelMetadata10 = new ModelMetadata( + KNNEngine.FAISS, + SpaceType.L2, + 128, + ModelState.CREATED, + time1, + "", + "", + "", + new MethodComponentContext("test", Collections.emptyMap()), + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ); - ModelMetadata modelMetadata9 = new ModelMetadata(KNNEngine.FAISS, SpaceType.L2, 128, ModelState.CREATED, time1, "", "diff error"); assertEquals(modelMetadata1, modelMetadata1); assertEquals(modelMetadata1, modelMetadata2); @@ -254,14 +554,112 @@ public void testHashCode() { String time1 = ZonedDateTime.now(ZoneOffset.UTC).toString(); String time2 = ZonedDateTime.of(2021, 9, 30, 12, 20, 45, 1, ZoneId.systemDefault()).toString(); - ModelMetadata modelMetadata1 = new ModelMetadata(KNNEngine.FAISS, SpaceType.L2, 128, ModelState.CREATED, time1, "", ""); - ModelMetadata modelMetadata2 = new ModelMetadata(KNNEngine.FAISS, SpaceType.L2, 128, ModelState.CREATED, time1, "", ""); + ModelMetadata modelMetadata1 = new ModelMetadata( + KNNEngine.FAISS, + SpaceType.L2, + 128, + ModelState.CREATED, + time1, + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); + ModelMetadata modelMetadata2 = new ModelMetadata( + KNNEngine.FAISS, + SpaceType.L2, + 128, + ModelState.CREATED, + time1, + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); - ModelMetadata modelMetadata3 = new ModelMetadata(KNNEngine.NMSLIB, SpaceType.L2, 128, ModelState.CREATED, time1, "", ""); - ModelMetadata modelMetadata4 = new ModelMetadata(KNNEngine.FAISS, SpaceType.L1, 128, ModelState.CREATED, time1, "", ""); - ModelMetadata modelMetadata5 = new ModelMetadata(KNNEngine.FAISS, SpaceType.L2, 129, ModelState.CREATED, time1, "", ""); - ModelMetadata modelMetadata6 = new ModelMetadata(KNNEngine.FAISS, SpaceType.L2, 128, ModelState.TRAINING, time1, "", ""); - ModelMetadata modelMetadata7 = new ModelMetadata(KNNEngine.FAISS, SpaceType.L2, 128, ModelState.CREATED, time2, "", ""); + ModelMetadata modelMetadata3 = new ModelMetadata( + KNNEngine.NMSLIB, + SpaceType.L2, + 128, + ModelState.CREATED, + time1, + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); + ModelMetadata modelMetadata4 = new ModelMetadata( + KNNEngine.FAISS, + SpaceType.L1, + 128, + ModelState.CREATED, + time1, + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); + ModelMetadata modelMetadata5 = new ModelMetadata( + KNNEngine.FAISS, + SpaceType.L2, + 129, + ModelState.CREATED, + time1, + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); + ModelMetadata modelMetadata6 = new ModelMetadata( + KNNEngine.FAISS, + SpaceType.L2, + 128, + ModelState.TRAINING, + time1, + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); + ModelMetadata modelMetadata7 = new ModelMetadata( + KNNEngine.FAISS, + SpaceType.L2, + 128, + ModelState.CREATED, + time2, + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); ModelMetadata modelMetadata8 = new ModelMetadata( KNNEngine.FAISS, SpaceType.L2, @@ -269,21 +667,57 @@ public void testHashCode() { ModelState.CREATED, time1, "diff descript", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); + ModelMetadata modelMetadata9 = new ModelMetadata( + KNNEngine.FAISS, + SpaceType.L2, + 128, + ModelState.CREATED, + time1, + "", + "diff error", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); + + ModelMetadata modelMetadata10 = new ModelMetadata( + KNNEngine.FAISS, + SpaceType.L2, + 128, + ModelState.CREATED, + time1, + "", + "", + "", + new MethodComponentContext("test", Collections.emptyMap()), + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ); - ModelMetadata modelMetadata9 = new ModelMetadata(KNNEngine.FAISS, SpaceType.L2, 128, ModelState.CREATED, time1, "", "diff error"); assertEquals(modelMetadata1.hashCode(), modelMetadata1.hashCode()); assertEquals(modelMetadata1.hashCode(), modelMetadata2.hashCode()); assertNotEquals(modelMetadata1.hashCode(), modelMetadata3.hashCode()); - assertNotEquals(modelMetadata1.hashCode(), modelMetadata3.hashCode()); assertNotEquals(modelMetadata1.hashCode(), modelMetadata4.hashCode()); assertNotEquals(modelMetadata1.hashCode(), modelMetadata5.hashCode()); assertNotEquals(modelMetadata1.hashCode(), modelMetadata6.hashCode()); assertNotEquals(modelMetadata1.hashCode(), modelMetadata7.hashCode()); assertNotEquals(modelMetadata1.hashCode(), modelMetadata8.hashCode()); assertNotEquals(modelMetadata1.hashCode(), modelMetadata9.hashCode()); + assertNotEquals(modelMetadata1.hashCode(), modelMetadata10.hashCode()); } public void testFromString() { @@ -294,6 +728,9 @@ public void testFromString() { String timestamp = ZonedDateTime.now(ZoneOffset.UTC).toString(); String description = "test-description"; String error = "test-error"; + String nodeAssignment = "test-node"; + MethodComponentContext methodComponentContext = MethodComponentContext.EMPTY; + Version version = Version.CURRENT; String stringRep1 = knnEngine.getName() + "," @@ -307,17 +744,144 @@ public void testFromString() { + "," + description + "," - + error; + + error + + "," + + nodeAssignment + + "," + + methodComponentContext.toClusterStateString() + + "," + + VectorDataType.DEFAULT.getValue() + + "," + + "," + + "," + + version.toString(); + + String stringRep2 = knnEngine.getName() + + "," + + spaceType.getValue() + + "," + + dimension + + "," + + modelState.getName() + + "," + + timestamp + + "," + + description + + "," + + error + + "," + + VectorDataType.DEFAULT.getValue(); + + String stringRep3 = knnEngine.getName() + + "," + + spaceType.getValue() + + "," + + dimension + + "," + + modelState.getName() + + "," + + timestamp + + "," + + description + + "," + + error + + "," + + nodeAssignment + + "," + + methodComponentContext.toClusterStateString() + + "," + + VectorDataType.DEFAULT.getValue() + + "," + + "," + + "," + + version; + + String stringRep4 = knnEngine.getName() + + "," + + spaceType.getValue() + + "," + + dimension + + "," + + modelState.getName() + + "," + + timestamp + + "," + + description + + "," + + error + + "," + + nodeAssignment + + "," + + methodComponentContext.toClusterStateString() + + "," + + VectorDataType.DEFAULT.getValue() + + "," + + Mode.ON_DISK.getName() + + "," + + CompressionLevel.x32.getName(); + + ModelMetadata expected1 = new ModelMetadata( + knnEngine, + spaceType, + dimension, + modelState, + timestamp, + description, + error, + nodeAssignment, + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.V_EMPTY + ); + + ModelMetadata expected2 = new ModelMetadata( + knnEngine, + spaceType, + dimension, + modelState, + timestamp, + description, + error, + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.V_EMPTY + ); + + ModelMetadata expected3 = new ModelMetadata( + knnEngine, + spaceType, + dimension, + modelState, + timestamp, + description, + error, + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.ON_DISK, + CompressionLevel.x32, + Version.CURRENT + ); - ModelMetadata expected = new ModelMetadata(knnEngine, spaceType, dimension, modelState, timestamp, description, error); ModelMetadata fromString1 = ModelMetadata.fromString(stringRep1); + ModelMetadata fromString2 = ModelMetadata.fromString(stringRep2); + ModelMetadata fromString3 = ModelMetadata.fromString(stringRep3); + ModelMetadata fromString4 = ModelMetadata.fromString(stringRep4); - assertEquals(expected, fromString1); + assertEquals(expected1, fromString1); + assertEquals(expected2, fromString2); + assertEquals(expected2, fromString3); + assertEquals(expected3, fromString4); expectThrows(IllegalArgumentException.class, () -> ModelMetadata.fromString("invalid")); } - public void testFromResponseMap() { + public void testFromResponseMap() throws IOException { KNNEngine knnEngine = KNNEngine.DEFAULT; SpaceType spaceType = SpaceType.L2; int dimension = 128; @@ -325,8 +889,74 @@ public void testFromResponseMap() { String timestamp = ZonedDateTime.now(ZoneOffset.UTC).toString(); String description = "test-description"; String error = "test-error"; + String nodeAssignment = "test-node"; + MethodComponentContext methodComponentContext = getMethodComponentContext(); + MethodComponentContext emptyMethodComponentContext = MethodComponentContext.EMPTY; + Version version = Version.CURRENT; + Version emptyVersion = Version.V_EMPTY; - ModelMetadata expected = new ModelMetadata(knnEngine, spaceType, dimension, modelState, timestamp, description, error); + ModelMetadata expected = new ModelMetadata( + knnEngine, + spaceType, + dimension, + modelState, + timestamp, + description, + error, + nodeAssignment, + methodComponentContext, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + version + ); + ModelMetadata expected2 = new ModelMetadata( + knnEngine, + spaceType, + dimension, + modelState, + timestamp, + description, + error, + "", + emptyMethodComponentContext, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + emptyVersion + ); + + ModelMetadata expected3 = new ModelMetadata( + knnEngine, + spaceType, + dimension, + modelState, + timestamp, + description, + error, + "", + emptyMethodComponentContext, + VectorDataType.DEFAULT, + Mode.ON_DISK, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); + + ModelMetadata expected4 = new ModelMetadata( + knnEngine, + spaceType, + dimension, + modelState, + timestamp, + description, + error, + "", + emptyMethodComponentContext, + VectorDataType.DEFAULT, + Mode.ON_DISK, + CompressionLevel.x16, + Version.CURRENT + ); Map metadataAsMap = new HashMap<>(); metadataAsMap.put(KNNConstants.KNN_ENGINE, knnEngine.getName()); metadataAsMap.put(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()); @@ -335,8 +965,78 @@ public void testFromResponseMap() { metadataAsMap.put(KNNConstants.MODEL_TIMESTAMP, timestamp); metadataAsMap.put(KNNConstants.MODEL_DESCRIPTION, description); metadataAsMap.put(KNNConstants.MODEL_ERROR, error); + metadataAsMap.put(KNNConstants.MODEL_NODE_ASSIGNMENT, nodeAssignment); + metadataAsMap.put(KNNConstants.VECTOR_DATA_TYPE_FIELD, VectorDataType.DEFAULT.getValue()); + + XContentBuilder builder = XContentFactory.jsonBuilder().startObject(); + builder = methodComponentContext.toXContent(builder, ToXContent.EMPTY_PARAMS).endObject(); + metadataAsMap.put(KNNConstants.MODEL_METHOD_COMPONENT_CONTEXT, builder.toString()); + metadataAsMap.put(KNNConstants.MODEL_VERSION, version.toString()); ModelMetadata fromMap = ModelMetadata.getMetadataFromSourceMap(metadataAsMap); assertEquals(expected, fromMap); + + metadataAsMap.put(KNNConstants.MODEL_NODE_ASSIGNMENT, null); + metadataAsMap.put(KNNConstants.MODEL_METHOD_COMPONENT_CONTEXT, null); + metadataAsMap.put(KNNConstants.MODEL_VERSION, emptyVersion); + assertEquals(expected2, fromMap); + + metadataAsMap.put(KNNConstants.MODE_PARAMETER, Mode.ON_DISK.getName()); + fromMap = ModelMetadata.getMetadataFromSourceMap(metadataAsMap); + assertEquals(expected3, fromMap); + + metadataAsMap.put(KNNConstants.COMPRESSION_LEVEL_PARAMETER, CompressionLevel.x16.getName()); + fromMap = ModelMetadata.getMetadataFromSourceMap(metadataAsMap); + assertEquals(expected4, fromMap); + } + + public void testBlockCommasInDescription() { + KNNEngine knnEngine = KNNEngine.DEFAULT; + SpaceType spaceType = SpaceType.L2; + int dimension = 128; + ModelState modelState = ModelState.TRAINING; + String timestamp = ZonedDateTime.now(ZoneOffset.UTC).toString(); + String description = "Test, comma, description"; + String error = "test-error"; + String nodeAssignment = "test-node"; + MethodComponentContext methodComponentContext = getMethodComponentContext(); + + Exception e = expectThrows( + IllegalArgumentException.class, + () -> new ModelMetadata( + knnEngine, + spaceType, + dimension, + modelState, + timestamp, + description, + error, + nodeAssignment, + methodComponentContext, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ) + ); + assertEquals("Model description cannot contain any commas: ','", e.getMessage()); + } + + private static MethodComponentContext getMethodComponentContext() { + Map nestedParameters = new HashMap() { + { + put("testNestedKey1", "testNestedString"); + put("testNestedKey2", 1); + } + }; + Map parameters = new HashMap<>() { + { + put("testKey1", "testString"); + put("testKey2", 0); + put("testKey3", new MethodComponentContext("ivf", nestedParameters)); + } + }; + MethodComponentContext methodComponentContext = new MethodComponentContext("hnsw", parameters); + return methodComponentContext; } } diff --git a/src/test/java/org/opensearch/knn/indices/ModelStateTests.java b/src/test/java/org/opensearch/knn/indices/ModelStateTests.java index 1cc8a0e82..1527de539 100644 --- a/src/test/java/org/opensearch/knn/indices/ModelStateTests.java +++ b/src/test/java/org/opensearch/knn/indices/ModelStateTests.java @@ -31,5 +31,8 @@ public void testStreams() throws IOException { public void testGetModelState() { assertEquals(ModelState.CREATED, ModelState.getModelState(ModelState.CREATED.getName())); + assertEquals(ModelState.TRAINING, ModelState.getModelState(ModelState.TRAINING.getName())); + assertEquals(ModelState.FAILED, ModelState.getModelState(ModelState.FAILED.getName())); + expectThrows(IllegalArgumentException.class, () -> ModelState.getModelState("throw-exception")); } } diff --git a/src/test/java/org/opensearch/knn/indices/ModelTests.java b/src/test/java/org/opensearch/knn/indices/ModelTests.java index 7a01fd528..59ecd66ec 100644 --- a/src/test/java/org/opensearch/knn/indices/ModelTests.java +++ b/src/test/java/org/opensearch/knn/indices/ModelTests.java @@ -11,18 +11,21 @@ package org.opensearch.knn.indices; +import org.opensearch.Version; import org.opensearch.knn.KNNTestCase; import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.HashMap; import java.util.Map; -import static org.opensearch.knn.index.KNNVectorFieldMapper.MAX_DIMENSION; - public class ModelTests extends KNNTestCase { public void testNullConstructor() { @@ -40,7 +43,13 @@ public void testInvalidConstructor() { ModelState.FAILED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ), null, "test-model" @@ -59,7 +68,13 @@ public void testInvalidDimension() { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ), new byte[16], "test-model" @@ -75,7 +90,13 @@ public void testInvalidDimension() { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ), new byte[16], "test-model" @@ -87,11 +108,17 @@ public void testInvalidDimension() { new ModelMetadata( KNNEngine.DEFAULT, SpaceType.DEFAULT, - MAX_DIMENSION + 1, + KNNEngine.getMaxDimensionByEngine(KNNEngine.DEFAULT) + 1, ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ), new byte[16], "test-model" @@ -108,7 +135,13 @@ public void testGetModelMetadata() { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ); Model model = new Model(modelMetadata, new byte[16], "test-model"); assertEquals(modelMetadata, model.getModelMetadata()); @@ -124,7 +157,13 @@ public void testGetModelBlob() { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ), modelBlob, "test-model" @@ -142,7 +181,13 @@ public void testGetLength() { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ), new byte[size], "test-model" @@ -157,7 +202,13 @@ public void testGetLength() { ModelState.TRAINING, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ), null, "test-model" @@ -168,7 +219,21 @@ public void testGetLength() { public void testSetModelBlob() { byte[] blob1 = "Hello blob 1".getBytes(); Model model = new Model( - new ModelMetadata(KNNEngine.DEFAULT, SpaceType.L1, 2, ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", ""), + new ModelMetadata( + KNNEngine.DEFAULT, + SpaceType.L1, + 2, + ModelState.CREATED, + ZonedDateTime.now(ZoneOffset.UTC).toString(), + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ), blob1, "test-model" ); @@ -184,22 +249,63 @@ public void testEquals() { String time = ZonedDateTime.now(ZoneOffset.UTC).toString(); Model model1 = new Model( - new ModelMetadata(KNNEngine.DEFAULT, SpaceType.L1, 2, ModelState.CREATED, time, "", ""), + new ModelMetadata( + KNNEngine.DEFAULT, + SpaceType.L1, + 2, + ModelState.CREATED, + time, + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ), new byte[16], "test-model-1" ); Model model2 = new Model( - new ModelMetadata(KNNEngine.DEFAULT, SpaceType.L1, 2, ModelState.CREATED, time, "", ""), + new ModelMetadata( + KNNEngine.DEFAULT, + SpaceType.L1, + 2, + ModelState.CREATED, + time, + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ), new byte[16], "test-model-1" ); Model model3 = new Model( - new ModelMetadata(KNNEngine.DEFAULT, SpaceType.L2, 2, ModelState.CREATED, time, "", ""), + new ModelMetadata( + KNNEngine.DEFAULT, + SpaceType.L2, + 2, + ModelState.CREATED, + time, + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ), new byte[16], "test-model-2" ); - assertEquals(model1, model1); assertEquals(model1, model2); assertNotEquals(model1, model3); } @@ -209,17 +315,59 @@ public void testHashCode() { String time = ZonedDateTime.now(ZoneOffset.UTC).toString(); Model model1 = new Model( - new ModelMetadata(KNNEngine.DEFAULT, SpaceType.L1, 2, ModelState.CREATED, time, "", ""), + new ModelMetadata( + KNNEngine.DEFAULT, + SpaceType.L1, + 2, + ModelState.CREATED, + time, + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ), new byte[16], "test-model-1" ); Model model2 = new Model( - new ModelMetadata(KNNEngine.DEFAULT, SpaceType.L1, 2, ModelState.CREATED, time, "", ""), + new ModelMetadata( + KNNEngine.DEFAULT, + SpaceType.L1, + 2, + ModelState.CREATED, + time, + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ), new byte[16], "test-model-1" ); Model model3 = new Model( - new ModelMetadata(KNNEngine.DEFAULT, SpaceType.L1, 2, ModelState.CREATED, time, "", ""), + new ModelMetadata( + KNNEngine.DEFAULT, + SpaceType.L1, + 2, + ModelState.CREATED, + time, + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ), new byte[16], "test-model-2" ); @@ -238,8 +386,23 @@ public void testModelFromSourceMap() { String timestamp = ZonedDateTime.now(ZoneOffset.UTC).toString(); String description = "test-description"; String error = "test-error"; + String nodeAssignment = "test-node"; - ModelMetadata metadata = new ModelMetadata(knnEngine, spaceType, dimension, modelState, timestamp, description, error); + ModelMetadata metadata = new ModelMetadata( + knnEngine, + spaceType, + dimension, + modelState, + timestamp, + description, + error, + nodeAssignment, + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); Map modelAsMap = new HashMap<>(); modelAsMap.put(KNNConstants.MODEL_ID, modelID); modelAsMap.put(KNNConstants.KNN_ENGINE, knnEngine.getName()); @@ -249,7 +412,11 @@ public void testModelFromSourceMap() { modelAsMap.put(KNNConstants.MODEL_TIMESTAMP, timestamp); modelAsMap.put(KNNConstants.MODEL_DESCRIPTION, description); modelAsMap.put(KNNConstants.MODEL_ERROR, error); + modelAsMap.put(KNNConstants.MODEL_NODE_ASSIGNMENT, nodeAssignment); modelAsMap.put(KNNConstants.MODEL_BLOB_PARAMETER, "aGVsbG8="); + modelAsMap.put(KNNConstants.VECTOR_DATA_TYPE_FIELD, VectorDataType.DEFAULT.getValue()); + modelAsMap.put(KNNConstants.MODE_PARAMETER, Mode.NOT_CONFIGURED.getName()); + modelAsMap.put(KNNConstants.COMPRESSION_LEVEL_PARAMETER, CompressionLevel.NOT_CONFIGURED.getName()); byte[] blob1 = "hello".getBytes(); Model expected = new Model(metadata, blob1, modelID); diff --git a/src/test/java/org/opensearch/knn/indices/ModelUtilTests.java b/src/test/java/org/opensearch/knn/indices/ModelUtilTests.java new file mode 100644 index 000000000..45597b4c9 --- /dev/null +++ b/src/test/java/org/opensearch/knn/indices/ModelUtilTests.java @@ -0,0 +1,41 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.indices; + +import org.junit.Assert; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.opensearch.knn.KNNTestCase; + +public class ModelUtilTests extends KNNTestCase { + private static final String MODEL_ID = "test-model"; + + public void testGetModelMetadata_whenVariousInputs_thenSuccess() { + Assert.assertNull(ModelUtil.getModelMetadata(null)); + Assert.assertNull(ModelUtil.getModelMetadata("")); + + ModelCache modelCache = Mockito.mock(ModelCache.class); + Model model = Mockito.mock(Model.class); + ModelMetadata modelMetadata = Mockito.mock(ModelMetadata.class); + MockedStatic modelCacheMockedStatic = Mockito.mockStatic(ModelCache.class); + + modelCacheMockedStatic.when(ModelCache::getInstance).thenReturn(modelCache); + try (MockedStatic modelDaoMockedStatic = Mockito.mockStatic(ModelDao.OpenSearchKNNModelDao.class)) { + ModelDao.OpenSearchKNNModelDao modelDao = Mockito.mock(ModelDao.OpenSearchKNNModelDao.class); + Mockito.when(modelDao.getMetadata(MODEL_ID)).thenReturn(modelMetadata); + Mockito.when(modelMetadata.getState()).thenReturn(ModelState.FAILED); + modelDaoMockedStatic.when(ModelDao.OpenSearchKNNModelDao::getInstance).thenReturn(modelDao); + + Mockito.when(modelCache.get(MODEL_ID)).thenReturn(model); + Mockito.when(model.getModelMetadata()).thenReturn(null); + Assert.assertThrows(IllegalArgumentException.class, () -> ModelUtil.getModelMetadata(MODEL_ID)); + + Mockito.when(modelMetadata.getState()).thenReturn(ModelState.CREATED); + Assert.assertNotNull(ModelUtil.getModelMetadata(MODEL_ID)); + } + modelCacheMockedStatic.close(); + } +} diff --git a/src/test/java/org/opensearch/knn/integ/BinaryIndexIT.java b/src/test/java/org/opensearch/knn/integ/BinaryIndexIT.java new file mode 100644 index 000000000..e98a1d769 --- /dev/null +++ b/src/test/java/org/opensearch/knn/integ/BinaryIndexIT.java @@ -0,0 +1,257 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.integ; + +import com.google.common.collect.ImmutableList; +import com.google.common.primitives.Floats; +import lombok.SneakyThrows; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang.ArrayUtils; +import org.apache.http.util.EntityUtils; +import org.junit.After; +import org.junit.BeforeClass; +import org.opensearch.client.Response; +import org.opensearch.common.settings.Settings; +import org.opensearch.knn.KNNJsonIndexMappingsBuilder; +import org.opensearch.knn.KNNJsonQueryBuilder; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.KNNResult; +import org.opensearch.knn.TestUtils; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; + +import java.io.IOException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; + +/** + * This class contains integration tests for binary index with HNSW in Faiss + */ +@Log4j2 +public class BinaryIndexIT extends KNNRestTestCase { + private static TestUtils.TestData testData; + private static final int NEVER_BUILD_GRAPH = -1; + private static final int ALWAYS_BUILD_GRAPH = 0; + + @BeforeClass + public static void setUpClass() throws IOException { + if (BinaryIndexIT.class.getClassLoader() == null) { + throw new IllegalStateException("ClassLoader of BinaryIndexIT Class is null"); + } + URL testIndexVectors = BinaryIndexIT.class.getClassLoader().getResource("data/test_vectors_binary_1000x128.json"); + URL testQueries = BinaryIndexIT.class.getClassLoader().getResource("data/test_queries_binary_100x128.csv"); + URL groundTruthValues = BinaryIndexIT.class.getClassLoader().getResource("data/test_ground_truth_binary_100.csv"); + assert testIndexVectors != null; + assert testQueries != null; + assert groundTruthValues != null; + testData = new TestUtils.TestData(testIndexVectors.getPath(), testQueries.getPath(), groundTruthValues.getPath()); + } + + @After + public void cleanUp() { + try { + deleteKNNIndex(INDEX_NAME); + } catch (Exception e) { + log.error(e); + } + } + + @SneakyThrows + public void testFaissHnswBinary_whenSmallDataSet_thenCreateIngestQueryWorks() { + // Create Index + createKnnHnswBinaryIndex(KNNEngine.FAISS, INDEX_NAME, FIELD_NAME, 16); + + // Ingest + Byte[] vector1 = { 0b00000001, 0b00000001 }; + Byte[] vector2 = { 0b00000011, 0b00000001 }; + Byte[] vector3 = { 0b00000111, 0b00000001 }; + Byte[] vector4 = { 0b00001111, 0b00000001 }; + addKnnDoc(INDEX_NAME, "1", FIELD_NAME, vector1); + addKnnDoc(INDEX_NAME, "2", FIELD_NAME, vector2); + addKnnDoc(INDEX_NAME, "3", FIELD_NAME, vector3); + addKnnDoc(INDEX_NAME, "4", FIELD_NAME, vector4); + + // Query + float[] queryVector = { (byte) 0b10001111, (byte) 0b10000000 }; + int k = 4; + List results = runKnnQuery(INDEX_NAME, FIELD_NAME, queryVector, k); + + // Validate + assertEquals(k, results.size()); + for (int i = 0; i < k; i++) { + assertEquals(k - i, Integer.parseInt(results.get(i).getDocId())); + } + } + + @SneakyThrows + public void testFaissHnswBinary_when1000Data_thenRecallIsAboveNinePointZero() { + // Create Index + createKnnHnswBinaryIndex(KNNEngine.FAISS, INDEX_NAME, FIELD_NAME, 128); + ingestTestData(INDEX_NAME, FIELD_NAME); + + int k = 100; + for (int i = 0; i < testData.queries.length; i++) { + List knnResults = runKnnQuery(INDEX_NAME, FIELD_NAME, testData.queries[i], k); + float recall = getRecall( + Set.of(Arrays.copyOf(testData.groundTruthValues[i], k)), + knnResults.stream().map(KNNResult::getDocId).collect(Collectors.toSet()) + ); + assertTrue("Recall: " + recall, recall > 0.1); + } + } + + @SneakyThrows + public void testFaissHnswBinary_whenBuildVectorGraphThresholdIsNegativeEndToEnd_thenBuildGraphBasedOnSetting() { + // Create Index + createKnnHnswBinaryIndex(KNNEngine.FAISS, INDEX_NAME, FIELD_NAME, 128, NEVER_BUILD_GRAPH); + ingestTestData(INDEX_NAME, FIELD_NAME); + + assertEquals(1, runKnnQuery(INDEX_NAME, FIELD_NAME, testData.queries[0], 1).size()); + + // update build vector data structure setting + updateIndexSettings(INDEX_NAME, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, ALWAYS_BUILD_GRAPH)); + forceMergeKnnIndex(INDEX_NAME, 1); + + int k = 100; + for (int i = 0; i < testData.queries.length; i++) { + List knnResults = runKnnQuery(INDEX_NAME, FIELD_NAME, testData.queries[i], k); + float recall = getRecall( + Set.of(Arrays.copyOf(testData.groundTruthValues[i], k)), + knnResults.stream().map(KNNResult::getDocId).collect(Collectors.toSet()) + ); + assertTrue("Recall: " + recall, recall > 0.1); + } + } + + @SneakyThrows + public void testFaissHnswBinary_whenBuildVectorGraphThresholdIsProvidedEndToEnd_thenBuildGraphBasedOnSetting() { + // Create Index + createKnnHnswBinaryIndex(KNNEngine.FAISS, INDEX_NAME, FIELD_NAME, 128, testData.indexData.docs.length); + ingestTestData(INDEX_NAME, FIELD_NAME, false); + + assertEquals(1, runKnnQuery(INDEX_NAME, FIELD_NAME, testData.queries[0], 1).size()); + + // update build vector data structure setting + updateIndexSettings(INDEX_NAME, Settings.builder().put(KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, ALWAYS_BUILD_GRAPH)); + forceMergeKnnIndex(INDEX_NAME, 1); + + int k = 100; + for (int i = 0; i < testData.queries.length; i++) { + List knnResults = runKnnQuery(INDEX_NAME, FIELD_NAME, testData.queries[i], k); + float recall = getRecall( + Set.of(Arrays.copyOf(testData.groundTruthValues[i], k)), + knnResults.stream().map(KNNResult::getDocId).collect(Collectors.toSet()) + ); + assertTrue("Recall: " + recall, recall > 0.1); + } + } + + @SneakyThrows + public void testFaissHnswBinary_whenRadialSearch_thenThrowException() { + // Create Index + createKnnHnswBinaryIndex(KNNEngine.FAISS, INDEX_NAME, FIELD_NAME, 16); + + // Query + float[] queryVector = { (byte) 0b10001111, (byte) 0b10000000 }; + Exception e = expectThrows(Exception.class, () -> runRnnQuery(INDEX_NAME, FIELD_NAME, queryVector, 1, 4)); + assertTrue(e.getMessage(), e.getMessage().contains("Binary data type does not support radial search")); + } + + private float getRecall(final Set truth, final Set result) { + // Count the number of relevant documents retrieved + result.retainAll(truth); + int relevantRetrieved = result.size(); + + // Total number of relevant documents + int totalRelevant = truth.size(); + + // Calculate recall + return (float) relevantRetrieved / totalRelevant; + } + + private List runRnnQuery( + final String indexName, + final String fieldName, + final float[] queryVector, + final float minScore, + final int size + ) throws Exception { + String query = KNNJsonQueryBuilder.builder() + .fieldName(fieldName) + .vector(ArrayUtils.toObject(queryVector)) + .minScore(minScore) + .build() + .getQueryString(); + Response response = searchKNNIndex(indexName, query, size); + return parseSearchResponse(EntityUtils.toString(response.getEntity()), fieldName); + } + + private List runKnnQuery(final String indexName, final String fieldName, final float[] queryVector, final int k) + throws Exception { + String query = KNNJsonQueryBuilder.builder() + .fieldName(fieldName) + .vector(ArrayUtils.toObject(queryVector)) + .k(k) + .build() + .getQueryString(); + Response response = searchKNNIndex(indexName, query, k); + return parseSearchResponse(EntityUtils.toString(response.getEntity()), fieldName); + } + + private void ingestTestData(final String indexName, final String fieldName) throws Exception { + ingestTestData(indexName, fieldName, true); + } + + private void ingestTestData(final String indexName, final String fieldName, boolean refresh) throws Exception { + // Index the test data + for (int i = 0; i < testData.indexData.docs.length; i++) { + addKnnDoc( + indexName, + Integer.toString(testData.indexData.docs[i]), + ImmutableList.of(fieldName), + ImmutableList.of(Floats.asList(testData.indexData.vectors[i]).toArray()), + refresh + ); + } + + // Assert we have the right number of documents in the index + refreshAllIndices(); + assertEquals(testData.indexData.docs.length, getDocCount(indexName)); + } + + private void createKnnHnswBinaryIndex( + final KNNEngine knnEngine, + final String indexName, + final String fieldName, + final int dimension, + final int threshold + ) throws IOException { + KNNJsonIndexMappingsBuilder.Method method = KNNJsonIndexMappingsBuilder.Method.builder() + .methodName(METHOD_HNSW) + .engine(knnEngine.getName()) + .build(); + + String knnIndexMapping = KNNJsonIndexMappingsBuilder.builder() + .fieldName(fieldName) + .dimension(dimension) + .vectorDataType(VectorDataType.BINARY.getValue()) + .method(method) + .build() + .getIndexMapping(); + createKnnIndex(indexName, buildKNNIndexSettings(threshold), knnIndexMapping); + } + + private void createKnnHnswBinaryIndex(final KNNEngine knnEngine, final String indexName, final String fieldName, final int dimension) + throws IOException { + createKnnHnswBinaryIndex(knnEngine, indexName, fieldName, dimension, ALWAYS_BUILD_GRAPH); + } +} diff --git a/src/test/java/org/opensearch/knn/integ/BinaryIndexInvalidMappingIT.java b/src/test/java/org/opensearch/knn/integ/BinaryIndexInvalidMappingIT.java new file mode 100644 index 000000000..29e710ec1 --- /dev/null +++ b/src/test/java/org/opensearch/knn/integ/BinaryIndexInvalidMappingIT.java @@ -0,0 +1,100 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.integ; + +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; +import lombok.AllArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.junit.After; +import org.opensearch.knn.KNNJsonIndexMappingsBuilder; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; + +import static com.carrotsearch.randomizedtesting.RandomizedTest.$; +import static com.carrotsearch.randomizedtesting.RandomizedTest.$$; +import static org.opensearch.knn.common.KNNConstants.ENCODER_SQ; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; + +/** + * This class contains integration tests for binary index with invalid mapping + */ +@Log4j2 +@AllArgsConstructor +public class BinaryIndexInvalidMappingIT extends KNNRestTestCase { + @After + public void cleanUp() { + try { + deleteKNNIndex(INDEX_NAME); + } catch (Exception e) { + log.error(e); + } + } + + private String description; + private String indexMapping; + private String expectedExceptionMessage; + + @ParametersFactory(argumentFormatting = "description:%1$s; indexMapping:%2$s, expectedExceptionMessage:%3$s") + public static Collection parameters() throws IOException { + return Arrays.asList( + $$( + $( + "Creation of binary index with lucene engine should fail", + createKnnHnswBinaryIndexMapping(KNNEngine.LUCENE, FIELD_NAME, 16, null), + "Validation Failed" + ), + $( + "Creation of binary index with nmslib engine should fail", + createKnnHnswBinaryIndexMapping(KNNEngine.NMSLIB, FIELD_NAME, 16, null), + "Validation Failed" + ), + $( + "Creation of binary index with encoder should fail", + createKnnHnswBinaryIndexMapping(KNNEngine.FAISS, FIELD_NAME, 16, ENCODER_SQ), + "Validation Failed" + ) + ) + ); + } + + public void testBinaryIndexCreation_whenInvalid_thenThrowException() { + Exception e = expectThrows(Exception.class, () -> createKnnIndex(INDEX_NAME, indexMapping)); + assertTrue(e.getMessage(), e.getMessage().contains(expectedExceptionMessage)); + } + + private static String createKnnHnswBinaryIndexMapping( + final KNNEngine knnEngine, + final String fieldName, + final int dimension, + final String encoderName + ) throws IOException { + KNNJsonIndexMappingsBuilder.Method.Parameters.Encoder encoder; + KNNJsonIndexMappingsBuilder.Method.Parameters parameters = null; + if (encoderName != null) { + encoder = KNNJsonIndexMappingsBuilder.Method.Parameters.Encoder.builder().name(encoderName).build(); + parameters = KNNJsonIndexMappingsBuilder.Method.Parameters.builder().encoder(encoder).build(); + } + + KNNJsonIndexMappingsBuilder.Method method = KNNJsonIndexMappingsBuilder.Method.builder() + .methodName(METHOD_HNSW) + .engine(knnEngine.getName()) + .parameters(parameters) + .build(); + + return KNNJsonIndexMappingsBuilder.builder() + .fieldName(fieldName) + .dimension(dimension) + .vectorDataType(VectorDataType.BINARY.getValue()) + .method(method) + .build() + .getIndexMapping(); + } +} diff --git a/src/test/java/org/opensearch/knn/integ/FilteredSearchBinaryIT.java b/src/test/java/org/opensearch/knn/integ/FilteredSearchBinaryIT.java new file mode 100644 index 000000000..6d0c8fde4 --- /dev/null +++ b/src/test/java/org/opensearch/knn/integ/FilteredSearchBinaryIT.java @@ -0,0 +1,104 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.integ; + +import com.google.common.collect.ImmutableMap; +import lombok.SneakyThrows; +import lombok.extern.log4j.Log4j2; +import org.apache.http.util.EntityUtils; +import org.junit.After; +import org.opensearch.client.Response; +import org.opensearch.common.settings.Settings; +import org.opensearch.knn.KNNJsonIndexMappingsBuilder; +import org.opensearch.knn.KNNJsonQueryBuilder; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; + +import java.util.List; + +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; + +@Log4j2 +public class FilteredSearchBinaryIT extends KNNRestTestCase { + @After + public void cleanUp() { + try { + deleteKNNIndex(INDEX_NAME); + } catch (Exception e) { + log.error(e); + } + } + + @SneakyThrows + public void testFilteredSearchWithFaissHnswBinary_whenDoingApproximateSearch_thenReturnCorrectResults() { + validateFilteredSearchWithFaissHnswBinary(INDEX_NAME, false); + } + + @SneakyThrows + public void testFilteredSearchWithFaissHnswBinary_whenDoingExactSearch_thenReturnCorrectResults() { + validateFilteredSearchWithFaissHnswBinary(INDEX_NAME, true); + } + + private void validateFilteredSearchWithFaissHnswBinary(final String indexName, final boolean doExactSearch) throws Exception { + String filterFieldName = "parking"; + createKnnBinaryIndex(indexName, FIELD_NAME, 24, KNNEngine.FAISS); + + for (byte i = 1; i < 4; i++) { + addKnnDocWithAttributes( + indexName, + Integer.toString(i), + FIELD_NAME, + new float[] { i, i, i }, + ImmutableMap.of(filterFieldName, i % 2 == 1 ? "true" : "false") + ); + } + refreshIndex(indexName); + forceMergeKnnIndex(indexName); + + // Set it as 0 for approximate search and 100(larger than number of filtered id) for exact search + updateIndexSettings( + indexName, + Settings.builder().put(KNNSettings.ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD, doExactSearch ? 100 : 0) + ); + + Float[] queryVector = { 3f, 3f, 3f }; + String query = KNNJsonQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .k(3) + .filterFieldName(filterFieldName) + .filterValue("true") + .build() + .getQueryString(); + Response response = searchKNNIndex(indexName, query, 3); + String entity = EntityUtils.toString(response.getEntity()); + List docIds = parseIds(entity); + assertEquals(2, docIds.size()); + assertEquals("3", docIds.get(0)); + assertEquals("1", docIds.get(1)); + assertEquals(2, parseTotalSearchHits(entity)); + } + + private void createKnnBinaryIndex(final String indexName, final String fieldName, final int dimension, final KNNEngine knnEngine) + throws Exception { + KNNJsonIndexMappingsBuilder.Method method = KNNJsonIndexMappingsBuilder.Method.builder() + .methodName(METHOD_HNSW) + .engine(knnEngine.getName()) + .build(); + + String knnIndexMapping = KNNJsonIndexMappingsBuilder.builder() + .fieldName(fieldName) + .dimension(dimension) + .vectorDataType(VectorDataType.BINARY.getValue()) + .method(method) + .build() + .getIndexMapping(); + + createKnnIndex(indexName, knnIndexMapping); + } +} diff --git a/src/test/java/org/opensearch/knn/integ/FilteredSearchByteIT.java b/src/test/java/org/opensearch/knn/integ/FilteredSearchByteIT.java new file mode 100644 index 000000000..9f91aa6f8 --- /dev/null +++ b/src/test/java/org/opensearch/knn/integ/FilteredSearchByteIT.java @@ -0,0 +1,104 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.integ; + +import com.google.common.collect.ImmutableMap; +import lombok.SneakyThrows; +import lombok.extern.log4j.Log4j2; +import org.apache.http.util.EntityUtils; +import org.junit.After; +import org.opensearch.client.Response; +import org.opensearch.common.settings.Settings; +import org.opensearch.knn.KNNJsonIndexMappingsBuilder; +import org.opensearch.knn.KNNJsonQueryBuilder; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; + +import java.util.List; + +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; + +@Log4j2 +public class FilteredSearchByteIT extends KNNRestTestCase { + @After + public void cleanUp() { + try { + deleteKNNIndex(INDEX_NAME); + } catch (Exception e) { + log.error(e); + } + } + + @SneakyThrows + public void testFilteredSearchWithFaissHnswByte_whenDoingApproximateSearch_thenReturnCorrectResults() { + validateFilteredSearchWithFaissHnswByte(INDEX_NAME, false); + } + + @SneakyThrows + public void testFilteredSearchWithFaissHnswByte_whenDoingExactSearch_thenReturnCorrectResults() { + validateFilteredSearchWithFaissHnswByte(INDEX_NAME, true); + } + + private void validateFilteredSearchWithFaissHnswByte(final String indexName, final boolean doExactSearch) throws Exception { + String filterFieldName = "parking"; + createKnnByteIndex(indexName, FIELD_NAME, 3, KNNEngine.FAISS); + + for (byte i = 1; i < 4; i++) { + addKnnDocWithAttributes( + indexName, + Integer.toString(i), + FIELD_NAME, + new float[] { i, i, i }, + ImmutableMap.of(filterFieldName, i % 2 == 1 ? "true" : "false") + ); + } + refreshIndex(indexName); + forceMergeKnnIndex(indexName); + + // Set it as 0 for approximate search and 100(larger than number of filtered id) for exact search + updateIndexSettings( + indexName, + Settings.builder().put(KNNSettings.ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD, doExactSearch ? 100 : 0) + ); + + Float[] queryVector = { 3f, 3f, 3f }; + String query = KNNJsonQueryBuilder.builder() + .fieldName(FIELD_NAME) + .vector(queryVector) + .k(3) + .filterFieldName(filterFieldName) + .filterValue("true") + .build() + .getQueryString(); + Response response = searchKNNIndex(indexName, query, 3); + String entity = EntityUtils.toString(response.getEntity()); + List docIds = parseIds(entity); + assertEquals(2, docIds.size()); + assertEquals("3", docIds.get(0)); + assertEquals("1", docIds.get(1)); + assertEquals(2, parseTotalSearchHits(entity)); + } + + private void createKnnByteIndex(final String indexName, final String fieldName, final int dimension, final KNNEngine knnEngine) + throws Exception { + KNNJsonIndexMappingsBuilder.Method method = KNNJsonIndexMappingsBuilder.Method.builder() + .methodName(METHOD_HNSW) + .engine(knnEngine.getName()) + .build(); + + String knnIndexMapping = KNNJsonIndexMappingsBuilder.builder() + .fieldName(fieldName) + .dimension(dimension) + .vectorDataType(VectorDataType.BYTE.getValue()) + .method(method) + .build() + .getIndexMapping(); + + createKnnIndex(indexName, knnIndexMapping); + } +} diff --git a/src/test/java/org/opensearch/knn/integ/IndexIT.java b/src/test/java/org/opensearch/knn/integ/IndexIT.java new file mode 100644 index 000000000..793c17f23 --- /dev/null +++ b/src/test/java/org/opensearch/knn/integ/IndexIT.java @@ -0,0 +1,139 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.integ; + +import com.google.common.primitives.Floats; +import lombok.SneakyThrows; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang.ArrayUtils; +import org.apache.http.util.EntityUtils; +import org.junit.After; +import org.junit.BeforeClass; +import org.opensearch.client.Response; +import org.opensearch.knn.KNNJsonIndexMappingsBuilder; +import org.opensearch.knn.KNNJsonQueryBuilder; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.KNNResult; +import org.opensearch.knn.TestUtils; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; + +import java.io.IOException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; + +/** + * This class contains integration tests for index + */ +@Log4j2 +public class IndexIT extends KNNRestTestCase { + private static TestUtils.TestData testData; + + @BeforeClass + public static void setUpClass() throws IOException { + if (IndexIT.class.getClassLoader() == null) { + throw new IllegalStateException("ClassLoader of IndexIT Class is null"); + } + URL testIndexVectors = IndexIT.class.getClassLoader().getResource("data/test_vectors_1000x128.json"); + URL testQueries = IndexIT.class.getClassLoader().getResource("data/test_queries_100x128.csv"); + URL groundTruthValues = IndexIT.class.getClassLoader().getResource("data/test_ground_truth_l2_100.csv"); + assert testIndexVectors != null; + assert testQueries != null; + assert groundTruthValues != null; + testData = new TestUtils.TestData(testIndexVectors.getPath(), testQueries.getPath(), groundTruthValues.getPath()); + } + + @After + public void cleanUp() { + try { + deleteKNNIndex(INDEX_NAME); + } catch (Exception e) { + log.error(e); + } + } + + @SneakyThrows + public void testFaissHnsw_when1000Data_thenRecallIsAboveNinePointZero() { + // Create Index + createKnnHnswIndex(KNNEngine.FAISS, INDEX_NAME, FIELD_NAME, 128); + ingestTestData(INDEX_NAME, FIELD_NAME); + + int k = 100; + for (int i = 0; i < testData.queries.length; i++) { + List knnResults = runKnnQuery(INDEX_NAME, FIELD_NAME, testData.queries[i], k); + float recall = getRecall( + Set.of(Arrays.copyOf(testData.groundTruthValues[i], k)), + knnResults.stream().map(KNNResult::getDocId).collect(Collectors.toSet()) + ); + assertTrue("Recall: " + recall, recall > 0.9); + } + } + + private float getRecall(final Set truth, final Set result) { + // Count the number of relevant documents retrieved + result.retainAll(truth); + int relevantRetrieved = result.size(); + + // Total number of relevant documents + int totalRelevant = truth.size(); + + // Calculate recall + return (float) relevantRetrieved / totalRelevant; + } + + private List runKnnQuery(final String indexName, final String fieldName, final float[] queryVector, final int k) + throws Exception { + String query = KNNJsonQueryBuilder.builder() + .fieldName(fieldName) + .vector(ArrayUtils.toObject(queryVector)) + .k(k) + .build() + .getQueryString(); + Response response = searchKNNIndex(indexName, query, k); + return parseSearchResponse(EntityUtils.toString(response.getEntity()), fieldName); + } + + private void ingestTestData(final String indexName, final String fieldName) throws Exception { + // Index the test data + for (int i = 0; i < testData.indexData.docs.length; i++) { + addKnnDoc( + indexName, + Integer.toString(testData.indexData.docs[i]), + fieldName, + Floats.asList(testData.indexData.vectors[i]).toArray() + ); + } + + // Assert we have the right number of documents in the index + refreshAllIndices(); + assertEquals(testData.indexData.docs.length, getDocCount(indexName)); + } + + private void createKnnHnswIndex(final KNNEngine knnEngine, final String indexName, final String fieldName, final int dimension) + throws IOException { + KNNJsonIndexMappingsBuilder.Method method = KNNJsonIndexMappingsBuilder.Method.builder() + .methodName(METHOD_HNSW) + .spaceType(SpaceType.L2.getValue()) + .engine(knnEngine.getName()) + .build(); + + String knnIndexMapping = KNNJsonIndexMappingsBuilder.builder() + .fieldName(fieldName) + .dimension(dimension) + .vectorDataType(VectorDataType.FLOAT.getValue()) + .method(method) + .build() + .getIndexMapping(); + + createKnnIndex(indexName, knnIndexMapping); + } +} diff --git a/src/test/java/org/opensearch/knn/integ/KNNScriptScoringIT.java b/src/test/java/org/opensearch/knn/integ/KNNScriptScoringIT.java new file mode 100644 index 000000000..ecd425c6c --- /dev/null +++ b/src/test/java/org/opensearch/knn/integ/KNNScriptScoringIT.java @@ -0,0 +1,875 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.integ; + +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; + +import lombok.SneakyThrows; +import org.opensearch.ExceptionsHelper; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.KNNResult; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.SpaceType; +import org.apache.http.util.EntityUtils; +import org.opensearch.client.Request; +import org.opensearch.client.Response; +import org.opensearch.client.ResponseException; +import org.opensearch.common.settings.Settings; +import org.opensearch.core.xcontent.MediaTypeRegistry; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.index.query.MatchAllQueryBuilder; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.functionscore.ScriptScoreQueryBuilder; +import org.opensearch.core.rest.RestStatus; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.mapper.KNNVectorFieldType; +import org.opensearch.knn.plugin.script.KNNScoringScriptEngine; +import org.opensearch.knn.plugin.script.KNNScoringSpace; +import org.opensearch.knn.plugin.script.KNNScoringSpaceFactory; +import org.opensearch.script.Script; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import static org.hamcrest.Matchers.containsString; +import static org.opensearch.knn.KNNTestCase.getMappingConfigForFlatMapping; +import static org.opensearch.knn.common.KNNConstants.FAISS_NAME; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.METHOD_IVF; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NLIST; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NPROBES; +import static org.opensearch.knn.common.KNNConstants.MODEL_ID; +import static org.opensearch.knn.common.KNNConstants.NAME; +import static org.opensearch.knn.common.KNNConstants.PARAMETERS; +import static org.opensearch.knn.common.KNNConstants.TYPE; +import static org.opensearch.knn.common.KNNConstants.TYPE_KNN_VECTOR; +import static org.opensearch.knn.common.KNNConstants.TRAIN_FIELD_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.TRAIN_INDEX_PARAMETER; + +public class KNNScriptScoringIT extends KNNRestTestCase { + + private static final String TEST_MODEL = "test-model"; + + public void testKNNL2ScriptScore() throws Exception { + testKNNScriptScore(SpaceType.L2); + } + + public void testKNNL1ScriptScore() throws Exception { + testKNNScriptScore(SpaceType.L1); + } + + public void testKNNLInfScriptScore() throws Exception { + testKNNScriptScore(SpaceType.LINF); + } + + public void testKNNCosineScriptScore() throws Exception { + testKNNScriptScore(SpaceType.COSINESIMIL); + } + + @SneakyThrows + public void testKNNHammingScriptScore() { + testKNNScriptScoreOnBinaryIndex(SpaceType.HAMMING); + } + + @SneakyThrows + public void testKNNHammingScriptScore_whenNonBinary_thenException() { + final int dims = randomIntBetween(2, 10) * 8; + final float[] queryVector = randomVector(dims, VectorDataType.BYTE); + final BiFunction scoreFunction = getScoreFunction(SpaceType.HAMMING, queryVector); + List nonBinary = List.of(VectorDataType.FLOAT, VectorDataType.BYTE); + for (VectorDataType vectorDataType : nonBinary) { + Exception e = expectThrows( + Exception.class, + () -> createIndexAndAssertScriptScore( + createKnnIndexMapping(FIELD_NAME, dims, vectorDataType), + SpaceType.HAMMING, + scoreFunction, + dims, + queryVector, + true, + false, + vectorDataType + ) + ); + assertTrue(e.getMessage(), e.getMessage().contains("data type should be [BINARY]")); + } + } + + public void testKNNNonHammingScriptScore_whenBinary_thenException() { + final int dims = randomIntBetween(2, 10) * 8; + final float[] queryVector = randomVector(dims, VectorDataType.BINARY); + final BiFunction scoreFunction = getScoreFunction(SpaceType.HAMMING, queryVector); + Set spaceTypeToExclude = Set.of(SpaceType.UNDEFINED, SpaceType.HAMMING); + Arrays.stream(SpaceType.values()).filter(s -> spaceTypeToExclude.contains(s) == false).forEach(s -> { + Exception e = expectThrows( + Exception.class, + () -> createIndexAndAssertScriptScore( + createKnnIndexMapping(FIELD_NAME, dims, VectorDataType.BINARY), + s, + scoreFunction, + dims, + queryVector, + true, + false, + VectorDataType.BINARY + ) + ); + assertTrue(e.getMessage(), e.getMessage().contains("Incompatible field_type")); + }); + } + + public void testKNNInvalidSourceScript() throws Exception { + /* + * Create knn index and populate data + */ + createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); + + /** + * Construct Search Request + */ + QueryBuilder qb = new MatchAllQueryBuilder(); + Map params = new HashMap<>(); + /* + * params": { + * "field": "my_dense_vector", + * "query_value": [2.0, 2.0], + * "space_type": "cosinesimil" + * } + */ + float[] queryVector = { 2.0f, -2.0f }; + params.put("field", FIELD_NAME); + params.put("query_value", queryVector); + params.put("space_type", SpaceType.COSINESIMIL.getValue()); + Script script = new Script(Script.DEFAULT_SCRIPT_TYPE, KNNScoringScriptEngine.NAME, "Dummy_source", params); + ScriptScoreQueryBuilder sc = new ScriptScoreQueryBuilder(qb, script); + + XContentBuilder builder = XContentFactory.jsonBuilder().startObject().startObject("query"); + + builder.startObject("script_score"); + builder.field("query"); + sc.query().toXContent(builder, ToXContent.EMPTY_PARAMS); + builder.field("script", script); + builder.endObject(); + builder.endObject(); + builder.endObject(); + Request request = new Request("POST", "/" + INDEX_NAME + "/_search"); + + request.setJsonEntity(builder.toString()); + ResponseException ex = expectThrows(ResponseException.class, () -> client().performRequest(request)); + assertThat(EntityUtils.toString(ex.getResponse().getEntity()), containsString("Unknown script name Dummy_source")); + } + + public void testInvalidSpace() throws Exception { + String INVALID_SPACE = "dummy"; + /* + * Create knn index and populate data + */ + createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); + + /** + * Construct Search Request + */ + QueryBuilder qb = new MatchAllQueryBuilder(); + Map params = new HashMap<>(); + float[] queryVector = { 2.0f, -2.0f }; + params.put("field", FIELD_NAME); + params.put("query_value", queryVector); + params.put("space_type", INVALID_SPACE); + Request request = constructKNNScriptQueryRequest(INDEX_NAME, qb, params); + ResponseException ex = expectThrows(ResponseException.class, () -> client().performRequest(request)); + assertThat( + EntityUtils.toString(ex.getResponse().getEntity()), + containsString("Invalid space type. Please refer to the available space types") + ); + } + + public void testMissingParamsInScript() throws Exception { + /* + * Create knn index and populate data + */ + createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); + + /** + * Construct Search Request + */ + QueryBuilder qb = new MatchAllQueryBuilder(); + Map params = new HashMap<>(); + float[] queryVector = { 2.0f, -2.0f }; + params.put("query_value", queryVector); + params.put("space_type", SpaceType.COSINESIMIL.getValue()); + Request request = constructKNNScriptQueryRequest(INDEX_NAME, qb, params); + ResponseException ex = expectThrows(ResponseException.class, () -> client().performRequest(request)); + assertThat(EntityUtils.toString(ex.getResponse().getEntity()), containsString("Missing parameter [field]")); + + // Remove query vector parameter + params.put("field", FIELD_NAME); + params.remove("query_value"); + Request vector_request = constructKNNScriptQueryRequest(INDEX_NAME, qb, params); + ex = expectThrows(ResponseException.class, () -> client().performRequest(vector_request)); + assertThat(EntityUtils.toString(ex.getResponse().getEntity()), containsString("Missing parameter [query_value]")); + + // Remove space parameter + params.put("query_value", queryVector); + params.remove("space_type"); + Request space_request = constructKNNScriptQueryRequest(INDEX_NAME, qb, params); + ex = expectThrows(ResponseException.class, () -> client().performRequest(space_request)); + assertThat(EntityUtils.toString(ex.getResponse().getEntity()), containsString("Missing parameter [space_type]")); + } + + public void testUnequalDimensions() throws Exception { + /* + * Create knn index and populate data + */ + createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); + Float[] f1 = { 1.0f, -1.0f }; + addKnnDoc(INDEX_NAME, "0", FIELD_NAME, f1); + + /** + * Construct Search Request + */ + QueryBuilder qb = new MatchAllQueryBuilder(); + Map params = new HashMap<>(); + float[] queryVector = { 2.0f, -2.0f, -2.0f }; // query dimension and field dimension mismatch + params.put("field", FIELD_NAME); + params.put("query_value", queryVector); + params.put("space_type", SpaceType.COSINESIMIL.getValue()); + Request request = constructKNNScriptQueryRequest(INDEX_NAME, qb, params); + ResponseException ex = expectThrows(ResponseException.class, () -> client().performRequest(request)); + assertThat(EntityUtils.toString(ex.getResponse().getEntity()), containsString("does not match")); + } + + @SuppressWarnings("unchecked") + public void testKNNScoreForNonVectorDocument() throws Exception { + /* + * Create knn index and populate data + */ + createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); + Float[] f1 = { 1.0f, 1.0f }; + addDocWithNumericField(INDEX_NAME, "0", "price", 10); + addKnnDoc(INDEX_NAME, "1", FIELD_NAME, f1); + forceMergeKnnIndex(INDEX_NAME); + /** + * Construct Search Request + */ + QueryBuilder qb = new MatchAllQueryBuilder(); + Map params = new HashMap<>(); + float[] queryVector = { 2.0f, 2.0f }; // query dimension and field dimension mismatch + params.put("field", FIELD_NAME); + params.put("query_value", queryVector); + params.put("space_type", SpaceType.L2.getValue()); + Request request = constructKNNScriptQueryRequest(INDEX_NAME, qb, params); + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + + String responseBody = EntityUtils.toString(response.getEntity()); + List hits = (List) ((Map) createParser(XContentType.JSON.xContent(), responseBody).map() + .get("hits")).get("hits"); + + List docIds = hits.stream().map(hit -> ((String) ((Map) hit).get("_id"))).collect(Collectors.toList()); + // assert document order + assertEquals("1", docIds.get(0)); + assertEquals("0", docIds.get(1)); + + List scores = hits.stream().map(hit -> { + Double score = ((Double) ((Map) hit).get("_score")); + return score; + }).collect(Collectors.toList()); + // assert scores + assertEquals(0.33333, scores.get(0), 0.001); + assertEquals(Float.MIN_VALUE, scores.get(1), 0.001); + } + + @SuppressWarnings("unchecked") + public void testHammingScriptScore_Long() throws Exception { + createIndex(INDEX_NAME, Settings.EMPTY); + String longMapping = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "long") + .endObject() + .endObject() + .endObject() + .toString(); + putMappingRequest(INDEX_NAME, longMapping); + + addDocWithNumericField(INDEX_NAME, "0", FIELD_NAME, 8L); + addDocWithNumericField(INDEX_NAME, "1", FIELD_NAME, 1L); + addDocWithNumericField(INDEX_NAME, "2", FIELD_NAME, -9_223_372_036_818_523_493L); + addDocWithNumericField(INDEX_NAME, "3", FIELD_NAME, 1_000_000_000_000_000L); + + // Add docs without the field. These docs should not appear in top 4 of results + addDocWithNumericField(INDEX_NAME, "4", "price", 10); + addDocWithNumericField(INDEX_NAME, "5", "price", 10); + addDocWithNumericField(INDEX_NAME, "6", "price", 10); + + /* + * Decimal to Binary conversions lookup + * 8 -> 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 + * 1 -> 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 + * -9_223_372_036_818_523_493 -> 1000 0000 0000 0000 0000 0000 0000 0000 0000 0010 0010 1001 0010 1010 1001 1011 + * 1_000_000_000_000_000 -> 0000 0000 0000 0011 1000 1101 0111 1110 1010 0100 1100 0110 1000 0000 0000 0000 + * -9_223_372_036_818_526_181 -> 1000 0000 0000 0000 0000 0000 0000 0000 0000 0010 0010 1001 0010 0000 0001 1011 + * 10 -> 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1010 + */ + + QueryBuilder qb1 = new MatchAllQueryBuilder(); + Map params1 = new HashMap<>(); + Long queryValue1 = -9223372036818526181L; + params1.put("field", FIELD_NAME); + params1.put("query_value", queryValue1); + params1.put("space_type", KNNScoringSpaceFactory.HAMMING_BIT); + Request request1 = constructKNNScriptQueryRequest(INDEX_NAME, qb1, params1, 4, Collections.emptyMap()); + Response response1 = client().performRequest(request1); + assertEquals(request1.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response1.getStatusLine().getStatusCode())); + + String responseBody1 = EntityUtils.toString(response1.getEntity()); + List hits1 = (List) ((Map) createParser(XContentType.JSON.xContent(), responseBody1).map() + .get("hits")).get("hits"); + + List docIds1 = hits1.stream().map(hit -> ((String) ((Map) hit).get("_id"))).collect(Collectors.toList()); + + List docScores1 = hits1.stream() + .map(hit -> ((Double) ((Map) hit).get("_score"))) + .collect(Collectors.toList()); + + double[] scores1 = new double[docScores1.size()]; + for (int i = 0; i < docScores1.size(); i++) { + scores1[i] = docScores1.get(i); + } + + List correctIds1 = Arrays.asList("2", "0", "1", "3"); + double[] correctScores1 = new double[] { 1.0 / (1 + 3), 1.0 / (1 + 9), 1.0 / (1 + 9), 1.0 / (1 + 30) }; + + assertEquals(4, correctIds1.size()); + assertArrayEquals(correctIds1.toArray(), docIds1.toArray()); + assertArrayEquals(correctScores1, scores1, 0.001); + + /* + * Force merge to one segment to confirm that docs without field are not included in the results when segment + * is mixed with docs that have the field and docs that dont. + */ + forceMergeKnnIndex(INDEX_NAME); + + QueryBuilder qb2 = new MatchAllQueryBuilder(); + Map params2 = new HashMap<>(); + Long queryValue2 = 10L; + params2.put("field", FIELD_NAME); + params2.put("query_value", queryValue2); + params2.put("space_type", KNNScoringSpaceFactory.HAMMING_BIT); + Request request2 = constructKNNScriptQueryRequest(INDEX_NAME, qb2, params2, 4, Collections.emptyMap()); + Response response2 = client().performRequest(request2); + assertEquals(request2.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response2.getStatusLine().getStatusCode())); + + String responseBody2 = EntityUtils.toString(response2.getEntity()); + List hits2 = (List) ((Map) createParser(XContentType.JSON.xContent(), responseBody2).map() + .get("hits")).get("hits"); + + List docIds2 = hits2.stream().map(hit -> ((String) ((Map) hit).get("_id"))).collect(Collectors.toList()); + + List docScores2 = hits2.stream() + .map(hit -> ((Double) ((Map) hit).get("_score"))) + .collect(Collectors.toList()); + + double[] scores2 = new double[docScores2.size()]; + for (int i = 0; i < docScores2.size(); i++) { + scores2[i] = docScores2.get(i); + } + + List correctIds2 = Arrays.asList("0", "1", "2", "3"); + double[] correctScores2 = new double[] { 1.0 / (1 + 1), 1.0 / (1 + 3), 1.0 / (1 + 11), 1.0 / (1 + 22) }; + + assertEquals(4, correctIds2.size()); + assertArrayEquals(correctIds2.toArray(), docIds2.toArray()); + assertArrayEquals(correctScores2, scores2, 0.001); + } + + @SuppressWarnings("unchecked") + public void testHammingScriptScore_Base64() throws Exception { + createIndex(INDEX_NAME, Settings.EMPTY); + String longMapping = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "binary") + .field("doc_values", true) + .endObject() + .endObject() + .endObject() + .toString(); + putMappingRequest(INDEX_NAME, longMapping); + + addDocWithBinaryField(INDEX_NAME, "0", FIELD_NAME, "AAAAAAAAAAk="); + addDocWithBinaryField(INDEX_NAME, "1", FIELD_NAME, "AAAAAAAAAAE="); + addDocWithBinaryField(INDEX_NAME, "2", FIELD_NAME, "gAAAAAIpKps="); + addDocWithBinaryField(INDEX_NAME, "3", FIELD_NAME, "AAONfqTGgAA="); + + // Add docs without the field. These docs should not appear in top 4 of results + addDocWithNumericField(INDEX_NAME, "4", "price", 10); + addDocWithNumericField(INDEX_NAME, "5", "price", 10); + addDocWithNumericField(INDEX_NAME, "6", "price", 10); + + /* + * Base64 encodings to Binary conversions lookup + * AAAAAAAAAAk= -> 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1001 + * AAAAAAAAAAE= -> 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 + * gAAAAAIpKps= -> 1000 0000 0000 0000 0000 0000 0000 0000 0000 0010 0010 1001 0010 1010 1001 1011 + * AAONfqTGgAA= -> 0000 0000 0000 0011 1000 1101 0111 1110 1010 0100 1100 0110 1000 0000 0000 0000 + * gAAAAAIpIBs= -> 1000 0000 0000 0000 0000 0000 0000 0000 0000 0010 0010 1001 0010 0000 0001 1011 + * AAAAAAIpIBs= -> 0000 0000 0000 0000 0000 0000 0000 0000 0000 0010 0010 1001 0010 0000 0001 1011 + */ + + QueryBuilder qb1 = new MatchAllQueryBuilder(); + Map params1 = new HashMap<>(); + String queryValue1 = "gAAAAAIpIBs="; + params1.put("field", FIELD_NAME); + params1.put("query_value", queryValue1); + params1.put("space_type", KNNScoringSpaceFactory.HAMMING_BIT); + Request request1 = constructKNNScriptQueryRequest(INDEX_NAME, qb1, params1, 4, Collections.emptyMap()); + Response response1 = client().performRequest(request1); + assertEquals(request1.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response1.getStatusLine().getStatusCode())); + + String responseBody1 = EntityUtils.toString(response1.getEntity()); + List hits1 = (List) ((Map) createParser(XContentType.JSON.xContent(), responseBody1).map() + .get("hits")).get("hits"); + + List docIds1 = hits1.stream().map(hit -> ((String) ((Map) hit).get("_id"))).collect(Collectors.toList()); + + List docScores1 = hits1.stream() + .map(hit -> ((Double) ((Map) hit).get("_score"))) + .collect(Collectors.toList()); + + double[] scores1 = new double[docScores1.size()]; + for (int i = 0; i < docScores1.size(); i++) { + scores1[i] = docScores1.get(i); + } + + List correctIds1 = Arrays.asList("2", "0", "1", "3"); + double[] correctScores1 = new double[] { 1.0 / (1 + 3), 1.0 / (1 + 8), 1.0 / (1 + 9), 1.0 / (1 + 30) }; + + assertEquals(correctIds1.size(), docIds1.size()); + assertArrayEquals(correctIds1.toArray(), docIds1.toArray()); + assertArrayEquals(correctScores1, scores1, 0.001); + + /* + * Force merge to one segment to confirm that docs without field are not included in the results when segment + * is mixed with docs that have the field and docs that dont. + */ + forceMergeKnnIndex(INDEX_NAME); + + QueryBuilder qb2 = new MatchAllQueryBuilder(); + Map params2 = new HashMap<>(); + String queryValue2 = "AAAAAAIpIBs="; + params2.put("field", FIELD_NAME); + params2.put("query_value", queryValue2); + params2.put("space_type", KNNScoringSpaceFactory.HAMMING_BIT); + Request request2 = constructKNNScriptQueryRequest(INDEX_NAME, qb2, params2, 4, Collections.emptyMap()); + Response response2 = client().performRequest(request2); + assertEquals(request2.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response2.getStatusLine().getStatusCode())); + + String responseBody2 = EntityUtils.toString(response2.getEntity()); + List hits2 = (List) ((Map) createParser(XContentType.JSON.xContent(), responseBody2).map() + .get("hits")).get("hits"); + + List docIds2 = hits2.stream().map(hit -> ((String) ((Map) hit).get("_id"))).collect(Collectors.toList()); + + List docScores2 = hits2.stream() + .map(hit -> ((Double) ((Map) hit).get("_score"))) + .collect(Collectors.toList()); + + double[] scores2 = new double[docScores2.size()]; + for (int i = 0; i < docScores2.size(); i++) { + scores2[i] = docScores2.get(i); + } + + List correctIds2 = Arrays.asList("2", "0", "1", "3"); + double[] correctScores2 = new double[] { 1.0 / (1 + 4), 1.0 / (1 + 7), 1.0 / (1 + 8), 1.0 / (1 + 29) }; + + assertEquals(correctIds2.size(), docIds2.size()); + assertArrayEquals(correctIds2.toArray(), docIds2.toArray()); + assertArrayEquals(correctScores2, scores2, 0.001); + } + + public void testKNNInnerProdScriptScore() throws Exception { + testKNNScriptScore(SpaceType.INNER_PRODUCT); + } + + public void testKNNScriptScoreWithRequestCacheEnabled() throws Exception { + /* + * Create knn index and populate data + */ + createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); + Float[] f1 = { 6.0f, 6.0f }; + addKnnDoc(INDEX_NAME, "1", FIELD_NAME, f1); + + Float[] f2 = { 2.0f, 2.0f }; + addKnnDoc(INDEX_NAME, "2", FIELD_NAME, f2); + + Float[] f3 = { 4.0f, 4.0f }; + addKnnDoc(INDEX_NAME, "3", FIELD_NAME, f3); + + Float[] f4 = { 3.0f, 3.0f }; + addKnnDoc(INDEX_NAME, "4", FIELD_NAME, f4); + + /** + * Construct Search Request + */ + QueryBuilder qb = new MatchAllQueryBuilder(); + Map scriptParams = new HashMap<>(); + /* + * params": { + * "field": "my_dense_vector", + * "vector": [2.0, 2.0] + * } + */ + float[] queryVector = { 1.0f, 1.0f }; + scriptParams.put("field", FIELD_NAME); + scriptParams.put("query_value", queryVector); + scriptParams.put("space_type", SpaceType.L2.getValue()); + Map searchParams = new HashMap<>(); + searchParams.put("request_cache", true); + + // first request with request cache enabled + Request firstScriptQueryRequest = constructKNNScriptQueryRequest(INDEX_NAME, qb, scriptParams, 4, searchParams); + Response firstScriptQueryResponse = client().performRequest(firstScriptQueryRequest); + assertEquals( + firstScriptQueryRequest.getEndpoint() + ": failed", + RestStatus.OK, + RestStatus.fromCode(firstScriptQueryResponse.getStatusLine().getStatusCode()) + ); + + List results = parseSearchResponse(EntityUtils.toString(firstScriptQueryResponse.getEntity()), FIELD_NAME); + List expectedDocids = Arrays.asList("2", "4", "3", "1"); + + List actualDocids = new ArrayList<>(); + for (KNNResult result : results) { + actualDocids.add(result.getDocId()); + } + + assertEquals(4, results.size()); + assertEquals(expectedDocids, actualDocids); + + // assert that the request cache was hit missed at first request + Request firstStatsRequest = new Request("GET", "/" + INDEX_NAME + "/_stats"); + Response firstStatsResponse = client().performRequest(firstStatsRequest); + assertEquals( + firstStatsRequest.getEndpoint() + ": failed", + RestStatus.OK, + RestStatus.fromCode(firstStatsResponse.getStatusLine().getStatusCode()) + ); + String firstStatsResponseBody = EntityUtils.toString(firstStatsResponse.getEntity()); + Map firstQueryCacheMap = Optional.ofNullable( + createParser(MediaTypeRegistry.getDefaultMediaType().xContent(), firstStatsResponseBody).map() + ) + .map(r -> (Map) r.get("indices")) + .map(i -> (Map) i.get(INDEX_NAME)) + .map(ind -> (Map) ind.get("total")) + .map(t -> (Map) t.get("request_cache")) + .orElseThrow(() -> new IllegalStateException("Query Cache Map not found")); + // assert that the request cache was hit missed at first request + assertEquals(1, firstQueryCacheMap.get("miss_count")); + assertEquals(0, firstQueryCacheMap.get("hit_count")); + + // second request with request cache enabled + Request secondScriptQueryRequest = constructKNNScriptQueryRequest(INDEX_NAME, qb, scriptParams, 4, searchParams); + Response secondScriptQueryResponse = client().performRequest(secondScriptQueryRequest); + assertEquals( + firstScriptQueryRequest.getEndpoint() + ": failed", + RestStatus.OK, + RestStatus.fromCode(secondScriptQueryResponse.getStatusLine().getStatusCode()) + ); + + Request secondStatsRequest = new Request("GET", "/" + INDEX_NAME + "/_stats"); + Response secondStatsResponse = client().performRequest(secondStatsRequest); + assertEquals( + secondStatsRequest.getEndpoint() + ": failed", + RestStatus.OK, + RestStatus.fromCode(secondStatsResponse.getStatusLine().getStatusCode()) + ); + String secondStatsResponseBody = EntityUtils.toString(secondStatsResponse.getEntity()); + Map secondQueryCacheMap = Optional.ofNullable( + createParser(MediaTypeRegistry.getDefaultMediaType().xContent(), secondStatsResponseBody).map() + ) + .map(r -> (Map) r.get("indices")) + .map(i -> (Map) i.get(INDEX_NAME)) + .map(ind -> (Map) ind.get("total")) + .map(t -> (Map) t.get("request_cache")) + .orElseThrow(() -> new IllegalStateException("Query Cache Map not found")); + assertEquals(1, secondQueryCacheMap.get("miss_count")); + // assert that the request cache was hit at second request + assertEquals(1, secondQueryCacheMap.get("hit_count")); + } + + public void testKNNScriptScoreOnModelBasedIndex() throws Exception { + int dimensions = randomIntBetween(2, 10); + String trainMapping = createKnnIndexMapping(TRAIN_FIELD_PARAMETER, dimensions); + createKnnIndex(TRAIN_INDEX_PARAMETER, trainMapping); + bulkIngestRandomVectors(TRAIN_INDEX_PARAMETER, TRAIN_FIELD_PARAMETER, dimensions * 3, dimensions); + + XContentBuilder methodBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_IVF) + .field(KNN_ENGINE, FAISS_NAME) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_NLIST, 4) + .field(METHOD_PARAMETER_NPROBES, 2) + .endObject() + .endObject(); + Map method = xContentBuilderToMap(methodBuilder); + + trainModel(TEST_MODEL, TRAIN_INDEX_PARAMETER, TRAIN_FIELD_PARAMETER, dimensions, method, "test model for script score"); + assertTrainingSucceeds(TEST_MODEL, 30, 1000); + + String testMapping = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES_FIELD) + .startObject(FIELD_NAME) + .field(TYPE, TYPE_KNN_VECTOR) + .field(MODEL_ID, TEST_MODEL) + .endObject() + .endObject() + .endObject() + .toString(); + + for (SpaceType spaceType : SpaceType.values()) { + if (SpaceType.UNDEFINED == spaceType || SpaceType.HAMMING == spaceType) { + continue; + } + final float[] queryVector = randomVector(dimensions); + final BiFunction scoreFunction = getScoreFunction(spaceType, queryVector); + createIndexAndAssertScriptScore(testMapping, spaceType, scoreFunction, dimensions, queryVector, true); + } + } + + private List createMappers(int dimensions) throws Exception { + return List.of( + createKnnIndexMapping(FIELD_NAME, dimensions), + createKnnIndexMapping( + FIELD_NAME, + dimensions, + KNNConstants.METHOD_HNSW, + KNNEngine.LUCENE.getName(), + SpaceType.DEFAULT.getValue(), + true + ), + createKnnIndexMapping( + FIELD_NAME, + dimensions, + KNNConstants.METHOD_HNSW, + KNNEngine.LUCENE.getName(), + SpaceType.DEFAULT.getValue(), + false + ) + ); + } + + private List createBinaryIndexMappers(int dimensions) throws Exception { + return List.of( + createKnnIndexMapping( + FIELD_NAME, + dimensions, + KNNConstants.METHOD_HNSW, + KNNEngine.FAISS.getName(), + SpaceType.DEFAULT_BINARY.getValue(), + true, + VectorDataType.BINARY + ), + createKnnIndexMapping( + FIELD_NAME, + dimensions, + KNNConstants.METHOD_HNSW, + KNNEngine.FAISS.getName(), + SpaceType.DEFAULT_BINARY.getValue(), + false, + VectorDataType.BINARY + ) + ); + } + + private float[] randomVector(final int dimensions) { + return randomVector(dimensions, VectorDataType.FLOAT); + } + + private float[] randomVector(final int dimensions, final VectorDataType vectorDataType) { + int size = VectorDataType.BINARY == vectorDataType ? dimensions / 8 : dimensions; + final float[] vector = new float[size]; + for (int i = 0; i < size; i++) { + vector[i] = VectorDataType.FLOAT == vectorDataType ? randomFloat() : randomByte(); + } + return vector; + } + + private Map createDataset( + Function scoreFunction, + int dimensions, + int numDocsWithField, + boolean dense, + VectorDataType vectorDataType + ) { + final Map dataset = new HashMap<>(dense ? numDocsWithField : numDocsWithField * 3); + int id = 0; + for (int i = 0; i < numDocsWithField; i++) { + final int dummyDocs = dense ? 0 : randomIntBetween(2, 5); + for (int j = 0; j < dummyDocs; j++) { + dataset.put(Integer.toString(id++), null); + } + final float[] vector = randomVector(dimensions, vectorDataType); + final float score = scoreFunction.apply(vector); + dataset.put(Integer.toString(id), new KNNResult(Integer.toString(id++), vector, score)); + } + return dataset; + } + + private BiFunction getScoreFunction(SpaceType spaceType, float[] queryVector) { + List target = new ArrayList<>(queryVector.length); + for (float f : queryVector) { + target.add(f); + } + KNNScoringSpace knnScoringSpace = KNNScoringSpaceFactory.create( + spaceType.getValue(), + target, + new KNNVectorFieldType( + FIELD_NAME, + Collections.emptyMap(), + SpaceType.HAMMING == spaceType ? VectorDataType.BINARY : VectorDataType.FLOAT, + getMappingConfigForFlatMapping(SpaceType.HAMMING == spaceType ? queryVector.length * 8 : queryVector.length) + ) + ); + switch (spaceType) { + case L1: + case L2: + case LINF: + case COSINESIMIL: + case INNER_PRODUCT: + case HAMMING: + return ((KNNScoringSpace.KNNFieldSpace) knnScoringSpace).getScoringMethod(); + default: + throw new IllegalArgumentException(); + } + } + + private void testKNNScriptScore(SpaceType spaceType) throws Exception { + final int dims = randomIntBetween(2, 10); + final float[] queryVector = randomVector(dims); + final BiFunction scoreFunction = getScoreFunction(spaceType, queryVector); + for (String mapper : createMappers(dims)) { + createIndexAndAssertScriptScore(mapper, spaceType, scoreFunction, dims, queryVector, true); + createIndexAndAssertScriptScore(mapper, spaceType, scoreFunction, dims, queryVector, false); + } + } + + private void testKNNScriptScoreOnBinaryIndex(SpaceType spaceType) throws Exception { + final int dims = randomIntBetween(2, 10) * 8; + final float[] queryVector = randomVector(dims, VectorDataType.BINARY); + final BiFunction scoreFunction = getScoreFunction(spaceType, queryVector); + + // Test when knn is enabled and engine is Faiss + for (String mapper : createBinaryIndexMappers(dims)) { + createIndexAndAssertScriptScore(mapper, spaceType, scoreFunction, dims, queryVector, true, true, VectorDataType.BINARY); + createIndexAndAssertScriptScore(mapper, spaceType, scoreFunction, dims, queryVector, false, true, VectorDataType.BINARY); + } + + // Test when knn is disabled and engine is default(Nmslib) + createIndexAndAssertScriptScore( + createKnnIndexMapping(FIELD_NAME, dims, VectorDataType.BINARY), + spaceType, + scoreFunction, + dims, + queryVector, + true, + false, + VectorDataType.BINARY + ); + createIndexAndAssertScriptScore( + createKnnIndexMapping(FIELD_NAME, dims, VectorDataType.BINARY), + spaceType, + scoreFunction, + dims, + queryVector, + false, + false, + VectorDataType.BINARY + ); + } + + private void createIndexAndAssertScriptScore( + String mapper, + SpaceType spaceType, + BiFunction scoreFunction, + int dimensions, + float[] queryVector, + boolean dense + ) throws Exception { + createIndexAndAssertScriptScore(mapper, spaceType, scoreFunction, dimensions, queryVector, dense, true, VectorDataType.FLOAT); + } + + private void createIndexAndAssertScriptScore( + String mapper, + SpaceType spaceType, + BiFunction scoreFunction, + int dimensions, + float[] queryVector, + boolean dense, + boolean enableKnn, + VectorDataType vectorDataType + ) throws Exception { + /* + * Create knn index and populate data + */ + Settings settings = Settings.builder().put("number_of_shards", 1).put("number_of_replicas", 0).put("index.knn", enableKnn).build(); + createKnnIndex(INDEX_NAME, settings, mapper); + try { + final int numDocsWithField = randomIntBetween(4, 10); + Map dataset = createDataset( + v -> scoreFunction.apply(queryVector, v), + dimensions, + numDocsWithField, + dense, + vectorDataType + ); + final float[] dummyVector = new float[1]; + dataset.forEach((k, v) -> { + final float[] vector = (v != null) ? v.getVector() : dummyVector; + ExceptionsHelper.catchAsRuntimeException(() -> addKnnDoc(INDEX_NAME, k, (v != null) ? FIELD_NAME : "dummy", vector)); + }); + + /** + * Construct Search Request + */ + QueryBuilder qb = new MatchAllQueryBuilder(); + Map params = new HashMap<>(); + /* + * params": { + * "field": FIELD_NAME, + * "vector": queryVector + * } + */ + params.put("field", FIELD_NAME); + params.put("query_value", queryVector); + params.put("space_type", spaceType.getValue()); + Request request = constructKNNScriptQueryRequest(INDEX_NAME, qb, params, numDocsWithField); + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + + List results = parseSearchResponse(EntityUtils.toString(response.getEntity()), FIELD_NAME); + assertTrue(results.stream().allMatch(r -> dataset.get(r.getDocId()).equals(r))); + } finally { + deleteKNNIndex(INDEX_NAME); + } + } +} diff --git a/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java b/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java new file mode 100644 index 000000000..91ee89aeb --- /dev/null +++ b/src/test/java/org/opensearch/knn/integ/ModeAndCompressionIT.java @@ -0,0 +1,508 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.integ; + +import lombok.SneakyThrows; +import org.apache.http.util.EntityUtils; +import org.junit.Assert; +import org.opensearch.client.Request; +import org.opensearch.client.Response; +import org.opensearch.client.ResponseException; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.rest.RestStatus; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; +import org.opensearch.knn.index.query.parser.RescoreParser; + +import java.util.List; + +import static org.opensearch.knn.common.KNNConstants.COMPRESSION_LEVEL_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.FAISS_NAME; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.KNN_METHOD; +import static org.opensearch.knn.common.KNNConstants.METHOD_IVF; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_SEARCH; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NLIST_DEFAULT; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NPROBES; +import static org.opensearch.knn.common.KNNConstants.MODEL_DESCRIPTION; +import static org.opensearch.knn.common.KNNConstants.MODE_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.NAME; +import static org.opensearch.knn.common.KNNConstants.TRAIN_FIELD_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.TRAIN_INDEX_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; +import static org.opensearch.knn.index.mapper.KNNVectorFieldMapper.MAPPING_COMPRESSION_NAMES_ARRAY; + +public class ModeAndCompressionIT extends KNNRestTestCase { + + private static final String TRAINING_INDEX_NAME = "training_index"; + private static final String TRAINING_FIELD_NAME = "training_field"; + private static final int TRAINING_VECS = 20; + + private static final int DIMENSION = 16; + private static final int NUM_DOCS = 20; + private static final int K = NUM_DOCS; + private final static float[] TEST_VECTOR = new float[] { + 1.0f, + 2.0f, + 1.0f, + 2.0f, + 1.0f, + 2.0f, + 1.0f, + 2.0f, + 1.0f, + 2.0f, + 1.0f, + 2.0f, + 1.0f, + 2.0f, + 1.0f, + 2.0f }; + + private static final String[] COMPRESSION_LEVELS = new String[] { + CompressionLevel.x2.getName(), + CompressionLevel.x4.getName(), + CompressionLevel.x8.getName(), + CompressionLevel.x16.getName(), + CompressionLevel.x32.getName() }; + + @SneakyThrows + public void testIndexCreation_whenInvalid_thenFail() { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", DIMENSION) + .field(VECTOR_DATA_TYPE_FIELD, "byte") + .field(MODE_PARAMETER, "on_disk") + .field(COMPRESSION_LEVEL_PARAMETER, "16x") + .endObject() + .endObject() + .endObject(); + String mapping2 = builder.toString(); + expectThrows(ResponseException.class, () -> createKnnIndex(INDEX_NAME, mapping2)); + + builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", DIMENSION) + .field(MODE_PARAMETER, "on_disk") + .field(COMPRESSION_LEVEL_PARAMETER, "8x") + .endObject() + .endObject() + .endObject(); + String mapping3 = builder.toString(); + expectThrows(ResponseException.class, () -> createKnnIndex(INDEX_NAME, mapping3)); + + builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", DIMENSION) + .field(MODE_PARAMETER, "on_disk1222") + .endObject() + .endObject() + .endObject(); + String mapping4 = builder.toString(); + expectThrows(ResponseException.class, () -> createKnnIndex(INDEX_NAME, mapping4)); + } + + @SneakyThrows + public void testIndexCreation_whenValid_ThenSucceed() { + XContentBuilder builder; + for (String compressionLevel : COMPRESSION_LEVELS) { + String indexName = INDEX_NAME + compressionLevel; + builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", DIMENSION) + .field(COMPRESSION_LEVEL_PARAMETER, compressionLevel) + .endObject() + .endObject() + .endObject(); + String mapping = builder.toString(); + validateIndex(indexName, mapping); + logger.info("Compression level {}", compressionLevel); + validateSearch( + indexName, + METHOD_PARAMETER_EF_SEARCH, + KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH, + compressionLevel, + Mode.NOT_CONFIGURED.getName() + ); + } + + for (String compressionLevel : COMPRESSION_LEVELS) { + for (String mode : Mode.NAMES_ARRAY) { + String indexName = INDEX_NAME + compressionLevel + "_" + mode; + builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", DIMENSION) + .field(MODE_PARAMETER, mode) + .field(COMPRESSION_LEVEL_PARAMETER, compressionLevel) + .endObject() + .endObject() + .endObject(); + String mapping = builder.toString(); + validateIndex(indexName, mapping); + logger.info("Compression level {}", compressionLevel); + validateSearch( + indexName, + METHOD_PARAMETER_EF_SEARCH, + KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH, + compressionLevel, + mode + ); + } + } + + for (String mode : Mode.NAMES_ARRAY) { + String indexName = INDEX_NAME + mode; + builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", DIMENSION) + .field(MODE_PARAMETER, mode) + .endObject() + .endObject() + .endObject(); + String mapping = builder.toString(); + validateIndex(indexName, mapping); + logger.info("Compression level {}", CompressionLevel.NOT_CONFIGURED.getName()); + validateSearch( + indexName, + METHOD_PARAMETER_EF_SEARCH, + KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH, + CompressionLevel.NOT_CONFIGURED.getName(), + mode + ); + } + } + + @SneakyThrows + public void testDeletedDocsWithSegmentMerge_whenValid_ThenSucceed() { + XContentBuilder builder; + CompressionLevel compressionLevel = CompressionLevel.x32; + String indexName = INDEX_NAME + compressionLevel; + builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", DIMENSION) + .field(COMPRESSION_LEVEL_PARAMETER, compressionLevel.getName()) + .field(MODE_PARAMETER, Mode.ON_DISK.getName()) + .endObject() + .endObject() + .endObject(); + String mapping = builder.toString(); + validateIndexWithDeletedDocs(indexName, mapping); + validateGreenIndex(indexName); + } + + @SneakyThrows + public void testCompressionIndexWithNonVectorFieldsSegment_whenValid_ThenSucceed() { + CompressionLevel compressionLevel = CompressionLevel.x32; + String indexName = INDEX_NAME + compressionLevel; + try ( + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", DIMENSION) + .field(COMPRESSION_LEVEL_PARAMETER, compressionLevel.getName()) + .field(MODE_PARAMETER, Mode.ON_DISK.getName()) + .endObject() + .endObject() + .endObject() + ) { + String mapping = builder.toString(); + Settings indexSettings = buildKNNIndexSettings(0); + createKnnIndex(indexName, indexSettings, mapping); + // since we are going to delete a document, so its better to have 1 more extra doc so that we can re-use some tests + addKNNDocs(indexName, FIELD_NAME, DIMENSION, 0, NUM_DOCS + 1); + addNonKNNDoc(indexName, String.valueOf(NUM_DOCS + 2), FIELD_NAME_NON_KNN, "Hello world"); + deleteKnnDoc(indexName, "0"); + validateGreenIndex(indexName); + validateSearch( + indexName, + METHOD_PARAMETER_EF_SEARCH, + KNNSettings.INDEX_KNN_DEFAULT_ALGO_PARAM_EF_SEARCH, + compressionLevel.getName(), + Mode.ON_DISK.getName() + ); + } + } + + @SneakyThrows + public void testTraining_whenInvalid_thenFail() { + setupTrainingIndex(); + String modelId = "test"; + XContentBuilder builder1 = XContentFactory.jsonBuilder() + .startObject() + .field(TRAIN_INDEX_PARAMETER, TRAINING_INDEX_NAME) + .field(TRAIN_FIELD_PARAMETER, TRAINING_FIELD_NAME) + .field(KNNConstants.DIMENSION, DIMENSION) + .startObject(KNN_METHOD) + .field(NAME, METHOD_IVF) + .field(KNN_ENGINE, FAISS_NAME) + .endObject() + .field(MODEL_DESCRIPTION, "") + .field(MODE_PARAMETER, Mode.ON_DISK) + .endObject(); + expectThrows(ResponseException.class, () -> trainModel(modelId, builder1)); + + XContentBuilder builder2 = XContentFactory.jsonBuilder() + .startObject() + .field(TRAIN_INDEX_PARAMETER, TRAINING_INDEX_NAME) + .field(TRAIN_FIELD_PARAMETER, TRAINING_FIELD_NAME) + .field(KNNConstants.DIMENSION, DIMENSION) + .field(VECTOR_DATA_TYPE_FIELD, "binary") + .field(MODEL_DESCRIPTION, "") + .field(MODE_PARAMETER, Mode.ON_DISK) + .endObject(); + expectThrows(ResponseException.class, () -> trainModel(modelId, builder2)); + } + + @SneakyThrows + public void testTraining_whenValid_thenSucceed() { + setupTrainingIndex(); + XContentBuilder builder; + for (String compressionLevel : MAPPING_COMPRESSION_NAMES_ARRAY) { + if (compressionLevel.equals("4x")) { + continue; + } + String indexName = INDEX_NAME + compressionLevel; + String modelId = indexName; + builder = XContentFactory.jsonBuilder() + .startObject() + .field(TRAIN_INDEX_PARAMETER, TRAINING_INDEX_NAME) + .field(TRAIN_FIELD_PARAMETER, TRAINING_FIELD_NAME) + .field(KNNConstants.DIMENSION, DIMENSION) + .field(MODEL_DESCRIPTION, "") + .field(COMPRESSION_LEVEL_PARAMETER, compressionLevel) + .endObject(); + validateTraining(modelId, builder); + builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("model_id", modelId) + .endObject() + .endObject() + .endObject(); + String mapping = builder.toString(); + validateIndex(indexName, mapping); + validateSearch( + indexName, + METHOD_PARAMETER_NPROBES, + METHOD_PARAMETER_NLIST_DEFAULT, + compressionLevel, + Mode.NOT_CONFIGURED.getName() + ); + deleteKNNIndex(indexName); + } + for (String mode : Mode.NAMES_ARRAY) { + if (mode == null) { + continue; + } + mode = mode.toLowerCase(); + String indexName = INDEX_NAME + mode; + String modelId = indexName; + builder = XContentFactory.jsonBuilder() + .startObject() + .field(TRAIN_INDEX_PARAMETER, TRAINING_INDEX_NAME) + .field(TRAIN_FIELD_PARAMETER, TRAINING_FIELD_NAME) + .field(KNNConstants.DIMENSION, DIMENSION) + .field(MODEL_DESCRIPTION, "") + .field(MODE_PARAMETER, mode) + .endObject(); + validateTraining(modelId, builder); + builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("model_id", modelId) + .endObject() + .endObject() + .endObject(); + String mapping = builder.toString(); + validateIndex(indexName, mapping); + validateSearch( + indexName, + METHOD_PARAMETER_NPROBES, + METHOD_PARAMETER_NLIST_DEFAULT, + CompressionLevel.NOT_CONFIGURED.getName(), + mode + ); + deleteKNNIndex(indexName); + } + } + + @SneakyThrows + private void validateIndex(String indexName, String mapping) { + createKnnIndex(indexName, mapping); + addKNNDocs(indexName, FIELD_NAME, DIMENSION, 0, NUM_DOCS); + forceMergeKnnIndex(indexName, 1); + } + + @SneakyThrows + private void validateIndexWithDeletedDocs(String indexName, String mapping) { + createKnnIndex(indexName, mapping); + addKNNDocs(indexName, FIELD_NAME, DIMENSION, 0, NUM_DOCS); + refreshIndex(indexName); + // this will simulate the deletion of the docs + addKNNDocs(indexName, FIELD_NAME, DIMENSION, 0, NUM_DOCS); + refreshIndex(indexName); + forceMergeKnnIndex(indexName, 1); + refreshIndex(indexName); + } + + @SneakyThrows + private void validateGreenIndex(String indexName) { + Request request = new Request("GET", "/_cat/indices/" + indexName + "?format=csv"); + Response response = client().performRequest(request); + assertOK(response); + assertEquals( + "The status of index " + indexName + " is not green", + "green", + new String(response.getEntity().getContent().readAllBytes()).split("\n")[0].split(" ")[0] + ); + + } + + @SneakyThrows + private void setupTrainingIndex() { + createBasicKnnIndex(TRAINING_INDEX_NAME, TRAINING_FIELD_NAME, DIMENSION); + bulkIngestRandomVectors(TRAINING_INDEX_NAME, TRAINING_FIELD_NAME, TRAINING_VECS, DIMENSION); + } + + @SneakyThrows + private void validateTraining(String modelId, XContentBuilder builder) { + Response trainResponse = trainModel(modelId, builder); + assertEquals(RestStatus.OK, RestStatus.fromCode(trainResponse.getStatusLine().getStatusCode())); + assertTrainingSucceeds(modelId, 360, 1000); + } + + @SneakyThrows + private void validateSearch( + String indexName, + String methodParameterName, + int methodParameterValue, + String compressionLevelString, + String mode + ) { + // Basic search + Response response = searchKNNIndex( + indexName, + XContentFactory.jsonBuilder() + .startObject() + .startObject("query") + .startObject("knn") + .startObject(FIELD_NAME) + .field("vector", TEST_VECTOR) + .field("k", K) + .startObject(METHOD_PARAMETER) + .field(methodParameterName, methodParameterValue) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(), + K + ); + assertOK(response); + String responseBody = EntityUtils.toString(response.getEntity()); + List knnResults = parseSearchResponseScore(responseBody, FIELD_NAME); + assertEquals(K, knnResults.size()); + + // Do exact search and gather right scores for the documents + Response exactSearchResponse = searchKNNIndex( + indexName, + XContentFactory.jsonBuilder() + .startObject() + .startObject("query") + .startObject("script_score") + .startObject("query") + .field("match_all") + .startObject() + .endObject() + .endObject() + .startObject("script") + .field("source", "knn_score") + .field("lang", "knn") + .startObject("params") + .field("field", FIELD_NAME) + .field("query_value", TEST_VECTOR) + .field("space_type", SpaceType.L2.getValue()) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(), + K + ); + assertOK(exactSearchResponse); + String exactSearchResponseBody = EntityUtils.toString(exactSearchResponse.getEntity()); + List exactSearchKnnResults = parseSearchResponseScore(exactSearchResponseBody, FIELD_NAME); + assertEquals(NUM_DOCS, exactSearchKnnResults.size()); + + if (CompressionLevel.x4.getName().equals(compressionLevelString) == false && Mode.ON_DISK.getName().equals(mode)) { + Assert.assertEquals(exactSearchKnnResults, knnResults); + } + + // Search with rescore + response = searchKNNIndex( + indexName, + XContentFactory.jsonBuilder() + .startObject() + .startObject("query") + .startObject("knn") + .startObject(FIELD_NAME) + .field("vector", TEST_VECTOR) + .field("k", K) + .startObject(RescoreParser.RESCORE_PARAMETER) + .field(RescoreParser.RESCORE_OVERSAMPLE_PARAMETER, 2.0f) + .endObject() + .startObject(METHOD_PARAMETER) + .field(methodParameterName, methodParameterValue) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(), + K + ); + assertOK(response); + responseBody = EntityUtils.toString(response.getEntity()); + knnResults = parseSearchResponseScore(responseBody, FIELD_NAME); + assertEquals(K, knnResults.size()); + if (CompressionLevel.x4.getName().equals(compressionLevelString) == false && Mode.ON_DISK.getName().equals(mode)) { + Assert.assertEquals(exactSearchKnnResults, knnResults); + } + } +} diff --git a/src/test/java/org/opensearch/knn/integ/NestedSearchBinaryIT.java b/src/test/java/org/opensearch/knn/integ/NestedSearchBinaryIT.java new file mode 100644 index 000000000..bb3767370 --- /dev/null +++ b/src/test/java/org/opensearch/knn/integ/NestedSearchBinaryIT.java @@ -0,0 +1,156 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.integ; + +import lombok.SneakyThrows; +import lombok.extern.log4j.Log4j2; +import org.apache.http.util.EntityUtils; +import org.junit.After; +import org.opensearch.client.Response; +import org.opensearch.common.settings.Settings; +import org.opensearch.knn.KNNJsonIndexMappingsBuilder; +import org.opensearch.knn.KNNJsonQueryBuilder; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.NestedKnnDocBuilder; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; + +import java.util.List; + +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; + +@Log4j2 +public class NestedSearchBinaryIT extends KNNRestTestCase { + @After + public void cleanUp() { + try { + deleteKNNIndex(INDEX_NAME); + } catch (Exception e) { + log.error(e); + } + } + + @SneakyThrows + public void testNestedSearchWithFaissHnswBinary_whenKIsTwo_thenReturnTwoResults() { + String nestedFieldName = "nested"; + createKnnBinaryIndexWithNestedField(INDEX_NAME, nestedFieldName, FIELD_NAME, 16, KNNEngine.FAISS); + + int totalDocCount = 15; + for (byte i = 0; i < totalDocCount; i++) { + String doc = NestedKnnDocBuilder.create(nestedFieldName) + .addVectors(FIELD_NAME, new Byte[] { i, i }, new Byte[] { i, i }) + .build(); + addKnnDoc(INDEX_NAME, String.valueOf(i), doc); + } + + refreshIndex(INDEX_NAME); + forceMergeKnnIndex(INDEX_NAME); + + Float[] queryVector = { 14f, 14f }; + String query = KNNJsonQueryBuilder.builder() + .nestedFieldName(nestedFieldName) + .fieldName(FIELD_NAME) + .vector(queryVector) + .k(2) + .build() + .getQueryString(); + Response response = searchKNNIndex(INDEX_NAME, query, 2); + String entity = EntityUtils.toString(response.getEntity()); + + assertEquals(2, parseHits(entity)); + assertEquals(2, parseTotalSearchHits(entity)); + assertEquals("14", parseIds(entity).get(0)); + assertNotEquals("14", parseIds(entity).get(1)); + } + + /** + * { + * "query": { + * "nested": { + * "path": "test_nested", + * "query": { + * "knn": { + * "test_nested.test_vector": { + * "vector": [ + * 1, 1, 1 + * ], + * "k": 3, + * "filter": { + * "term": { + * "parking": "true" + * } + * } + * } + * } + * } + * } + * } + * } + * + */ + @SneakyThrows + public void testNestedSearchWithFaissHnswBinary_whenDoingExactSearch_thenReturnCorrectResults() { + String nestedFieldName = "nested"; + String filterFieldName = "parking"; + createKnnBinaryIndexWithNestedField(INDEX_NAME, nestedFieldName, FIELD_NAME, 24, KNNEngine.FAISS); + + for (byte i = 1; i < 4; i++) { + String doc = NestedKnnDocBuilder.create(nestedFieldName) + .addVectors(FIELD_NAME, new Byte[] { i, i, i }, new Byte[] { i, i, i }, new Byte[] { i, i, i }) + .addTopLevelField(filterFieldName, i % 2 == 1 ? "true" : "false") + .build(); + addKnnDoc(INDEX_NAME, String.valueOf(i), doc); + } + refreshIndex(INDEX_NAME); + forceMergeKnnIndex(INDEX_NAME); + + // Make it as an exact search by setting the threshold larger than size of filteredIds(6) + updateIndexSettings(INDEX_NAME, Settings.builder().put(KNNSettings.ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD, 100)); + + Float[] queryVector = { 3f, 3f, 3f }; + String query = KNNJsonQueryBuilder.builder() + .nestedFieldName(nestedFieldName) + .fieldName(FIELD_NAME) + .vector(queryVector) + .k(3) + .filterFieldName(filterFieldName) + .filterValue("true") + .build() + .getQueryString(); + Response response = searchKNNIndex(INDEX_NAME, query, 3); + String entity = EntityUtils.toString(response.getEntity()); + List docIds = parseIds(entity); + assertEquals(2, docIds.size()); + assertEquals("3", docIds.get(0)); + assertEquals("1", docIds.get(1)); + assertEquals(2, parseTotalSearchHits(entity)); + } + + private void createKnnBinaryIndexWithNestedField( + final String indexName, + final String nestedFieldName, + final String fieldName, + final int dimension, + final KNNEngine knnEngine + ) throws Exception { + KNNJsonIndexMappingsBuilder.Method method = KNNJsonIndexMappingsBuilder.Method.builder() + .methodName(METHOD_HNSW) + .engine(knnEngine.getName()) + .build(); + + String knnIndexMapping = KNNJsonIndexMappingsBuilder.builder() + .nestedFieldName(nestedFieldName) + .fieldName(fieldName) + .dimension(dimension) + .vectorDataType(VectorDataType.BINARY.getValue()) + .method(method) + .build() + .getIndexMapping(); + + createKnnIndex(indexName, knnIndexMapping); + } +} diff --git a/src/test/java/org/opensearch/knn/integ/NestedSearchByteIT.java b/src/test/java/org/opensearch/knn/integ/NestedSearchByteIT.java new file mode 100644 index 000000000..f7b51c1aa --- /dev/null +++ b/src/test/java/org/opensearch/knn/integ/NestedSearchByteIT.java @@ -0,0 +1,156 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.integ; + +import lombok.SneakyThrows; +import lombok.extern.log4j.Log4j2; +import org.apache.http.util.EntityUtils; +import org.junit.After; +import org.opensearch.client.Response; +import org.opensearch.common.settings.Settings; +import org.opensearch.knn.KNNJsonIndexMappingsBuilder; +import org.opensearch.knn.KNNJsonQueryBuilder; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.NestedKnnDocBuilder; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; + +import java.util.List; + +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; + +@Log4j2 +public class NestedSearchByteIT extends KNNRestTestCase { + @After + public void cleanUp() { + try { + deleteKNNIndex(INDEX_NAME); + } catch (Exception e) { + log.error(e); + } + } + + @SneakyThrows + public void testNestedSearchWithFaissHnswByte_whenKIsTwo_thenReturnTwoResults() { + String nestedFieldName = "nested"; + createKnnByteIndexWithNestedField(INDEX_NAME, nestedFieldName, FIELD_NAME, 2, KNNEngine.FAISS); + + int totalDocCount = 15; + for (byte i = 0; i < totalDocCount; i++) { + String doc = NestedKnnDocBuilder.create(nestedFieldName) + .addVectors(FIELD_NAME, new Byte[] { i, i }, new Byte[] { i, i }) + .build(); + addKnnDoc(INDEX_NAME, String.valueOf(i), doc); + } + + refreshIndex(INDEX_NAME); + forceMergeKnnIndex(INDEX_NAME); + + Byte[] queryVector = { 14, 14 }; + String query = KNNJsonQueryBuilder.builder() + .nestedFieldName(nestedFieldName) + .fieldName(FIELD_NAME) + .vector(queryVector) + .k(2) + .build() + .getQueryString(); + Response response = searchKNNIndex(INDEX_NAME, query, 2); + String entity = EntityUtils.toString(response.getEntity()); + + assertEquals(2, parseHits(entity)); + assertEquals(2, parseTotalSearchHits(entity)); + assertEquals("14", parseIds(entity).get(0)); + assertNotEquals("14", parseIds(entity).get(1)); + } + + /** + * { + * "query": { + * "nested": { + * "path": "test_nested", + * "query": { + * "knn": { + * "test_nested.test_vector": { + * "vector": [ + * 1, 1, 1 + * ], + * "k": 3, + * "filter": { + * "term": { + * "parking": "true" + * } + * } + * } + * } + * } + * } + * } + * } + * + */ + @SneakyThrows + public void testNestedSearchWithFaissHnswByte_whenDoingExactSearch_thenReturnCorrectResults() { + String nestedFieldName = "nested"; + String filterFieldName = "parking"; + createKnnByteIndexWithNestedField(INDEX_NAME, nestedFieldName, FIELD_NAME, 3, KNNEngine.FAISS); + + for (byte i = 1; i < 4; i++) { + String doc = NestedKnnDocBuilder.create(nestedFieldName) + .addVectors(FIELD_NAME, new Byte[] { i, i, i }, new Byte[] { i, i, i }, new Byte[] { i, i, i }) + .addTopLevelField(filterFieldName, i % 2 == 1 ? "true" : "false") + .build(); + addKnnDoc(INDEX_NAME, String.valueOf(i), doc); + } + refreshIndex(INDEX_NAME); + forceMergeKnnIndex(INDEX_NAME); + + // Make it as an exact search by setting the threshold larger than size of filteredIds(6) + updateIndexSettings(INDEX_NAME, Settings.builder().put(KNNSettings.ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD, 100)); + + Byte[] queryVector = { 3, 3, 3 }; + String query = KNNJsonQueryBuilder.builder() + .nestedFieldName(nestedFieldName) + .fieldName(FIELD_NAME) + .vector(queryVector) + .k(3) + .filterFieldName(filterFieldName) + .filterValue("true") + .build() + .getQueryString(); + Response response = searchKNNIndex(INDEX_NAME, query, 3); + String entity = EntityUtils.toString(response.getEntity()); + List docIds = parseIds(entity); + assertEquals(2, docIds.size()); + assertEquals("3", docIds.get(0)); + assertEquals("1", docIds.get(1)); + assertEquals(2, parseTotalSearchHits(entity)); + } + + private void createKnnByteIndexWithNestedField( + final String indexName, + final String nestedFieldName, + final String fieldName, + final int dimension, + final KNNEngine knnEngine + ) throws Exception { + KNNJsonIndexMappingsBuilder.Method method = KNNJsonIndexMappingsBuilder.Method.builder() + .methodName(METHOD_HNSW) + .engine(knnEngine.getName()) + .build(); + + String knnIndexMapping = KNNJsonIndexMappingsBuilder.builder() + .nestedFieldName(nestedFieldName) + .fieldName(fieldName) + .dimension(dimension) + .vectorDataType(VectorDataType.BYTE.getValue()) + .method(method) + .build() + .getIndexMapping(); + + createKnnIndex(indexName, knnIndexMapping); + } +} diff --git a/src/test/java/org/opensearch/knn/integ/NestedSearchIT.java b/src/test/java/org/opensearch/knn/integ/NestedSearchIT.java new file mode 100644 index 000000000..12504b5d8 --- /dev/null +++ b/src/test/java/org/opensearch/knn/integ/NestedSearchIT.java @@ -0,0 +1,356 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.integ; + +import lombok.SneakyThrows; +import org.apache.http.util.EntityUtils; +import org.junit.After; +import org.opensearch.client.Request; +import org.opensearch.client.Response; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.rest.RestStatus; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.NestedKnnDocBuilder; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.engine.KNNEngine; + +import java.io.IOException; +import java.util.List; + +import static org.opensearch.knn.common.Constants.FIELD_FILTER; +import static org.opensearch.knn.common.Constants.FIELD_TERM; +import static org.opensearch.knn.common.KNNConstants.DIMENSION; +import static org.opensearch.knn.common.KNNConstants.K; +import static org.opensearch.knn.common.KNNConstants.KNN; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.KNN_METHOD; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_M; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; +import static org.opensearch.knn.common.KNNConstants.MIN_SCORE; +import static org.opensearch.knn.common.KNNConstants.NAME; +import static org.opensearch.knn.common.KNNConstants.PARAMETERS; +import static org.opensearch.knn.common.KNNConstants.PATH; +import static org.opensearch.knn.common.KNNConstants.QUERY; +import static org.opensearch.knn.common.KNNConstants.TYPE; +import static org.opensearch.knn.common.KNNConstants.TYPE_KNN_VECTOR; +import static org.opensearch.knn.common.KNNConstants.TYPE_NESTED; +import static org.opensearch.knn.common.KNNConstants.VECTOR; +import static org.opensearch.knn.index.query.parser.RescoreParser.RESCORE_OVERSAMPLE_PARAMETER; +import static org.opensearch.knn.index.query.parser.RescoreParser.RESCORE_PARAMETER; + +public class NestedSearchIT extends KNNRestTestCase { + private static final String INDEX_NAME = "test-index-nested-search"; + private static final String FIELD_NAME_NESTED = "test_nested"; + private static final String FIELD_NAME_VECTOR = "test_vector"; + private static final String FIELD_NAME_PARKING = "parking"; + private static final String FIELD_VALUE_TRUE = "true"; + private static final String FIELD_VALUE_FALSE = "false"; + private static final String PROPERTIES_FIELD = "properties"; + private static final int EF_CONSTRUCTION = 128; + private static final int M = 16; + private static final SpaceType SPACE_TYPE = SpaceType.L2; + + @After + @SneakyThrows + public final void cleanUp() { + deleteKNNIndex(INDEX_NAME); + } + + @SneakyThrows + public void testNestedSearchWithLucene_whenKIsTwo_thenReturnTwoResults() { + createKnnIndex(2, KNNEngine.LUCENE.getName()); + + int totalDocCount = 15; + for (int i = 0; i < totalDocCount; i++) { + String doc = NestedKnnDocBuilder.create(FIELD_NAME_NESTED) + .addVectors(FIELD_NAME_VECTOR, new Float[] { (float) i, (float) i }, new Float[] { (float) i, (float) i }) + .build(); + addKnnDoc(INDEX_NAME, String.valueOf(i), doc); + } + + refreshIndex(INDEX_NAME); + forceMergeKnnIndex(INDEX_NAME); + + Float[] queryVector = { 14f, 14f }; + Response response = queryNestedField(INDEX_NAME, 2, queryVector); + String entity = EntityUtils.toString(response.getEntity()); + assertEquals(2, parseHits(entity)); + assertEquals(2, parseTotalSearchHits(entity)); + assertEquals("14", parseIds(entity).get(0)); + assertEquals("13", parseIds(entity).get(1)); + } + + @SneakyThrows + public void testNestedSearchWithFaiss_whenKIsTwo_thenReturnTwoResults() { + createKnnIndex(2, KNNEngine.FAISS.getName()); + + int totalDocCount = 15; + for (int i = 0; i < totalDocCount; i++) { + String doc = NestedKnnDocBuilder.create(FIELD_NAME_NESTED) + .addVectors(FIELD_NAME_VECTOR, new Float[] { (float) i, (float) i }, new Float[] { (float) i, (float) i }) + .build(); + addKnnDoc(INDEX_NAME, String.valueOf(i), doc); + } + + refreshIndex(INDEX_NAME); + forceMergeKnnIndex(INDEX_NAME); + + Float[] queryVector = { 14f, 14f }; + Response response = queryNestedField(INDEX_NAME, 2, queryVector); + String entity = EntityUtils.toString(response.getEntity()); + assertEquals(2, parseHits(entity)); + assertEquals(2, parseTotalSearchHits(entity)); + assertEquals("14", parseIds(entity).get(0)); + assertEquals("13", parseIds(entity).get(1)); + } + + @SneakyThrows + public void testNestedSearchWithFaiss_whenRescoreEnabled_thenSucceed() { + createKnnIndex(2, KNNEngine.FAISS.getName()); + + int totalDocCount = 15; + for (int i = 0; i < totalDocCount; i++) { + String doc = NestedKnnDocBuilder.create(FIELD_NAME_NESTED) + .addVectors(FIELD_NAME_VECTOR, new Float[] { (float) i, (float) i }, new Float[] { (float) i, (float) i }) + .build(); + addKnnDoc(INDEX_NAME, String.valueOf(i), doc); + } + + refreshIndex(INDEX_NAME); + forceMergeKnnIndex(INDEX_NAME); + + Float[] queryVector = { 14f, 14f }; + Response response = queryNestedField(INDEX_NAME, 2, queryVector, null, null, null, 2.0f); + + String entity = EntityUtils.toString(response.getEntity()); + assertEquals(2, parseHits(entity)); + assertEquals(2, parseTotalSearchHits(entity)); + assertEquals("14", parseIds(entity).get(0)); + assertEquals("13", parseIds(entity).get(1)); + } + + /** + * { + * "query": { + * "nested": { + * "path": "test_nested", + * "query": { + * "knn": { + * "test_nested.test_vector": { + * "vector": [ + * 1, 1, 1 + * ], + * "k": 3, + * "filter": { + * "term": { + * "parking": "true" + * } + * } + * } + * } + * } + * } + * } + * } + * + */ + @SneakyThrows + public void testNestedSearchWithFaiss_whenDoingExactSearch_thenReturnCorrectResults() { + createKnnIndex(3, KNNEngine.FAISS.getName()); + + for (int i = 1; i < 4; i++) { + float value = (float) i; + String doc = NestedKnnDocBuilder.create(FIELD_NAME_NESTED) + .addVectors( + FIELD_NAME_VECTOR, + new Float[] { value, value, value }, + new Float[] { value, value, value }, + new Float[] { value, value, value } + ) + .addTopLevelField(FIELD_NAME_PARKING, i % 2 == 1 ? FIELD_VALUE_TRUE : FIELD_VALUE_FALSE) + .build(); + addKnnDoc(INDEX_NAME, String.valueOf(i), doc); + } + refreshIndex(INDEX_NAME); + forceMergeKnnIndex(INDEX_NAME); + + // Make it as an exact search by setting the threshold larger than size of filteredIds(6) + updateIndexSettings(INDEX_NAME, Settings.builder().put(KNNSettings.ADVANCED_FILTERED_EXACT_SEARCH_THRESHOLD, 100)); + + Float[] queryVector = { 3f, 3f, 3f }; + Response response = queryNestedField(INDEX_NAME, 3, queryVector, FIELD_NAME_PARKING, FIELD_VALUE_TRUE, null, null); + String entity = EntityUtils.toString(response.getEntity()); + List docIds = parseIds(entity); + assertEquals(2, docIds.size()); + assertEquals("3", docIds.get(0)); + assertEquals("1", docIds.get(1)); + assertEquals(2, parseTotalSearchHits(entity)); + } + + /** + * { + * "query": { + * "nested": { + * "path": "test_nested", + * "query": { + * "knn": { + * "test_nested.test_vector": { + * "vector": [ + * 1, 1, 1 + * ], + * "min_score": 0.00001, + * "filter": { + * "term": { + * "parking": "true" + * } + * } + * } + * } + * } + * } + * } + * } + * + */ + @SneakyThrows + public void testNestedWithFaiss_whenFilter_whenDoRadialSearch_thenReturnCorrectResults() { + createKnnIndex(3, KNNEngine.FAISS.getName()); + + for (int i = 1; i < 4; i++) { + float value = (float) i; + String doc = NestedKnnDocBuilder.create(FIELD_NAME_NESTED) + .addVectors( + FIELD_NAME_VECTOR, + new Float[] { value, value, value }, + new Float[] { value, value, value }, + new Float[] { value, value, value } + ) + .addTopLevelField(FIELD_NAME_PARKING, i % 2 == 1 ? FIELD_VALUE_TRUE : FIELD_VALUE_FALSE) + .build(); + addKnnDoc(INDEX_NAME, String.valueOf(i), doc); + } + refreshIndex(INDEX_NAME); + forceMergeKnnIndex(INDEX_NAME); + + Float[] queryVector = { 3f, 3f, 3f }; + Float minScore = 0.00001f; + Response response = queryNestedField(INDEX_NAME, null, queryVector, FIELD_NAME_PARKING, FIELD_VALUE_TRUE, minScore, null); + + String entity = EntityUtils.toString(response.getEntity()); + List docIds = parseIds(entity); + assertEquals(2, docIds.size()); + assertEquals("3", docIds.get(0)); + assertEquals("1", docIds.get(1)); + assertEquals(2, parseTotalSearchHits(entity)); + } + + /** + * { + * "properties": { + * "test_nested": { + * "type": "nested", + * "properties": { + * "test_vector": { + * "type": "knn_vector", + * "dimension": 3, + * "method": { + * "name": "hnsw", + * "space_type": "l2", + * "engine": "lucene", + * "parameters": { + * "ef_construction": 128, + * "m": 24 + * } + * } + * } + * } + * } + * } + * } + */ + private void createKnnIndex(final int dimension, final String engine) throws Exception { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES_FIELD) + .startObject(FIELD_NAME_NESTED) + .field(TYPE, TYPE_NESTED) + .startObject(PROPERTIES_FIELD) + .startObject(FIELD_NAME_VECTOR) + .field(TYPE, TYPE_KNN_VECTOR) + .field(DIMENSION, dimension) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, SPACE_TYPE) + .field(KNN_ENGINE, engine) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_M, M) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, EF_CONSTRUCTION) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + String mapping = builder.toString(); + createKnnIndex(INDEX_NAME, mapping); + } + + private Response queryNestedField(final String index, final int k, final Object[] vector) throws IOException { + return queryNestedField(index, k, vector, null, null, null, null); + } + + private Response queryNestedField( + final String index, + final Integer k, + final Object[] vector, + final String filterName, + final String filterValue, + final Float minScore, + final Float oversampleFactor + ) throws IOException { + XContentBuilder builder = XContentFactory.jsonBuilder().startObject().startObject(QUERY); + builder.startObject(TYPE_NESTED); + builder.field(PATH, FIELD_NAME_NESTED); + builder.startObject(QUERY).startObject(KNN).startObject(FIELD_NAME_NESTED + "." + FIELD_NAME_VECTOR); + builder.field(VECTOR, vector); + if (minScore != null) { + builder.field(MIN_SCORE, minScore); + } else if (k != null) { + builder.field(K, k); + } else { + throw new IllegalArgumentException("k or minScore must be provided in the query"); + } + if (filterName != null && filterValue != null) { + builder.startObject(FIELD_FILTER); + builder.startObject(FIELD_TERM); + builder.field(filterName, filterValue); + builder.endObject(); + builder.endObject(); + } + if (oversampleFactor != null) { + builder.startObject(RESCORE_PARAMETER); + builder.field(RESCORE_OVERSAMPLE_PARAMETER, oversampleFactor); + builder.endObject(); + } + + builder.endObject().endObject().endObject().endObject().endObject().endObject(); + + Request request = new Request("POST", "/" + index + "/_search"); + request.setJsonEntity(builder.toString()); + + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + + return response; + } +} diff --git a/src/test/java/org/opensearch/knn/integ/PainlessScriptFieldsIT.java b/src/test/java/org/opensearch/knn/integ/PainlessScriptFieldsIT.java new file mode 100644 index 000000000..85f980a25 --- /dev/null +++ b/src/test/java/org/opensearch/knn/integ/PainlessScriptFieldsIT.java @@ -0,0 +1,131 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.integ; + +import lombok.SneakyThrows; +import org.apache.http.util.EntityUtils; +import org.opensearch.client.Request; +import org.opensearch.client.Response; +import org.opensearch.core.rest.RestStatus; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.KNNResult; +import org.opensearch.knn.index.mapper.KNNVectorFieldMapper; +import org.opensearch.knn.integ.PainlessScriptHelper.MappingProperty; +import org.opensearch.script.Script; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static org.opensearch.knn.integ.PainlessScriptHelper.createMapping; + +// PainlesScriptScoreIT already tests every similarity methods with different field type. Hence, +// we don't have to recreate all tests for script_fields. From implementation point of view, +// it is clear if similarity method is supported by script_score, then same is applicable for script_fields +// provided script_fields context is supported. Hence, we test for one similarity method to verify that script_fields +// context is supported by this plugin. +public final class PainlessScriptFieldsIT extends KNNRestTestCase { + + private static final String NUMERIC_INDEX_FIELD_NAME = "price"; + + private void buildTestIndex(final Map knnDocuments) throws Exception { + List properties = buildMappingProperties(); + buildTestIndex(knnDocuments, properties); + } + + private void buildTestIndex(final Map knnDocuments, final List properties) throws Exception { + createKnnIndex(INDEX_NAME, createMapping(properties)); + for (Map.Entry data : knnDocuments.entrySet()) { + addKnnDoc(INDEX_NAME, data.getKey(), FIELD_NAME, data.getValue()); + } + } + + private Map getKnnVectorTestData() { + Map data = new HashMap<>(); + data.put("1", new Float[] { 100.0f, 1.0f }); + data.put("2", new Float[] { 99.0f, 2.0f }); + data.put("3", new Float[] { 97.0f, 3.0f }); + data.put("4", new Float[] { 98.0f, 4.0f }); + return data; + } + + private Map getCosineTestData() { + Map data = new HashMap<>(); + data.put("0", new Float[] { 1.0f, -1.0f }); + data.put("2", new Float[] { 1.0f, 1.0f }); + data.put("1", new Float[] { 1.0f, 0.0f }); + return data; + } + + /* + The doc['field'] will throw an error if field is missing from the mappings. + */ + private List buildMappingProperties() { + List properties = new ArrayList<>(); + properties.add(MappingProperty.builder().name(FIELD_NAME).type(KNNVectorFieldMapper.CONTENT_TYPE).dimension("2").build()); + properties.add(MappingProperty.builder().name(NUMERIC_INDEX_FIELD_NAME).type("integer").build()); + return properties; + } + + @SneakyThrows + public void testCosineSimilarity_whenUsedInScriptFields_thenExecutesScript() { + String source = String.format(Locale.ROOT, "1 + cosineSimilarity([2.0f, -2.0f], doc['%s'])", FIELD_NAME); + String scriptFieldName = "similarity"; + Request request = buildPainlessScriptFieldsRequest(source, 3, getCosineTestData(), scriptFieldName); + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + + List results = parseSearchResponseScriptFields(EntityUtils.toString(response.getEntity()), scriptFieldName); + assertEquals(3, results.size()); + + String[] expectedDocIDs = { "0", "1", "2" }; + for (int i = 0; i < results.size(); i++) { + assertEquals(expectedDocIDs[i], results.get(i).getDocId()); + } + deleteKNNIndex(INDEX_NAME); + } + + @SneakyThrows + public void testGetValue_whenUsedInScriptFields_thenReturnsDocValues() { + String source = String.format(Locale.ROOT, "doc['%s'].value[0]", FIELD_NAME); + String scriptFieldName = "doc_value_field"; + Map testData = getKnnVectorTestData(); + Request request = buildPainlessScriptFieldsRequest(source, testData.size(), testData, scriptFieldName); + + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + + List results = parseSearchResponseScriptFields(EntityUtils.toString(response.getEntity()), scriptFieldName); + assertEquals(testData.size(), results.size()); + + String[] expectedDocIDs = { "1", "2", "3", "4" }; + for (int i = 0; i < results.size(); i++) { + assertEquals(expectedDocIDs[i], results.get(i).getDocId()); + } + deleteKNNIndex(INDEX_NAME); + } + + private Request buildPainlessScriptFieldsRequest( + final String source, + final int size, + final Map documents, + final String scriptFieldName + ) throws Exception { + buildTestIndex(documents); + return constructScriptFieldsContextSearchRequest( + INDEX_NAME, + scriptFieldName, + Collections.emptyMap(), + Script.DEFAULT_SCRIPT_LANG, + source, + size, + Collections.emptyMap() + ); + } +} diff --git a/src/test/java/org/opensearch/knn/integ/PainlessScriptHelper.java b/src/test/java/org/opensearch/knn/integ/PainlessScriptHelper.java new file mode 100644 index 000000000..e53fc41e4 --- /dev/null +++ b/src/test/java/org/opensearch/knn/integ/PainlessScriptHelper.java @@ -0,0 +1,58 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.integ; + +import lombok.Builder; +import lombok.Getter; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.engine.KNNMethodContext; + +import java.io.IOException; +import java.util.List; +import java.util.Objects; + +public final class PainlessScriptHelper { + /** + * Utility to create a Index Mapping with multiple fields + */ + public static String createMapping(final List properties) throws IOException { + Objects.requireNonNull(properties); + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("properties"); + for (MappingProperty property : properties) { + XContentBuilder builder = xContentBuilder.startObject(property.getName()).field("type", property.getType()); + if (property.getDimension() != null) { + builder.field("dimension", property.getDimension()); + } + + if (property.getDocValues() != null) { + builder.field("doc_values", property.getDocValues()); + } + + if (property.getKnnMethodContext() != null) { + builder.startObject(KNNConstants.KNN_METHOD); + property.getKnnMethodContext().toXContent(builder, ToXContent.EMPTY_PARAMS); + builder.endObject(); + } + + builder.endObject(); + } + xContentBuilder.endObject().endObject(); + return xContentBuilder.toString(); + } + + @Getter + @Builder + final static class MappingProperty { + private final String name; + private final String type; + private String dimension; + private KNNMethodContext knnMethodContext; + private Boolean docValues; + } +} diff --git a/src/test/java/org/opensearch/knn/plugin/script/PainlessScriptIT.java b/src/test/java/org/opensearch/knn/integ/PainlessScriptScoreIT.java similarity index 76% rename from src/test/java/org/opensearch/knn/plugin/script/PainlessScriptIT.java rename to src/test/java/org/opensearch/knn/integ/PainlessScriptScoreIT.java index a8617896b..c8835f70f 100644 --- a/src/test/java/org/opensearch/knn/plugin/script/PainlessScriptIT.java +++ b/src/test/java/org/opensearch/knn/integ/PainlessScriptScoreIT.java @@ -3,53 +3,44 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.opensearch.knn.plugin.script; +package org.opensearch.knn.integ; +import lombok.SneakyThrows; +import org.opensearch.common.settings.Settings; import org.opensearch.knn.KNNRestTestCase; import org.opensearch.knn.KNNResult; -import org.opensearch.knn.index.KNNVectorFieldMapper; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.mapper.KNNVectorFieldMapper; import org.apache.http.util.EntityUtils; import org.opensearch.client.Request; import org.opensearch.client.Response; import org.opensearch.client.ResponseException; -import org.opensearch.common.Strings; -import org.opensearch.common.xcontent.XContentBuilder; -import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.index.query.MatchAllQueryBuilder; import org.opensearch.index.query.QueryBuilder; -import org.opensearch.rest.RestStatus; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.core.rest.RestStatus; +import org.opensearch.knn.integ.PainlessScriptHelper.MappingProperty; import org.opensearch.script.Script; -import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; -import java.util.Objects; -public class PainlessScriptIT extends KNNRestTestCase { +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.integ.PainlessScriptHelper.createMapping; + +public final class PainlessScriptScoreIT extends KNNRestTestCase { public static final int AGGREGATION_FIELD_NAME_MIN_LENGTH = 2; public static final int AGGREGATION_FIELD_NAME_MAX_LENGTH = 5; - private static String NUMERIC_INDEX_FIELD_NAME = "price"; - - /** - * Utility to create a Index Mapping with multiple fields - */ - protected String createMapping(List properties) throws IOException { - Objects.requireNonNull(properties); - XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("properties"); - for (MappingProperty property : properties) { - XContentBuilder builder = xContentBuilder.startObject(property.getName()).field("type", property.getType()); - if (property.getDimension() != null) { - builder.field("dimension", property.getDimension()); - } - builder.endObject(); - } - xContentBuilder.endObject().endObject(); - return Strings.toString(xContentBuilder); - } + private static final String NUMERIC_INDEX_FIELD_NAME = "price"; /* creates KnnIndex based on properties, we add single non-knn vector documents to verify whether actions @@ -57,6 +48,10 @@ protected String createMapping(List properties) throws IOExcept */ private void buildTestIndex(Map knnDocuments) throws Exception { List properties = buildMappingProperties(); + buildTestIndex(knnDocuments, properties); + } + + private void buildTestIndex(Map knnDocuments, List properties) throws Exception { createKnnIndex(INDEX_NAME, createMapping(properties)); for (Map.Entry data : knnDocuments.entrySet()) { addKnnDoc(INDEX_NAME, data.getKey(), FIELD_NAME, data.getValue()); @@ -121,8 +116,8 @@ private Map getCosineTestData() { */ private List buildMappingProperties() { List properties = new ArrayList<>(); - properties.add(new MappingProperty(FIELD_NAME, KNNVectorFieldMapper.CONTENT_TYPE).dimension("2")); - properties.add(new MappingProperty(NUMERIC_INDEX_FIELD_NAME, "integer")); + properties.add(MappingProperty.builder().name(FIELD_NAME).type(KNNVectorFieldMapper.CONTENT_TYPE).dimension("2").build()); + properties.add(MappingProperty.builder().name(NUMERIC_INDEX_FIELD_NAME).type("integer").build()); return properties; } @@ -137,7 +132,34 @@ public void testL2ScriptScoreFails() throws Exception { private Request buildPainlessScoreScriptRequest(String source, int size, Map documents) throws Exception { buildTestIndex(documents); QueryBuilder qb = new MatchAllQueryBuilder(); - return constructScriptScoreContextSearchRequest(INDEX_NAME, qb, Collections.emptyMap(), Script.DEFAULT_SCRIPT_LANG, source, size); + return constructScriptScoreContextSearchRequest( + INDEX_NAME, + qb, + Collections.emptyMap(), + Script.DEFAULT_SCRIPT_LANG, + source, + size, + Collections.emptyMap() + ); + } + + private Request buildPainlessScoreScriptRequest( + String source, + int size, + Map documents, + List properties + ) throws Exception { + buildTestIndex(documents, properties); + QueryBuilder qb = new MatchAllQueryBuilder(); + return constructScriptScoreContextSearchRequest( + INDEX_NAME, + qb, + Collections.emptyMap(), + Script.DEFAULT_SCRIPT_LANG, + source, + size, + Collections.emptyMap() + ); } private Request buildPainlessScriptedMetricRequest( @@ -506,32 +528,119 @@ public void testScriptedMetricIsSupported() throws Exception { deleteKNNIndex(INDEX_NAME); } - class MappingProperty { + public void testL2ScriptingWithLuceneBackedIndex() throws Exception { + List properties = new ArrayList<>(); + KNNMethodContext knnMethodContext = new KNNMethodContext( + KNNEngine.LUCENE, + SpaceType.DEFAULT, + new MethodComponentContext(METHOD_HNSW, Collections.emptyMap()) + ); + properties.add( + MappingProperty.builder() + .name(FIELD_NAME) + .type(KNNVectorFieldMapper.CONTENT_TYPE) + .dimension("2") + .knnMethodContext(knnMethodContext) + .docValues(randomBoolean()) + .build() + ); - private String name; - private String type; - private String dimension; + String source = String.format("1/(1 + l2Squared([1.0f, 1.0f], doc['%s']))", FIELD_NAME); + Request request = buildPainlessScoreScriptRequest(source, 3, getL2TestData(), properties); - MappingProperty(String name, String type) { - this.name = name; - this.type = type; - } + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + + List results = parseSearchResponse(EntityUtils.toString(response.getEntity()), FIELD_NAME); + assertEquals(3, results.size()); - MappingProperty dimension(String dimension) { - this.dimension = dimension; - return this; + String[] expectedDocIDs = { "2", "4", "3", "1" }; + for (int i = 0; i < results.size(); i++) { + assertEquals(expectedDocIDs[i], results.get(i).getDocId()); } + deleteKNNIndex(INDEX_NAME); + } - String getDimension() { - return dimension; + @SneakyThrows + public void testHammingPainlessScript_whenBinary_thenSuccess() { + int dimensions = 16; + String mappingForKnnDisabled = createKnnIndexMapping(FIELD_NAME, dimensions, VectorDataType.BINARY); + + // 0b00000001, 0b00000001 + String source = String.format("1/(1 + hamming([1.0f, 1.0f], doc['%s']))", FIELD_NAME); + + Map data = new HashMap<>(); + data.put("1", new Float[] { (float) 0b00000001, (float) 0b00000001 });// Hamming distance 0 + data.put("2", new Float[] { (float) 0b01101111, (float) 0b00000010 });// Hamming distance 6 + data.put("3", new Float[] { (float) 0b01100010, (float) 0b00000011 });// Hamming distance 5 + data.put("4", new Float[] { (float) 0b00000001, (float) 0b01001100 });// Hamming distance 4 + + Response response = buildIndexAndRunPainlessScript(source, 4, data, mappingForKnnDisabled, false); + List results = parseSearchResponse(EntityUtils.toString(response.getEntity()), FIELD_NAME); + assertEquals(4, results.size()); + + String[] expectedDocIDs = { "1", "4", "3", "2" }; + for (int i = 0; i < results.size(); i++) { + assertEquals(expectedDocIDs[i], results.get(i).getDocId()); } + } - String getName() { - return name; + @SneakyThrows + public void testPainlessScript_whenNonBinary_thenException() { + int dimensions = 2; + String mapping = createKnnIndexMapping(FIELD_NAME, dimensions); + String source = String.format("1/(1 + hamming([1.0f, 1.0f], doc['%s']))", FIELD_NAME); + Exception e = expectThrows( + ResponseException.class, + () -> buildIndexAndRunPainlessScript(source, 3, getKnnVectorTestData(), mapping, false) + ); + assertTrue(e.getMessage(), e.getMessage().contains("The data type should be binary")); + } + + @SneakyThrows + public void testNonPainlessScript_whenBinary_thenException() { + List functions = Arrays.asList("l2Squared", "lInfNorm", "l1Norm", "innerProduct", "cosineSimilarity"); + int dimensions = 16; + String mapping = createKnnIndexMapping(FIELD_NAME, dimensions, VectorDataType.BINARY); + for (String function : functions) { + String source = String.format(Locale.ROOT, "%s([1.0f, 1.0f], doc['%s'])", function, FIELD_NAME); + Exception e = expectThrows( + ResponseException.class, + () -> buildIndexAndRunPainlessScript(source, 3, getKnnVectorTestData(), mapping, false) + ); + assertTrue(e.getMessage(), e.getMessage().contains("The data type should be either float or byte")); } + } - String getType() { - return type; + private Response buildIndexAndRunPainlessScript( + final String source, + final int size, + Map documents, + final String mapper, + final boolean enableKnn + ) throws Exception { + /* + * Create knn index and populate data + */ + Settings settings = Settings.builder().put("number_of_shards", 1).put("number_of_replicas", 0).put("index.knn", enableKnn).build(); + createKnnIndex(INDEX_NAME, settings, mapper); + try { + for (Map.Entry data : documents.entrySet()) { + addKnnDoc(INDEX_NAME, data.getKey(), FIELD_NAME, data.getValue()); + } + QueryBuilder qb = new MatchAllQueryBuilder(); + Request request = constructScriptScoreContextSearchRequest( + INDEX_NAME, + qb, + Collections.emptyMap(), + Script.DEFAULT_SCRIPT_LANG, + source, + size, + Collections.emptyMap() + ); + return client().performRequest(request); + } finally { + deleteKNNIndex(INDEX_NAME); } } } diff --git a/src/test/java/org/opensearch/knn/integ/QFrameworkIT.java b/src/test/java/org/opensearch/knn/integ/QFrameworkIT.java new file mode 100644 index 000000000..f4ab5dc8a --- /dev/null +++ b/src/test/java/org/opensearch/knn/integ/QFrameworkIT.java @@ -0,0 +1,77 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.integ; + +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.index.engine.faiss.QFrameBitEncoder; + +import java.io.IOException; + +import static org.opensearch.knn.common.KNNConstants.FAISS_NAME; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.KNN_METHOD; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.NAME; +import static org.opensearch.knn.common.KNNConstants.PARAMETERS; + +public class QFrameworkIT extends KNNRestTestCase { + + private final static float[] TEST_VECTOR = new float[] { 1.0f, 2.0f }; + private final static int DIMENSION = 2; + private final static int K = 1; + + public void testBaseCase() throws IOException { + createTestIndex(4); + // TODO :- UnComment this once Search is Integrated and KNN_USE_LUCENE_VECTOR_FORMAT_ENABLED_SETTING is enabled + // addKnnDoc(INDEX_NAME, "1", FIELD_NAME, TEST_VECTOR); + // Response response = searchKNNIndex( + // // INDEX_NAME, + // // XContentFactory.jsonBuilder() + // // .startObject() + // // .startObject("query") + // // .startObject("knn") + // // .startObject(FIELD_NAME) + // // .field("vector", TEST_VECTOR) + // // .field("k", K) + // // .endObject() + // // .endObject() + // // .endObject() + // // .endObject(), + // // 1 + // // ); + // assertOK(response); + } + + private void createTestIndex(int bitCount) throws IOException { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", DIMENSION) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, FAISS_NAME) + .startObject(PARAMETERS) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, QFrameBitEncoder.NAME) + .startObject(PARAMETERS) + .field(QFrameBitEncoder.BITCOUNT_PARAM, bitCount) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + + String mapping = builder.toString(); + createKnnIndex(INDEX_NAME, mapping); + } +} diff --git a/src/test/java/org/opensearch/knn/integ/QueryParseIT.java b/src/test/java/org/opensearch/knn/integ/QueryParseIT.java new file mode 100644 index 000000000..bcaf6be12 --- /dev/null +++ b/src/test/java/org/opensearch/knn/integ/QueryParseIT.java @@ -0,0 +1,148 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.integ; + +import lombok.SneakyThrows; +import org.opensearch.client.Request; +import org.opensearch.client.ResponseException; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.knn.KNNRestTestCase; + +import java.io.IOException; +import java.util.Locale; + +import static org.opensearch.knn.index.query.parser.RescoreParser.RESCORE_OVERSAMPLE_PARAMETER; +import static org.opensearch.knn.index.query.parser.RescoreParser.RESCORE_PARAMETER; + +public class QueryParseIT extends KNNRestTestCase { + + private final static float[] TEST_VECTOR = new float[] { 1.0f, 2.0f }; + private final static int DIMENSION = 2; + private final static int K = 1; + + @SneakyThrows + public void testRescore() { + createTestIndex(); + assertValid( + buildRequest( + closeQueryXContentBuilder( + setupQueryXContentBuilder().field("vector", TEST_VECTOR).field("k", K).startObject("rescore").endObject() + ) + ) + ); + + assertValid( + buildRequest( + closeQueryXContentBuilder( + setupQueryXContentBuilder().field("vector", TEST_VECTOR) + .field("k", K) + .startObject(RESCORE_PARAMETER) + .field(RESCORE_OVERSAMPLE_PARAMETER, 2) + .endObject() + ) + ) + ); + + assertValid( + buildRequest( + closeQueryXContentBuilder( + setupQueryXContentBuilder().field("vector", TEST_VECTOR).field("k", K).startObject(RESCORE_PARAMETER).endObject() + ) + ) + ); + + assertValid( + buildRequest( + closeQueryXContentBuilder( + setupQueryXContentBuilder().field("vector", TEST_VECTOR).field("k", K).field(RESCORE_PARAMETER, true) + ) + ) + ); + + assertValid( + buildRequest( + closeQueryXContentBuilder( + setupQueryXContentBuilder().field("vector", TEST_VECTOR).field("k", K).field(RESCORE_PARAMETER, false) + ) + ) + ); + + // Invalid value for rescore + assertInvalid( + buildRequest( + closeQueryXContentBuilder( + setupQueryXContentBuilder().field("vector", TEST_VECTOR).field("k", K).field(RESCORE_PARAMETER, "invalid") + ) + ) + ); + + // Invalid rescore param + assertInvalid( + buildRequest( + closeQueryXContentBuilder( + setupQueryXContentBuilder().field("vector", TEST_VECTOR) + .field("k", K) + .startObject(RESCORE_OVERSAMPLE_PARAMETER) + .field("invalid_param", "invalid") + .endObject() + ) + ) + ); + + // Invalid rescore param value + assertInvalid( + buildRequest( + closeQueryXContentBuilder( + setupQueryXContentBuilder().field("vector", TEST_VECTOR) + .field("k", K) + .startObject(RESCORE_PARAMETER) + .field(RESCORE_OVERSAMPLE_PARAMETER, "invalid") + .endObject() + ) + ) + ); + } + + private XContentBuilder setupQueryXContentBuilder() throws IOException { + return XContentFactory.jsonBuilder().startObject().startObject("query").startObject("knn").startObject(FIELD_NAME); + } + + private XContentBuilder closeQueryXContentBuilder(XContentBuilder xContentBuilder) throws IOException { + return xContentBuilder.endObject().endObject().endObject().endObject(); + } + + private void assertValid(Request request) throws IOException { + assertOK(client().performRequest(request)); + } + + private void assertInvalid(Request request) { + expectThrows(ResponseException.class, () -> client().performRequest(request)); + } + + private Request buildRequest(XContentBuilder xContentBuilder) { + Request request = new Request("POST", String.format(Locale.ROOT, "/%s/_search", INDEX_NAME)); + request.addParameter("size", Integer.toString(10)); + request.addParameter("explain", Boolean.toString(true)); + request.setJsonEntity(xContentBuilder.toString()); + return request; + } + + private void createTestIndex() throws IOException { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", DIMENSION) + .endObject() + .endObject() + .endObject(); + + String mapping = builder.toString(); + createKnnIndex(INDEX_NAME, mapping); + } +} diff --git a/src/test/java/org/opensearch/knn/integ/TopLevelSpaceTypeParameterIT.java b/src/test/java/org/opensearch/knn/integ/TopLevelSpaceTypeParameterIT.java new file mode 100644 index 000000000..42cfb1491 --- /dev/null +++ b/src/test/java/org/opensearch/knn/integ/TopLevelSpaceTypeParameterIT.java @@ -0,0 +1,106 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.integ; + +import lombok.SneakyThrows; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.SpaceType; + +import java.io.IOException; + +import static org.opensearch.knn.common.KNNConstants.FAISS_NAME; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.KNN_METHOD; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.NAME; + +public class TopLevelSpaceTypeParameterIT extends KNNRestTestCase { + private final static float[] TEST_VECTOR = new float[] { 1.0f, 2.0f }; + private final static int DIMENSION = 2; + private final static int K = 1; + private static final String INDEX_NAME = "top-level-space-type-index"; + + @SneakyThrows + public void testBaseCase() { + createTestIndexWithTopLevelSpaceTypeOnly(); + addKnnDoc(INDEX_NAME, "0", FIELD_NAME, TEST_VECTOR); + validateKNNSearch(INDEX_NAME, FIELD_NAME, DIMENSION, 1, K); + deleteIndex(INDEX_NAME); + + createTestIndexWithTopLevelSpaceTypeAndMethodSpaceType(); + addKnnDoc(INDEX_NAME, "0", FIELD_NAME, TEST_VECTOR); + validateKNNSearch(INDEX_NAME, FIELD_NAME, DIMENSION, 1, K); + deleteIndex(INDEX_NAME); + + createTestIndexWithNoSpaceType(); + addKnnDoc(INDEX_NAME, "0", FIELD_NAME, TEST_VECTOR); + validateKNNSearch(INDEX_NAME, FIELD_NAME, DIMENSION, 1, K); + deleteIndex(INDEX_NAME); + } + + private void createTestIndexWithTopLevelSpaceTypeOnly() throws IOException { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", DIMENSION) + .field(KNNConstants.TOP_LEVEL_PARAMETER_SPACE_TYPE, SpaceType.INNER_PRODUCT.getValue()) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, FAISS_NAME) + .endObject() + .endObject() + .endObject() + .endObject(); + + String mapping = builder.toString(); + createKnnIndex(INDEX_NAME, mapping); + } + + private void createTestIndexWithTopLevelSpaceTypeAndMethodSpaceType() throws IOException { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", DIMENSION) + .field(KNNConstants.TOP_LEVEL_PARAMETER_SPACE_TYPE, SpaceType.INNER_PRODUCT.getValue()) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, FAISS_NAME) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, SpaceType.INNER_PRODUCT.getValue()) + .endObject() + .endObject() + .endObject() + .endObject(); + + String mapping = builder.toString(); + createKnnIndex(INDEX_NAME, mapping); + } + + private void createTestIndexWithNoSpaceType() throws IOException { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(FIELD_NAME) + .field("type", "knn_vector") + .field("dimension", DIMENSION) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, FAISS_NAME) + .endObject() + .endObject() + .endObject() + .endObject(); + + String mapping = builder.toString(); + createKnnIndex(INDEX_NAME, mapping); + } +} diff --git a/src/test/java/org/opensearch/knn/integ/search/ConcurrentSegmentSearchIT.java b/src/test/java/org/opensearch/knn/integ/search/ConcurrentSegmentSearchIT.java new file mode 100644 index 000000000..06346d1ca --- /dev/null +++ b/src/test/java/org/opensearch/knn/integ/search/ConcurrentSegmentSearchIT.java @@ -0,0 +1,141 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.integ.search; + +import com.google.common.primitives.Floats; +import lombok.SneakyThrows; +import org.apache.http.util.EntityUtils; +import org.junit.BeforeClass; +import org.opensearch.client.Response; +import org.opensearch.common.settings.Settings; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.knn.KNNJsonIndexMappingsBuilder; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.KNNResult; +import org.opensearch.knn.TestUtils; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.query.KNNQueryBuilder; +import org.opensearch.knn.plugin.script.KNNScoringUtil; + +import java.io.IOException; +import java.net.URL; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; + +/** + * Note that this is simply a sanity test to make sure that concurrent search code path is hit E2E and scores are intact + * There is no latency verification as it can be better encapsulated in nightly runs. + */ +public class ConcurrentSegmentSearchIT extends KNNRestTestCase { + + static TestUtils.TestData testData; + + @BeforeClass + public static void setUpClass() throws IOException { + if (ConcurrentSegmentSearchIT.class.getClassLoader() == null) { + throw new IllegalStateException("ClassLoader of ConcurrentSegmentSearchIT Class is null"); + } + URL testIndexVectors = ConcurrentSegmentSearchIT.class.getClassLoader().getResource("data/test_vectors_1000x128.json"); + URL testQueries = ConcurrentSegmentSearchIT.class.getClassLoader().getResource("data/test_queries_100x128.csv"); + assert testIndexVectors != null; + assert testQueries != null; + testData = new TestUtils.TestData(testIndexVectors.getPath(), testQueries.getPath()); + } + + @SneakyThrows + public void testConcurrentSegmentSearch_thenSucceed() { + String indexName = "test-concurrent-segment"; + String fieldName = "test-field-1"; + int dimension = testData.indexData.vectors[0].length; + final XContentBuilder indexBuilder = createFaissHnswIndexMapping(fieldName, dimension); + Map mappingMap = xContentBuilderToMap(indexBuilder); + String mapping = indexBuilder.toString(); + createKnnIndex(indexName, mapping); + assertEquals(new TreeMap<>(mappingMap), new TreeMap<>(getIndexMappingAsMap(indexName))); + + // Index the test data + for (int i = 0; i < testData.indexData.docs.length; i++) { + addKnnDoc( + indexName, + Integer.toString(testData.indexData.docs[i]), + fieldName, + Floats.asList(testData.indexData.vectors[i]).toArray() + ); + } + refreshAllNonSystemIndices(); + updateIndexSettings(indexName, Settings.builder().put("index.search.concurrent_segment_search.mode", "auto")); + + // Test search queries + int k = 10; + verifySearch(indexName, fieldName, k); + + updateIndexSettings(indexName, Settings.builder().put("index.search.concurrent_segment_search.mode", "all")); + verifySearch(indexName, fieldName, k); + + deleteKNNIndex(indexName); + } + + /* + { + "properties": { + "": { + "type": "knn_vector", + "dimension": , + "method": { + "name": "hnsw", + "space_type": "l2", + "engine": "faiss", + "parameters": { + "m": 16, + "ef_construction": 128, + "ef_search": 128 + } + } + } + } + */ + @SneakyThrows + private XContentBuilder createFaissHnswIndexMapping(String fieldName, int dimension) { + return KNNJsonIndexMappingsBuilder.builder() + .fieldName(fieldName) + .dimension(dimension) + .method( + KNNJsonIndexMappingsBuilder.Method.builder() + .engine(KNNEngine.FAISS.getName()) + .methodName(METHOD_HNSW) + .spaceType(SpaceType.L2.getValue()) + .parameters(KNNJsonIndexMappingsBuilder.Method.Parameters.builder().efConstruction(128).efSearch(128).m(16).build()) + .build() + ) + .build() + .getIndexMappingBuilder(); + } + + @SneakyThrows + private void verifySearch(String indexName, String fieldName, int k) { + for (int i = 0; i < testData.queries.length; i++) { + final KNNQueryBuilder queryBuilder = KNNQueryBuilder.builder().fieldName(fieldName).vector(testData.queries[i]).k(k).build(); + Response response = searchKNNIndex(indexName, queryBuilder, k); + String responseBody = EntityUtils.toString(response.getEntity()); + List knnResults = parseSearchResponse(responseBody, fieldName); + assertEquals(k, knnResults.size()); + + List actualScores = parseSearchResponseScore(responseBody, fieldName); + for (int j = 0; j < k; j++) { + float[] primitiveArray = knnResults.get(j).getVector(); + assertEquals( + KNNEngine.FAISS.score(KNNScoringUtil.l2Squared(testData.queries[i], primitiveArray), SpaceType.L2), + actualScores.get(j), + 0.0001 + ); + } + } + } +} diff --git a/src/test/java/org/opensearch/knn/jni/JNICommonsTest.java b/src/test/java/org/opensearch/knn/jni/JNICommonsTest.java new file mode 100644 index 000000000..bf27458b0 --- /dev/null +++ b/src/test/java/org/opensearch/knn/jni/JNICommonsTest.java @@ -0,0 +1,40 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.jni; + +import org.opensearch.knn.KNNTestCase; + +public class JNICommonsTest extends KNNTestCase { + + public void testStoreVectorData_whenVaildInputThenSuccess() { + float[][] data = new float[2][2]; + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + data[i][j] = i + j; + } + } + long memoryAddress = JNICommons.storeVectorData(0, data, 8); + assertTrue(memoryAddress > 0); + assertEquals(memoryAddress, JNICommons.storeVectorData(memoryAddress, data, 8)); + } + + public void testFreeVectorData_whenValidInput_ThenSuccess() { + float[][] data = new float[2][2]; + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + data[i][j] = i + j; + } + } + long memoryAddress = JNICommons.storeVectorData(0, data, 8); + JNICommons.freeVectorData(memoryAddress); + } +} diff --git a/src/test/java/org/opensearch/knn/jni/JNIServiceTests.java b/src/test/java/org/opensearch/knn/jni/JNIServiceTests.java index d85c53e08..e116ef3c6 100644 --- a/src/test/java/org/opensearch/knn/jni/JNIServiceTests.java +++ b/src/test/java/org/opensearch/knn/jni/JNIServiceTests.java @@ -13,67 +13,113 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import lombok.SneakyThrows; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexInput; +import org.apache.lucene.store.IndexOutput; import org.junit.BeforeClass; +import org.opensearch.Version; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.knn.KNNTestCase; import org.opensearch.knn.TestUtils; import org.opensearch.knn.common.KNNConstants; -import org.opensearch.knn.index.KNNMethodContext; -import org.opensearch.knn.index.KNNQueryResult; -import org.opensearch.knn.index.MethodComponentContext; +import org.opensearch.knn.common.RaisingIOExceptionIndexInput; +import org.opensearch.knn.common.RasingIOExceptionIndexOutput; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.nmslib.NmslibHNSWMethod; +import org.opensearch.knn.index.query.KNNQueryResult; +import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.store.IndexInputWithBuffer; +import org.opensearch.knn.index.store.IndexOutputWithBuffer; import java.io.IOException; import java.net.URL; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_M; import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_SIZE; import static org.opensearch.knn.common.KNNConstants.ENCODER_PQ; +import static org.opensearch.knn.common.KNNConstants.ENCODER_SQ; import static org.opensearch.knn.common.KNNConstants.FAISS_NAME; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_ENCODER_FP16; +import static org.opensearch.knn.common.KNNConstants.FAISS_SQ_TYPE; import static org.opensearch.knn.common.KNNConstants.INDEX_DESCRIPTION_PARAMETER; import static org.opensearch.knn.common.KNNConstants.INDEX_THREAD_QTY; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; import static org.opensearch.knn.common.KNNConstants.METHOD_IVF; import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NLIST; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; +import static org.opensearch.knn.common.KNNConstants.NAME; +import static org.opensearch.knn.common.KNNConstants.PARAMETERS; public class JNIServiceTests extends KNNTestCase { - + static final int FP16_MAX = 65504; + static final int FP16_MIN = -65504; static TestUtils.TestData testData; + static TestUtils.TestData testDataNested; private String faissMethod = "HNSW32,Flat"; + private String faissBinaryMethod = "BHNSW32"; @BeforeClass public static void setUpClass() throws IOException { + if (JNIServiceTests.class.getClassLoader() == null) { + throw new IllegalStateException("ClassLoader of JNIServiceTests Class is null"); + } URL testIndexVectors = JNIServiceTests.class.getClassLoader().getResource("data/test_vectors_1000x128.json"); + URL testIndexVectorsNested = JNIServiceTests.class.getClassLoader().getResource("data/test_vectors_nested_1000x128.json"); URL testQueries = JNIServiceTests.class.getClassLoader().getResource("data/test_queries_100x128.csv"); assert testIndexVectors != null; + assert testIndexVectorsNested != null; assert testQueries != null; testData = new TestUtils.TestData(testIndexVectors.getPath(), testQueries.getPath()); + testDataNested = new TestUtils.TestData(testIndexVectorsNested.getPath(), testQueries.getPath()); } + @SneakyThrows public void testCreateIndex_invalid_engineNotSupported() { - expectThrows( - IllegalArgumentException.class, - () -> JNIService.createIndex( - new int[] {}, - new float[][] {}, - "test", - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - "invalid-engine" - ) - ); + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + expectThrows( + IllegalArgumentException.class, + () -> TestUtils.createIndex( + new int[] {}, + 0, + 0, + directory, + "DONT_CARE", + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.LUCENE + ) + ); + } } public void testCreateIndex_invalid_engineNull() { expectThrows( Exception.class, - () -> JNIService.createIndex( + () -> TestUtils.createIndex( new int[] {}, - new float[][] {}, - "test", + 0, + 0, + null, + "DONT_CARE", ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), null ) @@ -81,147 +127,154 @@ public void testCreateIndex_invalid_engineNull() { } public void testCreateIndex_nmslib_invalid_noSpaceType() { - expectThrows( Exception.class, - () -> JNIService.createIndex( + () -> TestUtils.createIndex( testData.indexData.docs, - testData.indexData.vectors, - "something", + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + null, + "DONT_CARE", Collections.emptyMap(), - KNNEngine.NMSLIB.getName() + KNNEngine.NMSLIB ) ); } public void testCreateIndex_nmslib_invalid_vectorDocIDMismatch() throws IOException { - int[] docIds = new int[] { 1, 2, 3 }; float[][] vectors1 = new float[][] { { 1, 2 }, { 3, 4 } }; + long memoryAddress = JNICommons.storeVectorData(0, vectors1, vectors1.length * vectors1[0].length); + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1.tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress, + vectors1[0].length, + directory, + indexFileName1, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ) + ); - Path tmpFile1 = createTempFile(); - expectThrows( - Exception.class, - () -> JNIService.createIndex( - docIds, - vectors1, - tmpFile1.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB.getName() - ) - ); - - float[][] vectors2 = new float[][] { { 1, 2 }, { 3, 4 }, { 4, 5 }, { 6, 7 }, { 8, 9 } }; - - Path tmpFile2 = createTempFile(); - expectThrows( - Exception.class, - () -> JNIService.createIndex( - docIds, - vectors2, - tmpFile2.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB.getName() - ) - ); + float[][] vectors2 = new float[][] { { 1, 2 }, { 3, 4 }, { 4, 5 }, { 6, 7 }, { 8, 9 } }; + long memoryAddress2 = JNICommons.storeVectorData(0, vectors2, vectors2.length * vectors2[0].length); + + String indexFileName2 = "test2.tmp"; + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress2, + vectors2[0].length, + directory, + indexFileName2, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ) + ); + } } public void testCreateIndex_nmslib_invalid_nullArgument() throws IOException { + Path tempDirPath = createTempDir(); + String indexFileName = "test.tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + int[] docIds = new int[] {}; + float[][] vectors = new float[][] {}; + long memoryAddress = JNICommons.storeVectorData(0, vectors, vectors.length); + + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + null, + memoryAddress, + 0, + directory, + indexFileName, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ) + ); - int[] docIds = new int[] {}; - float[][] vectors = new float[][] {}; - - Path tmpFile = createTempFile(); - expectThrows( - Exception.class, - () -> JNIService.createIndex( - null, - vectors, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB.getName() - ) - ); - - expectThrows( - Exception.class, - () -> JNIService.createIndex( - docIds, - null, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB.getName() - ) - ); + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + 0, + 0, + directory, + indexFileName, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ) + ); - expectThrows( - Exception.class, - () -> JNIService.createIndex( - docIds, - vectors, - null, - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB.getName() - ) - ); + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress, + 0, + directory, + null, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ) + ); - expectThrows( - Exception.class, - () -> JNIService.createIndex(docIds, vectors, tmpFile.toAbsolutePath().toString(), null, KNNEngine.NMSLIB.getName()) - ); + expectThrows( + Exception.class, + () -> TestUtils.createIndex(docIds, memoryAddress, 0, directory, indexFileName, null, KNNEngine.NMSLIB) + ); - expectThrows( - Exception.class, - () -> JNIService.createIndex( - docIds, - vectors, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - null - ) - ); + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress, + 0, + directory, + indexFileName, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + null + ) + ); + } } public void testCreateIndex_nmslib_invalid_badSpace() throws IOException { int[] docIds = new int[] { 1 }; float[][] vectors = new float[][] { { 2, 3 } }; - - Path tmpFile = createTempFile(); - expectThrows( - Exception.class, - () -> JNIService.createIndex( - docIds, - vectors, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, "invalid"), - KNNEngine.NMSLIB.getName() - ) - ); - } - - public void testCreateIndex_nmslib_invalid_inconsistentDimensions() throws IOException { - - int[] docIds = new int[] { 1, 2 }; - float[][] vectors = new float[][] { { 2, 3 }, { 2, 3, 4 } }; - - Path tmpFile = createTempFile(); - expectThrows( - Exception.class, - () -> JNIService.createIndex( - docIds, - vectors, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB.getName() - ) - ); + long memoryAddress = JNICommons.storeVectorData(0, vectors, vectors.length * vectors[0].length); + Path tempDirPath = createTempDir(); + String indexFileName = "test.tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress, + vectors[0].length, + directory, + indexFileName, + ImmutableMap.of(KNNConstants.SPACE_TYPE, "invalid"), + KNNEngine.NMSLIB + ) + ); + } } public void testCreateIndex_nmslib_invalid_badParameterType() throws IOException { - int[] docIds = new int[] {}; - float[][] vectors = new float[][] {}; + int[] docIds = new int[] { 1 }; + float[][] vectors = new float[][] { { 2, 3 } }; + long memoryAddress = JNICommons.storeVectorData(0, vectors, vectors.length * vectors[0].length); Map parametersMap = ImmutableMap.of( KNNConstants.HNSW_ALGO_EF_CONSTRUCTION, @@ -229,295 +282,574 @@ public void testCreateIndex_nmslib_invalid_badParameterType() throws IOException KNNConstants.METHOD_PARAMETER_M, "12" ); - Path tmpFile = createTempFile(); - expectThrows( - Exception.class, - () -> JNIService.createIndex( - docIds, - vectors, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue(), KNNConstants.PARAMETERS, parametersMap), - KNNEngine.NMSLIB.getName() - ) - ); + Path tempDirPath = createTempDir(); + String indexFileName = "test.tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress, + vectors[0].length, + directory, + indexFileName, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue(), KNNConstants.PARAMETERS, parametersMap), + KNNEngine.NMSLIB + ) + ); + } } public void testCreateIndex_nmslib_valid() throws IOException { + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + for (SpaceType spaceType : NmslibHNSWMethod.SUPPORTED_SPACES) { + if (SpaceType.UNDEFINED == spaceType) { + continue; + } - for (SpaceType spaceType : KNNEngine.NMSLIB.getMethod(KNNConstants.METHOD_HNSW).getSpaces()) { - Path tmpFile = createTempFile(); + final String indexFileName1 = "test" + UUID.randomUUID() + ".tmp"; - JNIService.createIndex( - testData.indexData.docs, - testData.indexData.vectors, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), - KNNEngine.NMSLIB.getName() - ); - assertTrue(tmpFile.toFile().length() > 0); + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.NMSLIB + ); + assertTrue(directory.fileLength(indexFileName1) > 0); - tmpFile = createTempFile(); + final String indexFileName2 = "test" + UUID.randomUUID() + ".tmp"; - JNIService.createIndex( - testData.indexData.docs, - testData.indexData.vectors, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of( - KNNConstants.SPACE_TYPE, - spaceType.getValue(), - KNNConstants.HNSW_ALGO_EF_CONSTRUCTION, - 14, - KNNConstants.METHOD_PARAMETER_M, - 12 - ), - KNNEngine.NMSLIB.getName() - ); - assertTrue(tmpFile.toFile().length() > 0); + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName2, + ImmutableMap.of( + KNNConstants.SPACE_TYPE, + spaceType.getValue(), + KNNConstants.HNSW_ALGO_EF_CONSTRUCTION, + 14, + KNNConstants.METHOD_PARAMETER_M, + 12 + ), + KNNEngine.NMSLIB + ); + assertTrue(directory.fileLength(indexFileName2) > 0); + } } } + @SneakyThrows public void testCreateIndex_faiss_invalid_noSpaceType() { - int[] docIds = new int[] {}; - float[][] vectors = new float[][] {}; - expectThrows( - Exception.class, - () -> JNIService.createIndex( - docIds, - vectors, - "something", - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod), - FAISS_NAME - ) - ); + Path tempDirPath = createTempDir(); + String indexFileName = "test.tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod), + KNNEngine.FAISS + ) + ); + + } } public void testCreateIndex_faiss_invalid_vectorDocIDMismatch() throws IOException { int[] docIds = new int[] { 1, 2, 3 }; float[][] vectors1 = new float[][] { { 1, 2 }, { 3, 4 } }; + long memoryAddress = JNICommons.storeVectorData(0, vectors1, vectors1.length * vectors1[0].length); + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress, + vectors1[0].length, + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ) + ); - Path tmpFile1 = createTempFile(); - expectThrows( - Exception.class, - () -> JNIService.createIndex( - docIds, - vectors1, - tmpFile1.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - FAISS_NAME - ) - ); - - float[][] vectors2 = new float[][] { { 1, 2 }, { 3, 4 }, { 4, 5 }, { 6, 7 }, { 8, 9 } }; - - Path tmpFile2 = createTempFile(); - expectThrows( - Exception.class, - () -> JNIService.createIndex( - docIds, - vectors2, - tmpFile2.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - FAISS_NAME - ) - ); + float[][] vectors2 = new float[][] { { 1, 2 }, { 3, 4 }, { 4, 5 }, { 6, 7 }, { 8, 9 } }; + long memoryAddress2 = JNICommons.storeVectorData(0, vectors2, vectors2.length * vectors2[0].length); + String indexFileName2 = "test2" + UUID.randomUUID() + ".tmp"; + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress2, + vectors2[0].length, + directory, + indexFileName2, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ) + ); + } } public void testCreateIndex_faiss_invalid_null() throws IOException { + Path tempDirPath = createTempDir(); int[] docIds = new int[] {}; float[][] vectors = new float[][] {}; + long memoryAddress = JNICommons.storeVectorData(0, vectors, 0); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + null, + memoryAddress, + 0, + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ) + ); - Path tmpFile = createTempFile(); - expectThrows( - Exception.class, - () -> JNIService.createIndex( - null, - vectors, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - FAISS_NAME - ) - ); - - expectThrows( - Exception.class, - () -> JNIService.createIndex( - docIds, - null, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - FAISS_NAME - ) - ); + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + 0, + 0, + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ) + ); - expectThrows( - Exception.class, - () -> JNIService.createIndex( - docIds, - vectors, - null, - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - FAISS_NAME - ) - ); + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + null, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ) + ); - expectThrows(Exception.class, () -> JNIService.createIndex(docIds, vectors, tmpFile.toAbsolutePath().toString(), null, FAISS_NAME)); + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + null, + KNNEngine.FAISS + ) + ); - expectThrows( - Exception.class, - () -> JNIService.createIndex( - docIds, - vectors, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - null - ) - ); + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + null + ) + ); + } } public void testCreateIndex_faiss_invalid_invalidSpace() throws IOException { + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + int[] docIds = new int[] { 1 }; + float[][] vectors = new float[][] { { 2, 3 } }; + long memoryAddress = JNICommons.storeVectorData(0, vectors, (long) vectors.length * vectors[0].length); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress, + vectors[0].length, + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, "invalid"), + KNNEngine.FAISS + ) + ); + } + } - int[] docIds = new int[] { 1 }; - float[][] vectors = new float[][] { { 2, 3 } }; - - Path tmpFile = createTempFile(); - expectThrows( - Exception.class, - () -> JNIService.createIndex( - docIds, - vectors, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, "invalid"), - FAISS_NAME - ) - ); + public void testCreateIndex_faiss_invalid_noIndexDescription() throws IOException { + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + int[] docIds = new int[] { 1, 2 }; + float[][] vectors = new float[][] { { 2, 3 }, { 2, 3 } }; + long memoryAddress = JNICommons.storeVectorData(0, vectors, (long) vectors.length * vectors[0].length); + + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress, + vectors[0].length, + directory, + indexFileName1, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ) + ); + } } - public void testCreateIndex_faiss_invalid_inconsistentDimensions() throws IOException { + public void testCreateIndex_faiss_invalid_invalidIndexDescription() throws IOException { + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + int[] docIds = new int[] { 1, 2 }; + float[][] vectors = new float[][] { { 2, 3 }, { 2, 3 } }; + long memoryAddress = JNICommons.storeVectorData(0, vectors, (long) vectors.length * vectors[0].length); + + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress, + vectors[0].length, + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, "invalid", KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ) + ); + } + } - int[] docIds = new int[] { 1, 2 }; - float[][] vectors = new float[][] { { 2, 3 }, { 2, 3, 4 } }; + @SneakyThrows + public void testCreateIndex_faiss_sqfp16_invalidIndexDescription() { + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + int[] docIds = new int[] { 1, 2 }; + float[][] vectors = new float[][] { { 2, 3 }, { 3, 4 } }; + long memoryAddress = JNICommons.storeVectorData(0, vectors, (long) vectors.length * vectors[0].length); + + String sqfp16InvalidIndexDescription = "HNSW16,SQfp1655"; + + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + memoryAddress, + vectors[0].length, + directory, + indexFileName1, + ImmutableMap.of( + INDEX_DESCRIPTION_PARAMETER, + sqfp16InvalidIndexDescription, + KNNConstants.SPACE_TYPE, + SpaceType.L2.getValue() + ), + KNNEngine.FAISS + ) + ); + } + } - Path tmpFile = createTempFile(); - expectThrows( - Exception.class, - () -> JNIService.createIndex( + @SneakyThrows + public void testLoadIndex_faiss_sqfp16_valid() { + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + int[] docIds = new int[] { 1, 2 }; + float[][] vectors = new float[][] { { 2, 3 }, { 3, 4 } }; + String sqfp16IndexDescription = "HNSW16,SQfp16"; + long memoryAddress = JNICommons.storeVectorData(0, vectors, (long) vectors.length * vectors[0].length); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + TestUtils.createIndex( docIds, - vectors, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - FAISS_NAME - ) - ); + memoryAddress, + vectors[0].length, + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, sqfp16IndexDescription, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); + + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + long pointer = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + } + } } - public void testCreateIndex_faiss_invalid_noIndexDescription() throws IOException { + @SneakyThrows + public void testLoadIndex_when_io_exception_was_raised() { + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + int[] docIds = new int[] { 1, 2 }; + float[][] vectors = new float[][] { { 2, 3 }, { 3, 4 } }; + String sqfp16IndexDescription = "HNSW16,SQfp16"; + long memoryAddress = JNICommons.storeVectorData(0, vectors, (long) vectors.length * vectors[0].length); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + TestUtils.createIndex( + docIds, + memoryAddress, + vectors[0].length, + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, sqfp16IndexDescription, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); - int[] docIds = new int[] { 1, 2 }; - float[][] vectors = new float[][] { { 2, 3 }, { 2, 3, 4 } }; + final IndexInput raiseIOExceptionIndexInput = new RaisingIOExceptionIndexInput(); + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(raiseIOExceptionIndexInput); - Path tmpFile = createTempFile(); - expectThrows( - Exception.class, - () -> JNIService.createIndex( - docIds, - vectors, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - FAISS_NAME - ) - ); + try { + JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + fail("Exception thrown is expected."); + } catch (Throwable e) { + assertTrue(e.getMessage().contains("Reading bytes via IndexInput has failed.")); + } + } } - public void testCreateIndex_faiss_invalid_invalidIndexDescription() throws IOException { + @SneakyThrows + public void testQueryIndex_faiss_sqfp16_valid() { + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + String sqfp16IndexDescription = "HNSW16,SQfp16"; + int k = 10; + Map methodParameters = Map.of("ef_search", 12); + float[][] truncatedVectors = truncateToFp16Range(testData.indexData.vectors); + long memoryAddress = JNICommons.storeVectorData( + 0, + truncatedVectors, + (long) truncatedVectors.length * truncatedVectors[0].length + ); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + TestUtils.createIndex( + testData.indexData.docs, + memoryAddress, + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, sqfp16IndexDescription, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); - int[] docIds = new int[] { 1, 2 }; - float[][] vectors = new float[][] { { 2, 3 }, { 2, 3, 4 } }; + final long pointer; + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + pointer = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } - Path tmpFile = createTempFile(); - expectThrows( - Exception.class, - () -> JNIService.createIndex( - docIds, - vectors, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, "invalid", KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - FAISS_NAME - ) - ); + for (float[] query : testData.queries) { + KNNQueryResult[] results = JNIService.queryIndex(pointer, query, k, methodParameters, KNNEngine.FAISS, null, 0, null); + assertEquals(k, results.length); + } + + // Filter will result in no ids + for (float[] query : testData.queries) { + KNNQueryResult[] results = JNIService.queryIndex( + pointer, + query, + k, + methodParameters, + KNNEngine.FAISS, + new long[] { 0 }, + 0, + null + ); + assertEquals(0, results.length); + } + } } - public void testCreateIndex_faiss_invalid_invalidParameterType() throws IOException { + // If the value is outside of the fp16 range, then convert it to the fp16 minimum or maximum value + private float[][] truncateToFp16Range(final float[][] data) { + float[][] result = new float[data.length][data[0].length]; + for (int i = 0; i < data.length; i++) { + for (int j = 0; j < data[i].length; j++) { + float value = data[i][j]; + if (value < FP16_MIN || value > FP16_MAX) { + // If value is outside of the range, set it to the maximum or minimum value + result[i][j] = value < 0 ? FP16_MIN : FP16_MAX; + } else { + result[i][j] = value; + } + } + } + return result; + } - int[] docIds = new int[] {}; - float[][] vectors = new float[][] {}; + @SneakyThrows + public void testTrain_whenConfigurationIsIVFSQFP16_thenSucceed() { + long trainPointer = transferVectors(10); + int ivfNlistParam = 16; + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_IVF) + .field(KNN_ENGINE, FAISS_NAME) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.DEFAULT) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_NLIST, ivfNlistParam) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_SQ) + .startObject(PARAMETERS) + .field(FAISS_SQ_TYPE, FAISS_SQ_ENCODER_FP16) + .endObject() + .endObject() + .endObject() + .endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext = KNNMethodContext.parse(in); + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(Version.CURRENT) + .dimension(128) + .vectorDataType(VectorDataType.FLOAT) + .build(); + Map parameters = KNNEngine.FAISS.getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext) + .getLibraryParameters(); + + byte[] faissIndex = JNIService.trainIndex(parameters, 128, trainPointer, KNNEngine.FAISS); - Path tmpFile = createTempFile(); - expectThrows( - Exception.class, - () -> JNIService.createIndex( - docIds, - vectors, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of( - INDEX_DESCRIPTION_PARAMETER, - "IVF13", - KNNConstants.SPACE_TYPE, - SpaceType.L2.getValue(), - KNNConstants.PARAMETERS, - ImmutableMap.of(KNNConstants.METHOD_PARAMETER_NPROBES, "14") - ), - FAISS_NAME - ) - ); + assertNotEquals(0, faissIndex.length); + JNICommons.freeVectorData(trainPointer); + } + public void testCreateIndex_faiss_invalid_invalidParameterType() throws IOException { + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + int[] docIds = new int[] {}; + float[][] vectors = new float[][] {}; + + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + expectThrows( + Exception.class, + () -> TestUtils.createIndex( + docIds, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of( + INDEX_DESCRIPTION_PARAMETER, + "IVF13", + KNNConstants.SPACE_TYPE, + SpaceType.L2.getValue(), + KNNConstants.PARAMETERS, + ImmutableMap.of(KNNConstants.METHOD_PARAMETER_NPROBES, "14") + ), + KNNEngine.FAISS + ) + ); + } } public void testCreateIndex_faiss_valid() throws IOException { List methods = ImmutableList.of(faissMethod); List spaces = ImmutableList.of(SpaceType.L2, SpaceType.INNER_PRODUCT); - for (String method : methods) { - for (SpaceType spaceType : spaces) { - Path tmpFile1 = createTempFile(); - JNIService.createIndex( - testData.indexData.docs, - testData.indexData.vectors, - tmpFile1.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, method, KNNConstants.SPACE_TYPE, spaceType.getValue()), - FAISS_NAME - ); - assertTrue(tmpFile1.toFile().length() > 0); + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + for (String method : methods) { + for (SpaceType spaceType : spaces) { + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, method, KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); + } } } } + @SneakyThrows + public void testCreateIndex_binary_faiss_valid() { + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + long memoryAddr = testData.loadBinaryDataToMemoryAddress(); + TestUtils.createIndex( + testData.indexData.docs, + memoryAddr, + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of( + INDEX_DESCRIPTION_PARAMETER, + faissBinaryMethod, + KNNConstants.SPACE_TYPE, + SpaceType.HAMMING.getValue(), + KNNConstants.VECTOR_DATA_TYPE_FIELD, + VectorDataType.BINARY.getValue() + ), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); + } + } + public void testLoadIndex_invalidEngine() { - expectThrows(IllegalArgumentException.class, () -> JNIService.loadIndex("test", Collections.emptyMap(), "invalid-engine")); + expectThrows(IllegalArgumentException.class, () -> JNIService.loadIndex(null, Collections.emptyMap(), KNNEngine.LUCENE)); } public void testLoadIndex_nmslib_invalid_badSpaceType() { expectThrows( Exception.class, - () -> JNIService.loadIndex("test", ImmutableMap.of(KNNConstants.SPACE_TYPE, "invalid"), KNNEngine.NMSLIB.getName()) + () -> JNIService.loadIndex(null, ImmutableMap.of(KNNConstants.SPACE_TYPE, "invalid"), KNNEngine.NMSLIB) ); } public void testLoadIndex_nmslib_invalid_noSpaceType() { - expectThrows(Exception.class, () -> JNIService.loadIndex("test", Collections.emptyMap(), KNNEngine.NMSLIB.getName())); + expectThrows(Exception.class, () -> JNIService.loadIndex(null, Collections.emptyMap(), KNNEngine.NMSLIB)); } public void testLoadIndex_nmslib_invalid_fileDoesNotExist() { expectThrows( Exception.class, - () -> JNIService.loadIndex( - "invalid", - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB.getName() - ) + () -> JNIService.loadIndex(null, ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), KNNEngine.NMSLIB) ); } @@ -525,225 +857,755 @@ public void testLoadIndex_nmslib_invalid_badFile() throws IOException { Path tmpFile = createTempFile(); expectThrows( Exception.class, - () -> JNIService.loadIndex( - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB.getName() - ) + () -> JNIService.loadIndex(null, ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), KNNEngine.NMSLIB) ); } - public void testLoadIndex_nmslib_valid() throws IOException { + public void testLoadIndex_nmslib_valid() throws IOException { + + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ); + assertTrue(directory.fileLength(indexFileName1) > 0); + + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + long pointer = JNIService.loadIndex( + indexInputWithBuffer, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + } + } + } - Path tmpFile = createTempFile(); + public void testLoadIndex_nmslib_raise_io_exception() throws IOException { - JNIService.createIndex( - testData.indexData.docs, - testData.indexData.vectors, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB.getName() - ); - assertTrue(tmpFile.toFile().length() > 0); + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ); + assertTrue(directory.fileLength(indexFileName1) > 0); - long pointer = JNIService.loadIndex( - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB.getName() - ); - assertNotEquals(0, pointer); - } + final IndexInput raiseIOExceptionIndexInput = new RaisingIOExceptionIndexInput(); - public void testLoadIndex_faiss_invalid_fileDoesNotExist() { - expectThrows(Exception.class, () -> JNIService.loadIndex("invalid", Collections.emptyMap(), FAISS_NAME)); + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(raiseIOExceptionIndexInput); + try { + JNIService.loadIndex( + indexInputWithBuffer, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ); + fail("Exception expected"); + } catch (Throwable e) { + assertTrue(e.getMessage().contains("Reading bytes via IndexInput has failed.")); + } + } } - public void testLoadIndex_faiss_invalid_badFile() throws IOException { + public void testLoadIndex_nmslib_valid_with_stream() throws IOException { + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue(), KNN_ENGINE, KNNEngine.NMSLIB), + KNNEngine.NMSLIB + ); + assertTrue(directory.fileLength(indexFileName1) > 0); - Path tmpFile = createTempFile(); + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + long pointer = JNIService.loadIndex( + indexInputWithBuffer, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + } + } + } - expectThrows(Exception.class, () -> JNIService.loadIndex(tmpFile.toAbsolutePath().toString(), Collections.emptyMap(), FAISS_NAME)); + public void testWriteIndex_nmslib_when_io_exception_occured() { + try { + final IndexOutput indexOutput = new RasingIOExceptionIndexOutput(); + final IndexOutputWithBuffer indexOutputWithBuffer = new IndexOutputWithBuffer(indexOutput); + JNIService.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + indexOutputWithBuffer, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ); + fail("Exception thrown is expected."); + } catch (Throwable e) { + assertTrue(e.getMessage().contains("Writing bytes via IndexOutput has failed.")); + } } public void testLoadIndex_faiss_valid() throws IOException { + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); - Path tmpFile = createTempFile(); - - JNIService.createIndex( - testData.indexData.docs, - testData.indexData.vectors, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - FAISS_NAME - ); - assertTrue(tmpFile.toFile().length() > 0); - - long pointer = JNIService.loadIndex(tmpFile.toAbsolutePath().toString(), Collections.emptyMap(), FAISS_NAME); - assertNotEquals(0, pointer); + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + long pointer = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + } + } } public void testQueryIndex_invalidEngine() { - expectThrows(IllegalArgumentException.class, () -> JNIService.queryIndex(0L, new float[] {}, 0, "invalid-engine")); + expectThrows( + IllegalArgumentException.class, + () -> JNIService.queryIndex(0L, new float[] {}, 0, null, KNNEngine.LUCENE, null, 0, null) + ); } public void testQueryIndex_nmslib_invalid_badPointer() { - expectThrows(Exception.class, () -> JNIService.queryIndex(0L, new float[] {}, 0, KNNEngine.NMSLIB.getName())); + expectThrows(Exception.class, () -> JNIService.queryIndex(0L, new float[] {}, 0, null, KNNEngine.NMSLIB, null, 0, null)); } public void testQueryIndex_nmslib_invalid_nullQueryVector() throws IOException { - Path tmpFile = createTempFile(); - - JNIService.createIndex( - testData.indexData.docs, - testData.indexData.vectors, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB.getName() - ); - assertTrue(tmpFile.toFile().length() > 0); - - long pointer = JNIService.loadIndex( - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB.getName() - ); - assertNotEquals(0, pointer); + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ); + assertTrue(directory.fileLength(indexFileName1) > 0); + + final long pointer; + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + pointer = JNIService.loadIndex( + indexInputWithBuffer, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } - expectThrows(Exception.class, () -> JNIService.queryIndex(pointer, null, 10, KNNEngine.NMSLIB.getName())); + expectThrows(Exception.class, () -> JNIService.queryIndex(pointer, null, 10, null, KNNEngine.NMSLIB, null, 0, null)); + } } public void testQueryIndex_nmslib_valid() throws IOException { - int k = 50; - for (SpaceType spaceType : KNNEngine.NMSLIB.getMethod(KNNConstants.METHOD_HNSW).getSpaces()) { - Path tmpFile = createTempFile(); + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + int k = 50; + for (SpaceType spaceType : NmslibHNSWMethod.SUPPORTED_SPACES) { + if (SpaceType.UNDEFINED == spaceType) { + continue; + } - JNIService.createIndex( - testData.indexData.docs, - testData.indexData.vectors, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), - KNNEngine.NMSLIB.getName() - ); - assertTrue(tmpFile.toFile().length() > 0); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; - long pointer = JNIService.loadIndex( - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), - KNNEngine.NMSLIB.getName() - ); - assertNotEquals(0, pointer); + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.NMSLIB + ); + assertTrue(directory.fileLength(indexFileName1) > 0); + + final long pointer; + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + pointer = JNIService.loadIndex( + indexInputWithBuffer, + ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.NMSLIB + ); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } - for (float[] query : testData.queries) { - KNNQueryResult[] results = JNIService.queryIndex(pointer, query, k, KNNEngine.NMSLIB.getName()); - assertEquals(k, results.length); + for (float[] query : testData.queries) { + KNNQueryResult[] results = JNIService.queryIndex(pointer, query, k, null, KNNEngine.NMSLIB, null, 0, null); + assertEquals(k, results.length); + } } } } public void testQueryIndex_faiss_invalid_badPointer() { - expectThrows(Exception.class, () -> JNIService.queryIndex(0L, new float[] {}, 0, FAISS_NAME)); + expectThrows(Exception.class, () -> JNIService.queryIndex(0L, new float[] {}, 0, null, KNNEngine.FAISS, null, 0, null)); } public void testQueryIndex_faiss_invalid_nullQueryVector() throws IOException { - Path tmpFile = createTempFile(); + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); - JNIService.createIndex( - testData.indexData.docs, - testData.indexData.vectors, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - FAISS_NAME - ); - assertTrue(tmpFile.toFile().length() > 0); + final long pointer; + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + pointer = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } + + expectThrows(Exception.class, () -> JNIService.queryIndex(pointer, null, 10, null, KNNEngine.FAISS, null, 0, null)); + } + } + + public void testQueryIndex_faiss_streaming_invalid_nullQueryVector() throws IOException { + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); - long pointer = JNIService.loadIndex(tmpFile.toAbsolutePath().toString(), Collections.emptyMap(), FAISS_NAME); - assertNotEquals(0, pointer); + final long pointer; + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + pointer = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } - expectThrows(Exception.class, () -> JNIService.queryIndex(pointer, null, 10, FAISS_NAME)); + expectThrows(Exception.class, () -> JNIService.queryIndex(pointer, null, 10, null, KNNEngine.FAISS, null, 0, null)); + } } public void testQueryIndex_faiss_valid() throws IOException { int k = 10; + int efSearch = 100; + + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + List methods = ImmutableList.of(faissMethod); + List spaces = ImmutableList.of(SpaceType.L2, SpaceType.INNER_PRODUCT); + for (String method : methods) { + for (SpaceType spaceType : spaces) { + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, method, KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); + + final long pointer; + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + pointer = JNIService.loadIndex( + indexInputWithBuffer, + ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.FAISS + ); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } + + for (float[] query : testData.queries) { + KNNQueryResult[] results = JNIService.queryIndex( + pointer, + query, + k, + Map.of("ef_search", efSearch), + KNNEngine.FAISS, + null, + 0, + null + ); + assertEquals(k, results.length); + } + + // Filter will result in no ids + for (float[] query : testData.queries) { + KNNQueryResult[] results = JNIService.queryIndex( + pointer, + query, + k, + Map.of("ef_search", efSearch), + KNNEngine.FAISS, + new long[] { 0 }, + 0, + null + ); + assertEquals(0, results.length); + } + } + } + } + } + + public void testQueryIndex_faiss_streaming_valid() throws IOException { + int k = 10; + int efSearch = 100; + + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + List methods = ImmutableList.of(faissMethod); + List spaces = ImmutableList.of(SpaceType.L2, SpaceType.INNER_PRODUCT); + for (String method : methods) { + for (SpaceType spaceType : spaces) { + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, method, KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); + + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.READONCE)) { + long pointer = JNIService.loadIndex( + new IndexInputWithBuffer(indexInput), + ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.FAISS + ); + assertNotEquals(0, pointer); + + for (float[] query : testData.queries) { + KNNQueryResult[] results = JNIService.queryIndex( + pointer, + query, + k, + Map.of("ef_search", efSearch), + KNNEngine.FAISS, + null, + 0, + null + ); + assertEquals(k, results.length); + } + + // Filter will result in no ids + for (float[] query : testData.queries) { + KNNQueryResult[] results = JNIService.queryIndex( + pointer, + query, + k, + Map.of("ef_search", efSearch), + KNNEngine.FAISS, + new long[] { 0 }, + 0, + null + ); + assertEquals(0, results.length); + } // End for + } // End try + } // End for + } // End for + } + } - List methods = ImmutableList.of(faissMethod); - List spaces = ImmutableList.of(SpaceType.L2, SpaceType.INNER_PRODUCT); - for (String method : methods) { - for (SpaceType spaceType : spaces) { - Path tmpFile = createTempFile(); - JNIService.createIndex( - testData.indexData.docs, - testData.indexData.vectors, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, method, KNNConstants.SPACE_TYPE, spaceType.getValue()), - FAISS_NAME - ); - assertTrue(tmpFile.toFile().length() > 0); + public void testQueryIndex_faiss_parentIds() throws IOException { + + int k = 100; + int efSearch = 100; + + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + List methods = ImmutableList.of(faissMethod); + List spaces = ImmutableList.of(SpaceType.L2, SpaceType.INNER_PRODUCT); + int[] parentIds = toParentIdArray(testDataNested.indexData.docs); + Map idToParentIdMap = toIdToParentIdMap(testDataNested.indexData.docs); + for (String method : methods) { + for (SpaceType spaceType : spaces) { + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + TestUtils.createIndex( + testDataNested.indexData.docs, + testData.loadDataToMemoryAddress(), + testDataNested.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, method, KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); + + final long pointer; + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + pointer = JNIService.loadIndex( + indexInputWithBuffer, + ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.FAISS + ); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } + + for (float[] query : testDataNested.queries) { + KNNQueryResult[] results = JNIService.queryIndex( + pointer, + query, + k, + Map.of("ef_search", efSearch), + KNNEngine.FAISS, + null, + 0, + parentIds + ); + // Verify there is no more than one result from same parent + Set parentIdSet = toParentIdSet(results, idToParentIdMap); + assertEquals(results.length, parentIdSet.size()); + } + } + } + } + } - long pointer = JNIService.loadIndex( - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), - FAISS_NAME + public void testQueryIndex_faiss_streaming_parentIds() throws IOException { + + int k = 100; + int efSearch = 100; + + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + List methods = ImmutableList.of(faissMethod); + List spaces = ImmutableList.of(SpaceType.L2, SpaceType.INNER_PRODUCT); + int[] parentIds = toParentIdArray(testDataNested.indexData.docs); + Map idToParentIdMap = toIdToParentIdMap(testDataNested.indexData.docs); + for (String method : methods) { + for (SpaceType spaceType : spaces) { + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + TestUtils.createIndex( + testDataNested.indexData.docs, + testData.loadDataToMemoryAddress(), + testDataNested.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, method, KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); + + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.READONCE)) { + long pointer = JNIService.loadIndex( + new IndexInputWithBuffer(indexInput), + ImmutableMap.of(KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.FAISS + ); + assertNotEquals(0, pointer); + + for (float[] query : testDataNested.queries) { + KNNQueryResult[] results = JNIService.queryIndex( + pointer, + query, + k, + Map.of("ef_search", efSearch), + KNNEngine.FAISS, + null, + 0, + parentIds + ); + // Verify there is no more than one result from same parent + Set parentIdSet = toParentIdSet(results, idToParentIdMap); + assertEquals(results.length, parentIdSet.size()); + } // End for + } // End try + } // End for + } // End for + } + } + + @SneakyThrows + public void testQueryBinaryIndex_faiss_valid() { + int k = 10; + List methods = ImmutableList.of(faissBinaryMethod); + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + for (String method : methods) { + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + long memoryAddr = testData.loadBinaryDataToMemoryAddress(); + TestUtils.createIndex( + testData.indexData.docs, + memoryAddr, + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of( + INDEX_DESCRIPTION_PARAMETER, + method, + KNNConstants.SPACE_TYPE, + SpaceType.HAMMING.getValue(), + KNNConstants.VECTOR_DATA_TYPE_FIELD, + VectorDataType.BINARY.getValue() + ), + KNNEngine.FAISS ); - assertNotEquals(0, pointer); + assertTrue(directory.fileLength(indexFileName1) > 0); + + final long pointer; + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + pointer = JNIService.loadIndex( + indexInputWithBuffer, + ImmutableMap.of( + INDEX_DESCRIPTION_PARAMETER, + method, + KNNConstants.VECTOR_DATA_TYPE_FIELD, + VectorDataType.BINARY.getValue() + ), + KNNEngine.FAISS + ); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } - for (float[] query : testData.queries) { - KNNQueryResult[] results = JNIService.queryIndex(pointer, query, k, FAISS_NAME); + for (byte[] query : testData.binaryQueries) { + KNNQueryResult[] results = JNIService.queryBinaryIndex(pointer, query, k, null, KNNEngine.FAISS, null, 0, null); assertEquals(k, results.length); } } } } - public void testFree_invalidEngine() { - expectThrows(IllegalArgumentException.class, () -> JNIService.free(0L, "invalid-engine")); + @SneakyThrows + public void testQueryBinaryIndex_faiss_streaming_valid() { + int k = 10; + List methods = ImmutableList.of(faissBinaryMethod); + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + for (String method : methods) { + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + long memoryAddr = testData.loadBinaryDataToMemoryAddress(); + TestUtils.createIndex( + testData.indexData.docs, + memoryAddr, + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of( + INDEX_DESCRIPTION_PARAMETER, + method, + KNNConstants.SPACE_TYPE, + SpaceType.HAMMING.getValue(), + KNNConstants.VECTOR_DATA_TYPE_FIELD, + VectorDataType.BINARY.getValue() + ), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); + + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.READONCE)) { + long pointer = JNIService.loadIndex( + new IndexInputWithBuffer(indexInput), + ImmutableMap.of( + INDEX_DESCRIPTION_PARAMETER, + method, + KNNConstants.VECTOR_DATA_TYPE_FIELD, + VectorDataType.BINARY.getValue() + ), + KNNEngine.FAISS + ); + assertNotEquals(0, pointer); + + for (byte[] query : testData.binaryQueries) { + KNNQueryResult[] results = JNIService.queryBinaryIndex(pointer, query, k, null, KNNEngine.FAISS, null, 0, null); + assertEquals(k, results.length); + } // End for + } // End try + } // End for + } // End try } - public void testFree_nmslib_valid() throws IOException { + private Set toParentIdSet(KNNQueryResult[] results, Map idToParentIdMap) { + return Arrays.stream(results).map(result -> idToParentIdMap.get(result.getId())).collect(Collectors.toSet()); + } - Path tmpFile = createTempFile(); + private int[] toParentIdArray(int[] ids) { + int length = ids.length; + int[] sortedIds = Arrays.copyOf(ids, length); + Arrays.sort(sortedIds); + + List parentIds = new ArrayList<>(); + int largestId = sortedIds[length - 1]; + parentIds.add(largestId + 1); + for (int i = length - 2; i > -1; i--) { + if (sortedIds[i] != sortedIds[i + 1] - 1) { + parentIds.add(sortedIds[i] + 1); + } + } - JNIService.createIndex( - testData.indexData.docs, - testData.indexData.vectors, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB.getName() - ); - assertTrue(tmpFile.toFile().length() > 0); + Collections.shuffle(parentIds); + return parentIds.stream().mapToInt(Integer::intValue).toArray(); + } - long pointer = JNIService.loadIndex( - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - KNNEngine.NMSLIB.getName() - ); - assertNotEquals(0, pointer); + private Map toIdToParentIdMap(int[] ids) { + int length = ids.length; + int[] sortedIds = Arrays.copyOf(ids, length); + Arrays.sort(sortedIds); + + Map idToParentIdMap = new HashMap<>(); + int largestId = sortedIds[length - 1]; + int parentId = largestId + 1; + idToParentIdMap.put(largestId, parentId); + for (int i = length - 2; i > -1; i--) { + if (sortedIds[i] != sortedIds[i + 1] - 1) { + parentId = sortedIds[i] + 1; + } + idToParentIdMap.put(sortedIds[i], parentId); + } + return idToParentIdMap; + } - JNIService.free(pointer, KNNEngine.NMSLIB.getName()); + public void testFree_invalidEngine() { + expectThrows(IllegalArgumentException.class, () -> JNIService.free(0L, KNNEngine.LUCENE)); } - public void testFree_faiss_valid() throws IOException { + public void testFree_nmslib_valid() throws IOException { - Path tmpFile = createTempFile(); + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ); + assertTrue(directory.fileLength(indexFileName1) > 0); + + final long pointer; + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + pointer = JNIService.loadIndex( + indexInputWithBuffer, + ImmutableMap.of(KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.NMSLIB + ); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } - JNIService.createIndex( - testData.indexData.docs, - testData.indexData.vectors, - tmpFile.toAbsolutePath().toString(), - ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), - FAISS_NAME - ); - assertTrue(tmpFile.toFile().length() > 0); + JNIService.free(pointer, KNNEngine.NMSLIB); + } + } + + public void testFree_faiss_valid() throws IOException { - long pointer = JNIService.loadIndex(tmpFile.toAbsolutePath().toString(), Collections.emptyMap(), FAISS_NAME); - assertNotEquals(0, pointer); + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); + + final long pointer; + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + pointer = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } - JNIService.free(pointer, FAISS_NAME); + JNIService.free(pointer, KNNEngine.FAISS); + } } public void testTransferVectors() { @@ -756,34 +1618,121 @@ public void testTransferVectors() { assertEquals(trainPointer1, trainPointer2); } - JNIService.freeVectors(trainPointer1); + JNICommons.freeVectorData(trainPointer1); + } + + public void testTrain_whenConfigurationIsIVFFlat_thenSucceed() throws IOException { + long trainPointer = transferVectors(10); + int ivfNlistParam = 16; + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_IVF) + .field(KNN_ENGINE, FAISS_NAME) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.DEFAULT) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_NLIST, ivfNlistParam) + .endObject() + .endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext = KNNMethodContext.parse(in); + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .dimension(testData.indexData.getDimension()) + .versionCreated(Version.CURRENT) + .build(); + Map parameters = KNNEngine.FAISS.getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext) + .getLibraryParameters(); + + byte[] faissIndex = JNIService.trainIndex(parameters, 128, trainPointer, KNNEngine.FAISS); + + assertNotEquals(0, faissIndex.length); + JNICommons.freeVectorData(trainPointer); + } + + public void testTrain_whenConfigurationIsIVFPQ_thenSucceed() throws IOException { + long trainPointer = transferVectors(10); + int ivfNlistParam = 16; + int pqMParam = 4; + int pqCodeSizeParam = 4; + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_IVF) + .field(KNN_ENGINE, FAISS_NAME) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.DEFAULT.getValue()) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_NLIST, ivfNlistParam) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_PQ) + .startObject(PARAMETERS) + .field(ENCODER_PARAMETER_PQ_M, pqMParam) + .field(ENCODER_PARAMETER_PQ_CODE_SIZE, pqCodeSizeParam) + .endObject() + .endObject() + .endObject() + .endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext = KNNMethodContext.parse(in); + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(Version.CURRENT) + .dimension(128) + .vectorDataType(VectorDataType.FLOAT) + .build(); + Map parameters = KNNEngine.FAISS.getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext) + .getLibraryParameters(); + + byte[] faissIndex = JNIService.trainIndex(parameters, 128, trainPointer, KNNEngine.FAISS); + + assertNotEquals(0, faissIndex.length); + JNICommons.freeVectorData(trainPointer); } - public void testTrain() { + public void testTrain_whenConfigurationIsHNSWPQ_thenSucceed() throws IOException { + long trainPointer = transferVectors(10); + int pqMParam = 4; + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, FAISS_NAME) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.DEFAULT.getValue()) + .startObject(PARAMETERS) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_PQ) + .startObject(PARAMETERS) + .field(ENCODER_PARAMETER_PQ_M, pqMParam) + .endObject() + .endObject() + .endObject() + .endObject(); + Map in = xContentBuilderToMap(xContentBuilder); + KNNMethodContext knnMethodContext = KNNMethodContext.parse(in); + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .dimension(testData.indexData.getDimension()) + .versionCreated(Version.CURRENT) + .build(); + Map parameters = KNNEngine.FAISS.getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext) + .getLibraryParameters(); + + byte[] faissIndex = JNIService.trainIndex(parameters, 128, trainPointer, KNNEngine.FAISS); + + assertNotEquals(0, faissIndex.length); + JNICommons.freeVectorData(trainPointer); + } + private long transferVectors(int numDuplicates) { long trainPointer1 = JNIService.transferVectors(0, testData.indexData.vectors); assertNotEquals(0, trainPointer1); long trainPointer2; - for (int i = 0; i < 10; i++) { + for (int i = 0; i < numDuplicates; i++) { trainPointer2 = JNIService.transferVectors(trainPointer1, testData.indexData.vectors); assertEquals(trainPointer1, trainPointer2); } - Map parameters = ImmutableMap.of( - INDEX_DESCRIPTION_PARAMETER, - "IVF16,PQ4", - KNNConstants.SPACE_TYPE, - SpaceType.L2.getValue() - ); - - byte[] faissIndex = JNIService.trainIndex(parameters, 128, trainPointer1, FAISS_NAME); - - assertNotEquals(0, faissIndex.length); - JNIService.freeVectors(trainPointer1); + return trainPointer1; } - public void testCreateIndexFromTemplate() throws IOException { + public void createIndexFromTemplate() throws IOException { long trainPointer1 = JNIService.transferVectors(0, testData.indexData.vectors); assertNotEquals(0, trainPointer1); @@ -795,6 +1744,11 @@ public void testCreateIndexFromTemplate() throws IOException { } SpaceType spaceType = SpaceType.L2; + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(Version.CURRENT) + .dimension(128) + .vectorDataType(VectorDataType.FLOAT) + .build(); KNNMethodContext knnMethodContext = new KNNMethodContext( KNNEngine.FAISS, spaceType, @@ -809,7 +1763,11 @@ public void testCreateIndexFromTemplate() throws IOException { ) ); - String description = knnMethodContext.getEngine().getMethodAsMap(knnMethodContext).get(INDEX_DESCRIPTION_PARAMETER).toString(); + String description = knnMethodContext.getKnnEngine() + .getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext) + .getLibraryParameters() + .get(INDEX_DESCRIPTION_PARAMETER) + .toString(); assertEquals("IVF16,PQ16x8", description); Map parameters = ImmutableMap.of( @@ -819,23 +1777,271 @@ public void testCreateIndexFromTemplate() throws IOException { spaceType.getValue() ); - byte[] faissIndex = JNIService.trainIndex(parameters, 128, trainPointer1, FAISS_NAME); + byte[] faissIndex = JNIService.trainIndex(parameters, 128, trainPointer1, KNNEngine.FAISS); assertNotEquals(0, faissIndex.length); - JNIService.freeVectors(trainPointer1); + JNICommons.freeVectorData(trainPointer1); + + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + try (IndexOutput indexOutput = directory.createOutput(indexFileName1, IOContext.DEFAULT)) { + final IndexOutputWithBuffer indexOutputWithBuffer = new IndexOutputWithBuffer(indexOutput); + JNIService.createIndexFromTemplate( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + indexOutputWithBuffer, + faissIndex, + ImmutableMap.of(INDEX_THREAD_QTY, 1), + KNNEngine.FAISS + ); + } + assertTrue(directory.fileLength(indexFileName1) > 0); - Path tmpFile1 = createTempFile(); - JNIService.createIndexFromTemplate( - testData.indexData.docs, - testData.indexData.vectors, - tmpFile1.toAbsolutePath().toString(), - faissIndex, - ImmutableMap.of(INDEX_THREAD_QTY, 1), - FAISS_NAME + final long pointer; + try (IndexInput indexInput = directory.openInput(indexFileName1, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + pointer = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertNotEquals(0, pointer); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } + } + } + + @SneakyThrows + public void testCreateIndex_whenIOExceptionOccured() { + // Plain index + Map parameters = new HashMap<>( + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, SpaceType.L2.getValue()) + ); + + long trainPointer = JNIService.transferVectors(0, testData.indexData.vectors); + assertNotEquals(0, trainPointer); + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(Version.CURRENT) + .dimension(128) + .vectorDataType(VectorDataType.FLOAT) + .build(); + + byte[] faissIndex = JNIService.trainIndex(parameters, 128, trainPointer, KNNEngine.FAISS); + + assertNotEquals(0, faissIndex.length); + JNICommons.freeVectorData(trainPointer); + + final IndexOutput indexOutput = new RasingIOExceptionIndexOutput(); + final IndexOutputWithBuffer indexOutputWithBuffer = new IndexOutputWithBuffer(indexOutput); + try { + JNIService.createIndexFromTemplate( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + indexOutputWithBuffer, + faissIndex, + ImmutableMap.of(INDEX_THREAD_QTY, 1), + KNNEngine.FAISS + ); + fail("Exception thrown was expected"); + } catch (Throwable t) { + System.out.println("!!!!!!!!!!!!!!!!!!!!! " + t.getMessage()); + } + } + + @SneakyThrows + public void testIndexLoad_whenStateIsShared_thenSucceed() { + // Creates a single IVFPQ-l2 index. Then, we will configure a set of indices in memory in different ways to + // ensure that everything is loaded properly and the results are consistent. + int k = 10; + int ivfNlist = 16; + int pqM = 16; + int pqCodeSize = 4; + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + String indexIVFPQPath = createFaissIVFPQIndex(directory, ivfNlist, pqM, pqCodeSize, SpaceType.L2); + + final long indexIVFPQIndexTest1; + try (IndexInput indexInput = directory.openInput(indexIVFPQPath, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + indexIVFPQIndexTest1 = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertNotEquals(0, indexIVFPQIndexTest1); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } + final long indexIVFPQIndexTest2; + try (IndexInput indexInput = directory.openInput(indexIVFPQPath, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + indexIVFPQIndexTest2 = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertNotEquals(0, indexIVFPQIndexTest2); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } + + long sharedStateAddress = JNIService.initSharedIndexState(indexIVFPQIndexTest1, KNNEngine.FAISS); + JNIService.setSharedIndexState(indexIVFPQIndexTest1, sharedStateAddress, KNNEngine.FAISS); + JNIService.setSharedIndexState(indexIVFPQIndexTest2, sharedStateAddress, KNNEngine.FAISS); + + assertQueryResultsMatch(testData.queries, k, List.of(indexIVFPQIndexTest1, indexIVFPQIndexTest2)); + + // Free the first test index 1. This will ensure that the shared state persists after index that initialized + // shared state is gone. + JNIService.free(indexIVFPQIndexTest1, KNNEngine.FAISS); + + final long indexIVFPQIndexTest3; + try (IndexInput indexInput = directory.openInput(indexIVFPQPath, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + indexIVFPQIndexTest3 = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertNotEquals(0, indexIVFPQIndexTest3); + } catch (Throwable e) { + fail(e.getMessage()); + throw e; + } + + JNIService.setSharedIndexState(indexIVFPQIndexTest3, sharedStateAddress, KNNEngine.FAISS); + + assertQueryResultsMatch(testData.queries, k, List.of(indexIVFPQIndexTest2, indexIVFPQIndexTest3)); + + // Ensure everything gets freed + JNIService.free(indexIVFPQIndexTest2, KNNEngine.FAISS); + JNIService.free(indexIVFPQIndexTest3, KNNEngine.FAISS); + JNIService.freeSharedIndexState(sharedStateAddress, KNNEngine.FAISS); + } + } + + @SneakyThrows + public void testIsIndexIVFPQL2() { + long dummyAddress = 0; + assertFalse(JNIService.isSharedIndexStateRequired(dummyAddress, KNNEngine.NMSLIB)); + + Path tempDirPath = createTempDir(); + try (Directory directory = newFSDirectory(tempDirPath)) { + String faissIVFPQL2Index = createFaissIVFPQIndex(directory, 16, 16, 4, SpaceType.L2); + try (IndexInput indexInput = directory.openInput(faissIVFPQL2Index, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + long faissIVFPQL2Address = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertTrue(JNIService.isSharedIndexStateRequired(faissIVFPQL2Address, KNNEngine.FAISS)); + JNIService.free(faissIVFPQL2Address, KNNEngine.FAISS); + } + + String faissIVFPQIPIndex = createFaissIVFPQIndex(directory, 16, 16, 4, SpaceType.INNER_PRODUCT); + try (IndexInput indexInput = directory.openInput(faissIVFPQIPIndex, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + long faissIVFPQIPAddress = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertFalse(JNIService.isSharedIndexStateRequired(faissIVFPQIPAddress, KNNEngine.FAISS)); + JNIService.free(faissIVFPQIPAddress, KNNEngine.FAISS); + } + + String faissHNSWIndex = createFaissHNSWIndex(directory, SpaceType.L2); + try (IndexInput indexInput = directory.openInput(faissHNSWIndex, IOContext.LOAD)) { + final IndexInputWithBuffer indexInputWithBuffer = new IndexInputWithBuffer(indexInput); + long faissHNSWAddress = JNIService.loadIndex(indexInputWithBuffer, Collections.emptyMap(), KNNEngine.FAISS); + assertFalse(JNIService.isSharedIndexStateRequired(faissHNSWAddress, KNNEngine.FAISS)); + JNIService.free(faissHNSWAddress, KNNEngine.FAISS); + } + } + } + + @SneakyThrows + public void testFunctionsUnsupportedForEngine_whenEngineUnsupported_thenThrowIllegalArgumentException() { + int dummyAddress = 0; + expectThrows(IllegalArgumentException.class, () -> JNIService.initSharedIndexState(dummyAddress, KNNEngine.NMSLIB)); + expectThrows(IllegalArgumentException.class, () -> JNIService.setSharedIndexState(dummyAddress, dummyAddress, KNNEngine.NMSLIB)); + expectThrows(IllegalArgumentException.class, () -> JNIService.freeSharedIndexState(dummyAddress, KNNEngine.NMSLIB)); + } + + private void assertQueryResultsMatch(float[][] testQueries, int k, List indexAddresses) { + // Checks that the set of queries is consistent amongst all indices in the list + for (float[] query : testQueries) { + KNNQueryResult[][] allResults = new KNNQueryResult[indexAddresses.size()][]; + for (int i = 0; i < indexAddresses.size(); i++) { + allResults[i] = JNIService.queryIndex(indexAddresses.get(i), query, k, null, KNNEngine.FAISS, null, 0, null); + assertEquals(k, allResults[i].length); + } + + for (int i = 1; i < indexAddresses.size(); i++) { + for (int j = 0; j < k; j++) { + assertEquals(allResults[0][j].getId(), allResults[i][j].getId()); + assertEquals(allResults[0][j].getScore(), allResults[i][j].getScore(), 0.00001); + } + } + } + } + + private String createFaissIVFPQIndex(Directory directory, int ivfNlist, int pqM, int pqCodeSize, SpaceType spaceType) + throws IOException { + long trainPointer = JNIService.transferVectors(0, testData.indexData.vectors); + assertNotEquals(0, trainPointer); + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .versionCreated(Version.CURRENT) + .dimension(128) + .vectorDataType(VectorDataType.FLOAT) + .build(); + KNNMethodContext knnMethodContext = new KNNMethodContext( + KNNEngine.FAISS, + spaceType, + new MethodComponentContext( + METHOD_IVF, + ImmutableMap.of( + METHOD_PARAMETER_NLIST, + ivfNlist, + METHOD_ENCODER_PARAMETER, + new MethodComponentContext( + ENCODER_PQ, + ImmutableMap.of(ENCODER_PARAMETER_PQ_M, pqM, ENCODER_PARAMETER_PQ_CODE_SIZE, pqCodeSize) + ) + ) + ) + ); + + String description = knnMethodContext.getKnnEngine() + .getKNNLibraryIndexingContext(knnMethodContext, knnMethodConfigContext) + .getLibraryParameters() + .get(INDEX_DESCRIPTION_PARAMETER) + .toString(); + Map parameters = ImmutableMap.of( + INDEX_DESCRIPTION_PARAMETER, + description, + KNNConstants.SPACE_TYPE, + spaceType.getValue() ); - assertTrue(tmpFile1.toFile().length() > 0); - long pointer = JNIService.loadIndex(tmpFile1.toAbsolutePath().toString(), Collections.emptyMap(), FAISS_NAME); - assertNotEquals(0, pointer); + byte[] faissIndex = JNIService.trainIndex(parameters, 128, trainPointer, KNNEngine.FAISS); + + assertNotEquals(0, faissIndex.length); + JNICommons.freeVectorData(trainPointer); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (IndexOutput indexOutput = directory.createOutput(indexFileName1, IOContext.DEFAULT)) { + final IndexOutputWithBuffer indexOutputWithBuffer = new IndexOutputWithBuffer(indexOutput); + JNIService.createIndexFromTemplate( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + indexOutputWithBuffer, + faissIndex, + ImmutableMap.of(INDEX_THREAD_QTY, 1), + KNNEngine.FAISS + ); + } + assertTrue(directory.fileLength(indexFileName1) > 0); + + return indexFileName1; + } + + private String createFaissHNSWIndex(Directory directory, SpaceType spaceType) throws IOException { + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + TestUtils.createIndex( + testData.indexData.docs, + testData.loadDataToMemoryAddress(), + testData.indexData.getDimension(), + directory, + indexFileName1, + ImmutableMap.of(INDEX_DESCRIPTION_PARAMETER, faissMethod, KNNConstants.SPACE_TYPE, spaceType.getValue()), + KNNEngine.FAISS + ); + assertTrue(directory.fileLength(indexFileName1) > 0); + return indexFileName1; } } diff --git a/src/test/java/org/opensearch/knn/jni/PlatformUtilTests.java b/src/test/java/org/opensearch/knn/jni/PlatformUtilTests.java new file mode 100644 index 000000000..19c0abb07 --- /dev/null +++ b/src/test/java/org/opensearch/knn/jni/PlatformUtilTests.java @@ -0,0 +1,185 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.jni; + +import com.sun.jna.Platform; +import org.mockito.MockedStatic; +import org.opensearch.knn.KNNTestCase; +import oshi.util.platform.mac.SysctlUtil; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.stream.Stream; + +import static org.mockito.Mockito.mockStatic; +import static org.opensearch.knn.jni.PlatformUtils.isAVX2SupportedBySystem; +import static org.opensearch.knn.jni.PlatformUtils.isAVX512SupportedBySystem; + +public class PlatformUtilTests extends KNNTestCase { + public static final String MAC_CPU_FEATURES = "machdep.cpu.leaf7_features"; + public static final String LINUX_PROC_CPU_INFO = "/proc/cpuinfo"; + + public void testIsAVX2SupportedBySystem_platformIsNotIntel_returnsFalse() { + try (MockedStatic mockedPlatform = mockStatic(Platform.class)) { + mockedPlatform.when(Platform::isIntel).thenReturn(false); + assertFalse(isAVX2SupportedBySystem()); + } + } + + public void testIsAVX2SupportedBySystem_platformIsIntelWithOSAsWindows_returnsFalse() { + try (MockedStatic mockedPlatform = mockStatic(Platform.class)) { + mockedPlatform.when(Platform::isIntel).thenReturn(true); + mockedPlatform.when(Platform::isWindows).thenReturn(true); + assertFalse(isAVX2SupportedBySystem()); + } + } + + public void testIsAVX2SupportedBySystem_platformIsMac_returnsTrue() { + try (MockedStatic mockedPlatform = mockStatic(Platform.class)) { + mockedPlatform.when(Platform::isIntel).thenReturn(true); + mockedPlatform.when(Platform::isMac).thenReturn(true); + + try (MockedStatic mockedSysctlUtil = mockStatic(SysctlUtil.class)) { + mockedSysctlUtil.when(() -> SysctlUtil.sysctl(MAC_CPU_FEATURES, "empty")) + .thenReturn( + "RDWRFSGS TSC_THREAD_OFFSET SGX BMI1 AVX2 SMEP BMI2 ERMS INVPCID FPU_CSDS MPX RDSEED ADX SMAP CLFSOPT IPT SGXLC MDCLEAR TSXFA IBRS STIBP L1DF ACAPMSR SSBD" + ); + assertTrue(isAVX2SupportedBySystem()); + } + } + } + + public void testIsAVX2SupportedBySystem_platformIsMac_returnsFalse() { + try (MockedStatic mockedPlatform = mockStatic(Platform.class)) { + mockedPlatform.when(Platform::isIntel).thenReturn(true); + mockedPlatform.when(Platform::isMac).thenReturn(true); + + try (MockedStatic mockedSysctlUtil = mockStatic(SysctlUtil.class)) { + mockedSysctlUtil.when(() -> SysctlUtil.sysctl(MAC_CPU_FEATURES, "empty")).thenReturn("NO Flags"); + assertFalse(isAVX2SupportedBySystem()); + } + } + + } + + public void testIsAVX2SupportedBySystem_platformIsMac_throwsExceptionReturnsFalse() { + try (MockedStatic mockedPlatform = mockStatic(Platform.class)) { + mockedPlatform.when(Platform::isIntel).thenReturn(true); + mockedPlatform.when(Platform::isMac).thenReturn(true); + + try (MockedStatic mockedSysctlUtil = mockStatic(SysctlUtil.class)) { + mockedSysctlUtil.when(() -> SysctlUtil.sysctl(MAC_CPU_FEATURES, "empty")).thenThrow(RuntimeException.class); + assertFalse(isAVX2SupportedBySystem()); + } + } + + } + + public void testIsAVX2SupportedBySystem_platformIsLinux_returnsTrue() { + try (MockedStatic mockedPlatform = mockStatic(Platform.class)) { + mockedPlatform.when(Platform::isIntel).thenReturn(true); + mockedPlatform.when(Platform::isMac).thenReturn(false); + mockedPlatform.when(Platform::isLinux).thenReturn(true); + + try (MockedStatic mockedFiles = mockStatic(Files.class)) { + mockedFiles.when(() -> Files.lines(Paths.get(LINUX_PROC_CPU_INFO))).thenReturn(Stream.of("flags: AVX2", "dummy string")); + assertTrue(isAVX2SupportedBySystem()); + } + } + } + + public void testIsAVX2SupportedBySystem_platformIsLinux_returnsFalse() { + try (MockedStatic mockedPlatform = mockStatic(Platform.class)) { + mockedPlatform.when(Platform::isIntel).thenReturn(true); + mockedPlatform.when(Platform::isMac).thenReturn(false); + mockedPlatform.when(Platform::isLinux).thenReturn(true); + + try (MockedStatic mockedFiles = mockStatic(Files.class)) { + mockedFiles.when(() -> Files.lines(Paths.get(LINUX_PROC_CPU_INFO))).thenReturn(Stream.of("flags: ", "dummy string")); + assertFalse(isAVX2SupportedBySystem()); + } + } + + } + + public void testIsAVX2SupportedBySystem_platformIsLinux_throwsExceptionReturnsFalse() { + try (MockedStatic mockedPlatform = mockStatic(Platform.class)) { + mockedPlatform.when(Platform::isIntel).thenReturn(true); + mockedPlatform.when(Platform::isMac).thenReturn(false); + mockedPlatform.when(Platform::isLinux).thenReturn(true); + + try (MockedStatic mockedPaths = mockStatic(Paths.class)) { + mockedPaths.when(() -> Paths.get(LINUX_PROC_CPU_INFO)).thenThrow(RuntimeException.class); + assertFalse(isAVX2SupportedBySystem()); + } + } + + } + + // AVX512 tests + + public void testIsAVX512SupportedBySystem_platformIsNotIntel_returnsFalse() { + try (MockedStatic mockedPlatform = mockStatic(Platform.class)) { + mockedPlatform.when(Platform::isIntel).thenReturn(false); + assertFalse(isAVX512SupportedBySystem()); + } + } + + public void testIsAVX512SupportedBySystem_platformIsMac_returnsFalse() { + try (MockedStatic mockedPlatform = mockStatic(Platform.class)) { + mockedPlatform.when(Platform::isMac).thenReturn(false); + assertFalse(isAVX512SupportedBySystem()); + } + } + + public void testIsAVX512SupportedBySystem_platformIsIntelMac_returnsFalse() { + try (MockedStatic mockedPlatform = mockStatic(Platform.class)) { + mockedPlatform.when(Platform::isIntel).thenReturn(true); + mockedPlatform.when(Platform::isMac).thenReturn(true); + assertFalse(isAVX512SupportedBySystem()); + } + } + + public void testIsAVX512SupportedBySystem_platformIsIntelWithOSAsWindows_returnsFalse() { + try (MockedStatic mockedPlatform = mockStatic(Platform.class)) { + mockedPlatform.when(Platform::isIntel).thenReturn(true); + mockedPlatform.when(Platform::isWindows).thenReturn(true); + assertFalse(isAVX512SupportedBySystem()); + } + } + + public void testIsAVX512SupportedBySystem_platformIsLinuxAllAVX512FlagsPresent_returnsTrue() { + try (MockedStatic mockedPlatform = mockStatic(Platform.class)) { + mockedPlatform.when(Platform::isIntel).thenReturn(true); + mockedPlatform.when(Platform::isLinux).thenReturn(true); + + try (MockedStatic mockedFiles = mockStatic(Files.class)) { + mockedFiles.when(() -> Files.lines(Paths.get(LINUX_PROC_CPU_INFO))) + .thenReturn(Stream.of("flags: AVX2 avx512f avx512cd avx512vl avx512dq avx512bw", "dummy string")); + assertTrue(isAVX512SupportedBySystem()); + } + } + } + + public void testIsAVX512SupportedBySystem_platformIsLinuxSomeAVX512FlagsPresent_returnsFalse() { + try (MockedStatic mockedPlatform = mockStatic(Platform.class)) { + mockedPlatform.when(Platform::isIntel).thenReturn(true); + mockedPlatform.when(Platform::isLinux).thenReturn(true); + + try (MockedStatic mockedFiles = mockStatic(Files.class)) { + mockedFiles.when(() -> Files.lines(Paths.get(LINUX_PROC_CPU_INFO))) + .thenReturn(Stream.of("flags: AVX2 avx512vl avx512dq avx512bw avx512vbmi umip pku ospke avx512_vbmi2", "dummy string")); + assertFalse(isAVX512SupportedBySystem()); + } + } + } +} diff --git a/src/test/java/org/opensearch/knn/plugin/action/RestClearCacheHandlerIT.java b/src/test/java/org/opensearch/knn/plugin/action/RestClearCacheHandlerIT.java new file mode 100644 index 000000000..2b9f8a82f --- /dev/null +++ b/src/test/java/org/opensearch/knn/plugin/action/RestClearCacheHandlerIT.java @@ -0,0 +1,112 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.plugin.action; + +import lombok.SneakyThrows; +import org.opensearch.client.Request; +import org.opensearch.client.ResponseException; +import org.opensearch.common.settings.Settings; +import org.opensearch.knn.KNNRestTestCase; +import org.opensearch.knn.plugin.KNNPlugin; +import org.opensearch.rest.RestRequest; + +import java.util.Arrays; +import java.util.Collections; + +import static org.opensearch.knn.common.KNNConstants.CLEAR_CACHE; + +/** + * Integration tests to validate ClearCache API + */ + +public class RestClearCacheHandlerIT extends KNNRestTestCase { + private static final String TEST_FIELD = "test-field"; + private static final int DIMENSIONS = 2; + public static final int ALWAYS_BUILD_GRAPH = 0; + + @SneakyThrows + public void testNonExistentIndex() { + String nonExistentIndex = "non-existent-index"; + + String restURI = String.join("/", KNNPlugin.KNN_BASE_URI, CLEAR_CACHE, nonExistentIndex); + Request request = new Request(RestRequest.Method.POST.name(), restURI); + + ResponseException ex = expectThrows(ResponseException.class, () -> client().performRequest(request)); + assertTrue(ex.getMessage().contains(nonExistentIndex)); + } + + @SneakyThrows + public void testNotKnnIndex() { + String notKNNIndex = "not-knn-index"; + createIndex(notKNNIndex, Settings.EMPTY); + + String restURI = String.join("/", KNNPlugin.KNN_BASE_URI, CLEAR_CACHE, notKNNIndex); + Request request = new Request(RestRequest.Method.POST.name(), restURI); + + ResponseException ex = expectThrows(ResponseException.class, () -> client().performRequest(request)); + assertTrue(ex.getMessage().contains(notKNNIndex)); + } + + @SneakyThrows + public void testClearCacheSingleIndex() { + String testIndex = getTestName().toLowerCase(); + int graphCountBefore = getTotalGraphsInCache(); + createKnnIndex(testIndex, buildKNNIndexSettings(ALWAYS_BUILD_GRAPH), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + addKnnDoc(testIndex, String.valueOf(randomInt()), TEST_FIELD, new Float[] { randomFloat(), randomFloat() }); + + knnWarmup(Collections.singletonList(testIndex)); + + assertEquals(graphCountBefore + 1, getTotalGraphsInCache()); + + clearCache(Collections.singletonList(testIndex)); + assertEquals(graphCountBefore, getTotalGraphsInCache()); + } + + @SneakyThrows + public void testClearCacheMultipleIndices() { + String testIndex1 = getTestName().toLowerCase(); + String testIndex2 = getTestName().toLowerCase() + 1; + int graphCountBefore = getTotalGraphsInCache(); + + createKnnIndex(testIndex1, buildKNNIndexSettings(ALWAYS_BUILD_GRAPH), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + addKnnDoc(testIndex1, String.valueOf(randomInt()), TEST_FIELD, new Float[] { randomFloat(), randomFloat() }); + + createKnnIndex(testIndex2, buildKNNIndexSettings(0), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + addKnnDoc(testIndex2, String.valueOf(randomInt()), TEST_FIELD, new Float[] { randomFloat(), randomFloat() }); + + knnWarmup(Arrays.asList(testIndex1, testIndex2)); + + assertEquals(graphCountBefore + 2, getTotalGraphsInCache()); + + clearCache(Arrays.asList(testIndex1, testIndex2)); + assertEquals(graphCountBefore, getTotalGraphsInCache()); + } + + @SneakyThrows + public void testClearCacheMultipleIndicesWithPatterns() { + String testIndex1 = getTestName().toLowerCase(); + String testIndex2 = getTestName().toLowerCase() + 1; + String testIndex3 = "abc" + getTestName().toLowerCase(); + int graphCountBefore = getTotalGraphsInCache(); + + createKnnIndex(testIndex1, buildKNNIndexSettings(ALWAYS_BUILD_GRAPH), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + addKnnDoc(testIndex1, String.valueOf(randomInt()), TEST_FIELD, new Float[] { randomFloat(), randomFloat() }); + + createKnnIndex(testIndex2, buildKNNIndexSettings(ALWAYS_BUILD_GRAPH), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + addKnnDoc(testIndex2, String.valueOf(randomInt()), TEST_FIELD, new Float[] { randomFloat(), randomFloat() }); + + createKnnIndex(testIndex3, buildKNNIndexSettings(ALWAYS_BUILD_GRAPH), createKnnIndexMapping(TEST_FIELD, DIMENSIONS)); + addKnnDoc(testIndex3, String.valueOf(randomInt()), TEST_FIELD, new Float[] { randomFloat(), randomFloat() }); + + knnWarmup(Arrays.asList(testIndex1, testIndex2, testIndex3)); + + assertEquals(graphCountBefore + 3, getTotalGraphsInCache()); + String indexPattern = getTestName().toLowerCase() + "*"; + + clearCache(Arrays.asList(indexPattern)); + assertEquals(graphCountBefore + 1, getTotalGraphsInCache()); + } +} diff --git a/src/test/java/org/opensearch/knn/plugin/action/RestDeleteModelHandlerIT.java b/src/test/java/org/opensearch/knn/plugin/action/RestDeleteModelHandlerIT.java index edd8d2106..de78d2113 100644 --- a/src/test/java/org/opensearch/knn/plugin/action/RestDeleteModelHandlerIT.java +++ b/src/test/java/org/opensearch/knn/plugin/action/RestDeleteModelHandlerIT.java @@ -11,26 +11,31 @@ package org.opensearch.knn.plugin.action; +import lombok.SneakyThrows; import org.apache.http.util.EntityUtils; -import org.opensearch.action.DocWriteResponse; import org.opensearch.client.Request; import org.opensearch.client.Response; +import org.opensearch.client.ResponseException; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.common.xcontent.XContentType; import org.opensearch.knn.KNNRestTestCase; -import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.util.KNNEngine; -import org.opensearch.knn.indices.ModelMetadata; -import org.opensearch.knn.indices.ModelState; import org.opensearch.knn.plugin.KNNPlugin; -import org.opensearch.knn.plugin.transport.DeleteModelResponse; -import org.opensearch.rest.RestStatus; +import org.opensearch.core.rest.RestStatus; -import java.io.IOException; +import java.util.List; import java.util.Map; -import static org.opensearch.knn.common.KNNConstants.MODELS; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_SIZE; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_M; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; import static org.opensearch.knn.common.KNNConstants.MODEL_ID; -import static org.opensearch.knn.common.KNNConstants.MODEL_INDEX_NAME; +import static org.opensearch.knn.common.KNNConstants.MODELS; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NLIST; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.NAME; +import static org.opensearch.knn.common.KNNConstants.PARAMETERS; /** * Integration tests to check the correctness of {@link org.opensearch.knn.plugin.rest.RestDeleteModelHandler} @@ -38,42 +43,138 @@ public class RestDeleteModelHandlerIT extends KNNRestTestCase { - private ModelMetadata getModelMetadata() { - return new ModelMetadata(KNNEngine.DEFAULT, SpaceType.DEFAULT, 4, ModelState.CREATED, "2021-03-27", "test model", ""); - } + @SneakyThrows + public void testDelete_whenModelExists_thenDeletionSucceeds() { + String modelId = "test-model-id"; + String trainingIndexName = "train-index"; + String trainingFieldName = "train-field"; + int dimension = 8; + String modelDescription = "dummy description"; - public void testDeleteModelExists() throws IOException { - createModelSystemIndex(); - String testModelID = "test-model-id"; - byte[] testModelBlob = "hello".getBytes(); - ModelMetadata testModelMetadata = getModelMetadata(); + createBasicKnnIndex(trainingIndexName, trainingFieldName, dimension); + ingestDataAndTrainModel(modelId, trainingIndexName, trainingFieldName, dimension, modelDescription); + assertTrainingSucceeds(modelId, NUM_OF_ATTEMPTS, DELAY_MILLI_SEC); - addModelToSystemIndex(testModelID, testModelMetadata, testModelBlob); - assertEquals(getDocCount(MODEL_INDEX_NAME), 1); + Response getModelResponse = getModel(modelId, List.of()); + assertEquals(RestStatus.OK, RestStatus.fromCode(getModelResponse.getStatusLine().getStatusCode())); - String restURI = String.join("/", KNNPlugin.KNN_BASE_URI, MODELS, testModelID); - Request request = new Request("DELETE", restURI); + String responseBody = EntityUtils.toString(getModelResponse.getEntity()); + assertNotNull(responseBody); - Response response = client().performRequest(request); - assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + Map responseMap = createParser(XContentType.JSON.xContent(), responseBody).map(); + + assertEquals(modelId, responseMap.get(MODEL_ID)); + + String deleteModelRestURI = String.join("/", KNNPlugin.KNN_BASE_URI, MODELS, modelId); + Request deleteModelRequest = new Request("DELETE", deleteModelRestURI); + + Response deleteModelResponse = client().performRequest(deleteModelRequest); + assertEquals( + deleteModelRequest.getEndpoint() + ": failed", + RestStatus.OK, + RestStatus.fromCode(deleteModelResponse.getStatusLine().getStatusCode()) + ); - assertEquals(getDocCount(MODEL_INDEX_NAME), 0); + ResponseException ex = expectThrows(ResponseException.class, () -> getModel(modelId, List.of())); + assertTrue(ex.getMessage().contains(modelId)); } - public void testDeleteModelFailsInvalid() throws IOException { - createModelSystemIndex(); - String restURI = String.join("/", KNNPlugin.KNN_BASE_URI, MODELS, "invalid-model-id"); + public void testDelete_whenModelIDIsInvalid_thenFail() { + String modelId = "invalid-model-id"; + String restURI = String.join("/", KNNPlugin.KNN_BASE_URI, MODELS, modelId); + Request request = new Request("DELETE", restURI); + + ResponseException ex = expectThrows(ResponseException.class, () -> client().performRequest(request)); + assertEquals(RestStatus.NOT_FOUND.getStatus(), ex.getResponse().getStatusLine().getStatusCode()); + } + + // Test Train Model -> Delete Model -> Train Model with same modelId + @SneakyThrows + public void testTraining_whenModelHasBeenDeleted_thenSucceedTrainingModelWithSameID() { + String modelId = "test-model-id1"; + String trainingIndexName1 = "train-index-1"; + String trainingIndexName2 = "train-index-2"; + String trainingFieldName = "train-field"; + int dimension = 8; + + // Train Model + trainModel(modelId, trainingIndexName1, trainingFieldName, dimension); + + // Delete Model + String restURI = String.join("/", KNNPlugin.KNN_BASE_URI, MODELS, modelId); Request request = new Request("DELETE", restURI); Response response = client().performRequest(request); assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); - String responseBody = EntityUtils.toString(response.getEntity()); + + // Train Model again with same ModelId + trainModel(modelId, trainingIndexName2, trainingFieldName, dimension); + } + + private void trainModel(String modelId, String trainingIndexName, String trainingFieldName, int dimension) throws Exception { + + // Create a training index and randomly ingest data into it + createBasicKnnIndex(trainingIndexName, trainingFieldName, dimension); + int trainingDataCount = 200; + bulkIngestRandomVectors(trainingIndexName, trainingFieldName, trainingDataCount, dimension); + + // Call the train API with this definition: + /* + { + "training_index": "train_index", + "training_field": "train_field", + "dimension": 8, + "description": "this should be allowed to be null", + "method": { + "name":"ivf", + "engine":"faiss", + "space_type": "l2", + "parameters":{ + "nlist":1, + "encoder":{ + "name":"pq", + "parameters":{ + "code_size":2, + "m": 2 + } + } + } + } + } + */ + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, "ivf") + .field(KNN_ENGINE, "faiss") + .field(METHOD_PARAMETER_SPACE_TYPE, "l2") + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_NLIST, 1) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, "pq") + .startObject(PARAMETERS) + .field(ENCODER_PARAMETER_PQ_CODE_SIZE, 2) + .field(ENCODER_PARAMETER_PQ_M, 2) + .endObject() + .endObject() + .endObject() + .endObject(); + Map method = xContentBuilderToMap(builder); + + Response trainResponse = trainModel(modelId, trainingIndexName, trainingFieldName, dimension, method, "dummy description"); + + assertEquals(RestStatus.OK, RestStatus.fromCode(trainResponse.getStatusLine().getStatusCode())); + + // Confirm that the model gets created + Response getResponse = getModel(modelId, null); + String responseBody = EntityUtils.toString(getResponse.getEntity()); assertNotNull(responseBody); Map responseMap = createParser(XContentType.JSON.xContent(), responseBody).map(); - assertEquals("invalid-model-id", responseMap.get(MODEL_ID)); - assertEquals(DocWriteResponse.Result.NOT_FOUND.getLowercase(), responseMap.get(DeleteModelResponse.RESULT)); - assertNotNull(responseMap.get(DeleteModelResponse.ERROR_MSG)); + assertEquals(modelId, responseMap.get(MODEL_ID)); + + // Make sure training succeeds after 30 seconds + assertTrainingSucceeds(modelId, 30, 1000); } + } diff --git a/src/test/java/org/opensearch/knn/plugin/action/RestGetModelHandlerIT.java b/src/test/java/org/opensearch/knn/plugin/action/RestGetModelHandlerIT.java index b6853e8bb..21369f9ff 100644 --- a/src/test/java/org/opensearch/knn/plugin/action/RestGetModelHandlerIT.java +++ b/src/test/java/org/opensearch/knn/plugin/action/RestGetModelHandlerIT.java @@ -12,20 +12,16 @@ package org.opensearch.knn.plugin.action; import joptsimple.internal.Strings; +import lombok.SneakyThrows; import org.apache.http.util.EntityUtils; import org.opensearch.client.Request; import org.opensearch.client.Response; import org.opensearch.client.ResponseException; import org.opensearch.common.xcontent.XContentType; import org.opensearch.knn.KNNRestTestCase; -import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.util.KNNEngine; -import org.opensearch.knn.indices.ModelMetadata; -import org.opensearch.knn.indices.ModelState; import org.opensearch.knn.plugin.KNNPlugin; -import org.opensearch.rest.RestStatus; +import org.opensearch.core.rest.RestStatus; -import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -39,6 +35,8 @@ import static org.opensearch.knn.common.KNNConstants.MODEL_ID; import static org.opensearch.knn.common.KNNConstants.MODEL_STATE; import static org.opensearch.knn.common.KNNConstants.MODEL_TIMESTAMP; +import static org.opensearch.knn.index.SpaceType.L2; +import static org.opensearch.knn.index.engine.KNNEngine.FAISS; /** * Integration tests to check the correctness of {@link org.opensearch.knn.plugin.rest.RestGetModelHandler} @@ -46,19 +44,27 @@ public class RestGetModelHandlerIT extends KNNRestTestCase { - private ModelMetadata getModelMetadata() { - return new ModelMetadata(KNNEngine.DEFAULT, SpaceType.DEFAULT, 4, ModelState.CREATED, "2021-03-27", "test model", ""); - } - - public void testGetModelExists() throws IOException { - createModelSystemIndex(); - String testModelID = "test-model-id"; - byte[] testModelBlob = "hello".getBytes(); - ModelMetadata testModelMetadata = getModelMetadata(); - - addModelToSystemIndex(testModelID, testModelMetadata, testModelBlob); - - String restURI = String.join("/", KNNPlugin.KNN_BASE_URI, MODELS, testModelID); + @SneakyThrows + public void testGetModel_whenModelIdExists_thenSucceed() { + String modelId = "test-model-id"; + String trainingIndexName = "train-index"; + String trainingFieldName = "train-field"; + int dimension = 8; + String modelDescription = "dummy description"; + + createBasicKnnIndex(trainingIndexName, trainingFieldName, dimension); + + ingestDataAndTrainModel( + modelId, + trainingIndexName, + trainingFieldName, + dimension, + modelDescription, + xContentBuilderToMap(getModelMethodBuilder()) + ); + assertTrainingSucceeds(modelId, NUM_OF_ATTEMPTS, DELAY_MILLI_SEC); + + String restURI = String.join("/", KNNPlugin.KNN_BASE_URI, MODELS, modelId); Request request = new Request("GET", restURI); Response response = client().performRequest(request); @@ -68,30 +74,30 @@ public void testGetModelExists() throws IOException { assertNotNull(responseBody); Map responseMap = createParser(XContentType.JSON.xContent(), responseBody).map(); - - assertEquals(testModelID, responseMap.get(MODEL_ID)); - assertEquals(testModelMetadata.getDescription(), responseMap.get(MODEL_DESCRIPTION)); - assertEquals(testModelMetadata.getDimension(), responseMap.get(DIMENSION)); - assertEquals(testModelMetadata.getError(), responseMap.get(MODEL_ERROR)); - assertEquals(testModelMetadata.getKnnEngine().getName(), responseMap.get(KNN_ENGINE)); - assertEquals(testModelMetadata.getSpaceType().getValue(), responseMap.get(METHOD_PARAMETER_SPACE_TYPE)); - assertEquals(testModelMetadata.getState().getName(), responseMap.get(MODEL_STATE)); - assertEquals(testModelMetadata.getTimestamp(), responseMap.get(MODEL_TIMESTAMP)); + assertEquals(modelId, responseMap.get(MODEL_ID)); + assertEquals(modelDescription, responseMap.get(MODEL_DESCRIPTION)); + assertEquals(FAISS.getName(), responseMap.get(KNN_ENGINE)); + assertEquals(L2.getValue(), responseMap.get(METHOD_PARAMETER_SPACE_TYPE)); } - public void testGetModelExistsWithFilter() throws IOException { - createModelSystemIndex(); - String testModelID = "test-model-id"; - byte[] testModelBlob = "hello".getBytes(); - ModelMetadata testModelMetadata = getModelMetadata(); + @SneakyThrows + public void testGetModel_whenFilterApplied_thenReturnExpectedFields() { + String modelId = "test-model-id"; + String trainingIndexName = "train-index"; + String trainingFieldName = "train-field"; + int dimension = 8; + String modelDescription = "dummy description"; - addModelToSystemIndex(testModelID, testModelMetadata, testModelBlob); + createBasicKnnIndex(trainingIndexName, trainingFieldName, dimension); + Map method = xContentBuilderToMap(getModelMethodBuilder()); + ingestDataAndTrainModel(modelId, trainingIndexName, trainingFieldName, dimension, modelDescription, method); + assertTrainingSucceeds(modelId, NUM_OF_ATTEMPTS, DELAY_MILLI_SEC); - String restURI = String.join("/", KNNPlugin.KNN_BASE_URI, MODELS, testModelID); + String restURI = String.join("/", KNNPlugin.KNN_BASE_URI, MODELS, modelId); Request request = new Request("GET", restURI); - List filterdPath = Arrays.asList(MODEL_ID, MODEL_DESCRIPTION, MODEL_TIMESTAMP, KNN_ENGINE); - request.addParameter("filter_path", Strings.join(filterdPath, ",")); + List filteredPath = Arrays.asList(MODEL_ID, MODEL_DESCRIPTION, MODEL_TIMESTAMP, KNN_ENGINE); + request.addParameter("filter_path", Strings.join(filteredPath, ",")); Response response = client().performRequest(request); assertEquals(RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); @@ -101,19 +107,17 @@ public void testGetModelExistsWithFilter() throws IOException { Map responseMap = createParser(XContentType.JSON.xContent(), responseBody).map(); - assertTrue(responseMap.size() == filterdPath.size()); - assertEquals(testModelID, responseMap.get(MODEL_ID)); - assertEquals(testModelMetadata.getDescription(), responseMap.get(MODEL_DESCRIPTION)); - assertEquals(testModelMetadata.getTimestamp(), responseMap.get(MODEL_TIMESTAMP)); - assertEquals(testModelMetadata.getKnnEngine().getName(), responseMap.get(KNN_ENGINE)); + assertTrue(responseMap.size() == filteredPath.size()); + assertEquals(modelId, responseMap.get(MODEL_ID)); + assertEquals(modelDescription, responseMap.get(MODEL_DESCRIPTION)); + assertEquals(FAISS.getName(), responseMap.get(KNN_ENGINE)); assertFalse(responseMap.containsKey(DIMENSION)); assertFalse(responseMap.containsKey(MODEL_ERROR)); assertFalse(responseMap.containsKey(METHOD_PARAMETER_SPACE_TYPE)); assertFalse(responseMap.containsKey(MODEL_STATE)); } - public void testGetModelFailsInvalid() throws IOException { - createModelSystemIndex(); + public void testGetModel_whenModelIDIsInValid_thenFail() { String restURI = String.join("/", KNNPlugin.KNN_BASE_URI, MODELS, "invalid-model-id"); Request request = new Request("GET", restURI); @@ -121,8 +125,7 @@ public void testGetModelFailsInvalid() throws IOException { assertTrue(ex.getMessage().contains("\"invalid-model-id\"")); } - public void testGetModelFailsBlank() throws IOException { - createModelSystemIndex(); + public void testGetModel_whenIDIsBlank_thenFail() { String restURI = String.join("/", KNNPlugin.KNN_BASE_URI, MODELS, " "); Request request = new Request("GET", restURI); diff --git a/src/test/java/org/opensearch/knn/plugin/action/RestKNNStatsHandlerIT.java b/src/test/java/org/opensearch/knn/plugin/action/RestKNNStatsHandlerIT.java index 1a879aa0c..be71ff5bd 100644 --- a/src/test/java/org/opensearch/knn/plugin/action/RestKNNStatsHandlerIT.java +++ b/src/test/java/org/opensearch/knn/plugin/action/RestKNNStatsHandlerIT.java @@ -5,6 +5,7 @@ package org.opensearch.knn.plugin.action; +import lombok.SneakyThrows; import org.apache.http.util.EntityUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -16,24 +17,39 @@ import org.opensearch.cluster.health.ClusterHealthStatus; import org.opensearch.common.settings.Settings; import org.opensearch.common.unit.TimeValue; +import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.rest.RestStatus; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.index.query.MatchAllQueryBuilder; import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.QueryBuilders; import org.opensearch.knn.KNNRestTestCase; -import org.opensearch.knn.index.KNNQueryBuilder; import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.query.KNNQueryBuilder; import org.opensearch.knn.plugin.stats.KNNStats; import org.opensearch.knn.plugin.stats.StatNames; -import org.opensearch.rest.RestStatus; import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.opensearch.knn.plugin.stats.KNNStatsConfig.KNN_STATS; +import java.util.*; + +import static org.opensearch.knn.TestUtils.*; +import static org.opensearch.knn.TestUtils.PROPERTIES; +import static org.opensearch.knn.TestUtils.VECTOR_TYPE; +import static org.opensearch.knn.common.KNNConstants.FAISS_NAME; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.LUCENE_NAME; +import static org.opensearch.knn.common.KNNConstants.MAX_DISTANCE; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_IVF; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NLIST; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; +import static org.opensearch.knn.common.KNNConstants.MIN_SCORE; +import static org.opensearch.knn.common.KNNConstants.MODEL_ID; +import static org.opensearch.knn.common.KNNConstants.MODEL_INDEX_NAME; +import static org.opensearch.knn.common.KNNConstants.NMSLIB_NAME; +import static org.opensearch.knn.common.KNNConstants.PARAMETERS; +import static org.opensearch.knn.common.KNNConstants.NAME; /** * Integration tests to check the correctness of RestKNNStatsHandler @@ -41,14 +57,27 @@ public class RestKNNStatsHandlerIT extends KNNRestTestCase { private static final Logger logger = LogManager.getLogger(RestKNNStatsHandlerIT.class); + private static final String TRAINING_INDEX = "training-index"; + private static final String TRAINING_FIELD = "training-field"; + private static final String TEST_MODEL_ID = "model-id"; + private static final String TEST_INDEX = "test-index"; + private static final String MODEL_DESCRIPTION = "Description for train model test"; private boolean isDebuggingTest = new DisableOnDebug(null).isDebugging(); private boolean isDebuggingRemoteCluster = System.getProperty("cluster.debug", "false").equals("true"); + private static final String FIELD_NAME_2 = "test_field_two"; + private static final String FIELD_NAME_3 = "test_field_three"; + private static final String FIELD_LUCENE_NAME = "lucene_test_field"; + private static final int DIMENSION = 4; + private static int DOC_ID = 0; + private static final int NUM_DOCS = 10; + private static final int DELAY_MILLI_SEC = 1000; + private static final int NUM_OF_ATTEMPTS = 30; private KNNStats knnStats; @Before public void setup() { - knnStats = new KNNStats(KNN_STATS); + knnStats = new KNNStats(); } /** @@ -77,9 +106,10 @@ public void testStatsValueCheck() throws IOException { Map nodeStats0 = parseNodeStatsResponse(responseBody).get(0); Integer hitCount0 = (Integer) nodeStats0.get(StatNames.HIT_COUNT.getName()); Integer missCount0 = (Integer) nodeStats0.get(StatNames.MISS_COUNT.getName()); + Integer knnQueryWithFilterCount0 = (Integer) nodeStats0.get(StatNames.KNN_QUERY_WITH_FILTER_REQUESTS.getName()); // Setup index - createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); + createKnnIndex(INDEX_NAME, buildKNNIndexSettings(0), createKnnIndexMapping(FIELD_NAME, 2)); // Index test document Float[] vector = { 6.0f, 6.0f }; @@ -95,9 +125,11 @@ public void testStatsValueCheck() throws IOException { Map nodeStats1 = parseNodeStatsResponse(responseBody).get(0); Integer hitCount1 = (Integer) nodeStats1.get(StatNames.HIT_COUNT.getName()); Integer missCount1 = (Integer) nodeStats1.get(StatNames.MISS_COUNT.getName()); + Integer knnQueryWithFilterCount1 = (Integer) nodeStats1.get(StatNames.KNN_QUERY_WITH_FILTER_REQUESTS.getName()); assertEquals(hitCount0, hitCount1); assertEquals((Integer) (missCount0 + 1), missCount1); + assertEquals(knnQueryWithFilterCount0, knnQueryWithFilterCount1); // Second search: Ensure that hits=1 searchKNNIndex(INDEX_NAME, new KNNQueryBuilder(FIELD_NAME, qvector, 1), 1); @@ -108,9 +140,24 @@ public void testStatsValueCheck() throws IOException { Map nodeStats2 = parseNodeStatsResponse(responseBody).get(0); Integer hitCount2 = (Integer) nodeStats2.get(StatNames.HIT_COUNT.getName()); Integer missCount2 = (Integer) nodeStats2.get(StatNames.MISS_COUNT.getName()); + Integer knnQueryWithFilterCount2 = (Integer) nodeStats2.get(StatNames.KNN_QUERY_WITH_FILTER_REQUESTS.getName()); assertEquals(missCount1, missCount2); assertEquals((Integer) (hitCount1 + 1), hitCount2); + assertEquals(knnQueryWithFilterCount0, knnQueryWithFilterCount2); + + putMappingRequest(INDEX_NAME, createKnnIndexMapping(FIELD_LUCENE_NAME, 2, METHOD_HNSW, LUCENE_NAME)); + addKnnDoc(INDEX_NAME, "2", FIELD_LUCENE_NAME, vector); + + searchKNNIndex(INDEX_NAME, new KNNQueryBuilder(FIELD_LUCENE_NAME, qvector, 1, QueryBuilders.termQuery("_id", "1")), 1); + + response = getKnnStats(Collections.emptyList(), Collections.emptyList()); + responseBody = EntityUtils.toString(response.getEntity()); + + Map nodeStats3 = parseNodeStatsResponse(responseBody).get(0); + Integer knnQueryWithFilterCount3 = (Integer) nodeStats3.get(StatNames.KNN_QUERY_WITH_FILTER_REQUESTS.getName()); + + assertEquals((Integer) (knnQueryWithFilterCount0 + 1), knnQueryWithFilterCount3); } /** @@ -290,23 +337,29 @@ public void testScriptStats_multipleShards() throws Exception { assertEquals(initialScriptQueryErrors + 2, (int) (nodeStats.get(0).get(StatNames.SCRIPT_QUERY_ERRORS.getName()))); } - public void testModelIndexHealthMetricsStats() throws IOException { - // Create request that filters only model index + public void testModelIndexHealthMetricsStats() throws Exception { String modelIndexStatusName = StatNames.MODEL_INDEX_STATUS.getName(); + // index can be created in one of previous tests, and as we do not delete it each test the check below became optional + if (!systemIndexExists(MODEL_INDEX_NAME)) { - Response response = getKnnStats(Collections.emptyList(), Arrays.asList(modelIndexStatusName)); - String responseBody = EntityUtils.toString(response.getEntity()); - Map statsMap = createParser(XContentType.JSON.xContent(), responseBody).map(); + final Response response = getKnnStats(Collections.emptyList(), Arrays.asList(modelIndexStatusName)); + final String responseBody = EntityUtils.toString(response.getEntity()); + final Map statsMap = createParser(XContentType.JSON.xContent(), responseBody).map(); - // Check that model health status is null since model index is not created to system yet - assertNull(statsMap.get(StatNames.MODEL_INDEX_STATUS.getName())); + // Check that model health status is null since model index is not created to system yet + assertNull(statsMap.get(StatNames.MODEL_INDEX_STATUS.getName())); - createModelSystemIndex(); + // Train a model so that the system index will get created + createBasicKnnIndex(TRAINING_INDEX, TRAINING_FIELD, DIMENSION); + bulkIngestRandomVectors(TRAINING_INDEX, TRAINING_FIELD, NUM_DOCS, DIMENSION); + trainKnnModel(TEST_MODEL_ID, TRAINING_INDEX, TRAINING_FIELD, DIMENSION, MODEL_DESCRIPTION); + validateModelCreated(TEST_MODEL_ID); + } - response = getKnnStats(Collections.emptyList(), Arrays.asList(modelIndexStatusName)); + Response response = getKnnStats(Collections.emptyList(), Arrays.asList(modelIndexStatusName)); - responseBody = EntityUtils.toString(response.getEntity()); - statsMap = createParser(XContentType.JSON.xContent(), responseBody).map(); + final String responseBody = EntityUtils.toString(response.getEntity()); + final Map statsMap = createParser(XContentType.JSON.xContent(), responseBody).map(); // Check that model health status is not null assertNotNull(statsMap.get(modelIndexStatusName)); @@ -333,6 +386,186 @@ public void testModelIndexingDegradedMetricsStats() throws IOException { assertEquals(false, nodeStats.get(statName)); } + /** + * Test checks that handler correctly returns value for field per engine stats + * + * @throws IOException throws IOException + */ + public void testFieldByEngineStats() throws Exception { + createKnnIndex(INDEX_NAME, buildKNNIndexSettings(0), createKnnIndexMapping(FIELD_NAME, 2, METHOD_HNSW, NMSLIB_NAME)); + putMappingRequest(INDEX_NAME, createKnnIndexMapping(FIELD_NAME_2, 3, METHOD_HNSW, LUCENE_NAME)); + putMappingRequest(INDEX_NAME, createKnnIndexMapping(FIELD_NAME_3, 3, METHOD_HNSW, FAISS_NAME)); + + Response response = getKnnStats(Collections.emptyList(), Collections.emptyList()); + + String responseBody = EntityUtils.toString(response.getEntity()); + + Map nodeStats0 = parseNodeStatsResponse(responseBody).get(0); + boolean faissField = (Boolean) nodeStats0.get(StatNames.FAISS_LOADED.getName()); + boolean luceneField = (Boolean) nodeStats0.get(StatNames.LUCENE_LOADED.getName()); + boolean nmslibField = (Boolean) nodeStats0.get(StatNames.NMSLIB_LOADED.getName()); + + assertTrue(faissField); + assertTrue(luceneField); + assertTrue(nmslibField); + } + + public void testFieldsByEngineModelTraining() throws Exception { + createBasicKnnIndex(TRAINING_INDEX, TRAINING_FIELD, DIMENSION); + bulkIngestRandomVectors(TRAINING_INDEX, TRAINING_FIELD, NUM_DOCS, DIMENSION); + trainKnnModel(TEST_MODEL_ID, TRAINING_INDEX, TRAINING_FIELD, DIMENSION, MODEL_DESCRIPTION); + + validateModelCreated(TEST_MODEL_ID); + + createKnnIndex(TEST_INDEX, modelIndexMapping(FIELD_NAME, TEST_MODEL_ID)); + + addKNNDocs(TEST_INDEX, FIELD_NAME, DIMENSION, DOC_ID, NUM_DOCS); + + final Response response = getKnnStats(Collections.emptyList(), Collections.emptyList()); + final String responseBody = EntityUtils.toString(response.getEntity()); + + final Map nodeStats0 = parseNodeStatsResponse(responseBody).get(0); + + boolean faissField = (Boolean) nodeStats0.get(StatNames.FAISS_LOADED.getName()); + + assertTrue(faissField); + } + + public void testRadialSearchStats_thenSucceed() throws Exception { + createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2, METHOD_HNSW, LUCENE_NAME)); + Float[] vector = { 6.0f, 6.0f }; + addKnnDoc(INDEX_NAME, "1", FIELD_NAME, vector); + + // First search: radial search by min score + XContentBuilder queryBuilderMinScore = XContentFactory.jsonBuilder().startObject().startObject("query"); + queryBuilderMinScore.startObject("knn"); + queryBuilderMinScore.startObject(FIELD_NAME); + queryBuilderMinScore.field("vector", vector); + queryBuilderMinScore.field(MIN_SCORE, 0.95f); + queryBuilderMinScore.endObject(); + queryBuilderMinScore.endObject(); + queryBuilderMinScore.endObject().endObject(); + + Integer minScoreStatBeforeMinScoreSearch = getStatCount(StatNames.MIN_SCORE_QUERY_REQUESTS.getName()); + searchKNNIndex(INDEX_NAME, queryBuilderMinScore, 1); + Integer minScoreStatAfterMinScoreSearch = getStatCount(StatNames.MIN_SCORE_QUERY_REQUESTS.getName()); + + assertEquals(1, minScoreStatAfterMinScoreSearch - minScoreStatBeforeMinScoreSearch); + + // Second search: radial search by min score with filter + XContentBuilder queryBuilderMinScoreWithFilter = XContentFactory.jsonBuilder().startObject().startObject("query"); + queryBuilderMinScoreWithFilter.startObject("knn"); + queryBuilderMinScoreWithFilter.startObject(FIELD_NAME); + queryBuilderMinScoreWithFilter.field("vector", vector); + queryBuilderMinScoreWithFilter.field(MIN_SCORE, 0.95f); + queryBuilderMinScoreWithFilter.field("filter", QueryBuilders.termQuery("_id", "1")); + queryBuilderMinScoreWithFilter.endObject(); + queryBuilderMinScoreWithFilter.endObject(); + queryBuilderMinScoreWithFilter.endObject().endObject(); + + Integer minScoreWithFilterStatBeforeMinScoreWithFilterSearch = getStatCount( + StatNames.MIN_SCORE_QUERY_WITH_FILTER_REQUESTS.getName() + ); + Integer minScoreStatBeforeMinScoreWithFilterSearch = getStatCount(StatNames.MIN_SCORE_QUERY_REQUESTS.getName()); + searchKNNIndex(INDEX_NAME, queryBuilderMinScoreWithFilter, 1); + Integer minScoreWithFilterStatAfterMinScoreWithFilterSearch = getStatCount( + StatNames.MIN_SCORE_QUERY_WITH_FILTER_REQUESTS.getName() + ); + Integer minScoreStatAfterMinScoreWithFilterSearch = getStatCount(StatNames.MIN_SCORE_QUERY_REQUESTS.getName()); + + assertEquals(1, minScoreWithFilterStatAfterMinScoreWithFilterSearch - minScoreWithFilterStatBeforeMinScoreWithFilterSearch); + assertEquals(1, minScoreStatAfterMinScoreWithFilterSearch - minScoreStatBeforeMinScoreWithFilterSearch); + + // Third search: radial search by max distance + XContentBuilder queryBuilderMaxDistance = XContentFactory.jsonBuilder().startObject().startObject("query"); + queryBuilderMaxDistance.startObject("knn"); + queryBuilderMaxDistance.startObject(FIELD_NAME); + queryBuilderMaxDistance.field("vector", vector); + queryBuilderMaxDistance.field(MAX_DISTANCE, 100f); + queryBuilderMaxDistance.endObject(); + queryBuilderMaxDistance.endObject(); + queryBuilderMaxDistance.endObject().endObject(); + + Integer maxDistanceStatBeforeMaxDistanceSearch = getStatCount(StatNames.MAX_DISTANCE_QUERY_REQUESTS.getName()); + searchKNNIndex(INDEX_NAME, queryBuilderMaxDistance, 0); + Integer maxDistanceStatAfterMaxDistanceSearch = getStatCount(StatNames.MAX_DISTANCE_QUERY_REQUESTS.getName()); + + assertEquals(1, maxDistanceStatAfterMaxDistanceSearch - maxDistanceStatBeforeMaxDistanceSearch); + + // Fourth search: radial search by max distance with filter + XContentBuilder queryBuilderMaxDistanceWithFilter = XContentFactory.jsonBuilder().startObject().startObject("query"); + queryBuilderMaxDistanceWithFilter.startObject("knn"); + queryBuilderMaxDistanceWithFilter.startObject(FIELD_NAME); + queryBuilderMaxDistanceWithFilter.field("vector", vector); + queryBuilderMaxDistanceWithFilter.field(MAX_DISTANCE, 100f); + queryBuilderMaxDistanceWithFilter.field("filter", QueryBuilders.termQuery("_id", "1")); + queryBuilderMaxDistanceWithFilter.endObject(); + queryBuilderMaxDistanceWithFilter.endObject(); + queryBuilderMaxDistanceWithFilter.endObject().endObject(); + + Integer maxDistanceWithFilterStatBeforeMaxDistanceWithFilterSearch = getStatCount( + StatNames.MAX_DISTANCE_QUERY_WITH_FILTER_REQUESTS.getName() + ); + Integer maxDistanceStatBeforeMaxDistanceWithFilterSearch = getStatCount(StatNames.MAX_DISTANCE_QUERY_REQUESTS.getName()); + searchKNNIndex(INDEX_NAME, queryBuilderMaxDistanceWithFilter, 0); + Integer maxDistanceWithFilterStatAfterMaxDistanceWithFilterSearch = getStatCount( + StatNames.MAX_DISTANCE_QUERY_WITH_FILTER_REQUESTS.getName() + ); + Integer maxDistanceStatAfterMaxDistanceWithFilterSearch = getStatCount(StatNames.MAX_DISTANCE_QUERY_REQUESTS.getName()); + + assertEquals( + 1, + maxDistanceWithFilterStatAfterMaxDistanceWithFilterSearch - maxDistanceWithFilterStatBeforeMaxDistanceWithFilterSearch + ); + assertEquals(1, maxDistanceStatAfterMaxDistanceWithFilterSearch - maxDistanceStatBeforeMaxDistanceWithFilterSearch); + } + + public void trainKnnModel(String modelId, String trainingIndexName, String trainingFieldName, int dimension, String description) + throws IOException { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_IVF) + .field(KNN_ENGINE, FAISS_NAME) + .field(METHOD_PARAMETER_SPACE_TYPE, SpaceType.L2.getValue()) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_NLIST, 1) + .endObject() + .endObject(); + Map method = xContentBuilderToMap(builder); + + Response trainResponse = trainModel(modelId, trainingIndexName, trainingFieldName, dimension, method, description); + assertEquals(RestStatus.OK, RestStatus.fromCode(trainResponse.getStatusLine().getStatusCode())); + } + + public void validateModelCreated(String modelId) throws Exception { + Response getResponse = getModel(modelId, null); + String responseBody = EntityUtils.toString(getResponse.getEntity()); + assertNotNull(responseBody); + + Map responseMap = createParser(XContentType.JSON.xContent(), responseBody).map(); + assertEquals(modelId, responseMap.get(MODEL_ID)); + assertTrainingSucceeds(modelId, NUM_OF_ATTEMPTS, DELAY_MILLI_SEC); + } + + // mapping to create index from model + public String modelIndexMapping(String fieldName, String modelId) throws IOException { + return XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES) + .startObject(fieldName) + .field(VECTOR_TYPE, KNN_VECTOR) + .field(MODEL_ID, modelId) + .endObject() + .endObject() + .endObject() + .toString(); + } + + @Override + protected boolean preserveClusterUponCompletion() { + return false; + } + // Useful settings when debugging to prevent timeouts @Override protected Settings restClientSettings() { @@ -342,4 +575,11 @@ protected Settings restClientSettings() { return super.restClientSettings(); } } + + @SneakyThrows + private Integer getStatCount(String statName) { + Response response = getKnnStats(Collections.emptyList(), Collections.emptyList()); + String responseBody = EntityUtils.toString(response.getEntity()); + return (Integer) parseNodeStatsResponse(responseBody).get(0).get(statName); + } } diff --git a/src/test/java/org/opensearch/knn/plugin/action/RestKNNWarmupHandlerIT.java b/src/test/java/org/opensearch/knn/plugin/action/RestKNNWarmupHandlerIT.java index a2078c291..bc1865522 100644 --- a/src/test/java/org/opensearch/knn/plugin/action/RestKNNWarmupHandlerIT.java +++ b/src/test/java/org/opensearch/knn/plugin/action/RestKNNWarmupHandlerIT.java @@ -36,7 +36,7 @@ public void testNonKnnIndex() throws IOException { knnWarmup(Collections.singletonList("not-knn-index")); } - public void testEmptyIndex() throws IOException { + public void testEmptyIndex() throws Exception { int graphCountBefore = getTotalGraphsInCache(); createKnnIndex(testIndexName, getKNNDefaultIndexSettings(), createKnnIndexMapping(testFieldName, dimensions)); @@ -45,9 +45,9 @@ public void testEmptyIndex() throws IOException { assertEquals(graphCountBefore, getTotalGraphsInCache()); } - public void testSingleIndex() throws IOException { + public void testSingleIndex() throws Exception { int graphCountBefore = getTotalGraphsInCache(); - createKnnIndex(testIndexName, getKNNDefaultIndexSettings(), createKnnIndexMapping(testFieldName, dimensions)); + createKnnIndex(testIndexName, buildKNNIndexSettings(0), createKnnIndexMapping(testFieldName, dimensions)); addKnnDoc(testIndexName, "1", testFieldName, new Float[] { 6.0f, 6.0f }); knnWarmup(Collections.singletonList(testIndexName)); @@ -55,13 +55,13 @@ public void testSingleIndex() throws IOException { assertEquals(graphCountBefore + 1, getTotalGraphsInCache()); } - public void testMultipleIndices() throws IOException { + public void testMultipleIndices() throws Exception { int graphCountBefore = getTotalGraphsInCache(); - createKnnIndex(testIndexName + "1", getKNNDefaultIndexSettings(), createKnnIndexMapping(testFieldName, dimensions)); + createKnnIndex(testIndexName + "1", buildKNNIndexSettings(0), createKnnIndexMapping(testFieldName, dimensions)); addKnnDoc(testIndexName + "1", "1", testFieldName, new Float[] { 6.0f, 6.0f }); - createKnnIndex(testIndexName + "2", getKNNDefaultIndexSettings(), createKnnIndexMapping(testFieldName, dimensions)); + createKnnIndex(testIndexName + "2", buildKNNIndexSettings(0), createKnnIndexMapping(testFieldName, dimensions)); addKnnDoc(testIndexName + "2", "1", testFieldName, new Float[] { 6.0f, 6.0f }); knnWarmup(Arrays.asList(testIndexName + "1", testIndexName + "2")); diff --git a/src/test/java/org/opensearch/knn/plugin/action/RestLegacyKNNStatsHandlerIT.java b/src/test/java/org/opensearch/knn/plugin/action/RestLegacyKNNStatsHandlerIT.java index e4fe1891f..631d63874 100644 --- a/src/test/java/org/opensearch/knn/plugin/action/RestLegacyKNNStatsHandlerIT.java +++ b/src/test/java/org/opensearch/knn/plugin/action/RestLegacyKNNStatsHandlerIT.java @@ -13,7 +13,7 @@ import org.opensearch.knn.KNNRestTestCase; import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.KNNQueryBuilder; +import org.opensearch.knn.index.query.KNNQueryBuilder; import org.opensearch.knn.plugin.KNNPlugin; import org.opensearch.knn.plugin.stats.KNNStats; import org.opensearch.knn.plugin.stats.StatNames; @@ -29,7 +29,7 @@ import org.opensearch.common.unit.TimeValue; import org.opensearch.index.query.MatchAllQueryBuilder; import org.opensearch.index.query.QueryBuilder; -import org.opensearch.rest.RestStatus; +import org.opensearch.core.rest.RestStatus; import java.io.IOException; import java.util.Arrays; @@ -38,8 +38,6 @@ import java.util.List; import java.util.Map; -import static org.opensearch.knn.plugin.stats.KNNStatsConfig.KNN_STATS; - /** * Integration tests to check the correctness of Legacy stats api */ @@ -53,7 +51,7 @@ public class RestLegacyKNNStatsHandlerIT extends KNNRestTestCase { @Before public void setup() { - knnStats = new KNNStats(KNN_STATS); + knnStats = new KNNStats(); } /** @@ -84,7 +82,7 @@ public void testStatsValueCheck() throws IOException { Integer missCount0 = (Integer) nodeStats0.get(StatNames.MISS_COUNT.getName()); // Setup index - createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); + createKnnIndex(INDEX_NAME, buildKNNIndexSettings(0), createKnnIndexMapping(FIELD_NAME, 2)); // Index test document Float[] vector = { 6.0f, 6.0f }; @@ -321,10 +319,15 @@ public void testScriptStats_multipleShards() throws Exception { // Useful settings when debugging to prevent timeouts @Override protected Settings restClientSettings() { + final Settings.Builder builder = Settings.builder(); if (isDebuggingTest || isDebuggingRemoteCluster) { - return Settings.builder().put(CLIENT_SOCKET_TIMEOUT, TimeValue.timeValueMinutes(10)).build(); + builder.put(CLIENT_SOCKET_TIMEOUT, TimeValue.timeValueMinutes(10)); } else { - return super.restClientSettings(); + if (System.getProperty("tests.rest.client_path_prefix") != null) { + builder.put(CLIENT_PATH_PREFIX, System.getProperty("tests.rest.client_path_prefix")); + } } + builder.put("strictDeprecationMode", false); + return builder.build(); } } diff --git a/src/test/java/org/opensearch/knn/plugin/action/RestLegacyKNNWarmupHandlerIT.java b/src/test/java/org/opensearch/knn/plugin/action/RestLegacyKNNWarmupHandlerIT.java index f74135c0c..e0fe9ec5c 100644 --- a/src/test/java/org/opensearch/knn/plugin/action/RestLegacyKNNWarmupHandlerIT.java +++ b/src/test/java/org/opensearch/knn/plugin/action/RestLegacyKNNWarmupHandlerIT.java @@ -27,6 +27,7 @@ public class RestLegacyKNNWarmupHandlerIT extends KNNRestTestCase { + public static final int ALWAYS_BUILD_GRAPH = 0; private final String testIndexName = "test-index"; private final String testFieldName = "test-field"; private final int dimensions = 2; @@ -43,18 +44,18 @@ public void testNonKnnIndex() throws IOException { executeWarmupRequest(Collections.singletonList("not-knn-index"), KNNPlugin.LEGACY_KNN_BASE_URI); } - public void testEmptyIndex() throws IOException { + public void testEmptyIndex() throws Exception { int graphCountBefore = getTotalGraphsInCache(); - createKnnIndex(testIndexName, getKNNDefaultIndexSettings(), createKnnIndexMapping(testFieldName, dimensions)); + createKnnIndex(testIndexName, buildKNNIndexSettings(ALWAYS_BUILD_GRAPH), createKnnIndexMapping(testFieldName, dimensions)); executeWarmupRequest(Collections.singletonList(testIndexName), KNNPlugin.LEGACY_KNN_BASE_URI); assertEquals(graphCountBefore, getTotalGraphsInCache()); } - public void testSingleIndex() throws IOException { + public void testSingleIndex() throws Exception { int graphCountBefore = getTotalGraphsInCache(); - createKnnIndex(testIndexName, getKNNDefaultIndexSettings(), createKnnIndexMapping(testFieldName, dimensions)); + createKnnIndex(testIndexName, buildKNNIndexSettings(ALWAYS_BUILD_GRAPH), createKnnIndexMapping(testFieldName, dimensions)); addKnnDoc(testIndexName, "1", testFieldName, new Float[] { 6.0f, 6.0f }); executeWarmupRequest(Collections.singletonList(testIndexName), KNNPlugin.LEGACY_KNN_BASE_URI); @@ -62,13 +63,13 @@ public void testSingleIndex() throws IOException { assertEquals(graphCountBefore + 1, getTotalGraphsInCache()); } - public void testMultipleIndices() throws IOException { + public void testMultipleIndices() throws Exception { int graphCountBefore = getTotalGraphsInCache(); - createKnnIndex(testIndexName + "1", getKNNDefaultIndexSettings(), createKnnIndexMapping(testFieldName, dimensions)); + createKnnIndex(testIndexName + "1", buildKNNIndexSettings(0), createKnnIndexMapping(testFieldName, dimensions)); addKnnDoc(testIndexName + "1", "1", testFieldName, new Float[] { 6.0f, 6.0f }); - createKnnIndex(testIndexName + "2", getKNNDefaultIndexSettings(), createKnnIndexMapping(testFieldName, dimensions)); + createKnnIndex(testIndexName + "2", buildKNNIndexSettings(0), createKnnIndexMapping(testFieldName, dimensions)); addKnnDoc(testIndexName + "2", "1", testFieldName, new Float[] { 6.0f, 6.0f }); executeWarmupRequest(Arrays.asList(testIndexName + "1", testIndexName + "2"), KNNPlugin.LEGACY_KNN_BASE_URI); diff --git a/src/test/java/org/opensearch/knn/plugin/action/RestSearchModelHandlerIT.java b/src/test/java/org/opensearch/knn/plugin/action/RestSearchModelHandlerIT.java index d65f434f6..5d5f6d3bf 100644 --- a/src/test/java/org/opensearch/knn/plugin/action/RestSearchModelHandlerIT.java +++ b/src/test/java/org/opensearch/knn/plugin/action/RestSearchModelHandlerIT.java @@ -11,34 +11,32 @@ package org.opensearch.knn.plugin.action; +import lombok.SneakyThrows; import org.apache.http.util.EntityUtils; import org.opensearch.action.search.SearchResponse; import org.opensearch.client.Request; import org.opensearch.client.Response; import org.opensearch.client.ResponseException; -import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.XContentParser; +import org.opensearch.core.xcontent.XContentParser; import org.opensearch.common.xcontent.XContentType; import org.opensearch.knn.KNNRestTestCase; -import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.util.KNNEngine; import org.opensearch.knn.indices.Model; -import org.opensearch.knn.indices.ModelMetadata; -import org.opensearch.knn.indices.ModelState; import org.opensearch.knn.plugin.KNNPlugin; -import org.opensearch.rest.RestStatus; +import org.opensearch.core.rest.RestStatus; import org.opensearch.search.SearchHit; -import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.opensearch.knn.common.KNNConstants.MODELS; +import static org.opensearch.knn.common.KNNConstants.MODEL_INDEX_NAME; import static org.opensearch.knn.common.KNNConstants.PARAM_SIZE; import static org.opensearch.knn.common.KNNConstants.SEARCH_MODEL_MAX_SIZE; import static org.opensearch.knn.common.KNNConstants.SEARCH_MODEL_MIN_SIZE; +import static org.opensearch.knn.index.SpaceType.L2; +import static org.opensearch.knn.index.engine.KNNEngine.FAISS; /** * Integration tests to check the correctness of {@link org.opensearch.knn.plugin.rest.RestSearchModelHandler} @@ -46,12 +44,7 @@ public class RestSearchModelHandlerIT extends KNNRestTestCase { - private ModelMetadata getModelMetadata() { - return new ModelMetadata(KNNEngine.DEFAULT, SpaceType.DEFAULT, 4, ModelState.CREATED, "2021-03-27", "test model", ""); - } - - public void testNotSupportedParams() throws IOException { - createModelSystemIndex(); + public void testSearch_whenUnSupportedParamsPassed_thenFail() { String restURI = String.join("/", KNNPlugin.KNN_BASE_URI, MODELS, "_search"); Map invalidParams = new HashMap<>(); invalidParams.put("index", "index-name"); @@ -60,51 +53,69 @@ public void testNotSupportedParams() throws IOException { expectThrows(ResponseException.class, () -> client().performRequest(request)); } - public void testNoModelExists() throws IOException { - createModelSystemIndex(); + @SneakyThrows + public void testSearch_whenNoModelExists_thenReturnEmptyResults() { + // Currently, if the model index exists, we will return empty hits. If it does not exist, we will + // throw an exception. This is somewhat of a bug considering that the model index is supposed to be + // an implementation detail abstracted away from the user. However, in order to test, we need to handle + // the 2 different scenarios String restURI = String.join("/", KNNPlugin.KNN_BASE_URI, MODELS, "_search"); Request request = new Request("GET", restURI); request.setJsonEntity("{\n" + " \"query\": {\n" + " \"match_all\": {}\n" + " }\n" + "}"); - - Response response = client().performRequest(request); - assertEquals(RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); - - String responseBody = EntityUtils.toString(response.getEntity()); - assertNotNull(responseBody); - - XContentParser parser = createParser(XContentType.JSON.xContent(), responseBody); - SearchResponse searchResponse = SearchResponse.fromXContent(parser); - assertNotNull(searchResponse); - assertEquals(searchResponse.getHits().getHits().length, 0); - + if (!systemIndexExists(MODEL_INDEX_NAME)) { + ResponseException ex = expectThrows(ResponseException.class, () -> client().performRequest(request)); + assertEquals(RestStatus.NOT_FOUND.getStatus(), ex.getResponse().getStatusLine().getStatusCode()); + } else { + Response response = client().performRequest(request); + assertEquals(RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + String responseBody = EntityUtils.toString(response.getEntity()); + assertNotNull(responseBody); + XContentParser parser = createParser(XContentType.JSON.xContent(), responseBody); + SearchResponse searchResponse = SearchResponse.fromXContent(parser); + assertNotNull(searchResponse); + assertEquals(searchResponse.getHits().getHits().length, 0); + } } - public void testSizeValidationFailsInvalidSize() throws IOException { - createModelSystemIndex(); + public void testSearch_whenInvalidSizePassed_thenFail() { for (Integer invalidSize : Arrays.asList(SEARCH_MODEL_MIN_SIZE - 1, SEARCH_MODEL_MAX_SIZE + 1)) { String restURI = String.join("/", KNNPlugin.KNN_BASE_URI, MODELS, "_search?" + PARAM_SIZE + "=" + invalidSize); Request request = new Request("GET", restURI); ResponseException ex = expectThrows(ResponseException.class, () -> client().performRequest(request)); + String messageExpected = String.format( + "%s must be between %s and %s inclusive", + PARAM_SIZE, + SEARCH_MODEL_MIN_SIZE, + SEARCH_MODEL_MAX_SIZE + ); assertTrue( - ex.getMessage() - .contains( - String.format("%s must be between %d and %d inclusive", PARAM_SIZE, SEARCH_MODEL_MIN_SIZE, SEARCH_MODEL_MAX_SIZE) - ) + String.format("FAILED - Expected \"%s\" to have \"%s\"", ex.getMessage(), messageExpected), + ex.getMessage().contains(messageExpected) ); } } - public void testSearchModelExists() throws IOException { - createModelSystemIndex(); - createIndex("irrelevant-index", Settings.EMPTY); - addDocWithBinaryField("irrelevant-index", "id1", "field-name", "value"); + @SneakyThrows + public void testSearch_whenModelExists_thenSuccess() { + String trainingIndex = "irrelevant-index"; + String trainingFieldName = "train-field"; + int dimension = 8; + String modelDescription = "dummy description"; + createBasicKnnIndex(trainingIndex, trainingFieldName, dimension); + List testModelID = Arrays.asList("test-modelid1", "test-modelid2"); - byte[] testModelBlob = "hello".getBytes(); - ModelMetadata testModelMetadata = getModelMetadata(); - for (String modelID : testModelID) { - addModelToSystemIndex(modelID, testModelMetadata, testModelBlob); + for (String modelId : testModelID) { + ingestDataAndTrainModel( + modelId, + trainingIndex, + trainingFieldName, + dimension, + modelDescription, + xContentBuilderToMap(getModelMethodBuilder()) + ); + assertTrainingSucceeds(modelId, NUM_OF_ATTEMPTS, DELAY_MILLI_SEC); } String restURI = String.join("/", KNNPlugin.KNN_BASE_URI, MODELS, "_search"); @@ -128,21 +139,24 @@ public void testSearchModelExists() throws IOException { for (SearchHit hit : searchResponse.getHits().getHits()) { assertTrue(testModelID.contains(hit.getId())); Model model = Model.getModelFromSourceMap(hit.getSourceAsMap()); - assertEquals(getModelMetadata(), model.getModelMetadata()); - assertArrayEquals(testModelBlob, model.getModelBlob()); + assertEquals(modelDescription, model.getModelMetadata().getDescription()); + assertEquals(FAISS, model.getModelMetadata().getKnnEngine()); + assertEquals(L2, model.getModelMetadata().getSpaceType()); } } } - public void testSearchModelWithoutSource() throws IOException { - createModelSystemIndex(); - createIndex("irrelevant-index", Settings.EMPTY); - addDocWithBinaryField("irrelevant-index", "id1", "field-name", "value"); - List testModelID = Arrays.asList("test-modelid1", "test-modelid2"); - byte[] testModelBlob = "hello".getBytes(); - ModelMetadata testModelMetadata = getModelMetadata(); - for (String modelID : testModelID) { - addModelToSystemIndex(modelID, testModelMetadata, testModelBlob); + public void testSearchModelWithoutSource() throws Exception { + String trainingIndex = "irrelevant-index"; + String trainingFieldName = "train-field"; + int dimension = 8; + createBasicKnnIndex(trainingIndex, trainingFieldName, dimension); + + List testModelIds = Arrays.asList("test-modelid1", "test-modelid2"); + for (String modelId : testModelIds) { + String modelDescription = "dummy description"; + ingestDataAndTrainModel(modelId, trainingIndex, trainingFieldName, dimension, modelDescription); + assertTrainingSucceeds(modelId, NUM_OF_ATTEMPTS, DELAY_MILLI_SEC); } String restURI = String.join("/", KNNPlugin.KNN_BASE_URI, MODELS, "_search"); @@ -163,24 +177,26 @@ public void testSearchModelWithoutSource() throws IOException { assertNotNull(searchResponse); // returns only model from ModelIndex - assertEquals(searchResponse.getHits().getHits().length, testModelID.size()); + assertEquals(searchResponse.getHits().getHits().length, testModelIds.size()); for (SearchHit hit : searchResponse.getHits().getHits()) { - assertTrue(testModelID.contains(hit.getId())); + assertTrue(testModelIds.contains(hit.getId())); assertNull(hit.getSourceAsMap()); } } } - public void testSearchModelWithSourceFilteringIncludes() throws IOException { - createModelSystemIndex(); - createIndex("irrelevant-index", Settings.EMPTY); - addDocWithBinaryField("irrelevant-index", "id1", "field-name", "value"); - List testModelID = Arrays.asList("test-modelid1", "test-modelid2"); - byte[] testModelBlob = "hello".getBytes(); - ModelMetadata testModelMetadata = getModelMetadata(); - for (String modelID : testModelID) { - addModelToSystemIndex(modelID, testModelMetadata, testModelBlob); + public void testSearchModelWithSourceFilteringIncludes() throws Exception { + String trainingIndex = "irrelevant-index"; + String trainingFieldName = "train-field"; + int dimension = 8; + createBasicKnnIndex(trainingIndex, trainingFieldName, dimension); + + List testModelIds = Arrays.asList("test-modelid1", "test-modelid2"); + for (String modelId : testModelIds) { + String modelDescription = "dummy description"; + ingestDataAndTrainModel(modelId, trainingIndex, trainingFieldName, dimension, modelDescription); + assertTrainingSucceeds(modelId, NUM_OF_ATTEMPTS, DELAY_MILLI_SEC); } String restURI = String.join("/", KNNPlugin.KNN_BASE_URI, MODELS, "_search"); @@ -208,10 +224,10 @@ public void testSearchModelWithSourceFilteringIncludes() throws IOException { assertNotNull(searchResponse); // returns only model from ModelIndex - assertEquals(searchResponse.getHits().getHits().length, testModelID.size()); + assertEquals(searchResponse.getHits().getHits().length, testModelIds.size()); for (SearchHit hit : searchResponse.getHits().getHits()) { - assertTrue(testModelID.contains(hit.getId())); + assertTrue(testModelIds.contains(hit.getId())); Map sourceAsMap = hit.getSourceAsMap(); assertFalse(sourceAsMap.containsKey("model_blob")); assertTrue(sourceAsMap.containsKey("state")); @@ -221,15 +237,17 @@ public void testSearchModelWithSourceFilteringIncludes() throws IOException { } } - public void testSearchModelWithSourceFilteringExcludes() throws IOException { - createModelSystemIndex(); - createIndex("irrelevant-index", Settings.EMPTY); - addDocWithBinaryField("irrelevant-index", "id1", "field-name", "value"); - List testModelID = Arrays.asList("test-modelid1", "test-modelid2"); - byte[] testModelBlob = "hello".getBytes(); - ModelMetadata testModelMetadata = getModelMetadata(); - for (String modelID : testModelID) { - addModelToSystemIndex(modelID, testModelMetadata, testModelBlob); + public void testSearchModelWithSourceFilteringExcludes() throws Exception { + String trainingIndex = "irrelevant-index"; + String trainingFieldName = "train-field"; + int dimension = 8; + createBasicKnnIndex(trainingIndex, trainingFieldName, dimension); + + List testModelIds = Arrays.asList("test-modelid1", "test-modelid2"); + for (String modelId : testModelIds) { + String modelDescription = "dummy description"; + ingestDataAndTrainModel(modelId, trainingIndex, trainingFieldName, dimension, modelDescription); + assertTrainingSucceeds(modelId, NUM_OF_ATTEMPTS, DELAY_MILLI_SEC); } String restURI = String.join("/", KNNPlugin.KNN_BASE_URI, MODELS, "_search"); @@ -257,10 +275,10 @@ public void testSearchModelWithSourceFilteringExcludes() throws IOException { assertNotNull(searchResponse); // returns only model from ModelIndex - assertEquals(searchResponse.getHits().getHits().length, testModelID.size()); + assertEquals(searchResponse.getHits().getHits().length, testModelIds.size()); for (SearchHit hit : searchResponse.getHits().getHits()) { - assertTrue(testModelID.contains(hit.getId())); + assertTrue(testModelIds.contains(hit.getId())); Map sourceAsMap = hit.getSourceAsMap(); assertFalse(sourceAsMap.containsKey("model_blob")); assertTrue(sourceAsMap.containsKey("state")); diff --git a/src/test/java/org/opensearch/knn/plugin/action/RestTrainModelHandlerIT.java b/src/test/java/org/opensearch/knn/plugin/action/RestTrainModelHandlerIT.java index 4b72e22fa..1ba6eae9b 100644 --- a/src/test/java/org/opensearch/knn/plugin/action/RestTrainModelHandlerIT.java +++ b/src/test/java/org/opensearch/knn/plugin/action/RestTrainModelHandlerIT.java @@ -13,13 +13,13 @@ import org.apache.http.util.EntityUtils; import org.opensearch.client.Response; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.client.ResponseException; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.common.xcontent.XContentType; import org.opensearch.knn.KNNRestTestCase; -import org.opensearch.rest.RestStatus; +import org.opensearch.core.rest.RestStatus; -import java.io.IOException; import java.util.Map; import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_SIZE; @@ -34,7 +34,7 @@ public class RestTrainModelHandlerIT extends KNNRestTestCase { - public void testTrainModel_fail_notEnoughData() throws IOException, InterruptedException { + public void testTrainModel_fail_notEnoughData() throws Exception { // Check that training fails properly when there is not enough data @@ -193,7 +193,67 @@ public void testTrainModel_fail_tooMuchData() throws Exception { assertTrainingFails(modelId, 30, 1000); } - public void testTrainModel_success_withId() throws IOException, InterruptedException { + public void testTrainModel_fail_commaInDescription() throws Exception { + // Test checks that training when passing in an id succeeds + + String modelId = "test-model-id"; + String trainingIndexName = "train-index"; + String trainingFieldName = "train-field"; + int dimension = 8; + + // Create a training index and randomly ingest data into it + createBasicKnnIndex(trainingIndexName, trainingFieldName, dimension); + + // Call the train API with this definition: + /* + { + "training_index": "train_index", + "training_field": "train_field", + "dimension": 8, + "description": "this should be allowed to be null", + "method": { + "name":"ivf", + "engine":"faiss", + "space_type": "l2", + "parameters":{ + "nlist":1, + "encoder":{ + "name":"pq", + "parameters":{ + "code_size":2, + "m": 2 + } + } + } + } + } + */ + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, "ivf") + .field(KNN_ENGINE, "faiss") + .field(METHOD_PARAMETER_SPACE_TYPE, "l2") + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_NLIST, 1) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, "pq") + .startObject(PARAMETERS) + .field(ENCODER_PARAMETER_PQ_CODE_SIZE, 2) + .field(ENCODER_PARAMETER_PQ_M, 2) + .endObject() + .endObject() + .endObject() + .endObject(); + Map method = xContentBuilderToMap(builder); + + Exception e = expectThrows( + ResponseException.class, + () -> trainModel(modelId, trainingIndexName, trainingFieldName, dimension, method, "dummy description, with comma") + ); + assertTrue(e.getMessage().contains("Model description cannot contain any commas: ','")); + } + + public void testTrainModel_success_withId() throws Exception { // Test checks that training when passing in an id succeeds String modelId = "test-model-id"; @@ -265,7 +325,7 @@ public void testTrainModel_success_withId() throws IOException, InterruptedExcep assertTrainingSucceeds(modelId, 30, 1000); } - public void testTrainModel_success_noId() throws IOException, InterruptedException { + public void testTrainModel_success_noId() throws Exception { // Test to check if training succeeds when no id is passed in String trainingIndexName = "train-index"; @@ -342,4 +402,74 @@ public void testTrainModel_success_noId() throws IOException, InterruptedExcepti assertTrainingSucceeds(modelId, 30, 1000); } + + // Test to checks when user tries to train a model with nested fields + public void testTrainModel_success_nestedField() throws Exception { + String modelId = "test-model-id"; + String trainingIndexName = "train-index"; + String nestedFieldPath = "a.b.train-field"; + int dimension = 8; + + // Create a training index and randomly ingest data into it + String mapping = createKnnIndexNestedMapping(dimension, nestedFieldPath); + createKnnIndex(trainingIndexName, mapping); + int trainingDataCount = 200; + bulkIngestRandomVectorsWithNestedField(trainingIndexName, nestedFieldPath, trainingDataCount, dimension); + + // Call the train API with this definition: + /* + { + "training_index": "train_index", + "training_field": "a.b.train_field", + "dimension": 8, + "description": "this should be allowed to be null", + "method": { + "name":"ivf", + "engine":"faiss", + "space_type": "l2", + "parameters":{ + "nlist":1, + "encoder":{ + "name":"pq", + "parameters":{ + "code_size":2, + "m": 2 + } + } + } + } + } + */ + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, "ivf") + .field(KNN_ENGINE, "faiss") + .field(METHOD_PARAMETER_SPACE_TYPE, "l2") + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_NLIST, 1) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, "pq") + .startObject(PARAMETERS) + .field(ENCODER_PARAMETER_PQ_CODE_SIZE, 2) + .field(ENCODER_PARAMETER_PQ_M, 2) + .endObject() + .endObject() + .endObject() + .endObject(); + Map method = xContentBuilderToMap(builder); + + Response trainResponse = trainModel(modelId, trainingIndexName, nestedFieldPath, dimension, method, "dummy description"); + + assertEquals(RestStatus.OK, RestStatus.fromCode(trainResponse.getStatusLine().getStatusCode())); + + Response getResponse = getModel(modelId, null); + String responseBody = EntityUtils.toString(getResponse.getEntity()); + assertNotNull(responseBody); + + Map responseMap = createParser(XContentType.JSON.xContent(), responseBody).map(); + + assertEquals(modelId, responseMap.get(MODEL_ID)); + + assertTrainingSucceeds(modelId, 30, 1000); + } } diff --git a/src/test/java/org/opensearch/knn/plugin/script/KNNScoringScriptEngineTests.java b/src/test/java/org/opensearch/knn/plugin/script/KNNScoringScriptEngineTests.java new file mode 100644 index 000000000..cffd3d3cb --- /dev/null +++ b/src/test/java/org/opensearch/knn/plugin/script/KNNScoringScriptEngineTests.java @@ -0,0 +1,25 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.plugin.script; + +import java.io.IOException; +import java.util.Set; + +import org.opensearch.knn.KNNTestCase; +import org.opensearch.script.ScriptContext; +import org.opensearch.script.ScriptEngine; + +public class KNNScoringScriptEngineTests extends KNNTestCase { + + public void testGetSupportedContexts() throws IOException { + try (ScriptEngine engine = new KNNScoringScriptEngine()) { + Set> supportedContexts = engine.getSupportedContexts(); + assertNotNull(supportedContexts); + assertFalse(supportedContexts.isEmpty()); + } + } + +} diff --git a/src/test/java/org/opensearch/knn/plugin/script/KNNScoringSpaceFactoryTests.java b/src/test/java/org/opensearch/knn/plugin/script/KNNScoringSpaceFactoryTests.java index 70dbee248..c41e9763b 100644 --- a/src/test/java/org/opensearch/knn/plugin/script/KNNScoringSpaceFactoryTests.java +++ b/src/test/java/org/opensearch/knn/plugin/script/KNNScoringSpaceFactoryTests.java @@ -6,24 +6,30 @@ package org.opensearch.knn.plugin.script; import org.opensearch.knn.KNNTestCase; -import org.opensearch.knn.index.KNNVectorFieldMapper; +import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.SpaceType; import org.opensearch.index.mapper.NumberFieldMapper; +import org.opensearch.knn.index.mapper.KNNVectorFieldType; -import java.util.ArrayList; import java.util.List; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class KNNScoringSpaceFactoryTests extends KNNTestCase { public void testValidSpaces() { - - KNNVectorFieldMapper.KNNVectorFieldType knnVectorFieldType = mock(KNNVectorFieldMapper.KNNVectorFieldType.class); + KNNVectorFieldType knnVectorFieldType = mock(KNNVectorFieldType.class); + when(knnVectorFieldType.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(getDefaultKNNMethodContext(), 3)); + KNNVectorFieldType knnVectorFieldTypeBinary = mock(KNNVectorFieldType.class); + when(knnVectorFieldTypeBinary.getKnnMappingConfig()).thenReturn( + getMappingConfigForMethodMapping(getDefaultBinaryKNNMethodContext(), 24) + ); + when(knnVectorFieldTypeBinary.getVectorDataType()).thenReturn(VectorDataType.BINARY); NumberFieldMapper.NumberFieldType numberFieldType = new NumberFieldMapper.NumberFieldType( "field", NumberFieldMapper.NumberType.LONG ); - List floatQueryObject = new ArrayList<>(); + List floatQueryObject = List.of(1.0f, 1.0f, 1.0f); Long longQueryObject = 0L; assertTrue( @@ -45,7 +51,14 @@ public void testValidSpaces() { ); assertTrue( KNNScoringSpaceFactory.create( - SpaceType.HAMMING_BIT.getValue(), + SpaceType.HAMMING.getValue(), + floatQueryObject, + knnVectorFieldTypeBinary + ) instanceof KNNScoringSpace.Hamming + ); + assertTrue( + KNNScoringSpaceFactory.create( + KNNScoringSpaceFactory.HAMMING_BIT, longQueryObject, numberFieldType ) instanceof KNNScoringSpace.HammingBit @@ -53,6 +66,24 @@ public void testValidSpaces() { } public void testInvalidSpace() { + List floatQueryObject = List.of(1.0f, 1.0f, 1.0f); + KNNVectorFieldType knnVectorFieldType = mock(KNNVectorFieldType.class); + when(knnVectorFieldType.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(getDefaultKNNMethodContext(), 3)); + KNNVectorFieldType knnVectorFieldTypeBinary = mock(KNNVectorFieldType.class); + when(knnVectorFieldTypeBinary.getKnnMappingConfig()).thenReturn( + getMappingConfigForMethodMapping(getDefaultBinaryKNNMethodContext(), 24) + ); + when(knnVectorFieldTypeBinary.getVectorDataType()).thenReturn(VectorDataType.BINARY); + + // Verify expectThrows(IllegalArgumentException.class, () -> KNNScoringSpaceFactory.create(SpaceType.L2.getValue(), null, null)); + expectThrows( + IllegalArgumentException.class, + () -> KNNScoringSpaceFactory.create(SpaceType.L2.getValue(), floatQueryObject, knnVectorFieldTypeBinary) + ); + expectThrows( + IllegalArgumentException.class, + () -> KNNScoringSpaceFactory.create(SpaceType.HAMMING.getValue(), floatQueryObject, knnVectorFieldType) + ); } } diff --git a/src/test/java/org/opensearch/knn/plugin/script/KNNScoringSpaceTests.java b/src/test/java/org/opensearch/knn/plugin/script/KNNScoringSpaceTests.java index 090581970..4fc549d6b 100644 --- a/src/test/java/org/opensearch/knn/plugin/script/KNNScoringSpaceTests.java +++ b/src/test/java/org/opensearch/knn/plugin/script/KNNScoringSpaceTests.java @@ -5,10 +5,19 @@ package org.opensearch.knn.plugin.script; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Locale; + +import lombok.SneakyThrows; +import org.opensearch.index.mapper.MappedFieldType; import org.opensearch.knn.KNNTestCase; -import org.opensearch.knn.index.KNNVectorFieldMapper; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; import org.opensearch.index.mapper.BinaryFieldMapper; import org.opensearch.index.mapper.NumberFieldMapper; +import org.opensearch.knn.index.mapper.KNNVectorFieldType; import java.math.BigInteger; import java.util.ArrayList; @@ -19,49 +28,119 @@ import java.util.function.BiFunction; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.opensearch.knn.plugin.script.KNNScoringSpace.KNNFieldSpace.DATA_TYPES_DEFAULT; public class KNNScoringSpaceTests extends KNNTestCase { - public void testL2() { + private void expectThrowsExceptionWithNonKNNField(Class clazz) throws NoSuchMethodException { + Constructor constructor = clazz.getConstructor(Object.class, MappedFieldType.class); + NumberFieldMapper.NumberFieldType invalidFieldType = mock(NumberFieldMapper.NumberFieldType.class); + Exception e = expectThrows(InvocationTargetException.class, () -> constructor.newInstance(null, invalidFieldType)); + assertTrue(e.getCause() instanceof IllegalArgumentException); + assertTrue(e.getCause().getMessage(), e.getCause().getMessage().contains("The field type must be knn_vector")); + } + + private void expectThrowsExceptionWithKNNFieldWithBinaryDataType(Class clazz) throws NoSuchMethodException { + Constructor constructor = clazz.getConstructor(Object.class, MappedFieldType.class); + KNNVectorFieldType invalidFieldType = mock(KNNVectorFieldType.class); + when(invalidFieldType.getVectorDataType()).thenReturn(VectorDataType.BINARY); + Exception e = expectThrows(InvocationTargetException.class, () -> constructor.newInstance(null, invalidFieldType)); + assertTrue(e.getCause() instanceof IllegalArgumentException); + assertTrue( + e.getCause().getMessage(), + e.getCause().getMessage().contains(String.format("The data type should be %s", DATA_TYPES_DEFAULT)) + ); + } + + @SneakyThrows + public void testL2_whenValid_thenSucceed() { float[] arrayFloat = new float[] { 1.0f, 2.0f, 3.0f }; List arrayListQueryObject = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0)); - KNNVectorFieldMapper.KNNVectorFieldType fieldType = new KNNVectorFieldMapper.KNNVectorFieldType("test", Collections.emptyMap(), 3); + KNNMethodContext knnMethodContext = getDefaultKNNMethodContext(); + KNNVectorFieldType fieldType = new KNNVectorFieldType( + "test", + Collections.emptyMap(), + VectorDataType.FLOAT, + getMappingConfigForMethodMapping(knnMethodContext, 3) + ); KNNScoringSpace.L2 l2 = new KNNScoringSpace.L2(arrayListQueryObject, fieldType); - assertEquals(1F, l2.scoringMethod.apply(arrayFloat, arrayFloat), 0.1F); + assertEquals(1F, l2.getScoringMethod().apply(arrayFloat, arrayFloat), 0.1F); + } - NumberFieldMapper.NumberFieldType invalidFieldType = new NumberFieldMapper.NumberFieldType( - "field", - NumberFieldMapper.NumberType.INTEGER - ); - expectThrows(IllegalArgumentException.class, () -> new KNNScoringSpace.L2(arrayListQueryObject, invalidFieldType)); + @SneakyThrows + public void testL2_whenInvalidType_thenException() { + expectThrowsExceptionWithNonKNNField(KNNScoringSpace.L2.class); + expectThrowsExceptionWithKNNFieldWithBinaryDataType(KNNScoringSpace.L2.class); } - public void testCosineSimilarity() { + public void testCosineSimilarity_whenValid_thenSucceed() { float[] arrayFloat = new float[] { 1.0f, 2.0f, 3.0f }; - List arrayListQueryObject = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0)); + List arrayListQueryObject = new ArrayList<>(Arrays.asList(2.0, 4.0, 6.0)); float[] arrayFloat2 = new float[] { 2.0f, 4.0f, 6.0f }; - - KNNVectorFieldMapper.KNNVectorFieldType fieldType = new KNNVectorFieldMapper.KNNVectorFieldType("test", Collections.emptyMap(), 3); + KNNMethodContext knnMethodContext = getDefaultKNNMethodContext(); + KNNVectorFieldType fieldType = new KNNVectorFieldType( + "test", + Collections.emptyMap(), + VectorDataType.FLOAT, + getMappingConfigForMethodMapping(knnMethodContext, 3) + ); KNNScoringSpace.CosineSimilarity cosineSimilarity = new KNNScoringSpace.CosineSimilarity(arrayListQueryObject, fieldType); + assertEquals(2F, cosineSimilarity.getScoringMethod().apply(arrayFloat2, arrayFloat), 0.1F); + + // invalid zero vector + final List queryZeroVector = List.of(0.0f, 0.0f, 0.0f); + IllegalArgumentException exception1 = expectThrows( + IllegalArgumentException.class, + () -> new KNNScoringSpace.CosineSimilarity(queryZeroVector, fieldType) + ); + assertEquals( + String.format(Locale.ROOT, "zero vector is not supported when space type is [%s]", SpaceType.COSINESIMIL.getValue()), + exception1.getMessage() + ); + } - assertEquals(3F, cosineSimilarity.scoringMethod.apply(arrayFloat2, arrayFloat), 0.1F); + public void testCosineSimilarity_whenZeroVector_thenException() { + KNNMethodContext knnMethodContext = getDefaultKNNMethodContext(); + KNNVectorFieldType fieldType = new KNNVectorFieldType( + "test", + Collections.emptyMap(), + VectorDataType.FLOAT, + getMappingConfigForMethodMapping(knnMethodContext, 3) + ); - NumberFieldMapper.NumberFieldType invalidFieldType = new NumberFieldMapper.NumberFieldType( - "field", - NumberFieldMapper.NumberType.INTEGER + final List queryZeroVector = List.of(0.0f, 0.0f, 0.0f); + IllegalArgumentException exception1 = expectThrows( + IllegalArgumentException.class, + () -> new KNNScoringSpace.CosineSimilarity(queryZeroVector, fieldType) + ); + assertEquals( + String.format(Locale.ROOT, "zero vector is not supported when space type is [%s]", SpaceType.COSINESIMIL.getValue()), + exception1.getMessage() ); - expectThrows(IllegalArgumentException.class, () -> new KNNScoringSpace.CosineSimilarity(arrayListQueryObject, invalidFieldType)); } - public void testInnerProdSimilarity() { + @SneakyThrows + public void testCosineSimilarity_whenInvalidType_thenException() { + expectThrowsExceptionWithNonKNNField(KNNScoringSpace.CosineSimilarity.class); + expectThrowsExceptionWithKNNFieldWithBinaryDataType(KNNScoringSpace.CosineSimilarity.class); + } + + public void testInnerProd_whenValid_thenSucceed() { float[] arrayFloat_case1 = new float[] { 1.0f, 2.0f, 3.0f }; List arrayListQueryObject_case1 = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0)); float[] arrayFloat2_case1 = new float[] { 1.0f, 1.0f, 1.0f }; + KNNMethodContext knnMethodContext = getDefaultKNNMethodContext(); - KNNVectorFieldMapper.KNNVectorFieldType fieldType = new KNNVectorFieldMapper.KNNVectorFieldType("test", Collections.emptyMap(), 3); + KNNVectorFieldType fieldType = new KNNVectorFieldType( + "test", + Collections.emptyMap(), + VectorDataType.FLOAT, + getMappingConfigForMethodMapping(knnMethodContext, 3) + ); KNNScoringSpace.InnerProd innerProd = new KNNScoringSpace.InnerProd(arrayListQueryObject_case1, fieldType); - assertEquals(7.0F, innerProd.scoringMethod.apply(arrayFloat_case1, arrayFloat2_case1), 0.001F); + assertEquals(7.0F, innerProd.getScoringMethod().apply(arrayFloat_case1, arrayFloat2_case1), 0.001F); float[] arrayFloat_case2 = new float[] { 100_000.0f, 200_000.0f, 300_000.0f }; List arrayListQueryObject_case2 = new ArrayList<>(Arrays.asList(100_000.0, 200_000.0, 300_000.0)); @@ -69,7 +148,7 @@ public void testInnerProdSimilarity() { innerProd = new KNNScoringSpace.InnerProd(arrayListQueryObject_case2, fieldType); - assertEquals(7.142857143E-12F, innerProd.scoringMethod.apply(arrayFloat_case2, arrayFloat2_case2), 1.0E-11F); + assertEquals(7.142857143E-12F, innerProd.getScoringMethod().apply(arrayFloat_case2, arrayFloat2_case2), 1.0E-11F); float[] arrayFloat_case3 = new float[] { 100_000.0f, 200_000.0f, 300_000.0f }; List arrayListQueryObject_case3 = new ArrayList<>(Arrays.asList(100_000.0, 200_000.0, 300_000.0)); @@ -77,13 +156,13 @@ public void testInnerProdSimilarity() { innerProd = new KNNScoringSpace.InnerProd(arrayListQueryObject_case3, fieldType); - assertEquals(140_000_000_001F, innerProd.scoringMethod.apply(arrayFloat_case3, arrayFloat2_case3), 0.01F); + assertEquals(140_000_000_001F, innerProd.getScoringMethod().apply(arrayFloat_case3, arrayFloat2_case3), 0.01F); + } - NumberFieldMapper.NumberFieldType invalidFieldType = new NumberFieldMapper.NumberFieldType( - "field", - NumberFieldMapper.NumberType.INTEGER - ); - expectThrows(IllegalArgumentException.class, () -> new KNNScoringSpace.InnerProd(arrayListQueryObject_case2, invalidFieldType)); + @SneakyThrows + public void testInnerProd_whenInvalidType_thenException() { + expectThrowsExceptionWithNonKNNField(KNNScoringSpace.InnerProd.class); + expectThrowsExceptionWithKNNFieldWithBinaryDataType(KNNScoringSpace.InnerProd.class); } @SuppressWarnings("unchecked") @@ -94,9 +173,6 @@ public void testHammingBit_Long() { KNNScoringSpace.HammingBit hammingBit = new KNNScoringSpace.HammingBit(longObject1, fieldType); assertEquals(0.1111F, ((BiFunction) hammingBit.scoringMethod).apply(longObject1, longObject2), 0.1F); - - KNNVectorFieldMapper.KNNVectorFieldType invalidFieldType = mock(KNNVectorFieldMapper.KNNVectorFieldType.class); - expectThrows(IllegalArgumentException.class, () -> new KNNScoringSpace.HammingBit(longObject1, invalidFieldType)); } @SuppressWarnings("unchecked") @@ -122,8 +198,28 @@ public void testHammingBit_Base64() { ), 0.1F ); + } + + public void testHamming_whenKNNFieldType_thenSucceed() { + List arrayListQueryObject = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0)); + KNNMethodContext knnMethodContext = getDefaultKNNMethodContext(); + KNNVectorFieldType fieldType = new KNNVectorFieldType( + "test", + Collections.emptyMap(), + VectorDataType.BINARY, + getMappingConfigForMethodMapping(knnMethodContext, 8 * arrayListQueryObject.size()) + ); + + KNNScoringSpace.Hamming hamming = new KNNScoringSpace.Hamming(arrayListQueryObject, fieldType); + + float[] arrayFloat = new float[] { 1.0f, 2.0f, 3.0f }; + assertEquals(1F, hamming.getScoringMethod().apply(arrayFloat, arrayFloat), 0.1F); + } - KNNVectorFieldMapper.KNNVectorFieldType invalidFieldType = mock(KNNVectorFieldMapper.KNNVectorFieldType.class); - expectThrows(IllegalArgumentException.class, () -> new KNNScoringSpace.HammingBit(base64Object1, invalidFieldType)); + public void testHamming_whenNonBinaryVectorDataType_thenException() { + KNNVectorFieldType invalidFieldType = mock(KNNVectorFieldType.class); + when(invalidFieldType.getVectorDataType()).thenReturn(randomInt() % 2 == 0 ? VectorDataType.FLOAT : VectorDataType.BYTE); + Exception e = expectThrows(IllegalArgumentException.class, () -> new KNNScoringSpace.Hamming(null, invalidFieldType)); + assertTrue(e.getMessage(), e.getMessage().contains("The data type should be [BINARY]")); } } diff --git a/src/test/java/org/opensearch/knn/plugin/script/KNNScoringSpaceUtilTests.java b/src/test/java/org/opensearch/knn/plugin/script/KNNScoringSpaceUtilTests.java index 789432ec8..2374e4f7b 100644 --- a/src/test/java/org/opensearch/knn/plugin/script/KNNScoringSpaceUtilTests.java +++ b/src/test/java/org/opensearch/knn/plugin/script/KNNScoringSpaceUtilTests.java @@ -6,9 +6,10 @@ package org.opensearch.knn.plugin.script; import org.opensearch.knn.KNNTestCase; -import org.opensearch.knn.index.KNNVectorFieldMapper; +import org.opensearch.knn.index.VectorDataType; import org.opensearch.index.mapper.BinaryFieldMapper; import org.opensearch.index.mapper.NumberFieldMapper; +import org.opensearch.knn.index.mapper.KNNVectorFieldType; import java.math.BigInteger; import java.util.ArrayList; @@ -31,7 +32,7 @@ public void testFieldTypeCheck() { KNNScoringSpaceUtil.isBinaryFieldType(new NumberFieldMapper.NumberFieldType("field", NumberFieldMapper.NumberType.INTEGER)) ); - assertTrue(KNNScoringSpaceUtil.isKNNVectorFieldType(mock(KNNVectorFieldMapper.KNNVectorFieldType.class))); + assertTrue(KNNScoringSpaceUtil.isKNNVectorFieldType(mock(KNNVectorFieldType.class))); assertFalse(KNNScoringSpaceUtil.isKNNVectorFieldType(new BinaryFieldMapper.BinaryFieldType("test"))); } @@ -61,14 +62,52 @@ public void testParseKNNVectorQuery() { float[] arrayFloat = new float[] { 1.0f, 2.0f, 3.0f }; List arrayListQueryObject = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0)); - KNNVectorFieldMapper.KNNVectorFieldType fieldType = mock(KNNVectorFieldMapper.KNNVectorFieldType.class); + KNNVectorFieldType fieldType = mock(KNNVectorFieldType.class); - when(fieldType.getDimension()).thenReturn(3); - assertArrayEquals(arrayFloat, KNNScoringSpaceUtil.parseToFloatArray(arrayListQueryObject, 3), 0.1f); + when(fieldType.getKnnMappingConfig()).thenReturn(getMappingConfigForMethodMapping(getDefaultKNNMethodContext(), 3)); + assertArrayEquals(arrayFloat, KNNScoringSpaceUtil.parseToFloatArray(arrayListQueryObject, 3, VectorDataType.FLOAT), 0.1f); - expectThrows(IllegalStateException.class, () -> KNNScoringSpaceUtil.parseToFloatArray(arrayListQueryObject, 4)); + expectThrows( + IllegalStateException.class, + () -> KNNScoringSpaceUtil.parseToFloatArray(arrayListQueryObject, 4, VectorDataType.FLOAT) + ); String invalidObject = "invalidObject"; - expectThrows(ClassCastException.class, () -> KNNScoringSpaceUtil.parseToFloatArray(invalidObject, 3)); + expectThrows(ClassCastException.class, () -> KNNScoringSpaceUtil.parseToFloatArray(invalidObject, 3, VectorDataType.FLOAT)); + } + + public void testIsBinaryVectorDataType_whenBinary_thenReturnTrue() { + KNNVectorFieldType fieldType = mock(KNNVectorFieldType.class); + when(fieldType.getVectorDataType()).thenReturn(VectorDataType.BINARY); + assertTrue(KNNScoringSpaceUtil.isBinaryVectorDataType(fieldType)); + } + + public void testIsBinaryVectorDataType_whenNonBinary_thenReturnFalse() { + KNNVectorFieldType fieldType = mock(KNNVectorFieldType.class); + when(fieldType.getVectorDataType()).thenReturn(randomInt() % 2 == 0 ? VectorDataType.FLOAT : VectorDataType.BYTE); + assertFalse(KNNScoringSpaceUtil.isBinaryVectorDataType(fieldType)); + } + + public void testConvertVectorToPrimitive_whenBinaryWithValidInput_thenReturnPrimitive() { + Number number1 = mock(Number.class); + when(number1.floatValue()).thenReturn(1f); + Number number2 = mock(Number.class); + when(number2.floatValue()).thenReturn(2f); + List vector = List.of(number1, number2); + float[] expected = new float[] { 1, 2 }; + assertArrayEquals(expected, KNNScoringSpaceUtil.convertVectorToPrimitive(vector, VectorDataType.BINARY), 0.01f); + } + + public void testConvertVectorToPrimitive_whenBinaryWithOutOfRange_thenException() { + Number number1 = mock(Number.class); + when(number1.floatValue()).thenReturn(128f); + Number number2 = mock(Number.class); + when(number2.floatValue()).thenReturn(-129f); + List vector = List.of(number1, number2); + Exception e = expectThrows( + IllegalArgumentException.class, + () -> KNNScoringSpaceUtil.convertVectorToPrimitive(vector, VectorDataType.BINARY) + ); + assertTrue(e.getMessage().contains("KNN vector values are not within in the byte range")); } } diff --git a/src/test/java/org/opensearch/knn/plugin/script/KNNScoringUtilTests.java b/src/test/java/org/opensearch/knn/plugin/script/KNNScoringUtilTests.java index 7f66e909a..2cc20c8f9 100644 --- a/src/test/java/org/opensearch/knn/plugin/script/KNNScoringUtilTests.java +++ b/src/test/java/org/opensearch/knn/plugin/script/KNNScoringUtilTests.java @@ -5,8 +5,12 @@ package org.opensearch.knn.plugin.script; +import java.util.Arrays; +import java.util.Locale; import org.opensearch.knn.KNNTestCase; import org.opensearch.knn.index.KNNVectorScriptDocValues; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; import org.opensearch.knn.index.VectorField; import org.apache.lucene.tests.analysis.MockAnalyzer; import org.apache.lucene.document.BinaryDocValuesField; @@ -21,17 +25,20 @@ import java.io.IOException; import java.math.BigInteger; -import java.util.ArrayList; import java.util.List; +import java.util.function.BiFunction; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class KNNScoringUtilTests extends KNNTestCase { private List getTestQueryVector() { - List queryVector = new ArrayList<>(); - queryVector.add(1.0f); - queryVector.add(1.0f); - queryVector.add(1.0f); - return queryVector; + return List.of(1.0f, 1.0f, 1.0f); + } + + private List getTestZeroVector() { + return List.of(0.0f, 0.0f, 0.0f); } public void testL2SquaredScoringFunction() { @@ -81,7 +88,7 @@ public void testGetInvalidVectorMagnitudeSquared() { public void testConvertInvalidVectorToPrimitive() { float[] primitiveVector = null; - assertEquals(primitiveVector, KNNScoringSpaceUtil.convertVectorToPrimitive(primitiveVector)); + assertEquals(primitiveVector, KNNScoringSpaceUtil.convertVectorToPrimitive(primitiveVector, VectorDataType.FLOAT)); } public void testCosineSimilQueryVectorZeroMagnitude() { @@ -169,7 +176,7 @@ public void testBitHammingDistance_Long() { assertEquals(0.0, KNNScoringUtil.calculateHammingBit(long3, long3), 0.1); } - public void testL2SquaredWhitelistedScoringFunction() throws IOException { + public void testL2SquaredAllowlistedScoringFunction() throws IOException { List queryVector = getTestQueryVector(); TestKNNScriptDocValues dataset = new TestKNNScriptDocValues(); dataset.createKNNVectorDocument(new float[] { 4.0f, 4.0f, 4.0f }, "test-index-field-name"); @@ -210,6 +217,24 @@ public void testScriptDocValuesFailsCosineSimilarity() throws IOException { dataset.close(); } + public void testZeroVectorFailsCosineSimilarity() throws IOException { + List queryVector = getTestZeroVector(); + TestKNNScriptDocValues dataset = new TestKNNScriptDocValues(); + dataset.createKNNVectorDocument(new float[] { 4.0f, 4.0f, 4.0f }, "test-index-field-name"); + KNNVectorScriptDocValues scriptDocValues = dataset.getScriptDocValues("test-index-field-name"); + scriptDocValues.setNextDocId(0); + + IllegalArgumentException exception = expectThrows( + IllegalArgumentException.class, + () -> KNNScoringUtil.cosineSimilarity(queryVector, scriptDocValues) + ); + assertEquals( + String.format(Locale.ROOT, "zero vector is not supported when space type is [%s]", SpaceType.COSINESIMIL.getValue()), + exception.getMessage() + ); + dataset.close(); + } + public void testCosineSimilarityOptimizedScoringFunction() throws IOException { List queryVector = getTestQueryVector(); TestKNNScriptDocValues dataset = new TestKNNScriptDocValues(); @@ -226,10 +251,77 @@ public void testScriptDocValuesFailsCosineSimilarityOptimized() throws IOExcepti TestKNNScriptDocValues dataset = new TestKNNScriptDocValues(); dataset.createKNNVectorDocument(new float[] { 4.0f, 4.0f, 4.0f }, "test-index-field-name"); KNNVectorScriptDocValues scriptDocValues = dataset.getScriptDocValues("test-index-field-name"); - expectThrows(IllegalStateException.class, () -> KNNScoringUtil.cosineSimilarity(queryVector, scriptDocValues, 3.0f)); dataset.close(); } + public void testZeroVectorFailsCosineSimilarityOptimized() throws IOException { + List queryVector = getTestZeroVector(); + TestKNNScriptDocValues dataset = new TestKNNScriptDocValues(); + dataset.createKNNVectorDocument(new float[] { 4.0f, 4.0f, 4.0f }, "test-index-field-name"); + KNNVectorScriptDocValues scriptDocValues = dataset.getScriptDocValues("test-index-field-name"); + scriptDocValues.setNextDocId(0); + + IllegalArgumentException exception = expectThrows( + IllegalArgumentException.class, + () -> KNNScoringUtil.cosineSimilarity(queryVector, scriptDocValues, 3.0f) + ); + assertEquals( + String.format(Locale.ROOT, "zero vector is not supported when space type is [%s]", SpaceType.COSINESIMIL.getValue()), + exception.getMessage() + ); + dataset.close(); + } + + public void testCalculateHammingBit_whenByte_thenSuccess() { + byte[] v1 = { 1, 16, -128 }; // 0000 0001, 0001 0000, 1000 0000 + byte[] v2 = { 2, 17, -1 }; // 0000 0010, 0001 0001, 1111 1111 + assertEquals(10, KNNScoringUtil.calculateHammingBit(v1, v2), 0.001f); + } + + private void validateThrowExceptionOnGivenDataType( + final BiFunction, KNNVectorScriptDocValues, Float> func, + final VectorDataType dataType, + final String errorMsg + ) { + List queryVector = Arrays.asList(1, 2); + KNNVectorScriptDocValues docValues = mock(KNNVectorScriptDocValues.class); + when(docValues.getVectorDataType()).thenReturn(dataType); + Exception e = expectThrows(IllegalArgumentException.class, () -> func.apply(queryVector, docValues)); + assertTrue(e.getMessage().contains(errorMsg)); + } + + public void testLInfNorm_whenKNNVectorScriptDocValuesOfBinary_thenThrowException() { + validateThrowExceptionOnGivenDataType(KNNScoringUtil::lInfNorm, VectorDataType.BINARY, "should be either float or byte"); + } + + public void testL1Norm_whenKNNVectorScriptDocValuesOfBinary_thenThrowException() { + validateThrowExceptionOnGivenDataType(KNNScoringUtil::l1Norm, VectorDataType.BINARY, "should be either float or byte"); + } + + public void testInnerProduct_whenKNNVectorScriptDocValuesOfBinary_thenThrowException() { + validateThrowExceptionOnGivenDataType(KNNScoringUtil::innerProduct, VectorDataType.BINARY, "should be either float or byte"); + } + + public void testCosineSimilarity_whenKNNVectorScriptDocValuesOfBinary_thenThrowException() { + validateThrowExceptionOnGivenDataType(KNNScoringUtil::cosineSimilarity, VectorDataType.BINARY, "should be either float or byte"); + } + + public void testHamming_whenKNNVectorScriptDocValuesOfNonBinary_thenThrowException() { + validateThrowExceptionOnGivenDataType(KNNScoringUtil::hamming, VectorDataType.FLOAT, "should be binary"); + } + + public void testHamming_whenKNNVectorScriptDocValuesOfBinary_thenSuccess() { + byte[] b1 = { 1, 16, -128 }; // 0000 0001, 0001 0000, 1000 0000 + byte[] b2 = { 2, 17, -1 }; // 0000 0010, 0001 0001, 1111 1111 + float[] f1 = { 1, 16, -128 }; // 0000 0001, 0001 0000, 1000 0000 + float[] f2 = { 2, 17, -1 }; // 0000 0010, 0001 0001, 1111 1111 + List queryVector = Arrays.asList(f1[0], f1[1], f1[2]); + KNNVectorScriptDocValues docValues = mock(KNNVectorScriptDocValues.class); + when(docValues.getVectorDataType()).thenReturn(VectorDataType.BINARY); + when(docValues.getValue()).thenReturn(f2); + assertEquals(KNNScoringUtil.calculateHammingBit(b1, b2), KNNScoringUtil.hamming(queryVector, docValues), 0.01f); + } + class TestKNNScriptDocValues { private KNNVectorScriptDocValues scriptDocValues; private Directory directory; @@ -243,7 +335,11 @@ public KNNVectorScriptDocValues getScriptDocValues(String fieldName) throws IOEx if (scriptDocValues == null) { reader = DirectoryReader.open(directory); LeafReaderContext leafReaderContext = reader.getContext().leaves().get(0); - scriptDocValues = new KNNVectorScriptDocValues(leafReaderContext.reader().getBinaryDocValues(fieldName), fieldName); + scriptDocValues = KNNVectorScriptDocValues.create( + leafReaderContext.reader().getBinaryDocValues(fieldName), + fieldName, + VectorDataType.FLOAT + ); } return scriptDocValues; } diff --git a/src/test/java/org/opensearch/knn/plugin/script/KNNScriptScoringIT.java b/src/test/java/org/opensearch/knn/plugin/script/KNNScriptScoringIT.java deleted file mode 100644 index 16df35921..000000000 --- a/src/test/java/org/opensearch/knn/plugin/script/KNNScriptScoringIT.java +++ /dev/null @@ -1,679 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.opensearch.knn.plugin.script; - -import org.opensearch.knn.KNNRestTestCase; -import org.opensearch.knn.KNNResult; -import org.opensearch.knn.index.SpaceType; -import org.apache.http.util.EntityUtils; -import org.opensearch.client.Request; -import org.opensearch.client.Response; -import org.opensearch.client.ResponseException; -import org.opensearch.common.Strings; -import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.ToXContent; -import org.opensearch.common.xcontent.XContentBuilder; -import org.opensearch.common.xcontent.XContentFactory; -import org.opensearch.common.xcontent.XContentType; -import org.opensearch.index.query.MatchAllQueryBuilder; -import org.opensearch.index.query.QueryBuilder; -import org.opensearch.index.query.functionscore.ScriptScoreQueryBuilder; -import org.opensearch.rest.RestStatus; -import org.opensearch.script.Script; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import static org.hamcrest.Matchers.containsString; - -public class KNNScriptScoringIT extends KNNRestTestCase { - - public void testKNNL2ScriptScore() throws Exception { - /* - * Create knn index and populate data - */ - createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); - Float[] f1 = { 6.0f, 6.0f }; - addKnnDoc(INDEX_NAME, "1", FIELD_NAME, f1); - - Float[] f2 = { 2.0f, 2.0f }; - addKnnDoc(INDEX_NAME, "2", FIELD_NAME, f2); - - Float[] f3 = { 4.0f, 4.0f }; - addKnnDoc(INDEX_NAME, "3", FIELD_NAME, f3); - - Float[] f4 = { 3.0f, 3.0f }; - addKnnDoc(INDEX_NAME, "4", FIELD_NAME, f4); - - /** - * Construct Search Request - */ - QueryBuilder qb = new MatchAllQueryBuilder(); - Map params = new HashMap<>(); - /* - * params": { - * "field": "my_dense_vector", - * "vector": [2.0, 2.0] - * } - */ - float[] queryVector = { 1.0f, 1.0f }; - params.put("field", FIELD_NAME); - params.put("query_value", queryVector); - params.put("space_type", SpaceType.L2.getValue()); - Request request = constructKNNScriptQueryRequest(INDEX_NAME, qb, params); - Response response = client().performRequest(request); - assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); - - List results = parseSearchResponse(EntityUtils.toString(response.getEntity()), FIELD_NAME); - List expectedDocids = Arrays.asList("2", "4", "3", "1"); - - List actualDocids = new ArrayList<>(); - for (KNNResult result : results) { - actualDocids.add(result.getDocId()); - } - - assertEquals(4, results.size()); - - // assert document order - assertEquals("2", results.get(0).getDocId()); - assertEquals("4", results.get(1).getDocId()); - assertEquals("3", results.get(2).getDocId()); - assertEquals("1", results.get(3).getDocId()); - } - - public void testKNNL1ScriptScore() throws Exception { - /* - * Create knn index and populate data - */ - createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); - Float[] f1 = { 6.0f, 6.0f }; - addKnnDoc(INDEX_NAME, "1", FIELD_NAME, f1); - - Float[] f2 = { 4.0f, 1.0f }; - addKnnDoc(INDEX_NAME, "2", FIELD_NAME, f2); - - Float[] f3 = { 3.0f, 3.0f }; - addKnnDoc(INDEX_NAME, "3", FIELD_NAME, f3); - - Float[] f4 = { 5.0f, 5.0f }; - addKnnDoc(INDEX_NAME, "4", FIELD_NAME, f4); - - /** - * Construct Search Request - */ - QueryBuilder qb = new MatchAllQueryBuilder(); - Map params = new HashMap<>(); - /* - * params": { - * "field": "my_dense_vector", - * "vector": [1.0, 1.0] - * } - */ - float[] queryVector = { 1.0f, 1.0f }; - params.put("field", FIELD_NAME); - params.put("query_value", queryVector); - params.put("space_type", SpaceType.L1); - Request request = constructKNNScriptQueryRequest(INDEX_NAME, qb, params); - Response response = client().performRequest(request); - assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); - - List results = parseSearchResponse(EntityUtils.toString(response.getEntity()), FIELD_NAME); - List expectedDocids = Arrays.asList("2", "4", "3", "1"); - - List actualDocids = new ArrayList<>(); - for (KNNResult result : results) { - actualDocids.add(result.getDocId()); - } - - assertEquals(4, results.size()); - - // assert document order - assertEquals("2", results.get(0).getDocId()); - assertEquals("3", results.get(1).getDocId()); - assertEquals("4", results.get(2).getDocId()); - assertEquals("1", results.get(3).getDocId()); - } - - public void testKNNLInfScriptScore() throws Exception { - /* - * Create knn index and populate data - */ - createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); - Float[] f1 = { 6.0f, 6.0f }; - addKnnDoc(INDEX_NAME, "1", FIELD_NAME, f1); - - Float[] f2 = { 4.0f, 1.0f }; - addKnnDoc(INDEX_NAME, "2", FIELD_NAME, f2); - - Float[] f3 = { 3.0f, 3.0f }; - addKnnDoc(INDEX_NAME, "3", FIELD_NAME, f3); - - Float[] f4 = { 5.0f, 5.0f }; - addKnnDoc(INDEX_NAME, "4", FIELD_NAME, f4); - - /** - * Construct Search Request - */ - QueryBuilder qb = new MatchAllQueryBuilder(); - Map params = new HashMap<>(); - /* - * params": { - * "field": "my_dense_vector", - * "vector": [1.0, 1.0] - * } - */ - float[] queryVector = { 1.0f, 1.0f }; - params.put("field", FIELD_NAME); - params.put("query_value", queryVector); - params.put("space_type", SpaceType.LINF.getValue()); - Request request = constructKNNScriptQueryRequest(INDEX_NAME, qb, params); - Response response = client().performRequest(request); - assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); - - List results = parseSearchResponse(EntityUtils.toString(response.getEntity()), FIELD_NAME); - List expectedDocids = Arrays.asList("3", "2", "4", "1"); - - List actualDocids = new ArrayList<>(); - for (KNNResult result : results) { - actualDocids.add(result.getDocId()); - } - - assertEquals(4, results.size()); - - // assert document order - assertEquals("3", results.get(0).getDocId()); - assertEquals("2", results.get(1).getDocId()); - assertEquals("4", results.get(2).getDocId()); - assertEquals("1", results.get(3).getDocId()); - } - - public void testKNNCosineScriptScore() throws Exception { - /* - * Create knn index and populate data - */ - createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); - Float[] f1 = { 1.0f, -1.0f }; - addKnnDoc(INDEX_NAME, "0", FIELD_NAME, f1); - - Float[] f2 = { 1.0f, 0.0f }; - addKnnDoc(INDEX_NAME, "1", FIELD_NAME, f2); - - Float[] f3 = { 1.0f, 1.0f }; - addKnnDoc(INDEX_NAME, "2", FIELD_NAME, f3); - - /** - * Construct Search Request - */ - QueryBuilder qb = new MatchAllQueryBuilder(); - Map params = new HashMap<>(); - /* - * params": { - * "field": "my_dense_vector", - * "query_value": [2.0, 2.0], - * "space_type": "L2" - * } - * - * - */ - float[] queryVector = { 2.0f, -2.0f }; - params.put("field", FIELD_NAME); - params.put("query_value", queryVector); - params.put("space_type", SpaceType.COSINESIMIL.getValue()); - Request request = constructKNNScriptQueryRequest(INDEX_NAME, qb, params); - Response response = client().performRequest(request); - assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); - - List results = parseSearchResponse(EntityUtils.toString(response.getEntity()), FIELD_NAME); - List expectedDocids = Arrays.asList("0", "1", "2"); - - List actualDocids = new ArrayList<>(); - for (KNNResult result : results) { - actualDocids.add(result.getDocId()); - } - - assertEquals(3, results.size()); - - // assert document order - assertEquals("0", results.get(0).getDocId()); - assertEquals("1", results.get(1).getDocId()); - assertEquals("2", results.get(2).getDocId()); - } - - public void testKNNInvalidSourceScript() throws Exception { - /* - * Create knn index and populate data - */ - createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); - - /** - * Construct Search Request - */ - QueryBuilder qb = new MatchAllQueryBuilder(); - Map params = new HashMap<>(); - /* - * params": { - * "field": "my_dense_vector", - * "query_value": [2.0, 2.0], - * "space_type": "cosinesimil" - * } - */ - float[] queryVector = { 2.0f, -2.0f }; - params.put("field", FIELD_NAME); - params.put("query_value", queryVector); - params.put("space_type", SpaceType.COSINESIMIL.getValue()); - Script script = new Script(Script.DEFAULT_SCRIPT_TYPE, KNNScoringScriptEngine.NAME, "Dummy_source", params); - ScriptScoreQueryBuilder sc = new ScriptScoreQueryBuilder(qb, script); - - XContentBuilder builder = XContentFactory.jsonBuilder().startObject().startObject("query"); - - builder.startObject("script_score"); - builder.field("query"); - sc.query().toXContent(builder, ToXContent.EMPTY_PARAMS); - builder.field("script", script); - builder.endObject(); - builder.endObject(); - builder.endObject(); - Request request = new Request("POST", "/" + INDEX_NAME + "/_search"); - - request.setJsonEntity(Strings.toString(builder)); - ResponseException ex = expectThrows(ResponseException.class, () -> client().performRequest(request)); - assertThat(EntityUtils.toString(ex.getResponse().getEntity()), containsString("Unknown script name Dummy_source")); - } - - public void testInvalidSpace() throws Exception { - String INVALID_SPACE = "dummy"; - /* - * Create knn index and populate data - */ - createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); - - /** - * Construct Search Request - */ - QueryBuilder qb = new MatchAllQueryBuilder(); - Map params = new HashMap<>(); - float[] queryVector = { 2.0f, -2.0f }; - params.put("field", FIELD_NAME); - params.put("query_value", queryVector); - params.put("space_type", INVALID_SPACE); - Request request = constructKNNScriptQueryRequest(INDEX_NAME, qb, params); - ResponseException ex = expectThrows(ResponseException.class, () -> client().performRequest(request)); - assertThat( - EntityUtils.toString(ex.getResponse().getEntity()), - containsString("Invalid space type. Please refer to the available space types") - ); - } - - public void testMissingParamsInScript() throws Exception { - /* - * Create knn index and populate data - */ - createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); - - /** - * Construct Search Request - */ - QueryBuilder qb = new MatchAllQueryBuilder(); - Map params = new HashMap<>(); - float[] queryVector = { 2.0f, -2.0f }; - params.put("query_value", queryVector); - params.put("space_type", SpaceType.COSINESIMIL.getValue()); - Request request = constructKNNScriptQueryRequest(INDEX_NAME, qb, params); - ResponseException ex = expectThrows(ResponseException.class, () -> client().performRequest(request)); - assertThat(EntityUtils.toString(ex.getResponse().getEntity()), containsString("Missing parameter [field]")); - - // Remove query vector parameter - params.put("field", FIELD_NAME); - params.remove("query_value"); - Request vector_request = constructKNNScriptQueryRequest(INDEX_NAME, qb, params); - ex = expectThrows(ResponseException.class, () -> client().performRequest(vector_request)); - assertThat(EntityUtils.toString(ex.getResponse().getEntity()), containsString("Missing parameter [query_value]")); - - // Remove space parameter - params.put("query_value", queryVector); - params.remove("space_type"); - Request space_request = constructKNNScriptQueryRequest(INDEX_NAME, qb, params); - ex = expectThrows(ResponseException.class, () -> client().performRequest(space_request)); - assertThat(EntityUtils.toString(ex.getResponse().getEntity()), containsString("Missing parameter [space_type]")); - } - - public void testUnequalDimensions() throws Exception { - /* - * Create knn index and populate data - */ - createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); - Float[] f1 = { 1.0f, -1.0f }; - addKnnDoc(INDEX_NAME, "0", FIELD_NAME, f1); - - /** - * Construct Search Request - */ - QueryBuilder qb = new MatchAllQueryBuilder(); - Map params = new HashMap<>(); - float[] queryVector = { 2.0f, -2.0f, -2.0f }; // query dimension and field dimension mismatch - params.put("field", FIELD_NAME); - params.put("query_value", queryVector); - params.put("space_type", SpaceType.COSINESIMIL.getValue()); - Request request = constructKNNScriptQueryRequest(INDEX_NAME, qb, params); - ResponseException ex = expectThrows(ResponseException.class, () -> client().performRequest(request)); - assertThat(EntityUtils.toString(ex.getResponse().getEntity()), containsString("does not match")); - } - - @SuppressWarnings("unchecked") - public void testKNNScoreforNonVectorDocument() throws Exception { - /* - * Create knn index and populate data - */ - createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); - Float[] f1 = { 1.0f, 1.0f }; - addDocWithNumericField(INDEX_NAME, "0", "price", 10); - addKnnDoc(INDEX_NAME, "1", FIELD_NAME, f1); - forceMergeKnnIndex(INDEX_NAME); - /** - * Construct Search Request - */ - QueryBuilder qb = new MatchAllQueryBuilder(); - Map params = new HashMap<>(); - float[] queryVector = { 2.0f, 2.0f }; // query dimension and field dimension mismatch - params.put("field", FIELD_NAME); - params.put("query_value", queryVector); - params.put("space_type", SpaceType.L2.getValue()); - Request request = constructKNNScriptQueryRequest(INDEX_NAME, qb, params); - Response response = client().performRequest(request); - assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); - - String responseBody = EntityUtils.toString(response.getEntity()); - List hits = (List) ((Map) createParser(XContentType.JSON.xContent(), responseBody).map() - .get("hits")).get("hits"); - - List docIds = hits.stream().map(hit -> { - String id = ((String) ((Map) hit).get("_id")); - return id; - }).collect(Collectors.toList()); - // assert document order - assertEquals("1", docIds.get(0)); - assertEquals("0", docIds.get(1)); - - List scores = hits.stream().map(hit -> { - Double score = ((Double) ((Map) hit).get("_score")); - return score; - }).collect(Collectors.toList()); - // assert scores - assertEquals(0.33333, scores.get(0), 0.001); - assertEquals(Float.MIN_VALUE, scores.get(1), 0.001); - } - - @SuppressWarnings("unchecked") - public void testHammingScriptScore_Long() throws Exception { - createIndex(INDEX_NAME, Settings.EMPTY); - String longMapping = Strings.toString( - XContentFactory.jsonBuilder() - .startObject() - .startObject("properties") - .startObject(FIELD_NAME) - .field("type", "long") - .endObject() - .endObject() - .endObject() - ); - putMappingRequest(INDEX_NAME, longMapping); - - addDocWithNumericField(INDEX_NAME, "0", FIELD_NAME, 8L); - addDocWithNumericField(INDEX_NAME, "1", FIELD_NAME, 1L); - addDocWithNumericField(INDEX_NAME, "2", FIELD_NAME, -9_223_372_036_818_523_493L); - addDocWithNumericField(INDEX_NAME, "3", FIELD_NAME, 1_000_000_000_000_000L); - - // Add docs without the field. These docs should not appear in top 4 of results - addDocWithNumericField(INDEX_NAME, "4", "price", 10); - addDocWithNumericField(INDEX_NAME, "5", "price", 10); - addDocWithNumericField(INDEX_NAME, "6", "price", 10); - - /* - * Decimal to Binary conversions lookup - * 8 -> 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 - * 1 -> 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 - * -9_223_372_036_818_523_493 -> 1000 0000 0000 0000 0000 0000 0000 0000 0000 0010 0010 1001 0010 1010 1001 1011 - * 1_000_000_000_000_000 -> 0000 0000 0000 0011 1000 1101 0111 1110 1010 0100 1100 0110 1000 0000 0000 0000 - * -9_223_372_036_818_526_181 -> 1000 0000 0000 0000 0000 0000 0000 0000 0000 0010 0010 1001 0010 0000 0001 1011 - * 10 -> 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1010 - */ - - QueryBuilder qb1 = new MatchAllQueryBuilder(); - Map params1 = new HashMap<>(); - Long queryValue1 = -9223372036818526181L; - params1.put("field", FIELD_NAME); - params1.put("query_value", queryValue1); - params1.put("space_type", SpaceType.HAMMING_BIT.getValue()); - Request request1 = constructKNNScriptQueryRequest(INDEX_NAME, qb1, params1, 4); - Response response1 = client().performRequest(request1); - assertEquals(request1.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response1.getStatusLine().getStatusCode())); - - String responseBody1 = EntityUtils.toString(response1.getEntity()); - List hits1 = (List) ((Map) createParser(XContentType.JSON.xContent(), responseBody1).map() - .get("hits")).get("hits"); - - List docIds1 = hits1.stream().map(hit -> ((String) ((Map) hit).get("_id"))).collect(Collectors.toList()); - - List docScores1 = hits1.stream() - .map(hit -> ((Double) ((Map) hit).get("_score"))) - .collect(Collectors.toList()); - - double[] scores1 = new double[docScores1.size()]; - for (int i = 0; i < docScores1.size(); i++) { - scores1[i] = docScores1.get(i); - } - - List correctIds1 = Arrays.asList("2", "0", "1", "3"); - double[] correctScores1 = new double[] { 1.0 / (1 + 3), 1.0 / (1 + 9), 1.0 / (1 + 9), 1.0 / (1 + 30) }; - - assertEquals(4, correctIds1.size()); - assertArrayEquals(correctIds1.toArray(), docIds1.toArray()); - assertArrayEquals(correctScores1, scores1, 0.001); - - /* - * Force merge to one segment to confirm that docs without field are not included in the results when segment - * is mixed with docs that have the field and docs that dont. - */ - forceMergeKnnIndex(INDEX_NAME); - - QueryBuilder qb2 = new MatchAllQueryBuilder(); - Map params2 = new HashMap<>(); - Long queryValue2 = 10L; - params2.put("field", FIELD_NAME); - params2.put("query_value", queryValue2); - params2.put("space_type", SpaceType.HAMMING_BIT.getValue()); - Request request2 = constructKNNScriptQueryRequest(INDEX_NAME, qb2, params2, 4); - Response response2 = client().performRequest(request2); - assertEquals(request2.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response2.getStatusLine().getStatusCode())); - - String responseBody2 = EntityUtils.toString(response2.getEntity()); - List hits2 = (List) ((Map) createParser(XContentType.JSON.xContent(), responseBody2).map() - .get("hits")).get("hits"); - - List docIds2 = hits2.stream().map(hit -> ((String) ((Map) hit).get("_id"))).collect(Collectors.toList()); - - List docScores2 = hits2.stream() - .map(hit -> ((Double) ((Map) hit).get("_score"))) - .collect(Collectors.toList()); - - double[] scores2 = new double[docScores2.size()]; - for (int i = 0; i < docScores2.size(); i++) { - scores2[i] = docScores2.get(i); - } - - List correctIds2 = Arrays.asList("0", "1", "2", "3"); - double[] correctScores2 = new double[] { 1.0 / (1 + 1), 1.0 / (1 + 3), 1.0 / (1 + 11), 1.0 / (1 + 22) }; - - assertEquals(4, correctIds2.size()); - assertArrayEquals(correctIds2.toArray(), docIds2.toArray()); - assertArrayEquals(correctScores2, scores2, 0.001); - } - - @SuppressWarnings("unchecked") - public void testHammingScriptScore_Base64() throws Exception { - createIndex(INDEX_NAME, Settings.EMPTY); - String longMapping = Strings.toString( - XContentFactory.jsonBuilder() - .startObject() - .startObject("properties") - .startObject(FIELD_NAME) - .field("type", "binary") - .field("doc_values", true) - .endObject() - .endObject() - .endObject() - ); - putMappingRequest(INDEX_NAME, longMapping); - - addDocWithBinaryField(INDEX_NAME, "0", FIELD_NAME, "AAAAAAAAAAk="); - addDocWithBinaryField(INDEX_NAME, "1", FIELD_NAME, "AAAAAAAAAAE="); - addDocWithBinaryField(INDEX_NAME, "2", FIELD_NAME, "gAAAAAIpKps="); - addDocWithBinaryField(INDEX_NAME, "3", FIELD_NAME, "AAONfqTGgAA="); - - // Add docs without the field. These docs should not appear in top 4 of results - addDocWithNumericField(INDEX_NAME, "4", "price", 10); - addDocWithNumericField(INDEX_NAME, "5", "price", 10); - addDocWithNumericField(INDEX_NAME, "6", "price", 10); - - /* - * Base64 encodings to Binary conversions lookup - * AAAAAAAAAAk= -> 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1001 - * AAAAAAAAAAE= -> 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 - * gAAAAAIpKps= -> 1000 0000 0000 0000 0000 0000 0000 0000 0000 0010 0010 1001 0010 1010 1001 1011 - * AAONfqTGgAA= -> 0000 0000 0000 0011 1000 1101 0111 1110 1010 0100 1100 0110 1000 0000 0000 0000 - * gAAAAAIpIBs= -> 1000 0000 0000 0000 0000 0000 0000 0000 0000 0010 0010 1001 0010 0000 0001 1011 - * AAAAAAIpIBs= -> 0000 0000 0000 0000 0000 0000 0000 0000 0000 0010 0010 1001 0010 0000 0001 1011 - */ - - QueryBuilder qb1 = new MatchAllQueryBuilder(); - Map params1 = new HashMap<>(); - String queryValue1 = "gAAAAAIpIBs="; - params1.put("field", FIELD_NAME); - params1.put("query_value", queryValue1); - params1.put("space_type", SpaceType.HAMMING_BIT.getValue()); - Request request1 = constructKNNScriptQueryRequest(INDEX_NAME, qb1, params1, 4); - Response response1 = client().performRequest(request1); - assertEquals(request1.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response1.getStatusLine().getStatusCode())); - - String responseBody1 = EntityUtils.toString(response1.getEntity()); - List hits1 = (List) ((Map) createParser(XContentType.JSON.xContent(), responseBody1).map() - .get("hits")).get("hits"); - - List docIds1 = hits1.stream().map(hit -> ((String) ((Map) hit).get("_id"))).collect(Collectors.toList()); - - List docScores1 = hits1.stream() - .map(hit -> ((Double) ((Map) hit).get("_score"))) - .collect(Collectors.toList()); - - double[] scores1 = new double[docScores1.size()]; - for (int i = 0; i < docScores1.size(); i++) { - scores1[i] = docScores1.get(i); - } - - List correctIds1 = Arrays.asList("2", "0", "1", "3"); - double[] correctScores1 = new double[] { 1.0 / (1 + 3), 1.0 / (1 + 8), 1.0 / (1 + 9), 1.0 / (1 + 30) }; - - assertEquals(correctIds1.size(), docIds1.size()); - assertArrayEquals(correctIds1.toArray(), docIds1.toArray()); - assertArrayEquals(correctScores1, scores1, 0.001); - - /* - * Force merge to one segment to confirm that docs without field are not included in the results when segment - * is mixed with docs that have the field and docs that dont. - */ - forceMergeKnnIndex(INDEX_NAME); - - QueryBuilder qb2 = new MatchAllQueryBuilder(); - Map params2 = new HashMap<>(); - String queryValue2 = "AAAAAAIpIBs="; - params2.put("field", FIELD_NAME); - params2.put("query_value", queryValue2); - params2.put("space_type", SpaceType.HAMMING_BIT.getValue()); - Request request2 = constructKNNScriptQueryRequest(INDEX_NAME, qb2, params2, 4); - Response response2 = client().performRequest(request2); - assertEquals(request2.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response2.getStatusLine().getStatusCode())); - - String responseBody2 = EntityUtils.toString(response2.getEntity()); - List hits2 = (List) ((Map) createParser(XContentType.JSON.xContent(), responseBody2).map() - .get("hits")).get("hits"); - - List docIds2 = hits2.stream().map(hit -> ((String) ((Map) hit).get("_id"))).collect(Collectors.toList()); - - List docScores2 = hits2.stream() - .map(hit -> ((Double) ((Map) hit).get("_score"))) - .collect(Collectors.toList()); - - double[] scores2 = new double[docScores2.size()]; - for (int i = 0; i < docScores2.size(); i++) { - scores2[i] = docScores2.get(i); - } - - List correctIds2 = Arrays.asList("2", "0", "1", "3"); - double[] correctScores2 = new double[] { 1.0 / (1 + 4), 1.0 / (1 + 7), 1.0 / (1 + 8), 1.0 / (1 + 29) }; - - assertEquals(correctIds2.size(), docIds2.size()); - assertArrayEquals(correctIds2.toArray(), docIds2.toArray()); - assertArrayEquals(correctScores2, scores2, 0.001); - } - - public void testKNNInnerProdScriptScore() throws Exception { - /* - * Create knn index and populate data - */ - createKnnIndex(INDEX_NAME, createKnnIndexMapping(FIELD_NAME, 2)); - Float[] f1 = { -2.0f, -2.0f }; - addKnnDoc(INDEX_NAME, "1", FIELD_NAME, f1); - - Float[] f2 = { 1.0f, 1.0f }; - addKnnDoc(INDEX_NAME, "2", FIELD_NAME, f2); - - Float[] f3 = { 2.0f, 2.0f }; - addKnnDoc(INDEX_NAME, "3", FIELD_NAME, f3); - - Float[] f4 = { 2.0f, -2.0f }; - addKnnDoc(INDEX_NAME, "4", FIELD_NAME, f4); - - /** - * Construct Search Request - */ - QueryBuilder qb = new MatchAllQueryBuilder(); - Map params = new HashMap<>(); - /* - * params": { - * "field": "my_dense_vector", - * "query_value": [1.0, 1.0], - * "space_type": "innerproduct", - * } - */ - float[] queryVector = { 1.0f, 1.0f }; - params.put("field", FIELD_NAME); - params.put("query_value", queryVector); - params.put("space_type", SpaceType.INNER_PRODUCT.getValue()); - Request request = constructKNNScriptQueryRequest(INDEX_NAME, qb, params); - Response response = client().performRequest(request); - assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); - - List results = parseSearchResponse(EntityUtils.toString(response.getEntity()), FIELD_NAME); - List expectedDocids = Arrays.asList("3", "2", "4", "1"); - - List actualDocids = new ArrayList<>(); - for (KNNResult result : results) { - actualDocids.add(result.getDocId()); - } - - assertEquals(4, results.size()); - - // assert document order - assertEquals("3", results.get(0).getDocId()); - assertEquals("2", results.get(1).getDocId()); - assertEquals("4", results.get(2).getDocId()); - assertEquals("1", results.get(3).getDocId()); - } -} diff --git a/src/test/java/org/opensearch/knn/plugin/search/KNNConcurrentSearchRequestDeciderTests.java b/src/test/java/org/opensearch/knn/plugin/search/KNNConcurrentSearchRequestDeciderTests.java new file mode 100644 index 000000000..53fd520f7 --- /dev/null +++ b/src/test/java/org/opensearch/knn/plugin/search/KNNConcurrentSearchRequestDeciderTests.java @@ -0,0 +1,65 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.plugin.search; + +import org.opensearch.index.IndexSettings; +import org.opensearch.index.query.MatchAllQueryBuilder; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.index.query.KNNQueryBuilder; +import org.opensearch.search.deciders.ConcurrentSearchDecision; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class KNNConcurrentSearchRequestDeciderTests extends KNNTestCase { + + public void testDecider_thenSucceed() { + ConcurrentSearchDecision noop = new ConcurrentSearchDecision(ConcurrentSearchDecision.DecisionStatus.NO_OP, "Default decision"); + + KNNConcurrentSearchRequestDecider decider = new KNNConcurrentSearchRequestDecider(); + assertDecision(noop, decider.getConcurrentSearchDecision()); + IndexSettings indexSettingsMock = mock(IndexSettings.class); + when(indexSettingsMock.getValue(KNNSettings.IS_KNN_INDEX_SETTING)).thenReturn(Boolean.FALSE); + + // Non KNNQueryBuilder + decider.evaluateForQuery(new MatchAllQueryBuilder(), indexSettingsMock); + assertDecision(noop, decider.getConcurrentSearchDecision()); + decider.evaluateForQuery( + KNNQueryBuilder.builder().vector(new float[] { 1f, 2f, 3f, 4f, 5f, 6f }).fieldName("decider").k(10).build(), + indexSettingsMock + ); + assertDecision(noop, decider.getConcurrentSearchDecision()); + + when(indexSettingsMock.getValue(KNNSettings.IS_KNN_INDEX_SETTING)).thenReturn(Boolean.TRUE); + decider.evaluateForQuery( + KNNQueryBuilder.builder().vector(new float[] { 1f, 2f, 3f, 4f, 5f, 6f }).fieldName("decider").k(10).build(), + indexSettingsMock + ); + ConcurrentSearchDecision yes = new ConcurrentSearchDecision( + ConcurrentSearchDecision.DecisionStatus.YES, + "Enable concurrent search for knn as Query has k-NN query in it and index is k-nn index" + ); + assertDecision(yes, decider.getConcurrentSearchDecision()); + + decider.evaluateForQuery(new MatchAllQueryBuilder(), indexSettingsMock); + assertDecision(noop, decider.getConcurrentSearchDecision()); + } + + public void testDeciderFactory_thenSucceed() { + KNNConcurrentSearchRequestDecider.Factory factory = new KNNConcurrentSearchRequestDecider.Factory(); + IndexSettings indexSettingsMock = mock(IndexSettings.class); + when(indexSettingsMock.getValue(KNNSettings.IS_KNN_INDEX_SETTING)).thenReturn(Boolean.TRUE); + assertNotSame(factory.create(indexSettingsMock).get(), factory.create(indexSettingsMock).get()); + when(indexSettingsMock.getValue(KNNSettings.IS_KNN_INDEX_SETTING)).thenReturn(Boolean.FALSE); + assertTrue(factory.create(indexSettingsMock).isEmpty()); + } + + private void assertDecision(ConcurrentSearchDecision expected, ConcurrentSearchDecision actual) { + assertEquals(expected.getDecisionReason(), actual.getDecisionReason()); + assertEquals(expected.getDecisionStatus(), actual.getDecisionStatus()); + } +} diff --git a/src/test/java/org/opensearch/knn/plugin/stats/suppliers/LibraryInitializedSupplierTests.java b/src/test/java/org/opensearch/knn/plugin/stats/suppliers/LibraryInitializedSupplierTests.java index 759f4dd5b..5dbd0fc8b 100644 --- a/src/test/java/org/opensearch/knn/plugin/stats/suppliers/LibraryInitializedSupplierTests.java +++ b/src/test/java/org/opensearch/knn/plugin/stats/suppliers/LibraryInitializedSupplierTests.java @@ -12,14 +12,15 @@ package org.opensearch.knn.plugin.stats.suppliers; import org.opensearch.common.ValidationException; -import org.opensearch.knn.index.KNNMethod; -import org.opensearch.knn.index.KNNMethodContext; +import org.opensearch.knn.index.engine.KNNLibraryIndexingContext; +import org.opensearch.knn.index.engine.KNNLibrarySearchContext; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.util.KNNLibrary; +import org.opensearch.knn.index.engine.KNNLibrary; +import org.opensearch.knn.index.engine.ResolvedMethodContext; import org.opensearch.test.OpenSearchTestCase; -import java.util.Map; - public class LibraryInitializedSupplierTests extends OpenSearchTestCase { public void testEngineInitialized() { @@ -31,7 +32,7 @@ public void testEngineInitialized() { assertTrue(libraryInitializedSupplier.get()); } - private class TestLibrary implements KNNLibrary { + private static class TestLibrary implements KNNLibrary { private Boolean initialized; TestLibrary() { @@ -39,12 +40,7 @@ private class TestLibrary implements KNNLibrary { } @Override - public String getLatestBuildVersion() { - return null; - } - - @Override - public String getLatestLibVersion() { + public String getVersion() { return null; } @@ -59,7 +55,7 @@ public String getCompoundExtension() { } @Override - public KNNMethod getMethod(String methodName) { + public KNNLibrarySearchContext getKNNLibrarySearchContext(String methodName) { return null; } @@ -69,7 +65,17 @@ public float score(float rawScore, SpaceType spaceType) { } @Override - public ValidationException validateMethod(KNNMethodContext knnMethodContext) { + public Float distanceToRadialThreshold(Float distance, SpaceType spaceType) { + return 0.0f; + } + + @Override + public Float scoreToRadialThreshold(Float score, SpaceType spaceType) { + return 0.0f; + } + + @Override + public ValidationException validateMethod(KNNMethodContext knnMethodContext, KNNMethodConfigContext knnMethodConfigContext) { return null; } @@ -79,12 +85,15 @@ public boolean isTrainingRequired(KNNMethodContext knnMethodContext) { } @Override - public int estimateOverheadInKB(KNNMethodContext knnMethodContext, int dimension) { + public int estimateOverheadInKB(KNNMethodContext knnMethodContext, KNNMethodConfigContext knnMethodConfigContext) { return 0; } @Override - public Map getMethodAsMap(KNNMethodContext knnMethodContext) { + public KNNLibraryIndexingContext getKNNLibraryIndexingContext( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext + ) { return null; } @@ -97,5 +106,15 @@ public Boolean isInitialized() { public void setInitialized(Boolean isInitialized) { this.initialized = isInitialized; } + + @Override + public ResolvedMethodContext resolveMethod( + KNNMethodContext knnMethodContext, + KNNMethodConfigContext knnMethodConfigContext, + boolean shouldRequireTraining, + SpaceType spaceType + ) { + return null; + } } } diff --git a/src/test/java/org/opensearch/knn/plugin/transport/ClearCacheTransportActionTests.java b/src/test/java/org/opensearch/knn/plugin/transport/ClearCacheTransportActionTests.java new file mode 100644 index 000000000..d79f2f738 --- /dev/null +++ b/src/test/java/org/opensearch/knn/plugin/transport/ClearCacheTransportActionTests.java @@ -0,0 +1,117 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.plugin.transport; + +import lombok.SneakyThrows; +import org.opensearch.cluster.ClusterName; +import org.opensearch.cluster.ClusterState; +import org.opensearch.cluster.block.ClusterBlockException; +import org.opensearch.cluster.block.ClusterBlockLevel; +import org.opensearch.cluster.routing.ShardRouting; +import org.opensearch.cluster.routing.ShardsIterator; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.index.IndexService; +import org.opensearch.knn.KNNSingleNodeTestCase; +import org.opensearch.knn.index.memory.NativeMemoryCacheManager; + +import java.util.EnumSet; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ClearCacheTransportActionTests extends KNNSingleNodeTestCase { + private static final String TEST_FIELD = "test-field"; + private static final int DIMENSIONS = 2; + + @SneakyThrows + public void testShardOperation() { + String testIndex = getTestName().toLowerCase(); + KNNWarmupRequest knnWarmupRequest = new KNNWarmupRequest(testIndex); + KNNWarmupTransportAction knnWarmupTransportAction = node().injector().getInstance(KNNWarmupTransportAction.class); + assertEquals(0, NativeMemoryCacheManager.getInstance().getIndicesCacheStats().size()); + + IndexService indexService = createIndex(testIndex, getKNNDefaultIndexSettingsBuildsGraphAlways()); + createKnnIndexMapping(testIndex, TEST_FIELD, DIMENSIONS); + addKnnDoc(testIndex, String.valueOf(randomInt()), TEST_FIELD, new Float[] { randomFloat(), randomFloat() }); + ShardRouting shardRouting = indexService.iterator().next().routingEntry(); + + knnWarmupTransportAction.shardOperation(knnWarmupRequest, shardRouting); + assertEquals(1, NativeMemoryCacheManager.getInstance().getIndicesCacheStats().size()); + + ClearCacheRequest clearCacheRequest = new ClearCacheRequest(testIndex); + ClearCacheTransportAction clearCacheTransportAction = node().injector().getInstance(ClearCacheTransportAction.class); + clearCacheTransportAction.shardOperation(clearCacheRequest, shardRouting); + assertEquals(0, NativeMemoryCacheManager.getInstance().getIndicesCacheStats().size()); + } + + @SneakyThrows + public void testShards() { + String testIndex = getTestName().toLowerCase(); + ClusterService clusterService = node().injector().getInstance(ClusterService.class); + ClearCacheTransportAction clearCacheTransportAction = node().injector().getInstance(ClearCacheTransportAction.class); + ClearCacheRequest clearCacheRequest = new ClearCacheRequest(testIndex); + + createKNNIndex(testIndex); + createKnnIndexMapping(testIndex, TEST_FIELD, DIMENSIONS); + addKnnDoc(testIndex, String.valueOf(randomInt()), TEST_FIELD, new Float[] { randomFloat(), randomFloat() }); + + ShardsIterator shardsIterator = clearCacheTransportAction.shards( + clusterService.state(), + clearCacheRequest, + new String[] { testIndex } + ); + assertEquals(1, shardsIterator.size()); + } + + public void testCheckGlobalBlock_throwsClusterBlockException() { + String testIndex = getTestName().toLowerCase(); + String description = "testing metadata block"; + ClusterService clusterService = mock(ClusterService.class); + addGlobalClusterBlock(clusterService, description, EnumSet.of(ClusterBlockLevel.METADATA_WRITE)); + ClearCacheTransportAction clearCacheTransportAction = node().injector().getInstance(ClearCacheTransportAction.class); + ClearCacheRequest clearCacheRequest = new ClearCacheRequest(testIndex); + ClusterBlockException ex = clearCacheTransportAction.checkGlobalBlock(clusterService.state(), clearCacheRequest); + assertTrue(ex.getMessage().contains(description)); + } + + public void testCheckGlobalBlock_notThrowsClusterBlockException() { + String testIndex = getTestName().toLowerCase(); + ClusterService clusterService = mock(ClusterService.class); + ClearCacheTransportAction clearCacheTransportAction = node().injector().getInstance(ClearCacheTransportAction.class); + ClearCacheRequest clearCacheRequest = new ClearCacheRequest(testIndex); + ClusterState state = ClusterState.builder(ClusterName.DEFAULT).build(); + when(clusterService.state()).thenReturn(state); + assertNull(clearCacheTransportAction.checkGlobalBlock(clusterService.state(), clearCacheRequest)); + } + + public void testCheckRequestBlock_throwsClusterBlockException() { + String testIndex = getTestName().toLowerCase(); + String description = "testing index metadata block"; + ClusterService clusterService = mock(ClusterService.class); + addIndexClusterBlock(clusterService, description, EnumSet.of(ClusterBlockLevel.METADATA_WRITE), testIndex); + + ClearCacheTransportAction clearCacheTransportAction = node().injector().getInstance(ClearCacheTransportAction.class); + ClearCacheRequest clearCacheRequest = new ClearCacheRequest(testIndex); + ClusterBlockException ex = clearCacheTransportAction.checkRequestBlock( + clusterService.state(), + clearCacheRequest, + new String[] { testIndex } + ); + assertTrue(ex.getMessage().contains(testIndex)); + assertTrue(ex.getMessage().contains(description)); + + } + + public void testCheckRequestBlock_notThrowsClusterBlockException() { + String testIndex = getTestName().toLowerCase(); + ClusterService clusterService = mock(ClusterService.class); + ClearCacheTransportAction clearCacheTransportAction = node().injector().getInstance(ClearCacheTransportAction.class); + ClearCacheRequest clearCacheRequest = new ClearCacheRequest(testIndex); + ClusterState state = ClusterState.builder(ClusterName.DEFAULT).build(); + when(clusterService.state()).thenReturn(state); + assertNull(clearCacheTransportAction.checkRequestBlock(clusterService.state(), clearCacheRequest, new String[] { testIndex })); + } +} diff --git a/src/test/java/org/opensearch/knn/plugin/transport/DeleteModelResponseTests.java b/src/test/java/org/opensearch/knn/plugin/transport/DeleteModelResponseTests.java index d25c0929a..973ff00db 100644 --- a/src/test/java/org/opensearch/knn/plugin/transport/DeleteModelResponseTests.java +++ b/src/test/java/org/opensearch/knn/plugin/transport/DeleteModelResponseTests.java @@ -11,10 +11,9 @@ package org.opensearch.knn.plugin.transport; -import org.opensearch.common.Strings; import org.opensearch.common.io.stream.BytesStreamOutput; -import org.opensearch.common.xcontent.XContentBuilder; -import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.MediaTypeRegistry; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentType; import org.opensearch.knn.KNNTestCase; @@ -24,7 +23,7 @@ public class DeleteModelResponseTests extends KNNTestCase { public void testStreams() throws IOException { String modelId = "test-model"; - DeleteModelResponse deleteModelResponse = new DeleteModelResponse(modelId, "delete action failed", "error message"); + DeleteModelResponse deleteModelResponse = new DeleteModelResponse(modelId); BytesStreamOutput streamOutput = new BytesStreamOutput(); deleteModelResponse.writeTo(streamOutput); DeleteModelResponse deleteModelResponseCopy = new DeleteModelResponse(streamOutput.bytes().streamInput()); @@ -33,25 +32,14 @@ public void testStreams() throws IOException { assertEquals(deleteModelResponse.getErrorMessage(), deleteModelResponseCopy.getErrorMessage()); } - public void testXContentWithError() throws IOException { - String modelId = "test-model"; - DeleteModelResponse deleteModelResponse = new DeleteModelResponse(modelId, "not_found", "model id not found"); - BytesStreamOutput streamOutput = new BytesStreamOutput(); - deleteModelResponse.writeTo(streamOutput); - String expectedResponseString = "{\"model_id\":\"test-model\",\"result\":\"not_found\",\"error\":\"model id not found\"}"; - XContentBuilder xContentBuilder = XContentFactory.contentBuilder(XContentType.JSON); - deleteModelResponse.toXContent(xContentBuilder, null); - assertEquals(expectedResponseString, Strings.toString(xContentBuilder)); - } - public void testXContentWithoutError() throws IOException { String modelId = "test-model"; - DeleteModelResponse deleteModelResponse = new DeleteModelResponse(modelId, "deleted", null); + DeleteModelResponse deleteModelResponse = new DeleteModelResponse(modelId); BytesStreamOutput streamOutput = new BytesStreamOutput(); deleteModelResponse.writeTo(streamOutput); String expectedResponseString = "{\"model_id\":\"test-model\",\"result\":\"deleted\"}"; - XContentBuilder xContentBuilder = XContentFactory.contentBuilder(XContentType.JSON); + XContentBuilder xContentBuilder = MediaTypeRegistry.contentBuilder(XContentType.JSON); deleteModelResponse.toXContent(xContentBuilder, null); - assertEquals(expectedResponseString, Strings.toString(xContentBuilder)); + assertEquals(expectedResponseString, xContentBuilder.toString()); } } diff --git a/src/test/java/org/opensearch/knn/plugin/transport/GetModelResponseTests.java b/src/test/java/org/opensearch/knn/plugin/transport/GetModelResponseTests.java index 04d3b419e..771b07edd 100644 --- a/src/test/java/org/opensearch/knn/plugin/transport/GetModelResponseTests.java +++ b/src/test/java/org/opensearch/knn/plugin/transport/GetModelResponseTests.java @@ -11,24 +11,49 @@ package org.opensearch.knn.plugin.transport; -import org.opensearch.common.Strings; +import org.mockito.MockedStatic; +import org.opensearch.Version; import org.opensearch.common.io.stream.BytesStreamOutput; -import org.opensearch.common.xcontent.XContentBuilder; -import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.MediaTypeRegistry; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentType; import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; +import org.opensearch.knn.index.util.KNNClusterUtil; +import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; import org.opensearch.knn.indices.Model; import org.opensearch.knn.indices.ModelMetadata; import org.opensearch.knn.indices.ModelState; import java.io.IOException; +import java.util.Locale; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; public class GetModelResponseTests extends KNNTestCase { private ModelMetadata getModelMetadata(ModelState state) { - return new ModelMetadata(KNNEngine.DEFAULT, SpaceType.DEFAULT, 4, state, "2021-03-27 10:15:30 AM +05:30", "test model", ""); + return new ModelMetadata( + KNNEngine.DEFAULT, + SpaceType.DEFAULT, + 4, + state, + "2021-03-27 10:15:30 AM +05:30", + "test model", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ); } public void testStreams() throws IOException { @@ -43,25 +68,43 @@ public void testStreams() throws IOException { } public void testXContent() throws IOException { - String modelId = "test-model"; - byte[] testModelBlob = "hello".getBytes(); - Model model = new Model(getModelMetadata(ModelState.CREATED), testModelBlob, modelId); - GetModelResponse getModelResponse = new GetModelResponse(model); - String expectedResponseString = - "{\"model_id\":\"test-model\",\"model_blob\":\"aGVsbG8=\",\"state\":\"created\",\"timestamp\":\"2021-03-27 10:15:30 AM +05:30\",\"description\":\"test model\",\"error\":\"\",\"space_type\":\"l2\",\"dimension\":4,\"engine\":\"nmslib\"}"; - XContentBuilder xContentBuilder = XContentFactory.contentBuilder(XContentType.JSON); - getModelResponse.toXContent(xContentBuilder, null); - assertEquals(expectedResponseString, Strings.toString(xContentBuilder)); + try (MockedStatic knnClusterUtilMockedStatic = mockStatic(KNNClusterUtil.class)) { + final KNNClusterUtil knnClusterUtil = mock(KNNClusterUtil.class); + when(knnClusterUtil.getClusterMinVersion()).thenReturn(Version.CURRENT); + knnClusterUtilMockedStatic.when(KNNClusterUtil::instance).thenReturn(knnClusterUtil); + String modelId = "test-model"; + byte[] testModelBlob = "hello".getBytes(); + Model model = new Model(getModelMetadata(ModelState.CREATED), testModelBlob, modelId); + GetModelResponse getModelResponse = new GetModelResponse(model); + String expectedResponseString = String.format( + Locale.ROOT, + "{\"model_id\":\"test-model\",\"model_blob\":\"aGVsbG8=\",\"state\":\"created\",\"timestamp\":\"2021-03-27 10:15:30 AM +05:30\",\"description\":\"test model\",\"error\":\"\",\"space_type\":\"l2\",\"dimension\":4,\"engine\":\"%s\",\"training_node_assignment\":\"\",\"model_definition\":{\"name\":\"\",\"parameters\":{}},\"data_type\":\"float\",\"model_version\":\"%s\"}", + KNNEngine.DEFAULT.getName(), + Version.CURRENT.toString() + ); + XContentBuilder xContentBuilder = MediaTypeRegistry.contentBuilder(XContentType.JSON); + getModelResponse.toXContent(xContentBuilder, null); + assertEquals(expectedResponseString, xContentBuilder.toString()); + } } public void testXContentWithNoModelBlob() throws IOException { - String modelId = "test-model"; - Model model = new Model(getModelMetadata(ModelState.FAILED), null, modelId); - GetModelResponse getModelResponse = new GetModelResponse(model); - String expectedResponseString = - "{\"model_id\":\"test-model\",\"model_blob\":\"\",\"state\":\"failed\",\"timestamp\":\"2021-03-27 10:15:30 AM +05:30\",\"description\":\"test model\",\"error\":\"\",\"space_type\":\"l2\",\"dimension\":4,\"engine\":\"nmslib\"}"; - XContentBuilder xContentBuilder = XContentFactory.contentBuilder(XContentType.JSON); - getModelResponse.toXContent(xContentBuilder, null); - assertEquals(expectedResponseString, Strings.toString(xContentBuilder)); + try (MockedStatic knnClusterUtilMockedStatic = mockStatic(KNNClusterUtil.class)) { + final KNNClusterUtil knnClusterUtil = mock(KNNClusterUtil.class); + when(knnClusterUtil.getClusterMinVersion()).thenReturn(Version.CURRENT); + knnClusterUtilMockedStatic.when(KNNClusterUtil::instance).thenReturn(knnClusterUtil); + String modelId = "test-model"; + Model model = new Model(getModelMetadata(ModelState.FAILED), null, modelId); + GetModelResponse getModelResponse = new GetModelResponse(model); + String expectedResponseString = String.format( + Locale.ROOT, + "{\"model_id\":\"test-model\",\"model_blob\":\"\",\"state\":\"failed\",\"timestamp\":\"2021-03-27 10:15:30 AM +05:30\",\"description\":\"test model\",\"error\":\"\",\"space_type\":\"l2\",\"dimension\":4,\"engine\":\"%s\",\"training_node_assignment\":\"\",\"model_definition\":{\"name\":\"\",\"parameters\":{}},\"data_type\":\"float\",\"model_version\":\"%s\"}", + KNNEngine.DEFAULT.getName(), + Version.CURRENT.toString() + ); + XContentBuilder xContentBuilder = MediaTypeRegistry.contentBuilder(XContentType.JSON); + getModelResponse.toXContent(xContentBuilder, null); + assertEquals(expectedResponseString, xContentBuilder.toString()); + } } } diff --git a/src/test/java/org/opensearch/knn/plugin/transport/KNNWarmupTransportActionTests.java b/src/test/java/org/opensearch/knn/plugin/transport/KNNWarmupTransportActionTests.java index da4c8d834..f4b52246d 100644 --- a/src/test/java/org/opensearch/knn/plugin/transport/KNNWarmupTransportActionTests.java +++ b/src/test/java/org/opensearch/knn/plugin/transport/KNNWarmupTransportActionTests.java @@ -16,7 +16,7 @@ import org.opensearch.cluster.service.ClusterService; import org.opensearch.index.IndexService; import org.opensearch.knn.index.memory.NativeMemoryCacheManager; -import org.opensearch.rest.RestStatus; +import org.opensearch.core.rest.RestStatus; import java.io.IOException; import java.util.EnumSet; @@ -37,7 +37,7 @@ public void testShardOperation() throws IOException, ExecutionException, Interru KNNWarmupTransportAction knnWarmupTransportAction = node().injector().getInstance(KNNWarmupTransportAction.class); assertEquals(0, NativeMemoryCacheManager.getInstance().getIndicesCacheStats().size()); - indexService = createKNNIndex(testIndexName); + indexService = createIndex(testIndexName, getKNNDefaultIndexSettingsBuildsGraphAlways()); createKnnIndexMapping(testIndexName, testFieldName, dimensions); shardRouting = indexService.iterator().next().routingEntry(); diff --git a/src/test/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheTransportActionTests.java b/src/test/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheTransportActionTests.java index ae89d83e1..8f4cad112 100644 --- a/src/test/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheTransportActionTests.java +++ b/src/test/java/org/opensearch/knn/plugin/transport/RemoveModelFromCacheTransportActionTests.java @@ -13,12 +13,17 @@ import com.google.common.collect.ImmutableSet; import org.junit.Ignore; +import org.opensearch.Version; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.Settings; import org.opensearch.knn.KNNSingleNodeTestCase; +import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; import org.opensearch.knn.indices.Model; import org.opensearch.knn.indices.ModelCache; import org.opensearch.knn.indices.ModelDao; @@ -68,7 +73,21 @@ public void testNodeOperation_modelInCache() throws ExecutionException, Interrup ModelDao modelDao = mock(ModelDao.class); String modelId = "test-model-id"; Model model = new Model( - new ModelMetadata(KNNEngine.DEFAULT, SpaceType.L2, 16, ModelState.CREATED, "timestamp", "description", ""), + new ModelMetadata( + KNNEngine.DEFAULT, + SpaceType.L2, + 16, + ModelState.CREATED, + "timestamp", + "description", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ), new byte[128], modelId ); diff --git a/src/test/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoNodeResponseTests.java b/src/test/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoNodeResponseTests.java index 8085b0170..04f990dc9 100644 --- a/src/test/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoNodeResponseTests.java +++ b/src/test/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoNodeResponseTests.java @@ -16,9 +16,9 @@ import org.opensearch.Version; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.common.io.stream.BytesStreamOutput; -import org.opensearch.common.transport.TransportAddress; -import org.opensearch.common.xcontent.ToXContent; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.common.transport.TransportAddress; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.knn.KNNTestCase; diff --git a/src/test/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoResponseTests.java b/src/test/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoResponseTests.java index 87d0b7ee5..d828e0604 100644 --- a/src/test/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoResponseTests.java +++ b/src/test/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoResponseTests.java @@ -19,9 +19,9 @@ import org.opensearch.cluster.ClusterName; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.common.io.stream.BytesStreamOutput; -import org.opensearch.common.transport.TransportAddress; -import org.opensearch.common.xcontent.ToXContent; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.common.transport.TransportAddress; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.knn.KNNTestCase; diff --git a/src/test/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoTransportActionTests.java b/src/test/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoTransportActionTests.java index 7049bad2d..d07be2070 100644 --- a/src/test/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoTransportActionTests.java +++ b/src/test/java/org/opensearch/knn/plugin/transport/TrainingJobRouteDecisionInfoTransportActionTests.java @@ -11,95 +11,50 @@ package org.opensearch.knn.plugin.transport; -import org.junit.After; -import org.junit.Before; -import org.opensearch.action.ActionListener; -import org.opensearch.action.index.IndexResponse; -import org.opensearch.index.shard.ShardId; -import org.opensearch.knn.KNNSingleNodeTestCase; -import org.opensearch.knn.indices.Model; -import org.opensearch.knn.indices.ModelDao; -import org.opensearch.knn.training.TrainingJob; +import org.mockito.MockedStatic; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.cluster.node.DiscoveryNode; +import org.opensearch.knn.KNNTestCase; import org.opensearch.knn.training.TrainingJobRunner; import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.TransportService; -import java.io.IOException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; +import java.util.Collections; import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; -import static org.opensearch.knn.common.KNNConstants.MODEL_INDEX_NAME; -import static org.opensearch.knn.common.KNNConstants.TRAIN_THREAD_POOL; - -public class TrainingJobRouteDecisionInfoTransportActionTests extends KNNSingleNodeTestCase { - - ExecutorService executorService; - - @Before - public void setup() { - executorService = Executors.newSingleThreadExecutor(); - } - - @After - public void teardown() { - executorService.shutdown(); - } - - @SuppressWarnings("unchecked") - public void testNodeOperation() throws IOException, InterruptedException { - // Ensure initial value of train job count is 0 - TrainingJobRouteDecisionInfoTransportAction action = node().injector() - .getInstance(TrainingJobRouteDecisionInfoTransportAction.class); - - TrainingJobRouteDecisionInfoNodeRequest request = new TrainingJobRouteDecisionInfoNodeRequest(); - - TrainingJobRouteDecisionInfoNodeResponse response1 = action.nodeOperation(request); - assertEquals(0, response1.getTrainingJobCount().intValue()); - - // Setup mocked training job - String modelId = "model-id"; - Model model = mock(Model.class); - TrainingJob trainingJob = mock(TrainingJob.class); - when(trainingJob.getModelId()).thenReturn(modelId); - when(trainingJob.getModel()).thenReturn(model); - doAnswer(invocationOnMock -> null).when(trainingJob).run(); - - ModelDao modelDao = mock(ModelDao.class); - - // Here we check to make sure there is a running job - doAnswer(invocationOnMock -> { - TrainingJobRouteDecisionInfoNodeResponse response2 = action.nodeOperation(request); - assertEquals(1, response2.getTrainingJobCount().intValue()); - - IndexResponse indexResponse = new IndexResponse(new ShardId(MODEL_INDEX_NAME, "uuid", 0), modelId, 0, 0, 0, true); - ((ActionListener) invocationOnMock.getArguments()[1]).onResponse(indexResponse); - return null; - }).when(modelDao).put(any(Model.class), any(ActionListener.class)); - - // Set up the rest of the training logic - final CountDownLatch inProgressLatch = new CountDownLatch(1); - ActionListener responseListener = ActionListener.wrap( - indexResponse -> { inProgressLatch.countDown(); }, - e -> fail("Failure should not have occurred") - ); - - doAnswer(invocationOnMock -> { - responseListener.onResponse(mock(IndexResponse.class)); - return null; - }).when(modelDao).update(model, responseListener); +public class TrainingJobRouteDecisionInfoTransportActionTests extends KNNTestCase { + public void testNodeOperation() { + // Initialize mocked variables for the class + DiscoveryNode node = mock(DiscoveryNode.class); + when(clusterService.localNode()).thenReturn(node); ThreadPool threadPool = mock(ThreadPool.class); - when(threadPool.executor(TRAIN_THREAD_POOL)).thenReturn(executorService); - - // Initialize runner and execute job - TrainingJobRunner.initialize(threadPool, modelDao); - TrainingJobRunner.getInstance().execute(trainingJob, responseListener); - - assertTrue(inProgressLatch.await(100, TimeUnit.SECONDS)); + TransportService transportService = mock(TransportService.class); + doNothing().when(transportService).registerRequestHandler(any(), any(), any(), any()); + ActionFilters actionFilters = new ActionFilters(Collections.emptySet()); + + TrainingJobRouteDecisionInfoTransportAction trainingJobRouteDecisionInfoTransportAction = + new TrainingJobRouteDecisionInfoTransportAction(threadPool, clusterService, transportService, actionFilters); + + try (MockedStatic mockedTrainingJobRunnerStatic = mockStatic(TrainingJobRunner.class)) { + // Ensure the job count is correct + int initialJobCount = 4; + final TrainingJobRunner mockedTrainingJobRunner = mock(TrainingJobRunner.class); + when(mockedTrainingJobRunner.getJobCount()).thenReturn(initialJobCount); + mockedTrainingJobRunnerStatic.when(TrainingJobRunner::getInstance).thenReturn(mockedTrainingJobRunner); + + TrainingJobRouteDecisionInfoNodeRequest request = new TrainingJobRouteDecisionInfoNodeRequest(); + TrainingJobRouteDecisionInfoNodeResponse response = trainingJobRouteDecisionInfoTransportAction.nodeOperation(request); + assertEquals(initialJobCount, response.getTrainingJobCount().intValue()); + + int resetJobCount = 0; + when(mockedTrainingJobRunner.getJobCount()).thenReturn(resetJobCount); + response = trainingJobRouteDecisionInfoTransportAction.nodeOperation(request); + assertEquals(resetJobCount, response.getTrainingJobCount().intValue()); + } } } diff --git a/src/test/java/org/opensearch/knn/plugin/transport/TrainingJobRouterTransportActionTests.java b/src/test/java/org/opensearch/knn/plugin/transport/TrainingJobRouterTransportActionTests.java index 5f907b6c4..30c5d33a1 100644 --- a/src/test/java/org/opensearch/knn/plugin/transport/TrainingJobRouterTransportActionTests.java +++ b/src/test/java/org/opensearch/knn/plugin/transport/TrainingJobRouterTransportActionTests.java @@ -13,7 +13,7 @@ import com.google.common.collect.ImmutableList; import org.apache.lucene.search.TotalHits; -import org.opensearch.action.ActionListener; +import org.opensearch.core.action.ActionListener; import org.opensearch.action.search.SearchResponse; import org.opensearch.action.support.ActionFilters; import org.opensearch.client.Client; @@ -22,16 +22,19 @@ import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.node.DiscoveryNodes; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.collect.ImmutableOpenMap; import org.opensearch.knn.KNNTestCase; -import org.opensearch.knn.index.KNNMethodContext; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; import org.opensearch.search.SearchHit; import org.opensearch.search.SearchHits; import org.opensearch.transport.TransportService; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doAnswer; @@ -45,7 +48,7 @@ public void testSingleNode_withCapacity() { // Mock datanodes in the cluster through mocking the cluster service List nodeIds = ImmutableList.of("node-1"); - ImmutableOpenMap discoveryNodesMap = generateDiscoveryNodes(nodeIds); + Map discoveryNodesMap = generateDiscoveryNodes(nodeIds); ClusterService clusterService = generateMockedClusterService(discoveryNodesMap); // Create a response to be returned with job route decision info @@ -85,7 +88,7 @@ public void testSingleNode_withoutCapacity() { // Mock datanodes in the cluster through mocking the cluster service List nodeIds = ImmutableList.of("node-1"); - ImmutableOpenMap discoveryNodesMap = generateDiscoveryNodes(nodeIds); + Map discoveryNodesMap = generateDiscoveryNodes(nodeIds); ClusterService clusterService = generateMockedClusterService(discoveryNodesMap); // Create a response to be returned with job route decision info @@ -125,7 +128,7 @@ public void testMultiNode_withCapacity() { // Mock datanodes in the cluster through mocking the cluster service List nodeIds = ImmutableList.of("node-1", "node-2", "node-3"); - ImmutableOpenMap discoveryNodesMap = generateDiscoveryNodes(nodeIds); + Map discoveryNodesMap = generateDiscoveryNodes(nodeIds); ClusterService clusterService = generateMockedClusterService(discoveryNodesMap); // Create a response to be returned with job route decision info @@ -168,7 +171,7 @@ public void testMultiNode_withCapacity_withPreferredAvailable() { String preferredNode = nodeIds.get(2); - ImmutableOpenMap discoveryNodesMap = generateDiscoveryNodes(nodeIds); + Map discoveryNodesMap = generateDiscoveryNodes(nodeIds); ClusterService clusterService = generateMockedClusterService(discoveryNodesMap); // Create a response to be returned with job route decision info @@ -211,7 +214,7 @@ public void testMultiNode_withCapacity_withoutPreferredAvailable() { String preferredNode = nodeIds.get(2); - ImmutableOpenMap discoveryNodesMap = generateDiscoveryNodes(nodeIds); + Map discoveryNodesMap = generateDiscoveryNodes(nodeIds); ClusterService clusterService = generateMockedClusterService(discoveryNodesMap); // Create a response to be returned with job route decision info @@ -253,7 +256,7 @@ public void testMultiNode_withoutCapacity() { // Mock datanodes in the cluster through mocking the cluster service List nodeIds = ImmutableList.of("node-1", "node-2", "node-3"); - ImmutableOpenMap discoveryNodesMap = generateDiscoveryNodes(nodeIds); + Map discoveryNodesMap = generateDiscoveryNodes(nodeIds); ClusterService clusterService = generateMockedClusterService(discoveryNodesMap); // Create a response to be returned with job route decision info @@ -301,12 +304,15 @@ public void testTrainingIndexSize() { // Setup the request TrainingModelRequest trainingModelRequest = new TrainingModelRequest( null, - KNNMethodContext.getDefault(), + getDefaultKNNMethodContextForModel(), dimension, trainingIndexName, "training-field", null, - "description" + "description", + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED ); // Mock client to return the right number of docs @@ -338,19 +344,119 @@ public void testTrainingIndexSize() { transportAction.getTrainingIndexSizeInKB(trainingModelRequest, listener); } - private ImmutableOpenMap generateDiscoveryNodes(List dataNodeIds) { - ImmutableOpenMap.Builder builder = ImmutableOpenMap.builder(); + public void testTrainIndexSize_whenDataTypeIsBinary() { + String trainingIndexName = "training-index"; + int dimension = 8; + int vectorCount = 1000000; + int expectedSize = Byte.BYTES * (dimension / 8) * vectorCount / BYTES_PER_KILOBYTES + 1; // 977 KB + + // Setup the request + TrainingModelRequest trainingModelRequest = new TrainingModelRequest( + null, + getDefaultKNNMethodContextForModel(), + dimension, + trainingIndexName, + "training-field", + null, + "description", + VectorDataType.BINARY, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED + ); + + // Mock client to return the right number of docs + TotalHits totalHits = new TotalHits(vectorCount, TotalHits.Relation.EQUAL_TO); + SearchHits searchHits = new SearchHits(new SearchHit[2], totalHits, 1.0f); + SearchResponse searchResponse = mock(SearchResponse.class); + when(searchResponse.getHits()).thenReturn(searchHits); + Client client = mock(Client.class); + + doAnswer(invocationOnMock -> { + ((ActionListener) invocationOnMock.getArguments()[1]).onResponse(searchResponse); + return null; + }).when(client).search(any(), any()); + + // Setup the action + ClusterService clusterService = mock(ClusterService.class); + TransportService transportService = mock(TransportService.class); + TrainingJobRouterTransportAction transportAction = new TrainingJobRouterTransportAction( + transportService, + new ActionFilters(Collections.emptySet()), + clusterService, + client + ); + + ActionListener listener = ActionListener.wrap( + size -> assertEquals(expectedSize, size.intValue()), + e -> fail(e.getMessage()) + ); + + transportAction.getTrainingIndexSizeInKB(trainingModelRequest, listener); + } + + public void testTrainIndexSize_whenDataTypeIsByte() { + String trainingIndexName = "training-index"; + int dimension = 8; + int vectorCount = 1000000; + int expectedSize = Byte.BYTES * dimension * vectorCount / BYTES_PER_KILOBYTES + 1; // 7813 KB + + // Setup the request + TrainingModelRequest trainingModelRequest = new TrainingModelRequest( + null, + getDefaultKNNMethodContextForModel(), + dimension, + trainingIndexName, + "training-field", + null, + "description", + VectorDataType.BYTE, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED + ); + + // Mock client to return the right number of docs + TotalHits totalHits = new TotalHits(vectorCount, TotalHits.Relation.EQUAL_TO); + SearchHits searchHits = new SearchHits(new SearchHit[2], totalHits, 1.0f); + SearchResponse searchResponse = mock(SearchResponse.class); + when(searchResponse.getHits()).thenReturn(searchHits); + Client client = mock(Client.class); + + doAnswer(invocationOnMock -> { + ((ActionListener) invocationOnMock.getArguments()[1]).onResponse(searchResponse); + return null; + }).when(client).search(any(), any()); + + // Setup the action + ClusterService clusterService = mock(ClusterService.class); + TransportService transportService = mock(TransportService.class); + TrainingJobRouterTransportAction transportAction = new TrainingJobRouterTransportAction( + transportService, + new ActionFilters(Collections.emptySet()), + clusterService, + client + ); + + ActionListener listener = ActionListener.wrap( + size -> assertEquals(expectedSize, size.intValue()), + e -> fail(e.getMessage()) + ); + + transportAction.getTrainingIndexSizeInKB(trainingModelRequest, listener); + } + + private Map generateDiscoveryNodes(List dataNodeIds) { + Map nodes = new HashMap<>(); for (String nodeId : dataNodeIds) { DiscoveryNode discoveryNode = mock(DiscoveryNode.class); when(discoveryNode.getId()).thenReturn(nodeId); - builder.put(nodeId, discoveryNode); + nodes.put(nodeId, discoveryNode); } - return builder.build(); + return nodes; } - private ClusterService generateMockedClusterService(ImmutableOpenMap discoveryNodeMap) { + private ClusterService generateMockedClusterService(Map discoveryNodeMap) { DiscoveryNodes discoveryNodes = mock(DiscoveryNodes.class); when(discoveryNodes.getDataNodes()).thenReturn(discoveryNodeMap); ClusterState clusterState = mock(ClusterState.class); diff --git a/src/test/java/org/opensearch/knn/plugin/transport/TrainingModelRequestTests.java b/src/test/java/org/opensearch/knn/plugin/transport/TrainingModelRequestTests.java index 06b6f3d01..6fd399434 100644 --- a/src/test/java/org/opensearch/knn/plugin/transport/TrainingModelRequestTests.java +++ b/src/test/java/org/opensearch/knn/plugin/transport/TrainingModelRequestTests.java @@ -12,6 +12,7 @@ package org.opensearch.knn.plugin.transport; import com.google.common.collect.ImmutableMap; +import org.opensearch.Version; import org.opensearch.action.ActionRequestValidationException; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.metadata.IndexMetadata; @@ -20,14 +21,17 @@ import org.opensearch.cluster.node.DiscoveryNodes; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.ValidationException; -import org.opensearch.common.collect.ImmutableOpenMap; import org.opensearch.common.io.stream.BytesStreamOutput; import org.opensearch.knn.KNNTestCase; import org.opensearch.knn.common.KNNConstants; -import org.opensearch.knn.index.KNNMethodContext; -import org.opensearch.knn.index.KNNVectorFieldMapper; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.KNNVectorFieldMapper; import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.mapper.Mode; import org.opensearch.knn.indices.ModelDao; import org.opensearch.knn.indices.ModelMetadata; import org.opensearch.knn.indices.ModelState; @@ -42,12 +46,13 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; public class TrainingModelRequestTests extends KNNTestCase { public void testStreams() throws IOException { String modelId = "test-model-id"; - KNNMethodContext knnMethodContext = KNNMethodContext.getDefault(); + KNNMethodContext knnMethodContext = getDefaultKNNMethodContextForModel(); int dimension = 10; String trainingIndex = "test-training-index"; String trainingField = "test-training-field"; @@ -61,7 +66,10 @@ public void testStreams() throws IOException { trainingIndex, trainingField, preferredNode, - description + description, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED ); BytesStreamOutput streamOutput = new BytesStreamOutput(); @@ -74,6 +82,9 @@ public void testStreams() throws IOException { assertEquals(original1.getTrainingIndex(), copy1.getTrainingIndex()); assertEquals(original1.getTrainingField(), copy1.getTrainingField()); assertEquals(original1.getPreferredNodeId(), copy1.getPreferredNodeId()); + assertEquals(original1.getVectorDataType(), copy1.getVectorDataType()); + assertEquals(original1.getMode(), copy1.getMode()); + assertEquals(original1.getCompressionLevel(), copy1.getCompressionLevel()); // Also, check when preferred node and model id and description are null TrainingModelRequest original2 = new TrainingModelRequest( @@ -83,7 +94,10 @@ public void testStreams() throws IOException { trainingIndex, trainingField, null, - null + null, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED ); streamOutput = new BytesStreamOutput(); @@ -96,11 +110,39 @@ public void testStreams() throws IOException { assertEquals(original2.getTrainingIndex(), copy2.getTrainingIndex()); assertEquals(original2.getTrainingField(), copy2.getTrainingField()); assertEquals(original2.getPreferredNodeId(), copy2.getPreferredNodeId()); + assertEquals(original2.getVectorDataType(), copy2.getVectorDataType()); + assertEquals(original2.getMode(), copy2.getMode()); + assertEquals(original2.getCompressionLevel(), copy2.getCompressionLevel()); + + TrainingModelRequest original3 = new TrainingModelRequest( + null, + knnMethodContext, + dimension, + trainingIndex, + trainingField, + null, + null, + VectorDataType.DEFAULT, + Mode.ON_DISK, + CompressionLevel.x32 + ); + + streamOutput = new BytesStreamOutput(); + original3.writeTo(streamOutput); + TrainingModelRequest copy3 = new TrainingModelRequest(streamOutput.bytes().streamInput()); + + assertEquals(original3.getModelId(), copy3.getModelId()); + assertEquals(original3.getKnnMethodContext(), copy3.getKnnMethodContext()); + assertEquals(original3.getDimension(), copy3.getDimension()); + assertEquals(original3.getTrainingIndex(), copy3.getTrainingIndex()); + assertEquals(original3.getTrainingField(), copy3.getTrainingField()); + assertEquals(original3.getMode(), copy3.getMode()); + assertEquals(original3.getCompressionLevel(), copy3.getCompressionLevel()); } public void testGetters() { String modelId = "test-model-id"; - KNNMethodContext knnMethodContext = KNNMethodContext.getDefault(); + KNNMethodContext knnMethodContext = getDefaultKNNMethodContextForModel(); int dimension = 10; String trainingIndex = "test-training-index"; String trainingField = "test-training-field"; @@ -117,7 +159,10 @@ public void testGetters() { trainingIndex, trainingField, preferredNode, - description + description, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED ); trainingModelRequest.setMaximumVectorCount(maxVectorCount); @@ -125,7 +170,6 @@ public void testGetters() { trainingModelRequest.setTrainingDataSizeInKB(trainingSetSizeInKB); assertEquals(modelId, trainingModelRequest.getModelId()); - assertEquals(knnMethodContext, trainingModelRequest.getKnnMethodContext()); assertEquals(dimension, trainingModelRequest.getDimension()); assertEquals(trainingIndex, trainingModelRequest.getTrainingIndex()); assertEquals(trainingField, trainingModelRequest.getTrainingField()); @@ -142,21 +186,21 @@ public void testValidation_invalid_modelIdAlreadyExists() { // Setup the training request String modelId = "test-model-id"; - KNNMethodContext knnMethodContext = mock(KNNMethodContext.class); - when(knnMethodContext.validate()).thenReturn(null); - when(knnMethodContext.isTrainingRequired()).thenReturn(true); int dimension = 10; String trainingIndex = "test-training-index"; String trainingField = "test-training-field"; TrainingModelRequest trainingModelRequest = new TrainingModelRequest( modelId, - knnMethodContext, + getDefaultKNNMethodContextForModel(), dimension, trainingIndex, trainingField, null, - null + null, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED ); // Mock the model dao to return metadata for modelId to recognize it is a duplicate @@ -168,10 +212,19 @@ public void testValidation_invalid_modelIdAlreadyExists() { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ); when(modelDao.getMetadata(modelId)).thenReturn(modelMetadata); + // ModelId is not added to model graveyard + when(modelDao.isModelInGraveyard(modelId)).thenReturn(false); + // This cluster service will result in no validation exceptions ClusterService clusterService = getClusterServiceForValidReturns(trainingIndex, trainingField, dimension); @@ -186,37 +239,32 @@ public void testValidation_invalid_modelIdAlreadyExists() { assertTrue(validationErrors.get(0).contains("already exists")); } - public void testValidation_invalid_invalidMethodContext() { - // Check that validation produces exception when the method is invalid and does not require training + // Check that the validation produces an exception when we are + // training a model with modelId that is in model graveyard + public void testValidation_blocked_modelId() { // Setup the training request String modelId = "test-model-id"; - - // Mock throwing an exception on validation - KNNMethodContext knnMethodContext = mock(KNNMethodContext.class); - String validationExceptionMessage = "knn method invalid"; - ValidationException validationException = new ValidationException(); - validationException.addValidationError(validationExceptionMessage); - when(knnMethodContext.validate()).thenReturn(validationException); - - when(knnMethodContext.isTrainingRequired()).thenReturn(false); int dimension = 10; String trainingIndex = "test-training-index"; String trainingField = "test-training-field"; TrainingModelRequest trainingModelRequest = new TrainingModelRequest( modelId, - knnMethodContext, + getDefaultKNNMethodContextForModel(), dimension, trainingIndex, trainingField, null, - null + null, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED ); - // Mock the model dao to return null so that no exception is produced + // Mock the model dao to return true to recognize that the modelId is in graveyard ModelDao modelDao = mock(ModelDao.class); - when(modelDao.getMetadata(modelId)).thenReturn(null); + when(modelDao.isModelInGraveyard(modelId)).thenReturn(true); // This cluster service will result in no validation exceptions ClusterService clusterService = getClusterServiceForValidReturns(trainingIndex, trainingField, dimension); @@ -224,37 +272,66 @@ public void testValidation_invalid_invalidMethodContext() { // Initialize static components with the mocks TrainingModelRequest.initialize(modelDao, clusterService); - // Test that validation produces model already exists error message + // Test that validation produces an error message that modelId is being deleted ActionRequestValidationException exception = trainingModelRequest.validate(); assertNotNull(exception); List validationErrors = exception.validationErrors(); - assertEquals(2, validationErrors.size()); - assertTrue(validationErrors.get(0).contains(validationExceptionMessage)); - assertTrue(validationErrors.get(1).contains("Method does not require training.")); + assertEquals(1, validationErrors.size()); + assertTrue(validationErrors.get(0).contains("is being deleted")); } - public void testValidation_invalid_trainingIndexDoesNotExist() { - // Check that validation produces exception when the training index doesnt exist + public void testValidation_invalid_invalidMethodContext() { + // Check that validation produces exception when the method is invalid and does not require training // Setup the training request String modelId = "test-model-id"; - KNNMethodContext knnMethodContext = mock(KNNMethodContext.class); - when(knnMethodContext.validate()).thenReturn(null); + // Mock throwing an exception on validation + int dimension = 10; + String trainingIndex = "test-training-index"; + String trainingField = "test-training-field"; + + MethodComponentContext methodComponentContext = new MethodComponentContext(METHOD_HNSW, Collections.emptyMap()); + final KNNMethodContext knnMethodContext = new KNNMethodContext(KNNEngine.NMSLIB, SpaceType.DEFAULT, methodComponentContext); + + ValidationException validationException = expectThrows( + ValidationException.class, + () -> new TrainingModelRequest( + modelId, + knnMethodContext, + dimension, + trainingIndex, + trainingField, + null, + null, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED + ) + ); + assertTrue(validationException.getMessage().contains("engine from training context")); + } + + public void testValidation_invalid_trainingIndexDoesNotExist() { + // Check that validation produces exception when the training index doesnt exist - when(knnMethodContext.isTrainingRequired()).thenReturn(true); + // Setup the training request + String modelId = "test-model-id"; int dimension = 10; String trainingIndex = "test-training-index"; String trainingField = "test-training-field"; TrainingModelRequest trainingModelRequest = new TrainingModelRequest( modelId, - knnMethodContext, + null, dimension, trainingIndex, trainingField, null, - null + null, + VectorDataType.DEFAULT, + Mode.ON_DISK, + CompressionLevel.NOT_CONFIGURED ); // Mock the model dao to return null so that no exception is produced @@ -284,23 +361,21 @@ public void testValidation_invalid_trainingFieldDoesNotExist() { // Setup the training request String modelId = "test-model-id"; - - KNNMethodContext knnMethodContext = mock(KNNMethodContext.class); - when(knnMethodContext.validate()).thenReturn(null); - - when(knnMethodContext.isTrainingRequired()).thenReturn(true); int dimension = 10; String trainingIndex = "test-training-index"; String trainingField = "test-training-field"; TrainingModelRequest trainingModelRequest = new TrainingModelRequest( modelId, - knnMethodContext, + null, dimension, trainingIndex, trainingField, null, - null + null, + VectorDataType.DEFAULT, + Mode.ON_DISK, + CompressionLevel.NOT_CONFIGURED ); // Mock the model dao to return null so that no exception is produced @@ -326,6 +401,7 @@ public void testValidation_invalid_trainingFieldDoesNotExist() { ActionRequestValidationException exception = trainingModelRequest.validate(); assertNotNull(exception); List validationErrors = exception.validationErrors(); + logger.error("Validation errors: " + validationErrors); assertEquals(1, validationErrors.size()); assertTrue(validationErrors.get(0).contains("does not exist")); } @@ -335,23 +411,21 @@ public void testValidation_invalid_trainingFieldNotKnnVector() { // Setup the training request String modelId = "test-model-id"; - - KNNMethodContext knnMethodContext = mock(KNNMethodContext.class); - when(knnMethodContext.validate()).thenReturn(null); - - when(knnMethodContext.isTrainingRequired()).thenReturn(true); int dimension = 10; String trainingIndex = "test-training-index"; String trainingField = "test-training-field"; TrainingModelRequest trainingModelRequest = new TrainingModelRequest( modelId, - knnMethodContext, + null, dimension, trainingIndex, trainingField, null, - null + null, + VectorDataType.DEFAULT, + Mode.ON_DISK, + CompressionLevel.NOT_CONFIGURED ); // Mock the model dao to return null so that no exception is produced @@ -390,23 +464,21 @@ public void testValidation_invalid_dimensionDoesNotMatch() { // Setup the training request String modelId = "test-model-id"; - - KNNMethodContext knnMethodContext = mock(KNNMethodContext.class); - when(knnMethodContext.validate()).thenReturn(null); - - when(knnMethodContext.isTrainingRequired()).thenReturn(true); int dimension = 10; String trainingIndex = "test-training-index"; String trainingField = "test-training-field"; TrainingModelRequest trainingModelRequest = new TrainingModelRequest( modelId, - knnMethodContext, + null, dimension, trainingIndex, trainingField, null, - null + null, + VectorDataType.DEFAULT, + Mode.ON_DISK, + CompressionLevel.NOT_CONFIGURED ); // Mock the model dao to return null so that no exception is produced @@ -448,9 +520,6 @@ public void testValidation_invalid_preferredNodeDoesNotExist() { // Setup the training request String modelId = "test-model-id"; - KNNMethodContext knnMethodContext = mock(KNNMethodContext.class); - when(knnMethodContext.validate()).thenReturn(null); - when(knnMethodContext.isTrainingRequired()).thenReturn(true); int dimension = 10; String trainingIndex = "test-training-index"; String trainingField = "test-training-field"; @@ -458,12 +527,15 @@ public void testValidation_invalid_preferredNodeDoesNotExist() { TrainingModelRequest trainingModelRequest = new TrainingModelRequest( modelId, - knnMethodContext, + null, dimension, trainingIndex, trainingField, preferredNode, - null + null, + VectorDataType.DEFAULT, + Mode.ON_DISK, + CompressionLevel.NOT_CONFIGURED ); // Mock the model dao to return metadata for modelId to recognize it is a duplicate @@ -485,7 +557,7 @@ public void testValidation_invalid_preferredNodeDoesNotExist() { // Empty set of data nodes to produce exception DiscoveryNodes discoveryNodes = mock(DiscoveryNodes.class); - when(discoveryNodes.getDataNodes()).thenReturn(ImmutableOpenMap.of()); + when(discoveryNodes.getDataNodes()).thenReturn(Map.of()); ClusterState clusterState = mock(ClusterState.class); when(clusterState.metadata()).thenReturn(metadata); @@ -509,9 +581,6 @@ public void testValidation_invalid_descriptionToLong() { // Setup the training request String modelId = "test-model-id"; - KNNMethodContext knnMethodContext = mock(KNNMethodContext.class); - when(knnMethodContext.validate()).thenReturn(null); - when(knnMethodContext.isTrainingRequired()).thenReturn(true); int dimension = 10; String trainingIndex = "test-training-index"; String trainingField = "test-training-field"; @@ -523,12 +592,15 @@ public void testValidation_invalid_descriptionToLong() { TrainingModelRequest trainingModelRequest = new TrainingModelRequest( modelId, - knnMethodContext, + null, dimension, trainingIndex, trainingField, null, - description + description, + VectorDataType.DEFAULT, + Mode.ON_DISK, + CompressionLevel.NOT_CONFIGURED ); // Mock the model dao to return metadata for modelId to recognize it is a duplicate @@ -549,6 +621,7 @@ public void testValidation_invalid_descriptionToLong() { ActionRequestValidationException exception = trainingModelRequest.validate(); assertNotNull(exception); List validationErrors = exception.validationErrors(); + logger.error("Validation errorsa " + validationErrors); assertEquals(1, validationErrors.size()); assertTrue(validationErrors.get(0).contains("Description exceeds limit")); } @@ -558,21 +631,21 @@ public void testValidation_valid_trainingIndexBuiltFromMethod() { // Setup the training request String modelId = "test-model-id"; - KNNMethodContext knnMethodContext = mock(KNNMethodContext.class); - when(knnMethodContext.validate()).thenReturn(null); - when(knnMethodContext.isTrainingRequired()).thenReturn(true); int dimension = 10; String trainingIndex = "test-training-index"; String trainingField = "test-training-field"; TrainingModelRequest trainingModelRequest = new TrainingModelRequest( modelId, - knnMethodContext, + null, dimension, trainingIndex, trainingField, null, - null + null, + VectorDataType.DEFAULT, + Mode.ON_DISK, + CompressionLevel.NOT_CONFIGURED ); // Mock the model dao to return metadata for modelId to recognize it is a duplicate @@ -594,9 +667,6 @@ public void testValidation_valid_trainingIndexBuiltFromModel() { // Setup the training request String modelId = "test-model-id"; - KNNMethodContext knnMethodContext = mock(KNNMethodContext.class); - when(knnMethodContext.validate()).thenReturn(null); - when(knnMethodContext.isTrainingRequired()).thenReturn(true); int dimension = 10; String trainingIndex = "test-training-index"; String trainingField = "test-training-field"; @@ -604,17 +674,21 @@ public void testValidation_valid_trainingIndexBuiltFromModel() { TrainingModelRequest trainingModelRequest = new TrainingModelRequest( modelId, - knnMethodContext, + null, dimension, trainingIndex, trainingField, null, - null + null, + VectorDataType.DEFAULT, + Mode.ON_DISK, + CompressionLevel.NOT_CONFIGURED ); // Mock the model dao to return metadata for modelId to recognize it is a duplicate ModelMetadata trainingFieldModelMetadata = mock(ModelMetadata.class); when(trainingFieldModelMetadata.getDimension()).thenReturn(dimension); + when(trainingFieldModelMetadata.getState()).thenReturn(ModelState.CREATED); ModelDao modelDao = mock(ModelDao.class); when(modelDao.getMetadata(modelId)).thenReturn(null); @@ -635,7 +709,7 @@ public void testValidation_valid_trainingIndexBuiltFromModel() { Metadata metadata = mock(Metadata.class); when(metadata.index(trainingIndex)).thenReturn(indexMetadata); DiscoveryNodes discoveryNodes = mock(DiscoveryNodes.class); - when(discoveryNodes.getDataNodes()).thenReturn(ImmutableOpenMap.of()); + when(discoveryNodes.getDataNodes()).thenReturn(Map.of()); ClusterState clusterState = mock(ClusterState.class); when(clusterState.metadata()).thenReturn(metadata); diff --git a/src/test/java/org/opensearch/knn/plugin/transport/TrainingModelResponseTests.java b/src/test/java/org/opensearch/knn/plugin/transport/TrainingModelResponseTests.java index 2fef2635a..3459a11c3 100644 --- a/src/test/java/org/opensearch/knn/plugin/transport/TrainingModelResponseTests.java +++ b/src/test/java/org/opensearch/knn/plugin/transport/TrainingModelResponseTests.java @@ -13,8 +13,8 @@ import com.google.common.collect.Maps; import org.opensearch.common.io.stream.BytesStreamOutput; -import org.opensearch.common.xcontent.ToXContent; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.knn.KNNTestCase; import org.opensearch.knn.common.KNNConstants; diff --git a/src/test/java/org/opensearch/knn/plugin/transport/TrainingModelTransportActionTests.java b/src/test/java/org/opensearch/knn/plugin/transport/TrainingModelTransportActionTests.java index 90f26aa59..345f4feb5 100644 --- a/src/test/java/org/opensearch/knn/plugin/transport/TrainingModelTransportActionTests.java +++ b/src/test/java/org/opensearch/knn/plugin/transport/TrainingModelTransportActionTests.java @@ -11,13 +11,16 @@ package org.opensearch.knn.plugin.transport; -import org.opensearch.action.ActionListener; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.knn.KNNSingleNodeTestCase; -import org.opensearch.knn.index.KNNMethodContext; +import org.opensearch.knn.index.engine.KNNMethodContext; import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; import org.opensearch.knn.indices.ModelDao; import java.io.IOException; @@ -72,9 +75,12 @@ public void testDoExecute() throws InterruptedException, ExecutionException, IOE trainingIndexName, trainingFieldName, null, - "test-detector" + "test-detector", + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED ); - trainingModelRequest.setTrainingDataSizeInKB(estimateVectorSetSizeInKB(trainingDataCount, dimension)); + trainingModelRequest.setTrainingDataSizeInKB(estimateVectorSetSizeInKB(trainingDataCount, dimension, VectorDataType.DEFAULT)); // Create listener that ensures that the initial model put succeeds ActionListener listener = ActionListener.wrap( diff --git a/src/test/java/org/opensearch/knn/plugin/transport/UpdateModelGraveyardRequestTests.java b/src/test/java/org/opensearch/knn/plugin/transport/UpdateModelGraveyardRequestTests.java new file mode 100644 index 000000000..7c38adc36 --- /dev/null +++ b/src/test/java/org/opensearch/knn/plugin/transport/UpdateModelGraveyardRequestTests.java @@ -0,0 +1,58 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.plugin.transport; + +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.knn.KNNTestCase; +import java.io.IOException; + +public class UpdateModelGraveyardRequestTests extends KNNTestCase { + + public void testStreams() throws IOException { + String modelId = "test-model-id"; + boolean isRemoveRequest = false; + + UpdateModelGraveyardRequest updateModelGraveyardRequest = new UpdateModelGraveyardRequest(modelId, isRemoveRequest); + + BytesStreamOutput streamOutput = new BytesStreamOutput(); + updateModelGraveyardRequest.writeTo(streamOutput); + + UpdateModelGraveyardRequest updateModelGraveyardRequest1 = new UpdateModelGraveyardRequest(streamOutput.bytes().streamInput()); + + assertEquals(updateModelGraveyardRequest.getModelId(), updateModelGraveyardRequest1.getModelId()); + assertEquals(updateModelGraveyardRequest.isRemoveRequest(), updateModelGraveyardRequest1.isRemoveRequest()); + } + + public void testValidate() { + String modelId = "test-model-id"; + UpdateModelGraveyardRequest updateModelGraveyardRequest1 = new UpdateModelGraveyardRequest(modelId, false); + assertNull(updateModelGraveyardRequest1.validate()); + + UpdateModelGraveyardRequest updateModelGraveyardRequest2 = new UpdateModelGraveyardRequest(modelId, true); + assertNull(updateModelGraveyardRequest2.validate()); + + UpdateModelGraveyardRequest updateModelGraveyardRequest3 = new UpdateModelGraveyardRequest("", false); + assertNotNull(updateModelGraveyardRequest3.validate()); + + UpdateModelGraveyardRequest updateModelGraveyardRequest4 = new UpdateModelGraveyardRequest("", true); + assertNotNull(updateModelGraveyardRequest4.validate()); + } + + public void testGetModelId() { + String modelId = "test-model-id"; + UpdateModelGraveyardRequest updateModelGraveyardRequest = new UpdateModelGraveyardRequest(modelId, false); + + assertEquals(modelId, updateModelGraveyardRequest.getModelId()); + } + + public void testIsRemoveRequest() { + String modelId = "test-model-id"; + boolean isRemoveRequest = false; + UpdateModelGraveyardRequest updateModelGraveyardRequest = new UpdateModelGraveyardRequest(modelId, isRemoveRequest); + + assertEquals(isRemoveRequest, updateModelGraveyardRequest.isRemoveRequest()); + } +} diff --git a/src/test/java/org/opensearch/knn/plugin/transport/UpdateModelGraveyardTransportActionTests.java b/src/test/java/org/opensearch/knn/plugin/transport/UpdateModelGraveyardTransportActionTests.java new file mode 100644 index 000000000..cac5c1b9c --- /dev/null +++ b/src/test/java/org/opensearch/knn/plugin/transport/UpdateModelGraveyardTransportActionTests.java @@ -0,0 +1,396 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.plugin.transport; + +import org.opensearch.Version; +import org.opensearch.action.admin.indices.create.CreateIndexRequestBuilder; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.action.ActionListener; +import org.opensearch.action.support.master.AcknowledgedResponse; +import org.opensearch.cluster.ClusterState; +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.knn.KNNSingleNodeTestCase; +import org.opensearch.knn.TestUtils; +import org.opensearch.knn.common.exception.DeleteModelException; +import org.opensearch.knn.index.engine.MethodComponentContext; +import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; +import org.opensearch.knn.indices.Model; +import org.opensearch.knn.indices.ModelGraveyard; +import org.opensearch.knn.indices.ModelMetadata; +import org.opensearch.knn.indices.ModelState; +import org.opensearch.threadpool.ThreadPool; + +import java.io.IOException; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import static org.opensearch.knn.common.KNNConstants.DIMENSION; +import static org.opensearch.knn.common.KNNConstants.MODEL_ID; +import static org.opensearch.knn.common.KNNConstants.MODEL_INDEX_NAME; +import static org.opensearch.knn.common.KNNConstants.PROPERTIES; +import static org.opensearch.knn.common.KNNConstants.TYPE; +import static org.opensearch.knn.common.KNNConstants.TYPE_KNN_VECTOR; + +public class UpdateModelGraveyardTransportActionTests extends KNNSingleNodeTestCase { + + public void testExecutor() { + UpdateModelGraveyardTransportAction updateModelGraveyardTransportAction = node().injector() + .getInstance(UpdateModelGraveyardTransportAction.class); + assertEquals(ThreadPool.Names.SAME, updateModelGraveyardTransportAction.executor()); + } + + public void testRead() throws IOException { + UpdateModelGraveyardTransportAction updateModelGraveyardTransportAction = node().injector() + .getInstance(UpdateModelGraveyardTransportAction.class); + AcknowledgedResponse acknowledgedResponse = new AcknowledgedResponse(true); + BytesStreamOutput streamOutput = new BytesStreamOutput(); + acknowledgedResponse.writeTo(streamOutput); + AcknowledgedResponse acknowledgedResponse1 = updateModelGraveyardTransportAction.read(streamOutput.bytes().streamInput()); + + assertEquals(acknowledgedResponse, acknowledgedResponse1); + } + + public void testClusterManagerOperation() throws InterruptedException { + + String modelId = "test-model-id"; + + // Get update transport action + UpdateModelGraveyardTransportAction updateModelGraveyardTransportAction = node().injector() + .getInstance(UpdateModelGraveyardTransportAction.class); + + // Generate update request to add modelId to model graveyard + UpdateModelGraveyardRequest addModelGraveyardRequest = new UpdateModelGraveyardRequest(modelId, false); + + // Get cluster state, update metadata, check cluster state - all asynchronously + final CountDownLatch inProgressLatch1 = new CountDownLatch(1); + client().admin().cluster().prepareState().execute(ActionListener.wrap(stateResponse1 -> { + ClusterState clusterState1 = stateResponse1.getState(); + updateModelGraveyardTransportAction.clusterManagerOperation( + addModelGraveyardRequest, + clusterState1, + ActionListener.wrap(acknowledgedResponse -> { + assertTrue(acknowledgedResponse.isAcknowledged()); + + client().admin().cluster().prepareState().execute(ActionListener.wrap(stateResponse2 -> { + ClusterState updatedClusterState = stateResponse2.getState(); + ModelGraveyard modelGraveyard = updatedClusterState.metadata().custom(ModelGraveyard.TYPE); + + assertNotNull(modelGraveyard); + assertEquals(1, modelGraveyard.size()); + assertTrue(modelGraveyard.contains(modelId)); + + inProgressLatch1.countDown(); + + }, e -> fail("Update failed:" + e))); + }, e -> fail("Update failed: " + e)) + ); + }, e -> fail("Update failed: " + e))); + + assertTrue(inProgressLatch1.await(60, TimeUnit.SECONDS)); + + String modelId1 = "test-model-id-1"; + // Generate update request to add modelId1 to model graveyard + UpdateModelGraveyardRequest addModelGraveyardRequest1 = new UpdateModelGraveyardRequest(modelId1, false); + + final CountDownLatch inProgressLatch2 = new CountDownLatch(1); + client().admin().cluster().prepareState().execute(ActionListener.wrap(stateResponse1 -> { + ClusterState clusterState1 = stateResponse1.getState(); + updateModelGraveyardTransportAction.clusterManagerOperation( + addModelGraveyardRequest1, + clusterState1, + ActionListener.wrap(acknowledgedResponse -> { + assertTrue(acknowledgedResponse.isAcknowledged()); + + client().admin().cluster().prepareState().execute(ActionListener.wrap(stateResponse2 -> { + ClusterState updatedClusterState = stateResponse2.getState(); + ModelGraveyard modelGraveyard = updatedClusterState.metadata().custom(ModelGraveyard.TYPE); + + assertNotNull(modelGraveyard); + assertEquals(2, modelGraveyard.size()); + assertTrue(modelGraveyard.contains(modelId1)); + + ModelGraveyard modelGraveyardPrev = clusterState1.metadata().custom(ModelGraveyard.TYPE); + assertFalse(modelGraveyardPrev.contains(modelId1)); + + // Assertions to validate ModelGraveyard Diff + ModelGraveyard.ModelGraveyardDiff diff = new ModelGraveyard.ModelGraveyardDiff(modelGraveyardPrev, modelGraveyard); + assertEquals(0, diff.getRemoved().size()); + assertEquals(1, diff.getAdded().size()); + assertTrue(diff.getAdded().contains(modelId1)); + + ModelGraveyard updatedModelGraveyard = diff.apply(modelGraveyardPrev); + assertEquals(2, updatedModelGraveyard.size()); + assertTrue(updatedModelGraveyard.contains(modelId)); + assertTrue(updatedModelGraveyard.contains(modelId1)); + + inProgressLatch2.countDown(); + }, e -> fail("Update failed"))); + }, e -> fail("Update failed")) + ); + }, e -> fail("Update failed"))); + + assertTrue(inProgressLatch2.await(60, TimeUnit.SECONDS)); + + // Generate remove request to remove the modelId from model graveyard + UpdateModelGraveyardRequest removeModelGraveyardRequest = new UpdateModelGraveyardRequest(modelId, true); + + final CountDownLatch inProgressLatch3 = new CountDownLatch(1); + client().admin().cluster().prepareState().execute(ActionListener.wrap(stateResponse1 -> { + ClusterState clusterState1 = stateResponse1.getState(); + updateModelGraveyardTransportAction.clusterManagerOperation( + removeModelGraveyardRequest, + clusterState1, + ActionListener.wrap(acknowledgedResponse -> { + assertTrue(acknowledgedResponse.isAcknowledged()); + + client().admin().cluster().prepareState().execute(ActionListener.wrap(stateResponse2 -> { + ClusterState updatedClusterState = stateResponse2.getState(); + ModelGraveyard modelGraveyard = updatedClusterState.metadata().custom(ModelGraveyard.TYPE); + + assertNotNull(modelGraveyard); + assertEquals(1, modelGraveyard.size()); + assertFalse(modelGraveyard.contains(modelId)); + + ModelGraveyard modelGraveyardPrev = clusterState1.metadata().custom(ModelGraveyard.TYPE); + assertTrue(modelGraveyardPrev.contains(modelId)); + + // Assertions to validate ModelGraveyard Diff + ModelGraveyard.ModelGraveyardDiff diff = new ModelGraveyard.ModelGraveyardDiff(modelGraveyardPrev, modelGraveyard); + assertEquals(1, diff.getRemoved().size()); + assertEquals(0, diff.getAdded().size()); + assertTrue(diff.getRemoved().contains(modelId)); + + ModelGraveyard updatedModelGraveyard = diff.apply(modelGraveyardPrev); + assertEquals(1, updatedModelGraveyard.size()); + assertFalse(updatedModelGraveyard.contains(modelId)); + assertTrue(updatedModelGraveyard.contains(modelId1)); + + inProgressLatch3.countDown(); + }, e -> fail("Update failed"))); + }, e -> fail("Update failed")) + ); + }, e -> fail("Update failed"))); + + assertTrue(inProgressLatch3.await(60, TimeUnit.SECONDS)); + } + + public void testCheckBlock() { + UpdateModelGraveyardTransportAction updateModelGraveyardTransportAction = node().injector() + .getInstance(UpdateModelGraveyardTransportAction.class); + assertNull(updateModelGraveyardTransportAction.checkBlock(null, null)); + } + + public void testClusterManagerOperation_GetIndicesUsingModel() throws IOException, ExecutionException, InterruptedException { + // Get update transport action + UpdateModelGraveyardTransportAction updateModelGraveyardTransportAction = node().injector() + .getInstance(UpdateModelGraveyardTransportAction.class); + + String modelId = "test-model-id"; + byte[] modelBlob = "testModel".getBytes(); + int dimension = 2; + + createIndex(MODEL_INDEX_NAME); + + Model model = new Model( + new ModelMetadata( + KNNEngine.DEFAULT, + SpaceType.DEFAULT, + dimension, + ModelState.CREATED, + ZonedDateTime.now(ZoneOffset.UTC).toString(), + "", + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT + ), + modelBlob, + modelId + ); + + // created model and added it to index + addModel(model); + + // Create basic index (not using k-NN) + String testIndex1 = "test-index1"; + createIndex(testIndex1); + + // Attempt to add model id to graveyard with one non-knn index present, should succeed + UpdateModelGraveyardRequest addModelGraveyardRequest = new UpdateModelGraveyardRequest(modelId, false); + updateModelGraveyardAndAssertNoError(updateModelGraveyardTransportAction, addModelGraveyardRequest); + + // Remove model from graveyard to prepare for next check + UpdateModelGraveyardRequest removeModelGraveyardRequest = new UpdateModelGraveyardRequest(modelId, true); + updateModelGraveyardAndAssertNoError(updateModelGraveyardTransportAction, removeModelGraveyardRequest); + + // Create k-NN index not using the model + String testIndex2 = "test-index2"; + createKNNIndex(testIndex2); + + // Attempt to add model id to graveyard with one non-knn index and one k-nn index not using model present, should succeed + updateModelGraveyardAndAssertNoError(updateModelGraveyardTransportAction, addModelGraveyardRequest); + + // Remove model from graveyard to prepare for next check + updateModelGraveyardAndAssertNoError(updateModelGraveyardTransportAction, removeModelGraveyardRequest); + + // Create k-NN index using model + String testIndex3 = "test-index3"; + String testField3 = "test-field3"; + + /* + Constructs the following json: + { + "properties": { + "test-field3": { + "type": "knn_vector", + "model_id": "test-model-id" + } + } + } + */ + XContentBuilder mappings3 = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES) + .startObject(testField3) + .field(TYPE, TYPE_KNN_VECTOR) + .field(MODEL_ID, modelId) + .endObject() + .endObject() + .endObject(); + + XContentBuilder settings = XContentFactory.jsonBuilder().startObject().field(TestUtils.INDEX_KNN, "true").endObject(); + + CreateIndexRequestBuilder createIndexRequestBuilder3 = client().admin() + .indices() + .prepareCreate(testIndex3) + .setMapping(mappings3) + .setSettings(settings); + createIndex(testIndex3, createIndexRequestBuilder3); + + // Attempt to add model id to graveyard when one index is using model, should fail + List indicesUsingModel = new ArrayList<>(); + indicesUsingModel.add(testIndex3); + updateModelGraveyardAndAssertDeleteModelException( + updateModelGraveyardTransportAction, + addModelGraveyardRequest, + indicesUsingModel.toString() + ); + + // Create second k-NN index using model + String testIndex4 = "test-index4"; + String testField4 = "test-field4"; + String standardField = "standard-field"; + + /* + Constructs the following json: + { + "properties": { + "standard-field": { + "type": "knn_vector", + "dimension": "2" + } + "test-field4": { + "type": "knn_vector", + "model_id": "test-model-id" + } + } + } + */ + XContentBuilder mappings4 = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES) + .startObject(standardField) + .field(TYPE, TYPE_KNN_VECTOR) + .field(DIMENSION, dimension) + .endObject() + .startObject(testField4) + .field(TYPE, TYPE_KNN_VECTOR) + .field(MODEL_ID, modelId) + .endObject() + .endObject() + .endObject(); + + CreateIndexRequestBuilder createIndexRequestBuilder4 = client().admin() + .indices() + .prepareCreate(testIndex4) + .setMapping(mappings4) + .setSettings(settings); + createIndex(testIndex4, createIndexRequestBuilder4); + + // Add index at beginning to match order of list returned by getIndicesUsingModel() + indicesUsingModel.add(0, testIndex4); + + // Attempt to add model id to graveyard when one index is using model, should fail + updateModelGraveyardAndAssertDeleteModelException( + updateModelGraveyardTransportAction, + addModelGraveyardRequest, + indicesUsingModel.toString() + ); + } + + private void updateModelGraveyardAndAssertNoError( + UpdateModelGraveyardTransportAction updateModelGraveyardTransportAction, + UpdateModelGraveyardRequest updateModelGraveyardRequest + ) throws InterruptedException { + final CountDownLatch countDownLatch = new CountDownLatch(1); + client().admin().cluster().prepareState().execute(ActionListener.wrap(stateResponse1 -> { + ClusterState clusterState1 = stateResponse1.getState(); + updateModelGraveyardTransportAction.clusterManagerOperation( + updateModelGraveyardRequest, + clusterState1, + ActionListener.wrap(acknowledgedResponse -> { + assertTrue(acknowledgedResponse.isAcknowledged()); + countDownLatch.countDown(); + }, e -> { fail("Update failed: " + e); }) + ); + }, e -> fail("Update failed: " + e))); + assertTrue(countDownLatch.await(60, TimeUnit.SECONDS)); + } + + private void updateModelGraveyardAndAssertDeleteModelException( + UpdateModelGraveyardTransportAction updateModelGraveyardTransportAction, + UpdateModelGraveyardRequest updateModelGraveyardRequest, + String indicesPresentInException + ) throws InterruptedException { + final CountDownLatch countDownLatch = new CountDownLatch(1); + client().admin().cluster().prepareState().execute(ActionListener.wrap(stateResponse1 -> { + ClusterState clusterState1 = stateResponse1.getState(); + updateModelGraveyardTransportAction.clusterManagerOperation( + updateModelGraveyardRequest, + clusterState1, + ActionListener.wrap(acknowledgedResponse -> { + fail(); + }, e -> { + assertTrue(e instanceof DeleteModelException); + assertEquals( + String.format( + "Cannot delete model [%s]. Model is in use by the following indices %s, which must be deleted first.", + updateModelGraveyardRequest.getModelId(), + indicesPresentInException + ), + e.getMessage() + ); + countDownLatch.countDown(); + }) + ); + }, e -> fail("Update failed: " + e))); + + assertTrue(countDownLatch.await(60, TimeUnit.SECONDS)); + } +} diff --git a/src/test/java/org/opensearch/knn/plugin/transport/UpdateModelMetadataRequestTests.java b/src/test/java/org/opensearch/knn/plugin/transport/UpdateModelMetadataRequestTests.java index f38273f15..577762a42 100644 --- a/src/test/java/org/opensearch/knn/plugin/transport/UpdateModelMetadataRequestTests.java +++ b/src/test/java/org/opensearch/knn/plugin/transport/UpdateModelMetadataRequestTests.java @@ -11,10 +11,15 @@ package org.opensearch.knn.plugin.transport; +import org.opensearch.Version; import org.opensearch.common.io.stream.BytesStreamOutput; import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; import org.opensearch.knn.indices.ModelMetadata; import org.opensearch.knn.indices.ModelState; @@ -39,7 +44,13 @@ public void testStreams() throws IOException { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ); UpdateModelMetadataRequest updateModelMetadataRequest = new UpdateModelMetadataRequest(modelId, isRemoveRequest, modelMetadata); @@ -62,7 +73,13 @@ public void testValidate() { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ); UpdateModelMetadataRequest updateModelMetadataRequest1 = new UpdateModelMetadataRequest("test", true, null); @@ -100,7 +117,13 @@ public void testGetModelMetadata() { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ); UpdateModelMetadataRequest updateModelMetadataRequest = new UpdateModelMetadataRequest("test", true, modelMetadata); diff --git a/src/test/java/org/opensearch/knn/plugin/transport/UpdateModelMetadataTransportActionTests.java b/src/test/java/org/opensearch/knn/plugin/transport/UpdateModelMetadataTransportActionTests.java index a56c4d2bd..48b93653f 100644 --- a/src/test/java/org/opensearch/knn/plugin/transport/UpdateModelMetadataTransportActionTests.java +++ b/src/test/java/org/opensearch/knn/plugin/transport/UpdateModelMetadataTransportActionTests.java @@ -11,14 +11,19 @@ package org.opensearch.knn.plugin.transport; -import org.opensearch.action.ActionListener; +import org.opensearch.Version; +import org.opensearch.core.action.ActionListener; import org.opensearch.action.support.master.AcknowledgedResponse; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.common.io.stream.BytesStreamOutput; import org.opensearch.knn.KNNSingleNodeTestCase; +import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; import org.opensearch.knn.indices.ModelMetadata; import org.opensearch.knn.indices.ModelState; import org.opensearch.threadpool.ThreadPool; @@ -65,7 +70,13 @@ public void testClusterManagerOperation() throws InterruptedException { ModelState.CREATED, ZonedDateTime.now(ZoneOffset.UTC).toString(), "", - "" + "", + "", + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ); // Get update transport action @@ -79,7 +90,7 @@ public void testClusterManagerOperation() throws InterruptedException { final CountDownLatch inProgressLatch1 = new CountDownLatch(1); client().admin().cluster().prepareState().execute(ActionListener.wrap(stateResponse1 -> { ClusterState clusterState1 = stateResponse1.getState(); - updateModelMetadataTransportAction.masterOperation( + updateModelMetadataTransportAction.clusterManagerOperation( updateModelMetadataRequest, clusterState1, ActionListener.wrap(acknowledgedResponse -> { @@ -114,7 +125,7 @@ public void testClusterManagerOperation() throws InterruptedException { final CountDownLatch inProgressLatch2 = new CountDownLatch(1); client().admin().cluster().prepareState().execute(ActionListener.wrap(stateResponse1 -> { ClusterState clusterState1 = stateResponse1.getState(); - updateModelMetadataTransportAction.masterOperation( + updateModelMetadataTransportAction.clusterManagerOperation( removeModelMetadataRequest, clusterState1, ActionListener.wrap(acknowledgedResponse -> { diff --git a/src/test/java/org/opensearch/knn/quantization/enums/ScalarQuantizationTypeTests.java b/src/test/java/org/opensearch/knn/quantization/enums/ScalarQuantizationTypeTests.java new file mode 100644 index 000000000..99621a0e5 --- /dev/null +++ b/src/test/java/org/opensearch/knn/quantization/enums/ScalarQuantizationTypeTests.java @@ -0,0 +1,35 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.enums; + +import org.opensearch.knn.KNNTestCase; + +import java.util.HashSet; +import java.util.Set; + +public class ScalarQuantizationTypeTests extends KNNTestCase { + public void testSQTypesValues() { + ScalarQuantizationType[] expectedValues = { + ScalarQuantizationType.ONE_BIT, + ScalarQuantizationType.TWO_BIT, + ScalarQuantizationType.FOUR_BIT }; + assertArrayEquals(expectedValues, ScalarQuantizationType.values()); + } + + public void testSQTypesValueOf() { + assertEquals(ScalarQuantizationType.ONE_BIT, ScalarQuantizationType.valueOf("ONE_BIT")); + assertEquals(ScalarQuantizationType.TWO_BIT, ScalarQuantizationType.valueOf("TWO_BIT")); + assertEquals(ScalarQuantizationType.FOUR_BIT, ScalarQuantizationType.valueOf("FOUR_BIT")); + } + + public void testUniqueSQTypeValues() { + Set uniqueIds = new HashSet<>(); + for (ScalarQuantizationType type : ScalarQuantizationType.values()) { + boolean added = uniqueIds.add(type.getId()); + assertTrue("Duplicate value found: " + type.getId(), added); + } + } +} diff --git a/src/test/java/org/opensearch/knn/quantization/factory/QuantizerFactoryTests.java b/src/test/java/org/opensearch/knn/quantization/factory/QuantizerFactoryTests.java new file mode 100644 index 000000000..f6974aea2 --- /dev/null +++ b/src/test/java/org/opensearch/knn/quantization/factory/QuantizerFactoryTests.java @@ -0,0 +1,41 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.factory; + +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; +import org.opensearch.knn.quantization.models.quantizationParams.ScalarQuantizationParams; +import org.opensearch.knn.quantization.quantizer.MultiBitScalarQuantizer; +import org.opensearch.knn.quantization.quantizer.OneBitScalarQuantizer; +import org.opensearch.knn.quantization.quantizer.Quantizer; + +public class QuantizerFactoryTests extends KNNTestCase { + + public void test_Lazy_Registration() { + try { + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); + ScalarQuantizationParams paramsTwoBit = new ScalarQuantizationParams(ScalarQuantizationType.TWO_BIT); + ScalarQuantizationParams paramsFourBit = new ScalarQuantizationParams(ScalarQuantizationType.FOUR_BIT); + Quantizer oneBitQuantizer = QuantizerFactory.getQuantizer(params); + Quantizer quantizerTwoBit = QuantizerFactory.getQuantizer(paramsTwoBit); + Quantizer quantizerFourBit = QuantizerFactory.getQuantizer(paramsFourBit); + assertEquals(OneBitScalarQuantizer.class, oneBitQuantizer.getClass()); + assertEquals(MultiBitScalarQuantizer.class, quantizerTwoBit.getClass()); + assertEquals(MultiBitScalarQuantizer.class, quantizerFourBit.getClass()); + } catch (Exception e) { + assertTrue(e.getMessage().contains("already registered")); + } + } + + public void testGetQuantizer_withNullParams() { + try { + QuantizerFactory.getQuantizer(null); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals("Quantization parameters must not be null.", e.getMessage()); + } + } +} diff --git a/src/test/java/org/opensearch/knn/quantization/factory/QuantizerRegistryTests.java b/src/test/java/org/opensearch/knn/quantization/factory/QuantizerRegistryTests.java new file mode 100644 index 000000000..7c974e517 --- /dev/null +++ b/src/test/java/org/opensearch/knn/quantization/factory/QuantizerRegistryTests.java @@ -0,0 +1,86 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.factory; + +import org.junit.BeforeClass; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; +import org.opensearch.knn.quantization.models.quantizationParams.ScalarQuantizationParams; +import org.opensearch.knn.quantization.quantizer.MultiBitScalarQuantizer; +import org.opensearch.knn.quantization.quantizer.OneBitScalarQuantizer; +import org.opensearch.knn.quantization.quantizer.Quantizer; + +public class QuantizerRegistryTests extends KNNTestCase { + + @BeforeClass + public static void setup() { + try { + QuantizerRegistry.register( + ScalarQuantizationParams.generateTypeIdentifier(ScalarQuantizationType.ONE_BIT), + new OneBitScalarQuantizer() + ); + QuantizerRegistry.register( + ScalarQuantizationParams.generateTypeIdentifier(ScalarQuantizationType.TWO_BIT), + new MultiBitScalarQuantizer(2) + ); + QuantizerRegistry.register( + ScalarQuantizationParams.generateTypeIdentifier(ScalarQuantizationType.FOUR_BIT), + new MultiBitScalarQuantizer(4) + ); + } catch (Exception e) { + assertTrue(e.getMessage().contains("already registered")); + } + } + + public void testRegisterAndGetQuantizer() { + // Test for OneBitScalarQuantizer + ScalarQuantizationParams oneBitParams = new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); + Quantizer oneBitQuantizer = QuantizerRegistry.getQuantizer(oneBitParams); + assertEquals(oneBitQuantizer.getClass(), OneBitScalarQuantizer.class); + + // Test for MultiBitScalarQuantizer (2-bit) + ScalarQuantizationParams twoBitParams = new ScalarQuantizationParams(ScalarQuantizationType.TWO_BIT); + Quantizer twoBitQuantizer = QuantizerRegistry.getQuantizer(twoBitParams); + assertEquals(twoBitQuantizer.getClass(), MultiBitScalarQuantizer.class); + + // Test for MultiBitScalarQuantizer (4-bit) + ScalarQuantizationParams fourBitParams = new ScalarQuantizationParams(ScalarQuantizationType.FOUR_BIT); + Quantizer fourBitQuantizer = QuantizerRegistry.getQuantizer(fourBitParams); + assertEquals(fourBitQuantizer.getClass(), MultiBitScalarQuantizer.class); + } + + public void testQuantizerRegistryIsSingleton() { + // Ensure the same instance is returned for the same type identifier + ScalarQuantizationParams oneBitParams = new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); + Quantizer firstOneBitQuantizer = QuantizerRegistry.getQuantizer(oneBitParams); + Quantizer secondOneBitQuantizer = QuantizerRegistry.getQuantizer(oneBitParams); + assertSame(firstOneBitQuantizer, secondOneBitQuantizer); + + // Ensure the same instance is returned for the same type identifier (2-bit) + ScalarQuantizationParams twoBitParams = new ScalarQuantizationParams(ScalarQuantizationType.TWO_BIT); + Quantizer firstTwoBitQuantizer = QuantizerRegistry.getQuantizer(twoBitParams); + Quantizer secondTwoBitQuantizer = QuantizerRegistry.getQuantizer(twoBitParams); + assertSame(firstTwoBitQuantizer, secondTwoBitQuantizer); + + // Ensure the same instance is returned for the same type identifier (4-bit) + ScalarQuantizationParams fourBitParams = new ScalarQuantizationParams(ScalarQuantizationType.FOUR_BIT); + Quantizer firstFourBitQuantizer = QuantizerRegistry.getQuantizer(fourBitParams); + Quantizer secondFourBitQuantizer = QuantizerRegistry.getQuantizer(fourBitParams); + assertSame(firstFourBitQuantizer, secondFourBitQuantizer); + } + + public void testRegisterQuantizerThrowsExceptionWhenAlreadyRegistered() { + ScalarQuantizationParams oneBitParams = new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); + + // Attempt to register the same quantizer again should throw an exception + assertThrows(IllegalArgumentException.class, () -> { + QuantizerRegistry.register( + ScalarQuantizationParams.generateTypeIdentifier(ScalarQuantizationType.ONE_BIT), + new OneBitScalarQuantizer() + ); + }); + } +} diff --git a/src/test/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationStateCacheManagerTests.java b/src/test/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationStateCacheManagerTests.java new file mode 100644 index 000000000..14e55e627 --- /dev/null +++ b/src/test/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationStateCacheManagerTests.java @@ -0,0 +1,98 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.models.quantizationState; + +import lombok.SneakyThrows; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.codec.KNN990Codec.KNN990QuantizationStateReader; + +import static org.mockito.Mockito.times; + +public class QuantizationStateCacheManagerTests extends KNNTestCase { + + @SneakyThrows + public void testRebuildCache() { + try (MockedStatic mockedStaticCache = Mockito.mockStatic(QuantizationStateCache.class)) { + QuantizationStateCache quantizationStateCache = Mockito.mock(QuantizationStateCache.class); + mockedStaticCache.when(QuantizationStateCache::getInstance).thenReturn(quantizationStateCache); + Mockito.doNothing().when(quantizationStateCache).rebuildCache(); + QuantizationStateCacheManager.getInstance().rebuildCache(); + Mockito.verify(quantizationStateCache, times(1)).rebuildCache(); + } + } + + @SneakyThrows + public void testGetQuantizationState() { + try (MockedStatic mockedStaticCache = Mockito.mockStatic(QuantizationStateCache.class)) { + QuantizationStateReadConfig quantizationStateReadConfig = Mockito.mock(QuantizationStateReadConfig.class); + String cacheKey = "test-key"; + Mockito.when(quantizationStateReadConfig.getCacheKey()).thenReturn(cacheKey); + QuantizationState quantizationState = Mockito.mock(QuantizationState.class); + QuantizationStateCache quantizationStateCache = Mockito.mock(QuantizationStateCache.class); + mockedStaticCache.when(QuantizationStateCache::getInstance).thenReturn(quantizationStateCache); + Mockito.doNothing().when(quantizationStateCache).addQuantizationState(cacheKey, quantizationState); + try (MockedStatic mockedStaticReader = Mockito.mockStatic(KNN990QuantizationStateReader.class)) { + mockedStaticReader.when(() -> KNN990QuantizationStateReader.read(quantizationStateReadConfig)) + .thenReturn(quantizationState); + QuantizationStateCacheManager.getInstance().getQuantizationState(quantizationStateReadConfig); + Mockito.verify(quantizationStateCache, times(1)).addQuantizationState(cacheKey, quantizationState); + } + Mockito.when(quantizationStateCache.getQuantizationState(cacheKey)).thenReturn(quantizationState); + QuantizationStateCacheManager.getInstance().getQuantizationState(quantizationStateReadConfig); + Mockito.verify(quantizationStateCache, times(1)).addQuantizationState(cacheKey, quantizationState); + } + } + + @SneakyThrows + public void testEvict() { + try (MockedStatic mockedStaticCache = Mockito.mockStatic(QuantizationStateCache.class)) { + String field = "test-field"; + QuantizationStateCache quantizationStateCache = Mockito.mock(QuantizationStateCache.class); + mockedStaticCache.when(QuantizationStateCache::getInstance).thenReturn(quantizationStateCache); + Mockito.doNothing().when(quantizationStateCache).evict(field); + QuantizationStateCacheManager.getInstance().evict(field); + Mockito.verify(quantizationStateCache, times(1)).evict(field); + } + } + + @SneakyThrows + public void testAddQuantizationState() { + try (MockedStatic mockedStaticCache = Mockito.mockStatic(QuantizationStateCache.class)) { + String field = "test-field"; + QuantizationState quantizationState = Mockito.mock(QuantizationState.class); + QuantizationStateCache quantizationStateCache = Mockito.mock(QuantizationStateCache.class); + mockedStaticCache.when(QuantizationStateCache::getInstance).thenReturn(quantizationStateCache); + Mockito.doNothing().when(quantizationStateCache).addQuantizationState(field, quantizationState); + QuantizationStateCacheManager.getInstance().addQuantizationState(field, quantizationState); + Mockito.verify(quantizationStateCache, times(1)).addQuantizationState(field, quantizationState); + } + } + + @SneakyThrows + public void testSetMaxCacheSizeInKB() { + try (MockedStatic mockedStaticCache = Mockito.mockStatic(QuantizationStateCache.class)) { + long maxCacheSizeInKB = 1024; + QuantizationStateCache quantizationStateCache = Mockito.mock(QuantizationStateCache.class); + mockedStaticCache.when(QuantizationStateCache::getInstance).thenReturn(quantizationStateCache); + Mockito.doNothing().when(quantizationStateCache).setMaxCacheSizeInKB(maxCacheSizeInKB); + QuantizationStateCacheManager.getInstance().setMaxCacheSizeInKB(1024); + Mockito.verify(quantizationStateCache, times(1)).setMaxCacheSizeInKB(1024); + } + } + + @SneakyThrows + public void testClear() { + try (MockedStatic mockedStaticCache = Mockito.mockStatic(QuantizationStateCache.class)) { + QuantizationStateCache quantizationStateCache = Mockito.mock(QuantizationStateCache.class); + mockedStaticCache.when(QuantizationStateCache::getInstance).thenReturn(quantizationStateCache); + Mockito.doNothing().when(quantizationStateCache).clear(); + QuantizationStateCacheManager.getInstance().clear(); + Mockito.verify(quantizationStateCache, times(1)).clear(); + } + } +} diff --git a/src/test/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationStateCacheTests.java b/src/test/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationStateCacheTests.java new file mode 100644 index 000000000..e5381aec7 --- /dev/null +++ b/src/test/java/org/opensearch/knn/quantization/models/quantizationState/QuantizationStateCacheTests.java @@ -0,0 +1,450 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.models.quantizationState; + +import com.google.common.collect.ImmutableSet; +import lombok.SneakyThrows; +import org.opensearch.client.Client; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.settings.ClusterSettings; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.unit.TimeValue; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.KNNSettings; +import org.opensearch.knn.quantization.models.quantizationParams.ScalarQuantizationParams; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.opensearch.knn.index.KNNSettings.QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING; +import static org.opensearch.knn.index.KNNSettings.QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING; +import static org.opensearch.knn.quantization.enums.ScalarQuantizationType.ONE_BIT; + +public class QuantizationStateCacheTests extends KNNTestCase { + + @SneakyThrows + public void testSingleThreadedAddAndRetrieve() { + String fieldName = "singleThreadField"; + QuantizationState state = new OneBitScalarQuantizationState( + new ScalarQuantizationParams(ONE_BIT), + new float[] { 1.2f, 2.3f, 3.4f } + ); + + String cacheSize = "10%"; + TimeValue expiry = TimeValue.timeValueMinutes(30); + + Settings settings = Settings.builder() + .put(QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING.getKey(), cacheSize) + .put(QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING.getKey(), expiry) + .build(); + ClusterSettings clusterSettings = new ClusterSettings( + settings, + ImmutableSet.of(QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING, QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING) + ); + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.getClusterSettings()).thenReturn(clusterSettings); + when(clusterService.getSettings()).thenReturn(settings); + + QuantizationStateCache cache = QuantizationStateCache.getInstance(); + clusterService.getClusterSettings().applySettings(settings); + + // Add state + cache.addQuantizationState(fieldName, state); + + QuantizationState retrievedState = cache.getQuantizationState(fieldName); + assertNotNull("State should be retrieved successfully", retrievedState); + assertSame("Retrieved state should be the same instance as the one added", state, retrievedState); + } + + @SneakyThrows + public void testMultiThreadedAddAndRetrieve() { + int threadCount = 10; + ExecutorService executorService = Executors.newFixedThreadPool(threadCount); + CountDownLatch latch = new CountDownLatch(threadCount); + String fieldName = "multiThreadField"; + QuantizationState state = new OneBitScalarQuantizationState( + new ScalarQuantizationParams(ONE_BIT), + new float[] { 1.2f, 2.3f, 3.4f } + ); + String cacheSize = "10%"; + TimeValue expiry = TimeValue.timeValueMinutes(30); + + Settings settings = Settings.builder() + .put(QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING.getKey(), cacheSize) + .put(QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING.getKey(), expiry) + .build(); + ClusterSettings clusterSettings = new ClusterSettings( + settings, + ImmutableSet.of(QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING, QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING) + ); + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.getClusterSettings()).thenReturn(clusterSettings); + when(clusterService.getSettings()).thenReturn(settings); + + QuantizationStateCache cache = QuantizationStateCache.getInstance(); + clusterService.getClusterSettings().applySettings(settings); + + // Add state from multiple threads + for (int i = 0; i < threadCount; i++) { + executorService.submit(() -> { + try { + cache.addQuantizationState(fieldName, state); + } finally { + latch.countDown(); + } + }); + } + + // Wait for all threads to finish + latch.await(); + executorService.shutdown(); + + QuantizationState retrievedState = cache.getQuantizationState(fieldName); + assertNotNull("State should be retrieved successfully", retrievedState); + assertSame("Retrieved state should be the same instance as the one added", state, retrievedState); + } + + @SneakyThrows + public void testMultiThreadedEvict() { + int threadCount = 10; + ExecutorService executorService = Executors.newFixedThreadPool(threadCount); + CountDownLatch latch = new CountDownLatch(threadCount); + String fieldName = "multiThreadEvictField"; + QuantizationState state = new OneBitScalarQuantizationState( + new ScalarQuantizationParams(ONE_BIT), + new float[] { 1.2f, 2.3f, 3.4f } + ); + String cacheSize = "10%"; + TimeValue expiry = TimeValue.timeValueMinutes(30); + + Settings settings = Settings.builder() + .put(QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING.getKey(), cacheSize) + .put(QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING.getKey(), expiry) + .build(); + ClusterSettings clusterSettings = new ClusterSettings( + settings, + ImmutableSet.of(QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING, QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING) + ); + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.getClusterSettings()).thenReturn(clusterSettings); + when(clusterService.getSettings()).thenReturn(settings); + + QuantizationStateCache cache = QuantizationStateCache.getInstance(); + + clusterService.getClusterSettings().applySettings(settings); + + cache.addQuantizationState(fieldName, state); + + // Evict state from multiple threads + for (int i = 0; i < threadCount; i++) { + executorService.submit(() -> { + try { + cache.evict(fieldName); + } finally { + latch.countDown(); + } + }); + } + + // Wait for all threads to finish + latch.await(); + executorService.shutdown(); + + QuantizationState retrievedState = cache.getQuantizationState(fieldName); + assertNull("State should be null", retrievedState); + } + + @SneakyThrows + public void testConcurrentAddAndEvict() { + int threadCount = 10; + ExecutorService executorService = Executors.newFixedThreadPool(threadCount); + CountDownLatch latch = new CountDownLatch(threadCount); + String fieldName = "concurrentAddEvictField"; + QuantizationState state = new OneBitScalarQuantizationState( + new ScalarQuantizationParams(ONE_BIT), + new float[] { 1.2f, 2.3f, 3.4f } + ); + String cacheSize = "10%"; + TimeValue expiry = TimeValue.timeValueMinutes(30); + + Settings settings = Settings.builder() + .put(QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING.getKey(), cacheSize) + .put(QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING.getKey(), expiry) + .build(); + ClusterSettings clusterSettings = new ClusterSettings( + settings, + ImmutableSet.of(QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING, QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING) + ); + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.getClusterSettings()).thenReturn(clusterSettings); + when(clusterService.getSettings()).thenReturn(settings); + + QuantizationStateCache cache = QuantizationStateCache.getInstance(); + clusterService.getClusterSettings().applySettings(settings); + + // Concurrently add and evict state from multiple threads + for (int i = 0; i < threadCount; i++) { + if (i % 2 == 0) { + executorService.submit(() -> { + try { + cache.addQuantizationState(fieldName, state); + } finally { + latch.countDown(); + } + }); + } else { + executorService.submit(() -> { + try { + cache.evict(fieldName); + } finally { + latch.countDown(); + } + }); + } + + } + + // Wait for all threads to finish + latch.await(); + executorService.shutdown(); + + // Since operations are concurrent, we can't be sure of the final state, but we can assert that the cache handles it gracefully + QuantizationState retrievedState = cache.getQuantizationState(fieldName); + assertTrue("Final state should be either null or the added state", retrievedState == null || retrievedState == state); + } + + @SneakyThrows + public void testMultipleThreadedCacheClear() { + int threadCount = 10; + ExecutorService executorService = Executors.newFixedThreadPool(threadCount); + CountDownLatch latch = new CountDownLatch(threadCount); + String fieldName = "multiThreadField"; + QuantizationState state = new OneBitScalarQuantizationState( + new ScalarQuantizationParams(ONE_BIT), + new float[] { 1.2f, 2.3f, 3.4f } + ); + String cacheSize = "10%"; + TimeValue expiry = TimeValue.timeValueMinutes(30); + + Settings settings = Settings.builder() + .put(QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING.getKey(), cacheSize) + .put(QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING.getKey(), expiry) + .build(); + ClusterSettings clusterSettings = new ClusterSettings( + settings, + ImmutableSet.of(QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING, QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING) + ); + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.getClusterSettings()).thenReturn(clusterSettings); + when(clusterService.getSettings()).thenReturn(settings); + + QuantizationStateCache cache = QuantizationStateCache.getInstance(); + clusterService.getClusterSettings().applySettings(settings); + cache.addQuantizationState(fieldName, state); + + // Clear cache from multiple threads + for (int i = 0; i < threadCount; i++) { + executorService.submit(() -> { + try { + cache.clear(); + } finally { + latch.countDown(); + } + }); + } + + // Wait for all threads to finish + latch.await(); + executorService.shutdown(); + + QuantizationState retrievedState = cache.getQuantizationState(fieldName); + assertNull("State should be null", retrievedState); + } + + @SneakyThrows + public void testRebuild() { + int threadCount = 10; + ExecutorService executorService = Executors.newFixedThreadPool(threadCount); + CountDownLatch latch = new CountDownLatch(threadCount); + String fieldName = "rebuildField"; + QuantizationState state = new OneBitScalarQuantizationState( + new ScalarQuantizationParams(ONE_BIT), + new float[] { 1.2f, 2.3f, 3.4f } + ); + String cacheSize = "10%"; + TimeValue expiry = TimeValue.timeValueMinutes(30); + + Settings settings = Settings.builder() + .put(QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING.getKey(), cacheSize) + .put(QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING.getKey(), expiry) + .build(); + ClusterSettings clusterSettings = new ClusterSettings( + settings, + ImmutableSet.of(QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING, QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING) + ); + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.getClusterSettings()).thenReturn(clusterSettings); + when(clusterService.getSettings()).thenReturn(settings); + + QuantizationStateCache cache = QuantizationStateCache.getInstance(); + cache.addQuantizationState(fieldName, state); + + // Rebuild cache from multiple threads + for (int i = 0; i < threadCount; i++) { + executorService.submit(() -> { + try { + cache.rebuildCache(); + } finally { + latch.countDown(); + } + }); + } + + // Wait for all threads to finish + latch.await(); + executorService.shutdown(); + + QuantizationState retrievedState = cache.getQuantizationState(fieldName); + assertNull("State should be null", retrievedState); + } + + @SneakyThrows + public void testRebuildOnCacheSizeSettingsChange() { + int threadCount = 10; + ExecutorService executorService = Executors.newFixedThreadPool(threadCount); + CountDownLatch latch = new CountDownLatch(threadCount); + String fieldName = "rebuildField"; + QuantizationState state = new OneBitScalarQuantizationState( + new ScalarQuantizationParams(ONE_BIT), + new float[] { 1.2f, 2.3f, 3.4f } + ); + + Settings settings = Settings.builder().build(); + ClusterSettings clusterSettings = new ClusterSettings( + settings, + ImmutableSet.of(QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING, QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING) + ); + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.getClusterSettings()).thenReturn(clusterSettings); + when(clusterService.getSettings()).thenReturn(settings); + + Client client = mock(Client.class); + + KNNSettings.state().initialize(client, clusterService); + + QuantizationStateCache cache = QuantizationStateCache.getInstance(); + cache.rebuildCache(); + long maxCacheSizeInKB = cache.getMaxCacheSizeInKB(); + cache.addQuantizationState(fieldName, state); + + String newCacheSize = "10%"; + + Settings newSettings = Settings.builder().put(QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING.getKey(), newCacheSize).build(); + + // Rebuild cache from multiple threads + for (int i = 0; i < threadCount; i++) { + executorService.submit(() -> { + try { + clusterService.getClusterSettings().applySettings(newSettings); + } finally { + latch.countDown(); + } + }); + } + + // Wait for all threads to finish + latch.await(); + executorService.shutdown(); + + QuantizationState retrievedState = cache.getQuantizationState(fieldName); + assertNull("State should be null", retrievedState); + assertNotEquals(maxCacheSizeInKB, cache.getMaxCacheSizeInKB()); + } + + @SneakyThrows + public void testRebuildOnTimeExpirySettingsChange() { + int threadCount = 10; + ExecutorService executorService = Executors.newFixedThreadPool(threadCount); + CountDownLatch latch = new CountDownLatch(threadCount); + String fieldName = "rebuildField"; + QuantizationState state = new OneBitScalarQuantizationState( + new ScalarQuantizationParams(ONE_BIT), + new float[] { 1.2f, 2.3f, 3.4f } + ); + + Settings settings = Settings.builder().build(); + ClusterSettings clusterSettings = new ClusterSettings( + settings, + ImmutableSet.of(QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING, QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING) + ); + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.getClusterSettings()).thenReturn(clusterSettings); + when(clusterService.getSettings()).thenReturn(settings); + + Client client = mock(Client.class); + + KNNSettings.state().initialize(client, clusterService); + + QuantizationStateCache cache = QuantizationStateCache.getInstance(); + cache.addQuantizationState(fieldName, state); + + TimeValue newExpiry = TimeValue.timeValueMinutes(30); + + Settings newSettings = Settings.builder().put(QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING.getKey(), newExpiry).build(); + + // Rebuild cache from multiple threads + for (int i = 0; i < threadCount; i++) { + executorService.submit(() -> { + try { + clusterService.getClusterSettings().applySettings(newSettings); + } finally { + latch.countDown(); + } + }); + } + + // Wait for all threads to finish + latch.await(); + executorService.shutdown(); + + QuantizationState retrievedState = cache.getQuantizationState(fieldName); + assertNull("State should be null", retrievedState); + } + + public void testCacheEvictionDueToSize() { + String fieldName = "evictionField"; + // States have size of slightly over 500 bytes so that adding two will reach the max size of 1 kb for the cache + int arrayLength = 112; + float[] arr = new float[arrayLength]; + float[] arr2 = new float[arrayLength]; + for (int i = 0; i < arrayLength; i++) { + arr[i] = i; + arr[i] = i + 1; + } + QuantizationState state = new OneBitScalarQuantizationState(new ScalarQuantizationParams(ONE_BIT), arr); + QuantizationState state2 = new OneBitScalarQuantizationState(new ScalarQuantizationParams(ONE_BIT), arr2); + long cacheSize = 1; + Settings settings = Settings.builder().build(); + ClusterSettings clusterSettings = new ClusterSettings( + settings, + ImmutableSet.of(QUANTIZATION_STATE_CACHE_SIZE_LIMIT_SETTING, QUANTIZATION_STATE_CACHE_EXPIRY_TIME_MINUTES_SETTING) + ); + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.getClusterSettings()).thenReturn(clusterSettings); + when(clusterService.getSettings()).thenReturn(settings); + + QuantizationStateCache cache = new QuantizationStateCache(); + cache.setMaxCacheSizeInKB(cacheSize); + cache.rebuildCache(); + cache.addQuantizationState(fieldName, state); + cache.addQuantizationState(fieldName, state2); + cache.clear(); + assertNotNull(cache.getEvictedDueToSizeAt()); + } +} diff --git a/src/test/java/org/opensearch/knn/quantization/output/BinaryQuantizationOutputTests.java b/src/test/java/org/opensearch/knn/quantization/output/BinaryQuantizationOutputTests.java new file mode 100644 index 000000000..8eab5d00c --- /dev/null +++ b/src/test/java/org/opensearch/knn/quantization/output/BinaryQuantizationOutputTests.java @@ -0,0 +1,121 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.output; + +import org.junit.Before; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.quantization.models.quantizationOutput.BinaryQuantizationOutput; + +public class BinaryQuantizationOutputTests extends KNNTestCase { + + private static final int BITS_PER_COORDINATE = 1; + private BinaryQuantizationOutput quantizationOutput; + + @Before + public void setUp() throws Exception { + super.setUp(); + quantizationOutput = new BinaryQuantizationOutput(BITS_PER_COORDINATE); + } + + public void testPrepareQuantizedVector_ShouldInitializeCorrectly_WhenVectorLengthIsValid() { + // Arrange + int vectorLength = 10; + + // Act + quantizationOutput.prepareQuantizedVector(vectorLength); + + // Assert + assertNotNull(quantizationOutput.getQuantizedVector()); + } + + public void testPrepareQuantizedVector_ShouldThrowException_WhenVectorLengthIsZeroOrNegative() { + // Act and Assert + expectThrows(IllegalArgumentException.class, () -> quantizationOutput.prepareQuantizedVector(0)); + expectThrows(IllegalArgumentException.class, () -> quantizationOutput.prepareQuantizedVector(-1)); + } + + public void testIsPrepared_ShouldReturnTrue_WhenCalledWithSameVectorLength() { + // Arrange + int vectorLength = 8; + quantizationOutput.prepareQuantizedVector(vectorLength); + // Act and Assert + assertTrue(quantizationOutput.isPrepared(vectorLength)); + } + + public void testIsPrepared_ShouldReturnFalse_WhenCalledWithDifferentVectorLength() { + // Arrange + int vectorLength = 8; + quantizationOutput.prepareQuantizedVector(vectorLength); + // Act and Assert + assertFalse(quantizationOutput.isPrepared(vectorLength + 1)); + } + + public void testGetQuantizedVector_ShouldReturnSameReference() { + // Arrange + int vectorLength = 5; + quantizationOutput.prepareQuantizedVector(vectorLength); + // Act + byte[] vector = quantizationOutput.getQuantizedVector(); + // Assert + assertEquals(vector, quantizationOutput.getQuantizedVector()); + } + + public void testGetQuantizedVectorCopy_ShouldReturnCopyOfVector() { + // Arrange + int vectorLength = 5; + quantizationOutput.prepareQuantizedVector(vectorLength); + + // Act + byte[] vectorCopy = quantizationOutput.getQuantizedVectorCopy(); + + // Assert + assertNotSame(vectorCopy, quantizationOutput.getQuantizedVector()); + assertArrayEquals(vectorCopy, quantizationOutput.getQuantizedVector()); + } + + public void testGetQuantizedVectorCopy_ShouldReturnNewCopyOnEachCall() { + // Arrange + int vectorLength = 5; + quantizationOutput.prepareQuantizedVector(vectorLength); + + // Act + byte[] vectorCopy1 = quantizationOutput.getQuantizedVectorCopy(); + byte[] vectorCopy2 = quantizationOutput.getQuantizedVectorCopy(); + + // Assert + assertNotSame(vectorCopy1, vectorCopy2); + } + + public void testPrepareQuantizedVector_ShouldResetQuantizedVector_WhenCalledWithDifferentLength() { + // Arrange + int initialLength = 5; + int newLength = 10; + quantizationOutput.prepareQuantizedVector(initialLength); + byte[] initialVector = quantizationOutput.getQuantizedVector(); + + // Act + quantizationOutput.prepareQuantizedVector(newLength); + byte[] newVector = quantizationOutput.getQuantizedVector(); + + // Assert + assertNotSame(initialVector, newVector); // The array reference should change + assertEquals(newVector.length, (BITS_PER_COORDINATE * newLength + 7) / 8); // Correct size for new vector + } + + public void testPrepareQuantizedVector_ShouldRetainSameArray_WhenCalledWithSameLength() { + // Arrange + int vectorLength = 5; + quantizationOutput.prepareQuantizedVector(vectorLength); + byte[] initialVector = quantizationOutput.getQuantizedVector(); + + // Act + quantizationOutput.prepareQuantizedVector(vectorLength); + byte[] newVector = quantizationOutput.getQuantizedVector(); + + // Assert + assertSame(newVector, initialVector); // The array reference should remain the same + } +} diff --git a/src/test/java/org/opensearch/knn/quantization/quantizationState/QuantizationStateSerializerTests.java b/src/test/java/org/opensearch/knn/quantization/quantizationState/QuantizationStateSerializerTests.java new file mode 100644 index 000000000..fa25e8e80 --- /dev/null +++ b/src/test/java/org/opensearch/knn/quantization/quantizationState/QuantizationStateSerializerTests.java @@ -0,0 +1,46 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.quantizationState; + +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; +import org.opensearch.knn.quantization.models.quantizationParams.ScalarQuantizationParams; +import org.opensearch.knn.quantization.models.quantizationState.MultiBitScalarQuantizationState; +import org.opensearch.knn.quantization.models.quantizationState.OneBitScalarQuantizationState; + +import java.io.IOException; + +public class QuantizationStateSerializerTests extends KNNTestCase { + + public void testSerializeAndDeserializeOneBitScalarQuantizationState() throws IOException { + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); + float[] mean = new float[] { 0.1f, 0.2f, 0.3f }; + OneBitScalarQuantizationState state = new OneBitScalarQuantizationState(params, mean); + + // Serialize + byte[] serialized = state.toByteArray(); + + OneBitScalarQuantizationState deserialized = OneBitScalarQuantizationState.fromByteArray(serialized); + + assertArrayEquals(mean, deserialized.getMeanThresholds(), 0.0f); + assertEquals(params, deserialized.getQuantizationParams()); + } + + public void testSerializeAndDeserializeMultiBitScalarQuantizationState() throws IOException { + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.TWO_BIT); + float[][] thresholds = new float[][] { { 0.1f, 0.2f, 0.3f }, { 0.4f, 0.5f, 0.6f } }; + MultiBitScalarQuantizationState state = new MultiBitScalarQuantizationState(params, thresholds); + + // Serialize + byte[] serialized = state.toByteArray(); + MultiBitScalarQuantizationState deserialized = MultiBitScalarQuantizationState.fromByteArray(serialized); + + for (int i = 0; i < thresholds.length; i++) { + assertArrayEquals(thresholds[i], deserialized.getThresholds()[i], 0.0f); + } + assertEquals(params, deserialized.getQuantizationParams()); + } +} diff --git a/src/test/java/org/opensearch/knn/quantization/quantizationState/QuantizationStateTests.java b/src/test/java/org/opensearch/knn/quantization/quantizationState/QuantizationStateTests.java new file mode 100644 index 000000000..4fd4f40a6 --- /dev/null +++ b/src/test/java/org/opensearch/knn/quantization/quantizationState/QuantizationStateTests.java @@ -0,0 +1,213 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.quantizationState; + +import org.apache.lucene.util.RamUsageEstimator; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; +import org.opensearch.knn.quantization.models.quantizationParams.ScalarQuantizationParams; +import org.opensearch.knn.quantization.models.quantizationState.MultiBitScalarQuantizationState; +import org.opensearch.knn.quantization.models.quantizationState.OneBitScalarQuantizationState; + +import java.io.IOException; + +public class QuantizationStateTests extends KNNTestCase { + + public void testOneBitScalarQuantizationStateSerialization() throws IOException { + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); + float[] mean = { 1.0f, 2.0f, 3.0f }; + + OneBitScalarQuantizationState state = new OneBitScalarQuantizationState(params, mean); + + // Serialize + byte[] serializedState = state.toByteArray(); + + // Deserialize + StreamInput in = StreamInput.wrap(serializedState); + OneBitScalarQuantizationState deserializedState = new OneBitScalarQuantizationState(in); + + float delta = 0.0001f; + assertArrayEquals(mean, deserializedState.getMeanThresholds(), delta); + assertEquals(params.getSqType(), deserializedState.getQuantizationParams().getSqType()); + } + + public void testMultiBitScalarQuantizationStateSerialization() throws IOException { + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.TWO_BIT); + float[][] thresholds = { { 0.5f, 1.5f, 2.5f }, { 1.0f, 2.0f, 3.0f } }; + + MultiBitScalarQuantizationState state = new MultiBitScalarQuantizationState(params, thresholds); + byte[] serializedState = state.toByteArray(); + + // Deserialize + StreamInput in = StreamInput.wrap(serializedState); + MultiBitScalarQuantizationState deserializedState = new MultiBitScalarQuantizationState(in); + + float delta = 0.0001f; + for (int i = 0; i < thresholds.length; i++) { + assertArrayEquals(thresholds[i], deserializedState.getThresholds()[i], delta); + } + assertEquals(params.getSqType(), deserializedState.getQuantizationParams().getSqType()); + } + + public void testSerializationWithDifferentVersions() throws IOException { + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); + float[] mean = { 1.0f, 2.0f, 3.0f }; + + OneBitScalarQuantizationState state = new OneBitScalarQuantizationState(params, mean); + byte[] serializedState = state.toByteArray(); + StreamInput in = StreamInput.wrap(serializedState); + OneBitScalarQuantizationState deserializedState = new OneBitScalarQuantizationState(in); + + float delta = 0.0001f; + assertArrayEquals(mean, deserializedState.getMeanThresholds(), delta); + assertEquals(params.getSqType(), deserializedState.getQuantizationParams().getSqType()); + } + + public void testOneBitScalarQuantizationStateRamBytesUsed() throws IOException { + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); + float[] mean = { 1.0f, 2.0f, 3.0f }; + + OneBitScalarQuantizationState state = new OneBitScalarQuantizationState(params, mean); + + // 1. Manual Calculation of RAM Usage + long manualEstimatedRamBytesUsed = 0L; + + // OneBitScalarQuantizationState object overhead for Object Header + manualEstimatedRamBytesUsed += alignSize(16L); + + // ScalarQuantizationParams object overhead Object Header + manualEstimatedRamBytesUsed += alignSize(16L); + + // Mean array overhead (array header + size of elements) + manualEstimatedRamBytesUsed += alignSize(16L + 4L * mean.length); + + // 3. RAM Usage from RamUsageEstimator + long expectedRamBytesUsed = RamUsageEstimator.shallowSizeOfInstance(OneBitScalarQuantizationState.class) + RamUsageEstimator + .shallowSizeOf(params) + RamUsageEstimator.sizeOf(mean); + + long actualRamBytesUsed = state.ramBytesUsed(); + + // Allow a difference between manual estimation, serialization size, and actual RAM usage + assertTrue( + "The difference between manual and actual RAM usage exceeds 8 bytes", + Math.abs(manualEstimatedRamBytesUsed - actualRamBytesUsed) <= 8 + ); + + assertEquals(expectedRamBytesUsed, actualRamBytesUsed); + } + + public void testMultiBitScalarQuantizationStateGetDimensions_withDimensionNotMultipleOf8_thenSuccess() { + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.TWO_BIT); + + // Case 1: 3 thresholds, each with 2 dimensions + float[][] thresholds1 = { { 0.5f, 1.5f }, { 1.0f, 2.0f }, { 1.5f, 2.5f } }; + MultiBitScalarQuantizationState state1 = new MultiBitScalarQuantizationState(params, thresholds1); + int expectedDimensions1 = 24; // The next multiple of 8 considering all bits + assertEquals(expectedDimensions1, state1.getDimensions()); + + // Case 2: 1 threshold, with 5 dimensions (5 bits, should align to 8) + float[][] thresholds2 = { { 0.5f, 1.5f, 2.5f, 3.5f, 4.5f } }; + MultiBitScalarQuantizationState state2 = new MultiBitScalarQuantizationState(params, thresholds2); + int expectedDimensions2 = 8; // The next multiple of 8 considering all bits + assertEquals(expectedDimensions2, state2.getDimensions()); + + // Case 3: 4 thresholds, each with 7 dimensions (28 bits, should align to 32) + float[][] thresholds3 = { + { 0.5f, 1.5f, 2.5f, 3.5f, 4.5f, 5.5f, 6.5f }, + { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f }, + { 1.5f, 2.5f, 3.5f, 4.5f, 5.5f, 6.5f, 7.5f }, + { 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f } }; + MultiBitScalarQuantizationState state3 = new MultiBitScalarQuantizationState(params, thresholds3); + int expectedDimensions3 = 32; // The next multiple of 8 considering all bits + assertEquals(expectedDimensions3, state3.getDimensions()); + + // Case 4: 2 thresholds, each with 8 dimensions (16 bits, already aligned) + float[][] thresholds4 = { { 0.5f, 1.5f, 2.5f, 3.5f, 4.5f, 5.5f, 6.5f, 7.5f }, { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f } }; + MultiBitScalarQuantizationState state4 = new MultiBitScalarQuantizationState(params, thresholds4); + int expectedDimensions4 = 16; // Already aligned to 8 + assertEquals(expectedDimensions4, state4.getDimensions()); + + // Case 5: 2 thresholds, each with 6 dimensions (12 bits, should align to 16) + float[][] thresholds5 = { { 0.5f, 1.5f, 2.5f, 3.5f, 4.5f, 5.5f }, { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f } }; + MultiBitScalarQuantizationState state5 = new MultiBitScalarQuantizationState(params, thresholds5); + int expectedDimensions5 = 16; // The next multiple of 8 considering all bits + assertEquals(expectedDimensions5, state5.getDimensions()); + } + + public void testOneBitScalarQuantizationStateGetDimensions_withDimensionNotMultipleOf8_thenSuccess() { + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); + + // Case 1: 5 dimensions (should align to 8) + float[] thresholds1 = { 0.5f, 1.5f, 2.5f, 3.5f, 4.5f }; + OneBitScalarQuantizationState state1 = new OneBitScalarQuantizationState(params, thresholds1); + int expectedDimensions1 = 8; // The next multiple of 8 + assertEquals(expectedDimensions1, state1.getDimensions()); + + // Case 2: 7 dimensions (should align to 8) + float[] thresholds2 = { 0.5f, 1.5f, 2.5f, 3.5f, 4.5f, 5.5f, 6.5f }; + OneBitScalarQuantizationState state2 = new OneBitScalarQuantizationState(params, thresholds2); + int expectedDimensions2 = 8; // The next multiple of 8 + assertEquals(expectedDimensions2, state2.getDimensions()); + + // Case 3: 8 dimensions (already aligned to 8) + float[] thresholds3 = { 0.5f, 1.5f, 2.5f, 3.5f, 4.5f, 5.5f, 6.5f, 7.5f }; + OneBitScalarQuantizationState state3 = new OneBitScalarQuantizationState(params, thresholds3); + int expectedDimensions3 = 8; // Already aligned to 8 + assertEquals(expectedDimensions3, state3.getDimensions()); + + // Case 4: 10 dimensions (should align to 16) + float[] thresholds4 = { 0.5f, 1.5f, 2.5f, 3.5f, 4.5f, 5.5f, 6.5f, 7.5f, 8.5f, 9.5f }; + OneBitScalarQuantizationState state4 = new OneBitScalarQuantizationState(params, thresholds4); + int expectedDimensions4 = 16; // The next multiple of 8 + assertEquals(expectedDimensions4, state4.getDimensions()); + + // Case 5: 16 dimensions (already aligned to 16) + float[] thresholds5 = { 0.5f, 1.5f, 2.5f, 3.5f, 4.5f, 5.5f, 6.5f, 7.5f, 8.5f, 9.5f, 10.5f, 11.5f, 12.5f, 13.5f, 14.5f, 15.5f }; + OneBitScalarQuantizationState state5 = new OneBitScalarQuantizationState(params, thresholds5); + int expectedDimensions5 = 16; // Already aligned to 16 + assertEquals(expectedDimensions5, state5.getDimensions()); + } + + public void testMultiBitScalarQuantizationStateRamBytesUsedManualCalculation() throws IOException { + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.TWO_BIT); + float[][] thresholds = { { 0.5f, 1.5f, 2.5f }, { 1.0f, 2.0f, 3.0f } }; + + MultiBitScalarQuantizationState state = new MultiBitScalarQuantizationState(params, thresholds); + + // Manually estimate RAM usage with alignment + long manualEstimatedRamBytesUsed = 0L; + + // Estimate for MultiBitScalarQuantizationState object + manualEstimatedRamBytesUsed += alignSize(16L); // Example overhead for object + + // Estimate for ScalarQuantizationParams object + manualEstimatedRamBytesUsed += alignSize(16L); // Overhead for params object (including fields) + + // Estimate for thresholds array + manualEstimatedRamBytesUsed += alignSize(16L + 4L * thresholds.length); // Overhead for array + references to sub-arrays + + for (float[] row : thresholds) { + manualEstimatedRamBytesUsed += alignSize(16L + 4L * row.length); // Overhead for each sub-array + size of each float + } + + long ramEstimatorRamBytesUsed = RamUsageEstimator.shallowSizeOfInstance(MultiBitScalarQuantizationState.class) + RamUsageEstimator + .shallowSizeOf(params) + RamUsageEstimator.shallowSizeOf(thresholds); + + for (float[] row : thresholds) { + ramEstimatorRamBytesUsed += RamUsageEstimator.sizeOf(row); + } + + long difference = Math.abs(manualEstimatedRamBytesUsed - ramEstimatorRamBytesUsed); + assertTrue("The difference between manual and actual RAM usage exceeds 8 bytes", difference <= 8); + assertEquals(ramEstimatorRamBytesUsed, state.ramBytesUsed()); + } + + private long alignSize(long size) { + return (size + 7) & ~7; // Align to 8 bytes boundary + } + +} diff --git a/src/test/java/org/opensearch/knn/quantization/quantizer/MultiBitScalarQuantizerTests.java b/src/test/java/org/opensearch/knn/quantization/quantizer/MultiBitScalarQuantizerTests.java new file mode 100644 index 000000000..de815d8ad --- /dev/null +++ b/src/test/java/org/opensearch/knn/quantization/quantizer/MultiBitScalarQuantizerTests.java @@ -0,0 +1,227 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.quantizer; + +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; +import org.opensearch.knn.quantization.models.quantizationOutput.BinaryQuantizationOutput; +import org.opensearch.knn.quantization.models.quantizationParams.ScalarQuantizationParams; +import org.opensearch.knn.quantization.models.quantizationState.MultiBitScalarQuantizationState; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; +import org.opensearch.knn.quantization.models.requests.TrainingRequest; + +import java.io.IOException; + +public class MultiBitScalarQuantizerTests extends KNNTestCase { + + public void testTrain_twoBit() throws IOException { + float[][] vectors = { + { 0.5f, 1.5f, 2.5f, 3.5f, 4.5f, 5.5f, 6.5f, 7.5f }, + { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f }, + { 1.5f, 2.5f, 3.5f, 4.5f, 5.5f, 6.5f, 7.5f, 8.5f } }; + MultiBitScalarQuantizer twoBitQuantizer = new MultiBitScalarQuantizer(2); + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.TWO_BIT); + TrainingRequest request = new MockTrainingRequest(params, vectors); + QuantizationState state = twoBitQuantizer.train(request); + + assertTrue(state instanceof MultiBitScalarQuantizationState); + MultiBitScalarQuantizationState mbState = (MultiBitScalarQuantizationState) state; + assertNotNull(mbState.getThresholds()); + assertEquals(2, mbState.getThresholds().length); // 2-bit quantization should have 2 thresholds + } + + public void testTrain_fourBit() throws IOException { + MultiBitScalarQuantizer fourBitQuantizer = new MultiBitScalarQuantizer(4); + float[][] vectors = { + { 0.5f, 1.5f, 2.5f, 3.5f, 4.5f, 5.5f, 6.5f, 7.5f }, + { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f }, + { 1.5f, 2.5f, 3.5f, 4.5f, 5.5f, 6.5f, 7.5f, 8.5f } }; + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.FOUR_BIT); + TrainingRequest request = new MockTrainingRequest(params, vectors); + QuantizationState state = fourBitQuantizer.train(request); + + assertTrue(state instanceof MultiBitScalarQuantizationState); + MultiBitScalarQuantizationState mbState = (MultiBitScalarQuantizationState) state; + assertNotNull(mbState.getThresholds()); + assertEquals(4, mbState.getThresholds().length); // 4-bit quantization should have 4 thresholds + } + + public void testQuantize_twoBit() throws IOException { + MultiBitScalarQuantizer twoBitQuantizer = new MultiBitScalarQuantizer(2); + float[] vector = { 1.3f, 2.2f, 3.3f, 4.1f, 5.6f, 6.7f, 7.4f, 8.1f }; + float[][] thresholds = { { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f }, { 1.5f, 2.5f, 3.5f, 4.5f, 5.5f, 6.5f, 7.5f, 8.5f } }; + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.TWO_BIT); + MultiBitScalarQuantizationState state = new MultiBitScalarQuantizationState(params, thresholds); + + BinaryQuantizationOutput output = new BinaryQuantizationOutput(2); + twoBitQuantizer.quantize(vector, state, output); + + assertNotNull(output.getQuantizedVector()); + } + + public void testQuantize_fourBit() throws IOException { + MultiBitScalarQuantizer fourBitQuantizer = new MultiBitScalarQuantizer(4); + float[] vector = { 1.3f, 2.2f, 3.3f, 4.1f, 5.6f, 6.7f, 7.4f, 8.1f }; + float[][] thresholds = { + { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f }, + { 1.1f, 2.1f, 3.1f, 4.1f, 5.1f, 6.1f, 7.1f, 8.1f }, + { 1.2f, 2.2f, 3.2f, 4.2f, 5.2f, 6.2f, 7.2f, 8.2f }, + { 1.3f, 2.3f, 3.3f, 4.3f, 5.3f, 6.3f, 7.3f, 8.3f } }; + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.FOUR_BIT); + MultiBitScalarQuantizationState state = new MultiBitScalarQuantizationState(params, thresholds); + + BinaryQuantizationOutput output = new BinaryQuantizationOutput(4); + fourBitQuantizer.quantize(vector, state, output); + + assertNotNull(output.getQuantizedVector()); + } + + public void testQuantize_withNullVector() throws IOException { + MultiBitScalarQuantizer twoBitQuantizer = new MultiBitScalarQuantizer(2); + BinaryQuantizationOutput output = new BinaryQuantizationOutput(2); + output.prepareQuantizedVector(8); // Example length + expectThrows( + IllegalArgumentException.class, + () -> twoBitQuantizer.quantize( + null, + new MultiBitScalarQuantizationState(new ScalarQuantizationParams(ScalarQuantizationType.TWO_BIT), new float[2][8]), + output + ) + ); + } + + public void testQuantize_twoBit_multiple_times() throws IOException { + MultiBitScalarQuantizer twoBitQuantizer = new MultiBitScalarQuantizer(2); + float[] vector = { -2.5f, 1.5f, -0.5f, 4.0f, 6.5f, -3.5f, 0.0f, 7.2f }; + float[][] thresholds = { + { -3.0f, 1.0f, -1.0f, 3.5f, 5.0f, -4.0f, 0.5f, 7.0f }, + { -2.0f, 2.0f, 0.0f, 4.5f, 6.0f, -2.5f, -0.5f, 8.0f } }; + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.TWO_BIT); + MultiBitScalarQuantizationState state = new MultiBitScalarQuantizationState(params, thresholds); + + BinaryQuantizationOutput output = new BinaryQuantizationOutput(2); + + // First quantization + twoBitQuantizer.quantize(vector, state, output); + assertNotNull(output.getQuantizedVector()); + + // Save the reference to the byte array + byte[] firstByteArray = output.getQuantizedVector(); + + // Expected output after the first quantization + byte[] expectedPackedBits = new byte[] { (byte) 0b11111101, (byte) 0b00001010 }; + + // Check the output value after the first quantization + assertArrayEquals(expectedPackedBits, output.getQuantizedVector()); + + // Modify vector for a second quantization call + vector = new float[] { -2.5f, 1.5f, -0.5f, 4.0f, 6.5f, -3.5f, 0.0f, 7.2f }; + + // Second quantization + twoBitQuantizer.quantize(vector, state, output); + + // Assert that the same byte array reference is used + assertSame(firstByteArray, output.getQuantizedVector()); + + // Expected output after the second quantization (based on updated vector) + assertArrayEquals(expectedPackedBits, output.getQuantizedVector()); + } + + public void testQuantize_ReuseByteArray_forMultiBitQuantizer() throws IOException { + MultiBitScalarQuantizer twoBitQuantizer = new MultiBitScalarQuantizer(2); + float[] vector = { -2.5f, 1.5f, -0.5f, 4.0f, 6.5f, -3.5f, 0.0f, 7.2f }; + float[][] thresholds = { + { -3.0f, 1.0f, -1.0f, 3.5f, 5.0f, -4.0f, 0.5f, 7.0f }, + { -2.0f, 2.0f, 0.0f, 4.5f, 6.0f, -2.5f, -0.5f, 8.0f } }; + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.TWO_BIT); + MultiBitScalarQuantizationState state = new MultiBitScalarQuantizationState(params, thresholds); + + BinaryQuantizationOutput output = new BinaryQuantizationOutput(2); + + // First quantization + twoBitQuantizer.quantize(vector, state, output); + byte[] firstByteArray = output.getQuantizedVector(); + + // Expected output after the first quantization + byte[] expectedPackedBits = new byte[] { (byte) 0b11111101, (byte) 0b00001010 }; + + // Check the output value after the first quantization + assertArrayEquals(expectedPackedBits, output.getQuantizedVector()); + + // Second quantization with the same vector length + twoBitQuantizer.quantize(vector, state, output); + byte[] secondByteArray = output.getQuantizedVector(); + + // Assert that the same byte array reference is used + assertSame(firstByteArray, secondByteArray); + + // Check the output value after the second quantization + assertArrayEquals(expectedPackedBits, output.getQuantizedVector()); + + // Third quantization with the same vector length + twoBitQuantizer.quantize(vector, state, output); + byte[] thirdByteArray = output.getQuantizedVector(); + + // Assert that the same byte array reference is still used + assertSame(firstByteArray, thirdByteArray); + + // Check the output value after the third quantization + assertArrayEquals(expectedPackedBits, output.getQuantizedVector()); + } + + public void testQuantize_withMultipleVectors_inLoop() throws IOException { + MultiBitScalarQuantizer twoBitQuantizer = new MultiBitScalarQuantizer(2); + float[][] vectors = { + { -2.5f, 1.5f, -0.5f, 4.0f, 6.5f, -3.5f, 0.0f, 7.2f }, + { 2.0f, -1.0f, 3.5f, 0.0f, 5.5f, -2.5f, 1.5f, 6.0f }, + { -4.0f, 2.0f, -1.5f, 3.5f, -0.5f, 1.0f, 2.5f, -3.0f } }; + float[][] thresholds = { + { -3.0f, 1.0f, -1.0f, 3.5f, 5.0f, -4.0f, 0.5f, 7.0f }, + { -2.0f, 2.0f, 0.0f, 4.5f, 6.0f, -2.5f, -0.5f, 8.0f } }; + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.TWO_BIT); + MultiBitScalarQuantizationState state = new MultiBitScalarQuantizationState(params, thresholds); + + BinaryQuantizationOutput output = new BinaryQuantizationOutput(2); + + byte[] previousByteArray = null; + for (float[] vector : vectors) { + // Check if output is already prepared before quantization + boolean wasPrepared = output.isPrepared(vector.length); + + // Prepare the output for the new vector length + output.prepareQuantizedVector(vector.length); + + // Ensure that if it was prepared, it stays the same reference + if (wasPrepared) { + assertSame(previousByteArray, output.getQuantizedVector()); + } + + // Perform the quantization + twoBitQuantizer.quantize(vector, state, output); + + // Save the reference to the byte array after quantization + previousByteArray = output.getQuantizedVector(); + + // Check that the output vector is correctly prepared + assertTrue(output.isPrepared(vector.length)); + } + } + + // Mock classes for testing + private static class MockTrainingRequest extends TrainingRequest { + private final float[][] vectors; + + public MockTrainingRequest(ScalarQuantizationParams params, float[][] vectors) { + super(vectors.length); + this.vectors = vectors; + } + + @Override + public float[] getVectorAtThePosition(int position) { + return vectors[position]; + } + } +} diff --git a/src/test/java/org/opensearch/knn/quantization/quantizer/OneBitScalarQuantizerTests.java b/src/test/java/org/opensearch/knn/quantization/quantizer/OneBitScalarQuantizerTests.java new file mode 100644 index 000000000..a6b907ccb --- /dev/null +++ b/src/test/java/org/opensearch/knn/quantization/quantizer/OneBitScalarQuantizerTests.java @@ -0,0 +1,256 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.quantizer; + +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; +import org.opensearch.knn.quantization.models.quantizationOutput.BinaryQuantizationOutput; +import org.opensearch.knn.quantization.models.quantizationOutput.QuantizationOutput; +import org.opensearch.knn.quantization.models.quantizationParams.ScalarQuantizationParams; +import org.opensearch.knn.quantization.models.quantizationState.OneBitScalarQuantizationState; +import org.opensearch.knn.quantization.models.quantizationState.QuantizationState; +import org.opensearch.knn.quantization.models.requests.TrainingRequest; +import org.opensearch.knn.quantization.sampler.Sampler; +import org.opensearch.knn.quantization.sampler.SamplerType; +import org.opensearch.knn.quantization.sampler.SamplingFactory; + +import java.io.IOException; + +public class OneBitScalarQuantizerTests extends KNNTestCase { + + public void testTrain_withTrainingRequired() throws IOException { + float[][] vectors = { { 1.0f, 2.0f, 3.0f }, { 4.0f, 5.0f, 6.0f }, { 7.0f, 8.0f, 9.0f } }; + + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); + TrainingRequest originalRequest = new TrainingRequest(vectors.length) { + @Override + public float[] getVectorAtThePosition(int position) { + return vectors[position]; + } + }; + OneBitScalarQuantizer quantizer = new OneBitScalarQuantizer(); + QuantizationState state = quantizer.train(originalRequest); + + assertTrue(state instanceof OneBitScalarQuantizationState); + float[] meanThresholds = ((OneBitScalarQuantizationState) state).getMeanThresholds(); + assertArrayEquals(new float[] { 4.0f, 5.0f, 6.0f }, meanThresholds, 0.001f); + } + + public void testQuantize_withState() throws IOException { + float[] vector = { 3.0f, 6.0f, 9.0f }; + float[] thresholds = { 4.0f, 5.0f, 6.0f }; + OneBitScalarQuantizationState state = new OneBitScalarQuantizationState( + new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT), + thresholds + ); + + OneBitScalarQuantizer quantizer = new OneBitScalarQuantizer(); + BinaryQuantizationOutput output = new BinaryQuantizationOutput(1); + + quantizer.quantize(vector, state, output); + + assertNotNull(output); + byte[] expectedPackedBits = new byte[] { 0b01100000 }; // or 96 in decimal + assertArrayEquals(expectedPackedBits, output.getQuantizedVector()); + } + + public void testQuantize_withNullVector() throws IOException { + OneBitScalarQuantizer quantizer = new OneBitScalarQuantizer(); + OneBitScalarQuantizationState state = new OneBitScalarQuantizationState( + new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT), + new float[] { 0.0f } + ); + BinaryQuantizationOutput output = new BinaryQuantizationOutput(1); + expectThrows(IllegalArgumentException.class, () -> quantizer.quantize(null, state, output)); + } + + public void testQuantize_withInvalidState() throws IOException { + OneBitScalarQuantizer quantizer = new OneBitScalarQuantizer(); + float[] vector = { 1.0f, 2.0f, 3.0f }; + QuantizationState invalidState = new QuantizationState() { + @Override + public ScalarQuantizationParams getQuantizationParams() { + return new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); + } + + @Override + public int getBytesPerVector() { + return 0; + } + + @Override + public int getDimensions() { + return 0; + } + + @Override + public long ramBytesUsed() { + return 0; + } + + @Override + public byte[] toByteArray() { + return new byte[0]; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + // Empty implementation for test + } + }; + BinaryQuantizationOutput output = new BinaryQuantizationOutput(1); + expectThrows(IllegalArgumentException.class, () -> quantizer.quantize(vector, invalidState, output)); + } + + public void testQuantize_withMismatchedDimensions() throws IOException { + OneBitScalarQuantizer quantizer = new OneBitScalarQuantizer(); + float[] vector = { 1.0f, 2.0f, 3.0f }; + float[] thresholds = { 4.0f, 5.0f }; + OneBitScalarQuantizationState state = new OneBitScalarQuantizationState( + new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT), + thresholds + ); + QuantizationOutput output = new BinaryQuantizationOutput(1); + expectThrows(IllegalArgumentException.class, () -> quantizer.quantize(vector, state, output)); + } + + public void testCalculateMean() throws IOException { + float[][] vectors = { { 1.0f, 2.0f, 3.0f }, { 4.0f, 5.0f, 6.0f }, { 7.0f, 8.0f, 9.0f } }; + + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); + TrainingRequest samplingRequest = new TrainingRequest(vectors.length) { + @Override + public float[] getVectorAtThePosition(int position) { + return vectors[position]; + } + }; + + Sampler sampler = SamplingFactory.getSampler(SamplerType.RESERVOIR); + int[] sampledIndices = sampler.sample(vectors.length, 3); + float[] meanThresholds = QuantizerHelper.calculateMeanThresholds(samplingRequest, sampledIndices); + assertArrayEquals(new float[] { 4.0f, 5.0f, 6.0f }, meanThresholds, 0.001f); + } + + public void testCalculateMean_withNullVector() { + float[][] vectors = { { 1.0f, 2.0f, 3.0f }, null, { 7.0f, 8.0f, 9.0f } }; + + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); + TrainingRequest samplingRequest = new TrainingRequest(vectors.length) { + @Override + public float[] getVectorAtThePosition(int position) { + return vectors[position]; + } + }; + + Sampler sampler = SamplingFactory.getSampler(SamplerType.RESERVOIR); + int[] sampledIndices = sampler.sample(vectors.length, 3); + expectThrows(IllegalArgumentException.class, () -> QuantizerHelper.calculateMeanThresholds(samplingRequest, sampledIndices)); + } + + public void testQuantize_withState_multiple_times() throws IOException { + float[] vector = { 3.0f, 6.0f, 9.0f }; + float[] thresholds = { 4.0f, 5.0f, 6.0f }; + OneBitScalarQuantizationState state = new OneBitScalarQuantizationState( + new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT), + thresholds + ); + + OneBitScalarQuantizer quantizer = new OneBitScalarQuantizer(); + BinaryQuantizationOutput output = new BinaryQuantizationOutput(1); + + // First quantization + quantizer.quantize(vector, state, output); + assertNotNull(output); + byte[] expectedPackedBits = new byte[] { 0b01100000 }; // or 96 in decimal + assertArrayEquals(expectedPackedBits, output.getQuantizedVector()); + + // Save the reference to the byte array + byte[] firstByteArray = output.getQuantizedVector(); + + // Modify vector and thresholds for a second quantization call + vector = new float[] { 7.0f, 8.0f, 9.0f }; + thresholds = new float[] { 6.0f, 7.0f, 8.0f }; + state = new OneBitScalarQuantizationState(new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT), thresholds); + + // Second quantization + output.prepareQuantizedVector(vector.length); // Ensure it is prepared for the new vector + quantizer.quantize(vector, state, output); + + // Assert that the same byte array reference is used + assertSame(firstByteArray, output.getQuantizedVector()); + + // Check the new output + expectedPackedBits = new byte[] { (byte) 0b11100000 }; // or 224 in decimal + assertArrayEquals(expectedPackedBits, output.getQuantizedVector()); + } + + public void testQuantize_ReuseByteArray() throws IOException { + float[] vector = { 3.0f, 6.0f, 9.0f }; + float[] thresholds = { 4.0f, 5.0f, 6.0f }; + OneBitScalarQuantizationState state = new OneBitScalarQuantizationState( + new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT), + thresholds + ); + + OneBitScalarQuantizer quantizer = new OneBitScalarQuantizer(); + BinaryQuantizationOutput output = new BinaryQuantizationOutput(1); + output.prepareQuantizedVector(vector.length); + + // First quantization + quantizer.quantize(vector, state, output); + byte[] firstByteArray = output.getQuantizedVector(); + + // Second quantization with the same vector length + output.prepareQuantizedVector(vector.length); // Reuse the prepared output + byte[] secondByteArray = output.getQuantizedVector(); + + // Assert that the same byte array reference is used + assertSame(firstByteArray, secondByteArray); + + // Third quantization with the same vector length + output.prepareQuantizedVector(vector.length); // Reuse the prepared output again + quantizer.quantize(vector, state, output); + byte[] thirdByteArray = output.getQuantizedVector(); + + // Assert that the same byte array reference is still used + assertSame(firstByteArray, thirdByteArray); + } + + public void testQuantize_withMultipleVectors_inLoop() throws IOException { + OneBitScalarQuantizer oneBitQuantizer = new OneBitScalarQuantizer(); + float[][] vectors = { { 1.0f, 2.0f, 3.0f, 4.0f }, { 2.0f, 3.0f, 4.0f, 5.0f }, { 1.5f, 2.5f, 3.5f, 4.5f } }; + float[] thresholds = { 1.5f, 2.5f, 3.5f, 4.5f }; + + ScalarQuantizationParams params = new ScalarQuantizationParams(ScalarQuantizationType.ONE_BIT); + OneBitScalarQuantizationState state = new OneBitScalarQuantizationState(params, thresholds); + + BinaryQuantizationOutput output = new BinaryQuantizationOutput(1); + + byte[] previousByteArray = null; + for (float[] vector : vectors) { + // Check if output is already prepared before quantization + boolean wasPrepared = output.isPrepared(vector.length); + + // Prepare the output for the new vector length + output.prepareQuantizedVector(vector.length); + + // Ensure that if it was prepared, it stays the same reference + if (wasPrepared) { + assertSame(previousByteArray, output.getQuantizedVector()); + } + + // Perform the quantization + oneBitQuantizer.quantize(vector, state, output); + + // Save the reference to the byte array after quantization + previousByteArray = output.getQuantizedVector(); + + // Check that the output vector is correctly prepared + assertTrue(output.isPrepared(vector.length)); + } + } +} diff --git a/src/test/java/org/opensearch/knn/quantization/sampler/ReservoirSamplerTests.java b/src/test/java/org/opensearch/knn/quantization/sampler/ReservoirSamplerTests.java new file mode 100644 index 000000000..59952eb10 --- /dev/null +++ b/src/test/java/org/opensearch/knn/quantization/sampler/ReservoirSamplerTests.java @@ -0,0 +1,63 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.sampler; + +import org.opensearch.knn.KNNTestCase; + +import java.util.Arrays; +import java.util.stream.IntStream; + +public class ReservoirSamplerTests extends KNNTestCase { + + public void testSampleLessThanSampleSize() { + ReservoirSampler sampler = ReservoirSampler.getInstance(); + int totalNumberOfVectors = 5; + int sampleSize = 10; + int[] sampledIndices = sampler.sample(totalNumberOfVectors, sampleSize); + int[] expectedIndices = IntStream.range(0, totalNumberOfVectors).toArray(); + assertArrayEquals("Sampled indices should include all available indices.", expectedIndices, sampledIndices); + } + + public void testSampleEqualToSampleSize() { + ReservoirSampler sampler = ReservoirSampler.getInstance(); + int totalNumberOfVectors = 10; + int sampleSize = 10; + int[] sampledIndices = sampler.sample(totalNumberOfVectors, sampleSize); + int[] expectedIndices = IntStream.range(0, totalNumberOfVectors).toArray(); + assertArrayEquals("Sampled indices should include all available indices.", expectedIndices, sampledIndices); + } + + public void testSampleRandomness() { + ReservoirSampler sampler1 = ReservoirSampler.getInstance(); + ReservoirSampler sampler2 = ReservoirSampler.getInstance(); + int totalNumberOfVectors = 100; + int sampleSize = 10; + + int[] sampledIndices1 = sampler1.sample(totalNumberOfVectors, sampleSize); + int[] sampledIndices2 = sampler2.sample(totalNumberOfVectors, sampleSize); + + // It's unlikely but possible for the two samples to be equal, so we just check they are sorted correctly + Arrays.sort(sampledIndices1); + Arrays.sort(sampledIndices2); + assertFalse("Sampled indices should be different", Arrays.equals(sampledIndices1, sampledIndices2)); + } + + public void testEdgeCaseZeroVectors() { + ReservoirSampler sampler = ReservoirSampler.getInstance(); + int totalNumberOfVectors = 0; + int sampleSize = 10; + int[] sampledIndices = sampler.sample(totalNumberOfVectors, sampleSize); + assertEquals("Sampled indices should be empty when there are zero vectors.", 0, sampledIndices.length); + } + + public void testEdgeCaseZeroSampleSize() { + ReservoirSampler sampler = ReservoirSampler.getInstance(); + int totalNumberOfVectors = 10; + int sampleSize = 0; + int[] sampledIndices = sampler.sample(totalNumberOfVectors, sampleSize); + assertEquals("Sampled indices should be empty when sample size is zero.", 0, sampledIndices.length); + } +} diff --git a/src/test/java/org/opensearch/knn/quantization/sampler/SamplingFactoryTests.java b/src/test/java/org/opensearch/knn/quantization/sampler/SamplingFactoryTests.java new file mode 100644 index 000000000..db8772b70 --- /dev/null +++ b/src/test/java/org/opensearch/knn/quantization/sampler/SamplingFactoryTests.java @@ -0,0 +1,19 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn.quantization.sampler; + +import org.opensearch.knn.KNNTestCase; + +public class SamplingFactoryTests extends KNNTestCase { + public void testGetSampler_withReservoir() { + Sampler sampler = SamplingFactory.getSampler(SamplerType.RESERVOIR); + assertTrue(sampler instanceof ReservoirSampler); + } + + public void testGetSampler_withUnsupportedType() { + expectThrows(NullPointerException.class, () -> SamplingFactory.getSampler(null)); // This should throw an exception + } +} diff --git a/src/test/java/org/opensearch/knn/recall/RecallTestsIT.java b/src/test/java/org/opensearch/knn/recall/RecallTestsIT.java index 889f3916f..3dfb67415 100644 --- a/src/test/java/org/opensearch/knn/recall/RecallTestsIT.java +++ b/src/test/java/org/opensearch/knn/recall/RecallTestsIT.java @@ -11,54 +11,526 @@ package org.opensearch.knn.recall; +import lombok.SneakyThrows; +import org.junit.Before; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.knn.KNNRestTestCase; import org.opensearch.knn.TestUtils; import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.engine.KNNEngine; + import java.util.List; +import java.util.Map; import java.util.Set; + +import static org.opensearch.knn.common.KNNConstants.DIMENSION; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_SIZE; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_M; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PQ; +import static org.opensearch.knn.common.KNNConstants.FAISS_NAME; +import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; +import static org.opensearch.knn.common.KNNConstants.KNN_METHOD; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_HNSW; +import static org.opensearch.knn.common.KNNConstants.METHOD_IVF; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_SEARCH; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_M; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NLIST; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NPROBES; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; +import static org.opensearch.knn.common.KNNConstants.MODEL_ID; +import static org.opensearch.knn.common.KNNConstants.NAME; +import static org.opensearch.knn.common.KNNConstants.PARAMETERS; +import static org.opensearch.knn.common.KNNConstants.TYPE; +import static org.opensearch.knn.common.KNNConstants.TYPE_KNN_VECTOR; +import static org.opensearch.knn.index.KNNSettings.KNN_ALGO_PARAM_EF_SEARCH; import static org.opensearch.knn.index.KNNSettings.KNN_ALGO_PARAM_INDEX_THREAD_QTY; import static org.opensearch.knn.index.KNNSettings.KNN_MEMORY_CIRCUIT_BREAKER_ENABLED; +/** + * Tests confirm that for the different supported configurations, recall is sound. The recall thresholds are + * conservatively and empirically determined to prevent flakiness. + * + * This test suite can take a long time to run. The primary reason is that training can take a long time for PQ. + * The parameters for PQ have been reduced significantly, but it still takes time. + */ public class RecallTestsIT extends KNNRestTestCase { - private final String testFieldName = "test-field"; - private final int dimensions = 50; - private final int docCount = 10000; - private final int queryCount = 100; - private final int k = 5; - private final double expRecallValue = 1.0; - - public void testRecallL2StandardData() throws Exception { - String testIndexStandard = "test-index-standard"; - - addDocs(testIndexStandard, testFieldName, dimensions, docCount, true); - float[][] indexVectors = getIndexVectorsFromIndex(testIndexStandard, testFieldName, docCount, dimensions); - float[][] queryVectors = TestUtils.getQueryVectors(queryCount, dimensions, docCount, true); - List> groundTruthValues = TestUtils.computeGroundTruthValues(indexVectors, queryVectors, SpaceType.L2, k); - List> searchResults = bulkSearch(testIndexStandard, testFieldName, queryVectors, k); - double recallValue = TestUtils.calculateRecallValue(searchResults, groundTruthValues, k); - assertEquals(expRecallValue, recallValue, 0.2); + private static final String PROPERTIES_FIELD = "properties"; + private final static String TEST_INDEX_PREFIX_NAME = "test_index"; + private final static String TEST_FIELD_NAME = "test_field"; + private final static String TRAIN_INDEX_NAME = "train_index"; + private final static String TRAIN_FIELD_NAME = "train_field"; + private final static String TEST_MODEL_ID = "test_model_id"; + private final static int TEST_DIMENSION = 32; + private final static int DOC_COUNT = 500; + private final static int QUERY_COUNT = 100; + private final static int TEST_K = 100; + private final static double PERFECT_RECALL = 1.0; + private final static int SHARD_COUNT = 1; + private final static int REPLICA_COUNT = 0; + private final static int MAX_SEGMENT_COUNT = 10; + + // Standard algorithm parameters + private final static int HNSW_M = 16; + private final static int HNSW_EF_CONSTRUCTION = 100; + private final static int HNSW_EF_SEARCH = TEST_K; // For consistency with lucene + private final static int IVF_NLIST = 4; + private final static int IVF_NPROBES = IVF_NLIST; // This equates to essentially a brute force search + private final static int PQ_CODE_SIZE = 8; // This is low and going to produce bad recall, but reduces build time + private final static int PQ_M = TEST_DIMENSION / 8; // Will give low recall, but required for test time + + // Setup ground truth for all tests once + private final static float[][] INDEX_VECTORS = TestUtils.getIndexVectors(DOC_COUNT, TEST_DIMENSION, true); + private final static float[][] QUERY_VECTORS = TestUtils.getQueryVectors(QUERY_COUNT, TEST_DIMENSION, DOC_COUNT, true); + private final static Map>> GROUND_TRUTH = Map.of( + SpaceType.L2, + TestUtils.computeGroundTruthValues(INDEX_VECTORS, QUERY_VECTORS, SpaceType.L2, TEST_K), + SpaceType.COSINESIMIL, + TestUtils.computeGroundTruthValues(INDEX_VECTORS, QUERY_VECTORS, SpaceType.COSINESIMIL, TEST_K), + SpaceType.INNER_PRODUCT, + TestUtils.computeGroundTruthValues(INDEX_VECTORS, QUERY_VECTORS, SpaceType.INNER_PRODUCT, TEST_K) + ); + + @SneakyThrows + @Before + public void setupClusterSettings() { + updateClusterSettings(KNN_ALGO_PARAM_INDEX_THREAD_QTY, 2); + updateClusterSettings(KNN_MEMORY_CIRCUIT_BREAKER_ENABLED, true); + } + + /** + * { + * "properties": { + * { + * "type": "knn_vector", + * "dimension": {DIMENSION}, + * "method": { + * "name":"hnsw", + * "engine":"nmslib", + * "space_type": "{SPACE_TYPE}", + * "parameters":{ + * "m":{HNSW_M}, + * "ef_construction": {HNSW_EF_CONSTRUCTION}, + * "ef_search": {HNSW_EF_SEARCH} + * } + * } + * } + * } + * } + */ + @SneakyThrows + public void testRecall_whenNmslibHnswFP32_thenRecallAbove75percent() { + List spaceTypes = List.of(SpaceType.L2, SpaceType.COSINESIMIL, SpaceType.INNER_PRODUCT); + for (SpaceType spaceType : spaceTypes) { + String indexName = createIndexName(KNNEngine.NMSLIB, spaceType); + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES_FIELD) + .startObject(TEST_FIELD_NAME) + .field(TYPE, TYPE_KNN_VECTOR) + .field(DIMENSION, TEST_DIMENSION) + .startObject(KNN_METHOD) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNN_ENGINE, KNNEngine.NMSLIB.getName()) + .field(NAME, METHOD_HNSW) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, HNSW_EF_CONSTRUCTION) + .field(METHOD_PARAMETER_M, HNSW_M) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + createIndexAndIngestDocs( + indexName, + TEST_FIELD_NAME, + Settings.builder() + .put("number_of_shards", SHARD_COUNT) + .put("number_of_replicas", REPLICA_COUNT) + .put("index.knn", true) + .put(KNN_ALGO_PARAM_EF_SEARCH, HNSW_EF_SEARCH) + .build(), + builder.toString() + ); + assertRecall(indexName, spaceType, 0.25f); + } } - public void testRecallL2RandomData() throws Exception { - String testIndexRandom = "test-index-random"; + /** + * { + * "properties": { + * { + * "type": "knn_vector", + * "dimension": {DIMENSION}, + * "method": { + * "name":"hnsw", + * "engine":"lucene", + * "space_type": "{SPACE_TYPE}", + * "parameters":{ + * "m":{HNSW_M}, + * "ef_construction": {HNSW_EF_CONSTRUCTION} + * } + * } + * } + * } + * } + */ + @SneakyThrows + public void testRecall_whenLuceneHnswFP32_thenRecallAbove75percent() { + List spaceTypes = List.of(SpaceType.L2, SpaceType.COSINESIMIL); + for (SpaceType spaceType : spaceTypes) { + String indexName = createIndexName(KNNEngine.LUCENE, spaceType); + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES_FIELD) + .startObject(TEST_FIELD_NAME) + .field(TYPE, TYPE_KNN_VECTOR) + .field(DIMENSION, TEST_DIMENSION) + .startObject(KNN_METHOD) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNN_ENGINE, KNNEngine.LUCENE.getName()) + .field(NAME, METHOD_HNSW) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, HNSW_EF_CONSTRUCTION) + .field(METHOD_PARAMETER_M, HNSW_M) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + createIndexAndIngestDocs(indexName, TEST_FIELD_NAME, getSettings(), builder.toString()); + assertRecall(indexName, spaceType, 0.25f); + } + } - addDocs(testIndexRandom, testFieldName, dimensions, docCount, false); - float[][] indexVectors = getIndexVectorsFromIndex(testIndexRandom, testFieldName, docCount, dimensions); - float[][] queryVectors = TestUtils.getQueryVectors(queryCount, dimensions, docCount, false); - List> groundTruthValues = TestUtils.computeGroundTruthValues(indexVectors, queryVectors, SpaceType.L2, k); - List> searchResults = bulkSearch(testIndexRandom, testFieldName, queryVectors, k); - double recallValue = TestUtils.calculateRecallValue(searchResults, groundTruthValues, k); - assertEquals(expRecallValue, recallValue, 0.2); + /** + * { + * "properties": { + * { + * "type": "knn_vector", + * "dimension": {TEST_DIMENSION}, + * "method": { + * "name":"hnsw", + * "engine":"faiss", + * "space_type": "{SPACE_TYPE}", + * "parameters":{ + * "m":{HNSW_M}, + * "ef_construction": {HNSW_EF_CONSTRUCTION}, + * "ef_search": {HNSW_EF_SEARCH}, + * } + * } + * } + * } + * } + */ + @SneakyThrows + public void testRecall_whenFaissHnswFP32_thenRecallAbove75percent() { + List spaceTypes = List.of(SpaceType.L2, SpaceType.INNER_PRODUCT); + for (SpaceType spaceType : spaceTypes) { + String indexName = createIndexName(KNNEngine.FAISS, spaceType); + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES_FIELD) + .startObject(TEST_FIELD_NAME) + .field(TYPE, TYPE_KNN_VECTOR) + .field(DIMENSION, TEST_DIMENSION) + .startObject(KNN_METHOD) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNN_ENGINE, KNNEngine.FAISS.getName()) + .field(NAME, METHOD_HNSW) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, HNSW_EF_CONSTRUCTION) + .field(METHOD_PARAMETER_M, HNSW_M) + .field(METHOD_PARAMETER_EF_SEARCH, HNSW_EF_SEARCH) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject(); + createIndexAndIngestDocs(indexName, TEST_FIELD_NAME, getSettings(), builder.toString()); + assertRecall(indexName, spaceType, 0.25f); + } } - private void addDocs(String testIndex, String testField, int dimensions, int docCount, boolean isStandard) throws Exception { - createKnnIndex(testIndex, getKNNDefaultIndexSettings(), createKnnIndexMapping(testField, dimensions)); + /** + * Train context: + * { + * "method": { + * "name":"ivf", + * "engine":"faiss", + * "space_type": "{SPACE_TYPE}", + * "parameters":{ + * "nlist":{IVF_NLIST}, + * "nprobes": {IVF_NPROBES} + * } + * } + * } + * + * Index Mapping: + * { + * "properties": { + * { + * "type": "knn_vector", + * "model_id": {MODEL_ID} + * } + * } + * } + */ + @SneakyThrows + public void testRecall_whenFaissIVFFP32_thenRecallAbove75percent() { + List spaceTypes = List.of(SpaceType.L2, SpaceType.INNER_PRODUCT); + setupTrainingIndex(); + for (SpaceType spaceType : spaceTypes) { + String indexName = createIndexName(KNNEngine.FAISS, spaceType); - updateClusterSettings(KNN_ALGO_PARAM_INDEX_THREAD_QTY, 2); - updateClusterSettings(KNN_MEMORY_CIRCUIT_BREAKER_ENABLED, true); + // Train the model + XContentBuilder trainingBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_IVF) + .field(KNN_ENGINE, FAISS_NAME) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_NLIST, IVF_NLIST) + .field(METHOD_PARAMETER_NPROBES, IVF_NPROBES) + .endObject() + .endObject(); + trainModel( + TEST_MODEL_ID, + TRAIN_INDEX_NAME, + TRAIN_FIELD_NAME, + TEST_DIMENSION, + xContentBuilderToMap(trainingBuilder), + String.format("%s-%s", KNNEngine.FAISS.getName(), spaceType.getValue()) + ); + assertTrainingSucceeds(TEST_MODEL_ID, 100, 1000 * 5); + + // Build the index + createIndexAndIngestDocs(indexName, TEST_FIELD_NAME, getSettings(), getModelMapping()); + assertRecall(indexName, spaceType, 0.25f); - bulkAddKnnDocs(testIndex, testField, TestUtils.getIndexVectors(docCount, dimensions, isStandard), docCount); + deleteIndex(indexName); + + // Delete the model + deleteModel(TEST_MODEL_ID); + } } + /** + * Train context: + * { + * "properties": { + * { + * "type": "knn_vector", + * "dimension": {TEST_DIMENSION}, + * "method": { + * "name":"hnsw", + * "engine":"faiss", + * "space_type": "{SPACE_TYPE}", + * "parameters":{ + * "m":{HNSW_M}, + * "ef_construction": {HNSW_EF_CONSTRUCTION}, + * "ef_search": {HNSW_EF_SEARCH}, + * } + * } + * } + * } + * } + * + * Index Mapping: + * { + * "properties": { + * { + * "type": "knn_vector", + * "model_id": {MODEL_ID} + * } + * } + * } + */ + @SneakyThrows + public void testRecall_whenFaissIVFPQFP32_thenRecallAbove50percent() { + List spaceTypes = List.of(SpaceType.L2, SpaceType.INNER_PRODUCT); + setupTrainingIndex(); + for (SpaceType spaceType : spaceTypes) { + String indexName = createIndexName(KNNEngine.FAISS, spaceType); + + // Train the model + XContentBuilder trainingBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_IVF) + .field(KNN_ENGINE, FAISS_NAME) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_NLIST, IVF_NLIST) + .field(METHOD_PARAMETER_NPROBES, IVF_NPROBES) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_PQ) + .startObject(PARAMETERS) + .field(ENCODER_PARAMETER_PQ_CODE_SIZE, PQ_CODE_SIZE) + .field(ENCODER_PARAMETER_PQ_M, PQ_M) + .endObject() + .endObject() + .endObject() + .endObject(); + trainModel( + TEST_MODEL_ID, + TRAIN_INDEX_NAME, + TRAIN_FIELD_NAME, + TEST_DIMENSION, + xContentBuilderToMap(trainingBuilder), + String.format("%s-%s", KNNEngine.FAISS.getName(), spaceType.getValue()) + ); + assertTrainingSucceeds(TEST_MODEL_ID, 100, 1000 * 5); + + // Build the index + createIndexAndIngestDocs(indexName, TEST_FIELD_NAME, getSettings(), getModelMapping()); + assertRecall(indexName, spaceType, 0.5f); + + deleteIndex(indexName); + + // Delete the model + deleteModel(TEST_MODEL_ID); + } + } + + /** + * Train context: + * { + * "properties": { + * { + * "type": "knn_vector", + * "dimension": {TEST_DIMENSION}, + * "method": { + * "name":"hnsw", + * "engine":"faiss", + * "space_type": "{SPACE_TYPE}", + * "parameters":{ + * "m":{HNSW_M}, + * "ef_construction": {HNSW_EF_CONSTRUCTION}, + * "ef_search": {HNSW_EF_SEARCH}, + * } + * } + * } + * } + * } + * + * Index Mapping: + * { + * "properties": { + * { + * "type": "knn_vector", + * "model_id": {MODEL_ID} + * } + * } + * } + */ + @SneakyThrows + public void testRecall_whenFaissHNSWPQFP32_thenRecallAbove50percent() { + List spaceTypes = List.of(SpaceType.L2, SpaceType.INNER_PRODUCT); + setupTrainingIndex(); + for (SpaceType spaceType : spaceTypes) { + String indexName = createIndexName(KNNEngine.FAISS, spaceType); + + // Train the model + XContentBuilder trainingBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, METHOD_HNSW) + .field(KNN_ENGINE, FAISS_NAME) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_M, HNSW_M) + .field(METHOD_PARAMETER_EF_SEARCH, HNSW_EF_SEARCH) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, HNSW_EF_CONSTRUCTION) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, ENCODER_PQ) + .startObject(PARAMETERS) + .field(ENCODER_PARAMETER_PQ_CODE_SIZE, PQ_CODE_SIZE) + .field(ENCODER_PARAMETER_PQ_M, PQ_M) + .endObject() + .endObject() + .endObject() + .endObject(); + trainModel( + TEST_MODEL_ID, + TRAIN_INDEX_NAME, + TRAIN_FIELD_NAME, + TEST_DIMENSION, + xContentBuilderToMap(trainingBuilder), + String.format("%s-%s", KNNEngine.FAISS.getName(), spaceType.getValue()) + ); + assertTrainingSucceeds(TEST_MODEL_ID, 100, 1000 * 5); + + // Build the index + createIndexAndIngestDocs(indexName, TEST_FIELD_NAME, getSettings(), getModelMapping()); + assertRecall(indexName, spaceType, 0.5f); + + deleteIndex(indexName); + + // Delete the model + deleteModel(TEST_MODEL_ID); + } + } + + @SneakyThrows + private void assertRecall(String testIndexName, SpaceType spaceType, float acceptableRecallFromPerfect) { + List> searchResults = bulkSearch(testIndexName, TEST_FIELD_NAME, QUERY_VECTORS, TEST_K); + double recallValue = TestUtils.calculateRecallValue(searchResults, GROUND_TRUTH.get(spaceType), TEST_K); + logger.info("Recall value = {}", recallValue); + assertEquals(PERFECT_RECALL, recallValue, acceptableRecallFromPerfect); + } + + private String createIndexName(KNNEngine knnEngine, SpaceType spaceType) { + return String.format("%s_%s_%s", TEST_INDEX_PREFIX_NAME, knnEngine.getName(), spaceType.getValue()); + } + + @SneakyThrows + private void createIndexAndIngestDocs(String indexName, String fieldName, Settings settings, String mapping) { + createKnnIndex(indexName, settings, mapping); + bulkAddKnnDocs(indexName, fieldName, INDEX_VECTORS, DOC_COUNT); + forceMergeKnnIndex(indexName, MAX_SEGMENT_COUNT); + } + + @SneakyThrows + private void setupTrainingIndex() { + XContentBuilder trainingIndexBuilder = XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES_FIELD) + .startObject(TRAIN_FIELD_NAME) + .field(TYPE, TYPE_KNN_VECTOR) + .field(DIMENSION, TEST_DIMENSION) + .endObject() + .endObject() + .endObject(); + createIndexAndIngestDocs( + TRAIN_INDEX_NAME, + TRAIN_FIELD_NAME, + Settings.builder().put("number_of_shards", SHARD_COUNT).put("number_of_replicas", REPLICA_COUNT).build(), + trainingIndexBuilder.toString() + ); + } + + @SneakyThrows + private String getModelMapping() { + return XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES_FIELD) + .startObject(TEST_FIELD_NAME) + .field(TYPE, TYPE_KNN_VECTOR) + .field(MODEL_ID, TEST_MODEL_ID) + .endObject() + .endObject() + .endObject() + .toString(); + } + + private Settings getSettings() { + return Settings.builder() + .put("number_of_shards", SHARD_COUNT) + .put("number_of_replicas", REPLICA_COUNT) + .put("index.knn", true) + .build(); + } } diff --git a/src/test/java/org/opensearch/knn/training/FloatTrainingDataConsumerTests.java b/src/test/java/org/opensearch/knn/training/FloatTrainingDataConsumerTests.java new file mode 100644 index 000000000..6e0410853 --- /dev/null +++ b/src/test/java/org/opensearch/knn/training/FloatTrainingDataConsumerTests.java @@ -0,0 +1,95 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.training; + +import org.mockito.ArgumentCaptor; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.index.engine.qframe.QuantizationConfig; +import org.opensearch.knn.index.memory.NativeMemoryAllocation; +import org.opensearch.knn.quantization.enums.ScalarQuantizationType; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class FloatTrainingDataConsumerTests extends KNNTestCase { + + public void testAccept() { + + // Mock the training data allocation + int dimension = 128; + NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation = mock(NativeMemoryAllocation.TrainingDataAllocation.class); + + when(trainingDataAllocation.getMemoryAddress()).thenReturn(0L); + + when(trainingDataAllocation.getQuantizationConfig()).thenReturn(QuantizationConfig.EMPTY); + + // Capture argument passed to set pointer + ArgumentCaptor valueCapture = ArgumentCaptor.forClass(Long.class); + + FloatTrainingDataConsumer floatTrainingDataConsumer = new FloatTrainingDataConsumer(trainingDataAllocation); + + List vectorSet1 = new ArrayList<>(3); + for (int i = 0; i < 3; i++) { + Float[] vector = new Float[dimension]; + Arrays.fill(vector, (float) i); + vectorSet1.add(vector); + } + + // Transfer vectors + floatTrainingDataConsumer.accept(vectorSet1); + + // Ensure that the pointer captured has been updated + verify(trainingDataAllocation).setMemoryAddress(valueCapture.capture()); + when(trainingDataAllocation.getMemoryAddress()).thenReturn(valueCapture.getValue()); + + assertNotEquals(0, trainingDataAllocation.getMemoryAddress()); + } + + public void testAccept_withQuantizationConfig() { + + // Mock the training data allocation + int dimension = 128; + NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation = mock(NativeMemoryAllocation.TrainingDataAllocation.class); + + when(trainingDataAllocation.getMemoryAddress()).thenReturn(0L); + + QuantizationConfig quantizationConfig = mock(QuantizationConfig.class); + when(quantizationConfig.getQuantizationType()).thenReturn(ScalarQuantizationType.ONE_BIT); + when(trainingDataAllocation.getQuantizationConfig()).thenReturn(QuantizationConfig.EMPTY); + + // Capture argument passed to set pointer + ArgumentCaptor valueCapture = ArgumentCaptor.forClass(Long.class); + + FloatTrainingDataConsumer floatTrainingDataConsumer = new FloatTrainingDataConsumer(trainingDataAllocation); + + List vectorSet1 = new ArrayList<>(3); + for (int i = 0; i < 3; i++) { + Float[] vector = new Float[dimension]; + Arrays.fill(vector, (float) i); + vectorSet1.add(vector); + } + + // Transfer vectors + floatTrainingDataConsumer.accept(vectorSet1); + + // Ensure that the pointer captured has been updated + verify(trainingDataAllocation).setMemoryAddress(valueCapture.capture()); + when(trainingDataAllocation.getMemoryAddress()).thenReturn(valueCapture.getValue()); + + assertNotEquals(0, trainingDataAllocation.getMemoryAddress()); + } +} diff --git a/src/test/java/org/opensearch/knn/training/TrainingDataConsumerTests.java b/src/test/java/org/opensearch/knn/training/TrainingDataConsumerTests.java deleted file mode 100644 index d5a66c5b6..000000000 --- a/src/test/java/org/opensearch/knn/training/TrainingDataConsumerTests.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.knn.training; - -import org.mockito.ArgumentCaptor; -import org.opensearch.knn.KNNTestCase; -import org.opensearch.knn.index.memory.NativeMemoryAllocation; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class TrainingDataConsumerTests extends KNNTestCase { - - public void testAccept() { - - // Mock the training data allocation - int dimension = 128; - NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation = mock(NativeMemoryAllocation.TrainingDataAllocation.class); // new - // NativeMemoryAllocation.TrainingDataAllocation(0, - // numVectors*dimension* - // Float.BYTES); - when(trainingDataAllocation.getMemoryAddress()).thenReturn(0L); - - // Capture argument passed to set pointer - ArgumentCaptor valueCapture = ArgumentCaptor.forClass(Long.class); - - TrainingDataConsumer trainingDataConsumer = new TrainingDataConsumer(trainingDataAllocation); - - List vectorSet1 = new ArrayList<>(3); - for (int i = 0; i < 3; i++) { - Float[] vector = new Float[dimension]; - Arrays.fill(vector, (float) i); - vectorSet1.add(vector); - } - - when(trainingDataAllocation.getMemoryAddress()).thenReturn(0L); - - // Transfer vectors - trainingDataConsumer.accept(vectorSet1); - - // Ensure that the pointer captured has been updated - verify(trainingDataAllocation).setMemoryAddress(valueCapture.capture()); - when(trainingDataAllocation.getMemoryAddress()).thenReturn(valueCapture.getValue()); - - assertNotEquals(0, trainingDataAllocation.getMemoryAddress()); - } -} diff --git a/src/test/java/org/opensearch/knn/training/TrainingJobClusterStateListenerTests.java b/src/test/java/org/opensearch/knn/training/TrainingJobClusterStateListenerTests.java new file mode 100644 index 000000000..672a54110 --- /dev/null +++ b/src/test/java/org/opensearch/knn/training/TrainingJobClusterStateListenerTests.java @@ -0,0 +1,181 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn.training; + +import org.opensearch.action.search.SearchRequest; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.cluster.ClusterChangedEvent; +import org.opensearch.cluster.node.DiscoveryNode; +import org.opensearch.cluster.node.DiscoveryNodes; +import org.opensearch.common.unit.TimeValue; +import org.opensearch.core.action.ActionListener; +import org.opensearch.knn.KNNTestCase; +import org.opensearch.knn.indices.Model; +import org.opensearch.knn.indices.ModelDao; +import org.opensearch.knn.indices.ModelMetadata; +import org.opensearch.knn.indices.ModelState; +import org.opensearch.search.SearchHit; +import org.opensearch.search.SearchHits; +import org.opensearch.threadpool.ThreadPool; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.opensearch.knn.common.KNNConstants.TRAIN_THREAD_POOL; + +public class TrainingJobClusterStateListenerTests extends KNNTestCase { + public void testClusterChanged() throws InterruptedException { + ExecutorService executorService = Executors.newSingleThreadExecutor(); + + TrainingJobClusterStateListener trainingJobClusterStateListener = TrainingJobClusterStateListener.getInstance(); + + ThreadPool threadPool = mock(ThreadPool.class); + when(threadPool.executor(TRAIN_THREAD_POOL)).thenReturn(executorService); + doAnswer(invocationOnMock -> { return null; }).when(threadPool) + .schedule(any(Runnable.class), any(TimeValue.class), any(String.class)); + + ModelDao modelDao = mock(ModelDao.class); + ClusterChangedEvent clusterChangedEvent = mock(ClusterChangedEvent.class); + when(clusterChangedEvent.localNodeClusterManager()).thenReturn(true); + when(clusterChangedEvent.isNewCluster()).thenReturn(true); + + TrainingJobClusterStateListener.initialize(threadPool, modelDao, clusterService); + + trainingJobClusterStateListener.clusterChanged(clusterChangedEvent); + + verify(threadPool, times(1)).schedule(any(Runnable.class), any(TimeValue.class), any(String.class)); + + when(clusterChangedEvent.isNewCluster()).thenReturn(false); + when(clusterChangedEvent.nodesRemoved()).thenReturn(true); + DiscoveryNodes.Delta delta = mock(DiscoveryNodes.Delta.class); + List nodes = new ArrayList<>(); + when(clusterChangedEvent.nodesDelta()).thenReturn(delta); + when(delta.removedNodes()).thenReturn(nodes); + + trainingJobClusterStateListener.clusterChanged(clusterChangedEvent); + + verify(threadPool, times(2)).schedule(any(Runnable.class), any(TimeValue.class), any(String.class)); + verify(clusterChangedEvent, times(1)).nodesDelta(); + + when(clusterChangedEvent.nodesRemoved()).thenReturn(false); + trainingJobClusterStateListener.clusterChanged(clusterChangedEvent); + verify(threadPool, times(2)).schedule(any(Runnable.class), any(TimeValue.class), any(String.class)); + + when(clusterChangedEvent.localNodeClusterManager()).thenReturn(false); + trainingJobClusterStateListener.clusterChanged(clusterChangedEvent); + verify(threadPool, times(2)).schedule(any(Runnable.class), any(TimeValue.class), any(String.class)); + + executorService.shutdown(); + executorService.awaitTermination(10, TimeUnit.SECONDS); + } + + public void testUpdateModelsNewCluster() throws IOException, InterruptedException, ExecutionException { + ExecutorService executorService = Executors.newSingleThreadExecutor(); + + TrainingJobClusterStateListener trainingJobClusterStateListener = TrainingJobClusterStateListener.getInstance(); + + ThreadPool threadPool = mock(ThreadPool.class); + when(threadPool.executor(TRAIN_THREAD_POOL)).thenReturn(executorService); + + String modelId = "test-model-id"; + Model model = mock(Model.class); + ModelMetadata modelMetadata = mock(ModelMetadata.class); + when(modelMetadata.getState()).thenReturn(ModelState.TRAINING); + when(model.getModelMetadata()).thenReturn(modelMetadata); + ModelDao modelDao = mock(ModelDao.class); + when(modelDao.isCreated()).thenReturn(true); + when(modelDao.get(modelId)).thenReturn(model); + when(modelDao.getMetadata(modelId)).thenReturn(modelMetadata); + doAnswer(invocationOnMock -> { + SearchResponse searchResponse = mock(SearchResponse.class); + SearchHits searchHits = mock(SearchHits.class); + when(searchResponse.getHits()).thenReturn(searchHits); + SearchHit searchHit = mock(SearchHit.class); + when(searchHit.getId()).thenReturn(modelId); + SearchHit[] searchHitArray = new SearchHit[1]; + searchHitArray[0] = searchHit; + when(searchHits.getHits()).thenReturn(searchHitArray); + ((ActionListener) invocationOnMock.getArguments()[1]).onResponse(searchResponse); + return null; + }).when(modelDao).search(any(SearchRequest.class), any(ActionListener.class)); + doAnswer(invocationOnMock -> { return null; }).when(modelDao).update(any(Model.class), any(ActionListener.class)); + + TrainingJobClusterStateListener.initialize(threadPool, modelDao, clusterService); + + trainingJobClusterStateListener.updateModelsNewCluster(); + + executorService.shutdown(); + executorService.awaitTermination(10, TimeUnit.SECONDS); + + verify(modelMetadata, times(1)).setState(ModelState.FAILED); + verify(modelMetadata, times(1)).setError("Training failed to complete as cluster crashed"); + verify(modelDao, times(1)).update(any(Model.class), any(ActionListener.class)); + } + + public void testUpdateModelsNodesRemoved() throws IOException, InterruptedException, ExecutionException { + ExecutorService executorService = Executors.newSingleThreadExecutor(); + + TrainingJobClusterStateListener trainingJobClusterStateListener = TrainingJobClusterStateListener.getInstance(); + + ThreadPool threadPool = mock(ThreadPool.class); + when(threadPool.executor(TRAIN_THREAD_POOL)).thenReturn(executorService); + + String modelId = "test-model-id"; + Model model = mock(Model.class); + ModelMetadata modelMetadata = mock(ModelMetadata.class); + when(modelMetadata.getState()).thenReturn(ModelState.TRAINING); + when(modelMetadata.getNodeAssignment()).thenReturn("test-node-model-match"); + when(model.getModelMetadata()).thenReturn(modelMetadata); + ModelDao modelDao = mock(ModelDao.class); + when(modelDao.isCreated()).thenReturn(true); + when(modelDao.get(modelId)).thenReturn(model); + when(modelDao.getMetadata(modelId)).thenReturn(modelMetadata); + DiscoveryNode node1 = mock(DiscoveryNode.class); + when(node1.getEphemeralId()).thenReturn("test-node-model-match"); + DiscoveryNode node2 = mock(DiscoveryNode.class); + when(node2.getEphemeralId()).thenReturn("test-node-not-model-match"); + List nodes = new ArrayList(); + nodes.add(node1); + nodes.add(node2); + doAnswer(invocationOnMock -> { + SearchResponse searchResponse = mock(SearchResponse.class); + SearchHits searchHits = mock(SearchHits.class); + when(searchResponse.getHits()).thenReturn(searchHits); + SearchHit searchHit = mock(SearchHit.class); + when(searchHit.getId()).thenReturn(modelId); + SearchHit[] searchHitArray = new SearchHit[1]; + searchHitArray[0] = searchHit; + when(searchHits.getHits()).thenReturn(searchHitArray); + ((ActionListener) invocationOnMock.getArguments()[1]).onResponse(searchResponse); + return null; + }).when(modelDao).search(any(SearchRequest.class), any(ActionListener.class)); + doAnswer(invocationOnMock -> { return null; }).when(modelDao).update(any(Model.class), any(ActionListener.class)); + + TrainingJobClusterStateListener.initialize(threadPool, modelDao, clusterService); + + trainingJobClusterStateListener.updateModelsNodesRemoved(nodes); + + executorService.shutdown(); + executorService.awaitTermination(10, TimeUnit.SECONDS); + + verify(modelMetadata, times(1)).setState(ModelState.FAILED); + verify(modelMetadata, times(1)).setError("Training failed to complete as node dropped"); + verify(modelDao, times(1)).update(any(Model.class), any(ActionListener.class)); + } +} diff --git a/src/test/java/org/opensearch/knn/training/TrainingJobRunnerTests.java b/src/test/java/org/opensearch/knn/training/TrainingJobRunnerTests.java index 8b56518a8..9671b6b5a 100644 --- a/src/test/java/org/opensearch/knn/training/TrainingJobRunnerTests.java +++ b/src/test/java/org/opensearch/knn/training/TrainingJobRunnerTests.java @@ -11,19 +11,18 @@ package org.opensearch.knn.training; -import org.opensearch.action.ActionListener; +import org.opensearch.core.action.ActionListener; import org.opensearch.action.index.IndexResponse; -import org.opensearch.index.shard.ShardId; +import org.opensearch.core.index.shard.ShardId; import org.opensearch.knn.KNNTestCase; import org.opensearch.knn.indices.Model; import org.opensearch.knn.indices.ModelDao; +import org.opensearch.knn.indices.ModelMetadata; +import org.opensearch.knn.indices.ModelState; import org.opensearch.threadpool.ThreadPool; import java.io.IOException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doAnswer; @@ -37,7 +36,7 @@ public class TrainingJobRunnerTests extends KNNTestCase { @SuppressWarnings("unchecked") - public void testExecute_success() throws IOException, InterruptedException { + public void testExecute_success() throws IOException, InterruptedException, ExecutionException { // Test makes sure the correct execution logic follows on successful run ExecutorService executorService = Executors.newSingleThreadExecutor(); @@ -48,6 +47,9 @@ public void testExecute_success() throws IOException, InterruptedException { String modelId = "test-model-id"; Model model = mock(Model.class); + ModelMetadata modelMetadata = mock(ModelMetadata.class); + when(modelMetadata.getState()).thenReturn(ModelState.TRAINING); + when(model.getModelMetadata()).thenReturn(modelMetadata); TrainingJob trainingJob = mock(TrainingJob.class); when(trainingJob.getModelId()).thenReturn(modelId); when(trainingJob.getModel()).thenReturn(model); @@ -63,6 +65,7 @@ public void testExecute_success() throws IOException, InterruptedException { // After put finishes, it should call the onResponse function that will call responseListener and then kickoff // training. ModelDao modelDao = mock(ModelDao.class); + when(modelDao.getMetadata(modelId)).thenReturn(modelMetadata); doAnswer(invocationOnMock -> { assertEquals(1, trainingJobRunner.getJobCount()); // Make sure job count is correct IndexResponse indexResponse = new IndexResponse(new ShardId(MODEL_INDEX_NAME, "uuid", 0), modelId, 0, 0, 0, true); @@ -88,7 +91,7 @@ public void testExecute_success() throws IOException, InterruptedException { } @SuppressWarnings("unchecked") - public void testExecute_failure_rejected() throws IOException, InterruptedException { + public void testExecute_failure_rejected() throws IOException, InterruptedException, ExecutionException { // This test makes sure we reject another request when one is ongoing. To do this, we call // trainingJobRunner.execute(trainingJob, responseListener) in the mocked modeldao.update. At this point, // the call should produce a failure because a training job is already ongoing. @@ -100,6 +103,9 @@ public void testExecute_failure_rejected() throws IOException, InterruptedExcept String modelId = "test-model-id"; Model model = mock(Model.class); + ModelMetadata modelMetadata = mock(ModelMetadata.class); + when(modelMetadata.getState()).thenReturn(ModelState.TRAINING); + when(model.getModelMetadata()).thenReturn(modelMetadata); TrainingJob trainingJob = mock(TrainingJob.class); when(trainingJob.getModelId()).thenReturn(modelId); when(trainingJob.getModel()).thenReturn(model); @@ -115,6 +121,7 @@ public void testExecute_failure_rejected() throws IOException, InterruptedExcept // After put finishes, it should call the onResponse function that will call responseListener and then kickoff // training. ModelDao modelDao = mock(ModelDao.class); + when(modelDao.get(modelId)).thenReturn(model); doAnswer(invocationOnMock -> { IndexResponse indexResponse = new IndexResponse(new ShardId(MODEL_INDEX_NAME, "uuid", 0), modelId, 0, 0, 0, true); ((ActionListener) invocationOnMock.getArguments()[1]).onResponse(indexResponse); diff --git a/src/test/java/org/opensearch/knn/training/TrainingJobTests.java b/src/test/java/org/opensearch/knn/training/TrainingJobTests.java index a4a9bda98..4706bd000 100644 --- a/src/test/java/org/opensearch/knn/training/TrainingJobTests.java +++ b/src/test/java/org/opensearch/knn/training/TrainingJobTests.java @@ -12,22 +12,33 @@ package org.opensearch.knn.training; import com.google.common.collect.ImmutableMap; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexOutput; +import org.opensearch.Version; +import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.knn.KNNTestCase; -import org.opensearch.knn.jni.JNIService; -import org.opensearch.knn.index.KNNMethodContext; -import org.opensearch.knn.index.MethodComponentContext; +import org.opensearch.knn.index.engine.KNNMethodConfigContext; +import org.opensearch.knn.index.engine.KNNMethodContext; +import org.opensearch.knn.index.engine.MethodComponentContext; import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.mapper.CompressionLevel; +import org.opensearch.knn.index.mapper.Mode; import org.opensearch.knn.index.memory.NativeMemoryAllocation; import org.opensearch.knn.index.memory.NativeMemoryCacheManager; import org.opensearch.knn.index.memory.NativeMemoryEntryContext; -import org.opensearch.knn.index.util.KNNEngine; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.store.IndexOutputWithBuffer; import org.opensearch.knn.indices.Model; import org.opensearch.knn.indices.ModelMetadata; import org.opensearch.knn.indices.ModelState; +import org.opensearch.knn.jni.JNICommons; +import org.opensearch.knn.jni.JNIService; -import java.io.File; import java.io.IOException; import java.nio.file.Path; +import java.util.UUID; import java.util.concurrent.ExecutionException; import static org.mockito.Mockito.doAnswer; @@ -39,11 +50,22 @@ public class TrainingJobTests extends KNNTestCase { + private final String trainingIndexName = "trainingindexname"; + + @Override + public void setUp() throws Exception { + super.setUp(); + DiscoveryNode mockedDiscoveryNode = mock(DiscoveryNode.class); + when(clusterService.localNode()).thenReturn(mockedDiscoveryNode); + when(mockedDiscoveryNode.getVersion()).thenReturn(Version.CURRENT); + } + public void testGetModelId() { String modelId = "test-model-id"; KNNMethodContext knnMethodContext = mock(KNNMethodContext.class); - when(knnMethodContext.getEngine()).thenReturn(KNNEngine.DEFAULT); + when(knnMethodContext.getKnnEngine()).thenReturn(KNNEngine.DEFAULT); when(knnMethodContext.getSpaceType()).thenReturn(SpaceType.DEFAULT); + when(knnMethodContext.getMethodComponentContext()).thenReturn(MethodComponentContext.EMPTY); TrainingJob trainingJob = new TrainingJob( modelId, @@ -51,8 +73,11 @@ public void testGetModelId() { mock(NativeMemoryCacheManager.class), mock(NativeMemoryEntryContext.TrainingDataEntryContext.class), mock(NativeMemoryEntryContext.AnonymousEntryContext.class), - 10, - "" + KNNMethodConfigContext.builder().vectorDataType(VectorDataType.DEFAULT).dimension(10).versionCreated(Version.CURRENT).build(), + "", + "test-node", + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED ); assertEquals(modelId, trainingJob.getModelId()); @@ -62,12 +87,15 @@ public void testGetModel() { SpaceType spaceType = SpaceType.INNER_PRODUCT; KNNEngine knnEngine = KNNEngine.DEFAULT; int dimension = 10; - String desciption = "test description"; + String description = "test description"; String error = ""; + String nodeAssignment = "test-node"; + MethodComponentContext methodComponentContext = MethodComponentContext.EMPTY; KNNMethodContext knnMethodContext = mock(KNNMethodContext.class); - when(knnMethodContext.getEngine()).thenReturn(knnEngine); + when(knnMethodContext.getKnnEngine()).thenReturn(knnEngine); when(knnMethodContext.getSpaceType()).thenReturn(spaceType); + when(knnMethodContext.getMethodComponentContext()).thenReturn(methodComponentContext); String modelID = "test-model-id"; TrainingJob trainingJob = new TrainingJob( @@ -76,8 +104,15 @@ public void testGetModel() { mock(NativeMemoryCacheManager.class), mock(NativeMemoryEntryContext.TrainingDataEntryContext.class), mock(NativeMemoryEntryContext.AnonymousEntryContext.class), - dimension, - desciption + KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .dimension(dimension) + .versionCreated(Version.CURRENT) + .build(), + description, + nodeAssignment, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED ); Model model = new Model( @@ -87,8 +122,14 @@ public void testGetModel() { dimension, ModelState.TRAINING, trainingJob.getModel().getModelMetadata().getTimestamp(), - desciption, - error + description, + error, + nodeAssignment, + MethodComponentContext.EMPTY, + VectorDataType.DEFAULT, + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED, + Version.CURRENT ), null, modelID @@ -105,6 +146,11 @@ public void testRun_success() throws IOException, ExecutionException { int nlists = 5; int dimension = 16; KNNEngine knnEngine = KNNEngine.FAISS; + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .dimension(dimension) + .versionCreated(Version.CURRENT) + .build(); KNNMethodContext knnMethodContext = new KNNMethodContext( knnEngine, SpaceType.INNER_PRODUCT, @@ -145,10 +191,12 @@ public void testRun_success() throws IOException, ExecutionException { NativeMemoryEntryContext.TrainingDataEntryContext.class ); when(trainingDataEntryContext.getKey()).thenReturn(tdataKey); + when(trainingDataEntryContext.getTrainIndexName()).thenReturn(trainingIndexName); + when(trainingDataEntryContext.getClusterService()).thenReturn(clusterService); when(nativeMemoryCacheManager.get(trainingDataEntryContext, false)).thenReturn(nativeMemoryAllocation); doAnswer(invocationOnMock -> { - JNIService.freeVectors(memoryAddress); + JNICommons.freeVectorData(memoryAddress); return null; }).when(nativeMemoryCacheManager).invalidate(tdataKey); @@ -158,8 +206,11 @@ public void testRun_success() throws IOException, ExecutionException { nativeMemoryCacheManager, trainingDataEntryContext, modelContext, - dimension, - "" + knnMethodConfigContext, + "", + "test-node", + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED ); trainingJob.run(); @@ -173,17 +224,24 @@ public void testRun_success() throws IOException, ExecutionException { int[] ids = { 1, 2, 3, 4 }; float[][] vectors = new float[ids.length][dimension]; fillFloatArrayRandomly(vectors); - - Path indexPath = createTempFile(); - JNIService.createIndexFromTemplate( - ids, - vectors, - indexPath.toString(), - model.getModelBlob(), - ImmutableMap.of(INDEX_THREAD_QTY, 1), - knnEngine.getName() - ); - assertNotEquals(0, new File(indexPath.toString()).length()); + long vectorsMemoryAddress = JNICommons.storeVectorData(0, vectors, (long) vectors.length * vectors[0].length); + Path tempDirPath = createTempDir(); + String indexFileName1 = "test1" + UUID.randomUUID() + ".tmp"; + try (Directory directory = newFSDirectory(tempDirPath)) { + try (IndexOutput indexOutput = directory.createOutput(indexFileName1, IOContext.DEFAULT)) { + final IndexOutputWithBuffer indexOutputWithBuffer = new IndexOutputWithBuffer(indexOutput); + JNIService.createIndexFromTemplate( + ids, + vectorsMemoryAddress, + vectors[0].length, + indexOutputWithBuffer, + model.getModelBlob(), + ImmutableMap.of(INDEX_THREAD_QTY, 1), + knnEngine + ); + } + assertTrue(directory.fileLength(indexFileName1) > 0); + } } public void testRun_failure_onGetTrainingDataAllocation() throws ExecutionException { @@ -195,6 +253,11 @@ public void testRun_failure_onGetTrainingDataAllocation() throws ExecutionExcept int nlists = 5; int dimension = 16; KNNEngine knnEngine = KNNEngine.FAISS; + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .dimension(dimension) + .versionCreated(Version.CURRENT) + .build(); KNNMethodContext knnMethodContext = new KNNMethodContext( knnEngine, SpaceType.INNER_PRODUCT, @@ -234,8 +297,11 @@ public void testRun_failure_onGetTrainingDataAllocation() throws ExecutionExcept nativeMemoryCacheManager, trainingDataEntryContext, modelContext, - dimension, - "" + knnMethodConfigContext, + "", + "test-node", + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED ); trainingJob.run(); @@ -255,6 +321,11 @@ public void testRun_failure_onGetModelAnonymousAllocation() throws ExecutionExce int nlists = 5; int dimension = 16; KNNEngine knnEngine = KNNEngine.FAISS; + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .dimension(dimension) + .versionCreated(Version.CURRENT) + .build(); KNNMethodContext knnMethodContext = new KNNMethodContext( knnEngine, SpaceType.INNER_PRODUCT, @@ -300,8 +371,11 @@ public void testRun_failure_onGetModelAnonymousAllocation() throws ExecutionExce nativeMemoryCacheManager, trainingDataEntryContext, modelContext, - dimension, - "" + knnMethodConfigContext, + "", + "test-node", + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED ); trainingJob.run(); @@ -321,6 +395,11 @@ public void testRun_failure_closedTrainingDataAllocation() throws ExecutionExcep int nlists = 5; int dimension = 16; KNNEngine knnEngine = KNNEngine.FAISS; + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .dimension(dimension) + .versionCreated(Version.CURRENT) + .build(); KNNMethodContext knnMethodContext = new KNNMethodContext( knnEngine, SpaceType.INNER_PRODUCT, @@ -365,8 +444,11 @@ public void testRun_failure_closedTrainingDataAllocation() throws ExecutionExcep nativeMemoryCacheManager, trainingDataEntryContext, mock(NativeMemoryEntryContext.AnonymousEntryContext.class), - dimension, - "" + knnMethodConfigContext, + "", + "test-node", + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED ); trainingJob.run(); @@ -384,6 +466,11 @@ public void testRun_failure_notEnoughTrainingData() throws ExecutionException { int nlists = 1024; // setting this to 1024 will cause training to fail when there is only 2 data points int dimension = 16; KNNEngine knnEngine = KNNEngine.FAISS; + KNNMethodConfigContext knnMethodConfigContext = KNNMethodConfigContext.builder() + .vectorDataType(VectorDataType.FLOAT) + .dimension(dimension) + .versionCreated(Version.CURRENT) + .build(); KNNMethodContext knnMethodContext = new KNNMethodContext( knnEngine, SpaceType.INNER_PRODUCT, @@ -427,7 +514,7 @@ public void testRun_failure_notEnoughTrainingData() throws ExecutionException { when(nativeMemoryCacheManager.get(trainingDataEntryContext, false)).thenReturn(nativeMemoryAllocation); doAnswer(invocationOnMock -> { - JNIService.freeVectors(memoryAddress); + JNICommons.freeVectorData(memoryAddress); return null; }).when(nativeMemoryCacheManager).invalidate(tdataKey); @@ -437,8 +524,11 @@ public void testRun_failure_notEnoughTrainingData() throws ExecutionException { nativeMemoryCacheManager, trainingDataEntryContext, modelContext, - dimension, - "" + knnMethodConfigContext, + "", + "test-node", + Mode.NOT_CONFIGURED, + CompressionLevel.NOT_CONFIGURED ); trainingJob.run(); diff --git a/src/test/java/org/opensearch/knn/training/VectorReaderTests.java b/src/test/java/org/opensearch/knn/training/VectorReaderTests.java index 0b1e15289..b69b43a39 100644 --- a/src/test/java/org/opensearch/knn/training/VectorReaderTests.java +++ b/src/test/java/org/opensearch/knn/training/VectorReaderTests.java @@ -8,25 +8,23 @@ * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ - package org.opensearch.knn.training; -import org.opensearch.action.ActionListener; +import lombok.Getter; +import org.opensearch.core.action.ActionListener; import org.opensearch.action.search.SearchResponse; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.ValidationException; import org.opensearch.knn.KNNSingleNodeTestCase; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.memory.NativeMemoryAllocation; +import org.opensearch.common.ValidationException; +import org.opensearch.search.SearchHit; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Random; +import java.util.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; import java.util.stream.Collectors; public class VectorReaderTests extends KNNSingleNodeTestCase { @@ -34,10 +32,11 @@ public class VectorReaderTests extends KNNSingleNodeTestCase { private final static int DEFAULT_LATCH_TIMEOUT = 100; private final static String DEFAULT_INDEX_NAME = "test-index"; private final static String DEFAULT_FIELD_NAME = "test-field"; + private final static String DEFAULT_NESTED_FIELD_PATH = "a.b.test-field"; private final static int DEFAULT_DIMENSION = 16; - private final static int DEFAULT_NUM_VECTORS = 100; + private final static int DEFAULT_NUM_VECTORS = 50; private final static int DEFAULT_MAX_VECTOR_COUNT = 10000; - private final static int DEFAULT_SEARCH_SIZE = 10; + private final static int DEFAULT_SEARCH_SIZE = 120; public void testRead_valid_completeIndex() throws InterruptedException, ExecutionException, IOException { createIndex(DEFAULT_INDEX_NAME); @@ -55,9 +54,9 @@ public void testRead_valid_completeIndex() throws InterruptedException, Executio // Configure VectorReader ClusterService clusterService = node().injector().getInstance(ClusterService.class); VectorReader vectorReader = new VectorReader(client()); + TestFloatTrainingDataConsumer trainingDataConsumer = new TestFloatTrainingDataConsumer(createMockTrainingDataAllocation()); // Read all vectors and confirm they match vectors - TestVectorConsumer testVectorConsumer = new TestVectorConsumer(); final CountDownLatch inProgressLatch = new CountDownLatch(1); vectorReader.read( clusterService, @@ -65,14 +64,14 @@ public void testRead_valid_completeIndex() throws InterruptedException, Executio DEFAULT_FIELD_NAME, DEFAULT_MAX_VECTOR_COUNT, DEFAULT_SEARCH_SIZE, - testVectorConsumer, + trainingDataConsumer, createOnSearchResponseCountDownListener(inProgressLatch) ); assertLatchDecremented(inProgressLatch); - List consumedVectors = testVectorConsumer.getVectorsConsumed(); - assertEquals(DEFAULT_NUM_VECTORS, consumedVectors.size()); + List consumedVectors = trainingDataConsumer.getTotalAddedVectors(); + assertEquals(DEFAULT_NUM_VECTORS, trainingDataConsumer.getTotalVectorsCountAdded()); List flatVectors = vectors.stream().flatMap(Arrays::stream).collect(Collectors.toList()); List flatConsumedVectors = consumedVectors.stream().flatMap(Arrays::stream).collect(Collectors.toList()); @@ -97,21 +96,21 @@ public void testRead_valid_trainVectorsIngestedAsIntegers() throws IOException, VectorReader vectorReader = new VectorReader(client()); // Read all vectors and confirm they match vectors - TestVectorConsumer testVectorConsumer = new TestVectorConsumer(); final CountDownLatch inProgressLatch = new CountDownLatch(1); + TestFloatTrainingDataConsumer trainingDataConsumer = new TestFloatTrainingDataConsumer(createMockTrainingDataAllocation()); vectorReader.read( clusterService, DEFAULT_INDEX_NAME, DEFAULT_FIELD_NAME, DEFAULT_MAX_VECTOR_COUNT, DEFAULT_SEARCH_SIZE, - testVectorConsumer, + trainingDataConsumer, createOnSearchResponseCountDownListener(inProgressLatch) ); assertLatchDecremented(inProgressLatch); - List consumedVectors = testVectorConsumer.getVectorsConsumed(); + List consumedVectors = trainingDataConsumer.getTotalAddedVectors(); assertEquals(DEFAULT_NUM_VECTORS, consumedVectors.size()); List flatVectors = vectors.stream().flatMap(Arrays::stream).map(Integer::floatValue).collect(Collectors.toList()); @@ -148,21 +147,22 @@ public void testRead_valid_incompleteIndex() throws InterruptedException, Execut VectorReader vectorReader = new VectorReader(client()); // Read all vectors and confirm they match vectors - TestVectorConsumer testVectorConsumer = new TestVectorConsumer(); final CountDownLatch inProgressLatch = new CountDownLatch(1); + TestFloatTrainingDataConsumer trainingDataConsumer = new TestFloatTrainingDataConsumer(createMockTrainingDataAllocation()); + vectorReader.read( clusterService, DEFAULT_INDEX_NAME, DEFAULT_FIELD_NAME, DEFAULT_MAX_VECTOR_COUNT, DEFAULT_SEARCH_SIZE, - testVectorConsumer, + trainingDataConsumer, createOnSearchResponseCountDownListener(inProgressLatch) ); assertLatchDecremented(inProgressLatch); - List consumedVectors = testVectorConsumer.getVectorsConsumed(); + List consumedVectors = trainingDataConsumer.getTotalAddedVectors(); assertEquals(DEFAULT_NUM_VECTORS, consumedVectors.size()); List flatVectors = vectors.stream().flatMap(Arrays::stream).collect(Collectors.toList()); @@ -191,21 +191,21 @@ public void testRead_valid_OnlyGetMaxVectors() throws InterruptedException, Exec VectorReader vectorReader = new VectorReader(client()); // Read maxNumVectorsRead vectors - TestVectorConsumer testVectorConsumer = new TestVectorConsumer(); final CountDownLatch inProgressLatch = new CountDownLatch(1); + TestFloatTrainingDataConsumer trainingDataConsumer = new TestFloatTrainingDataConsumer(createMockTrainingDataAllocation()); vectorReader.read( clusterService, DEFAULT_INDEX_NAME, DEFAULT_FIELD_NAME, maxNumVectorsRead, DEFAULT_SEARCH_SIZE, - testVectorConsumer, + trainingDataConsumer, createOnSearchResponseCountDownListener(inProgressLatch) ); assertLatchDecremented(inProgressLatch); - List consumedVectors = testVectorConsumer.getVectorsConsumed(); + List consumedVectors = trainingDataConsumer.getTotalAddedVectors(); assertEquals(maxNumVectorsRead, consumedVectors.size()); } @@ -345,22 +345,44 @@ public void testRead_invalid_fieldIsNotKnn() throws InterruptedException, Execut ); } - private static class TestVectorConsumer implements Consumer> { - - List vectorsConsumed; + public void testRead_valid_NestedField() throws InterruptedException, ExecutionException, IOException { + createIndex(DEFAULT_INDEX_NAME); + createKnnNestedIndexMapping(DEFAULT_INDEX_NAME, DEFAULT_NESTED_FIELD_PATH, DEFAULT_DIMENSION); - TestVectorConsumer() { - vectorsConsumed = new ArrayList<>(); + // Create list of random vectors and ingest + Random random = new Random(); + List vectors = new ArrayList<>(); + for (int i = 0; i < DEFAULT_NUM_VECTORS; i++) { + Float[] vector = random.doubles(DEFAULT_DIMENSION).boxed().map(Double::floatValue).toArray(Float[]::new); + vectors.add(vector); + addKnnNestedDoc(DEFAULT_INDEX_NAME, Integer.toString(i), DEFAULT_NESTED_FIELD_PATH, vector); } - @Override - public void accept(List vectors) { - vectorsConsumed.addAll(vectors); - } + // Configure VectorReader + ClusterService clusterService = node().injector().getInstance(ClusterService.class); + VectorReader vectorReader = new VectorReader(client()); - public List getVectorsConsumed() { - return vectorsConsumed; - } + // Read all vectors and confirm they match vectors + final CountDownLatch inProgressLatch = new CountDownLatch(1); + TestFloatTrainingDataConsumer trainingDataConsumer = new TestFloatTrainingDataConsumer(createMockTrainingDataAllocation()); + vectorReader.read( + clusterService, + DEFAULT_INDEX_NAME, + DEFAULT_NESTED_FIELD_PATH, + DEFAULT_MAX_VECTOR_COUNT, + DEFAULT_SEARCH_SIZE, + trainingDataConsumer, + createOnSearchResponseCountDownListener(inProgressLatch) + ); + + assertLatchDecremented(inProgressLatch); + + List consumedVectors = trainingDataConsumer.getTotalAddedVectors(); + assertEquals(DEFAULT_NUM_VECTORS, consumedVectors.size()); + + List flatVectors = vectors.stream().flatMap(Arrays::stream).collect(Collectors.toList()); + List flatConsumedVectors = consumedVectors.stream().flatMap(Arrays::stream).collect(Collectors.toList()); + assertEquals(new HashSet<>(flatVectors), new HashSet<>(flatConsumedVectors)); } private void assertLatchDecremented(CountDownLatch countDownLatch) throws InterruptedException { @@ -370,4 +392,40 @@ private void assertLatchDecremented(CountDownLatch countDownLatch) throws Interr private ActionListener createOnSearchResponseCountDownListener(CountDownLatch countDownLatch) { return ActionListener.wrap(response -> countDownLatch.countDown(), Throwable::printStackTrace); } + + private NativeMemoryAllocation.TrainingDataAllocation createMockTrainingDataAllocation() { + return new NativeMemoryAllocation.TrainingDataAllocation(null, 0, 0, VectorDataType.FLOAT); + } + + // create test float training data consumer class extending FloatTrainingDataConsumer + private static class TestFloatTrainingDataConsumer extends FloatTrainingDataConsumer { + @Getter + private List totalAddedVectors = new ArrayList<>(); + + public TestFloatTrainingDataConsumer(NativeMemoryAllocation.TrainingDataAllocation trainingDataAllocation) { + super(trainingDataAllocation); + } + + @Override + public void processTrainingVectors(SearchResponse searchResponse, int vectorsToAdd, String fieldName) { + SearchHit[] hits = searchResponse.getHits().getHits(); + List vectors = new ArrayList<>(); + + String[] fieldPath = fieldName.split("\\."); + + for (int vector = 0; vector < vectorsToAdd; vector++) { + Object fieldValue = extractFieldValue(hits[vector], fieldPath); + if (!(fieldValue instanceof List)) { + continue; + } + + List fieldList = (List) fieldValue; + vectors.add(fieldList.stream().map(Number::floatValue).toArray(Float[]::new)); + } + + totalAddedVectors.addAll(vectors); + setTotalVectorsCountAdded(getTotalVectorsCountAdded() + vectors.size()); + accept(vectors); + } + } } diff --git a/src/test/resources/data/README.md b/src/test/resources/data/README.md new file mode 100644 index 000000000..df103870f --- /dev/null +++ b/src/test/resources/data/README.md @@ -0,0 +1,26 @@ +# test_vectors_binary_1000x128.json +The file contains a simulated data to represent binary vector. The data is generated by quantizing data from +test_vectors_1000x128.json and packing 8 bits to 1 byte which ends up with 16 length of vector per document. +For quantization technique, we calculated the median(49935.95941056451) of all values and converted it as 0 +if it is less than the median and 1 if it is equal to or larger than the median. + +# test_queries_binary_100x128.csv +The file contains a simulated query data in binary vector format. The data is generated by quantizing data from +test_queries_100x128.csv and packing 8 bits to 1 byte with ends up with 16 length of vector per query. +For quantization technique, we calculated the median(49935.95941056451) of all values in test_vectors_1000x128.json +and converted it as 0 if it is less than the median and 1 if it is equal to or larger than the median. + +# test_ground_truth_binary_100.csv +The file contains the ground truth for the query test_queries_binary_100x128.csv against the data +test_vectors_binary_1000x128.json using hamming distance. + +# test_ground_truth_l2_100.csv +The file contains the ground truth for the query test_queries_100x128.csv against the data test_vectors_1000x128.json +using l2 distance + +# test_vectors_nested_1000x128.json +The file contains a simulated data to represent nested field. +Consecutive ids are assigned for data from same parent document. +For example, let's say there are ids of 2, 3, 4, 5, 10, 11, 12. +Then, the 2, 3, 4, 5 are ids of nested field in parent document with id 6 and 10, 11, 12 are ids of nested field +in parent document with id 13. \ No newline at end of file diff --git a/src/test/resources/data/test_ground_truth_binary_100.csv b/src/test/resources/data/test_ground_truth_binary_100.csv new file mode 100644 index 000000000..bafc11a28 --- /dev/null +++ b/src/test/resources/data/test_ground_truth_binary_100.csv @@ -0,0 +1,100 @@ +76960303, 62740044, 2607799, 66428795, 37435892, 30998561, 60106217, 9575954, 43933006, 76703609, 7687278, 92033260, 94711842, 17081350, 54987042, 62693428, 45075471, 23848565, 49598724, 12571310, 61263068, 63152504, 16971929, 90004325, 6986898, 247198, 68694897, 16387575, 92803766, 41092172, 84840549, 79922758, 74137926, 91990218, 32250045, 34236719, 33061250, 22108665, 75407347, 34698463, 42307449, 16099750, 69786756, 71469330, 79136082, 4099191, 7182642, 57359924, 24314885, 18806856, 56484900, 20002147, 3294781, 8128637, 57803235, 46870723, 99224392, 94076128, 44550764, 2917920, 88904910, 45049308, 26664538, 19376156, 91240048, 60176618, 92787493, 4095116, 94516935, 91727510, 4434662, 68102437, 58749, 66045587, 9860968, 61271144, 36580610, 93515664, 20885148, 53802686, 97011160, 32159704, 6153406, 21993752, 38658347, 35456853, 3773993, 176257, 75135448, 24826575, 30090481, 68000591, 92867155, 29932657, 39171895, 76671482, 72738685, 39847321, 7685448, 86118021, 25636669, 8791066, 39373729, 34044787, 24915585, 79806380, 87720882, 50668599, 83302115, 72357096, 79322415, 67513640, 15536795, 53632373, 99515901, 48774913, 30771409, 99971982, 65851721, 50806615, 97641116, 53888755, 61141874, 19457589, 36753250, 43506672, 71083565, 26493119, 97281847, 8634541, 57241521, 17539625, 84684495, 40865610, 75128745, 73235980, 28796059, 30694952, 8651647, 42947632, 1788101, 51472275, 70036438, 4978543, 9860195, 16595436, 45794415, 1569515, 40027975, 13348726, 77377183, 7423788, 5640302, 68875490, 39801351, 1022677, 37183543, 93933709, 42967683, 3233569, 11543098, 84293052, 80555751, 35192533, 7955293, 83948335, 38365584, 75986488, 29188588, 27187213, 7646095, 73109997, 6808825, 18783702, 50007421, 95957797, 96726697, 16054533, 90654836, 37659250, 33935899, 56153345, 72373496, 73786944, 8733068, 1431742, 40781854, 56424103, 96193415, 74452589, 11581181, 99333375, 31727379, 26744917, 56955985, 37192445, 17385531, 22129328, 4064751, 40534591, 54058788, 94595668, 97057187, 61897582, 22721500, 5111370, 44627776, 39553046, 82339363, 9599614, 93057697, 83210802, 38645117, 72725103, 17764950, 16405341, 83533741, 75543508, 59371804, 44348328, 19939935, 21001913, 27185644, 76315420, 72019362, 3487592, 91141395, 58224549, 93270084, 66667729, 28787861, 508198, 4515343, 55602660, 78602717, 78549759, 98943869, 21919959, 70782102, 36933038, 54014062, 6157724, 15948937, 94539824, 24591705, 65715134, 21070110, 53648154, 40984766, 86460488, 30395570, 15064655, 19486173, 65880522, 76434144, 30569392, 86301513, 98739783, 82886381, 21289531, 37675718, 89217461, 16097038, 30543215, 66271566, 52293995, 8913721, 26063929, 63506281, 13468268, 80014588, 73617245, 84904436, 26507214, 44847298, 36845587, 415901, 85116755, 48658605, 51047803, 70372191, 32426752, 42073124, 12303248, 74724075, 18663507, 51464002, 7066775, 17146629, 33237508, 5970607, 43376279, 41481685, 7517032, 52060076, 40781449, 30811010, 74110882, 99021067, 23194618, 51466049, 12024238, 60393039, 24953498, 64055960, 60955663, 14731700, 66319530, 59501487, 66663942, 34497327, 9603598, 36930650, 77187825, 55669657, 86022504, 24168411, 87710366, 33565483, 17037369, 45381876, 84127901, 50702367, 17386996, 14349098, 82779622, 21397057, 44060493, 71657078, 79821897, 45995325, 16380211, 34432810, 44983451, 2717150, 74743862, 11365791, 82897371, 48893685, 9398733, 321665, 94911072, 62452034, 91281584, 95251277, 32274392, 31904591, 68824981, 32161669, 36139942, 40677414, 90310261, 76540481, 79942022, 97940276, 90272749, 44473167, 47361209, 21533347, 13231279, 26863229, 26998766, 78785507, 49882705, 28928175, 54762643, 7104732, 9623492, 44664587, 36312813, 68702632, 95581843, 53842979, 96735716, 2208785, 96420429, 26114953, 20568363, 85023028, 81274566, 89419466, 14093520, 75229462, 49236559, 36189527, 44915235, 92398073, 77272628, 17365188, 14626618, 62430984, 54663246, 7919588, 81853704, 53547802, 73392814, 22879907, 22942635, 62490109, 38009615, 38061439, 44177011, 37224844, 64602895, 78589145, 64157906, 47213183, 92071990, 63059024, 10649306, 92554120, 38341669, 31161687, 62051033, 17016156, 29959549, 82979980, 6111563, 42199455, 54427233, 874791, 84002370, 46851987, 19101477, 37560164, 77620120, 23110625, 92541302, 6793819, 57163802, 9188443, 98371444, 71300104, 63967300, 33249630, 29029316, 44889423, 2204165, 93562257, 23740167, 55960386, 7011964, 4508700, 58751351, 29834512, 31126490, 77898274, 61859143, 20122224, 28851716, 27625735, 65047700, 50188404, 29994197, 95726235, 47298834, 43152977, 55247455, 6819644, 67474219, 77284619, 30218878, 73021291, 62496012, 5822202, 77072625, 38256849, 68939068, 50567636, 39986008, 45848907, 26292919, 77413012, 36396314, 1250437, 86821229, 72278539, 4985896, 56515456, 72358170, 44846932, 36812683, 57020481, 77300457, 83368048, 79191827, 92530431, 99917599, 82327024, 10366309, 5267545, 69136837, 8115266, 13470059, 83651211, 45407418, 20642888, 9058407, 26102057, 85242963, 35996293, 80316608, 45790169, 45667668, 33431960, 94090109, 13173644, 29510992, 81855445, 58208470, 57961282, 98462867, 88698958, 96852321, 86001008, 83133790, 38510840, 16445503, 90457870, 87160386, 63372756, 54199160, 3880712, 62115552, 3183975, 81805959, 23569917, 75052463, 1936762, 47792865, 55470718, 14326617, 36468541, 22997281, 68128438, 1197320, 20836893, 15015906, 67227442, 3633375, 32699744, 65338021, 89499542, 69605283, 30891921, 99604946, 30787683, 61373987, 83789391, 87598888, 99861373, 93790285, 8729146, 45990383, 52613508, 8129978, 20765474, 75777973, 33123618, 77301523, 569864, 98653983, 17058722, 55189057, 82052050, 51149804, 16019925, 79880247, 2331773, 30463802, 48892201, 91957544, 73124510, 92692283, 89214616, 54868730, 99690194, 59177718, 22766820, 43322743, 77694700, 47708850, 70367851, 83747892, 41245325, 67281495, 56461322, 47090124, 89811711, 38022834, 77787724, 18131876, 16424199, 49641577, 36135, 32058615, 4091162, 69355476, 82532312, 27665211, 1204161, 4069912, 44479073, 72777973, 72238278, 9257405, 78300864, 83083131, 67030811, 11757872, 62762857, 97783876, 83269727, 45996863, 40686254, 96709982, 52261574, 82726008, 76330843, 96318094, 40152546, 73031054, 30163921, 6521313, 90090964, 92353856, 29065964, 65038678, 61960400, 54517921, 93566986, 90158785, 35512853, 51588015, 48673079, 14947650, 33797252, 33336148, 8373289, 19891772, 3233084, 25842078, 17957593, 33304202, 35092039, 17068582, 43357947, 51715482, 93359396, 99658235, 61623915, 10961421, 11923835, 48395186, 88653118, 79657802, 26776922, 42782652, 82427263, 57248122, 91802888, 6038457, 38494874, 91831487, 59981773, 83150534, 26392416, 84406788, 10358899, 9829782, 48675329, 6497830, 67451935, 18466635, 34667286, 27325428, 51590803, 89996536, 59614746, 84166196, 64098930, 53393358, 20486294, 73222868, 82178706, 62232211, 44844121, 15902805, 54263880, 70004753, 55753905, 98648327, 61928316, 44784505, 3235882, 78561158, 42644903, 62803858, 77312810, 33553959, 23134715, 7300047, 60102965, 48088883, 4204661, 53666583, 26275734, 58180653, 40197395, 36685023, 66322959, 65081429, 4798568, 72614359, 62936963, 36505482, 37739481, 61741594, 49597667, 41380093, 34295794, 15535065, 9175338, 18411915, 92604458, 112651, 92998591, 37620363, 51426311, 56473732, 99297164, 41442762, 71316369, 59329511, 91664334, 88047921, 59910624, 81774825, 68316156, 82651278, 4119747, 54232247, 92692978, 49271185, 63015256, 16684829, 68204242, 45919976, 10264691, 31491938, 99524975, 86242799, 4668450, 98948034, 61982238, 70420215, 33201905, 89699445, 46540998, 34946859, 32151165, 168541, 56531125, 526217, 18833224, 43501211, 74614639, 84349107, 59109400, 23432750, 99549373, 22435353, 68041839, 45617087, 97561745, 30139692, 29516592, 69579137, 92215320, 75820087, 8099994, 75153252, 53084256, 34698428, 66611759, 62357986, 8055981, 33895336, 60430369, 68816413, 95010552, 88444207, 26397786, 13862149, 63628376, 22200378, 4199704, 37957788, 15075176, 65454636, 11161731, 72495719, 15163258, 30764367, 69848388, 26734892, 24058273, 70596786, 19898053, 55770687, 68110330, 12416768, 24619760, 76170907, 67031644, 43045786, 60581278, 47887470, 86543538, 94360702, 99729357, 48260151, 55615885, 78909561, 61815223, 29635537, 42692881, 12348118, 71522968, 36808486, 18504197, 99965001, 33699435, 22113133, 27041967, 52204879, 78766358, 85711894, 71920426, 18699206, 91938191, 19272365, 86798033, 65074535, 14363867, 29401781, 85571389, 39201414, 16567550, 1375023, 20645197, 20140249, 57658654, 37166608, 12664567, 57240218, 83155442, 17727650, 29819940, 5832946, 10453030, 6871053, 10597197, 67793644, 66832478, 669105, 91255408, 14220886, 8696647, 65017137, 76825057, 22405120, 26139110, 80251430, 78218845, 3509435, 69641513, 71965942, 72274002, 88251446, 65271999, 45237957, 3088684, 49328608, 7229550, 51315460, 9259676, 66903004, 42237907, 57393458, 37280276, 64848072, 47893286, 749283, 28734791, 17976208, 90061527, 81677380, 38008118, 6525948, 61712234, 78884452, 85695762, 80246713, 8807940, 64087743, 89046466, 20867149, 70727211, 31733363, 88130087, 62552524, 65275241, 73168565, 61728685, 55850790, 72793444, 85117092, 30653863, 32590267, 45428665, 66885828, 44245960, 6505939, 70541760, 68644627, 29466406, 55143724, 40356877, 74441448, 36780454, 89078848, 69697787, 45306070, 9886593, 44481640, 61859581, 4787945, 4806458, 70800879, 27411561, 2891150, 18001617, 90013093, 17894977, 95290172, 59405277, 14723410, 98130363, 17857111, 81898046, 21673260, 79738755, 59197747, 69255765, 66116458, 95395112, 13819358, 89804152, 51507425, 5073754, 81172706, 37501808, 97379790, 824872, 99226875, 62428472, 84187166, 37891451, 67124282, 89637706, 15148031, 66250369, 33628349, 67084351, 30366150, 10309525, 1239555, 71333116, 74357852, 14045383, 54606384, 7502255, 59318837, 66137019, 93053405, 22450468, 20681921, 48360198, 72732205, 41092102, 99125126, 24733232, 28550822, 34493392, 96906410, 47738185, 5482538, 6221471, 44842615 +36930650, 18466635, 82052050, 5267545, 36139942, 98943869, 42644903, 73124510, 41245325, 10597197, 80316608, 84840549, 23569917, 30787683, 1569515, 76434144, 77377183, 77312810, 30543215, 79136082, 18783702, 16684829, 31727379, 56531125, 39553046, 97940276, 17894977, 68702632, 48675329, 44915235, 17976208, 30764367, 73222868, 40027975, 17016156, 3233569, 36685023, 99729357, 874791, 80555751, 78766358, 30811010, 76960303, 56484900, 24953498, 14731700, 26292919, 83155442, 47738185, 40152546, 247198, 50806615, 5111370, 74614639, 45075471, 83368048, 13470059, 17764950, 68041839, 80251430, 13231279, 78785507, 10961421, 28796059, 62115552, 14326617, 61271144, 36933038, 1788101, 32250045, 72495719, 81677380, 14626618, 24591705, 19898053, 81898046, 22942635, 15902805, 38061439, 55753905, 78589145, 64157906, 33123618, 1022677, 89217461, 99690194, 63967300, 93562257, 71316369, 18131876, 74357852, 24915585, 83302115, 79322415, 87710366, 56955985, 46870723, 17727650, 48774913, 94076128, 99971982, 669105, 82897371, 48893685, 9886593, 88904910, 20642888, 45667668, 90272749, 29516592, 3509435, 35092039, 62357986, 33895336, 66045587, 30694952, 83150534, 26397786, 37280276, 6157724, 16595436, 22879907, 62490109, 38009615, 33061250, 59197747, 52613508, 92071990, 77301523, 51149804, 79880247, 55615885, 62936963, 30463802, 98371444, 48658605, 22766820, 2204165, 16424199, 82532312, 65047700, 92033260, 4069912, 85571389, 8733068, 12024238, 60393039, 60955663, 73021291, 9603598, 97783876, 37166608, 89699445, 50567636, 45381876, 45996863, 29819940, 40534591, 45995325, 34432810, 97057187, 61897582, 2717150, 11365791, 2917920, 45306070, 56515456, 19457589, 18833224, 99917599, 93566986, 9599614, 19376156, 92803766, 60176618, 8115266, 48673079, 4434662, 49598724, 47361209, 44348328, 84684495, 3233084, 72019362, 99658235, 40865610, 91141395, 48395186, 54762643, 73235980, 66667729, 95581843, 3183975, 33628349, 20568363, 9860968, 89419466, 75229462, 10358899, 65454636, 53802686, 34236719, 81853704, 67227442, 78884452, 21070110, 35456853, 21673260, 64087743, 24619760, 8729146, 30090481, 98648327, 43045786, 62803858, 75777973, 55850790, 16097038, 7300047, 98653983, 58180653, 85117092, 76703609, 63506281, 13468268, 2331773, 36845587, 78909561, 72738685, 18411915, 7685448, 89214616, 42073124, 12303248, 12348118, 18663507, 71469330, 67281495, 7066775, 41442762, 52204879, 91664334, 96726697, 7517032, 28851716, 92692978, 74110882, 27665211, 24314885, 63015256, 50668599, 23194618, 40356877, 16567550, 37435892, 78300864, 66319530, 3294781, 62496012, 59501487, 34497327, 74452589, 54606384, 62428472, 26744917, 15536795, 36396314, 53632373, 6986898, 65851721, 168541, 4985896, 6521313, 67124282, 89637706, 94911072, 44846932, 43501211, 79191827, 38645117, 76825057, 90158785, 23432750, 92787493, 70800879, 75543508, 59371804, 33336148, 18001617, 30139692, 8634541, 57241521, 13173644, 72274002, 3487592, 63372756, 66611759, 58224549, 45237957, 36312813, 30998561, 96420429, 79922758, 91802888, 78602717, 78549759, 81274566, 68816413, 60106217, 95290172, 95010552, 36580610, 22200378, 51472275, 28734791, 67451935, 20885148, 90061527, 17365188, 22997281, 34493392, 38008118, 64098930, 15015906, 24058273, 73392814, 30891921, 12416768, 30395570, 70004753, 37224844, 64602895, 62552524, 9575954, 44784505, 47213183, 78561158, 10649306, 98739783, 38341669, 73168565, 61263068, 569864, 43933006, 72793444, 76671482, 66271566, 8913721, 48260151, 65081429, 46851987, 32590267, 37560164, 23110625, 29635537, 57163802, 9188443, 16099750, 51047803, 96906410, 54868730, 112651, 66885828, 27187213, 29029316, 6808825, 56473732, 8791066, 4508700, 39373729, 38022834, 2607799, 16054533, 68316156, 52060076, 66428795, 99021067, 68204242, 9257405, 72373496, 51466049, 47298834, 1431742, 20002147, 99226875, 99333375, 84127901, 57803235, 17385531, 99515901, 4064751, 5832946, 34946859, 59318837, 30163921, 90090964, 74743862, 8696647, 61141874, 82339363, 23848565, 40677414, 59109400, 9058407, 83533741, 2891150, 22435353, 93053405, 97561745, 92215320, 69641513, 88698958, 96852321, 86001008, 17957593, 83133790, 76315420, 16445503, 61623915, 9623492, 44664587, 66250369, 42782652, 60430369, 81805959, 26114953, 51315460, 85023028, 21919959, 26392416, 63628376, 4978543, 9860195, 11161731, 92398073, 10309525, 15948937, 77272628, 84166196, 6525948, 14723410, 26734892, 55770687, 69605283, 38658347, 44844121, 40984766, 54263880, 44177011, 61373987, 70727211, 75135448, 65880522, 13348726, 45990383, 69255765, 13819358, 29959549, 33553959, 11543098, 42307449, 84904436, 26507214, 72614359, 36505482, 19101477, 48892201, 415901, 92692283, 15535065, 75986488, 9175338, 74724075, 7646095, 36808486, 63152504, 18504197, 99965001, 99297164, 58751351, 22113133, 71333116, 29466406, 29834512, 43376279, 31126490, 88047921, 85711894, 41481685, 32058615, 77898274, 57359924, 20122224, 90654836, 27625735, 72732205, 37659250, 54232247, 7687278, 72777973, 95726235, 94711842, 1375023, 55247455, 86242799, 67030811, 98948034, 5822202, 96193415, 77072625, 57658654, 77187825, 38256849, 86022504, 36780454, 33201905, 17081350, 82779622, 7502255, 54058788, 32151165, 79821897, 94595668, 73031054, 54987042, 72358170, 44627776, 91281584, 36753250, 45049308, 16387575, 61960400, 32274392, 68824981, 24733232, 82327024, 61859581, 93057697, 10366309, 91240048, 69136837, 4787945, 35512853, 4806458, 16405341, 4095116, 79942022, 94516935, 66137019, 26139110, 33797252, 19891772, 98462867, 25842078, 21001913, 51715482, 93359396, 68102437, 75128745, 65271999, 93270084, 3088684, 54199160, 79657802, 53842979, 82427263, 4515343, 55602660, 6038457, 74137926, 91831487, 59981773, 42947632, 70782102, 88444207, 67084351, 30366150, 54014062, 64848072, 9829782, 749283, 4199704, 34667286, 94539824, 69848388, 1197320, 7919588, 61712234, 3633375, 53393358, 70596786, 85695762, 21993752, 89046466, 87598888, 15064655, 76170907, 12571310, 24826575, 88130087, 7423788, 95395112, 20765474, 61728685, 60581278, 82886381, 93933709, 39171895, 17058722, 52293995, 26063929, 66322959, 54427233, 80014588, 84002370, 44847298, 83948335, 38365584, 70372191, 33249630, 44245960, 81172706, 71522968, 37620363, 83747892, 70541760, 16971929, 49641577, 90004325, 40781449, 71920426, 33935899, 49271185, 72238278, 39201414, 73786944, 45919976, 99524975, 43152977, 64055960, 83083131, 4668450, 77284619, 62762857, 41092102, 11581181, 17037369, 67513640, 39986008, 77413012, 57240218, 82726008, 21397057, 44060493, 30771409, 71657078, 66832478, 44983451, 72278539, 68694897, 9398733, 62452034, 65017137, 36812683, 57020481, 95251277, 92530431, 44481640, 26664538, 84349107, 51588015, 41092172, 22405120, 85242963, 14947650, 91727510, 26493119, 44473167, 8373289, 94090109, 21533347, 58208470, 57961282, 38510840, 27185644, 33304202, 43357947, 90457870, 22450468, 88251446, 28550822, 75153252, 53084256, 11923835, 34698428, 88653118, 49328608, 508198, 57248122, 9259676, 75052463, 8651647, 14093520, 57393458, 13862149, 70036438, 93515664, 37957788, 27325428, 15163258, 59614746, 54663246, 65715134, 65338021, 80246713, 3773993, 19486173, 5482538, 61928316, 86301513, 8129978, 63059024, 68875490, 31161687, 29932657, 21289531, 47887470, 37675718, 94360702, 60102965, 4204661, 26275734, 42199455, 55189057, 34698463, 51507425, 84293052, 73617245, 30653863, 37739481, 35192533, 1239555, 91957544, 92541302, 34295794, 29188588, 6793819, 71300104, 92604458, 44889423, 47708850, 23740167, 86118021, 50007421, 56461322, 33699435, 17146629, 7182642, 55143724, 61859143, 4119747, 91938191, 1204161, 19272365, 86798033, 65074535, 14363867, 44479073, 50188404, 56153345, 87720882, 29401781, 31491938, 74441448, 67474219, 30218878, 66663942, 61982238, 70420215, 24168411, 84187166, 37192445, 45848907, 17386996, 40686254, 1250437, 76330843, 14349098, 46540998, 37891451, 97641116, 14220886, 62693428, 321665, 99125126, 29065964, 526217, 65038678, 54517921, 32161669, 83210802, 90310261, 83651211, 45407418, 76540481, 97281847, 29510992, 69579137, 81855445, 26863229, 71965942, 90013093, 17068582, 8099994, 87160386, 58749, 6221471, 7104732, 3880712, 7229550, 38494874, 47792865, 55470718, 36468541, 42237907, 36189527, 51590803, 32159704, 98130363, 53547802, 6153406, 20486294, 62232211, 99604946, 176257, 83789391, 3235882, 75407347, 30569392, 5640302, 39801351, 65275241, 62051033, 92867155, 37183543, 42967683, 86543538, 53666583, 40197395, 62740044, 49597667, 45428665, 39847321, 85116755, 69786756, 59177718, 92998591, 77694700, 51426311, 4099191, 47090124, 34044787, 59329511, 95957797, 14045383, 59910624, 97379790, 36135, 81774825, 82651278, 69355476, 79806380, 29994197, 10264691, 20645197, 6819644, 824872, 56424103, 11757872, 33565483, 50702367, 6871053, 16380211, 67793644, 53888755, 44550764, 77300457, 43506672, 31904591, 44842615, 26102057, 35996293, 45790169, 17539625, 75820087, 19939935, 49882705, 8055981, 2208785, 1936762, 66903004, 49236559, 97011160, 62430984, 17857111, 45794415, 53648154, 8807940, 86460488, 99861373, 48360198, 22108665, 92554120, 82979980, 23134715, 6111563, 16019925, 7955293, 43322743, 42692881, 5073754, 73109997, 6505939, 25636669, 33237508, 68644627, 5970607, 18806856, 40781854, 83269727, 8128637, 89078848, 52261574, 10453030, 91255408, 69697787, 72725103, 99549373, 45617087, 33431960, 26998766, 28928175, 91990218, 47893286, 6497830, 15075176, 59405277, 68128438, 20681921, 82178706, 20867149, 31733363, 93790285, 67031644, 48088883, 89804152, 4798568, 61815223, 61741594, 41380093, 77620120, 32426752, 70367851, 51464002, 55960386, 89811711, 27041967, 4091162, 18699206, 55669657, 22129328, 96318094, 86821229, 78218845, 89996536, 32699744, 68110330, 79738755, 68000591, 37501808, 7011964, 77787724, 72357096, 68939068, 96709982, 99224392, 22721500, 92353856, 15148031, 27411561, 20836893, 89499542, 66116458, 28787861, 26776922, 12664567, 71083565, 96735716, 20140249, 84406788 +15064655, 56461322, 44846932, 92803766, 21070110, 4069912, 68204242, 30218878, 62428472, 54987042, 75153252, 48395186, 78549759, 73168565, 51507425, 57163802, 87710366, 97641116, 74614639, 19891772, 81855445, 78785507, 26776922, 47792865, 90061527, 72495719, 92071990, 33553959, 42967683, 30543215, 63506281, 46851987, 9188443, 99690194, 59177718, 47708850, 68644627, 88047921, 55143724, 82532312, 72777973, 83302115, 24953498, 37166608, 89637706, 83368048, 5267545, 23432750, 13231279, 3233084, 51715482, 45237957, 26114953, 34236719, 6153406, 73392814, 62232211, 12571310, 77377183, 37675718, 43933006, 17058722, 8913721, 48260151, 13468268, 874791, 92604458, 12348118, 71522968, 71469330, 41245325, 7066775, 71316369, 4119747, 72238278, 45919976, 1431742, 67030811, 99226875, 86022504, 17386996, 5832946, 10597197, 669105, 62452034, 93566986, 24733232, 82327024, 40677414, 4095116, 83533741, 97940276, 91727510, 80316608, 33431960, 8099994, 22450468, 61623915, 10961421, 33895336, 62115552, 9860968, 14326617, 36468541, 61271144, 51472275, 48675329, 3633375, 32699744, 30891921, 38009615, 30787683, 55753905, 22108665, 5640302, 31161687, 21289531, 77312810, 94360702, 66322959, 54427233, 55615885, 4798568, 32590267, 6793819, 48658605, 73109997, 70541760, 56473732, 7011964, 99297164, 33237508, 59910624, 7517032, 69355476, 79806380, 7687278, 65047700, 63015256, 12024238, 43152977, 60955663, 66319530, 56424103, 77072625, 97783876, 84187166, 26292919, 36396314, 57240218, 1250437, 22129328, 82779622, 79821897, 6871053, 45995325, 67793644, 168541, 50806615, 72278539, 4985896, 90090964, 44550764, 48893685, 45306070, 95251277, 61960400, 32274392, 68824981, 59109400, 4787945, 45407418, 92787493, 76540481, 26102057, 35996293, 93053405, 45667668, 97561745, 17539625, 96852321, 17894977, 53084256, 6221471, 53842979, 49328608, 60430369, 33628349, 81274566, 14093520, 88444207, 63628376, 749283, 37957788, 10309525, 32250045, 15163258, 20836893, 81853704, 38658347, 22879907, 22942635, 40027975, 37224844, 78589145, 52613508, 78561158, 66116458, 63059024, 33123618, 7300047, 42199455, 11543098, 99729357, 84293052, 36845587, 78909561, 23110625, 72738685, 29188588, 89214616, 70372191, 32426752, 44889423, 44245960, 36808486, 83747892, 50007421, 33699435, 27041967, 74357852, 16424199, 41481685, 81774825, 68316156, 40781449, 28851716, 27625735, 72732205, 92692978, 71920426, 24314885, 33935899, 87720882, 40356877, 47298834, 1375023, 14731700, 6819644, 62496012, 59501487, 66663942, 96193415, 74452589, 54606384, 45996863, 83155442, 44060493, 29819940, 54058788, 59318837, 16380211, 61897582, 86821229, 68694897, 2917920, 56515456, 5111370, 94911072, 9886593, 91281584, 43506672, 43501211, 32161669, 61859581, 19376156, 69136837, 41092172, 85242963, 66137019, 2891150, 26139110, 59371804, 8373289, 8634541, 13173644, 21533347, 35092039, 16445503, 72019362, 28928175, 73235980, 44664587, 54199160, 7229550, 42947632, 21919959, 95290172, 67451935, 59405277, 15948937, 27325428, 81677380, 34493392, 59614746, 67227442, 85695762, 35456853, 73222868, 21673260, 80246713, 70004753, 75135448, 48360198, 64157906, 13348726, 69255765, 75407347, 86301513, 43045786, 10649306, 20765474, 75777973, 29959549, 61263068, 77301523, 93933709, 48088883, 76671482, 66271566, 42307449, 80555751, 62936963, 37739481, 61741594, 415901, 41380093, 75986488, 9175338, 85116755, 18411915, 12303248, 77694700, 79136082, 23740167, 17146629, 95957797, 29466406, 18131876, 96726697, 20122224, 24915585, 90654836, 74110882, 19272365, 50188404, 23194618, 72373496, 37435892, 55247455, 86242799, 40781854, 77284619, 3294781, 34497327, 57658654, 79322415, 41092102, 17037369, 56955985, 15536795, 6986898, 50702367, 40686254, 14349098, 71657078, 40152546, 94076128, 44983451, 11365791, 92353856, 56531125, 36812683, 57020481, 36753250, 79191827, 31904591, 91240048, 38645117, 8115266, 35512853, 48673079, 27411561, 75543508, 4434662, 33797252, 68041839, 90272749, 57241521, 29510992, 92215320, 3509435, 88698958, 25842078, 17957593, 38510840, 33304202, 43357947, 90457870, 99658235, 28550822, 54762643, 7104732, 68702632, 30998561, 2208785, 55602660, 3183975, 74137926, 78602717, 23569917, 66903004, 85023028, 59981773, 36580610, 54014062, 10358899, 22200378, 9829782, 11161731, 22997281, 14626618, 32159704, 26734892, 17857111, 7919588, 16595436, 65715134, 24058273, 45794415, 62490109, 15902805, 12416768, 38061439, 83789391, 59197747, 19486173, 3235882, 8129978, 7423788, 68875490, 95395112, 62803858, 61728685, 47887470, 1022677, 16097038, 60102965, 58180653, 51149804, 85117092, 80014588, 73617245, 30653863, 36505482, 48892201, 73124510, 29635537, 16099750, 7685448, 71300104, 42073124, 63967300, 33249630, 27187213, 93562257, 63152504, 6808825, 67281495, 55960386, 86118021, 25636669, 4508700, 89811711, 5970607, 78766358, 85711894, 97379790, 49641577, 32058615, 61859143, 52060076, 27665211, 86798033, 76960303, 99021067, 16567550, 8733068, 94711842, 31491938, 56484900, 74441448, 67474219, 72357096, 5822202, 36930650, 24168411, 89699445, 11581181, 39986008, 45381876, 77413012, 53632373, 84127901, 17385531, 76330843, 17727650, 47738185, 34946859, 10453030, 37891451, 247198, 34432810, 99971982, 97057187, 73031054, 74743862, 69697787, 9398733, 99125126, 61141874, 19457589, 92530431, 82339363, 23848565, 90158785, 17764950, 9058407, 70800879, 45790169, 49598724, 33336148, 30139692, 29516592, 58208470, 69641513, 84684495, 19939935, 83133790, 72274002, 76315420, 88251446, 11923835, 34698428, 66611759, 88653118, 62357986, 9623492, 79657802, 96420429, 57248122, 51315460, 75052463, 38494874, 1936762, 68816413, 89419466, 75229462, 83150534, 26397786, 49236559, 70036438, 4199704, 36189527, 6497830, 93515664, 28734791, 4978543, 17976208, 15075176, 9860195, 51590803, 68128438, 62430984, 54663246, 6525948, 61712234, 19898053, 55770687, 21993752, 81898046, 8807940, 68110330, 64087743, 79738755, 24826575, 65880522, 76434144, 67031644, 61928316, 44784505, 47213183, 98739783, 42644903, 65275241, 569864, 89217461, 37183543, 86543538, 39171895, 53666583, 26275734, 82052050, 52293995, 76703609, 26063929, 16019925, 84904436, 65081429, 26507214, 62740044, 7955293, 19101477, 91957544, 38365584, 37560164, 92692283, 77620120, 92541302, 34295794, 98371444, 51047803, 54868730, 112651, 92998591, 42692881, 5073754, 81172706, 7646095, 51426311, 99965001, 18783702, 58751351, 16971929, 91664334, 16054533, 82651278, 4091162, 54232247, 49271185, 92033260, 14363867, 16684829, 56153345, 39201414, 10264691, 95726235, 20645197, 20002147, 73021291, 70420215, 9603598, 38256849, 33201905, 31727379, 68939068, 8128637, 96709982, 52261574, 99224392, 48774913, 7502255, 40534591, 32151165, 96318094, 66832478, 91255408, 6521313, 67124282, 321665, 72358170, 16387575, 39553046, 15148031, 26664538, 9599614, 84349107, 83210802, 90310261, 51588015, 99549373, 4806458, 20642888, 94090109, 69579137, 47361209, 57961282, 75820087, 44348328, 98462867, 27185644, 93359396, 87160386, 40865610, 91141395, 93270084, 66250369, 36312813, 28796059, 82427263, 81805959, 98943869, 70782102, 1788101, 91990218, 57393458, 84406788, 47893286, 6157724, 65454636, 18466635, 34667286, 97011160, 89996536, 30764367, 64098930, 14723410, 98130363, 24591705, 20681921, 53547802, 65338021, 70596786, 78884452, 69605283, 20486294, 82178706, 30395570, 24619760, 176257, 64602895, 88130087, 30569392, 92554120, 92867155, 60581278, 82886381, 17016156, 4204661, 3233569, 72793444, 34698463, 79880247, 72614359, 44847298, 35192533, 61815223, 39847321, 22766820, 66885828, 4099191, 41442762, 39373729, 71333116, 29834512, 14045383, 90004325, 30811010, 37659250, 50668599, 29994197, 51466049, 99524975, 78300864, 4668450, 98948034, 20140249, 99333375, 50567636, 67513640, 45848907, 57803235, 46870723, 4064751, 30771409, 46540998, 94595668, 30163921, 22721500, 2717150, 65017137, 88904910, 44627776, 29065964, 77300457, 18833224, 99917599, 44481640, 36139942, 76825057, 14947650, 26493119, 45617087, 97281847, 80251430, 78218845, 26863229, 21001913, 90013093, 17068582, 84840549, 68102437, 75128745, 3487592, 58749, 95581843, 28787861, 3880712, 508198, 4515343, 6038457, 9259676, 20568363, 8651647, 67084351, 42237907, 30366150, 13862149, 64848072, 44915235, 20885148, 17365188, 94539824, 38008118, 1197320, 15015906, 86460488, 54263880, 61373987, 70727211, 31733363, 5482538, 45990383, 13819358, 55850790, 89804152, 98653983, 30463802, 83948335, 15535065, 43322743, 29029316, 2204165, 37620363, 6505939, 22113133, 59329511, 77787724, 52204879, 36135, 57359924, 18699206, 65074535, 29401781, 85571389, 73786944, 64055960, 83083131, 11757872, 77187825, 83269727, 26744917, 37192445, 89078848, 17081350, 82726008, 82897371, 62693428, 45049308, 526217, 65038678, 10366309, 60176618, 83651211, 79942022, 94516935, 22435353, 18001617, 86001008, 3088684, 66045587, 79922758, 91802888, 95010552, 37280276, 92398073, 44844121, 99604946, 20867149, 87598888, 99861373, 76170907, 98648327, 68000591, 62552524, 9575954, 39801351, 62051033, 29932657, 23134715, 6111563, 2331773, 84002370, 1239555, 49597667, 45428665, 69786756, 74724075, 18663507, 37501808, 47090124, 8791066, 7182642, 77898274, 91938191, 1204161, 60393039, 824872, 61982238, 55669657, 36780454, 14220886, 8696647, 45075471, 44842615, 72725103, 22405120, 16405341, 44473167, 63372756, 65271999, 66667729, 26392416, 89046466, 1569515, 8729146, 30090481, 38341669, 82979980, 55189057, 40197395, 36685023, 70367851, 51464002, 18504197, 34044787, 2607799, 43376279, 44479073, 9257405, 18806856, 62762857, 65851721, 71083565, 13470059, 26998766, 58224549, 96735716, 30694952, 55470718, 77272628, 69848388, 53648154, 3773993, 93790285, 66428795, 33565483, 54517921, 93057697, 71965942, 49882705, 8055981, 42782652, 60106217, 36933038, 53802686, 84166196, 53393358, 89499542, 40984766, 33061250, 44177011, 38022834, 31126490, 99515901, 53888755, 91831487, 96906410, 12664567, 21397057 +3183975, 20836893, 61982238, 45049308, 9623492, 96420429, 89419466, 37675718, 4798568, 61741594, 83302115, 83083131, 40781854, 86821229, 82339363, 82327024, 69136837, 2891150, 29510992, 38510840, 22450468, 78785507, 34698428, 3088684, 82427263, 79922758, 57393458, 99604946, 20867149, 31733363, 19101477, 6793819, 7646095, 90004325, 39201414, 39986008, 96318094, 44983451, 2717150, 6521313, 8115266, 90158785, 72725103, 76540481, 8373289, 3509435, 83133790, 49882705, 75153252, 3487592, 10961421, 8055981, 79657802, 91802888, 6038457, 51315460, 4978543, 98130363, 20681921, 70596786, 68110330, 70727211, 13819358, 77301523, 26275734, 30543215, 11543098, 30463802, 48892201, 49597667, 42692881, 73109997, 77787724, 71333116, 91664334, 14045383, 54232247, 33935899, 91938191, 65047700, 45919976, 6819644, 30218878, 73021291, 72357096, 86022504, 89699445, 99333375, 15536795, 22129328, 14349098, 5832946, 97057187, 91281584, 65038678, 61960400, 26664538, 38645117, 70800879, 27411561, 35996293, 91727510, 80316608, 26493119, 18001617, 13231279, 44348328, 17957593, 68102437, 99658235, 91141395, 73235980, 38494874, 9860968, 95290172, 36933038, 51472275, 36189527, 20885148, 45794415, 55770687, 44844121, 12416768, 92071990, 68875490, 10649306, 65275241, 73168565, 47887470, 89804152, 82052050, 52293995, 65081429, 35192533, 57163802, 98371444, 32426752, 112651, 63967300, 51464002, 55960386, 33699435, 33237508, 27041967, 55143724, 81774825, 52060076, 20122224, 99021067, 23194618, 10264691, 16567550, 12024238, 1375023, 43152977, 74441448, 78300864, 20002147, 67030811, 824872, 57658654, 79322415, 26292919, 36396314, 82779622, 7502255, 6871053, 22721500, 321665, 72358170, 99125126, 44846932, 32161669, 61859581, 10366309, 99549373, 45407418, 97940276, 68041839, 90272749, 80251430, 88698958, 96852321, 3233084, 25842078, 33304202, 90013093, 43357947, 28550822, 61623915, 28796059, 53842979, 4515343, 60430369, 91831487, 85023028, 14326617, 42237907, 84406788, 10358899, 47893286, 67451935, 92398073, 32250045, 27325428, 17365188, 89996536, 54663246, 64098930, 14723410, 35456853, 62232211, 86460488, 33061250, 89046466, 61373987, 76170907, 48360198, 30090481, 75407347, 30569392, 86301513, 8129978, 98739783, 92554120, 92867155, 55850790, 61263068, 77312810, 569864, 72793444, 42199455, 40197395, 36685023, 26063929, 16019925, 66322959, 79880247, 46851987, 26507214, 37739481, 61815223, 415901, 39847321, 9188443, 12303248, 66885828, 44245960, 37620363, 36808486, 23740167, 56473732, 7011964, 95957797, 52204879, 7182642, 59910624, 49641577, 24915585, 69355476, 19272365, 65074535, 29994197, 37435892, 4668450, 77284619, 59501487, 96193415, 70420215, 11757872, 41092102, 33565483, 37192445, 12664567, 57240218, 17386996, 40686254, 96709982, 1250437, 17727650, 29819940, 71657078, 46540998, 54058788, 40152546, 65851721, 73031054, 67793644, 30163921, 4985896, 44550764, 11365791, 2917920, 36812683, 57020481, 16387575, 526217, 20642888, 85242963, 83533741, 93053405, 4434662, 29516592, 78218845, 69579137, 47361209, 76315420, 8099994, 72019362, 53084256, 62357986, 45237957, 36312813, 3880712, 74137926, 26114953, 59981773, 30366150, 22200378, 6497830, 15075176, 9860195, 34667286, 90061527, 94539824, 32159704, 59614746, 1197320, 15015906, 89499542, 53648154, 73222868, 81898046, 21673260, 44177011, 1569515, 70004753, 93790285, 37224844, 12571310, 19486173, 5482538, 22108665, 62552524, 45990383, 47213183, 66116458, 7423788, 39801351, 38341669, 29959549, 89217461, 33553959, 93933709, 42967683, 86543538, 94360702, 39171895, 98653983, 17058722, 8913721, 30653863, 84002370, 78909561, 91957544, 38365584, 37560164, 92692283, 77620120, 23110625, 72738685, 99690194, 92998591, 43322743, 77694700, 81172706, 71469330, 7066775, 34044787, 22113133, 5970607, 74357852, 90654836, 27625735, 24314885, 18806856, 98948034, 20140249, 66663942, 56424103, 77072625, 77187825, 37166608, 62428472, 45848907, 45996863, 6986898, 4064751, 48774913, 10453030, 59318837, 16380211, 34432810, 82897371, 9398733, 9886593, 36753250, 77300457, 18833224, 43506672, 32274392, 45075471, 31904591, 68824981, 24733232, 44842615, 4787945, 51588015, 4806458, 41092172, 9058407, 26102057, 45790169, 44473167, 33431960, 21533347, 57961282, 69641513, 84684495, 26863229, 86001008, 19939935, 71965942, 58749, 54762643, 7104732, 66250369, 68702632, 508198, 2208785, 78602717, 23569917, 60106217, 75229462, 95010552, 67084351, 63628376, 37280276, 48675329, 44915235, 17976208, 53802686, 15948937, 72495719, 81677380, 62430984, 15163258, 84166196, 38008118, 17857111, 24591705, 53393358, 38658347, 82178706, 62490109, 40984766, 24619760, 176257, 87598888, 15064655, 67031644, 98648327, 44784505, 3235882, 42644903, 31161687, 62803858, 61728685, 6111563, 7300047, 53666583, 58180653, 55189057, 34698463, 63506281, 80555751, 7955293, 83948335, 92541302, 29188588, 9175338, 85116755, 18411915, 89214616, 12348118, 37501808, 93562257, 6808825, 47090124, 17146629, 58751351, 68644627, 59329511, 2607799, 36135, 72732205, 92692978, 1204161, 66428795, 16684829, 56153345, 50668599, 51466049, 1431742, 60955663, 67474219, 5822202, 99226875, 97783876, 33201905, 83269727, 68939068, 77413012, 57803235, 17385531, 99515901, 47738185, 91255408, 14220886, 90090964, 74743862, 8696647, 56531125, 65017137, 44627776, 5267545, 83210802, 22405120, 26139110, 75543508, 59371804, 33797252, 97561745, 17539625, 81855445, 98462867, 72274002, 17068582, 90457870, 40865610, 33895336, 49328608, 62115552, 9259676, 47792865, 36468541, 70782102, 61271144, 88444207, 36580610, 91990218, 13862149, 64848072, 9829782, 4199704, 59405277, 65454636, 10309525, 22997281, 14626618, 68128438, 34236719, 6525948, 26734892, 3633375, 32699744, 6153406, 78884452, 22879907, 22942635, 64087743, 99861373, 79738755, 55753905, 24826575, 76434144, 13348726, 61928316, 9575954, 52613508, 43045786, 29932657, 60581278, 82886381, 33123618, 1022677, 16097038, 48088883, 3233569, 76671482, 76703609, 13468268, 874791, 84293052, 44847298, 1239555, 34295794, 15535065, 16099750, 7685448, 51047803, 54868730, 42073124, 22766820, 29029316, 71522968, 18663507, 63152504, 6505939, 79136082, 18504197, 70541760, 86118021, 4099191, 99297164, 89811711, 71316369, 38022834, 43376279, 32058615, 7517032, 77898274, 61859143, 4091162, 28851716, 37659250, 4119747, 82532312, 27665211, 14363867, 44479073, 50188404, 87720882, 72238278, 94711842, 31491938, 56484900, 55247455, 64055960, 86242799, 9603598, 24168411, 87710366, 26744917, 53632373, 52261574, 99224392, 34946859, 97641116, 72278539, 48893685, 69697787, 67124282, 89637706, 29065964, 43501211, 71083565, 83368048, 92530431, 44481640, 15148031, 93057697, 19376156, 84349107, 59109400, 76825057, 23432750, 35512853, 17764950, 16405341, 4095116, 79942022, 94516935, 14947650, 22435353, 13173644, 58208470, 21001913, 27185644, 35092039, 16445503, 87160386, 84840549, 88251446, 11923835, 65271999, 66611759, 48395186, 88653118, 54199160, 26776922, 30998561, 96735716, 42782652, 57248122, 81805959, 30694952, 33628349, 66903004, 42947632, 14093520, 1788101, 26397786, 49236559, 749283, 51590803, 77272628, 61712234, 67227442, 16595436, 69605283, 85695762, 80246713, 3773993, 54263880, 30787683, 8729146, 75135448, 59197747, 65880522, 64602895, 78589145, 20765474, 75777973, 17016156, 37183543, 4204661, 85117092, 42307449, 72614359, 36845587, 62936963, 32590267, 29635537, 71300104, 70372191, 59177718, 44889423, 47708850, 51426311, 25636669, 8791066, 29466406, 31126490, 88047921, 82651278, 57359924, 74110882, 79806380, 86798033, 92033260, 72777973, 85571389, 40356877, 24953498, 20645197, 14731700, 3294781, 55669657, 38256849, 11581181, 50567636, 8128637, 84127901, 83155442, 17081350, 82726008, 46870723, 44060493, 32151165, 79821897, 168541, 53888755, 54987042, 45306070, 5111370, 62452034, 61141874, 54517921, 39553046, 79191827, 99917599, 93566986, 23848565, 40677414, 83651211, 66137019, 45617087, 97281847, 8634541, 57241521, 94090109, 75820087, 17894977, 26998766, 58224549, 44664587, 95581843, 28787861, 20568363, 75052463, 83150534, 26392416, 70036438, 28734791, 6157724, 11161731, 97011160, 30764367, 69848388, 81853704, 65715134, 65338021, 21070110, 20486294, 30891921, 15902805, 8807940, 30395570, 40027975, 78561158, 69255765, 5640302, 62051033, 21289531, 82979980, 23134715, 51149804, 99729357, 48260151, 51507425, 73617245, 2331773, 36505482, 73124510, 75986488, 45428665, 48658605, 33249630, 27187213, 83747892, 4508700, 41442762, 16971929, 18131876, 85711894, 97379790, 16424199, 68316156, 18699206, 7687278, 63015256, 76960303, 4069912, 68204242, 9257405, 95726235, 60393039, 62496012, 34497327, 74452589, 54606384, 36780454, 31727379, 17037369, 84187166, 56955985, 76330843, 21397057, 30771409, 247198, 10597197, 50806615, 61897582, 669105, 56515456, 88904910, 95251277, 74614639, 90310261, 33336148, 45667668, 30139692, 92215320, 51715482, 93359396, 6221471, 78549759, 8651647, 98943869, 81274566, 54014062, 37957788, 18466635, 34493392, 7919588, 73392814, 21993752, 38009615, 38061439, 64157906, 95395112, 43933006, 60102965, 54427233, 84904436, 55615885, 92604458, 69786756, 5073754, 2204165, 41245325, 99965001, 78766358, 16054533, 71920426, 72373496, 73786944, 8733068, 47298834, 66319530, 36930650, 89078848, 40534591, 45995325, 37891451, 94076128, 99971982, 92353856, 68694897, 62693428, 94911072, 19457589, 9599614, 13470059, 48673079, 19891772, 28928175, 75128745, 63372756, 66667729, 55602660, 7229550, 1936762, 55470718, 21919959, 53547802, 19898053, 77377183, 63059024, 41380093, 96906410, 74724075, 70367851, 67281495, 50007421, 56461322, 39373729, 29834512, 41481685, 30811010, 29401781, 62762857, 67513640, 45381876, 50702367, 94595668, 92803766, 93270084, 68816413, 24058273, 62740044, 18783702, 96726697, 40781449, 49271185, 99524975, 66832478, 36139942, 60176618, 92787493, 49598724, 66045587, 93515664, 68000591, 80014588, 88130087, 66271566, 91240048, 83789391 +23110625, 78561158, 92692283, 87710366, 94911072, 24591705, 63015256, 98462867, 25842078, 55602660, 38341669, 77312810, 39171895, 58180653, 84002370, 74724075, 56473732, 16971929, 4069912, 31727379, 65851721, 73031054, 19457589, 59109400, 60176618, 27185644, 17894977, 16445503, 66611759, 61271144, 15075176, 32250045, 72495719, 54663246, 77377183, 42644903, 72793444, 37560164, 29029316, 17146629, 71316369, 76960303, 98948034, 17037369, 53632373, 17386996, 4985896, 2717150, 33336148, 75820087, 26863229, 17957593, 22450468, 10961421, 68702632, 62115552, 90061527, 14626618, 38008118, 81853704, 86460488, 61373987, 40027975, 61928316, 45990383, 31161687, 1022677, 3233569, 17058722, 51149804, 8913721, 54427233, 874791, 79880247, 46851987, 62936963, 79136082, 91664334, 20122224, 72732205, 99524975, 37435892, 40152546, 43506672, 91240048, 83210802, 40677414, 23432750, 4787945, 35512853, 17764950, 48673079, 21533347, 17068582, 51715482, 26998766, 49882705, 75153252, 53084256, 3183975, 81805959, 78549759, 81274566, 89419466, 70782102, 91990218, 4199704, 67451935, 11161731, 15948937, 14723410, 26734892, 3633375, 62232211, 22942635, 8807940, 38061439, 76434144, 13348726, 75407347, 68875490, 82886381, 61263068, 77301523, 37675718, 48088883, 30543215, 16019925, 13468268, 84293052, 36505482, 7955293, 83948335, 72738685, 6793819, 12348118, 44889423, 25636669, 29466406, 29834512, 74110882, 91938191, 87720882, 68204242, 23194618, 85571389, 8733068, 12024238, 56484900, 6819644, 73021291, 66663942, 74452589, 77187825, 38256849, 99333375, 57803235, 6986898, 22129328, 99224392, 54987042, 86821229, 22721500, 44550764, 11365791, 18833224, 79191827, 99917599, 68824981, 82327024, 61859581, 10366309, 23848565, 90158785, 4806458, 9058407, 4095116, 97940276, 93053405, 68041839, 29510992, 47361209, 81855445, 92215320, 3509435, 38510840, 43357947, 68102437, 40865610, 3487592, 54762643, 28787861, 33895336, 26776922, 30998561, 23569917, 9259676, 9860968, 85023028, 47792865, 75229462, 36468541, 84406788, 63628376, 48675329, 37957788, 59405277, 10309525, 77272628, 94539824, 32159704, 98130363, 1197320, 16595436, 35456853, 81898046, 89046466, 70727211, 31733363, 64157906, 30090481, 22108665, 9575954, 98739783, 20765474, 29932657, 82979980, 23134715, 66271566, 66322959, 37739481, 48892201, 38365584, 92541302, 45428665, 9188443, 18411915, 69786756, 42692881, 23740167, 55960386, 47090124, 41442762, 39373729, 89811711, 38022834, 97379790, 61859143, 24915585, 79806380, 49271185, 56153345, 72373496, 39201414, 95726235, 47298834, 78300864, 66319530, 57658654, 9603598, 50567636, 45381876, 26292919, 36396314, 99515901, 14349098, 46870723, 21397057, 79821897, 37891451, 94076128, 34432810, 97057187, 72278539, 90090964, 8696647, 61141874, 29065964, 91281584, 45075471, 32161669, 93057697, 36139942, 19376156, 8115266, 83651211, 26102057, 85242963, 83533741, 26139110, 80316608, 22435353, 4434662, 44473167, 44348328, 78785507, 61623915, 93270084, 66250369, 82427263, 30694952, 33628349, 51315460, 38494874, 66903004, 60106217, 42947632, 14093520, 95010552, 88444207, 1788101, 54014062, 17976208, 18466635, 34667286, 27325428, 89996536, 6153406, 53393358, 70596786, 73392814, 78884452, 89499542, 55770687, 21993752, 64087743, 99604946, 87598888, 59197747, 24826575, 64602895, 88130087, 78589145, 8129978, 7423788, 5640302, 92554120, 16097038, 43933006, 26275734, 36685023, 42307449, 76703609, 80014588, 16099750, 70372191, 42073124, 63967300, 47708850, 81172706, 7646095, 99965001, 7011964, 8791066, 27041967, 74357852, 96726697, 85711894, 16424199, 41481685, 32058615, 68316156, 77898274, 52060076, 30811010, 28851716, 82532312, 27665211, 1204161, 65047700, 65074535, 16684829, 50188404, 99021067, 29401781, 45919976, 1375023, 60955663, 3294781, 96193415, 79322415, 37166608, 33201905, 11581181, 84187166, 68939068, 8128637, 15536795, 96709982, 17385531, 52261574, 48774913, 29819940, 10597197, 99971982, 30163921, 61897582, 669105, 6521313, 9398733, 321665, 72358170, 16387575, 526217, 95251277, 39553046, 83368048, 92530431, 92803766, 51588015, 16405341, 79942022, 2891150, 49598724, 45667668, 29516592, 8373289, 33431960, 94090109, 13173644, 13231279, 84684495, 96852321, 83133790, 72274002, 8099994, 11923835, 62357986, 3088684, 95581843, 96420429, 57248122, 79922758, 78602717, 20568363, 75052463, 68816413, 55470718, 67084351, 42237907, 10358899, 49236559, 37280276, 9829782, 51472275, 36189527, 44915235, 68128438, 6525948, 85695762, 38658347, 30891921, 15902805, 33061250, 3773993, 54263880, 30787683, 93790285, 8729146, 55753905, 62552524, 52613508, 92071990, 30569392, 61728685, 33123618, 21289531, 86543538, 7300047, 82052050, 52293995, 84904436, 30653863, 44847298, 35192533, 61815223, 32590267, 77620120, 34295794, 15535065, 29635537, 96906410, 32426752, 92998591, 12303248, 44245960, 73109997, 6505939, 99297164, 58751351, 68644627, 95957797, 71333116, 43376279, 88047921, 55143724, 16054533, 59910624, 40781449, 24314885, 86798033, 72777973, 40356877, 94711842, 55247455, 20645197, 40781854, 4668450, 67030811, 30218878, 59501487, 11757872, 36930650, 97783876, 86022504, 89699445, 67513640, 45996863, 40686254, 83155442, 17081350, 47738185, 44060493, 30771409, 54058788, 32151165, 96318094, 45995325, 67793644, 168541, 50806615, 97641116, 74743862, 48893685, 69697787, 2917920, 67124282, 5111370, 56531125, 9886593, 44627776, 65038678, 74614639, 32274392, 93566986, 82339363, 15148031, 24733232, 44842615, 76825057, 70800879, 59371804, 45790169, 26493119, 45617087, 18001617, 97561745, 57241521, 80251430, 69579137, 17539625, 58208470, 88698958, 86001008, 71965942, 33304202, 35092039, 93359396, 91141395, 6221471, 9623492, 36312813, 79657802, 49328608, 508198, 2208785, 91802888, 6038457, 74137926, 8651647, 98943869, 1936762, 83150534, 95290172, 57393458, 30366150, 64848072, 47893286, 70036438, 6497830, 4978543, 65454636, 62430984, 84166196, 69848388, 17857111, 15015906, 24058273, 65338021, 45794415, 20486294, 73222868, 22879907, 44844121, 80246713, 68110330, 30395570, 44177011, 1569515, 20867149, 79738755, 37224844, 76170907, 48360198, 47213183, 63059024, 43045786, 10649306, 95395112, 65275241, 62051033, 75777973, 37183543, 60102965, 55189057, 76671482, 11543098, 85117092, 34698463, 63506281, 51507425, 55615885, 36845587, 415901, 41380093, 29188588, 9175338, 7685448, 71300104, 89214616, 92604458, 54868730, 112651, 33249630, 27187213, 70367851, 2204165, 93562257, 6808825, 18504197, 41245325, 51426311, 86118021, 4099191, 33699435, 4508700, 22113133, 2607799, 78766358, 7517032, 57359924, 4091162, 90654836, 69355476, 7687278, 66428795, 92033260, 50668599, 51466049, 83302115, 67474219, 77284619, 824872, 34497327, 77072625, 62762857, 41092102, 62428472, 33565483, 39986008, 45848907, 1250437, 76330843, 5832946, 34946859, 66832478, 91255408, 82897371, 92353856, 45306070, 56515456, 88904910, 36753250, 45049308, 26664538, 38645117, 99549373, 41092172, 20642888, 76540481, 66137019, 91727510, 75543508, 90272749, 69641513, 3233084, 90457870, 84840549, 75128745, 34698428, 65271999, 73235980, 66667729, 28796059, 66045587, 26114953, 14326617, 93515664, 92398073, 20885148, 53802686, 81677380, 22997281, 34493392, 64098930, 20836893, 38009615, 12416768, 70004753, 12571310, 75135448, 5482538, 66116458, 62803858, 73168565, 47887470, 89217461, 93933709, 89804152, 99729357, 26507214, 80555751, 19101477, 61741594, 91957544, 49597667, 75986488, 85116755, 98371444, 48658605, 51047803, 22766820, 43322743, 71522968, 18663507, 71469330, 37620363, 51464002, 83747892, 7066775, 56461322, 33237508, 18131876, 31126490, 14045383, 27625735, 44479073, 72238278, 9257405, 16567550, 60393039, 31491938, 43152977, 24953498, 64055960, 1431742, 86242799, 20002147, 62496012, 5822202, 99226875, 61982238, 55669657, 24168411, 26744917, 56955985, 12664567, 77413012, 57240218, 17727650, 71657078, 59318837, 6871053, 16380211, 62693428, 65017137, 44846932, 36812683, 77300457, 61960400, 71083565, 54517921, 31904591, 84349107, 69136837, 13470059, 72725103, 22405120, 27411561, 33797252, 19891772, 21001913, 90013093, 76315420, 87160386, 99658235, 28928175, 28550822, 63372756, 8055981, 45237957, 3880712, 4515343, 60430369, 59981773, 26397786, 36580610, 22200378, 749283, 51590803, 30764367, 34236719, 19898053, 40984766, 83789391, 99861373, 15064655, 68000591, 44784505, 69255765, 39801351, 13819358, 17016156, 569864, 42967683, 6111563, 94360702, 98653983, 42199455, 26063929, 62740044, 4798568, 78909561, 30463802, 73124510, 39847321, 66885828, 5073754, 77694700, 37501808, 70541760, 34044787, 59329511, 52204879, 49641577, 82651278, 4119747, 92692978, 33935899, 19272365, 10264691, 83083131, 14731700, 72357096, 70420215, 54606384, 37192445, 89078848, 82779622, 7502255, 40534591, 10453030, 247198, 44983451, 68694897, 62452034, 99125126, 9599614, 35996293, 94516935, 14947650, 97281847, 78218845, 57961282, 88251446, 88653118, 7104732, 44664587, 53842979, 42782652, 91831487, 21919959, 28734791, 9860195, 6157724, 17365188, 97011160, 59614746, 7919588, 67227442, 32699744, 53547802, 21070110, 176257, 98648327, 55850790, 33553959, 4204661, 53666583, 73617245, 2331773, 57163802, 36808486, 63152504, 18783702, 50007421, 77787724, 7182642, 36135, 81774825, 54232247, 18699206, 29994197, 74441448, 56424103, 36780454, 84127901, 50702367, 82726008, 94595668, 53888755, 89637706, 43501211, 90310261, 45407418, 8634541, 19939935, 72019362, 58749, 48395186, 7229550, 61712234, 20681921, 65715134, 69605283, 53648154, 19486173, 65880522, 3235882, 40197395, 1239555, 99690194, 67281495, 90004325, 71920426, 14363867, 73786944, 20140249, 83269727, 4064751, 46540998, 14220886, 57020481, 5267545, 30139692, 58224549, 54199160, 36933038, 13862149, 15163258, 82178706, 21673260, 62490109, 92867155, 60581278, 48260151, 65081429, 18806856, 92787493, 96735716, 26392416, 24619760, 67031644, 86301513, 29959549, 59177718, 37659250, 44481640, 72614359, 5970607 +7300047, 874791, 9398733, 65338021, 3233569, 56484900, 33336148, 62357986, 22200378, 5640302, 92692978, 63015256, 33565483, 57803235, 526217, 60176618, 8115266, 3487592, 30998561, 20568363, 94539824, 54663246, 1197320, 3235882, 26063929, 62740044, 37560164, 27187213, 23740167, 97379790, 29401781, 12024238, 89078848, 82779622, 11365791, 23848565, 19376156, 9259676, 60106217, 36189527, 4978543, 76170907, 64602895, 7423788, 1022677, 76671482, 42307449, 30653863, 39847321, 56153345, 37435892, 98948034, 68939068, 99224392, 65851721, 67793644, 44983451, 69697787, 62452034, 22405120, 9058407, 70800879, 75543508, 47361209, 21001913, 35092039, 22450468, 11923835, 58224549, 96735716, 91802888, 7229550, 42237907, 15948937, 34667286, 68128438, 34493392, 89046466, 61928316, 82979980, 72793444, 13468268, 54427233, 79880247, 55615885, 38365584, 49597667, 92692283, 9175338, 22766820, 55143724, 57359924, 37659250, 1204161, 86798033, 9257405, 29994197, 20645197, 73021291, 89699445, 45381876, 84127901, 4064751, 40534591, 40152546, 6521313, 2917920, 67124282, 45049308, 18833224, 54517921, 93057697, 83210802, 40677414, 4787945, 20642888, 76540481, 83533741, 22435353, 45617087, 8373289, 29510992, 72274002, 53084256, 93270084, 60430369, 51315460, 13862149, 4199704, 65454636, 53802686, 15015906, 85695762, 38658347, 62232211, 1569515, 40027975, 12571310, 59197747, 65880522, 64157906, 66116458, 65275241, 31161687, 62051033, 569864, 89217461, 16097038, 36685023, 66271566, 85117092, 80014588, 65081429, 30463802, 61815223, 19101477, 32590267, 96906410, 18504197, 91664334, 68316156, 24314885, 4069912, 87720882, 31491938, 9603598, 24168411, 37166608, 50702367, 14349098, 46870723, 46540998, 54058788, 72278539, 56515456, 88904910, 44627776, 36753250, 82327024, 5267545, 13470059, 90158785, 72725103, 16405341, 57241521, 78218845, 21533347, 3509435, 33304202, 17894977, 17068582, 16445503, 68102437, 45237957, 53842979, 42782652, 78549759, 70782102, 36933038, 47893286, 67451935, 17976208, 16595436, 53648154, 81898046, 62490109, 3773993, 61373987, 79738755, 88130087, 62552524, 9575954, 44784505, 47213183, 68875490, 21289531, 77312810, 37183543, 4204661, 82052050, 4798568, 44847298, 1239555, 83948335, 73124510, 98371444, 51047803, 70372191, 112651, 63967300, 66885828, 12348118, 18663507, 83747892, 56473732, 71316369, 18131876, 43376279, 74357852, 14045383, 36135, 20122224, 27625735, 54232247, 71920426, 82532312, 39201414, 95726235, 83302115, 60393039, 47298834, 55247455, 83083131, 60955663, 77284619, 59501487, 70420215, 74452589, 11581181, 56955985, 36396314, 83155442, 17081350, 44060493, 5832946, 10453030, 168541, 50806615, 66832478, 91255408, 2717150, 8696647, 94911072, 99917599, 82339363, 15148031, 61859581, 91240048, 38645117, 45667668, 69579137, 81855445, 69641513, 86001008, 3233084, 38510840, 90013093, 87160386, 88251446, 91141395, 6221471, 66611759, 7104732, 66250369, 36312813, 33895336, 66045587, 96420429, 30694952, 33628349, 75052463, 98943869, 9860968, 81274566, 55470718, 75229462, 14326617, 36580610, 9860195, 59405277, 72495719, 17365188, 14626618, 62430984, 59614746, 81853704, 19898053, 21993752, 21673260, 30787683, 87598888, 5482538, 98648327, 68000591, 45990383, 69255765, 86301513, 39801351, 92554120, 92867155, 75777973, 55850790, 47887470, 37675718, 30543215, 52293995, 66322959, 72614359, 62936963, 61741594, 15535065, 45428665, 29635537, 48658605, 54868730, 69786756, 12303248, 29029316, 44889423, 2204165, 7646095, 37501808, 36808486, 79136082, 18783702, 56461322, 99297164, 52204879, 2607799, 96726697, 49641577, 24915585, 28851716, 27665211, 33935899, 65074535, 76960303, 16684829, 99021067, 85571389, 40356877, 99226875, 96193415, 11757872, 77187825, 55669657, 38256849, 87710366, 36780454, 15536795, 26292919, 1250437, 52261574, 82726008, 48774913, 21397057, 45995325, 99971982, 61897582, 74743862, 68694897, 62693428, 89637706, 9886593, 36812683, 19457589, 91281584, 16387575, 43501211, 26664538, 35512853, 17764950, 94516935, 66137019, 91727510, 2891150, 18001617, 44473167, 58208470, 75820087, 98462867, 25842078, 8099994, 51715482, 26998766, 49882705, 28550822, 75128745, 58749, 10961421, 65271999, 66667729, 28796059, 3880712, 57248122, 62115552, 3183975, 26114953, 1936762, 85023028, 21919959, 36468541, 1788101, 26392416, 63628376, 64848072, 749283, 44915235, 77272628, 97011160, 32159704, 69848388, 98130363, 26734892, 24591705, 20681921, 53393358, 73222868, 44844121, 68110330, 86460488, 83789391, 8729146, 19486173, 48360198, 13348726, 52613508, 8129978, 43045786, 98739783, 13819358, 42644903, 29932657, 60581278, 33123618, 29959549, 93933709, 43933006, 53666583, 58180653, 40197395, 11543098, 51149804, 76703609, 16019925, 73617245, 46851987, 80555751, 7955293, 41380093, 77620120, 34295794, 72738685, 71300104, 89214616, 99690194, 81172706, 71522968, 6808825, 67281495, 55960386, 17146629, 34044787, 89811711, 38022834, 77787724, 71333116, 78766358, 7182642, 85711894, 81774825, 82651278, 4091162, 65047700, 92033260, 44479073, 10264691, 16567550, 51466049, 18806856, 86242799, 40781854, 67474219, 72357096, 34497327, 77072625, 97783876, 31727379, 84187166, 45996863, 12664567, 53632373, 6986898, 40686254, 17385531, 99515901, 22129328, 17727650, 47738185, 29819940, 30771409, 71657078, 32151165, 96318094, 247198, 94595668, 97057187, 86821229, 4985896, 14220886, 44550764, 48893685, 321665, 65017137, 77300457, 45075471, 83368048, 32161669, 10366309, 36139942, 90310261, 59109400, 76825057, 4434662, 49598724, 29516592, 57961282, 71965942, 27185644, 43357947, 93359396, 84840549, 78785507, 48395186, 54762643, 3088684, 68702632, 82427263, 6038457, 74137926, 95290172, 88444207, 26397786, 6497830, 93515664, 37957788, 27325428, 90061527, 81677380, 30764367, 38008118, 64098930, 7919588, 61712234, 73392814, 89499542, 55770687, 20486294, 12416768, 64087743, 24619760, 54263880, 70004753, 31733363, 15064655, 55753905, 24826575, 78589145, 76434144, 30090481, 30569392, 20765474, 38341669, 73168565, 77301523, 86543538, 98653983, 34698463, 48260151, 84904436, 2331773, 26507214, 78909561, 37739481, 23110625, 92541302, 6793819, 16099750, 18411915, 7685448, 59177718, 92998591, 33249630, 5073754, 77694700, 74724075, 6505939, 51464002, 51426311, 99965001, 47090124, 7011964, 4508700, 22113133, 29834512, 16971929, 16054533, 52060076, 90004325, 4119747, 74110882, 49271185, 7687278, 66428795, 14363867, 94711842, 74441448, 64055960, 78300864, 14731700, 20002147, 6819644, 67030811, 62496012, 66663942, 54606384, 62428472, 26744917, 50567636, 37192445, 45848907, 77413012, 57240218, 6871053, 94076128, 53888755, 54987042, 22721500, 90090964, 92353856, 45306070, 5111370, 56531125, 99125126, 44846932, 61141874, 29065964, 95251277, 65038678, 32274392, 92530431, 93566986, 68824981, 44481640, 44842615, 84349107, 83651211, 51588015, 99549373, 92787493, 26102057, 79942022, 35996293, 45790169, 97561745, 90272749, 33431960, 17539625, 92215320, 88698958, 96852321, 26863229, 19939935, 17957593, 83133790, 72019362, 40865610, 61623915, 75153252, 34698428, 88653118, 73235980, 28787861, 508198, 2208785, 79922758, 55602660, 81805959, 23569917, 38494874, 91831487, 68816413, 59981773, 83150534, 95010552, 61271144, 91990218, 30366150, 10358899, 48675329, 28734791, 6157724, 20885148, 18466635, 51590803, 22997281, 89996536, 34236719, 6525948, 14723410, 32699744, 53547802, 45794415, 70596786, 22942635, 30891921, 15902805, 44177011, 20867149, 75135448, 22108665, 78561158, 10649306, 95395112, 61263068, 6111563, 26275734, 55189057, 99729357, 63506281, 36845587, 9188443, 92604458, 42073124, 70367851, 71469330, 37620363, 73109997, 70541760, 7066775, 33699435, 41442762, 58751351, 5970607, 31126490, 41481685, 7517032, 77898274, 69355476, 91938191, 23194618, 72777973, 72238278, 4668450, 3294781, 824872, 61982238, 62762857, 86022504, 83269727, 17386996, 96709982, 76330843, 34946859, 59318837, 10597197, 34432810, 30163921, 82897371, 72358170, 57020481, 43506672, 74614639, 71083565, 4806458, 48673079, 27411561, 97940276, 26139110, 33797252, 26493119, 97281847, 44348328, 84684495, 76315420, 99658235, 28928175, 95581843, 78602717, 66903004, 54014062, 84406788, 9829782, 51472275, 70036438, 15075176, 92398073, 17857111, 20836893, 3633375, 78884452, 22879907, 8807940, 40984766, 38009615, 38061439, 70727211, 99861373, 77377183, 92071990, 61728685, 17016156, 42967683, 94360702, 39171895, 60102965, 35192533, 415901, 75986488, 57163802, 85116755, 43322743, 42692881, 47708850, 93562257, 63152504, 41245325, 4099191, 8791066, 39373729, 68644627, 29466406, 27041967, 88047921, 61859143, 40781449, 30811010, 90654836, 79806380, 18699206, 19272365, 50188404, 50668599, 68204242, 73786944, 24953498, 57658654, 79322415, 41092102, 33201905, 17037369, 8128637, 37891451, 61960400, 39553046, 79191827, 31904591, 69136837, 4095116, 30139692, 8634541, 94090109, 80251430, 63372756, 8055981, 9623492, 44664587, 54199160, 42947632, 47792865, 57393458, 37280276, 11161731, 32250045, 15163258, 84166196, 65715134, 24058273, 69605283, 21070110, 35456853, 80246713, 67031644, 75407347, 62803858, 82886381, 48088883, 89804152, 42199455, 84002370, 48892201, 91957544, 29188588, 44245960, 50007421, 95957797, 16424199, 72732205, 72373496, 8733068, 1431742, 20140249, 56424103, 99333375, 39986008, 669105, 23432750, 41092172, 85242963, 14947650, 59371804, 80316608, 19891772, 90457870, 4515343, 8651647, 89419466, 14093520, 67084351, 49236559, 10309525, 67227442, 6153406, 33061250, 30395570, 93790285, 37224844, 63059024, 33553959, 23134715, 51507425, 84293052, 36505482, 33237508, 59329511, 59910624, 32058615, 45919976, 99524975, 43152977, 5822202, 36930650, 67513640, 7502255, 73031054, 97641116, 24733232, 9599614, 45407418, 13173644, 79657802, 26776922, 82178706, 99604946, 17058722, 25636669, 66319530, 30218878, 92803766, 93053405, 68041839, 13231279, 8913721, 32426752, 1375023, 16380211, 176257, 49328608, 86118021, 79821897 +22721500, 90158785, 5640302, 31161687, 11581181, 7011964, 11365791, 70782102, 28734791, 14626618, 54663246, 6525948, 44784505, 47213183, 34295794, 23740167, 94076128, 62452034, 19457589, 45049308, 98462867, 90457870, 93270084, 60430369, 13862149, 48675329, 34667286, 68128438, 61712234, 65338021, 21673260, 70727211, 78561158, 75777973, 43933006, 85117092, 874791, 84904436, 26507214, 36845587, 71522968, 40781449, 69355476, 74110882, 9398733, 36812683, 18833224, 79191827, 15148031, 26664538, 29516592, 69579137, 26863229, 17894977, 54762643, 30998561, 20568363, 83150534, 17976208, 30764367, 98130363, 89499542, 30891921, 64087743, 10649306, 47887470, 7300047, 60102965, 4204661, 51507425, 62740044, 6793819, 57163802, 18411915, 77694700, 18663507, 6808825, 99965001, 58751351, 39373729, 95957797, 43376279, 87720882, 83302115, 31491938, 24168411, 62428472, 89078848, 50702367, 96709982, 40534591, 45995325, 67793644, 2717150, 56515456, 5111370, 92803766, 59109400, 51588015, 79942022, 35996293, 75543508, 57241521, 75820087, 21001913, 16445503, 88251446, 6221471, 81805959, 30694952, 26392416, 63628376, 70036438, 32250045, 51590803, 84166196, 78884452, 69605283, 15902805, 8807940, 79738755, 12571310, 19486173, 61928316, 45990383, 39801351, 20765474, 42644903, 65275241, 62051033, 55850790, 21289531, 86543538, 94360702, 72793444, 76703609, 48260151, 54427233, 72614359, 91957544, 41380093, 23110625, 45428665, 63967300, 33249630, 27187213, 74724075, 18504197, 2607799, 41481685, 63015256, 56153345, 9257405, 45919976, 10264691, 95726235, 98948034, 56955985, 37192445, 45381876, 77413012, 57803235, 40686254, 83155442, 17385531, 4064751, 47738185, 37891451, 16380211, 94595668, 53888755, 48893685, 69697787, 62693428, 526217, 74614639, 44481640, 91240048, 45407418, 76540481, 14947650, 66137019, 91727510, 45790169, 33336148, 45617087, 47361209, 81855445, 21533347, 27185644, 22450468, 28550822, 75153252, 66667729, 28787861, 53842979, 66045587, 508198, 2208785, 62115552, 33628349, 42947632, 47792865, 9829782, 749283, 36189527, 93515664, 10309525, 18466635, 90061527, 17365188, 17857111, 35456853, 53648154, 22942635, 62490109, 99604946, 3773993, 65880522, 5482538, 13348726, 98648327, 69255765, 75407347, 66116458, 7423788, 62803858, 61728685, 33553959, 82979980, 98653983, 52293995, 26063929, 16019925, 13468268, 80014588, 79880247, 19101477, 38365584, 77620120, 9175338, 98371444, 54868730, 70372191, 37620363, 36808486, 70541760, 50007421, 96726697, 88047921, 7182642, 55143724, 97379790, 77898274, 30811010, 37659250, 27665211, 19272365, 86798033, 72238278, 72373496, 85571389, 12024238, 47298834, 99524975, 56484900, 64055960, 78300864, 86242799, 60955663, 20002147, 67474219, 30218878, 20140249, 99226875, 56424103, 70420215, 55669657, 84187166, 68939068, 67513640, 39986008, 15536795, 44060493, 5832946, 99971982, 168541, 97641116, 66832478, 669105, 44983451, 6521313, 92353856, 65038678, 54517921, 32274392, 83368048, 92530431, 31904591, 24733232, 44842615, 10366309, 69136837, 8115266, 23432750, 92787493, 9058407, 4095116, 2891150, 33797252, 49598724, 45667668, 13173644, 29510992, 57961282, 88698958, 3233084, 71965942, 72274002, 17068582, 3487592, 48395186, 66250369, 79657802, 33895336, 49328608, 96735716, 74137926, 78549759, 51315460, 89419466, 75229462, 42237907, 10358899, 4978543, 15948937, 72495719, 97011160, 59614746, 53393358, 70596786, 73392814, 22879907, 44844121, 87598888, 62552524, 9575954, 1022677, 77312810, 48088883, 26275734, 55189057, 82052050, 11543098, 51149804, 84293052, 55615885, 2331773, 80555751, 62936963, 7955293, 83948335, 32590267, 73124510, 92692283, 92604458, 96906410, 22766820, 66885828, 44889423, 70367851, 73109997, 67281495, 51426311, 18783702, 56473732, 47090124, 4508700, 99297164, 77787724, 27041967, 18131876, 78766358, 31126490, 81774825, 57359924, 20122224, 24915585, 28851716, 71920426, 82532312, 49271185, 4069912, 16567550, 51466049, 24953498, 20645197, 4668450, 3294781, 824872, 66663942, 37166608, 36780454, 33201905, 99333375, 22129328, 82779622, 30771409, 10453030, 32151165, 40152546, 73031054, 30163921, 61897582, 86821229, 14220886, 44550764, 74743862, 68694897, 45306070, 321665, 9886593, 39553046, 32161669, 82327024, 19376156, 38645117, 60176618, 76825057, 13470059, 17764950, 16405341, 80316608, 22435353, 25842078, 17957593, 38510840, 35092039, 43357947, 8099994, 49882705, 68102437, 10961421, 11923835, 91141395, 66611759, 62357986, 3088684, 68702632, 3880712, 82427263, 9259676, 98943869, 1936762, 66903004, 91831487, 21919959, 36468541, 91990218, 54014062, 84406788, 22200378, 6497830, 9860195, 6157724, 65454636, 81677380, 34236719, 7919588, 81853704, 20681921, 32699744, 6153406, 45794415, 21070110, 38658347, 81898046, 80246713, 61373987, 31733363, 8729146, 75135448, 77377183, 3235882, 8129978, 92554120, 82886381, 33123618, 569864, 37183543, 93933709, 3233569, 40197395, 42307449, 63506281, 65081429, 61815223, 37560164, 75986488, 29635537, 16099750, 51047803, 99690194, 92998591, 5073754, 12348118, 29029316, 44245960, 47708850, 2204165, 7646095, 6505939, 33699435, 8791066, 33237508, 89811711, 59329511, 29834512, 74357852, 16054533, 82651278, 79806380, 18699206, 33935899, 65074535, 14363867, 44479073, 16684829, 99021067, 23194618, 40356877, 60393039, 1375023, 55247455, 14731700, 41092102, 87710366, 31727379, 33565483, 8128637, 26292919, 36396314, 57240218, 99515901, 82726008, 29819940, 54058788, 96318094, 247198, 10597197, 97057187, 54987042, 72278539, 91255408, 4985896, 90090964, 8696647, 56531125, 89637706, 88904910, 43506672, 71083565, 68824981, 84349107, 4787945, 22405120, 48673079, 26102057, 85242963, 83533741, 27411561, 94516935, 90272749, 8634541, 94090109, 3509435, 84684495, 86001008, 19939935, 51715482, 87160386, 72019362, 84840549, 99658235, 28928175, 61623915, 58749, 28796059, 26776922, 42782652, 95290172, 95010552, 1788101, 64848072, 44915235, 67451935, 37957788, 11161731, 20885148, 64098930, 24591705, 24058273, 21993752, 62232211, 12416768, 89046466, 176257, 83789391, 24826575, 30090481, 95395112, 60581278, 29959549, 53666583, 58180653, 66271566, 8913721, 34698463, 66322959, 61741594, 48892201, 39847321, 85116755, 9188443, 59177718, 42073124, 51464002, 55960386, 56461322, 41442762, 22113133, 71316369, 68644627, 5970607, 38022834, 71333116, 16971929, 91664334, 59910624, 49641577, 36135, 32058615, 68316156, 27625735, 72732205, 54232247, 29401781, 73786944, 94711842, 43152977, 83083131, 77284619, 73021291, 61982238, 11757872, 74452589, 86022504, 17037369, 45996863, 53632373, 84127901, 17081350, 52261574, 14349098, 46870723, 48774913, 59318837, 82897371, 2917920, 94911072, 72358170, 99125126, 43501211, 61960400, 45075471, 99917599, 93566986, 82339363, 23848565, 5267545, 83210802, 90310261, 35512853, 4806458, 26493119, 18001617, 8373289, 69641513, 96852321, 83133790, 26998766, 53084256, 75128745, 34698428, 36312813, 54199160, 4515343, 91802888, 55602660, 6038457, 3183975, 75052463, 8651647, 14093520, 88444207, 49236559, 37280276, 4199704, 92398073, 27325428, 77272628, 34493392, 62430984, 94539824, 32159704, 14723410, 26734892, 53547802, 85695762, 40984766, 38009615, 70004753, 99861373, 40027975, 15064655, 64602895, 78589145, 64157906, 67031644, 68000591, 22108665, 63059024, 13819358, 38341669, 6111563, 16097038, 36685023, 76671482, 4798568, 49597667, 72738685, 48658605, 7685448, 112651, 42692881, 81172706, 71469330, 37501808, 93562257, 79136082, 29466406, 85711894, 14045383, 16424199, 61859143, 52060076, 90004325, 4091162, 90654836, 92033260, 68204242, 6819644, 72357096, 59501487, 5822202, 96193415, 34497327, 57658654, 62762857, 77187825, 89699445, 50567636, 45848907, 12664567, 17727650, 21397057, 71657078, 46540998, 6871053, 34432810, 65017137, 91281584, 77300457, 61859581, 40677414, 72725103, 93053405, 44473167, 33431960, 19891772, 92215320, 58224549, 8055981, 73235980, 9623492, 7229550, 78602717, 85023028, 60106217, 59981773, 55470718, 61271144, 36933038, 36580610, 30366150, 15075176, 59405277, 53802686, 38008118, 1197320, 20836893, 67227442, 16595436, 3633375, 68110330, 24619760, 30787683, 1569515, 20867149, 37224844, 76170907, 55753905, 30569392, 43045786, 68875490, 98739783, 92867155, 77301523, 37675718, 89217461, 42967683, 89804152, 30543215, 73617245, 30653863, 84002370, 46851987, 37739481, 1239555, 89214616, 83747892, 41245325, 4099191, 25636669, 52204879, 92692978, 24314885, 65047700, 50188404, 29994197, 39201414, 8733068, 40781854, 66319530, 83269727, 26744917, 6986898, 17386996, 76330843, 99224392, 7502255, 65851721, 67124282, 44846932, 29065964, 57020481, 36753250, 95251277, 93057697, 83651211, 99549373, 20642888, 70800879, 97940276, 97561745, 80251430, 78218845, 58208470, 13231279, 90013093, 76315420, 93359396, 78785507, 40865610, 44664587, 57248122, 26114953, 81274566, 26397786, 67084351, 57393458, 47893286, 22997281, 89996536, 15015906, 55770687, 73222868, 82178706, 86460488, 30395570, 54263880, 48360198, 88130087, 52613508, 86301513, 73168565, 29932657, 61263068, 17058722, 78909561, 35192533, 30463802, 92541302, 71300104, 32426752, 12303248, 43322743, 7517032, 91938191, 66428795, 76960303, 74441448, 67030811, 77072625, 36930650, 97783876, 54606384, 1250437, 50806615, 36139942, 41092172, 26139110, 59371804, 4434662, 68041839, 97281847, 17539625, 44348328, 33304202, 63372756, 65271999, 88653118, 7104732, 95581843, 96420429, 23569917, 15163258, 69848388, 20486294, 38061439, 33061250, 59197747, 76434144, 92071990, 23134715, 39171895, 42199455, 99729357, 36505482, 415901, 29188588, 69786756, 63152504, 7066775, 86118021, 4119747, 1204161, 72777973, 37435892, 9603598, 34946859, 61141874, 16387575, 45237957, 79922758, 9860968, 14326617, 51472275, 44177011, 34044787, 50668599, 79322415, 38494874, 17016156, 44847298, 17146629, 18806856, 1431742, 38256849, 44627776, 30139692, 65715134, 93790285, 7687278, 62496012, 79821897, 68816413, 19898053, 15535065, 9599614 +37166608, 37957788, 19891772, 75153252, 61928316, 70541760, 50007421, 40356877, 17539625, 95010552, 44784505, 42307449, 63506281, 89699445, 62428472, 2917920, 5111370, 91240048, 45407418, 83533741, 66137019, 18001617, 13173644, 33628349, 72495719, 66322959, 71522968, 63152504, 79136082, 41245325, 55960386, 68316156, 7687278, 94711842, 55247455, 66663942, 74452589, 86022504, 77413012, 76330843, 91255408, 62693428, 83368048, 44481640, 61859581, 92803766, 83210802, 92787493, 35996293, 21533347, 65271999, 33895336, 64848072, 47893286, 15075176, 19898053, 9575954, 62803858, 86543538, 52293995, 84293052, 34295794, 45428665, 98371444, 63967300, 27187213, 23740167, 51426311, 7182642, 71920426, 44479073, 45919976, 99524975, 64055960, 67030811, 77284619, 37192445, 82779622, 44060493, 29819940, 61897582, 54987042, 67124282, 94911072, 62452034, 57020481, 36753250, 74614639, 17764950, 70800879, 97940276, 33336148, 45667668, 57241521, 3233084, 17957593, 62357986, 53842979, 66045587, 26114953, 23569917, 9259676, 1788101, 10358899, 70036438, 93515664, 18466635, 32250045, 34493392, 94539824, 89996536, 30764367, 53547802, 69605283, 21993752, 54263880, 30787683, 75135448, 59197747, 22108665, 45990383, 69255765, 8129978, 5640302, 39801351, 92867155, 93933709, 94360702, 4204661, 58180653, 55189057, 30543215, 66271566, 11543098, 99729357, 84904436, 4798568, 61815223, 73124510, 57163802, 16099750, 66885828, 12348118, 71469330, 95957797, 74357852, 96726697, 88047921, 27625735, 24314885, 68204242, 95726235, 56484900, 60955663, 96193415, 41092102, 36396314, 17081350, 79821897, 10597197, 73031054, 53888755, 72358170, 61141874, 16387575, 79191827, 24733232, 5267545, 19376156, 83651211, 41092172, 14947650, 93053405, 90272749, 94090109, 69579137, 69641513, 98462867, 88698958, 26863229, 51715482, 87160386, 53084256, 91141395, 88653118, 7104732, 93270084, 28796059, 3880712, 42782652, 51315460, 20568363, 98943869, 81274566, 54014062, 51472275, 27325428, 34236719, 15015906, 81853704, 21070110, 53648154, 22879907, 22942635, 12416768, 44177011, 70727211, 40027975, 12571310, 64602895, 30090481, 68000591, 77377183, 78561158, 92071990, 98739783, 65275241, 3233569, 26275734, 72793444, 36685023, 16019925, 13468268, 51507425, 26507214, 7955293, 48892201, 32590267, 59177718, 92998591, 6505939, 6808825, 18783702, 56473732, 41442762, 71316369, 55143724, 49641577, 61859143, 69355476, 79806380, 33935899, 66428795, 50188404, 87720882, 50668599, 16567550, 31491938, 67474219, 98948034, 62496012, 9603598, 55669657, 11581181, 45848907, 8128637, 89078848, 40686254, 52261574, 47738185, 7502255, 46540998, 34946859, 45995325, 37891451, 168541, 6521313, 92353856, 9398733, 88904910, 29065964, 93057697, 90310261, 38645117, 59109400, 60176618, 35512853, 20642888, 48673079, 76540481, 91727510, 22435353, 33797252, 29510992, 84684495, 96852321, 86001008, 90457870, 84840549, 26998766, 78785507, 28928175, 58749, 66611759, 54762643, 9623492, 44664587, 95581843, 508198, 55602660, 78602717, 42947632, 21919959, 26397786, 749283, 36189527, 67451935, 9860195, 53802686, 68128438, 32159704, 17857111, 32699744, 85695762, 38009615, 64087743, 3773993, 83789391, 79738755, 93790285, 19486173, 24826575, 65880522, 76434144, 13348726, 75407347, 66116458, 73168565, 29932657, 33123618, 1022677, 53666583, 98653983, 76671482, 51149804, 85117092, 8913721, 34698463, 48260151, 26063929, 73617245, 30653863, 65081429, 92692283, 77620120, 23110625, 39847321, 71300104, 96906410, 12303248, 5073754, 70367851, 18663507, 37620363, 56461322, 99297164, 58751351, 33237508, 71333116, 16971929, 59910624, 97379790, 32058615, 81774825, 82651278, 92692978, 18699206, 86798033, 92033260, 14363867, 56153345, 23194618, 29401781, 9257405, 29994197, 83302115, 47298834, 24953498, 74441448, 78300864, 30218878, 61982238, 62762857, 24168411, 87710366, 99333375, 15536795, 50702367, 17727650, 5832946, 40534591, 32151165, 94076128, 16380211, 97057187, 30163921, 66832478, 14220886, 90090964, 48893685, 99125126, 19457589, 91281584, 526217, 43501211, 45075471, 26664538, 23848565, 4095116, 79942022, 75543508, 80316608, 45790169, 97561745, 8634541, 72274002, 43357947, 8099994, 22450468, 72019362, 28550822, 3487592, 10961421, 58224549, 79657802, 82427263, 57248122, 38494874, 1936762, 68816413, 89419466, 14093520, 47792865, 36468541, 9829782, 48675329, 4199704, 11161731, 20885148, 10309525, 14723410, 24591705, 67227442, 73392814, 38658347, 73222868, 21673260, 15902805, 40984766, 99604946, 20867149, 87598888, 55753905, 5482538, 88130087, 3235882, 47213183, 95395112, 92554120, 42644903, 62051033, 75777973, 77301523, 33553959, 23134715, 43933006, 39171895, 60102965, 48088883, 54427233, 55615885, 2331773, 46851987, 72614359, 78909561, 37739481, 415901, 15535065, 9188443, 99690194, 69786756, 42073124, 22766820, 44889423, 44245960, 47708850, 93562257, 36808486, 25636669, 34044787, 68644627, 38022834, 29834512, 2607799, 85711894, 16054533, 41481685, 57359924, 90004325, 30811010, 65047700, 19272365, 63015256, 39201414, 60393039, 83083131, 86242799, 6819644, 20140249, 36930650, 54606384, 36780454, 33201905, 17037369, 26744917, 50567636, 67513640, 39986008, 56955985, 45996863, 84127901, 57803235, 17386996, 96709982, 82726008, 4064751, 30771409, 59318837, 6871053, 34432810, 50806615, 669105, 11365791, 82897371, 69697787, 8696647, 56531125, 65017137, 9886593, 44627776, 65038678, 18833224, 99917599, 15148031, 82327024, 36139942, 76825057, 8115266, 13470059, 4787945, 51588015, 4434662, 68041839, 45617087, 81855445, 58208470, 71965942, 17068582, 61623915, 75128745, 48395186, 36312813, 4515343, 60430369, 62115552, 75052463, 91831487, 70782102, 61271144, 36933038, 26392416, 36580610, 30366150, 22200378, 28734791, 34667286, 90061527, 51590803, 81677380, 64098930, 26734892, 30891921, 8807940, 24619760, 1569515, 176257, 99861373, 37224844, 78589145, 67031644, 7423788, 10649306, 31161687, 61728685, 47887470, 29959549, 37675718, 77312810, 82979980, 42967683, 7300047, 17058722, 76703609, 874791, 36505482, 30463802, 83948335, 37560164, 92541302, 75986488, 29188588, 6793819, 48658605, 51047803, 92604458, 54868730, 43322743, 77694700, 81172706, 2204165, 73109997, 18504197, 7066775, 86118021, 4099191, 47090124, 7011964, 8791066, 39373729, 18131876, 36135, 7517032, 4091162, 24915585, 37659250, 54232247, 74110882, 82532312, 49271185, 72238278, 72373496, 85571389, 51466049, 8733068, 4668450, 20002147, 3294781, 31727379, 84187166, 68939068, 6986898, 17385531, 46870723, 99224392, 48774913, 247198, 99971982, 67793644, 97641116, 86821229, 44983451, 74743862, 56515456, 36812683, 45049308, 54517921, 92530431, 93566986, 82339363, 68824981, 44842615, 9599614, 84349107, 22405120, 16405341, 9058407, 2891150, 97281847, 80251430, 78218845, 57961282, 83133790, 27185644, 90013093, 76315420, 68102437, 40865610, 11923835, 63372756, 6221471, 45237957, 3088684, 28787861, 54199160, 49328608, 96735716, 96420429, 7229550, 81805959, 30694952, 60106217, 83150534, 67084351, 42237907, 49236559, 44915235, 6157724, 17365188, 84166196, 54663246, 69848388, 20681921, 16595436, 3633375, 24058273, 65338021, 6153406, 53393358, 70596786, 55770687, 35456853, 62232211, 81898046, 44844121, 80246713, 68110330, 38061439, 33061250, 70004753, 76170907, 48360198, 62552524, 52613508, 30569392, 86301513, 63059024, 20765474, 55850790, 21289531, 16097038, 89804152, 82052050, 40197395, 84002370, 80555751, 61741594, 38365584, 29635537, 9175338, 18411915, 32426752, 112651, 33249630, 42692881, 37501808, 29466406, 91664334, 78766358, 14045383, 16424199, 52060076, 20122224, 90654836, 28851716, 91938191, 1204161, 76960303, 99021067, 73786944, 10264691, 18806856, 12024238, 1431742, 72357096, 824872, 70420215, 77072625, 79322415, 97783876, 45381876, 26292919, 22129328, 14349098, 71657078, 10453030, 96318094, 65851721, 72278539, 22721500, 89637706, 61960400, 32161669, 40677414, 90158785, 99549373, 85242963, 27411561, 26139110, 59371804, 30139692, 44473167, 47361209, 92215320, 13231279, 44348328, 25842078, 33304202, 35092039, 49882705, 73235980, 66250369, 79922758, 91802888, 9860968, 14326617, 88444207, 63628376, 6497830, 77272628, 14626618, 62430984, 15163258, 61712234, 45794415, 62490109, 86460488, 89046466, 15064655, 64157906, 89217461, 42199455, 62740044, 36845587, 62936963, 19101477, 41380093, 89214616, 70372191, 29029316, 33699435, 89811711, 77787724, 4119747, 27665211, 65074535, 1375023, 14731700, 59501487, 56424103, 34497327, 57658654, 38256849, 33565483, 57240218, 83155442, 1250437, 21397057, 40152546, 4985896, 2717150, 44550764, 68694897, 44846932, 71083565, 31904591, 10366309, 23432750, 26102057, 49598724, 8373289, 75820087, 3509435, 19939935, 38510840, 93359396, 99658235, 30998561, 6038457, 3183975, 74137926, 66903004, 95290172, 91990218, 57393458, 37280276, 17976208, 59405277, 92398073, 15948937, 59614746, 6525948, 20836893, 82178706, 61373987, 8729146, 43045786, 68875490, 38341669, 60581278, 61263068, 37183543, 80014588, 44847298, 35192533, 1239555, 72738685, 85116755, 7685448, 74724075, 7646095, 51464002, 83747892, 67281495, 99965001, 4508700, 59329511, 27041967, 52204879, 43376279, 31126490, 40781449, 72732205, 4069912, 72777973, 43152977, 37435892, 40781854, 66319530, 73021291, 99226875, 77187825, 53632373, 99515901, 39553046, 69136837, 4806458, 33431960, 21001913, 17894977, 16445503, 88251446, 34698428, 66667729, 68702632, 2208785, 8651647, 55470718, 84406788, 4978543, 65454636, 97011160, 22997281, 1197320, 65715134, 78884452, 20486294, 98648327, 6111563, 79880247, 91957544, 49597667, 17146629, 77898274, 20645197, 11757872, 54058788, 45306070, 321665, 77300457, 95251277, 32274392, 72725103, 26493119, 26776922, 78549759, 85023028, 59981773, 75229462, 13862149, 38008118, 98130363, 89499542, 17016156, 22113133, 5970607, 16684829, 5822202, 83269727, 43506672, 30395570, 31733363, 82886381, 94595668, 94516935, 8055981, 7919588, 569864, 29516592, 13819358, 12664567 +3509435, 45381876, 48675329, 11161731, 9575954, 52204879, 31727379, 84127901, 92353856, 19376156, 91240048, 66611759, 33895336, 44915235, 24826575, 63506281, 9188443, 6808825, 10264691, 89699445, 36396314, 168541, 72278539, 96420429, 68816413, 97011160, 81853704, 61928316, 69255765, 66116458, 1022677, 48088883, 66271566, 66322959, 41380093, 99690194, 73109997, 99965001, 4508700, 54232247, 69355476, 63015256, 50188404, 67474219, 70420215, 45996863, 57803235, 83651211, 97940276, 17539625, 35092039, 65271999, 58224549, 53842979, 95010552, 15902805, 54263880, 88130087, 64157906, 52613508, 62051033, 92867155, 47887470, 77301523, 53666583, 55189057, 42307449, 83948335, 92692283, 92541302, 34295794, 15535065, 70372191, 29029316, 18663507, 7182642, 16054533, 9257405, 56484900, 4668450, 14731700, 67030811, 41092102, 86022504, 24168411, 56955985, 22129328, 50806615, 90090964, 61141874, 45075471, 84349107, 35512853, 90272749, 86001008, 17957593, 61623915, 66250369, 3880712, 4515343, 6038457, 23569917, 55470718, 75229462, 36468541, 1788101, 67084351, 30366150, 749283, 67451935, 6157724, 65454636, 10309525, 18466635, 84166196, 65715134, 35456853, 62232211, 38009615, 176257, 76170907, 8129978, 7423788, 95395112, 33123618, 569864, 94360702, 72793444, 99729357, 84904436, 55615885, 30653863, 26507214, 415901, 73124510, 7685448, 7646095, 18783702, 71333116, 59910624, 41481685, 79806380, 7687278, 29994197, 83302115, 43152977, 86242799, 66319530, 57240218, 76330843, 62693428, 94911072, 65017137, 44627776, 99917599, 26664538, 76825057, 13173644, 69641513, 51715482, 49882705, 28550822, 54762643, 3088684, 66045587, 96735716, 42782652, 26114953, 9259676, 89419466, 59981773, 42237907, 57393458, 22200378, 9829782, 15075176, 34667286, 27325428, 17365188, 68128438, 6525948, 20681921, 12416768, 70004753, 64602895, 30090481, 77377183, 30569392, 43045786, 68875490, 65275241, 38341669, 82886381, 61263068, 93933709, 42967683, 3233569, 58180653, 36685023, 52293995, 34698463, 874791, 62740044, 44847298, 7955293, 48892201, 38365584, 45428665, 29188588, 29635537, 98371444, 42692881, 2204165, 6505939, 79136082, 7066775, 56473732, 89811711, 71316369, 4091162, 30811010, 65047700, 19272365, 44479073, 56153345, 72777973, 51466049, 60393039, 1431742, 60955663, 20002147, 56424103, 79322415, 54606384, 87710366, 89078848, 17386996, 29819940, 10453030, 32151165, 34432810, 669105, 4985896, 11365791, 45306070, 62452034, 9886593, 526217, 65038678, 43506672, 71083565, 54517921, 39553046, 82327024, 27411561, 66137019, 22435353, 33797252, 78218845, 57961282, 75820087, 84684495, 26863229, 33304202, 17894977, 22450468, 28928175, 11923835, 63372756, 6221471, 36312813, 33628349, 38494874, 98943869, 26392416, 13862149, 51472275, 32250045, 90061527, 22997281, 34493392, 62430984, 94539824, 64098930, 53547802, 65338021, 22879907, 8807940, 24619760, 44177011, 89046466, 12571310, 75135448, 55753905, 67031644, 13348726, 68000591, 61728685, 55850790, 77312810, 82979980, 16097038, 26275734, 40197395, 76671482, 51149804, 76703609, 51507425, 73617245, 80555751, 62936963, 36505482, 37739481, 35192533, 61815223, 61741594, 32590267, 49597667, 37560164, 23110625, 85116755, 16099750, 71300104, 89214616, 112651, 63967300, 77694700, 12348118, 81172706, 71522968, 37501808, 55960386, 50007421, 99297164, 68644627, 29466406, 97379790, 90654836, 4119747, 71920426, 92033260, 50668599, 72238278, 18806856, 47298834, 20645197, 64055960, 6819644, 77284619, 73021291, 99226875, 66663942, 96193415, 77072625, 36930650, 38256849, 33201905, 83269727, 62428472, 40686254, 17081350, 96709982, 52261574, 46870723, 47738185, 46540998, 40534591, 54058788, 6871053, 94076128, 10597197, 99971982, 65851721, 2717150, 48893685, 69697787, 8696647, 68694897, 2917920, 56531125, 72358170, 99125126, 45049308, 43501211, 83368048, 79191827, 44481640, 24733232, 32161669, 9599614, 10366309, 8115266, 51588015, 76540481, 35996293, 75543508, 49598724, 33336148, 97561745, 30139692, 44473167, 21533347, 13231279, 44348328, 3233084, 25842078, 38510840, 21001913, 93359396, 84840549, 26998766, 78785507, 68102437, 7104732, 73235980, 66667729, 28787861, 30998561, 49328608, 2208785, 60430369, 79922758, 91802888, 81805959, 78549759, 81274566, 83150534, 91990218, 63628376, 93515664, 37957788, 20885148, 15163258, 38008118, 69848388, 32699744, 78884452, 21993752, 38658347, 80246713, 40984766, 99604946, 38061439, 70727211, 99861373, 93790285, 40027975, 59197747, 5482538, 78589145, 98648327, 3235882, 47213183, 86301513, 10649306, 98739783, 92554120, 20765474, 31161687, 73168565, 89217461, 7300047, 42199455, 8913721, 16019925, 84002370, 1239555, 77620120, 75986488, 39847321, 6793819, 96906410, 54868730, 69786756, 59177718, 32426752, 36808486, 63152504, 51464002, 83747892, 51426311, 8791066, 17146629, 39373729, 34044787, 18131876, 43376279, 91664334, 96726697, 31126490, 55143724, 32058615, 68316156, 40781449, 37659250, 74110882, 33935899, 16684829, 68204242, 23194618, 72373496, 40356877, 95726235, 31491938, 99524975, 37435892, 83083131, 40781854, 30218878, 77187825, 11581181, 33565483, 17037369, 84187166, 39986008, 45848907, 17385531, 1250437, 48774913, 44060493, 59318837, 45995325, 40152546, 16380211, 73031054, 14220886, 82897371, 321665, 29065964, 19457589, 36753250, 16387575, 18833224, 74614639, 32274392, 68824981, 93057697, 5267545, 90158785, 23432750, 72725103, 92787493, 22405120, 16405341, 9058407, 48673079, 26102057, 59371804, 93053405, 97281847, 8373289, 8634541, 57241521, 94090109, 81855445, 92215320, 19939935, 83133790, 17068582, 8099994, 87160386, 3487592, 48395186, 62357986, 8055981, 44664587, 93270084, 57248122, 7229550, 62115552, 51315460, 20568363, 8651647, 85023028, 42947632, 14093520, 47792865, 21919959, 95290172, 61271144, 70036438, 36189527, 17976208, 15948937, 30764367, 54663246, 1197320, 7919588, 20836893, 67227442, 3633375, 45794415, 89499542, 73222868, 22942635, 44844121, 30787683, 1569515, 83789391, 8729146, 78561158, 92071990, 75407347, 63059024, 5640302, 21289531, 37675718, 60102965, 17058722, 82052050, 30543215, 13468268, 54427233, 79880247, 65081429, 2331773, 46851987, 4798568, 36845587, 78909561, 19101477, 18411915, 42073124, 33249630, 12303248, 22766820, 66885828, 5073754, 27187213, 74724075, 47708850, 86118021, 47090124, 25636669, 33237508, 16971929, 78766358, 81774825, 7517032, 52060076, 91938191, 49271185, 1204161, 86798033, 14363867, 87720882, 39201414, 45919976, 16567550, 12024238, 1375023, 59501487, 5822202, 61982238, 57658654, 9603598, 11757872, 62762857, 26744917, 50567636, 8128637, 6986898, 82726008, 99224392, 17727650, 7502255, 97057187, 61897582, 66832478, 53888755, 91255408, 44550764, 9398733, 89637706, 88904910, 82339363, 15148031, 36139942, 92803766, 83210802, 40677414, 59109400, 13470059, 17764950, 41092172, 70800879, 85242963, 83533741, 79942022, 14947650, 91727510, 2891150, 45790169, 68041839, 45617087, 80251430, 29510992, 69579137, 71965942, 72019362, 88251446, 40865610, 53084256, 68702632, 79657802, 508198, 78602717, 30694952, 1936762, 60106217, 36933038, 54014062, 84406788, 64848072, 4199704, 4978543, 72495719, 14626618, 32159704, 34236719, 14723410, 15015906, 16595436, 70596786, 19898053, 73392814, 69605283, 21070110, 21673260, 33061250, 3773993, 30395570, 61373987, 20867149, 15064655, 19486173, 48360198, 65880522, 44784505, 13819358, 42644903, 75777973, 60581278, 33553959, 37183543, 39171895, 11543098, 48260151, 9175338, 48658605, 92998591, 70367851, 71469330, 93562257, 70541760, 56461322, 33699435, 22113133, 59329511, 38022834, 77787724, 2607799, 85711894, 16424199, 49641577, 61859143, 24915585, 27625735, 82532312, 76960303, 73786944, 94711842, 55247455, 78300864, 3294781, 824872, 34497327, 74452589, 55669657, 26292919, 53632373, 83155442, 99515901, 4064751, 79821897, 6521313, 67124282, 77300457, 61960400, 92530431, 93566986, 23848565, 38645117, 45407418, 20642888, 45667668, 19891772, 58208470, 96852321, 27185644, 90013093, 72274002, 16445503, 75153252, 91141395, 88653118, 45237957, 95581843, 26776922, 55602660, 3183975, 74137926, 91831487, 26397786, 36580610, 10358899, 9860195, 59405277, 92398073, 53802686, 24058273, 20486294, 82178706, 30891921, 62490109, 68110330, 86460488, 64087743, 79738755, 76434144, 62803858, 29959549, 43933006, 98653983, 85117092, 72614359, 91957544, 57163802, 92604458, 43322743, 41245325, 23740167, 4099191, 7011964, 29834512, 27041967, 74357852, 77898274, 57359924, 90004325, 20122224, 72732205, 65074535, 4069912, 99021067, 29401781, 85571389, 72357096, 20140249, 68939068, 67513640, 37192445, 12664567, 77413012, 14349098, 5832946, 30771409, 71657078, 96318094, 97641116, 54987042, 86821229, 44983451, 22721500, 74743862, 56515456, 5111370, 61859581, 44842615, 90310261, 60176618, 4787945, 99549373, 4095116, 94516935, 26139110, 4434662, 29516592, 47361209, 98462867, 88698958, 43357947, 90457870, 99658235, 58749, 10961421, 34698428, 82427263, 75052463, 66903004, 9860968, 88444207, 28734791, 89996536, 59614746, 98130363, 24591705, 61712234, 53393358, 55770687, 53648154, 22108665, 62552524, 45990383, 6111563, 4204661, 26063929, 80014588, 84293052, 30463802, 44245960, 37620363, 18504197, 67281495, 5970607, 92692978, 66428795, 8733068, 74441448, 37166608, 99333375, 15536795, 82779622, 37891451, 67793644, 31904591, 69136837, 4806458, 33431960, 9623492, 54199160, 14326617, 70782102, 49236559, 37280276, 51590803, 81677380, 85695762, 81898046, 37224844, 39801351, 29932657, 51047803, 44889423, 58751351, 14045383, 36135, 28851716, 27665211, 24314885, 24953498, 98948034, 62496012, 36780454, 34946859, 30163921, 44846932, 36812683, 91281584, 26493119, 18001617, 76315420, 28796059, 47893286, 6497830, 77272628, 26734892, 17857111, 87598888, 31733363, 23134715, 89804152, 95957797, 88047921, 18699206, 247198, 94595668, 75128745, 6153406, 86543538, 82651278, 50702367, 95251277, 80316608, 41442762, 97783876, 17016156, 21397057, 72738685, 57020481 +69605283, 4064751, 62452034, 72019362, 3880712, 76170907, 60581278, 16097038, 48892201, 14731700, 6819644, 31727379, 14220886, 88904910, 51588015, 58749, 38494874, 98943869, 4199704, 40984766, 7423788, 84293052, 65081429, 112651, 93562257, 79136082, 70541760, 56473732, 95957797, 77787724, 74357852, 7182642, 18806856, 79322415, 39553046, 4095116, 18001617, 17957593, 54762643, 26114953, 59981773, 10358899, 44915235, 17365188, 62490109, 44177011, 89046466, 37675718, 77312810, 6111563, 76671482, 8913721, 84904436, 78909561, 61741594, 63967300, 37501808, 63152504, 31126490, 54232247, 69355476, 20645197, 74441448, 66319530, 73021291, 72357096, 59501487, 96193415, 11581181, 96709982, 82779622, 29819940, 40534591, 94076128, 97057187, 168541, 26664538, 9599614, 8115266, 90158785, 45617087, 58208470, 43357947, 6038457, 78549759, 9860968, 95010552, 9829782, 97011160, 67227442, 21993752, 35456853, 33061250, 70004753, 30090481, 9575954, 75407347, 98739783, 55850790, 29932657, 47887470, 76703609, 73617245, 4798568, 36845587, 62936963, 57163802, 16099750, 98371444, 70372191, 33249630, 18663507, 4099191, 27041967, 96726697, 90004325, 4091162, 27625735, 65047700, 99021067, 72238278, 55247455, 83083131, 30218878, 24168411, 83269727, 57240218, 57803235, 30771409, 66832478, 54987042, 90090964, 44550764, 56515456, 526217, 95251277, 71083565, 84349107, 69136837, 13470059, 14947650, 21533347, 44348328, 69641513, 76315420, 53084256, 11923835, 91141395, 6221471, 93270084, 28787861, 33895336, 96735716, 96420429, 79922758, 78602717, 23569917, 61271144, 36933038, 26392416, 42237907, 749283, 70036438, 6497830, 15075176, 9860195, 6157724, 77272628, 32159704, 34236719, 81853704, 70596786, 21070110, 21673260, 176257, 93790285, 40027975, 8729146, 12571310, 76434144, 22108665, 10649306, 42644903, 29959549, 569864, 82052050, 52293995, 30653863, 2331773, 1239555, 91957544, 39847321, 7685448, 51047803, 69786756, 22766820, 66885828, 7646095, 71469330, 58751351, 34044787, 68644627, 38022834, 41481685, 32058615, 52060076, 24314885, 18699206, 50188404, 9257405, 40356877, 51466049, 12024238, 99524975, 99333375, 37192445, 84127901, 40686254, 82726008, 14349098, 53888755, 44846932, 43506672, 61960400, 45075471, 92530431, 82339363, 44481640, 15148031, 91240048, 60176618, 23432750, 17764950, 27411561, 79942022, 94516935, 75543508, 4434662, 97561745, 8634541, 57961282, 71965942, 27185644, 90013093, 90457870, 93359396, 87160386, 34698428, 9623492, 66250369, 28796059, 33628349, 51315460, 75052463, 60106217, 1788101, 48675329, 61712234, 38658347, 53648154, 12416768, 99604946, 54263880, 64602895, 64157906, 61728685, 33123618, 21289531, 77301523, 33553959, 48088883, 55189057, 11543098, 26063929, 63506281, 26507214, 72614359, 30463802, 49597667, 15535065, 72738685, 6793819, 42692881, 44245960, 81172706, 36808486, 6505939, 51464002, 51426311, 55960386, 7011964, 29834512, 55143724, 85711894, 14045383, 49641577, 68316156, 57359924, 90654836, 33935899, 19272365, 66428795, 16684829, 68204242, 72373496, 29994197, 8733068, 60393039, 78300864, 20002147, 98948034, 77072625, 11757872, 97783876, 87710366, 33565483, 17037369, 8128637, 17385531, 46870723, 71657078, 10453030, 6871053, 37891451, 65851721, 73031054, 72278539, 22721500, 92353856, 62693428, 89637706, 36812683, 36753250, 16387575, 32274392, 79191827, 23848565, 5267545, 36139942, 90310261, 70800879, 97940276, 59371804, 80316608, 22435353, 90272749, 33431960, 69579137, 47361209, 17539625, 84684495, 88698958, 19939935, 25842078, 22450468, 26998766, 68102437, 63372756, 65271999, 73235980, 66667729, 95581843, 82427263, 62115552, 81805959, 42947632, 47792865, 75229462, 21919959, 36468541, 91990218, 51472275, 36189527, 17976208, 18466635, 16595436, 32699744, 24058273, 65338021, 73392814, 55770687, 81898046, 68110330, 30395570, 70727211, 31733363, 37224844, 75135448, 19486173, 65880522, 5482538, 77377183, 3235882, 8129978, 5640302, 68875490, 13819358, 92554120, 92867155, 62803858, 82886381, 1022677, 89217461, 42967683, 72793444, 98653983, 13468268, 66322959, 51507425, 874791, 44847298, 37739481, 92541302, 34295794, 29188588, 99690194, 67281495, 7066775, 50007421, 56461322, 17146629, 33237508, 52204879, 2607799, 91664334, 82651278, 71920426, 7687278, 87720882, 23194618, 29401781, 10264691, 83302115, 1375023, 67474219, 3294781, 61982238, 9603598, 41092102, 86022504, 37166608, 56955985, 26292919, 17081350, 1250437, 22129328, 45995325, 247198, 10597197, 34432810, 50806615, 669105, 11365791, 82897371, 69697787, 2917920, 9886593, 45049308, 65038678, 18833224, 83368048, 32161669, 92803766, 59109400, 99549373, 41092172, 16405341, 76540481, 83533741, 93053405, 33797252, 49598724, 68041839, 97281847, 44473167, 57241521, 13173644, 29510992, 75820087, 98462867, 38510840, 33304202, 17894977, 78785507, 40865610, 28550822, 3487592, 8055981, 3088684, 36312813, 68702632, 60430369, 91802888, 30694952, 81274566, 89419466, 14093520, 14326617, 70782102, 36580610, 63628376, 37957788, 65454636, 92398073, 20885148, 90061527, 22997281, 68128438, 62430984, 15163258, 6525948, 26734892, 7919588, 20836893, 64087743, 3773993, 61373987, 20867149, 99861373, 98648327, 68000591, 44784505, 86301513, 43045786, 38341669, 73168565, 86543538, 94360702, 43933006, 3233569, 53666583, 17058722, 40197395, 36685023, 99729357, 16019925, 46851987, 19101477, 83948335, 75986488, 29635537, 48658605, 71300104, 92604458, 59177718, 42073124, 12303248, 77694700, 27187213, 12348118, 74724075, 37620363, 83747892, 6808825, 18504197, 47090124, 33699435, 25636669, 8791066, 41442762, 22113133, 71316369, 43376279, 88047921, 59910624, 77898274, 30811010, 37659250, 79806380, 86798033, 76960303, 14363867, 39201414, 73786944, 16567550, 56484900, 64055960, 86242799, 4668450, 56424103, 70420215, 36930650, 74452589, 55669657, 54606384, 36780454, 33201905, 89699445, 62428472, 67513640, 39986008, 45381876, 45996863, 12664567, 89078848, 50702367, 17386996, 99224392, 21397057, 7502255, 46540998, 34946859, 54058788, 86821229, 4985896, 74743862, 8696647, 9398733, 57020481, 43501211, 74614639, 24733232, 61859581, 44842615, 93057697, 4787945, 35512853, 45407418, 35996293, 91727510, 2891150, 26493119, 29516592, 8373289, 78218845, 92215320, 26863229, 3233084, 21001913, 17068582, 16445503, 84840549, 88251446, 99658235, 61623915, 88653118, 7104732, 58224549, 53842979, 26776922, 66045587, 30998561, 2208785, 57248122, 3183975, 8651647, 1936762, 68816413, 55470718, 67084351, 57393458, 30366150, 54014062, 13862149, 84406788, 37280276, 47893286, 93515664, 28734791, 67451935, 4978543, 10309525, 32250045, 27325428, 69848388, 17857111, 15015906, 20681921, 45794415, 15902805, 8807940, 38009615, 24619760, 87598888, 55753905, 24826575, 45990383, 52613508, 47213183, 92071990, 30569392, 95395112, 20765474, 31161687, 82979980, 37183543, 7300047, 39171895, 26275734, 89804152, 42199455, 34698463, 42307449, 55615885, 80555751, 36505482, 35192533, 7955293, 415901, 73124510, 23110625, 9175338, 9188443, 43322743, 71522968, 23740167, 99965001, 18783702, 4508700, 59329511, 5970607, 29466406, 18131876, 97379790, 16424199, 27665211, 49271185, 1204161, 85571389, 45919976, 95726235, 94711842, 31491938, 43152977, 60955663, 40781854, 77284619, 20140249, 66663942, 62762857, 77187825, 84187166, 68939068, 26744917, 45848907, 53632373, 6986898, 76330843, 48774913, 5832946, 32151165, 30163921, 44983451, 48893685, 56531125, 321665, 72358170, 44627776, 91281584, 77300457, 54517921, 93566986, 68824981, 40677414, 72725103, 4806458, 22405120, 85242963, 66137019, 45790169, 33336148, 80251430, 81855445, 13231279, 3509435, 86001008, 83133790, 35092039, 72274002, 51715482, 49882705, 28928175, 75153252, 75128745, 42782652, 508198, 4515343, 7229550, 85023028, 83150534, 95290172, 88444207, 26397786, 49236559, 53802686, 34667286, 34493392, 94539824, 89996536, 84166196, 64098930, 14723410, 65715134, 53547802, 53393358, 89499542, 20486294, 44844121, 80246713, 38061439, 30787683, 83789391, 15064655, 61928316, 62552524, 78561158, 69255765, 62051033, 61263068, 60102965, 58180653, 30543215, 51149804, 85117092, 62740044, 32590267, 37560164, 92692283, 18411915, 89214616, 54868730, 92998591, 29029316, 47708850, 70367851, 73109997, 39373729, 89811711, 78766358, 7517032, 61859143, 28851716, 92692978, 74110882, 65074535, 63015256, 4069912, 44479073, 56153345, 24953498, 1431742, 824872, 34497327, 50567636, 17727650, 59318837, 16380211, 94595668, 67793644, 97641116, 2717150, 6521313, 68694897, 67124282, 5111370, 94911072, 19457589, 10366309, 19376156, 83210802, 83651211, 92787493, 20642888, 45667668, 19891772, 8099994, 79657802, 9259676, 20568363, 91831487, 11161731, 51590803, 59614746, 54663246, 1197320, 24591705, 3633375, 6153406, 78884452, 85695762, 82178706, 22879907, 22942635, 86460488, 78589145, 75777973, 17016156, 93933709, 66271566, 80014588, 84002370, 38365584, 41380093, 45428665, 2204165, 41245325, 86118021, 99297164, 16971929, 16054533, 24915585, 40781449, 4119747, 82532312, 91938191, 37435892, 38256849, 77413012, 52261574, 47738185, 44060493, 40152546, 91255408, 99125126, 61141874, 29065964, 38645117, 48673079, 30139692, 94090109, 48395186, 74137926, 64848072, 15948937, 30764367, 30891921, 1569515, 79738755, 48360198, 67031644, 13348726, 39801351, 4204661, 79880247, 77620120, 85116755, 32426752, 36135, 81774825, 20122224, 72732205, 92033260, 50668599, 72777973, 47298834, 15536795, 36396314, 99515901, 96318094, 65017137, 99917599, 31904591, 82327024, 26139110, 44664587, 54199160, 66903004, 22200378, 72495719, 81677380, 14626618, 73222868, 59197747, 88130087, 65275241, 48260151, 54427233, 5073754, 44889423, 71333116, 67030811, 5822202, 57658654, 79821897, 45306070, 45237957, 55602660, 59405277, 38008118, 23134715, 61815223, 96906410, 62496012, 99226875, 83155442, 99971982, 9058407, 96852321, 62357986, 49328608, 98130363, 19898053, 63059024, 61897582, 76825057, 26102057, 10961421, 66611759, 62232211, 66116458 +84840549, 19376156, 72274002, 60430369, 96906410, 4508700, 24915585, 65047700, 26744917, 67793644, 16387575, 49882705, 62357986, 20765474, 89214616, 97379790, 33935899, 91938191, 12664567, 50702367, 99971982, 33431960, 6221471, 9259676, 36189527, 17976208, 53802686, 38008118, 24058273, 37224844, 59197747, 69255765, 86543538, 5073754, 72732205, 7687278, 16567550, 70420215, 9603598, 86022504, 36396314, 54058788, 92353856, 29065964, 45049308, 71083565, 31904591, 69136837, 72725103, 9058407, 21533347, 19939935, 3233084, 90013093, 58224549, 66250369, 3183975, 20568363, 91831487, 14626618, 54663246, 6525948, 65338021, 8129978, 55850790, 60581278, 3233569, 26275734, 415901, 45428665, 54868730, 37620363, 79806380, 24314885, 12024238, 1431742, 67474219, 824872, 74452589, 15536795, 47738185, 6871053, 97057187, 82897371, 45306070, 67124282, 61960400, 36139942, 92803766, 40677414, 99549373, 92787493, 33797252, 30139692, 78218845, 44348328, 69641513, 76315420, 93359396, 75128745, 10961421, 44664587, 54199160, 30998561, 75052463, 75229462, 83150534, 95290172, 57393458, 63628376, 59405277, 27325428, 90061527, 30764367, 69848388, 15015906, 3633375, 55770687, 81898046, 30891921, 44844121, 8807940, 54263880, 176257, 8729146, 62552524, 45990383, 86301513, 7423788, 5640302, 61728685, 11543098, 66322959, 62740044, 32590267, 37560164, 18411915, 98371444, 32426752, 42692881, 29029316, 73109997, 6808825, 34044787, 5970607, 29466406, 40781449, 4119747, 92033260, 85571389, 73786944, 8733068, 43152977, 40781854, 99226875, 55669657, 37166608, 33201905, 99333375, 50567636, 44060493, 34946859, 96318094, 16380211, 61897582, 91255408, 74743862, 56531125, 36812683, 91281584, 36753250, 82339363, 68824981, 44481640, 82327024, 44842615, 20642888, 94516935, 29516592, 29510992, 84684495, 17068582, 78785507, 53084256, 66611759, 3088684, 95581843, 74137926, 85023028, 47792865, 26397786, 13862149, 93515664, 28734791, 65454636, 51590803, 81677380, 32159704, 84166196, 73222868, 62232211, 68110330, 12416768, 86460488, 30395570, 61373987, 1569515, 79738755, 55753905, 64157906, 98648327, 92071990, 75407347, 30569392, 66116458, 39801351, 95395112, 42644903, 65275241, 62803858, 75777973, 73168565, 33123618, 82979980, 42967683, 7300047, 42199455, 66271566, 34698463, 42307449, 48260151, 63506281, 54427233, 84002370, 35192533, 30463802, 73124510, 77620120, 29635537, 92604458, 42073124, 47708850, 70367851, 71522968, 18504197, 41245325, 67281495, 56461322, 33699435, 17146629, 39373729, 33237508, 71316369, 59329511, 95957797, 71333116, 32058615, 7517032, 49271185, 68204242, 23194618, 72777973, 47298834, 20645197, 83083131, 4668450, 77284619, 62496012, 59501487, 57658654, 54606384, 17037369, 39986008, 45848907, 76330843, 48774913, 21397057, 5832946, 97641116, 72278539, 14220886, 9886593, 57020481, 65038678, 18833224, 54517921, 39553046, 15148031, 38645117, 51588015, 41092172, 26102057, 83533741, 27411561, 22435353, 93053405, 49598724, 19891772, 58208470, 35092039, 8099994, 16445503, 22450468, 26998766, 68102437, 88251446, 40865610, 8055981, 49328608, 2208785, 96420429, 26114953, 68816413, 60106217, 55470718, 14326617, 36468541, 84406788, 18466635, 34493392, 59614746, 34236719, 17857111, 7919588, 81853704, 20681921, 16595436, 65715134, 53547802, 6153406, 45794415, 53393358, 85695762, 21993752, 38658347, 20486294, 22879907, 22942635, 40984766, 31733363, 64602895, 88130087, 67031644, 22108665, 3235882, 47213183, 13819358, 31161687, 62051033, 93933709, 48088883, 55189057, 40197395, 36685023, 80014588, 46851987, 48892201, 83948335, 38365584, 41380093, 92541302, 29188588, 57163802, 85116755, 99690194, 59177718, 12303248, 66885828, 77694700, 44889423, 2204165, 23740167, 50007421, 25636669, 58751351, 27041967, 74357852, 19272365, 14363867, 44479073, 16684829, 50668599, 72373496, 29994197, 39201414, 18806856, 37435892, 24953498, 60955663, 14731700, 67030811, 5822202, 66663942, 96193415, 11757872, 41092102, 68939068, 17081350, 14349098, 29819940, 46540998, 32151165, 37891451, 34432810, 94595668, 54987042, 86821229, 6521313, 90090964, 11365791, 65017137, 88904910, 99917599, 10366309, 23848565, 83210802, 90310261, 70800879, 14947650, 91727510, 2891150, 26139110, 4434662, 68041839, 13173644, 69579137, 92215320, 57961282, 25842078, 83133790, 38510840, 33304202, 51715482, 99658235, 3487592, 88653118, 73235980, 45237957, 66667729, 36312813, 96735716, 4515343, 57248122, 7229550, 81805959, 23569917, 30694952, 8651647, 1936762, 91990218, 54014062, 10358899, 47893286, 51472275, 48675329, 67451935, 20885148, 15948937, 34667286, 72495719, 77272628, 17365188, 97011160, 1197320, 26734892, 15902805, 64087743, 99604946, 70727211, 87598888, 19486173, 24826575, 5482538, 78589145, 77377183, 9575954, 78561158, 98739783, 38341669, 92867155, 94360702, 43933006, 4204661, 58180653, 82052050, 13468268, 51507425, 79880247, 73617245, 2331773, 80555751, 61815223, 7955293, 19101477, 61741594, 91957544, 15535065, 72738685, 39847321, 6793819, 9175338, 7685448, 71300104, 70372191, 112651, 33249630, 22766820, 74724075, 44245960, 83747892, 70541760, 51426311, 18783702, 86118021, 47090124, 7011964, 22113133, 77787724, 16971929, 2607799, 91664334, 78766358, 16054533, 36135, 54232247, 92692978, 74110882, 1204161, 86798033, 87720882, 9257405, 45919976, 10264691, 51466049, 94711842, 31491938, 1375023, 56484900, 55247455, 78300864, 20002147, 30218878, 20140249, 34497327, 77187825, 38256849, 24168411, 83269727, 62428472, 56955985, 77413012, 53632373, 40686254, 99515901, 22129328, 99224392, 79821897, 66832478, 2717150, 69697787, 68694897, 2917920, 9398733, 44627776, 95251277, 61859581, 84349107, 4787945, 4806458, 45407418, 22405120, 16405341, 35996293, 66137019, 75543508, 59371804, 45790169, 33336148, 26493119, 45667668, 45617087, 97281847, 80251430, 47361209, 75820087, 3509435, 17894977, 72019362, 63372756, 48395186, 68702632, 28796059, 3880712, 61271144, 1788101, 67084351, 30366150, 64848072, 4978543, 15075176, 11161731, 10309525, 15163258, 14723410, 98130363, 24591705, 61712234, 89499542, 62490109, 44177011, 30787683, 20867149, 76170907, 65880522, 52613508, 63059024, 43045786, 29932657, 29959549, 569864, 37183543, 23134715, 60102965, 98653983, 52293995, 16019925, 84904436, 30653863, 65081429, 78909561, 36505482, 1239555, 23110625, 69786756, 27187213, 12348118, 18663507, 71469330, 6505939, 7066775, 8791066, 99297164, 89811711, 38022834, 29834512, 52204879, 96726697, 31126490, 16424199, 49641577, 68316156, 52060076, 27625735, 69355476, 27665211, 18699206, 65074535, 56153345, 60393039, 73021291, 3294781, 61982238, 31727379, 33565483, 89078848, 84127901, 17385531, 52261574, 4064751, 82779622, 71657078, 10453030, 45995325, 40152546, 10597197, 65851721, 73031054, 50806615, 48893685, 8696647, 56515456, 89637706, 94911072, 72358170, 99125126, 44846932, 61141874, 19457589, 526217, 77300457, 83368048, 79191827, 93566986, 9599614, 93057697, 91240048, 59109400, 60176618, 90158785, 4095116, 81855445, 88698958, 21001913, 61623915, 65271999, 9623492, 93270084, 26776922, 66045587, 42782652, 508198, 79922758, 91802888, 33628349, 38494874, 98943869, 81274566, 42947632, 14093520, 21919959, 37280276, 4199704, 6497830, 9860195, 6157724, 92398073, 22997281, 62430984, 94539824, 64098930, 20836893, 70596786, 19898053, 53648154, 3773993, 93790285, 12571310, 48360198, 76434144, 68000591, 44784505, 10649306, 61263068, 77301523, 77312810, 16097038, 39171895, 89804152, 72793444, 30543215, 85117092, 8913721, 99729357, 44847298, 49597667, 92692283, 34295794, 75986488, 9188443, 92998591, 7646095, 37501808, 51464002, 99965001, 41442762, 68644627, 18131876, 43376279, 88047921, 7182642, 55143724, 59910624, 77898274, 37659250, 71920426, 82532312, 4069912, 29401781, 72238278, 95726235, 83302115, 74441448, 64055960, 86242799, 66319530, 62762857, 36780454, 11581181, 84187166, 45381876, 6986898, 30771409, 40534591, 62693428, 321665, 32274392, 45075471, 5267545, 83651211, 35512853, 17764950, 76540481, 85242963, 18001617, 90272749, 44473167, 17539625, 13231279, 96852321, 17957593, 43357947, 28928175, 11923835, 91141395, 53842979, 6038457, 78602717, 89419466, 59981773, 36580610, 49236559, 22200378, 749283, 37957788, 32250045, 89996536, 32699744, 73392814, 78884452, 69605283, 38061439, 33061250, 24619760, 89046466, 83789391, 75135448, 13348726, 30090481, 21289531, 89217461, 53666583, 26063929, 874791, 4798568, 37739481, 16099750, 48658605, 51047803, 63967300, 43322743, 81172706, 36808486, 63152504, 55960386, 4099191, 61859143, 57359924, 20122224, 28851716, 76960303, 40356877, 6819644, 56424103, 87710366, 67513640, 57803235, 17386996, 96709982, 82726008, 17727650, 59318837, 30163921, 44983451, 22721500, 44550764, 5111370, 62452034, 43501211, 74614639, 24733232, 26664538, 32161669, 76825057, 8115266, 48673079, 79942022, 80316608, 97561745, 8373289, 57241521, 98462867, 26863229, 71965942, 87160386, 55602660, 36933038, 26392416, 42237907, 44915235, 68128438, 21070110, 82178706, 21673260, 80246713, 99861373, 47887470, 17016156, 17058722, 51149804, 84293052, 26507214, 93562257, 14045383, 90004325, 66428795, 63015256, 50188404, 99524975, 98948034, 72357096, 77072625, 97783876, 89699445, 37192445, 83155442, 1250437, 46870723, 7502255, 168541, 669105, 97940276, 8634541, 94090109, 90457870, 28550822, 75153252, 34698428, 7104732, 78549759, 66903004, 70036438, 38009615, 61928316, 92554120, 1022677, 33553959, 6111563, 55615885, 72614359, 79136082, 81774825, 82651278, 4091162, 30811010, 79322415, 8128637, 26292919, 247198, 13470059, 86001008, 27185644, 58749, 54762643, 28787861, 79657802, 33895336, 9860968, 95010552, 88444207, 9829782, 67227442, 70004753, 40027975, 15064655, 76671482, 76703609, 36845587, 62936963, 85711894, 41481685, 90654836, 94076128, 4985896, 62115552, 51315460, 35456853, 68875490, 82886381, 99021067, 53888755, 43506672, 92530431, 23432750, 70782102, 37675718, 36930650, 45996863, 57240218, 82427263, 56473732 +99524975, 9860195, 75135448, 96735716, 36580610, 99861373, 36845587, 8791066, 17146629, 94595668, 92787493, 40984766, 12416768, 52293995, 62740044, 18783702, 25636669, 5970607, 85711894, 57359924, 68204242, 68939068, 37192445, 12664567, 17081350, 48774913, 96318094, 56515456, 526217, 26664538, 19376156, 35996293, 80251430, 84840549, 49882705, 95581843, 53842979, 66045587, 65715134, 53648154, 93790285, 30569392, 8129978, 92867155, 82886381, 37675718, 6111563, 7955293, 19101477, 77620120, 34295794, 51426311, 37659250, 65074535, 44479073, 56424103, 36780454, 84127901, 50702367, 99515901, 46870723, 86821229, 22721500, 2917920, 61141874, 5267545, 94516935, 98462867, 68102437, 91990218, 44915235, 6157724, 24058273, 86460488, 61373987, 64602895, 68000591, 7423788, 60581278, 33123618, 26275734, 40197395, 79880247, 92541302, 57163802, 98371444, 71300104, 2204165, 58751351, 33237508, 41481685, 52060076, 4091162, 74110882, 1204161, 74441448, 64055960, 73021291, 76330843, 21397057, 34946859, 97641116, 62693428, 71083565, 45075471, 79191827, 44481640, 10366309, 36139942, 84349107, 91240048, 85242963, 45617087, 44473167, 29510992, 27185644, 87160386, 53084256, 75128745, 508198, 96420429, 91802888, 9259676, 1788101, 26392416, 22200378, 4978543, 97011160, 22879907, 21673260, 33061250, 30395570, 21289531, 7300047, 42199455, 76671482, 11543098, 42307449, 63506281, 30653863, 30463802, 39847321, 96906410, 112651, 43322743, 6808825, 89811711, 38022834, 16971929, 30811010, 33935899, 66428795, 50668599, 9257405, 51466049, 1431742, 20002147, 62496012, 83269727, 99224392, 44060493, 50806615, 53888755, 91255408, 6521313, 11365791, 9398733, 62452034, 72358170, 65038678, 68824981, 15148031, 32161669, 69136837, 83210802, 40677414, 13470059, 35512853, 27411561, 91727510, 26998766, 48395186, 54762643, 3088684, 66250369, 36312813, 42782652, 4515343, 55602660, 23569917, 30694952, 38494874, 98943869, 91831487, 60106217, 42947632, 55470718, 21919959, 67084351, 13862149, 84406788, 10358899, 4199704, 36189527, 51590803, 68128438, 69848388, 32699744, 65338021, 82178706, 44844121, 70004753, 20867149, 87598888, 37224844, 24826575, 65880522, 67031644, 13348726, 3235882, 75407347, 20765474, 62803858, 4204661, 30543215, 26063929, 13468268, 80014588, 84904436, 65081429, 84002370, 35192533, 61741594, 415901, 1239555, 91957544, 38365584, 37560164, 41380093, 15535065, 54868730, 33249630, 12303248, 5073754, 74724075, 70367851, 79136082, 18504197, 23740167, 99965001, 86118021, 50007421, 22113133, 29834512, 7182642, 82651278, 4119747, 65047700, 19272365, 50188404, 95726235, 47298834, 78300864, 60955663, 4668450, 67474219, 3294781, 59501487, 38256849, 54606384, 89699445, 33565483, 26744917, 94076128, 669105, 90090964, 74743862, 48893685, 321665, 29065964, 36753250, 45049308, 54517921, 39553046, 83368048, 99917599, 82339363, 93057697, 60176618, 99549373, 22405120, 16405341, 48673079, 26102057, 14947650, 97940276, 18001617, 13173644, 69641513, 84684495, 21001913, 76315420, 40865610, 28550822, 63372756, 65271999, 58224549, 8055981, 93270084, 3880712, 68816413, 59981773, 34493392, 38008118, 54663246, 24591705, 15015906, 53393358, 78884452, 21993752, 38658347, 62490109, 64087743, 99604946, 24619760, 44177011, 1569515, 15064655, 76170907, 78589145, 45990383, 98739783, 39801351, 95395112, 75777973, 1022677, 93933709, 23134715, 60102965, 53666583, 72793444, 58180653, 36685023, 34698463, 76703609, 55615885, 26507214, 80555751, 9188443, 18411915, 70372191, 69786756, 42073124, 81172706, 71522968, 18663507, 37620363, 37501808, 36808486, 47090124, 39373729, 59329511, 95957797, 77787724, 29466406, 52204879, 18131876, 2607799, 96726697, 16054533, 16424199, 49641577, 72732205, 69355476, 49271185, 63015256, 14363867, 56153345, 23194618, 85571389, 29994197, 39201414, 73786944, 18806856, 60393039, 77284619, 20140249, 5822202, 66663942, 34497327, 70420215, 11757872, 62762857, 77187825, 55669657, 24168411, 33201905, 99333375, 31727379, 8128637, 77413012, 57803235, 17385531, 82726008, 4064751, 17727650, 46540998, 6871053, 37891451, 16380211, 10597197, 34432810, 72278539, 4985896, 89637706, 65017137, 16387575, 32274392, 9599614, 23848565, 90310261, 59109400, 4787945, 41092172, 76540481, 66137019, 75543508, 80316608, 45790169, 29516592, 78218845, 47361209, 21533347, 92215320, 75820087, 3509435, 44348328, 38510840, 90013093, 17068582, 22450468, 88251446, 91141395, 34698428, 66611759, 62357986, 44664587, 66667729, 28787861, 54199160, 79657802, 26776922, 30998561, 62115552, 8651647, 14326617, 42237907, 30366150, 63628376, 49236559, 37280276, 64848072, 93515664, 37957788, 27325428, 77272628, 62430984, 30764367, 64098930, 14723410, 98130363, 26734892, 17857111, 20836893, 20681921, 19898053, 55770687, 62232211, 15902805, 38061439, 3773993, 31733363, 55753905, 19486173, 5482538, 44784505, 52613508, 69255765, 92554120, 29932657, 77312810, 82979980, 43933006, 89804152, 55189057, 54427233, 2331773, 36505482, 61815223, 83948335, 49597667, 92692283, 75986488, 45428665, 7685448, 51047803, 92604458, 63967300, 22766820, 29029316, 47708850, 7646095, 71469330, 73109997, 6505939, 51464002, 83747892, 67281495, 55960386, 4099191, 56473732, 56461322, 33699435, 4508700, 99297164, 27041967, 91664334, 88047921, 32058615, 7517032, 77898274, 24915585, 28851716, 86798033, 99021067, 29401781, 72238278, 10264691, 56484900, 37435892, 83083131, 40781854, 67030811, 96193415, 57658654, 9603598, 79322415, 97783876, 84187166, 67513640, 39986008, 56955985, 45848907, 45381876, 89078848, 36396314, 53632373, 22129328, 30771409, 99971982, 65851721, 61897582, 66832478, 14220886, 2717150, 82897371, 69697787, 8696647, 45306070, 18833224, 43506672, 43501211, 61960400, 24733232, 92803766, 8115266, 72725103, 17764950, 20642888, 9058407, 70800879, 79942022, 22435353, 4434662, 68041839, 97281847, 8373289, 8634541, 69579137, 17539625, 57961282, 26863229, 86001008, 3233084, 33304202, 35092039, 99658235, 61623915, 75153252, 10961421, 11923835, 6221471, 7104732, 60430369, 57248122, 79922758, 6038457, 74137926, 66903004, 85023028, 36468541, 36933038, 88444207, 67451935, 59405277, 15948937, 22997281, 14626618, 94539824, 7919588, 45794415, 69605283, 85695762, 73222868, 30891921, 89046466, 79738755, 40027975, 48360198, 76434144, 64157906, 98648327, 61928316, 62552524, 9575954, 47213183, 78561158, 13819358, 31161687, 62051033, 61728685, 55850790, 33553959, 86543538, 39171895, 3233569, 98653983, 66322959, 51507425, 73617245, 46851987, 44847298, 48892201, 32590267, 73124510, 29188588, 16099750, 48658605, 92998591, 77694700, 27187213, 93562257, 41245325, 7011964, 41442762, 55143724, 97379790, 36135, 61859143, 90004325, 54232247, 71920426, 18699206, 72777973, 55247455, 20645197, 86242799, 14731700, 30218878, 77072625, 36930650, 37166608, 11581181, 17037369, 15536795, 57240218, 6986898, 40686254, 52261574, 14349098, 29819940, 7502255, 54058788, 59318837, 247198, 168541, 44550764, 92353856, 68694897, 44846932, 88904910, 91281584, 77300457, 74614639, 44842615, 38645117, 76825057, 90158785, 83533741, 2891150, 26139110, 33797252, 33336148, 97561745, 30139692, 33431960, 96852321, 25842078, 83133790, 17894977, 72274002, 93359396, 82427263, 3183975, 26114953, 78549759, 89419466, 47792865, 75229462, 95290172, 57393458, 47893286, 48675329, 65454636, 92398073, 53802686, 18466635, 32250045, 17365188, 32159704, 34236719, 6525948, 3633375, 73392814, 35456853, 81898046, 80246713, 68110330, 176257, 12571310, 22108665, 63059024, 10649306, 47887470, 17016156, 29959549, 569864, 37183543, 42967683, 82052050, 85117092, 99729357, 48260151, 23110625, 9175338, 89214616, 42692881, 66885828, 44245960, 63152504, 70541760, 7066775, 34044787, 68644627, 71333116, 74357852, 59910624, 20122224, 90654836, 27665211, 24314885, 7687278, 4069912, 72373496, 31491938, 24953498, 66319530, 98948034, 72357096, 824872, 74452589, 41092102, 96709982, 1250437, 71657078, 40534591, 10453030, 32151165, 79821897, 40152546, 97057187, 30163921, 67124282, 44627776, 36812683, 95251277, 31904591, 93566986, 83651211, 45407418, 93053405, 90272749, 94090109, 19891772, 71965942, 8099994, 51715482, 28928175, 58749, 88653118, 68702632, 28796059, 49328608, 51315460, 20568363, 1936762, 9860968, 81274566, 95010552, 61271144, 70036438, 28734791, 20885148, 81677380, 59614746, 61712234, 16595436, 53547802, 70596786, 20486294, 30787683, 66116458, 86301513, 5640302, 42644903, 65275241, 38341669, 77301523, 89217461, 16097038, 48088883, 17058722, 51149804, 16019925, 84293052, 72614359, 78909561, 37739481, 72738685, 29635537, 99690194, 12348118, 71316369, 43376279, 40781449, 92692978, 92033260, 87720882, 40356877, 45919976, 16567550, 83302115, 94711842, 1375023, 43152977, 6819644, 61982238, 62428472, 50567636, 45996863, 17386996, 5832946, 73031054, 67793644, 54987042, 5111370, 94911072, 9886593, 92530431, 61859581, 4806458, 59371804, 49598724, 26493119, 57241521, 19939935, 90457870, 3487592, 73235980, 78602717, 33628349, 75052463, 14093520, 83150534, 70782102, 749283, 11161731, 72495719, 89996536, 81853704, 6153406, 21070110, 83789391, 70727211, 59197747, 88130087, 30090481, 77377183, 92071990, 61263068, 94360702, 4798568, 62936963, 14045383, 81774825, 68316156, 82532312, 99226875, 83155442, 47738185, 44983451, 99125126, 19457589, 45667668, 58208470, 13231279, 88698958, 17957593, 16445503, 78785507, 9623492, 2208785, 81805959, 26397786, 9829782, 6497830, 17976208, 15075176, 90061527, 15163258, 84166196, 1197320, 67227442, 89499542, 54263880, 8729146, 43045786, 68875490, 73168565, 874791, 32426752, 78766358, 31126490, 27625735, 91938191, 76960303, 16684829, 12024238, 86022504, 26292919, 45995325, 82327024, 23432750, 51588015, 4095116, 72019362, 45237957, 51472275, 10309525, 34667286, 8807940, 66271566, 8913721, 85116755, 59177718, 44889423, 79806380, 87710366, 82779622, 56531125, 57020481, 81855445, 33895336, 7229550, 22942635, 38009615, 8733068, 54014062, 6793819, 43357947 +56424103, 91957544, 33797252, 62803858, 6111563, 53666583, 34044787, 84349107, 18001617, 72274002, 66250369, 6153406, 29959549, 90004325, 18699206, 20140249, 47738185, 46540998, 71083565, 39553046, 9599614, 68041839, 25842078, 6497830, 65454636, 32699744, 38061439, 40197395, 48260151, 84904436, 18411915, 68644627, 7182642, 97379790, 55669657, 83269727, 45848907, 14349098, 53888755, 44842615, 45667668, 58208470, 19939935, 8055981, 95581843, 749283, 54663246, 35456853, 98648327, 39801351, 23134715, 16097038, 94360702, 60102965, 89804152, 51507425, 26507214, 77620120, 59177718, 32426752, 47708850, 71469330, 51426311, 41442762, 5970607, 27041967, 88047921, 59910624, 9257405, 67474219, 99226875, 74452589, 34946859, 94076128, 22405120, 94516935, 59371804, 29516592, 99658235, 26776922, 2208785, 57248122, 7229550, 14093520, 49236559, 70036438, 92398073, 20681921, 30891921, 8807940, 3773993, 176257, 99861373, 12571310, 3235882, 69255765, 10649306, 95395112, 75777973, 43933006, 36845587, 7955293, 49597667, 41380093, 92604458, 54868730, 99690194, 81172706, 2204165, 36808486, 41245325, 4099191, 8791066, 52204879, 36135, 30811010, 19272365, 39201414, 73786944, 8733068, 24953498, 57658654, 26744917, 15536795, 83155442, 7502255, 79821897, 59318837, 45995325, 5111370, 62452034, 44846932, 45049308, 74614639, 61960400, 32274392, 31904591, 24733232, 51588015, 76540481, 45790169, 26493119, 97281847, 97561745, 21533347, 69641513, 90457870, 58749, 6221471, 79657802, 66045587, 3183975, 78602717, 1936762, 75229462, 83150534, 26392416, 13862149, 48675329, 4199704, 6525948, 61712234, 70596786, 82178706, 33061250, 30395570, 19486173, 30090481, 44784505, 47213183, 78561158, 86301513, 61728685, 82886381, 47887470, 89217461, 33553959, 4204661, 99729357, 65081429, 35192533, 34295794, 75986488, 12348118, 71522968, 6505939, 18504197, 7011964, 59329511, 95957797, 16971929, 55143724, 7517032, 85571389, 31491938, 74441448, 83083131, 60955663, 72357096, 59501487, 70420215, 89699445, 62428472, 31727379, 56955985, 12664567, 89078848, 36396314, 17385531, 76330843, 30771409, 86821229, 72278539, 22721500, 82897371, 8696647, 89637706, 65017137, 72358170, 57020481, 43506672, 43501211, 79191827, 32161669, 5267545, 40677414, 83651211, 20642888, 9058407, 48673079, 85242963, 79942022, 35996293, 14947650, 33431960, 13173644, 17894977, 72019362, 10961421, 28796059, 3880712, 42782652, 20568363, 8651647, 85023028, 47792865, 30366150, 37280276, 28734791, 37957788, 4978543, 15075176, 10309525, 81677380, 30764367, 26734892, 17857111, 67227442, 55770687, 53648154, 22879907, 62490109, 38009615, 12416768, 24619760, 54263880, 40027975, 76170907, 24826575, 68000591, 9575954, 43045786, 20765474, 55850790, 86543538, 48088883, 98653983, 58180653, 30543215, 11543098, 66322959, 4798568, 80555751, 36505482, 61741594, 32590267, 23110625, 29635537, 16099750, 7685448, 92998591, 5073754, 44245960, 70367851, 70541760, 55960386, 99965001, 86118021, 33699435, 4508700, 99297164, 33237508, 31126490, 49641577, 57359924, 4091162, 27625735, 79806380, 49271185, 65074535, 16684829, 87720882, 23194618, 29994197, 16567550, 1431742, 78300864, 86242799, 66319530, 98948034, 824872, 11757872, 38256849, 54606384, 33201905, 17037369, 84127901, 6986898, 82726008, 29819940, 5832946, 16380211, 66832478, 54987042, 14220886, 6521313, 90090964, 48893685, 45306070, 321665, 99125126, 88904910, 44627776, 91281584, 16387575, 95251277, 65038678, 54517921, 92530431, 44481640, 90310261, 45407418, 92787493, 16405341, 4095116, 27411561, 26139110, 80316608, 93053405, 45617087, 19891772, 17539625, 92215320, 26863229, 86001008, 83133790, 90013093, 76315420, 51715482, 49882705, 68102437, 88251446, 63372756, 73235980, 44664587, 28787861, 30998561, 49328608, 508198, 82427263, 55602660, 38494874, 98943869, 89419466, 95290172, 84406788, 64848072, 93515664, 18466635, 90061527, 97011160, 14723410, 20836893, 24058273, 78884452, 20486294, 22942635, 40984766, 68110330, 64087743, 44177011, 89046466, 70727211, 87598888, 37224844, 8729146, 66116458, 42644903, 38341669, 73168565, 77312810, 93933709, 42967683, 3233569, 17058722, 55189057, 76671482, 8913721, 34698463, 55615885, 30653863, 2331773, 37739481, 48892201, 73124510, 92541302, 85116755, 98371444, 48658605, 51047803, 71300104, 42073124, 42692881, 37501808, 93562257, 51464002, 67281495, 39373729, 89811711, 29466406, 2607799, 96726697, 78766358, 77898274, 24915585, 40781449, 90654836, 28851716, 92692978, 74110882, 33935899, 66428795, 44479073, 68204242, 72238278, 45919976, 10264691, 60393039, 1375023, 99524975, 43152977, 20645197, 40781854, 97783876, 87710366, 36780454, 11581181, 99333375, 84187166, 57240218, 17386996, 99224392, 48774913, 54058788, 37891451, 10597197, 94595668, 99971982, 73031054, 168541, 44983451, 4985896, 2717150, 74743862, 62693428, 9886593, 19457589, 77300457, 68824981, 15148031, 38645117, 13470059, 70800879, 2891150, 75543508, 30139692, 78218845, 57961282, 75820087, 44348328, 84684495, 88698958, 3233084, 17957593, 17068582, 8099994, 16445503, 87160386, 28550822, 54762643, 68702632, 91802888, 74137926, 26114953, 23569917, 33628349, 51315460, 9259676, 91831487, 14326617, 21919959, 36468541, 67084351, 42237907, 10358899, 22200378, 9829782, 51472275, 44915235, 67451935, 9860195, 6157724, 34493392, 15163258, 89996536, 34236719, 7919588, 81853704, 53547802, 45794415, 89499542, 85695762, 21070110, 81898046, 44844121, 99604946, 70004753, 20867149, 83789391, 31733363, 48360198, 22108665, 75407347, 60581278, 21289531, 26275734, 42199455, 82052050, 36685023, 52293995, 85117092, 63506281, 54427233, 80014588, 19101477, 415901, 83948335, 45428665, 9175338, 9188443, 70372191, 69786756, 112651, 33249630, 43322743, 74724075, 83747892, 25636669, 77787724, 29834512, 85711894, 16424199, 54232247, 91938191, 1204161, 86798033, 63015256, 99021067, 72777973, 40356877, 18806856, 94711842, 55247455, 30218878, 79322415, 67513640, 45381876, 50702367, 40686254, 17081350, 99515901, 21397057, 10453030, 97057187, 67793644, 50806615, 30163921, 61897582, 669105, 11365791, 67124282, 29065964, 36753250, 18833224, 93566986, 61859581, 36139942, 92803766, 60176618, 72725103, 35512853, 26102057, 66137019, 33336148, 44473167, 57241521, 94090109, 29510992, 69579137, 38510840, 21001913, 27185644, 43357947, 84840549, 26998766, 48395186, 88653118, 66667729, 60106217, 36933038, 88444207, 26397786, 54014062, 63628376, 59405277, 27325428, 51590803, 77272628, 68128438, 59614746, 64098930, 15015906, 73222868, 80246713, 1569515, 15064655, 55753905, 59197747, 64602895, 78589145, 67031644, 62552524, 30569392, 13819358, 65275241, 33123618, 77301523, 51149804, 79880247, 62740044, 72614359, 37560164, 6793819, 89214616, 22766820, 29029316, 7646095, 63152504, 18783702, 71333116, 74357852, 14045383, 81774825, 61859143, 4119747, 71920426, 82532312, 50668599, 29401781, 72373496, 51466049, 12024238, 47298834, 37435892, 64055960, 4668450, 6819644, 77284619, 36930650, 68939068, 8128637, 46870723, 71657078, 32151165, 6871053, 34432810, 65851721, 44550764, 92353856, 69697787, 9398733, 526217, 45075471, 26664538, 59109400, 76825057, 90158785, 23432750, 4806458, 22435353, 90272749, 80251430, 47361209, 81855445, 98462867, 33304202, 93359396, 40865610, 75153252, 91141395, 34698428, 9623492, 60430369, 78549759, 66903004, 9860968, 68816413, 59981773, 55470718, 61271144, 91990218, 57393458, 47893286, 17976208, 11161731, 53802686, 34667286, 17365188, 14626618, 84166196, 38008118, 69848388, 21673260, 15902805, 30787683, 5482538, 88130087, 61928316, 45990383, 63059024, 7423788, 98739783, 31161687, 62051033, 1022677, 82979980, 66271566, 42307449, 84293052, 84002370, 44847298, 62936963, 78909561, 96906410, 12303248, 66885828, 44889423, 79136082, 6808825, 7066775, 56473732, 22113133, 71316369, 38022834, 91664334, 16054533, 32058615, 82651278, 52060076, 20122224, 72732205, 92033260, 14363867, 50188404, 95726235, 20002147, 3294781, 62496012, 5822202, 66663942, 77072625, 9603598, 41092102, 24168411, 37166608, 39986008, 37192445, 53632373, 96709982, 17727650, 82779622, 40534591, 247198, 97641116, 91255408, 68694897, 56531125, 36812683, 83368048, 99917599, 82327024, 93057697, 23848565, 17764950, 41092172, 97940276, 4434662, 3509435, 22450468, 28928175, 75128745, 11923835, 45237957, 3088684, 36312813, 54199160, 33895336, 4515343, 96420429, 79922758, 6038457, 62115552, 81805959, 75052463, 20885148, 72495719, 32159704, 24591705, 79738755, 93790285, 75135448, 65880522, 76434144, 64157906, 77377183, 52613508, 92867155, 29932657, 37675718, 37183543, 76703609, 46851987, 30463802, 61815223, 1239555, 92692283, 29188588, 39847321, 57163802, 63967300, 77694700, 18663507, 73109997, 50007421, 17146629, 58751351, 18131876, 37659250, 69355476, 27665211, 24314885, 65047700, 67030811, 73021291, 96193415, 62762857, 50567636, 45996863, 26292919, 77413012, 57803235, 52261574, 22129328, 4064751, 44060493, 96318094, 40152546, 2917920, 56515456, 82339363, 10366309, 69136837, 8115266, 99549373, 8373289, 8634541, 13231279, 35092039, 61623915, 62357986, 7104732, 58224549, 93270084, 96735716, 30694952, 42947632, 36189527, 15948937, 62430984, 1197320, 16595436, 65715134, 3633375, 65338021, 13348726, 8129978, 5640302, 92554120, 17016156, 72793444, 16019925, 13468268, 73617245, 72738685, 27187213, 37620363, 23740167, 56461322, 47090124, 43376279, 41481685, 7687278, 4069912, 56484900, 14731700, 61982238, 34497327, 77187825, 1250437, 94911072, 61141874, 91240048, 83210802, 83533741, 49598724, 96852321, 65271999, 53842979, 81274566, 70782102, 36580610, 94539824, 19898053, 73392814, 69605283, 38658347, 86460488, 61373987, 61263068, 874791, 38365584, 15535065, 68316156, 56153345, 33565483, 91727510, 78785507, 3487592, 95010552, 1788101, 32250045, 98130363, 21993752, 92071990, 569864, 7300047, 26063929, 76960303, 83302115, 86022504, 4787945, 66611759, 22997281, 53393358, 62232211, 68875490, 39171895, 71965942, 19376156, 53084256 +42199455, 59981773, 26063929, 53084256, 17146629, 55143724, 56515456, 5267545, 3235882, 31161687, 37675718, 30543215, 99965001, 54232247, 38256849, 45381876, 4985896, 89637706, 526217, 24733232, 26664538, 84349107, 33336148, 3509435, 3233084, 21001913, 6221471, 60430369, 48675329, 81853704, 78884452, 38061439, 75407347, 66116458, 61741594, 29188588, 112651, 7011964, 16424199, 90654836, 37435892, 50567636, 59109400, 8115266, 72725103, 22435353, 97561745, 17894977, 3088684, 75229462, 36580610, 13862149, 10358899, 15163258, 62232211, 76170907, 75135448, 7423788, 60581278, 21289531, 42967683, 76671482, 73617245, 55615885, 35192533, 41380093, 92692283, 99690194, 12303248, 4119747, 92692978, 65047700, 39201414, 12024238, 24953498, 14731700, 824872, 70420215, 83269727, 84187166, 57803235, 96709982, 44060493, 59318837, 321665, 43501211, 40677414, 38645117, 4095116, 26102057, 83533741, 27411561, 2891150, 98462867, 76315420, 28550822, 63372756, 62357986, 93270084, 26776922, 51472275, 44915235, 59405277, 65454636, 97011160, 32159704, 15015906, 3633375, 80246713, 30395570, 1022677, 44847298, 36845587, 32590267, 34295794, 39847321, 48658605, 51047803, 33249630, 77694700, 7646095, 37501808, 77787724, 32058615, 50188404, 23194618, 85571389, 51466049, 6819644, 73021291, 86022504, 33565483, 57240218, 22129328, 10597197, 669105, 69697787, 44627776, 45075471, 45407418, 94516935, 66137019, 68041839, 69579137, 17539625, 21533347, 44348328, 33304202, 11923835, 95581843, 33895336, 66045587, 9860968, 61271144, 63628376, 6497830, 90061527, 98130363, 16595436, 65715134, 73222868, 82178706, 22879907, 86460488, 15064655, 12571310, 19486173, 5482538, 67031644, 30569392, 86301513, 8129978, 10649306, 92554120, 6111563, 17058722, 82052050, 85117092, 84904436, 30653863, 62740044, 72614359, 30463802, 15535065, 32426752, 92998591, 29029316, 73109997, 4508700, 99297164, 33237508, 74357852, 81774825, 82651278, 52060076, 4091162, 20122224, 86798033, 63015256, 14363867, 4069912, 44479073, 29994197, 66319530, 68939068, 56955985, 37192445, 12664567, 26292919, 6986898, 17727650, 48774913, 54058788, 168541, 54987042, 44983451, 44550764, 62452034, 9886593, 44846932, 36753250, 32274392, 79191827, 13470059, 92787493, 48673079, 33797252, 26493119, 30139692, 29516592, 8634541, 80251430, 13173644, 78218845, 57961282, 69641513, 84684495, 78785507, 88251446, 99658235, 61623915, 48395186, 2208785, 7229550, 78549759, 91831487, 36468541, 30366150, 54014062, 4199704, 15075176, 6157724, 55770687, 81898046, 15902805, 20867149, 83789391, 31733363, 55753905, 59197747, 64157906, 13348726, 52613508, 68875490, 13819358, 38341669, 33123618, 569864, 7300047, 39171895, 36685023, 42307449, 13468268, 46851987, 26507214, 19101477, 415901, 83948335, 38365584, 77620120, 92541302, 57163802, 9175338, 85116755, 70372191, 63967300, 63152504, 7066775, 4099191, 56461322, 34044787, 29834512, 85711894, 14045383, 36135, 30811010, 37659250, 69355476, 1204161, 19272365, 73786944, 18806856, 74441448, 1431742, 4668450, 62496012, 99226875, 74452589, 87710366, 62428472, 39986008, 45848907, 36396314, 84127901, 83155442, 1250437, 14349098, 46540998, 45995325, 94076128, 34432810, 99971982, 50806615, 30163921, 72278539, 6521313, 90090964, 74743862, 9398733, 45306070, 29065964, 15148031, 93057697, 92803766, 76825057, 23432750, 51588015, 99549373, 41092172, 20642888, 76540481, 97940276, 91727510, 45790169, 45617087, 57241521, 19891772, 29510992, 13231279, 75820087, 27185644, 90457870, 22450468, 93359396, 40865610, 65271999, 66611759, 54762643, 8055981, 66667729, 42782652, 82427263, 57248122, 91802888, 6038457, 3183975, 55470718, 21919959, 36933038, 42237907, 37280276, 22200378, 64848072, 9829782, 70036438, 36189527, 34667286, 27325428, 17365188, 22997281, 14626618, 20836893, 67227442, 22942635, 99604946, 79738755, 65880522, 61928316, 44784505, 92071990, 63059024, 20765474, 62051033, 77301523, 77312810, 89217461, 86543538, 16097038, 72793444, 58180653, 55189057, 40197395, 52293995, 63506281, 874791, 65081429, 78909561, 49597667, 23110625, 72738685, 45428665, 6793819, 9188443, 92604458, 69786756, 43322743, 12348118, 44889423, 70367851, 71469330, 36808486, 70541760, 67281495, 55960386, 47090124, 33699435, 25636669, 89811711, 5970607, 71333116, 29466406, 88047921, 59910624, 61859143, 57359924, 28851716, 72732205, 33935899, 49271185, 66428795, 50668599, 72373496, 78300864, 67474219, 66663942, 61982238, 96193415, 34497327, 36930650, 37166608, 36780454, 8128637, 15536795, 50702367, 82726008, 99224392, 4064751, 47738185, 21397057, 29819940, 5832946, 71657078, 34946859, 96318094, 40152546, 37891451, 73031054, 67793644, 53888755, 91255408, 8696647, 68694897, 62693428, 56531125, 72358170, 61141874, 77300457, 43506672, 99917599, 82339363, 61859581, 23848565, 19376156, 83210802, 90158785, 4787945, 35512853, 9058407, 80316608, 93053405, 49598724, 97281847, 92215320, 25842078, 83133790, 72274002, 8099994, 26998766, 49882705, 68102437, 75153252, 3487592, 7104732, 58224549, 73235980, 66250369, 68702632, 28796059, 3880712, 53842979, 96735716, 79922758, 23569917, 20568363, 66903004, 89419466, 47792865, 83150534, 95290172, 95010552, 70782102, 88444207, 1788101, 67084351, 91990218, 57393458, 49236559, 749283, 67451935, 81677380, 68128438, 34493392, 59614746, 34236719, 38008118, 54663246, 64098930, 14723410, 69605283, 21993752, 20486294, 21673260, 8807940, 68110330, 38009615, 12416768, 3773993, 24619760, 1569515, 176257, 70727211, 99861373, 24826575, 48360198, 64602895, 78589145, 22108665, 43045786, 65275241, 92867155, 73168565, 61728685, 17016156, 61263068, 82979980, 93933709, 43933006, 60102965, 48088883, 4204661, 98653983, 51149804, 66322959, 54427233, 84002370, 36505482, 37739481, 73124510, 37560164, 75986488, 18411915, 98371444, 71300104, 54868730, 42692881, 74724075, 44245960, 50007421, 8791066, 22113133, 71316369, 68644627, 38022834, 18131876, 31126490, 41481685, 68316156, 90004325, 24915585, 40781449, 71920426, 74110882, 7687278, 92033260, 16684829, 56153345, 29401781, 83302115, 47298834, 55247455, 20645197, 83083131, 20002147, 30218878, 3294781, 56424103, 77072625, 9603598, 11757872, 79322415, 41092102, 54606384, 24168411, 33201905, 11581181, 99333375, 45996863, 53632373, 17386996, 40686254, 17385531, 52261574, 30771409, 10453030, 32151165, 6871053, 247198, 16380211, 94595668, 65851721, 66832478, 86821229, 2717150, 65017137, 16387575, 18833224, 71083565, 82327024, 10366309, 36139942, 69136837, 16405341, 70800879, 35996293, 59371804, 45667668, 90272749, 8373289, 33431960, 19939935, 17957593, 90013093, 10961421, 88653118, 45237957, 44664587, 36312813, 28787861, 54199160, 79657802, 508198, 96420429, 78602717, 81805959, 9259676, 38494874, 68816413, 42947632, 26397786, 28734791, 4978543, 9860195, 11161731, 10309525, 15948937, 32250045, 72495719, 94539824, 1197320, 7919588, 24591705, 53547802, 65338021, 70596786, 85695762, 38658347, 35456853, 53648154, 62490109, 54263880, 44177011, 61373987, 70004753, 93790285, 45990383, 69255765, 5640302, 95395112, 82886381, 33553959, 37183543, 3233569, 26275734, 76703609, 16019925, 51507425, 4798568, 16099750, 59177718, 66885828, 47708850, 18663507, 2204165, 37620363, 6808825, 51426311, 56473732, 59329511, 16971929, 27041967, 52204879, 96726697, 78766358, 7182642, 49641577, 27625735, 82532312, 24314885, 65074535, 99021067, 68204242, 72238278, 40356877, 45919976, 10264691, 16567550, 60393039, 31491938, 99524975, 86242799, 60955663, 40781854, 5822202, 57658654, 97783876, 55669657, 31727379, 7502255, 97057187, 97641116, 11365791, 94911072, 99125126, 36812683, 45049308, 95251277, 74614639, 92530431, 68824981, 32161669, 9599614, 91240048, 83651211, 85242963, 14947650, 26139110, 18001617, 47361209, 17068582, 43357947, 16445503, 51715482, 84840549, 28928175, 58749, 34698428, 9623492, 30998561, 4515343, 55602660, 74137926, 8651647, 81274566, 14093520, 26392416, 47893286, 93515664, 20885148, 62430984, 30764367, 20681921, 24058273, 6153406, 45794415, 19898053, 30891921, 33061250, 89046466, 87598888, 98648327, 68000591, 78561158, 98739783, 62803858, 75777973, 29932657, 89804152, 66271566, 80014588, 62936963, 61815223, 7955293, 1239555, 91957544, 29635537, 5073754, 51464002, 23740167, 86118021, 95957797, 2607799, 43376279, 91664334, 16054533, 18699206, 43152977, 56484900, 62762857, 89699445, 17037369, 77413012, 89078848, 99515901, 46870723, 82779622, 40534591, 61897582, 22721500, 14220886, 82897371, 2917920, 88904910, 19457589, 91281584, 65038678, 93566986, 44842615, 90310261, 60176618, 4806458, 17764950, 94090109, 81855445, 88698958, 96852321, 71965942, 35092039, 62115552, 26114953, 33628349, 51315460, 1936762, 85023028, 60106217, 84406788, 18466635, 77272628, 6525948, 69848388, 17857111, 32699744, 89499542, 40984766, 64087743, 40027975, 37224844, 88130087, 76434144, 62552524, 9575954, 29959549, 11543098, 79880247, 80555751, 7685448, 89214616, 81172706, 71522968, 83747892, 18504197, 18783702, 58751351, 39373729, 97379790, 7517032, 77898274, 91938191, 76960303, 87720882, 9257405, 95726235, 1375023, 67030811, 77284619, 59501487, 77187825, 26744917, 17081350, 79821897, 54517921, 39553046, 83368048, 44481640, 22405120, 79942022, 4434662, 58208470, 26863229, 86001008, 38510840, 87160386, 72019362, 37957788, 53802686, 26734892, 73392814, 21070110, 30787683, 77377183, 47213183, 55850790, 47887470, 94360702, 34698463, 22766820, 27187213, 93562257, 41442762, 27665211, 72777973, 94711842, 98948034, 72357096, 20140249, 67513640, 48893685, 67124282, 5111370, 31904591, 75128745, 49328608, 14326617, 17976208, 92398073, 51590803, 84166196, 53393358, 39801351, 42644903, 8913721, 99729357, 84293052, 48892201, 96906410, 42073124, 6505939, 79136082, 76330843, 92353856, 44473167, 91141395, 75052463, 98943869, 89996536, 61712234, 44844121, 8729146, 23134715, 53666583, 41245325, 79806380, 64055960, 61960400, 30694952, 30090481, 48260151, 2331773, 57020481, 75543508, 8733068 +38658347, 45428665, 58751351, 1431742, 4064751, 99971982, 90090964, 88698958, 66667729, 79922758, 57393458, 73222868, 30395570, 92867155, 55850790, 44479073, 36780454, 12664567, 38645117, 20642888, 75543508, 45617087, 53084256, 28796059, 97011160, 45794415, 78884452, 21993752, 40984766, 75135448, 63059024, 42644903, 60581278, 13468268, 29188588, 71300104, 33249630, 74724075, 18663507, 37501808, 99965001, 33699435, 77787724, 54232247, 27665211, 9257405, 51466049, 83083131, 73021291, 824872, 37166608, 6986898, 40534591, 54987042, 44550764, 2917920, 39553046, 31904591, 32161669, 59371804, 57961282, 88251446, 28550822, 3880712, 78602717, 55470718, 36933038, 64848072, 77272628, 34236719, 12416768, 86460488, 79738755, 45990383, 569864, 7300047, 26275734, 42199455, 78909561, 30463802, 92541302, 51047803, 37620363, 5970607, 27041967, 96726697, 88047921, 32058615, 24314885, 86798033, 14363867, 23194618, 72238278, 10264691, 16567550, 37435892, 20645197, 14731700, 20002147, 61982238, 83269727, 33565483, 30771409, 54058788, 96318094, 14220886, 74743862, 36812683, 16387575, 71083565, 61859581, 72725103, 17764950, 91727510, 80316608, 49598724, 90272749, 33431960, 13173644, 84684495, 3233084, 72274002, 76315420, 84840549, 28928175, 40865610, 75153252, 45237957, 44664587, 93270084, 57248122, 91802888, 26114953, 23569917, 1936762, 91831487, 36580610, 22200378, 6157724, 18466635, 22997281, 14626618, 15163258, 32159704, 30764367, 59614746, 53393358, 21673260, 64087743, 99604946, 99861373, 19486173, 78589145, 62552524, 8129978, 21289531, 72793444, 36685023, 79880247, 62936963, 35192533, 92604458, 96906410, 66885828, 12348118, 7646095, 86118021, 47090124, 25636669, 71316369, 38022834, 95957797, 74357852, 14045383, 49641577, 24915585, 37659250, 1204161, 56153345, 50668599, 73786944, 96193415, 77072625, 31727379, 84127901, 21397057, 94595668, 97057187, 30163921, 8696647, 61141874, 65038678, 82327024, 93057697, 83210802, 51588015, 99549373, 83533741, 4434662, 33336148, 57241521, 94090109, 44348328, 98462867, 71965942, 38510840, 21001913, 17068582, 22450468, 26998766, 3487592, 65271999, 73235980, 96420429, 60430369, 66903004, 42947632, 54014062, 84406788, 10358899, 47893286, 36189527, 27325428, 34493392, 15015906, 3633375, 69605283, 85695762, 20486294, 22108665, 44784505, 78561158, 82979980, 37183543, 16097038, 39171895, 40197395, 55615885, 30653863, 65081429, 2331773, 84002370, 26507214, 32590267, 15535065, 6793819, 85116755, 16099750, 42073124, 112651, 63967300, 22766820, 42692881, 27187213, 67281495, 56461322, 43376279, 7182642, 16054533, 16424199, 41481685, 52060076, 90654836, 74110882, 92033260, 4069912, 87720882, 40356877, 18806856, 99524975, 74441448, 6819644, 11757872, 74452589, 24168411, 39986008, 45381876, 57803235, 50702367, 52261574, 82726008, 7502255, 37891451, 61897582, 91255408, 22721500, 6521313, 69697787, 68694897, 9398733, 72358170, 88904910, 74614639, 99917599, 15148031, 26664538, 8115266, 23432750, 48673079, 76540481, 26102057, 2891150, 33797252, 68041839, 8373289, 80251430, 78218845, 47361209, 69641513, 96852321, 33304202, 90013093, 72019362, 61623915, 63372756, 58224549, 9623492, 68702632, 96735716, 82427263, 7229550, 30694952, 8651647, 9860968, 95290172, 61271144, 63628376, 93515664, 17976208, 9860195, 10309525, 51590803, 17365188, 81677380, 69848388, 53547802, 24058273, 65338021, 81898046, 44844121, 15902805, 8807940, 68110330, 44177011, 1569515, 70004753, 176257, 12571310, 55753905, 64602895, 5482538, 13348726, 77377183, 43045786, 68875490, 13819358, 92554120, 31161687, 47887470, 3233569, 30543215, 11543098, 85117092, 42307449, 26063929, 84904436, 46851987, 80555751, 83948335, 39847321, 57163802, 9188443, 59177718, 77694700, 99297164, 39373729, 85711894, 36135, 40781449, 18699206, 33935899, 66428795, 68204242, 60393039, 43152977, 40781854, 30218878, 20140249, 5822202, 97783876, 41092102, 62428472, 99333375, 26744917, 8128637, 77413012, 53632373, 96709982, 46870723, 34946859, 45995325, 247198, 94076128, 73031054, 66832478, 72278539, 82897371, 56515456, 65017137, 36753250, 54517921, 45075471, 36139942, 76825057, 90158785, 83651211, 35512853, 45407418, 16405341, 85242963, 27411561, 14947650, 22435353, 18001617, 69579137, 21533347, 58208470, 13231279, 19939935, 27185644, 8099994, 16445503, 90457870, 93359396, 49882705, 34698428, 6221471, 3088684, 79657802, 53842979, 26776922, 66045587, 49328608, 62115552, 20568363, 81274566, 60106217, 59981773, 75229462, 14326617, 83150534, 1788101, 42237907, 91990218, 49236559, 51472275, 70036438, 4199704, 67451935, 11161731, 32250045, 62430984, 14723410, 26734892, 7919588, 6153406, 53648154, 30787683, 20867149, 87598888, 31733363, 40027975, 15064655, 68000591, 75407347, 61728685, 29932657, 82886381, 33123618, 29959549, 61263068, 77312810, 89217461, 42967683, 86543538, 89804152, 82052050, 76671482, 16019925, 66322959, 73617245, 36845587, 61815223, 61741594, 415901, 1239555, 37560164, 92692283, 77620120, 23110625, 9175338, 7685448, 69786756, 92998591, 12303248, 43322743, 5073754, 81172706, 71469330, 93562257, 63152504, 6808825, 18504197, 41245325, 7066775, 50007421, 4508700, 29834512, 18131876, 31126490, 7517032, 82651278, 61859143, 90004325, 20122224, 30811010, 72732205, 92692978, 49271185, 65047700, 72373496, 12024238, 86242799, 67474219, 77284619, 72357096, 34497327, 70420215, 9603598, 54606384, 86022504, 68939068, 67513640, 56955985, 37192445, 45848907, 40686254, 17081350, 47738185, 48774913, 82779622, 71657078, 32151165, 40152546, 16380211, 34432810, 67793644, 50806615, 53888755, 11365791, 92353856, 48893685, 62693428, 94911072, 19457589, 526217, 43506672, 83368048, 82339363, 10366309, 19376156, 91240048, 69136837, 4806458, 41092172, 22405120, 94516935, 97940276, 26139110, 17539625, 92215320, 3509435, 83133790, 17894977, 78785507, 75128745, 11923835, 54762643, 88653118, 2208785, 4515343, 6038457, 74137926, 81805959, 78549759, 75052463, 38494874, 85023028, 70782102, 26397786, 26392416, 6497830, 15075176, 53802686, 54663246, 6525948, 98130363, 16595436, 22879907, 80246713, 83789391, 93790285, 8729146, 88130087, 76434144, 67031644, 52613508, 92071990, 30569392, 66116458, 7423788, 5640302, 10649306, 65275241, 37675718, 23134715, 48088883, 4204661, 17058722, 55189057, 66271566, 76703609, 99729357, 48260151, 874791, 80014588, 62740044, 36505482, 19101477, 38365584, 41380093, 32426752, 29029316, 44245960, 70367851, 36808486, 70541760, 51426311, 4099191, 56473732, 8791066, 41442762, 33237508, 89811711, 22113133, 71333116, 2607799, 91664334, 55143724, 97379790, 77898274, 91938191, 16684829, 50188404, 99021067, 83302115, 8733068, 47298834, 56484900, 55247455, 64055960, 78300864, 3294781, 62496012, 66663942, 56424103, 36930650, 62762857, 79322415, 33201905, 89699445, 45996863, 26292919, 36396314, 17385531, 99515901, 22129328, 14349098, 99224392, 17727650, 44060493, 46540998, 79821897, 6871053, 10597197, 65851721, 97641116, 44983451, 4985896, 45306070, 321665, 89637706, 62452034, 44846932, 44627776, 91281584, 18833224, 93566986, 44481640, 5267545, 92803766, 59109400, 60176618, 92787493, 9058407, 4095116, 93053405, 26493119, 97281847, 30139692, 29516592, 75820087, 26863229, 17957593, 35092039, 43357947, 87160386, 58749, 91141395, 62357986, 8055981, 66250369, 36312813, 95581843, 30998561, 508198, 9259676, 68816413, 89419466, 95010552, 28734791, 37957788, 65454636, 90061527, 94539824, 89996536, 64098930, 1197320, 17857111, 24591705, 20681921, 65715134, 35456853, 82178706, 38061439, 33061250, 3773993, 61373987, 65880522, 98648327, 61928316, 9575954, 3235882, 98739783, 17016156, 1022677, 93933709, 94360702, 98653983, 58180653, 8913721, 34698463, 54427233, 84293052, 37739481, 48892201, 49597667, 72738685, 98371444, 44889423, 71522968, 2204165, 73109997, 79136082, 55960386, 17146629, 68644627, 16971929, 4091162, 4119747, 71920426, 79806380, 63015256, 76960303, 29401781, 85571389, 29994197, 45919976, 95726235, 31491938, 1375023, 59501487, 57658654, 38256849, 87710366, 11581181, 50567636, 15536795, 89078848, 57240218, 1250437, 5832946, 59318837, 669105, 67124282, 95251277, 32274392, 79191827, 92530431, 24733232, 9599614, 84349107, 90310261, 79942022, 66137019, 45790169, 44473167, 19891772, 86001008, 25842078, 68102437, 7104732, 28787861, 54199160, 33895336, 42782652, 55602660, 3183975, 98943869, 47792865, 88444207, 13862149, 37280276, 92398073, 68128438, 38008118, 20836893, 61712234, 81853704, 70596786, 55770687, 62232211, 62490109, 24619760, 54263880, 70727211, 37224844, 24826575, 64157906, 69255765, 86301513, 95395112, 20765474, 38341669, 62803858, 73168565, 33553959, 43933006, 60102965, 51149804, 52293995, 51507425, 7955293, 75986488, 89214616, 47708850, 7011964, 29466406, 78766358, 27625735, 69355476, 82532312, 19272365, 94711842, 67030811, 17037369, 84187166, 29819940, 86821229, 2717150, 5111370, 56531125, 29065964, 57020481, 45049308, 43501211, 61960400, 44842615, 4787945, 70800879, 29510992, 10961421, 66611759, 14093520, 67084351, 9829782, 749283, 48675329, 15948937, 84166196, 67227442, 89499542, 22942635, 30891921, 38009615, 59197747, 30090481, 62051033, 75777973, 72614359, 44847298, 99690194, 23740167, 18783702, 34044787, 52204879, 59910624, 68316156, 57359924, 72777973, 24953498, 60955663, 4668450, 77187825, 76330843, 99125126, 9886593, 77300457, 40677414, 13470059, 35996293, 97561745, 8634541, 81855445, 36468541, 44915235, 4978543, 20885148, 72495719, 32699744, 73392814, 21070110, 89046466, 76170907, 47213183, 77301523, 6111563, 73124510, 34295794, 48658605, 70372191, 6505939, 51464002, 83747892, 59329511, 7687278, 65074535, 39201414, 66319530, 99226875, 55669657, 17386996, 10453030, 68824981, 21919959, 59405277, 34667286, 19898053, 53666583, 63506281, 4798568, 91957544, 29635537, 81774825, 28851716, 98948034, 168541, 23848565, 45667668, 51715482, 99658235, 48395186, 51315460, 30366150, 48360198, 39801351, 18411915, 83155442, 54868730, 33628349 +68939068, 99515901, 67124282, 47361209, 75128745, 15015906, 1569515, 62552524, 66832478, 26139110, 63372756, 81274566, 83150534, 32159704, 59197747, 1204161, 4064751, 99917599, 44842615, 66137019, 40865610, 54199160, 55470718, 42644903, 54427233, 78909561, 30463802, 51047803, 27187213, 18504197, 9257405, 95726235, 55247455, 40781854, 34497327, 36930650, 36780454, 67793644, 95251277, 31904591, 10366309, 90013093, 17894977, 91802888, 74137926, 1197320, 78884452, 21993752, 12571310, 68000591, 92071990, 569864, 3233569, 42307449, 69786756, 37501808, 51426311, 33935899, 92033260, 76960303, 14731700, 45381876, 12664567, 77413012, 6986898, 50702367, 46870723, 34946859, 32151165, 14220886, 48893685, 94911072, 65017137, 9886593, 44627776, 65038678, 54517921, 5267545, 83651211, 16405341, 45617087, 13173644, 81855445, 76315420, 84840549, 28550822, 28796059, 1936762, 21919959, 57393458, 54014062, 54663246, 14723410, 69848388, 53393358, 19898053, 20486294, 15902805, 99604946, 24619760, 8729146, 78589145, 76434144, 66116458, 63059024, 55850790, 89217461, 86543538, 7300047, 48260151, 79880247, 65081429, 61815223, 83948335, 85116755, 92998591, 33249630, 42692881, 2204165, 37620363, 36808486, 83747892, 67281495, 47090124, 7011964, 43376279, 91664334, 74357852, 78766358, 24915585, 30811010, 27665211, 66428795, 44479073, 56153345, 85571389, 73786944, 16567550, 51466049, 6819644, 99226875, 9603598, 11757872, 62428472, 76330843, 79821897, 247198, 669105, 82897371, 2917920, 9398733, 99125126, 16387575, 93566986, 82327024, 60176618, 92787493, 22405120, 75543508, 49598724, 44473167, 8373289, 57241521, 69579137, 21533347, 53084256, 88653118, 45237957, 66667729, 66045587, 49328608, 81805959, 9259676, 68816413, 60106217, 42947632, 26397786, 42237907, 70036438, 77272628, 22997281, 6153406, 85695762, 44844121, 21673260, 86460488, 44177011, 176257, 87598888, 31733363, 99861373, 37224844, 55753905, 64157906, 21289531, 77312810, 93933709, 42967683, 39171895, 60102965, 53666583, 26275734, 51149804, 13468268, 55615885, 2331773, 44847298, 37560164, 45428665, 9175338, 71300104, 63967300, 22766820, 79136082, 51464002, 88047921, 16054533, 68316156, 57359924, 37659250, 54232247, 92692978, 86798033, 72238278, 18806856, 43152977, 24953498, 20002147, 3294781, 33565483, 50567636, 45848907, 26292919, 82726008, 44060493, 40534591, 40152546, 99971982, 69697787, 56531125, 88904910, 29065964, 57020481, 36753250, 74614639, 45075471, 83368048, 44481640, 15148031, 61859581, 8115266, 35512853, 45407418, 20642888, 14947650, 2891150, 93053405, 4434662, 30139692, 29516592, 94090109, 57961282, 84684495, 26863229, 35092039, 17068582, 26998766, 3487592, 58749, 6221471, 62357986, 44664587, 66250369, 36312813, 96735716, 57248122, 79922758, 75052463, 36933038, 88444207, 64848072, 48675329, 17976208, 10309525, 18466635, 81677380, 14626618, 34493392, 34236719, 89499542, 82178706, 8807940, 30395570, 61373987, 76170907, 64602895, 13348726, 45990383, 3235882, 78561158, 69255765, 61728685, 47887470, 37675718, 82979980, 98653983, 42199455, 82052050, 66271566, 85117092, 80014588, 30653863, 72614359, 62936963, 36505482, 19101477, 23110625, 92541302, 98371444, 92604458, 96906410, 42073124, 66885828, 74724075, 71469330, 41245325, 70541760, 55960386, 50007421, 4099191, 56461322, 22113133, 38022834, 29834512, 96726697, 7182642, 55143724, 32058615, 40781449, 4119747, 24314885, 14363867, 4069912, 99021067, 60393039, 47298834, 20645197, 77284619, 73021291, 72357096, 62496012, 96193415, 77072625, 74452589, 11581181, 99333375, 53632373, 17081350, 17385531, 1250437, 14349098, 99224392, 7502255, 97057187, 50806615, 44550764, 8696647, 45306070, 72358170, 44846932, 61141874, 45049308, 18833224, 23848565, 36139942, 19376156, 90310261, 99549373, 17764950, 41092172, 48673079, 68041839, 8634541, 78218845, 69641513, 83133790, 72274002, 22450468, 75153252, 91141395, 65271999, 66611759, 48395186, 58224549, 42782652, 96420429, 7229550, 6038457, 78602717, 66903004, 75229462, 1788101, 22200378, 47893286, 53802686, 15948937, 68128438, 15163258, 94539824, 26734892, 17857111, 20836893, 3633375, 53547802, 24058273, 73392814, 69605283, 21070110, 73222868, 22942635, 30891921, 80246713, 68110330, 38009615, 64087743, 33061250, 30787683, 79738755, 40027975, 75135448, 61928316, 44784505, 52613508, 75407347, 86301513, 98739783, 65275241, 38341669, 29932657, 60581278, 17016156, 61263068, 89804152, 40197395, 30543215, 26063929, 73617245, 26507214, 36845587, 32590267, 73124510, 75986488, 59177718, 12303248, 5073754, 44889423, 81172706, 7646095, 99965001, 33699435, 17146629, 41442762, 58751351, 71316369, 16971929, 85711894, 14045383, 59910624, 97379790, 49641577, 4091162, 20122224, 90654836, 28851716, 71920426, 74110882, 23194618, 29994197, 40356877, 8733068, 94711842, 12024238, 31491938, 1431742, 83083131, 86242799, 20140249, 824872, 5822202, 61982238, 56424103, 70420215, 62762857, 97783876, 38256849, 54606384, 37166608, 31727379, 67513640, 84127901, 57240218, 83155442, 52261574, 48774913, 21397057, 96318094, 94595668, 65851721, 30163921, 53888755, 90090964, 74743862, 11365791, 68694897, 43501211, 71083565, 39553046, 92530431, 68824981, 32161669, 9599614, 93057697, 83210802, 40677414, 76825057, 23432750, 76540481, 91727510, 33336148, 97281847, 90272749, 58208470, 88698958, 86001008, 78785507, 49882705, 28928175, 68702632, 95581843, 30998561, 82427263, 30694952, 8651647, 9860968, 91831487, 14093520, 95290172, 26392416, 65454636, 11161731, 97011160, 30764367, 84166196, 61712234, 16595436, 32699744, 70596786, 53648154, 62232211, 12416768, 89046466, 70004753, 20867149, 83789391, 70727211, 24826575, 48360198, 65880522, 30090481, 30569392, 68875490, 10649306, 62051033, 62803858, 29959549, 37183543, 23134715, 6111563, 4204661, 72793444, 17058722, 55189057, 76671482, 11543098, 52293995, 8913721, 16019925, 4798568, 80555751, 35192533, 38365584, 92692283, 72738685, 39847321, 48658605, 99690194, 112651, 71522968, 63152504, 6808825, 25636669, 4508700, 34044787, 89811711, 31126490, 7517032, 82651278, 90004325, 72732205, 91938191, 49271185, 19272365, 39201414, 99524975, 66319530, 57658654, 24168411, 39986008, 40686254, 22129328, 17727650, 47738185, 5832946, 30771409, 46540998, 59318837, 6871053, 45995325, 37891451, 73031054, 92353856, 56515456, 5111370, 321665, 89637706, 36812683, 19457589, 77300457, 82339363, 91240048, 38645117, 4787945, 70800879, 26102057, 85242963, 83533741, 94516935, 80316608, 22435353, 45667668, 18001617, 33431960, 92215320, 96852321, 19939935, 17957593, 71965942, 33304202, 43357947, 16445503, 90457870, 93359396, 87160386, 68102437, 61623915, 10961421, 3880712, 79657802, 26776922, 4515343, 60430369, 62115552, 3183975, 20568363, 85023028, 59981773, 36468541, 70782102, 49236559, 4199704, 6497830, 93515664, 37957788, 6157724, 27325428, 17365188, 62430984, 89996536, 59614746, 64098930, 81853704, 67227442, 65338021, 45794415, 38658347, 35456853, 38061439, 88130087, 67031644, 98648327, 77377183, 9575954, 95395112, 13819358, 31161687, 75777973, 33123618, 94360702, 58180653, 34698463, 76703609, 66322959, 874791, 84293052, 84002370, 46851987, 48892201, 415901, 49597667, 77620120, 18411915, 89214616, 6505939, 7066775, 18783702, 86118021, 99297164, 39373729, 68644627, 5970607, 77787724, 71333116, 29466406, 27041967, 18131876, 2607799, 81774825, 27625735, 82532312, 63015256, 16684829, 50188404, 29401781, 72373496, 83302115, 74441448, 78300864, 59501487, 77187825, 86022504, 33201905, 83269727, 17037369, 26744917, 15536795, 57803235, 17386996, 71657078, 44983451, 6521313, 61960400, 84349107, 4806458, 35996293, 97940276, 59371804, 29510992, 3233084, 21001913, 27185644, 99658235, 7104732, 73235980, 28787861, 33895336, 508198, 2208785, 55602660, 26114953, 38494874, 98943869, 36580610, 91990218, 30366150, 84406788, 9829782, 36189527, 4978543, 9860195, 59405277, 32250045, 90061527, 51590803, 6525948, 98130363, 24591705, 22879907, 81898046, 40984766, 54263880, 15064655, 47213183, 7423788, 43045786, 20765474, 92867155, 73168565, 82886381, 1022677, 16097038, 48088883, 84904436, 34295794, 15535065, 57163802, 7685448, 32426752, 77694700, 12348118, 18663507, 56473732, 59329511, 36135, 69355476, 18699206, 7687278, 72777973, 45919976, 10264691, 56484900, 37435892, 4668450, 67474219, 98948034, 55669657, 89699445, 84187166, 56955985, 36396314, 94076128, 61897582, 54987042, 72278539, 91255408, 4985896, 22721500, 62693428, 62452034, 526217, 32274392, 24733232, 69136837, 13470059, 9058407, 4095116, 27411561, 79942022, 33797252, 26493119, 8099994, 51715482, 72019362, 34698428, 93270084, 53842979, 23569917, 33628349, 51315460, 47792865, 95010552, 61271144, 67084351, 63628376, 28734791, 67451935, 92398073, 20885148, 72495719, 7919588, 65715134, 5640302, 39801351, 99729357, 1239555, 29188588, 6793819, 29635537, 9188443, 16099750, 47708850, 23740167, 33237508, 52204879, 16424199, 41481685, 65047700, 65074535, 87720882, 68204242, 67030811, 30218878, 79322415, 45996863, 8128637, 89078848, 96709982, 82779622, 29819940, 16380211, 10597197, 168541, 97641116, 86821229, 91281584, 43506672, 26664538, 92803766, 97561745, 80251430, 19891772, 98462867, 25842078, 38510840, 88251446, 11923835, 8055981, 9623492, 3088684, 78549759, 89419466, 13862149, 10358899, 37280276, 749283, 15075176, 34667286, 38008118, 20681921, 55770687, 93790285, 22108665, 8129978, 77301523, 43933006, 36685023, 63506281, 51507425, 62740044, 37739481, 7955293, 41380093, 70372191, 29029316, 44245960, 70367851, 8791066, 61859143, 79806380, 50668599, 64055960, 60955663, 37192445, 79191827, 59109400, 3509435, 54762643, 51472275, 44915235, 3773993, 19486173, 5482538, 61741594, 91957544, 54868730, 43322743, 93562257, 77898274, 52060076, 66663942, 87710366, 34432810, 2717150, 90158785, 72725103, 51588015, 17539625, 13231279, 44348328, 14326617, 62490109, 92554120, 73109997, 1375023, 41092102, 54058788, 75820087, 33553959, 10453030, 45790169, 95957797 +32151165, 67084351, 59910624, 5822202, 36930650, 35512853, 63372756, 53842979, 68816413, 26397786, 99965001, 85711894, 9599614, 85242963, 96852321, 75128745, 95581843, 72495719, 61712234, 17016156, 3233569, 51149804, 29635537, 81172706, 41442762, 28851716, 47298834, 56955985, 99515901, 50806615, 67124282, 65017137, 36753250, 54517921, 99917599, 92787493, 69579137, 59405277, 11161731, 64098930, 20681921, 78884452, 22879907, 88130087, 75407347, 98739783, 61728685, 47887470, 89804152, 48260151, 63506281, 13468268, 30463802, 96906410, 81774825, 71920426, 44479073, 67030811, 62496012, 33201905, 89078848, 17081350, 73031054, 669105, 92353856, 48893685, 91281584, 526217, 74614639, 92530431, 44842615, 20642888, 16405341, 33797252, 78218845, 35092039, 66045587, 55602660, 91831487, 30366150, 92398073, 18466635, 34493392, 99604946, 70004753, 24826575, 68000591, 75777973, 33123618, 82979980, 23134715, 6111563, 94360702, 60102965, 55615885, 61815223, 23110625, 9188443, 33249630, 5073754, 12348118, 6505939, 67281495, 86118021, 50007421, 88047921, 32058615, 20122224, 30811010, 74110882, 50188404, 85571389, 45919976, 99524975, 9603598, 54606384, 36780454, 83269727, 45381876, 50702367, 22129328, 6871053, 10597197, 9398733, 43501211, 32274392, 5267545, 90310261, 59109400, 99549373, 4806458, 68041839, 45667668, 13173644, 92215320, 83133790, 84840549, 26998766, 54199160, 30998561, 1936762, 21919959, 95010552, 749283, 37957788, 97011160, 89499542, 85695762, 21070110, 64602895, 13348726, 98648327, 3235882, 92071990, 38341669, 55850790, 39171895, 48088883, 26275734, 42199455, 40197395, 8913721, 66322959, 51507425, 2331773, 36505482, 1239555, 18411915, 51047803, 54868730, 44245960, 71522968, 2204165, 83747892, 51426311, 39373729, 33237508, 38022834, 78766358, 7182642, 97379790, 36135, 4091162, 72732205, 27665211, 18699206, 91938191, 92033260, 29401781, 66319530, 20140249, 77187825, 31727379, 45996863, 76330843, 14349098, 47738185, 7502255, 40534591, 79821897, 45995325, 40152546, 16380211, 6521313, 82897371, 45306070, 62693428, 88904910, 29065964, 19457589, 57020481, 77300457, 43506672, 93566986, 68824981, 15148031, 10366309, 23848565, 84349107, 83210802, 76825057, 23432750, 41092172, 48673079, 27411561, 14947650, 97940276, 26139110, 59371804, 80316608, 21533347, 75820087, 8099994, 51715482, 49882705, 65271999, 62357986, 36312813, 28787861, 49328608, 57248122, 3183975, 81274566, 89419466, 14093520, 91990218, 48675329, 17976208, 6157724, 22997281, 69848388, 15015906, 65715134, 70596786, 53648154, 62232211, 38009615, 64087743, 89046466, 176257, 83789391, 70727211, 99861373, 79738755, 93790285, 12571310, 55753905, 59197747, 78589145, 69255765, 30569392, 7423788, 68875490, 13819358, 42644903, 62051033, 86543538, 43933006, 76671482, 52293995, 99729357, 84293052, 84904436, 36845587, 62936963, 35192533, 415901, 83948335, 91957544, 73124510, 75986488, 48658605, 69786756, 59177718, 70367851, 37620363, 18783702, 56473732, 99297164, 16971929, 27041967, 18131876, 31126490, 14045383, 68316156, 4119747, 63015256, 76960303, 14363867, 4069912, 31491938, 24953498, 1431742, 40781854, 67474219, 59501487, 99226875, 61982238, 38256849, 33565483, 26292919, 53632373, 6986898, 83155442, 46870723, 48774913, 44060493, 30771409, 37891451, 247198, 99971982, 67793644, 168541, 30163921, 66832478, 86821229, 56531125, 321665, 89637706, 62452034, 99125126, 44846932, 61141874, 45049308, 95251277, 61960400, 39553046, 31904591, 82327024, 36139942, 40677414, 83651211, 72725103, 9058407, 66137019, 45790169, 93053405, 45617087, 30139692, 57241521, 80251430, 13231279, 88698958, 21001913, 27185644, 90457870, 22450468, 28550822, 54762643, 88653118, 73235980, 96735716, 42782652, 2208785, 62115552, 78549759, 23569917, 98943869, 14326617, 36468541, 42237907, 37280276, 22200378, 44915235, 93515664, 15075176, 65454636, 90061527, 81677380, 38008118, 14723410, 1197320, 17857111, 24591705, 53393358, 86460488, 30395570, 76170907, 48360198, 65880522, 77377183, 61928316, 78561158, 66116458, 95395112, 65275241, 29932657, 82886381, 89217461, 17058722, 30543215, 85117092, 79880247, 73617245, 84002370, 26507214, 44847298, 37560164, 34295794, 15535065, 85116755, 71300104, 89214616, 42073124, 92998591, 77694700, 18663507, 93562257, 73109997, 51464002, 18504197, 55960386, 56461322, 22113133, 71316369, 68644627, 71333116, 16054533, 49641577, 41481685, 90004325, 24915585, 27625735, 37659250, 82532312, 56153345, 23194618, 72777973, 40356877, 10264691, 95726235, 18806856, 94711842, 43152977, 20645197, 74441448, 64055960, 6819644, 70420215, 77072625, 97783876, 74452589, 89699445, 99333375, 26744917, 50567636, 67513640, 17727650, 29819940, 61897582, 53888755, 72278539, 14220886, 90090964, 69697787, 8696647, 68694897, 5111370, 83368048, 26664538, 19376156, 91240048, 90158785, 45407418, 17764950, 4095116, 26102057, 49598724, 97561745, 44473167, 8373289, 8634541, 19891772, 58208470, 86001008, 3233084, 90013093, 72274002, 76315420, 16445503, 68102437, 28928175, 53084256, 6221471, 58224549, 45237957, 44664587, 3880712, 96420429, 26114953, 30694952, 33628349, 75052463, 38494874, 85023028, 60106217, 59981773, 55470718, 75229462, 83150534, 95290172, 70782102, 88444207, 49236559, 47893286, 6497830, 10309525, 15163258, 89996536, 84166196, 20836893, 67227442, 3633375, 65338021, 19898053, 69605283, 15902805, 24619760, 54263880, 44177011, 31733363, 15064655, 64157906, 67031644, 30090481, 22108665, 9575954, 47213183, 5640302, 37183543, 42967683, 16097038, 98653983, 55189057, 66271566, 34698463, 54427233, 30653863, 65081429, 80555751, 37739481, 38365584, 92692283, 77620120, 92541302, 45428665, 9175338, 92604458, 63967300, 22766820, 43322743, 66885828, 74724075, 7646095, 41245325, 23740167, 47090124, 7011964, 8791066, 52204879, 2607799, 74357852, 61859143, 52060076, 49271185, 1204161, 65047700, 66428795, 65074535, 72238278, 29994197, 39201414, 12024238, 1375023, 55247455, 86242799, 60955663, 73021291, 3294781, 824872, 56424103, 11757872, 62762857, 79322415, 55669657, 17037369, 84187166, 39986008, 37192445, 12664567, 84127901, 57803235, 40686254, 99224392, 34946859, 59318837, 94076128, 34432810, 94595668, 22721500, 44550764, 74743862, 56515456, 72358170, 44627776, 16387575, 65038678, 71083565, 24733232, 32161669, 93057697, 69136837, 13470059, 51588015, 76540481, 70800879, 75543508, 22435353, 18001617, 90272749, 17539625, 44348328, 17957593, 33304202, 87160386, 61623915, 10961421, 7104732, 9623492, 3088684, 79657802, 33895336, 508198, 7229550, 74137926, 78602717, 81805959, 9259676, 8651647, 1788101, 57393458, 54014062, 84406788, 34667286, 32250045, 77272628, 14626618, 6525948, 98130363, 26734892, 32699744, 24058273, 6153406, 73392814, 21993752, 73222868, 22942635, 30891921, 21673260, 38061439, 40027975, 37224844, 8729146, 76434144, 45990383, 52613508, 86301513, 39801351, 20765474, 92867155, 60581278, 21289531, 29959549, 1022677, 569864, 33553959, 7300047, 4204661, 42307449, 80014588, 62740044, 29188588, 7685448, 99690194, 32426752, 42692881, 27187213, 29029316, 47708850, 79136082, 6808825, 7066775, 34044787, 5970607, 29466406, 29834512, 55143724, 7517032, 77898274, 82651278, 90654836, 54232247, 79806380, 33935899, 7687278, 19272365, 86798033, 16684829, 51466049, 60393039, 83083131, 14731700, 20002147, 72357096, 57658654, 24168411, 87710366, 37166608, 77413012, 57240218, 17386996, 17385531, 82726008, 21397057, 71657078, 46540998, 96318094, 97057187, 97641116, 91255408, 11365791, 94911072, 9886593, 36812683, 79191827, 92803766, 33336148, 26493119, 97281847, 29516592, 94090109, 3509435, 69641513, 26863229, 38510840, 17894977, 93359396, 66611759, 48395186, 66250369, 28796059, 26776922, 60430369, 91802888, 20568363, 66903004, 47792865, 26392416, 64848072, 9829782, 51472275, 4199704, 4978543, 53802686, 15948937, 62430984, 94539824, 30764367, 34236719, 54663246, 7919588, 20486294, 82178706, 44844121, 40984766, 12416768, 3773993, 61373987, 20867149, 62552524, 63059024, 43045786, 10649306, 31161687, 62803858, 73168565, 37675718, 77312810, 53666583, 72793444, 58180653, 82052050, 36685023, 76703609, 16019925, 874791, 46851987, 78909561, 49597667, 41380093, 39847321, 112651, 12303248, 71469330, 37501808, 36808486, 70541760, 33699435, 4508700, 59329511, 95957797, 43376279, 91664334, 16424199, 40781449, 92692978, 69355476, 99021067, 68204242, 8733068, 56484900, 98948034, 66663942, 41092102, 11581181, 68939068, 45848907, 15536795, 54058788, 44983451, 4985896, 44481640, 38645117, 94516935, 4434662, 25842078, 71965942, 17068582, 43357947, 72019362, 58749, 11923835, 91141395, 34698428, 66667729, 82427263, 4515343, 6038457, 51315460, 61271144, 36933038, 13862149, 10358899, 63628376, 9860195, 16595436, 53547802, 35456853, 81898046, 68110330, 87598888, 75135448, 44784505, 7955293, 19101477, 61741594, 44889423, 17146629, 96726697, 57359924, 9257405, 72373496, 73786944, 16567550, 83302115, 78300864, 4668450, 96193415, 34497327, 86022504, 36396314, 5832946, 65851721, 54987042, 2917920, 18833224, 45075471, 61859581, 60176618, 4787945, 83533741, 79942022, 35996293, 91727510, 2891150, 81855445, 84684495, 98462867, 78785507, 88251446, 99658235, 40865610, 8055981, 68702632, 79922758, 42947632, 70036438, 51590803, 68128438, 59614746, 45794415, 80246713, 33061250, 30787683, 1569515, 19486173, 5482538, 8129978, 92554120, 61263068, 93933709, 11543098, 26063929, 72614359, 32590267, 72738685, 16099750, 98371444, 4099191, 58751351, 87720882, 50668599, 37435892, 62428472, 8128637, 1250437, 52261574, 10453030, 2717150, 82339363, 22405120, 19939935, 75153252, 3487592, 36189527, 28734791, 67451935, 20885148, 27325428, 17365188, 32159704, 62490109, 8807940, 77301523, 4798568, 48892201, 57163802, 70372191, 63152504, 25636669, 89811711, 24314885, 8115266, 33431960, 29510992, 47361209, 57961282, 93270084, 9860968, 36580610, 6793819, 77787724, 77284619, 30218878, 96709982, 82779622, 55770687, 4064751, 38658347, 81853704 +94711842, 3235882, 7687278, 75229462, 83789391, 48360198, 37659250, 24953498, 74441448, 34432810, 69697787, 60581278, 29466406, 6871053, 168541, 44627776, 9599614, 48673079, 69579137, 99658235, 32159704, 21070110, 17058722, 73617245, 72614359, 17146629, 85711894, 66428795, 29994197, 45848907, 84127901, 46870723, 16387575, 95251277, 68824981, 45407418, 16405341, 76540481, 83533741, 33336148, 53084256, 44664587, 85023028, 42947632, 14626618, 15163258, 24591705, 15015906, 21993752, 38061439, 67031644, 68000591, 30569392, 55850790, 29932657, 77301523, 39171895, 66271566, 76703609, 874791, 84904436, 72738685, 39847321, 12303248, 66885828, 96726697, 36135, 57359924, 33935899, 91938191, 39201414, 95726235, 18806856, 86022504, 82726008, 4064751, 94595668, 97057187, 91255408, 43501211, 82339363, 92803766, 79942022, 26493119, 98462867, 21001913, 27185644, 76315420, 40865610, 75153252, 3487592, 93270084, 81274566, 21919959, 70782102, 54014062, 10358899, 63628376, 20885148, 72495719, 81853704, 24058273, 38658347, 53648154, 76434144, 8129978, 62051033, 1022677, 26063929, 30653863, 15535065, 70372191, 63967300, 92998591, 5073754, 71469330, 73109997, 4099191, 56461322, 77787724, 16971929, 68316156, 90654836, 4119747, 56153345, 50668599, 31491938, 99524975, 86242799, 20002147, 6819644, 67030811, 66663942, 34497327, 9603598, 37166608, 11581181, 33565483, 26292919, 36396314, 53632373, 48774913, 40534591, 32151165, 65851721, 67793644, 86821229, 11365791, 321665, 61141874, 36753250, 45075471, 10366309, 19376156, 40677414, 13470059, 4787945, 35512853, 92787493, 41092172, 97940276, 22435353, 45667668, 8634541, 17539625, 93359396, 28550822, 66611759, 88653118, 66045587, 49328608, 82427263, 81805959, 33628349, 59981773, 88444207, 36580610, 70036438, 44915235, 59405277, 77272628, 54663246, 20836893, 55770687, 80246713, 3773993, 24619760, 76170907, 59197747, 64602895, 61928316, 33123618, 61263068, 569864, 33553959, 3233569, 76671482, 51149804, 75986488, 9175338, 85116755, 71300104, 99690194, 27187213, 36808486, 86118021, 25636669, 71316369, 2607799, 78766358, 81774825, 4091162, 54232247, 65074535, 72777973, 45919976, 67474219, 70420215, 62762857, 38256849, 31727379, 50567636, 82779622, 97641116, 53888755, 44983451, 48893685, 2917920, 99125126, 71083565, 15148031, 69136837, 60176618, 23432750, 22405120, 70800879, 94516935, 91727510, 80316608, 29510992, 83133790, 8099994, 78785507, 58749, 34698428, 62357986, 7104732, 58224549, 36312813, 68702632, 95581843, 60430369, 51315460, 9259676, 75052463, 14093520, 47792865, 83150534, 4199704, 6497830, 15075176, 92398073, 15948937, 27325428, 90061527, 17365188, 94539824, 26734892, 17857111, 61712234, 3633375, 70596786, 19898053, 40984766, 33061250, 44177011, 89046466, 61373987, 70727211, 37224844, 15064655, 8729146, 55753905, 78561158, 68875490, 92554120, 29959549, 37675718, 86543538, 6111563, 36685023, 84293052, 46851987, 4798568, 44847298, 61815223, 1239555, 38365584, 32590267, 37560164, 29635537, 48658605, 69786756, 44889423, 2204165, 7646095, 70541760, 51426311, 5970607, 7182642, 90004325, 72732205, 92692978, 82532312, 65047700, 19272365, 76960303, 50188404, 99021067, 83302115, 98948034, 61982238, 96193415, 33201905, 89699445, 8128637, 6986898, 96709982, 99515901, 52261574, 99224392, 44060493, 50806615, 82897371, 67124282, 56515456, 5111370, 94911072, 36812683, 19457589, 32274392, 82327024, 44842615, 93057697, 23848565, 5267545, 83210802, 72725103, 66137019, 26139110, 75543508, 59371804, 93053405, 4434662, 18001617, 97561745, 8373289, 80251430, 13173644, 78218845, 47361209, 81855445, 21533347, 58208470, 88698958, 17957593, 90457870, 87160386, 72019362, 61623915, 3088684, 54199160, 33895336, 508198, 79922758, 91802888, 91831487, 60106217, 9829782, 51472275, 6157724, 18466635, 32250045, 34493392, 34236719, 98130363, 20681921, 6153406, 89499542, 20486294, 73222868, 86460488, 30787683, 176257, 99861373, 93790285, 65880522, 22108665, 45990383, 9575954, 52613508, 69255765, 39801351, 20765474, 65275241, 38341669, 62803858, 21289531, 47887470, 17016156, 77312810, 89217461, 42967683, 43933006, 4204661, 89804152, 82052050, 52293995, 34698463, 16019925, 54427233, 80014588, 65081429, 30463802, 61741594, 415901, 73124510, 92692283, 77620120, 34295794, 57163802, 9188443, 98371444, 51047803, 89214616, 32426752, 112651, 33249630, 44245960, 37620363, 63152504, 50007421, 47090124, 68644627, 95957797, 74357852, 88047921, 16054533, 41481685, 61859143, 52060076, 30811010, 27625735, 69355476, 71920426, 49271185, 86798033, 44479073, 16684829, 85571389, 47298834, 55247455, 20645197, 1431742, 78300864, 30218878, 56424103, 11757872, 79322415, 55669657, 24168411, 37192445, 57803235, 17081350, 17385531, 1250437, 76330843, 29819940, 5832946, 7502255, 34946859, 10453030, 96318094, 247198, 4985896, 22721500, 74743862, 92353856, 9398733, 89637706, 62452034, 88904910, 91281584, 45049308, 77300457, 18833224, 39553046, 83368048, 79191827, 93566986, 61859581, 84349107, 90310261, 20642888, 9058407, 2891150, 30139692, 92215320, 75820087, 44348328, 17894977, 43357947, 9623492, 66250369, 28787861, 28796059, 96735716, 42782652, 7229550, 95010552, 36468541, 36933038, 67084351, 22200378, 36189527, 28734791, 53802686, 51590803, 97011160, 68128438, 65715134, 32699744, 53393358, 78884452, 69605283, 54263880, 1569515, 70004753, 20867149, 19486173, 5482538, 64157906, 75407347, 63059024, 43045786, 5640302, 10649306, 98739783, 31161687, 75777973, 73168565, 93933709, 94360702, 48088883, 53666583, 8913721, 42307449, 63506281, 13468268, 51507425, 62740044, 36845587, 62936963, 19101477, 48892201, 49597667, 23110625, 92604458, 59177718, 42073124, 43322743, 12348118, 29029316, 70367851, 79136082, 18504197, 23740167, 7066775, 55960386, 18783702, 33699435, 58751351, 33237508, 89811711, 29834512, 43376279, 16424199, 7517032, 82651278, 28851716, 74110882, 1204161, 92033260, 4069912, 23194618, 29401781, 9257405, 72373496, 12024238, 1375023, 43152977, 56484900, 4668450, 66319530, 824872, 62496012, 99226875, 74452589, 54606384, 83269727, 99333375, 17037369, 45996863, 15536795, 77413012, 89078848, 83155442, 21397057, 71657078, 79821897, 59318837, 40152546, 37891451, 73031054, 30163921, 72278539, 45306070, 44846932, 29065964, 57020481, 99917599, 31904591, 26664538, 36139942, 59109400, 85242963, 35996293, 45790169, 49598724, 68041839, 29516592, 33431960, 69641513, 84684495, 26863229, 3233084, 25842078, 71965942, 90013093, 72274002, 84840549, 26998766, 75128745, 10961421, 11923835, 63372756, 65271999, 6221471, 48395186, 66667729, 26776922, 2208785, 4515343, 96420429, 6038457, 3183975, 30694952, 38494874, 8651647, 9860968, 68816413, 89419466, 55470718, 1788101, 26392416, 30366150, 13862149, 49236559, 47893286, 67451935, 37957788, 4978543, 34667286, 89996536, 59614746, 14723410, 1197320, 67227442, 73392814, 85695762, 82178706, 62232211, 22942635, 21673260, 62490109, 68110330, 99604946, 31733363, 40027975, 12571310, 75135448, 24826575, 98648327, 44784505, 47213183, 92071990, 86301513, 7423788, 92867155, 16097038, 26275734, 42199455, 55189057, 40197395, 85117092, 66322959, 80555751, 78909561, 36505482, 37739481, 7955293, 91957544, 41380093, 29188588, 6793819, 7685448, 42692881, 77694700, 37501808, 51464002, 6808825, 41245325, 8791066, 99297164, 41442762, 59329511, 38022834, 71333116, 27041967, 18131876, 91664334, 55143724, 14045383, 49641577, 32058615, 79806380, 24314885, 14363867, 87720882, 68204242, 16567550, 8733068, 14731700, 73021291, 3294781, 72357096, 20140249, 59501487, 77072625, 87710366, 62428472, 84187166, 68939068, 50702367, 45995325, 16380211, 66832478, 2717150, 44550764, 68694897, 56531125, 65017137, 72358170, 9886593, 526217, 43506672, 61960400, 54517921, 92530431, 44481640, 24733232, 38645117, 90158785, 83651211, 4095116, 97281847, 90272749, 94090109, 19891772, 57961282, 86001008, 19939935, 33304202, 17068582, 68102437, 28928175, 53842979, 55602660, 74137926, 23569917, 66903004, 14326617, 26397786, 65454636, 10309525, 81677380, 22997281, 84166196, 16595436, 53547802, 65338021, 35456853, 22879907, 81898046, 30891921, 15902805, 87598888, 78589145, 30090481, 95395112, 13819358, 82979980, 60102965, 98653983, 30543215, 79880247, 55615885, 84002370, 26507214, 35192533, 92541302, 96906410, 54868730, 56473732, 7011964, 22113133, 31126490, 59910624, 97379790, 63015256, 40356877, 51466049, 60393039, 37435892, 83083131, 40781854, 5822202, 36930650, 97783876, 77187825, 26744917, 39986008, 56955985, 12664567, 40686254, 22129328, 14349098, 17727650, 30771409, 46540998, 61897582, 54987042, 669105, 14220886, 90090964, 8696647, 74614639, 8115266, 17764950, 26102057, 27411561, 33797252, 13231279, 38510840, 35092039, 49882705, 91141395, 8055981, 62115552, 78602717, 78549759, 61271144, 749283, 48675329, 93515664, 17976208, 9860195, 11161731, 69848388, 7919588, 44844121, 8807940, 12416768, 30395570, 13348726, 77377183, 62552524, 66116458, 42644903, 61728685, 82886381, 23134715, 7300047, 58180653, 11543098, 2331773, 83948335, 45428665, 81172706, 71522968, 6505939, 83747892, 67281495, 4508700, 34044787, 52204879, 20122224, 27665211, 77284619, 57658654, 67513640, 45381876, 54058788, 10597197, 65038678, 32161669, 91240048, 99549373, 14947650, 44473167, 57241521, 16445503, 51715482, 22450468, 73235980, 45237957, 79657802, 30998561, 57393458, 37280276, 64848072, 62430984, 30764367, 45794415, 38009615, 79738755, 37183543, 72793444, 48260151, 22766820, 47708850, 93562257, 99965001, 77898274, 24915585, 40781449, 73786944, 64055960, 60955663, 36780454, 57240218, 6521313, 62693428, 76825057, 51588015, 45617087, 3509435, 96852321, 88251446, 3880712, 57248122, 26114953, 20568363, 98943869, 42237907, 91990218, 38008118, 64098930, 6525948, 88130087, 16099750, 18411915, 74724075, 18663507, 39373729, 18699206, 10264691, 47738185, 4806458, 41092102, 17386996, 99971982, 54762643, 84406788, 99729357, 94076128, 1936762, 95290172, 64087743, 72238278 +68110330, 45990383, 3233084, 45237957, 89804152, 61815223, 45428665, 99917599, 75153252, 63059024, 86543538, 61859581, 2891150, 3487592, 91141395, 66611759, 18466635, 17857111, 59177718, 91664334, 88047921, 92033260, 45919976, 62496012, 89699445, 30771409, 7502255, 74614639, 26139110, 69579137, 26863229, 73235980, 64098930, 86460488, 70004753, 22108665, 53666583, 30543215, 13468268, 51507425, 37560164, 16099750, 97379790, 72357096, 62428472, 17081350, 47738185, 90090964, 91240048, 66137019, 45667668, 18001617, 19891772, 96852321, 86001008, 65271999, 96420429, 3183975, 95010552, 61271144, 54014062, 84406788, 94539824, 32159704, 30764367, 53393358, 15902805, 70727211, 61928316, 44784505, 62051033, 61263068, 77301523, 89217461, 23134715, 79880247, 26507214, 7955293, 63967300, 12348118, 7182642, 90004325, 27665211, 33935899, 14363867, 95726235, 37435892, 86242799, 60955663, 36930650, 37166608, 99333375, 39986008, 77413012, 79821897, 99971982, 73031054, 4985896, 2917920, 99125126, 36812683, 19457589, 82339363, 82327024, 45790169, 4434662, 90272749, 47361209, 17539625, 13231279, 99658235, 3088684, 49328608, 42782652, 55602660, 66903004, 57393458, 64848072, 47893286, 27325428, 51590803, 14626618, 69848388, 19898053, 85695762, 21993752, 73222868, 38009615, 87598888, 99861373, 79738755, 5482538, 69255765, 92867155, 4204661, 11543098, 16019925, 54427233, 29188588, 39847321, 42073124, 92998591, 22766820, 70367851, 79136082, 18504197, 50007421, 56461322, 89811711, 14045383, 82651278, 20122224, 4119747, 74110882, 79806380, 24314885, 91938191, 78300864, 6819644, 30218878, 86022504, 84127901, 57240218, 17385531, 52261574, 17727650, 48774913, 82779622, 40152546, 97641116, 30163921, 91255408, 8696647, 67124282, 57020481, 43501211, 83368048, 10366309, 69136837, 83651211, 85242963, 83533741, 97940276, 59371804, 33797252, 68041839, 19939935, 76315420, 93359396, 11923835, 88653118, 36312813, 9860968, 95290172, 36933038, 26392416, 36189527, 37957788, 92398073, 32250045, 22997281, 15163258, 34236719, 98130363, 53547802, 6153406, 82178706, 62490109, 12416768, 20867149, 31733363, 93790285, 55753905, 24826575, 64602895, 78589145, 62552524, 9575954, 66116458, 8129978, 10649306, 95395112, 61728685, 60581278, 17016156, 94360702, 48088883, 3233569, 58180653, 36685023, 85117092, 99729357, 55615885, 4798568, 80555751, 38365584, 77620120, 85116755, 32426752, 27187213, 44889423, 6505939, 33237508, 71316369, 38022834, 74357852, 78766358, 16054533, 16424199, 81774825, 24915585, 90654836, 72732205, 71920426, 4069912, 73786944, 12024238, 40781854, 73021291, 3294781, 99226875, 66663942, 34497327, 77072625, 57658654, 41092102, 36780454, 50567636, 67513640, 6986898, 99515901, 76330843, 99224392, 29819940, 96318094, 59318837, 247198, 97057187, 14220886, 68694897, 45306070, 56531125, 94911072, 88904910, 44627776, 29065964, 36753250, 31904591, 26664538, 36139942, 38645117, 76825057, 17764950, 14947650, 93053405, 57241521, 29510992, 81855445, 88698958, 17957593, 17068582, 68102437, 58749, 30998561, 62115552, 30694952, 1936762, 91990218, 17976208, 15075176, 34667286, 59614746, 22942635, 33061250, 44177011, 83789391, 40027975, 8729146, 59197747, 77377183, 92071990, 86301513, 43045786, 92554120, 62803858, 75777973, 29932657, 82886381, 21289531, 47887470, 569864, 55189057, 8913721, 34698463, 42307449, 48260151, 80014588, 84904436, 1239555, 91957544, 34295794, 98371444, 7685448, 99690194, 112651, 5073754, 71469330, 93562257, 6808825, 55960386, 7011964, 99297164, 41442762, 58751351, 2607799, 40781449, 7687278, 65074535, 63015256, 50188404, 56153345, 99021067, 23194618, 47298834, 55247455, 67474219, 77284619, 20140249, 824872, 5822202, 61982238, 9603598, 79322415, 97783876, 77187825, 55669657, 26744917, 45848907, 45381876, 12664567, 26292919, 40686254, 5832946, 71657078, 34946859, 45995325, 2717150, 44550764, 48893685, 89637706, 72358170, 95251277, 18833224, 54517921, 45075471, 39553046, 92530431, 68824981, 32161669, 44842615, 93057697, 90158785, 4787945, 45407418, 41092172, 20642888, 22405120, 76540481, 26102057, 27411561, 80316608, 97281847, 78218845, 57961282, 75820087, 71965942, 72274002, 16445503, 90457870, 72019362, 40865610, 10961421, 63372756, 7104732, 44664587, 66667729, 28787861, 28796059, 79657802, 33895336, 57248122, 7229550, 78602717, 81805959, 33628349, 9259676, 20568363, 85023028, 68816413, 89419466, 55470718, 88444207, 26397786, 37280276, 22200378, 749283, 48675329, 11161731, 34493392, 54663246, 6525948, 14723410, 15015906, 67227442, 32699744, 89499542, 53648154, 62232211, 8807940, 99604946, 37224844, 75135448, 48360198, 65880522, 88130087, 3235882, 78561158, 30569392, 7423788, 68875490, 77312810, 82979980, 93933709, 98653983, 40197395, 51149804, 26063929, 84002370, 62936963, 78909561, 32590267, 23110625, 6793819, 57163802, 92604458, 96906410, 54868730, 69786756, 42692881, 74724075, 47708850, 51464002, 23740167, 99965001, 18783702, 86118021, 56473732, 33699435, 25636669, 4508700, 22113133, 5970607, 71333116, 29466406, 29834512, 96726697, 55143724, 36135, 68316156, 4091162, 27625735, 37659250, 92692978, 82532312, 18699206, 86798033, 44479073, 87720882, 68204242, 72238278, 85571389, 29994197, 40356877, 18806856, 60393039, 31491938, 1375023, 99524975, 43152977, 24953498, 11757872, 74452589, 11581181, 17037369, 37192445, 45996863, 15536795, 50702367, 83155442, 96709982, 14349098, 54058788, 94076128, 65851721, 66832478, 86821229, 72278539, 74743862, 11365791, 69697787, 9398733, 321665, 9886593, 44846932, 61141874, 43506672, 32274392, 93566986, 24733232, 19376156, 59109400, 35512853, 51588015, 92787493, 16405341, 29516592, 44473167, 8634541, 33431960, 94090109, 13173644, 58208470, 84684495, 83133790, 27185644, 90013093, 43357947, 51715482, 26998766, 78785507, 28928175, 66250369, 54199160, 26776922, 96735716, 508198, 82427263, 2208785, 91802888, 6038457, 14093520, 47792865, 70782102, 1788101, 49236559, 51472275, 6497830, 44915235, 28734791, 67451935, 9860195, 59405277, 6157724, 65454636, 20885148, 81677380, 84166196, 38008118, 1197320, 26734892, 20681921, 3633375, 70596786, 78884452, 55770687, 64087743, 54263880, 30787683, 76434144, 67031644, 39801351, 20765474, 55850790, 42967683, 16097038, 39171895, 72793444, 17058722, 874791, 84293052, 65081429, 2331773, 72614359, 44847298, 30463802, 415901, 92692283, 92541302, 51047803, 43322743, 29029316, 81172706, 18663507, 37620363, 37501808, 36808486, 83747892, 41245325, 70541760, 7066775, 17146629, 68644627, 95957797, 16971929, 27041967, 31126490, 49641577, 32058615, 52060076, 30811010, 65047700, 29401781, 9257405, 10264691, 16567550, 51466049, 8733068, 94711842, 56484900, 74441448, 14731700, 67030811, 98948034, 96193415, 54606384, 33201905, 83269727, 84187166, 68939068, 8128637, 53632373, 17386996, 1250437, 4064751, 21397057, 44060493, 40534591, 6871053, 16380211, 10597197, 67793644, 50806615, 61897582, 82897371, 45049308, 65038678, 71083565, 44481640, 83210802, 40677414, 8115266, 23432750, 72725103, 9058407, 4095116, 33336148, 26493119, 8373289, 80251430, 92215320, 3509435, 25842078, 35092039, 22450468, 87160386, 28550822, 75128745, 34698428, 54762643, 58224549, 9623492, 3880712, 53842979, 4515343, 74137926, 23569917, 42947632, 36468541, 10358899, 63628376, 9829782, 90061527, 17365188, 20836893, 24591705, 61712234, 81853704, 24058273, 45794415, 20486294, 35456853, 81898046, 44844121, 21673260, 40984766, 89046466, 1569515, 176257, 19486173, 64157906, 68000591, 47213183, 5640302, 98739783, 13819358, 42644903, 65275241, 37675718, 37183543, 60102965, 82052050, 66271566, 66322959, 73617245, 36845587, 36505482, 37739481, 35192533, 61741594, 48892201, 83948335, 49597667, 72738685, 9175338, 9188443, 70372191, 71522968, 34044787, 41481685, 28851716, 54232247, 49271185, 1204161, 76960303, 50668599, 72777973, 83302115, 1431742, 83083131, 4668450, 20002147, 38256849, 24168411, 31727379, 33565483, 89078848, 36396314, 22129328, 37891451, 94595668, 53888755, 669105, 44983451, 22721500, 62452034, 65017137, 91281584, 526217, 77300457, 61960400, 79191827, 9599614, 92803766, 13470059, 99549373, 4806458, 48673079, 70800879, 79942022, 35996293, 22435353, 45617087, 97561745, 21533347, 69641513, 38510840, 8099994, 84840549, 61623915, 6221471, 62357986, 8055981, 93270084, 68702632, 66045587, 60430369, 78549759, 51315460, 91831487, 81274566, 59981773, 75229462, 21919959, 67084351, 42237907, 13862149, 70036438, 53802686, 68128438, 89996536, 38658347, 22879907, 30891921, 38061439, 3773993, 24619760, 61373987, 12571310, 30090481, 75407347, 38341669, 31161687, 73168565, 29959549, 1022677, 33553959, 6111563, 43933006, 26275734, 42199455, 63506281, 46851987, 73124510, 48658605, 89214616, 12303248, 77694700, 2204165, 67281495, 8791066, 39373729, 52204879, 59910624, 7517032, 77898274, 61859143, 57359924, 19272365, 39201414, 56424103, 70420215, 82726008, 46870723, 46540998, 32151165, 34432810, 54987042, 5111370, 16387575, 15148031, 5267545, 60176618, 75543508, 49598724, 44348328, 98462867, 21001913, 33304202, 53084256, 48395186, 95581843, 75052463, 14326617, 36580610, 93515664, 15948937, 72495719, 73392814, 69605283, 80246713, 30395570, 76170907, 33123618, 7300047, 76671482, 76703609, 41380093, 15535065, 75986488, 33249630, 66885828, 63152504, 51426311, 59329511, 77787724, 43376279, 85711894, 16684829, 72373496, 62762857, 57803235, 10453030, 168541, 6521313, 92353856, 56515456, 62693428, 23848565, 90310261, 94516935, 91727510, 17894977, 49882705, 88251446, 26114953, 38494874, 83150534, 4199704, 4978543, 62430984, 16595436, 65338021, 15064655, 13348726, 30653863, 18411915, 71300104, 44245960, 47090124, 18131876, 20645197, 64055960, 66319530, 59501487, 30366150, 10309525, 77272628, 7919588, 21070110, 52613508, 62740044, 29635537, 7646095, 73109997, 4099191, 69355476, 66428795, 87710366, 56955985, 30139692, 8651647, 98943869, 60106217, 97011160, 65715134, 52293995, 19101477, 84349107, 98648327, 79922758 +90310261, 22405120, 71300104, 4099191, 2607799, 17764950, 69641513, 84002370, 96906410, 99226875, 99971982, 6521313, 61859581, 8373289, 3509435, 49236559, 51472275, 88130087, 72793444, 2331773, 18806856, 11757872, 36780454, 79821897, 14220886, 67124282, 44627776, 91240048, 38645117, 45617087, 90013093, 26998766, 28928175, 95581843, 96735716, 79922758, 89996536, 73392814, 38061439, 68000591, 20765474, 62803858, 40197395, 12303248, 99965001, 22113133, 52204879, 1204161, 83083131, 34497327, 38256849, 37192445, 53632373, 46540998, 56531125, 99917599, 60176618, 16405341, 68041839, 26493119, 18001617, 78218845, 3487592, 58749, 65271999, 93270084, 66667729, 2208785, 1936762, 9860968, 14093520, 36933038, 18466635, 51590803, 34236719, 98130363, 53547802, 22879907, 44844121, 61373987, 75135448, 61928316, 44784505, 43045786, 38341669, 16097038, 26275734, 58180653, 42199455, 76671482, 85117092, 63506281, 66322959, 874791, 73617245, 30463802, 19101477, 37560164, 45428665, 42073124, 77694700, 25636669, 58751351, 29834512, 82651278, 28851716, 71920426, 86798033, 65074535, 68204242, 10264691, 8733068, 20002147, 20140249, 89699445, 26744917, 50702367, 10597197, 54987042, 2717150, 62693428, 526217, 32274392, 82339363, 36139942, 92803766, 59109400, 90158785, 4806458, 92787493, 20642888, 76540481, 26139110, 80316608, 47361209, 92215320, 71965942, 17894977, 72274002, 43357947, 11923835, 58224549, 3880712, 66045587, 42782652, 508198, 4515343, 96420429, 66903004, 30366150, 13862149, 47893286, 59405277, 34493392, 54663246, 3633375, 45794415, 80246713, 89046466, 30787683, 76170907, 78589145, 55850790, 60581278, 21289531, 29959549, 30543215, 26063929, 80014588, 84904436, 4798568, 7955293, 73124510, 85116755, 16099750, 70372191, 74724075, 93562257, 18783702, 86118021, 8791066, 18131876, 68316156, 30811010, 27625735, 69355476, 49271185, 7687278, 87720882, 39201414, 83302115, 64055960, 60955663, 72357096, 824872, 59501487, 77072625, 77187825, 37166608, 36396314, 57803235, 17081350, 46870723, 34946859, 37891451, 168541, 50806615, 4985896, 9398733, 29065964, 43501211, 45075471, 83368048, 79191827, 9599614, 83651211, 35512853, 9058407, 35996293, 75543508, 59371804, 97281847, 97561745, 94090109, 84684495, 96852321, 83133790, 90457870, 93359396, 88251446, 61623915, 66250369, 28796059, 7229550, 74137926, 23569917, 30694952, 36468541, 26392416, 36580610, 6497830, 44915235, 4978543, 14626618, 68128438, 94539824, 30764367, 84166196, 15015906, 21070110, 38658347, 82178706, 62232211, 62490109, 86460488, 87598888, 93790285, 77377183, 8129978, 75777973, 17016156, 37675718, 569864, 89217461, 82979980, 39171895, 60102965, 53666583, 55189057, 76703609, 65081429, 26507214, 62740044, 44847298, 78909561, 37739481, 35192533, 38365584, 29635537, 9175338, 89214616, 92604458, 59177718, 32426752, 18663507, 7646095, 33699435, 99297164, 41442762, 39373729, 34044787, 5970607, 71333116, 43376279, 91664334, 97379790, 77898274, 24915585, 37659250, 54232247, 44479073, 50188404, 51466049, 12024238, 73021291, 62496012, 62762857, 11581181, 84187166, 56955985, 45381876, 77413012, 89078848, 82726008, 82779622, 32151165, 96318094, 34432810, 94595668, 97641116, 44983451, 22721500, 74743862, 69697787, 5111370, 91281584, 36753250, 26664538, 32161669, 84349107, 48673079, 70800879, 26102057, 79942022, 2891150, 4434662, 49598724, 33336148, 44473167, 80251430, 86001008, 16445503, 84840549, 91141395, 9623492, 3088684, 33895336, 30998561, 62115552, 51315460, 75052463, 81274566, 42237907, 22200378, 70036438, 4199704, 67451935, 27325428, 61712234, 65338021, 55770687, 53648154, 73222868, 22942635, 21673260, 40984766, 33061250, 44177011, 1569515, 70004753, 99861373, 12571310, 19486173, 64602895, 30090481, 62552524, 78561158, 86301513, 7423788, 95395112, 65275241, 31161687, 73168565, 29932657, 33123618, 1022677, 61263068, 93933709, 23134715, 34698463, 99729357, 16019925, 13468268, 54427233, 46851987, 72614359, 62936963, 61741594, 41380093, 92692283, 77620120, 23110625, 15535065, 72738685, 39847321, 57163802, 43322743, 42692881, 66885828, 27187213, 29029316, 71522968, 36808486, 18504197, 41245325, 7066775, 56461322, 59329511, 16971929, 31126490, 59910624, 49641577, 36135, 40781449, 72732205, 65047700, 19272365, 66428795, 63015256, 16684829, 85571389, 95726235, 99524975, 43152977, 56484900, 86242799, 40781854, 3294781, 61982238, 56424103, 97783876, 74452589, 86022504, 33201905, 50567636, 45848907, 45996863, 15536795, 12664567, 96709982, 99515901, 14349098, 44060493, 7502255, 59318837, 6871053, 40152546, 97057187, 30163921, 53888755, 72278539, 90090964, 82897371, 68694897, 2917920, 321665, 72358170, 88904910, 19457589, 54517921, 44481640, 15148031, 10366309, 5267545, 8115266, 72725103, 99549373, 85242963, 27411561, 94516935, 91727510, 33797252, 13173644, 17539625, 81855445, 58208470, 26863229, 19939935, 38510840, 76315420, 22450468, 68102437, 99658235, 28550822, 44664587, 60430369, 91802888, 3183975, 26114953, 20568363, 98943869, 85023028, 59981773, 47792865, 55470718, 21919959, 95290172, 95010552, 61271144, 67084351, 15075176, 11161731, 34667286, 90061527, 77272628, 15163258, 38008118, 64098930, 14723410, 7919588, 16595436, 69605283, 20486294, 81898046, 68110330, 38009615, 3773993, 24619760, 48360198, 5482538, 76434144, 13348726, 98648327, 9575954, 92071990, 69255765, 63059024, 98739783, 13819358, 92554120, 6111563, 94360702, 89804152, 11543098, 48260151, 30653863, 61815223, 415901, 32590267, 49597667, 98371444, 51047803, 69786756, 33249630, 22766820, 37501808, 51464002, 51426311, 55960386, 17146629, 71316369, 38022834, 29466406, 74357852, 16054533, 57359924, 4091162, 92692978, 74110882, 18699206, 92033260, 50668599, 29401781, 9257405, 29994197, 31491938, 20645197, 6819644, 67474219, 77284619, 98948034, 70420215, 57658654, 55669657, 41092102, 24168411, 87710366, 17037369, 6986898, 17386996, 83155442, 52261574, 99224392, 4064751, 17727650, 47738185, 48774913, 71657078, 40534591, 54058788, 94076128, 86821229, 91255408, 92353856, 48893685, 62452034, 65017137, 16387575, 18833224, 43506672, 74614639, 61960400, 71083565, 39553046, 93566986, 24733232, 44842615, 19376156, 76825057, 13470059, 41092172, 93053405, 45667668, 29510992, 17957593, 17068582, 87160386, 6221471, 73235980, 45237957, 36312813, 79657802, 26776922, 49328608, 82427263, 55602660, 78602717, 33628349, 8651647, 75229462, 14326617, 26397786, 91990218, 57393458, 54014062, 10358899, 64848072, 48675329, 65454636, 92398073, 10309525, 15948937, 97011160, 32159704, 59614746, 26734892, 81853704, 67227442, 20681921, 32699744, 24058273, 6153406, 78884452, 89499542, 30891921, 8807940, 12416768, 70727211, 31733363, 40027975, 15064655, 55753905, 3235882, 75407347, 30569392, 66116458, 5640302, 68875490, 42644903, 82886381, 33553959, 86543538, 48088883, 36685023, 52293995, 42307449, 36845587, 80555751, 1239555, 75986488, 29188588, 18411915, 54868730, 5073754, 44245960, 81172706, 71469330, 6505939, 79136082, 83747892, 70541760, 23740167, 50007421, 56473732, 47090124, 68644627, 77787724, 27041967, 78766358, 88047921, 7182642, 55143724, 41481685, 7517032, 61859143, 52060076, 90004325, 20122224, 4119747, 24314885, 14363867, 4069912, 56153345, 99021067, 72238278, 73786944, 40356877, 60393039, 37435892, 74441448, 4668450, 79322415, 62428472, 68939068, 39986008, 84127901, 76330843, 5832946, 30771409, 10453030, 45995325, 73031054, 67793644, 61897582, 45306070, 56515456, 36812683, 57020481, 45049308, 92530431, 93057697, 4787945, 45407418, 97940276, 66137019, 30139692, 29516592, 57241521, 75820087, 88698958, 25842078, 35092039, 51715482, 49882705, 40865610, 75128745, 10961421, 88653118, 62357986, 68702632, 57248122, 91831487, 83150534, 1788101, 84406788, 63628376, 36189527, 93515664, 6157724, 53802686, 32250045, 81677380, 69848388, 1197320, 17857111, 20836893, 65715134, 70596786, 19898053, 30395570, 20867149, 83789391, 8729146, 59197747, 65880522, 67031644, 22108665, 52613508, 10649306, 39801351, 77312810, 7300047, 4204661, 3233569, 82052050, 66271566, 8913721, 51507425, 84293052, 36505482, 91957544, 92541302, 9188443, 7685448, 112651, 12348118, 44889423, 70367851, 2204165, 67281495, 7011964, 95957797, 96726697, 81774825, 90654836, 82532312, 76960303, 72777973, 72373496, 16567550, 94711842, 47298834, 1375023, 55247455, 1431742, 78300864, 66319530, 67030811, 5822202, 9603598, 54606384, 67513640, 57240218, 1250437, 21397057, 247198, 16380211, 669105, 44550764, 99125126, 44846932, 31904591, 83210802, 14947650, 45790169, 69579137, 21533347, 13231279, 57961282, 44348328, 98462867, 21001913, 8099994, 75153252, 53084256, 34698428, 63372756, 48395186, 8055981, 28787861, 54199160, 53842979, 6038457, 68816413, 60106217, 42947632, 37280276, 9829782, 28734791, 9860195, 22997281, 21993752, 99604946, 54263880, 176257, 37224844, 47213183, 62051033, 61728685, 37183543, 42967683, 79880247, 83948335, 6793819, 48658605, 63152504, 33237508, 89811711, 14045383, 16424199, 23194618, 83269727, 99333375, 31727379, 33565483, 26292919, 66832478, 8696647, 89637706, 94911072, 77300457, 95251277, 68824981, 69136837, 40677414, 4095116, 83533741, 33431960, 19891772, 27185644, 78785507, 54762643, 7104732, 78549759, 38494874, 37957788, 17976208, 20885148, 62430984, 6525948, 35456853, 15902805, 79738755, 64157906, 45990383, 92867155, 47887470, 55615885, 34295794, 92998591, 47708850, 73109997, 4508700, 85711894, 27665211, 33935899, 91938191, 24953498, 14731700, 66663942, 96193415, 40686254, 17385531, 22129328, 29819940, 23848565, 8634541, 72019362, 81805959, 89419466, 70782102, 749283, 72495719, 17365188, 85695762, 24826575, 51149804, 48892201, 63967300, 6808825, 32058615, 79806380, 45919976, 36930650, 8128637, 65851721, 11365791, 9886593, 61141874, 82327024, 23432750, 51588015, 90272749, 3233084, 33304202, 66611759, 9259676, 88444207, 24591705, 53393358, 77301523, 98653983, 17058722, 99690194, 37620363, 30218878, 65038678, 22435353, 64087743, 43933006 +10649306, 82052050, 2917920, 81855445, 24314885, 83302115, 11581181, 11365791, 508198, 569864, 85117092, 4099191, 17385531, 45075471, 44481640, 26863229, 96420429, 14626618, 9575954, 33553959, 8913721, 73124510, 7687278, 65074535, 94711842, 50567636, 26292919, 89078848, 5832946, 43501211, 39553046, 68824981, 45407418, 93359396, 9623492, 42782652, 26392416, 22879907, 70004753, 31733363, 30090481, 47213183, 42644903, 61263068, 63506281, 84904436, 2331773, 48658605, 89214616, 74724075, 7646095, 58751351, 30811010, 73021291, 3294781, 70420215, 33201905, 39986008, 45381876, 4064751, 59318837, 168541, 9886593, 29065964, 45049308, 10366309, 13470059, 49598724, 13173644, 69641513, 84684495, 19939935, 90457870, 78785507, 11923835, 8055981, 53842979, 95290172, 95010552, 10358899, 17976208, 18466635, 32250045, 98130363, 67227442, 32699744, 55770687, 30891921, 62490109, 3773993, 15064655, 19486173, 64602895, 98739783, 13819358, 33123618, 47887470, 42967683, 51149804, 76703609, 4798568, 72614359, 35192533, 91957544, 34295794, 51047803, 54868730, 70372191, 92998591, 70367851, 47090124, 95957797, 77787724, 43376279, 90004325, 20122224, 69355476, 18806856, 4668450, 6819644, 97783876, 37166608, 67513640, 6986898, 48774913, 40534591, 96318094, 74743862, 56531125, 91281584, 526217, 18833224, 32274392, 72725103, 70800879, 93053405, 8373289, 8634541, 29510992, 25842078, 35092039, 72274002, 99658235, 34698428, 3088684, 79922758, 81805959, 26397786, 63628376, 90061527, 73392814, 53648154, 38061439, 176257, 79738755, 8729146, 98648327, 77377183, 44784505, 38341669, 62803858, 61728685, 60581278, 17016156, 89217461, 39171895, 60102965, 26507214, 15535065, 6793819, 57163802, 71300104, 77694700, 29029316, 6808825, 23740167, 55960386, 7011964, 68644627, 2607799, 78766358, 7517032, 54232247, 27665211, 92033260, 56153345, 72373496, 45919976, 16567550, 95726235, 8733068, 60393039, 24953498, 34497327, 47738185, 6871053, 45995325, 247198, 34432810, 73031054, 4985896, 82897371, 68694897, 99125126, 65038678, 83368048, 79191827, 24733232, 32161669, 69136837, 92803766, 97940276, 66137019, 75543508, 59371804, 69579137, 47361209, 27185644, 63372756, 66611759, 68702632, 28787861, 28796059, 66045587, 49328608, 30694952, 91831487, 85023028, 42947632, 47792865, 75229462, 67084351, 70036438, 4978543, 6157724, 15948937, 72495719, 17365188, 15163258, 30764367, 84166196, 6525948, 26734892, 24058273, 78884452, 21993752, 35456853, 21673260, 40984766, 86460488, 87598888, 48360198, 22108665, 61928316, 52613508, 78561158, 30569392, 31161687, 17058722, 36685023, 26063929, 874791, 84293052, 84002370, 44847298, 19101477, 48892201, 38365584, 72738685, 98371444, 12303248, 66885828, 27187213, 44245960, 18663507, 67281495, 99965001, 56473732, 34044787, 16971929, 27041967, 16054533, 97379790, 90654836, 37659250, 82532312, 14363867, 44479073, 16684829, 23194618, 29994197, 43152977, 824872, 61982238, 57658654, 36930650, 33565483, 84187166, 84127901, 37891451, 94076128, 94595668, 97641116, 19457589, 61960400, 9599614, 36139942, 59109400, 60176618, 23432750, 4787945, 4806458, 41092172, 94516935, 80316608, 33336148, 45667668, 45617087, 97281847, 90272749, 30139692, 57961282, 3509435, 83133790, 90013093, 16445503, 75153252, 10961421, 66250369, 36312813, 33895336, 2208785, 7229550, 62115552, 74137926, 51315460, 98943869, 89419466, 70782102, 61271144, 30366150, 9829782, 47893286, 51472275, 28734791, 11161731, 89996536, 54663246, 1197320, 24591705, 16595436, 45794415, 69605283, 21070110, 22942635, 99604946, 54263880, 61373987, 93790285, 5482538, 68000591, 92071990, 8129978, 7423788, 95395112, 21289531, 29959549, 77301523, 16097038, 48088883, 53666583, 76671482, 66271566, 11543098, 34698463, 48260151, 16019925, 62740044, 62936963, 49597667, 92692283, 75986488, 32426752, 43322743, 73109997, 6505939, 7066775, 51426311, 18783702, 50007421, 71316369, 59329511, 29466406, 96726697, 31126490, 59910624, 16424199, 32058615, 81774825, 57359924, 4091162, 92692978, 91938191, 19272365, 63015256, 50188404, 99021067, 87720882, 50668599, 72238278, 9257405, 51466049, 47298834, 56484900, 86242799, 60955663, 67474219, 98948034, 5822202, 66663942, 11757872, 86022504, 24168411, 36780454, 99333375, 26744917, 37192445, 57803235, 17386996, 82726008, 22129328, 21397057, 29819940, 7502255, 34946859, 40152546, 54987042, 86821229, 2717150, 48893685, 5111370, 62693428, 62452034, 72358170, 88904910, 44627776, 16387575, 74614639, 99917599, 31904591, 82339363, 44842615, 5267545, 90158785, 35512853, 17764950, 22405120, 76540481, 85242963, 79942022, 26139110, 57241521, 80251430, 98462867, 86001008, 17894977, 43357947, 28550822, 91141395, 88653118, 44664587, 93270084, 66667729, 54199160, 26776922, 30998561, 4515343, 60430369, 20568363, 75052463, 1936762, 59981773, 14093520, 83150534, 36933038, 36580610, 54014062, 4199704, 6497830, 15075176, 92398073, 20885148, 10309525, 53802686, 34667286, 27325428, 22997281, 62430984, 64098930, 7919588, 20836893, 61712234, 20681921, 3633375, 70596786, 15902805, 33061250, 24619760, 30787683, 70727211, 99861373, 75135448, 3235882, 69255765, 75407347, 86301513, 43045786, 92554120, 62051033, 75777973, 29932657, 82886381, 1022677, 37675718, 77312810, 93933709, 94360702, 7300047, 4204661, 98653983, 58180653, 55189057, 30543215, 30653863, 7955293, 83948335, 39847321, 29635537, 96906410, 59177718, 42073124, 33249630, 22766820, 47708850, 37501808, 36808486, 51464002, 17146629, 99297164, 41442762, 22113133, 38022834, 91664334, 74357852, 88047921, 49641577, 41481685, 77898274, 61859143, 28851716, 27625735, 74110882, 18699206, 33935899, 65047700, 76960303, 4069912, 85571389, 39201414, 10264691, 1375023, 20645197, 74441448, 83083131, 40781854, 30218878, 59501487, 77072625, 54606384, 83269727, 31727379, 68939068, 56955985, 8128637, 15536795, 12664567, 50702367, 46870723, 99224392, 71657078, 54058788, 32151165, 79821897, 99971982, 669105, 6521313, 90090964, 69697787, 8696647, 45306070, 56515456, 321665, 65017137, 36812683, 57020481, 36753250, 77300457, 54517921, 92530431, 93566986, 15148031, 26664538, 61859581, 93057697, 84349107, 91240048, 8115266, 51588015, 92787493, 16405341, 4095116, 14947650, 2891150, 22435353, 4434662, 33797252, 26493119, 97561745, 29516592, 17539625, 58208470, 17957593, 71965942, 38510840, 33304202, 17068582, 72019362, 84840549, 3487592, 58749, 65271999, 6221471, 48395186, 62357986, 58224549, 96735716, 82427263, 78549759, 33628349, 9259676, 8651647, 21919959, 88444207, 1788101, 13862149, 44915235, 37957788, 59405277, 97011160, 68128438, 34493392, 94539824, 32159704, 34236719, 38008118, 81853704, 65715134, 6153406, 81898046, 8807940, 38009615, 30395570, 44177011, 1569515, 83789391, 37224844, 12571310, 76434144, 13348726, 62552524, 63059024, 39801351, 20765474, 92867155, 73168565, 86543538, 26275734, 72793444, 42199455, 40197395, 52293995, 42307449, 99729357, 66322959, 54427233, 55615885, 65081429, 36845587, 80555751, 78909561, 36505482, 30463802, 415901, 1239555, 41380093, 23110625, 18411915, 7685448, 99690194, 112651, 63967300, 42692881, 71522968, 37620363, 93562257, 63152504, 79136082, 70541760, 86118021, 33699435, 25636669, 71333116, 52204879, 18131876, 7182642, 68316156, 40781449, 72732205, 71920426, 49271185, 12024238, 31491938, 99524975, 37435892, 1431742, 14731700, 20002147, 66319530, 72357096, 20140249, 99226875, 56424103, 38256849, 41092102, 62428472, 77413012, 36396314, 53632373, 57240218, 96709982, 14349098, 17727650, 30771409, 10453030, 97057187, 67793644, 30163921, 61897582, 44983451, 22721500, 92353856, 94911072, 19376156, 40677414, 90310261, 76825057, 20642888, 35996293, 91727510, 18001617, 33431960, 78218845, 19891772, 92215320, 44348328, 88698958, 3233084, 76315420, 8099994, 68102437, 88251446, 40865610, 53084256, 55602660, 3183975, 78602717, 23569917, 9860968, 81274566, 60106217, 55470718, 36468541, 42237907, 91990218, 84406788, 48675329, 69848388, 53547802, 65338021, 19898053, 89499542, 38658347, 73222868, 82178706, 44844121, 80246713, 12416768, 40027975, 76170907, 59197747, 64157906, 67031644, 65275241, 82979980, 37183543, 23134715, 6111563, 43933006, 89804152, 73617245, 46851987, 61815223, 37560164, 29188588, 85116755, 18504197, 56461322, 29834512, 55143724, 85711894, 14045383, 82651278, 52060076, 79806380, 86798033, 66428795, 68204242, 73786944, 55247455, 78300864, 96193415, 62762857, 79322415, 87710366, 99515901, 76330843, 10597197, 72278539, 91255408, 9398733, 67124282, 89637706, 44846932, 95251277, 71083565, 82327024, 83210802, 38645117, 83651211, 99549373, 48673079, 26102057, 68041839, 94090109, 75820087, 96852321, 28928175, 75128745, 54762643, 45237957, 95581843, 3880712, 79657802, 91802888, 66903004, 68816413, 14326617, 57393458, 37280276, 22200378, 36189527, 67451935, 9860195, 65454636, 51590803, 77272628, 81677380, 15015906, 20486294, 89046466, 20867149, 55753905, 24826575, 65880522, 78589145, 45990383, 66116458, 5640302, 55850790, 3233569, 51507425, 80014588, 79880247, 16099750, 92604458, 12348118, 44889423, 71469330, 83747892, 41245325, 39373729, 33237508, 1204161, 72777973, 40356877, 67030811, 74452589, 77187825, 89699445, 45996863, 40686254, 1250437, 82779622, 16380211, 65851721, 66832478, 14220886, 44550764, 61141874, 9058407, 83533741, 27411561, 22450468, 87160386, 49882705, 7104732, 73235980, 57248122, 6038457, 26114953, 38494874, 49236559, 749283, 93515664, 14723410, 68110330, 88130087, 68875490, 13468268, 37739481, 32590267, 9175338, 69786756, 5073754, 81172706, 8791066, 4508700, 89811711, 36135, 24915585, 29401781, 64055960, 77284619, 9603598, 83155442, 17081350, 44060493, 50806615, 53888755, 43506672, 44473167, 21533347, 13231279, 21001913, 51715482, 61623915, 64848072, 62232211, 77620120, 92541302, 45428665, 5970607, 4119747, 62496012, 55669657, 17037369, 23848565, 45790169, 26998766, 9188443, 45848907, 52261574, 59614746, 17857111, 53393358, 61741594, 2204165, 85695762, 64087743, 46540998 +11543098, 54427233, 53084256, 96735716, 4091162, 6871053, 91255408, 79191827, 92787493, 29510992, 17068582, 28928175, 78561158, 8129978, 4204661, 89214616, 70372191, 12303248, 7646095, 74357852, 16054533, 57359924, 8733068, 32151165, 94595668, 14220886, 19457589, 78218845, 78785507, 16595436, 22108665, 9575954, 66116458, 98739783, 86543538, 13468268, 30653863, 71300104, 36808486, 55960386, 33935899, 29994197, 74452589, 37192445, 17081350, 17727650, 37891451, 66832478, 94911072, 82327024, 90310261, 60176618, 41092172, 18001617, 57241521, 75229462, 59405277, 32159704, 24058273, 55770687, 21993752, 12416768, 44784505, 7423788, 93933709, 80014588, 9175338, 92604458, 66885828, 5073754, 44889423, 86118021, 95957797, 29834512, 32058615, 20122224, 69355476, 71920426, 24314885, 99524975, 55247455, 86242799, 67030811, 98948034, 96193415, 34497327, 97783876, 56955985, 26292919, 77413012, 10453030, 50806615, 11365791, 5111370, 36753250, 54517921, 82339363, 19376156, 69136837, 90158785, 4787945, 17764950, 48673079, 26139110, 17539625, 17957593, 90013093, 72274002, 72019362, 40865610, 93270084, 66250369, 96420429, 6038457, 20568363, 36468541, 42237907, 15948937, 77272628, 14626618, 89996536, 26734892, 15015906, 73222868, 62232211, 22942635, 99604946, 64602895, 30090481, 68000591, 47213183, 31161687, 55850790, 29932657, 58180653, 40197395, 16019925, 874791, 73617245, 19101477, 32590267, 73124510, 72738685, 45428665, 29635537, 74724075, 29029316, 37620363, 51426311, 16971929, 78766358, 97379790, 16424199, 27625735, 49271185, 7687278, 39201414, 94711842, 37435892, 86022504, 24168411, 83269727, 11581181, 33565483, 68939068, 26744917, 45381876, 6986898, 4064751, 29819940, 34946859, 86821229, 74743862, 69697787, 67124282, 526217, 43501211, 61960400, 93566986, 44481640, 61859581, 9599614, 23848565, 38645117, 22405120, 83533741, 79942022, 91727510, 8373289, 21533347, 38510840, 27185644, 35092039, 22450468, 62357986, 3088684, 95581843, 28787861, 66045587, 74137926, 51315460, 9860968, 60106217, 13862149, 84406788, 49236559, 37280276, 47893286, 48675329, 28734791, 15075176, 9860195, 34667286, 32250045, 62430984, 94539824, 34236719, 54663246, 6525948, 1197320, 81853704, 78884452, 38658347, 80246713, 3773993, 87598888, 76170907, 75135448, 48360198, 76434144, 61928316, 62552524, 42644903, 60581278, 21289531, 77312810, 39171895, 48088883, 89804152, 42199455, 30543215, 76671482, 62740044, 44847298, 96906410, 32426752, 77694700, 27187213, 71522968, 51464002, 41245325, 67281495, 56461322, 33699435, 29466406, 59910624, 82651278, 90004325, 72732205, 74110882, 91938191, 66428795, 45919976, 43152977, 56484900, 24953498, 40781854, 20002147, 6819644, 3294781, 824872, 99226875, 66663942, 11757872, 55669657, 37166608, 17037369, 45848907, 50702367, 83155442, 76330843, 44060493, 5832946, 46540998, 44983451, 2917920, 62693428, 321665, 99125126, 88904910, 16387575, 18833224, 26664538, 44842615, 36139942, 91240048, 35512853, 9058407, 80316608, 33336148, 45667668, 30139692, 33431960, 92215320, 3509435, 69641513, 96852321, 19939935, 28550822, 63372756, 65271999, 45237957, 30998561, 26114953, 23569917, 33628349, 9259676, 98943869, 70782102, 1788101, 4199704, 93515664, 67451935, 10309525, 53802686, 17365188, 68128438, 30764367, 20836893, 65338021, 45794415, 85695762, 53648154, 82178706, 81898046, 30891921, 8807940, 24619760, 44177011, 61373987, 8729146, 55753905, 65880522, 3235882, 92071990, 39801351, 75777973, 37675718, 66271566, 34698463, 66322959, 65081429, 46851987, 78909561, 30463802, 415901, 91957544, 37560164, 6793819, 16099750, 18411915, 51047803, 54868730, 99690194, 22766820, 6808825, 70541760, 50007421, 4099191, 7011964, 17146629, 41442762, 58751351, 89811711, 22113133, 2607799, 41481685, 36135, 30811010, 92692978, 56153345, 68204242, 23194618, 29401781, 85571389, 95726235, 83302115, 12024238, 60393039, 1431742, 66319530, 59501487, 61982238, 62762857, 38256849, 33201905, 50567636, 36396314, 84127901, 57803235, 40686254, 47738185, 48774913, 40534591, 67793644, 61897582, 72278539, 22721500, 90090964, 48893685, 68694897, 62452034, 44627776, 29065964, 91281584, 45075471, 83368048, 68824981, 15148031, 32161669, 83651211, 51588015, 99549373, 76540481, 85242963, 94516935, 97940276, 66137019, 75543508, 22435353, 45790169, 45617087, 97281847, 44473167, 19891772, 47361209, 58208470, 75820087, 44348328, 84684495, 25842078, 17894977, 43357947, 8099994, 90457870, 84840549, 68102437, 75128745, 3487592, 44664587, 508198, 2208785, 60430369, 79922758, 55602660, 91831487, 85023028, 47792865, 55470718, 61271144, 88444207, 91990218, 30366150, 54014062, 10358899, 749283, 37957788, 20885148, 90061527, 72495719, 34493392, 84166196, 14723410, 98130363, 61712234, 20681921, 53547802, 6153406, 73392814, 89499542, 21673260, 86460488, 176257, 20867149, 31733363, 99861373, 78589145, 67031644, 45990383, 68875490, 10649306, 13819358, 92554120, 65275241, 38341669, 92867155, 47887470, 1022677, 61263068, 33553959, 42967683, 94360702, 60102965, 3233569, 36685023, 84904436, 4798568, 72614359, 35192533, 92692283, 42073124, 92998591, 42692881, 81172706, 2204165, 6505939, 18504197, 39373729, 59329511, 27041967, 91664334, 96726697, 88047921, 7182642, 68316156, 28851716, 37659250, 54232247, 1204161, 92033260, 63015256, 76960303, 14363867, 44479073, 72777973, 9257405, 72373496, 10264691, 31491938, 20645197, 4668450, 14731700, 67474219, 57658654, 8128637, 12664567, 53632373, 57240218, 17386996, 52261574, 99224392, 82779622, 7502255, 96318094, 34432810, 97057187, 168541, 2717150, 92353856, 9398733, 89637706, 57020481, 45049308, 95251277, 65038678, 71083565, 92530431, 5267545, 84349107, 92803766, 83210802, 59109400, 13470059, 4806458, 45407418, 16405341, 2891150, 4434662, 33797252, 94090109, 69579137, 26863229, 86001008, 93359396, 26998766, 88251446, 75153252, 58749, 11923835, 91141395, 34698428, 6221471, 66611759, 48395186, 58224549, 66667729, 36312813, 3880712, 53842979, 26776922, 57248122, 91802888, 7229550, 3183975, 78549759, 68816413, 14326617, 83150534, 21919959, 36933038, 26392416, 57393458, 11161731, 92398073, 27325428, 3633375, 70596786, 19898053, 21070110, 44844121, 40984766, 38061439, 33061250, 54263880, 89046466, 1569515, 37224844, 13348726, 69255765, 75407347, 63059024, 62051033, 17016156, 29959549, 89217461, 7300047, 82052050, 8913721, 42307449, 76703609, 26063929, 51507425, 84293052, 84002370, 36845587, 80555751, 1239555, 83948335, 23110625, 92541302, 29188588, 57163802, 48658605, 69786756, 47708850, 37501808, 73109997, 23740167, 18783702, 8791066, 34044787, 52204879, 43376279, 85711894, 7517032, 52060076, 4119747, 18699206, 65074535, 4069912, 72238278, 51466049, 74441448, 60955663, 77284619, 73021291, 72357096, 20140249, 9603598, 36930650, 89699445, 84187166, 67513640, 99515901, 82726008, 22129328, 14349098, 79821897, 59318837, 94076128, 10597197, 99971982, 65851721, 54987042, 669105, 82897371, 56531125, 65017137, 44846932, 61141874, 77300457, 99917599, 31904591, 93057697, 40677414, 20642888, 4095116, 26102057, 14947650, 26493119, 90272749, 3233084, 71965942, 33304202, 76315420, 16445503, 61623915, 73235980, 54199160, 49328608, 78602717, 81805959, 38494874, 1936762, 42947632, 14093520, 64848072, 36189527, 44915235, 51590803, 22997281, 15163258, 17857111, 7919588, 65715134, 15902805, 30787683, 70004753, 83789391, 40027975, 59197747, 19486173, 5482538, 88130087, 64157906, 5640302, 20765474, 569864, 82979980, 43933006, 53666583, 26275734, 98653983, 52293995, 63506281, 79880247, 26507214, 48892201, 15535065, 75986488, 12348118, 44245960, 70367851, 71469330, 63152504, 79136082, 99965001, 47090124, 25636669, 4508700, 5970607, 31126490, 55143724, 81774825, 61859143, 27665211, 86798033, 16684829, 87720882, 50668599, 47298834, 64055960, 78300864, 83083131, 77187825, 87710366, 62428472, 15536795, 89078848, 17385531, 46870723, 30771409, 247198, 45306070, 9886593, 36812683, 74614639, 32274392, 76825057, 8115266, 23432750, 72725103, 70800879, 59371804, 49598724, 29516592, 81855445, 83133790, 49882705, 99658235, 79657802, 62115552, 30694952, 66903004, 95290172, 67084351, 36580610, 22200378, 4978543, 17976208, 6157724, 97011160, 69605283, 20486294, 35456853, 22879907, 62490109, 68110330, 93790285, 12571310, 98648327, 77377183, 52613508, 86301513, 33123618, 77301523, 16097038, 72793444, 85117092, 99729357, 48260151, 55615885, 36505482, 61815223, 7955293, 38365584, 49597667, 41380093, 39847321, 85116755, 9188443, 63967300, 43322743, 83747892, 33237508, 71316369, 18131876, 24915585, 79806380, 65047700, 19272365, 1375023, 77072625, 99333375, 21397057, 54058788, 40152546, 16380211, 30163921, 53888755, 4985896, 44550764, 72358170, 24733232, 10366309, 27411561, 35996293, 13173644, 88698958, 87160386, 10961421, 54762643, 33895336, 4515343, 81274566, 89419466, 63628376, 51472275, 70036438, 81677380, 32699744, 38009615, 64087743, 15064655, 24826575, 30569392, 43045786, 62803858, 73168565, 82886381, 37183543, 23134715, 6111563, 17058722, 55189057, 51149804, 2331773, 98371444, 33249630, 56473732, 68644627, 38022834, 14045383, 40356877, 16567550, 41092102, 54606384, 39986008, 96709982, 1250437, 45995325, 73031054, 6521313, 8696647, 56515456, 93053405, 68041839, 97561745, 98462867, 51715482, 7104732, 8055981, 9623492, 68702632, 82427263, 75052463, 59981773, 26397786, 65454636, 18466635, 69848388, 24591705, 67227442, 53393358, 30395570, 70727211, 79738755, 95395112, 61728685, 62936963, 37739481, 77620120, 34295794, 7685448, 59177718, 112651, 99297164, 49641577, 77898274, 82532312, 50188404, 99021067, 56424103, 70420215, 79322415, 36780454, 31727379, 97641116, 8634541, 80251430, 13231279, 57961282, 88653118, 28796059, 42782652, 8651647, 95010552, 9829782, 6497830, 59614746, 61741594, 18663507, 93562257, 7066775, 77787724, 71333116, 30218878, 45996863, 71657078, 43506672, 39553046, 21001913, 38008118, 40781449, 90654836, 73786944, 62496012, 18806856, 5822202, 64098930 +17016156, 76671482, 85117092, 59177718, 57803235, 92787493, 18001617, 92215320, 28550822, 53842979, 66903004, 73617245, 69786756, 56473732, 14045383, 10264691, 12024238, 74441448, 36780454, 6521313, 36139942, 27185644, 51715482, 28787861, 34493392, 24591705, 92554120, 38365584, 71300104, 99965001, 61859143, 65047700, 60955663, 29819940, 91255408, 2917920, 84349107, 20642888, 33336148, 78218845, 62357986, 36468541, 67084351, 91990218, 30366150, 10358899, 72495719, 16595436, 48360198, 64602895, 5482538, 67031644, 68000591, 68875490, 73168565, 26063929, 37560164, 34295794, 43322743, 7066775, 47090124, 99297164, 16971929, 28851716, 4069912, 67030811, 33201905, 84187166, 14349098, 46870723, 62693428, 16387575, 83368048, 82339363, 90158785, 4806458, 80251430, 98462867, 26998766, 49882705, 53084256, 91141395, 1936762, 14093520, 95010552, 26397786, 44915235, 6157724, 90061527, 59614746, 67227442, 24058273, 38658347, 62490109, 65880522, 3235882, 75407347, 42644903, 61728685, 60581278, 1022677, 37183543, 89804152, 72793444, 17058722, 7955293, 1239555, 92692283, 92541302, 39847321, 63967300, 33249630, 33699435, 36135, 4091162, 37659250, 92033260, 51466049, 31491938, 99524975, 54606384, 77413012, 6986898, 96709982, 52261574, 22129328, 82779622, 6871053, 40152546, 247198, 73031054, 86821229, 90090964, 92353856, 69697787, 36753250, 526217, 43501211, 92530431, 26664538, 61859581, 91240048, 90310261, 16405341, 9058407, 48673079, 27411561, 59371804, 97561745, 88698958, 8099994, 54762643, 3088684, 66667729, 28796059, 3880712, 79922758, 7229550, 62115552, 3183975, 21919959, 49236559, 4199704, 22997281, 68128438, 94539824, 20681921, 73392814, 73222868, 38061439, 89046466, 15064655, 12571310, 19486173, 13348726, 98648327, 66116458, 8129978, 38341669, 62051033, 33123618, 21289531, 37675718, 39171895, 36685023, 8913721, 13468268, 66322959, 874791, 80014588, 62740044, 15535065, 16099750, 96906410, 5073754, 77694700, 12348118, 29029316, 73109997, 23740167, 18131876, 27625735, 4119747, 79806380, 63015256, 16684829, 50188404, 50668599, 47298834, 1375023, 67474219, 73021291, 62496012, 56424103, 77072625, 33565483, 68939068, 45996863, 89078848, 53632373, 47738185, 79821897, 45995325, 94595668, 99971982, 97641116, 30163921, 61897582, 72278539, 44550764, 11365791, 9398733, 89637706, 99125126, 88904910, 32274392, 45075471, 32161669, 9599614, 92803766, 83210802, 23432750, 4787945, 41092172, 4095116, 26102057, 45667668, 45617087, 30139692, 57241521, 47361209, 21533347, 58208470, 3509435, 25842078, 87160386, 84840549, 58749, 34698428, 65271999, 7104732, 2208785, 96420429, 23569917, 30694952, 75229462, 36580610, 13862149, 47893286, 65454636, 15948937, 77272628, 38008118, 98130363, 65338021, 55770687, 53648154, 80246713, 15902805, 12416768, 54263880, 75135448, 24826575, 78589145, 64157906, 61928316, 52613508, 39801351, 55850790, 29932657, 29959549, 26275734, 66271566, 51149804, 54427233, 55615885, 84002370, 62936963, 35192533, 61815223, 23110625, 6793819, 29635537, 85116755, 42073124, 7646095, 70541760, 4099191, 7011964, 17146629, 38022834, 27041967, 91664334, 85711894, 59910624, 49641577, 90654836, 69355476, 91938191, 7687278, 1204161, 56153345, 18806856, 37435892, 83083131, 86242799, 34497327, 79322415, 74452589, 38256849, 86022504, 87710366, 83269727, 62428472, 99333375, 50567636, 37192445, 48774913, 71657078, 54058788, 10597197, 34432810, 168541, 66832478, 62452034, 19457589, 39553046, 99917599, 38645117, 35996293, 94516935, 33797252, 8634541, 94090109, 83133790, 21001913, 76315420, 90457870, 61623915, 75153252, 3487592, 11923835, 93270084, 36312813, 66045587, 42782652, 60430369, 91802888, 81805959, 9860968, 89419466, 63628376, 37280276, 22200378, 51472275, 6497830, 59405277, 15163258, 64098930, 26734892, 6153406, 70596786, 19898053, 81898046, 44844121, 68110330, 86460488, 64087743, 176257, 79738755, 93790285, 88130087, 22108665, 45990383, 78561158, 47887470, 82979980, 86543538, 7300047, 52293995, 79880247, 84293052, 84904436, 30653863, 65081429, 2331773, 72614359, 36845587, 80555751, 19101477, 91957544, 41380093, 51047803, 112651, 12303248, 66885828, 27187213, 44245960, 47708850, 70367851, 6808825, 18783702, 86118021, 50007421, 25636669, 39373729, 34044787, 33237508, 71333116, 7182642, 55143724, 68316156, 7517032, 20122224, 27665211, 18699206, 86798033, 76960303, 99021067, 68204242, 29401781, 40356877, 94711842, 43152977, 20645197, 64055960, 1431742, 66319530, 824872, 59501487, 99226875, 70420215, 11757872, 77187825, 55669657, 37166608, 31727379, 12664567, 50702367, 17386996, 17385531, 17727650, 40534591, 37891451, 2717150, 74743862, 56515456, 65017137, 36812683, 77300457, 95251277, 71083565, 93566986, 44481640, 10366309, 23848565, 5267545, 19376156, 40677414, 76825057, 72725103, 35512853, 22405120, 85242963, 66137019, 45790169, 49598724, 90272749, 19891772, 17539625, 57961282, 75820087, 96852321, 86001008, 19939935, 3233084, 22450468, 99658235, 28928175, 40865610, 63372756, 45237957, 44664587, 68702632, 95581843, 33895336, 96735716, 57248122, 26114953, 51315460, 38494874, 81274566, 60106217, 59981773, 42947632, 42237907, 93515664, 37957788, 4978543, 18466635, 32250045, 51590803, 97011160, 81677380, 89996536, 34236719, 3633375, 21993752, 22879907, 33061250, 30395570, 20867149, 99861373, 40027975, 37224844, 55753905, 76434144, 9575954, 69255765, 30569392, 63059024, 95395112, 20765474, 61263068, 82052050, 30543215, 76703609, 63506281, 46851987, 44847298, 37739481, 30463802, 32590267, 29188588, 98371444, 48658605, 54868730, 92998591, 81172706, 71522968, 2204165, 71469330, 37501808, 79136082, 56461322, 58751351, 89811711, 68644627, 95957797, 43376279, 31126490, 16424199, 41481685, 81774825, 82651278, 52060076, 40781449, 30811010, 49271185, 19272365, 66428795, 44479073, 23194618, 56484900, 14731700, 66663942, 61982238, 57658654, 89699445, 11581181, 26744917, 67513640, 36396314, 84127901, 57240218, 99515901, 82726008, 76330843, 7502255, 32151165, 96318094, 59318837, 97057187, 67793644, 50806615, 53888755, 54987042, 44983451, 4985896, 14220886, 67124282, 94911072, 44846932, 44627776, 74614639, 68824981, 44842615, 45407418, 76540481, 14947650, 97940276, 22435353, 93053405, 26493119, 97281847, 13173644, 69579137, 13231279, 69641513, 26863229, 38510840, 33304202, 72274002, 43357947, 10961421, 6221471, 48395186, 8055981, 9623492, 54199160, 26776922, 30998561, 55602660, 33628349, 75052463, 8651647, 68816413, 70782102, 57393458, 9829782, 67451935, 15075176, 34667286, 14626618, 14723410, 69848388, 1197320, 7919588, 32699744, 53393358, 78884452, 69605283, 99604946, 24619760, 44177011, 70004753, 70727211, 87598888, 76170907, 30090481, 92071990, 86301513, 31161687, 92867155, 82886381, 77301523, 77312810, 93933709, 16097038, 43933006, 60102965, 3233569, 53666583, 58180653, 42199455, 55189057, 40197395, 42307449, 51507425, 36505482, 61741594, 49597667, 77620120, 75986488, 45428665, 9188443, 92604458, 99690194, 70372191, 22766820, 44889423, 93562257, 36808486, 63152504, 6505939, 51464002, 41442762, 22113133, 71316369, 5970607, 52204879, 74357852, 78766358, 88047921, 57359924, 90004325, 92692978, 71920426, 72238278, 9257405, 24953498, 3294781, 72357096, 9603598, 24168411, 45848907, 83155442, 17081350, 99224392, 21397057, 44060493, 30771409, 34946859, 10453030, 16380211, 82897371, 321665, 72358170, 61141874, 29065964, 91281584, 57020481, 43506672, 54517921, 79191827, 59109400, 13470059, 83651211, 51588015, 99549373, 70800879, 75543508, 80316608, 4434662, 44473167, 8373289, 29510992, 44348328, 17068582, 16445503, 78785507, 66250369, 82427263, 78549759, 20568363, 55470718, 14326617, 36933038, 36189527, 9860195, 11161731, 10309525, 54663246, 20836893, 15015906, 53547802, 45794415, 62232211, 22942635, 30891921, 21673260, 40984766, 3773993, 83789391, 59197747, 77377183, 44784505, 43045786, 10649306, 65275241, 75777973, 23134715, 6111563, 48088883, 4204661, 11543098, 34698463, 26507214, 48892201, 9175338, 18411915, 7685448, 67281495, 59329511, 77787724, 16054533, 97379790, 32058615, 72732205, 54232247, 82532312, 33935899, 29994197, 45919976, 16567550, 83302115, 20002147, 6819644, 97783876, 41092102, 17037369, 8128637, 1250437, 4064751, 5832946, 46540998, 94076128, 669105, 22721500, 48893685, 8696647, 18833224, 61960400, 31904591, 93057697, 60176618, 8115266, 83533741, 79942022, 91727510, 2891150, 26139110, 68041839, 29516592, 81855445, 84684495, 71965942, 90013093, 17894977, 93359396, 88251446, 75128745, 66611759, 88653118, 58224549, 73235980, 79657802, 74137926, 9259676, 98943869, 91831487, 85023028, 83150534, 61271144, 88444207, 26392416, 54014062, 64848072, 48675329, 70036438, 92398073, 53802686, 62430984, 32159704, 30764367, 17857111, 61712234, 65715134, 21070110, 20486294, 8807940, 30787683, 61373987, 5640302, 13819358, 62803858, 569864, 89217461, 94360702, 16019925, 4798568, 415901, 72738685, 57163802, 42692881, 51426311, 55960386, 4508700, 29834512, 96726697, 77898274, 24915585, 65074535, 72373496, 73786944, 95726235, 55247455, 98948034, 20140249, 5822202, 36930650, 62762857, 45381876, 15536795, 65851721, 68694897, 5111370, 56531125, 24733232, 82327024, 69136837, 72019362, 68102437, 508198, 4515343, 1788101, 84406788, 17976208, 27325428, 84166196, 82178706, 1569515, 62552524, 7423788, 83948335, 89214616, 32426752, 74724075, 37620363, 83747892, 41245325, 8791066, 29466406, 2607799, 74110882, 14363867, 72777973, 85571389, 39201414, 40781854, 4668450, 77284619, 39986008, 56955985, 26292919, 40686254, 45306070, 15148031, 17764950, 17957593, 35092039, 47792865, 749283, 20885148, 17365188, 89499542, 85695762, 35456853, 38009615, 31733363, 8729146, 33553959, 42967683, 99729357, 78909561, 18663507, 18504197, 87720882, 8733068, 60393039, 78300864, 30218878, 45049308, 65038678, 49328608, 6038457, 78602717, 28734791, 47213183, 98739783, 98653983, 48260151, 73124510, 24314885, 9886593, 33431960, 95290172, 6525948, 81853704, 96193415 +76540481, 92604458, 61859143, 32161669, 37957788, 83948335, 64055960, 17037369, 17385531, 99224392, 62693428, 62357986, 47792865, 16595436, 30787683, 12348118, 44245960, 97783876, 91281584, 33336148, 38510840, 43357947, 54762643, 88653118, 4515343, 75407347, 65275241, 42199455, 52293995, 34698463, 62740044, 92998591, 47708850, 43376279, 1431742, 44060493, 30163921, 44550764, 93566986, 40677414, 90310261, 26493119, 18001617, 97561745, 19891772, 21533347, 93359396, 72019362, 10961421, 55602660, 26114953, 42237907, 9860195, 62232211, 3773993, 89046466, 83789391, 88130087, 13348726, 33553959, 86543538, 40197395, 8913721, 42307449, 36845587, 62936963, 38365584, 9188443, 41442762, 95957797, 52060076, 57359924, 4119747, 92692978, 27665211, 33935899, 34497327, 74452589, 68939068, 21397057, 30771409, 7502255, 34946859, 10453030, 97057187, 54987042, 44983451, 91255408, 9398733, 56515456, 43506672, 9599614, 92803766, 8115266, 70800879, 35996293, 14947650, 91727510, 33797252, 57241521, 86001008, 3233084, 25842078, 72274002, 22450468, 26998766, 28928175, 9623492, 53842979, 96735716, 82427263, 36580610, 64848072, 15075176, 97011160, 81677380, 62430984, 85695762, 38658347, 30395570, 15064655, 12571310, 67031644, 38341669, 92867155, 29932657, 16097038, 4204661, 3233569, 26275734, 72793444, 76671482, 76703609, 26063929, 46851987, 37739481, 61741594, 77620120, 57163802, 16099750, 44889423, 71469330, 41245325, 23740167, 7066775, 51426311, 4508700, 68644627, 36135, 7517032, 24915585, 28851716, 27625735, 82532312, 18699206, 4069912, 50668599, 39201414, 1375023, 37435892, 24953498, 55247455, 78300864, 83083131, 3294781, 20140249, 87710366, 89699445, 15536795, 89078848, 96709982, 79821897, 66832478, 669105, 6521313, 19457589, 36753250, 45049308, 95251277, 71083565, 92530431, 61859581, 5267545, 83210802, 59109400, 83651211, 41092172, 4095116, 26102057, 2891150, 47361209, 90013093, 84840549, 58749, 28796059, 79657802, 508198, 57248122, 9860968, 60106217, 88444207, 749283, 36189527, 20885148, 53802686, 32250045, 27325428, 34493392, 94539824, 54663246, 22942635, 70004753, 19486173, 5640302, 39801351, 23134715, 6111563, 43933006, 7300047, 39171895, 874791, 79880247, 84293052, 73617245, 4798568, 92541302, 29188588, 89214616, 59177718, 32426752, 63967300, 66885828, 5073754, 71522968, 37620363, 63152504, 6505939, 8791066, 89811711, 71316369, 38022834, 71333116, 74357852, 49641577, 24314885, 65047700, 19272365, 87720882, 68204242, 83302115, 94711842, 12024238, 99524975, 40781854, 98948034, 824872, 62496012, 59501487, 66663942, 96193415, 37166608, 33565483, 45848907, 50702367, 1250437, 22129328, 14349098, 17727650, 46540998, 96318094, 16380211, 61897582, 86821229, 8696647, 94911072, 36812683, 57020481, 24733232, 23848565, 19376156, 38645117, 23432750, 51588015, 17764950, 9058407, 97940276, 59371804, 97281847, 69641513, 88698958, 96852321, 17957593, 71965942, 49882705, 63372756, 6221471, 48395186, 58224549, 73235980, 93270084, 3880712, 3183975, 23569917, 20568363, 38494874, 1936762, 59981773, 36468541, 61271144, 84406788, 10358899, 49236559, 51472275, 70036438, 93515664, 38008118, 14723410, 7919588, 15015906, 21993752, 22879907, 38009615, 54263880, 1569515, 31733363, 76434144, 30090481, 62552524, 52613508, 30569392, 66116458, 63059024, 7423788, 43045786, 98739783, 62803858, 75777973, 33123618, 21289531, 48088883, 66271566, 11543098, 13468268, 54427233, 84904436, 75986488, 39847321, 29635537, 33249630, 77694700, 74724075, 81172706, 70367851, 93562257, 79136082, 6808825, 70541760, 25636669, 17146629, 58751351, 39373729, 34044787, 33237508, 5970607, 55143724, 85711894, 20122224, 90654836, 79806380, 49271185, 7687278, 1204161, 86798033, 72777973, 72373496, 29994197, 74441448, 60955663, 67030811, 99226875, 9603598, 24168411, 83269727, 62428472, 84187166, 37192445, 45996863, 6986898, 40686254, 99515901, 82779622, 32151165, 99971982, 65851721, 73031054, 50806615, 53888755, 2717150, 11365791, 68694897, 67124282, 65017137, 44627776, 16387575, 18833224, 74614639, 32274392, 45075471, 79191827, 31904591, 68824981, 15148031, 36139942, 35512853, 99549373, 4806458, 45407418, 92787493, 48673079, 83533741, 27411561, 66137019, 80316608, 45790169, 45667668, 90272749, 44473167, 8634541, 29510992, 69579137, 58208470, 13231279, 44348328, 83133790, 76315420, 16445503, 40865610, 28550822, 75153252, 34698428, 7104732, 66250369, 26776922, 42782652, 91802888, 95290172, 36933038, 26397786, 30366150, 9829782, 6497830, 44915235, 67451935, 59405277, 6157724, 65454636, 90061527, 51590803, 89996536, 59614746, 98130363, 20681921, 65338021, 45794415, 73392814, 69605283, 20486294, 35456853, 64087743, 33061250, 37224844, 5482538, 45990383, 44784505, 78561158, 92554120, 20765474, 77301523, 37675718, 89804152, 17058722, 30543215, 48260151, 16019925, 80014588, 55615885, 84002370, 72614359, 78909561, 7955293, 19101477, 1239555, 91957544, 6793819, 98371444, 71300104, 69786756, 42073124, 112651, 22766820, 73109997, 36808486, 83747892, 55960386, 4099191, 56473732, 7011964, 33699435, 99297164, 27041967, 2607799, 91664334, 78766358, 7182642, 14045383, 68316156, 71920426, 76960303, 14363867, 29401781, 85571389, 40356877, 16567550, 47298834, 20645197, 67474219, 77284619, 62762857, 54606384, 36780454, 8128637, 77413012, 84127901, 57803235, 83155442, 48774913, 5832946, 71657078, 54058788, 37891451, 247198, 34432810, 72278539, 69697787, 72358170, 44846932, 93057697, 10366309, 60176618, 72725103, 4787945, 20642888, 85242963, 79942022, 4434662, 45617087, 33431960, 94090109, 3509435, 21001913, 27185644, 51715482, 68102437, 88251446, 75128745, 3487592, 11923835, 65271999, 45237957, 36312813, 28787861, 33895336, 96420429, 74137926, 78549759, 75052463, 8651647, 98943869, 66903004, 91831487, 68816413, 89419466, 75229462, 70782102, 26392416, 54014062, 37280276, 28734791, 4978543, 11161731, 15948937, 72495719, 17365188, 34236719, 17857111, 20836893, 81853704, 3633375, 32699744, 53547802, 24058273, 6153406, 89499542, 80246713, 15902805, 12416768, 86460488, 44177011, 61373987, 93790285, 75135448, 24826575, 77377183, 9575954, 69255765, 86301513, 95395112, 61728685, 82886381, 29959549, 1022677, 37183543, 42967683, 94360702, 60102965, 55189057, 82052050, 85117092, 99729357, 51507425, 30653863, 26507214, 80555751, 36505482, 30463802, 48892201, 41380093, 72738685, 48658605, 51047803, 54868730, 99690194, 67281495, 99965001, 18783702, 86118021, 50007421, 59329511, 77787724, 88047921, 81774825, 30811010, 54232247, 69355476, 74110882, 66428795, 92033260, 65074535, 44479073, 56153345, 51466049, 18806856, 8733068, 73021291, 61982238, 56424103, 70420215, 77072625, 57658654, 11757872, 77187825, 38256849, 33201905, 99333375, 39986008, 56955985, 57240218, 17386996, 52261574, 76330843, 47738185, 59318837, 22721500, 74743862, 45306070, 62452034, 99125126, 88904910, 77300457, 54517921, 99917599, 82339363, 13470059, 26139110, 75543508, 93053405, 49598724, 8373289, 78218845, 57961282, 98462867, 33304202, 35092039, 90457870, 78785507, 8055981, 66667729, 54199160, 7229550, 30694952, 33628349, 51315460, 42947632, 14093520, 95010552, 1788101, 67084351, 47893286, 4199704, 14626618, 15163258, 32159704, 26734892, 65715134, 70596786, 55770687, 21070110, 73222868, 21673260, 8807940, 40984766, 68110330, 99604946, 24619760, 79738755, 48360198, 65880522, 68000591, 61928316, 3235882, 92071990, 8129978, 10649306, 62051033, 60581278, 77312810, 82979980, 36685023, 63506281, 61815223, 415901, 32590267, 92692283, 15535065, 45428665, 85116755, 18411915, 12303248, 18663507, 51464002, 29466406, 29834512, 41481685, 77898274, 82651278, 63015256, 23194618, 72238278, 45919976, 60393039, 56484900, 14731700, 6819644, 66319530, 5822202, 36930650, 55669657, 41092102, 26744917, 36396314, 82726008, 46870723, 29819940, 45995325, 40152546, 94595668, 67793644, 168541, 4985896, 82897371, 48893685, 2917920, 89637706, 9886593, 61141874, 39553046, 83368048, 30139692, 13173644, 81855445, 92215320, 19939935, 87160386, 99658235, 61623915, 91141395, 44664587, 68702632, 66045587, 2208785, 79922758, 81805959, 85023028, 55470718, 21919959, 91990218, 57393458, 22200378, 77272628, 68128438, 64098930, 69848388, 24591705, 53393358, 19898053, 82178706, 30891921, 38061439, 70727211, 99861373, 40027975, 76170907, 78589145, 98648327, 68875490, 42644903, 73168565, 17016156, 61263068, 569864, 53666583, 98653983, 58180653, 51149804, 66322959, 35192533, 49597667, 73124510, 37560164, 23110625, 34295794, 9175338, 70372191, 43322743, 27187213, 29029316, 7646095, 56461322, 16971929, 18131876, 16054533, 97379790, 16424199, 72732205, 37659250, 16684829, 50188404, 99021067, 73786944, 4668450, 20002147, 72357096, 79322415, 31727379, 67513640, 17081350, 4064751, 56531125, 29065964, 526217, 43501211, 91240048, 76825057, 22405120, 22435353, 68041839, 29516592, 80251430, 17539625, 26863229, 53084256, 30998561, 60430369, 62115552, 9259676, 14326617, 83150534, 63628376, 30764367, 6525948, 20867149, 59197747, 22108665, 47213183, 13819358, 31161687, 47887470, 89217461, 93933709, 2331773, 2204165, 37501808, 18504197, 47090124, 96726697, 59910624, 32058615, 90004325, 40781449, 9257405, 95726235, 31491938, 43152977, 30218878, 50567636, 12664567, 26292919, 53632373, 40534591, 94076128, 97641116, 90090964, 321665, 65038678, 44842615, 84349107, 90158785, 16405341, 84684495, 17068582, 8099994, 66611759, 6038457, 78602717, 13862149, 48675329, 17976208, 92398073, 10309525, 84166196, 61712234, 67227442, 78884452, 53648154, 81898046, 44844121, 62490109, 176257, 64602895, 55850790, 44847298, 7685448, 42692881, 52204879, 91938191, 86242799, 45381876, 6871053, 10597197, 92353856, 5111370, 26664538, 82327024, 69136837, 94516935, 49328608, 18466635, 22997281, 1197320, 87598888, 55753905, 64157906, 65081429, 96906410, 22113133, 31126490, 10264691, 11581181, 14220886, 44481640, 75820087, 17894977, 95581843, 81274566, 8729146, 4091162, 86022504, 61960400, 3088684, 34667286 +30998561, 27665211, 49271185, 2717150, 78785507, 84166196, 99971982, 82339363, 10309525, 62552524, 20122224, 73786944, 22721500, 68694897, 49598724, 75820087, 66250369, 54199160, 26392416, 30764367, 54663246, 81898046, 77377183, 66116458, 17016156, 89804152, 84002370, 1239555, 49597667, 18504197, 52204879, 97379790, 99125126, 65038678, 36139942, 76825057, 4787945, 33431960, 62357986, 66045587, 13862149, 17976208, 9860195, 77272628, 64098930, 6525948, 64602895, 5482538, 78589145, 20765474, 75777973, 37183543, 16097038, 94360702, 4204661, 55615885, 39847321, 43322743, 2204165, 56461322, 22113133, 91664334, 16054533, 30811010, 72732205, 18699206, 77284619, 5822202, 62762857, 33565483, 84187166, 56955985, 12664567, 30771409, 96318094, 16380211, 94595668, 2917920, 45306070, 29065964, 59109400, 83651211, 4434662, 13173644, 16445503, 88251446, 58224549, 45237957, 66667729, 36312813, 79657802, 62115552, 66903004, 68816413, 55470718, 14326617, 84406788, 27325428, 22997281, 14626618, 89996536, 69848388, 7919588, 65338021, 45794415, 20486294, 1569515, 55753905, 19486173, 68000591, 52613508, 47213183, 62051033, 61728685, 47887470, 61263068, 43933006, 7300047, 26275734, 42199455, 66271566, 11543098, 80014588, 45428665, 9175338, 29029316, 18783702, 71333116, 36135, 24915585, 40781449, 24314885, 92033260, 76960303, 14363867, 8733068, 83083131, 86242799, 50702367, 17081350, 82726008, 5832946, 54058788, 32151165, 66832478, 72278539, 74743862, 69697787, 60176618, 99549373, 70800879, 94516935, 45617087, 19891772, 47361209, 81855445, 35092039, 43357947, 84840549, 28928175, 66611759, 49328608, 4515343, 60430369, 74137926, 23569917, 75052463, 85023028, 60106217, 47792865, 75229462, 26397786, 42237907, 28734791, 90061527, 61712234, 24058273, 55770687, 44844121, 86460488, 22108665, 45990383, 9575954, 63059024, 68875490, 42644903, 31161687, 569864, 48088883, 58180653, 85117092, 99729357, 48260151, 30653863, 62740044, 7685448, 89214616, 92604458, 69786756, 42073124, 12348118, 71522968, 37501808, 47090124, 71316369, 59329511, 16971929, 27041967, 18131876, 78766358, 55143724, 49641577, 77898274, 33935899, 50668599, 72238278, 9257405, 10264691, 16567550, 1431742, 67474219, 20140249, 70420215, 38256849, 24168411, 50567636, 89078848, 99515901, 40152546, 97641116, 30163921, 67124282, 88904910, 45049308, 18833224, 43501211, 54517921, 31904591, 93566986, 93057697, 40677414, 51588015, 4806458, 92787493, 27411561, 29516592, 3509435, 26863229, 90013093, 10961421, 48395186, 44664587, 3088684, 55602660, 83150534, 36580610, 22200378, 36189527, 67451935, 38008118, 1197320, 53393358, 8807940, 40984766, 20867149, 87598888, 31733363, 79738755, 93790285, 76170907, 12571310, 75135448, 88130087, 76434144, 67031644, 98648327, 92071990, 69255765, 5640302, 39801351, 33553959, 60102965, 53666583, 34698463, 63506281, 79880247, 26507214, 37739481, 35192533, 32590267, 37560164, 15535065, 75986488, 85116755, 96906410, 70372191, 59177718, 70367851, 18663507, 7646095, 79136082, 25636669, 41442762, 43376279, 14045383, 16424199, 32058615, 27625735, 91938191, 99021067, 40356877, 18806856, 47298834, 31491938, 60955663, 40781854, 67030811, 73021291, 72357096, 824872, 34497327, 77187825, 41092102, 86022504, 36780454, 33201905, 89699445, 45381876, 15536795, 6986898, 17386996, 46870723, 4064751, 47738185, 10453030, 247198, 67793644, 168541, 669105, 44983451, 4985896, 56515456, 5111370, 56531125, 89637706, 94911072, 526217, 74614639, 39553046, 99917599, 9599614, 10366309, 5267545, 19376156, 69136837, 17764950, 22405120, 16405341, 4095116, 91727510, 22435353, 8373289, 44348328, 84684495, 96852321, 72274002, 76315420, 51715482, 49882705, 61623915, 75153252, 73235980, 96735716, 81805959, 9259676, 20568363, 91831487, 89419466, 95290172, 30366150, 63628376, 37280276, 6157724, 20885148, 53802686, 15948937, 20836893, 65715134, 53547802, 62490109, 61373987, 70727211, 13348726, 30090481, 8129978, 7423788, 65275241, 73168565, 82886381, 33123618, 89217461, 93933709, 3233569, 17058722, 30543215, 51149804, 8913721, 51507425, 84904436, 2331773, 44847298, 36845587, 61815223, 61741594, 16099750, 51047803, 54868730, 12303248, 42692881, 77694700, 37620363, 93562257, 73109997, 83747892, 67281495, 99965001, 86118021, 50007421, 56473732, 58751351, 39373729, 38022834, 29466406, 2607799, 7182642, 59910624, 81774825, 7517032, 69355476, 74110882, 82532312, 79806380, 65074535, 63015256, 68204242, 95726235, 83302115, 60393039, 43152977, 14731700, 20002147, 99226875, 66663942, 61982238, 54606384, 37166608, 99333375, 17037369, 26744917, 67513640, 37192445, 83155442, 17385531, 52261574, 48774913, 21397057, 71657078, 10597197, 86821229, 6521313, 90090964, 9886593, 16387575, 61960400, 44481640, 15148031, 82327024, 83210802, 38645117, 8115266, 35512853, 9058407, 26102057, 97940276, 80316608, 33336148, 80251430, 17539625, 21533347, 13231279, 17068582, 8099994, 22450468, 87160386, 99658235, 75128745, 3487592, 28787861, 42782652, 79922758, 6038457, 59981773, 21919959, 88444207, 54014062, 47893286, 48675329, 59614746, 34236719, 98130363, 26734892, 67227442, 20681921, 16595436, 3633375, 6153406, 78884452, 85695762, 22879907, 62232211, 30891921, 15902805, 68110330, 12416768, 64087743, 30395570, 54263880, 70004753, 176257, 99861373, 59197747, 64157906, 78561158, 43045786, 10649306, 92867155, 55850790, 82979980, 23134715, 86543538, 6111563, 39171895, 72793444, 98653983, 82052050, 40197395, 36685023, 54427233, 65081429, 46851987, 80555751, 78909561, 19101477, 415901, 91957544, 92692283, 57163802, 18411915, 63967300, 22766820, 5073754, 81172706, 51464002, 41245325, 23740167, 7066775, 55960386, 4099191, 4508700, 29834512, 96726697, 31126490, 88047921, 68316156, 4091162, 90654836, 7687278, 1204161, 19272365, 66428795, 23194618, 29401781, 72777973, 51466049, 56484900, 37435892, 55247455, 77072625, 57658654, 9603598, 11757872, 36930650, 79322415, 87710366, 68939068, 36396314, 53632373, 57240218, 57803235, 1250437, 76330843, 14349098, 34946859, 59318837, 6871053, 94076128, 97057187, 65851721, 44627776, 36753250, 77300457, 95251277, 71083565, 45075471, 79191827, 61859581, 23848565, 84349107, 94090109, 78218845, 29510992, 69641513, 88698958, 3233084, 27185644, 17894977, 90457870, 40865610, 54762643, 88653118, 3880712, 26776922, 508198, 57248122, 7229550, 78602717, 78549759, 30694952, 8651647, 1936762, 81274566, 61271144, 36933038, 91990218, 57393458, 44915235, 59405277, 92398073, 32250045, 51590803, 72495719, 15163258, 32159704, 17857111, 15015906, 19898053, 73392814, 38658347, 82178706, 21673260, 38009615, 38061439, 33061250, 37224844, 8729146, 24826575, 61928316, 44784505, 86301513, 13819358, 29932657, 66322959, 73617245, 83948335, 73124510, 23110625, 92541302, 34295794, 29635537, 48658605, 71300104, 92998591, 33249630, 66885828, 47708850, 6505939, 70541760, 33699435, 17146629, 99297164, 34044787, 89811711, 68644627, 85711894, 82651278, 61859143, 90004325, 28851716, 54232247, 71920426, 86798033, 44479073, 16684829, 72373496, 85571389, 29994197, 12024238, 4668450, 97783876, 74452589, 55669657, 11581181, 39986008, 45996863, 26292919, 77413012, 40686254, 44060493, 46540998, 79821897, 73031054, 50806615, 14220886, 9398733, 62693428, 72358170, 61141874, 36812683, 43506672, 32274392, 24733232, 26664538, 92803766, 90158785, 23432750, 85242963, 35996293, 14947650, 2891150, 75543508, 33797252, 26493119, 90272749, 30139692, 44473167, 57241521, 69579137, 58208470, 92215320, 86001008, 19939935, 25842078, 93359396, 34698428, 6221471, 68702632, 33895336, 2208785, 3183975, 36468541, 67084351, 10358899, 49236559, 64848072, 749283, 37957788, 4978543, 65454636, 97011160, 68128438, 14723410, 81853704, 32699744, 89499542, 53648154, 99604946, 3773993, 30787683, 40027975, 15064655, 65880522, 92554120, 62803858, 60581278, 21289531, 55189057, 52293995, 42307449, 16019925, 13468268, 72614359, 62936963, 30463802, 48892201, 29188588, 6793819, 74724075, 71469330, 36808486, 51426311, 7011964, 33237508, 5970607, 77787724, 74357852, 41481685, 37659250, 65047700, 4069912, 50188404, 1375023, 99524975, 74441448, 78300864, 66319530, 62496012, 8128637, 84127901, 99224392, 45995325, 37891451, 34432810, 61897582, 54987042, 44550764, 92353856, 48893685, 44846932, 19457589, 57020481, 83368048, 92530431, 32161669, 91240048, 45407418, 20642888, 48673079, 76540481, 83533741, 26139110, 68041839, 45667668, 18001617, 71965942, 33304202, 11923835, 91141395, 7104732, 93270084, 95581843, 82427263, 33628349, 98943869, 42947632, 95010552, 1788101, 4199704, 6497830, 93515664, 34667286, 24591705, 70596786, 35456853, 22942635, 89046466, 83789391, 95395112, 38341669, 77301523, 77312810, 42967683, 76671482, 76703609, 874791, 36505482, 7955293, 38365584, 41380093, 77620120, 9188443, 98371444, 32426752, 44889423, 63152504, 8791066, 57359924, 4119747, 92692978, 56153345, 87720882, 45919976, 24953498, 20645197, 3294781, 59501487, 56424103, 83269727, 31727379, 45848907, 40534591, 53888755, 11365791, 82897371, 321665, 65017137, 91281584, 44842615, 90310261, 72725103, 41092172, 79942022, 66137019, 59371804, 97281847, 38510840, 72019362, 26998766, 68102437, 28550822, 53084256, 53842979, 96420429, 91802888, 70782102, 15075176, 18466635, 17365188, 81677380, 34493392, 62430984, 21070110, 24619760, 98739783, 4798568, 112651, 27187213, 44245960, 52060076, 64055960, 6819644, 98948034, 30218878, 17727650, 82779622, 29819940, 91255408, 8696647, 62452034, 13470059, 97561745, 98462867, 17957593, 21001913, 63372756, 65271999, 8055981, 28796059, 26114953, 51315460, 38494874, 9860968, 9829782, 94539824, 69605283, 80246713, 44177011, 48360198, 75407347, 30569392, 37675718, 26063929, 84293052, 99690194, 6808825, 95957797, 39201414, 62428472, 22129328, 68824981, 93053405, 83133790, 9623492, 70036438, 11161731, 29959549, 72738685, 96193415, 96709982, 8634541, 57961282, 58749, 3235882, 1022677, 94711842, 7502255, 45790169, 14093520, 21993752, 73222868, 51472275 +168541, 83789391, 86242799, 1197320, 24058273, 55189057, 29466406, 66319530, 92353856, 43501211, 90310261, 19891772, 88444207, 84166196, 55770687, 48360198, 38341669, 60581278, 47887470, 84293052, 29635537, 91938191, 6819644, 96709982, 36753250, 84349107, 48673079, 22435353, 71965942, 90457870, 7229550, 81805959, 14093520, 84406788, 20486294, 62232211, 73168565, 60102965, 89804152, 76703609, 1239555, 91957544, 51047803, 29029316, 52204879, 43376279, 52060076, 4119747, 69355476, 63015256, 33201905, 17037369, 67793644, 30163921, 669105, 99125126, 32274392, 19376156, 4806458, 92787493, 79942022, 49598724, 30139692, 78218845, 93359396, 91802888, 30694952, 98943869, 75229462, 57393458, 9829782, 22942635, 12416768, 38061439, 54263880, 44177011, 93790285, 8729146, 24826575, 6111563, 13468268, 44847298, 62936963, 61741594, 48892201, 18411915, 71300104, 34044787, 4091162, 72732205, 7687278, 50188404, 1375023, 99524975, 40781854, 73021291, 37192445, 8128637, 46870723, 97057187, 45049308, 18833224, 79191827, 8115266, 72725103, 16405341, 94090109, 75820087, 26863229, 83133790, 76315420, 8099994, 66611759, 58224549, 66667729, 30998561, 96735716, 20568363, 66903004, 95010552, 67451935, 65454636, 11161731, 15948937, 51590803, 20681921, 176257, 40027975, 64157906, 52613508, 98739783, 62051033, 75777973, 23134715, 4204661, 53666583, 17058722, 66271566, 55615885, 2331773, 72614359, 38365584, 70372191, 47090124, 71316369, 68644627, 31126490, 61859143, 37659250, 4069912, 39201414, 94711842, 66663942, 79322415, 50567636, 45381876, 26292919, 53632373, 17386996, 76330843, 17727650, 48774913, 5832946, 40534591, 6871053, 73031054, 72278539, 2717150, 56531125, 321665, 94911072, 61960400, 91240048, 90158785, 35512853, 99549373, 9058407, 29516592, 57241521, 92215320, 33304202, 17068582, 7104732, 36312813, 3880712, 66045587, 2208785, 4515343, 38494874, 1936762, 91831487, 81274566, 68816413, 26397786, 4199704, 28734791, 10309525, 18466635, 72495719, 81677380, 59614746, 61712234, 78884452, 30891921, 99604946, 89046466, 19486173, 64602895, 5482538, 76434144, 13348726, 22108665, 47213183, 30569392, 13819358, 92554120, 92867155, 55850790, 82886381, 77301523, 82052050, 48260151, 51507425, 80014588, 80555751, 35192533, 30463802, 37560164, 34295794, 29188588, 96906410, 22766820, 5073754, 74724075, 44245960, 2204165, 23740167, 50007421, 56473732, 59329511, 38022834, 16971929, 27041967, 91664334, 74357852, 78766358, 85711894, 41481685, 68316156, 30811010, 27625735, 33935899, 19272365, 65074535, 50668599, 60393039, 64055960, 4668450, 20002147, 30218878, 56424103, 11581181, 67513640, 57803235, 17081350, 29819940, 79821897, 34432810, 66832478, 74743862, 11365791, 69697787, 65017137, 44846932, 44627776, 36812683, 526217, 43506672, 39553046, 92530431, 31904591, 36139942, 59109400, 60176618, 22405120, 26102057, 85242963, 80316608, 80251430, 17539625, 21533347, 34698428, 66250369, 508198, 6038457, 23569917, 60106217, 59981773, 70782102, 37280276, 44915235, 17976208, 92398073, 17365188, 62430984, 32159704, 89996536, 7919588, 24591705, 67227442, 3633375, 70596786, 53648154, 81898046, 62490109, 40984766, 38009615, 33061250, 70727211, 65880522, 67031644, 98648327, 68000591, 77377183, 61928316, 78561158, 92071990, 39801351, 20765474, 29959549, 94360702, 48088883, 72793444, 98653983, 85117092, 66322959, 26507214, 36505482, 73124510, 92541302, 15535065, 98371444, 48658605, 54868730, 43322743, 42692881, 77694700, 12348118, 51464002, 83747892, 6808825, 55960386, 99965001, 7011964, 33699435, 29834512, 18131876, 90004325, 90654836, 82532312, 76960303, 16684829, 68204242, 45919976, 51466049, 83302115, 12024238, 31491938, 24953498, 20645197, 67474219, 3294781, 72357096, 824872, 41092102, 45848907, 36396314, 57240218, 83155442, 14349098, 96318094, 59318837, 247198, 97641116, 14220886, 90090964, 82897371, 68694897, 9886593, 82339363, 44481640, 32161669, 82327024, 44842615, 23848565, 5267545, 83210802, 45407418, 94516935, 93053405, 68041839, 18001617, 58208470, 57961282, 84684495, 25842078, 27185644, 72274002, 28550822, 53084256, 3487592, 11923835, 6221471, 73235980, 9623492, 28796059, 54199160, 49328608, 96420429, 60430369, 62115552, 89419466, 47792865, 36933038, 37957788, 34667286, 14626618, 15163258, 14723410, 98130363, 65715134, 32699744, 65338021, 53393358, 89499542, 85695762, 38658347, 44844121, 80246713, 3773993, 30395570, 24619760, 30787683, 37224844, 55753905, 88130087, 78589145, 9575954, 3235882, 86301513, 63059024, 7423788, 43045786, 68875490, 31161687, 1022677, 569864, 33553959, 16097038, 39171895, 76671482, 51149804, 52293995, 874791, 84002370, 61815223, 23110625, 75986488, 39847321, 6793819, 9175338, 89214616, 99690194, 42073124, 63967300, 66885828, 27187213, 81172706, 7646095, 73109997, 63152504, 79136082, 67281495, 18783702, 41442762, 58751351, 22113133, 5970607, 59910624, 49641577, 7517032, 77898274, 57359924, 92692978, 56153345, 87720882, 29401781, 72777973, 8733068, 47298834, 43152977, 83083131, 14731700, 67030811, 59501487, 5822202, 61982238, 57658654, 9603598, 83269727, 31727379, 56955985, 1250437, 82726008, 47738185, 44060493, 7502255, 46540998, 32151165, 94595668, 65851721, 50806615, 44983451, 2917920, 72358170, 19457589, 71083565, 15148031, 9599614, 10366309, 38645117, 76825057, 13470059, 23432750, 83651211, 35996293, 14947650, 91727510, 75543508, 4434662, 33797252, 97281847, 47361209, 96852321, 19939935, 21001913, 90013093, 17894977, 35092039, 43357947, 87160386, 72019362, 99658235, 75153252, 91141395, 63372756, 65271999, 8055981, 93270084, 95581843, 53842979, 57248122, 26114953, 33628349, 51315460, 9860968, 42947632, 21919959, 95290172, 1788101, 67084351, 51472275, 59405277, 20885148, 22997281, 94539824, 6525948, 69848388, 17857111, 16595436, 45794415, 73392814, 21993752, 21070110, 70004753, 87598888, 31733363, 8129978, 10649306, 95395112, 42644903, 61728685, 21289531, 17016156, 61263068, 82979980, 37183543, 93933709, 43933006, 58180653, 16019925, 73617245, 84904436, 46851987, 78909561, 37739481, 32426752, 92998591, 33249630, 70367851, 93562257, 36808486, 70541760, 7066775, 25636669, 17146629, 71333116, 2607799, 24915585, 74110882, 79806380, 49271185, 65047700, 86798033, 92033260, 29994197, 18806856, 37435892, 20140249, 70420215, 36930650, 74452589, 77187825, 87710366, 99333375, 68939068, 39986008, 89078848, 84127901, 6986898, 17385531, 22129328, 21397057, 37891451, 94076128, 10597197, 91255408, 6521313, 8696647, 62693428, 88904910, 16387575, 77300457, 45075471, 93566986, 24733232, 26664538, 69136837, 92803766, 4787945, 51588015, 26139110, 59371804, 33336148, 45617087, 44473167, 33431960, 69579137, 81855445, 16445503, 84840549, 26998766, 78785507, 68102437, 75128745, 48395186, 62357986, 45237957, 44664587, 68702632, 28787861, 79657802, 42782652, 82427263, 9259676, 75052463, 85023028, 83150534, 36468541, 26392416, 42237907, 91990218, 63628376, 6497830, 15075176, 9860195, 32250045, 27325428, 90061527, 97011160, 34493392, 34236719, 38008118, 64098930, 20836893, 15015906, 69605283, 82178706, 21673260, 68110330, 61373987, 20867149, 79738755, 12571310, 62552524, 45990383, 44784505, 75407347, 62803858, 42967683, 86543538, 7300047, 3233569, 26275734, 54427233, 79880247, 65081429, 19101477, 83948335, 92692283, 72738685, 9188443, 16099750, 7685448, 59177718, 112651, 44889423, 18504197, 51426311, 33237508, 88047921, 16424199, 20122224, 40781449, 71920426, 66428795, 23194618, 85571389, 73786944, 40356877, 10264691, 95726235, 74441448, 77284619, 98948034, 62496012, 99226875, 34497327, 77072625, 11757872, 55669657, 86022504, 33565483, 45996863, 12664567, 40686254, 99515901, 52261574, 82779622, 10453030, 40152546, 61897582, 54987042, 86821229, 4985896, 44550764, 45306070, 67124282, 56515456, 89637706, 61141874, 29065964, 91281584, 57020481, 65038678, 54517921, 99917599, 68824981, 61859581, 40677414, 20642888, 4095116, 70800879, 83533741, 97940276, 26493119, 90272749, 8373289, 8634541, 86001008, 3233084, 38510840, 58749, 10961421, 3088684, 26776922, 79922758, 55602660, 78602717, 55470718, 61271144, 13862149, 10358899, 47893286, 749283, 36189527, 4978543, 77272628, 54663246, 26734892, 81853704, 53547802, 6153406, 19898053, 22879907, 15902805, 86460488, 15064655, 76170907, 75135448, 65275241, 29932657, 77312810, 89217461, 42199455, 11543098, 8913721, 34698463, 26063929, 63506281, 30653863, 62740044, 4798568, 49597667, 92604458, 12303248, 71522968, 71469330, 37620363, 41245325, 8791066, 89811711, 77787724, 96726697, 14045383, 97379790, 32058615, 81774825, 82651278, 54232247, 27665211, 24314885, 18699206, 14363867, 99021067, 72238278, 55247455, 1431742, 96193415, 54606384, 24168411, 62428472, 84187166, 99224392, 4064751, 71657078, 34946859, 54058788, 45995325, 16380211, 22721500, 48893685, 9398733, 5111370, 83368048, 93057697, 41092172, 2891150, 45790169, 45667668, 3509435, 44348328, 98462867, 17957593, 22450468, 88251446, 33895336, 74137926, 14326617, 30366150, 64848072, 53802686, 68128438, 30764367, 73222868, 64087743, 30090481, 69255765, 66116458, 5640302, 33123618, 37675718, 36685023, 42307449, 36845587, 7955293, 41380093, 77620120, 85116755, 47708850, 18663507, 6505939, 99297164, 39373729, 55143724, 1204161, 9257405, 16567550, 78300864, 89699445, 15536795, 50702367, 95251277, 17764950, 27411561, 66137019, 69641513, 51715482, 28928175, 40865610, 3183975, 8651647, 93515664, 6157724, 8807940, 99861373, 59197747, 40197395, 99729357, 415901, 32590267, 57163802, 37501808, 86118021, 4099191, 95957797, 16054533, 28851716, 44479073, 72373496, 56484900, 60955663, 62762857, 97783876, 38256849, 36780454, 77413012, 53888755, 74614639, 97561745, 13173644, 29510992, 49882705, 61623915, 54762643, 78549759, 36580610, 54014062, 22200378, 48675329, 70036438, 35456853, 1569515, 30543215, 69786756, 56461322, 36135, 30771409, 76540481, 88698958, 49236559, 45428665, 4508700, 26744917, 99971982, 13231279, 88653118, 37166608, 7182642, 62452034 +17764950, 4515343, 11923835, 77787724, 4064751, 75543508, 76315420, 96420429, 24058273, 61373987, 37224844, 65275241, 75777973, 60581278, 75986488, 27187213, 20140249, 321665, 69641513, 43357947, 93270084, 47792865, 6497830, 20885148, 53547802, 76434144, 47213183, 98739783, 77312810, 4204661, 76703609, 32590267, 72738685, 4099191, 25636669, 8791066, 38022834, 74357852, 96726697, 14363867, 20002147, 57658654, 17727650, 168541, 67124282, 29065964, 18833224, 61960400, 39553046, 79191827, 10366309, 92803766, 72725103, 80316608, 71965942, 16445503, 84840549, 99658235, 3088684, 20568363, 8651647, 1788101, 67084351, 44915235, 67451935, 59405277, 10309525, 81853704, 65715134, 89499542, 53648154, 68110330, 89046466, 78561158, 43045786, 20765474, 21289531, 6111563, 40197395, 34698463, 874791, 84904436, 19101477, 96906410, 99690194, 70372191, 42073124, 66885828, 77898274, 74110882, 99021067, 39201414, 24953498, 74441448, 4668450, 26744917, 45996863, 8128637, 34946859, 79821897, 97057187, 82897371, 92353856, 99917599, 15148031, 26664538, 93057697, 90158785, 51588015, 70800879, 4434662, 29516592, 78218845, 58208470, 84684495, 38510840, 58224549, 9623492, 66667729, 28796059, 85023028, 89419466, 14093520, 75229462, 36468541, 30366150, 84406788, 37280276, 27325428, 14626618, 15163258, 34236719, 54663246, 1197320, 15015906, 16595436, 70596786, 44844121, 1569515, 99861373, 8729146, 44784505, 37675718, 58180653, 82052050, 30543215, 36685023, 35192533, 29635537, 57163802, 98371444, 92604458, 18663507, 7646095, 67281495, 58751351, 34044787, 22113133, 95957797, 2607799, 31126490, 14045383, 7517032, 49271185, 1204161, 65047700, 66428795, 65074535, 16684829, 72777973, 40356877, 45919976, 1375023, 40781854, 66319530, 67474219, 30218878, 824872, 61982238, 96193415, 62762857, 86022504, 11581181, 15536795, 46870723, 47738185, 7502255, 40534591, 96318094, 50806615, 30163921, 53888755, 54987042, 44983451, 22721500, 48893685, 56531125, 9886593, 45049308, 83368048, 31904591, 82327024, 19376156, 90310261, 59109400, 4787945, 45407418, 41092172, 83533741, 35996293, 59371804, 30139692, 8634541, 33431960, 3509435, 26863229, 8099994, 49882705, 40865610, 91141395, 34698428, 6221471, 88653118, 8055981, 68702632, 91802888, 23569917, 9829782, 4199704, 4978543, 15075176, 92398073, 62430984, 14723410, 55770687, 21070110, 30891921, 80246713, 33061250, 70004753, 30090481, 92071990, 82886381, 42967683, 98653983, 78909561, 30463802, 61741594, 415901, 77620120, 85116755, 9188443, 89214616, 54868730, 32426752, 71522968, 2204165, 93562257, 18504197, 5970607, 29466406, 18131876, 78766358, 16424199, 82651278, 29994197, 83302115, 77284619, 3294781, 59501487, 34497327, 9603598, 11757872, 36930650, 79322415, 24168411, 12664567, 26292919, 36396314, 84127901, 57240218, 40686254, 17081350, 17385531, 76330843, 21397057, 5832946, 30771409, 16380211, 10597197, 34432810, 97641116, 66832478, 91255408, 11365791, 44846932, 36812683, 19457589, 65038678, 32274392, 44481640, 24733232, 38645117, 26493119, 97281847, 44473167, 29510992, 69579137, 44348328, 72274002, 17068582, 90457870, 26998766, 68102437, 28550822, 75128745, 65271999, 44664587, 26776922, 82427263, 2208785, 79922758, 3183975, 74137926, 26114953, 1936762, 91831487, 59981773, 95010552, 42237907, 91990218, 57393458, 70036438, 94539824, 45794415, 69605283, 62490109, 8807940, 12416768, 176257, 20867149, 70727211, 31733363, 76170907, 19486173, 24826575, 65880522, 88130087, 98648327, 68000591, 3235882, 52613508, 7423788, 31161687, 73168565, 569864, 33553959, 93933709, 86543538, 60102965, 53666583, 66271566, 26063929, 2331773, 84002370, 4798568, 72614359, 62936963, 48892201, 1239555, 91957544, 73124510, 45428665, 18411915, 71300104, 69786756, 33249630, 29029316, 63152504, 70541760, 86118021, 33699435, 41442762, 68644627, 29834512, 81774825, 52060076, 24915585, 30811010, 27625735, 91938191, 76960303, 56153345, 87720882, 68204242, 8733068, 94711842, 56484900, 55247455, 78300864, 83083131, 66663942, 56424103, 83269727, 33565483, 50567636, 39986008, 50702367, 96709982, 52261574, 82726008, 46540998, 37891451, 67793644, 14220886, 2717150, 2917920, 89637706, 61141874, 526217, 43506672, 71083565, 68824981, 84349107, 83210802, 60176618, 99549373, 22405120, 4095116, 27411561, 94516935, 2891150, 90272749, 8373289, 80251430, 13173644, 47361209, 92215320, 13231279, 17957593, 83133790, 88251446, 61623915, 66611759, 36312813, 95581843, 54199160, 96735716, 60430369, 6038457, 78602717, 30694952, 51315460, 83150534, 63628376, 22200378, 749283, 93515664, 28734791, 6157724, 18466635, 34667286, 32250045, 51590803, 17365188, 34493392, 84166196, 6525948, 26734892, 61712234, 20681921, 19898053, 21993752, 21673260, 15902805, 93790285, 55753905, 9575954, 8129978, 63059024, 10649306, 42644903, 38341669, 55850790, 33123618, 82979980, 94360702, 11543098, 85117092, 66322959, 80014588, 84293052, 55615885, 41380093, 92541302, 29188588, 6793819, 48658605, 22766820, 74724075, 44889423, 37620363, 73109997, 18783702, 56461322, 39373729, 52204879, 97379790, 49641577, 41481685, 57359924, 90654836, 69355476, 27665211, 79806380, 7687278, 19272365, 50668599, 72238278, 72373496, 85571389, 47298834, 99524975, 37435892, 64055960, 14731700, 77187825, 38256849, 54606384, 99333375, 17037369, 56955985, 37192445, 44060493, 71657078, 54058788, 59318837, 65851721, 669105, 6521313, 90090964, 74743862, 69697787, 62693428, 94911072, 62452034, 99125126, 44627776, 57020481, 16405341, 79942022, 66137019, 91727510, 93053405, 68041839, 45667668, 97561745, 94090109, 17539625, 98462867, 96852321, 90013093, 17894977, 78785507, 58749, 63372756, 54762643, 62357986, 66250369, 33895336, 508198, 75052463, 38494874, 98943869, 66903004, 68816413, 55470718, 21919959, 95290172, 10358899, 51472275, 90061527, 77272628, 89996536, 38008118, 69848388, 98130363, 32699744, 6153406, 73392814, 73222868, 62232211, 22942635, 40984766, 86460488, 99604946, 24619760, 83789391, 79738755, 40027975, 12571310, 48360198, 64157906, 13348726, 62552524, 75407347, 86301513, 13819358, 92554120, 61263068, 39171895, 3233569, 26275734, 42199455, 55189057, 76671482, 52293995, 8913721, 48260151, 13468268, 54427233, 79880247, 30653863, 65081429, 26507214, 36505482, 37739481, 7955293, 83948335, 92692283, 92998591, 77694700, 44245960, 6505939, 6808825, 41245325, 23740167, 99965001, 99297164, 33237508, 89811711, 59329511, 43376279, 16054533, 68316156, 61859143, 20122224, 71920426, 24314885, 18699206, 33935899, 73786944, 10264691, 16567550, 18806856, 97783876, 74452589, 41092102, 37166608, 33201905, 89699445, 31727379, 89078848, 99515901, 22129328, 99224392, 82779622, 29819940, 32151165, 99971982, 73031054, 61897582, 86821229, 5111370, 65017137, 16387575, 45075471, 9599614, 23848565, 5267545, 91240048, 23432750, 9058407, 85242963, 26139110, 45790169, 33797252, 45617087, 57241521, 57961282, 75820087, 86001008, 3233084, 27185644, 93359396, 72019362, 10961421, 3880712, 49328608, 42782652, 7229550, 81805959, 78549759, 33628349, 60106217, 61271144, 36933038, 88444207, 26397786, 49236559, 36189527, 37957788, 53802686, 97011160, 81677380, 68128438, 30764367, 59614746, 65338021, 82178706, 38009615, 64087743, 38061439, 3773993, 44177011, 87598888, 59197747, 5482538, 78589145, 39801351, 62803858, 29932657, 17016156, 77301523, 89217461, 43933006, 48088883, 49597667, 37560164, 15535065, 51047803, 59177718, 112651, 43322743, 5073754, 71469330, 36808486, 51464002, 55960386, 50007421, 56473732, 7011964, 4508700, 71333116, 16971929, 27041967, 91664334, 7182642, 59910624, 4091162, 72732205, 37659250, 63015256, 44479073, 50188404, 29401781, 9257405, 43152977, 20645197, 1431742, 67030811, 98948034, 73021291, 72357096, 68939068, 67513640, 45848907, 1250437, 10453030, 6871053, 40152546, 44550764, 8696647, 9398733, 72358170, 91281584, 36753250, 77300457, 92530431, 82339363, 44842615, 36139942, 69136837, 13470059, 4806458, 92787493, 48673079, 76540481, 14947650, 81855445, 88698958, 25842078, 35092039, 22450468, 48395186, 30998561, 42947632, 14326617, 70782102, 26392416, 54014062, 17976208, 9860195, 65454636, 20836893, 53393358, 85695762, 38658347, 20486294, 22879907, 81898046, 30395570, 54263880, 30787683, 64602895, 77377183, 45990383, 5640302, 95395112, 47887470, 29959549, 1022677, 37183543, 16097038, 72793444, 17058722, 62740044, 44847298, 80555751, 23110625, 34295794, 39847321, 16099750, 7685448, 12348118, 81172706, 70367851, 7066775, 47090124, 17146629, 32058615, 40781449, 54232247, 82532312, 92033260, 4069912, 23194618, 95726235, 51466049, 60393039, 86242799, 60955663, 6819644, 5822202, 87710366, 77413012, 57803235, 17386996, 83155442, 48774913, 94076128, 94595668, 4985896, 45306070, 95251277, 54517921, 76825057, 8115266, 83651211, 49598724, 33336148, 19939935, 21001913, 28928175, 75153252, 79657802, 66045587, 57248122, 55602660, 9860968, 81274566, 36580610, 13862149, 64848072, 48675329, 15948937, 22997281, 32159704, 17857111, 67227442, 3633375, 78884452, 35456853, 68875490, 92867155, 23134715, 51149804, 16019925, 51507425, 73617245, 46851987, 36845587, 9175338, 63967300, 12303248, 42692881, 37501808, 79136082, 83747892, 51426311, 88047921, 90004325, 4119747, 86798033, 99226875, 55669657, 36780454, 84187166, 74614639, 32161669, 97940276, 22435353, 21533347, 53084256, 3487592, 7104732, 28787861, 53842979, 62115552, 47893286, 7919588, 75135448, 67031644, 22108665, 69255765, 30569392, 66116458, 62051033, 7300047, 89804152, 42307449, 63506281, 61815223, 38365584, 71316369, 55143724, 85711894, 36135, 31491938, 70420215, 62428472, 45995325, 247198, 68694897, 56515456, 43501211, 93566986, 61859581, 35512853, 26102057, 18001617, 33304202, 51715482, 73235980, 45237957, 9259676, 72495719, 64098930, 15064655, 61728685, 47708850, 92692978, 62496012, 77072625, 45381876, 88904910, 40677414, 20642888, 19891772, 11161731, 99729357, 28851716, 6986898, 72278539, 87160386, 24591705, 61928316, 12024238, 14349098, 53632373 +99690194, 5267545, 8099994, 72019362, 42237907, 18466635, 42644903, 37183543, 92033260, 97057187, 9886593, 92215320, 34493392, 34236719, 569864, 84904436, 30653863, 89214616, 7517032, 54232247, 16567550, 12024238, 50702367, 83155442, 90090964, 40677414, 35512853, 51588015, 4434662, 97281847, 57961282, 35092039, 3487592, 60430369, 78602717, 36468541, 65454636, 17365188, 15163258, 26734892, 81853704, 45794415, 8807940, 24619760, 76434144, 30569392, 874791, 7685448, 2204165, 36808486, 83747892, 55960386, 91664334, 36135, 72732205, 4119747, 23194618, 72777973, 62428472, 99224392, 47738185, 45995325, 99125126, 36753250, 74614639, 93057697, 8115266, 4806458, 83533741, 80316608, 33336148, 33431960, 69641513, 72274002, 22450468, 78785507, 58224549, 45237957, 55602660, 6038457, 20568363, 59981773, 83150534, 67451935, 54663246, 69848388, 80246713, 44177011, 15064655, 55753905, 3235882, 47213183, 20765474, 55850790, 61263068, 43933006, 7300047, 82052050, 76671482, 55615885, 80555751, 78909561, 19101477, 48892201, 29635537, 9175338, 32426752, 44889423, 56461322, 96726697, 78766358, 90004325, 90654836, 71920426, 49271185, 87720882, 85571389, 39201414, 40356877, 20645197, 97783876, 77187825, 56955985, 168541, 669105, 44983451, 11365791, 45306070, 5111370, 44627776, 19457589, 95251277, 19376156, 90310261, 99549373, 17764950, 48673079, 26102057, 17539625, 21001913, 63372756, 88653118, 30998561, 82427263, 91802888, 14326617, 1788101, 6497830, 7919588, 16595436, 32699744, 24058273, 73392814, 78884452, 21993752, 22942635, 38061439, 24826575, 65880522, 78589145, 77377183, 9575954, 52613508, 92071990, 43045786, 98739783, 31161687, 75777973, 61728685, 33123618, 1022677, 82979980, 4204661, 3233569, 89804152, 98653983, 55189057, 66271566, 11543098, 8913721, 76703609, 99729357, 2331773, 62936963, 36505482, 83948335, 23110625, 72738685, 45428665, 39847321, 9188443, 51047803, 70372191, 42073124, 22766820, 74724075, 29029316, 7646095, 50007421, 34044787, 97379790, 16424199, 40781449, 92692978, 27665211, 24314885, 16684829, 68204242, 29401781, 72373496, 29994197, 45919976, 8733068, 1375023, 99524975, 43152977, 14731700, 6819644, 59501487, 70420215, 54606384, 37166608, 17037369, 45848907, 26292919, 57803235, 1250437, 5832946, 91255408, 44550764, 48893685, 8696647, 36812683, 16387575, 43506672, 39553046, 31904591, 36139942, 13470059, 76540481, 14947650, 22435353, 93053405, 45617087, 90272749, 47361209, 38510840, 27185644, 43357947, 58749, 10961421, 66611759, 8055981, 36312813, 74137926, 9860968, 85023028, 95290172, 61271144, 26397786, 13862149, 10358899, 9829782, 51472275, 70036438, 17976208, 59405277, 6157724, 11161731, 97011160, 81677380, 59614746, 61712234, 65715134, 38658347, 20486294, 38009615, 30787683, 176257, 20867149, 59197747, 64157906, 45990383, 33553959, 93933709, 42967683, 17058722, 58180653, 40197395, 30463802, 61815223, 32590267, 29188588, 6793819, 33249630, 77694700, 27187213, 12348118, 41245325, 23740167, 18783702, 17146629, 4508700, 68644627, 38022834, 29834512, 74357852, 88047921, 55143724, 41481685, 79806380, 91938191, 19272365, 63015256, 9257405, 83302115, 1431742, 20002147, 66319530, 5822202, 56424103, 9603598, 11757872, 45381876, 45996863, 89078848, 4064751, 82779622, 44060493, 30771409, 7502255, 10453030, 247198, 30163921, 66832478, 14220886, 82897371, 92353856, 68694897, 56531125, 65017137, 57020481, 526217, 18833224, 61960400, 83368048, 99917599, 44481640, 82327024, 41092172, 16405341, 70800879, 66137019, 26493119, 45667668, 30139692, 8373289, 58208470, 98462867, 96852321, 17894977, 6221471, 48395186, 66250369, 28796059, 96735716, 508198, 2208785, 4515343, 57248122, 7229550, 62115552, 78549759, 30694952, 98943869, 1936762, 63628376, 22200378, 4199704, 93515664, 37957788, 15075176, 20885148, 34667286, 51590803, 30764367, 1197320, 19898053, 85695762, 22879907, 62232211, 81898046, 30891921, 21673260, 40984766, 1569515, 83789391, 8729146, 76170907, 19486173, 69255765, 10649306, 13819358, 62051033, 62803858, 73168565, 29932657, 21289531, 77301523, 23134715, 16097038, 72793444, 36685023, 48260151, 26063929, 16019925, 79880247, 84293052, 46851987, 85116755, 48658605, 96906410, 69786756, 59177718, 112651, 63967300, 12303248, 66885828, 5073754, 47708850, 79136082, 6808825, 67281495, 51426311, 99965001, 47090124, 25636669, 89811711, 71316369, 27041967, 85711894, 81774825, 57359924, 27625735, 33935899, 65047700, 94711842, 47298834, 31491938, 56484900, 24953498, 40781854, 4668450, 98948034, 30218878, 73021291, 72357096, 34497327, 77072625, 41092102, 83269727, 89699445, 11581181, 99333375, 50567636, 36396314, 84127901, 46540998, 40534591, 32151165, 94076128, 16380211, 73031054, 4985896, 2917920, 56515456, 94911072, 72358170, 91281584, 43501211, 92530431, 93566986, 82339363, 68824981, 60176618, 72725103, 45407418, 4095116, 85242963, 97940276, 91727510, 33797252, 80251430, 81855445, 88698958, 19939935, 3233084, 83133790, 90013093, 16445503, 51715482, 87160386, 68102437, 53084256, 11923835, 54762643, 7104732, 9623492, 26776922, 66045587, 3183975, 33628349, 68816413, 47792865, 55470718, 36580610, 57393458, 53802686, 90061527, 14723410, 3633375, 53547802, 65338021, 53393358, 21070110, 3773993, 89046466, 70004753, 87598888, 31733363, 12571310, 64602895, 88130087, 68000591, 61928316, 86301513, 63059024, 5640302, 68875490, 65275241, 38341669, 60581278, 29959549, 89217461, 42199455, 30543215, 63506281, 13468268, 66322959, 65081429, 72614359, 415901, 38365584, 73124510, 41380093, 92541302, 98371444, 92998591, 71469330, 37620363, 93562257, 6505939, 70541760, 7066775, 4099191, 56473732, 7011964, 58751351, 95957797, 43376279, 14045383, 68316156, 4091162, 20122224, 30811010, 74110882, 18699206, 86798033, 66428795, 76960303, 4069912, 50188404, 56153345, 60393039, 37435892, 55247455, 78300864, 67474219, 3294781, 96193415, 36930650, 84187166, 26744917, 12664567, 96709982, 14349098, 48774913, 29819940, 34946859, 6871053, 40152546, 37891451, 99971982, 67793644, 61897582, 54987042, 72278539, 74743862, 69697787, 89637706, 77300457, 71083565, 32274392, 45075471, 79191827, 24733232, 32161669, 9599614, 91240048, 92803766, 9058407, 2891150, 75543508, 59371804, 49598724, 57241521, 94090109, 13173644, 78218845, 29510992, 13231279, 17957593, 71965942, 17068582, 90457870, 84840549, 88251446, 28928175, 40865610, 65271999, 93270084, 3880712, 49328608, 96420429, 79922758, 66903004, 81274566, 54014062, 84406788, 49236559, 37280276, 36189527, 28734791, 14626618, 62430984, 17857111, 15015906, 67227442, 70596786, 89499542, 44844121, 62490109, 15902805, 86460488, 99604946, 40027975, 37224844, 13348726, 98648327, 7423788, 95395112, 92867155, 47887470, 77312810, 86543538, 6111563, 85117092, 42307449, 54427233, 80014588, 73617245, 62740044, 4798568, 44847298, 61741594, 49597667, 15535065, 57163802, 16099750, 18411915, 92604458, 81172706, 73109997, 33699435, 39373729, 16971929, 18131876, 7182642, 16054533, 59910624, 77898274, 61859143, 52060076, 24915585, 69355476, 82532312, 7687278, 99021067, 72238278, 73786944, 95726235, 51466049, 18806856, 74441448, 64055960, 77284619, 62496012, 66663942, 38256849, 86022504, 87710366, 36780454, 31727379, 39986008, 37192445, 15536795, 53632373, 99515901, 76330843, 22129328, 34432810, 97641116, 2717150, 9398733, 67124282, 26664538, 44842615, 23848565, 84349107, 83210802, 38645117, 83651211, 4787945, 92787493, 20642888, 26139110, 29516592, 8634541, 19891772, 3509435, 44348328, 86001008, 26998766, 28550822, 75128745, 91141395, 34698428, 62357986, 73235980, 44664587, 3088684, 95581843, 53842979, 81805959, 26114953, 9259676, 75052463, 38494874, 8651647, 89419466, 36933038, 88444207, 26392416, 67084351, 64848072, 749283, 48675329, 92398073, 32250045, 27325428, 72495719, 89996536, 84166196, 38008118, 98130363, 20681921, 55770687, 69605283, 35456853, 53648154, 73222868, 82178706, 68110330, 64087743, 61373987, 70727211, 99861373, 67031644, 62552524, 44784505, 78561158, 66116458, 92554120, 17016156, 37675718, 39171895, 53666583, 26275734, 51507425, 1239555, 91957544, 77787724, 71333116, 29466406, 52204879, 2607799, 31126490, 32058615, 1204161, 50668599, 10264691, 83083131, 824872, 99226875, 61982238, 57658654, 62762857, 74452589, 55669657, 33565483, 67513640, 8128637, 77413012, 57240218, 6986898, 40686254, 17385531, 52261574, 82726008, 46870723, 17727650, 21397057, 54058788, 79821897, 96318094, 59318837, 65851721, 50806615, 62693428, 44846932, 45049308, 65038678, 61859581, 69136837, 59109400, 76825057, 90158785, 22405120, 94516935, 45790169, 18001617, 69579137, 21533347, 75820087, 26863229, 99658235, 75153252, 66667729, 79657802, 23569917, 42947632, 75229462, 70782102, 30366150, 4978543, 9860195, 10309525, 94539824, 6525948, 20836893, 24591705, 6153406, 12416768, 30395570, 93790285, 75407347, 39801351, 51149804, 36845587, 37739481, 77620120, 71300104, 42692881, 44245960, 18663507, 37501808, 63152504, 18504197, 86118021, 8791066, 33237508, 5970607, 49641577, 82651278, 37659250, 65074535, 86242799, 67030811, 24168411, 94595668, 22721500, 62452034, 88904910, 29065964, 15148031, 10366309, 79942022, 44473167, 25842078, 76315420, 93359396, 68702632, 54199160, 33895336, 91831487, 21919959, 91990218, 15948937, 68128438, 64098930, 75135448, 48360198, 5482538, 94360702, 52293995, 34698463, 26507214, 35192533, 92692283, 75986488, 54868730, 59329511, 14363867, 44479073, 60955663, 17386996, 10597197, 23432750, 27411561, 35996293, 68041839, 97561745, 84684495, 49882705, 51315460, 14093520, 77272628, 22997281, 79738755, 30090481, 8129978, 82886381, 48088883, 84002370, 37560164, 43322743, 20140249, 79322415, 33201905, 71657078, 53888755, 321665, 54517921, 61623915, 28787861, 42782652, 60106217, 47893286, 22108665, 7955293, 34295794, 71522968, 68939068, 17081350, 61141874, 33304202, 95010552, 44915235, 32159704, 41442762, 28851716, 86821229, 6521313, 33061250, 54263880, 60102965, 51464002, 99297164, 22113133, 70367851 +53084256, 70541760, 83083131, 90310261, 99549373, 12416768, 3294781, 6521313, 20642888, 11923835, 32159704, 75777973, 67281495, 74357852, 82779622, 8115266, 51588015, 18001617, 6221471, 75229462, 94539824, 70596786, 19486173, 68000591, 86301513, 61741594, 99297164, 68644627, 79806380, 19272365, 16567550, 29819940, 54058788, 97057187, 32274392, 44481640, 83651211, 75543508, 33336148, 62357986, 60430369, 9259676, 20836893, 81898046, 30395570, 7423788, 5640302, 95395112, 65275241, 7300047, 40197395, 54427233, 46851987, 96906410, 59177718, 63967300, 71522968, 61859143, 92692978, 33935899, 14363867, 40356877, 64055960, 14731700, 824872, 68939068, 37192445, 17727650, 7502255, 67793644, 54987042, 44550764, 67124282, 45049308, 44842615, 4787945, 76540481, 70800879, 83533741, 97940276, 45667668, 78218845, 38510840, 72019362, 58749, 63372756, 57248122, 3183975, 38494874, 91831487, 95290172, 36933038, 10358899, 72495719, 22997281, 34493392, 14723410, 15015906, 81853704, 85695762, 21673260, 76170907, 88130087, 78589145, 3235882, 23134715, 26063929, 13468268, 26507214, 72614359, 36845587, 36505482, 38365584, 32590267, 41380093, 92541302, 66885828, 27187213, 71469330, 79136082, 43376279, 91664334, 57359924, 7687278, 39201414, 12024238, 74441448, 72357096, 62496012, 17037369, 77413012, 52261574, 72278539, 91255408, 89637706, 65017137, 36753250, 68824981, 32161669, 26139110, 80316608, 93053405, 8373289, 21533347, 83133790, 71965942, 21001913, 33304202, 90013093, 40865610, 75128745, 96420429, 68816413, 22200378, 93515664, 28734791, 37957788, 59405277, 6157724, 15948937, 18466635, 30764367, 65338021, 45794415, 38658347, 82178706, 80246713, 86460488, 54263880, 70004753, 37224844, 44784505, 30569392, 92867155, 62803858, 55850790, 21289531, 29959549, 77312810, 4204661, 42307449, 84293052, 15535065, 45428665, 9188443, 51047803, 33249630, 44245960, 37620363, 18783702, 50007421, 7011964, 4508700, 41442762, 71316369, 82532312, 65047700, 29994197, 99524975, 55247455, 20002147, 6819644, 59501487, 74452589, 54606384, 24168411, 62428472, 96709982, 22129328, 14349098, 46870723, 44060493, 40534591, 66832478, 92353856, 56515456, 94911072, 9886593, 36812683, 16387575, 65038678, 15148031, 23848565, 84349107, 92803766, 40677414, 38645117, 13470059, 16405341, 27411561, 4434662, 97561745, 30139692, 57241521, 19891772, 29510992, 17539625, 81855445, 69641513, 19939935, 25842078, 72274002, 51715482, 87160386, 84840549, 28550822, 34698428, 48395186, 88653118, 9623492, 45237957, 66250369, 79657802, 7229550, 23569917, 9860968, 13862149, 64848072, 36189527, 65454636, 97011160, 14626618, 62430984, 15163258, 59614746, 34236719, 1197320, 17857111, 16595436, 69605283, 20486294, 62232211, 30787683, 20867149, 83789391, 12571310, 5482538, 64157906, 47213183, 66116458, 42644903, 73168565, 33553959, 86543538, 26275734, 89804152, 79880247, 55615885, 65081429, 80555751, 62936963, 30463802, 83948335, 37560164, 72738685, 92604458, 54868730, 99690194, 69786756, 112651, 22766820, 5073754, 74724075, 44889423, 7646095, 63152504, 8791066, 17146629, 5970607, 18131876, 68316156, 82651278, 27625735, 69355476, 91938191, 1204161, 86798033, 87720882, 10264691, 43152977, 24953498, 67030811, 9603598, 11757872, 38256849, 41092102, 87710366, 89699445, 15536795, 26292919, 36396314, 6986898, 50702367, 40686254, 10453030, 96318094, 6871053, 40152546, 37891451, 73031054, 53888755, 669105, 86821229, 44983451, 4985896, 14220886, 82897371, 68694897, 62452034, 72358170, 44846932, 57020481, 93566986, 61859581, 83210802, 59109400, 22405120, 26102057, 85242963, 66137019, 59371804, 44473167, 69579137, 58208470, 26863229, 3233084, 8099994, 93270084, 28796059, 96735716, 4515343, 81805959, 98943869, 14326617, 26397786, 36580610, 749283, 4199704, 9860195, 53802686, 81677380, 68128438, 89996536, 98130363, 7919588, 61712234, 53547802, 24058273, 6153406, 22879907, 30891921, 40984766, 24619760, 99861373, 79738755, 15064655, 67031644, 45990383, 69255765, 75407347, 43045786, 3233569, 17058722, 42199455, 11543098, 52293995, 85117092, 874791, 73617245, 62740044, 4798568, 92692283, 77620120, 6793819, 71300104, 42073124, 92998591, 43322743, 77694700, 12348118, 29029316, 47708850, 2204165, 73109997, 6808825, 18504197, 41245325, 99965001, 58751351, 33237508, 95957797, 77787724, 2607799, 96726697, 55143724, 14045383, 49641577, 32058615, 4091162, 20122224, 40781449, 30811010, 90654836, 28851716, 72732205, 4119747, 54232247, 18699206, 49271185, 4069912, 99021067, 50668599, 68204242, 73786944, 1375023, 99226875, 56424103, 34497327, 77072625, 11581181, 39986008, 45848907, 83155442, 99515901, 76330843, 4064751, 46540998, 32151165, 16380211, 10597197, 34432810, 168541, 50806615, 61897582, 90090964, 48893685, 88904910, 44627776, 526217, 74614639, 99917599, 26664538, 5267545, 76825057, 23432750, 45407418, 92787493, 9058407, 48673079, 79942022, 94516935, 14947650, 33797252, 8634541, 57961282, 44348328, 98462867, 88698958, 17957593, 35092039, 76315420, 26998766, 68102437, 88251446, 28928175, 75153252, 44664587, 54199160, 53842979, 33895336, 82427263, 26114953, 33628349, 75052463, 60106217, 42947632, 14093520, 21919959, 95010552, 26392416, 42237907, 30366150, 9829782, 47893286, 51472275, 6497830, 67451935, 27325428, 17365188, 65715134, 21993752, 21070110, 73222868, 8807940, 68110330, 64087743, 89046466, 31733363, 93790285, 65880522, 62552524, 92071990, 63059024, 39801351, 13819358, 20765474, 38341669, 60581278, 33123618, 47887470, 17016156, 1022677, 77301523, 6111563, 16097038, 94360702, 39171895, 60102965, 72793444, 98653983, 36685023, 76671482, 66271566, 30653863, 78909561, 37739481, 61815223, 19101477, 1239555, 73124510, 23110625, 29188588, 85116755, 12303248, 81172706, 37501808, 36808486, 86118021, 56461322, 33699435, 78766358, 31126490, 88047921, 85711894, 16054533, 97379790, 77898274, 37659250, 44479073, 23194618, 29401781, 95726235, 31491938, 20645197, 78300864, 40781854, 4668450, 36930650, 62762857, 97783876, 37166608, 31727379, 84187166, 8128637, 12664567, 89078848, 57803235, 21397057, 5832946, 34946859, 59318837, 94076128, 2717150, 69697787, 9398733, 45306070, 56531125, 61141874, 91281584, 43501211, 61960400, 92530431, 82339363, 82327024, 19376156, 91240048, 60176618, 90158785, 17764950, 91727510, 29516592, 94090109, 80251430, 84684495, 17894977, 22450468, 61623915, 10961421, 91141395, 7104732, 58224549, 66667729, 68702632, 66045587, 42782652, 2208785, 79922758, 91802888, 6038457, 8651647, 47792865, 55470718, 1788101, 91990218, 54014062, 49236559, 37280276, 10309525, 90061527, 77272628, 54663246, 78884452, 62490109, 15902805, 1569515, 70727211, 87598888, 55753905, 13348726, 98648327, 77377183, 10649306, 98739783, 92554120, 31161687, 61728685, 89217461, 43933006, 55189057, 30543215, 48260151, 16019925, 66322959, 51507425, 84904436, 415901, 39847321, 29635537, 9175338, 98371444, 7685448, 93562257, 83747892, 29466406, 16971929, 41481685, 81774825, 63015256, 72238278, 9257405, 51466049, 60393039, 66319530, 77284619, 66663942, 96193415, 57658654, 36780454, 99333375, 33565483, 50567636, 67513640, 56955985, 45996863, 57240218, 17385531, 1250437, 82726008, 99224392, 30771409, 30163921, 11365791, 8696647, 62693428, 99125126, 43506672, 54517921, 39553046, 31904591, 93057697, 10366309, 35996293, 22435353, 45617087, 13231279, 96852321, 17068582, 90457870, 49882705, 65271999, 36312813, 28787861, 3880712, 30998561, 49328608, 55602660, 78602717, 78549759, 59981773, 83150534, 36468541, 61271144, 67084351, 63628376, 44915235, 4978543, 11161731, 20885148, 51590803, 69848388, 3633375, 53393358, 73392814, 89499542, 53648154, 33061250, 3773993, 44177011, 64602895, 52613508, 8129978, 62051033, 29932657, 37675718, 569864, 82979980, 37183543, 42967683, 48088883, 58180653, 8913721, 84002370, 57163802, 48658605, 89214616, 32426752, 70367851, 18663507, 6505939, 7066775, 51426311, 55960386, 56473732, 47090124, 22113133, 38022834, 29834512, 27041967, 7182642, 36135, 7517032, 52060076, 90004325, 74110882, 66428795, 76960303, 45919976, 18806856, 83302115, 8733068, 56484900, 37435892, 1431742, 67474219, 73021291, 55669657, 86022504, 83269727, 45381876, 53632373, 84127901, 48774913, 79821897, 45995325, 94595668, 99971982, 74743862, 321665, 95251277, 71083565, 79191827, 24733232, 9599614, 69136837, 72725103, 35512853, 2891150, 45790169, 49598724, 26493119, 47361209, 92215320, 86001008, 43357947, 16445503, 95581843, 26776922, 30694952, 51315460, 20568363, 66903004, 85023028, 81274566, 70782102, 57393458, 84406788, 92398073, 84166196, 6525948, 26734892, 20681921, 32699744, 22942635, 44844121, 99604946, 176257, 40027975, 8729146, 59197747, 76434144, 68875490, 82886381, 51149804, 34698463, 2331773, 91957544, 49597667, 34295794, 16099750, 18411915, 51464002, 23740167, 4099191, 34044787, 89811711, 59910624, 16424199, 71920426, 24314885, 92033260, 85571389, 86242799, 98948034, 30218878, 61982238, 77187825, 33201905, 26744917, 17081350, 71657078, 247198, 65851721, 29065964, 18833224, 36139942, 4095116, 68041839, 90272749, 3509435, 27185644, 93359396, 3487592, 8055981, 73235980, 3088684, 74137926, 1936762, 88444207, 70036438, 15075176, 32250045, 64098930, 24591705, 19898053, 55770687, 35456853, 38061439, 75135448, 48360198, 30090481, 22108665, 61928316, 78561158, 93933709, 53666583, 76703609, 80014588, 44847298, 7955293, 48892201, 39373729, 52204879, 65074535, 56153345, 60955663, 70420215, 79322415, 19457589, 83368048, 41092172, 97281847, 78785507, 99658235, 66611759, 54762643, 62115552, 89419466, 17976208, 34667286, 61373987, 24826575, 9575954, 61263068, 82052050, 99729357, 35192533, 75986488, 70372191, 42692881, 24915585, 27665211, 16684829, 50188404, 94711842, 47298834, 20140249, 47738185, 2917920, 5111370, 77300457, 45075471, 4806458, 33431960, 75820087, 508198, 63506281, 59329511, 71333116, 72777973, 5822202, 17386996, 22721500, 72373496, 48675329, 38008118, 67227442, 38009615, 97641116, 13173644, 25636669 +11161731, 93053405, 42947632, 61928316, 62496012, 62762857, 69697787, 66137019, 92215320, 33628349, 48675329, 81677380, 44177011, 72793444, 16019925, 5073754, 89811711, 29466406, 41481685, 40781449, 71920426, 86798033, 9257405, 29994197, 24953498, 94595668, 44842615, 9599614, 22405120, 14947650, 91727510, 63372756, 26776922, 23569917, 51315460, 9259676, 55470718, 64848072, 28734791, 97011160, 65880522, 44784505, 30569392, 63059024, 65275241, 55850790, 73617245, 77620120, 85116755, 83747892, 70541760, 25636669, 85711894, 37659250, 99524975, 98948034, 66663942, 83269727, 39986008, 45848907, 1250437, 59318837, 66832478, 72278539, 61859581, 19376156, 40677414, 35512853, 68041839, 18001617, 94090109, 98462867, 17957593, 83133790, 28928175, 3487592, 65271999, 44664587, 96735716, 7229550, 98943869, 81274566, 36933038, 88444207, 54014062, 68128438, 34493392, 94539824, 15015906, 3633375, 22942635, 80246713, 64087743, 30787683, 55753905, 43045786, 13819358, 92867155, 569864, 58180653, 40197395, 42307449, 99729357, 66322959, 65081429, 36505482, 75986488, 9175338, 92604458, 12303248, 22766820, 79136082, 47090124, 8791066, 5970607, 38022834, 91664334, 92692978, 19272365, 50668599, 85571389, 95726235, 56484900, 66319530, 86022504, 62428472, 17037369, 45996863, 77413012, 36396314, 84127901, 10453030, 53888755, 22721500, 14220886, 8696647, 29065964, 93057697, 8115266, 13470059, 92787493, 48673079, 83533741, 22435353, 13173644, 69579137, 47361209, 96852321, 28550822, 61623915, 58749, 3880712, 49328608, 42782652, 91802888, 59981773, 749283, 15163258, 53393358, 78884452, 20486294, 82178706, 62232211, 21673260, 40984766, 24619760, 20867149, 24826575, 48360198, 78589145, 67031644, 78561158, 92071990, 69255765, 75407347, 42644903, 33123618, 6111563, 48088883, 17058722, 51149804, 13468268, 36845587, 415901, 34295794, 15535065, 39847321, 29635537, 57163802, 9188443, 99690194, 59177718, 42073124, 112651, 66885828, 77694700, 44889423, 71469330, 50007421, 71316369, 2607799, 78766358, 36135, 30811010, 28851716, 72732205, 4119747, 54232247, 82532312, 66428795, 39201414, 51466049, 31491938, 1431742, 86242799, 4668450, 79322415, 50567636, 40686254, 96709982, 30163921, 9398733, 45306070, 89637706, 65017137, 43501211, 74614639, 54517921, 83368048, 82339363, 23848565, 76825057, 26139110, 4434662, 49598724, 97281847, 30139692, 29516592, 44473167, 78218845, 29510992, 35092039, 78785507, 11923835, 66611759, 45237957, 95581843, 79657802, 508198, 26114953, 38494874, 9860968, 68816413, 83150534, 95290172, 1788101, 63628376, 70036438, 36189527, 27325428, 32159704, 84166196, 38008118, 69848388, 24591705, 81853704, 20681921, 85695762, 21070110, 68110330, 38061439, 83789391, 76170907, 9575954, 3235882, 47213183, 66116458, 8129978, 92554120, 38341669, 1022677, 42967683, 7300047, 4204661, 55189057, 66271566, 52293995, 48260151, 51507425, 79880247, 30653863, 62740044, 72614359, 44847298, 78909561, 61815223, 16099750, 63967300, 81172706, 70367851, 63152504, 41245325, 7066775, 18783702, 86118021, 29834512, 74357852, 55143724, 4091162, 49271185, 4069912, 44479073, 23194618, 45919976, 10264691, 18806856, 60393039, 37435892, 64055960, 78300864, 72357096, 61982238, 56424103, 77072625, 11757872, 77187825, 38256849, 36780454, 50702367, 82726008, 76330843, 4064751, 44060493, 29819940, 30771409, 6871053, 45995325, 16380211, 168541, 82897371, 92353856, 56515456, 5111370, 99125126, 36753250, 77300457, 95251277, 92530431, 36139942, 69136837, 92803766, 90310261, 23432750, 99549373, 4806458, 9058407, 79942022, 26493119, 8634541, 33431960, 17539625, 13231279, 84684495, 88698958, 21001913, 33304202, 90457870, 72019362, 68102437, 6221471, 58224549, 93270084, 33895336, 82427263, 96420429, 60430369, 57248122, 79922758, 55602660, 62115552, 81805959, 14093520, 21919959, 30366150, 13862149, 49236559, 22200378, 4199704, 44915235, 67451935, 15075176, 32250045, 51590803, 17365188, 30764367, 17857111, 67227442, 53547802, 70596786, 19898053, 73392814, 21993752, 73222868, 15902805, 86460488, 61373987, 99861373, 15064655, 75135448, 59197747, 64157906, 13348726, 98648327, 68000591, 45990383, 5640302, 95395112, 60581278, 29959549, 61263068, 82979980, 34698463, 63506281, 46851987, 19101477, 48892201, 1239555, 41380093, 92541302, 29188588, 18411915, 42692881, 12348118, 74724075, 29029316, 2204165, 37620363, 37501808, 73109997, 6505939, 51426311, 55960386, 17146629, 41442762, 39373729, 33237508, 71333116, 31126490, 7182642, 68316156, 82651278, 52060076, 57359924, 24915585, 27625735, 24314885, 7687278, 65047700, 63015256, 68204242, 83302115, 60955663, 14731700, 96193415, 34497327, 87710366, 37166608, 89699445, 31727379, 68939068, 56955985, 8128637, 26292919, 53632373, 57803235, 99515901, 52261574, 99224392, 48774913, 37891451, 10597197, 97057187, 65851721, 669105, 91255408, 67124282, 9886593, 88904910, 61141874, 44627776, 93566986, 44481640, 24733232, 26664538, 5267545, 38645117, 20642888, 4095116, 76540481, 26102057, 35996293, 59371804, 33797252, 8373289, 57241521, 80251430, 19891772, 58208470, 69641513, 17894977, 26998766, 88251446, 40865610, 54762643, 88653118, 7104732, 54199160, 6038457, 74137926, 78602717, 75052463, 66903004, 91831487, 89419466, 47792865, 14326617, 95010552, 26392416, 67084351, 42237907, 51472275, 20885148, 18466635, 34667286, 90061527, 77272628, 89996536, 65715134, 69605283, 81898046, 44844121, 30395570, 8729146, 88130087, 76434144, 52613508, 20765474, 31161687, 62051033, 62803858, 29932657, 47887470, 37675718, 86543538, 43933006, 39171895, 60102965, 3233569, 89804152, 98653983, 42199455, 82052050, 11543098, 8913721, 55615885, 83948335, 32590267, 23110625, 7685448, 71300104, 96906410, 43322743, 18663507, 18504197, 4099191, 7011964, 4508700, 58751351, 34044787, 59329511, 77787724, 43376279, 96726697, 14045383, 90004325, 74110882, 33935899, 76960303, 87720882, 29401781, 72777973, 73786944, 20645197, 67474219, 77284619, 30218878, 3294781, 5822202, 9603598, 97783876, 55669657, 33565483, 26744917, 37192445, 45381876, 12664567, 57240218, 6986898, 47738185, 21397057, 34946859, 32151165, 79821897, 247198, 50806615, 44983451, 4985896, 6521313, 44550764, 68694897, 62693428, 321665, 72358170, 526217, 71083565, 32274392, 68824981, 15148031, 32161669, 82327024, 10366309, 91240048, 45407418, 41092172, 16405341, 27411561, 94516935, 33336148, 21533347, 86001008, 71965942, 38510840, 90013093, 49882705, 99658235, 34698428, 8055981, 66250369, 36312813, 68702632, 4515343, 30694952, 75229462, 70782102, 36580610, 57393458, 84406788, 37280276, 6497830, 93515664, 6157724, 59614746, 64098930, 98130363, 32699744, 24058273, 89499542, 38658347, 8807940, 38009615, 12416768, 3773993, 54263880, 70727211, 93790285, 19486173, 30090481, 77377183, 62552524, 68875490, 39801351, 33553959, 93933709, 16097038, 53666583, 36685023, 76671482, 85117092, 874791, 80014588, 84904436, 26507214, 30463802, 91957544, 38365584, 92692283, 72738685, 98371444, 48658605, 44245960, 47708850, 36808486, 51464002, 56461322, 99297164, 68644627, 16971929, 52204879, 18131876, 88047921, 16054533, 27665211, 14363867, 50188404, 72373496, 16567550, 47298834, 74441448, 83083131, 20002147, 20140249, 70420215, 57658654, 74452589, 41092102, 24168411, 33201905, 99333375, 15536795, 17081350, 14349098, 46870723, 17727650, 5832946, 73031054, 86821229, 2717150, 48893685, 56531125, 94911072, 19457589, 65038678, 18833224, 45075471, 99917599, 83210802, 60176618, 83651211, 4787945, 45667668, 45617087, 90272749, 57961282, 75820087, 3509435, 44348328, 26863229, 3233084, 17068582, 93359396, 84840549, 53084256, 48395186, 9623492, 66667729, 2208785, 3183975, 8651647, 10358899, 47893286, 37957788, 17976208, 65454636, 10309525, 72495719, 62430984, 34236719, 6525948, 1197320, 26734892, 20836893, 16595436, 6153406, 45794415, 53648154, 33061250, 1569515, 176257, 40027975, 7423788, 61728685, 37183543, 23134715, 76703609, 26063929, 84293052, 2331773, 84002370, 4798568, 62936963, 7955293, 37560164, 45428665, 89214616, 32426752, 33249630, 27187213, 71522968, 6808825, 67281495, 99965001, 56473732, 22113133, 81774825, 7517032, 77898274, 20122224, 90654836, 69355476, 91938191, 1204161, 92033260, 16684829, 72238278, 43152977, 55247455, 67030811, 59501487, 99226875, 83155442, 17385531, 82779622, 7502255, 46540998, 40534591, 40152546, 94076128, 34432810, 99971982, 61897582, 90090964, 16387575, 43506672, 61960400, 39553046, 90158785, 72725103, 17764950, 70800879, 85242963, 97940276, 2891150, 75543508, 45790169, 27185644, 72274002, 8099994, 16445503, 51715482, 75128745, 10961421, 91141395, 73235980, 53842979, 78549759, 20568363, 36468541, 9829782, 4978543, 9860195, 59405277, 22997281, 14723410, 22879907, 30891921, 89046466, 87598888, 37224844, 12571310, 98739783, 75777973, 21289531, 77301523, 89217461, 26275734, 30543215, 54427233, 49597667, 73124510, 6793819, 51047803, 70372191, 7646095, 93562257, 23740167, 27041967, 16424199, 32058615, 65074535, 94711842, 40781854, 36930650, 22129328, 97641116, 54987042, 74743862, 44846932, 45049308, 31904591, 80316608, 97561745, 43357947, 76315420, 87160386, 75153252, 62357986, 3088684, 28787861, 66045587, 30998561, 1936762, 92398073, 53802686, 14626618, 54663246, 7919588, 61712234, 65338021, 35456853, 62490109, 99604946, 70004753, 64602895, 5482538, 17016156, 80555751, 61741594, 92998591, 33699435, 61859143, 56153345, 40356877, 1375023, 6819644, 84187166, 67513640, 96318094, 67793644, 11365791, 62452034, 36812683, 91281584, 84349107, 59109400, 51588015, 22450468, 28796059, 55770687, 31733363, 86301513, 10649306, 73168565, 77312810, 37739481, 54868730, 69786756, 49641577, 18699206, 8733068, 12024238, 73021291, 824872, 54606384, 89078848, 17386996, 71657078, 2917920, 57020481, 79191827, 19939935, 25842078, 60106217, 61271144, 26397786, 91990218, 15948937, 79738755, 82886381, 94360702, 35192533, 95957797, 59910624, 97379790, 79806380, 11581181, 54058788, 85023028, 22108665, 81855445, 99021067 +5073754, 749283, 36685023, 25636669, 32058615, 47738185, 34946859, 10366309, 66137019, 96852321, 93359396, 58224549, 95010552, 67084351, 3633375, 93790285, 48360198, 64602895, 5482538, 32590267, 75986488, 42692881, 33237508, 38022834, 90654836, 87720882, 17386996, 72358170, 99125126, 91281584, 92787493, 4095116, 59371804, 68041839, 30366150, 69848388, 30787683, 9575954, 44784505, 13819358, 66271566, 15535065, 57163802, 89214616, 63152504, 77787724, 7517032, 20122224, 27625735, 71920426, 65074535, 56153345, 60393039, 74441448, 20140249, 32151165, 97057187, 89637706, 15148031, 22435353, 30139692, 3509435, 75153252, 54762643, 49328608, 61271144, 54014062, 47893286, 59405277, 62430984, 81853704, 54263880, 70004753, 83789391, 99861373, 68875490, 48260151, 72614359, 92998591, 37620363, 33699435, 88047921, 16424199, 27665211, 24314885, 86798033, 72373496, 56484900, 40781854, 824872, 11757872, 79322415, 54606384, 87710366, 82726008, 95251277, 71083565, 36139942, 45617087, 90272749, 57241521, 13173644, 21533347, 58208470, 17957593, 43357947, 88251446, 99658235, 28928175, 10961421, 6221471, 9623492, 3088684, 74137926, 23569917, 59981773, 47792865, 83150534, 64848072, 10309525, 18466635, 90061527, 54663246, 67227442, 16595436, 24058273, 22879907, 99604946, 43045786, 93933709, 42967683, 7300047, 72793444, 42307449, 99729357, 63506281, 13468268, 84002370, 48892201, 1239555, 34295794, 29188588, 70372191, 59177718, 77694700, 27187213, 18663507, 37501808, 6808825, 8791066, 68644627, 74357852, 78766358, 28851716, 4069912, 16684829, 50668599, 29994197, 73021291, 66663942, 97783876, 33201905, 33565483, 17037369, 8128637, 5832946, 10453030, 6871053, 168541, 11365791, 67124282, 321665, 29065964, 45049308, 526217, 31904591, 24733232, 40677414, 72725103, 51588015, 17764950, 20642888, 16405341, 94516935, 97940276, 33797252, 33431960, 13231279, 69641513, 72274002, 78785507, 44664587, 66250369, 36312813, 68702632, 54199160, 68816413, 10358899, 22200378, 15075176, 20885148, 77272628, 6525948, 65715134, 53547802, 69605283, 68110330, 87598888, 88130087, 30090481, 92071990, 63059024, 10649306, 31161687, 82886381, 77301523, 94360702, 30543215, 85117092, 8913721, 34698463, 51507425, 874791, 37739481, 19101477, 45428665, 48658605, 92604458, 63967300, 74724075, 44889423, 47708850, 6505939, 7066775, 86118021, 4099191, 89811711, 95957797, 27041967, 41481685, 72732205, 49271185, 7687278, 63015256, 76960303, 50188404, 72777973, 1375023, 37435892, 55247455, 67474219, 77284619, 74452589, 11581181, 56955985, 12664567, 26292919, 77413012, 89078848, 36396314, 96709982, 17385531, 21397057, 7502255, 59318837, 65851721, 73031054, 54987042, 86821229, 8696647, 16387575, 77300457, 18833224, 43501211, 61960400, 45075471, 39553046, 93057697, 92803766, 59109400, 83651211, 27411561, 79942022, 2891150, 75543508, 45790169, 18001617, 29510992, 69579137, 44348328, 88698958, 19939935, 27185644, 35092039, 76315420, 72019362, 49882705, 40865610, 11923835, 66611759, 88653118, 66667729, 95581843, 53842979, 42782652, 2208785, 96420429, 7229550, 6038457, 78602717, 78549759, 75052463, 66903004, 89419466, 42237907, 13862149, 48675329, 92398073, 27325428, 68128438, 34493392, 30764367, 14723410, 20836893, 53648154, 73222868, 22942635, 44844121, 38009615, 86460488, 30395570, 76170907, 55753905, 78589145, 61928316, 75407347, 8129978, 39801351, 75777973, 73168565, 61728685, 47887470, 33553959, 37183543, 86543538, 60102965, 48088883, 26275734, 82052050, 40197395, 73617245, 84904436, 44847298, 78909561, 36505482, 30463802, 37560164, 6793819, 9175338, 98371444, 32426752, 44245960, 71522968, 50007421, 71316369, 29466406, 52204879, 18131876, 96726697, 31126490, 55143724, 16054533, 59910624, 82651278, 24915585, 37659250, 74110882, 29401781, 85571389, 39201414, 16567550, 18806856, 47298834, 24953498, 86242799, 14731700, 30218878, 34497327, 9603598, 36930650, 86022504, 37166608, 89699445, 50567636, 39986008, 17081350, 17727650, 54058788, 61897582, 53888755, 44983451, 91255408, 6521313, 90090964, 2917920, 9398733, 5111370, 88904910, 57020481, 36753250, 54517921, 99917599, 44481640, 32161669, 82327024, 61859581, 5267545, 90310261, 60176618, 23432750, 45407418, 48673079, 26102057, 80316608, 49598724, 26493119, 97281847, 44473167, 80251430, 19891772, 57961282, 75820087, 25842078, 38510840, 17068582, 8099994, 16445503, 75128745, 28796059, 508198, 60430369, 62115552, 26114953, 9860968, 91831487, 14093520, 55470718, 36468541, 70782102, 88444207, 1788101, 26397786, 37280276, 4199704, 44915235, 93515664, 28734791, 37957788, 65454636, 17365188, 81677380, 89996536, 38008118, 26734892, 17857111, 24591705, 61712234, 65338021, 19898053, 38658347, 20486294, 35456853, 64087743, 38061439, 44177011, 61373987, 79738755, 40027975, 37224844, 12571310, 65880522, 64157906, 67031644, 22108665, 62552524, 52613508, 66116458, 65275241, 38341669, 92867155, 29932657, 17016156, 6111563, 42199455, 54427233, 80014588, 79880247, 30653863, 62936963, 61815223, 7955293, 9188443, 16099750, 71300104, 66885828, 2204165, 7646095, 79136082, 70541760, 4508700, 41442762, 39373729, 49641577, 36135, 52060076, 30811010, 54232247, 79806380, 44479073, 99021067, 40356877, 45919976, 20645197, 4668450, 67030811, 70420215, 57658654, 24168411, 36780454, 83269727, 84187166, 26744917, 84127901, 83155442, 30771409, 46540998, 79821897, 16380211, 10597197, 50806615, 30163921, 669105, 4985896, 2717150, 74743862, 48893685, 69697787, 68694897, 94911072, 9886593, 19457589, 65038678, 74614639, 83368048, 79191827, 9599614, 19376156, 91240048, 69136837, 83210802, 4787945, 41092172, 70800879, 35996293, 8373289, 47361209, 84684495, 3233084, 83133790, 33304202, 26998766, 68102437, 3487592, 91141395, 34698428, 65271999, 62357986, 7104732, 8055981, 73235980, 79657802, 26776922, 66045587, 96735716, 57248122, 79922758, 51315460, 8651647, 14326617, 36933038, 57393458, 84406788, 63628376, 49236559, 11161731, 34667286, 72495719, 97011160, 14626618, 15163258, 94539824, 32159704, 84166196, 64098930, 20681921, 6153406, 78884452, 55770687, 21993752, 82178706, 12416768, 33061250, 1569515, 31733363, 75135448, 59197747, 3235882, 47213183, 78561158, 98739783, 95395112, 42644903, 62803858, 55850790, 60581278, 98653983, 58180653, 55189057, 16019925, 66322959, 84293052, 4798568, 85116755, 99690194, 70367851, 73109997, 51464002, 83747892, 67281495, 99965001, 56461322, 99297164, 43376279, 14045383, 97379790, 57359924, 92692978, 69355476, 18699206, 33935899, 14363867, 72238278, 95726235, 83302115, 8733068, 1431742, 60955663, 66319530, 5822202, 99226875, 61982238, 96193415, 62762857, 38256849, 68939068, 52261574, 22129328, 14349098, 4064751, 82779622, 44060493, 96318094, 40152546, 22721500, 62452034, 44627776, 36812683, 43506672, 92530431, 68824981, 38645117, 76825057, 83533741, 91727510, 33336148, 45667668, 97561745, 8634541, 94090109, 78218845, 92215320, 90013093, 17894977, 51715482, 87160386, 93270084, 28787861, 3880712, 82427263, 81805959, 33628349, 20568363, 1936762, 85023028, 95290172, 9829782, 6497830, 53802686, 51590803, 22997281, 34236719, 70596786, 85695762, 81898046, 80246713, 15902805, 8807940, 89046466, 19486173, 76434144, 98648327, 69255765, 7423788, 20765474, 62051033, 33123618, 21289531, 77312810, 89217461, 16097038, 39171895, 4204661, 89804152, 17058722, 51149804, 76703609, 55615885, 46851987, 80555751, 35192533, 415901, 83948335, 91957544, 41380093, 77620120, 29635537, 96906410, 54868730, 69786756, 22766820, 12348118, 71469330, 41245325, 56473732, 58751351, 22113133, 71333116, 29834512, 65047700, 66428795, 92033260, 68204242, 23194618, 9257405, 73786944, 94711842, 99524975, 6819644, 98948034, 3294781, 62496012, 77187825, 99333375, 31727379, 37192445, 45381876, 15536795, 53632373, 57803235, 6986898, 50702367, 1250437, 99224392, 71657078, 40534591, 37891451, 247198, 94076128, 34432810, 97641116, 66832478, 14220886, 45306070, 56515456, 56531125, 93566986, 26664538, 84349107, 8115266, 90158785, 35512853, 22405120, 14947650, 26139110, 81855445, 26863229, 86001008, 21001913, 4515343, 91802888, 9259676, 81274566, 21919959, 91990218, 51472275, 36189527, 4978543, 15948937, 32250045, 1197320, 7919588, 32699744, 53393358, 21070110, 62232211, 3773993, 15064655, 24826575, 68000591, 77377183, 45990383, 86301513, 37675718, 82979980, 23134715, 11543098, 52293995, 26507214, 62740044, 73124510, 92692283, 7685448, 51047803, 42073124, 93562257, 36808486, 18783702, 7011964, 17146629, 59329511, 16971929, 2607799, 7182642, 85711894, 81774825, 90004325, 4091162, 40781449, 4119747, 91938191, 43152977, 78300864, 20002147, 72357096, 41092102, 62428472, 45848907, 45996863, 40686254, 76330843, 46870723, 29819940, 45995325, 92353856, 62693428, 61141874, 9058407, 76540481, 17539625, 71965942, 22450468, 28550822, 61623915, 63372756, 45237957, 33895336, 30998561, 55602660, 30694952, 38494874, 67451935, 59614746, 98130363, 45794415, 73392814, 89499542, 30891921, 21673260, 40984766, 20867149, 70727211, 30569392, 92554120, 43933006, 3233569, 53666583, 76671482, 2331773, 36845587, 61741594, 92541302, 72738685, 12303248, 43322743, 29029316, 51426311, 68316156, 77898274, 19272365, 12024238, 31491938, 56424103, 77072625, 67513640, 48774913, 72278539, 82897371, 65017137, 32274392, 82339363, 99549373, 93053405, 29516592, 84840549, 53084256, 48395186, 17976208, 9860195, 13348726, 5640302, 1022677, 61263068, 569864, 26063929, 38365584, 49597667, 23110625, 18411915, 33249630, 81172706, 18504197, 47090124, 34044787, 91664334, 61859143, 82532312, 59501487, 55669657, 99971982, 67793644, 44550764, 23848565, 13470059, 85242963, 98462867, 90457870, 42947632, 26392416, 36580610, 70036438, 15015906, 176257, 8729146, 29959549, 39847321, 23740167, 1204161, 10264691, 83083131, 57240218, 94595668, 4806458, 3183975, 60106217, 75229462, 6157724, 62490109, 55960386, 5970607, 64055960, 99515901, 44842615, 4434662, 58749, 24619760, 65081429, 51466049, 44846932, 98943869, 112651 +31161687, 37891451, 38341669, 77620120, 80316608, 56473732, 50188404, 40356877, 96193415, 168541, 79191827, 24733232, 48673079, 35996293, 58749, 83150534, 10358899, 22879907, 62803858, 33553959, 48260151, 98371444, 71300104, 33249630, 44245960, 6505939, 86022504, 4787945, 13173644, 84684495, 83133790, 43357947, 49328608, 4515343, 81274566, 67084351, 57393458, 70036438, 65715134, 64087743, 55850790, 89217461, 40197395, 29188588, 42692881, 7066775, 55960386, 47090124, 29834512, 74357852, 85711894, 59910624, 61859143, 54232247, 68204242, 60393039, 30218878, 50702367, 40152546, 97641116, 11365791, 45306070, 43501211, 82327024, 36139942, 45407418, 41092172, 91727510, 22435353, 80251430, 81855445, 21533347, 21001913, 61623915, 66611759, 33628349, 38494874, 9860968, 55470718, 70782102, 1788101, 84166196, 61712234, 67227442, 32699744, 19898053, 44177011, 70727211, 19486173, 22108665, 47213183, 75407347, 98739783, 39801351, 20765474, 77301523, 6111563, 53666583, 42199455, 8913721, 63506281, 874791, 26507214, 30463802, 16099750, 18411915, 43322743, 66885828, 12348118, 51464002, 51426311, 86118021, 5970607, 95957797, 96726697, 88047921, 4119747, 69355476, 18699206, 23194618, 20002147, 6819644, 824872, 56424103, 41092102, 7502255, 44550764, 74743862, 94911072, 9886593, 61141874, 91281584, 57020481, 43506672, 93566986, 92803766, 23432750, 83651211, 35512853, 70800879, 26102057, 66137019, 93053405, 4434662, 26493119, 18001617, 17539625, 26998766, 28550822, 68702632, 66045587, 78549759, 8651647, 59981773, 9829782, 67451935, 9860195, 15948937, 27325428, 89996536, 98130363, 20836893, 3633375, 69605283, 38009615, 99604946, 3773993, 89046466, 1569515, 37224844, 76434144, 13348726, 92071990, 62051033, 42967683, 89804152, 46851987, 36845587, 61741594, 57163802, 70372191, 63152504, 41245325, 25636669, 58751351, 29466406, 43376279, 55143724, 91938191, 99021067, 50668599, 39201414, 16567550, 64055960, 62762857, 37166608, 62428472, 99333375, 77413012, 82726008, 46870723, 48774913, 82779622, 44060493, 79821897, 6871053, 94595668, 73031054, 30163921, 61897582, 53888755, 86821229, 2717150, 2917920, 62693428, 44846932, 61960400, 92530431, 26664538, 8115266, 72725103, 76540481, 8373289, 33431960, 19891772, 47361209, 71965942, 76315420, 90457870, 84840549, 68102437, 75153252, 10961421, 3880712, 79657802, 53842979, 60430369, 81805959, 23569917, 42947632, 14093520, 75229462, 95290172, 36580610, 54014062, 48675329, 28734791, 37957788, 15075176, 6157724, 65454636, 11161731, 14626618, 59614746, 6153406, 73392814, 89499542, 21070110, 81898046, 38061439, 93790285, 55753905, 78589145, 8129978, 73168565, 29932657, 21289531, 17016156, 86543538, 43933006, 39171895, 98653983, 66271566, 52293995, 26063929, 65081429, 19101477, 48892201, 91957544, 75986488, 51047803, 92604458, 54868730, 112651, 63967300, 12303248, 44889423, 7646095, 67281495, 50007421, 4099191, 56461322, 33699435, 8791066, 89811711, 41481685, 68316156, 4091162, 27665211, 1204161, 65047700, 76960303, 10264691, 83302115, 94711842, 12024238, 47298834, 1375023, 99524975, 56484900, 1431742, 78300864, 55669657, 38256849, 87710366, 11581181, 33565483, 17037369, 84187166, 37192445, 15536795, 53632373, 57240218, 83155442, 52261574, 17727650, 247198, 67124282, 56515456, 56531125, 29065964, 36753250, 16387575, 77300457, 32274392, 31904591, 9599614, 5267545, 84349107, 91240048, 69136837, 59109400, 76825057, 51588015, 4806458, 79942022, 68041839, 97561745, 29516592, 44473167, 8634541, 94090109, 92215320, 13231279, 57961282, 75820087, 69641513, 33304202, 72274002, 51715482, 22450468, 93359396, 99658235, 65271999, 79922758, 55602660, 30694952, 51315460, 75052463, 98943869, 47792865, 36468541, 22200378, 64848072, 51590803, 72495719, 81677380, 22997281, 68128438, 54663246, 64098930, 14723410, 26734892, 15015906, 24058273, 70596786, 53648154, 30891921, 80246713, 24619760, 176257, 79738755, 48360198, 77377183, 45990383, 66116458, 86301513, 5640302, 95395112, 92867155, 60581278, 82886381, 61263068, 37675718, 23134715, 94360702, 72793444, 58180653, 34698463, 76703609, 51507425, 79880247, 84293052, 73617245, 84002370, 37739481, 35192533, 49597667, 37560164, 34295794, 15535065, 39847321, 9188443, 48658605, 89214616, 99690194, 27187213, 47708850, 70541760, 7011964, 41442762, 77787724, 71333116, 27041967, 18131876, 2607799, 16054533, 49641577, 77898274, 52060076, 57359924, 37659250, 71920426, 74110882, 86798033, 44479073, 72238278, 45919976, 31491938, 24953498, 74441448, 40781854, 4668450, 66319530, 62496012, 34497327, 77072625, 9603598, 67513640, 56955985, 45848907, 8128637, 40686254, 96709982, 32151165, 54987042, 91255408, 5111370, 36812683, 65038678, 83368048, 32161669, 90310261, 90158785, 16405341, 26139110, 45667668, 90272749, 30139692, 29510992, 69579137, 44348328, 88698958, 86001008, 27185644, 35092039, 8099994, 28928175, 3487592, 34698428, 93270084, 36312813, 33895336, 26776922, 66903004, 91831487, 61271144, 26392416, 63628376, 37280276, 749283, 6497830, 92398073, 20885148, 10309525, 32250045, 77272628, 53393358, 35456853, 22942635, 21673260, 61373987, 70004753, 20867149, 15064655, 12571310, 75135448, 98648327, 52613508, 69255765, 30569392, 13819358, 92554120, 65275241, 61728685, 60102965, 4204661, 26275734, 17058722, 55189057, 82052050, 76671482, 51149804, 16019925, 84904436, 55615885, 2331773, 72614359, 80555751, 62936963, 1239555, 83948335, 38365584, 73124510, 23110625, 45428665, 6793819, 9175338, 96906410, 59177718, 5073754, 74724075, 81172706, 71522968, 36808486, 79136082, 99965001, 17146629, 34044787, 33237508, 14045383, 90654836, 82532312, 33935899, 49271185, 19272365, 85571389, 95726235, 83083131, 67030811, 99226875, 57658654, 36930650, 97783876, 83269727, 68939068, 45996863, 36396314, 6986898, 76330843, 4064751, 46540998, 10597197, 66832478, 4985896, 22721500, 82897371, 68694897, 99125126, 19457589, 74614639, 82339363, 44481640, 15148031, 93057697, 83210802, 38645117, 99549373, 22405120, 4095116, 97940276, 45617087, 98462867, 96852321, 88251446, 53084256, 63372756, 62357986, 73235980, 9623492, 45237957, 66250369, 28796059, 6038457, 74137926, 85023028, 60106217, 21919959, 13862149, 49236559, 51472275, 36189527, 93515664, 59405277, 15163258, 30764367, 6525948, 69848388, 17857111, 7919588, 20681921, 55770687, 20486294, 82178706, 44844121, 62490109, 15902805, 68110330, 86460488, 83789391, 99861373, 59197747, 24826575, 64602895, 68000591, 78561158, 43045786, 42644903, 33123618, 1022677, 48088883, 30543215, 36685023, 85117092, 42307449, 99729357, 66322959, 36505482, 415901, 72738685, 29635537, 7685448, 42073124, 77694700, 18663507, 2204165, 37620363, 18783702, 99297164, 39373729, 22113133, 38022834, 52204879, 31126490, 36135, 81774825, 82651278, 90004325, 20122224, 24915585, 40781449, 28851716, 92692978, 66428795, 92033260, 14363867, 29401781, 9257405, 72373496, 55247455, 60955663, 98948034, 3294781, 59501487, 5822202, 77187825, 54606384, 36780454, 89699445, 31727379, 89078848, 84127901, 14349098, 29819940, 5832946, 71657078, 40534591, 54058788, 10453030, 59318837, 94076128, 97057187, 67793644, 50806615, 669105, 14220886, 6521313, 90090964, 92353856, 8696647, 9398733, 89637706, 62452034, 44627776, 95251277, 44842615, 60176618, 92787493, 20642888, 83533741, 2891150, 75543508, 59371804, 45790169, 49598724, 33336148, 97281847, 57241521, 58208470, 17957593, 17894977, 87160386, 78785507, 91141395, 6221471, 54762643, 88653118, 44664587, 95581843, 28787861, 30998561, 82427263, 91802888, 26114953, 9259676, 20568363, 68816413, 95010552, 88444207, 4199704, 18466635, 90061527, 32159704, 24591705, 81853704, 45794415, 78884452, 8807940, 30395570, 31733363, 40027975, 76170907, 5482538, 67031644, 30090481, 61928316, 3235882, 75777973, 47887470, 29959549, 77312810, 82979980, 37183543, 93933709, 7300047, 13468268, 30653863, 62740044, 4798568, 61815223, 32590267, 92692283, 92998591, 22766820, 29029316, 70367851, 71469330, 93562257, 73109997, 23740167, 4508700, 71316369, 16971929, 32058615, 27625735, 72732205, 24314885, 7687278, 65074535, 4069912, 56153345, 29994197, 73786944, 51466049, 14731700, 73021291, 72357096, 20140249, 61982238, 70420215, 79322415, 33201905, 50567636, 12664567, 57803235, 17386996, 17081350, 99515901, 22129328, 47738185, 44983451, 72358170, 526217, 71083565, 54517921, 68824981, 61859581, 40677414, 13470059, 94516935, 14947650, 3509435, 19939935, 3233084, 90013093, 40865610, 75128745, 48395186, 7104732, 3088684, 54199160, 42782652, 508198, 2208785, 96420429, 57248122, 7229550, 89419466, 42237907, 30366150, 84406788, 53802686, 34667286, 1197320, 21993752, 62232211, 40984766, 12416768, 64157906, 9575954, 10649306, 11543098, 41380093, 69786756, 6808825, 68644627, 59329511, 91664334, 78766358, 7182642, 16424199, 30811010, 79806380, 72777973, 18806856, 8733068, 43152977, 20645197, 86242799, 77284619, 11757872, 26744917, 26292919, 17385531, 30771409, 96318094, 45995325, 16380211, 34432810, 69697787, 65017137, 45049308, 23848565, 19376156, 17764950, 85242963, 78218845, 25842078, 38510840, 72019362, 49882705, 8055981, 66667729, 62115552, 3183975, 78602717, 47893286, 34493392, 34236719, 16595436, 53547802, 65338021, 33061250, 30787683, 8729146, 65880522, 88130087, 62552524, 569864, 16097038, 3233569, 54427233, 80014588, 78909561, 85116755, 7517032, 87720882, 37435892, 67474219, 66663942, 24168411, 39986008, 1250437, 99224392, 21397057, 34946859, 99971982, 72278539, 321665, 45075471, 39553046, 99917599, 27411561, 33797252, 26863229, 26397786, 17976208, 62430984, 94539824, 44784505, 63059024, 92541302, 32426752, 18504197, 97379790, 74452589, 10366309, 58224549, 96735716, 91990218, 17365188, 38008118, 85695762, 73222868, 54263880, 87598888, 7423788, 44847298, 37501808, 16684829, 45381876, 65851721, 48893685, 18833224, 9058407, 17068582, 16445503, 11923835, 1936762, 97011160, 68875490, 7955293, 83747892, 63015256, 88904910, 14326617, 36933038, 44915235, 4978543, 38658347 +48360198, 62496012, 17081350, 19376156, 92803766, 92787493, 54014062, 10358899, 30463802, 41245325, 90310261, 33797252, 28928175, 62490109, 15535065, 44245960, 33699435, 95957797, 37659250, 99021067, 24953498, 34946859, 14220886, 90090964, 9599614, 69136837, 94516935, 84840549, 11923835, 19898053, 75407347, 8129978, 60581278, 17016156, 91957544, 77694700, 50007421, 90004325, 38256849, 17037369, 26744917, 76330843, 94595668, 97057187, 45306070, 16387575, 68824981, 5267545, 69579137, 75128745, 3487592, 36312813, 66045587, 51590803, 17365188, 14626618, 61712234, 65715134, 38658347, 62232211, 55850790, 77301523, 40197395, 63506281, 54427233, 874791, 61741594, 48892201, 63967300, 5073754, 44889423, 70367851, 4508700, 58751351, 38022834, 92692978, 7687278, 8733068, 74441448, 20002147, 86022504, 33201905, 12664567, 26292919, 89078848, 40534591, 37891451, 66832478, 2917920, 9398733, 43501211, 93566986, 10366309, 91727510, 22435353, 18001617, 33431960, 80251430, 47361209, 88698958, 83133790, 72274002, 58749, 88653118, 58224549, 3088684, 95581843, 30694952, 33628349, 47792865, 97011160, 32159704, 24058273, 6153406, 55770687, 44844121, 68110330, 78589145, 68000591, 47213183, 30569392, 98739783, 62051033, 47887470, 37675718, 93933709, 89804152, 13468268, 80014588, 73617245, 46851987, 44847298, 80555751, 61815223, 38365584, 98371444, 69786756, 42073124, 12303248, 22766820, 42692881, 66885828, 63152504, 70541760, 56461322, 17146629, 34044787, 33237508, 5970607, 24915585, 28851716, 4119747, 49271185, 87720882, 23194618, 72373496, 18806856, 56484900, 78300864, 67474219, 67030811, 59501487, 34497327, 33565483, 56955985, 8128637, 36396314, 4064751, 21397057, 44060493, 10453030, 32151165, 40152546, 247198, 67793644, 61897582, 91255408, 2717150, 92353856, 57020481, 45049308, 74614639, 82339363, 38645117, 72725103, 48673079, 26102057, 27411561, 29510992, 17539625, 44348328, 38510840, 76315420, 8099994, 26998766, 99658235, 48395186, 93270084, 66250369, 49328608, 23569917, 68816413, 55470718, 21919959, 51472275, 36189527, 93515664, 20885148, 27325428, 81677380, 22997281, 89996536, 84166196, 6525948, 30395570, 61373987, 40027975, 8729146, 12571310, 75135448, 65880522, 22108665, 69255765, 39801351, 29932657, 86543538, 84293052, 84002370, 62740044, 72614359, 78909561, 1239555, 32590267, 72738685, 7685448, 71300104, 89214616, 32426752, 81172706, 37620363, 73109997, 6808825, 67281495, 77787724, 2607799, 96726697, 16424199, 68316156, 52060076, 30811010, 72732205, 91938191, 86798033, 50668599, 29401781, 94711842, 31491938, 14731700, 77284619, 72357096, 62762857, 55669657, 83269727, 11581181, 50702367, 96709982, 22129328, 17727650, 48774913, 30771409, 7502255, 73031054, 168541, 48893685, 68694897, 56515456, 56531125, 62452034, 36812683, 19457589, 36753250, 61960400, 71083565, 31904591, 93057697, 23848565, 36139942, 8115266, 23432750, 41092172, 22405120, 66137019, 97561745, 21001913, 33304202, 72019362, 78785507, 68102437, 65271999, 66611759, 54199160, 30998561, 96735716, 42782652, 6038457, 98943869, 66903004, 9860968, 59981773, 83150534, 36468541, 1788101, 36580610, 49236559, 6497830, 59405277, 65454636, 90061527, 20836893, 67227442, 89499542, 69605283, 21993752, 21070110, 53648154, 81898046, 22942635, 38061439, 54263880, 15064655, 55753905, 19486173, 64602895, 67031644, 3235882, 63059024, 95395112, 20765474, 38341669, 569864, 89217461, 37183543, 42967683, 58180653, 36685023, 11543098, 84904436, 2331773, 4798568, 7955293, 19101477, 415901, 57163802, 92604458, 54868730, 27187213, 7646095, 79136082, 83747892, 18783702, 86118021, 7011964, 25636669, 8791066, 89811711, 29466406, 27041967, 59910624, 7517032, 61859143, 54232247, 33935899, 65047700, 50188404, 56153345, 72777973, 29994197, 6819644, 73021291, 11757872, 74452589, 68939068, 14349098, 5832946, 79821897, 96318094, 6871053, 34432810, 99971982, 97641116, 86821229, 72278539, 74743862, 11365791, 69697787, 321665, 89637706, 94911072, 99125126, 91281584, 526217, 95251277, 45075471, 26664538, 82327024, 84349107, 59109400, 83651211, 99549373, 20642888, 16405341, 9058407, 4095116, 79942022, 4434662, 49598724, 45667668, 97281847, 44473167, 8634541, 21533347, 58208470, 92215320, 84684495, 98462867, 96852321, 19939935, 17957593, 71965942, 35092039, 17068582, 43357947, 44664587, 68702632, 3880712, 91802888, 78602717, 20568363, 70782102, 61271144, 88444207, 26397786, 91990218, 13862149, 84406788, 63628376, 22200378, 4978543, 9860195, 15948937, 18466635, 34493392, 62430984, 94539824, 64098930, 26734892, 24591705, 3633375, 32699744, 65338021, 45794415, 78884452, 73222868, 40984766, 33061250, 3773993, 30787683, 1569515, 70004753, 83789391, 37224844, 24826575, 5482538, 62552524, 92071990, 66116458, 86301513, 10649306, 13819358, 77312810, 6111563, 43933006, 39171895, 26275734, 42199455, 82052050, 8913721, 99729357, 30653863, 65081429, 36845587, 36505482, 75986488, 45428665, 29188588, 16099750, 18411915, 70372191, 59177718, 43322743, 47708850, 7066775, 41442762, 71316369, 43376279, 74357852, 88047921, 85711894, 41481685, 36135, 32058615, 57359924, 74110882, 27665211, 24314885, 66428795, 4069912, 72238278, 40356877, 12024238, 37435892, 20645197, 1431742, 86242799, 40781854, 4668450, 3294781, 824872, 66663942, 79322415, 37166608, 84127901, 17386996, 83155442, 82726008, 46540998, 65851721, 50806615, 4985896, 44550764, 8696647, 5111370, 44627776, 39553046, 79191827, 99917599, 40677414, 90158785, 51588015, 76540481, 70800879, 35996293, 75543508, 80316608, 45790169, 93053405, 26493119, 13173644, 81855445, 57961282, 75820087, 69641513, 17894977, 93359396, 88251446, 28550822, 53084256, 91141395, 6221471, 73235980, 45237957, 66667729, 28796059, 26776922, 96420429, 60430369, 55602660, 7229550, 91831487, 85023028, 14093520, 14326617, 36933038, 67084351, 42237907, 57393458, 92398073, 77272628, 59614746, 54663246, 14723410, 69848388, 81853704, 73392814, 20486294, 35456853, 82178706, 80246713, 15902805, 24619760, 44177011, 87598888, 30090481, 98648327, 45990383, 9575954, 44784505, 52613508, 92554120, 31161687, 92867155, 62803858, 23134715, 16097038, 60102965, 85117092, 76703609, 26063929, 51507425, 79880247, 55615885, 92541302, 9175338, 48658605, 96906410, 112651, 2204165, 71469330, 36808486, 51464002, 23740167, 56473732, 47090124, 68644627, 59329511, 52204879, 91664334, 14045383, 49641577, 82651278, 4091162, 20122224, 90654836, 69355476, 79806380, 1204161, 19272365, 92033260, 44479073, 68204242, 9257405, 45919976, 16567550, 1375023, 99524975, 43152977, 83083131, 98948034, 30218878, 20140249, 96193415, 77072625, 9603598, 97783876, 54606384, 99333375, 37192445, 15536795, 77413012, 40686254, 1250437, 99224392, 82779622, 29819940, 53888755, 67124282, 62693428, 65017137, 9886593, 61141874, 29065964, 65038678, 43506672, 44481640, 24733232, 91240048, 83210802, 17764950, 83533741, 14947650, 97940276, 2891150, 26139110, 59371804, 68041839, 33336148, 30139692, 94090109, 78218845, 19891772, 3233084, 87160386, 61623915, 10961421, 54762643, 62357986, 508198, 2208785, 74137926, 81805959, 78549759, 51315460, 75052463, 60106217, 89419466, 42947632, 95010552, 37280276, 64848072, 9829782, 70036438, 4199704, 28734791, 10309525, 34667286, 72495719, 15163258, 30764367, 7919588, 20681921, 53393358, 70596786, 85695762, 30891921, 8807940, 38009615, 86460488, 99604946, 176257, 31733363, 99861373, 93790285, 59197747, 64157906, 61928316, 43045786, 68875490, 42644903, 61728685, 33123618, 21289531, 29959549, 61263068, 33553959, 82979980, 94360702, 4204661, 3233569, 53666583, 17058722, 42307449, 48260151, 66322959, 62936963, 35192533, 77620120, 85116755, 51047803, 12348118, 29834512, 18131876, 78766358, 16054533, 81774825, 71920426, 82532312, 65074535, 16684829, 73786944, 83302115, 60393039, 47298834, 64055960, 60955663, 5822202, 99226875, 56424103, 62428472, 31727379, 45848907, 57240218, 6986898, 99515901, 59318837, 54987042, 44983451, 72358170, 88904910, 77300457, 32274392, 92530431, 15148031, 61859581, 60176618, 13470059, 4787945, 35512853, 4806458, 45407418, 85242963, 45617087, 29516592, 57241521, 3509435, 86001008, 90013093, 16445503, 49882705, 75153252, 34698428, 63372756, 28787861, 79657802, 53842979, 79922758, 3183975, 1936762, 81274566, 75229462, 26392416, 30366150, 47893286, 17976208, 6157724, 11161731, 53802686, 32250045, 34236719, 1197320, 15015906, 16595436, 12416768, 76434144, 13348726, 7423788, 75777973, 73168565, 1022677, 7300047, 48088883, 98653983, 30543215, 76671482, 66271566, 34698463, 37739481, 49597667, 73124510, 39847321, 29635537, 9188443, 99690194, 92998591, 33249630, 71522968, 18663507, 6505939, 18504197, 55960386, 99965001, 4099191, 39373729, 22113133, 16971929, 7182642, 97379790, 77898274, 27625735, 63015256, 76960303, 85571389, 95726235, 51466049, 55247455, 66319530, 61982238, 70420215, 57658654, 36930650, 41092102, 24168411, 89699445, 50567636, 53632373, 57803235, 54058788, 45995325, 10597197, 6521313, 18833224, 54517921, 83368048, 90272749, 26863229, 40865610, 7104732, 4515343, 95290172, 749283, 48675329, 44915235, 67451935, 37957788, 38008118, 98130363, 53547802, 21673260, 89046466, 70727211, 79738755, 77377183, 5640302, 82886381, 72793444, 55189057, 51149804, 16019925, 26507214, 41380093, 92692283, 34295794, 37501808, 93562257, 31126490, 18699206, 39201414, 77187825, 87710366, 67513640, 17385531, 52261574, 30163921, 669105, 22721500, 82897371, 44846932, 76825057, 8373289, 13231279, 51715482, 22450468, 8055981, 82427263, 26114953, 38494874, 8651647, 17857111, 22879907, 64087743, 88130087, 78561158, 83948335, 23110625, 6793819, 51426311, 99297164, 55143724, 40781449, 10264691, 36780454, 84187166, 45381876, 45996863, 46870723, 47738185, 71657078, 16380211, 44842615, 27185644, 33895336, 62115552, 9259676, 68128438, 76170907, 52293995, 37560164, 29029316, 14363867, 39986008, 94076128, 32161669, 90457870, 9623492, 65275241, 74724075, 25842078, 15075176, 20867149, 71333116, 57248122 +95726235, 99524975, 44481640, 83651211, 64848072, 37192445, 69641513, 40865610, 36933038, 70036438, 32159704, 61928316, 77072625, 52261574, 2917920, 5267545, 78785507, 66667729, 53393358, 40027975, 64602895, 29932657, 48088883, 85117092, 54427233, 15535065, 16099750, 58751351, 22113133, 38022834, 78766358, 36780454, 38645117, 45407418, 17764950, 45667668, 90457870, 54762643, 68702632, 42947632, 95010552, 14723410, 53547802, 12416768, 5482538, 63059024, 39801351, 61263068, 16097038, 7300047, 55189057, 51149804, 76703609, 13468268, 92692283, 96906410, 12303248, 18504197, 41442762, 29401781, 73786944, 87710366, 21397057, 30771409, 62693428, 62452034, 19457589, 54517921, 23848565, 83210802, 79942022, 75153252, 53084256, 44664587, 36312813, 96735716, 51315460, 55470718, 6157724, 30891921, 68110330, 68000591, 62552524, 92867155, 72793444, 2331773, 415901, 32590267, 27187213, 37620363, 36808486, 51426311, 18131876, 49641577, 81774825, 66428795, 14363867, 4069912, 72238278, 9257405, 16567550, 56484900, 96193415, 36930650, 56955985, 8128637, 6986898, 32151165, 94076128, 53888755, 22721500, 5111370, 88904910, 526217, 26664538, 44842615, 93057697, 59109400, 91727510, 33797252, 94090109, 13231279, 44348328, 84684495, 16445503, 28928175, 66250369, 95581843, 1936762, 37280276, 94539824, 17857111, 24058273, 70596786, 89499542, 53648154, 15902805, 54263880, 76434144, 77377183, 22108665, 8129978, 42644903, 37675718, 77312810, 93933709, 94360702, 26275734, 66271566, 51507425, 79880247, 26507214, 36845587, 7955293, 1239555, 38365584, 85116755, 71300104, 63967300, 33249630, 70367851, 18783702, 86118021, 34044787, 2607799, 96726697, 16054533, 74110882, 18699206, 63015256, 76960303, 51466049, 60393039, 31491938, 60955663, 4668450, 6819644, 57658654, 37166608, 33201905, 11581181, 31727379, 84127901, 17081350, 17385531, 99515901, 22129328, 34946859, 40534591, 247198, 99971982, 73031054, 50806615, 72278539, 91255408, 74743862, 82897371, 8696647, 56531125, 94911072, 61141874, 65038678, 61960400, 71083565, 45075471, 92530431, 82327024, 61859581, 36139942, 91240048, 76825057, 35512853, 99549373, 48673079, 33336148, 8634541, 13173644, 19891772, 57961282, 88698958, 76315420, 26998766, 75128745, 91141395, 88653118, 45237957, 33895336, 81805959, 9259676, 68816413, 60106217, 21919959, 10358899, 49236559, 47893286, 48675329, 22997281, 84166196, 45794415, 85695762, 40984766, 86460488, 64087743, 99604946, 61373987, 70004753, 176257, 83789391, 37224844, 75135448, 64157906, 45990383, 69255765, 92554120, 62051033, 47887470, 39171895, 4204661, 98653983, 30543215, 76671482, 11543098, 84293052, 84904436, 92541302, 75986488, 9188443, 59177718, 32426752, 22766820, 42692881, 29029316, 71522968, 2204165, 71469330, 79136082, 56461322, 25636669, 39373729, 29834512, 31126490, 7182642, 14045383, 68316156, 90004325, 82532312, 19272365, 83302115, 74441448, 1431742, 3294781, 9603598, 62762857, 84187166, 45381876, 26292919, 89078848, 53632373, 82726008, 4064751, 47738185, 40152546, 66832478, 669105, 14220886, 68694897, 321665, 44846932, 91281584, 36753250, 16387575, 95251277, 43506672, 74614639, 79191827, 93566986, 9599614, 90310261, 8115266, 4806458, 22405120, 76540481, 83533741, 94516935, 97940276, 66137019, 59371804, 80316608, 8373289, 17539625, 3509435, 86001008, 19939935, 71965942, 38510840, 27185644, 90013093, 17894977, 84840549, 28550822, 61623915, 65271999, 66611759, 7104732, 93270084, 3088684, 79657802, 49328608, 57248122, 78602717, 38494874, 8651647, 98943869, 81274566, 75229462, 83150534, 95290172, 70782102, 61271144, 91990218, 57393458, 54014062, 4199704, 93515664, 67451935, 17976208, 77272628, 34493392, 69848388, 19898053, 55770687, 21070110, 35456853, 8807940, 38009615, 33061250, 99861373, 30090481, 9575954, 78561158, 92071990, 75407347, 5640302, 68875490, 98739783, 17016156, 29959549, 58180653, 82052050, 99729357, 26063929, 66322959, 73617245, 55615885, 4798568, 72614359, 78909561, 36505482, 19101477, 48892201, 73124510, 41380093, 72738685, 9175338, 18411915, 73109997, 6505939, 41245325, 70541760, 55960386, 50007421, 7011964, 33237508, 5970607, 95957797, 77787724, 43376279, 91664334, 85711894, 41481685, 32058615, 52060076, 24915585, 27665211, 24314885, 49271185, 86798033, 92033260, 24953498, 55247455, 20645197, 20002147, 30218878, 73021291, 99226875, 11757872, 74452589, 86022504, 68939068, 39986008, 57240218, 1250437, 29819940, 5832946, 10597197, 65851721, 168541, 97641116, 48893685, 67124282, 56515456, 9886593, 45049308, 82339363, 15148031, 84349107, 23432750, 27411561, 14947650, 75543508, 22435353, 4434662, 49598724, 18001617, 29516592, 57241521, 80251430, 21533347, 96852321, 17957593, 8099994, 49882705, 58749, 6221471, 66045587, 91802888, 26114953, 23569917, 33628349, 66903004, 89419466, 88444207, 26397786, 26392416, 30366150, 22200378, 36189527, 44915235, 37957788, 4978543, 15075176, 10309525, 81677380, 1197320, 7919588, 67227442, 3633375, 69605283, 81898046, 22942635, 21673260, 38061439, 65880522, 88130087, 13348726, 47213183, 7423788, 65275241, 55850790, 60581278, 89217461, 3233569, 40197395, 42307449, 48260151, 65081429, 80555751, 62936963, 91957544, 77620120, 34295794, 45428665, 39847321, 48658605, 69786756, 112651, 92998591, 12348118, 18663507, 37501808, 51464002, 67281495, 56473732, 47090124, 68644627, 59329511, 71333116, 16971929, 27041967, 52204879, 97379790, 16424199, 7517032, 82651278, 61859143, 40781449, 30811010, 28851716, 27625735, 54232247, 85571389, 40356877, 45919976, 94711842, 47298834, 64055960, 67474219, 98948034, 62496012, 59501487, 77187825, 55669657, 62428472, 57803235, 50702367, 40686254, 96709982, 76330843, 14349098, 46870723, 17727650, 48774913, 71657078, 54058788, 79821897, 59318837, 94595668, 30163921, 54987042, 92353856, 69697787, 89637706, 99125126, 29065964, 18833224, 83368048, 99917599, 19376156, 69136837, 4787945, 92787493, 41092172, 16405341, 4095116, 70800879, 93053405, 68041839, 26493119, 45617087, 97561745, 90272749, 44473167, 78218845, 69579137, 92215320, 98462867, 26863229, 33304202, 17068582, 22450468, 88251446, 28787861, 28796059, 54199160, 3880712, 2208785, 96420429, 55602660, 7229550, 3183975, 78549759, 13862149, 749283, 51472275, 28734791, 20885148, 34667286, 32250045, 27325428, 72495719, 17365188, 97011160, 14626618, 62430984, 89996536, 30764367, 34236719, 15015906, 73392814, 78884452, 82178706, 44844121, 80246713, 44177011, 70727211, 87598888, 31733363, 8729146, 19486173, 48360198, 44784505, 66116458, 43045786, 10649306, 20765474, 31161687, 62803858, 75777973, 77301523, 82979980, 86543538, 16019925, 874791, 80014588, 30653863, 46851987, 62740044, 44847298, 35192533, 61815223, 61741594, 49597667, 29635537, 51047803, 92604458, 99690194, 43322743, 44889423, 81172706, 6808825, 7066775, 99965001, 4099191, 33699435, 99297164, 29466406, 74357852, 77898274, 57359924, 4091162, 90654836, 69355476, 1204161, 50668599, 72373496, 29994197, 39201414, 37435892, 78300864, 86242799, 14731700, 66319530, 77284619, 20140249, 5822202, 66663942, 97783876, 38256849, 41092102, 54606384, 24168411, 83269727, 99333375, 17037369, 26744917, 67513640, 45848907, 45996863, 12664567, 77413012, 17386996, 44060493, 46540998, 44983451, 4985896, 90090964, 65017137, 77300457, 31904591, 32161669, 10366309, 51588015, 26139110, 30139692, 83133790, 35092039, 72274002, 72019362, 68102437, 99658235, 63372756, 58224549, 73235980, 60430369, 79922758, 30694952, 20568363, 75052463, 91831487, 14093520, 14326617, 63628376, 6497830, 11161731, 18466635, 51590803, 54663246, 64098930, 20836893, 24591705, 81853704, 65715134, 21993752, 62232211, 79738755, 93790285, 12571310, 55753905, 59197747, 24826575, 78589145, 98648327, 30569392, 95395112, 569864, 23134715, 42967683, 6111563, 60102965, 53666583, 89804152, 17058722, 36685023, 52293995, 84002370, 37739481, 23110625, 29188588, 44245960, 93562257, 63152504, 8791066, 89811711, 36135, 72732205, 37659250, 71920426, 99021067, 68204242, 10264691, 18806856, 67030811, 72357096, 824872, 61982238, 56424103, 34497327, 79322415, 99224392, 45995325, 37891451, 16380211, 34432810, 67793644, 86821229, 2717150, 9398733, 36812683, 92803766, 60176618, 90158785, 85242963, 33431960, 25842078, 21001913, 51715482, 93359396, 10961421, 34698428, 53842979, 26776922, 42782652, 82427263, 4515343, 6038457, 62115552, 47792865, 36468541, 1788101, 42237907, 36580610, 9829782, 9860195, 59405277, 53802686, 15948937, 68128438, 15163258, 59614746, 38008118, 6525948, 98130363, 61712234, 65338021, 73222868, 89046466, 30787683, 1569515, 20867149, 67031644, 52613508, 86301513, 13819358, 61728685, 82886381, 33123618, 21289531, 37183543, 43933006, 42199455, 34698463, 83948335, 6793819, 98371444, 7685448, 42073124, 66885828, 5073754, 77694700, 7646095, 83747892, 23740167, 17146629, 59910624, 4119747, 33935899, 44479073, 16684829, 72777973, 8733068, 1375023, 40781854, 33565483, 50567636, 82779622, 7502255, 10453030, 96318094, 6871053, 61897582, 6521313, 44550764, 57020481, 43501211, 68824981, 13470059, 72725103, 26102057, 35996293, 2891150, 45790169, 97281847, 29510992, 47361209, 81855445, 58208470, 75820087, 3233084, 87160386, 11923835, 48395186, 9623492, 30998561, 74137926, 90061527, 16595436, 6153406, 38658347, 20486294, 22879907, 62490109, 3773993, 3235882, 38341669, 33553959, 8913721, 63506281, 30463802, 37560164, 57163802, 70372191, 74724075, 47708850, 88047921, 20122224, 65074535, 50188404, 56153345, 87720882, 12024238, 43152977, 83083131, 97057187, 45306070, 72358170, 39553046, 20642888, 9058407, 3487592, 62357986, 85023028, 67084351, 26734892, 32699744, 30395570, 15064655, 1022677, 89214616, 4508700, 71316369, 79806380, 91938191, 7687278, 89699445, 83155442, 11365791, 32274392, 24733232, 43357947, 8055981, 84406788, 92398073, 76170907, 73168565, 54868730, 55143724, 92692978, 65047700, 70420215, 15536795, 36396314, 44627776, 9860968, 20681921, 508198, 59981773, 65454636, 24619760, 23194618, 40677414 +75986488, 74137926, 43322743, 4099191, 7517032, 92787493, 19101477, 25636669, 50668599, 7919588, 52613508, 38341669, 72614359, 29029316, 16971929, 16424199, 1431742, 8128637, 17081350, 5832946, 5111370, 77300457, 78218845, 40865610, 88653118, 54199160, 59614746, 176257, 75135448, 82052050, 91957544, 27187213, 63152504, 67281495, 29466406, 76960303, 37166608, 17727650, 37891451, 69697787, 321665, 48673079, 26493119, 30139692, 19891772, 17894977, 90457870, 84840549, 88251446, 66667729, 36580610, 15948937, 65715134, 24058273, 64602895, 8129978, 63059024, 39171895, 98653983, 62740044, 77620120, 89214616, 34044787, 59329511, 83302115, 94711842, 62762857, 82726008, 21397057, 34432810, 30163921, 66832478, 89637706, 94911072, 29065964, 57020481, 45075471, 5267545, 84349107, 38645117, 83651211, 4787945, 35512853, 66137019, 22435353, 92215320, 63372756, 79922758, 78549759, 20568363, 59405277, 73222868, 33061250, 5482538, 92554120, 65275241, 62803858, 1022677, 48260151, 44847298, 41380093, 72738685, 71300104, 44889423, 41442762, 58751351, 71333116, 29834512, 18131876, 88047921, 27625735, 66428795, 16684829, 29994197, 1375023, 37435892, 83083131, 38256849, 87710366, 37192445, 6986898, 52261574, 59318837, 94595668, 67793644, 97641116, 53888755, 669105, 11365791, 36753250, 26664538, 93057697, 60176618, 76825057, 13470059, 94516935, 91727510, 80316608, 97281847, 75820087, 93359396, 49882705, 99658235, 58224549, 44664587, 93270084, 79657802, 66045587, 60430369, 57248122, 38494874, 66903004, 1788101, 13862149, 51472275, 6497830, 93515664, 28734791, 20885148, 54663246, 78884452, 55770687, 20486294, 8807940, 3773993, 87598888, 76170907, 19486173, 65880522, 39801351, 20765474, 73168565, 4204661, 53666583, 85117092, 34698463, 76703609, 874791, 73617245, 30463802, 48892201, 32590267, 92692283, 29188588, 48658605, 70372191, 32426752, 112651, 2204165, 7646095, 36808486, 33237508, 85711894, 57359924, 4091162, 30811010, 4119747, 7687278, 14363867, 72777973, 18806856, 24953498, 20645197, 74441448, 3294781, 77187825, 33565483, 67513640, 12664567, 77413012, 84127901, 50702367, 40686254, 17385531, 1250437, 99224392, 71657078, 40534591, 45995325, 61897582, 54987042, 91255408, 82897371, 68694897, 2917920, 62693428, 19457589, 16387575, 61960400, 54517921, 79191827, 93566986, 44481640, 23848565, 36139942, 90310261, 23432750, 4806458, 22405120, 45790169, 18001617, 97561745, 8634541, 47361209, 21533347, 3509435, 27185644, 76315420, 28928175, 66611759, 3088684, 95581843, 59981773, 42947632, 55470718, 30366150, 4199704, 34667286, 77272628, 84166196, 34236719, 6525948, 26734892, 81853704, 16595436, 53547802, 38658347, 44844121, 80246713, 38061439, 30395570, 24619760, 61373987, 1569515, 93790285, 40027975, 12571310, 76434144, 68000591, 69255765, 92867155, 37675718, 23134715, 86543538, 60102965, 48088883, 40197395, 30543215, 36685023, 76671482, 30653863, 36505482, 23110625, 15535065, 85116755, 16099750, 92998591, 33249630, 77694700, 47708850, 71469330, 37620363, 79136082, 7066775, 56461322, 47090124, 17146629, 39373729, 38022834, 43376279, 74357852, 59910624, 61859143, 37659250, 54232247, 49271185, 1204161, 92033260, 4069912, 29401781, 72238278, 72373496, 85571389, 39201414, 86242799, 20002147, 66663942, 56424103, 97783876, 86022504, 84187166, 56955985, 53632373, 17386996, 96709982, 46870723, 47738185, 34946859, 86821229, 74743862, 62452034, 65017137, 44846932, 61141874, 44627776, 36812683, 526217, 43501211, 83368048, 31904591, 32161669, 9599614, 9058407, 76540481, 79942022, 35996293, 26139110, 33431960, 94090109, 58208470, 57961282, 44348328, 72274002, 16445503, 28550822, 58749, 10961421, 62357986, 508198, 82427263, 91802888, 55602660, 8651647, 9860968, 75229462, 95290172, 36933038, 10358899, 37280276, 22200378, 749283, 67451935, 15075176, 92398073, 72495719, 68128438, 62430984, 89996536, 38008118, 67227442, 6153406, 19898053, 30891921, 64087743, 48360198, 30090481, 92071990, 31161687, 62051033, 55850790, 29932657, 33553959, 42967683, 72793444, 42199455, 80014588, 84293052, 84904436, 84002370, 73124510, 29635537, 57163802, 7685448, 51047803, 92604458, 96906410, 54868730, 99690194, 69786756, 22766820, 41245325, 86118021, 7011964, 8791066, 2607799, 78766358, 81774825, 82532312, 27665211, 18699206, 44479073, 16567550, 56484900, 60955663, 40781854, 67030811, 77284619, 20140249, 34497327, 11757872, 17037369, 50567636, 45848907, 57240218, 96318094, 247198, 94076128, 16380211, 50806615, 44983451, 22721500, 2717150, 9398733, 99125126, 9886593, 88904910, 95251277, 32274392, 15148031, 10366309, 40677414, 59109400, 72725103, 45407418, 93053405, 45617087, 44473167, 17539625, 69641513, 84684495, 96852321, 86001008, 19939935, 3233084, 38510840, 21001913, 8099994, 22450468, 87160386, 78785507, 75153252, 3487592, 65271999, 6221471, 54762643, 9623492, 66250369, 36312813, 28787861, 53842979, 26776922, 96735716, 2208785, 62115552, 81805959, 26114953, 33628349, 95010552, 36468541, 70782102, 26397786, 26392416, 67084351, 54014062, 84406788, 63628376, 49236559, 36189527, 44915235, 4978543, 6157724, 65454636, 53802686, 81677380, 34493392, 14723410, 1197320, 24591705, 70596786, 73392814, 69605283, 21070110, 62232211, 81898046, 21673260, 38009615, 54263880, 89046466, 30787683, 70004753, 83789391, 99861373, 37224844, 15064655, 67031644, 98648327, 22108665, 61928316, 9575954, 47213183, 75407347, 10649306, 13819358, 33123618, 17016156, 43933006, 7300047, 89804152, 58180653, 66271566, 52293995, 99729357, 26063929, 54427233, 55615885, 26507214, 80555751, 37739481, 415901, 83948335, 34295794, 45428665, 39847321, 6793819, 9175338, 12303248, 42692881, 5073754, 44245960, 70367851, 51464002, 23740167, 99965001, 18783702, 33699435, 89811711, 22113133, 68644627, 95957797, 27041967, 52204879, 41481685, 36135, 82651278, 20122224, 28851716, 91938191, 9257405, 73786944, 14731700, 66319530, 9603598, 74452589, 55669657, 54606384, 24168411, 36780454, 33201905, 89699445, 83155442, 76330843, 48774913, 44060493, 29819940, 46540998, 6871053, 10597197, 99971982, 168541, 72278539, 56531125, 72358170, 91281584, 45049308, 43506672, 24733232, 61859581, 19376156, 91240048, 92803766, 41092172, 4095116, 97940276, 33797252, 49598724, 90272749, 57241521, 80251430, 33304202, 43357947, 8055981, 45237957, 28796059, 3880712, 4515343, 7229550, 75052463, 98943869, 91831487, 85023028, 68816413, 89419466, 83150534, 21919959, 88444207, 57393458, 64848072, 48675329, 70036438, 9860195, 27325428, 14626618, 64098930, 69848388, 98130363, 3633375, 65338021, 89499542, 53648154, 22879907, 15902805, 40984766, 12416768, 99604946, 20867149, 55753905, 13348726, 45990383, 3235882, 78561158, 86301513, 7423788, 43045786, 68875490, 60581278, 89217461, 37183543, 6111563, 26275734, 51149804, 42307449, 63506281, 13468268, 51507425, 79880247, 46851987, 36845587, 78909561, 61741594, 1239555, 92541302, 18411915, 59177718, 66885828, 74724075, 81172706, 37501808, 73109997, 83747892, 50007421, 99297164, 77787724, 55143724, 49641577, 69355476, 71920426, 65074535, 63015256, 50188404, 56153345, 99021067, 95726235, 51466049, 8733068, 99524975, 4668450, 824872, 62496012, 5822202, 99226875, 70420215, 36930650, 83269727, 11581181, 68939068, 39986008, 89078848, 4064751, 7502255, 79821897, 97057187, 65851721, 4985896, 90090964, 45306070, 65038678, 39553046, 99917599, 68824981, 82327024, 69136837, 83210802, 20642888, 83133790, 51715482, 26998766, 68102437, 53084256, 34698428, 42782652, 96420429, 78602717, 51315460, 60106217, 47792865, 14326617, 91990218, 47893286, 37957788, 32250045, 90061527, 51590803, 15163258, 20836893, 20681921, 21993752, 22942635, 62490109, 86460488, 8729146, 59197747, 88130087, 77377183, 44784505, 30569392, 66116458, 98739783, 95395112, 42644903, 61728685, 82886381, 21289531, 569864, 82979980, 93933709, 17058722, 55189057, 2331773, 4798568, 35192533, 49597667, 18663507, 70541760, 51426311, 55960386, 4508700, 31126490, 14045383, 16054533, 68316156, 52060076, 90654836, 24314885, 33935899, 19272365, 68204242, 40356877, 43152977, 64055960, 30218878, 72357096, 59501487, 77072625, 57658654, 41092102, 31727379, 45996863, 15536795, 36396314, 57803235, 82779622, 30771409, 32151165, 71083565, 92530431, 44842615, 8115266, 51588015, 17764950, 16405341, 70800879, 85242963, 14947650, 59371804, 68041839, 13173644, 81855445, 13231279, 71965942, 17068582, 7104732, 33895336, 30998561, 6038457, 14093520, 61271144, 42237907, 10309525, 18466635, 97011160, 22997281, 94539824, 32159704, 17857111, 61712234, 32699744, 35456853, 82178706, 68110330, 24826575, 78589145, 62552524, 5640302, 75777973, 16097038, 3233569, 11543098, 8913721, 16019925, 65081429, 7955293, 9188443, 98371444, 42073124, 12348118, 93562257, 6505939, 56473732, 71316369, 5970607, 96726697, 97379790, 32058615, 77898274, 24915585, 72732205, 92692978, 74110882, 79806380, 86798033, 45919976, 12024238, 60393039, 55247455, 67474219, 96193415, 99333375, 26744917, 45381876, 10453030, 14220886, 6521313, 48893685, 8696647, 67124282, 18833224, 82339363, 90158785, 26102057, 27411561, 75543508, 33336148, 45667668, 29510992, 88698958, 26863229, 25842078, 72019362, 11923835, 48395186, 68702632, 49328608, 23569917, 81274566, 9829782, 17365188, 30764367, 15015906, 45794415, 85695762, 79738755, 64157906, 47887470, 61263068, 77312810, 94360702, 38365584, 71522968, 6808825, 18504197, 90004325, 40781449, 65047700, 87720882, 10264691, 47298834, 78300864, 6819644, 98948034, 73021291, 61982238, 99515901, 54058788, 40152546, 73031054, 44550764, 92353856, 56515456, 74614639, 99549373, 2891150, 29516592, 8373289, 98462867, 17957593, 35092039, 75128745, 73235980, 3183975, 9259676, 1936762, 11161731, 53393358, 31733363, 29959549, 66322959, 61815223, 91664334, 23194618, 79322415, 62428472, 26292919, 22129328, 14349098, 4434662, 69579137, 90013093, 61623915, 91141395, 44177011, 70727211, 37560164, 63967300, 7182642, 30694952, 77301523, 62936963, 31491938, 17976208, 83533741 +3294781, 66611759, 43501211, 47361209, 49328608, 6038457, 19898053, 89214616, 50188404, 72373496, 94911072, 31904591, 61859581, 91727510, 8099994, 40865610, 48395186, 8055981, 96735716, 14626618, 1197320, 21993752, 31733363, 67031644, 47213183, 61263068, 77301523, 42967683, 5970607, 27041967, 91938191, 24953498, 61982238, 57658654, 22129328, 21397057, 96318094, 2917920, 99917599, 5267545, 36139942, 19376156, 41092172, 57241521, 71965942, 38510840, 78785507, 53084256, 75128745, 91802888, 55470718, 26734892, 86460488, 48360198, 78589145, 9575954, 63059024, 43045786, 43933006, 89804152, 76703609, 13468268, 54427233, 9175338, 22766820, 37620363, 20122224, 30811010, 92033260, 51466049, 37435892, 20645197, 46870723, 94076128, 97057187, 44983451, 11365791, 19457589, 36753250, 77300457, 82327024, 85242963, 94516935, 29516592, 8634541, 44348328, 51315460, 67084351, 42237907, 13862149, 28734791, 11161731, 51590803, 24591705, 24058273, 78884452, 85695762, 20486294, 44177011, 13348726, 569864, 23134715, 34698463, 99729357, 874791, 80555751, 83948335, 91957544, 70372191, 42692881, 5073754, 70541760, 43376279, 16424199, 41481685, 82532312, 56153345, 14731700, 36930650, 33201905, 8128637, 26292919, 32151165, 247198, 16380211, 48893685, 99125126, 71083565, 68824981, 26102057, 4434662, 45667668, 75820087, 3509435, 84684495, 21001913, 43357947, 72019362, 62357986, 9623492, 36312813, 54199160, 3183975, 26114953, 9259676, 66903004, 95290172, 26392416, 36580610, 57393458, 37280276, 20885148, 27325428, 62430984, 16595436, 38658347, 61373987, 75135448, 24826575, 76434144, 92071990, 31161687, 29932657, 47887470, 77312810, 7300047, 3233569, 98653983, 79880247, 73617245, 62740044, 36845587, 38365584, 15535065, 9188443, 48658605, 79136082, 18783702, 50007421, 56461322, 47090124, 7011964, 89811711, 22113133, 38022834, 32058615, 92692978, 27665211, 63015256, 76960303, 68204242, 16567550, 95726235, 8733068, 99524975, 43152977, 1431742, 40781854, 20002147, 34497327, 11581181, 99333375, 33565483, 17037369, 50567636, 67513640, 57240218, 99515901, 34946859, 40534591, 79821897, 94595668, 2717150, 56515456, 18833224, 74614639, 90310261, 23432750, 4787945, 16405341, 76540481, 26139110, 90272749, 33431960, 80251430, 69579137, 96852321, 27185644, 33304202, 35092039, 28550822, 10961421, 45237957, 68702632, 79657802, 26776922, 30998561, 96420429, 55602660, 78602717, 68816413, 47792865, 36468541, 36933038, 88444207, 17976208, 9860195, 10309525, 18466635, 68128438, 34493392, 30764367, 81853704, 65715134, 6153406, 53393358, 62232211, 30891921, 80246713, 8807940, 30395570, 176257, 87598888, 62552524, 52613508, 78561158, 13819358, 17016156, 86543538, 4204661, 17058722, 82052050, 36685023, 76671482, 66271566, 55615885, 72614359, 62936963, 415901, 92541302, 39847321, 7685448, 54868730, 92998591, 12303248, 44889423, 73109997, 6505939, 67281495, 23740167, 99965001, 25636669, 17146629, 59329511, 29466406, 14045383, 72732205, 4119747, 71920426, 33935899, 49271185, 7687278, 1204161, 19272365, 99021067, 23194618, 72777973, 29994197, 83302115, 60393039, 31491938, 4668450, 6819644, 67030811, 73021291, 59501487, 77072625, 9603598, 77187825, 68939068, 15536795, 12664567, 6986898, 96709982, 76330843, 47738185, 40152546, 34432810, 168541, 50806615, 90090964, 8696647, 68694897, 45306070, 56531125, 29065964, 57020481, 45049308, 526217, 43506672, 93566986, 26664538, 59109400, 60176618, 8115266, 13470059, 35512853, 4806458, 48673079, 59371804, 80316608, 18001617, 21533347, 17068582, 76315420, 16445503, 84840549, 28928175, 75153252, 34698428, 66045587, 82427263, 23569917, 8651647, 98943869, 83150534, 54014062, 64848072, 9829782, 48675329, 4199704, 53802686, 32250045, 90061527, 17365188, 97011160, 15163258, 54663246, 98130363, 20681921, 3633375, 73392814, 55770687, 21673260, 15902805, 38009615, 70004753, 99861373, 15064655, 8729146, 64157906, 68000591, 66116458, 8129978, 7423788, 10649306, 98739783, 92867155, 61728685, 1022677, 37183543, 93933709, 6111563, 39171895, 26275734, 72793444, 30543215, 11543098, 26063929, 2331773, 36505482, 77620120, 72738685, 45428665, 29635537, 57163802, 85116755, 51047803, 96906410, 99690194, 32426752, 66885828, 12348118, 74724075, 70367851, 83747892, 55960386, 56473732, 4508700, 33237508, 77787724, 91664334, 78766358, 88047921, 16054533, 82651278, 4091162, 27625735, 79806380, 66428795, 4069912, 16684829, 50668599, 72238278, 40356877, 45919976, 96193415, 97783876, 38256849, 87710366, 83269727, 89699445, 26744917, 39986008, 84127901, 17386996, 83155442, 1250437, 14349098, 30771409, 10453030, 99971982, 61897582, 22721500, 9398733, 67124282, 5111370, 62693428, 9886593, 44627776, 32274392, 39553046, 15148031, 44842615, 83210802, 17764950, 97940276, 66137019, 33336148, 44473167, 29510992, 17539625, 58208470, 92215320, 57961282, 88698958, 17894977, 90457870, 68102437, 3487592, 58749, 88653118, 66667729, 81805959, 30694952, 60106217, 59981773, 75229462, 14326617, 30366150, 84406788, 63628376, 51472275, 37957788, 92398073, 34667286, 77272628, 81677380, 22997281, 89996536, 59614746, 34236719, 38008118, 6525948, 17857111, 7919588, 20836893, 32699744, 65338021, 45794415, 89499542, 82178706, 44844121, 12416768, 99604946, 33061250, 24619760, 30787683, 93790285, 40027975, 76170907, 88130087, 22108665, 61928316, 68875490, 92554120, 20765474, 29959549, 37675718, 94360702, 48088883, 58180653, 66322959, 84293052, 84904436, 4798568, 44847298, 78909561, 35192533, 30463802, 48892201, 73124510, 92692283, 34295794, 75986488, 71300104, 69786756, 42073124, 63967300, 7646095, 37501808, 6808825, 86118021, 58751351, 68644627, 18131876, 74357852, 81774825, 61859143, 37659250, 86798033, 14363867, 85571389, 39201414, 94711842, 55247455, 74441448, 78300864, 86242799, 66319530, 98948034, 66663942, 62762857, 45996863, 52261574, 48774913, 82779622, 5832946, 6871053, 67793644, 97641116, 669105, 91255408, 4985896, 74743862, 92353856, 72358170, 88904910, 95251277, 65038678, 44481640, 24733232, 32161669, 93057697, 10366309, 84349107, 69136837, 76825057, 51588015, 99549373, 45407418, 92787493, 20642888, 83533741, 2891150, 22435353, 49598724, 68041839, 97281847, 8373289, 94090109, 19891772, 98462867, 3233084, 83133790, 93359396, 49882705, 88251446, 99658235, 61623915, 95581843, 508198, 2208785, 4515343, 60430369, 79922758, 7229550, 74137926, 20568363, 1936762, 91831487, 21919959, 1788101, 70036438, 36189527, 6497830, 6157724, 65454636, 94539824, 61712234, 67227442, 53547802, 73222868, 68110330, 38061439, 3773993, 89046466, 83789391, 70727211, 39801351, 42644903, 65275241, 62051033, 62803858, 75777973, 73168565, 82886381, 89217461, 85117092, 42307449, 51507425, 80014588, 30653863, 65081429, 19101477, 1239555, 32590267, 29188588, 6793819, 92604458, 59177718, 112651, 77694700, 71522968, 51464002, 33699435, 39373729, 95957797, 71333116, 52204879, 96726697, 49641577, 77898274, 74110882, 24314885, 18699206, 47298834, 1375023, 30218878, 20140249, 62496012, 99226875, 70420215, 54606384, 56955985, 50702367, 40686254, 17385531, 4064751, 17727650, 44060493, 46540998, 54058788, 73031054, 30163921, 66832478, 72278539, 14220886, 44550764, 82897371, 16387575, 82339363, 40677414, 90158785, 83651211, 22405120, 35996293, 75543508, 30139692, 13173644, 13231279, 86001008, 63372756, 93270084, 66250369, 28787861, 28796059, 42782652, 62115552, 75052463, 38494874, 9860968, 85023028, 42947632, 14093520, 70782102, 61271144, 26397786, 10358899, 44915235, 15075176, 59405277, 32159704, 84166196, 64098930, 14723410, 69848388, 22942635, 40984766, 54263880, 55753905, 59197747, 30090481, 98648327, 77377183, 45990383, 75407347, 95395112, 60581278, 82979980, 42199455, 55189057, 40197395, 51149804, 48260151, 16019925, 84002370, 46851987, 26507214, 49597667, 23110625, 18411915, 98371444, 43322743, 27187213, 47708850, 81172706, 2204165, 36808486, 34044787, 2607799, 31126490, 7182642, 55143724, 85711894, 68316156, 7517032, 90004325, 69355476, 10264691, 67474219, 77284619, 5822202, 56424103, 79322415, 31727379, 84187166, 45848907, 53632373, 59318837, 37891451, 69697787, 89637706, 65017137, 54517921, 45075471, 83368048, 79191827, 92530431, 9599614, 23848565, 91240048, 92803766, 72725103, 9058407, 4095116, 79942022, 14947650, 78218845, 81855445, 19939935, 17957593, 90013093, 72274002, 87160386, 11923835, 65271999, 6221471, 7104732, 58224549, 73235980, 44664587, 3088684, 53842979, 57248122, 78549759, 89419466, 91990218, 49236559, 67451935, 72495719, 15015906, 70596786, 69605283, 21070110, 22879907, 62490109, 1569515, 79738755, 37224844, 65880522, 38341669, 55850790, 33123618, 33553959, 60102965, 8913721, 63506281, 7955293, 33249630, 18663507, 63152504, 51426311, 41442762, 71316369, 36135, 52060076, 57359924, 40781449, 90654836, 28851716, 65074535, 29401781, 73786944, 824872, 55669657, 41092102, 86022504, 77413012, 57803235, 17081350, 71657078, 45995325, 65851721, 53888755, 54987042, 86821229, 321665, 44846932, 61141874, 36812683, 61960400, 38645117, 93053405, 33797252, 26863229, 25842078, 51715482, 22450468, 91141395, 54762643, 81274566, 95010552, 22200378, 47893286, 15948937, 35456853, 53648154, 81898046, 64087743, 20867149, 12571310, 19486173, 5482538, 44784505, 30569392, 86301513, 21289531, 53666583, 52293995, 37739481, 37560164, 44245960, 41245325, 4099191, 8791066, 29834512, 16971929, 97379790, 24915585, 65047700, 87720882, 18806856, 12024238, 56484900, 72357096, 37192445, 45381876, 10597197, 6521313, 91281584, 70800879, 45790169, 45617087, 3880712, 33895336, 4978543, 3235882, 5640302, 16097038, 61815223, 16099750, 29029316, 71469330, 93562257, 7066775, 54232247, 83083131, 24168411, 89078848, 99224392, 29819940, 7502255, 27411561, 26493119, 26998766, 33628349, 749283, 64602895, 69255765, 41380093, 18504197, 9257405, 64055960, 11757872, 74452589, 37166608, 36780454, 36396314, 82726008, 69641513, 93515664, 61741594, 59910624, 44479073, 60955663, 62452034, 97561745, 62428472, 99297164 +77413012, 73124510, 74724075, 59109400, 72495719, 13819358, 7300047, 84002370, 56531125, 29065964, 41092172, 23569917, 36580610, 73392814, 20765474, 23134715, 63506281, 35192533, 415901, 15535065, 98371444, 89214616, 54868730, 77694700, 79136082, 70420215, 57658654, 11581181, 50567636, 22129328, 6871053, 62693428, 16387575, 92530431, 23848565, 5267545, 36139942, 92803766, 60176618, 3509435, 98462867, 68702632, 26392416, 91990218, 18466635, 14626618, 89996536, 22879907, 93790285, 76170907, 13348726, 47213183, 7423788, 39801351, 31161687, 34698463, 29029316, 81172706, 17146629, 92692978, 79806380, 91938191, 65074535, 50668599, 47298834, 56484900, 4668450, 66663942, 54606384, 87710366, 37891451, 9886593, 91281584, 43501211, 61960400, 83368048, 93566986, 68824981, 69136837, 13470059, 90158785, 35996293, 94516935, 35092039, 53084256, 58749, 48395186, 42782652, 96420429, 33628349, 1936762, 91831487, 59981773, 55470718, 14326617, 10358899, 59405277, 77272628, 26734892, 88130087, 77377183, 9575954, 95395112, 73168565, 77301523, 51149804, 26063929, 72738685, 70372191, 12303248, 51464002, 55960386, 7011964, 99297164, 16054533, 7687278, 86798033, 92033260, 72373496, 29994197, 9603598, 38256849, 33201905, 99333375, 84187166, 15536795, 94076128, 97641116, 6521313, 56515456, 40677414, 51588015, 97940276, 91727510, 80316608, 93053405, 33336148, 57241521, 19891772, 84684495, 83133790, 72274002, 16445503, 72019362, 61623915, 11923835, 8055981, 95581843, 28787861, 26776922, 60430369, 62115552, 20568363, 68816413, 47792865, 13862149, 37957788, 90061527, 20836893, 3633375, 38658347, 21673260, 40984766, 86460488, 30395570, 78589145, 98648327, 42644903, 55850790, 89217461, 60102965, 26275734, 17058722, 85117092, 84293052, 62740044, 30463802, 91957544, 6793819, 57163802, 59177718, 33249630, 44245960, 47708850, 70367851, 71522968, 7646095, 33237508, 27041967, 52204879, 18131876, 37659250, 69355476, 82532312, 63015256, 76960303, 99021067, 8733068, 74441448, 6819644, 67030811, 67513640, 47738185, 21397057, 34432810, 86821229, 91255408, 4985896, 14220886, 44550764, 2917920, 65017137, 36753250, 45049308, 32274392, 44481640, 15148031, 24733232, 26664538, 82327024, 61859581, 44842615, 76825057, 92787493, 20642888, 4095116, 26139110, 68041839, 30139692, 8634541, 80251430, 13173644, 29510992, 69579137, 81855445, 21533347, 3487592, 34698428, 9623492, 66250369, 79657802, 53842979, 30998561, 3183975, 38494874, 98943869, 75229462, 83150534, 26397786, 67084351, 30366150, 63628376, 11161731, 32250045, 27325428, 51590803, 22997281, 30764367, 54663246, 24591705, 81853704, 24058273, 89046466, 87598888, 55753905, 64157906, 68000591, 45990383, 52613508, 69255765, 75407347, 30569392, 33123618, 17016156, 93933709, 72793444, 13468268, 84904436, 7955293, 48892201, 92692283, 39847321, 9175338, 9188443, 92604458, 99690194, 41245325, 70541760, 23740167, 18783702, 56473732, 8791066, 4508700, 39373729, 22113133, 59329511, 29466406, 43376279, 16424199, 32058615, 82651278, 72732205, 19272365, 50188404, 23194618, 39201414, 12024238, 31491938, 64055960, 78300864, 55669657, 24168411, 31727379, 99515901, 48774913, 5832946, 54058788, 32151165, 96318094, 16380211, 94595668, 168541, 61897582, 2717150, 48893685, 61141874, 45075471, 99917599, 10366309, 19376156, 4787945, 17764950, 22405120, 9058407, 48673079, 83533741, 66137019, 90272749, 8373289, 44348328, 69641513, 71965942, 17894977, 51715482, 84840549, 10961421, 91141395, 54762643, 44664587, 93270084, 508198, 78549759, 9259676, 9860968, 42947632, 57393458, 47893286, 51472275, 4199704, 93515664, 28734791, 15075176, 20885148, 84166196, 98130363, 65715134, 6153406, 80246713, 64087743, 30787683, 31733363, 59197747, 48360198, 65880522, 5482538, 22108665, 86301513, 43045786, 10649306, 75777973, 47887470, 1022677, 37675718, 16097038, 94360702, 48088883, 89804152, 8913721, 42307449, 99729357, 80014588, 73617245, 72614359, 36505482, 61815223, 83948335, 38365584, 45428665, 7685448, 22766820, 5073754, 93562257, 67281495, 99965001, 86118021, 56461322, 47090124, 33699435, 34044787, 71316369, 68644627, 95957797, 71333116, 2607799, 96726697, 14045383, 97379790, 49641577, 77898274, 61859143, 4091162, 20122224, 18699206, 49271185, 65047700, 14363867, 68204242, 16567550, 83302115, 94711842, 60393039, 99524975, 43152977, 37435892, 40781854, 67474219, 3294781, 824872, 59501487, 99226875, 34497327, 77187825, 89699445, 33565483, 39986008, 45848907, 26292919, 57803235, 6986898, 14349098, 79821897, 10597197, 73031054, 669105, 11365791, 68694897, 45306070, 5111370, 72358170, 57020481, 82339363, 32161669, 91240048, 83651211, 99549373, 4806458, 85242963, 27411561, 75543508, 45790169, 4434662, 49598724, 97281847, 92215320, 75820087, 96852321, 3233084, 25842078, 33304202, 68102437, 45237957, 3088684, 36312813, 28796059, 3880712, 49328608, 55602660, 74137926, 81805959, 26114953, 30694952, 51315460, 95010552, 36468541, 36933038, 1788101, 37280276, 36189527, 44915235, 67451935, 92398073, 53802686, 17365188, 68128438, 94539824, 6525948, 69848388, 7919588, 61712234, 15015906, 21993752, 21070110, 35456853, 73222868, 81898046, 62490109, 38061439, 24619760, 54263880, 44177011, 70727211, 79738755, 12571310, 75135448, 76434144, 67031644, 92071990, 66116458, 5640302, 65275241, 29932657, 569864, 86543538, 6111563, 43933006, 39171895, 4204661, 98653983, 42199455, 36685023, 76671482, 66271566, 11543098, 48260151, 54427233, 51507425, 2331773, 46851987, 80555751, 37560164, 41380093, 29635537, 51047803, 71300104, 112651, 42692881, 66885828, 37620363, 83747892, 7066775, 50007421, 4099191, 41442762, 5970607, 88047921, 55143724, 36135, 57359924, 44479073, 9257405, 73786944, 10264691, 95726235, 1375023, 86242799, 14731700, 5822202, 11757872, 62762857, 37166608, 68939068, 89078848, 53632373, 96709982, 17385531, 52261574, 82726008, 46870723, 29819940, 71657078, 40534591, 59318837, 40152546, 99971982, 97057187, 67793644, 54987042, 90090964, 67124282, 321665, 89637706, 62452034, 526217, 65038678, 18833224, 43506672, 54517921, 9599614, 93057697, 90310261, 8115266, 35512853, 16405341, 26102057, 79942022, 59371804, 45667668, 29516592, 78218845, 17539625, 58208470, 19939935, 17957593, 21001913, 27185644, 8099994, 78785507, 49882705, 88251446, 28928175, 40865610, 28550822, 75153252, 75128745, 63372756, 62357986, 75052463, 85023028, 60106217, 21919959, 54014062, 4978543, 17976208, 6157724, 65454636, 10309525, 15948937, 62430984, 15163258, 34236719, 38008118, 64098930, 67227442, 55770687, 69605283, 53648154, 22942635, 44844121, 38009615, 33061250, 1569515, 70004753, 20867149, 37224844, 30090481, 62552524, 3235882, 8129978, 98739783, 38341669, 61728685, 60581278, 21289531, 61263068, 42967683, 3233569, 58180653, 82052050, 65081429, 26507214, 4798568, 44847298, 36845587, 37739481, 19101477, 61741594, 32590267, 49597667, 23110625, 29188588, 48658605, 96906410, 69786756, 63967300, 27187213, 12348118, 18663507, 37501808, 73109997, 6505939, 6808825, 18504197, 58751351, 91664334, 59910624, 41481685, 68316156, 7517032, 52060076, 90654836, 71920426, 27665211, 24314885, 4069912, 56153345, 72777973, 45919976, 24953498, 73021291, 62496012, 56424103, 77072625, 41092102, 86022504, 26744917, 56955985, 12664567, 50702367, 17386996, 7502255, 247198, 66832478, 92353856, 8696647, 94911072, 88904910, 19457589, 39553046, 31904591, 84349107, 83210802, 23432750, 72725103, 76540481, 70800879, 33797252, 18001617, 97561745, 33431960, 47361209, 13231279, 88698958, 26863229, 86001008, 90013093, 17068582, 93359396, 6221471, 73235980, 54199160, 82427263, 4515343, 79922758, 78602717, 95290172, 61271144, 9829782, 17857111, 20681921, 65338021, 45794415, 53393358, 78884452, 30891921, 15902805, 8807940, 12416768, 3773993, 99861373, 40027975, 19486173, 64602895, 61928316, 92554120, 62803858, 82886381, 82979980, 53666583, 55189057, 52293995, 16019925, 66322959, 874791, 79880247, 62936963, 78909561, 77620120, 34295794, 75986488, 32426752, 42073124, 43322743, 44889423, 71469330, 63152504, 51426311, 89811711, 38022834, 77787724, 16971929, 74357852, 7182642, 30811010, 54232247, 33935899, 1204161, 16684829, 87720882, 40356877, 51466049, 55247455, 20645197, 83083131, 60955663, 20002147, 66319530, 77284619, 98948034, 61982238, 96193415, 36780454, 45381876, 36396314, 57240218, 40686254, 76330843, 30771409, 34946859, 10453030, 44983451, 72278539, 74743862, 44846932, 36812683, 95251277, 74614639, 26998766, 65271999, 66611759, 58224549, 33895336, 66045587, 7229550, 8651647, 66903004, 81274566, 89419466, 14093520, 88444207, 42237907, 84406788, 22200378, 749283, 9860195, 81677380, 34493392, 14723410, 70596786, 89499542, 85695762, 20486294, 61373987, 15064655, 8729146, 44784505, 78561158, 62051033, 92867155, 29959549, 77312810, 33553959, 37183543, 40197395, 1239555, 92541302, 18411915, 92998591, 24915585, 4119747, 1431742, 72357096, 36930650, 79322415, 97783876, 83269727, 17037369, 37192445, 45996863, 1250437, 4064751, 17727650, 45995325, 65851721, 9398733, 99125126, 77300457, 79191827, 45407418, 14947650, 2891150, 22435353, 94090109, 57961282, 38510840, 76315420, 90457870, 22450468, 99658235, 88653118, 7104732, 66667729, 96735716, 70782102, 49236559, 48675329, 6497830, 34667286, 97011160, 32159704, 1197320, 16595436, 19898053, 82178706, 68110330, 99604946, 63059024, 30543215, 76703609, 85116755, 25636669, 90004325, 40781449, 28851716, 74110882, 66428795, 29401781, 72238278, 85571389, 30218878, 74452589, 62428472, 8128637, 83155442, 82779622, 44060493, 46540998, 50806615, 22721500, 82897371, 44627776, 71083565, 44473167, 87160386, 2208785, 57248122, 64848072, 70036438, 32699744, 62232211, 68875490, 30653863, 2204165, 36808486, 31126490, 81774825, 27625735, 17081350, 53888755, 69697787, 26493119, 91802888, 6038457, 176257, 24826575, 55615885, 29834512, 78766358, 85711894, 18806856, 20140249, 99224392, 45617087, 43357947, 53547802, 83789391, 30163921, 59614746, 16099750, 84127901, 38645117 +65047700, 66322959, 65851721, 4985896, 44348328, 8055981, 27325428, 30395570, 31733363, 73109997, 33201905, 83269727, 73031054, 4095116, 26776922, 82886381, 43322743, 32058615, 2917920, 62693428, 72725103, 27411561, 85023028, 12416768, 86460488, 24826575, 17058722, 30543215, 52293995, 84002370, 36505482, 34295794, 7646095, 93562257, 4508700, 71920426, 68204242, 51466049, 8733068, 67474219, 22721500, 6521313, 77300457, 68824981, 8115266, 99549373, 97940276, 97561745, 80251430, 27185644, 62357986, 68702632, 95581843, 14326617, 36580610, 4199704, 11161731, 22997281, 5482538, 9575954, 78561158, 63059024, 13819358, 42644903, 6111563, 43933006, 55189057, 37739481, 61741594, 69786756, 77694700, 2204165, 37501808, 25636669, 17146629, 61859143, 91938191, 77284619, 99333375, 67513640, 54058788, 10453030, 91255408, 90090964, 45049308, 526217, 26664538, 9599614, 83210802, 4787945, 68041839, 33431960, 13231279, 3509435, 98462867, 3233084, 33304202, 84840549, 53084256, 75128745, 11923835, 3880712, 33628349, 9259676, 98943869, 67084351, 91990218, 10358899, 10309525, 68128438, 65715134, 3633375, 24058273, 53393358, 62232211, 81898046, 30891921, 40984766, 30787683, 61373987, 20867149, 12571310, 48360198, 66116458, 7423788, 98739783, 77301523, 89217461, 82979980, 60102965, 58180653, 11543098, 63506281, 84904436, 44847298, 91957544, 39847321, 42073124, 112651, 92998591, 33699435, 58751351, 71316369, 59329511, 27041967, 14045383, 16424199, 74110882, 7687278, 14363867, 50188404, 50668599, 73786944, 99524975, 37435892, 66319530, 30218878, 73021291, 824872, 62496012, 39986008, 45996863, 15536795, 26292919, 48774913, 21397057, 37891451, 34432810, 65038678, 18833224, 82339363, 10366309, 23848565, 84349107, 69136837, 4806458, 35996293, 94516935, 59371804, 90272749, 30139692, 13173644, 29510992, 69579137, 47361209, 51715482, 87160386, 78785507, 63372756, 3183975, 23569917, 30694952, 38494874, 66903004, 68816413, 59981773, 30366150, 64848072, 65454636, 92398073, 32159704, 38008118, 6525948, 69848388, 20836893, 20681921, 16595436, 99604946, 59197747, 19486173, 22108665, 44784505, 8129978, 10649306, 95395112, 20765474, 60581278, 33123618, 37675718, 77312810, 42967683, 72793444, 82052050, 79880247, 30653863, 65081429, 62740044, 35192533, 415901, 38365584, 45428665, 18411915, 32426752, 5073754, 44245960, 51464002, 83747892, 47090124, 41442762, 85711894, 68316156, 24915585, 69355476, 24314885, 33935899, 65074535, 23194618, 72777973, 72373496, 40356877, 10264691, 20645197, 78300864, 4668450, 59501487, 5822202, 66663942, 96193415, 57658654, 79322415, 77187825, 41092102, 24168411, 84187166, 12664567, 32151165, 96318094, 92353856, 45306070, 89637706, 72358170, 43501211, 61960400, 71083565, 39553046, 82327024, 61859581, 40677414, 59109400, 90158785, 17764950, 41092172, 70800879, 83533741, 2891150, 22435353, 8373289, 21533347, 92215320, 75820087, 25842078, 38510840, 17894977, 49882705, 75153252, 34698428, 65271999, 54762643, 73235980, 9623492, 36312813, 79657802, 33895336, 6038457, 62115552, 75052463, 91831487, 60106217, 89419466, 42947632, 14093520, 47792865, 55470718, 95290172, 1788101, 84406788, 47893286, 6497830, 67451935, 37957788, 4978543, 90061527, 17365188, 97011160, 54663246, 98130363, 24591705, 81853704, 67227442, 45794415, 19898053, 78884452, 85695762, 38061439, 64157906, 67031644, 13348726, 98648327, 61928316, 45990383, 75407347, 68875490, 31161687, 21289531, 29959549, 61263068, 86543538, 48088883, 26275734, 76671482, 51149804, 99729357, 54427233, 46851987, 36845587, 61815223, 19101477, 49597667, 92692283, 77620120, 72738685, 29188588, 29635537, 9188443, 48658605, 89214616, 92604458, 54868730, 12303248, 47708850, 71522968, 70541760, 7066775, 18783702, 86118021, 56473732, 7011964, 99297164, 34044787, 33237508, 95957797, 77787724, 16971929, 96726697, 97379790, 49641577, 77898274, 52060076, 90004325, 20122224, 30811010, 27625735, 72732205, 4119747, 79806380, 92033260, 4069912, 74441448, 1431742, 36930650, 55669657, 68939068, 26744917, 50567636, 45848907, 89078848, 96709982, 1250437, 29819940, 34946859, 168541, 86821229, 11365791, 48893685, 68694897, 56531125, 9886593, 61141874, 29065964, 32274392, 15148031, 92803766, 60176618, 76825057, 13470059, 23432750, 92787493, 85242963, 45790169, 93053405, 29516592, 78218845, 26863229, 17068582, 43357947, 88251446, 61623915, 10961421, 7104732, 45237957, 93270084, 3088684, 66250369, 96735716, 42782652, 96420429, 79922758, 91802888, 55602660, 26114953, 78549759, 9860968, 70782102, 88444207, 13862149, 9829782, 48675329, 36189527, 62430984, 84166196, 34236719, 64098930, 7919588, 89499542, 55770687, 69605283, 21993752, 20486294, 53648154, 62490109, 15902805, 33061250, 1569515, 79738755, 93790285, 76170907, 75135448, 55753905, 65880522, 30090481, 52613508, 92071990, 86301513, 43045786, 92867155, 62803858, 73168565, 61728685, 55850790, 47887470, 7300047, 89804152, 36685023, 8913721, 874791, 1239555, 15535065, 6793819, 57163802, 16099750, 74724075, 29029316, 18663507, 79136082, 51426311, 99965001, 8791066, 39373729, 89811711, 22113133, 38022834, 71333116, 52204879, 7182642, 55143724, 41481685, 36135, 81774825, 82651278, 92692978, 49271185, 66428795, 16684829, 56153345, 29401781, 39201414, 18806856, 86242799, 60955663, 6819644, 67030811, 3294781, 72357096, 61982238, 77072625, 62762857, 31727379, 33565483, 84127901, 57240218, 40686254, 99224392, 17727650, 30771409, 59318837, 16380211, 10597197, 66832478, 54987042, 44983451, 72278539, 44550764, 74743862, 67124282, 5111370, 321665, 88904910, 91281584, 92530431, 44481640, 24733232, 44842615, 93057697, 19376156, 51588015, 9058407, 66137019, 26139110, 80316608, 33797252, 49598724, 33336148, 26493119, 44473167, 57241521, 17539625, 58208470, 84684495, 96852321, 17957593, 35092039, 72274002, 76315420, 93359396, 68102437, 28928175, 6221471, 66045587, 30998561, 508198, 81805959, 81274566, 75229462, 42237907, 57393458, 63628376, 51472275, 44915235, 28734791, 15075176, 59405277, 34667286, 14626618, 89996536, 26734892, 17857111, 15015906, 32699744, 44844121, 54263880, 44177011, 176257, 70727211, 99861373, 8729146, 78589145, 68000591, 62552524, 3235882, 30569392, 92554120, 17016156, 1022677, 37183543, 16097038, 94360702, 40197395, 66271566, 16019925, 73617245, 62936963, 48892201, 83948335, 37560164, 92541302, 51047803, 71300104, 70372191, 63967300, 42692881, 66885828, 12348118, 81172706, 71469330, 63152504, 41245325, 56461322, 29466406, 18131876, 91664334, 74357852, 78766358, 31126490, 7517032, 40781449, 28851716, 54232247, 19272365, 63015256, 44479073, 99021067, 87720882, 72238278, 9257405, 85571389, 83302115, 47298834, 56484900, 24953498, 64055960, 83083131, 40781854, 14731700, 9603598, 74452589, 38256849, 86022504, 87710366, 36780454, 37192445, 45381876, 6986898, 17081350, 82726008, 22129328, 6871053, 45995325, 247198, 94076128, 97057187, 97641116, 53888755, 669105, 2717150, 82897371, 8696647, 56515456, 94911072, 44846932, 57020481, 36753250, 16387575, 95251277, 79191827, 32161669, 48673079, 76540481, 26102057, 45667668, 8634541, 81855445, 57961282, 88698958, 83133790, 71965942, 21001913, 22450468, 26998766, 28550822, 91141395, 66611759, 48395186, 28787861, 49328608, 82427263, 2208785, 78602717, 36468541, 22200378, 70036438, 6157724, 53802686, 15948937, 32250045, 77272628, 94539824, 30764367, 14723410, 1197320, 53547802, 65338021, 73392814, 38658347, 80246713, 8807940, 68110330, 64087743, 70004753, 87598888, 37224844, 15064655, 64602895, 88130087, 47213183, 69255765, 5640302, 65275241, 38341669, 569864, 23134715, 3233569, 42199455, 51507425, 80014588, 2331773, 80555751, 78909561, 30463802, 41380093, 9175338, 85116755, 98371444, 7685448, 96906410, 70367851, 18504197, 67281495, 55960386, 4099191, 68644627, 5970607, 2607799, 16054533, 59910624, 4091162, 82532312, 27665211, 18699206, 86798033, 45919976, 16567550, 60393039, 55247455, 98948034, 20140249, 56424103, 54606384, 89699445, 17037369, 36396314, 17386996, 52261574, 76330843, 14349098, 47738185, 44060493, 71657078, 46540998, 40152546, 67793644, 50806615, 61897582, 9398733, 44627776, 36812683, 83368048, 93566986, 90310261, 38645117, 45407418, 20642888, 14947650, 75543508, 4434662, 45617087, 19891772, 69641513, 19939935, 90013093, 99658235, 88653118, 66667729, 53842979, 60430369, 57248122, 1936762, 83150534, 95010552, 54014062, 17976208, 9860195, 81677380, 34493392, 61712234, 6153406, 73222868, 82178706, 38009615, 3773993, 76434144, 77377183, 62051033, 75777973, 29932657, 93933709, 85117092, 26063929, 84293052, 4798568, 72614359, 32590267, 59177718, 22766820, 44889423, 23740167, 50007421, 29834512, 29994197, 95726235, 12024238, 31491938, 1375023, 8128637, 77413012, 46870723, 4064751, 5832946, 79821897, 94595668, 14220886, 62452034, 43506672, 74614639, 54517921, 45075471, 5267545, 16405341, 97281847, 18001617, 94090109, 8099994, 72019362, 54199160, 4515343, 7229550, 20568363, 61271144, 26397786, 26392416, 49236559, 37280276, 93515664, 51590803, 72495719, 15163258, 21070110, 24619760, 33553959, 39171895, 4204661, 53666583, 55615885, 23110625, 99690194, 33249630, 6808825, 88047921, 90654836, 1204161, 76960303, 94711842, 20002147, 34497327, 70420215, 97783876, 53632373, 57803235, 83155442, 99515901, 82779622, 7502255, 40534591, 99971982, 99917599, 31904591, 91240048, 83651211, 22405120, 91727510, 86001008, 16445503, 40865610, 3487592, 58224549, 44664587, 51315460, 8651647, 21919959, 36933038, 18466635, 35456853, 22942635, 89046466, 40027975, 39801351, 98653983, 34698463, 76703609, 13468268, 26507214, 7955293, 75986488, 27187213, 37620363, 57359924, 37659250, 43152977, 99226875, 37166608, 11581181, 56955985, 17385531, 65017137, 99125126, 36139942, 35512853, 58749, 28796059, 20885148, 59614746, 70596786, 22879907, 21673260, 83789391, 42307449, 30163921, 69697787, 79942022, 749283, 48260151, 73124510, 36808486, 6505939, 11757872, 62428472, 50702367, 19457589, 90457870, 43376279, 74137926 +74614639, 59614746, 54232247, 69136837, 22450468, 53084256, 7423788, 6793819, 99971982, 54987042, 9398733, 77300457, 76825057, 40865610, 53842979, 74137926, 32699744, 15064655, 92692283, 7646095, 32058615, 52060076, 99515901, 96318094, 11365791, 56515456, 8115266, 45617087, 81855445, 19939935, 21001913, 72274002, 93359396, 58224549, 66045587, 51472275, 93515664, 92398073, 65338021, 86460488, 92071990, 8129978, 65275241, 26275734, 30543215, 83948335, 29188588, 85116755, 7685448, 89214616, 112651, 42692881, 47090124, 17146629, 28851716, 27625735, 12024238, 34497327, 11757872, 97783876, 11581181, 84187166, 77413012, 53632373, 45995325, 73031054, 48893685, 89637706, 62452034, 36812683, 83210802, 38645117, 23432750, 16405341, 85242963, 47361209, 88698958, 79657802, 15163258, 54663246, 98130363, 55770687, 8807940, 99604946, 3773993, 176257, 79738755, 30090481, 45990383, 3235882, 37675718, 52293995, 48260151, 26063929, 63506281, 46851987, 44847298, 36845587, 35192533, 9175338, 70372191, 42073124, 74724075, 47708850, 18663507, 36808486, 33237508, 29834512, 43376279, 88047921, 24915585, 63015256, 99021067, 5822202, 99226875, 86022504, 33565483, 56955985, 45381876, 8128637, 50702367, 17386996, 14349098, 65851721, 50806615, 61897582, 88904910, 61960400, 54517921, 45075471, 82339363, 15148031, 24733232, 26664538, 10366309, 72725103, 45407418, 9058407, 26102057, 35996293, 2891150, 18001617, 21533347, 38510840, 33304202, 75153252, 91141395, 6221471, 62357986, 73235980, 57248122, 78602717, 68816413, 75229462, 48675329, 34667286, 65715134, 3633375, 73392814, 78884452, 38061439, 70727211, 64602895, 62552524, 75407347, 68875490, 13819358, 1022677, 55189057, 16019925, 13468268, 55615885, 62740044, 61741594, 38365584, 23110625, 72738685, 63967300, 92998591, 33249630, 77694700, 44889423, 71469330, 23740167, 50007421, 4099191, 7011964, 4508700, 99297164, 58751351, 18131876, 55143724, 97379790, 57359924, 37659250, 4069912, 23194618, 47298834, 1375023, 43152977, 60955663, 40781854, 4668450, 14731700, 59501487, 96193415, 70420215, 74452589, 41092102, 37166608, 68939068, 57803235, 96709982, 4064751, 67793644, 66832478, 44983451, 14220886, 6521313, 44550764, 69697787, 2917920, 321665, 72358170, 45049308, 43501211, 32274392, 31904591, 68824981, 61859581, 19376156, 13470059, 14947650, 91727510, 45790169, 49598724, 26493119, 57961282, 84684495, 26863229, 83133790, 8099994, 90457870, 87160386, 78785507, 88251446, 99658235, 48395186, 30998561, 82427263, 9259676, 75052463, 85023028, 36580610, 54014062, 9829782, 17976208, 9860195, 6157724, 53802686, 27325428, 32159704, 89996536, 34236719, 24591705, 21993752, 38658347, 35456853, 70004753, 87598888, 31733363, 75135448, 13348726, 52613508, 78561158, 5640302, 29932657, 82886381, 33553959, 42967683, 39171895, 48088883, 42199455, 79880247, 30653863, 2331773, 26507214, 72614359, 37739481, 49597667, 41380093, 57163802, 98371444, 48658605, 22766820, 37620363, 37501808, 67281495, 56473732, 56461322, 41442762, 39373729, 95957797, 77787724, 16971929, 31126490, 7182642, 49641577, 20122224, 1204161, 95726235, 18806856, 94711842, 55247455, 20645197, 6819644, 67030811, 77284619, 73021291, 824872, 56424103, 36930650, 62428472, 26292919, 57240218, 83155442, 71657078, 6871053, 247198, 94595668, 91255408, 4985896, 2717150, 90090964, 45306070, 94911072, 44627776, 91281584, 16387575, 44481640, 82327024, 23848565, 91240048, 40677414, 4787945, 17764950, 20642888, 22405120, 70800879, 97281847, 8373289, 94090109, 90013093, 84840549, 28550822, 3487592, 10961421, 34698428, 9623492, 28787861, 28796059, 33895336, 49328608, 2208785, 62115552, 38494874, 36468541, 70782102, 61271144, 26397786, 49236559, 70036438, 4978543, 59405277, 15948937, 90061527, 22997281, 14626618, 68128438, 64098930, 6525948, 1197320, 81853704, 16595436, 45794415, 62232211, 22942635, 12416768, 93790285, 8729146, 48360198, 64157906, 77377183, 22108665, 30569392, 92554120, 61728685, 29959549, 569864, 37183543, 42307449, 76703609, 54427233, 874791, 65081429, 84002370, 4798568, 80555751, 19101477, 48892201, 15535065, 63152504, 83747892, 18504197, 71333116, 78766358, 16054533, 59910624, 36135, 68316156, 77898274, 40781449, 30811010, 90654836, 82532312, 65047700, 66428795, 73786944, 16567550, 56484900, 37435892, 83083131, 86242799, 61982238, 77072625, 38256849, 31727379, 17037369, 26744917, 12664567, 36396314, 82726008, 46870723, 48774913, 29819940, 30771409, 54058788, 40152546, 97641116, 30163921, 86821229, 72278539, 5111370, 19457589, 57020481, 79191827, 99917599, 44842615, 92803766, 27411561, 79942022, 97940276, 66137019, 80316608, 22435353, 33797252, 68041839, 90272749, 44473167, 8634541, 29510992, 69579137, 44348328, 69641513, 3233084, 17894977, 43357947, 16445503, 49882705, 61623915, 75128745, 66611759, 45237957, 44664587, 3088684, 66250369, 95581843, 54199160, 508198, 4515343, 96420429, 79922758, 78549759, 20568363, 9860968, 81274566, 89419466, 42947632, 47792865, 14326617, 21919959, 1788101, 13862149, 84406788, 47893286, 749283, 4199704, 36189527, 67451935, 18466635, 32250045, 72495719, 17365188, 97011160, 38008118, 17857111, 61712234, 15015906, 89499542, 44844121, 62490109, 80246713, 40984766, 64087743, 30395570, 89046466, 61373987, 76170907, 65880522, 5482538, 78589145, 76434144, 61928316, 69255765, 98739783, 39801351, 20765474, 62803858, 75777973, 73168565, 60581278, 21289531, 17016156, 61263068, 94360702, 72793444, 98653983, 58180653, 82052050, 40197395, 66271566, 51149804, 85117092, 34698463, 51507425, 61815223, 7955293, 415901, 1239555, 73124510, 37560164, 77620120, 92541302, 45428665, 51047803, 66885828, 27187213, 29029316, 44245960, 71522968, 73109997, 6808825, 7066775, 51426311, 33699435, 8791066, 22113133, 71316369, 59329511, 2607799, 91664334, 85711894, 81774825, 7517032, 4119747, 92692978, 74110882, 24314885, 18699206, 7687278, 86798033, 92033260, 76960303, 14363867, 44479073, 72777973, 72238278, 85571389, 45919976, 51466049, 83302115, 20002147, 30218878, 3294781, 54606384, 24168411, 89699445, 50567636, 45848907, 84127901, 6986898, 40686254, 52261574, 22129328, 44060493, 32151165, 79821897, 59318837, 53888755, 82897371, 8696647, 68694897, 67124282, 65017137, 44846932, 95251277, 65038678, 71083565, 32161669, 5267545, 90158785, 83651211, 35512853, 26139110, 4434662, 45667668, 57241521, 33431960, 78218845, 13231279, 96852321, 86001008, 25842078, 17957593, 35092039, 17068582, 76315420, 26998766, 68102437, 28928175, 11923835, 63372756, 54762643, 7104732, 93270084, 68702632, 3880712, 60430369, 81805959, 30694952, 14093520, 83150534, 26392416, 42237907, 57393458, 10358899, 22200378, 64848072, 28734791, 37957788, 11161731, 10309525, 77272628, 34493392, 30764367, 14723410, 26734892, 20836893, 69605283, 20486294, 73222868, 82178706, 22879907, 81898046, 38009615, 33061250, 99861373, 12571310, 55753905, 59197747, 86301513, 95395112, 42644903, 31161687, 62051033, 33123618, 77301523, 77312810, 82979980, 93933709, 86543538, 16097038, 60102965, 36685023, 11543098, 99729357, 80014588, 84293052, 84904436, 32590267, 16099750, 54868730, 32426752, 12303248, 70367851, 2204165, 6505939, 51464002, 41245325, 55960386, 99965001, 18783702, 5970607, 29466406, 14045383, 82651278, 90004325, 4091162, 33935899, 91938191, 49271185, 19272365, 16684829, 56153345, 29401781, 39201414, 24953498, 98948034, 62496012, 66663942, 62762857, 79322415, 83269727, 67513640, 89078848, 17385531, 1250437, 17727650, 5832946, 40534591, 37891451, 94076128, 10597197, 92353856, 61141874, 29065964, 526217, 43506672, 39553046, 93566986, 93057697, 36139942, 90310261, 51588015, 4806458, 4095116, 76540481, 97561745, 98462867, 71965942, 27185644, 51715482, 65271999, 88653118, 96735716, 42782652, 51315460, 66903004, 60106217, 95290172, 95010552, 88444207, 91990218, 37280276, 44915235, 15075176, 62430984, 94539824, 7919588, 53393358, 19898053, 21070110, 30891921, 68110330, 1569515, 20867149, 10649306, 38341669, 92867155, 55850790, 47887470, 89217461, 7300047, 4204661, 3233569, 89804152, 76671482, 8913721, 66322959, 73617245, 30463802, 34295794, 75986488, 71300104, 43322743, 86118021, 25636669, 34044787, 68644627, 52204879, 74357852, 96726697, 16424199, 69355476, 71920426, 27665211, 87720882, 68204242, 72373496, 10264691, 8733068, 99524975, 64055960, 1431742, 78300864, 66319530, 67474219, 72357096, 57658654, 99333375, 37192445, 17081350, 47738185, 82779622, 7502255, 46540998, 16380211, 34432810, 56531125, 9886593, 36753250, 18833224, 83368048, 59109400, 60176618, 99549373, 92787493, 94516935, 75543508, 59371804, 93053405, 33336148, 80251430, 13173644, 19891772, 58208470, 75820087, 3509435, 72019362, 66667729, 91802888, 55602660, 6038457, 26114953, 23569917, 33628349, 98943869, 1936762, 91831487, 36933038, 67084351, 30366150, 6497830, 20885148, 51590803, 84166196, 69848388, 53547802, 24058273, 53648154, 21673260, 44177011, 40027975, 24826575, 67031644, 68000591, 44784505, 47213183, 63059024, 23134715, 6111563, 91957544, 29635537, 96906410, 89811711, 72732205, 50668599, 40356877, 60393039, 31491938, 9603598, 87710366, 33201905, 39986008, 45996863, 21397057, 34946859, 10453030, 97057187, 168541, 669105, 74743862, 62693428, 9599614, 48673079, 83533741, 30139692, 8055981, 36312813, 26776922, 3183975, 8651647, 63628376, 65454636, 81677380, 20681921, 6153406, 70596786, 30787683, 83789391, 9575954, 66116458, 36505482, 18411915, 99690194, 41481685, 79806380, 50188404, 29994197, 20140249, 77187825, 55669657, 36780454, 15536795, 99224392, 99125126, 92530431, 84349107, 92215320, 67227442, 37224844, 19486173, 98648327, 43933006, 62936963, 39847321, 9188443, 92604458, 69786756, 81172706, 93562257, 79136082, 70541760, 27041967, 65074535, 9257405, 22721500, 29516592, 58749, 59981773, 55470718, 85695762, 24619760, 54263880, 88130087, 53666583, 17058722, 78909561, 59177718, 5073754, 12348118, 61859143, 74441448, 41092172, 17539625, 7229550, 43045786, 76330843, 38022834, 15902805 +36933038, 21673260, 39847321, 45667668, 90013093, 53547802, 62552524, 44784505, 59177718, 34497327, 36396314, 10453030, 67124282, 61859581, 36139942, 75543508, 69641513, 40865610, 45237957, 51315460, 47893286, 6153406, 21070110, 81898046, 19486173, 88130087, 66116458, 78909561, 85116755, 41245325, 62762857, 26744917, 47738185, 79821897, 99125126, 91727510, 30139692, 17068582, 33895336, 23569917, 51590803, 15163258, 24058273, 45794415, 68000591, 55850790, 29959549, 86543538, 94360702, 16019925, 84002370, 45428665, 5073754, 79136082, 86118021, 33935899, 9257405, 73786944, 8733068, 31491938, 74452589, 15536795, 77413012, 40686254, 76330843, 44060493, 34946859, 94595668, 30163921, 14220886, 2717150, 48893685, 56531125, 82339363, 44481640, 92803766, 38645117, 83651211, 16405341, 94090109, 3509435, 28550822, 96735716, 21919959, 54014062, 36189527, 34236719, 15015906, 67227442, 22942635, 44177011, 37224844, 76434144, 77377183, 92071990, 86301513, 8129978, 63059024, 95395112, 75777973, 33553959, 4204661, 53666583, 66322959, 2331773, 37739481, 73124510, 29635537, 9175338, 16099750, 92604458, 66885828, 29029316, 71522968, 37501808, 99297164, 22113133, 29466406, 31126490, 32058615, 40781449, 79806380, 18699206, 7687278, 72373496, 10264691, 62496012, 99226875, 62428472, 4064751, 82779622, 71657078, 6871053, 99971982, 22721500, 5111370, 29065964, 36753250, 54517921, 83368048, 99917599, 4787945, 83533741, 33336148, 29516592, 44473167, 8634541, 13173644, 86001008, 72274002, 78785507, 68102437, 58749, 3088684, 28787861, 49328608, 1936762, 14093520, 55470718, 88444207, 49236559, 48675329, 70036438, 10309525, 27325428, 77272628, 7919588, 61712234, 38658347, 80246713, 3773993, 52613508, 69255765, 43045786, 33123618, 48088883, 36685023, 99729357, 26063929, 62936963, 36505482, 61815223, 41380093, 92541302, 15535065, 71300104, 96906410, 77694700, 27187213, 36808486, 18504197, 55960386, 4099191, 71333116, 16971929, 52204879, 7182642, 16054533, 97379790, 4091162, 92692978, 24314885, 86798033, 14363867, 68204242, 29994197, 55247455, 74441448, 61982238, 96193415, 9603598, 33201905, 11581181, 57803235, 96709982, 5832946, 16380211, 34432810, 91255408, 19457589, 83210802, 35512853, 92787493, 41092172, 4434662, 18001617, 80251430, 19891772, 58208470, 13231279, 96852321, 71965942, 72019362, 88251446, 99658235, 28928175, 65271999, 93270084, 66250369, 66667729, 26776922, 42782652, 2208785, 57248122, 81805959, 26114953, 75052463, 47792865, 61271144, 26397786, 64848072, 6157724, 18466635, 14626618, 34493392, 17857111, 73392814, 55770687, 62490109, 40984766, 87598888, 76170907, 55753905, 48360198, 30090481, 61928316, 30569392, 7423788, 65275241, 60581278, 23134715, 3233569, 66271566, 11543098, 48260151, 80014588, 73617245, 55615885, 80555751, 38365584, 77620120, 75986488, 57163802, 98371444, 89214616, 54868730, 12303248, 22766820, 70367851, 51464002, 70541760, 4508700, 29834512, 91664334, 74357852, 88047921, 16424199, 28851716, 4119747, 71920426, 49271185, 19272365, 50668599, 72777973, 45919976, 83302115, 1431742, 60955663, 77284619, 66663942, 55669657, 36780454, 89699445, 56955985, 45381876, 17081350, 99515901, 14349098, 17727650, 46540998, 40534591, 40152546, 168541, 50806615, 66832478, 90090964, 74743862, 92353856, 69697787, 68694897, 2917920, 9886593, 61141874, 93566986, 26664538, 44842615, 91240048, 59109400, 90158785, 17764950, 76540481, 85242963, 2891150, 26139110, 59371804, 33797252, 90272749, 98462867, 17957593, 83133790, 35092039, 16445503, 61623915, 75153252, 75128745, 11923835, 6221471, 66611759, 7104732, 58224549, 9623492, 44664587, 36312813, 7229550, 33628349, 26392416, 30366150, 10358899, 9829782, 44915235, 9860195, 20885148, 53802686, 32159704, 30764367, 84166196, 54663246, 81853704, 20681921, 16595436, 3633375, 65338021, 53393358, 73222868, 62232211, 86460488, 54263880, 15064655, 59197747, 64157906, 67031644, 98648327, 22108665, 78561158, 39801351, 92554120, 92867155, 17016156, 61263068, 16097038, 43933006, 89804152, 72793444, 42199455, 85117092, 13468268, 54427233, 84293052, 30653863, 65081429, 26507214, 62740044, 4798568, 44847298, 415901, 1239555, 49597667, 51047803, 99690194, 32426752, 44889423, 81172706, 2204165, 71469330, 6505939, 67281495, 51426311, 18783702, 50007421, 33699435, 41442762, 43376279, 78766358, 36135, 20122224, 54232247, 82532312, 1204161, 92033260, 50188404, 95726235, 56484900, 83083131, 20002147, 98948034, 3294781, 824872, 70420215, 57658654, 38256849, 86022504, 33565483, 17037369, 84187166, 12664567, 57240218, 50702367, 17385531, 52261574, 10597197, 73031054, 61897582, 53888755, 72278539, 6521313, 82897371, 57020481, 65038678, 18833224, 43501211, 24733232, 90310261, 60176618, 13470059, 4806458, 22405120, 4095116, 27411561, 35996293, 94516935, 66137019, 49598724, 45617087, 97561745, 69579137, 47361209, 21533347, 84684495, 3233084, 43357947, 76315420, 93359396, 49882705, 48395186, 88653118, 95581843, 30998561, 508198, 60430369, 79922758, 20568363, 8651647, 9860968, 68816413, 59981773, 14326617, 67084351, 36580610, 57393458, 63628376, 37280276, 17976208, 59405277, 15948937, 32250045, 97011160, 22997281, 94539824, 69848388, 98130363, 1197320, 70596786, 69605283, 85695762, 22879907, 12416768, 30395570, 24619760, 30787683, 176257, 70727211, 93790285, 40027975, 75135448, 5482538, 47213183, 38341669, 73168565, 61728685, 29932657, 37675718, 569864, 89217461, 93933709, 7300047, 26275734, 40197395, 52293995, 8913721, 42307449, 63506281, 84904436, 30463802, 61741594, 37560164, 9188443, 42073124, 74724075, 47708850, 37620363, 93562257, 23740167, 56461322, 7011964, 8791066, 89811711, 71316369, 68644627, 5970607, 38022834, 18131876, 2607799, 85711894, 68316156, 61859143, 52060076, 90004325, 90654836, 72732205, 27665211, 66428795, 65074535, 40356877, 51466049, 60393039, 99524975, 43152977, 24953498, 64055960, 78300864, 67030811, 72357096, 20140249, 11757872, 41092102, 24168411, 37166608, 50567636, 67513640, 39986008, 37192445, 45848907, 17386996, 46870723, 30771409, 32151165, 96318094, 59318837, 45995325, 97057187, 67793644, 97641116, 45306070, 62693428, 65017137, 71083565, 45075471, 82327024, 5267545, 99549373, 9058407, 70800879, 79942022, 97940276, 80316608, 45790169, 97281847, 57241521, 33431960, 17539625, 81855445, 88698958, 19939935, 17894977, 51715482, 87160386, 3487592, 91141395, 62357986, 3880712, 79657802, 3183975, 74137926, 30694952, 66903004, 91831487, 60106217, 1788101, 91990218, 13862149, 22200378, 28734791, 15075176, 17365188, 59614746, 64098930, 6525948, 20836893, 24591705, 65715134, 32699744, 19898053, 53648154, 82178706, 30891921, 15902805, 68110330, 64087743, 61373987, 83789391, 24826575, 65880522, 64602895, 78589145, 13348726, 3235882, 5640302, 98739783, 20765474, 31161687, 47887470, 42967683, 6111563, 60102965, 58180653, 46851987, 36845587, 35192533, 83948335, 32590267, 18411915, 7685448, 69786756, 33249630, 7646095, 63152504, 6808825, 47090124, 58751351, 39373729, 34044787, 59329511, 95957797, 27041967, 55143724, 14045383, 49641577, 41481685, 81774825, 82651278, 57359924, 30811010, 91938191, 65047700, 44479073, 16684829, 56153345, 99021067, 72238278, 39201414, 94711842, 12024238, 1375023, 86242799, 14731700, 30218878, 59501487, 79322415, 97783876, 83269727, 99333375, 8128637, 26292919, 89078848, 6986898, 1250437, 29819940, 65851721, 44983451, 11365791, 8696647, 321665, 88904910, 44627776, 91281584, 526217, 95251277, 74614639, 39553046, 84349107, 76825057, 51588015, 45407418, 20642888, 22435353, 93053405, 8373289, 29510992, 92215320, 75820087, 25842078, 90457870, 53084256, 8055981, 28796059, 54199160, 96420429, 6038457, 38494874, 98943869, 42947632, 51472275, 6497830, 37957788, 72495719, 68128438, 62430984, 38008118, 20486294, 89046466, 1569515, 70004753, 8729146, 12571310, 45990383, 75407347, 42644903, 21289531, 82979980, 37183543, 55189057, 76671482, 51149804, 34698463, 7955293, 19101477, 72738685, 29188588, 48658605, 70372191, 112651, 63967300, 42692881, 12348118, 18663507, 7066775, 99965001, 25636669, 77787724, 7517032, 77898274, 37659250, 69355476, 16567550, 47298834, 37435892, 40781854, 73021291, 31727379, 68939068, 53632373, 82726008, 48774913, 247198, 54987042, 669105, 4985896, 62452034, 44846932, 16387575, 61960400, 79191827, 92530431, 31904591, 15148031, 32161669, 9599614, 93057697, 10366309, 19376156, 69136837, 48673079, 78218845, 44348328, 26863229, 38510840, 8099994, 26998766, 73235980, 68702632, 53842979, 4515343, 9259676, 81274566, 75229462, 83150534, 95290172, 95010552, 70782102, 749283, 92398073, 44844121, 8807940, 20867149, 79738755, 9575954, 68875490, 13819358, 77301523, 77312810, 39171895, 98653983, 874791, 79880247, 34295794, 44245960, 73109997, 96726697, 59910624, 27625735, 76960303, 4069912, 87720882, 85571389, 66319530, 54606384, 87710366, 45996863, 83155442, 54058788, 37891451, 9398733, 45049308, 77300457, 68824981, 23848565, 40677414, 8115266, 23432750, 72725103, 26102057, 14947650, 68041839, 26493119, 57961282, 21001913, 27185644, 34698428, 66045587, 82427263, 62115552, 78602717, 89419466, 36468541, 42237907, 34667286, 90061527, 14723410, 26734892, 78884452, 21993752, 38061439, 33061250, 62803858, 30543215, 51507425, 72614359, 92692283, 23110625, 6793819, 92998591, 56473732, 17146629, 24915585, 74110882, 29401781, 18806856, 4668450, 67474219, 36930650, 21397057, 94076128, 94911072, 72358170, 32274392, 22450468, 84840549, 10961421, 54762643, 78549759, 84406788, 4199704, 67451935, 81677380, 35456853, 99604946, 99861373, 62051033, 1022677, 82052050, 76703609, 48892201, 91957544, 43322743, 33237508, 63015256, 56424103, 77187825, 84127901, 22129328, 99224392, 7502255, 89637706, 36812683, 33304202, 63372756, 91802888, 93515664, 4978543, 65454636, 11161731, 89996536, 38009615, 10649306, 17058722, 83747892, 23194618, 6819644, 5822202, 77072625, 86821229, 56515456, 43506672, 55602660, 85023028, 31733363, 82886381, 20645197, 44550764, 89499542 +79922758, 43322743, 85711894, 25842078, 78549759, 38494874, 33565483, 61141874, 53842979, 37280276, 38008118, 65338021, 64087743, 70004753, 39801351, 92554120, 36685023, 35192533, 1239555, 16099750, 67281495, 56473732, 41442762, 32058615, 56484900, 34497327, 54987042, 86821229, 80316608, 23569917, 91831487, 61271144, 67084351, 22200378, 6157724, 62430984, 24591705, 77377183, 42644903, 26063929, 80014588, 37560164, 47090124, 88047921, 20122224, 90654836, 92033260, 99021067, 73786944, 824872, 50806615, 11365791, 321665, 89637706, 94911072, 83651211, 79942022, 75543508, 22435353, 45617087, 33304202, 79657802, 55602660, 64848072, 16595436, 98648327, 78561158, 8129978, 33123618, 17016156, 33553959, 6111563, 60102965, 76671482, 52293995, 48260151, 75986488, 74724075, 4099191, 82651278, 65074535, 76960303, 14363867, 1375023, 60955663, 62762857, 84187166, 77413012, 10453030, 10597197, 97641116, 30163921, 74743862, 2917920, 29065964, 65038678, 79191827, 23432750, 72725103, 13173644, 47361209, 35092039, 88251446, 28550822, 65271999, 68702632, 28787861, 2208785, 57248122, 60106217, 95010552, 70782102, 84406788, 10358899, 37957788, 32250045, 77272628, 22997281, 54663246, 67227442, 86460488, 1569515, 93790285, 65880522, 64602895, 88130087, 76434144, 13348726, 30090481, 73168565, 23134715, 58180653, 82052050, 79880247, 83948335, 92692283, 33249630, 47708850, 81172706, 7066775, 51426311, 99297164, 29834512, 52204879, 43376279, 52060076, 27665211, 4069912, 64055960, 14731700, 30218878, 96193415, 97783876, 37166608, 62428472, 50567636, 4064751, 17727650, 48774913, 37891451, 247198, 94595668, 65851721, 66832478, 53888755, 9886593, 44846932, 45075471, 90158785, 4787945, 51588015, 70800879, 97940276, 90272749, 44473167, 57241521, 92215320, 13231279, 44348328, 86001008, 27185644, 93359396, 91141395, 63372756, 54762643, 62357986, 7104732, 54199160, 33895336, 82427263, 75052463, 8651647, 9860968, 89419466, 42947632, 55470718, 1788101, 47893286, 44915235, 4978543, 68128438, 6525948, 3633375, 55770687, 35456853, 53648154, 21673260, 40984766, 12416768, 15064655, 44784505, 30569392, 29932657, 86543538, 4204661, 26275734, 16019925, 84002370, 46851987, 26507214, 62740044, 30463802, 61741594, 41380093, 6793819, 98371444, 71300104, 70372191, 112651, 27187213, 41245325, 33699435, 39373729, 71333116, 16971929, 31126490, 55143724, 14045383, 16054533, 7517032, 57359924, 40781449, 28851716, 54232247, 69355476, 18699206, 44479073, 9257405, 10264691, 47298834, 1431742, 4668450, 62496012, 61982238, 36930650, 36780454, 83269727, 8128637, 15536795, 57803235, 50702367, 17385531, 34946859, 79821897, 4985896, 6521313, 9398733, 45306070, 99125126, 91281584, 45049308, 77300457, 95251277, 43506672, 74614639, 83368048, 93057697, 36139942, 92803766, 76825057, 48673079, 4095116, 94516935, 66137019, 26493119, 58208470, 84684495, 88698958, 83133790, 26998766, 49882705, 68102437, 99658235, 40865610, 75153252, 34698428, 9623492, 28796059, 42782652, 4515343, 62115552, 30694952, 33628349, 51315460, 66903004, 81274566, 59981773, 88444207, 36580610, 57393458, 63628376, 15075176, 92398073, 89996536, 69848388, 98130363, 20836893, 15015906, 20681921, 32699744, 24058273, 73392814, 3773993, 30395570, 61373987, 79738755, 62552524, 3235882, 52613508, 95395112, 62051033, 75777973, 37183543, 89804152, 30543215, 66271566, 51149804, 34698463, 42307449, 36845587, 80555751, 34295794, 29188588, 18411915, 96906410, 99690194, 59177718, 29029316, 71522968, 93562257, 36808486, 55960386, 50007421, 8791066, 22113133, 68644627, 38022834, 95957797, 18131876, 7182642, 16424199, 41481685, 77898274, 4091162, 24915585, 74110882, 24314885, 1204161, 66428795, 68204242, 99524975, 24953498, 98948034, 11757872, 74452589, 87710366, 68939068, 67513640, 45381876, 12664567, 17386996, 17081350, 46870723, 7502255, 71657078, 40534591, 32151165, 45995325, 16380211, 73031054, 168541, 44983451, 2717150, 8696647, 68694897, 62452034, 72358170, 31904591, 93566986, 44481640, 32161669, 10366309, 60176618, 13470059, 99549373, 4806458, 92787493, 41092172, 26102057, 26139110, 4434662, 49598724, 68041839, 33431960, 19891772, 81855445, 21533347, 3509435, 69641513, 98462867, 38510840, 72274002, 43357947, 78785507, 10961421, 6221471, 66611759, 48395186, 58224549, 44664587, 66667729, 26776922, 26114953, 20568363, 68816413, 47792865, 36933038, 91990218, 749283, 48675329, 28734791, 17976208, 9860195, 20885148, 10309525, 27325428, 51590803, 72495719, 94539824, 32159704, 61712234, 65715134, 70596786, 69605283, 22879907, 81898046, 30891921, 62490109, 38009615, 99861373, 37224844, 75135448, 55753905, 19486173, 48360198, 5482538, 78589145, 64157906, 13819358, 20765474, 65275241, 38341669, 21289531, 29959549, 61263068, 37675718, 89217461, 42967683, 72793444, 42199455, 85117092, 8913721, 84293052, 55615885, 72614359, 62936963, 78909561, 36505482, 61815223, 19101477, 48892201, 77620120, 23110625, 15535065, 45428665, 54868730, 63967300, 92998591, 77694700, 70367851, 18663507, 63152504, 79136082, 86118021, 89811711, 77787724, 74357852, 97379790, 36135, 61859143, 4119747, 91938191, 63015256, 56153345, 87720882, 50668599, 29401781, 72777973, 94711842, 78300864, 20002147, 66319530, 67030811, 5822202, 66663942, 57658654, 9603598, 79322415, 77187825, 54606384, 24168411, 99333375, 26744917, 37192445, 26292919, 57240218, 6986898, 82726008, 76330843, 82779622, 21397057, 30771409, 34432810, 99971982, 82897371, 48893685, 62693428, 56531125, 65017137, 526217, 43501211, 71083565, 39553046, 92530431, 68824981, 15148031, 24733232, 26664538, 61859581, 9599614, 91240048, 69136837, 83210802, 59109400, 20642888, 22405120, 85242963, 14947650, 91727510, 59371804, 45790169, 33797252, 18001617, 30139692, 29516592, 75820087, 96852321, 19939935, 17957593, 21001913, 17068582, 51715482, 87160386, 72019362, 75128745, 8055981, 93270084, 36312813, 3880712, 14093520, 75229462, 26392416, 30366150, 9829782, 93515664, 67451935, 59405277, 15948937, 34667286, 81677380, 14626618, 15163258, 84166196, 64098930, 53393358, 19898053, 73222868, 80246713, 68110330, 99604946, 24619760, 44177011, 176257, 83789391, 70727211, 87598888, 31733363, 12571310, 59197747, 24826575, 45990383, 75407347, 66116458, 98739783, 31161687, 92867155, 55850790, 60581278, 77301523, 82979980, 94360702, 43933006, 7300047, 48088883, 40197395, 76703609, 99729357, 54427233, 874791, 73617245, 2331773, 37739481, 415901, 49597667, 9175338, 9188443, 48658605, 51047803, 89214616, 69786756, 22766820, 44245960, 7646095, 37620363, 37501808, 51464002, 18504197, 23740167, 4508700, 58751351, 29466406, 27041967, 59910624, 30811010, 72373496, 39201414, 16567550, 95726235, 18806856, 83302115, 86242799, 77284619, 73021291, 99226875, 55669657, 41092102, 89699445, 45848907, 45996863, 89078848, 53632373, 40686254, 83155442, 99515901, 52261574, 47738185, 44060493, 96318094, 59318837, 40152546, 61897582, 669105, 22721500, 56515456, 88904910, 19457589, 57020481, 16387575, 32274392, 23848565, 5267545, 40677414, 45407418, 17764950, 2891150, 93053405, 97561745, 80251430, 29510992, 26863229, 71965942, 28928175, 61623915, 53084256, 58749, 11923835, 45237957, 3088684, 66045587, 49328608, 508198, 6038457, 81805959, 1936762, 21919959, 95290172, 26397786, 51472275, 70036438, 36189527, 65454636, 97011160, 34493392, 30764367, 34236719, 14723410, 26734892, 17857111, 6153406, 89499542, 21070110, 38658347, 22942635, 15902805, 38061439, 33061250, 30787683, 40027975, 76170907, 47213183, 63059024, 7423788, 10649306, 62803858, 47887470, 77312810, 16097038, 53666583, 55189057, 13468268, 51507425, 30653863, 4798568, 38365584, 32590267, 29635537, 57163802, 92604458, 42073124, 12303248, 42692881, 66885828, 5073754, 44889423, 6808825, 70541760, 99965001, 25636669, 33237508, 71316369, 96726697, 78766358, 81774825, 68316156, 90004325, 92692978, 82532312, 79806380, 33935899, 7687278, 65047700, 85571389, 45919976, 51466049, 60393039, 55247455, 74441448, 83083131, 70420215, 38256849, 36396314, 96709982, 5832946, 6871053, 91255408, 69697787, 67124282, 5111370, 36812683, 18833224, 61960400, 54517921, 82327024, 38645117, 35512853, 9058407, 83533741, 35996293, 97281847, 8373289, 8634541, 94090109, 78218845, 57961282, 22450468, 88653118, 96420429, 91802888, 74137926, 9259676, 14326617, 36468541, 42237907, 54014062, 13862149, 49236559, 53802686, 17365188, 59614746, 1197320, 7919588, 81853704, 21993752, 62232211, 44844121, 89046466, 8729146, 9575954, 92071990, 43045786, 68875490, 61728685, 82886381, 1022677, 569864, 93933709, 98653983, 17058722, 11543098, 63506281, 44847298, 91957544, 73124510, 72738685, 39847321, 7685448, 32426752, 12348118, 73109997, 56461322, 59329511, 2607799, 49641577, 37659250, 16684829, 50188404, 23194618, 29994197, 8733068, 20645197, 40781854, 77072625, 31727379, 84127901, 14349098, 54058788, 94076128, 90090964, 44550764, 92353856, 44627776, 99917599, 82339363, 84349107, 90310261, 8115266, 16405341, 33336148, 3233084, 90013093, 8099994, 90457870, 84840549, 73235980, 95581843, 7229550, 85023028, 4199704, 11161731, 53547802, 45794415, 78884452, 54263880, 67031644, 5640302, 39171895, 3233569, 66322959, 65081429, 92541302, 85116755, 6505939, 7011964, 17146629, 91664334, 19272365, 86798033, 40356877, 31491938, 43152977, 37435892, 67474219, 3294781, 20140249, 59501487, 56424103, 33201905, 11581181, 17037369, 56955985, 1250437, 97057187, 67793644, 36753250, 44842615, 76540481, 45667668, 69579137, 17539625, 76315420, 16445503, 66250369, 96735716, 60430369, 98943869, 83150534, 18466635, 90061527, 85695762, 82178706, 68000591, 22108665, 61928316, 69255765, 86301513, 7955293, 2204165, 71469330, 83747892, 18783702, 5970607, 72732205, 49271185, 72238278, 72357096, 86022504, 39986008, 14220886, 19376156, 3487592, 30998561, 3183975, 78602717, 6497830, 8807940, 20867149, 27625735, 12024238, 6819644, 99224392, 29819940, 46540998, 27411561, 34044787, 71920426, 22129328, 72278539, 17894977, 84904436, 20486294 +17058722, 69786756, 73168565, 27625735, 79806380, 99524975, 68816413, 20765474, 42644903, 37891451, 4985896, 4434662, 49598724, 53084256, 1197320, 92071990, 58180653, 8913721, 12303248, 19272365, 68204242, 64055960, 16405341, 75543508, 28928175, 40865610, 54199160, 3183975, 78549759, 51315460, 72495719, 30891921, 7646095, 86118021, 17146629, 4508700, 18131876, 4069912, 24168411, 45996863, 32274392, 82327024, 17764950, 26102057, 59371804, 44473167, 29510992, 81855445, 84684495, 21001913, 43357947, 28787861, 33628349, 98943869, 66903004, 26392416, 10358899, 4978543, 92398073, 15948937, 69605283, 81898046, 31733363, 30090481, 68000591, 8129978, 16097038, 48088883, 66271566, 13468268, 19101477, 92604458, 96906410, 63967300, 83747892, 33699435, 5970607, 27041967, 30811010, 39201414, 20645197, 59501487, 77072625, 11757872, 89078848, 53632373, 84127901, 57240218, 71657078, 94076128, 67793644, 97641116, 99917599, 82339363, 84349107, 8115266, 83533741, 35996293, 93053405, 21533347, 3509435, 49328608, 67451935, 59405277, 81677380, 15015906, 67227442, 24058273, 45794415, 38061439, 15064655, 8729146, 13348726, 62552524, 77312810, 76671482, 54427233, 65081429, 84002370, 415901, 37560164, 16099750, 42692881, 2204165, 79136082, 56473732, 38022834, 16054533, 72732205, 49271185, 76960303, 87720882, 72373496, 47298834, 74441448, 14731700, 77187825, 87710366, 8128637, 6986898, 17081350, 14349098, 94595668, 22721500, 5111370, 62693428, 89637706, 9886593, 44846932, 61141874, 19457589, 45049308, 526217, 77300457, 65038678, 43501211, 39553046, 92530431, 31904591, 68824981, 44842615, 93057697, 69136837, 23432750, 35512853, 45407418, 97561745, 17539625, 51715482, 49882705, 88251446, 44664587, 68702632, 4515343, 47792865, 75229462, 70782102, 91990218, 84406788, 4199704, 6157724, 22997281, 89996536, 70596786, 22942635, 8807940, 12416768, 89046466, 78589145, 67031644, 98648327, 9575954, 63059024, 43045786, 39171895, 4204661, 79880247, 84904436, 30653863, 62936963, 41380093, 23110625, 6793819, 89214616, 42073124, 66885828, 37620363, 6505939, 56461322, 25636669, 99297164, 68644627, 29466406, 43376279, 36135, 7517032, 69355476, 56153345, 23194618, 10264691, 51466049, 83302115, 1431742, 40781854, 4668450, 38256849, 33201905, 11581181, 99333375, 31727379, 68939068, 56955985, 45381876, 26292919, 82726008, 46870723, 47738185, 21397057, 72278539, 2717150, 48893685, 69697787, 68694897, 2917920, 56531125, 94911072, 57020481, 95251277, 43506672, 79191827, 26664538, 61859581, 9599614, 10366309, 91240048, 76825057, 4787945, 92787493, 20642888, 9058407, 85242963, 94516935, 78218845, 88698958, 26863229, 90457870, 22450468, 75128745, 48395186, 45237957, 3088684, 66045587, 42782652, 57248122, 6038457, 62115552, 81805959, 81274566, 60106217, 14326617, 21919959, 36468541, 13862149, 48675329, 6497830, 93515664, 18466635, 77272628, 34493392, 98130363, 65338021, 53393358, 73392814, 20486294, 15902805, 68110330, 64087743, 24619760, 176257, 99861373, 19486173, 64602895, 64157906, 52613508, 7423788, 10649306, 95395112, 61728685, 21289531, 17016156, 61263068, 77301523, 37675718, 89217461, 33553959, 94360702, 60102965, 82052050, 11543098, 51149804, 52293995, 26063929, 51507425, 84293052, 73617245, 46851987, 36845587, 80555751, 35192533, 30463802, 38365584, 92692283, 9188443, 18411915, 71300104, 59177718, 33249630, 55960386, 34044787, 22113133, 77787724, 2607799, 91664334, 78766358, 16424199, 41481685, 81774825, 82651278, 52060076, 40781449, 37659250, 92692978, 71920426, 82532312, 91938191, 7687278, 1204161, 86798033, 16684829, 99021067, 72238278, 16567550, 8733068, 55247455, 83083131, 60955663, 66319530, 67030811, 3294781, 66663942, 56424103, 57658654, 62762857, 84187166, 37192445, 12664567, 99515901, 48774913, 79821897, 10597197, 34432810, 65851721, 669105, 44983451, 6521313, 74743862, 8696647, 9398733, 16387575, 24733232, 23848565, 19376156, 40677414, 59109400, 79942022, 91727510, 57241521, 94090109, 13173644, 47361209, 58208470, 96852321, 33304202, 17894977, 84840549, 68102437, 75153252, 91141395, 34698428, 63372756, 66250369, 95581843, 79657802, 26776922, 30998561, 79922758, 91802888, 30694952, 8651647, 1936762, 91831487, 59981773, 14093520, 55470718, 42237907, 30366150, 22200378, 749283, 51472275, 70036438, 28734791, 37957788, 10309525, 53802686, 90061527, 14626618, 32159704, 54663246, 7919588, 16595436, 85695762, 38658347, 21673260, 80246713, 33061250, 1569515, 87598888, 79738755, 48360198, 65880522, 76434144, 77377183, 22108665, 61928316, 45990383, 78561158, 30569392, 66116458, 98739783, 62051033, 62803858, 75777973, 55850790, 33123618, 86543538, 98653983, 42199455, 55189057, 40197395, 30543215, 85117092, 34698463, 42307449, 99729357, 26507214, 78909561, 7955293, 91957544, 32590267, 15535065, 85116755, 99690194, 70372191, 22766820, 74724075, 71522968, 18663507, 70541760, 67281495, 51426311, 41442762, 58751351, 39373729, 59329511, 95957797, 29834512, 85711894, 59910624, 49641577, 20122224, 28851716, 66428795, 92033260, 63015256, 44479073, 31491938, 1375023, 24953498, 86242799, 96193415, 79322415, 97783876, 74452589, 54606384, 17037369, 26744917, 52261574, 22129328, 29819940, 32151165, 96318094, 45995325, 40152546, 54987042, 90090964, 44550764, 92353856, 67124282, 56515456, 321665, 62452034, 65017137, 44627776, 91281584, 61960400, 90158785, 99549373, 41092172, 22405120, 4095116, 76540481, 14947650, 97940276, 26139110, 22435353, 68041839, 45667668, 8373289, 80251430, 92215320, 13231279, 98462867, 3233084, 17068582, 61623915, 58749, 54762643, 62357986, 36312813, 2208785, 96420429, 55602660, 7229550, 74137926, 23569917, 75052463, 38494874, 89419466, 88444207, 67084351, 57393458, 64848072, 44915235, 65454636, 11161731, 32250045, 27325428, 17365188, 97011160, 68128438, 38008118, 24591705, 65715134, 53547802, 6153406, 21070110, 53648154, 82178706, 62232211, 86460488, 44177011, 61373987, 70004753, 83789391, 40027975, 59197747, 44784505, 47213183, 86301513, 68875490, 31161687, 82886381, 37183543, 23134715, 43933006, 26275734, 89804152, 72793444, 16019925, 66322959, 80014588, 44847298, 61815223, 1239555, 92541302, 75986488, 29188588, 57163802, 9175338, 112651, 5073754, 77694700, 44889423, 81172706, 18504197, 23740167, 18783702, 8791066, 33237508, 16971929, 52204879, 96726697, 88047921, 7182642, 77898274, 90004325, 4091162, 24915585, 90654836, 74110882, 27665211, 24314885, 65047700, 50188404, 50668599, 72777973, 95726235, 18806856, 94711842, 56484900, 20002147, 77284619, 30218878, 73021291, 72357096, 20140249, 34497327, 9603598, 36930650, 37166608, 83269727, 50567636, 45848907, 77413012, 36396314, 57803235, 40686254, 17385531, 76330843, 54058788, 59318837, 168541, 30163921, 66832478, 45306070, 72358170, 36812683, 83368048, 93566986, 44481640, 32161669, 5267545, 92803766, 83210802, 90310261, 60176618, 51588015, 48673079, 70800879, 27411561, 80316608, 45790169, 30139692, 8634541, 69579137, 44348328, 25842078, 83133790, 27185644, 16445503, 93359396, 87160386, 26998766, 78785507, 99658235, 10961421, 66667729, 53842979, 33895336, 96735716, 82427263, 78602717, 9860968, 85023028, 83150534, 95290172, 36933038, 36580610, 63628376, 49236559, 37280276, 20885148, 34667286, 51590803, 30764367, 59614746, 26734892, 81853704, 20681921, 3633375, 32699744, 78884452, 89499542, 22879907, 44844121, 62490109, 99604946, 30395570, 30787683, 20867149, 37224844, 76170907, 75135448, 88130087, 75407347, 39801351, 65275241, 38341669, 29959549, 93933709, 42967683, 6111563, 3233569, 76703609, 63506281, 874791, 2331773, 62740044, 4798568, 49597667, 48658605, 7685448, 43322743, 47708850, 71469330, 37501808, 73109997, 41245325, 7066775, 50007421, 7011964, 89811711, 71316369, 71333116, 14045383, 68316156, 4119747, 29401781, 9257405, 29994197, 73786944, 45919976, 78300864, 6819644, 98948034, 824872, 62496012, 5822202, 61982238, 70420215, 86022504, 15536795, 17386996, 4064751, 82779622, 44060493, 34946859, 10453030, 73031054, 50806615, 91255408, 14220886, 82897371, 29065964, 18833224, 45075471, 38645117, 83651211, 66137019, 33797252, 33336148, 97281847, 18001617, 90272749, 29516592, 69641513, 19939935, 35092039, 8099994, 28550822, 3487592, 6221471, 66611759, 88653118, 7104732, 58224549, 9623492, 60430369, 9259676, 1788101, 36189527, 9860195, 62430984, 64098930, 14723410, 69848388, 35456853, 12571310, 5482538, 69255765, 92867155, 60581278, 47887470, 569864, 7300047, 53666583, 72614359, 48892201, 29635537, 51047803, 27187213, 29029316, 44245960, 70367851, 93562257, 51464002, 99965001, 4099191, 47090124, 74357852, 31126490, 55143724, 97379790, 32058615, 57359924, 33935899, 14363867, 40356877, 60393039, 37435892, 99226875, 89699445, 62428472, 33565483, 67513640, 39986008, 83155442, 96709982, 17727650, 16380211, 53888755, 86821229, 99125126, 88904910, 36753250, 71083565, 15148031, 72725103, 45617087, 33431960, 57961282, 38510840, 72019362, 11923835, 8055981, 42947632, 61271144, 26397786, 47893286, 17976208, 15163258, 94539824, 84166196, 17857111, 20836893, 19898053, 21993752, 3773993, 70727211, 93790285, 24826575, 5640302, 13819358, 29932657, 36685023, 48260151, 55615885, 37739481, 61741594, 83948335, 34295794, 72738685, 39847321, 92998591, 36808486, 65074535, 43152977, 67474219, 55669657, 41092102, 1250437, 5832946, 46540998, 40534591, 6871053, 247198, 99971982, 11365791, 74614639, 36139942, 2891150, 19891772, 75820087, 17957593, 71965942, 76315420, 73235980, 3880712, 508198, 26114953, 20568363, 95010552, 54014062, 9829782, 15075176, 34236719, 6525948, 61712234, 73222868, 54263880, 55753905, 3235882, 92554120, 36505482, 73124510, 98371444, 54868730, 6808825, 61859143, 85571389, 12024238, 50702367, 99224392, 30771409, 97057187, 61897582, 54517921, 13470059, 4806458, 90013093, 65271999, 55770687, 1022677, 77620120, 32426752, 12348118, 54232247, 18699206, 36780454, 7502255, 26493119, 86001008, 72274002, 93270084, 38009615, 82979980, 45428665, 63152504, 28796059, 40984766 +112651, 13819358, 92554120, 93566986, 4806458, 3487592, 15075176, 55850790, 16097038, 18504197, 16684829, 86242799, 61859581, 14947650, 45617087, 88698958, 22450468, 58749, 59981773, 47792865, 95290172, 51472275, 43045786, 3233569, 2331773, 16099750, 47708850, 95957797, 31126490, 9257405, 77072625, 37166608, 36780454, 74614639, 51588015, 90272749, 47361209, 73235980, 44664587, 91802888, 20568363, 34667286, 73392814, 89046466, 70004753, 569864, 89217461, 99729357, 78909561, 1239555, 23110625, 9188443, 74724075, 7011964, 58751351, 97379790, 4091162, 14363867, 39201414, 1375023, 43152977, 55247455, 5822202, 11757872, 83269727, 89078848, 50702367, 4064751, 10453030, 94595668, 44550764, 91281584, 83368048, 35512853, 99549373, 17764950, 86001008, 16445503, 90457870, 66667729, 78549759, 14326617, 84406788, 47893286, 70036438, 30891921, 8807940, 98739783, 47887470, 72793444, 8913721, 63506281, 73617245, 62936963, 61815223, 83948335, 91957544, 92692283, 57163802, 33249630, 12303248, 22766820, 83747892, 41245325, 4508700, 29834512, 52204879, 52060076, 59501487, 96193415, 99333375, 45381876, 14349098, 82779622, 37891451, 97057187, 67793644, 90090964, 48893685, 62693428, 36812683, 18833224, 44842615, 16405341, 85242963, 80316608, 45790169, 3233084, 38510840, 21001913, 68102437, 28550822, 63372756, 54762643, 9623492, 96420429, 57248122, 6038457, 74137926, 51315460, 89419466, 61271144, 36933038, 63628376, 67451935, 59405277, 18466635, 90061527, 51590803, 15163258, 89996536, 61712234, 85695762, 59197747, 5482538, 88130087, 30090481, 77377183, 52613508, 30569392, 5640302, 21289531, 61263068, 77312810, 23134715, 42199455, 76671482, 16019925, 84293052, 84002370, 45428665, 92604458, 66885828, 12348118, 18663507, 51464002, 51426311, 56473732, 41442762, 68644627, 38022834, 77787724, 27041967, 43376279, 96726697, 24314885, 72238278, 85571389, 29994197, 12024238, 60955663, 6819644, 57658654, 97783876, 33565483, 26744917, 67513640, 57240218, 96709982, 7502255, 71657078, 54058788, 59318837, 10597197, 73031054, 44983451, 22721500, 14220886, 2717150, 82897371, 62452034, 88904910, 526217, 43506672, 71083565, 54517921, 9599614, 36139942, 92803766, 60176618, 8115266, 90158785, 23432750, 20642888, 22405120, 9058407, 97940276, 26139110, 4434662, 33797252, 26493119, 18001617, 97561745, 81855445, 92215320, 57961282, 84684495, 72274002, 17068582, 88251446, 99658235, 65271999, 88653118, 508198, 82427263, 55602660, 3183975, 55470718, 91990218, 10358899, 22200378, 749283, 36189527, 6497830, 92398073, 94539824, 59614746, 64098930, 67227442, 32699744, 24058273, 89499542, 35456853, 8729146, 8129978, 7423788, 42644903, 38341669, 60581278, 37183543, 86543538, 7300047, 89804152, 55189057, 82052050, 13468268, 30653863, 46851987, 4798568, 80555751, 30463802, 7955293, 415901, 72738685, 29188588, 6793819, 89214616, 42073124, 7646095, 93562257, 6505939, 23740167, 55960386, 99965001, 47090124, 25636669, 33237508, 22113133, 2607799, 91664334, 7182642, 14045383, 77898274, 20122224, 82532312, 91938191, 19272365, 50188404, 68204242, 45919976, 10264691, 51466049, 83302115, 8733068, 78300864, 72357096, 70420215, 62762857, 55669657, 41092102, 31727379, 26292919, 99224392, 21397057, 30771409, 40534591, 74743862, 8696647, 45306070, 321665, 72358170, 44627776, 57020481, 45075471, 39553046, 92530431, 82339363, 26664538, 82327024, 93057697, 91240048, 83651211, 72725103, 41092172, 76540481, 35996293, 2891150, 93053405, 68041839, 44473167, 57241521, 58208470, 3509435, 44348328, 25842078, 17957593, 71965942, 90013093, 17894977, 43357947, 26998766, 91141395, 58224549, 28787861, 53842979, 26776922, 49328608, 96735716, 26114953, 33628349, 75052463, 98943869, 9860968, 83150534, 95010552, 26392416, 42237907, 36580610, 30366150, 64848072, 17976208, 6157724, 14626618, 34493392, 54663246, 6525948, 98130363, 15015906, 20681921, 65338021, 78884452, 22879907, 62232211, 22942635, 44844121, 68110330, 38009615, 64087743, 3773993, 22108665, 45990383, 78561158, 69255765, 62803858, 73168565, 29932657, 17016156, 42967683, 94360702, 98653983, 40197395, 85117092, 76703609, 48260151, 54427233, 79880247, 84904436, 62740044, 72614359, 44847298, 61741594, 39847321, 29635537, 9175338, 85116755, 7685448, 99690194, 59177718, 43322743, 42692881, 77694700, 44245960, 81172706, 71522968, 36808486, 86118021, 34044787, 71333116, 29466406, 85711894, 41481685, 61859143, 27625735, 92692978, 27665211, 18699206, 49271185, 1204161, 65074535, 56153345, 23194618, 29401781, 72777973, 72373496, 64055960, 1431742, 66319530, 98948034, 99226875, 66663942, 61982238, 34497327, 77187825, 62428472, 11581181, 39986008, 45996863, 6986898, 17386996, 22129328, 47738185, 45995325, 40152546, 247198, 94076128, 4985896, 11365791, 92353856, 69697787, 68694897, 5111370, 56531125, 29065964, 19457589, 95251277, 43501211, 61960400, 79191827, 99917599, 32161669, 69136837, 13470059, 27411561, 79942022, 91727510, 29510992, 17539625, 13231279, 75820087, 96852321, 8099994, 51715482, 84840549, 61623915, 11923835, 34698428, 45237957, 3088684, 28796059, 3880712, 62115552, 81805959, 8651647, 91831487, 75229462, 57393458, 13862149, 37280276, 48675329, 9860195, 65454636, 11161731, 27325428, 77272628, 30764367, 14723410, 1197320, 6153406, 70596786, 19898053, 38658347, 73222868, 21673260, 62490109, 80246713, 54263880, 87598888, 31733363, 40027975, 63059024, 10649306, 95395112, 20765474, 31161687, 62051033, 75777973, 61728685, 82886381, 33123618, 29959549, 1022677, 77301523, 33553959, 6111563, 39171895, 48088883, 53666583, 58180653, 26063929, 874791, 65081429, 26507214, 36845587, 32590267, 37560164, 18411915, 98371444, 51047803, 32426752, 63967300, 92998591, 27187213, 29029316, 44889423, 33699435, 89811711, 18131876, 88047921, 16054533, 36135, 81774825, 7517032, 24915585, 30811010, 90654836, 54232247, 65047700, 86798033, 92033260, 76960303, 4069912, 99021067, 73786944, 94711842, 20645197, 4668450, 14731700, 20002147, 20140249, 9603598, 54606384, 87710366, 17037369, 84187166, 68939068, 15536795, 40686254, 17385531, 1250437, 79821897, 50806615, 97641116, 30163921, 66832478, 91255408, 6521313, 2917920, 67124282, 44846932, 45049308, 32274392, 31904591, 68824981, 15148031, 24733232, 59109400, 76825057, 4787945, 45407418, 59371804, 49598724, 45667668, 29516592, 98462867, 83133790, 33304202, 72019362, 66611759, 93270084, 66250369, 33895336, 2208785, 79922758, 23569917, 1936762, 68816413, 60106217, 14093520, 67084351, 54014062, 44915235, 37957788, 53802686, 15948937, 97011160, 81677380, 62430984, 38008118, 16595436, 53393358, 55770687, 21070110, 82178706, 81898046, 40984766, 12416768, 38061439, 24619760, 44177011, 176257, 15064655, 76170907, 48360198, 65880522, 64602895, 67031644, 13348726, 98648327, 47213183, 37675718, 82979980, 26275734, 36685023, 34698463, 51507425, 73124510, 77620120, 75986488, 48658605, 96906410, 54868730, 70372191, 37620363, 79136082, 7066775, 18783702, 4099191, 56461322, 8791066, 17146629, 39373729, 71316369, 5970607, 74357852, 49641577, 82651278, 90004325, 40781449, 72732205, 4119747, 63015256, 44479073, 16567550, 18806856, 99524975, 56484900, 30218878, 73021291, 56424103, 74452589, 89699445, 12664567, 36396314, 53632373, 84127901, 83155442, 17727650, 46540998, 16380211, 99971982, 168541, 54987042, 669105, 86821229, 72278539, 89637706, 94911072, 99125126, 9886593, 36753250, 44481640, 10366309, 5267545, 84349107, 38645117, 70800879, 83533741, 66137019, 75543508, 97281847, 8373289, 8634541, 78218845, 19891772, 69579137, 69641513, 26863229, 19939935, 35092039, 76315420, 93359396, 87160386, 28928175, 40865610, 10961421, 36312813, 30998561, 60430369, 85023028, 42947632, 36468541, 70782102, 88444207, 1788101, 49236559, 9829782, 93515664, 10309525, 32250045, 72495719, 17365188, 68128438, 32159704, 34236719, 26734892, 45794415, 69605283, 20486294, 53648154, 1569515, 79738755, 93790285, 75135448, 55753905, 19486173, 78589145, 76434144, 68000591, 61928316, 62552524, 3235882, 60102965, 4204661, 51149804, 42307449, 80014588, 37739481, 35192533, 48892201, 38365584, 49597667, 41380093, 92541302, 15535065, 69786756, 71469330, 59329511, 78766358, 55143724, 68316156, 57359924, 37659250, 79806380, 87720882, 95726235, 60393039, 47298834, 40781854, 67474219, 3294781, 62496012, 79322415, 38256849, 24168411, 8128637, 99515901, 82726008, 76330843, 34946859, 61897582, 77300457, 83210802, 40677414, 90310261, 4095116, 33336148, 94090109, 13173644, 27185644, 78785507, 6221471, 62357986, 8055981, 79657802, 4515343, 7229550, 78602717, 30694952, 9259676, 38494874, 4199704, 4978543, 22997281, 84166196, 7919588, 24591705, 3633375, 53547802, 21993752, 15902805, 86460488, 30395570, 30787683, 20867149, 83789391, 70727211, 37224844, 24826575, 9575954, 86301513, 39801351, 65275241, 92867155, 43933006, 17058722, 30543215, 66271566, 11543098, 70367851, 63152504, 67281495, 59910624, 69355476, 71920426, 74110882, 66428795, 37435892, 24953498, 67030811, 36930650, 86022504, 50567636, 56955985, 45848907, 77413012, 57803235, 17081350, 52261574, 48774913, 29819940, 32151165, 65851721, 9398733, 56515456, 19376156, 48673079, 26102057, 22435353, 80251430, 48395186, 7104732, 68702632, 66903004, 69848388, 65715134, 99604946, 61373987, 99861373, 12571310, 44784505, 92071990, 68875490, 93933709, 36505482, 19101477, 34295794, 71300104, 5073754, 2204165, 37501808, 70541760, 99297164, 16424199, 28851716, 33935899, 40356877, 31491938, 824872, 46870723, 5832946, 34432810, 65017137, 23848565, 92787493, 94516935, 33431960, 21533347, 49882705, 53084256, 95581843, 66045587, 42782652, 81274566, 21919959, 28734791, 20885148, 17857111, 33061250, 64157906, 75407347, 55615885, 6808825, 50007421, 16971929, 32058615, 50668599, 83083131, 77284619, 44060493, 96318094, 6871053, 61141874, 75153252, 26397786, 20836893, 52293995, 66322959, 73109997, 7687278, 74441448, 33201905, 37192445, 16387575, 65038678, 75128745, 54199160, 66116458, 53888755, 81853704, 30139692 +59614746, 56461322, 6871053, 4787945, 28734791, 33237508, 14349098, 71657078, 57020481, 95251277, 43506672, 80316608, 81855445, 87160386, 78602717, 70782102, 26734892, 86301513, 20765474, 13468268, 72738685, 42692881, 47090124, 14045383, 37659250, 68204242, 8128637, 83155442, 67793644, 82339363, 82327024, 84349107, 16405341, 45790169, 83150534, 6497830, 31161687, 17016156, 94360702, 36845587, 80555751, 12348118, 71469330, 72732205, 18699206, 86242799, 97783876, 77187825, 37166608, 11581181, 96709982, 86821229, 68694897, 79191827, 61859581, 69136837, 38645117, 79942022, 45617087, 90272749, 8634541, 68102437, 36312813, 14326617, 65338021, 44844121, 64087743, 89046466, 76170907, 12571310, 21289531, 40197395, 85117092, 51507425, 46851987, 44847298, 30463802, 57163802, 48658605, 7685448, 6505939, 23740167, 7011964, 33699435, 29834512, 16424199, 20122224, 4119747, 99021067, 60955663, 6819644, 67030811, 56424103, 77072625, 87710366, 84187166, 68939068, 52261574, 82779622, 40152546, 73031054, 97641116, 74743862, 94911072, 91281584, 65038678, 43501211, 74614639, 66137019, 2891150, 47361209, 21533347, 51715482, 75153252, 45237957, 44664587, 68702632, 54199160, 81805959, 78549759, 66903004, 42947632, 95290172, 36468541, 13862149, 10358899, 37280276, 47893286, 89996536, 14723410, 7919588, 65715134, 32699744, 15902805, 22108665, 69255765, 5640302, 39801351, 95395112, 60581278, 29959549, 37675718, 77312810, 42967683, 86543538, 39171895, 76703609, 72614359, 23110625, 67281495, 56473732, 5970607, 18131876, 96726697, 88047921, 55143724, 49641577, 41481685, 77898274, 40781449, 82532312, 65074535, 14363867, 87720882, 72373496, 16567550, 95726235, 12024238, 9603598, 55669657, 77413012, 17386996, 247198, 10597197, 97057187, 168541, 53888755, 54987042, 9398733, 44846932, 36812683, 61960400, 92530431, 44481640, 5267545, 23432750, 51588015, 4806458, 26102057, 85242963, 18001617, 33431960, 19891772, 21001913, 43357947, 76315420, 22450468, 93359396, 88251446, 99658235, 53084256, 75128745, 58749, 28796059, 79657802, 49328608, 4515343, 62115552, 38494874, 9860968, 36189527, 30764367, 54663246, 17857111, 67227442, 53393358, 70596786, 73392814, 81898046, 30891921, 21673260, 70727211, 87598888, 75135448, 59197747, 5482538, 98648327, 8129978, 43045786, 13819358, 62051033, 89217461, 33553959, 26275734, 89804152, 48260151, 26063929, 80014588, 32590267, 49597667, 73124510, 29188588, 9175338, 71300104, 12303248, 43322743, 47708850, 37501808, 93562257, 25636669, 59329511, 27041967, 52204879, 28851716, 92692978, 71920426, 49271185, 51466049, 47298834, 31491938, 24953498, 20645197, 1431742, 30218878, 20140249, 59501487, 99226875, 96193415, 54606384, 37192445, 84127901, 6986898, 46870723, 34946859, 32151165, 16380211, 67124282, 56515456, 99125126, 9886593, 77300457, 15148031, 26664538, 90310261, 83651211, 72725103, 20642888, 4095116, 27411561, 35996293, 80251430, 17539625, 13231279, 96852321, 86001008, 83133790, 71965942, 38510840, 27185644, 16445503, 3487592, 9623492, 95581843, 3880712, 3183975, 74137926, 8651647, 81274566, 59981773, 47792865, 36580610, 49236559, 749283, 48675329, 37957788, 9860195, 59405277, 6525948, 69848388, 20681921, 24058273, 21070110, 53648154, 82178706, 22942635, 68110330, 12416768, 3773993, 37224844, 19486173, 76434144, 92071990, 73168565, 61728685, 55850790, 82886381, 23134715, 48088883, 3233569, 30543215, 36685023, 16019925, 874791, 84293052, 78909561, 37739481, 35192533, 61741594, 37560164, 77620120, 75986488, 6793819, 16099750, 98371444, 92998591, 27187213, 74724075, 63152504, 79136082, 83747892, 18504197, 39373729, 89811711, 29466406, 43376279, 81774825, 24915585, 27625735, 54232247, 27665211, 33935899, 91938191, 39201414, 73786944, 40356877, 83083131, 40781854, 20002147, 77284619, 98948034, 824872, 57658654, 11757872, 36780454, 31727379, 67513640, 53632373, 82726008, 40534591, 96318094, 61897582, 44983451, 91255408, 22721500, 14220886, 11365791, 8696647, 5111370, 321665, 72358170, 88904910, 61141874, 29065964, 36753250, 24733232, 36139942, 60176618, 76825057, 76540481, 94516935, 91727510, 22435353, 68041839, 45667668, 97561745, 94090109, 78218845, 57961282, 75820087, 84684495, 19939935, 17957593, 72019362, 26998766, 40865610, 65271999, 48395186, 93270084, 66250369, 53842979, 26776922, 508198, 91802888, 51315460, 21919959, 61271144, 1788101, 22200378, 67451935, 65454636, 92398073, 10309525, 32250045, 22997281, 68128438, 38008118, 98130363, 1197320, 61712234, 53547802, 19898053, 55770687, 69605283, 20486294, 62490109, 99604946, 33061250, 30395570, 79738755, 93790285, 55753905, 48360198, 13348726, 45990383, 63059024, 68875490, 98739783, 75777973, 1022677, 43933006, 7300047, 17058722, 51149804, 34698463, 63506281, 73617245, 30653863, 65081429, 84002370, 26507214, 4798568, 61815223, 19101477, 1239555, 92692283, 51047803, 42073124, 63967300, 44889423, 71522968, 18663507, 37620363, 73109997, 51464002, 70541760, 7066775, 55960386, 50007421, 8791066, 41442762, 58751351, 22113133, 68644627, 77787724, 74357852, 85711894, 32058615, 7517032, 82651278, 4091162, 74110882, 79806380, 65047700, 63015256, 76960303, 50188404, 56153345, 29401781, 85571389, 60393039, 56484900, 55247455, 73021291, 79322415, 74452589, 86022504, 24168411, 26744917, 57240218, 50702367, 40686254, 47738185, 48774913, 21397057, 44060493, 5832946, 54058788, 30163921, 2717150, 69697787, 2917920, 45306070, 62452034, 71083565, 32274392, 83368048, 31904591, 93566986, 44842615, 9599614, 93057697, 59109400, 8115266, 90158785, 35512853, 45407418, 92787493, 41092172, 26139110, 26493119, 29516592, 92215320, 3509435, 3233084, 35092039, 78785507, 61623915, 6221471, 54762643, 62357986, 7104732, 58224549, 73235980, 28787861, 96735716, 57248122, 79922758, 33628349, 9259676, 75052463, 68816413, 36933038, 91990218, 63628376, 64848072, 9829782, 18466635, 17365188, 14626618, 34493392, 64098930, 20836893, 89499542, 85695762, 21993752, 22879907, 38009615, 86460488, 61373987, 1569515, 20867149, 31733363, 40027975, 15064655, 30090481, 68000591, 9575954, 3235882, 47213183, 66116458, 92554120, 77301523, 37183543, 93933709, 6111563, 98653983, 55189057, 66271566, 52293995, 99729357, 79880247, 48892201, 91957544, 15535065, 39847321, 92604458, 96906410, 70372191, 59177718, 112651, 66885828, 44245960, 2204165, 51426311, 18783702, 4099191, 4508700, 34044787, 95957797, 78766358, 16054533, 59910624, 36135, 68316156, 57359924, 69355476, 1204161, 19272365, 66428795, 4069912, 50668599, 9257405, 10264691, 18806856, 64055960, 72357096, 62496012, 34497327, 89699445, 99333375, 26292919, 1250437, 99224392, 4064751, 94076128, 94595668, 72278539, 4985896, 6521313, 90090964, 89637706, 92803766, 83210802, 40677414, 70800879, 75543508, 59371804, 4434662, 44473167, 8373289, 57241521, 29510992, 88698958, 33304202, 17068582, 8099994, 84840549, 28928175, 10961421, 34698428, 66611759, 88653118, 66667729, 60430369, 7229550, 6038457, 26114953, 60106217, 75229462, 88444207, 26397786, 26392416, 67084351, 57393458, 54014062, 70036438, 20885148, 15948937, 90061527, 15163258, 32159704, 15015906, 3633375, 78884452, 35456853, 8807940, 38061439, 44177011, 70004753, 83789391, 67031644, 44784505, 52613508, 30569392, 38341669, 62803858, 33123618, 60102965, 4204661, 53666583, 42199455, 82052050, 76671482, 84904436, 415901, 38365584, 45428665, 85116755, 9188443, 18411915, 89214616, 99690194, 36808486, 86118021, 17146629, 99297164, 71333116, 61859143, 52060076, 90654836, 23194618, 72238278, 29994197, 1375023, 99524975, 4668450, 14731700, 66319530, 5822202, 66663942, 70420215, 41092102, 33201905, 33565483, 15536795, 12664567, 36396314, 99515901, 76330843, 22129328, 79821897, 59318837, 37891451, 34432810, 65851721, 44550764, 62693428, 65017137, 45049308, 54517921, 45075471, 32161669, 23848565, 22405120, 83533741, 14947650, 97940276, 97281847, 13173644, 44348328, 69641513, 98462867, 90013093, 17894977, 91141395, 66045587, 82427263, 85023028, 14093520, 42237907, 30366150, 84406788, 4199704, 44915235, 51590803, 72495719, 77272628, 81677380, 62430984, 34236719, 24591705, 6153406, 73222868, 62232211, 30787683, 65880522, 64602895, 77377183, 61928316, 65275241, 92867155, 16097038, 72793444, 58180653, 11543098, 66322959, 7955293, 83948335, 41380093, 54868730, 69786756, 33249630, 22766820, 77694700, 7646095, 41245325, 71316369, 38022834, 31126490, 7182642, 97379790, 44479073, 8733068, 94711842, 74441448, 3294781, 36930650, 62762857, 38256849, 45996863, 30771409, 46540998, 99971982, 66832478, 669105, 92353856, 56531125, 19457589, 68824981, 19376156, 99549373, 17764950, 48673079, 33797252, 49598724, 69579137, 72274002, 90457870, 11923835, 3088684, 33895336, 2208785, 20568363, 55470718, 95010552, 51472275, 15075176, 11161731, 53802686, 34667286, 84166196, 16595436, 45794415, 38658347, 24619760, 54263880, 99861373, 8729146, 88130087, 78589145, 62552524, 75407347, 10649306, 42644903, 29932657, 47887470, 61263068, 569864, 82979980, 8913721, 42307449, 62936963, 34295794, 5073754, 81172706, 2607799, 91664334, 7687278, 86798033, 16684829, 72777973, 83302115, 43152977, 61982238, 50567636, 39986008, 45848907, 89078848, 17081350, 7502255, 10453030, 45995325, 82897371, 48893685, 44627776, 16387575, 18833224, 10366309, 91240048, 13470059, 9058407, 93053405, 49882705, 28550822, 8055981, 30998561, 96420429, 55602660, 23569917, 30694952, 98943869, 91831487, 89419466, 93515664, 4978543, 17976208, 6157724, 27325428, 94539824, 80246713, 176257, 64157906, 78561158, 54427233, 55615885, 36505482, 29635537, 32426752, 29029316, 99965001, 16971929, 90004325, 24314885, 92033260, 78300864, 62428472, 57803235, 50806615, 526217, 39553046, 99917599, 30139692, 58208470, 25842078, 63372756, 1936762, 81853704, 40984766, 7423788, 2331773, 62740044, 92541302, 70367851, 6808825, 30811010, 37435892, 83269727, 56955985, 17727650, 97011160, 45381876, 17385531, 29819940, 33336148, 45919976, 26863229, 42782652, 24826575, 67474219, 17037369 +17539625, 14723410, 96726697, 77898274, 44844121, 43933006, 44889423, 74110882, 72777973, 98948034, 74743862, 5111370, 31904591, 68041839, 57961282, 72019362, 22108665, 92604458, 87720882, 86242799, 22721500, 44550764, 93566986, 94090109, 78602717, 14326617, 1788101, 57393458, 67451935, 81677380, 20486294, 8729146, 63059024, 89217461, 30543215, 84293052, 80555751, 93562257, 67281495, 58751351, 5970607, 90004325, 76960303, 94711842, 39986008, 52261574, 7502255, 2917920, 57020481, 54517921, 32274392, 45407418, 48673079, 33431960, 33304202, 10961421, 44664587, 49328608, 85023028, 81274566, 70782102, 22200378, 28734791, 4978543, 14626618, 34236719, 1197320, 85695762, 35456853, 30787683, 47213183, 86301513, 43045786, 13819358, 62803858, 98653983, 13468268, 84904436, 65081429, 62740044, 32590267, 92692283, 42073124, 12303248, 43322743, 7646095, 68644627, 41481685, 73786944, 40781854, 6819644, 30218878, 56424103, 77072625, 79821897, 45306070, 72358170, 82327024, 93057697, 99549373, 59371804, 80316608, 86001008, 38510840, 17894977, 66611759, 9259676, 55470718, 54014062, 49236559, 70036438, 92398073, 10309525, 90061527, 94539824, 45794415, 21070110, 53648154, 22879907, 38009615, 19486173, 62552524, 65275241, 82886381, 93933709, 94360702, 17058722, 99729357, 77620120, 6793819, 32426752, 22766820, 37620363, 18504197, 56473732, 25636669, 41442762, 77787724, 27041967, 88047921, 49641577, 66428795, 23194618, 60393039, 67030811, 72357096, 59501487, 99333375, 31727379, 17037369, 8128637, 15536795, 17727650, 34946859, 59318837, 97057187, 73031054, 61897582, 86821229, 4985896, 68694897, 29065964, 16387575, 61960400, 92530431, 44842615, 9599614, 60176618, 76825057, 8115266, 72725103, 22405120, 76540481, 97940276, 91727510, 90272749, 57241521, 80251430, 88698958, 8099994, 90457870, 78785507, 88251446, 75153252, 88653118, 66667729, 28796059, 60430369, 57248122, 6038457, 26114953, 33628349, 95290172, 13862149, 64848072, 51472275, 37957788, 11161731, 18466635, 62430984, 89996536, 59614746, 54663246, 17857111, 81853704, 32699744, 81898046, 80246713, 3773993, 44177011, 20867149, 37224844, 5482538, 78589145, 67031644, 68000591, 9575954, 30569392, 39801351, 55850790, 47887470, 17016156, 29959549, 77301523, 23134715, 58180653, 11543098, 54427233, 26507214, 23110625, 85116755, 9188443, 18411915, 48658605, 96906410, 99690194, 69786756, 59177718, 36808486, 99965001, 18783702, 4099191, 47090124, 33699435, 34044787, 95957797, 85711894, 59910624, 16424199, 36135, 61859143, 4091162, 4119747, 54232247, 91938191, 49271185, 86798033, 14363867, 4069912, 68204242, 10264691, 43152977, 14731700, 3294781, 20140249, 99226875, 77187825, 41092102, 87710366, 45848907, 12664567, 26292919, 89078848, 17081350, 82726008, 99224392, 46540998, 247198, 94076128, 168541, 53888755, 54987042, 669105, 72278539, 91255408, 8696647, 321665, 44846932, 19457589, 65038678, 43506672, 43501211, 91240048, 69136837, 83651211, 4787945, 26102057, 83533741, 26139110, 93053405, 8373289, 19891772, 81855445, 44348328, 43357947, 76315420, 28928175, 61623915, 75128745, 63372756, 58224549, 73235980, 9623492, 45237957, 68702632, 79657802, 30998561, 4515343, 62115552, 74137926, 30694952, 51315460, 20568363, 83150534, 36580610, 47893286, 93515664, 59405277, 6157724, 65454636, 15948937, 32159704, 84166196, 6525948, 3633375, 6153406, 19898053, 69605283, 73222868, 30891921, 38061439, 12571310, 24826575, 48360198, 30090481, 44784505, 92071990, 5640302, 10649306, 31161687, 75777973, 33553959, 82979980, 37183543, 42967683, 6111563, 16097038, 53666583, 85117092, 48260151, 55615885, 2331773, 62936963, 36505482, 35192533, 61815223, 61741594, 38365584, 73124510, 92541302, 75986488, 51047803, 71300104, 27187213, 12348118, 74724075, 71522968, 63152504, 41245325, 86118021, 89811711, 59329511, 52204879, 2607799, 16054533, 81774825, 69355476, 99021067, 9257405, 83302115, 8733068, 37435892, 24953498, 4668450, 67474219, 77284619, 66663942, 57658654, 36930650, 97783876, 55669657, 24168411, 89699445, 67513640, 36396314, 53632373, 6986898, 40686254, 1250437, 21397057, 71657078, 54058788, 34432810, 94595668, 67793644, 97641116, 11365791, 94911072, 91281584, 36753250, 95251277, 61859581, 84349107, 38645117, 13470059, 35512853, 17764950, 20642888, 79942022, 75543508, 4434662, 97561745, 29516592, 29510992, 84684495, 96852321, 83133790, 21001913, 27185644, 22450468, 58749, 34698428, 65271999, 48395186, 7104732, 8055981, 91802888, 3183975, 8651647, 9860968, 91831487, 42947632, 75229462, 21919959, 36933038, 63628376, 749283, 4199704, 6497830, 51590803, 72495719, 20836893, 61712234, 78884452, 21993752, 38658347, 40984766, 24619760, 61373987, 70004753, 83789391, 31733363, 75135448, 78561158, 66116458, 98739783, 21289531, 77312810, 39171895, 48088883, 89804152, 55189057, 36685023, 51149804, 52293995, 34698463, 16019925, 51507425, 874791, 79880247, 4798568, 72614359, 1239555, 91957544, 41380093, 72738685, 45428665, 70372191, 92998591, 66885828, 47708850, 18663507, 2204165, 71469330, 73109997, 6808825, 51426311, 50007421, 56461322, 55143724, 82651278, 40781449, 27625735, 37659250, 7687278, 19272365, 72238278, 72373496, 29994197, 39201414, 51466049, 1431742, 20002147, 66319530, 824872, 5822202, 79322415, 86022504, 26744917, 84127901, 50702367, 83155442, 76330843, 48774913, 82779622, 40534591, 96318094, 16380211, 50806615, 30163921, 44983451, 90090964, 82897371, 92353856, 88904910, 44627776, 36812683, 45049308, 77300457, 45075471, 79191827, 15148031, 24733232, 32161669, 10366309, 51588015, 4806458, 92787493, 9058407, 27411561, 22435353, 45790169, 33797252, 49598724, 26493119, 47361209, 58208470, 92215320, 98462867, 26863229, 3233084, 90013093, 72274002, 99658235, 3487592, 91141395, 36312813, 54199160, 33895336, 26776922, 66045587, 508198, 82427263, 2208785, 55602660, 98943869, 1936762, 14093520, 84406788, 9829782, 34667286, 77272628, 22997281, 69848388, 98130363, 65715134, 53393358, 89499542, 82178706, 62232211, 22942635, 15902805, 68110330, 12416768, 99604946, 33061250, 1569515, 176257, 79738755, 93790285, 64602895, 88130087, 76434144, 45990383, 3235882, 69255765, 68875490, 92554120, 92867155, 1022677, 61263068, 7300047, 3233569, 42199455, 82052050, 66271566, 42307449, 73617245, 46851987, 37739481, 7955293, 48892201, 49597667, 29188588, 9175338, 63967300, 5073754, 44245960, 6505939, 51464002, 7066775, 8791066, 4508700, 33237508, 22113133, 16971929, 31126490, 7182642, 97379790, 7517032, 71920426, 79806380, 24314885, 63015256, 16684829, 50188404, 56153345, 50668599, 40356877, 16567550, 95726235, 47298834, 56484900, 74441448, 60955663, 73021291, 61982238, 34497327, 11757872, 54606384, 57240218, 46870723, 4064751, 29819940, 40152546, 37891451, 2717150, 65017137, 71083565, 39553046, 83368048, 68824981, 44481640, 26664538, 5267545, 83210802, 40677414, 16405341, 85242963, 66137019, 2891150, 30139692, 44473167, 13173644, 13231279, 75820087, 3509435, 69641513, 19939935, 71965942, 17068582, 87160386, 26998766, 68102437, 40865610, 28550822, 11923835, 54762643, 66250369, 3880712, 96420429, 7229550, 78549759, 23569917, 38494874, 66903004, 47792865, 95010552, 61271144, 88444207, 91990218, 36189527, 9860195, 34493392, 15163258, 30764367, 26734892, 67227442, 53547802, 70596786, 55770687, 21673260, 8807940, 70727211, 99861373, 65880522, 77377183, 61928316, 52613508, 8129978, 95395112, 20765474, 42644903, 38341669, 62051033, 73168565, 29932657, 60581278, 37675718, 86543538, 72793444, 40197395, 8913721, 76703609, 63506281, 66322959, 30463802, 16099750, 7685448, 33249630, 29029316, 79136082, 83747892, 17146629, 99297164, 71333116, 29834512, 78766358, 14045383, 32058615, 24915585, 30811010, 90654836, 28851716, 82532312, 27665211, 65047700, 92033260, 85571389, 45919976, 12024238, 31491938, 55247455, 20645197, 64055960, 62496012, 9603598, 37166608, 33201905, 83269727, 33565483, 56955985, 45381876, 45996863, 22129328, 14349098, 5832946, 10453030, 45995325, 99971982, 65851721, 6521313, 67124282, 56515456, 62693428, 89637706, 62452034, 61141874, 18833224, 82339363, 23848565, 36139942, 4095116, 35996293, 33336148, 97281847, 17957593, 51715482, 93270084, 95581843, 81805959, 26392416, 20885148, 97011160, 68128438, 7919588, 20681921, 62490109, 15064655, 55753905, 59197747, 64157906, 98648327, 61728685, 60102965, 30653863, 44847298, 36845587, 78909561, 19101477, 415901, 37560164, 34295794, 29635537, 57163802, 42692881, 77694700, 81172706, 37501808, 70541760, 7011964, 39373729, 91664334, 68316156, 52060076, 57359924, 20122224, 72732205, 65074535, 44479073, 96193415, 62762857, 36780454, 62428472, 11581181, 50567636, 37192445, 77413012, 96709982, 17385531, 47738185, 6871053, 48893685, 99917599, 19376156, 92803766, 90310261, 23432750, 41092172, 94516935, 45617087, 78218845, 69579137, 21533347, 25842078, 35092039, 93359396, 84840549, 3088684, 96735716, 68816413, 26397786, 10358899, 15075176, 32250045, 27325428, 17365188, 64098930, 24591705, 24058273, 86460488, 30395570, 89046466, 87598888, 40027975, 569864, 26275734, 26063929, 83948335, 98371444, 54868730, 23740167, 55960386, 71316369, 38022834, 43376279, 92692978, 33935899, 29401781, 1375023, 78300864, 83083131, 17386996, 99515901, 69697787, 9398733, 56531125, 99125126, 9886593, 74614639, 90158785, 45667668, 18001617, 8634541, 16445503, 49882705, 53084256, 79922758, 89419466, 59981773, 67084351, 37280276, 53802686, 65338021, 64087743, 4204661, 15535065, 112651, 18131876, 18699206, 1204161, 74452589, 38256849, 68939068, 30771409, 32151165, 66832478, 526217, 59109400, 14947650, 28787861, 42782652, 75052463, 60106217, 36468541, 42237907, 15015906, 73392814, 54263880, 7423788, 80014588, 39847321, 89214616, 70367851, 74357852, 18806856, 99524975, 57803235, 44060493, 10597197, 14220886, 70800879, 30366150, 48675329, 44915235, 76170907, 13348726, 75407347, 33123618, 76671482, 84002370, 29466406, 70420215, 6221471, 62357986, 53842979, 17976208, 38008118, 16595436, 84187166 +7685448, 94911072, 24733232, 6505939, 80316608, 57241521, 54762643, 1788101, 53802686, 78589145, 37739481, 83948335, 57163802, 51426311, 37659250, 45848907, 4985896, 44846932, 32161669, 5267545, 29516592, 8099994, 66250369, 26397786, 48675329, 44915235, 176257, 82886381, 77301523, 42199455, 16019925, 84293052, 4798568, 77620120, 36808486, 39373729, 14045383, 19272365, 44479073, 57658654, 15536795, 54058788, 32151165, 90090964, 36753250, 43501211, 23432750, 97940276, 3509435, 19939935, 99658235, 48395186, 36312813, 95581843, 28796059, 3880712, 79657802, 62115552, 85023028, 30764367, 64098930, 62490109, 30395570, 75407347, 60581278, 39171895, 76671482, 13468268, 874791, 79880247, 55615885, 35192533, 39847321, 98371444, 12303248, 42692881, 29029316, 51464002, 8791066, 59329511, 81774825, 77898274, 52060076, 50188404, 99021067, 40356877, 94711842, 20002147, 72357096, 99333375, 29819940, 79821897, 59318837, 73031054, 2717150, 48893685, 45306070, 56531125, 89637706, 72358170, 74614639, 59109400, 60176618, 90158785, 48673079, 66137019, 4434662, 69641513, 35092039, 88653118, 68702632, 49328608, 55602660, 78602717, 20568363, 9860968, 60106217, 36580610, 10358899, 20885148, 17857111, 81853704, 44844121, 13348726, 9575954, 20765474, 17016156, 1022677, 37675718, 23134715, 86543538, 43933006, 80014588, 84904436, 30463802, 9175338, 16099750, 51047803, 99690194, 59177718, 81172706, 23740167, 29466406, 74357852, 96726697, 88047921, 32058615, 24915585, 30811010, 72732205, 7687278, 1204161, 76960303, 29994197, 12024238, 60393039, 47298834, 43152977, 78300864, 4668450, 67474219, 77284619, 30218878, 89699445, 11581181, 31727379, 26744917, 26292919, 53632373, 46870723, 4064751, 48774913, 82779622, 46540998, 6871053, 10597197, 86821229, 6521313, 43506672, 83368048, 9599614, 69136837, 35512853, 85242963, 91727510, 22435353, 90272749, 69579137, 96852321, 86001008, 43357947, 26998766, 62357986, 7104732, 60430369, 23569917, 59981773, 70782102, 26392416, 13862149, 749283, 4199704, 15075176, 34667286, 51590803, 97011160, 14626618, 62430984, 69848388, 61712234, 32699744, 6153406, 54263880, 89046466, 70727211, 40027975, 75135448, 59197747, 3235882, 52613508, 78561158, 63059024, 39801351, 38341669, 31161687, 55850790, 77312810, 89217461, 94360702, 48088883, 51149804, 85117092, 48260151, 62740044, 36845587, 45428665, 9188443, 54868730, 5073754, 37501808, 18504197, 41245325, 4099191, 56461322, 4508700, 5970607, 77787724, 27041967, 55143724, 49641577, 65047700, 23194618, 72777973, 9257405, 1431742, 6819644, 36930650, 38256849, 33201905, 84187166, 50567636, 57803235, 83155442, 168541, 30163921, 54987042, 14220886, 11365791, 56515456, 62693428, 88904910, 16387575, 45075471, 15148031, 61859581, 36139942, 83651211, 22405120, 4095116, 79942022, 49598724, 18001617, 97561745, 30139692, 13173644, 21533347, 88698958, 17894977, 76315420, 87160386, 68102437, 3487592, 34698428, 58224549, 73235980, 44664587, 82427263, 96420429, 8651647, 89419466, 75229462, 83150534, 54014062, 37280276, 36189527, 17976208, 90061527, 59614746, 89499542, 80246713, 38009615, 12416768, 99604946, 33061250, 76170907, 65880522, 64157906, 98648327, 47213183, 7423788, 10649306, 92554120, 75777973, 29932657, 21289531, 61263068, 89804152, 17058722, 42307449, 99729357, 26507214, 72614359, 44847298, 73124510, 15535065, 70372191, 112651, 63967300, 93562257, 70541760, 18783702, 7011964, 58751351, 34044787, 71333116, 29834512, 54232247, 71920426, 65074535, 87720882, 73786944, 45919976, 8733068, 99524975, 56484900, 40781854, 73021291, 59501487, 5822202, 56424103, 96193415, 79322415, 74452589, 54606384, 36780454, 83269727, 68939068, 12664567, 77413012, 84127901, 17081350, 17385531, 99224392, 5832946, 45995325, 94076128, 16380211, 67793644, 50806615, 669105, 44550764, 8696647, 5111370, 62452034, 57020481, 77300457, 95251277, 44481640, 83210802, 76825057, 8115266, 35996293, 33797252, 68041839, 33336148, 26493119, 8373289, 33431960, 80251430, 19891772, 75820087, 3233084, 83133790, 71965942, 38510840, 72274002, 16445503, 22450468, 84840549, 61623915, 53084256, 91141395, 96735716, 508198, 91802888, 78549759, 91831487, 88444207, 22200378, 6497830, 37957788, 9860195, 59405277, 32250045, 27325428, 17365188, 15163258, 89996536, 84166196, 53393358, 70596786, 85695762, 21070110, 20486294, 73222868, 22942635, 21673260, 8807940, 40984766, 44177011, 70004753, 83789391, 99861373, 79738755, 12571310, 55753905, 24826575, 48360198, 88130087, 67031644, 77377183, 22108665, 44784505, 86301513, 8129978, 43045786, 65275241, 62803858, 33123618, 47887470, 93933709, 7300047, 58180653, 82052050, 30543215, 66271566, 52293995, 34698463, 51507425, 73617245, 78909561, 36505482, 61815223, 7955293, 61741594, 48892201, 38365584, 37560164, 41380093, 92541302, 29188588, 6793819, 92998591, 22766820, 71522968, 18663507, 83747892, 99965001, 17146629, 99297164, 33237508, 71316369, 2607799, 91664334, 31126490, 7182642, 16054533, 97379790, 40781449, 4119747, 92692978, 33935899, 86798033, 92033260, 63015256, 56153345, 37435892, 20645197, 74441448, 86242799, 14731700, 98948034, 824872, 99226875, 70420215, 77072625, 9603598, 97783876, 33565483, 56955985, 37192445, 8128637, 57240218, 82726008, 22129328, 47738185, 30771409, 99971982, 61897582, 74743862, 9398733, 44627776, 36812683, 19457589, 65038678, 71083565, 99917599, 68824981, 44842615, 19376156, 72725103, 41092172, 16405341, 27411561, 75543508, 45790169, 97281847, 29510992, 13231279, 84684495, 17957593, 49882705, 28550822, 75153252, 93270084, 66045587, 4515343, 7229550, 6038457, 3183975, 26114953, 33628349, 1936762, 66903004, 47792865, 61271144, 67084351, 57393458, 49236559, 9829782, 51472275, 70036438, 28734791, 6157724, 65454636, 15948937, 18466635, 32159704, 34236719, 7919588, 20836893, 15015906, 65715134, 53547802, 73392814, 78884452, 15902805, 64087743, 3773993, 1569515, 31733363, 64602895, 98739783, 95395112, 13819358, 62051033, 92867155, 61728685, 82979980, 16097038, 60102965, 4204661, 3233569, 53666583, 72793444, 55189057, 76703609, 63506281, 62936963, 32590267, 23110625, 75986488, 48658605, 89214616, 42073124, 27187213, 12348118, 70367851, 71469330, 73109997, 63152504, 86118021, 25636669, 89811711, 22113133, 95957797, 52204879, 78766358, 85711894, 82651278, 90654836, 27625735, 69355476, 74110882, 82532312, 18699206, 91938191, 14363867, 4069912, 24953498, 55247455, 60955663, 66319530, 66663942, 77187825, 37166608, 67513640, 39986008, 45381876, 45996863, 89078848, 50702367, 40686254, 96709982, 99515901, 71657078, 34946859, 40534591, 10453030, 40152546, 34432810, 97057187, 97641116, 53888755, 44983451, 72278539, 29065964, 91281584, 45049308, 526217, 18833224, 61960400, 32274392, 92530431, 31904591, 93566986, 82339363, 82327024, 10366309, 91240048, 92803766, 40677414, 13470059, 4806458, 45407418, 92787493, 76540481, 93053405, 8634541, 17539625, 44348328, 98462867, 26863229, 21001913, 27185644, 17068582, 72019362, 75128745, 66667729, 28787861, 53842979, 33895336, 26776922, 42782652, 57248122, 51315460, 75052463, 98943869, 55470718, 21919959, 95290172, 36468541, 42237907, 30366150, 84406788, 47893286, 93515664, 72495719, 77272628, 94539824, 6525948, 98130363, 26734892, 20681921, 45794415, 38658347, 35456853, 53648154, 82178706, 22879907, 24619760, 93790285, 8729146, 5482538, 76434144, 30569392, 66116458, 5640302, 68875490, 42644903, 73168565, 29959549, 42967683, 6111563, 98653983, 40197395, 8913721, 80555751, 415901, 1239555, 49597667, 34295794, 29635537, 69786756, 32426752, 33249630, 74724075, 44245960, 7646095, 79136082, 67281495, 7066775, 55960386, 50007421, 56473732, 41442762, 38022834, 43376279, 59910624, 79806380, 16684829, 39201414, 31491938, 83083131, 67030811, 3294781, 62496012, 62762857, 86022504, 17037369, 6986898, 37891451, 91255408, 68694897, 2917920, 65017137, 54517921, 39553046, 93057697, 84349107, 51588015, 17764950, 20642888, 26102057, 45667668, 92215320, 25842078, 33304202, 90013093, 93359396, 78785507, 88251446, 58749, 65271999, 6221471, 66611759, 3088684, 79922758, 81805959, 30694952, 81274566, 42947632, 64848072, 67451935, 4978543, 11161731, 92398073, 81677380, 68128438, 67227442, 19898053, 81898046, 68110330, 86460488, 30787683, 20867149, 19486173, 61928316, 45990383, 92071990, 69255765, 36685023, 11543098, 26063929, 54427233, 30653863, 2331773, 91957544, 18411915, 92604458, 66885828, 77694700, 47708850, 2204165, 6808825, 33699435, 68644627, 18131876, 36135, 68316156, 7517032, 57359924, 90004325, 4091162, 20122224, 24314885, 49271185, 50668599, 72238278, 85571389, 10264691, 51466049, 20140249, 61982238, 24168411, 36396314, 17386996, 1250437, 7502255, 247198, 65851721, 66832478, 22721500, 92353856, 69697787, 99125126, 26664538, 70800879, 83533741, 94516935, 14947650, 2891150, 26139110, 45617087, 47361209, 57961282, 8055981, 54199160, 30998561, 2208785, 74137926, 95010552, 91990218, 10309525, 22997281, 54663246, 14723410, 1197320, 3633375, 69605283, 38061439, 61373987, 62552524, 569864, 37183543, 66322959, 84002370, 19101477, 43322743, 16424199, 28851716, 66428795, 29401781, 18806856, 11757872, 55669657, 62428472, 52261574, 82897371, 321665, 9886593, 79191827, 4787945, 44473167, 78218845, 81855445, 90457870, 40865610, 11923835, 63372756, 9623492, 9259676, 36933038, 34493392, 38008118, 16595436, 55770687, 30891921, 37224844, 15064655, 26275734, 65081429, 46851987, 92692283, 72738685, 44889423, 37620363, 61859143, 68204242, 72373496, 95726235, 83302115, 1375023, 41092102, 14349098, 21397057, 96318094, 67124282, 23848565, 90310261, 38645117, 99549373, 28928175, 10961421, 38494874, 14093520, 14326617, 63628376, 24591705, 24058273, 21993752, 87598888, 68000591, 33553959, 71300104, 96906410, 47090124, 16971929, 27665211, 16567550, 34497327, 76330843, 17727650, 44060493, 94595668, 59371804, 94090109, 58208470, 45237957, 68816413, 65338021, 41481685, 64055960, 87710366, 61141874, 9058407, 51715482, 62232211, 30090481, 85116755 +83651211, 72019362, 75128745, 6038457, 75229462, 64602895, 76434144, 26507214, 29029316, 91664334, 77187825, 62428472, 50806615, 30163921, 75153252, 49236559, 92071990, 61728685, 77301523, 11543098, 54427233, 29188588, 67281495, 78766358, 81774825, 76960303, 73786944, 34497327, 1250437, 52261574, 32151165, 669105, 11365791, 19457589, 92530431, 4787945, 40865610, 6221471, 66611759, 66667729, 33895336, 4515343, 36933038, 749283, 94539824, 34236719, 65715134, 45794415, 68000591, 62552524, 44784505, 75407347, 62051033, 29959549, 33553959, 37183543, 30543215, 42073124, 6808825, 41442762, 68644627, 16424199, 4069912, 87720882, 1375023, 24953498, 3294781, 79322415, 37192445, 45848907, 74743862, 94911072, 65038678, 31904591, 36139942, 45407418, 79942022, 94090109, 69641513, 26863229, 38510840, 78785507, 82427263, 2208785, 96420429, 62115552, 38494874, 57393458, 48675329, 44915235, 10309525, 81853704, 70596786, 89499542, 81898046, 176257, 37224844, 29932657, 82886381, 89804152, 52293995, 55615885, 36845587, 70372191, 32426752, 22766820, 7646095, 37620363, 96726697, 31126490, 4091162, 90654836, 54232247, 7687278, 66428795, 68204242, 99524975, 83083131, 14731700, 61982238, 36930650, 89699445, 8128637, 57803235, 6986898, 29819940, 59318837, 66832478, 53888755, 92353856, 48893685, 9886593, 526217, 95251277, 32274392, 32161669, 23848565, 99549373, 4095116, 85242963, 4434662, 33797252, 49598724, 97281847, 33431960, 92215320, 93359396, 11923835, 8055981, 66250369, 66045587, 78549759, 30694952, 1936762, 68816413, 55470718, 70782102, 37280276, 92398073, 81677380, 22997281, 32159704, 17857111, 16595436, 24058273, 22942635, 44177011, 70004753, 15064655, 8729146, 5482538, 67031644, 47213183, 69255765, 30569392, 63059024, 92554120, 65275241, 17016156, 48088883, 4204661, 40197395, 66271566, 80014588, 79880247, 36505482, 35192533, 48892201, 32590267, 72738685, 16099750, 59177718, 63967300, 92998591, 27187213, 93562257, 79136082, 4099191, 22113133, 5970607, 74357852, 85711894, 40781449, 27665211, 92033260, 29401781, 72777973, 824872, 11757872, 86022504, 24168411, 57240218, 17386996, 99515901, 17727650, 47738185, 34946859, 96318094, 16380211, 34432810, 73031054, 168541, 54987042, 44983451, 72278539, 14220886, 8696647, 2917920, 9398733, 56515456, 321665, 44846932, 9599614, 38645117, 4806458, 48673079, 26102057, 66137019, 59371804, 93053405, 33336148, 26493119, 30139692, 8373289, 57241521, 17539625, 13231279, 44348328, 90013093, 76315420, 99658235, 28928175, 58749, 63372756, 48395186, 58224549, 3088684, 28787861, 26776922, 96735716, 91802888, 33628349, 21919959, 26397786, 6157724, 65454636, 15163258, 14723410, 69848388, 98130363, 1197320, 61712234, 15015906, 32699744, 53547802, 19898053, 35456853, 53648154, 73222868, 30891921, 80246713, 15902805, 68110330, 99604946, 61373987, 70727211, 75135448, 24826575, 9575954, 52613508, 66116458, 68875490, 21289531, 37675718, 77312810, 93933709, 6111563, 60102965, 17058722, 42199455, 36685023, 51149804, 26063929, 72614359, 44847298, 80555751, 1239555, 34295794, 15535065, 6793819, 29635537, 9175338, 85116755, 7685448, 96906410, 99690194, 42692881, 37501808, 50007421, 56461322, 99297164, 39373729, 88047921, 14045383, 41481685, 32058615, 7517032, 90004325, 4119747, 69355476, 74110882, 18699206, 91938191, 99021067, 40356877, 10264691, 95726235, 74441448, 40781854, 77284619, 98948034, 30218878, 72357096, 9603598, 87710366, 50567636, 12664567, 40686254, 17081350, 46870723, 5832946, 79821897, 94076128, 99971982, 22721500, 90090964, 44550764, 45306070, 5111370, 89637706, 71083565, 51588015, 41092172, 91727510, 2891150, 26139110, 80316608, 22435353, 90272749, 44473167, 78218845, 81855445, 84684495, 19939935, 27185644, 68102437, 28550822, 53084256, 91141395, 65271999, 45237957, 36312813, 68702632, 54199160, 79657802, 49328608, 60430369, 79922758, 74137926, 51315460, 9259676, 47792865, 88444207, 1788101, 67084351, 91990218, 22200378, 28734791, 59405277, 20885148, 15948937, 32250045, 77272628, 97011160, 14626618, 34493392, 62430984, 6525948, 20836893, 65338021, 21993752, 38658347, 20486294, 40984766, 86460488, 30395570, 79738755, 55753905, 59197747, 48360198, 88130087, 3235882, 43045786, 39801351, 42644903, 75777973, 60581278, 1022677, 42967683, 86543538, 43933006, 58180653, 55189057, 48260151, 46851987, 4798568, 62936963, 78909561, 415901, 49597667, 41380093, 75986488, 45428665, 9188443, 98371444, 33249630, 44889423, 81172706, 71469330, 36808486, 23740167, 99965001, 25636669, 58751351, 34044787, 77787724, 97379790, 49271185, 65047700, 19272365, 50668599, 72373496, 51466049, 94711842, 60393039, 47298834, 55247455, 64055960, 78300864, 60955663, 67474219, 99226875, 97783876, 83269727, 17037369, 39986008, 45381876, 50702367, 83155442, 96709982, 17385531, 82726008, 4064751, 48774913, 44060493, 30771409, 6871053, 40152546, 65851721, 86821229, 68694897, 65017137, 88904910, 36812683, 43506672, 74614639, 54517921, 45075471, 39553046, 79191827, 82339363, 44481640, 15148031, 26664538, 61859581, 44842615, 93057697, 84349107, 69136837, 83210802, 40677414, 8115266, 13470059, 92787493, 27411561, 94516935, 14947650, 45667668, 8634541, 80251430, 29510992, 57961282, 98462867, 72274002, 90457870, 51715482, 88251446, 61623915, 34698428, 7104732, 30998561, 78602717, 20568363, 8651647, 9860968, 14326617, 83150534, 36468541, 64848072, 9829782, 70036438, 36189527, 6497830, 67451935, 18466635, 68128438, 54663246, 7919588, 24591705, 20681921, 3633375, 6153406, 55770687, 85695762, 21070110, 62490109, 12416768, 64087743, 1569515, 20867149, 83789391, 31733363, 12571310, 78589145, 13348726, 30090481, 86301513, 10649306, 92867155, 61263068, 89217461, 23134715, 16097038, 94360702, 53666583, 26275734, 82052050, 8913721, 76703609, 51507425, 84293052, 73617245, 84904436, 30653863, 2331773, 84002370, 19101477, 38365584, 37560164, 92692283, 77620120, 51047803, 89214616, 63152504, 51426311, 86118021, 56473732, 7011964, 33237508, 89811711, 29834512, 16971929, 52204879, 43376279, 68316156, 82651278, 27625735, 71920426, 82532312, 79806380, 33935899, 1204161, 14363867, 83302115, 12024238, 86242799, 4668450, 66319530, 5822202, 66663942, 41092102, 11581181, 99333375, 56955985, 26292919, 84127901, 76330843, 82779622, 7502255, 46540998, 10453030, 45995325, 97057187, 97641116, 91255408, 62452034, 72358170, 61141874, 44627776, 43501211, 83368048, 99917599, 93566986, 68824981, 90310261, 60176618, 72725103, 9058407, 83533741, 97940276, 68041839, 29516592, 19891772, 75820087, 25842078, 83133790, 35092039, 17068582, 43357947, 87160386, 49882705, 10961421, 88653118, 9623492, 95581843, 55602660, 81805959, 91831487, 61271144, 26392416, 13862149, 84406788, 93515664, 4978543, 17976208, 9860195, 17365188, 84166196, 26734892, 73392814, 62232211, 38009615, 38061439, 54263880, 99861373, 40027975, 76170907, 19486173, 64157906, 98648327, 61928316, 78561158, 5640302, 20765474, 38341669, 31161687, 47887470, 39171895, 98653983, 34698463, 42307449, 16019925, 66322959, 61741594, 91957544, 73124510, 23110625, 92541302, 18411915, 48658605, 69786756, 43322743, 66885828, 77694700, 74724075, 70367851, 73109997, 83747892, 18504197, 70541760, 55960386, 18783702, 47090124, 8791066, 59329511, 2607799, 55143724, 16054533, 59910624, 52060076, 57359924, 30811010, 92692978, 65074535, 44479073, 23194618, 72238278, 9257405, 85571389, 43152977, 37435892, 20645197, 6819644, 67030811, 73021291, 20140249, 62496012, 37166608, 77413012, 99224392, 21397057, 247198, 94595668, 2717150, 82897371, 69697787, 56531125, 99125126, 29065964, 57020481, 36753250, 77300457, 18833224, 61960400, 10366309, 19376156, 91240048, 23432750, 35512853, 22405120, 16405341, 70800879, 75543508, 45617087, 97561745, 69579137, 88698958, 96852321, 86001008, 33304202, 17894977, 8099994, 26998766, 73235980, 3880712, 53842979, 57248122, 3183975, 26114953, 23569917, 75052463, 85023028, 14093520, 95290172, 95010552, 54014062, 37957788, 59614746, 38008118, 21673260, 8807940, 33061250, 93790285, 77377183, 45990383, 8129978, 7423788, 98739783, 95395112, 569864, 7300047, 76671482, 13468268, 62740044, 37739481, 30463802, 92604458, 12303248, 5073754, 12348118, 47708850, 2204165, 7066775, 17146629, 71316369, 29466406, 7182642, 49641577, 36135, 61859143, 20122224, 86798033, 63015256, 18806856, 1431742, 20002147, 59501487, 56424103, 96193415, 77072625, 57658654, 74452589, 55669657, 38256849, 54606384, 36780454, 31727379, 67513640, 45996863, 71657078, 67793644, 4985896, 24733232, 82327024, 5267545, 90158785, 76540481, 13173644, 21533347, 58208470, 3509435, 3233084, 17957593, 71965942, 21001913, 54762643, 44664587, 93270084, 28796059, 42782652, 98943869, 66903004, 81274566, 89419466, 59981773, 42947632, 42237907, 36580610, 51472275, 53802686, 51590803, 30764367, 64098930, 67227442, 78884452, 69605283, 24619760, 65880522, 13819358, 73168565, 55850790, 33123618, 82979980, 85117092, 874791, 54868730, 112651, 6505939, 41245325, 4508700, 38022834, 71333116, 77898274, 72732205, 24314885, 16684829, 29994197, 39201414, 16567550, 8733068, 62762857, 33565483, 84187166, 26744917, 36396314, 22129328, 40534591, 54058788, 10597197, 61897582, 62693428, 91281584, 16387575, 17764950, 45790169, 18001617, 47361209, 16445503, 22450468, 84840549, 7229550, 47893286, 4199704, 34667286, 90061527, 53393358, 89046466, 62803858, 72793444, 61815223, 39847321, 57163802, 71300104, 44245960, 71522968, 51464002, 33699435, 95957797, 50188404, 56153345, 45919976, 56484900, 33201905, 68939068, 67124282, 45049308, 59109400, 76825057, 35996293, 3487592, 62357986, 60106217, 30366150, 10358899, 63628376, 15075176, 11161731, 72495719, 82178706, 22879907, 3773993, 30787683, 87598888, 3233569, 65081429, 83948335, 24915585, 28851716, 31491938, 15536795, 89078848, 53632373, 6521313, 92803766, 508198, 22108665, 63506281, 7955293, 27041967, 18131876, 37891451, 20642888, 27325428, 89996536, 99729357, 37659250, 14349098, 70420215, 44844121, 18663507 +71300104, 56461322, 44846932, 4787945, 31161687, 21289531, 47090124, 65715134, 98739783, 39171895, 76703609, 51426311, 55960386, 16424199, 96193415, 34432810, 74743862, 17539625, 98462867, 85023028, 37675718, 72738685, 51047803, 12348118, 37620363, 58751351, 95957797, 96726697, 61859143, 90004325, 12024238, 1431742, 11581181, 5832946, 94595668, 86821229, 94911072, 91281584, 75543508, 33431960, 80251430, 92215320, 61623915, 508198, 96420429, 78602717, 70782102, 36580610, 63628376, 67451935, 37957788, 98130363, 7919588, 21993752, 21070110, 19486173, 48360198, 47213183, 61263068, 43933006, 26063929, 19101477, 41380093, 57163802, 47708850, 67281495, 56473732, 5970607, 85711894, 7517032, 37659250, 31491938, 37435892, 20645197, 40781854, 6819644, 30218878, 67513640, 26292919, 99224392, 17727650, 48774913, 54058788, 94076128, 10597197, 82897371, 8696647, 61141874, 16387575, 95251277, 61960400, 79191827, 93057697, 69136837, 72725103, 4806458, 17764950, 4095116, 83533741, 79942022, 80316608, 22435353, 29510992, 57961282, 3509435, 35092039, 16445503, 68702632, 78549759, 9860968, 75229462, 28734791, 59405277, 32250045, 26734892, 53547802, 6153406, 21673260, 62490109, 15902805, 8807940, 83789391, 79738755, 22108665, 8129978, 60581278, 29959549, 77312810, 89217461, 48088883, 17058722, 84293052, 4798568, 30463802, 92692283, 6793819, 112651, 43322743, 6505939, 6808825, 89811711, 18131876, 74357852, 59910624, 82651278, 4119747, 54232247, 1204161, 76960303, 44479073, 99021067, 23194618, 72373496, 39201414, 95726235, 24953498, 74441448, 98948034, 97783876, 33201905, 83269727, 46870723, 40534591, 96318094, 168541, 54987042, 4985896, 11365791, 56515456, 43501211, 83368048, 82327024, 91240048, 26102057, 66137019, 2891150, 26139110, 4434662, 8634541, 58208470, 75820087, 69641513, 8099994, 22450468, 72019362, 68102437, 40865610, 34698428, 65271999, 66611759, 54762643, 88653118, 7104732, 9623492, 26776922, 81805959, 81274566, 42947632, 21919959, 4199704, 6497830, 4978543, 20836893, 19898053, 53648154, 22879907, 37224844, 13348726, 98648327, 78561158, 92071990, 7423788, 5640302, 20765474, 17016156, 77301523, 569864, 33553959, 93933709, 82052050, 66271566, 874791, 73617245, 65081429, 36505482, 38365584, 32590267, 49597667, 73124510, 77620120, 39847321, 70372191, 66885828, 74724075, 44245960, 41245325, 34044787, 49641577, 41481685, 81774825, 4091162, 71920426, 82532312, 27665211, 18699206, 65047700, 4069912, 50188404, 50668599, 83302115, 8733068, 60393039, 47298834, 56484900, 20002147, 824872, 9603598, 84187166, 26744917, 37192445, 84127901, 57240218, 40686254, 82779622, 29819940, 7502255, 6871053, 97641116, 61897582, 91255408, 2917920, 321665, 89637706, 9886593, 36753250, 65038678, 43506672, 93566986, 68824981, 26664538, 32161669, 84349107, 83210802, 8115266, 90158785, 23432750, 35512853, 51588015, 45407418, 59371804, 13173644, 19939935, 25842078, 38510840, 76315420, 28550822, 53084256, 58749, 11923835, 48395186, 45237957, 93270084, 3088684, 66250369, 28787861, 55602660, 62115552, 30694952, 8651647, 98943869, 66903004, 59981773, 47792865, 83150534, 36468541, 61271144, 1788101, 26392416, 70036438, 15075176, 15948937, 62430984, 30764367, 1197320, 32699744, 45794415, 55770687, 69605283, 44844121, 12416768, 24619760, 99861373, 8729146, 78589145, 86301513, 43045786, 95395112, 65275241, 29932657, 82886381, 4204661, 58180653, 76671482, 16019925, 79880247, 46851987, 44847298, 78909561, 35192533, 75986488, 29188588, 48658605, 99690194, 92998591, 33249630, 44889423, 71522968, 73109997, 36808486, 70541760, 4099191, 27041967, 43376279, 78766358, 14045383, 20122224, 92692978, 74110882, 86798033, 68204242, 72238278, 85571389, 40356877, 94711842, 99524975, 60955663, 3294781, 36930650, 79322415, 41092102, 86022504, 99333375, 31727379, 68939068, 45996863, 8128637, 89078848, 53632373, 6986898, 52261574, 67793644, 669105, 5111370, 62693428, 62452034, 29065964, 19457589, 57020481, 32274392, 39553046, 31904591, 44842615, 5267545, 36139942, 40677414, 59109400, 48673079, 76540481, 70800879, 85242963, 14947650, 97940276, 33797252, 33336148, 97281847, 29516592, 8373289, 69579137, 81855445, 21533347, 13231279, 71965942, 21001913, 33304202, 17894977, 87160386, 84840549, 78785507, 63372756, 53842979, 33895336, 96735716, 79922758, 91802888, 3183975, 26114953, 23569917, 38494874, 95290172, 88444207, 54014062, 10358899, 49236559, 37280276, 36189527, 6157724, 34667286, 97011160, 22997281, 14626618, 14723410, 81853704, 67227442, 65338021, 78884452, 73222868, 30891921, 86460488, 99604946, 3773993, 44177011, 89046466, 70004753, 20867149, 40027975, 15064655, 55753905, 88130087, 67031644, 10649306, 92867155, 62803858, 42967683, 86543538, 3233569, 40197395, 36685023, 85117092, 13468268, 84904436, 30653863, 62740044, 415901, 9188443, 16099750, 18411915, 54868730, 63967300, 12303248, 27187213, 29029316, 2204165, 93562257, 23740167, 33699435, 8791066, 17146629, 29834512, 88047921, 16054533, 52060076, 57359924, 72732205, 69355476, 33935899, 19272365, 65074535, 14363867, 56153345, 29401781, 29994197, 45919976, 16567550, 51466049, 55247455, 64055960, 66319530, 99226875, 56424103, 77187825, 87710366, 37166608, 36780454, 33565483, 39986008, 12664567, 57803235, 17385531, 76330843, 4064751, 47738185, 71657078, 79821897, 59318837, 45995325, 40152546, 37891451, 247198, 73031054, 45306070, 67124282, 526217, 18833224, 9599614, 92803766, 99549373, 41092172, 27411561, 93053405, 26493119, 45667668, 45617087, 97561745, 57241521, 44348328, 84684495, 86001008, 27185644, 17068582, 90457870, 26998766, 10961421, 8055981, 44664587, 28796059, 54199160, 3880712, 79657802, 66045587, 2208785, 9259676, 20568363, 60106217, 55470718, 57393458, 13862149, 9829782, 17976208, 9860195, 65454636, 11161731, 92398073, 53802686, 72495719, 17365188, 81677380, 34493392, 15163258, 38008118, 15015906, 20681921, 53393358, 70596786, 85695762, 20486294, 62232211, 81898046, 22942635, 68110330, 38009615, 38061439, 54263880, 61373987, 176257, 87598888, 31733363, 12571310, 75135448, 59197747, 24826575, 5482538, 30090481, 3235882, 75407347, 63059024, 1022677, 16097038, 7300047, 89804152, 72793444, 11543098, 51149804, 52293995, 54427233, 51507425, 80014588, 55615885, 72614359, 36845587, 80555751, 62936963, 37739481, 61815223, 48892201, 1239555, 91957544, 92541302, 15535065, 29635537, 7685448, 69786756, 42073124, 81172706, 70367851, 18663507, 7646095, 37501808, 79136082, 7011964, 4508700, 39373729, 68644627, 38022834, 29466406, 31126490, 55143724, 77898274, 28851716, 79806380, 24314885, 49271185, 92033260, 87720882, 9257405, 18806856, 78300864, 83083131, 4668450, 14731700, 73021291, 72357096, 59501487, 34497327, 77072625, 57658654, 54606384, 45848907, 77413012, 17081350, 30771409, 10453030, 53888755, 2717150, 90090964, 44550764, 92353856, 65017137, 36812683, 77300457, 71083565, 54517921, 45075471, 44481640, 15148031, 24733232, 90310261, 20642888, 22405120, 35996293, 94516935, 91727510, 45790169, 49598724, 18001617, 30139692, 94090109, 78218845, 19891772, 96852321, 17957593, 90013093, 43357947, 93359396, 75153252, 3487592, 6221471, 73235980, 36312813, 30998561, 82427263, 60430369, 57248122, 7229550, 74137926, 33628349, 51315460, 91831487, 14093520, 14326617, 42237907, 749283, 20885148, 32159704, 89996536, 64098930, 61712234, 16595436, 3633375, 24058273, 89499542, 35456853, 82178706, 64087743, 33061250, 30787683, 70727211, 76170907, 65880522, 64157906, 30569392, 68875490, 92554120, 38341669, 75777973, 61728685, 33123618, 37183543, 26275734, 98653983, 8913721, 34698463, 26507214, 7955293, 83948335, 45428665, 92604458, 32426752, 42692881, 71469330, 63152504, 51464002, 7066775, 18783702, 99297164, 22113133, 77787724, 16971929, 36135, 90654836, 7687278, 16684829, 72777973, 73786944, 86242799, 11757872, 38256849, 24168411, 50567636, 56955985, 50702367, 1250437, 22129328, 14349098, 21397057, 46540998, 32151165, 97057187, 30163921, 44983451, 72278539, 69697787, 68694897, 9398733, 92530431, 82339363, 19376156, 60176618, 13470059, 44473167, 47361209, 3233084, 83133790, 51715482, 49882705, 88251446, 99658235, 75128745, 95581843, 75052463, 36933038, 67084351, 84406788, 22200378, 51472275, 93515664, 18466635, 27325428, 90061527, 51590803, 77272628, 68128438, 59614746, 34236719, 54663246, 6525948, 69848388, 24591705, 40984766, 30395570, 93790285, 76434144, 77377183, 61928316, 9575954, 52613508, 39801351, 13819358, 47887470, 82979980, 23134715, 6111563, 60102965, 53666583, 55189057, 30543215, 42307449, 48260151, 84002370, 37560164, 23110625, 96906410, 59177718, 86118021, 25636669, 59329511, 91664334, 7182642, 32058615, 68316156, 91938191, 66428795, 67030811, 20140249, 5822202, 66663942, 61982238, 55669657, 62428472, 17037369, 45381876, 15536795, 96709982, 16380211, 99971982, 22721500, 6521313, 99125126, 88904910, 44627776, 61859581, 83651211, 62357986, 66667729, 4515343, 68816413, 95010552, 48675329, 94539824, 84166196, 73392814, 80246713, 68000591, 62552524, 44784505, 66116458, 73168565, 94360702, 42199455, 63506281, 66322959, 61741594, 98371444, 22766820, 5073754, 99965001, 33237508, 71316369, 71333116, 52204879, 97379790, 40781449, 30811010, 27625735, 10264691, 43152977, 67474219, 70420215, 74452589, 36396314, 17386996, 83155442, 34946859, 65851721, 50806615, 66832478, 56531125, 45049308, 10366309, 38645117, 90272749, 72274002, 91141395, 58224549, 49328608, 1936762, 91990218, 64848072, 47893286, 44915235, 17857111, 1569515, 64602895, 42644903, 55850790, 99729357, 2331773, 9175338, 77694700, 83747892, 18504197, 50007421, 41442762, 2607799, 63015256, 62762857, 89699445, 82726008, 44060493, 14220886, 48893685, 72358170, 23848565, 16405341, 88698958, 26863229, 28928175, 6038457, 89419466, 10309525, 38658347, 62051033, 34295794, 85116755, 89214616, 24915585, 99515901, 74614639, 99917599, 76825057, 68041839, 42782652, 92787493, 9058407, 26397786, 30366150, 45990383, 69255765, 1375023, 77284619, 62496012 +59329511, 22879907, 52204879, 2917920, 61960400, 99917599, 36933038, 73124510, 18411915, 18504197, 4668450, 17386996, 40152546, 68824981, 98943869, 4978543, 55770687, 44844121, 8129978, 58180653, 30543215, 99729357, 2331773, 92692283, 41442762, 72373496, 73786944, 11757872, 97783876, 41092102, 45381876, 12664567, 94076128, 48893685, 26664538, 69136837, 99549373, 4806458, 78785507, 8055981, 66250369, 95581843, 66045587, 1936762, 73392814, 78884452, 30891921, 87598888, 40027975, 52613508, 42644903, 36505482, 32426752, 12303248, 22766820, 34044787, 97379790, 54232247, 63015256, 8733068, 62496012, 59501487, 26744917, 76330843, 5832946, 56531125, 65017137, 29065964, 54517921, 36139942, 19376156, 90310261, 17764950, 93053405, 80251430, 69641513, 84684495, 33304202, 75128745, 66611759, 55470718, 1788101, 62430984, 1197320, 45794415, 35456853, 3773993, 64602895, 30569392, 75777973, 77312810, 89217461, 23134715, 55189057, 84293052, 80555751, 35192533, 61741594, 48892201, 51047803, 96906410, 42692881, 6808825, 41245325, 55960386, 33699435, 17146629, 90004325, 24915585, 69355476, 49271185, 77072625, 67513640, 56955985, 40534591, 37891451, 90090964, 321665, 91281584, 65038678, 45075471, 79191827, 93566986, 44481640, 91240048, 76825057, 13470059, 83651211, 22405120, 22435353, 81855445, 88698958, 90013093, 17894977, 8099994, 10961421, 48395186, 58224549, 66667729, 36312813, 30998561, 49328608, 79922758, 20568363, 68816413, 14326617, 21919959, 22200378, 47893286, 51472275, 17976208, 34667286, 90061527, 84166196, 54663246, 14723410, 32699744, 53648154, 82178706, 40984766, 99604946, 61373987, 8729146, 64157906, 62552524, 92071990, 63059024, 7423788, 10649306, 95395112, 62803858, 55850790, 7300047, 26275734, 36685023, 85117092, 54427233, 44847298, 30463802, 61815223, 38365584, 72738685, 54868730, 29029316, 47708850, 18663507, 7646095, 37620363, 37501808, 83747892, 67281495, 50007421, 7011964, 58751351, 68644627, 95957797, 91664334, 59910624, 81774825, 77898274, 40781449, 19272365, 68204242, 72777973, 16567550, 14731700, 98948034, 30218878, 73021291, 3294781, 5822202, 70420215, 55669657, 36780454, 39986008, 26292919, 89078848, 36396314, 53632373, 50702367, 17081350, 99515901, 22129328, 17727650, 48774913, 34946859, 96318094, 59318837, 247198, 73031054, 97641116, 72278539, 82897371, 44627776, 45049308, 526217, 39553046, 44842615, 10366309, 5267545, 84349107, 83210802, 59109400, 16405341, 9058407, 85242963, 27411561, 14947650, 33797252, 49598724, 68041839, 29516592, 44473167, 94090109, 3509435, 44348328, 83133790, 38510840, 68102437, 91141395, 34698428, 3880712, 26776922, 57248122, 6038457, 62115552, 74137926, 78602717, 9259676, 60106217, 47792865, 95290172, 95010552, 91990218, 36189527, 97011160, 22997281, 38008118, 24591705, 61712234, 67227442, 62232211, 81898046, 21673260, 68110330, 38061439, 33061250, 54263880, 48360198, 30090481, 68000591, 77377183, 98739783, 39801351, 13819358, 62051033, 47887470, 17016156, 77301523, 569864, 93933709, 42967683, 72793444, 48260151, 63506281, 874791, 84002370, 62740044, 4798568, 78909561, 7955293, 23110625, 9175338, 69786756, 42073124, 112651, 77694700, 27187213, 74724075, 70367851, 2204165, 51464002, 4099191, 56473732, 8791066, 33237508, 22113133, 29466406, 18131876, 43376279, 78766358, 31126490, 68316156, 4091162, 20122224, 30811010, 90654836, 18699206, 91938191, 4069912, 16684829, 23194618, 83302115, 1375023, 60955663, 72357096, 57658654, 54606384, 83269727, 17037369, 50567636, 15536795, 40686254, 21397057, 79821897, 45995325, 10597197, 99971982, 65851721, 168541, 68694897, 9886593, 57020481, 43501211, 9599614, 92803766, 38645117, 60176618, 8115266, 72725103, 35512853, 91727510, 45617087, 8634541, 19891772, 47361209, 57961282, 26863229, 86001008, 35092039, 72274002, 16445503, 88251446, 63372756, 44664587, 96420429, 55602660, 23569917, 51315460, 81274566, 26397786, 57393458, 13862149, 48675329, 70036438, 93515664, 67451935, 92398073, 18466635, 89996536, 6525948, 69848388, 65715134, 19898053, 21070110, 62490109, 86460488, 44177011, 176257, 15064655, 75135448, 78589145, 98648327, 3235882, 47213183, 43933006, 60102965, 3233569, 53666583, 82052050, 11543098, 13468268, 72614359, 19101477, 415901, 83948335, 92541302, 48658605, 71300104, 70372191, 44245960, 71522968, 18783702, 4508700, 71316369, 38022834, 77787724, 88047921, 49641577, 36135, 7517032, 92692978, 92033260, 65074535, 99021067, 72238278, 9257405, 85571389, 60393039, 43152977, 56484900, 1431742, 78300864, 86242799, 40781854, 6819644, 66319530, 67474219, 77284619, 99226875, 38256849, 30771409, 94595668, 67793644, 669105, 86821229, 44983451, 4985896, 2717150, 6521313, 74743862, 11365791, 45306070, 56515456, 5111370, 88904910, 83368048, 31904591, 82339363, 24733232, 61859581, 93057697, 23848565, 51588015, 92787493, 26102057, 94516935, 97940276, 66137019, 26139110, 45790169, 30139692, 57241521, 29510992, 49882705, 99658235, 9623492, 3088684, 28787861, 54199160, 79657802, 2208785, 3183975, 78549759, 66903004, 91831487, 61271144, 84406788, 64848072, 11161731, 15948937, 51590803, 72495719, 17365188, 81677380, 34493392, 59614746, 24058273, 53393358, 85695762, 38658347, 22942635, 64087743, 30395570, 24619760, 1569515, 70004753, 31733363, 37224844, 55753905, 24826575, 76434144, 22108665, 61928316, 9575954, 69255765, 75407347, 86301513, 33123618, 29959549, 61263068, 82979980, 6111563, 39171895, 98653983, 17058722, 8913721, 66322959, 46851987, 62936963, 91957544, 49597667, 77620120, 15535065, 98371444, 89214616, 92604458, 33249630, 43322743, 81172706, 73109997, 36808486, 51426311, 56461322, 47090124, 25636669, 99297164, 39373729, 29834512, 27041967, 85711894, 16424199, 32058615, 61859143, 72732205, 37659250, 86798033, 45919976, 10264691, 12024238, 99524975, 24953498, 36930650, 99333375, 54058788, 97057187, 66832478, 53888755, 54987042, 92353856, 69697787, 62452034, 32274392, 90158785, 20642888, 70800879, 59371804, 80316608, 45667668, 17539625, 75820087, 98462867, 19939935, 3233084, 25842078, 71965942, 21001913, 90457870, 22450468, 11923835, 65271999, 45237957, 93270084, 33895336, 96735716, 42782652, 60430369, 30694952, 33628349, 8651647, 89419466, 14093520, 83150534, 70782102, 26392416, 67084351, 30366150, 54014062, 10358899, 63628376, 37280276, 749283, 28734791, 59405277, 53802686, 14626618, 94539824, 34236719, 98130363, 7919588, 81853704, 20486294, 15902805, 8807940, 12416768, 93790285, 65880522, 5482538, 78561158, 5640302, 65275241, 38341669, 61728685, 1022677, 37675718, 33553959, 37183543, 94360702, 4204661, 89804152, 52293995, 42307449, 26063929, 51507425, 79880247, 36845587, 37739481, 41380093, 57163802, 85116755, 7685448, 99690194, 44889423, 93562257, 79136082, 99965001, 71333116, 16971929, 2607799, 41481685, 82651278, 52060076, 74110882, 24314885, 7687278, 1204161, 66428795, 76960303, 29401781, 51466049, 18806856, 94711842, 31491938, 37435892, 55247455, 64055960, 20002147, 824872, 34497327, 62762857, 86022504, 33201905, 96709982, 1250437, 14349098, 46870723, 4064751, 29819940, 46540998, 50806615, 30163921, 61897582, 22721500, 14220886, 61141874, 36753250, 16387575, 43506672, 74614639, 40677414, 4787945, 45407418, 48673079, 76540481, 35996293, 33431960, 13173644, 93359396, 28928175, 28550822, 53084256, 58749, 6221471, 54762643, 28796059, 82427263, 75052463, 85023028, 42947632, 42237907, 36580610, 49236559, 9829782, 44915235, 6157724, 65454636, 32250045, 27325428, 30764367, 64098930, 26734892, 17857111, 20836893, 20681921, 3633375, 65338021, 70596786, 80246713, 38009615, 76170907, 12571310, 19486173, 88130087, 44784505, 66116458, 20765474, 31161687, 73168565, 21289531, 16097038, 42199455, 51149804, 76703609, 84904436, 55615885, 34295794, 16099750, 5073754, 23740167, 86118021, 74357852, 55143724, 16054533, 28851716, 82532312, 44479073, 50188404, 87720882, 50668599, 29994197, 39201414, 20645197, 67030811, 66663942, 96193415, 9603598, 79322415, 87710366, 62428472, 11581181, 84187166, 37192445, 45996863, 8128637, 57240218, 6986898, 83155442, 17385531, 47738185, 6871053, 91255408, 44550764, 8696647, 72358170, 99125126, 77300457, 18833224, 92530431, 15148031, 82327024, 4095116, 2891150, 18001617, 90272749, 8373289, 78218845, 69579137, 58208470, 13231279, 17957593, 27185644, 17068582, 76315420, 87160386, 72019362, 84840549, 26998766, 40865610, 88653118, 73235980, 68702632, 508198, 4515343, 81805959, 59981773, 75229462, 88444207, 4199704, 9860195, 10309525, 32159704, 6153406, 73222868, 70727211, 79738755, 59197747, 45990383, 43045786, 92554120, 92867155, 29932657, 60581278, 16019925, 80014588, 73617245, 26507214, 32590267, 75986488, 45428665, 29188588, 6793819, 29635537, 66885828, 71469330, 63152504, 70541760, 7066775, 96726697, 7182642, 57359924, 79806380, 56153345, 95726235, 47298834, 83083131, 20140249, 61982238, 77187825, 31727379, 33565483, 68939068, 57803235, 52261574, 99224392, 7502255, 71657078, 10453030, 16380211, 34432810, 62693428, 89637706, 36812683, 19457589, 71083565, 32161669, 41092172, 79942022, 4434662, 33336148, 26493119, 97281847, 97561745, 92215320, 96852321, 43357947, 53842979, 91802888, 26114953, 38494874, 9860968, 36468541, 6497830, 68128438, 15015906, 16595436, 53547802, 21993752, 20867149, 67031644, 13348726, 82886381, 48088883, 34698463, 30653863, 65081429, 1239555, 39847321, 59177718, 12348118, 6505939, 89811711, 5970607, 27625735, 33935899, 14363867, 74452589, 24168411, 37166608, 45848907, 82726008, 82779622, 44060493, 32151165, 44846932, 95251277, 83533741, 75543508, 51715482, 61623915, 75153252, 7104732, 7229550, 37957788, 15075176, 20885148, 77272628, 15163258, 89499542, 69605283, 89046466, 99861373, 68875490, 86543538, 40197395, 37560164, 9188443, 63967300, 27665211, 65047700, 74441448, 56424103, 21533347, 3487592, 92998591, 14045383, 4119747, 77413012, 9398733, 67124282, 94911072, 23432750, 62357986, 30787683, 76671482, 66271566, 71920426, 89699445, 84127901, 83789391, 40356877 +33553959, 73031054, 35996293, 9829782, 92398073, 98739783, 52293995, 56473732, 13470059, 9058407, 9623492, 24591705, 18663507, 93562257, 8733068, 98948034, 61897582, 8696647, 24733232, 86001008, 33895336, 67084351, 70036438, 6497830, 37957788, 30891921, 99604946, 76434144, 62552524, 36685023, 63506281, 2331773, 37739481, 6793819, 27625735, 1375023, 67513640, 89078848, 7502255, 83210802, 14947650, 45667668, 57961282, 72274002, 75153252, 8055981, 42782652, 2208785, 4515343, 1788101, 4978543, 15948937, 55770687, 12416768, 3773993, 70004753, 75135448, 30090481, 7423788, 65275241, 62803858, 61728685, 77301523, 93933709, 16097038, 60102965, 72793444, 58180653, 34698463, 45428665, 98371444, 48658605, 2204165, 41245325, 67281495, 23740167, 18783702, 41442762, 78766358, 88047921, 97379790, 54232247, 49271185, 96193415, 36930650, 41092102, 33201905, 89699445, 99333375, 40686254, 47738185, 34432810, 53888755, 2717150, 11365791, 82897371, 91281584, 36753250, 39553046, 83368048, 22435353, 93053405, 97281847, 94090109, 80251430, 19891772, 81855445, 90013093, 87160386, 91141395, 65271999, 58224549, 3088684, 66045587, 62115552, 30694952, 85023028, 26392416, 51472275, 51590803, 64098930, 38009615, 44177011, 176257, 70727211, 79738755, 19486173, 24826575, 48360198, 5482538, 78561158, 69255765, 10649306, 95395112, 92867155, 73168565, 29932657, 89217461, 43933006, 53666583, 26275734, 98653983, 30543215, 99729357, 48260151, 1239555, 70372191, 69786756, 32426752, 71522968, 71469330, 55960386, 59329511, 59910624, 68316156, 61859143, 71920426, 50188404, 87720882, 68204242, 72777973, 73786944, 18806856, 55247455, 30218878, 62428472, 17037369, 50702367, 76330843, 17727650, 45995325, 94076128, 16380211, 94595668, 65851721, 168541, 30163921, 45306070, 5111370, 62452034, 72358170, 36812683, 57020481, 54517921, 32274392, 31904591, 93566986, 44842615, 93057697, 69136837, 59109400, 90158785, 45407418, 22405120, 2891150, 45790169, 33797252, 49598724, 97561745, 17539625, 34698428, 48395186, 88653118, 28796059, 79657802, 508198, 57248122, 33628349, 51315460, 9259676, 95290172, 26397786, 30366150, 18466635, 32250045, 72495719, 68128438, 61712234, 81853704, 65715134, 19898053, 69605283, 53648154, 62490109, 38061439, 15064655, 8729146, 92071990, 29959549, 6111563, 94360702, 55189057, 66271566, 76703609, 66322959, 54427233, 73617245, 84904436, 26507214, 4798568, 61815223, 48892201, 38365584, 92998591, 42692881, 77694700, 70367851, 37620363, 6808825, 51426311, 25636669, 38022834, 16971929, 16054533, 16424199, 49641577, 7517032, 90004325, 44479073, 99021067, 72238278, 40356877, 45919976, 60393039, 47298834, 74441448, 86242799, 60955663, 6819644, 67474219, 20140249, 97783876, 24168411, 37166608, 56955985, 15536795, 29819940, 30771409, 79821897, 59318837, 50806615, 54987042, 91255408, 22721500, 48893685, 2917920, 67124282, 56515456, 61141874, 43501211, 10366309, 19376156, 91240048, 40677414, 8115266, 51588015, 4095116, 76540481, 66137019, 80316608, 68041839, 45617087, 8634541, 21533347, 58208470, 84684495, 26863229, 25842078, 17957593, 38510840, 84840549, 78785507, 10961421, 53842979, 30998561, 23569917, 68816413, 36468541, 4199704, 67451935, 15075176, 9860195, 65454636, 11161731, 77272628, 34493392, 94539824, 54663246, 32699744, 85695762, 21070110, 20486294, 22879907, 22942635, 8807940, 64087743, 24619760, 1569515, 61928316, 30569392, 5640302, 33123618, 17016156, 39171895, 3233569, 89804152, 26063929, 84293052, 62740044, 80555751, 62936963, 36505482, 91957544, 49597667, 73124510, 77620120, 34295794, 72738685, 29635537, 57163802, 85116755, 71300104, 112651, 63152504, 18504197, 99965001, 47090124, 8791066, 58751351, 22113133, 68644627, 71333116, 2607799, 96726697, 31126490, 85711894, 41481685, 77898274, 74110882, 79806380, 24314885, 85571389, 39201414, 95726235, 51466049, 83302115, 83083131, 77284619, 824872, 59501487, 66663942, 61982238, 62762857, 79322415, 74452589, 82726008, 46540998, 10453030, 96318094, 44983451, 6521313, 92353856, 9398733, 62693428, 9886593, 44846932, 18833224, 71083565, 99917599, 44481640, 15148031, 26664538, 83651211, 4787945, 35512853, 17764950, 70800879, 91727510, 75543508, 59371804, 33336148, 90272749, 8373289, 13173644, 47361209, 75820087, 3233084, 21001913, 27185644, 17894977, 43357947, 8099994, 16445503, 90457870, 51715482, 22450468, 40865610, 11923835, 63372756, 54762643, 95581843, 3880712, 26776922, 91802888, 55602660, 78602717, 8651647, 14093520, 47792865, 57393458, 36189527, 93515664, 28734791, 17976208, 10309525, 53802686, 89996536, 98130363, 6153406, 73392814, 38658347, 82178706, 33061250, 83789391, 99861373, 40027975, 59197747, 98648327, 3235882, 47213183, 75777973, 82886381, 47887470, 37675718, 77312810, 82979980, 23134715, 17058722, 51507425, 30653863, 84002370, 78909561, 35192533, 7955293, 75986488, 29188588, 54868730, 42073124, 33249630, 12348118, 74724075, 44889423, 47708850, 37501808, 36808486, 6505939, 79136082, 5970607, 77787724, 91664334, 81774825, 52060076, 40781449, 30811010, 90654836, 27665211, 92033260, 72373496, 64055960, 78300864, 4668450, 66319530, 67030811, 3294781, 72357096, 62496012, 5822202, 38256849, 86022504, 87710366, 83269727, 45848907, 45381876, 45996863, 17386996, 83155442, 17081350, 17385531, 1250437, 99515901, 14349098, 82779622, 5832946, 71657078, 34946859, 10597197, 99971982, 97641116, 669105, 86821229, 90090964, 44627776, 29065964, 45049308, 16387575, 65038678, 61960400, 45075471, 79191827, 68824981, 61859581, 9599614, 5267545, 36139942, 90310261, 60176618, 72725103, 99549373, 4806458, 26102057, 79942022, 26493119, 18001617, 33431960, 69579137, 13231279, 98462867, 71965942, 17068582, 72019362, 49882705, 68102437, 99658235, 3487592, 58749, 66611759, 66250369, 36312813, 82427263, 96420429, 6038457, 20568363, 75052463, 1936762, 9860968, 91831487, 89419466, 83150534, 95010552, 70782102, 61271144, 88444207, 42237907, 36580610, 49236559, 37280276, 48675329, 59405277, 20885148, 27325428, 97011160, 15163258, 30764367, 84166196, 14723410, 69848388, 1197320, 26734892, 17857111, 24058273, 65338021, 35456853, 73222868, 62232211, 80246713, 40984766, 87598888, 77377183, 52613508, 8129978, 42967683, 86543538, 51149804, 8913721, 42307449, 874791, 55615885, 72614359, 83948335, 32590267, 92692283, 92541302, 15535065, 92604458, 59177718, 63967300, 66885828, 27187213, 4099191, 17146629, 4508700, 99297164, 34044787, 71316369, 95957797, 29466406, 18131876, 7182642, 55143724, 82532312, 76960303, 56153345, 50668599, 94711842, 37435892, 40781854, 99226875, 34497327, 70420215, 57658654, 9603598, 54606384, 50567636, 39986008, 8128637, 46870723, 48774913, 21397057, 66832478, 56531125, 94911072, 99125126, 88904910, 19457589, 526217, 43506672, 92803766, 92787493, 83533741, 27411561, 97940276, 44473167, 69641513, 96852321, 19939935, 83133790, 33304202, 93359396, 28550822, 53084256, 7104732, 73235980, 28787861, 49328608, 74137926, 81805959, 55470718, 36933038, 54014062, 10358899, 22200378, 47893286, 22997281, 14626618, 59614746, 38008118, 6525948, 15015906, 67227442, 20681921, 53547802, 78884452, 21673260, 30395570, 54263880, 61373987, 12571310, 88130087, 67031644, 44784505, 75407347, 86301513, 39801351, 20765474, 38341669, 31161687, 61263068, 569864, 4204661, 40197395, 76671482, 85117092, 16019925, 65081429, 46851987, 36845587, 61741594, 415901, 37560164, 41380093, 16099750, 7685448, 89214616, 96906410, 99690194, 81172706, 7646095, 50007421, 56461322, 89811711, 27041967, 74357852, 14045383, 36135, 57359924, 4119747, 18699206, 33935899, 1204161, 65047700, 19272365, 86798033, 65074535, 14363867, 4069912, 23194618, 9257405, 10264691, 16567550, 31491938, 99524975, 56484900, 20645197, 1431742, 77187825, 11581181, 84187166, 26744917, 37192445, 36396314, 96709982, 52261574, 54058788, 37891451, 247198, 72278539, 44550764, 89637706, 65017137, 32161669, 23848565, 76825057, 23432750, 41092172, 16405341, 48673079, 44348328, 88698958, 35092039, 26998766, 61623915, 62357986, 45237957, 44664587, 68702632, 26114953, 38494874, 98943869, 81274566, 75229462, 14326617, 84406788, 44915235, 62430984, 34236719, 20836893, 16595436, 3633375, 45794415, 53393358, 89499542, 86460488, 89046466, 30787683, 20867149, 93790285, 65880522, 64602895, 22108665, 45990383, 9575954, 63059024, 43045786, 13819358, 55850790, 1022677, 37183543, 48088883, 82052050, 11543098, 44847298, 30463802, 19101477, 23110625, 39847321, 18411915, 51047803, 12303248, 22766820, 43322743, 29029316, 44245960, 73109997, 70541760, 7066775, 33699435, 29834512, 52204879, 32058615, 82651278, 28851716, 69355476, 63015256, 16684829, 29401781, 24953498, 20002147, 56424103, 55669657, 36780454, 33565483, 57240218, 57803235, 44060493, 40534591, 74743862, 95251277, 92530431, 82327024, 38645117, 20642888, 94516935, 4434662, 30139692, 57241521, 78218845, 29510992, 92215320, 76315420, 28928175, 75128745, 93270084, 54199160, 60430369, 3183975, 66903004, 60106217, 59981773, 63628376, 64848072, 749283, 32159704, 21993752, 37224844, 64157906, 13348726, 68875490, 42644903, 21289531, 42199455, 13468268, 80014588, 79880247, 51464002, 43376279, 4091162, 24915585, 37659250, 92692978, 7687278, 66428795, 29994197, 43152977, 14731700, 68939068, 84127901, 40152546, 97057187, 4985896, 14220886, 68694897, 77300457, 26139110, 29516592, 88251446, 6221471, 78549759, 42947632, 21919959, 91990218, 6157724, 34667286, 90061527, 81677380, 70596786, 81898046, 15902805, 68110330, 31733363, 76170907, 78589145, 66116458, 60581278, 9175338, 5073754, 83747892, 7011964, 20122224, 72732205, 91938191, 12024238, 77072625, 11757872, 31727379, 26292919, 77413012, 53632373, 22129328, 99224392, 32151165, 6871053, 69697787, 321665, 74614639, 84349107, 85242963, 3509435, 66667729, 96735716, 44844121, 55753905, 68000591, 92554120, 62051033, 86118021, 39373729, 73021291, 12664567, 4064751, 67793644, 82339363, 79922758, 7229550, 17365188, 7919588, 9188443, 6986898, 13862149, 7300047, 33237508 +60430369, 56461322, 59371804, 78602717, 7685448, 42692881, 18783702, 76960303, 34946859, 26102057, 54199160, 30998561, 30891921, 92071990, 20765474, 75777973, 73168565, 4798568, 38365584, 32590267, 41245325, 68644627, 5970607, 27625735, 59501487, 56955985, 36139942, 8099994, 21919959, 17976208, 59614746, 48360198, 48260151, 54427233, 79880247, 37620363, 20122224, 72732205, 16567550, 4668450, 11581181, 50567636, 17386996, 17081350, 94076128, 67793644, 2917920, 67124282, 91281584, 45049308, 39553046, 19376156, 8115266, 20642888, 80316608, 84684495, 75128745, 66611759, 48395186, 14326617, 13862149, 18466635, 84166196, 53393358, 44844121, 86460488, 65880522, 47213183, 68875490, 3233569, 84293052, 30463802, 91957544, 6793819, 12303248, 22766820, 44889423, 83747892, 70541760, 50007421, 33699435, 33237508, 71316369, 16424199, 63015256, 72373496, 74441448, 67030811, 77072625, 26744917, 67513640, 79821897, 74743862, 88904910, 44481640, 5267545, 84349107, 69136837, 99549373, 27411561, 91727510, 22435353, 49598724, 68041839, 87160386, 78785507, 44664587, 66250369, 3183975, 74137926, 23569917, 66903004, 68816413, 95010552, 88444207, 92398073, 30764367, 17857111, 24591705, 67227442, 3633375, 32699744, 24058273, 54263880, 30787683, 87598888, 93790285, 22108665, 75407347, 43045786, 55850790, 17016156, 37183543, 23134715, 6111563, 48088883, 89804152, 66271566, 13468268, 51507425, 44847298, 48892201, 1239555, 71300104, 89214616, 92604458, 59177718, 77694700, 52204879, 18131876, 78766358, 88047921, 37659250, 71920426, 19272365, 4069912, 99021067, 95726235, 51466049, 8733068, 94711842, 12024238, 6819644, 70420215, 54606384, 99333375, 53632373, 84127901, 83155442, 46870723, 168541, 44983451, 6521313, 89637706, 72358170, 36753250, 526217, 95251277, 31904591, 82327024, 44842615, 59109400, 13470059, 92787493, 16405341, 94090109, 47361209, 17894977, 93359396, 10961421, 58224549, 36312813, 28796059, 66045587, 57248122, 6038457, 81274566, 36933038, 47893286, 51472275, 93515664, 72495719, 77272628, 97011160, 26734892, 61712234, 65338021, 78884452, 20486294, 21673260, 68110330, 38061439, 33061250, 83789391, 31733363, 76434144, 60581278, 29959549, 37675718, 93933709, 4204661, 30543215, 99729357, 63506281, 80555751, 92692283, 15535065, 96906410, 5073754, 12348118, 47708850, 37501808, 79136082, 23740167, 56473732, 17146629, 32058615, 24915585, 82532312, 18806856, 20645197, 40781854, 14731700, 20002147, 9603598, 97783876, 74452589, 77187825, 87710366, 31727379, 17037369, 1250437, 30771409, 45995325, 16380211, 97641116, 54987042, 90090964, 82897371, 5111370, 57020481, 61960400, 99917599, 93566986, 26664538, 91240048, 83210802, 90310261, 22405120, 9058407, 93053405, 33797252, 97281847, 18001617, 29516592, 8373289, 80251430, 17539625, 21001913, 27185644, 33304202, 72274002, 43357947, 72019362, 99658235, 40865610, 61623915, 75153252, 66667729, 68702632, 55602660, 62115552, 78549759, 9259676, 83150534, 26397786, 91990218, 57393458, 84406788, 6497830, 28734791, 4978543, 53802686, 90061527, 51590803, 81677380, 1197320, 70596786, 19898053, 85695762, 44177011, 1569515, 37224844, 55753905, 64157906, 67031644, 61928316, 9575954, 95395112, 31161687, 62051033, 47887470, 77312810, 16097038, 7300047, 60102965, 98653983, 17058722, 55189057, 82052050, 51149804, 42307449, 874791, 80014588, 73617245, 37560164, 23110625, 9175338, 70372191, 43322743, 71469330, 22113133, 38022834, 95957797, 29466406, 27041967, 91664334, 14045383, 41481685, 4091162, 28851716, 4119747, 92692978, 27665211, 79806380, 49271185, 86798033, 87720882, 68204242, 72777973, 73786944, 47298834, 1431742, 86242799, 98948034, 30218878, 73021291, 62496012, 99226875, 66663942, 89699445, 8128637, 12664567, 50702367, 99515901, 47738185, 82779622, 21397057, 71657078, 54058788, 96318094, 6871053, 10597197, 97057187, 30163921, 669105, 72278539, 4985896, 56515456, 62452034, 65017137, 99125126, 19457589, 16387575, 77300457, 43501211, 54517921, 15148031, 10366309, 60176618, 4787945, 4806458, 17764950, 70800879, 85242963, 26139110, 4434662, 92215320, 13231279, 3509435, 44348328, 17068582, 76315420, 16445503, 51715482, 53084256, 54762643, 73235980, 3088684, 95581843, 49328608, 508198, 2208785, 7229550, 85023028, 59981773, 47792865, 36580610, 30366150, 54014062, 63628376, 37280276, 22200378, 4199704, 36189527, 9860195, 65454636, 10309525, 15948937, 14626618, 62430984, 89996536, 54663246, 7919588, 81853704, 65715134, 53547802, 6153406, 73392814, 38658347, 73222868, 82178706, 62490109, 12416768, 89046466, 70004753, 20867149, 40027975, 76170907, 78589145, 13348726, 98648327, 68000591, 8129978, 63059024, 13819358, 62803858, 61728685, 1022677, 61263068, 77301523, 569864, 94360702, 43933006, 36685023, 76671482, 76703609, 65081429, 2331773, 46851987, 62740044, 72614359, 78909561, 36505482, 35192533, 7955293, 83948335, 73124510, 72738685, 75986488, 29188588, 85116755, 70367851, 71522968, 18663507, 6505939, 18504197, 4099191, 47090124, 58751351, 59329511, 74357852, 31126490, 97379790, 49641577, 40781449, 90654836, 18699206, 91938191, 7687278, 1204161, 65047700, 65074535, 23194618, 85571389, 39201414, 40356877, 56484900, 78300864, 72357096, 824872, 96193415, 34497327, 36930650, 62762857, 24168411, 68939068, 39986008, 77413012, 6986898, 96709982, 46540998, 37891451, 247198, 73031054, 91255408, 14220886, 92353856, 48893685, 69697787, 68694897, 94911072, 61141874, 36812683, 18833224, 32274392, 45075471, 82339363, 40677414, 35512853, 94516935, 14947650, 26493119, 45617087, 44473167, 57241521, 21533347, 96852321, 19939935, 71965942, 38510840, 90013093, 35092039, 22450468, 68102437, 28928175, 63372756, 6221471, 9623492, 79657802, 33895336, 96735716, 4515343, 91802888, 33628349, 9860968, 95290172, 61271144, 26392416, 49236559, 749283, 37957788, 59405277, 20885148, 15163258, 34236719, 64098930, 6525948, 20681921, 55770687, 69605283, 81898046, 8807940, 64087743, 3773993, 99861373, 79738755, 12571310, 24826575, 64602895, 77377183, 3235882, 52613508, 39801351, 92867155, 89217461, 86543538, 26275734, 85117092, 66322959, 26507214, 49597667, 41380093, 77620120, 34295794, 29635537, 57163802, 48658605, 99690194, 63967300, 44245960, 2204165, 73109997, 6808825, 67281495, 86118021, 25636669, 8791066, 77787724, 29834512, 96726697, 59910624, 68316156, 82651278, 57359924, 74110882, 24314885, 66428795, 16684829, 50188404, 50668599, 29994197, 99524975, 24953498, 83083131, 3294781, 20140249, 5822202, 56424103, 57658654, 41092102, 33201905, 37192445, 45381876, 15536795, 40686254, 52261574, 82726008, 76330843, 99224392, 40534591, 10453030, 32151165, 59318837, 40152546, 34432810, 94595668, 53888755, 44550764, 11365791, 8696647, 9398733, 9886593, 44846932, 29065964, 65038678, 43506672, 83368048, 68824981, 61859581, 93057697, 92803766, 76825057, 83651211, 72725103, 51588015, 48673079, 4095116, 79942022, 97940276, 75543508, 45790169, 30139692, 33431960, 19891772, 58208470, 57961282, 75820087, 98462867, 26863229, 83133790, 90457870, 49882705, 3487592, 88653118, 28787861, 26114953, 20568363, 38494874, 98943869, 42947632, 14093520, 75229462, 36468541, 70782102, 1788101, 64848072, 44915235, 15075176, 11161731, 68128438, 34493392, 32159704, 38008118, 69848388, 15015906, 45794415, 22879907, 62232211, 80246713, 24619760, 61373987, 70727211, 15064655, 8729146, 59197747, 19486173, 88130087, 44784505, 69255765, 30569392, 86301513, 98739783, 92554120, 65275241, 38341669, 21289531, 39171895, 72793444, 40197395, 11543098, 30653863, 36845587, 62936963, 37739481, 61815223, 19101477, 415901, 16099750, 18411915, 98371444, 51047803, 112651, 92998591, 33249630, 74724075, 36808486, 63152504, 51426311, 55960386, 7011964, 99297164, 39373729, 43376279, 85711894, 16054533, 36135, 81774825, 90004325, 69355476, 92033260, 14363867, 56153345, 29401781, 72238278, 9257405, 31491938, 37435892, 55247455, 37166608, 36780454, 84187166, 45996863, 26292919, 36396314, 57240218, 14349098, 4064751, 5832946, 99971982, 65851721, 50806615, 61897582, 66832478, 86821229, 56531125, 321665, 44627776, 74614639, 71083565, 92530431, 90158785, 2891150, 33336148, 97561745, 8634541, 69579137, 69641513, 88698958, 26998766, 88251446, 11923835, 34698428, 7104732, 45237957, 3880712, 96420429, 81805959, 51315460, 75052463, 8651647, 60106217, 67084351, 42237907, 34667286, 38009615, 99604946, 176257, 75135448, 5482538, 30090481, 62552524, 78561158, 7423788, 5640302, 42644903, 29932657, 82886381, 42967683, 58180653, 42199455, 8913721, 26063929, 55615885, 54868730, 69786756, 32426752, 42073124, 27187213, 81172706, 93562257, 99965001, 4508700, 34044787, 89811711, 77898274, 30811010, 54232247, 33935899, 45919976, 66319530, 67474219, 77284619, 61982238, 11757872, 33565483, 45848907, 89078848, 22721500, 2717150, 45306070, 32161669, 9599614, 23432750, 45407418, 66137019, 45667668, 13173644, 78218845, 29510992, 86001008, 25842078, 84840549, 91141395, 65271999, 82427263, 79922758, 1936762, 55470718, 9829782, 48675329, 6157724, 17365188, 98130363, 20836893, 21993752, 21070110, 35456853, 53648154, 15902805, 40984766, 33123618, 33553959, 52293995, 84002370, 61741594, 92541302, 39847321, 9188443, 29029316, 51464002, 41442762, 16971929, 55143724, 7517032, 52060076, 44479073, 1375023, 64055960, 79322415, 83269727, 17385531, 22129328, 17727650, 62693428, 79191827, 41092172, 83533741, 81855445, 58749, 62357986, 53842979, 26776922, 30694952, 10358899, 70036438, 67451935, 32250045, 14723410, 22942635, 30395570, 45990383, 34698463, 84904436, 66885828, 2607799, 7182642, 61859143, 10264691, 83302115, 60393039, 43152977, 38256849, 86022504, 62428472, 44060493, 23848565, 38645117, 76540481, 35996293, 90272749, 28550822, 8055981, 93270084, 91831487, 27325428, 22997281, 89499542, 66116458, 10649306, 16019925, 45428665, 7646095, 7066775, 71333116, 55669657, 48774913, 29819940, 17957593, 89419466, 94539824, 16595436, 82979980, 53666583, 60955663, 7502255, 3233084, 42782652, 57803235, 24733232 +76960303, 43357947, 12571310, 47213183, 54663246, 31161687, 5832946, 3183975, 27665211, 50702367, 40152546, 70800879, 47361209, 9860968, 13862149, 90061527, 84166196, 64098930, 65275241, 37183543, 19101477, 91957544, 91664334, 86821229, 2717150, 77300457, 72725103, 4787945, 91727510, 92215320, 78785507, 96420429, 95290172, 48675329, 94539824, 20836893, 61373987, 20867149, 66116458, 92554120, 20765474, 33553959, 82052050, 49597667, 77620120, 59177718, 43322743, 54232247, 83083131, 60955663, 87710366, 33565483, 99224392, 96318094, 11365791, 69697787, 9398733, 99125126, 29065964, 45049308, 82339363, 90310261, 4806458, 26102057, 4434662, 26493119, 8373289, 33431960, 35092039, 76315420, 51715482, 10961421, 63372756, 66611759, 62357986, 2208785, 60430369, 74137926, 75229462, 42237907, 28734791, 65454636, 81677380, 14626618, 89499542, 44844121, 1569515, 15064655, 55753905, 19486173, 78589145, 98648327, 38341669, 75777973, 40197395, 30543215, 34698463, 48260151, 55615885, 39847321, 70372191, 42073124, 92998591, 47708850, 89811711, 49641577, 81774825, 20122224, 28851716, 7687278, 92033260, 85571389, 39201414, 1375023, 37435892, 72357096, 20140249, 61982238, 89699445, 15536795, 17727650, 46540998, 54058788, 10453030, 97057187, 44983451, 6521313, 74743862, 526217, 93566986, 36139942, 22435353, 57241521, 29510992, 17539625, 13231279, 75820087, 72019362, 49882705, 88251446, 28928175, 91141395, 45237957, 79657802, 26776922, 6038457, 51315460, 85023028, 47792865, 36468541, 84406788, 22200378, 6497830, 9860195, 3633375, 68000591, 44784505, 92071990, 92867155, 61263068, 82979980, 76703609, 79880247, 62740044, 4798568, 72614359, 61741594, 34295794, 85116755, 54868730, 66885828, 12348118, 29029316, 73109997, 39373729, 34044787, 74357852, 31126490, 14045383, 82651278, 90004325, 71920426, 82532312, 24314885, 65047700, 14363867, 29994197, 73786944, 40356877, 24953498, 40781854, 824872, 59501487, 99226875, 97783876, 77187825, 38256849, 84187166, 39986008, 37192445, 77413012, 17386996, 82779622, 59318837, 30163921, 22721500, 45306070, 9886593, 38645117, 8115266, 76540481, 2891150, 59371804, 80316608, 58208470, 44348328, 69641513, 22450468, 26998766, 99658235, 3487592, 88653118, 8055981, 73235980, 3088684, 30998561, 79922758, 26114953, 78549759, 20568363, 91831487, 67084351, 49236559, 67451935, 6157724, 27325428, 38008118, 61712234, 20681921, 16595436, 32699744, 53547802, 6153406, 45794415, 19898053, 35456853, 81898046, 86460488, 70727211, 76170907, 65880522, 5482538, 52613508, 75407347, 43045786, 5640302, 39801351, 42644903, 62051033, 21289531, 17016156, 1022677, 60102965, 4204661, 89804152, 58180653, 11543098, 874791, 80014588, 84904436, 65081429, 36845587, 80555751, 37739481, 30463802, 1239555, 83948335, 75986488, 29635537, 57163802, 89214616, 22766820, 44889423, 37620363, 79136082, 70541760, 67281495, 7066775, 99965001, 56461322, 47090124, 58751351, 5970607, 16971929, 96726697, 55143724, 36135, 61859143, 57359924, 30811010, 1204161, 83302115, 94711842, 56484900, 1431742, 14731700, 34497327, 57658654, 9603598, 11757872, 41092102, 86022504, 17037369, 50567636, 56955985, 45848907, 84127901, 57240218, 57803235, 6986898, 83155442, 17081350, 1250437, 46870723, 99971982, 168541, 44550764, 82897371, 2917920, 67124282, 44846932, 36753250, 74614639, 32274392, 31904591, 24733232, 26664538, 32161669, 93057697, 83210802, 40677414, 90158785, 51588015, 22405120, 4095116, 35996293, 26139110, 33336148, 18001617, 29516592, 78218845, 96852321, 83133790, 38510840, 90013093, 8099994, 75153252, 58749, 11923835, 9623492, 66667729, 54199160, 508198, 62115552, 33628349, 61271144, 36580610, 63628376, 36189527, 4978543, 17976208, 59405277, 20885148, 34667286, 68128438, 15163258, 30764367, 34236719, 6525948, 98130363, 15015906, 65715134, 70596786, 22879907, 62490109, 68110330, 30787683, 64602895, 88130087, 30090481, 9575954, 68875490, 13819358, 62803858, 29932657, 77301523, 569864, 89217461, 86543538, 6111563, 43933006, 48088883, 53666583, 42199455, 76671482, 52293995, 8913721, 26507214, 44847298, 415901, 37560164, 92692283, 23110625, 9188443, 7685448, 92604458, 96906410, 32426752, 42692881, 44245960, 71522968, 7646095, 4099191, 56473732, 8791066, 41442762, 68644627, 71333116, 85711894, 97379790, 68316156, 7517032, 77898274, 24915585, 40781449, 27625735, 69355476, 79806380, 49271185, 66428795, 4069912, 99021067, 50668599, 10264691, 16567550, 18806856, 8733068, 12024238, 4668450, 20002147, 67474219, 67030811, 98948034, 3294781, 77072625, 36780454, 62428472, 45996863, 12664567, 36396314, 17385531, 47738185, 30771409, 247198, 16380211, 67793644, 50806615, 66832478, 53888755, 68694897, 5111370, 62693428, 89637706, 36812683, 19457589, 43501211, 54517921, 83368048, 92530431, 61859581, 9599614, 92803766, 76825057, 35512853, 45407418, 16405341, 45617087, 97281847, 30139692, 44473167, 3509435, 19939935, 25842078, 72274002, 16445503, 84840549, 34698428, 66250369, 53842979, 33895336, 82427263, 7229550, 38494874, 66903004, 14326617, 83150534, 21919959, 95010552, 26392416, 30366150, 10358899, 37280276, 749283, 44915235, 15075176, 92398073, 15948937, 97011160, 22997281, 89996536, 59614746, 26734892, 65338021, 20486294, 53648154, 21673260, 80246713, 8807940, 24619760, 89046466, 87598888, 31733363, 99861373, 93790285, 37224844, 59197747, 24826575, 67031644, 62552524, 3235882, 86301513, 63059024, 73168565, 61728685, 55850790, 47887470, 23134715, 7300047, 72793444, 36685023, 66271566, 85117092, 26063929, 73617245, 30653863, 38365584, 32590267, 73124510, 6793819, 51047803, 63967300, 27187213, 70367851, 36808486, 63152504, 6808825, 23740167, 18783702, 86118021, 33237508, 22113133, 52204879, 18131876, 78766358, 7182642, 52060076, 4091162, 37659250, 18699206, 33935899, 91938191, 50188404, 23194618, 29401781, 47298834, 74441448, 78300864, 77284619, 62496012, 66663942, 55669657, 54606384, 83269727, 26744917, 89078848, 40686254, 71657078, 45995325, 34432810, 54987042, 72278539, 14220886, 321665, 65017137, 72358170, 88904910, 95251277, 61960400, 71083565, 45075471, 44481640, 23848565, 19376156, 69136837, 92787493, 41092172, 20642888, 66137019, 75543508, 33797252, 97561745, 90272749, 80251430, 84684495, 71965942, 93359396, 28550822, 61623915, 65271999, 6221471, 58224549, 28796059, 66045587, 57248122, 91802888, 55602660, 23569917, 30694952, 8651647, 59981773, 55470718, 88444207, 47893286, 4199704, 93515664, 10309525, 53802686, 32250045, 72495719, 62430984, 14723410, 1197320, 17857111, 55770687, 21070110, 15902805, 40984766, 38009615, 176257, 48360198, 64157906, 13348726, 45990383, 69255765, 30569392, 7423788, 10649306, 60581278, 42967683, 94360702, 3233569, 17058722, 66322959, 84002370, 46851987, 48892201, 45428665, 9175338, 18411915, 71300104, 99690194, 112651, 5073754, 77694700, 81172706, 71469330, 93562257, 51464002, 83747892, 55960386, 50007421, 7011964, 4508700, 71316369, 29466406, 2607799, 88047921, 16424199, 41481685, 90654836, 72732205, 4119747, 92692978, 74110882, 63015256, 45919976, 60393039, 31491938, 64055960, 56424103, 62762857, 79322415, 37166608, 33201905, 68939068, 26292919, 53632373, 99515901, 52261574, 82726008, 14349098, 44060493, 29819940, 34946859, 79821897, 6871053, 10597197, 48893685, 56515456, 94911072, 44627776, 91281584, 57020481, 18833224, 43506672, 39553046, 15148031, 44842615, 5267545, 84349107, 60176618, 83651211, 99549373, 17764950, 9058407, 85242963, 83533741, 27411561, 14947650, 97940276, 45667668, 94090109, 13173644, 81855445, 21533347, 57961282, 26863229, 86001008, 21001913, 27185644, 33304202, 90457870, 68102437, 40865610, 48395186, 7104732, 44664587, 68702632, 28787861, 3880712, 96735716, 4515343, 75052463, 60106217, 89419466, 70782102, 36933038, 26397786, 64848072, 51472275, 11161731, 51590803, 24591705, 73222868, 82178706, 62232211, 38061439, 3773993, 44177011, 79738755, 78561158, 98739783, 82886381, 93933709, 26275734, 42307449, 16019925, 54427233, 84293052, 62936963, 36505482, 35192533, 61815223, 7955293, 41380093, 92541302, 15535065, 48658605, 18663507, 2204165, 6505939, 59329511, 38022834, 95957797, 77787724, 59910624, 32058615, 19272365, 86798033, 65074535, 16684829, 72777973, 72238278, 9257405, 72373496, 20645197, 73021291, 5822202, 36930650, 24168411, 11581181, 99333375, 8128637, 96709982, 48774913, 7502255, 32151165, 94076128, 94595668, 97641116, 669105, 4985896, 92353856, 56531125, 65038678, 68824981, 49598724, 68041839, 19891772, 69579137, 88698958, 3233084, 17957593, 17894977, 87160386, 75128745, 54762643, 93270084, 36312813, 78602717, 9259676, 81274566, 14093520, 1788101, 91990218, 57393458, 54014062, 77272628, 69848388, 7919588, 81853704, 67227442, 73392814, 21993752, 22942635, 30891921, 64087743, 33061250, 30395570, 70004753, 40027975, 22108665, 61928316, 8129978, 95395112, 33123618, 29959549, 37675718, 16097038, 55189057, 51149804, 99729357, 63506281, 51507425, 72738685, 29188588, 16099750, 98371444, 33249630, 41245325, 25636669, 17146629, 99297164, 29834512, 43376279, 44479073, 56153345, 87720882, 95726235, 43152977, 55247455, 96193415, 70420215, 74452589, 76330843, 4064751, 21397057, 37891451, 73031054, 91255408, 8696647, 79191827, 82327024, 59109400, 13470059, 79942022, 94516935, 45790169, 8634541, 17068582, 49328608, 42782652, 81805959, 98943869, 37957788, 34493392, 32159704, 24058273, 78884452, 85695762, 99604946, 54263880, 75135448, 39171895, 98653983, 13468268, 2331773, 78909561, 69786756, 12303248, 18504197, 27041967, 68204242, 51466049, 30218878, 67513640, 22129328, 65851721, 61897582, 90090964, 62452034, 61141874, 16387575, 99917599, 10366309, 91240048, 23432750, 48673079, 93053405, 98462867, 1936762, 9829782, 70036438, 18466635, 17365188, 38658347, 76434144, 77377183, 77312810, 86242799, 31727379, 68816413, 12416768, 83789391, 8729146, 74724075, 37501808, 51426311, 33699435, 16054533, 6819644, 66319530, 45381876, 40534591, 42947632, 53393358, 69605283, 99524975, 95581843, 53084256 +64602895, 62936963, 37560164, 86118021, 24168411, 60176618, 65338021, 68000591, 62051033, 55850790, 27187213, 16054533, 82339363, 75229462, 89046466, 61373987, 8729146, 84002370, 52204879, 65074535, 73021291, 62762857, 84127901, 2917920, 16387575, 93566986, 84349107, 47361209, 70782102, 6157724, 15948937, 69848388, 73392814, 1569515, 20867149, 37224844, 60581278, 61263068, 89217461, 39847321, 92604458, 33249630, 12303248, 74724075, 37620363, 4099191, 71316369, 78766358, 57359924, 33935899, 34497327, 26744917, 57803235, 17081350, 61141874, 82327024, 36139942, 4434662, 81855445, 3509435, 35092039, 76315420, 93359396, 81274566, 60106217, 55470718, 53802686, 59614746, 21070110, 20486294, 22942635, 87598888, 98648327, 45990383, 38341669, 92867155, 75777973, 73168565, 21289531, 3233569, 12348118, 22113133, 29834512, 43376279, 97379790, 24915585, 7687278, 56153345, 72373496, 47298834, 64055960, 98948034, 9603598, 37166608, 89699445, 31727379, 82726008, 71657078, 94595668, 97057187, 65851721, 97641116, 669105, 44550764, 74743862, 11365791, 29065964, 57020481, 18833224, 83368048, 79191827, 92530431, 31904591, 10366309, 91240048, 75543508, 21533347, 3487592, 73235980, 93270084, 66250369, 66667729, 36312813, 28787861, 49328608, 57248122, 81805959, 33628349, 20568363, 75052463, 98943869, 47792865, 26392416, 92398073, 10309525, 38658347, 22879907, 76170907, 59197747, 76434144, 69255765, 68875490, 20765474, 31161687, 52293995, 8913721, 73617245, 30653863, 30463802, 1239555, 49597667, 57163802, 96906410, 69786756, 66885828, 2204165, 51464002, 33699435, 58751351, 39373729, 96726697, 37659250, 4119747, 91938191, 49271185, 1204161, 68204242, 9257405, 1431742, 86242799, 20002147, 6819644, 77187825, 45996863, 99515901, 46540998, 40534591, 34432810, 22721500, 9886593, 36812683, 61960400, 71083565, 32274392, 45075471, 39553046, 99917599, 19376156, 13470059, 4787945, 16405341, 49598724, 97561745, 71965942, 43357947, 16445503, 51715482, 72019362, 99658235, 40865610, 75128745, 65271999, 58224549, 45237957, 44664587, 3880712, 66045587, 4515343, 79922758, 30694952, 85023028, 68816413, 67451935, 20885148, 18466635, 61712234, 67227442, 24058273, 53393358, 89499542, 69605283, 81898046, 24619760, 93790285, 88130087, 9575954, 8129978, 33553959, 16097038, 7300047, 48088883, 98653983, 66271566, 26063929, 73124510, 72738685, 29188588, 85116755, 16099750, 22766820, 44889423, 7646095, 6505939, 56473732, 99297164, 41442762, 34044787, 18131876, 59910624, 49641577, 41481685, 68316156, 7517032, 77898274, 69355476, 27665211, 79806380, 18699206, 66428795, 92033260, 87720882, 50668599, 23194618, 29994197, 94711842, 56484900, 4668450, 96193415, 36930650, 55669657, 41092102, 45381876, 52261574, 4064751, 48774913, 82779622, 7502255, 247198, 67793644, 69697787, 62693428, 65017137, 72358170, 99125126, 44846932, 88904910, 44627776, 19457589, 91281584, 95251277, 83651211, 35512853, 4806458, 92787493, 22405120, 48673079, 83533741, 2891150, 59371804, 93053405, 26493119, 90272749, 44473167, 58208470, 92215320, 84684495, 3233084, 21001913, 90457870, 87160386, 68102437, 61623915, 66611759, 54199160, 30998561, 508198, 9259676, 38494874, 66903004, 14326617, 1788101, 91990218, 57393458, 30366150, 749283, 15075176, 14626618, 34493392, 14723410, 7919588, 15015906, 73222868, 44844121, 40984766, 64087743, 44177011, 30090481, 77377183, 98739783, 39801351, 13819358, 42644903, 61728685, 29932657, 82886381, 47887470, 17016156, 37675718, 23134715, 4204661, 17058722, 51149804, 34698463, 13468268, 80014588, 79880247, 84293052, 65081429, 35192533, 23110625, 71300104, 89214616, 70372191, 63967300, 5073754, 77694700, 18663507, 79136082, 18504197, 41245325, 67281495, 23740167, 55960386, 18783702, 56461322, 47090124, 8791066, 88047921, 85711894, 61859143, 4091162, 27625735, 71920426, 74110882, 76960303, 99021067, 72777973, 16567550, 95726235, 18806856, 83302115, 60393039, 55247455, 83083131, 66319530, 67030811, 59501487, 99226875, 11757872, 11581181, 33565483, 45848907, 8128637, 77413012, 57240218, 46870723, 17727650, 44060493, 10453030, 6871053, 10597197, 168541, 30163921, 90090964, 82897371, 68694897, 67124282, 5111370, 9599614, 76825057, 51588015, 97940276, 91727510, 26139110, 22435353, 78218845, 96852321, 26863229, 86001008, 33304202, 90013093, 22450468, 26998766, 78785507, 53084256, 68702632, 53842979, 33895336, 96420429, 91802888, 6038457, 3183975, 74137926, 78549759, 8651647, 59981773, 88444207, 84406788, 10358899, 37280276, 9829782, 6497830, 37957788, 9860195, 65454636, 34667286, 27325428, 77272628, 68128438, 94539824, 34236719, 54663246, 6525948, 26734892, 3633375, 78884452, 30891921, 12416768, 86460488, 38061439, 33061250, 3773993, 19486173, 5482538, 78589145, 62552524, 47213183, 86301513, 5640302, 92554120, 77301523, 569864, 42967683, 6111563, 94360702, 43933006, 39171895, 53666583, 26275734, 89804152, 874791, 2331773, 44847298, 37739481, 61741594, 38365584, 6793819, 9188443, 42073124, 43322743, 44245960, 47708850, 93562257, 83747892, 51426311, 25636669, 95957797, 71333116, 2607799, 91664334, 55143724, 16424199, 90654836, 54232247, 65047700, 16684829, 51466049, 12024238, 31491938, 1375023, 99524975, 24953498, 20645197, 40781854, 3294781, 72357096, 824872, 56424103, 79322415, 36780454, 99333375, 67513640, 37192445, 36396314, 53632373, 6986898, 50702367, 83155442, 99224392, 47738185, 79821897, 45995325, 37891451, 99971982, 50806615, 66832478, 86821229, 72278539, 91255408, 14220886, 2717150, 9398733, 45306070, 56531125, 321665, 94911072, 65038678, 54517921, 44481640, 44842615, 93057697, 83210802, 23432750, 17764950, 76540481, 35996293, 66137019, 33336148, 8373289, 94090109, 80251430, 69641513, 98462867, 84840549, 49882705, 28928175, 75153252, 58749, 10961421, 91141395, 34698428, 63372756, 7104732, 28796059, 96735716, 55602660, 62115552, 23569917, 51315460, 9860968, 42947632, 14093520, 21919959, 95010552, 36468541, 61271144, 67084351, 36580610, 13862149, 48675329, 4199704, 36189527, 17976208, 17365188, 22997281, 84166196, 64098930, 17857111, 20681921, 53547802, 6153406, 70596786, 19898053, 21993752, 21673260, 15902805, 83789391, 79738755, 55753905, 22108665, 61928316, 44784505, 78561158, 66116458, 63059024, 43045786, 95395112, 65275241, 62803858, 33123618, 29959549, 72793444, 55189057, 36685023, 76703609, 16019925, 46851987, 26507214, 4798568, 72614359, 80555751, 78909561, 41380093, 34295794, 15535065, 29635537, 59177718, 112651, 92998591, 29029316, 71469330, 7066775, 33237508, 59329511, 16971929, 31126490, 7182642, 36135, 82651278, 82532312, 24314885, 29401781, 85571389, 73786944, 10264691, 8733068, 78300864, 77284619, 30218878, 97783876, 74452589, 86022504, 17037369, 40686254, 1250437, 76330843, 21397057, 29819940, 5832946, 30771409, 32151165, 53888755, 54987042, 8696647, 36753250, 526217, 43501211, 26664538, 61859581, 23848565, 69136837, 40677414, 45407418, 9058407, 85242963, 27411561, 94516935, 68041839, 45667668, 30139692, 57241521, 19891772, 29510992, 25842078, 17957593, 83133790, 72274002, 88251446, 54762643, 62357986, 3088684, 26776922, 91831487, 89419466, 36933038, 26397786, 54014062, 63628376, 22200378, 44915235, 93515664, 59405277, 11161731, 32250045, 51590803, 72495719, 81677380, 32159704, 89996536, 38008118, 1197320, 16595436, 65715134, 82178706, 62490109, 30395570, 54263880, 30787683, 176257, 70727211, 65880522, 13348726, 3235882, 52613508, 75407347, 30569392, 1022677, 77312810, 37183543, 60102965, 82052050, 40197395, 11543098, 85117092, 99729357, 48260151, 55615885, 36505482, 61815223, 19101477, 92692283, 92541302, 98371444, 51047803, 50007421, 17146629, 38022834, 77787724, 29466406, 27041967, 74357852, 81774825, 20122224, 92692978, 63015256, 44479073, 50188404, 72238278, 39201414, 74441448, 14731700, 5822202, 70420215, 77072625, 54606384, 87710366, 62428472, 26292919, 89078848, 17386996, 17385531, 54058788, 59318837, 40152546, 94076128, 44983451, 6521313, 92353856, 45049308, 43506672, 74614639, 15148031, 32161669, 90310261, 8115266, 90158785, 41092172, 79942022, 80316608, 97281847, 13173644, 69579137, 17539625, 13231279, 88698958, 38510840, 28550822, 88653118, 8055981, 60430369, 26114953, 83150534, 95290172, 47893286, 28734791, 4978543, 90061527, 62430984, 15163258, 24591705, 81853704, 45794415, 55770687, 85695762, 53648154, 80246713, 8807940, 38009615, 31733363, 99861373, 40027975, 12571310, 75135448, 48360198, 64157906, 92071990, 7423788, 42307449, 63506281, 66322959, 54427233, 7955293, 83948335, 91957544, 75986488, 45428665, 18411915, 48658605, 7685448, 99690194, 42692881, 37501808, 63152504, 6808825, 70541760, 99965001, 5970607, 14045383, 32058615, 52060076, 30811010, 72732205, 14363867, 4069912, 43152977, 60955663, 67474219, 20140249, 66663942, 61982238, 84187166, 15536795, 12664567, 14349098, 68824981, 24733232, 38645117, 59109400, 99549373, 70800879, 33797252, 45617087, 18001617, 57961282, 75820087, 44348328, 27185644, 48395186, 42782652, 82427263, 78602717, 1936762, 42237907, 49236559, 64848072, 51472275, 30764367, 98130363, 35456853, 62232211, 68110330, 99604946, 70004753, 24826575, 10649306, 82979980, 58180653, 42199455, 51507425, 84904436, 415901, 9175338, 54868730, 81172706, 71522968, 89811711, 68644627, 40781449, 86798033, 40356877, 37435892, 62496012, 57658654, 33201905, 68939068, 50567636, 39986008, 56955985, 96709982, 34946859, 96318094, 16380211, 61897582, 4985896, 48893685, 62452034, 77300457, 5267545, 92803766, 72725103, 4095116, 14947650, 33431960, 8099994, 6221471, 9623492, 7229550, 20836893, 67031644, 86543538, 76671482, 62740044, 36845587, 32590267, 77620120, 32426752, 70367851, 73109997, 36808486, 4508700, 38256849, 73031054, 20642888, 26102057, 19939935, 17894977, 17068582, 11923835, 79657802, 2208785, 70036438, 32699744, 15064655, 93933709, 30543215, 48892201, 28851716, 19272365, 83269727, 56515456, 89637706, 45790169, 29516592, 8634541, 95581843, 97011160, 7011964, 45919976, 22129328, 90004325 +9398733, 70800879, 62357986, 42237907, 62740044, 1239555, 45428665, 30163921, 60176618, 30694952, 75052463, 29932657, 37739481, 63967300, 4119747, 50188404, 61982238, 2891150, 8373289, 80251430, 7104732, 28796059, 85023028, 65454636, 89046466, 52613508, 80014588, 78909561, 18504197, 23740167, 99965001, 29834512, 14045383, 68316156, 82651278, 44479073, 29401781, 12024238, 36930650, 33565483, 21397057, 43506672, 74614639, 91240048, 80316608, 97281847, 98462867, 68102437, 48395186, 28787861, 82427263, 60106217, 59981773, 6497830, 18466635, 51590803, 97011160, 89996536, 64098930, 32699744, 65338021, 19898053, 73222868, 22942635, 19486173, 65880522, 78589145, 13348726, 33123618, 21289531, 29959549, 8913721, 42307449, 84293052, 73617245, 62936963, 61741594, 38365584, 29635537, 7685448, 69786756, 12303248, 37501808, 93562257, 33699435, 32058615, 1204161, 9257405, 40356877, 16567550, 8733068, 47298834, 55247455, 98948034, 54606384, 89699445, 99333375, 77413012, 99515901, 30771409, 10453030, 32151165, 11365791, 8696647, 67124282, 56515456, 95251277, 43501211, 79191827, 24733232, 5267545, 8115266, 35512853, 20642888, 79942022, 33336148, 90013093, 43357947, 90457870, 11923835, 54762643, 4515343, 57248122, 66903004, 42947632, 36468541, 1788101, 26397786, 36580610, 9829782, 9860195, 77272628, 68128438, 30764367, 20681921, 16595436, 69605283, 38009615, 70727211, 76170907, 13819358, 17016156, 37183543, 86543538, 43933006, 30543215, 34698463, 30653863, 4798568, 49597667, 92541302, 98371444, 70372191, 33249630, 66885828, 81172706, 7066775, 18783702, 56461322, 38022834, 7182642, 41481685, 90654836, 16684829, 56153345, 99021067, 6819644, 73021291, 20140249, 824872, 34497327, 74452589, 38256849, 84187166, 26744917, 89078848, 57803235, 83155442, 46540998, 34946859, 45995325, 50806615, 54987042, 90090964, 44550764, 45306070, 94911072, 88904910, 19457589, 45075471, 99917599, 15148031, 90158785, 4806458, 27411561, 14947650, 68041839, 45617087, 18001617, 97561745, 13173644, 92215320, 86001008, 72274002, 8099994, 26998766, 93270084, 36312813, 66045587, 96735716, 55602660, 62115552, 78549759, 37280276, 47893286, 51472275, 15075176, 6157724, 53802686, 32250045, 27325428, 59614746, 61712234, 15015906, 62490109, 44177011, 79738755, 37224844, 48360198, 88130087, 76434144, 67031644, 98648327, 92554120, 75777973, 1022677, 3233569, 58180653, 42199455, 36685023, 76671482, 11543098, 85117092, 874791, 65081429, 30463802, 9175338, 16099750, 12348118, 18663507, 6505939, 51464002, 83747892, 51426311, 4099191, 95957797, 52204879, 2607799, 77898274, 20122224, 28851716, 27625735, 91938191, 87720882, 72777973, 85571389, 51466049, 43152977, 20645197, 30218878, 96193415, 62762857, 97783876, 41092102, 24168411, 11581181, 67513640, 45848907, 53632373, 50702367, 17386996, 40686254, 82726008, 48774913, 82779622, 16380211, 10597197, 74743862, 92353856, 44627776, 36753250, 77300457, 18833224, 71083565, 13470059, 83651211, 22405120, 9058407, 48673079, 91727510, 26139110, 22435353, 90272749, 57241521, 69579137, 21533347, 75820087, 25842078, 38510840, 61623915, 3487592, 6221471, 88653118, 73235980, 3088684, 66667729, 508198, 2208785, 78602717, 23569917, 67084351, 30366150, 84406788, 49236559, 48675329, 17976208, 20885148, 15948937, 81677380, 62430984, 26734892, 53393358, 20486294, 44844121, 86460488, 99604946, 3773993, 30787683, 1569515, 176257, 83789391, 87598888, 31733363, 64602895, 77377183, 62552524, 44784505, 68875490, 20765474, 38341669, 62803858, 73168565, 60581278, 47887470, 77301523, 33553959, 82979980, 23134715, 26275734, 72793444, 99729357, 63506281, 79880247, 2331773, 36845587, 48892201, 83948335, 23110625, 29188588, 71300104, 96906410, 74724075, 44245960, 71522968, 36808486, 79136082, 41245325, 67281495, 47090124, 89811711, 68644627, 78766358, 54232247, 18699206, 65047700, 65074535, 99524975, 99226875, 9603598, 36780454, 31727379, 68939068, 14349098, 4064751, 54058788, 79821897, 96318094, 40152546, 247198, 34432810, 99971982, 73031054, 67793644, 168541, 61897582, 66832478, 91255408, 4985896, 2717150, 62452034, 16387575, 526217, 54517921, 93566986, 32161669, 10366309, 69136837, 83210802, 40677414, 17764950, 16405341, 4095116, 97940276, 45790169, 4434662, 33797252, 49598724, 8634541, 3509435, 88698958, 96852321, 83133790, 71965942, 21001913, 17068582, 76315420, 22450468, 93359396, 84840549, 49882705, 28550822, 53084256, 58749, 10961421, 34698428, 65271999, 8055981, 68702632, 54199160, 3880712, 53842979, 79922758, 33628349, 9860968, 14093520, 47792865, 21919959, 57393458, 54014062, 13862149, 4199704, 36189527, 17365188, 34493392, 38008118, 14723410, 69848388, 17857111, 7919588, 73392814, 38658347, 81898046, 24619760, 70004753, 99861373, 93790285, 8729146, 59197747, 3235882, 78561158, 30569392, 86301513, 5640302, 10649306, 39801351, 62051033, 92867155, 61728685, 82886381, 37675718, 77312810, 6111563, 94360702, 39171895, 48088883, 98653983, 55189057, 40197395, 76703609, 26063929, 66322959, 55615885, 36505482, 61815223, 37560164, 34295794, 85116755, 112651, 92998591, 43322743, 70367851, 2204165, 71469330, 63152504, 6808825, 56473732, 99297164, 71316369, 71333116, 27041967, 43376279, 55143724, 16054533, 97379790, 52060076, 24915585, 72732205, 37659250, 92692978, 74110882, 24314885, 49271185, 19272365, 14363867, 1431742, 78300864, 67474219, 62496012, 59501487, 5822202, 77187825, 62428472, 50567636, 45381876, 15536795, 84127901, 17081350, 96709982, 99224392, 44060493, 59318837, 65851721, 97641116, 22721500, 6521313, 62693428, 72358170, 61141874, 29065964, 91281584, 57020481, 39553046, 44481640, 44842615, 36139942, 19376156, 84349107, 59109400, 85242963, 66137019, 26493119, 45667668, 78218845, 19891772, 29510992, 13231279, 44348328, 3233084, 33304202, 51715482, 88251446, 99658235, 75128745, 91141395, 58224549, 44664587, 95581843, 79657802, 33895336, 60430369, 26114953, 1936762, 91831487, 68816413, 55470718, 83150534, 70782102, 63628376, 22200378, 44915235, 37957788, 11161731, 34667286, 90061527, 22997281, 94539824, 32159704, 34236719, 6525948, 24058273, 89499542, 21070110, 15902805, 8807940, 68110330, 64087743, 20867149, 15064655, 24826575, 92071990, 69255765, 7423788, 43045786, 98739783, 65275241, 93933709, 82052050, 51149804, 48260151, 16019925, 54427233, 84904436, 84002370, 7955293, 415901, 73124510, 92692283, 57163802, 48658605, 51047803, 54868730, 42073124, 22766820, 7646095, 37620363, 70541760, 55960386, 8791066, 17146629, 58751351, 34044787, 77787724, 74357852, 88047921, 59910624, 16424199, 49641577, 36135, 7517032, 57359924, 30811010, 71920426, 79806380, 7687278, 86798033, 66428795, 92033260, 68204242, 45919976, 94711842, 60393039, 31491938, 64055960, 83083131, 86242799, 40781854, 20002147, 77284619, 72357096, 37166608, 33201905, 17037369, 45996863, 26292919, 1250437, 47738185, 29819940, 7502255, 71657078, 40534591, 37891451, 97057187, 53888755, 14220886, 56531125, 32274392, 92530431, 68824981, 61859581, 9599614, 72725103, 99549373, 45407418, 92787493, 76540481, 26102057, 35996293, 94516935, 75543508, 30139692, 29516592, 94090109, 47361209, 27185644, 35092039, 87160386, 9623492, 66250369, 42782652, 91802888, 6038457, 20568363, 98943869, 61271144, 70036438, 92398073, 10309525, 14626618, 15163258, 84166196, 6153406, 85695762, 21993752, 80246713, 12416768, 33061250, 54263880, 40027975, 75135448, 9575954, 55850790, 52293995, 13468268, 51507425, 46851987, 72614359, 44847298, 41380093, 77620120, 15535065, 75986488, 39847321, 6793819, 9188443, 99690194, 5073754, 47708850, 7011964, 25636669, 4508700, 39373729, 33237508, 5970607, 96726697, 85711894, 81774825, 61859143, 4091162, 82532312, 27665211, 33935899, 76960303, 72373496, 29994197, 39201414, 18806856, 37435892, 24953498, 74441448, 67030811, 66663942, 56424103, 70420215, 57658654, 11757872, 55669657, 22129328, 46870723, 44983451, 69697787, 2917920, 89637706, 65017137, 99125126, 9886593, 44846932, 45049308, 65038678, 61960400, 83368048, 31904591, 93057697, 38645117, 23432750, 4787945, 41092172, 83533741, 33431960, 17539625, 58208470, 57961282, 69641513, 17957593, 16445503, 78785507, 63372756, 66611759, 30998561, 7229550, 3183975, 81805959, 51315460, 38494874, 8651647, 81274566, 14326617, 36933038, 26392416, 91990218, 10358899, 64848072, 93515664, 4978543, 59405277, 54663246, 98130363, 24591705, 45794415, 55770687, 35456853, 38061439, 30395570, 61373987, 5482538, 30090481, 68000591, 61928316, 45990383, 75407347, 8129978, 95395112, 61263068, 569864, 7300047, 4204661, 89804152, 17058722, 66271566, 26507214, 32590267, 18411915, 89214616, 59177718, 77694700, 86118021, 50007421, 16971929, 18131876, 31126490, 40781449, 72238278, 73786944, 10264691, 95726235, 83302115, 1375023, 56484900, 60955663, 14731700, 66319530, 3294781, 79322415, 83269727, 57240218, 52261574, 94595668, 669105, 321665, 36812683, 51588015, 44473167, 81855445, 84684495, 19939935, 28928175, 75153252, 45237957, 49328608, 89419466, 95010552, 28734791, 72495719, 20836893, 67227442, 3633375, 70596786, 78884452, 53648154, 82178706, 62232211, 21673260, 40984766, 64157906, 66116458, 63059024, 89217461, 60102965, 35192533, 91957544, 72738685, 92604458, 32426752, 42692881, 27187213, 44889423, 41442762, 22113133, 91664334, 90004325, 4069912, 23194618, 4668450, 86022504, 56955985, 8128637, 36396314, 6986898, 17385531, 76330843, 17727650, 6871053, 86821229, 82897371, 48893685, 68694897, 5111370, 92803766, 93053405, 17894977, 40865610, 96420429, 74137926, 9259676, 749283, 67451935, 1197320, 81853704, 65715134, 30891921, 12571310, 55753905, 22108665, 47213183, 42644903, 31161687, 19101477, 29029316, 73109997, 59329511, 69355476, 63015256, 50668599, 87710366, 12664567, 94076128, 72278539, 26664538, 82327024, 90310261, 76825057, 26863229, 72019362, 26776922, 95290172, 88444207, 53547802, 22879907, 53666583, 77072625, 39986008, 37192445, 5832946, 82339363, 16097038, 80555751, 29466406, 23848565, 59371804, 75229462, 42967683 +65074535, 65038678, 99917599, 31904591, 84349107, 26139110, 1569515, 18783702, 32058615, 12664567, 50702367, 37891451, 48893685, 68824981, 82327024, 75128745, 92398073, 10309525, 26734892, 99861373, 76170907, 17058722, 54427233, 4099191, 33699435, 59910624, 91938191, 34946859, 79821897, 321665, 60176618, 81855445, 66250369, 95581843, 55470718, 14626618, 34236719, 6153406, 52613508, 98739783, 65275241, 77312810, 80014588, 70372191, 27187213, 56461322, 17146629, 16054533, 99021067, 72777973, 72373496, 34497327, 86821229, 74743862, 94911072, 95251277, 83651211, 4787945, 45617087, 40865610, 54199160, 49328608, 74137926, 85023028, 21919959, 20885148, 77272628, 76434144, 68000591, 78561158, 75407347, 66116458, 26063929, 77620120, 48658605, 96906410, 32426752, 70367851, 37501808, 67281495, 50007421, 68644627, 71333116, 57359924, 74110882, 27665211, 18699206, 1204161, 824872, 15536795, 40686254, 83155442, 99515901, 52261574, 50806615, 97641116, 30163921, 66832478, 44983451, 45306070, 67124282, 56531125, 93566986, 59109400, 99549373, 41092172, 85242963, 94516935, 29516592, 8373289, 49882705, 28550822, 6221471, 88653118, 28787861, 6038457, 1936762, 68816413, 36580610, 54014062, 47893286, 59405277, 54663246, 17857111, 20836893, 81853704, 24058273, 53393358, 73222868, 80246713, 24619760, 55753905, 45990383, 44784505, 8129978, 43045786, 75777973, 82886381, 37675718, 569864, 23134715, 39171895, 60102965, 53666583, 8913721, 26507214, 32590267, 41380093, 23110625, 89214616, 92604458, 69786756, 18504197, 7011964, 33237508, 16971929, 78766358, 16424199, 81774825, 7517032, 16684829, 23194618, 95726235, 94711842, 74441448, 20002147, 62762857, 33565483, 50567636, 17081350, 76330843, 46870723, 5832946, 32151165, 10597197, 34432810, 99971982, 67793644, 91255408, 22721500, 90090964, 56515456, 5111370, 57020481, 526217, 44481640, 10366309, 48673079, 4095116, 97281847, 94090109, 58208470, 3509435, 44348328, 98462867, 17068582, 75153252, 66611759, 93270084, 28796059, 33895336, 60430369, 57248122, 55602660, 3183975, 81805959, 60106217, 61271144, 88444207, 26397786, 49236559, 28734791, 15075176, 15948937, 32159704, 6525948, 7919588, 19898053, 21993752, 30891921, 8807940, 64087743, 99604946, 64602895, 62552524, 9575954, 3235882, 63059024, 62051033, 21289531, 89804152, 98653983, 42199455, 36685023, 48260151, 13468268, 84293052, 4798568, 61815223, 19101477, 73124510, 75986488, 85116755, 7685448, 51047803, 54868730, 12303248, 22766820, 81172706, 7646095, 6505939, 55960386, 86118021, 99297164, 22113133, 5970607, 52204879, 74357852, 96726697, 31126490, 88047921, 4091162, 20122224, 19272365, 68204242, 45919976, 16567550, 78300864, 5822202, 54606384, 37166608, 68939068, 45848907, 17727650, 82779622, 21397057, 96318094, 6871053, 45995325, 94595668, 669105, 69697787, 62452034, 65017137, 9886593, 61141874, 29065964, 16387575, 83368048, 79191827, 92530431, 17764950, 75543508, 80316608, 45790169, 45667668, 44473167, 29510992, 84684495, 17894977, 76315420, 8099994, 88251446, 28928175, 48395186, 66667729, 30998561, 2208785, 20568363, 47792865, 83150534, 36933038, 57393458, 30366150, 13862149, 37280276, 22200378, 93515664, 37957788, 32250045, 90061527, 68128438, 15163258, 67227442, 65715134, 65338021, 70596786, 78884452, 89499542, 85695762, 68110330, 30395570, 54263880, 61373987, 31733363, 79738755, 37224844, 48360198, 88130087, 98648327, 77377183, 47213183, 68875490, 95395112, 42644903, 33123618, 29959549, 93933709, 58180653, 30543215, 16019925, 874791, 84904436, 91957544, 37560164, 92692283, 72738685, 45428665, 9175338, 71300104, 42073124, 33249630, 43322743, 77694700, 74724075, 44889423, 71522968, 51464002, 41245325, 25636669, 59329511, 95957797, 29834512, 27041967, 14045383, 97379790, 61859143, 30811010, 72732205, 79806380, 49271185, 92033260, 14363867, 56153345, 50668599, 72238278, 29994197, 40781854, 14731700, 67474219, 67030811, 77284619, 62496012, 70420215, 57658654, 74452589, 11581181, 26744917, 57803235, 6986898, 40534591, 16380211, 168541, 53888755, 54987042, 4985896, 11365791, 61960400, 15148031, 26664538, 9599614, 93057697, 36139942, 92803766, 40677414, 13470059, 51588015, 92787493, 9058407, 70800879, 66137019, 59371804, 93053405, 68041839, 97561745, 30139692, 80251430, 26863229, 86001008, 3233084, 83133790, 72274002, 87160386, 58749, 11923835, 62357986, 7104732, 3088684, 26776922, 96735716, 42782652, 82427263, 79922758, 91802888, 78602717, 78549759, 33628349, 8651647, 66903004, 59981773, 42947632, 91990218, 70036438, 6497830, 6157724, 53802686, 62430984, 84166196, 98130363, 1197320, 24591705, 61712234, 15015906, 16595436, 32699744, 82178706, 62232211, 33061250, 3773993, 20867149, 87598888, 93790285, 59197747, 67031644, 22108665, 69255765, 7423788, 39801351, 38341669, 31161687, 73168565, 61728685, 29932657, 60581278, 77301523, 33553959, 37183543, 42967683, 86543538, 94360702, 34698463, 63506281, 51507425, 44847298, 36845587, 62936963, 37739481, 35192533, 30463802, 48892201, 83948335, 49597667, 39847321, 92998591, 5073754, 29029316, 44245960, 37620363, 63152504, 99965001, 4508700, 41442762, 89811711, 77787724, 41481685, 68316156, 71920426, 24314885, 66428795, 76960303, 44479073, 87720882, 29401781, 9257405, 85571389, 83302115, 99524975, 43152977, 55247455, 20645197, 86242799, 4668450, 6819644, 56424103, 96193415, 9603598, 36930650, 77187825, 24168411, 8128637, 36396314, 82726008, 22129328, 4064751, 7502255, 40152546, 61897582, 14220886, 2917920, 62693428, 44627776, 36812683, 91281584, 18833224, 43506672, 74614639, 71083565, 39553046, 82339363, 24733232, 44842615, 23848565, 69136837, 83210802, 90310261, 76825057, 16405341, 14947650, 8634541, 13173644, 69579137, 47361209, 21533347, 19939935, 25842078, 71965942, 90013093, 43357947, 84840549, 61623915, 53084256, 10961421, 58224549, 36312813, 66045587, 4515343, 96420429, 7229550, 23569917, 9259676, 26392416, 749283, 44915235, 18466635, 34667286, 97011160, 94539824, 89996536, 69848388, 20681921, 53547802, 73392814, 69605283, 20486294, 35456853, 53648154, 22879907, 22942635, 70004753, 83789391, 40027975, 8729146, 64157906, 10649306, 13819358, 92554120, 62803858, 55850790, 17016156, 82979980, 43933006, 7300047, 4204661, 40197395, 11543098, 52293995, 85117092, 79880247, 30653863, 72614359, 78909561, 1239555, 38365584, 15535065, 29188588, 6793819, 16099750, 18411915, 99690194, 112651, 42692881, 12348118, 36808486, 79136082, 8791066, 71316369, 29466406, 55143724, 85711894, 82651278, 37659250, 33935899, 50188404, 8733068, 1375023, 24953498, 3294781, 20140249, 59501487, 99226875, 66663942, 77072625, 38256849, 17037369, 67513640, 39986008, 37192445, 45381876, 26292919, 77413012, 84127901, 44060493, 29819940, 46540998, 65851721, 92353856, 8696647, 68694897, 89637706, 44846932, 77300457, 43501211, 54517921, 45075471, 91240048, 35512853, 45407418, 20642888, 26102057, 79942022, 35996293, 91727510, 22435353, 33431960, 17539625, 21001913, 35092039, 90457870, 68102437, 99658235, 63372756, 65271999, 8055981, 9623492, 45237957, 44664587, 98943869, 75229462, 95010552, 70782102, 1788101, 9829782, 36189527, 17976208, 34493392, 30764367, 38008118, 55770687, 38658347, 44844121, 21673260, 12416768, 44177011, 89046466, 176257, 75135448, 19486173, 5482538, 78589145, 30090481, 92071990, 61263068, 89217461, 6111563, 3233569, 26275734, 66322959, 73617245, 55615885, 65081429, 2331773, 80555751, 36505482, 92541302, 34295794, 9188443, 59177718, 63967300, 66885828, 47708850, 93562257, 73109997, 6808825, 70541760, 51426311, 58751351, 39373729, 18131876, 24915585, 40781449, 4119747, 54232247, 69355476, 86798033, 63015256, 39201414, 73786944, 47298834, 64055960, 1431742, 98948034, 30218878, 97783876, 55669657, 86022504, 87710366, 36780454, 83269727, 89699445, 62428472, 56955985, 89078848, 17385531, 99224392, 47738185, 71657078, 94076128, 97057187, 72358170, 99125126, 19457589, 32161669, 61859581, 19376156, 23432750, 72725103, 76540481, 97940276, 2891150, 26493119, 78218845, 13231279, 88698958, 27185644, 16445503, 51715482, 93359396, 72019362, 26998766, 78785507, 54762643, 68702632, 79657802, 53842979, 508198, 30694952, 51315460, 75052463, 81274566, 89419466, 14093520, 14326617, 67084351, 64848072, 67451935, 9860195, 27325428, 59614746, 14723410, 45794415, 21070110, 40984766, 86460488, 15064655, 24826575, 13348726, 86301513, 5640302, 20765474, 1022677, 82052050, 66271566, 51149804, 42307449, 99729357, 84002370, 46851987, 62740044, 7955293, 29635537, 98371444, 18663507, 83747892, 56473732, 34044787, 43376279, 91664334, 77898274, 90004325, 28851716, 7687278, 65047700, 4069912, 40356877, 12024238, 56484900, 66319530, 61982238, 11757872, 33201905, 31727379, 45996863, 96709982, 1250437, 14349098, 48774913, 59318837, 73031054, 72278539, 44550764, 82897371, 45049308, 22405120, 83533741, 27411561, 4434662, 33797252, 49598724, 33336148, 57241521, 19891772, 75820087, 69641513, 96852321, 38510840, 22450468, 73235980, 38494874, 95290172, 36468541, 42237907, 84406788, 63628376, 51472275, 4978543, 11161731, 72495719, 81677380, 22997281, 15902805, 61928316, 47887470, 48088883, 57163802, 71469330, 23740167, 7066775, 47090124, 2607799, 7182642, 36135, 18806856, 73021291, 72357096, 41092102, 99333375, 53632373, 57240218, 17386996, 30771409, 54058788, 10453030, 9398733, 88904910, 36753250, 32274392, 5267545, 38645117, 90158785, 4806458, 18001617, 90272749, 57961282, 33304202, 3487592, 91141395, 34698428, 62115552, 26114953, 91831487, 48675329, 4199704, 65454636, 51590803, 3633375, 81898046, 38009615, 38061439, 30787683, 70727211, 12571310, 30569392, 38022834, 52060076, 90654836, 92692978, 82532312, 10264691, 51466049, 37435892, 247198, 6521313, 8115266, 17957593, 3880712, 9860968, 10358899, 17365188, 92867155, 2204165, 49641577, 27625735, 60393039, 31491938, 83083131, 79322415, 2717150, 92215320, 65880522, 16097038, 55189057, 76671482, 76703609, 61741594, 415901, 84187166, 64098930, 62490109, 72793444, 60955663 +36312813, 95251277, 66250369, 80014588, 4099191, 22113133, 4119747, 95726235, 32151165, 10366309, 76315420, 89499542, 33061250, 61373987, 37675718, 51149804, 67281495, 43376279, 76960303, 824872, 68939068, 26292919, 57803235, 6871053, 56515456, 29065964, 65038678, 91727510, 49882705, 75128745, 68702632, 15948937, 6153406, 89046466, 55753905, 64602895, 6111563, 26063929, 2331773, 26507214, 30463802, 73124510, 74110882, 65074535, 20002147, 99515901, 71657078, 247198, 168541, 30163921, 669105, 45075471, 44481640, 60176618, 83651211, 22405120, 79942022, 75543508, 68102437, 54199160, 42947632, 75229462, 83150534, 26397786, 28734791, 14626618, 54663246, 70596786, 99604946, 30395570, 31733363, 98648327, 39801351, 55850790, 23134715, 16097038, 37739481, 7685448, 27187213, 29029316, 37620363, 51464002, 18504197, 68644627, 59329511, 27041967, 49641577, 55247455, 40781854, 59501487, 34497327, 12664567, 53632373, 52261574, 94595668, 67793644, 11365791, 44846932, 88904910, 45049308, 43506672, 61960400, 92530431, 9599614, 59109400, 80316608, 8373289, 69579137, 19939935, 35092039, 93359396, 87160386, 6221471, 58224549, 73235980, 93270084, 66667729, 95581843, 75052463, 8651647, 47792865, 55470718, 21919959, 88444207, 17365188, 32159704, 65715134, 3633375, 65338021, 80246713, 1569515, 48360198, 78589145, 62552524, 75407347, 98739783, 38341669, 29932657, 60581278, 39171895, 42199455, 16019925, 13468268, 79880247, 84293052, 72614359, 36505482, 77620120, 15535065, 33249630, 12303248, 36808486, 55960386, 33237508, 5970607, 78766358, 85711894, 32058615, 1204161, 29994197, 94711842, 73021291, 99226875, 77187825, 11581181, 31727379, 45848907, 46870723, 4064751, 48774913, 5832946, 66832478, 86821229, 74743862, 56531125, 321665, 89637706, 94911072, 9886593, 16387575, 54517921, 15148031, 32161669, 5267545, 94516935, 66137019, 26139110, 90013093, 8099994, 16445503, 40865610, 48395186, 7104732, 44664587, 28796059, 30998561, 96735716, 82427263, 85023028, 61271144, 26392416, 37280276, 36189527, 44915235, 4978543, 51590803, 77272628, 15163258, 7919588, 61712234, 53393358, 73392814, 78884452, 69605283, 21070110, 40984766, 176257, 93790285, 37224844, 65880522, 76434144, 47213183, 30569392, 75777973, 61728685, 82886381, 29959549, 93933709, 94360702, 7300047, 48088883, 26275734, 89804152, 85117092, 84002370, 62740044, 36845587, 35192533, 19101477, 48892201, 1239555, 72738685, 9175338, 51047803, 71300104, 89214616, 70372191, 42692881, 81172706, 18783702, 7011964, 41442762, 16971929, 52204879, 31126490, 7517032, 52060076, 37659250, 82532312, 18699206, 7687278, 72777973, 83302115, 12024238, 24953498, 20645197, 4668450, 6819644, 98948034, 5822202, 36930650, 33201905, 17037369, 37192445, 15536795, 50702367, 17081350, 96709982, 17385531, 1250437, 40534591, 96318094, 99971982, 65851721, 73031054, 22721500, 2717150, 82897371, 65017137, 526217, 43501211, 31904591, 84349107, 69136837, 90158785, 23432750, 4787945, 35512853, 48673079, 85242963, 30139692, 80251430, 47361209, 21533347, 57961282, 3509435, 69641513, 96852321, 25842078, 83133790, 17894977, 72019362, 88251446, 99658235, 3487592, 74137926, 30694952, 51315460, 91831487, 60106217, 36933038, 1788101, 36580610, 91990218, 93515664, 59405277, 53802686, 62430984, 84166196, 34236719, 6525948, 17857111, 20836893, 32699744, 62232211, 44844121, 12416768, 24619760, 54263880, 70004753, 87598888, 8729146, 76170907, 12571310, 52613508, 69255765, 95395112, 92554120, 20765474, 62051033, 62803858, 17016156, 77312810, 86543538, 43933006, 98653983, 40197395, 34698463, 48260151, 54427233, 73617245, 44847298, 61741594, 91957544, 38365584, 37560164, 75986488, 57163802, 98371444, 63967300, 22766820, 77694700, 74724075, 71469330, 41245325, 7066775, 99965001, 56461322, 47090124, 77787724, 91664334, 55143724, 16054533, 57359924, 24915585, 54232247, 91938191, 19272365, 44479073, 99021067, 39201414, 73786944, 47298834, 99524975, 83083131, 14731700, 77072625, 9603598, 74452589, 54606384, 99333375, 50567636, 67513640, 8128637, 77413012, 40686254, 82726008, 79821897, 40152546, 34432810, 50806615, 53888755, 54987042, 4985896, 90090964, 69697787, 68694897, 62693428, 36812683, 19457589, 18833224, 71083565, 93566986, 26664538, 82327024, 23848565, 83210802, 76825057, 8115266, 99549373, 45407418, 97940276, 2891150, 49598724, 45617087, 97281847, 90272749, 44473167, 33431960, 81855445, 84684495, 98462867, 71965942, 21001913, 72274002, 84840549, 78785507, 75153252, 53084256, 88653118, 62357986, 28787861, 66045587, 49328608, 4515343, 57248122, 79922758, 3183975, 81805959, 33628349, 66903004, 70782102, 57393458, 84406788, 10358899, 9829782, 4199704, 6497830, 67451935, 17976208, 6157724, 92398073, 72495719, 22997281, 59614746, 85695762, 20486294, 81898046, 83789391, 40027975, 75135448, 5482538, 44784505, 92071990, 66116458, 8129978, 68875490, 10649306, 13819358, 31161687, 92867155, 73168565, 1022677, 569864, 60102965, 82052050, 52293995, 76703609, 51507425, 46851987, 4798568, 62936963, 83948335, 41380093, 92604458, 96906410, 54868730, 42073124, 66885828, 5073754, 44889423, 44245960, 47708850, 73109997, 79136082, 23740167, 86118021, 33699435, 17146629, 58751351, 39373729, 34044787, 71316369, 29834512, 18131876, 2607799, 88047921, 59910624, 81774825, 77898274, 4091162, 40781449, 90654836, 28851716, 72732205, 69355476, 86798033, 66428795, 63015256, 4069912, 74441448, 1431742, 66319530, 77284619, 62496012, 56424103, 11757872, 62762857, 38256849, 41092102, 37166608, 33565483, 56955985, 57240218, 17386996, 34946859, 45995325, 44983451, 14220886, 48893685, 9398733, 45306070, 67124282, 99125126, 36753250, 32274392, 24733232, 44842615, 93057697, 36139942, 90310261, 13470059, 4806458, 92787493, 17764950, 16405341, 9058407, 76540481, 70800879, 93053405, 4434662, 68041839, 26493119, 29516592, 94090109, 13173644, 26863229, 33304202, 90457870, 28928175, 28550822, 58749, 91141395, 34698428, 66611759, 54762643, 53842979, 33895336, 60430369, 55602660, 6038457, 78602717, 78549759, 38494874, 1936762, 81274566, 68816413, 59981773, 14326617, 67084351, 30366150, 13862149, 63628376, 49236559, 749283, 70036438, 15075176, 20885148, 32250045, 14723410, 69848388, 26734892, 67227442, 45794415, 19898053, 55770687, 38658347, 35456853, 21673260, 44177011, 59197747, 88130087, 13348726, 68000591, 22108665, 3235882, 86301513, 7423788, 65275241, 77301523, 89217461, 82979980, 76671482, 8913721, 84904436, 30653863, 78909561, 49597667, 23110625, 92541302, 39847321, 6793819, 85116755, 9188443, 18411915, 99690194, 59177718, 32426752, 112651, 7646095, 6808825, 51426311, 4508700, 38022834, 29466406, 74357852, 96726697, 20122224, 30811010, 71920426, 27665211, 92033260, 14363867, 9257405, 16567550, 8733068, 1375023, 64055960, 78300864, 67474219, 20140249, 57658654, 55669657, 45381876, 89078848, 6986898, 14349098, 21397057, 7502255, 10453030, 59318837, 10597197, 97057187, 44550764, 92353856, 2917920, 72358170, 61141874, 57020481, 79191827, 99917599, 68824981, 19376156, 40677414, 38645117, 41092172, 4095116, 26102057, 14947650, 59371804, 22435353, 8634541, 58208470, 92215320, 75820087, 86001008, 17068582, 26998766, 45237957, 42782652, 91802888, 62115552, 23569917, 20568363, 9860968, 95290172, 54014062, 22200378, 9860195, 18466635, 27325428, 97011160, 81677380, 30764367, 98130363, 1197320, 24591705, 15015906, 20681921, 24058273, 21993752, 22942635, 30891921, 62490109, 86460488, 38061439, 3773993, 30787683, 99861373, 79738755, 19486173, 64157906, 77377183, 45990383, 63059024, 42644903, 47887470, 61263068, 42967683, 4204661, 53666583, 17058722, 58180653, 30543215, 63506281, 55615885, 65081429, 80555751, 61815223, 92692283, 34295794, 45428665, 29188588, 29635537, 16099750, 69786756, 92998591, 37501808, 93562257, 6505939, 83747892, 99297164, 71333116, 14045383, 97379790, 16424199, 68316156, 90004325, 33935899, 65047700, 50188404, 50668599, 68204242, 29401781, 40356877, 51466049, 18806856, 60393039, 43152977, 56484900, 3294781, 72357096, 66663942, 61982238, 79322415, 89699445, 84187166, 26744917, 83155442, 22129328, 82779622, 46540998, 16380211, 97641116, 8696647, 5111370, 62452034, 77300457, 74614639, 39553046, 82339363, 61859581, 91240048, 72725103, 35996293, 45790169, 33797252, 18001617, 97561745, 57241521, 78218845, 44348328, 88698958, 27185644, 43357947, 63372756, 65271999, 9623492, 3088684, 79657802, 508198, 2208785, 26114953, 14093520, 47893286, 37957788, 10309525, 34667286, 34493392, 89996536, 38008118, 16595436, 73222868, 15902805, 70727211, 15064655, 30090481, 61928316, 78561158, 55189057, 66271566, 42307449, 874791, 7955293, 415901, 43322743, 12348118, 18663507, 63152504, 50007421, 25636669, 8791066, 36135, 82651278, 61859143, 49271185, 16684829, 56153345, 23194618, 72373496, 37435892, 86242799, 67030811, 30218878, 97783876, 24168411, 87710366, 36780454, 62428472, 76330843, 99224392, 44060493, 30771409, 37891451, 94076128, 61897582, 91281584, 83368048, 29510992, 13231279, 3233084, 38510840, 22450468, 61623915, 10961421, 11923835, 8055981, 26776922, 9259676, 98943869, 64848072, 51472275, 48675329, 68128438, 81853704, 53648154, 22879907, 38009615, 64087743, 67031644, 9575954, 32590267, 48658605, 70367851, 70541760, 41481685, 92692978, 24314885, 72238278, 85571389, 45919976, 10264691, 96193415, 86022504, 84127901, 17727650, 54058788, 91255408, 6521313, 27411561, 33336148, 45667668, 19891772, 96420429, 7229550, 95010552, 42237907, 65454636, 11161731, 94539824, 53547802, 68110330, 20867149, 24826575, 43045786, 21289531, 33553959, 3233569, 72793444, 11543098, 66322959, 56473732, 7182642, 27625735, 79806380, 87720882, 60955663, 70420215, 83269727, 39986008, 29819940, 72278539, 44627776, 92803766, 17539625, 3880712, 89419466, 36468541, 90061527, 8807940, 5640302, 33123618, 37183543, 71522968, 2204165, 89811711, 31491938, 45996863, 47738185, 51588015, 20642888, 17957593, 64098930, 82178706, 36685023, 95957797, 99729357, 36396314, 51715482, 83533741 +27411561, 22113133, 97379790, 44915235, 4978543, 53547802, 55753905, 92554120, 16097038, 89804152, 41245325, 55143724, 99971982, 65851721, 26664538, 92787493, 17068582, 35456853, 7955293, 36808486, 66663942, 6986898, 17385531, 82897371, 71083565, 38645117, 33797252, 38510840, 73235980, 21919959, 82886381, 21289531, 37675718, 55615885, 41380093, 72738685, 32426752, 71469330, 39373729, 89811711, 7182642, 52060076, 40781449, 66428795, 50668599, 72777973, 77072625, 86022504, 89699445, 26744917, 30771409, 71657078, 69697787, 29065964, 54517921, 2891150, 45617087, 94090109, 47361209, 3509435, 96852321, 26863229, 19939935, 3487592, 45237957, 93270084, 33895336, 91990218, 30366150, 54014062, 51472275, 15075176, 8807940, 40984766, 30395570, 30787683, 75135448, 30090481, 78561158, 68875490, 61263068, 99729357, 91957544, 92692283, 6808825, 58751351, 34044787, 59329511, 96726697, 4091162, 4069912, 18806856, 91255408, 22721500, 6521313, 89637706, 45075471, 83368048, 93566986, 22405120, 4095116, 26493119, 18001617, 8634541, 44348328, 51715482, 99658235, 63372756, 58224549, 96420429, 78602717, 66903004, 9860968, 93515664, 6157724, 38008118, 81853704, 33061250, 64157906, 22108665, 61928316, 62552524, 66116458, 7423788, 82979980, 93933709, 7300047, 60102965, 63506281, 79880247, 32590267, 37560164, 34295794, 85116755, 77694700, 51464002, 7066775, 4099191, 4508700, 49641577, 32058615, 77898274, 90004325, 71920426, 24314885, 19272365, 63015256, 76960303, 72238278, 9257405, 73786944, 24953498, 74441448, 14731700, 98948034, 62496012, 56424103, 79322415, 41092102, 17037369, 56955985, 84127901, 57240218, 17727650, 59318837, 94076128, 50806615, 30163921, 66832478, 14220886, 62452034, 61960400, 92530431, 9599614, 83210802, 17764950, 35996293, 14947650, 97561745, 90272749, 29510992, 58208470, 25842078, 90457870, 93359396, 87160386, 72019362, 84840549, 40865610, 11923835, 66667729, 3183975, 51315460, 98943869, 95290172, 36933038, 88444207, 37280276, 22200378, 749283, 48675329, 67451935, 59405277, 10309525, 53802686, 62430984, 67227442, 24058273, 78884452, 38658347, 82178706, 176257, 93790285, 64602895, 5482538, 68000591, 69255765, 63059024, 60581278, 55189057, 40197395, 11543098, 51149804, 73617245, 30653863, 65081429, 62740044, 72614359, 44847298, 36505482, 19101477, 39847321, 16099750, 7685448, 99690194, 70372191, 12348118, 29029316, 44889423, 81172706, 37501808, 6505939, 83747892, 18783702, 50007421, 56461322, 25636669, 41442762, 5970607, 29466406, 18131876, 91664334, 14045383, 28851716, 27625735, 49271185, 92033260, 14363867, 10264691, 51466049, 60393039, 56484900, 60955663, 77284619, 59501487, 70420215, 97783876, 55669657, 24168411, 87710366, 83269727, 84187166, 39986008, 45381876, 77413012, 99515901, 4064751, 54058788, 10453030, 53888755, 44983451, 90090964, 2917920, 5111370, 56531125, 65017137, 88904910, 84349107, 76825057, 4787945, 4806458, 59371804, 68041839, 45667668, 97281847, 8373289, 13231279, 69641513, 84684495, 72274002, 76315420, 58749, 54762643, 7104732, 3088684, 66250369, 36312813, 95581843, 26776922, 96735716, 42782652, 55602660, 23569917, 81274566, 89419466, 75229462, 37957788, 90061527, 94539824, 32159704, 30764367, 20836893, 24591705, 65715134, 3633375, 45794415, 55770687, 73222868, 62232211, 21673260, 24826575, 65880522, 44784505, 92071990, 75407347, 8129978, 13819358, 42967683, 6111563, 94360702, 48088883, 53666583, 98653983, 36685023, 13468268, 54427233, 80014588, 415901, 83948335, 38365584, 92541302, 15535065, 45428665, 57163802, 71300104, 89214616, 92604458, 42073124, 12303248, 27187213, 73109997, 55960386, 99965001, 8791066, 33237508, 78766358, 31126490, 61859143, 57359924, 74110882, 99021067, 72373496, 83302115, 37435892, 64055960, 40781854, 67474219, 72357096, 96193415, 11581181, 31727379, 37192445, 12664567, 82726008, 47738185, 29819940, 34946859, 45995325, 247198, 94595668, 61897582, 669105, 72278539, 44550764, 48893685, 62693428, 72358170, 61141874, 36753250, 526217, 77300457, 18833224, 82339363, 82327024, 10366309, 36139942, 40677414, 8115266, 23432750, 41092172, 48673079, 83533741, 79942022, 91727510, 33336148, 44473167, 92215320, 57961282, 86001008, 27185644, 33304202, 17894977, 49882705, 28928175, 28550822, 10961421, 91141395, 28787861, 2208785, 91802888, 62115552, 74137926, 9259676, 26392416, 67084351, 36580610, 49236559, 64848072, 47893286, 4199704, 17976208, 9860195, 15948937, 17365188, 15163258, 34236719, 6525948, 14723410, 26734892, 32699744, 65338021, 6153406, 53393358, 19898053, 73392814, 22942635, 38009615, 24619760, 20867149, 70727211, 99861373, 12571310, 88130087, 76434144, 45990383, 3235882, 86301513, 42644903, 65275241, 92867155, 61728685, 55850790, 17016156, 569864, 4204661, 58180653, 30543215, 76671482, 42307449, 48260151, 26063929, 16019925, 66322959, 26507214, 36845587, 23110625, 75986488, 29188588, 29635537, 69786756, 63967300, 43322743, 42692881, 5073754, 74724075, 70367851, 2204165, 63152504, 70541760, 47090124, 99297164, 77787724, 16054533, 16424199, 7517032, 82651278, 20122224, 72732205, 37659250, 92692978, 18699206, 68204242, 29994197, 39201414, 31491938, 1431742, 78300864, 20002147, 73021291, 3294781, 11757872, 62762857, 74452589, 36780454, 33201905, 67513640, 45848907, 36396314, 1250437, 99224392, 44060493, 79821897, 6871053, 40152546, 97057187, 97641116, 4985896, 74743862, 68694897, 321665, 94911072, 99125126, 45049308, 16387575, 65038678, 79191827, 31904591, 61859581, 44842615, 69136837, 83651211, 45407418, 9058407, 22435353, 45790169, 93053405, 29516592, 13173644, 88698958, 17957593, 43357947, 22450468, 65271999, 66611759, 88653118, 44664587, 508198, 79922758, 6038457, 30694952, 20568363, 1936762, 68816413, 60106217, 42947632, 47792865, 14326617, 26397786, 57393458, 84406788, 70036438, 6497830, 11161731, 92398073, 34667286, 77272628, 34493392, 64098930, 98130363, 17857111, 20681921, 69605283, 81898046, 62490109, 68110330, 86460488, 3773993, 54263880, 1569515, 70004753, 15064655, 8729146, 76170907, 59197747, 48360198, 78589145, 9575954, 98739783, 31161687, 62803858, 29932657, 89217461, 37183543, 52293995, 8913721, 84293052, 4798568, 80555751, 35192533, 49597667, 77620120, 9188443, 18411915, 98371444, 96906410, 54868730, 59177718, 22766820, 47708850, 7646095, 79136082, 86118021, 71316369, 68644627, 71333116, 16971929, 52204879, 24915585, 30811010, 65047700, 87720882, 23194618, 40356877, 8733068, 67030811, 30218878, 20140249, 824872, 5822202, 34497327, 36930650, 38256849, 37166608, 68939068, 50567636, 45996863, 8128637, 17386996, 83155442, 76330843, 22129328, 14349098, 21397057, 7502255, 96318094, 37891451, 10597197, 54987042, 67124282, 56515456, 44627776, 36812683, 68824981, 44481640, 32161669, 23848565, 92803766, 13470059, 51588015, 20642888, 76540481, 4434662, 57241521, 33431960, 17539625, 75820087, 71965942, 90013093, 16445503, 78785507, 68102437, 75128745, 34698428, 6221471, 54199160, 3880712, 79657802, 66045587, 38494874, 8651647, 14093520, 95010552, 36468541, 42237907, 13862149, 63628376, 22997281, 68128438, 89996536, 59614746, 84166196, 54663246, 69848388, 1197320, 16595436, 21993752, 21070110, 53648154, 22879907, 80246713, 15902805, 12416768, 44177011, 61373987, 87598888, 40027975, 13348726, 30569392, 5640302, 10649306, 39801351, 95395112, 20765474, 38341669, 62051033, 75777973, 29959549, 1022677, 77312810, 33553959, 23134715, 43933006, 3233569, 17058722, 66271566, 85117092, 76703609, 51507425, 874791, 84904436, 84002370, 78909561, 37739481, 61815223, 73124510, 6793819, 92998591, 71522968, 18663507, 37620363, 56473732, 33699435, 17146629, 38022834, 74357852, 81774825, 68316156, 27665211, 33935899, 1204161, 86798033, 44479073, 50188404, 45919976, 95726235, 94711842, 12024238, 55247455, 83083131, 86242799, 4668450, 66319530, 99226875, 57658654, 77187825, 33565483, 15536795, 17081350, 46870723, 46540998, 40534591, 67793644, 168541, 86821229, 2717150, 8696647, 9886593, 91281584, 95251277, 43506672, 43501211, 74614639, 39553046, 15148031, 59109400, 60176618, 90158785, 99549373, 97940276, 26139110, 98462867, 21001913, 26998766, 61623915, 75153252, 53084256, 48395186, 8055981, 9623492, 68702632, 53842979, 30998561, 7229550, 91831487, 83150534, 61271144, 1788101, 10358899, 9829782, 36189527, 28734791, 20885148, 72495719, 97011160, 7919588, 89499542, 85695762, 30891921, 44844121, 79738755, 37224844, 98648327, 47887470, 39171895, 72793444, 42199455, 2331773, 48892201, 48658605, 112651, 23740167, 51426311, 95957797, 29834512, 27041967, 43376279, 88047921, 59910624, 90654836, 54232247, 69355476, 79806380, 65074535, 16684829, 29401781, 47298834, 99524975, 6819644, 61982238, 99333375, 26292919, 50702367, 40686254, 96709982, 52261574, 48774913, 82779622, 5832946, 16380211, 34432810, 73031054, 92353856, 19457589, 32274392, 93057697, 5267545, 19376156, 91240048, 90310261, 72725103, 16405341, 26102057, 85242963, 94516935, 30139692, 80251430, 78218845, 19891772, 69579137, 21533347, 35092039, 88251446, 62357986, 28796059, 4515343, 60430369, 57248122, 81805959, 26114953, 33628349, 85023028, 70782102, 18466635, 32250045, 27325428, 14626618, 61712234, 15015906, 99604946, 19486173, 52613508, 47213183, 43045786, 33123618, 86543538, 82052050, 34698463, 46851987, 61741594, 9175338, 51047803, 33249630, 66885828, 44245960, 18504197, 67281495, 2607799, 36135, 7687278, 56153345, 85571389, 1375023, 43152977, 20645197, 9603598, 62428472, 53632373, 57803235, 11365791, 9398733, 45306070, 44846932, 99917599, 24733232, 35512853, 66137019, 75543508, 3233084, 49328608, 82427263, 75052463, 55470718, 20486294, 64087743, 38061439, 89046466, 83789391, 31733363, 67031644, 77377183, 73168565, 77301523, 26275734, 1239555, 93562257, 85711894, 4119747, 91938191, 16567550, 54606384, 89078848, 32151165, 57020481, 81855445, 83133790, 59981773, 65454636, 70596786, 62936963, 7011964, 82532312, 8099994, 78549759, 41481685, 70800879, 80316608, 51590803, 81677380, 30463802, 49598724 +65338021, 25842078, 26392416, 17976208, 16097038, 84187166, 65851721, 68694897, 27185644, 93933709, 45381876, 89078848, 33304202, 9259676, 42237907, 63628376, 33061250, 87598888, 61263068, 51149804, 18504197, 99965001, 79806380, 24314885, 18806856, 70420215, 77072625, 46540998, 4985896, 82897371, 45049308, 93566986, 3509435, 19939935, 7104732, 73235980, 1936762, 91831487, 20885148, 34667286, 38008118, 78884452, 44844121, 75135448, 30090481, 26275734, 66271566, 54427233, 38365584, 85116755, 59177718, 18663507, 7011964, 33699435, 43376279, 27625735, 14363867, 51466049, 99226875, 15536795, 10366309, 19376156, 99549373, 75543508, 49598724, 88698958, 3233084, 49882705, 53084256, 91141395, 48395186, 54762643, 68702632, 54199160, 30998561, 82427263, 2208785, 79922758, 7229550, 81805959, 26397786, 36580610, 64848072, 70036438, 14626618, 24591705, 15902805, 12416768, 8729146, 12571310, 5482538, 95395112, 86543538, 82052050, 42307449, 44847298, 32590267, 92541302, 70367851, 37620363, 73109997, 51426311, 4508700, 99297164, 40781449, 30811010, 28851716, 69355476, 27665211, 92033260, 63015256, 56153345, 60955663, 4668450, 96193415, 62762857, 55669657, 36780454, 12664567, 26292919, 6986898, 17385531, 32151165, 247198, 99971982, 321665, 89637706, 88904910, 36812683, 43506672, 44842615, 90310261, 76825057, 45617087, 97561745, 63372756, 93270084, 8651647, 4978543, 90061527, 72495719, 22997281, 89996536, 64098930, 73392814, 69605283, 21993752, 40984766, 68110330, 1569515, 31733363, 65880522, 62552524, 44784505, 30569392, 66116458, 5640302, 92867155, 60581278, 17016156, 569864, 82979980, 3233569, 76671482, 85117092, 76703609, 51507425, 79880247, 62740044, 61815223, 83948335, 34295794, 69786756, 63967300, 74724075, 2204165, 37501808, 83747892, 71316369, 68644627, 38022834, 71333116, 27041967, 20122224, 4119747, 49271185, 1204161, 10264691, 16567550, 95726235, 12024238, 31491938, 43152977, 37435892, 14731700, 98948034, 9603598, 54606384, 31727379, 68939068, 50567636, 57803235, 17386996, 82726008, 76330843, 14349098, 40534591, 10453030, 67793644, 669105, 65017137, 45075471, 82339363, 32161669, 93057697, 83210802, 13470059, 35512853, 51588015, 9058407, 94516935, 97940276, 2891150, 33797252, 29516592, 57241521, 47361209, 81855445, 44348328, 98462867, 26863229, 21001913, 51715482, 22450468, 26998766, 78785507, 10961421, 62357986, 8055981, 9623492, 28787861, 66045587, 508198, 62115552, 78602717, 78549759, 20568363, 75052463, 66903004, 59981773, 42947632, 14326617, 83150534, 36933038, 91990218, 47893286, 6157724, 65454636, 53802686, 97011160, 94539824, 15015906, 20681921, 80246713, 86460488, 64087743, 54263880, 59197747, 64602895, 67031644, 98648327, 22108665, 9575954, 78561158, 7423788, 10649306, 42644903, 47887470, 29959549, 77312810, 94360702, 60102965, 72793444, 58180653, 30543215, 36685023, 11543098, 99729357, 80014588, 73617245, 2331773, 4798568, 72614359, 36845587, 62936963, 7955293, 19101477, 92692283, 9175338, 92998591, 43322743, 93562257, 51464002, 6808825, 56473732, 47090124, 17146629, 22113133, 59329511, 95957797, 52204879, 18131876, 78766358, 31126490, 16054533, 41481685, 36135, 7517032, 57359924, 90004325, 24915585, 72732205, 37659250, 92692978, 86798033, 65074535, 16684829, 9257405, 85571389, 73786944, 8733068, 60393039, 73021291, 61982238, 74452589, 24168411, 99333375, 56955985, 53632373, 22129328, 48774913, 21397057, 54058788, 59318837, 45995325, 40152546, 94076128, 34432810, 97641116, 44983451, 22721500, 2717150, 48893685, 69697787, 9398733, 56515456, 62693428, 91281584, 95251277, 18833224, 39553046, 92530431, 44481640, 5267545, 60176618, 23432750, 72725103, 17764950, 20642888, 4095116, 14947650, 33336148, 8373289, 33431960, 29510992, 21533347, 86001008, 17957593, 71965942, 35092039, 17068582, 76315420, 87160386, 28928175, 3487592, 45237957, 96735716, 57248122, 3183975, 81274566, 60106217, 89419466, 95290172, 13862149, 10358899, 9829782, 4199704, 36189527, 93515664, 51590803, 17365188, 68128438, 62430984, 32159704, 30764367, 16595436, 53393358, 85695762, 30891921, 21673260, 38061439, 79738755, 15064655, 19486173, 48360198, 61928316, 39801351, 75777973, 43933006, 7300047, 42199455, 34698463, 26063929, 874791, 65081429, 84002370, 78909561, 36505482, 30463802, 73124510, 16099750, 18411915, 92604458, 32426752, 12303248, 44245960, 71522968, 63152504, 67281495, 7066775, 4099191, 25636669, 77787724, 16971929, 55143724, 16424199, 82532312, 18699206, 91938191, 87720882, 56484900, 20645197, 64055960, 83083131, 6819644, 59501487, 5822202, 36930650, 79322415, 38256849, 87710366, 39986008, 45996863, 17081350, 99224392, 30771409, 71657078, 37891451, 94595668, 72278539, 6521313, 44550764, 74743862, 92353856, 56531125, 99125126, 9886593, 44627776, 526217, 43501211, 74614639, 54517921, 79191827, 99917599, 15148031, 23848565, 84349107, 59109400, 90158785, 4806458, 22405120, 48673079, 26102057, 91727510, 26139110, 59371804, 22435353, 4434662, 26493119, 13173644, 69579137, 58208470, 57961282, 83133790, 90013093, 8099994, 16445503, 93359396, 84840549, 34698428, 88653118, 58224549, 66250369, 28796059, 79657802, 42782652, 60430369, 55602660, 74137926, 98943869, 21919959, 70782102, 84406788, 51472275, 6497830, 11161731, 77272628, 81677380, 34493392, 70596786, 38658347, 73222868, 8807940, 3773993, 89046466, 83789391, 40027975, 24826575, 64157906, 77377183, 45990383, 86301513, 8129978, 68875490, 62051033, 62803858, 61728685, 33123618, 89217461, 6111563, 39171895, 55189057, 16019925, 46851987, 80555751, 61741594, 1239555, 37560164, 41380093, 23110625, 75986488, 39847321, 48658605, 51047803, 89214616, 54868730, 99690194, 42692881, 12348118, 29029316, 81172706, 71469330, 70541760, 23740167, 39373729, 29466406, 2607799, 88047921, 7182642, 49641577, 68316156, 74110882, 44479073, 23194618, 72373496, 29994197, 39201414, 99524975, 1431742, 67474219, 30218878, 824872, 34497327, 37166608, 33201905, 83269727, 62428472, 11581181, 33565483, 67513640, 84127901, 1250437, 99515901, 4064751, 16380211, 10597197, 97057187, 53888755, 8696647, 2917920, 62452034, 29065964, 16387575, 77300457, 71083565, 32274392, 68824981, 61859581, 69136837, 16405341, 76540481, 70800879, 85242963, 80251430, 19891772, 92215320, 75820087, 17894977, 43357947, 72019362, 88251446, 66667729, 36312813, 53842979, 30694952, 33628349, 68816413, 61271144, 57393458, 30366150, 22200378, 48675329, 37957788, 9860195, 59405277, 92398073, 15948937, 32250045, 6525948, 14723410, 98130363, 1197320, 67227442, 3633375, 45794415, 55770687, 35456853, 82178706, 81898046, 55753905, 13348726, 47213183, 92071990, 69255765, 63059024, 55850790, 29932657, 48088883, 4204661, 8913721, 13468268, 66322959, 30653863, 48892201, 415901, 6793819, 57163802, 98371444, 96906410, 70372191, 22766820, 66885828, 5073754, 77694700, 27187213, 44889423, 56461322, 41442762, 58751351, 74357852, 14045383, 97379790, 32058615, 81774825, 82651278, 90654836, 76960303, 29401781, 72238278, 86242799, 40781854, 66319530, 62496012, 66663942, 11757872, 97783876, 77187825, 26744917, 37192445, 96709982, 52261574, 47738185, 44060493, 34946859, 79821897, 61897582, 54987042, 91255408, 14220886, 94911072, 44846932, 19457589, 83368048, 26664538, 36139942, 91240048, 92803766, 92787493, 41092172, 83533741, 66137019, 93053405, 68041839, 30139692, 69641513, 84684495, 72274002, 68102437, 99658235, 61623915, 75153252, 44664587, 3880712, 26776922, 91802888, 23569917, 55470718, 75229462, 95010552, 1788101, 37280276, 749283, 28734791, 10309525, 27325428, 59614746, 84166196, 69848388, 7919588, 61712234, 32699744, 6153406, 20486294, 53648154, 22879907, 62232211, 62490109, 30395570, 30787683, 20867149, 99861373, 88130087, 68000591, 3235882, 43045786, 65275241, 38341669, 31161687, 73168565, 82886381, 1022677, 42967683, 17058722, 37739481, 35192533, 91957544, 49597667, 15535065, 29635537, 9188443, 7685448, 112651, 47708850, 36808486, 86118021, 50007421, 33237508, 29834512, 91664334, 59910624, 77898274, 61859143, 52060076, 33935899, 66428795, 4069912, 99021067, 67030811, 77284619, 3294781, 20140249, 45848907, 57240218, 40686254, 83155442, 46870723, 17727650, 5832946, 7502255, 73031054, 66832478, 86821229, 90090964, 67124282, 72358170, 65038678, 61960400, 82327024, 9599614, 40677414, 38645117, 83651211, 4787945, 45407418, 45790169, 97281847, 18001617, 44473167, 78218845, 17539625, 90457870, 40865610, 75128745, 49328608, 6038457, 26114953, 36468541, 54014062, 44915235, 67451935, 15075176, 18466635, 54663246, 17857111, 20836893, 81853704, 89499542, 99604946, 24619760, 61373987, 70727211, 76170907, 78589145, 52613508, 75407347, 13819358, 92554120, 20765474, 37675718, 37183543, 89804152, 98653983, 55615885, 26507214, 77620120, 72738685, 45428665, 71300104, 33249630, 7646095, 79136082, 18783702, 8791066, 85711894, 7687278, 65047700, 50188404, 50668599, 72777973, 45919976, 83302115, 94711842, 55247455, 74441448, 78300864, 20002147, 57658654, 41092102, 8128637, 77413012, 50702367, 82779622, 96318094, 6871053, 30163921, 11365791, 5111370, 57020481, 36753250, 8115266, 79942022, 35996293, 45667668, 13231279, 11923835, 3088684, 33895336, 51315460, 38494874, 9860968, 14093520, 47792865, 88444207, 67084351, 26734892, 53547802, 76434144, 98739783, 21289531, 40197395, 52293995, 34044787, 4091162, 19272365, 47298834, 1375023, 24953498, 72357096, 86022504, 17037369, 168541, 50806615, 61141874, 27411561, 80316608, 90272749, 96852321, 38510840, 28550822, 58749, 66611759, 95581843, 4515343, 96420429, 85023028, 49236559, 34236719, 24058273, 21070110, 22942635, 44177011, 70004753, 176257, 37224844, 77301523, 33553959, 53666583, 48260151, 63506281, 84293052, 84904436, 29188588, 42073124, 41245325, 55960386, 89811711, 5970607, 54232247, 71920426, 89699445, 36396314, 45306070, 31904591, 24733232, 8634541, 94090109, 65271999, 6221471, 15163258, 65715134, 19898053, 38009615, 93790285, 56424103, 29819940, 23134715, 6505939, 40356877, 96726697, 68204242 +21993752, 82779622, 41092172, 75153252, 39801351, 80014588, 27041967, 74110882, 91281584, 28796059, 17976208, 20885148, 45990383, 75407347, 92867155, 17058722, 37560164, 37192445, 57803235, 14349098, 97057187, 45049308, 71083565, 26139110, 97561745, 25842078, 93270084, 53842979, 47893286, 77272628, 24591705, 54263880, 37224844, 29932657, 89804152, 52293995, 84293052, 59177718, 12348118, 44245960, 47708850, 70367851, 50007421, 16971929, 96726697, 61859143, 79806380, 24314885, 87720882, 23194618, 85571389, 33201905, 15536795, 36396314, 40534591, 54058788, 50806615, 61897582, 86821229, 8696647, 9398733, 32161669, 20642888, 22435353, 96852321, 3233084, 35092039, 68102437, 7104732, 3880712, 2208785, 78549759, 75052463, 60106217, 47792865, 61271144, 97011160, 94539824, 32159704, 6153406, 38658347, 73222868, 3773993, 93790285, 64602895, 22108665, 38341669, 86543538, 94360702, 8913721, 48260151, 84002370, 61815223, 38365584, 98371444, 42692881, 56473732, 71316369, 16054533, 32058615, 7517032, 65047700, 94711842, 67474219, 30218878, 6986898, 50702367, 99224392, 21397057, 34432810, 67793644, 97641116, 11365791, 62693428, 16387575, 74614639, 72725103, 4095116, 94516935, 91727510, 19891772, 19939935, 83133790, 51715482, 84840549, 10961421, 28787861, 26114953, 30694952, 36189527, 9860195, 10309525, 15948937, 81677380, 38008118, 26734892, 21070110, 61373987, 19486173, 44784505, 69255765, 62051033, 60581278, 16019925, 46851987, 62936963, 48892201, 7685448, 73109997, 63152504, 79136082, 6808825, 86118021, 56461322, 39373729, 68644627, 24915585, 28851716, 82532312, 33935899, 50188404, 99021067, 68204242, 72373496, 45919976, 20645197, 1431742, 67030811, 62496012, 96193415, 24168411, 16380211, 53888755, 22721500, 45075471, 31904591, 68824981, 36139942, 69136837, 83210802, 90310261, 60176618, 83533741, 75543508, 45790169, 4434662, 29516592, 57241521, 13173644, 17539625, 21533347, 98462867, 71965942, 28928175, 62357986, 79922758, 23569917, 33628349, 42947632, 6497830, 7919588, 20836893, 45794415, 85695762, 40984766, 64087743, 30395570, 44177011, 30787683, 64157906, 67031644, 68875490, 42644903, 73168565, 82979980, 43933006, 36685023, 11543098, 76703609, 13468268, 66322959, 874791, 4798568, 78909561, 91957544, 77620120, 34295794, 15535065, 72738685, 45428665, 39847321, 69786756, 27187213, 71522968, 51464002, 23740167, 7066775, 58751351, 33237508, 88047921, 30811010, 27665211, 7687278, 65074535, 29401781, 40356877, 74441448, 77284619, 73021291, 37166608, 62428472, 68939068, 26744917, 77413012, 17081350, 29819940, 5832946, 34946859, 6871053, 72278539, 14220886, 90090964, 321665, 72358170, 61141874, 36812683, 36753250, 65038678, 82339363, 15148031, 82327024, 84349107, 92803766, 40677414, 97940276, 97281847, 8373289, 80251430, 44348328, 84684495, 17068582, 93359396, 72019362, 26998766, 61623915, 11923835, 68702632, 54199160, 30998561, 42782652, 60430369, 7229550, 78602717, 81805959, 9860968, 14093520, 70782102, 36580610, 37280276, 65454636, 27325428, 90061527, 51590803, 72495719, 84166196, 64098930, 69848388, 61712234, 20681921, 22942635, 99604946, 33061250, 176257, 83789391, 99861373, 76170907, 88130087, 13348726, 98648327, 77377183, 62552524, 52613508, 78561158, 30569392, 8129978, 13819358, 92554120, 55850790, 33123618, 21289531, 77312810, 39171895, 3233569, 66271566, 34698463, 73617245, 2331773, 26507214, 62740044, 80555751, 37739481, 30463802, 61741594, 92541302, 54868730, 42073124, 92998591, 33249630, 22766820, 77694700, 18663507, 41245325, 70541760, 89811711, 95957797, 52204879, 59910624, 77898274, 82651278, 40781449, 69355476, 66428795, 63015256, 56153345, 31491938, 56484900, 55247455, 6819644, 20140249, 66663942, 61982238, 11757872, 79322415, 74452589, 55669657, 54606384, 45848907, 26292919, 96709982, 76330843, 22129328, 44060493, 71657078, 79821897, 37891451, 99971982, 73031054, 30163921, 54987042, 6521313, 92353856, 48893685, 2917920, 45306070, 29065964, 95251277, 18833224, 43506672, 93566986, 61859581, 44842615, 23848565, 35512853, 92787493, 76540481, 26102057, 79942022, 68041839, 33431960, 47361209, 17957593, 27185644, 43357947, 76315420, 78785507, 49882705, 75128745, 65271999, 95581843, 26776922, 49328608, 508198, 96420429, 81274566, 89419466, 88444207, 26397786, 91990218, 54014062, 13862149, 10358899, 9829782, 15075176, 32250045, 14626618, 15163258, 89996536, 30764367, 59614746, 34236719, 16595436, 24058273, 89499542, 20486294, 53648154, 12416768, 86460488, 89046466, 1569515, 70727211, 40027975, 15064655, 75135448, 55753905, 76434144, 98739783, 20765474, 65275241, 31161687, 77301523, 23134715, 60102965, 53666583, 26275734, 98653983, 42307449, 99729357, 79880247, 36845587, 7955293, 1239555, 49597667, 23110625, 75986488, 6793819, 16099750, 89214616, 43322743, 81172706, 7646095, 93562257, 6505939, 67281495, 33699435, 99297164, 71333116, 97379790, 16424199, 36135, 18699206, 49271185, 1204161, 4069912, 44479073, 50668599, 29994197, 16567550, 8733068, 1375023, 37435892, 64055960, 83083131, 86242799, 98948034, 72357096, 57658654, 62762857, 11581181, 67513640, 8128637, 53632373, 17386996, 99515901, 17727650, 48774913, 10453030, 45995325, 94595668, 65851721, 168541, 66832478, 91255408, 44550764, 74743862, 69697787, 68694897, 5111370, 62452034, 19457589, 57020481, 526217, 61960400, 24733232, 93057697, 19376156, 91240048, 90158785, 4787945, 16405341, 48673079, 14947650, 66137019, 80316608, 49598724, 90272749, 8634541, 29510992, 81855445, 75820087, 26863229, 33304202, 90013093, 8099994, 16445503, 87160386, 3487592, 58749, 6221471, 88653118, 73235980, 9623492, 45237957, 3088684, 66250369, 91802888, 3183975, 51315460, 59981773, 14326617, 1788101, 49236559, 64848072, 28734791, 6157724, 11161731, 53802686, 34667286, 17365188, 22997281, 68128438, 54663246, 3633375, 53393358, 22879907, 44844121, 87598888, 8729146, 9575954, 3235882, 66116458, 7423788, 5640302, 95395112, 75777973, 61728685, 1022677, 37183543, 7300047, 4204661, 42199455, 40197395, 63506281, 51507425, 84904436, 35192533, 415901, 85116755, 48658605, 92604458, 96906410, 63967300, 66885828, 51426311, 99965001, 18783702, 47090124, 25636669, 8791066, 59329511, 41481685, 52060076, 57359924, 72732205, 37659250, 92033260, 14363867, 18806856, 12024238, 47298834, 99524975, 43152977, 24953498, 20002147, 59501487, 70420215, 36930650, 87710366, 36780454, 31727379, 39986008, 12664567, 83155442, 52261574, 46870723, 4064751, 7502255, 46540998, 32151165, 10597197, 44983451, 67124282, 56531125, 94911072, 65017137, 9886593, 39553046, 83368048, 44481640, 26664538, 59109400, 23432750, 83651211, 99549373, 4806458, 85242963, 27411561, 35996293, 59371804, 33797252, 33336148, 45667668, 45617087, 18001617, 30139692, 44473167, 69579137, 58208470, 88698958, 86001008, 21001913, 72274002, 40865610, 53084256, 91141395, 54762643, 8055981, 66667729, 79657802, 57248122, 62115552, 9259676, 20568363, 38494874, 8651647, 98943869, 1936762, 91831487, 95010552, 36468541, 22200378, 37957788, 92398073, 34493392, 6525948, 17857111, 81853704, 65338021, 81898046, 30891921, 15902805, 68110330, 31733363, 79738755, 12571310, 59197747, 48360198, 61928316, 47213183, 43045786, 10649306, 62803858, 29959549, 61263068, 569864, 42967683, 6111563, 48088883, 55189057, 85117092, 26063929, 65081429, 44847298, 19101477, 32590267, 73124510, 41380093, 92692283, 71300104, 5073754, 44889423, 71469330, 37620363, 36808486, 7011964, 5970607, 38022834, 29466406, 43376279, 91664334, 85711894, 14045383, 68316156, 90004325, 20122224, 90654836, 4119747, 91938191, 86798033, 72238278, 9257405, 10264691, 95726235, 51466049, 60393039, 60955663, 824872, 56424103, 34497327, 9603598, 99333375, 17037369, 50567636, 40686254, 17385531, 1250437, 82726008, 2717150, 82897371, 56515456, 44846932, 88904910, 44627776, 54517921, 79191827, 92530431, 10366309, 38645117, 76825057, 13470059, 22405120, 9058407, 70800879, 2891150, 94090109, 3509435, 38510840, 17894977, 88251446, 99658235, 28550822, 66611759, 36312813, 33895336, 55602660, 74137926, 66903004, 85023028, 68816413, 75229462, 83150534, 21919959, 26392416, 42237907, 84406788, 63628376, 749283, 51472275, 48675329, 4199704, 44915235, 59405277, 18466635, 62430984, 1197320, 67227442, 32699744, 53547802, 70596786, 73392814, 69605283, 35456853, 62232211, 62490109, 38061439, 24619760, 5482538, 78589145, 68000591, 82886381, 47887470, 17016156, 37675718, 30543215, 54427233, 55615885, 30653863, 36505482, 83948335, 29188588, 57163802, 18411915, 99690194, 70372191, 12303248, 74724075, 29029316, 18504197, 4099191, 17146629, 77787724, 29834512, 2607799, 74357852, 7182642, 55143724, 54232247, 92692978, 71920426, 76960303, 16684829, 72777973, 73786944, 40781854, 14731700, 66319530, 3294781, 97783876, 77187825, 86022504, 89699445, 33565483, 45381876, 47738185, 59318837, 40152546, 247198, 669105, 4985896, 32274392, 9599614, 51588015, 26493119, 78218845, 13231279, 57961282, 34698428, 82427263, 55470718, 95290172, 67084351, 57393458, 30366150, 65715134, 19898053, 21673260, 80246713, 8807940, 20867149, 86301513, 63059024, 33553959, 72793444, 82052050, 29635537, 9175338, 9188443, 37501808, 4508700, 34044787, 22113133, 18131876, 31126490, 4091162, 19272365, 39201414, 78300864, 41092102, 83269727, 84127901, 96318094, 94076128, 99125126, 43501211, 99917599, 69641513, 90457870, 63372756, 48395186, 44664587, 66045587, 6038457, 36933038, 70036438, 93515664, 4978543, 14723410, 82178706, 38009615, 24826575, 65880522, 30090481, 89217461, 93933709, 32426752, 112651, 55960386, 41442762, 49641577, 81774825, 83302115, 77072625, 38256849, 84187166, 89078848, 30771409, 77300457, 17764950, 92215320, 96735716, 4515343, 67451935, 98130363, 15015906, 55770687, 16097038, 58180653, 76671482, 51149804, 51047803, 4668450, 5822202, 99226875, 93053405, 58224549, 70004753, 92071990, 27625735, 57240218, 89637706, 5267545, 8115266, 78884452, 72614359, 83747892, 78766358, 56955985, 45996863, 45407418, 22450468, 2204165 +88130087, 65851721, 61741594, 1239555, 4119747, 94516935, 7104732, 9860195, 93790285, 43045786, 29635537, 168541, 321665, 43506672, 24733232, 97940276, 80251430, 82427263, 33628349, 65454636, 7919588, 89046466, 6111563, 43322743, 93562257, 67281495, 85711894, 52060076, 85571389, 36930650, 89078848, 17081350, 34946859, 50806615, 91255408, 77300457, 79191827, 93566986, 48673079, 70800879, 30139692, 58208470, 21001913, 63372756, 78549759, 6157724, 20885148, 20681921, 38061439, 48360198, 44784505, 43933006, 55189057, 37739481, 54868730, 99690194, 47708850, 56473732, 30811010, 29401781, 62762857, 56955985, 17727650, 7502255, 46540998, 40534591, 94595668, 11365791, 45306070, 56531125, 89637706, 9886593, 91281584, 45049308, 26664538, 97561745, 86001008, 93359396, 88251446, 11923835, 45237957, 53842979, 42782652, 57248122, 68816413, 26392416, 28734791, 11161731, 92398073, 17365188, 84166196, 19898053, 69605283, 35456853, 53648154, 62490109, 38009615, 24826575, 65880522, 64157906, 67031644, 3235882, 98739783, 39801351, 55850790, 1022677, 34698463, 48260151, 80014588, 84904436, 91957544, 41380093, 89214616, 96906410, 44245960, 18663507, 89811711, 68644627, 38022834, 92033260, 63015256, 14363867, 16684829, 45919976, 1375023, 37435892, 45996863, 57240218, 10453030, 53888755, 74743862, 8696647, 67124282, 5267545, 84349107, 91240048, 90310261, 76825057, 72725103, 16405341, 26139110, 97281847, 35092039, 68102437, 95581843, 79657802, 75052463, 14326617, 1788101, 64848072, 70036438, 4199704, 97011160, 81677380, 22997281, 61712234, 65715134, 89499542, 21673260, 3773993, 70004753, 15064655, 30090481, 98648327, 30569392, 62051033, 75777973, 60581278, 82886381, 47887470, 33553959, 86543538, 89804152, 30543215, 51507425, 874791, 2331773, 80555751, 19101477, 38365584, 75986488, 69786756, 112651, 66885828, 51464002, 99965001, 4099191, 56461322, 8791066, 27041967, 41481685, 36135, 68316156, 82651278, 90004325, 28851716, 74110882, 24314885, 18699206, 72777973, 29994197, 16567550, 94711842, 1431742, 30218878, 59501487, 99226875, 96193415, 33201905, 26292919, 57803235, 59318837, 66832478, 669105, 4985896, 9398733, 99125126, 61960400, 54517921, 92530431, 93057697, 83210802, 40677414, 13470059, 80316608, 68041839, 26493119, 33431960, 3509435, 3233084, 78785507, 65271999, 54762643, 73235980, 3088684, 66667729, 66045587, 55602660, 26114953, 23569917, 38494874, 14093520, 21919959, 88444207, 37280276, 9829782, 749283, 44915235, 37957788, 34667286, 27325428, 54663246, 24591705, 15015906, 70596786, 44844121, 33061250, 24619760, 83789391, 99861373, 40027975, 8729146, 55753905, 22108665, 45990383, 52613508, 92554120, 65275241, 92867155, 77301523, 569864, 89217461, 98653983, 42199455, 82052050, 36685023, 54427233, 55615885, 65081429, 44847298, 61815223, 48892201, 73124510, 77620120, 34295794, 9175338, 18411915, 7685448, 81172706, 2204165, 36808486, 18783702, 33699435, 18131876, 59910624, 16424199, 81774825, 61859143, 57359924, 20122224, 90654836, 86798033, 65074535, 60955663, 14731700, 73021291, 62428472, 33565483, 17037369, 15536795, 36396314, 53632373, 40686254, 83155442, 96709982, 48774913, 97641116, 61897582, 82897371, 48893685, 62693428, 44846932, 88904910, 61141874, 44627776, 43501211, 74614639, 15148031, 9599614, 23848565, 59109400, 8115266, 90158785, 75543508, 44473167, 13231279, 57961282, 75820087, 44348328, 83133790, 72274002, 72019362, 84840549, 28928175, 40865610, 10961421, 66611759, 48395186, 62357986, 8055981, 66250369, 68702632, 96735716, 508198, 2208785, 98943869, 9860968, 55470718, 95010552, 61271144, 36933038, 67084351, 42237907, 57393458, 54014062, 13862149, 84406788, 49236559, 93515664, 15075176, 38008118, 64098930, 69848388, 16595436, 55770687, 21070110, 20486294, 80246713, 15902805, 40984766, 30787683, 1569515, 20867149, 76170907, 64602895, 76434144, 77377183, 8129978, 7423788, 13819358, 38341669, 29959549, 77312810, 82979980, 93933709, 16097038, 94360702, 48088883, 4204661, 58180653, 76671482, 66271566, 42307449, 76703609, 99729357, 26063929, 73617245, 30653863, 62936963, 39847321, 98371444, 70372191, 5073754, 29029316, 70367851, 7646095, 37620363, 41245325, 50007421, 34044787, 22113133, 59329511, 5970607, 29466406, 52204879, 43376279, 78766358, 7182642, 16054533, 77898274, 37659250, 79806380, 1204161, 65047700, 66428795, 50188404, 39201414, 73786944, 83302115, 60393039, 99524975, 55247455, 64055960, 66319530, 67474219, 98948034, 824872, 56424103, 79322415, 41092102, 54606384, 89699445, 31727379, 67513640, 37192445, 8128637, 84127901, 17385531, 14349098, 82779622, 21397057, 29819940, 30771409, 32151165, 45995325, 94076128, 34432810, 22721500, 2717150, 2917920, 5111370, 19457589, 95251277, 65038678, 71083565, 32161669, 92803766, 23432750, 4787945, 35512853, 4806458, 22405120, 9058407, 26102057, 59371804, 45790169, 33797252, 45667668, 18001617, 90272749, 19891772, 17539625, 81855445, 21533347, 98462867, 25842078, 17957593, 27185644, 33304202, 17894977, 43357947, 22450468, 99658235, 91141395, 6221471, 9623492, 36312813, 3880712, 3183975, 30694952, 8651647, 60106217, 47792865, 30366150, 63628376, 48675329, 53802686, 51590803, 15163258, 1197320, 20836893, 62232211, 81898046, 44177011, 61373987, 37224844, 19486173, 78561158, 86301513, 63059024, 73168565, 29932657, 33123618, 17016156, 60102965, 3233569, 53666583, 8913721, 63506281, 66322959, 79880247, 84293052, 26507214, 62740044, 78909561, 92692283, 23110625, 15535065, 85116755, 9188443, 16099750, 48658605, 51047803, 74724075, 63152504, 79136082, 18504197, 55960386, 86118021, 4508700, 99297164, 41442762, 58751351, 95957797, 2607799, 96726697, 14045383, 7517032, 4091162, 24915585, 69355476, 12024238, 56484900, 24953498, 78300864, 86242799, 4668450, 67030811, 77284619, 20140249, 66663942, 61982238, 9603598, 11757872, 97783876, 55669657, 24168411, 36780454, 83269727, 45848907, 12664567, 77413012, 76330843, 47738185, 5832946, 96318094, 40152546, 247198, 73031054, 86821229, 44983451, 90090964, 44550764, 56515456, 62452034, 65017137, 18833224, 32274392, 45075471, 44481640, 44842615, 10366309, 69136837, 83651211, 92787493, 83533741, 27411561, 66137019, 93053405, 49598724, 33336148, 29516592, 8373289, 8634541, 94090109, 13173644, 92215320, 69641513, 71965942, 76315420, 51715482, 87160386, 28550822, 61623915, 58224549, 44664587, 54199160, 4515343, 7229550, 51315460, 1936762, 66903004, 91831487, 85023028, 81274566, 75229462, 36468541, 22200378, 51472275, 36189527, 67451935, 4978543, 17976208, 59405277, 15948937, 34493392, 94539824, 34236719, 6525948, 98130363, 26734892, 67227442, 32699744, 24058273, 65338021, 6153406, 45794415, 73392814, 78884452, 21993752, 22879907, 22942635, 12416768, 86460488, 64087743, 176257, 31733363, 13348726, 61928316, 95395112, 62803858, 61263068, 37183543, 72793444, 51149804, 84002370, 72614359, 36505482, 30463802, 415901, 49597667, 92541302, 29188588, 71300104, 59177718, 42073124, 63967300, 22766820, 27187213, 12348118, 51426311, 39373729, 33237508, 29834512, 16971929, 88047921, 55143724, 97379790, 27665211, 4069912, 44479073, 95726235, 51466049, 8733068, 47298834, 62496012, 5822202, 34497327, 38256849, 26744917, 45381876, 52261574, 82726008, 4064751, 44060493, 71657078, 79821897, 30163921, 72278539, 92353856, 68694897, 72358170, 526217, 83368048, 31904591, 82339363, 68824981, 36139942, 38645117, 60176618, 51588015, 17764950, 20642888, 4095116, 76540481, 85242963, 79942022, 35996293, 2891150, 22435353, 57241521, 29510992, 47361209, 84684495, 88698958, 96852321, 17068582, 26998766, 49882705, 34698428, 88653118, 28787861, 28796059, 30998561, 60430369, 91802888, 6038457, 78602717, 81805959, 89419466, 95290172, 70782102, 26397786, 36580610, 91990218, 10309525, 18466635, 72495719, 77272628, 68128438, 32159704, 30764367, 59614746, 81853704, 3633375, 53393358, 85695762, 38658347, 73222868, 99604946, 54263880, 78589145, 62552524, 66116458, 68875490, 37675718, 23134715, 39171895, 26275734, 17058722, 11543098, 13468268, 35192533, 83948335, 72738685, 45428665, 92604458, 92998591, 33249630, 42692881, 71522968, 6808825, 7066775, 17146629, 71333116, 31126490, 32058615, 27625735, 71920426, 82532312, 91938191, 76960303, 50668599, 9257405, 72373496, 10264691, 18806856, 31491938, 20645197, 74441448, 6819644, 3294781, 72357096, 57658654, 74452589, 86022504, 11581181, 99333375, 84187166, 17386996, 1250437, 46870723, 99224392, 37891451, 97057187, 6521313, 69697787, 16387575, 99917599, 82327024, 61859581, 41092172, 14947650, 91727510, 4434662, 45617087, 26863229, 19939935, 8099994, 90457870, 3487592, 58749, 33895336, 26776922, 49328608, 79922758, 20568363, 10358899, 14626618, 62430984, 17857111, 8807940, 30395570, 79738755, 75135448, 59197747, 5482538, 47213183, 92071990, 69255765, 5640302, 20765474, 42967683, 40197395, 52293995, 16019925, 46851987, 4798568, 37560164, 32426752, 44889423, 6505939, 23740167, 7011964, 71316369, 74357852, 49641577, 40781449, 54232247, 49271185, 99021067, 87720882, 68204242, 23194618, 72238278, 40356877, 77072625, 77187825, 87710366, 39986008, 6986898, 50702367, 54058788, 16380211, 10597197, 99971982, 14220886, 94911072, 36753250, 39553046, 19376156, 99549373, 38510840, 90013093, 75153252, 93270084, 96420429, 9259676, 59981773, 42947632, 47893286, 6497830, 53547802, 82178706, 30891921, 70727211, 12571310, 9575954, 10649306, 42644903, 31161687, 21289531, 7300047, 85117092, 36845587, 32590267, 37501808, 83747892, 70541760, 47090124, 25636669, 92692978, 33935899, 7687278, 19272365, 56153345, 43152977, 20002147, 68939068, 22129328, 54987042, 29065964, 57020481, 78218845, 62115552, 32250045, 90061527, 89996536, 14723410, 68110330, 87598888, 68000591, 75407347, 57163802, 77694700, 71469330, 73109997, 83083131, 40781854, 70420215, 99515901, 6871053, 36812683, 69579137, 16445503, 53084256, 75128745, 74137926, 61728685, 7955293, 12303248, 77787724, 72732205, 37166608, 50567636, 67793644, 45407418, 83150534, 6793819, 91664334 +19939935, 67513640, 97561745, 73235980, 43933006, 39171895, 97783876, 45075471, 78549759, 15948937, 39801351, 38341669, 92541302, 56461322, 29819940, 7502255, 669105, 11365791, 19457589, 43506672, 91240048, 13470059, 4806458, 17539625, 25842078, 8099994, 30998561, 78602717, 1788101, 36189527, 69605283, 85695762, 33061250, 3773993, 99861373, 8729146, 69255765, 65275241, 82886381, 77312810, 40197395, 79880247, 84293052, 26507214, 91957544, 67281495, 18783702, 8791066, 16971929, 97379790, 7517032, 64055960, 40781854, 37192445, 40686254, 17081350, 99224392, 45995325, 69697787, 91727510, 19891772, 51715482, 99658235, 55602660, 13862149, 4978543, 7919588, 64602895, 45990383, 29932657, 4798568, 36845587, 7955293, 44889423, 51464002, 50007421, 25636669, 71316369, 59910624, 16424199, 49641577, 77898274, 30811010, 28851716, 76960303, 87720882, 85571389, 77284619, 98948034, 3294781, 17037369, 57240218, 17386996, 99515901, 46540998, 94076128, 30163921, 61897582, 82897371, 61960400, 44842615, 23848565, 83210802, 22405120, 33336148, 66250369, 28787861, 74137926, 23569917, 98943869, 85023028, 68816413, 26392416, 49236559, 37280276, 72495719, 89996536, 26734892, 20486294, 8807940, 59197747, 65880522, 78589145, 95395112, 21289531, 58180653, 13468268, 36505482, 35192533, 92692283, 77620120, 72738685, 43322743, 81172706, 71469330, 93562257, 6505939, 17146629, 99297164, 38022834, 2607799, 37659250, 82532312, 18699206, 1204161, 65074535, 4069912, 8733068, 37435892, 60955663, 4668450, 72357096, 62428472, 56955985, 26292919, 57803235, 76330843, 59318837, 54987042, 8696647, 68694897, 62693428, 9886593, 16387575, 71083565, 44481640, 15148031, 23432750, 99549373, 49598724, 57241521, 94090109, 98462867, 96852321, 38510840, 17894977, 16445503, 68102437, 28928175, 11923835, 91141395, 42782652, 60430369, 1936762, 66903004, 21919959, 91990218, 84406788, 70036438, 93515664, 9860195, 92398073, 97011160, 62430984, 30764367, 6153406, 45794415, 19898053, 22942635, 64087743, 99604946, 54263880, 61373987, 20867149, 87598888, 5482538, 88130087, 13348726, 22108665, 52613508, 30569392, 98739783, 62051033, 75777973, 61728685, 94360702, 48088883, 26275734, 51149804, 85117092, 76703609, 874791, 55615885, 62740044, 48892201, 38365584, 32590267, 73124510, 41380093, 85116755, 18411915, 70372191, 69786756, 27187213, 29029316, 47708850, 71522968, 55960386, 99965001, 4099191, 39373729, 68644627, 43376279, 96726697, 31126490, 52060076, 65047700, 44479073, 72777973, 43152977, 6819644, 99226875, 34497327, 36930650, 5832946, 40152546, 34432810, 66832478, 53888755, 2717150, 321665, 65017137, 29065964, 36753250, 54517921, 32274392, 10366309, 69136837, 40677414, 4787945, 35512853, 17764950, 48673079, 76540481, 27411561, 66137019, 26139110, 22435353, 97281847, 30139692, 8373289, 33431960, 80251430, 81855445, 21533347, 92215320, 17068582, 76315420, 72019362, 78785507, 88251446, 75128745, 63372756, 8055981, 45237957, 93270084, 81805959, 30694952, 51315460, 20568363, 81274566, 75229462, 95290172, 36468541, 70782102, 26397786, 42237907, 36580610, 4199704, 67451935, 65454636, 32250045, 90061527, 77272628, 81677380, 59614746, 64098930, 16595436, 65338021, 73392814, 53648154, 81898046, 1569515, 83789391, 76434144, 67031644, 77377183, 62552524, 47213183, 78561158, 92071990, 42644903, 62803858, 17016156, 29959549, 33553959, 82979980, 23134715, 6111563, 17058722, 30543215, 36685023, 11543098, 99729357, 54427233, 84002370, 78909561, 61815223, 29188588, 6793819, 16099750, 59177718, 42073124, 112651, 12303248, 66885828, 12348118, 2204165, 37620363, 37501808, 36808486, 79136082, 56473732, 41442762, 33237508, 29834512, 16054533, 68316156, 20122224, 71920426, 79806380, 49271185, 56153345, 99021067, 72238278, 29994197, 95726235, 51466049, 12024238, 60393039, 1375023, 99524975, 1431742, 83083131, 86242799, 20002147, 66319530, 73021291, 55669657, 99333375, 45848907, 8128637, 50702367, 83155442, 22129328, 46870723, 17727650, 21397057, 54058788, 79821897, 99971982, 73031054, 97641116, 86821229, 72278539, 90090964, 44550764, 74743862, 94911072, 62452034, 44846932, 44627776, 57020481, 45049308, 526217, 65038678, 74614639, 92530431, 93566986, 68824981, 26664538, 32161669, 19376156, 84349107, 8115266, 90158785, 9058407, 26102057, 85242963, 79942022, 14947650, 4434662, 26493119, 75820087, 69641513, 84684495, 26863229, 71965942, 72274002, 93359396, 87160386, 61623915, 75153252, 34698428, 54762643, 28796059, 66045587, 82427263, 2208785, 89419466, 36933038, 63628376, 64848072, 9829782, 47893286, 28734791, 37957788, 59405277, 22997281, 34493392, 94539824, 67227442, 53547802, 70596786, 78884452, 22879907, 44844121, 80246713, 38061439, 31733363, 93790285, 37224844, 15064655, 12571310, 19486173, 24826575, 30090481, 61928316, 75407347, 7423788, 5640302, 68875490, 10649306, 92554120, 20765474, 31161687, 92867155, 77301523, 37675718, 569864, 60102965, 3233569, 98653983, 42199455, 55189057, 8913721, 34698463, 48260151, 16019925, 73617245, 65081429, 72614359, 44847298, 80555751, 37739481, 19101477, 415901, 34295794, 98371444, 32426752, 22766820, 42692881, 7646095, 73109997, 18504197, 41245325, 7066775, 71333116, 27041967, 52204879, 91664334, 85711894, 14045383, 41481685, 81774825, 40781449, 27625735, 4119747, 54232247, 27665211, 86798033, 23194618, 29401781, 39201414, 73786944, 40356877, 10264691, 47298834, 24953498, 55247455, 67474219, 62496012, 59501487, 79322415, 38256849, 41092102, 54606384, 24168411, 36780454, 31727379, 26744917, 45996863, 89078848, 36396314, 82726008, 14349098, 97057187, 168541, 44983451, 4985896, 6521313, 9398733, 56515456, 89637706, 39553046, 99917599, 24733232, 9599614, 36139942, 90310261, 60176618, 51588015, 4095116, 35996293, 45790169, 45667668, 44473167, 13231279, 3509435, 27185644, 33304202, 35092039, 84840549, 49882705, 40865610, 66611759, 48395186, 58224549, 36312813, 95581843, 79657802, 26776922, 49328608, 57248122, 91802888, 33628349, 8651647, 9860968, 60106217, 57393458, 54014062, 10358899, 22200378, 6497830, 44915235, 10309525, 53802686, 18466635, 34667286, 84166196, 34236719, 14723410, 98130363, 1197320, 24591705, 55770687, 73222868, 21673260, 62490109, 15902805, 12416768, 30395570, 70004753, 176257, 70727211, 79738755, 76170907, 64157906, 3235882, 66116458, 73168565, 16097038, 4204661, 53666583, 89804152, 26063929, 66322959, 80014588, 84904436, 46851987, 1239555, 83948335, 49597667, 75986488, 39847321, 48658605, 7685448, 51047803, 71300104, 92604458, 96906410, 54868730, 99690194, 63967300, 92998591, 77694700, 70367851, 63152504, 23740167, 22113133, 29466406, 74357852, 88047921, 55143724, 32058615, 82651278, 61859143, 4091162, 92692978, 74110882, 24314885, 19272365, 66428795, 68204242, 18806856, 20140249, 9603598, 74452589, 87710366, 11581181, 53632373, 17385531, 96318094, 247198, 10597197, 94595668, 50806615, 22721500, 92353856, 5111370, 72358170, 88904910, 95251277, 43501211, 31904591, 93057697, 5267545, 38645117, 59109400, 76825057, 83651211, 70800879, 83533741, 97940276, 80316608, 33797252, 90272749, 8634541, 69579137, 47361209, 57961282, 44348328, 86001008, 3233084, 83133790, 21001913, 90013093, 26998766, 28550822, 53084256, 58749, 10961421, 88653118, 68702632, 54199160, 3880712, 508198, 7229550, 6038457, 62115552, 38494874, 91831487, 55470718, 14326617, 88444207, 30366150, 51472275, 48675329, 15075176, 6157724, 20885148, 51590803, 6525948, 17857111, 61712234, 81853704, 53393358, 21070110, 86460488, 24619760, 89046466, 75135448, 98648327, 44784505, 60581278, 47887470, 1022677, 89217461, 37183543, 66271566, 52293995, 51507425, 30653863, 30463802, 23110625, 15535065, 45428665, 29635537, 33249630, 6808825, 51426311, 47090124, 58751351, 34044787, 89811711, 59329511, 95957797, 18131876, 78766358, 57359924, 90004325, 72732205, 69355476, 63015256, 50188404, 9257405, 83302115, 20645197, 14731700, 30218878, 5822202, 62762857, 37166608, 33201905, 68939068, 45381876, 15536795, 84127901, 52261574, 4064751, 47738185, 44060493, 30771409, 34946859, 40534591, 6871053, 48893685, 45306070, 61141874, 91281584, 77300457, 18833224, 61859581, 72725103, 92787493, 20642888, 68041839, 18001617, 88698958, 17957593, 90457870, 7104732, 9623492, 53842979, 33895336, 96735716, 96420429, 3183975, 75052463, 14093520, 47792865, 17976208, 11161731, 54663246, 69848388, 3633375, 32699744, 21993752, 35456853, 62232211, 38009615, 48360198, 68000591, 9575954, 86301513, 63059024, 43045786, 61263068, 86543538, 82052050, 61741594, 37560164, 57163802, 89214616, 5073754, 74724075, 18663507, 83747892, 70541760, 86118021, 4508700, 5970607, 24915585, 33935899, 7687278, 14363867, 16684829, 45919976, 16567550, 94711842, 56484900, 56424103, 96193415, 77072625, 11757872, 77187825, 39986008, 6986898, 1250437, 82779622, 71657078, 65851721, 67793644, 91255408, 14220886, 2917920, 56531125, 36812683, 83368048, 79191827, 92803766, 16405341, 94516935, 93053405, 29516592, 13173644, 78218845, 29510992, 22450468, 44664587, 3088684, 66667729, 79922758, 9259676, 42947632, 61271144, 14626618, 68128438, 32159704, 38008118, 65715134, 38658347, 13819358, 33123618, 93933709, 42967683, 72793444, 76671482, 2331773, 62936963, 44245960, 7011964, 33699435, 36135, 92033260, 72373496, 31491938, 74441448, 824872, 66663942, 86022504, 84187166, 12664567, 96709982, 99125126, 82339363, 45407418, 41092172, 2891150, 59371804, 43357947, 3487592, 65271999, 62357986, 4515343, 26114953, 83150534, 95010552, 67084351, 749283, 17365188, 24058273, 89499542, 30891921, 68110330, 55753905, 8129978, 55850790, 7300047, 42307449, 63506281, 9175338, 9188443, 90654836, 91938191, 50668599, 67030811, 57658654, 83269727, 89699445, 50567636, 77413012, 48774913, 32151165, 37891451, 67124282, 82327024, 75543508, 45617087, 58208470, 6221471, 59981773, 27325428, 15163258, 20836893, 20681921, 82178706, 40984766, 44177011, 30787683, 40027975, 77787724, 7182642, 78300864, 70420215, 33565483, 10453030, 16380211, 15015906, 61982238 +65715134, 82886381, 29188588, 42782652, 88444207, 824872, 34946859, 90090964, 8373289, 69605283, 569864, 63506281, 98371444, 63152504, 72777973, 73021291, 62496012, 7502255, 65851721, 14220886, 74743862, 44473167, 84684495, 28928175, 6038457, 38494874, 95290172, 93515664, 89499542, 35456853, 44177011, 16097038, 42199455, 44245960, 18663507, 4099191, 52060076, 4119747, 74110882, 24314885, 65074535, 83302115, 30218878, 56424103, 17037369, 89078848, 94595668, 50806615, 56531125, 24733232, 93057697, 29510992, 81855445, 88251446, 58749, 7104732, 4515343, 61271144, 70036438, 9860195, 11161731, 3633375, 99861373, 15064655, 66116458, 86301513, 98739783, 34698463, 2331773, 48892201, 1239555, 16099750, 89214616, 81172706, 70367851, 41245325, 7066775, 18783702, 86118021, 25636669, 58751351, 16054533, 77284619, 5822202, 61982238, 79322415, 86022504, 62428472, 17081350, 10453030, 44550764, 56515456, 91281584, 99917599, 10366309, 83651211, 41092172, 48673079, 26102057, 79942022, 35996293, 59371804, 33797252, 13231279, 72274002, 17068582, 93359396, 87160386, 72019362, 11923835, 58224549, 73235980, 66250369, 36312813, 53842979, 30694952, 57393458, 10358899, 51590803, 6525948, 69848388, 32699744, 21993752, 38009615, 86460488, 24619760, 83789391, 93790285, 67031644, 44784505, 75407347, 63059024, 43045786, 92867155, 33553959, 93933709, 11543098, 84904436, 46851987, 37739481, 61741594, 48658605, 77694700, 5970607, 38022834, 55143724, 41481685, 57359924, 27665211, 18806856, 60393039, 78300864, 66319530, 66663942, 74452589, 24168411, 56955985, 45381876, 57803235, 17727650, 30771409, 46540998, 32151165, 68694897, 9886593, 61141874, 32274392, 92530431, 82339363, 9599614, 69136837, 90310261, 97940276, 45790169, 93053405, 45617087, 97281847, 94090109, 58208470, 26863229, 83133790, 75128745, 3487592, 91141395, 63372756, 6221471, 55602660, 7229550, 83150534, 42237907, 92398073, 17365188, 62430984, 15163258, 34236719, 16595436, 19898053, 73392814, 38658347, 62232211, 40984766, 20867149, 8729146, 55753905, 65880522, 64602895, 68000591, 9575954, 95395112, 13819358, 92554120, 60581278, 82979980, 66271566, 8913721, 44847298, 34295794, 29635537, 9188443, 92604458, 36808486, 6808825, 8791066, 17146629, 41442762, 33237508, 77787724, 43376279, 36135, 68316156, 90004325, 54232247, 71920426, 65047700, 44479073, 50668599, 29994197, 94711842, 56484900, 77072625, 11757872, 62762857, 41092102, 83269727, 8128637, 50702367, 96709982, 99224392, 44060493, 79821897, 99971982, 66832478, 91255408, 4985896, 8696647, 45306070, 67124282, 65017137, 77300457, 65038678, 61960400, 54517921, 45075471, 79191827, 44481640, 82327024, 91240048, 83210802, 40677414, 76825057, 8115266, 72725103, 45407418, 92787493, 66137019, 75543508, 68041839, 33431960, 80251430, 90013093, 35092039, 26998766, 78785507, 49882705, 10961421, 65271999, 8055981, 45237957, 95581843, 82427263, 96420429, 26114953, 23569917, 33628349, 75052463, 9860968, 68816413, 14093520, 55470718, 1788101, 67084351, 22200378, 28734791, 20885148, 18466635, 97011160, 81677380, 22997281, 94539824, 84166196, 98130363, 7919588, 20836893, 61712234, 15015906, 81853704, 45794415, 82178706, 62490109, 33061250, 3773993, 30395570, 54263880, 79738755, 75135448, 24826575, 5482538, 64157906, 98648327, 22108665, 61928316, 92071990, 30569392, 7423788, 42644903, 38341669, 62051033, 55850790, 42967683, 6111563, 60102965, 53666583, 89804152, 55189057, 40197395, 52293995, 48260151, 16019925, 13468268, 66322959, 80555751, 36505482, 61815223, 91957544, 37560164, 15535065, 57163802, 18411915, 51047803, 96906410, 54868730, 70372191, 42073124, 63967300, 66885828, 29029316, 47708850, 7646095, 37620363, 55960386, 50007421, 56473732, 34044787, 89811711, 59329511, 31126490, 32058615, 7517032, 24915585, 92692978, 91938191, 50188404, 87720882, 39201414, 45919976, 1375023, 55247455, 20645197, 64055960, 67474219, 98948034, 3294781, 20140249, 33201905, 33565483, 45996863, 17385531, 82726008, 22129328, 29819940, 96318094, 6871053, 37891451, 247198, 54987042, 669105, 86821229, 22721500, 82897371, 92353856, 48893685, 9398733, 62693428, 89637706, 44846932, 44627776, 71083565, 31904591, 68824981, 13470059, 35512853, 51588015, 76540481, 70800879, 94516935, 14947650, 91727510, 2891150, 4434662, 49598724, 26493119, 97561745, 30139692, 69579137, 21001913, 22450468, 40865610, 28550822, 34698428, 88653118, 93270084, 3088684, 54199160, 30998561, 79922758, 1936762, 59981773, 47792865, 14326617, 95010552, 26397786, 36580610, 91990218, 84406788, 49236559, 64848072, 749283, 51472275, 4978543, 53802686, 34493392, 32159704, 26734892, 67227442, 53393358, 85695762, 20486294, 53648154, 73222868, 99604946, 38061439, 70004753, 31733363, 37224844, 19486173, 30090481, 68875490, 61728685, 29932657, 47887470, 29959549, 37183543, 86543538, 7300047, 82052050, 99729357, 54427233, 84002370, 26507214, 62936963, 78909561, 83948335, 49597667, 41380093, 77620120, 39847321, 7685448, 32426752, 5073754, 12348118, 44889423, 93562257, 67281495, 51426311, 99965001, 7011964, 33699435, 99297164, 22113133, 68644627, 29834512, 16971929, 52204879, 96726697, 78766358, 88047921, 85711894, 16424199, 49641577, 82651278, 4091162, 40781449, 90654836, 18699206, 7687278, 86798033, 66428795, 92033260, 63015256, 56153345, 68204242, 29401781, 72238278, 16567550, 95726235, 47298834, 14731700, 34497327, 54606384, 89699445, 31727379, 37192445, 26292919, 36396314, 40686254, 83155442, 99515901, 76330843, 4064751, 48774913, 59318837, 40152546, 168541, 44983451, 321665, 57020481, 526217, 95251277, 43506672, 93566986, 15148031, 32161669, 23848565, 38645117, 23432750, 17764950, 27411561, 22435353, 90272749, 57961282, 69641513, 98462867, 96852321, 86001008, 3233084, 71965942, 33304202, 76315420, 84840549, 68102437, 61623915, 66611759, 54762643, 9623492, 68702632, 79657802, 49328608, 96735716, 91802888, 3183975, 78602717, 78549759, 8651647, 91831487, 81274566, 42947632, 36933038, 30366150, 47893286, 59405277, 6157724, 15948937, 27325428, 14626618, 38008118, 20681921, 65338021, 55770687, 22879907, 81898046, 80246713, 12416768, 64087743, 30787683, 1569515, 48360198, 88130087, 76434144, 62552524, 3235882, 5640302, 10649306, 39801351, 65275241, 62803858, 33123618, 77301523, 77312810, 94360702, 43933006, 48088883, 98653983, 36685023, 26063929, 874791, 80014588, 79880247, 73617245, 55615885, 30653863, 65081429, 35192533, 30463802, 59177718, 74724075, 71469330, 37501808, 73109997, 51464002, 83747892, 39373729, 71316369, 95957797, 29466406, 2607799, 74357852, 61859143, 30811010, 28851716, 19272365, 14363867, 23194618, 72373496, 40356877, 37435892, 83083131, 86242799, 6819644, 59501487, 96193415, 36930650, 55669657, 38256849, 68939068, 12664567, 77413012, 53632373, 57240218, 1250437, 14349098, 5832946, 40534591, 45995325, 72278539, 11365791, 62452034, 72358170, 16387575, 43501211, 26664538, 5267545, 19376156, 84349107, 92803766, 59109400, 16405341, 85242963, 83533741, 26139110, 45667668, 18001617, 29516592, 8634541, 13173644, 19891772, 17539625, 21533347, 17957593, 38510840, 75153252, 44664587, 60430369, 62115552, 51315460, 85023028, 89419466, 21919959, 70782102, 26392416, 54014062, 4199704, 15075176, 65454636, 10309525, 14723410, 21070110, 22942635, 30891921, 21673260, 89046466, 59197747, 13348726, 77377183, 45990383, 47213183, 69255765, 21289531, 61263068, 37675718, 89217461, 23134715, 39171895, 26275734, 72793444, 58180653, 30543215, 51149804, 42307449, 62740044, 38365584, 32590267, 73124510, 92541302, 85116755, 99690194, 33249630, 12303248, 43322743, 42692881, 27187213, 79136082, 70541760, 56461322, 4508700, 71333116, 59910624, 77898274, 20122224, 37659250, 79806380, 33935899, 1204161, 4069912, 16684829, 85571389, 10264691, 99524975, 1431742, 40781854, 72357096, 99226875, 70420215, 57658654, 9603598, 37166608, 36780454, 11581181, 50567636, 39986008, 45848907, 84127901, 6986898, 17386996, 82779622, 16380211, 97057187, 73031054, 97641116, 61897582, 53888755, 69697787, 2917920, 99125126, 88904910, 36753250, 45049308, 74614639, 39553046, 44842615, 36139942, 90158785, 4787945, 99549373, 20642888, 22405120, 4095116, 80316608, 47361209, 75820087, 19939935, 27185644, 17894977, 43357947, 48395186, 28787861, 28796059, 3880712, 26776922, 2208785, 81805959, 20568363, 98943869, 36468541, 9829782, 48675329, 44915235, 72495719, 77272628, 68128438, 70596786, 44844121, 68110330, 70727211, 52613508, 8129978, 20765474, 31161687, 73168565, 4204661, 51507425, 84293052, 36845587, 7955293, 23110625, 72738685, 9175338, 69786756, 22766820, 71522968, 2204165, 6505939, 27041967, 91664334, 7182642, 14045383, 27625735, 72732205, 49271185, 76960303, 99021067, 73786944, 51466049, 8733068, 12024238, 24953498, 74441448, 60955663, 4668450, 20002147, 67030811, 87710366, 15536795, 52261574, 21397057, 71657078, 10597197, 30163921, 19457589, 83368048, 60176618, 78218845, 3509435, 44348328, 88698958, 25842078, 51715482, 66667729, 33895336, 66045587, 508198, 60106217, 63628376, 37280276, 36189527, 67451935, 17976208, 34667286, 89996536, 30764367, 59614746, 54663246, 17857111, 24591705, 53547802, 24058273, 6153406, 78884452, 61373987, 176257, 40027975, 78589145, 78561158, 75777973, 17016156, 1022677, 3233569, 17058722, 76671482, 76703609, 19101477, 92692283, 75986488, 45428665, 6793819, 71300104, 112651, 92998591, 23740167, 47090124, 18131876, 97379790, 81774825, 69355476, 77187825, 26744917, 34432810, 5111370, 94911072, 36812683, 18833224, 33336148, 16445503, 90457870, 53084256, 74137926, 9259676, 75229462, 13862149, 37957788, 32250045, 8807940, 87598888, 76170907, 12571310, 72614359, 82532312, 9257405, 43152977, 97783876, 67513640, 2717150, 6521313, 29065964, 61859581, 4806458, 9058407, 99658235, 62357986, 57248122, 66903004, 6497830, 64098930, 1197320, 4798568, 99333375, 46870723, 47738185, 54058788, 94076128, 67793644, 92215320, 90061527, 415901, 8099994, 85117092, 18504197, 57241521, 15902805, 31491938, 84187166 +45919976, 91255408, 44481640, 57961282, 84293052, 8634541, 29932657, 26063929, 41380093, 57163802, 95957797, 53888755, 83210802, 35996293, 88698958, 2331773, 52060076, 57359924, 56424103, 50702367, 56515456, 61960400, 97281847, 13173644, 88653118, 26114953, 17365188, 17857111, 69605283, 64157906, 62803858, 37620363, 36808486, 50007421, 39373729, 72238278, 9257405, 59501487, 70420215, 61897582, 8696647, 5111370, 62452034, 16387575, 83368048, 24733232, 44842615, 90310261, 45407418, 41092172, 22405120, 45617087, 69641513, 40865610, 3088684, 8651647, 36189527, 21070110, 82178706, 8807940, 40027975, 17016156, 93933709, 11543098, 52293995, 16019925, 84904436, 65081429, 7955293, 91957544, 77694700, 70367851, 70541760, 23740167, 5970607, 18699206, 19272365, 94711842, 14731700, 5822202, 55669657, 84187166, 77413012, 21397057, 48893685, 95251277, 31904591, 36139942, 69136837, 26139110, 58208470, 86001008, 90013093, 22450468, 49882705, 95581843, 60106217, 14093520, 61271144, 26397786, 9829782, 4199704, 18466635, 51590803, 53547802, 89499542, 35456853, 22942635, 62490109, 80246713, 76434144, 3235882, 75407347, 39801351, 73168565, 37183543, 42967683, 85117092, 34698463, 42307449, 76703609, 48260151, 4798568, 72614359, 72738685, 98371444, 7685448, 71300104, 112651, 33249630, 22766820, 44889423, 18663507, 6808825, 7011964, 17146629, 4508700, 34044787, 90004325, 16567550, 64055960, 4668450, 20002147, 57658654, 77187825, 36780454, 26744917, 39986008, 37192445, 15536795, 12664567, 14349098, 82779622, 94076128, 16380211, 14220886, 67124282, 65017137, 99125126, 61141874, 57020481, 36753250, 15148031, 23848565, 84349107, 16405341, 94516935, 14947650, 78218845, 19939935, 25842078, 71965942, 72019362, 58749, 54762643, 7104732, 54199160, 3880712, 60430369, 74137926, 1936762, 54014062, 51472275, 70036438, 93515664, 37957788, 53393358, 19898053, 68110330, 64087743, 99604946, 44177011, 61373987, 176257, 79738755, 93790285, 37224844, 68000591, 5640302, 98739783, 13819358, 92867155, 61263068, 569864, 82979980, 98653983, 55189057, 99729357, 80014588, 48892201, 415901, 38365584, 49597667, 75986488, 45428665, 99690194, 32426752, 27187213, 63152504, 6505939, 41245325, 51426311, 18783702, 86118021, 8791066, 58751351, 59329511, 77787724, 96726697, 7182642, 14045383, 77898274, 61859143, 90654836, 24314885, 86798033, 66428795, 68204242, 72373496, 85571389, 29994197, 73786944, 83302115, 60393039, 47298834, 96193415, 36930650, 37166608, 29819940, 59318837, 45995325, 37891451, 30163921, 90090964, 2917920, 56531125, 321665, 9886593, 36812683, 91281584, 71083565, 93566986, 26664538, 82327024, 5267545, 19376156, 92803766, 40677414, 59109400, 13470059, 90158785, 45790169, 93053405, 45667668, 97561745, 29510992, 44348328, 98462867, 3233084, 83133790, 91141395, 6221471, 8055981, 66667729, 36312813, 28787861, 28796059, 57248122, 55602660, 30694952, 51315460, 21919959, 36933038, 88444207, 1788101, 26392416, 10358899, 48675329, 17976208, 9860195, 6157724, 53802686, 34667286, 77272628, 15163258, 94539824, 7919588, 61712234, 6153406, 73222868, 1569515, 15064655, 76170907, 19486173, 65880522, 47213183, 86301513, 63059024, 92554120, 42644903, 37675718, 23134715, 36685023, 8913721, 63506281, 61815223, 19101477, 83948335, 73124510, 9188443, 18411915, 48658605, 92604458, 96906410, 54868730, 42073124, 44245960, 7646095, 18504197, 55960386, 18131876, 74357852, 31126490, 7517032, 37659250, 54232247, 79806380, 1204161, 99021067, 29401781, 31491938, 1375023, 74441448, 78300864, 6819644, 77284619, 11757872, 62762857, 97783876, 86022504, 62428472, 11581181, 33565483, 45848907, 8128637, 17385531, 1250437, 99515901, 22129328, 47738185, 44060493, 30771409, 34946859, 99971982, 67793644, 97641116, 74743862, 92353856, 89637706, 72358170, 44846932, 526217, 65038678, 74614639, 92530431, 68824981, 61859581, 35512853, 9058407, 4095116, 70800879, 85242963, 83533741, 66137019, 91727510, 33797252, 68041839, 18001617, 30139692, 29516592, 44473167, 33431960, 80251430, 21533347, 13231279, 17957593, 21001913, 33304202, 26998766, 28928175, 10961421, 48395186, 93270084, 79657802, 53842979, 66045587, 49328608, 42782652, 81805959, 9259676, 20568363, 59981773, 55470718, 95010552, 36580610, 91990218, 30366150, 49236559, 64848072, 15075176, 59405277, 20885148, 27325428, 97011160, 68128438, 84166196, 54663246, 32699744, 55770687, 22879907, 30891921, 12416768, 89046466, 20867149, 87598888, 75135448, 24826575, 64602895, 5482538, 67031644, 13348726, 98648327, 61928316, 69255765, 7423788, 31161687, 62051033, 55850790, 33123618, 26275734, 82052050, 51507425, 874791, 26507214, 36505482, 37739481, 32590267, 92541302, 34295794, 39847321, 6793819, 29635537, 85116755, 16099750, 69786756, 59177718, 92998591, 66885828, 71522968, 71469330, 37501808, 83747892, 67281495, 33699435, 89811711, 68644627, 29834512, 27041967, 78766358, 59910624, 16424199, 81774825, 68316156, 82651278, 24915585, 30811010, 28851716, 92033260, 50188404, 72777973, 40356877, 95726235, 43152977, 56484900, 24953498, 30218878, 824872, 62496012, 66663942, 61982238, 34497327, 9603598, 74452589, 38256849, 41092102, 99333375, 68939068, 96709982, 82726008, 5832946, 71657078, 46540998, 96318094, 247198, 10597197, 94595668, 50806615, 66832478, 86821229, 44550764, 11365791, 82897371, 62693428, 29065964, 18833224, 43506672, 79191827, 93057697, 60176618, 8115266, 99549373, 92787493, 17764950, 76540481, 80316608, 4434662, 33336148, 90272749, 8373289, 57241521, 94090109, 81855445, 84684495, 35092039, 72274002, 17068582, 43357947, 16445503, 90457870, 87160386, 84840549, 28550822, 53084256, 11923835, 65271999, 44664587, 33895336, 2208785, 3183975, 78602717, 75052463, 98943869, 9860968, 85023028, 89419466, 83150534, 95290172, 70782102, 57393458, 47893286, 749283, 28734791, 11161731, 15948937, 90061527, 32159704, 30764367, 34236719, 38008118, 64098930, 69848388, 15015906, 81853704, 24058273, 45794415, 53648154, 21673260, 86460488, 3773993, 70004753, 31733363, 99861373, 59197747, 77377183, 62552524, 45990383, 8129978, 95395112, 65275241, 38341669, 75777973, 29959549, 77301523, 33553959, 16097038, 43933006, 39171895, 53666583, 72793444, 17058722, 54427233, 73617245, 78909561, 15535065, 70372191, 43322743, 42692881, 81172706, 93562257, 4099191, 25636669, 33237508, 71333116, 29466406, 16971929, 55143724, 97379790, 36135, 32058615, 69355476, 71920426, 74110882, 7687278, 65074535, 14363867, 87720882, 10264691, 12024238, 98948034, 3294781, 72357096, 54606384, 33201905, 31727379, 56955985, 45996863, 89078848, 36396314, 84127901, 40686254, 76330843, 46870723, 17727650, 48774913, 34432810, 73031054, 72278539, 6521313, 19457589, 77300457, 45075471, 39553046, 82339363, 32161669, 9599614, 10366309, 91240048, 38645117, 76825057, 4787945, 51588015, 27411561, 79942022, 2891150, 75543508, 22435353, 26493119, 19891772, 93359396, 68102437, 61623915, 3487592, 34698428, 63372756, 26776922, 508198, 91802888, 7229550, 33628349, 81274566, 14326617, 36468541, 84406788, 37280276, 22200378, 6497830, 44915235, 4978543, 24591705, 67227442, 3633375, 73392814, 21993752, 38658347, 44844121, 40984766, 38009615, 30395570, 24619760, 54263880, 30787683, 70727211, 8729146, 48360198, 78589145, 30090481, 9575954, 44784505, 52613508, 92071990, 30569392, 10649306, 20765474, 61728685, 1022677, 94360702, 60102965, 89804152, 76671482, 66271566, 55615885, 80555751, 62936963, 61741594, 1239555, 23110625, 9175338, 89214616, 63967300, 12303248, 5073754, 74724075, 47708850, 51464002, 56473732, 56461322, 47090124, 41442762, 38022834, 2607799, 4091162, 40781449, 27625735, 27665211, 65047700, 44479073, 16684829, 23194618, 18806856, 8733068, 99524975, 20645197, 67030811, 99226875, 83269727, 67513640, 53632373, 4064751, 7502255, 79821897, 40152546, 97057187, 54987042, 9398733, 94911072, 88904910, 44627776, 45049308, 43501211, 32274392, 99917599, 72725103, 4806458, 48673079, 26102057, 97940276, 59371804, 47361209, 92215320, 3509435, 96852321, 26863229, 8099994, 78785507, 88251446, 75128745, 66611759, 58224549, 73235980, 9623492, 96735716, 4515343, 96420429, 79922758, 23569917, 38494874, 42947632, 13862149, 67451935, 81677380, 22997281, 14626618, 34493392, 59614746, 14723410, 26734892, 20681921, 70596786, 81898046, 38061439, 33061250, 88130087, 22108665, 78561158, 60581278, 21289531, 89217461, 86543538, 6111563, 7300047, 4204661, 51149804, 13468268, 66322959, 79880247, 84002370, 30463802, 37560164, 92692283, 77620120, 29029316, 73109997, 79136082, 7066775, 99965001, 99297164, 22113133, 43376279, 91664334, 88047921, 85711894, 16054533, 49641577, 92692978, 82532312, 33935899, 50668599, 39201414, 55247455, 1431742, 83083131, 60955663, 66319530, 20140249, 77072625, 87710366, 17037369, 57803235, 6986898, 52261574, 40534591, 10453030, 22721500, 45306070, 54517921, 83651211, 20642888, 38510840, 76315420, 51715482, 99658235, 62357986, 66250369, 68702632, 82427263, 6038457, 66903004, 68816413, 47792865, 65454636, 92398073, 32250045, 89996536, 98130363, 1197320, 20836893, 65715134, 65338021, 62232211, 15902805, 83789391, 43045786, 68875490, 82886381, 47887470, 58180653, 42199455, 40197395, 30653863, 62740044, 29188588, 51047803, 72732205, 91938191, 49271185, 76960303, 4069912, 51466049, 40781854, 67474219, 73021291, 89699445, 50567636, 17386996, 83155442, 99224392, 6871053, 65851721, 168541, 669105, 44983451, 68694897, 23432750, 49598724, 27185644, 17894977, 75153252, 62115552, 78549759, 91831487, 67084351, 42237907, 10309525, 62430984, 85695762, 20486294, 12571310, 55753905, 66116458, 77312810, 3233569, 30543215, 44847298, 36845587, 2204165, 71316369, 52204879, 41481685, 4119747, 63015256, 56153345, 37435892, 86242799, 45381876, 26292919, 17081350, 54058788, 2717150, 69697787, 69579137, 45237957, 30998561, 63628376, 72495719, 16595436, 48088883, 79322415, 24168411, 17539625, 75820087, 75229462, 78884452, 35192533, 12348118, 20122224, 57240218, 4985896, 6525948, 46851987, 32151165 +75135448, 56484900, 68939068, 37891451, 29065964, 79191827, 91831487, 10649306, 62803858, 60102965, 77787724, 16971929, 50702367, 21397057, 54987042, 45407418, 26776922, 89996536, 62552524, 58180653, 82052050, 46851987, 75986488, 57163802, 98371444, 51464002, 25636669, 24314885, 39201414, 66663942, 14220886, 89637706, 45049308, 69136837, 72725103, 91727510, 13173644, 44348328, 84684495, 49882705, 28796059, 508198, 91802888, 9259676, 89419466, 84406788, 32250045, 14626618, 15015906, 31733363, 44784505, 95395112, 7955293, 415901, 44889423, 29834512, 72732205, 98948034, 96193415, 62762857, 87710366, 61897582, 669105, 321665, 45075471, 60176618, 99549373, 83533741, 26139110, 68041839, 29510992, 17894977, 17068582, 90457870, 93270084, 68702632, 49328608, 42947632, 83150534, 67451935, 32159704, 6525948, 81853704, 3633375, 21993752, 8729146, 59197747, 45990383, 47213183, 7423788, 13819358, 33553959, 98653983, 62740044, 73124510, 92604458, 42692881, 74724075, 70367851, 18663507, 41245325, 5970607, 29466406, 2607799, 37659250, 27665211, 23194618, 85571389, 18806856, 78300864, 4668450, 20002147, 20140249, 70420215, 39986008, 94595668, 99971982, 65851721, 97641116, 44983451, 62452034, 18833224, 61960400, 15148031, 23848565, 13470059, 79942022, 14947650, 33431960, 21533347, 25842078, 27185644, 33304202, 22450468, 3487592, 54199160, 42782652, 36580610, 37280276, 93515664, 28734791, 38008118, 80246713, 40984766, 87598888, 12571310, 65880522, 30569392, 8129978, 20765474, 82979980, 26275734, 11543098, 34698463, 48260151, 26063929, 63506281, 51507425, 4798568, 36845587, 35192533, 30463802, 92692283, 72738685, 18411915, 96906410, 27187213, 44245960, 37620363, 63152504, 51426311, 86118021, 47090124, 8791066, 41442762, 58751351, 22113133, 74357852, 88047921, 16054533, 57359924, 74110882, 65047700, 65074535, 14363867, 44479073, 72373496, 45919976, 24168411, 37166608, 99333375, 17037369, 84187166, 45848907, 82726008, 34432810, 2717150, 69697787, 56515456, 9886593, 61141874, 65038678, 32274392, 83368048, 92530431, 31904591, 9599614, 19376156, 92803766, 38645117, 17764950, 41092172, 20642888, 76540481, 45790169, 93053405, 29516592, 8373289, 78218845, 98462867, 3233084, 16445503, 93359396, 84840549, 88251446, 61623915, 75153252, 53084256, 10961421, 88653118, 4515343, 33628349, 51315460, 20568363, 38494874, 98943869, 95290172, 4978543, 11161731, 27325428, 90061527, 77272628, 26734892, 82178706, 44844121, 33061250, 30395570, 30787683, 20867149, 99861373, 55753905, 64157906, 98648327, 22108665, 78561158, 75407347, 42644903, 29932657, 61263068, 93933709, 48088883, 17058722, 30543215, 51149804, 52293995, 99729357, 80014588, 72614359, 91957544, 32426752, 92998591, 22766820, 56461322, 39373729, 33237508, 71316369, 95957797, 97379790, 41481685, 32058615, 24915585, 69355476, 49271185, 87720882, 37435892, 74441448, 64055960, 1431742, 83083131, 40781854, 61982238, 57658654, 9603598, 83269727, 31727379, 12664567, 44060493, 46540998, 79821897, 96318094, 67793644, 66832478, 82897371, 5111370, 94911072, 36812683, 16387575, 54517921, 99917599, 68824981, 24733232, 61859581, 44842615, 93057697, 10366309, 40677414, 76825057, 92787493, 9058407, 48673079, 94516935, 66137019, 2891150, 22435353, 4434662, 97561745, 71965942, 72274002, 76315420, 26998766, 99658235, 91141395, 63372756, 54762643, 44664587, 95581843, 79657802, 33895336, 79922758, 7229550, 36468541, 63628376, 64848072, 70036438, 44915235, 9860195, 20885148, 10309525, 53802686, 17365188, 81677380, 30764367, 54663246, 24591705, 6153406, 55770687, 85695762, 35456853, 73222868, 30891921, 12416768, 64087743, 3773993, 78589145, 76434144, 30090481, 9575954, 69255765, 65275241, 31161687, 60581278, 33123618, 37675718, 569864, 89217461, 23134715, 42199455, 76671482, 13468268, 73617245, 65081429, 84002370, 48892201, 83948335, 32590267, 37560164, 41380093, 77620120, 6793819, 9188443, 33249630, 43322743, 5073754, 77694700, 71522968, 7646095, 71469330, 6505939, 83747892, 18504197, 70541760, 67281495, 55960386, 33699435, 99297164, 71333116, 96726697, 85711894, 16424199, 82651278, 90004325, 28851716, 54232247, 82532312, 19272365, 63015256, 76960303, 4069912, 72238278, 16567550, 95726235, 77284619, 3294781, 62496012, 99226875, 34497327, 11757872, 74452589, 55669657, 38256849, 56955985, 45381876, 8128637, 15536795, 77413012, 22129328, 46870723, 30771409, 59318837, 45995325, 97057187, 53888755, 4985896, 8696647, 65017137, 44846932, 71083565, 44481640, 82327024, 83210802, 90158785, 23432750, 22405120, 35996293, 80316608, 33797252, 49598724, 26493119, 45617087, 97281847, 18001617, 44473167, 19891772, 69579137, 47361209, 17539625, 57961282, 96852321, 26863229, 19939935, 17957593, 83133790, 51715482, 72019362, 28928175, 75128745, 58749, 11923835, 65271999, 6221471, 62357986, 9623492, 2208785, 6038457, 74137926, 78602717, 81274566, 14093520, 75229462, 21919959, 88444207, 26392416, 42237907, 54014062, 10358899, 47893286, 749283, 17976208, 15075176, 59405277, 18466635, 34667286, 68128438, 34493392, 94539824, 84166196, 14723410, 98130363, 20836893, 53547802, 65338021, 21070110, 53648154, 81898046, 24619760, 1569515, 83789391, 79738755, 15064655, 19486173, 48360198, 64602895, 13348726, 52613508, 92071990, 39801351, 92867155, 82886381, 21289531, 77301523, 42967683, 86543538, 6111563, 94360702, 43933006, 7300047, 72793444, 85117092, 42307449, 66322959, 78909561, 61815223, 19101477, 61741594, 45428665, 29188588, 9175338, 71300104, 89214616, 54868730, 70372191, 59177718, 63967300, 12303248, 66885828, 36808486, 6808825, 23740167, 7066775, 50007421, 7011964, 17146629, 34044787, 59329511, 49641577, 36135, 81774825, 77898274, 61859143, 40781449, 30811010, 99021067, 50668599, 10264691, 51466049, 83302115, 94711842, 99524975, 43152977, 6819644, 66319530, 67474219, 73021291, 59501487, 97783876, 41092102, 26744917, 37192445, 53632373, 57803235, 6986898, 40686254, 76330843, 14349098, 4064751, 5832946, 34946859, 247198, 94076128, 16380211, 10597197, 30163921, 91255408, 22721500, 6521313, 44550764, 92353856, 62693428, 56531125, 72358170, 88904910, 19457589, 91281584, 57020481, 93566986, 82339363, 90310261, 59109400, 70800879, 75543508, 57241521, 75820087, 88698958, 38510840, 35092039, 8099994, 34698428, 3088684, 66045587, 82427263, 96420429, 55602660, 3183975, 81805959, 23569917, 66903004, 9860968, 60106217, 59981773, 47792865, 55470718, 14326617, 61271144, 36933038, 1788101, 91990218, 30366150, 4199704, 36189527, 6497830, 6157724, 72495719, 62430984, 17857111, 16595436, 24058273, 89499542, 38658347, 22942635, 21673260, 62490109, 15902805, 8807940, 68110330, 38009615, 38061439, 44177011, 61373987, 40027975, 37224844, 5482538, 66116458, 86301513, 5640302, 98739783, 92554120, 38341669, 73168565, 16097038, 4204661, 3233569, 55189057, 40197395, 36685023, 66271566, 2331773, 44847298, 36505482, 51047803, 99690194, 112651, 47708850, 81172706, 4099191, 89811711, 27041967, 52204879, 18131876, 31126490, 59910624, 4091162, 20122224, 92692978, 71920426, 33935899, 1204161, 66428795, 92033260, 68204242, 29401781, 9257405, 29994197, 73786944, 1375023, 24953498, 60955663, 14731700, 67030811, 30218878, 72357096, 5822202, 56424103, 77072625, 77187825, 62428472, 50567636, 89078848, 84127901, 17386996, 83155442, 1250437, 99515901, 52261574, 7502255, 71657078, 40534591, 10453030, 86821229, 74743862, 68694897, 45306070, 67124282, 99125126, 39553046, 32161669, 5267545, 8115266, 51588015, 4095116, 85242963, 59371804, 30139692, 3509435, 78785507, 40865610, 28550822, 48395186, 7104732, 8055981, 73235980, 3880712, 60430369, 26114953, 78549759, 85023028, 95010552, 70782102, 13862149, 51472275, 51590803, 59614746, 64098930, 69848388, 67227442, 32699744, 53393358, 69605283, 86460488, 99604946, 70727211, 93790285, 68000591, 77377183, 61928316, 43045786, 75777973, 61728685, 29959549, 77312810, 16019925, 79880247, 55615885, 26507214, 23110625, 15535065, 85116755, 16099750, 48658605, 7685448, 2204165, 73109997, 99965001, 18783702, 4508700, 43376279, 91664334, 7182642, 55143724, 14045383, 68316156, 7517032, 52060076, 90654836, 27625735, 91938191, 16684829, 50188404, 56153345, 40356877, 8733068, 60393039, 55247455, 20645197, 86242799, 824872, 54606384, 86022504, 33201905, 33565483, 26292919, 36396314, 57240218, 17385531, 54058788, 32151165, 50806615, 72278539, 11365791, 526217, 77300457, 74614639, 36139942, 84349107, 91240048, 83651211, 35512853, 27411561, 90272749, 8634541, 94090109, 80251430, 81855445, 90013093, 68102437, 58224549, 28787861, 53842979, 30998561, 57248122, 30694952, 75052463, 1936762, 68816413, 67084351, 22200378, 48675329, 92398073, 15163258, 7919588, 65715134, 45794415, 78884452, 89046466, 76170907, 17016156, 1022677, 39171895, 53666583, 89804152, 8913721, 76703609, 84293052, 80555751, 49597667, 92541302, 34295794, 39847321, 42073124, 29029316, 37501808, 38022834, 78766358, 79806380, 18699206, 7687278, 86798033, 72777973, 47298834, 79322415, 36780454, 11581181, 17081350, 99224392, 17727650, 47738185, 48774913, 29819940, 48893685, 2917920, 9398733, 95251277, 43506672, 4806458, 26102057, 92215320, 13231279, 69641513, 66611759, 45237957, 66250369, 66667729, 96735716, 62115552, 8651647, 49236559, 9829782, 65454636, 15948937, 97011160, 34236719, 1197320, 61712234, 70596786, 20486294, 22879907, 62232211, 24826575, 67031644, 63059024, 55850790, 54427233, 30653863, 37739481, 1239555, 69786756, 12348118, 93562257, 56473732, 31491938, 36930650, 67513640, 40152546, 90090964, 43501211, 16405341, 33336148, 45667668, 58208470, 86001008, 43357947, 26397786, 57393458, 37957788, 22997281, 20681921, 19898053, 54263880, 176257, 88130087, 47887470, 37183543, 874791, 62936963, 29635537, 79136082, 68644627, 4119747, 89699445, 82779622, 6871053, 73031054, 36753250, 26664538, 4787945, 97940276, 21001913, 87160386, 70004753, 3235882, 62051033, 84904436, 12024238, 44627776, 36312813, 38365584, 45996863, 96709982, 73392814, 68875490, 168541 +69641513, 68702632, 32159704, 4668450, 94911072, 29510992, 9860968, 68128438, 45794415, 8807940, 66271566, 79880247, 49597667, 6793819, 29834512, 26292919, 70800879, 83533741, 66137019, 44473167, 17957593, 66250369, 81805959, 75229462, 61271144, 77272628, 26734892, 20836893, 37224844, 8729146, 78589145, 77301523, 77312810, 85571389, 96193415, 99515901, 89637706, 74614639, 8115266, 22405120, 79942022, 4434662, 33431960, 47361209, 92215320, 98462867, 8055981, 9259676, 98943869, 53802686, 94539824, 34236719, 81853704, 89499542, 59197747, 44784505, 47213183, 16019925, 46851987, 78909561, 72738685, 16054533, 82651278, 54232247, 76960303, 16567550, 60393039, 77187825, 87710366, 68939068, 48774913, 32151165, 79821897, 8696647, 45306070, 62693428, 9886593, 61859581, 44348328, 26863229, 25842078, 76315420, 72019362, 78785507, 61623915, 75153252, 91141395, 82427263, 30694952, 55470718, 10358899, 28734791, 27325428, 81677380, 84166196, 14723410, 61373987, 62552524, 78561158, 65275241, 31161687, 62051033, 29932657, 89217461, 48088883, 82052050, 52293995, 80014588, 4798568, 39847321, 63967300, 92998591, 2204165, 79136082, 70541760, 8791066, 43376279, 96726697, 57359924, 92692978, 29401781, 40356877, 56484900, 24953498, 66319530, 72357096, 57658654, 36930650, 37192445, 5832946, 54058788, 65851721, 86821229, 56531125, 44846932, 77300457, 61960400, 54517921, 68824981, 32161669, 51588015, 45407418, 17764950, 94516935, 2891150, 75543508, 80316608, 33304202, 88653118, 6038457, 91831487, 47792865, 36468541, 26392416, 48675329, 4199704, 11161731, 22997281, 15015906, 85695762, 81898046, 80246713, 12416768, 89046466, 65880522, 8129978, 39801351, 20765474, 61263068, 72793444, 58180653, 66322959, 30653863, 72614359, 36845587, 83948335, 37560164, 29635537, 7685448, 51047803, 71300104, 22766820, 36808486, 6808825, 18504197, 67281495, 51426311, 58751351, 34044787, 71316369, 74357852, 97379790, 49271185, 1204161, 50668599, 68204242, 95726235, 8733068, 99524975, 64055960, 78300864, 83083131, 6819644, 98948034, 30218878, 17037369, 84187166, 12664567, 53632373, 57240218, 40686254, 1250437, 10453030, 96318094, 54987042, 4985896, 16387575, 95251277, 44481640, 93057697, 5267545, 40677414, 4787945, 14947650, 68041839, 29516592, 8373289, 57241521, 94090109, 69579137, 58208470, 13231279, 84684495, 35092039, 16445503, 99658235, 40865610, 66611759, 48395186, 9623492, 79657802, 33628349, 51315460, 85023028, 64848072, 37957788, 4978543, 6525948, 16595436, 65715134, 3633375, 24058273, 20486294, 30891921, 86460488, 38061439, 31733363, 99861373, 88130087, 9575954, 66116458, 92554120, 42644903, 29959549, 33553959, 93933709, 7300047, 17058722, 40197395, 36685023, 54427233, 65081429, 61741594, 415901, 32590267, 92692283, 89214616, 33249630, 43322743, 44889423, 7646095, 93562257, 51464002, 7066775, 4099191, 56461322, 39373729, 77787724, 16971929, 52204879, 2607799, 78766358, 85711894, 14045383, 41481685, 24915585, 72732205, 69355476, 79806380, 24314885, 33935899, 65047700, 14363867, 4069912, 99021067, 87720882, 23194618, 39201414, 12024238, 55247455, 60955663, 67474219, 20140249, 99226875, 74452589, 62428472, 67513640, 45848907, 76330843, 46870723, 99224392, 29819940, 97057187, 66832478, 2717150, 74743862, 11365791, 9398733, 67124282, 56515456, 61141874, 45049308, 32274392, 82339363, 10366309, 69136837, 90310261, 59109400, 23432750, 83651211, 20642888, 4095116, 97940276, 26139110, 49598724, 33336148, 26493119, 97281847, 30139692, 80251430, 21533347, 72274002, 84840549, 26998766, 28928175, 75128745, 58749, 11923835, 34698428, 63372756, 54762643, 45237957, 3088684, 28796059, 26776922, 66045587, 55602660, 75052463, 81274566, 95290172, 42237907, 13862149, 9860195, 65454636, 92398073, 20885148, 17365188, 34493392, 30764367, 54663246, 69848388, 32699744, 53547802, 65338021, 70596786, 19898053, 35456853, 53648154, 73222868, 22942635, 38009615, 64087743, 54263880, 1569515, 20867149, 5482538, 64157906, 13348726, 45990383, 43045786, 95395112, 13819358, 62803858, 61728685, 82886381, 33123618, 4204661, 3233569, 26275734, 98653983, 55189057, 11543098, 874791, 84293052, 62936963, 35192533, 9188443, 16099750, 98371444, 42692881, 74724075, 47708850, 71522968, 37501808, 55960386, 86118021, 47090124, 41442762, 22113133, 29466406, 91664334, 7182642, 77898274, 90004325, 27625735, 19272365, 65074535, 16684829, 31491938, 1375023, 20002147, 77284619, 3294781, 34497327, 9603598, 38256849, 54606384, 11581181, 33565483, 39986008, 84127901, 57803235, 52261574, 17727650, 82779622, 30771409, 7502255, 46540998, 40152546, 37891451, 10597197, 73031054, 669105, 14220886, 6521313, 90090964, 44550764, 68694897, 2917920, 62452034, 88904910, 44627776, 19457589, 65038678, 18833224, 43501211, 15148031, 24733232, 19376156, 91240048, 92803766, 60176618, 90158785, 99549373, 93053405, 45667668, 18001617, 13173644, 81855445, 57961282, 75820087, 19939935, 71965942, 90013093, 43357947, 51715482, 87160386, 68102437, 65271999, 6221471, 73235980, 3880712, 42782652, 78549759, 23569917, 60106217, 42947632, 14326617, 83150534, 95010552, 70782102, 88444207, 36580610, 749283, 6497830, 93515664, 17976208, 38008118, 53393358, 73392814, 55770687, 21070110, 82178706, 44844121, 33061250, 44177011, 30787683, 70004753, 83789391, 70727211, 40027975, 76170907, 55753905, 48360198, 67031644, 30090481, 98648327, 3235882, 52613508, 69255765, 30569392, 98739783, 75777973, 60581278, 21289531, 47887470, 37675718, 23134715, 86543538, 43933006, 30543215, 76671482, 51149804, 85117092, 34698463, 76703609, 84904436, 44847298, 36505482, 7955293, 19101477, 1239555, 91957544, 38365584, 77620120, 92541302, 34295794, 15535065, 85116755, 18411915, 92604458, 96906410, 70372191, 42073124, 112651, 12303248, 27187213, 12348118, 81172706, 63152504, 41245325, 7011964, 4508700, 33237508, 95957797, 18131876, 16424199, 81774825, 40781449, 30811010, 28851716, 4119747, 27665211, 18699206, 91938191, 66428795, 72373496, 73786944, 51466049, 18806856, 37435892, 20645197, 40781854, 14731700, 59501487, 11757872, 41092102, 37166608, 36780454, 33201905, 31727379, 26744917, 45381876, 45996863, 8128637, 89078848, 17386996, 17081350, 14349098, 47738185, 21397057, 59318837, 99971982, 168541, 50806615, 53888755, 72278539, 48893685, 69697787, 5111370, 72358170, 29065964, 91281584, 71083565, 92530431, 44842615, 36139942, 72725103, 4806458, 76540481, 35996293, 97561745, 17539625, 96852321, 83133790, 38510840, 21001913, 17068582, 90457870, 49882705, 88251446, 28550822, 53084256, 10961421, 62357986, 44664587, 508198, 4515343, 79922758, 91802888, 7229550, 3183975, 26114953, 38494874, 8651647, 21919959, 36933038, 84406788, 49236559, 37280276, 9829782, 47893286, 70036438, 67451935, 6157724, 10309525, 18466635, 34667286, 32250045, 51590803, 62430984, 89996536, 59614746, 7919588, 6153406, 21993752, 24619760, 87598888, 12571310, 64602895, 92071990, 86301513, 5640302, 17016156, 1022677, 569864, 6111563, 60102965, 73617245, 37739481, 30463802, 48892201, 41380093, 45428665, 9175338, 54868730, 59177718, 32426752, 70367851, 71469330, 18783702, 56473732, 33699435, 17146629, 99297164, 89811711, 59329511, 5970607, 71333116, 31126490, 88047921, 68316156, 7517032, 61859143, 37659250, 7687278, 92033260, 72238278, 9257405, 29994197, 45919976, 83302115, 43152977, 61982238, 77072625, 79322415, 55669657, 83269727, 50567636, 77413012, 96709982, 4064751, 34946859, 247198, 94076128, 94595668, 44983451, 82897371, 92353856, 321665, 526217, 45075471, 39553046, 83368048, 79191827, 9599614, 23848565, 83210802, 13470059, 26102057, 91727510, 90272749, 19891772, 27185644, 17894977, 22450468, 7104732, 58224549, 36312813, 28787861, 49328608, 96735716, 2208785, 96420429, 74137926, 20568363, 68816413, 14093520, 1788101, 91990218, 57393458, 54014062, 51472275, 90061527, 17857111, 61712234, 78884452, 62232211, 21673260, 62490109, 15902805, 30395570, 79738755, 93790285, 15064655, 75135448, 19486173, 24826575, 76434144, 68000591, 77377183, 61928316, 63059024, 10649306, 16097038, 42199455, 42307449, 51507425, 55615885, 84002370, 66885828, 44245960, 37620363, 6505939, 83747892, 23740167, 50007421, 38022834, 36135, 4091162, 20122224, 82532312, 44479073, 94711842, 47298834, 74441448, 824872, 66663942, 97783876, 15536795, 6986898, 22129328, 34432810, 61897582, 31904591, 93566986, 82327024, 76825057, 35512853, 48673079, 85242963, 59371804, 45790169, 33797252, 45617087, 78218845, 3509435, 86001008, 3233084, 8099994, 93359396, 3487592, 93270084, 66667729, 30998561, 60430369, 57248122, 78602717, 89419466, 30366150, 59405277, 72495719, 97011160, 98130363, 1197320, 68110330, 99604946, 3773993, 176257, 75407347, 7423788, 68875490, 94360702, 48260151, 13468268, 62740044, 80555751, 61815223, 23110625, 48658605, 69786756, 5073754, 29029316, 18663507, 73109997, 99965001, 55143724, 90654836, 74110882, 63015256, 1431742, 86242799, 73021291, 56424103, 62762857, 24168411, 89699445, 99333375, 83155442, 17385531, 67793644, 97641116, 30163921, 22721500, 65017137, 57020481, 36753250, 43506672, 38645117, 92787493, 9058407, 22435353, 88698958, 54199160, 53842979, 33895336, 66903004, 67084351, 36189527, 44915235, 15075176, 15948937, 64098930, 67227442, 20681921, 69605283, 40984766, 38341669, 92867155, 55850790, 37183543, 39171895, 89804152, 99729357, 26507214, 73124510, 75986488, 29188588, 57163802, 99690194, 25636669, 68644627, 49641577, 32058615, 52060076, 71920426, 86798033, 56153345, 62496012, 36396314, 50702367, 82726008, 44060493, 16380211, 99125126, 36812683, 99917599, 84349107, 41092172, 27411561, 8634541, 62115552, 1936762, 26397786, 63628376, 15163258, 24591705, 22879907, 82979980, 42967683, 53666583, 8913721, 26063929, 2331773, 59910624, 72777973, 10264691, 67030811, 86022504, 56955985, 40534591, 6871053, 26664538, 16405341, 95581843, 59981773, 22200378, 73168565, 77694700, 50188404, 5822202, 70420215, 71657078, 91255408, 14626618, 38658347, 22108665, 27041967, 45995325, 63506281 +7517032, 37166608, 22450468, 39171895, 17058722, 94911072, 34493392, 98739783, 76703609, 74357852, 99971982, 48673079, 13231279, 28928175, 28550822, 9623492, 15075176, 99604946, 78561158, 8129978, 26275734, 48892201, 25636669, 59329511, 824872, 31727379, 37192445, 96709982, 30163921, 9599614, 38645117, 4787945, 35512853, 38510840, 65271999, 6221471, 78602717, 83150534, 95010552, 26397786, 51472275, 53802686, 7919588, 19898053, 59197747, 52613508, 38341669, 16019925, 32590267, 15535065, 72738685, 92998591, 66885828, 81172706, 71333116, 29466406, 61859143, 40356877, 94711842, 86022504, 62428472, 45996863, 34946859, 32151165, 97057187, 54987042, 90090964, 5111370, 56531125, 99917599, 97561745, 19939935, 8099994, 49882705, 28796059, 33895336, 4199704, 44915235, 37957788, 73392814, 38658347, 73222868, 8807940, 54263880, 89046466, 43045786, 68875490, 42644903, 73168565, 1022677, 58180653, 42199455, 36685023, 85117092, 34698463, 78909561, 29188588, 16099750, 63967300, 44245960, 7646095, 37501808, 68316156, 72777973, 18806856, 8733068, 20645197, 1431742, 20002147, 77072625, 97783876, 74452589, 41092102, 17385531, 5832946, 74743862, 39553046, 61859581, 83210802, 51588015, 41092172, 80316608, 93053405, 33336148, 29510992, 17539625, 84684495, 17068582, 72019362, 40865610, 93270084, 95581843, 82427263, 91802888, 26114953, 66903004, 1788101, 36580610, 84406788, 47893286, 36189527, 9860195, 59405277, 72495719, 15163258, 26734892, 20486294, 80246713, 55753905, 5482538, 77377183, 61928316, 92554120, 92867155, 77301523, 16097038, 42307449, 26507214, 37739481, 35192533, 1239555, 92692283, 6793819, 57163802, 89214616, 32426752, 6808825, 41245325, 23740167, 17146629, 39373729, 43376279, 97379790, 16424199, 81774825, 37659250, 54232247, 1204161, 66428795, 92033260, 44479073, 50668599, 23194618, 12024238, 1375023, 67474219, 5822202, 17037369, 89078848, 52261574, 59318837, 10597197, 34432810, 50806615, 61897582, 44983451, 72278539, 8696647, 2917920, 99125126, 91281584, 92530431, 26664538, 36139942, 83651211, 72725103, 17764950, 4095116, 70800879, 26102057, 85242963, 97940276, 26493119, 13173644, 81855445, 92215320, 44348328, 72274002, 43357947, 76315420, 84840549, 88251446, 10961421, 88653118, 73235980, 66667729, 36312813, 68702632, 79922758, 55602660, 7229550, 62115552, 74137926, 20568363, 8651647, 61271144, 54014062, 6497830, 4978543, 11161731, 20885148, 90061527, 77272628, 94539824, 89996536, 81853704, 16595436, 53547802, 30891921, 3773993, 30395570, 88130087, 76434144, 30090481, 65275241, 29932657, 82052050, 30543215, 76671482, 66271566, 99729357, 13468268, 874791, 79880247, 84293052, 84904436, 30463802, 23110625, 45428665, 7685448, 71300104, 70372191, 12303248, 42692881, 27187213, 29029316, 37620363, 7066775, 4099191, 56473732, 56461322, 33699435, 34044787, 71316369, 95957797, 78766358, 49641577, 52060076, 27665211, 65047700, 72238278, 72373496, 29994197, 37435892, 64055960, 83083131, 98948034, 73021291, 9603598, 54606384, 89699445, 33565483, 56955985, 45848907, 1250437, 99224392, 47738185, 7502255, 71657078, 46540998, 40152546, 37891451, 73031054, 67793644, 86821229, 91255408, 82897371, 69697787, 65017137, 72358170, 9886593, 88904910, 36812683, 19457589, 36753250, 71083565, 45075471, 79191827, 24733232, 93057697, 19376156, 84349107, 90310261, 23432750, 4806458, 20642888, 66137019, 91727510, 49598724, 45617087, 94090109, 21533347, 3509435, 88698958, 17894977, 93359396, 87160386, 26998766, 78785507, 53084256, 58749, 63372756, 48395186, 54762643, 7104732, 53842979, 66045587, 38494874, 81274566, 60106217, 36933038, 57393458, 10358899, 49236559, 93515664, 67451935, 15948937, 32250045, 97011160, 32159704, 38008118, 14723410, 98130363, 65715134, 45794415, 70596786, 85695762, 21993752, 81898046, 22942635, 40984766, 64087743, 38061439, 33061250, 30787683, 1569515, 176257, 79738755, 93790285, 15064655, 75135448, 64602895, 67031644, 22108665, 9575954, 3235882, 92071990, 69255765, 75407347, 66116458, 86301513, 95395112, 61728685, 55850790, 82886381, 33123618, 37675718, 569864, 40197395, 63506281, 54427233, 51507425, 73617245, 30653863, 46851987, 61815223, 7955293, 73124510, 41380093, 75986488, 51047803, 92604458, 54868730, 42073124, 12348118, 93562257, 55960386, 86118021, 38022834, 16971929, 85711894, 30811010, 72732205, 92692978, 82532312, 18699206, 76960303, 56153345, 85571389, 45919976, 83302115, 78300864, 60955663, 40781854, 6819644, 77284619, 61982238, 57658654, 87710366, 36780454, 45381876, 8128637, 36396314, 57803235, 6986898, 17386996, 29819940, 40534591, 6871053, 669105, 2717150, 48893685, 9398733, 89637706, 44627776, 57020481, 95251277, 43506672, 61960400, 83368048, 82339363, 68824981, 10366309, 99549373, 9058407, 76540481, 27411561, 14947650, 22435353, 4434662, 33797252, 97281847, 90272749, 30139692, 8373289, 57241521, 19891772, 57961282, 69641513, 17957593, 71965942, 90013093, 75153252, 34698428, 66611759, 62357986, 58224549, 3088684, 66250369, 79657802, 508198, 2208785, 4515343, 96420429, 3183975, 1936762, 9860968, 91831487, 68816413, 89419466, 88444207, 42237907, 22200378, 64848072, 70036438, 14626618, 62430984, 59614746, 34236719, 6525948, 3633375, 55770687, 21070110, 22879907, 44844121, 15902805, 86460488, 24619760, 44177011, 61373987, 20867149, 40027975, 8729146, 76170907, 19486173, 78589145, 64157906, 98648327, 68000591, 10649306, 62051033, 61263068, 37183543, 93933709, 42967683, 48088883, 53666583, 51149804, 52293995, 8913721, 48260151, 80014588, 2331773, 84002370, 4798568, 44847298, 36505482, 92541302, 9188443, 98371444, 96906410, 112651, 5073754, 70367851, 73109997, 6505939, 70541760, 99965001, 18783702, 47090124, 58751351, 68644627, 18131876, 2607799, 96726697, 31126490, 59910624, 32058615, 27625735, 69355476, 71920426, 79806380, 24314885, 91938191, 7687278, 19272365, 65074535, 4069912, 16684829, 87720882, 68204242, 9257405, 39201414, 16567550, 51466049, 56484900, 24953498, 4668450, 14731700, 67030811, 59501487, 11757872, 79322415, 77187825, 33201905, 11581181, 53632373, 50702367, 46870723, 17727650, 82779622, 21397057, 44060493, 54058788, 79821897, 45995325, 168541, 66832478, 14220886, 92353856, 65038678, 31904591, 93566986, 82327024, 91240048, 60176618, 68041839, 18001617, 78218845, 75820087, 25842078, 83133790, 33304202, 3487592, 44664587, 28787861, 54199160, 96735716, 60430369, 57248122, 23569917, 98943869, 85023028, 59981773, 47792865, 75229462, 36468541, 67084351, 30366150, 9829782, 48675329, 17976208, 10309525, 18466635, 17365188, 22997281, 84166196, 64098930, 15015906, 20681921, 24058273, 65338021, 78884452, 35456853, 62232211, 21673260, 12416768, 48360198, 13348726, 45990383, 44784505, 30569392, 63059024, 7423788, 47887470, 17016156, 77312810, 82979980, 23134715, 3233569, 89804152, 72793444, 55189057, 66322959, 83948335, 77620120, 85116755, 69786756, 44889423, 36808486, 63152504, 79136082, 51426311, 50007421, 99297164, 33237508, 22113133, 91664334, 55143724, 14045383, 16054533, 90654836, 49271185, 63015256, 50188404, 99021067, 29401781, 95726235, 47298834, 74441448, 20140249, 62496012, 36930650, 55669657, 84187166, 26292919, 77413012, 84127901, 83155442, 22129328, 30771409, 247198, 94076128, 65851721, 53888755, 11365791, 68694897, 62452034, 44846932, 61141874, 29065964, 16387575, 18833224, 74614639, 44842615, 5267545, 76825057, 8115266, 79942022, 35996293, 2891150, 75543508, 59371804, 8634541, 69579137, 98462867, 96852321, 26863229, 27185644, 16445503, 90457870, 51715482, 99658235, 75128745, 91141395, 49328608, 6038457, 81805959, 78549759, 51315460, 14326617, 95290172, 26392416, 63628376, 37280276, 749283, 28734791, 92398073, 34667286, 30764367, 54663246, 1197320, 61712234, 32699744, 6153406, 53393358, 83789391, 70727211, 87598888, 99861373, 37224844, 12571310, 62552524, 20765474, 31161687, 75777973, 60581278, 33553959, 60102965, 98653983, 55615885, 80555751, 19101477, 38365584, 37560164, 18411915, 22766820, 43322743, 74724075, 47708850, 18663507, 41442762, 89811711, 77787724, 27041967, 88047921, 77898274, 24915585, 14363867, 99524975, 55247455, 86242799, 30218878, 99226875, 66663942, 56424103, 96193415, 62762857, 38256849, 50567636, 15536795, 40686254, 17081350, 82726008, 48774913, 96318094, 97641116, 6521313, 67124282, 56515456, 526217, 43501211, 54517921, 15148031, 32161669, 23848565, 22405120, 16405341, 83533741, 26139110, 45790169, 33431960, 47361209, 86001008, 61623915, 8055981, 45237957, 26776922, 30694952, 42947632, 55470718, 21919959, 27325428, 81677380, 68128438, 17857111, 24591705, 69605283, 53648154, 24826575, 5640302, 89217461, 86543538, 4204661, 11543098, 26063929, 415901, 49597667, 34295794, 39847321, 9175338, 48658605, 99690194, 77694700, 83747892, 67281495, 4508700, 29834512, 52204879, 57359924, 90004325, 4119747, 73786944, 60393039, 31491938, 66319530, 72357096, 34497327, 70420215, 26744917, 39986008, 12664567, 99515901, 76330843, 4064751, 45306070, 62693428, 77300457, 32274392, 44481640, 59109400, 13470059, 92787493, 45667668, 80251430, 3233084, 21001913, 35092039, 11923835, 42782652, 33628349, 9259676, 75052463, 70782102, 91990218, 6157724, 65454636, 51590803, 20836893, 67227442, 89499542, 62490109, 70004753, 65880522, 39801351, 62803858, 21289531, 29959549, 62740044, 72614359, 36845587, 62936963, 91957544, 29635537, 59177718, 33249630, 2204165, 71469330, 51464002, 7011964, 8791066, 5970607, 4091162, 20122224, 40781449, 74110882, 10264691, 83269727, 57240218, 10453030, 16380211, 44550764, 69136837, 92803766, 40677414, 90158785, 45407418, 58208470, 3880712, 30998561, 68110330, 38009615, 31733363, 47213183, 7300047, 65081429, 61741594, 7182642, 41481685, 36135, 82651278, 28851716, 33935899, 86798033, 43152977, 3294781, 24168411, 68939068, 67513640, 94595668, 22721500, 94516935, 29516592, 68102437, 14093520, 13819358, 94360702, 99333375, 14349098, 4985896, 321665, 45049308, 69848388, 6111563, 71522968, 18504197, 43933006, 82178706, 44473167, 13862149 +33237508, 3880712, 93566986, 66611759, 11161731, 90061527, 37501808, 1431742, 31727379, 33895336, 9259676, 17365188, 15902805, 92692283, 12348118, 55143724, 41481685, 67030811, 53888755, 56515456, 16387575, 31904591, 45407418, 21533347, 57961282, 3233084, 33304202, 65271999, 68816413, 54014062, 6157724, 81853704, 20681921, 73222868, 61728685, 37675718, 37183543, 7300047, 55189057, 52293995, 51507425, 32590267, 41380093, 37620363, 6808825, 56461322, 47090124, 59910624, 90654836, 37659250, 16684829, 23194618, 29994197, 66663942, 34497327, 86022504, 84127901, 45995325, 91255408, 69697787, 8696647, 88904910, 92530431, 84349107, 23432750, 83533741, 2891150, 18001617, 57241521, 13173644, 72019362, 61623915, 53084256, 63372756, 66045587, 49328608, 60430369, 81805959, 81274566, 14093520, 749283, 37957788, 34667286, 67227442, 65338021, 78884452, 69605283, 76170907, 67031644, 78561158, 92071990, 66116458, 31161687, 60581278, 39171895, 72793444, 36685023, 99729357, 61815223, 34295794, 39847321, 112651, 22766820, 43322743, 6505939, 7066775, 55960386, 4099191, 25636669, 32058615, 61859143, 19272365, 45919976, 10264691, 16567550, 60393039, 86242799, 14731700, 99226875, 96193415, 37166608, 39986008, 36396314, 40686254, 16380211, 34432810, 65851721, 61897582, 669105, 72278539, 11365791, 2917920, 321665, 89637706, 94911072, 61141874, 71083565, 15148031, 61859581, 9599614, 51588015, 93053405, 97281847, 33431960, 80251430, 17539625, 92215320, 83133790, 22450468, 93359396, 87160386, 78785507, 54762643, 58224549, 75229462, 14326617, 70782102, 70036438, 93515664, 15075176, 65454636, 32250045, 59614746, 3633375, 53547802, 45794415, 99604946, 64602895, 5482538, 61928316, 45990383, 52613508, 68875490, 38341669, 33123618, 1022677, 77301523, 42967683, 43933006, 17058722, 51149804, 13468268, 73617245, 30653863, 72614359, 19101477, 23110625, 15535065, 29188588, 29635537, 57163802, 92604458, 92998591, 42692881, 36808486, 83747892, 50007421, 56473732, 7011964, 39373729, 7182642, 20122224, 54232247, 71920426, 1204161, 65047700, 86798033, 63015256, 50668599, 29401781, 73786944, 31491938, 98948034, 30218878, 77072625, 9603598, 36930650, 29819940, 247198, 94076128, 97641116, 66832478, 6521313, 44550764, 74743862, 62452034, 99125126, 43501211, 74614639, 61960400, 83368048, 82339363, 44842615, 93057697, 36139942, 40677414, 13470059, 4095116, 70800879, 97940276, 66137019, 22435353, 45617087, 58208470, 3509435, 44348328, 88698958, 3487592, 7104732, 73235980, 44664587, 79657802, 53842979, 74137926, 26114953, 21919959, 95010552, 42237907, 57393458, 9829782, 67451935, 10309525, 62430984, 26734892, 24591705, 21070110, 53648154, 12416768, 64087743, 38061439, 44177011, 20867149, 83789391, 93790285, 8729146, 12571310, 75135448, 3235882, 69255765, 8129978, 63059024, 92554120, 42644903, 65275241, 62051033, 62803858, 29932657, 61263068, 77312810, 569864, 33553959, 93933709, 48088883, 66271566, 42307449, 63506281, 80555751, 92541302, 89214616, 99690194, 69786756, 63967300, 33249630, 66885828, 5073754, 27187213, 44889423, 47708850, 7646095, 93562257, 73109997, 63152504, 70541760, 8791066, 41442762, 89811711, 71316369, 52204879, 88047921, 85711894, 14045383, 97379790, 81774825, 90004325, 69355476, 68204242, 72238278, 43152977, 4668450, 6819644, 77284619, 59501487, 41092102, 24168411, 36780454, 99333375, 56955985, 26292919, 57803235, 7502255, 96318094, 59318837, 10597197, 50806615, 54987042, 4985896, 29065964, 77300457, 45075471, 26664538, 82327024, 69136837, 83210802, 76825057, 90158785, 72725103, 99549373, 9058407, 48673079, 27411561, 33797252, 97561745, 78218845, 19891772, 29510992, 69641513, 98462867, 86001008, 17957593, 21001913, 27185644, 17894977, 8099994, 90457870, 49882705, 88251446, 45237957, 57248122, 91802888, 6038457, 62115552, 23569917, 33628349, 38494874, 98943869, 91831487, 89419466, 88444207, 1788101, 26397786, 36580610, 47893286, 48675329, 4199704, 28734791, 59405277, 18466635, 81677380, 30764367, 84166196, 14723410, 69848388, 7919588, 32699744, 55770687, 38658347, 35456853, 22879907, 62232211, 22942635, 38009615, 54263880, 89046466, 70727211, 79738755, 15064655, 55753905, 59197747, 24826575, 48360198, 88130087, 64157906, 22108665, 44784505, 30569392, 95395112, 13819358, 55850790, 82886381, 17016156, 82979980, 94360702, 11543098, 85117092, 8913721, 76703609, 66322959, 874791, 84904436, 46851987, 4798568, 44847298, 36845587, 61741594, 37560164, 6793819, 9188443, 18411915, 98371444, 71300104, 42073124, 74724075, 29029316, 44245960, 71522968, 2204165, 51426311, 18783702, 86118021, 68644627, 95957797, 29466406, 27041967, 96726697, 16424199, 68316156, 77898274, 57359924, 4091162, 24915585, 27625735, 74110882, 91938191, 49271185, 14363867, 4069912, 87720882, 72777973, 72373496, 85571389, 95726235, 51466049, 83302115, 47298834, 1375023, 56484900, 40781854, 20002147, 66319530, 824872, 70420215, 62762857, 79322415, 97783876, 54606384, 87710366, 83269727, 89699445, 84187166, 26744917, 45996863, 8128637, 57240218, 96709982, 17385531, 1250437, 47738185, 30771409, 10453030, 32151165, 73031054, 82897371, 92353856, 72358170, 44627776, 95251277, 54517921, 79191827, 91240048, 92803766, 8115266, 14947650, 59371804, 68041839, 33336148, 30139692, 94090109, 47361209, 13231279, 43357947, 51715482, 84840549, 99658235, 28550822, 34698428, 48395186, 88653118, 62357986, 66250369, 36312813, 28787861, 42782652, 508198, 79922758, 55602660, 78602717, 20568363, 66903004, 42947632, 95290172, 36933038, 13862149, 22200378, 9860195, 53802686, 72495719, 34493392, 15163258, 94539824, 6525948, 17857111, 65715134, 24058273, 21993752, 81898046, 3773993, 19486173, 78589145, 76434144, 75407347, 73168565, 21289531, 42199455, 84293052, 55615885, 65081429, 48892201, 415901, 77620120, 9175338, 16099750, 48658605, 7685448, 96906410, 12303248, 77694700, 71469330, 67281495, 99965001, 33699435, 4508700, 34044787, 77787724, 71333116, 78766358, 16054533, 49641577, 82651278, 52060076, 40781449, 24314885, 18699206, 33935899, 66428795, 92033260, 50188404, 9257405, 18806856, 12024238, 37435892, 24953498, 74441448, 61982238, 56424103, 55669657, 50567636, 45381876, 12664567, 77413012, 52261574, 22129328, 4064751, 44060493, 54058788, 6871053, 97057187, 168541, 30163921, 44983451, 90090964, 45306070, 62693428, 65017137, 9886593, 91281584, 526217, 65038678, 32274392, 44481640, 24733232, 32161669, 10366309, 5267545, 19376156, 90310261, 38645117, 60176618, 4787945, 4806458, 92787493, 17764950, 41092172, 76540481, 85242963, 35996293, 45790169, 26493119, 90272749, 44473167, 81855445, 75820087, 84684495, 25842078, 38510840, 72274002, 76315420, 10961421, 8055981, 9623492, 93270084, 28796059, 26776922, 85023028, 59981773, 55470718, 83150534, 36468541, 26392416, 91990218, 30366150, 63628376, 37280276, 64848072, 51472275, 6497830, 44915235, 20885148, 97011160, 38008118, 54663246, 16595436, 19898053, 44844121, 40984766, 68110330, 33061250, 24619760, 30787683, 70004753, 176257, 40027975, 65880522, 13348726, 30090481, 98648327, 9575954, 7423788, 5640302, 92867155, 29959549, 89217461, 16097038, 3233569, 26275734, 89804152, 82052050, 30543215, 76671482, 34698463, 26063929, 16019925, 26507214, 78909561, 36505482, 7955293, 1239555, 83948335, 38365584, 73124510, 85116755, 54868730, 70372191, 59177718, 18663507, 79136082, 51464002, 23740167, 99297164, 22113133, 59329511, 29834512, 16971929, 18131876, 43376279, 74357852, 36135, 28851716, 72732205, 4119747, 79806380, 99021067, 94711842, 99524975, 55247455, 67474219, 20140249, 5822202, 77187825, 38256849, 33201905, 6986898, 82726008, 14349098, 46870723, 17727650, 34946859, 79821897, 40152546, 99971982, 14220886, 67124282, 57020481, 18833224, 23848565, 83651211, 35512853, 20642888, 22405120, 16405341, 26102057, 79942022, 26139110, 8373289, 8634541, 69579137, 96852321, 19939935, 90013093, 35092039, 16445503, 26998766, 68102437, 28928175, 40865610, 75153252, 11923835, 6221471, 3088684, 95581843, 54199160, 96735716, 96420429, 7229550, 8651647, 1936762, 9860968, 47792865, 61271144, 67084351, 84406788, 10358899, 49236559, 27325428, 77272628, 34236719, 53393358, 73392814, 8807940, 61373987, 1569515, 99861373, 68000591, 62552524, 47213183, 20765474, 23134715, 6111563, 4204661, 54427233, 80014588, 2331773, 62740044, 62936963, 49597667, 72738685, 75986488, 45428665, 32426752, 81172706, 58751351, 30811010, 92692978, 27665211, 7687278, 76960303, 40356877, 64055960, 83083131, 60955663, 73021291, 57658654, 62428472, 17037369, 68939068, 45848907, 50702367, 76330843, 99224392, 21397057, 71657078, 40534591, 37891451, 94595668, 22721500, 44846932, 19457589, 36753250, 39553046, 71965942, 17068582, 91141395, 4515343, 78549759, 36189527, 15948937, 14626618, 68128438, 32159704, 1197320, 6153406, 20486294, 82178706, 80246713, 31733363, 37224844, 86301513, 43045786, 75777973, 47887470, 86543538, 53666583, 98653983, 40197395, 79880247, 37739481, 30463802, 51047803, 18504197, 41245325, 38022834, 31126490, 65074535, 39201414, 20645197, 11757872, 74452589, 33565483, 37192445, 17081350, 82779622, 46540998, 86821229, 5111370, 36812683, 99917599, 91727510, 49598724, 45667668, 26863229, 66667729, 68702632, 82427263, 51315460, 60106217, 92398073, 22997281, 89996536, 64098930, 98130363, 15015906, 89499542, 21673260, 86460488, 30395570, 10649306, 58180653, 48260151, 84002370, 35192533, 70367851, 5970607, 91664334, 44479073, 56153345, 78300864, 72357096, 62496012, 67513640, 15536795, 89078848, 53632373, 17386996, 83155442, 5832946, 67793644, 48893685, 68694897, 43506672, 68824981, 59109400, 30998561, 3183975, 20836893, 61712234, 70596786, 62490109, 77377183, 60102965, 17146629, 2607799, 7517032, 82532312, 3294781, 11581181, 99515901, 48774913, 94516935, 80316608, 29516592, 75128745, 2208785, 75052463, 4978543, 17976208, 85695762, 98739783, 91957544, 8733068, 9398733, 56531125, 75543508, 4434662, 58749, 51590803, 39801351, 30694952, 30891921, 87598888, 2717150, 45049308 +60393039, 45381876, 26493119, 11161731, 72238278, 508198, 36505482, 31126490, 36780454, 67513640, 68694897, 88444207, 75135448, 39171895, 60102965, 63152504, 57359924, 92692978, 71920426, 22721500, 57020481, 60176618, 23432750, 26863229, 68702632, 49328608, 81805959, 67227442, 89217461, 82979980, 98653983, 74724075, 37501808, 41442762, 59329511, 2607799, 43376279, 55143724, 41481685, 74110882, 44479073, 56153345, 95726235, 86242799, 56955985, 53888755, 56515456, 62693428, 72358170, 24733232, 76825057, 98462867, 33304202, 28928175, 8055981, 93270084, 42782652, 78602717, 749283, 4199704, 98130363, 61712234, 3633375, 78884452, 99604946, 13819358, 31161687, 82886381, 47887470, 37675718, 569864, 16097038, 72793444, 30653863, 84002370, 34295794, 72738685, 75986488, 39847321, 16099750, 37620363, 51464002, 99965001, 22113133, 16971929, 27625735, 37659250, 54232247, 66428795, 73786944, 6819644, 77284619, 98948034, 5822202, 79322415, 55669657, 8128637, 57240218, 82726008, 7502255, 32151165, 6871053, 669105, 321665, 61141874, 77300457, 18833224, 54517921, 93566986, 32161669, 10366309, 3233084, 17957593, 35092039, 16445503, 87160386, 75128745, 65271999, 79657802, 96735716, 51315460, 70782102, 67084351, 54014062, 70036438, 15075176, 53802686, 27325428, 97011160, 68128438, 69605283, 82178706, 30395570, 61373987, 83789391, 8729146, 55753905, 63059024, 98739783, 92554120, 33553959, 42967683, 17058722, 58180653, 51149804, 44847298, 48658605, 43322743, 77694700, 29029316, 44889423, 18663507, 93562257, 51426311, 4099191, 25636669, 17146629, 89811711, 77787724, 88047921, 85711894, 28851716, 86798033, 65074535, 14363867, 50668599, 72373496, 83302115, 36930650, 83269727, 33565483, 17081350, 30771409, 94595668, 65851721, 73031054, 72278539, 82897371, 8696647, 65038678, 83368048, 9599614, 83210802, 4806458, 22405120, 35996293, 66137019, 2891150, 75543508, 22435353, 45617087, 44473167, 81855445, 57961282, 75820087, 71965942, 21001913, 28550822, 3487592, 45237957, 30998561, 62115552, 74137926, 98943869, 9860968, 91831487, 55470718, 83150534, 26392416, 49236559, 64848072, 9829782, 93515664, 28734791, 92398073, 15948937, 34667286, 22997281, 62430984, 32159704, 24591705, 65715134, 6153406, 19898053, 73392814, 89499542, 21673260, 40984766, 64087743, 44177011, 79738755, 59197747, 24826575, 48360198, 88130087, 30090481, 61928316, 44784505, 47213183, 78561158, 92071990, 68875490, 92867155, 93933709, 48088883, 52293995, 16019925, 13468268, 54427233, 73617245, 37739481, 77620120, 29188588, 57163802, 9175338, 7685448, 51047803, 32426752, 42692881, 44245960, 36808486, 58751351, 38022834, 71333116, 59910624, 32058615, 77898274, 52060076, 4091162, 20122224, 40781449, 27665211, 24314885, 49271185, 92033260, 39201414, 51466049, 20645197, 66319530, 3294781, 72357096, 66663942, 77187825, 68939068, 39986008, 37192445, 26292919, 53632373, 17386996, 40686254, 4064751, 45995325, 247198, 94076128, 61897582, 66832478, 2717150, 92353856, 5111370, 94911072, 99125126, 91281584, 43501211, 71083565, 68824981, 44842615, 69136837, 59109400, 8115266, 13470059, 45407418, 4095116, 59371804, 33797252, 49598724, 90272749, 8373289, 94090109, 58208470, 92215320, 13231279, 25842078, 83133790, 90457870, 75153252, 63372756, 54762643, 73235980, 9623492, 95581843, 66045587, 2208785, 96420429, 57248122, 79922758, 55602660, 7229550, 6038457, 9259676, 75052463, 8651647, 59981773, 95290172, 1788101, 48675329, 77272628, 34236719, 6525948, 69848388, 20486294, 22942635, 80246713, 12416768, 3773993, 99861373, 93790285, 5482538, 9575954, 3235882, 66116458, 8129978, 95395112, 42644903, 38341669, 1022677, 61263068, 23134715, 43933006, 7300047, 89804152, 34698463, 76703609, 84904436, 62740044, 72614359, 30463802, 91957544, 41380093, 92692283, 85116755, 12303248, 5073754, 47708850, 70367851, 71469330, 67281495, 7011964, 8791066, 29466406, 27041967, 91664334, 96726697, 78766358, 7182642, 16054533, 30811010, 33935899, 63015256, 72777973, 29994197, 45919976, 10264691, 31491938, 37435892, 24953498, 62496012, 74452589, 86022504, 89699445, 14349098, 17727650, 48774913, 5832946, 59318837, 168541, 30163921, 86821229, 6521313, 9398733, 65017137, 44846932, 44627776, 36812683, 29065964, 526217, 95251277, 32274392, 39553046, 31904591, 90158785, 83651211, 4787945, 17764950, 9058407, 97940276, 91727510, 80316608, 68041839, 97281847, 8634541, 3509435, 84684495, 19939935, 90013093, 76315420, 49882705, 40865610, 61623915, 34698428, 44664587, 33895336, 26776922, 38494874, 1936762, 75229462, 14326617, 61271144, 36580610, 84406788, 10358899, 37957788, 17976208, 20885148, 14626618, 89996536, 84166196, 64098930, 14723410, 26734892, 32699744, 53393358, 85695762, 21070110, 73222868, 30891921, 86460488, 38061439, 54263880, 30787683, 20867149, 87598888, 31733363, 40027975, 65880522, 76434144, 67031644, 22108665, 69255765, 30569392, 7423788, 65275241, 29932657, 29959549, 86543538, 6111563, 40197395, 66271566, 48260151, 65081429, 2331773, 61815223, 73124510, 29635537, 71300104, 92604458, 112651, 33249630, 2204165, 7646095, 6505939, 7066775, 55960386, 86118021, 47090124, 39373729, 34044787, 33237508, 5970607, 29834512, 74357852, 16424199, 82532312, 18699206, 68204242, 8733068, 99524975, 4668450, 67474219, 824872, 99226875, 96193415, 70420215, 77072625, 38256849, 41092102, 54606384, 24168411, 87710366, 33201905, 99333375, 15536795, 12664567, 57803235, 6986898, 46870723, 47738185, 44060493, 10453030, 37891451, 16380211, 10597197, 54987042, 91255408, 90090964, 48893685, 69697787, 45306070, 56531125, 89637706, 9886593, 88904910, 36753250, 92530431, 44481640, 26664538, 82327024, 61859581, 23848565, 91240048, 40677414, 90310261, 51588015, 16405341, 48673079, 76540481, 79942022, 94516935, 26139110, 93053405, 18001617, 30139692, 33431960, 13173644, 29510992, 69579137, 47361209, 96852321, 27185644, 17894977, 17068582, 72019362, 88251446, 11923835, 66611759, 36312813, 3880712, 82427263, 91802888, 26114953, 23569917, 33628349, 20568363, 85023028, 47792865, 42237907, 57393458, 51472275, 9860195, 59405277, 18466635, 32250045, 15163258, 94539824, 30764367, 38008118, 20836893, 81853704, 20681921, 16595436, 21993752, 22879907, 62232211, 38009615, 24619760, 70004753, 70727211, 68000591, 45990383, 75407347, 5640302, 61728685, 60581278, 17016156, 3233569, 53666583, 42199455, 55189057, 99729357, 26063929, 51507425, 79880247, 55615885, 26507214, 78909561, 35192533, 7955293, 19101477, 83948335, 49597667, 9188443, 59177718, 63967300, 92998591, 66885828, 27187213, 81172706, 73109997, 18504197, 23740167, 18783702, 99297164, 52204879, 18131876, 36135, 7517032, 61859143, 90004325, 4119747, 16684829, 87720882, 23194618, 29401781, 85571389, 43152977, 56484900, 55247455, 64055960, 83083131, 40781854, 61982238, 56424103, 34497327, 62762857, 62428472, 17037369, 1250437, 76330843, 99971982, 50806615, 44983451, 4985896, 44550764, 74743862, 11365791, 43506672, 74614639, 61960400, 45075471, 99917599, 82339363, 15148031, 93057697, 19376156, 38645117, 35512853, 99549373, 92787493, 85242963, 45790169, 45667668, 57241521, 86001008, 38510840, 8099994, 22450468, 93359396, 53084256, 10961421, 7104732, 58224549, 54199160, 30694952, 66903004, 81274566, 42947632, 14093520, 21919959, 36189527, 44915235, 67451935, 4978543, 6157724, 10309525, 51590803, 81677380, 34493392, 7919588, 15015906, 53547802, 45794415, 35456853, 53648154, 176257, 64602895, 62552524, 43045786, 62051033, 62803858, 55850790, 33123618, 77312810, 94360702, 26275734, 82052050, 30543215, 11543098, 63506281, 80014588, 36845587, 62936963, 61741594, 38365584, 92541302, 15535065, 45428665, 6793819, 18411915, 54868730, 99690194, 12348118, 71522968, 79136082, 6808825, 41245325, 68644627, 49641577, 68316156, 79806380, 76960303, 4069912, 50188404, 18806856, 78300864, 60955663, 20140249, 11757872, 37166608, 31727379, 84187166, 26744917, 45996863, 36396314, 50702367, 52261574, 22129328, 99224392, 46540998, 54058788, 14220886, 2917920, 19457589, 79191827, 92803766, 41092172, 33336148, 97561745, 29516592, 78218845, 21533347, 69641513, 43357947, 26998766, 68102437, 48395186, 88653118, 66250369, 28787861, 28796059, 53842979, 78549759, 68816413, 89419466, 36933038, 37280276, 47893286, 6497830, 54663246, 17857111, 65338021, 38658347, 12571310, 78589145, 13348726, 98648327, 10649306, 4204661, 36685023, 76671482, 8913721, 874791, 84293052, 46851987, 1239555, 37560164, 23110625, 89214616, 22766820, 70541760, 56461322, 4508700, 71316369, 24915585, 7687278, 99021067, 9257405, 94711842, 74441448, 14731700, 20002147, 67030811, 30218878, 73021291, 59501487, 11581181, 50567636, 45848907, 84127901, 82779622, 21397057, 71657078, 40534591, 40152546, 34432810, 67793644, 67124282, 62452034, 84349107, 72725103, 26102057, 14947650, 80251430, 19891772, 44348328, 88698958, 51715482, 78785507, 6221471, 3088684, 95010552, 26397786, 13862149, 63628376, 22200378, 90061527, 17365188, 59614746, 1197320, 24058273, 70596786, 8807940, 33061250, 1569515, 76170907, 19486173, 64157906, 77377183, 75777973, 73168565, 21289531, 37183543, 98371444, 70372191, 69786756, 42073124, 83747892, 56473732, 97379790, 81774825, 82651278, 90654836, 69355476, 91938191, 1204161, 40356877, 12024238, 47298834, 1375023, 57658654, 9603598, 97783876, 77413012, 89078848, 29819940, 96318094, 97057187, 16387575, 5267545, 36139942, 4434662, 17539625, 72274002, 58749, 91141395, 60106217, 30366150, 65454636, 72495719, 81898046, 44844121, 68110330, 52613508, 86301513, 39801351, 85117092, 42307449, 66322959, 415901, 32590267, 96906410, 50007421, 33699435, 14045383, 72732205, 65047700, 83155442, 96709982, 99515901, 34946859, 83533741, 66667729, 60430369, 3183975, 36468541, 91990218, 89046466, 15064655, 77301523, 4798568, 48892201, 19272365, 1431742, 17385531, 79821897, 97641116, 45049308, 27411561, 84840549, 99658235, 62357986, 4515343, 62490109, 37224844, 80555751, 95957797, 20642888, 70800879, 55770687, 16567550, 15902805, 20765474 +41092102, 83210802, 83533741, 51715482, 36312813, 53547802, 85695762, 55753905, 65880522, 64157906, 66116458, 55143724, 66319530, 99549373, 57961282, 81274566, 67451935, 62232211, 12571310, 68875490, 20765474, 11543098, 34295794, 66885828, 63152504, 55247455, 30218878, 66663942, 44060493, 46540998, 54058788, 9886593, 82339363, 4806458, 30139692, 17068582, 76315420, 6038457, 9259676, 83150534, 91990218, 14723410, 20836893, 20486294, 20867149, 59197747, 48360198, 86301513, 73168565, 61263068, 78909561, 415901, 32590267, 92692283, 71469330, 36808486, 51464002, 49641577, 71920426, 33935899, 39201414, 10264691, 60393039, 74452589, 86022504, 99224392, 669105, 6521313, 90090964, 44550764, 92353856, 56531125, 29065964, 18833224, 45075471, 38645117, 51588015, 27411561, 35996293, 69579137, 17539625, 98462867, 96852321, 22450468, 91141395, 93270084, 66250369, 30998561, 3183975, 47893286, 93515664, 4978543, 10309525, 22997281, 89996536, 83789391, 70727211, 79738755, 5482538, 22108665, 69255765, 5640302, 92867155, 82979980, 93933709, 86543538, 16097038, 48088883, 55189057, 66322959, 30653863, 37560164, 9188443, 44889423, 47708850, 71522968, 2204165, 73109997, 91664334, 36135, 82651278, 90654836, 72732205, 4119747, 65047700, 65074535, 56153345, 50668599, 29994197, 40356877, 77284619, 20140249, 57658654, 68939068, 39986008, 45381876, 40686254, 82726008, 71657078, 69697787, 68824981, 93057697, 60176618, 41092172, 22435353, 94090109, 19939935, 87160386, 78785507, 49882705, 75153252, 53084256, 6221471, 48395186, 54762643, 62357986, 73235980, 7229550, 20568363, 66903004, 21919959, 61271144, 42237907, 84406788, 10358899, 15075176, 65454636, 62430984, 84166196, 16595436, 65715134, 81898046, 68110330, 54263880, 30787683, 78589145, 61928316, 95395112, 31161687, 61728685, 21289531, 60102965, 4204661, 89804152, 4798568, 61741594, 29188588, 39847321, 18411915, 77694700, 29029316, 23740167, 59329511, 74357852, 16424199, 41481685, 61859143, 40781449, 92692978, 24314885, 7687278, 50188404, 31491938, 64055960, 83083131, 40781854, 5822202, 9603598, 55669657, 24168411, 87710366, 62428472, 99333375, 84187166, 67513640, 45996863, 17386996, 1250437, 22129328, 17727650, 82779622, 29819940, 5832946, 247198, 10597197, 44983451, 72278539, 22721500, 2717150, 74743862, 82897371, 68694897, 62693428, 99125126, 57020481, 61960400, 26664538, 23848565, 59109400, 76825057, 92787493, 9058407, 66137019, 2891150, 33336148, 45667668, 57241521, 33431960, 19891772, 29510992, 3233084, 25842078, 75128745, 7104732, 9623492, 55602660, 74137926, 51315460, 59981773, 36468541, 36933038, 30366150, 63628376, 64848072, 36189527, 44915235, 9860195, 53802686, 32250045, 27325428, 81677380, 34236719, 1197320, 26734892, 67227442, 3633375, 45794415, 53393358, 19898053, 73392814, 44844121, 15902805, 12416768, 99604946, 38061439, 33061250, 19486173, 67031644, 68000591, 77312810, 37183543, 42967683, 43933006, 7300047, 40197395, 76703609, 51507425, 36505482, 37739481, 35192533, 49597667, 92541302, 85116755, 98371444, 92604458, 54868730, 32426752, 92998591, 70541760, 7066775, 55960386, 56461322, 25636669, 4508700, 71316369, 68316156, 4091162, 20122224, 28851716, 69355476, 19272365, 66428795, 92033260, 72238278, 45919976, 83302115, 37435892, 20645197, 6819644, 67474219, 73021291, 3294781, 824872, 8128637, 83155442, 96709982, 52261574, 4064751, 32151165, 6871053, 97057187, 66832478, 86821229, 89637706, 61141874, 36753250, 77300457, 71083565, 15148031, 5267545, 91240048, 83651211, 45407418, 17764950, 20642888, 48673079, 4095116, 26102057, 91727510, 8373289, 3509435, 27185644, 33304202, 17894977, 43357947, 90457870, 10961421, 11923835, 34698428, 63372756, 65271999, 66611759, 8055981, 54199160, 26776922, 96735716, 2208785, 60430369, 62115552, 1936762, 42947632, 14093520, 9829782, 48675329, 70036438, 4199704, 11161731, 15948937, 34667286, 72495719, 30764367, 59614746, 38008118, 54663246, 6525948, 61712234, 69605283, 80246713, 8807940, 86460488, 3773993, 61373987, 87598888, 93790285, 37224844, 76170907, 75135448, 64602895, 30569392, 10649306, 62051033, 55850790, 47887470, 29959549, 89217461, 6111563, 3233569, 53666583, 26275734, 72793444, 98653983, 17058722, 30543215, 85117092, 73617245, 65081429, 72614359, 62936963, 7955293, 23110625, 72738685, 6793819, 57163802, 99690194, 70372191, 69786756, 42073124, 33249630, 12303248, 5073754, 12348118, 18663507, 37501808, 18783702, 86118021, 50007421, 33699435, 99297164, 39373729, 34044787, 16971929, 78766358, 7182642, 97379790, 32058615, 52060076, 49271185, 29401781, 72777973, 73786944, 8733068, 74441448, 78300864, 60955663, 4668450, 62496012, 99226875, 61982238, 56424103, 77072625, 79322415, 77187825, 33201905, 17037369, 50567636, 56955985, 37192445, 26292919, 77413012, 36396314, 17081350, 14349098, 48774913, 7502255, 10453030, 79821897, 16380211, 73031054, 91255408, 2917920, 44627776, 16387575, 65038678, 82327024, 61859581, 10366309, 19376156, 84349107, 4787945, 97940276, 59371804, 45790169, 93053405, 26493119, 45617087, 47361209, 58208470, 13231279, 75820087, 84684495, 26863229, 71965942, 35092039, 8099994, 28550822, 61623915, 45237957, 44664587, 3880712, 4515343, 57248122, 75052463, 38494874, 8651647, 98943869, 9860968, 91831487, 85023028, 89419466, 95290172, 36580610, 57393458, 13862149, 51472275, 6497830, 37957788, 90061527, 77272628, 34493392, 32159704, 69848388, 7919588, 24591705, 78884452, 89499542, 53648154, 73222868, 22879907, 38009615, 30395570, 44177011, 31733363, 15064655, 88130087, 13348726, 62552524, 45990383, 3235882, 47213183, 92071990, 8129978, 98739783, 42644903, 29932657, 82886381, 58180653, 42199455, 82052050, 76671482, 66271566, 26063929, 63506281, 13468268, 54427233, 79880247, 84002370, 46851987, 26507214, 44847298, 80555751, 61815223, 19101477, 83948335, 77620120, 51047803, 71300104, 43322743, 42692881, 27187213, 37620363, 99965001, 4099191, 56473732, 17146629, 58751351, 89811711, 22113133, 5970607, 71333116, 29834512, 27041967, 18131876, 96726697, 31126490, 59910624, 81774825, 7517032, 57359924, 24915585, 79806380, 18699206, 63015256, 76960303, 23194618, 18806856, 12024238, 43152977, 86242799, 20002147, 96193415, 54606384, 36780454, 45848907, 76330843, 46870723, 47738185, 30771409, 34946859, 59318837, 99971982, 65851721, 67793644, 168541, 4985896, 14220886, 56515456, 62452034, 65017137, 72358170, 44846932, 36812683, 45049308, 526217, 92530431, 69136837, 8115266, 90158785, 70800879, 4434662, 33797252, 49598724, 68041839, 97561745, 29516592, 8634541, 81855445, 92215320, 44348328, 17957593, 90013093, 68102437, 99658235, 28928175, 58749, 88653118, 66667729, 79657802, 53842979, 91802888, 78549759, 33628349, 68816413, 75229462, 26397786, 22200378, 749283, 28734791, 59405277, 20885148, 51590803, 17365188, 64098930, 98130363, 17857111, 15015906, 81853704, 20681921, 65338021, 55770687, 21993752, 24619760, 99861373, 24826575, 77377183, 9575954, 75407347, 39801351, 13819358, 65275241, 62803858, 60581278, 1022677, 77301523, 33553959, 52293995, 8913721, 84293052, 84904436, 30463802, 48892201, 38365584, 73124510, 45428665, 29635537, 59177718, 7646095, 6505939, 47090124, 7011964, 8791066, 33237508, 68644627, 38022834, 2607799, 88047921, 14045383, 16054533, 77898274, 90004325, 27625735, 74110882, 27665211, 91938191, 14363867, 4069912, 44479073, 68204242, 95726235, 47298834, 99524975, 14731700, 67030811, 98948034, 72357096, 70420215, 83269727, 31727379, 26744917, 53632373, 57240218, 50702367, 17385531, 21397057, 96318094, 45995325, 94076128, 97641116, 30163921, 5111370, 94911072, 88904910, 54517921, 99917599, 44481640, 40677414, 90310261, 13470059, 23432750, 22405120, 94516935, 14947650, 90272749, 80251430, 21533347, 69641513, 21001913, 72274002, 16445503, 72019362, 88251446, 3487592, 58224549, 3088684, 33895336, 66045587, 49328608, 508198, 82427263, 78602717, 81805959, 30694952, 60106217, 47792865, 14326617, 95010552, 70782102, 88444207, 67084351, 54014062, 49236559, 6157724, 14626618, 68128438, 15163258, 6153406, 35456853, 82178706, 22942635, 30891921, 76434144, 98648327, 52613508, 63059024, 569864, 23134715, 39171895, 51149804, 42307449, 16019925, 55615885, 62740044, 36845587, 91957544, 41380093, 96906410, 74724075, 44245960, 70367851, 79136082, 83747892, 18504197, 51426311, 41442762, 77787724, 52204879, 43376279, 54232247, 1204161, 9257405, 72373496, 51466049, 56484900, 1431742, 11757872, 38256849, 15536795, 57803235, 6986898, 40152546, 37891451, 50806615, 61897582, 53888755, 54987042, 11365791, 8696647, 45306070, 32274392, 39553046, 79191827, 32161669, 72725103, 35512853, 85242963, 79942022, 97281847, 44473167, 13173644, 88698958, 95581843, 28796059, 55470718, 26392416, 92398073, 94539824, 32699744, 24058273, 70596786, 40984766, 1569515, 44784505, 78561158, 7423788, 43045786, 17016156, 37675718, 94360702, 874791, 1239555, 15535065, 75986488, 16099750, 48658605, 7685448, 89214616, 63967300, 22766820, 6808825, 41245325, 67281495, 29466406, 37659250, 86798033, 87720882, 85571389, 16567550, 1375023, 59501487, 34497327, 36930650, 11581181, 33565483, 12664567, 84127901, 99515901, 48893685, 9398733, 67124282, 321665, 91281584, 43506672, 43501211, 31904591, 24733232, 76540481, 26139110, 75543508, 38510840, 68702632, 28787861, 42782652, 26114953, 23569917, 1788101, 17976208, 21070110, 62490109, 64087743, 89046466, 40027975, 8729146, 92554120, 75777973, 36685023, 48260151, 9175338, 81172706, 95957797, 85711894, 30811010, 82532312, 16684829, 99021067, 94711842, 62762857, 97783876, 37166608, 89078848, 94595668, 19457589, 95251277, 83368048, 93566986, 44842615, 36139942, 18001617, 78218845, 84840549, 26998766, 40865610, 96420429, 18466635, 97011160, 38658347, 21673260, 176257, 30090481, 33123618, 99729357, 2331773, 112651, 93562257, 24953498, 89699445, 40534591, 74614639, 9599614, 92803766, 86001008, 93359396, 37280276, 34698463, 80014588, 34432810, 16405341, 80316608, 79922758, 38341669, 83133790, 70004753 +168541, 71522968, 57393458, 60581278, 62428472, 4064751, 47738185, 40534591, 79191827, 44481640, 90013093, 6221471, 17365188, 24058273, 20765474, 66322959, 84002370, 71300104, 90654836, 51466049, 3294781, 46540998, 669105, 99125126, 45407418, 78218845, 69641513, 66250369, 21919959, 42237907, 10358899, 48675329, 55770687, 44844121, 19486173, 77377183, 33123618, 7300047, 72793444, 2331773, 62936963, 32590267, 6793819, 95957797, 16567550, 20002147, 66319530, 11581181, 44550764, 92353856, 526217, 19376156, 33336148, 57961282, 2208785, 4515343, 10309525, 22997281, 22879907, 62490109, 80246713, 13348726, 7423788, 31161687, 569864, 6111563, 16097038, 4204661, 3233569, 61741594, 38365584, 73124510, 98371444, 70372191, 29029316, 78766358, 16684829, 39201414, 73786944, 4668450, 30218878, 61982238, 37166608, 84187166, 45996863, 57240218, 5832946, 11365791, 19457589, 45075471, 26664538, 36139942, 92803766, 8115266, 90158785, 20642888, 83533741, 4434662, 33797252, 90272749, 94090109, 13173644, 92215320, 21001913, 11923835, 34698428, 66611759, 58224549, 3088684, 66667729, 53842979, 66045587, 96420429, 60430369, 7229550, 23569917, 1936762, 14093520, 95010552, 36468541, 1788101, 91990218, 30366150, 51590803, 61712234, 3633375, 73392814, 21673260, 99604946, 54263880, 44177011, 70727211, 24826575, 8129978, 73168565, 21289531, 86543538, 42199455, 11543098, 55615885, 80555751, 78909561, 48892201, 49597667, 15535065, 85116755, 16099750, 92604458, 96906410, 77694700, 74724075, 2204165, 38022834, 72373496, 40356877, 45919976, 18806856, 31491938, 78300864, 83083131, 824872, 96193415, 97783876, 41092102, 56955985, 96709982, 82726008, 96318094, 10597197, 90090964, 2917920, 9398733, 9886593, 39553046, 99917599, 24733232, 5267545, 84349107, 91240048, 83210802, 90310261, 27411561, 59371804, 22435353, 49598724, 30139692, 80251430, 84684495, 84840549, 28550822, 93270084, 3880712, 91831487, 85023028, 59981773, 84406788, 51472275, 70036438, 4199704, 36189527, 6157724, 18466635, 97011160, 15015906, 21070110, 38658347, 86460488, 3773993, 70004753, 12571310, 5482538, 67031644, 68000591, 61928316, 66116458, 86301513, 92554120, 47887470, 1022677, 60102965, 55189057, 82052050, 85117092, 26063929, 30653863, 65081429, 26507214, 19101477, 415901, 29188588, 29635537, 18411915, 112651, 12303248, 70367851, 7646095, 79136082, 6808825, 70541760, 7011964, 33699435, 71316369, 68644627, 52204879, 31126490, 16054533, 68316156, 54232247, 79806380, 68204242, 10264691, 47298834, 1375023, 99524975, 43152977, 1431742, 86242799, 56424103, 86022504, 24168411, 83269727, 31727379, 39986008, 77413012, 84127901, 76330843, 17727650, 30771409, 34946859, 16380211, 4985896, 22721500, 14220886, 72358170, 18833224, 15148031, 35512853, 99549373, 22405120, 70800879, 26102057, 35996293, 94516935, 75543508, 45617087, 18001617, 47361209, 17539625, 98462867, 19939935, 72274002, 90457870, 78785507, 58749, 91141395, 508198, 79922758, 62115552, 38494874, 98943869, 55470718, 75229462, 83150534, 36933038, 93515664, 15075176, 27325428, 30764367, 34236719, 38008118, 69605283, 35456853, 53648154, 22942635, 30891921, 40984766, 68110330, 38061439, 176257, 20867149, 31733363, 75135448, 65880522, 64157906, 44784505, 3235882, 95395112, 42644903, 38341669, 62051033, 92867155, 75777973, 61728685, 33553959, 94360702, 48088883, 26275734, 98653983, 40197395, 76671482, 52293995, 874791, 84904436, 46851987, 4798568, 35192533, 30463802, 66885828, 5073754, 27187213, 44245960, 47708850, 18663507, 51426311, 99965001, 4099191, 56473732, 47090124, 34044787, 89811711, 77787724, 71333116, 91664334, 88047921, 55143724, 41481685, 57359924, 72732205, 69355476, 27665211, 18699206, 91938191, 7687278, 86798033, 50188404, 9257405, 8733068, 12024238, 56484900, 60955663, 6819644, 99226875, 34497327, 9603598, 11757872, 62762857, 54606384, 36780454, 33201905, 45381876, 89078848, 36396314, 57803235, 40686254, 17385531, 44060493, 29819940, 54058788, 79821897, 37891451, 247198, 97641116, 53888755, 72278539, 69697787, 67124282, 56531125, 65017137, 29065964, 91281584, 36753250, 16387575, 71083565, 83368048, 82339363, 32161669, 51588015, 92787493, 17764950, 14947650, 45667668, 8634541, 57241521, 81855445, 13231279, 83133790, 17894977, 35092039, 17068582, 22450468, 26998766, 28928175, 75153252, 63372756, 65271999, 8055981, 9623492, 36312813, 28796059, 30998561, 96735716, 82427263, 26114953, 33628349, 26392416, 36580610, 13862149, 63628376, 37280276, 9829782, 4978543, 65454636, 11161731, 14626618, 68128438, 34493392, 15163258, 59614746, 84166196, 6525948, 14723410, 81853704, 67227442, 16595436, 32699744, 19898053, 15902805, 12416768, 30787683, 93790285, 40027975, 37224844, 75407347, 63059024, 10649306, 98739783, 13819358, 55850790, 29932657, 61263068, 37183543, 42967683, 66271566, 8913721, 99729357, 73617245, 72614359, 41380093, 92541302, 39847321, 57163802, 9188443, 99690194, 63967300, 33249630, 42692881, 37620363, 37501808, 93562257, 73109997, 18504197, 41245325, 67281495, 23740167, 55960386, 18783702, 50007421, 8791066, 99297164, 22113133, 59329511, 5970607, 2607799, 74357852, 36135, 7517032, 61859143, 27625735, 33935899, 65047700, 19272365, 14363867, 87720882, 94711842, 60393039, 55247455, 64055960, 67474219, 73021291, 72357096, 70420215, 57658654, 26744917, 50567636, 37192445, 8128637, 83155442, 7502255, 10453030, 32151165, 6871053, 94076128, 97057187, 73031054, 91255408, 2717150, 82897371, 48893685, 8696647, 62452034, 36812683, 65038678, 32274392, 10366309, 59109400, 85242963, 91727510, 93053405, 33431960, 3509435, 88698958, 26863229, 71965942, 38510840, 16445503, 93359396, 72019362, 61623915, 10961421, 28787861, 57248122, 3183975, 81805959, 78549759, 9259676, 8651647, 60106217, 95290172, 61271144, 44915235, 77272628, 81677380, 62430984, 69848388, 20681921, 65715134, 6153406, 45794415, 70596786, 89499542, 20486294, 82178706, 81898046, 24619760, 87598888, 15064655, 48360198, 76434144, 30090481, 98648327, 45990383, 47213183, 92071990, 69255765, 30569392, 43045786, 39801351, 17016156, 29959549, 89804152, 34698463, 76703609, 63506281, 62740044, 7955293, 1239555, 45428665, 9175338, 51047803, 32426752, 42073124, 92998591, 22766820, 63152504, 25636669, 17146629, 58751351, 43376279, 96726697, 7182642, 97379790, 16424199, 49641577, 82651278, 52060076, 20122224, 30811010, 92692978, 49271185, 44479073, 50668599, 83302115, 37435892, 24953498, 20645197, 74441448, 40781854, 14731700, 59501487, 5822202, 66663942, 36930650, 99333375, 17037369, 67513640, 45848907, 12664567, 53632373, 50702367, 17386996, 52261574, 99224392, 71657078, 34432810, 94595668, 50806615, 30163921, 6521313, 74743862, 68694897, 321665, 61141874, 61960400, 44842615, 38645117, 83651211, 72725103, 4787945, 4806458, 16405341, 9058407, 76540481, 2891150, 80316608, 29516592, 8373289, 3233084, 33304202, 43357947, 8099994, 51715482, 88251446, 99658235, 40865610, 53084256, 3487592, 7104732, 44664587, 95581843, 79657802, 33895336, 49328608, 42782652, 91802888, 6038457, 74137926, 51315460, 20568363, 75052463, 66903004, 81274566, 89419466, 88444207, 67084351, 47893286, 17976208, 9860195, 34667286, 54663246, 64098930, 98130363, 17857111, 7919588, 53547802, 53393358, 78884452, 73222868, 62232211, 8807940, 38009615, 30395570, 61373987, 83789391, 79738755, 8729146, 76170907, 55753905, 64602895, 78589145, 22108665, 9575954, 5640302, 82886381, 77301523, 93933709, 58180653, 30543215, 36685023, 84293052, 36505482, 37560164, 92692283, 7685448, 89214616, 69786756, 6505939, 83747892, 7066775, 86118021, 39373729, 29466406, 29834512, 27041967, 32058615, 77898274, 90004325, 4091162, 24915585, 82532312, 24314885, 1204161, 56153345, 85571389, 20140249, 74452589, 38256849, 87710366, 89699445, 33565483, 15536795, 1250437, 46870723, 82779622, 45995325, 40152546, 99971982, 67793644, 61897582, 56515456, 5111370, 44627776, 45049308, 43501211, 54517921, 92530431, 82327024, 93057697, 69136837, 60176618, 41092172, 48673079, 44473167, 69579137, 17957593, 27185644, 87160386, 48395186, 88653118, 68702632, 30694952, 42947632, 26397786, 749283, 28734791, 67451935, 37957788, 59405277, 20885148, 15948937, 32159704, 64087743, 89046466, 1569515, 88130087, 62552524, 52613508, 78561158, 37675718, 89217461, 82979980, 23134715, 43933006, 51149804, 42307449, 16019925, 54427233, 51507425, 80014588, 79880247, 36845587, 37739481, 83948335, 91957544, 23110625, 34295794, 75986488, 48658605, 59177718, 12348118, 36808486, 56461322, 33237508, 85711894, 14045383, 59910624, 81774825, 40781449, 28851716, 4119747, 71920426, 74110882, 66428795, 65074535, 63015256, 23194618, 29994197, 77284619, 62496012, 77072625, 79322415, 26292919, 22129328, 59318837, 65851721, 89637706, 43506672, 61859581, 9599614, 76825057, 13470059, 79942022, 97940276, 68041839, 97281847, 97561745, 19891772, 58208470, 75820087, 44348328, 96852321, 86001008, 25842078, 76315420, 68102437, 62357986, 45237957, 54199160, 26776922, 9860968, 47792865, 22200378, 32250045, 90061527, 65338021, 21993752, 68875490, 65275241, 62803858, 39171895, 53666583, 44847298, 61815223, 72738685, 54868730, 81172706, 71469330, 51464002, 4508700, 37659250, 76960303, 72238278, 17081350, 21397057, 66832478, 45306070, 62693428, 44846932, 88904910, 77300457, 95251277, 74614639, 31904591, 68824981, 40677414, 66137019, 45790169, 26493119, 29510992, 21533347, 54762643, 73235980, 55602660, 78602717, 68816413, 14326617, 6497830, 72495719, 89996536, 1197320, 20836893, 99861373, 59197747, 17058722, 77620120, 44889423, 18131876, 72777973, 95726235, 67030811, 77187825, 55669657, 99515901, 86821229, 44983451, 93566986, 23848565, 23432750, 49882705, 75128745, 54014062, 49236559, 64848072, 53802686, 94539824, 85695762, 33061250, 77312810, 48260151, 13468268, 43322743, 16971929, 99021067, 98948034, 68939068, 48774913, 54987042, 94911072, 57020481, 4095116, 26139110, 70782102, 26734892, 92033260, 4069912, 29401781, 6986898, 92398073, 24591705, 41442762, 14349098 +37560164, 65047700, 35512853, 79922758, 61928316, 23110625, 24733232, 4787945, 3509435, 40865610, 75052463, 95010552, 17976208, 8807940, 13348726, 78561158, 55189057, 73617245, 48892201, 92692283, 4099191, 38022834, 85711894, 69355476, 68204242, 51466049, 83302115, 12024238, 64055960, 33201905, 37192445, 54987042, 17764950, 49598724, 78785507, 26114953, 53802686, 34236719, 69605283, 44844121, 33061250, 37224844, 92554120, 77312810, 42199455, 37739481, 29188588, 98371444, 71333116, 63015256, 44479073, 94711842, 20645197, 73021291, 74452589, 86022504, 11581181, 45996863, 57803235, 96318094, 90090964, 69697787, 56531125, 44846932, 44627776, 59109400, 4806458, 35996293, 80316608, 90272749, 13173644, 29510992, 22450468, 54762643, 66250369, 82427263, 4515343, 81274566, 47792865, 63628376, 9860195, 92398073, 54663246, 73392814, 22879907, 21673260, 24619760, 64602895, 76434144, 67031644, 52613508, 92071990, 65275241, 39171895, 30543215, 52293995, 34698463, 16019925, 65081429, 78909561, 57163802, 12348118, 29029316, 37620363, 93562257, 56461322, 47090124, 17146629, 59329511, 29466406, 24915585, 54232247, 56153345, 72238278, 99524975, 24953498, 824872, 37166608, 62428472, 53632373, 17081350, 6871053, 10597197, 30163921, 82897371, 56515456, 72358170, 61141874, 39553046, 93057697, 91240048, 45407418, 70800879, 75543508, 45617087, 80251430, 69641513, 84684495, 49882705, 10961421, 3183975, 78602717, 23569917, 30694952, 61271144, 84406788, 48675329, 4199704, 36189527, 44915235, 67451935, 4978543, 15075176, 51590803, 38008118, 15015906, 67227442, 38658347, 73222868, 22942635, 54263880, 20867149, 55753905, 9575954, 44784505, 8129978, 5640302, 98739783, 20765474, 73168565, 21289531, 1022677, 37675718, 72793444, 98653983, 58180653, 76703609, 84904436, 84002370, 35192533, 19101477, 6793819, 9188443, 96906410, 12303248, 18663507, 36808486, 41245325, 51426311, 56473732, 25636669, 58751351, 39373729, 71316369, 52204879, 91664334, 31126490, 52060076, 30811010, 27625735, 71920426, 92033260, 76960303, 23194618, 40356877, 60955663, 67474219, 61982238, 34497327, 41092102, 50567636, 67513640, 45848907, 17386996, 48774913, 59318837, 37891451, 168541, 669105, 44983451, 6521313, 2917920, 94911072, 36812683, 43506672, 45075471, 82339363, 19376156, 92803766, 23432750, 92787493, 41092172, 79942022, 97940276, 66137019, 91727510, 93053405, 4434662, 68041839, 13231279, 75820087, 25842078, 90013093, 8099994, 16445503, 93359396, 72019362, 84840549, 26998766, 88251446, 28550822, 75153252, 65271999, 3088684, 95581843, 3880712, 508198, 96420429, 55602660, 62115552, 78549759, 20568363, 66903004, 60106217, 42947632, 1788101, 36580610, 57393458, 93515664, 28734791, 32250045, 72495719, 30764367, 98130363, 26734892, 16595436, 65715134, 3633375, 53547802, 70596786, 21993752, 35456853, 30891921, 40984766, 99604946, 89046466, 61373987, 87598888, 24826575, 5482538, 77377183, 75407347, 63059024, 31161687, 92867155, 93933709, 16097038, 17058722, 82052050, 76671482, 42307449, 63506281, 66322959, 874791, 79880247, 30653863, 26507214, 7955293, 83948335, 29635537, 9175338, 16099750, 51047803, 42073124, 44245960, 47708850, 81172706, 73109997, 63152504, 55960386, 33699435, 8791066, 29834512, 43376279, 74357852, 18699206, 66428795, 4069912, 39201414, 47298834, 55247455, 78300864, 83083131, 66319530, 77284619, 30218878, 77072625, 79322415, 77187825, 54606384, 24168411, 31727379, 56955985, 84127901, 96709982, 17385531, 99515901, 82726008, 99224392, 4064751, 5832946, 7502255, 46540998, 79821897, 97057187, 50806615, 66832478, 2717150, 74743862, 92353856, 67124282, 62693428, 65017137, 36753250, 18833224, 32274392, 79191827, 99917599, 26664538, 32161669, 10366309, 5267545, 84349107, 83210802, 60176618, 26102057, 27411561, 59371804, 22435353, 97281847, 44473167, 94090109, 47361209, 98462867, 26863229, 3233084, 76315420, 87160386, 61623915, 48395186, 73235980, 44664587, 93270084, 66667729, 36312813, 28796059, 53842979, 60430369, 81805959, 1936762, 89419466, 83150534, 88444207, 64848072, 15948937, 22997281, 34493392, 62430984, 89996536, 84166196, 20486294, 53648154, 81898046, 38061439, 30395570, 1569515, 176257, 76170907, 65880522, 64157906, 30090481, 68875490, 10649306, 42644903, 38341669, 62051033, 75777973, 48088883, 3233569, 26275734, 8913721, 80014588, 84293052, 62936963, 30463802, 92541302, 39847321, 71300104, 89214616, 99690194, 70372191, 43322743, 42692881, 2204165, 37501808, 99965001, 86118021, 33237508, 68644627, 77787724, 18131876, 55143724, 49641577, 81774825, 7517032, 82651278, 57359924, 4119747, 82532312, 27665211, 1204161, 65074535, 50668599, 16567550, 1431742, 4668450, 11757872, 55669657, 17037369, 36396314, 57240218, 76330843, 46870723, 29819940, 71657078, 99971982, 4985896, 11365791, 48893685, 9398733, 5111370, 91281584, 54517921, 83368048, 82327024, 69136837, 16405341, 14947650, 33431960, 21533347, 17957593, 71965942, 38510840, 21001913, 33304202, 35092039, 43357947, 53084256, 34698428, 7104732, 33895336, 42782652, 91802888, 74137926, 38494874, 98943869, 9860968, 85023028, 95290172, 36468541, 26397786, 26392416, 67084351, 91990218, 13862149, 22200378, 10309525, 18466635, 34667286, 27325428, 77272628, 81677380, 15163258, 32699744, 65338021, 45794415, 62232211, 62490109, 80246713, 30787683, 31733363, 99861373, 93790285, 12571310, 59197747, 19486173, 78589145, 60581278, 33123618, 77301523, 37183543, 42967683, 7300047, 53666583, 36685023, 66271566, 26063929, 13468268, 55615885, 46851987, 44847298, 38365584, 41380093, 77620120, 34295794, 66885828, 71469330, 23740167, 18783702, 99297164, 22113133, 27041967, 59910624, 36135, 37659250, 92692978, 24314885, 86798033, 16684829, 50188404, 72777973, 9257405, 45919976, 95726235, 60393039, 37435892, 86242799, 14731700, 67030811, 98948034, 56424103, 87710366, 36780454, 83269727, 89699445, 33565483, 12664567, 1250437, 52261574, 17727650, 82779622, 45995325, 94076128, 94595668, 65851721, 67793644, 72278539, 22721500, 14220886, 88904910, 45049308, 16387575, 23848565, 36139942, 8115266, 90158785, 83651211, 51588015, 76540481, 33336148, 26493119, 97561745, 29516592, 8373289, 57241521, 19891772, 81855445, 92215320, 96852321, 27185644, 17894977, 90457870, 68102437, 28928175, 91141395, 62357986, 58224549, 8055981, 45237957, 68702632, 79657802, 66045587, 6038457, 51315460, 59981773, 14326617, 70782102, 30366150, 10358899, 49236559, 20885148, 17365188, 97011160, 94539824, 6525948, 1197320, 7919588, 20836893, 24591705, 55770687, 85695762, 21070110, 38009615, 12416768, 86460488, 64087743, 70727211, 8729146, 22108665, 62552524, 45990383, 3235882, 43045786, 55850790, 61263068, 569864, 23134715, 86543538, 43933006, 60102965, 4204661, 40197395, 11543098, 48260151, 61815223, 61741594, 415901, 91957544, 32590267, 73124510, 72738685, 75986488, 7685448, 112651, 63967300, 74724075, 70367851, 7646095, 51464002, 6808825, 70541760, 89811711, 95957797, 16971929, 7182642, 16054533, 68316156, 90004325, 40781449, 74110882, 91938191, 19272365, 87720882, 18806856, 1375023, 74441448, 72357096, 20140249, 96193415, 70420215, 97783876, 99333375, 84187166, 68939068, 26744917, 45381876, 15536795, 89078848, 40686254, 22129328, 54058788, 32151165, 40152546, 34432810, 97641116, 86821229, 91255408, 89637706, 99125126, 19457589, 526217, 77300457, 95251277, 71083565, 92530431, 31904591, 15148031, 44842615, 9599614, 90310261, 38645117, 20642888, 48673079, 85242963, 2891150, 26139110, 69579137, 58208470, 44348328, 88698958, 86001008, 72274002, 99658235, 63372756, 6221471, 9623492, 28787861, 54199160, 30998561, 33628349, 8651647, 91831487, 14093520, 42237907, 47893286, 749283, 51472275, 59405277, 6157724, 11161731, 90061527, 68128438, 32159704, 14723410, 17857111, 61712234, 81853704, 78884452, 89499542, 44177011, 70004753, 79738755, 75135448, 48360198, 88130087, 47213183, 66116458, 39801351, 62803858, 29932657, 82886381, 54427233, 2331773, 72614359, 49597667, 15535065, 45428665, 92604458, 69786756, 92998591, 33249630, 22766820, 77694700, 6505939, 79136082, 18504197, 7066775, 7011964, 34044787, 96726697, 78766358, 88047921, 16424199, 4091162, 90654836, 79806380, 33935899, 7687278, 99021067, 72373496, 85571389, 56484900, 40781854, 62496012, 59501487, 66663942, 9603598, 38256849, 14349098, 34946859, 40534591, 247198, 16380211, 44550764, 321665, 29065964, 57020481, 65038678, 44481640, 61859581, 72725103, 22405120, 4095116, 94516935, 45790169, 8634541, 17539625, 17068582, 75128745, 3487592, 66611759, 88653118, 96735716, 7229550, 68816413, 55470718, 75229462, 21919959, 36933038, 70036438, 14626618, 69848388, 24058273, 19898053, 15902805, 3773993, 83789391, 40027975, 98648327, 68000591, 69255765, 30569392, 61728685, 89217461, 94360702, 51149804, 85117092, 51507425, 1239555, 18411915, 54868730, 32426752, 27187213, 83747892, 67281495, 50007421, 4508700, 5970607, 77898274, 49271185, 14363867, 29401781, 29994197, 73786944, 10264691, 8733068, 20002147, 6819644, 5822202, 57658654, 62762857, 77413012, 50702367, 47738185, 21397057, 44060493, 62452034, 9886593, 43501211, 61960400, 40677414, 9058407, 18001617, 30139692, 19939935, 83133790, 51715482, 57248122, 54014062, 9829782, 37957788, 59614746, 64098930, 53393358, 86301513, 7423788, 82979980, 6111563, 89804152, 99729357, 4798568, 36505482, 85116755, 48658605, 59177718, 44889423, 71522968, 14045383, 41481685, 61859143, 20122224, 72732205, 3294781, 99226875, 8128637, 26292919, 6986898, 30771409, 10453030, 53888755, 8696647, 74614639, 68824981, 83533741, 78218845, 58749, 9259676, 37280276, 6497830, 20681921, 6153406, 82178706, 95395112, 47887470, 33553959, 62740044, 36845587, 5073754, 41442762, 97379790, 32058615, 28851716, 36930650, 39986008, 73031054, 68694897, 45306070, 13470059, 33797252, 57961282, 26776922, 49328608, 15064655, 29959549, 2607799, 31491938, 43152977, 83155442, 61897582, 93566986, 76825057, 99549373, 11923835, 2208785, 65454636, 68110330, 13819358, 80555751, 17016156, 45667668 +93359396, 15075176, 65081429, 91255408, 67124282, 59371804, 18001617, 57248122, 38494874, 37957788, 31733363, 18131876, 30163921, 13470059, 27185644, 95010552, 27325428, 89046466, 92554120, 84904436, 37739481, 6505939, 22113133, 57359924, 71920426, 56955985, 6521313, 23432750, 17764950, 90457870, 40865610, 3633375, 93790285, 45990383, 33553959, 23134715, 98653983, 92604458, 71333116, 27041967, 74357852, 63015256, 39201414, 7502255, 99125126, 61141874, 61859581, 83210802, 72725103, 16405341, 26493119, 13231279, 25842078, 71965942, 72274002, 28928175, 53084256, 65271999, 45237957, 26114953, 6157724, 92398073, 15163258, 17857111, 80246713, 3773993, 30787683, 99861373, 3235882, 65275241, 86543538, 6111563, 4204661, 55189057, 13468268, 51507425, 1239555, 37560164, 41380093, 92692283, 6793819, 96906410, 5073754, 12348118, 74724075, 44245960, 81172706, 38022834, 7182642, 49641577, 27625735, 37659250, 50668599, 72238278, 1375023, 64055960, 67030811, 66663942, 61982238, 97783876, 24168411, 89699445, 33565483, 17037369, 37192445, 45996863, 84127901, 17081350, 46870723, 17727650, 30771409, 54987042, 44983451, 82897371, 44846932, 36812683, 77300457, 99917599, 15148031, 59109400, 76825057, 70800879, 57241521, 21533347, 43357947, 76315420, 28550822, 6221471, 28796059, 79657802, 42782652, 508198, 7229550, 1936762, 66903004, 14093520, 75229462, 36933038, 26397786, 64848072, 749283, 93515664, 34493392, 54663246, 7919588, 81853704, 53547802, 21673260, 44177011, 83789391, 5482538, 30090481, 78561158, 43045786, 98739783, 55850790, 82886381, 48088883, 89804152, 40197395, 874791, 30653863, 83948335, 71300104, 89214616, 112651, 92998591, 22766820, 77694700, 37620363, 18783702, 77787724, 29466406, 31126490, 77898274, 52060076, 92692978, 82532312, 27665211, 18699206, 14363867, 4069912, 99524975, 74441448, 40781854, 20002147, 41092102, 67513640, 57240218, 14349098, 47738185, 71657078, 37891451, 65851721, 73031054, 53888755, 72278539, 65017137, 45049308, 71083565, 92530431, 93566986, 92803766, 90310261, 38645117, 48673079, 68041839, 44473167, 8373289, 94090109, 47361209, 17957593, 38510840, 22450468, 26998766, 75153252, 73235980, 44664587, 23569917, 51315460, 68816413, 95290172, 70782102, 88444207, 67084351, 42237907, 84406788, 22200378, 4199704, 44915235, 67451935, 11161731, 72495719, 89996536, 59614746, 98130363, 26734892, 24591705, 16595436, 70596786, 69605283, 62232211, 61373987, 70004753, 70727211, 88130087, 76434144, 9575954, 92071990, 7423788, 13819358, 38341669, 1022677, 82979980, 16097038, 39171895, 60102965, 72793444, 52293995, 76703609, 99729357, 26063929, 55615885, 26507214, 44847298, 80555751, 91957544, 77620120, 23110625, 45428665, 29635537, 70372191, 18663507, 7646095, 79136082, 99965001, 50007421, 5970607, 91664334, 88047921, 55143724, 85711894, 14045383, 59910624, 32058615, 68316156, 82651278, 61859143, 4091162, 54232247, 33935899, 56153345, 29994197, 40356877, 45919976, 94711842, 47298834, 31491938, 1431742, 6819644, 3294781, 59501487, 96193415, 36930650, 62762857, 74452589, 54606384, 87710366, 31727379, 6986898, 83155442, 21397057, 6871053, 2717150, 2917920, 62693428, 94911072, 19457589, 526217, 95251277, 43506672, 32274392, 83368048, 44481640, 26664538, 32161669, 82327024, 44842615, 91240048, 26102057, 85242963, 27411561, 93053405, 33797252, 97561745, 90272749, 78218845, 19891772, 58208470, 3509435, 44348328, 69641513, 21001913, 88653118, 62357986, 8055981, 9623492, 28787861, 4515343, 60430369, 6038457, 3183975, 20568363, 47792865, 26392416, 30366150, 49236559, 9829782, 47893286, 28734791, 17976208, 32250045, 90061527, 84166196, 1197320, 24058273, 53393358, 38658347, 22879907, 99604946, 13348726, 68000591, 22108665, 86301513, 39801351, 20765474, 75777973, 60581278, 37675718, 42967683, 94360702, 3233569, 26275734, 17058722, 82052050, 76671482, 34698463, 16019925, 79880247, 2331773, 62936963, 36505482, 35192533, 7955293, 34295794, 85116755, 48658605, 54868730, 71469330, 70541760, 86118021, 56473732, 33699435, 17146629, 99297164, 39373729, 33237508, 95957797, 29834512, 16971929, 2607799, 96726697, 16054533, 49271185, 1204161, 19272365, 86798033, 66428795, 83302115, 83083131, 4668450, 98948034, 73021291, 70420215, 77072625, 36780454, 83269727, 11581181, 84187166, 45381876, 8128637, 77413012, 89078848, 40686254, 99515901, 52261574, 22129328, 82779622, 44060493, 79821897, 247198, 97057187, 67793644, 66832478, 669105, 11365791, 69697787, 5111370, 321665, 89637706, 62452034, 44627776, 18833224, 43501211, 54517921, 82339363, 24733232, 9599614, 84349107, 90158785, 83651211, 35512853, 51588015, 20642888, 75543508, 33336148, 45667668, 45617087, 30139692, 69579137, 17539625, 26863229, 86001008, 33304202, 35092039, 8099994, 87160386, 72019362, 75128745, 66667729, 3880712, 33895336, 66045587, 96735716, 96420429, 74137926, 75052463, 98943869, 81274566, 42947632, 61271144, 57393458, 54014062, 10358899, 63628376, 37280276, 70036438, 4978543, 9860195, 20885148, 10309525, 53802686, 18466635, 97011160, 68128438, 14723410, 20836893, 15015906, 89499542, 73222868, 8807940, 38009615, 86460488, 30395570, 54263880, 176257, 20867149, 64602895, 61928316, 62552524, 75407347, 66116458, 63059024, 68875490, 31161687, 92867155, 29932657, 21289531, 47887470, 569864, 7300047, 85117092, 48260151, 84293052, 84002370, 72614359, 36845587, 19101477, 38365584, 32590267, 29188588, 9175338, 9188443, 99690194, 63967300, 33249630, 43322743, 42692881, 27187213, 29029316, 44889423, 47708850, 70367851, 36808486, 23740167, 47090124, 4508700, 41442762, 71316369, 43376279, 41481685, 36135, 20122224, 24915585, 69355476, 76960303, 68204242, 29401781, 10264691, 95726235, 18806856, 56484900, 37435892, 20645197, 78300864, 14731700, 67474219, 20140249, 34497327, 9603598, 77187825, 33201905, 26744917, 36396314, 53632373, 96709982, 82726008, 4064751, 29819940, 46540998, 54058788, 32151165, 45995325, 168541, 86821229, 14220886, 44550764, 48893685, 8696647, 68694897, 56515456, 72358170, 88904910, 91281584, 57020481, 36753250, 61960400, 79191827, 68824981, 93057697, 23848565, 5267545, 36139942, 4787945, 4806458, 41092172, 76540481, 79942022, 35996293, 97940276, 66137019, 91727510, 26139110, 22435353, 49598724, 97281847, 57961282, 84684495, 88698958, 96852321, 3233084, 90013093, 17894977, 17068582, 16445503, 51715482, 49882705, 88251446, 11923835, 63372756, 66611759, 54762643, 58224549, 3088684, 54199160, 79922758, 91802888, 78602717, 78549759, 8651647, 9860968, 85023028, 60106217, 89419466, 36580610, 13862149, 6497830, 59405277, 81677380, 14626618, 32159704, 6525948, 61712234, 67227442, 45794415, 78884452, 21993752, 30891921, 40984766, 64087743, 24619760, 76170907, 55753905, 19486173, 24826575, 48360198, 64157906, 77377183, 52613508, 47213183, 69255765, 42644903, 73168565, 29959549, 37183543, 42199455, 51149804, 73617245, 46851987, 61741594, 415901, 49597667, 92541302, 15535065, 75986488, 39847321, 57163802, 18411915, 69786756, 42073124, 12303248, 66885828, 2204165, 37501808, 73109997, 63152504, 51464002, 7066775, 55960386, 8791066, 58751351, 90004325, 30811010, 28851716, 72732205, 74110882, 92033260, 23194618, 85571389, 16567550, 12024238, 55247455, 77284619, 5822202, 55669657, 38256849, 86022504, 68939068, 50567636, 39986008, 45848907, 50702367, 17385531, 48774913, 40534591, 96318094, 34432810, 50806615, 61897582, 74743862, 92353856, 9398733, 74614639, 39553046, 10366309, 60176618, 9058407, 94516935, 80316608, 4434662, 33431960, 61623915, 53842979, 49328608, 2208785, 81805959, 33628349, 9259676, 91831487, 36468541, 51472275, 77272628, 62430984, 94539824, 34236719, 38008118, 69848388, 65715134, 55770687, 85695762, 20486294, 38061439, 87598888, 79738755, 78589145, 44784505, 8129978, 62803858, 61728685, 61263068, 77301523, 93933709, 43933006, 30543215, 36685023, 66322959, 80014588, 62740044, 4798568, 30463802, 48892201, 73124510, 72738685, 16099750, 51047803, 93562257, 67281495, 51426311, 4099191, 25636669, 89811711, 68644627, 97379790, 16424199, 81774825, 90654836, 91938191, 65047700, 16684829, 50188404, 99021067, 72777973, 72373496, 60393039, 24953498, 86242799, 99226875, 56424103, 62428472, 99333375, 12664567, 57803235, 76330843, 99224392, 10453030, 94076128, 94595668, 99971982, 22721500, 56531125, 29065964, 16387575, 65038678, 31904591, 69136837, 40677414, 8115266, 45407418, 4095116, 14947650, 2891150, 80251430, 13173644, 92215320, 75820087, 98462867, 19939935, 83133790, 99658235, 3487592, 10961421, 91141395, 93270084, 66250369, 36312813, 95581843, 30694952, 55470718, 14326617, 21919959, 15948937, 22997281, 30764367, 20681921, 32699744, 82178706, 81898046, 22942635, 44844121, 68110330, 33061250, 1569515, 75135448, 65880522, 5640302, 62051033, 33123618, 53666583, 58180653, 66271566, 8913721, 42307449, 63506281, 61815223, 98371444, 6808825, 56461322, 59329511, 52204879, 78766358, 7517032, 4119747, 24314885, 65074535, 44479073, 73786944, 51466049, 30218878, 824872, 62496012, 11757872, 37166608, 15536795, 17386996, 1250437, 34946859, 59318837, 40152546, 16380211, 10597197, 97641116, 4985896, 9886593, 19376156, 99549373, 92787493, 83533741, 45790169, 29516592, 8634541, 78785507, 58749, 7104732, 68702632, 30998561, 55602660, 59981773, 83150534, 91990218, 48675329, 36189527, 65454636, 65338021, 6153406, 19898053, 53648154, 15902805, 12416768, 40027975, 37224844, 8729146, 12571310, 59197747, 67031644, 98648327, 30569392, 95395112, 89217461, 54427233, 7685448, 59177718, 32426752, 71522968, 41245325, 34044787, 79806380, 7687278, 9257405, 8733068, 43152977, 66319530, 57658654, 79322415, 5832946, 45306070, 45075471, 22405120, 68102437, 26776922, 82427263, 34667286, 21070110, 62490109, 15064655, 17016156, 11543098, 18504197, 40781449, 87720882, 60955663, 29510992, 84840549, 34698428, 48395186, 1788101, 51590803, 64098930, 73392814, 10649306, 77312810, 26292919, 81855445, 62115552, 17365188, 35456853, 78909561, 83747892, 7011964, 72357096, 90090964 +78785507, 54606384, 65275241, 3233569, 70372191, 78766358, 5832946, 54058788, 50806615, 9886593, 30139692, 40865610, 15163258, 85695762, 80246713, 52613508, 569864, 85116755, 48658605, 112651, 91664334, 16054533, 27665211, 11365791, 49598724, 8099994, 84840549, 63372756, 22997281, 34493392, 20486294, 176257, 78561158, 33123618, 54427233, 874791, 415901, 15535065, 71522968, 56461322, 31126490, 68316156, 7517032, 76960303, 45919976, 1375023, 20002147, 61982238, 36930650, 97783876, 26292919, 83155442, 17764950, 45667668, 17894977, 34698428, 28787861, 85023028, 27325428, 16595436, 19898053, 30891921, 99861373, 55753905, 77377183, 20765474, 77301523, 86543538, 39171895, 17058722, 42199455, 82052050, 48892201, 39847321, 89214616, 92998591, 29029316, 71333116, 82532312, 96193415, 62428472, 50702367, 48893685, 45306070, 89637706, 36812683, 19457589, 24733232, 23848565, 36139942, 19376156, 83210802, 83533741, 14947650, 91727510, 26139110, 29516592, 8373289, 78218845, 21001913, 62357986, 58224549, 3088684, 2208785, 7229550, 74137926, 55470718, 75229462, 83150534, 26397786, 17976208, 32250045, 72495719, 94539824, 34236719, 26734892, 65715134, 6153406, 55770687, 1569515, 5482538, 76434144, 64157906, 67031644, 13348726, 62552524, 8129978, 7423788, 68875490, 42644903, 29932657, 61263068, 77312810, 30543215, 66322959, 79880247, 84904436, 80555751, 1239555, 32590267, 45428665, 18663507, 79136082, 74357852, 32058615, 81774825, 79806380, 24314885, 85571389, 39201414, 95726235, 94711842, 47298834, 37435892, 55247455, 5822202, 62762857, 37166608, 99515901, 82726008, 46870723, 4064751, 47738185, 29819940, 40152546, 94595668, 73031054, 66832478, 44983451, 69697787, 8696647, 67124282, 44846932, 65038678, 45075471, 83368048, 99917599, 82339363, 68824981, 44481640, 15148031, 93057697, 4787945, 48673079, 33336148, 17539625, 19939935, 28928175, 28550822, 53084256, 91141395, 88653118, 45237957, 95581843, 30998561, 9259676, 60106217, 1788101, 30366150, 70036438, 15075176, 14626618, 81853704, 3633375, 12416768, 86460488, 64087743, 70004753, 15064655, 59197747, 64602895, 3235882, 92867155, 73168565, 33553959, 7300047, 4204661, 58180653, 85117092, 26063929, 80014588, 55615885, 46851987, 78909561, 61815223, 72738685, 75986488, 51047803, 99690194, 59177718, 32426752, 33249630, 42692881, 66885828, 77694700, 27187213, 71469330, 7066775, 18783702, 29466406, 16971929, 54232247, 71920426, 91938191, 49271185, 1204161, 86798033, 66428795, 14363867, 4069912, 16684829, 29994197, 16567550, 12024238, 14731700, 30218878, 66663942, 34497327, 57658654, 83269727, 33565483, 50567636, 67513640, 40686254, 76330843, 71657078, 45995325, 10597197, 82897371, 5111370, 56531125, 321665, 526217, 18833224, 61960400, 26664538, 9599614, 5267545, 91240048, 92803766, 40677414, 13470059, 99549373, 4806458, 22405120, 66137019, 33797252, 8634541, 33431960, 13173644, 19891772, 69579137, 13231279, 57961282, 3233084, 76315420, 16445503, 61623915, 75153252, 10961421, 66611759, 44664587, 68702632, 28796059, 53842979, 49328608, 82427263, 6038457, 81805959, 78549759, 8651647, 68816413, 42947632, 61271144, 88444207, 26392416, 42237907, 57393458, 37280276, 9829782, 4199704, 36189527, 44915235, 67451935, 9860195, 6157724, 77272628, 81677380, 59614746, 64098930, 24058273, 53393358, 73392814, 8807940, 68110330, 99604946, 38061439, 24619760, 54263880, 61373987, 20867149, 83789391, 79738755, 76170907, 75135448, 48360198, 65880522, 78589145, 22108665, 45990383, 92071990, 69255765, 43045786, 38341669, 1022677, 89217461, 51149804, 65081429, 37739481, 35192533, 38365584, 73124510, 29188588, 6793819, 9175338, 69786756, 63967300, 12303248, 12348118, 47708850, 2204165, 18504197, 67281495, 33699435, 99297164, 71316369, 18131876, 97379790, 16424199, 36135, 77898274, 82651278, 24915585, 37659250, 92692978, 40356877, 51466049, 18806856, 83302115, 8733068, 99524975, 1431742, 86242799, 4668450, 67474219, 38256849, 86022504, 87710366, 17037369, 37192445, 45848907, 45381876, 8128637, 57240218, 6986898, 1250437, 52261574, 99224392, 17727650, 82779622, 34946859, 79821897, 16380211, 67793644, 168541, 97641116, 61897582, 669105, 86821229, 68694897, 2917920, 9398733, 56515456, 94911072, 29065964, 91281584, 16387575, 77300457, 95251277, 43501211, 54517921, 39553046, 31904591, 82327024, 61859581, 44842615, 69136837, 90310261, 76825057, 35512853, 45407418, 41092172, 59371804, 80316608, 22435353, 4434662, 26493119, 97561745, 90272749, 57241521, 69641513, 88698958, 26863229, 86001008, 90013093, 72274002, 90457870, 99658235, 54199160, 96420429, 60430369, 57248122, 62115552, 26114953, 33628349, 1936762, 59981773, 22200378, 51472275, 48675329, 28734791, 65454636, 11161731, 92398073, 10309525, 15948937, 97011160, 68128438, 30764367, 54663246, 1197320, 53547802, 38658347, 62232211, 81898046, 44844121, 15902805, 30395570, 63059024, 13819358, 17016156, 29959549, 42967683, 94360702, 98653983, 76671482, 66271566, 8913721, 76703609, 13468268, 30653863, 2331773, 62740044, 72614359, 36845587, 62936963, 36505482, 30463802, 41380093, 77620120, 92541302, 16099750, 71300104, 92604458, 96906410, 22766820, 99965001, 17146629, 4508700, 58751351, 22113133, 77787724, 29834512, 96726697, 88047921, 57359924, 20122224, 30811010, 90654836, 27625735, 65047700, 72777973, 73786944, 10264691, 60393039, 43152977, 40781854, 66319530, 62496012, 77072625, 9603598, 11757872, 36780454, 68939068, 39986008, 45996863, 84127901, 57803235, 22129328, 44060493, 7502255, 32151165, 59318837, 6871053, 34432810, 97057187, 30163921, 2717150, 88904910, 44627776, 36753250, 93566986, 90158785, 23432750, 72725103, 20642888, 9058407, 70800879, 85242963, 94516935, 2891150, 75543508, 97281847, 47361209, 81855445, 44348328, 25842078, 71965942, 35092039, 93359396, 6221471, 7104732, 8055981, 9623492, 66250369, 33895336, 26776922, 66045587, 4515343, 79922758, 51315460, 75052463, 81274566, 21919959, 95290172, 95010552, 36933038, 36580610, 54014062, 10358899, 20885148, 51590803, 32159704, 38008118, 6525948, 69848388, 24591705, 61712234, 45794415, 78884452, 21993752, 73222868, 22879907, 38009615, 30787683, 70727211, 88130087, 61928316, 75407347, 30569392, 66116458, 10649306, 98739783, 39801351, 95395112, 31161687, 62051033, 55850790, 60581278, 21289531, 47887470, 6111563, 53666583, 36685023, 99729357, 84002370, 26507214, 83948335, 92692283, 7685448, 5073754, 7646095, 50007421, 47090124, 7011964, 2607799, 85711894, 14045383, 41481685, 90004325, 40781449, 72732205, 4119747, 65074535, 72238278, 56484900, 20645197, 67030811, 3294781, 72357096, 59501487, 99226875, 70420215, 41092102, 24168411, 33201905, 99333375, 84187166, 15536795, 77413012, 46540998, 40534591, 96318094, 37891451, 4985896, 90090964, 44550764, 74743862, 65017137, 99125126, 61141874, 74614639, 32274392, 92530431, 38645117, 51588015, 4095116, 76540481, 35996293, 97940276, 45790169, 93053405, 94090109, 84684495, 33304202, 51715482, 22450468, 87160386, 26998766, 3487592, 11923835, 65271999, 48395186, 93270084, 66667729, 36312813, 508198, 91802888, 3183975, 23569917, 89419466, 14326617, 36468541, 13862149, 6497830, 34667286, 84166196, 98130363, 20836893, 15015906, 89499542, 21070110, 82178706, 62490109, 89046466, 31733363, 93790285, 40027975, 19486173, 98648327, 86301513, 82886381, 43933006, 60102965, 26275734, 89804152, 72793444, 55189057, 40197395, 52293995, 34698463, 63506281, 16019925, 51507425, 73617245, 4798568, 44847298, 19101477, 49597667, 29635537, 98371444, 42073124, 44889423, 44245960, 37620363, 36808486, 51464002, 6808825, 41245325, 70541760, 23740167, 55960386, 86118021, 4099191, 56473732, 25636669, 34044787, 33237508, 59329511, 7182642, 55143724, 59910624, 49641577, 61859143, 52060076, 4091162, 28851716, 69355476, 18699206, 7687278, 92033260, 63015256, 50188404, 56153345, 50668599, 68204242, 72373496, 31491938, 24953498, 60955663, 73021291, 56955985, 89078848, 36396314, 53632373, 96709982, 17385531, 30771409, 94076128, 99971982, 65851721, 53888755, 91255408, 14220886, 62693428, 62452034, 45049308, 43506672, 32161669, 10366309, 8115266, 16405341, 26102057, 29510992, 27185644, 49882705, 58749, 73235980, 9860968, 91831487, 14093520, 47792865, 91990218, 63628376, 49236559, 47893286, 93515664, 4978543, 59405277, 53802686, 18466635, 89996536, 17857111, 7919588, 65338021, 70596786, 53648154, 44177011, 87598888, 37224844, 8729146, 12571310, 24826575, 9575954, 44784505, 5640302, 92554120, 61728685, 37675718, 82979980, 37183543, 16097038, 48088883, 48260151, 7955293, 91957544, 23110625, 34295794, 18411915, 70367851, 73109997, 63152504, 39373729, 89811711, 68644627, 5970607, 95957797, 43376279, 74110882, 33935899, 23194618, 29401781, 64055960, 78300864, 83083131, 6819644, 20140249, 74452589, 11581181, 31727379, 17386996, 21397057, 72278539, 59109400, 83651211, 92787493, 3509435, 98462867, 96852321, 83133790, 38510840, 72019362, 68102437, 75128745, 79657802, 55602660, 70782102, 84406788, 64848072, 37957788, 90061527, 17365188, 14723410, 69605283, 22942635, 21673260, 40984766, 33061250, 3773993, 47213183, 62803858, 75777973, 93933709, 11543098, 42307449, 61741594, 54868730, 81172706, 37501808, 93562257, 6505939, 51426311, 41442762, 38022834, 52204879, 19272365, 99021067, 9257405, 74441448, 824872, 77187825, 26744917, 12664567, 17081350, 48774913, 10453030, 247198, 54987042, 92353856, 57020481, 71083565, 79191827, 80251430, 21533347, 92215320, 17957593, 17068582, 96735716, 42782652, 78602717, 38494874, 98943869, 66903004, 67084351, 749283, 62430984, 67227442, 20681921, 32699744, 35456853, 30090481, 84293052, 37560164, 43322743, 74724075, 27041967, 44479073, 77284619, 56424103, 79322415, 6521313, 72358170, 79942022, 45617087, 18001617, 44473167, 58208470, 43357947, 3880712, 30694952, 20568363, 68000591, 23134715, 57163802, 9188443, 83747892, 98948034, 89699445, 14349098, 22721500, 84349107, 27411561, 75820087, 88251446, 54762643, 8791066, 55669657, 60176618, 68041839, 87720882 +43933006, 874791, 87720882, 8373289, 86301513, 67793644, 91255408, 43357947, 11923835, 66903004, 84406788, 59405277, 84904436, 52060076, 33201905, 83155442, 11365791, 83210802, 60176618, 90158785, 16405341, 9058407, 97561745, 9860968, 70782102, 7423788, 82979980, 80014588, 38365584, 23110625, 7646095, 17146629, 96726697, 14363867, 6819644, 77187825, 55669657, 15536795, 53632373, 65851721, 95251277, 43506672, 40677414, 59109400, 20642888, 68041839, 33431960, 38510840, 3487592, 62115552, 30694952, 75052463, 95290172, 65454636, 18466635, 27325428, 68128438, 17857111, 98648327, 63059024, 5640302, 13819358, 20765474, 31161687, 17016156, 51507425, 72614359, 44847298, 62936963, 45428665, 48658605, 44245960, 93562257, 99965001, 68316156, 61859143, 72732205, 4069912, 12024238, 47298834, 97783876, 40686254, 44550764, 92353856, 45306070, 67124282, 5111370, 65017137, 72358170, 43501211, 61960400, 32274392, 44481640, 24733232, 82327024, 13470059, 26139110, 80316608, 45617087, 80251430, 78218845, 3233084, 25842078, 72274002, 84840549, 26776922, 7229550, 26114953, 33628349, 85023028, 6497830, 28734791, 11161731, 15948937, 51590803, 14626618, 89996536, 30764367, 3633375, 89499542, 20486294, 81898046, 44844121, 80246713, 31733363, 75135448, 5482538, 62803858, 94360702, 39171895, 60102965, 17058722, 84293052, 73617245, 62740044, 37739481, 83948335, 49597667, 37560164, 51047803, 96906410, 70372191, 74724075, 73109997, 83747892, 67281495, 56473732, 58751351, 68644627, 27041967, 77898274, 57359924, 50188404, 72777973, 39201414, 20002147, 3294781, 5822202, 38256849, 77413012, 17727650, 82779622, 21397057, 54058788, 40152546, 2717150, 74743862, 36812683, 93566986, 15148031, 32161669, 93057697, 69136837, 72725103, 99549373, 22405120, 26102057, 27411561, 22435353, 49598724, 26493119, 47361209, 44348328, 27185644, 22450468, 93359396, 62357986, 2208785, 78549759, 20568363, 14093520, 14326617, 1788101, 26397786, 22200378, 34667286, 90061527, 72495719, 97011160, 22997281, 64098930, 98130363, 7919588, 69605283, 73222868, 20867149, 70727211, 15064655, 12571310, 24826575, 78589145, 44784505, 78561158, 98739783, 39801351, 21289531, 23134715, 6111563, 58180653, 30543215, 2331773, 61815223, 61741594, 91957544, 32590267, 72738685, 85116755, 71300104, 43322743, 5073754, 18663507, 51464002, 18504197, 23740167, 47090124, 4508700, 99297164, 89811711, 59329511, 77787724, 2607799, 55143724, 27625735, 37659250, 69355476, 74110882, 49271185, 65047700, 86798033, 65074535, 85571389, 10264691, 8733068, 43152977, 37435892, 20645197, 86242799, 59501487, 33565483, 84187166, 50702367, 14349098, 6871053, 168541, 30163921, 61897582, 86821229, 22721500, 68694897, 9398733, 56531125, 44627776, 91281584, 45049308, 61859581, 44842615, 84349107, 35512853, 4806458, 92787493, 85242963, 91727510, 29516592, 57241521, 21533347, 58208470, 75820087, 96852321, 17894977, 35092039, 26998766, 88251446, 58749, 45237957, 66667729, 95581843, 66045587, 60430369, 57248122, 91802888, 78602717, 81805959, 9259676, 47893286, 4199704, 36189527, 37957788, 92398073, 62430984, 84166196, 54663246, 69848388, 1197320, 15015906, 21673260, 86460488, 3773993, 30395570, 30787683, 87598888, 79738755, 88130087, 64157906, 22108665, 3235882, 52613508, 10649306, 55850790, 60581278, 82886381, 29959549, 1022677, 33553959, 3233569, 42199455, 36685023, 76671482, 11543098, 51149804, 34698463, 99729357, 65081429, 36505482, 19101477, 92541302, 29635537, 9175338, 69786756, 27187213, 12348118, 29029316, 44889423, 63152504, 33699435, 25636669, 41442762, 5970607, 29466406, 31126490, 49641577, 4091162, 20122224, 24915585, 30811010, 4119747, 92692978, 71920426, 24314885, 33935899, 91938191, 7687278, 63015256, 23194618, 9257405, 40356877, 45919976, 83083131, 77284619, 98948034, 72357096, 41092102, 24168411, 89699445, 99333375, 26744917, 50567636, 67513640, 39986008, 8128637, 89078848, 99224392, 46540998, 40534591, 96318094, 37891451, 94595668, 14220886, 6521313, 48893685, 94911072, 44846932, 526217, 79191827, 92530431, 19376156, 90310261, 76825057, 8115266, 23432750, 76540481, 70800879, 94516935, 97940276, 45790169, 93053405, 33336148, 30139692, 19891772, 81855445, 92215320, 57961282, 86001008, 19939935, 71965942, 21001913, 17068582, 87160386, 68102437, 28928175, 53084256, 63372756, 48395186, 73235980, 36312813, 28796059, 3880712, 82427263, 4515343, 55602660, 98943869, 91831487, 60106217, 59981773, 47792865, 55470718, 36468541, 67084351, 42237907, 91990218, 10358899, 51472275, 4978543, 15075176, 81677380, 59614746, 65338021, 70596786, 85695762, 62232211, 22942635, 62490109, 38009615, 61373987, 176257, 93790285, 13348726, 61928316, 30569392, 43045786, 92554120, 38341669, 75777973, 47887470, 89217461, 42967683, 86543538, 85117092, 48260151, 16019925, 13468268, 66322959, 54427233, 55615885, 30653863, 30463802, 77620120, 39847321, 6793819, 9188443, 92604458, 54868730, 59177718, 42073124, 12303248, 42692881, 71469330, 50007421, 71316369, 95957797, 91664334, 16054533, 59910624, 32058615, 82651278, 28851716, 79806380, 19272365, 51466049, 60393039, 1375023, 99524975, 1431742, 40781854, 4668450, 67030811, 73021291, 824872, 66663942, 61982238, 96193415, 77072625, 57658654, 62762857, 45848907, 45996863, 84127901, 6986898, 17081350, 82726008, 48774913, 34946859, 59318837, 16380211, 97057187, 97641116, 44983451, 72278539, 90090964, 69697787, 62693428, 89637706, 19457589, 36753250, 18833224, 39553046, 83368048, 68824981, 10366309, 92803766, 38645117, 4095116, 35996293, 75543508, 33797252, 90272749, 17539625, 3509435, 83133790, 8099994, 75153252, 10961421, 88653118, 7104732, 58224549, 44664587, 93270084, 28787861, 508198, 3183975, 81274566, 42947632, 75229462, 88444207, 26392416, 57393458, 13862149, 63628376, 49236559, 64848072, 17976208, 20885148, 15163258, 34236719, 38008118, 6525948, 14723410, 26734892, 20836893, 61712234, 20681921, 19898053, 55770687, 21993752, 21070110, 38658347, 53648154, 82178706, 30891921, 40984766, 24619760, 89046466, 1569515, 99861373, 8729146, 76170907, 59197747, 19486173, 48360198, 65880522, 64602895, 67031644, 77377183, 45990383, 47213183, 65275241, 62051033, 92867155, 73168565, 569864, 37183543, 93933709, 7300047, 48088883, 89804152, 52293995, 42307449, 26063929, 63506281, 46851987, 4798568, 80555751, 7955293, 1239555, 41380093, 34295794, 57163802, 7685448, 89214616, 32426752, 112651, 22766820, 66885828, 81172706, 37620363, 79136082, 18783702, 86118021, 8791066, 38022834, 16971929, 18131876, 7182642, 85711894, 14045383, 97379790, 36135, 81774825, 27665211, 76960303, 44479073, 68204242, 29401781, 73786944, 16567550, 94711842, 24953498, 60955663, 14731700, 30218878, 20140249, 62496012, 99226875, 9603598, 11757872, 36930650, 86022504, 11581181, 26292919, 57240218, 96709982, 1250437, 22129328, 44060493, 10453030, 247198, 34432810, 54987042, 669105, 4985896, 56515456, 321665, 57020481, 74614639, 71083565, 54517921, 9599614, 23848565, 4787945, 48673079, 2891150, 13231279, 69641513, 84684495, 88698958, 17957593, 33304202, 90013093, 16445503, 90457870, 51715482, 72019362, 75128745, 8055981, 3088684, 54199160, 49328608, 96735716, 96420429, 6038457, 8651647, 83150534, 95010552, 61271144, 36580610, 30366150, 9829782, 44915235, 93515664, 67451935, 10309525, 32250045, 94539824, 32159704, 24591705, 16595436, 24058273, 53393358, 73392814, 35456853, 68110330, 33061250, 40027975, 68000591, 9575954, 69255765, 42644903, 29932657, 77301523, 16097038, 72793444, 55189057, 82052050, 76703609, 79880247, 84002370, 48892201, 92692283, 75986488, 29188588, 18411915, 92998591, 77694700, 47708850, 2204165, 41245325, 70541760, 7066775, 51426311, 56461322, 7011964, 39373729, 29834512, 74357852, 78766358, 16424199, 41481685, 40781449, 54232247, 82532312, 18699206, 56153345, 99021067, 18806856, 83302115, 31491938, 56484900, 64055960, 67474219, 56424103, 79322415, 87710366, 31727379, 68939068, 56955985, 37192445, 45381876, 12664567, 36396314, 57803235, 47738185, 29819940, 30771409, 7502255, 71657078, 45995325, 94076128, 73031054, 50806615, 82897371, 2917920, 99125126, 88904910, 61141874, 77300457, 26664538, 5267545, 36139942, 83651211, 41092172, 66137019, 59371804, 4434662, 45667668, 97281847, 18001617, 29510992, 69579137, 98462867, 26863229, 76315420, 49882705, 91141395, 6221471, 9623492, 79657802, 79922758, 51315460, 38494874, 1936762, 37280276, 749283, 17365188, 81853704, 53547802, 78884452, 22879907, 15902805, 8807940, 99604946, 70004753, 83789391, 37224844, 62552524, 92071990, 66116458, 33123618, 61263068, 77312810, 53666583, 26275734, 66271566, 8913721, 36845587, 415901, 73124510, 99690194, 70367851, 37501808, 55960386, 34044787, 71333116, 88047921, 7517032, 1204161, 92033260, 16684829, 50668599, 72238278, 72373496, 29994197, 78300864, 70420215, 74452589, 54606384, 37166608, 36780454, 83269727, 62428472, 99515901, 76330843, 46870723, 32151165, 99971982, 53888755, 8696647, 62452034, 9886593, 29065964, 16387575, 65038678, 99917599, 31904591, 91240048, 51588015, 45407418, 17764950, 83533741, 79942022, 14947650, 8634541, 94090109, 99658235, 61623915, 66611759, 68702632, 53842979, 30998561, 74137926, 21919959, 54014062, 48675329, 53802686, 34493392, 65715134, 32699744, 6153406, 12416768, 38061439, 44177011, 55753905, 76434144, 95395112, 61728685, 15535065, 98371444, 6505939, 6808825, 33237508, 90004325, 55247455, 74441448, 66319530, 34497327, 17037369, 17386996, 4064751, 5832946, 79821897, 82339363, 44473167, 13173644, 40865610, 34698428, 66250369, 33895336, 42782652, 23569917, 68816413, 89419466, 36933038, 70036438, 9860195, 77272628, 64087743, 54263880, 75407347, 68875490, 37675718, 40197395, 26507214, 35192533, 63967300, 33249630, 36808486, 22113133, 90654836, 95726235, 52261574, 10597197, 78785507, 65271999, 54762643, 6157724, 45794415, 30090481, 4204661, 98653983, 16099750, 71522968, 4099191, 43376279, 66428795, 66832478, 8129978, 52204879, 17385531, 78909561, 45075471, 28550822, 67227442 +61373987, 7423788, 62762857, 69848388, 77301523, 83155442, 1250437, 53888755, 669105, 44983451, 9599614, 90013093, 75128745, 30366150, 12416768, 62552524, 92867155, 1239555, 48658605, 59329511, 78766358, 77284619, 34497327, 72278539, 44846932, 88904910, 45667668, 78785507, 68702632, 85023028, 42947632, 49236559, 37280276, 47893286, 4978543, 94539824, 82178706, 76170907, 98648327, 43933006, 52293995, 72738685, 73109997, 6505939, 18504197, 70541760, 22113133, 2607799, 32058615, 74110882, 82532312, 74441448, 98948034, 73021291, 62496012, 34432810, 50806615, 22721500, 44481640, 27411561, 91727510, 29510992, 17539625, 34698428, 33895336, 91802888, 51315460, 67084351, 42237907, 28734791, 14723410, 17857111, 21070110, 38658347, 30891921, 75135448, 64602895, 5482538, 76434144, 68875490, 61728685, 29932657, 37675718, 33553959, 39171895, 85117092, 73617245, 37739481, 73124510, 15535065, 112651, 12303248, 44889423, 18783702, 56461322, 41442762, 91664334, 31126490, 24953498, 1431742, 9603598, 36930650, 26744917, 45848907, 50702367, 52261574, 5832946, 34946859, 10453030, 97057187, 65851721, 54987042, 86821229, 4985896, 6521313, 67124282, 92530431, 5267545, 76540481, 70800879, 97940276, 80251430, 71965942, 38510840, 8099994, 26998766, 49882705, 10961421, 7104732, 66250369, 26776922, 4515343, 3183975, 8651647, 60106217, 70782102, 1788101, 4199704, 6497830, 20885148, 32250045, 81677380, 34493392, 20836893, 81853704, 53547802, 78884452, 62232211, 1569515, 68000591, 77377183, 44784505, 86301513, 39801351, 95395112, 65275241, 33123618, 7300047, 53666583, 26275734, 26063929, 16019925, 66322959, 54427233, 30653863, 62740044, 44847298, 92692283, 77620120, 57163802, 69786756, 5073754, 71469330, 67281495, 33237508, 29466406, 16424199, 81774825, 66428795, 65074535, 99021067, 29401781, 85571389, 73786944, 95726235, 60955663, 3294781, 20140249, 59501487, 97783876, 54606384, 89699445, 99515901, 71657078, 5111370, 93566986, 93057697, 10366309, 23848565, 84349107, 13470059, 83651211, 99549373, 9058407, 94516935, 4434662, 26493119, 33431960, 17894977, 72274002, 43357947, 91141395, 6221471, 88653118, 95581843, 79657802, 96735716, 6038457, 33628349, 68816413, 89419466, 55470718, 75229462, 749283, 44915235, 9860195, 65454636, 15948937, 77272628, 32159704, 64098930, 7919588, 15015906, 70596786, 89499542, 55770687, 85695762, 15902805, 40984766, 99604946, 30787683, 87598888, 37224844, 12571310, 47213183, 69255765, 75407347, 43045786, 5640302, 98739783, 13819358, 20765474, 38341669, 62803858, 17016156, 3233569, 72793444, 51149804, 99729357, 63506281, 4798568, 72614359, 36845587, 36505482, 61741594, 48892201, 415901, 96906410, 32426752, 63967300, 12348118, 18663507, 83747892, 51426311, 99965001, 86118021, 56473732, 8791066, 29834512, 16054533, 28851716, 72732205, 33935899, 49271185, 7687278, 50668599, 16567550, 51466049, 99524975, 37435892, 66319530, 67474219, 77072625, 57658654, 77187825, 38256849, 41092102, 37166608, 33201905, 17037369, 68939068, 39986008, 36396314, 57803235, 6986898, 99224392, 29819940, 6871053, 40152546, 99971982, 67793644, 66832478, 14220886, 74743862, 45306070, 56515456, 91281584, 57020481, 16387575, 95251277, 65038678, 18833224, 82339363, 61859581, 44842615, 19376156, 90310261, 22435353, 33797252, 97561745, 30139692, 44348328, 84684495, 96852321, 83133790, 35092039, 84840549, 68102437, 99658235, 40865610, 53084256, 11923835, 48395186, 45237957, 93270084, 36312813, 30998561, 42782652, 57248122, 75052463, 47792865, 21919959, 26397786, 91990218, 63628376, 70036438, 36189527, 67451935, 17365188, 97011160, 14626618, 59614746, 34236719, 38008118, 6525948, 1197320, 65715134, 24058273, 19898053, 53648154, 62490109, 3773993, 54263880, 44177011, 89046466, 99861373, 19486173, 88130087, 64157906, 45990383, 10649306, 29959549, 61263068, 77312810, 89217461, 82979980, 6111563, 48088883, 89804152, 98653983, 17058722, 42199455, 30543215, 8913721, 80014588, 2331773, 61815223, 19101477, 45428665, 39847321, 9175338, 54868730, 92998591, 42692881, 27187213, 81172706, 2204165, 41245325, 23740167, 16971929, 52204879, 18131876, 96726697, 49641577, 68316156, 7517032, 82651278, 52060076, 24915585, 24314885, 18699206, 1204161, 29994197, 39201414, 40356877, 18806856, 94711842, 43152977, 55247455, 83083131, 20002147, 6819644, 99226875, 74452589, 24168411, 37192445, 15536795, 84127901, 57240218, 96709982, 82726008, 76330843, 22129328, 21397057, 44060493, 30771409, 32151165, 59318837, 94076128, 10597197, 94595668, 29065964, 36753250, 45049308, 61960400, 54517921, 32274392, 68824981, 32161669, 40677414, 38645117, 90158785, 4787945, 17764950, 22405120, 48673079, 2891150, 93053405, 49598724, 29516592, 78218845, 69579137, 81855445, 92215320, 13231279, 86001008, 76315420, 16445503, 51715482, 87160386, 61623915, 65271999, 62357986, 58224549, 8055981, 44664587, 66667729, 28796059, 54199160, 96420429, 79922758, 55602660, 26114953, 38494874, 14326617, 95290172, 54014062, 51472275, 48675329, 93515664, 59405277, 30764367, 35456853, 73222868, 81898046, 30395570, 24619760, 176257, 20867149, 83789391, 15064655, 3235882, 52613508, 78561158, 66116458, 75777973, 60581278, 82886381, 16097038, 94360702, 40197395, 11543098, 76703609, 51507425, 84293052, 55615885, 80555751, 78909561, 7955293, 91957544, 38365584, 49597667, 9188443, 18411915, 71300104, 99690194, 22766820, 43322743, 77694700, 37620363, 79136082, 51464002, 6808825, 7011964, 25636669, 17146629, 58751351, 34044787, 5970607, 38022834, 27041967, 36135, 61859143, 54232247, 69355476, 71920426, 65047700, 19272365, 9257405, 83302115, 56484900, 40781854, 4668450, 72357096, 824872, 66663942, 96193415, 83269727, 62428472, 50567636, 8128637, 12664567, 89078848, 17385531, 14349098, 48774913, 82779622, 46540998, 45995325, 37891451, 73031054, 168541, 91255408, 2717150, 82897371, 9398733, 62693428, 62452034, 99125126, 9886593, 36812683, 77300457, 43501211, 39553046, 31904591, 24733232, 69136837, 60176618, 8115266, 23432750, 4806458, 41092172, 83533741, 79942022, 75543508, 80316608, 33336148, 97281847, 8634541, 19891772, 21533347, 75820087, 17957593, 27185644, 33304202, 88251446, 75153252, 58749, 73235980, 3088684, 28787861, 49328608, 508198, 62115552, 74137926, 78602717, 78549759, 30694952, 9259676, 20568363, 98943869, 9860968, 91831487, 83150534, 36468541, 61271144, 26392416, 13862149, 10358899, 64848072, 37957788, 17976208, 53802686, 34667286, 62430984, 84166196, 98130363, 24591705, 16595436, 6153406, 53393358, 21993752, 44844121, 33061250, 31733363, 40027975, 59197747, 9575954, 30569392, 63059024, 92554120, 31161687, 21289531, 569864, 23134715, 42967683, 86543538, 4204661, 76671482, 42307449, 48260151, 874791, 79880247, 46851987, 85116755, 16099750, 89214616, 70372191, 44245960, 71522968, 7066775, 99297164, 71316369, 77787724, 88047921, 7182642, 55143724, 14045383, 59910624, 97379790, 57359924, 30811010, 27665211, 79806380, 63015256, 4069912, 16684829, 87720882, 8733068, 47298834, 31491938, 86242799, 67030811, 5822202, 61982238, 79322415, 55669657, 11581181, 99333375, 31727379, 40686254, 17081350, 17727650, 7502255, 40534591, 54058788, 79821897, 16380211, 61897582, 69697787, 8696647, 526217, 43506672, 71083565, 45075471, 79191827, 99917599, 36139942, 59109400, 16405341, 4095116, 26102057, 66137019, 45790169, 8373289, 3509435, 69641513, 26863229, 19939935, 93359396, 72019362, 28928175, 66611759, 2208785, 60430369, 23569917, 66903004, 59981773, 36580610, 84406788, 22200378, 9829782, 15075176, 6157724, 92398073, 27325428, 51590803, 22997281, 89996536, 61712234, 67227442, 65338021, 73392814, 69605283, 80246713, 68110330, 70727211, 79738755, 93790285, 8729146, 48360198, 65880522, 78589145, 67031644, 30090481, 22108665, 42644903, 47887470, 93933709, 60102965, 82052050, 36685023, 34698463, 13468268, 84904436, 84002370, 26507214, 62936963, 35192533, 32590267, 37560164, 41380093, 92541302, 6793819, 98371444, 7685448, 51047803, 59177718, 42073124, 66885828, 7646095, 93562257, 4508700, 95957797, 71333116, 74357852, 85711894, 41481685, 77898274, 90004325, 4091162, 20122224, 40781449, 27625735, 92692978, 50188404, 72238278, 72373496, 20645197, 56424103, 45996863, 26292919, 77413012, 96318094, 44550764, 11365791, 92353856, 68694897, 56531125, 321665, 89637706, 94911072, 65017137, 61141874, 44627776, 19457589, 74614639, 26664538, 82327024, 91240048, 92803766, 72725103, 35512853, 85242963, 14947650, 26139110, 59371804, 18001617, 47361209, 57961282, 3233084, 17068582, 22450468, 28550822, 54762643, 9623492, 3880712, 82427263, 81274566, 14093520, 36933038, 68128438, 54663246, 26734892, 3633375, 20486294, 21673260, 38009615, 64087743, 38061439, 24826575, 61928316, 92071990, 8129978, 73168565, 55850790, 1022677, 58180653, 55189057, 66271566, 30463802, 83948335, 23110625, 34295794, 29188588, 92604458, 37501808, 63152504, 55960386, 4099191, 47090124, 89811711, 37659250, 4119747, 91938191, 86798033, 76960303, 56153345, 68204242, 12024238, 1375023, 78300864, 11757872, 84187166, 53632373, 17386996, 47738185, 97641116, 2917920, 72358170, 15148031, 76825057, 51588015, 92787493, 68041839, 45617087, 44473167, 57241521, 94090109, 13173644, 63372756, 53842979, 81805959, 95010552, 72495719, 45794415, 22879907, 22942635, 8807940, 70004753, 13348726, 37183543, 29635537, 33249630, 74724075, 47708850, 70367851, 50007421, 68644627, 90654836, 14363867, 23194618, 72777973, 60393039, 14731700, 87710366, 33565483, 56955985, 45381876, 46870723, 247198, 30163921, 90090964, 48893685, 83210802, 45407418, 90272749, 58208470, 98462867, 88698958, 25842078, 3487592, 66045587, 7229550, 1936762, 88444207, 10309525, 18466635, 86460488, 55753905, 62051033, 65081429, 75986488, 29029316, 33699435, 39373729, 92033260, 44479073, 45919976, 86022504, 36780454, 4064751, 83368048, 35996293, 21001913, 90457870, 11161731, 90061527, 43376279, 10264691, 70420215, 57393458, 32699744, 36808486, 30218878, 67513640, 20642888, 15163258, 64055960, 20681921 +77300457, 7104732, 44784505, 68875490, 13819358, 82886381, 96906410, 82651278, 63015256, 4985896, 54199160, 78766358, 7687278, 37891451, 94076128, 9398733, 90310261, 16405341, 48673079, 45790169, 79657802, 53842979, 53802686, 17857111, 68000591, 75407347, 7423788, 80555751, 88047921, 32058615, 11757872, 39986008, 34432810, 69697787, 45049308, 54517921, 99917599, 8115266, 72725103, 92787493, 75820087, 28928175, 66250369, 66667729, 54014062, 749283, 28734791, 32159704, 81853704, 48360198, 8129978, 65275241, 75777973, 55850790, 1022677, 72793444, 34698463, 54427233, 1239555, 41380093, 89214616, 22766820, 18663507, 83747892, 67281495, 86118021, 56461322, 33237508, 81774825, 37659250, 71920426, 19272365, 72373496, 30218878, 824872, 66663942, 11581181, 56955985, 53632373, 52261574, 46870723, 29819940, 59318837, 65851721, 54987042, 91255408, 6521313, 8696647, 321665, 89637706, 88904910, 36812683, 16387575, 526217, 79191827, 31904591, 15148031, 26664538, 10366309, 19376156, 91240048, 69136837, 76825057, 79942022, 94516935, 59371804, 33797252, 18001617, 29510992, 21001913, 6221471, 54762643, 58224549, 95581843, 42782652, 93515664, 20885148, 15948937, 38008118, 24591705, 38658347, 65880522, 3235882, 43045786, 77301523, 89804152, 55189057, 42307449, 48260151, 26063929, 63506281, 80014588, 84293052, 62740044, 72614359, 38365584, 34295794, 57163802, 63967300, 77694700, 70367851, 6808825, 18783702, 50007421, 58751351, 22113133, 71316369, 5970607, 18131876, 16424199, 24915585, 40781449, 90654836, 27625735, 72732205, 74110882, 79806380, 92033260, 8733068, 20645197, 40781854, 14731700, 73021291, 59501487, 36930650, 24168411, 17037369, 68939068, 45848907, 36396314, 17081350, 71657078, 10453030, 32151165, 96318094, 99971982, 50806615, 53888755, 72278539, 22721500, 56515456, 62452034, 29065964, 65038678, 32274392, 92530431, 68824981, 85242963, 75543508, 94090109, 3509435, 90457870, 93359396, 88251446, 10961421, 9623492, 3088684, 60430369, 57248122, 78549759, 85023028, 36468541, 88444207, 13862149, 51472275, 4199704, 65454636, 10309525, 34493392, 1197320, 65715134, 65338021, 89499542, 21993752, 12416768, 38061439, 30787683, 99861373, 93790285, 40027975, 67031644, 62552524, 9575954, 63059024, 20765474, 62051033, 33123618, 60102965, 66271566, 85117092, 66322959, 4798568, 45428665, 7685448, 92604458, 70372191, 42073124, 42692881, 5073754, 47708850, 71522968, 4508700, 39373729, 27041967, 74357852, 41481685, 36135, 30811010, 28851716, 82532312, 24314885, 50668599, 29401781, 18806856, 94711842, 56484900, 37435892, 78300864, 86242799, 3294781, 20140249, 62762857, 86022504, 87710366, 26744917, 37192445, 12664567, 77413012, 89078848, 6986898, 83155442, 96709982, 5832946, 30771409, 45995325, 97057187, 66832478, 669105, 86821229, 44983451, 44550764, 48893685, 67124282, 72358170, 91281584, 36753250, 93566986, 61859581, 84349107, 40677414, 13470059, 90158785, 4787945, 9058407, 83533741, 27411561, 26139110, 93053405, 68041839, 97281847, 80251430, 78218845, 69579137, 21533347, 58208470, 27185644, 43357947, 22450468, 40865610, 3487592, 34698428, 62357986, 93270084, 36312813, 3880712, 66045587, 49328608, 96420429, 79922758, 3183975, 33628349, 68816413, 14093520, 70782102, 37280276, 48675329, 44915235, 4978543, 15075176, 11161731, 92398073, 34667286, 17365188, 81677380, 14626618, 94539824, 59614746, 54663246, 32699744, 78884452, 20486294, 62232211, 30891921, 40984766, 3773993, 89046466, 61373987, 31733363, 8729146, 76170907, 64602895, 88130087, 64157906, 30090481, 98648327, 61928316, 47213183, 78561158, 66116458, 95395112, 92867155, 62803858, 61728685, 29959549, 37675718, 33553959, 23134715, 86543538, 48088883, 98653983, 82052050, 8913721, 76703609, 13468268, 51507425, 30653863, 65081429, 36845587, 37739481, 7955293, 19101477, 73124510, 92692283, 29635537, 9175338, 92998591, 44889423, 37620363, 23740167, 8791066, 17146629, 29466406, 16971929, 52204879, 14045383, 68316156, 49271185, 76960303, 4069912, 99021067, 87720882, 29994197, 77284619, 99226875, 61982238, 74452589, 89699445, 8128637, 1250437, 34946859, 6871053, 73031054, 97641116, 82339363, 44842615, 23848565, 41092172, 20642888, 45617087, 17539625, 33304202, 17068582, 72019362, 84840549, 26998766, 49882705, 28550822, 53084256, 75128745, 65271999, 66611759, 33895336, 96735716, 82427263, 2208785, 4515343, 6038457, 62115552, 23569917, 38494874, 81274566, 42947632, 47792865, 36933038, 67084351, 91990218, 30366150, 84406788, 49236559, 47893286, 36189527, 6497830, 97011160, 89996536, 69848388, 98130363, 26734892, 61712234, 15015906, 20681921, 45794415, 21070110, 73222868, 82178706, 8807940, 86460488, 33061250, 30395570, 24619760, 44177011, 83789391, 21289531, 82979980, 37183543, 26275734, 58180653, 42199455, 30543215, 16019925, 79880247, 55615885, 2331773, 44847298, 91957544, 23110625, 15535065, 29188588, 6793819, 18411915, 54868730, 69786756, 112651, 66885828, 74724075, 37501808, 79136082, 41245325, 70541760, 4099191, 33699435, 34044787, 59329511, 95957797, 43376279, 97379790, 90004325, 54232247, 1204161, 65047700, 66428795, 16684829, 68204242, 85571389, 95726235, 83302115, 47298834, 24953498, 55247455, 74441448, 67030811, 98948034, 62496012, 5822202, 56424103, 96193415, 57658654, 9603598, 38256849, 54606384, 83269727, 50567636, 45381876, 26292919, 57803235, 17385531, 82726008, 82779622, 21397057, 40534591, 79821897, 10597197, 94595668, 67793644, 168541, 14220886, 74743862, 11365791, 45306070, 5111370, 94911072, 61141874, 57020481, 18833224, 61960400, 71083565, 5267545, 83210802, 59109400, 60176618, 83651211, 35512853, 26102057, 14947650, 97940276, 22435353, 45667668, 30139692, 44473167, 8373289, 8634541, 57241521, 84684495, 96852321, 26863229, 3233084, 83133790, 71965942, 17894977, 51715482, 87160386, 68102437, 75153252, 508198, 74137926, 78602717, 30694952, 66903004, 9860968, 89419466, 75229462, 14326617, 83150534, 21919959, 26397786, 42237907, 22200378, 64848072, 67451935, 18466635, 72495719, 68128438, 84166196, 34236719, 20836893, 16595436, 3633375, 53547802, 55770687, 35456853, 22942635, 44844121, 21673260, 62490109, 68110330, 99604946, 54263880, 176257, 15064655, 75135448, 55753905, 19486173, 77377183, 92071990, 92554120, 38341669, 73168565, 47887470, 61263068, 89217461, 94360702, 4204661, 17058722, 99729357, 73617245, 84002370, 35192533, 61741594, 37560164, 72738685, 9188443, 33249630, 29029316, 73109997, 99965001, 56473732, 7011964, 25636669, 41442762, 68644627, 38022834, 71333116, 85711894, 7517032, 52060076, 69355476, 91938191, 44479073, 39201414, 1375023, 99524975, 64055960, 67474219, 70420215, 79322415, 77187825, 31727379, 33565483, 84187166, 84127901, 50702367, 99515901, 22129328, 14349098, 17727650, 48774913, 16380211, 68694897, 65017137, 9886593, 74614639, 32161669, 82327024, 9599614, 92803766, 38645117, 23432750, 4095116, 35996293, 66137019, 91727510, 2891150, 80316608, 90272749, 19891772, 81855445, 57961282, 88698958, 86001008, 35092039, 11923835, 91141395, 63372756, 48395186, 8055981, 28787861, 28796059, 26776922, 91802888, 55602660, 51315460, 20568363, 60106217, 26392416, 36580610, 9829782, 32250045, 27325428, 90061527, 51590803, 15163258, 30764367, 6525948, 14723410, 7919588, 6153406, 53393358, 53648154, 81898046, 15902805, 64087743, 1569515, 87598888, 79738755, 5482538, 78589145, 22108665, 45990383, 52613508, 69255765, 30569392, 5640302, 39801351, 29932657, 17016156, 569864, 6111563, 53666583, 36685023, 51149804, 52293995, 874791, 26507214, 62936963, 36505482, 30463802, 48892201, 415901, 77620120, 16099750, 12303248, 27187213, 12348118, 44245960, 81172706, 2204165, 7646095, 89811711, 77787724, 49641577, 77898274, 61859143, 20122224, 27665211, 65074535, 50188404, 56153345, 72777973, 73786944, 40356877, 45919976, 16567550, 12024238, 1431742, 4668450, 77072625, 33201905, 45996863, 15536795, 17386996, 47738185, 46540998, 54058788, 247198, 30163921, 61897582, 92353856, 62693428, 56531125, 99125126, 44627776, 95251277, 43501211, 83368048, 24733232, 4806458, 17764950, 76540481, 26493119, 97561745, 92215320, 13231279, 44348328, 90013093, 72274002, 76315420, 8099994, 78785507, 99658235, 61623915, 88653118, 73235980, 44664587, 30998561, 7229550, 26114953, 9259676, 98943869, 1936762, 91831487, 95290172, 95010552, 61271144, 1788101, 63628376, 17976208, 9860195, 59405277, 77272628, 67227442, 73392814, 69605283, 85695762, 22879907, 80246713, 38009615, 20867149, 10649306, 98739783, 42644903, 60581278, 42967683, 7300047, 40197395, 11543098, 84904436, 75986488, 85116755, 98371444, 71300104, 59177718, 32426752, 43322743, 36808486, 6505939, 51464002, 18504197, 99297164, 96726697, 31126490, 16054533, 59910624, 4091162, 4119747, 92692978, 86798033, 10264691, 51466049, 43152977, 83083131, 60955663, 6819644, 72357096, 34497327, 97783876, 37166608, 99333375, 99224392, 44060493, 7502255, 90090964, 44846932, 43506672, 45075471, 39553046, 93057697, 51588015, 45407418, 22405120, 70800879, 4434662, 49598724, 33336148, 33431960, 47361209, 98462867, 19939935, 17957593, 38510840, 16445503, 45237957, 68702632, 81805959, 59981773, 10358899, 6157724, 62430984, 24058273, 70596786, 12571310, 76434144, 13348726, 86301513, 77312810, 43933006, 76671482, 78909561, 61815223, 32590267, 49597667, 92541302, 99690194, 71469330, 93562257, 55960386, 29834512, 2607799, 91664334, 55143724, 18699206, 33935899, 14363867, 60393039, 31491938, 20002147, 66319530, 41092102, 40686254, 4064751, 82897371, 2917920, 44481640, 36139942, 69641513, 58749, 70036438, 37957788, 64098930, 19898053, 70004753, 70727211, 31161687, 93933709, 16097038, 39171895, 3233569, 46851987, 83948335, 39847321, 48658605, 7066775, 57359924, 57240218, 40152546, 2717150, 19457589, 99549373, 13173644, 75052463, 8651647, 55470718, 57393458, 59197747, 24826575, 63152504, 51426311, 47090124, 7182642, 23194618, 72238278, 62428472, 67513640, 29516592, 22997281, 37224844, 9257405, 36780454, 76330843, 55669657, 51047803, 25842078 +52613508, 45407418, 70036438, 44481640, 89078848, 84166196, 24058273, 84904436, 47090124, 5111370, 93566986, 4806458, 70800879, 27411561, 35996293, 61623915, 47792865, 30090481, 20765474, 94360702, 75986488, 18783702, 95957797, 2607799, 83302115, 41092102, 24168411, 99333375, 10453030, 16380211, 168541, 84349107, 83651211, 45617087, 16445503, 75229462, 57393458, 30366150, 9829782, 54663246, 7919588, 21070110, 65275241, 62803858, 75777973, 61263068, 33553959, 48088883, 63506281, 73617245, 73124510, 85116755, 32426752, 43322743, 71522968, 4099191, 56461322, 25636669, 68644627, 54232247, 18699206, 49271185, 9257405, 16567550, 1431742, 77187825, 67513640, 94595668, 73031054, 97641116, 45306070, 67124282, 91281584, 83368048, 92803766, 4095116, 49598724, 3509435, 69641513, 90457870, 48395186, 79657802, 4515343, 74137926, 67084351, 13862149, 6497830, 20885148, 6525948, 89499542, 30891921, 5482538, 88130087, 13348726, 68000591, 63059024, 10649306, 98739783, 4204661, 82052050, 72614359, 9175338, 18411915, 70372191, 92998591, 2204165, 83747892, 18504197, 55960386, 7011964, 4508700, 33237508, 43376279, 14045383, 49641577, 69355476, 1375023, 99524975, 55247455, 67474219, 96193415, 62762857, 76330843, 54058788, 61897582, 669105, 44983451, 22721500, 74743862, 2917920, 62693428, 62452034, 29065964, 71083565, 79191827, 8373289, 92215320, 43357947, 84840549, 88653118, 62357986, 60430369, 91802888, 10358899, 51472275, 6157724, 62430984, 89996536, 67227442, 53547802, 55770687, 44844121, 8807940, 176257, 99861373, 12571310, 75135448, 59197747, 19486173, 92071990, 86301513, 7423788, 31161687, 42967683, 39171895, 76671482, 66271566, 84293052, 2331773, 46851987, 4798568, 78909561, 19101477, 415901, 6793819, 51047803, 71300104, 89214616, 59177718, 93562257, 36808486, 58751351, 34044787, 89811711, 22113133, 29834512, 78766358, 81774825, 19272365, 14363867, 16684829, 50188404, 72373496, 73786944, 94711842, 60955663, 4668450, 20002147, 20140249, 77072625, 36780454, 11581181, 84187166, 57240218, 52261574, 46870723, 32151165, 94076128, 2717150, 92353856, 68694897, 99125126, 45075471, 15148031, 90310261, 51588015, 92787493, 17764950, 76540481, 97940276, 80316608, 33797252, 45667668, 97561745, 90272749, 80251430, 78218845, 81855445, 71965942, 38510840, 78785507, 34698428, 58224549, 8055981, 66250369, 96735716, 95290172, 36468541, 61271144, 26397786, 36580610, 48675329, 10309525, 15948937, 18466635, 90061527, 59614746, 34236719, 81853704, 65338021, 45794415, 69605283, 38658347, 99604946, 3773993, 70004753, 20867149, 76434144, 67031644, 69255765, 43045786, 13819358, 42644903, 82886381, 17016156, 37675718, 37183543, 60102965, 3233569, 85117092, 30653863, 44847298, 35192533, 30463802, 1239555, 83948335, 91957544, 32590267, 72738685, 29635537, 98371444, 92604458, 69786756, 29029316, 6505939, 51464002, 51426311, 99965001, 86118021, 16054533, 59910624, 97379790, 16424199, 27625735, 99021067, 85571389, 29994197, 43152977, 20645197, 74441448, 86242799, 6819644, 30218878, 86022504, 83269727, 50702367, 79821897, 40152546, 67793644, 82897371, 56531125, 321665, 526217, 77300457, 43506672, 43501211, 61960400, 60176618, 4787945, 35512853, 79942022, 66137019, 91727510, 33336148, 26493119, 44473167, 13173644, 57961282, 86001008, 27185644, 51715482, 49882705, 88251446, 99658235, 40865610, 10961421, 66611759, 54762643, 44664587, 3088684, 66667729, 36312813, 28787861, 26776922, 49328608, 508198, 96420429, 57248122, 62115552, 66903004, 85023028, 14093520, 55470718, 14326617, 83150534, 91990218, 28734791, 15075176, 59405277, 92398073, 53802686, 68128438, 15163258, 15015906, 65715134, 6153406, 73392814, 35456853, 53648154, 22879907, 21673260, 40984766, 38009615, 38061439, 33061250, 89046466, 70727211, 76170907, 98648327, 77377183, 9575954, 30569392, 5640302, 39801351, 38341669, 61728685, 55850790, 47887470, 89217461, 93933709, 7300047, 53666583, 17058722, 42199455, 76703609, 51507425, 874791, 26507214, 62740044, 48892201, 41380093, 92692283, 57163802, 7685448, 42692881, 47708850, 7646095, 79136082, 67281495, 56473732, 8791066, 38022834, 52204879, 96726697, 31126490, 7182642, 41481685, 36135, 32058615, 68316156, 30811010, 4119747, 71920426, 27665211, 1204161, 65047700, 92033260, 76960303, 56153345, 72777973, 39201414, 95726235, 18806856, 8733068, 12024238, 47298834, 40781854, 77284619, 59501487, 99226875, 57658654, 36930650, 37166608, 33201905, 33565483, 39986008, 56955985, 12664567, 53632373, 17386996, 4064751, 17727650, 47738185, 21397057, 5832946, 71657078, 40534591, 96318094, 59318837, 34432810, 97057187, 65851721, 66832478, 53888755, 54987042, 86821229, 14220886, 11365791, 48893685, 16387575, 65038678, 18833224, 39553046, 92530431, 9599614, 5267545, 36139942, 69136837, 76825057, 90158785, 99549373, 16405341, 9058407, 48673079, 14947650, 75543508, 22435353, 97281847, 47361209, 17539625, 75820087, 84684495, 26863229, 83133790, 17894977, 8099994, 58749, 11923835, 91141395, 63372756, 68702632, 54199160, 33895336, 66045587, 2208785, 79922758, 3183975, 8651647, 91831487, 68816413, 59981773, 42947632, 21919959, 26392416, 63628376, 49236559, 44915235, 93515664, 77272628, 97011160, 14626618, 64098930, 20681921, 16595436, 53393358, 15902805, 61373987, 1569515, 87598888, 40027975, 15064655, 8729146, 55753905, 24826575, 95395112, 60581278, 33123618, 77312810, 86543538, 6111563, 16097038, 43933006, 72793444, 55189057, 11543098, 52293995, 8913721, 54427233, 80014588, 84002370, 36845587, 62936963, 37739481, 61815223, 61741594, 49597667, 39847321, 9188443, 16099750, 96906410, 42073124, 112651, 33249630, 12303248, 66885828, 27187213, 18663507, 7066775, 33699435, 59329511, 5970607, 77787724, 71333116, 27041967, 18131876, 7517032, 57359924, 4091162, 20122224, 37659250, 74110882, 82532312, 65074535, 72238278, 83083131, 824872, 5822202, 61982238, 97783876, 38256849, 87710366, 89699445, 26744917, 37192445, 40686254, 17081350, 96709982, 17385531, 6871053, 45995325, 247198, 10597197, 50806615, 91255408, 4985896, 6521313, 56515456, 88904910, 36812683, 19457589, 57020481, 74614639, 54517921, 31904591, 82339363, 68824981, 26664538, 44842615, 91240048, 59109400, 83533741, 2891150, 93053405, 30139692, 8634541, 57241521, 21533347, 44348328, 25842078, 21001913, 90013093, 72274002, 22450468, 93359396, 26998766, 75128745, 6221471, 73235980, 45237957, 3880712, 82427263, 6038457, 81805959, 51315460, 20568363, 38494874, 1936762, 89419466, 95010552, 36933038, 42237907, 37280276, 34667286, 30764367, 38008118, 17857111, 70596786, 19898053, 78884452, 62232211, 22942635, 24619760, 31733363, 93790285, 37224844, 64602895, 22108665, 62552524, 8129978, 92554120, 77301523, 82979980, 26275734, 58180653, 40197395, 30543215, 36685023, 34698463, 26063929, 66322959, 36505482, 7955293, 23110625, 29188588, 48658605, 99690194, 22766820, 77694700, 12348118, 44245960, 71469330, 37620363, 63152504, 41245325, 23740167, 50007421, 41442762, 39373729, 55143724, 77898274, 61859143, 52060076, 40781449, 90654836, 79806380, 33935899, 91938191, 7687278, 66428795, 44479073, 45919976, 10264691, 51466049, 56484900, 67030811, 3294781, 66663942, 9603598, 55669657, 54606384, 17037369, 68939068, 50567636, 45996863, 8128637, 15536795, 6986898, 83155442, 1250437, 99515901, 82726008, 30771409, 34946859, 37891451, 90090964, 89637706, 61141874, 44627776, 36753250, 45049308, 24733232, 19376156, 13470059, 72725103, 41092172, 94516935, 94090109, 19891772, 13231279, 19939935, 17957593, 35092039, 76315420, 87160386, 68102437, 28928175, 65271999, 7104732, 93270084, 95581843, 30998561, 78602717, 26114953, 78549759, 33628349, 75052463, 98943869, 70782102, 1788101, 22200378, 749283, 36189527, 37957788, 65454636, 32250045, 27325428, 51590803, 34493392, 98130363, 1197320, 61712234, 21993752, 82178706, 80246713, 86460488, 30395570, 44177011, 3235882, 47213183, 78561158, 73168565, 21289531, 29959549, 1022677, 48260151, 65081429, 80555751, 77620120, 92541302, 15535065, 45428665, 5073754, 74724075, 44889423, 37501808, 73109997, 99297164, 29466406, 91664334, 85711894, 28851716, 72732205, 86798033, 4069912, 50668599, 68204242, 29401781, 64055960, 78300864, 56424103, 34497327, 70420215, 31727379, 45381876, 77413012, 36396314, 84127901, 57803235, 14349098, 82779622, 44060493, 29819940, 7502255, 72278539, 69697787, 94911072, 72358170, 9886593, 44846932, 95251277, 32161669, 82327024, 61859581, 10366309, 38645117, 8115266, 59371804, 45790169, 29516592, 33431960, 58208470, 98462867, 88698958, 17068582, 28550822, 3487592, 9623492, 53842979, 55602660, 23569917, 88444207, 84406788, 64848072, 67451935, 4978543, 17976208, 14723410, 69848388, 24591705, 62490109, 12416768, 54263880, 48360198, 65880522, 64157906, 45990383, 75407347, 66116458, 29932657, 569864, 23134715, 51149804, 99729357, 38365584, 54868730, 70367851, 6808825, 70541760, 16971929, 82651278, 90004325, 24915585, 23194618, 31491938, 24953498, 66319530, 98948034, 72357096, 74452589, 62428472, 45848907, 26292919, 22129328, 48774913, 46540998, 44550764, 32274392, 99917599, 93057697, 23848565, 83210802, 40677414, 23432750, 22405120, 26102057, 85242963, 96852321, 3233084, 72019362, 75153252, 53084256, 28796059, 42782652, 30694952, 81274566, 60106217, 54014062, 47893286, 11161731, 72495719, 17365188, 32699744, 20486294, 81898046, 30787683, 83789391, 79738755, 44784505, 68875490, 62051033, 92867155, 89804152, 98653983, 55615885, 34295794, 81172706, 17146629, 74357852, 24314885, 40356877, 60393039, 14731700, 11757872, 99971982, 30163921, 65017137, 26139110, 9259676, 9860195, 22997281, 94539824, 26734892, 20836893, 85695762, 68110330, 78589145, 61928316, 42307449, 16019925, 13468268, 88047921, 87720882, 37435892, 73021291, 79322415, 20642888, 4434662, 18001617, 69579137, 4199704, 81677380, 3633375, 73222868, 64087743, 63967300, 71316369, 99224392, 9398733, 68041839, 29510992, 33304202, 32159704, 79880247, 62496012, 63015256, 9860968, 37560164, 7229550, 92692978, 8696647 +31126490, 13173644, 4095116, 78766358, 5822202, 10597197, 24733232, 78602717, 67227442, 60102965, 36505482, 7011964, 25636669, 61960400, 84349107, 17764950, 2891150, 49882705, 83150534, 1788101, 6153406, 30395570, 12571310, 98648327, 9575954, 92071990, 7423788, 57163802, 9188443, 59177718, 32426752, 12348118, 18663507, 33237508, 71920426, 72373496, 56424103, 321665, 82327024, 14947650, 33797252, 29516592, 13231279, 3509435, 73235980, 95581843, 33895336, 59981773, 18466635, 30764367, 3633375, 21070110, 20867149, 55753905, 44784505, 63059024, 68875490, 82886381, 82979980, 51149804, 29188588, 48658605, 63967300, 2204165, 71469330, 63152504, 70541760, 99965001, 34044787, 59329511, 7517032, 65047700, 92033260, 76960303, 44479073, 40356877, 18806856, 40781854, 66319530, 20140249, 824872, 61982238, 59318837, 45995325, 53888755, 54987042, 669105, 14220886, 82897371, 56531125, 92530431, 26664538, 9599614, 8115266, 45407418, 48673079, 27411561, 35996293, 30139692, 88698958, 83133790, 11923835, 54762643, 93270084, 68702632, 26776922, 3183975, 81805959, 89419466, 14093520, 42237907, 48675329, 70036438, 93515664, 67451935, 22997281, 68128438, 15015906, 32699744, 82178706, 64087743, 87598888, 78589145, 5640302, 10649306, 65275241, 31161687, 569864, 42967683, 48088883, 26275734, 17058722, 63506281, 84002370, 44847298, 61815223, 73124510, 77620120, 75986488, 45428665, 85116755, 98371444, 42692881, 83747892, 8791066, 71333116, 7182642, 32058615, 81774825, 20122224, 37659250, 18699206, 73786944, 16567550, 83302115, 24953498, 86242799, 20002147, 67474219, 66663942, 79322415, 83269727, 45996863, 96709982, 5832946, 79821897, 40152546, 99971982, 97057187, 66832478, 44983451, 89637706, 88904910, 29065964, 31904591, 10366309, 76825057, 92787493, 16405341, 68041839, 97281847, 90272749, 8634541, 94090109, 80251430, 84684495, 76315420, 84840549, 88251446, 61623915, 63372756, 7104732, 66667729, 28787861, 28796059, 54199160, 79657802, 79922758, 7229550, 62115552, 74137926, 26114953, 21919959, 6497830, 28734791, 11161731, 27325428, 34236719, 38008118, 7919588, 65715134, 53547802, 44844121, 38009615, 86460488, 99604946, 99861373, 40027975, 75135448, 98739783, 95395112, 13819358, 42644903, 62803858, 21289531, 58180653, 66271566, 85117092, 99729357, 48260151, 84904436, 51047803, 54868730, 42073124, 51464002, 18783702, 4099191, 56461322, 33699435, 41442762, 29834512, 16971929, 55143724, 16424199, 4091162, 30811010, 27625735, 54232247, 27665211, 79806380, 49271185, 63015256, 16684829, 50188404, 72238278, 8733068, 37435892, 60955663, 14731700, 72357096, 55669657, 54606384, 86022504, 87710366, 36780454, 56955985, 45848907, 45381876, 57803235, 83155442, 30771409, 32151165, 50806615, 90090964, 92353856, 5111370, 62693428, 65017137, 9886593, 79191827, 99917599, 92803766, 40677414, 38645117, 59371804, 80316608, 22435353, 45667668, 45617087, 69579137, 96852321, 3233084, 17894977, 8099994, 28550822, 10961421, 58224549, 30998561, 82427263, 8651647, 66903004, 47792865, 55470718, 26397786, 36580610, 51472275, 44915235, 15075176, 10309525, 19898053, 78884452, 69605283, 20486294, 35456853, 22942635, 15902805, 54263880, 76170907, 19486173, 30090481, 66116458, 43045786, 38341669, 92867155, 61728685, 47887470, 29959549, 61263068, 89217461, 16097038, 43933006, 3233569, 55189057, 30543215, 8913721, 13468268, 874791, 73617245, 72614359, 37739481, 415901, 83948335, 92692283, 23110625, 9175338, 7685448, 71300104, 27187213, 47708850, 71522968, 7646095, 37620363, 73109997, 7066775, 29466406, 18131876, 88047921, 90004325, 40781449, 82532312, 66428795, 14363867, 85571389, 29994197, 39201414, 45919976, 47298834, 1431742, 73021291, 62496012, 36930650, 38256849, 41092102, 17037369, 84187166, 17386996, 71657078, 46540998, 40534591, 54058788, 247198, 94076128, 168541, 4985896, 22721500, 6521313, 44550764, 69697787, 68694897, 56515456, 18833224, 43501211, 54517921, 32274392, 5267545, 83210802, 90310261, 99549373, 22405120, 76540481, 94516935, 91727510, 26139110, 33431960, 47361209, 21533347, 58208470, 75820087, 90013093, 35092039, 16445503, 72019362, 78785507, 28928175, 3487592, 58749, 91141395, 6221471, 9623492, 45237957, 3880712, 66045587, 49328608, 23569917, 1936762, 9860968, 68816413, 42947632, 95290172, 36468541, 67084351, 30366150, 54014062, 9829782, 749283, 4199704, 9860195, 59405277, 65454636, 90061527, 17365188, 97011160, 14626618, 84166196, 54663246, 64098930, 30891921, 8807940, 12416768, 3773993, 24619760, 30787683, 1569515, 70727211, 79738755, 15064655, 59197747, 24826575, 65880522, 5482538, 13348726, 61928316, 52613508, 33553959, 93933709, 94360702, 7300047, 72793444, 98653983, 82052050, 34698463, 51507425, 80014588, 78909561, 35192533, 7955293, 61741594, 91957544, 72738685, 39847321, 70372191, 43322743, 66885828, 77694700, 81172706, 37501808, 93562257, 18504197, 41245325, 67281495, 55960386, 56473732, 47090124, 89811711, 71316369, 52204879, 2607799, 74357852, 14045383, 16054533, 49641577, 68316156, 57359924, 1204161, 4069912, 99021067, 95726235, 12024238, 60393039, 56484900, 78300864, 77284619, 3294781, 11757872, 74452589, 11581181, 31727379, 50567636, 39986008, 15536795, 26292919, 89078848, 57240218, 6986898, 50702367, 52261574, 46870723, 29819940, 96318094, 6871053, 34432810, 65851721, 73031054, 94911072, 44846932, 77300457, 43506672, 74614639, 45075471, 39553046, 83368048, 32161669, 36139942, 90158785, 23432750, 72725103, 51588015, 9058407, 85242963, 97940276, 66137019, 45790169, 49598724, 26493119, 81855445, 92215320, 57961282, 98462867, 17957593, 72274002, 90457870, 22450468, 40865610, 75128745, 34698428, 88653118, 44664587, 66250369, 508198, 2208785, 96420429, 91802888, 75052463, 91831487, 85023028, 36933038, 49236559, 37280276, 47893286, 36189527, 92398073, 34667286, 32250045, 77272628, 15163258, 89996536, 59614746, 69848388, 98130363, 73392814, 85695762, 21993752, 73222868, 22879907, 38061439, 44177011, 176257, 93790285, 8729146, 88130087, 64157906, 68000591, 75407347, 86301513, 92554120, 20765474, 55850790, 37675718, 37183543, 53666583, 89804152, 36685023, 11543098, 16019925, 66322959, 55615885, 26507214, 36845587, 19101477, 48892201, 34295794, 29635537, 16099750, 92604458, 99690194, 74724075, 44889423, 4508700, 99297164, 38022834, 27041967, 96726697, 77898274, 52060076, 90654836, 28851716, 92692978, 91938191, 19272365, 86798033, 50668599, 68204242, 9257405, 51466049, 83083131, 99226875, 96193415, 70420215, 77072625, 57658654, 62762857, 33201905, 89699445, 68939068, 26744917, 8128637, 53632373, 84127901, 40686254, 1250437, 99515901, 22129328, 14349098, 17727650, 7502255, 37891451, 16380211, 2717150, 67124282, 44627776, 91281584, 57020481, 36753250, 65038678, 71083565, 93566986, 44481640, 61859581, 44842615, 93057697, 19376156, 69136837, 59109400, 83651211, 4787945, 35512853, 4806458, 18001617, 97561745, 8373289, 57241521, 78218845, 19891772, 44348328, 69641513, 19939935, 71965942, 38510840, 21001913, 51715482, 93359396, 65271999, 42782652, 55602660, 6038457, 30694952, 20568363, 81274566, 75229462, 14326617, 95010552, 88444207, 26392416, 91990218, 57393458, 84406788, 10358899, 4978543, 34493392, 62430984, 32159704, 14723410, 61712234, 16595436, 24058273, 53393358, 38658347, 21673260, 80246713, 40984766, 33061250, 64602895, 67031644, 77377183, 62552524, 47213183, 78561158, 6111563, 4204661, 42307449, 26063929, 84293052, 65081429, 2331773, 80555751, 62936963, 30463802, 32590267, 41380093, 92541302, 18411915, 89214616, 69786756, 92998591, 22766820, 5073754, 29029316, 79136082, 6808825, 22113133, 5970607, 41481685, 82651278, 61859143, 24915585, 24314885, 87720882, 23194618, 72777973, 31491938, 6819644, 59501487, 34497327, 37166608, 62428472, 67513640, 77413012, 36396314, 76330843, 44060493, 94595668, 30163921, 61897582, 72278539, 48893685, 9398733, 72358170, 99125126, 61141874, 95251277, 82339363, 68824981, 15148031, 23848565, 60176618, 20642888, 26102057, 79942022, 29510992, 17539625, 26863229, 86001008, 33304202, 68102437, 62357986, 8055981, 36312813, 96735716, 4515343, 57248122, 98943869, 60106217, 61271144, 37957788, 17976208, 81677380, 94539824, 6525948, 1197320, 20836893, 81853704, 65338021, 81898046, 62490109, 68110330, 89046466, 48360198, 76434144, 22108665, 8129978, 75777973, 73168565, 60581278, 33123618, 17016156, 1022677, 77301523, 39171895, 42199455, 76671482, 52293995, 54427233, 46851987, 62740044, 49597667, 37560164, 15535065, 6793819, 112651, 44245960, 70367851, 51426311, 86118021, 58751351, 39373729, 77787724, 43376279, 85711894, 59910624, 97379790, 72732205, 69355476, 74110882, 33935899, 65074535, 56153345, 99524975, 55247455, 4668450, 98948034, 30218878, 9603598, 97783876, 24168411, 33565483, 17081350, 17385531, 82726008, 47738185, 48774913, 34946859, 67793644, 97641116, 86821229, 74743862, 8696647, 45306070, 19457589, 45049308, 16387575, 41092172, 83533741, 93053405, 4434662, 33336148, 44473167, 25842078, 27185644, 87160386, 75153252, 3088684, 53842979, 60430369, 78549759, 38494874, 70782102, 22200378, 20885148, 26734892, 45794415, 53648154, 83789391, 37224844, 3235882, 77312810, 23134715, 76703609, 79880247, 30653863, 38365584, 33249630, 50007421, 17146629, 68644627, 95957797, 91664334, 36135, 4119747, 7687278, 10264691, 43152977, 20645197, 64055960, 99333375, 37192445, 12664567, 10453030, 91255408, 526217, 91240048, 13470059, 75543508, 17068582, 43357947, 26998766, 66611759, 48395186, 13862149, 63628376, 6157724, 53802686, 72495719, 24591705, 20681921, 70596786, 89499542, 55770687, 62232211, 31733363, 45990383, 69255765, 86543538, 40197395, 12303248, 6505939, 23740167, 74441448, 77187825, 99224392, 21397057, 62452034, 36812683, 70800879, 99658235, 33628349, 51315460, 9259676, 51590803, 17857111, 61373987, 30569392, 39801351, 4798568, 1239555, 96906410, 36808486, 29401781, 94711842, 1375023, 4064751, 11365791, 2917920, 64848072, 70004753, 67030811, 82779622, 15948937, 62051033, 29932657, 53084256 +47792865, 72373496, 76330843, 68824981, 55960386, 58751351, 95957797, 98948034, 94595668, 35996293, 69579137, 87598888, 8129978, 57163802, 37620363, 6808825, 37659250, 19272365, 5832946, 90310261, 2891150, 96735716, 96420429, 82178706, 53666583, 79136082, 83747892, 5970607, 49641577, 7517032, 49271185, 18806856, 31491938, 99524975, 33201905, 26744917, 16380211, 66832478, 14220886, 68694897, 39553046, 19376156, 69136837, 14947650, 47361209, 68102437, 8055981, 9623492, 93270084, 26776922, 508198, 98943869, 59981773, 26392416, 6497830, 92398073, 78884452, 38658347, 44844121, 8729146, 98648327, 68000591, 10649306, 92867155, 30463802, 415901, 49597667, 39847321, 92604458, 70541760, 22113133, 78766358, 79806380, 72238278, 64055960, 60955663, 20140249, 96193415, 11757872, 45996863, 84127901, 17385531, 10453030, 96318094, 2717150, 69697787, 56515456, 65017137, 43501211, 93057697, 85242963, 75543508, 49598724, 21001913, 90013093, 72019362, 28928175, 40865610, 66045587, 30998561, 30694952, 36933038, 10358899, 17365188, 38008118, 45794415, 73392814, 89499542, 21993752, 8807940, 40984766, 68110330, 15064655, 61928316, 62552524, 95395112, 55850790, 61263068, 37675718, 4204661, 40197395, 54427233, 51507425, 73617245, 36845587, 62936963, 71300104, 22766820, 77694700, 12348118, 18663507, 86118021, 4508700, 34044787, 33237508, 52204879, 91664334, 92692978, 9257405, 95726235, 20645197, 78300864, 83083131, 40781854, 83269727, 12664567, 53632373, 99515901, 34432810, 97641116, 44983451, 22721500, 44846932, 57020481, 74614639, 32274392, 4787945, 76540481, 59371804, 44473167, 29510992, 58208470, 38510840, 16445503, 90457870, 93359396, 26998766, 58749, 34698428, 73235980, 28787861, 54199160, 3183975, 68816413, 13862149, 6157724, 15163258, 67227442, 20681921, 65715134, 24058273, 65338021, 21070110, 62232211, 86460488, 33061250, 30395570, 31733363, 76170907, 47213183, 69255765, 63059024, 7423788, 98739783, 13819358, 20765474, 65275241, 61728685, 60581278, 29959549, 569864, 33553959, 42967683, 17058722, 8913721, 30653863, 61815223, 19101477, 91957544, 38365584, 77620120, 85116755, 89214616, 33249630, 66885828, 44245960, 71522968, 56461322, 7011964, 25636669, 29834512, 43376279, 16424199, 77898274, 90004325, 69355476, 27665211, 18699206, 33935899, 4069912, 50668599, 23194618, 72777973, 85571389, 8733068, 24953498, 74441448, 4668450, 99226875, 61982238, 77072625, 55669657, 38256849, 37166608, 37192445, 89078848, 96709982, 22129328, 48774913, 40534591, 6871053, 67793644, 50806615, 90090964, 48893685, 45306070, 36812683, 77300457, 18833224, 54517921, 99917599, 31904591, 93566986, 32161669, 92803766, 35512853, 17764950, 22405120, 4095116, 91727510, 93053405, 29516592, 8634541, 33431960, 81855445, 69641513, 96852321, 71965942, 84840549, 88251446, 48395186, 44664587, 66250369, 74137926, 26114953, 91831487, 36468541, 88444207, 67084351, 91990218, 57393458, 84406788, 9829782, 48675329, 36189527, 28734791, 53802686, 90061527, 51590803, 97011160, 14626618, 34493392, 32699744, 53648154, 15902805, 38061439, 24619760, 61373987, 40027975, 30090481, 22108665, 9575954, 78561158, 5640302, 31161687, 62803858, 29932657, 89217461, 48088883, 26275734, 58180653, 82052050, 11543098, 76703609, 66322959, 80014588, 84904436, 84002370, 4798568, 44847298, 80555751, 78909561, 7955293, 83948335, 73124510, 72738685, 70372191, 59177718, 32426752, 42073124, 63967300, 42692881, 5073754, 70367851, 37501808, 67281495, 51426311, 47090124, 17146629, 41442762, 89811711, 59329511, 71333116, 29466406, 2607799, 85711894, 41481685, 82651278, 4091162, 24915585, 72732205, 54232247, 71920426, 7687278, 86798033, 68204242, 29994197, 39201414, 16567550, 1431742, 20002147, 67474219, 30218878, 3294781, 62496012, 59501487, 41092102, 89699445, 84187166, 50567636, 45848907, 17386996, 40686254, 52261574, 82726008, 99224392, 17727650, 47738185, 54058788, 37891451, 72278539, 92353856, 9398733, 5111370, 62693428, 91281584, 36753250, 16387575, 61960400, 71083565, 83368048, 82339363, 9599614, 84349107, 8115266, 4806458, 45407418, 26102057, 83533741, 66137019, 26139110, 45790169, 4434662, 33797252, 8373289, 94090109, 78218845, 3509435, 98462867, 25842078, 83133790, 33304202, 75128745, 88653118, 45237957, 95581843, 79657802, 53842979, 62115552, 78602717, 9259676, 8651647, 66903004, 85023028, 42947632, 14093520, 55470718, 75229462, 95290172, 70782102, 61271144, 54014062, 17976208, 9860195, 59405277, 11161731, 18466635, 22997281, 89996536, 84166196, 64098930, 6525948, 26734892, 6153406, 69605283, 30891921, 12416768, 99861373, 75135448, 48360198, 92071990, 66116458, 82886381, 21289531, 17016156, 77301523, 16097038, 43933006, 7300047, 99729357, 13468268, 65081429, 2331773, 46851987, 37739481, 1239555, 92541302, 18411915, 74724075, 44889423, 81172706, 2204165, 4099191, 33699435, 8791066, 71316369, 18131876, 7182642, 30811010, 82532312, 1204161, 65074535, 50188404, 87720882, 29401781, 73786944, 51466049, 83302115, 1375023, 73021291, 824872, 34497327, 57658654, 74452589, 86022504, 24168411, 11581181, 31727379, 39986008, 45381876, 8128637, 15536795, 6986898, 50702367, 17081350, 1250437, 32151165, 45995325, 40152546, 94076128, 10597197, 99971982, 97057187, 65851721, 321665, 62452034, 99125126, 29065964, 19457589, 45049308, 45075471, 26664538, 82327024, 90158785, 70800879, 22435353, 97281847, 80251430, 13173644, 21533347, 92215320, 75820087, 44348328, 26863229, 17957593, 17894977, 35092039, 22450468, 49882705, 99658235, 61623915, 53084256, 3487592, 11923835, 6221471, 36312813, 49328608, 79922758, 91802888, 6038457, 81805959, 23569917, 38494874, 14326617, 36580610, 70036438, 4978543, 15075176, 20885148, 34667286, 27325428, 68128438, 62430984, 32159704, 53547802, 53393358, 55770687, 85695762, 21673260, 3773993, 44177011, 89046466, 1569515, 70004753, 176257, 20867149, 70727211, 64602895, 75407347, 30569392, 68875490, 75777973, 6111563, 60102965, 76671482, 52293995, 34698463, 63506281, 26507214, 62740044, 72614359, 48892201, 15535065, 51047803, 112651, 12303248, 27187213, 7646095, 73109997, 36808486, 18783702, 56473732, 39373729, 68644627, 38022834, 77787724, 27041967, 74357852, 31126490, 81774825, 68316156, 57359924, 27625735, 24314885, 91938191, 16684829, 56153345, 60393039, 47298834, 37435892, 55247455, 67030811, 72357096, 70420215, 36930650, 79322415, 54606384, 33565483, 67513640, 57240218, 4064751, 29819940, 34946859, 73031054, 53888755, 4985896, 82897371, 2917920, 67124282, 56531125, 9886593, 61141874, 44627776, 95251277, 92530431, 44481640, 61859581, 44842615, 23848565, 72725103, 99549373, 92787493, 41092172, 16405341, 79942022, 45667668, 97561745, 57241521, 19891772, 57961282, 84684495, 88698958, 19939935, 17068582, 43357947, 8099994, 91141395, 66611759, 54762643, 3088684, 66667729, 68702632, 3880712, 7229550, 33628349, 51315460, 1936762, 81274566, 60106217, 21919959, 42237907, 63628376, 64848072, 4199704, 67451935, 81677380, 94539824, 54663246, 98130363, 17857111, 7919588, 61712234, 15015906, 81853704, 35456853, 22879907, 22942635, 62490109, 54263880, 83789391, 12571310, 19486173, 24826575, 5482538, 78589145, 44784505, 3235882, 52613508, 86301513, 39801351, 62051033, 33123618, 47887470, 77312810, 82979980, 93933709, 23134715, 86543538, 89804152, 72793444, 16019925, 79880247, 36505482, 61741594, 29635537, 9188443, 7685448, 69786756, 92998591, 43322743, 47708850, 51464002, 41245325, 16971929, 97379790, 36135, 52060076, 40781449, 28851716, 63015256, 14363867, 44479073, 99021067, 56484900, 86242799, 14731700, 6819644, 5822202, 56424103, 9603598, 62762857, 97783876, 77187825, 36780454, 17037369, 68939068, 14349098, 46870723, 21397057, 71657078, 79821897, 59318837, 247198, 86821229, 6521313, 44550764, 74743862, 8696647, 88904910, 526217, 65038678, 43506672, 79191827, 24733232, 10366309, 36139942, 91240048, 83210802, 40677414, 38645117, 59109400, 27411561, 94516935, 33336148, 26493119, 18001617, 86001008, 76315420, 78785507, 28550822, 65271999, 58224549, 4515343, 57248122, 55602660, 9860968, 89419466, 83150534, 95010552, 49236559, 749283, 51472275, 93515664, 37957788, 34236719, 1197320, 20836893, 24591705, 16595436, 3633375, 70596786, 19898053, 20486294, 81898046, 38009615, 64087743, 99604946, 30787683, 37224844, 67031644, 43045786, 42644903, 73168565, 94360702, 3233569, 55189057, 66271566, 42307449, 48260151, 26063929, 874791, 84293052, 35192533, 32590267, 41380093, 75986488, 6793819, 16099750, 98371444, 48658605, 54868730, 71469330, 18504197, 88047921, 55143724, 14045383, 16054533, 32058615, 74110882, 76960303, 45919976, 10264691, 66663942, 87710366, 56955985, 26292919, 57803235, 82779622, 168541, 669105, 91255408, 94911072, 15148031, 5267545, 51588015, 9058407, 97940276, 45617087, 17539625, 13231279, 3233084, 51715482, 87160386, 63372756, 33895336, 42782652, 2208785, 60430369, 78549759, 20568363, 75052463, 26397786, 30366150, 37280276, 47893286, 44915235, 65454636, 32250045, 59614746, 73222868, 55753905, 64157906, 45990383, 1022677, 36685023, 37560164, 29188588, 9175338, 96906410, 99690194, 93562257, 6505939, 7066775, 99965001, 99297164, 96726697, 59910624, 90654836, 4119747, 92033260, 94711842, 43152977, 77284619, 36396314, 30771409, 7502255, 54987042, 60176618, 13470059, 83651211, 20642888, 48673079, 75153252, 10961421, 7104732, 1788101, 10309525, 72495719, 77272628, 30764367, 69848388, 79738755, 59197747, 13348726, 98653983, 42199455, 51149804, 85117092, 55615885, 23110625, 34295794, 45428665, 50007421, 61859143, 20122224, 65047700, 66428795, 40356877, 66319530, 62428472, 77413012, 44060493, 46540998, 89637706, 23432750, 90272749, 30139692, 62357986, 82427263, 22200378, 15948937, 14723410, 93790285, 65880522, 88130087, 38341669, 37183543, 29029316, 63152504, 23740167, 12024238, 83155442, 72358170, 76825057, 80316608, 68041839, 80246713, 77377183, 92554120, 39171895, 30543215, 92692283, 99333375, 61897582, 11365791, 27185644, 30163921, 72274002, 28796059, 76434144 +6819644, 7502255, 86821229, 15148031, 13231279, 3633375, 79880247, 99965001, 74743862, 83368048, 4806458, 13819358, 61815223, 92692283, 36780454, 39986008, 97057187, 93566986, 57961282, 28928175, 28796059, 79657802, 11161731, 90061527, 84166196, 69605283, 35456853, 30787683, 5482538, 569864, 82979980, 39171895, 66322959, 18663507, 68316156, 30218878, 33565483, 67513640, 73031054, 67124282, 72358170, 60176618, 4787945, 27411561, 17894977, 17068582, 26998766, 57393458, 36189527, 10309525, 94539824, 30395570, 61373987, 77377183, 66116458, 43045786, 98739783, 92554120, 1022677, 86543538, 3233569, 40197395, 98371444, 12348118, 18783702, 56473732, 58751351, 71333116, 96726697, 59910624, 32058615, 77898274, 61859143, 24915585, 27665211, 65074535, 56153345, 73786944, 83302115, 60393039, 86242799, 20002147, 36930650, 97783876, 62428472, 99224392, 17727650, 34946859, 669105, 6521313, 44550764, 11365791, 8696647, 5111370, 56531125, 99125126, 32161669, 61859581, 91240048, 83210802, 35512853, 41092172, 68041839, 26493119, 97561745, 90272749, 8373289, 47361209, 87160386, 88653118, 66250369, 508198, 2208785, 96420429, 9860968, 81274566, 83150534, 95290172, 54014062, 47893286, 48675329, 15075176, 15948937, 77272628, 81677380, 54663246, 19898053, 78884452, 62232211, 99604946, 1569515, 99861373, 76434144, 64157906, 30090481, 3235882, 38341669, 29932657, 82886381, 21289531, 72793444, 42199455, 63506281, 874791, 84904436, 37739481, 37560164, 45428665, 77694700, 29029316, 37501808, 93562257, 47090124, 74357852, 49641577, 41481685, 28851716, 24314885, 1204161, 85571389, 29994197, 51466049, 94711842, 5822202, 66663942, 62762857, 41092102, 24168411, 33201905, 40686254, 82726008, 47738185, 82779622, 32151165, 6871053, 247198, 61897582, 91255408, 22721500, 82897371, 68694897, 44627776, 36812683, 91281584, 93057697, 90310261, 13470059, 83651211, 97940276, 33336148, 13173644, 98462867, 27185644, 28550822, 91141395, 63372756, 66611759, 9623492, 36312813, 3880712, 42782652, 6038457, 62115552, 81805959, 75052463, 38494874, 95010552, 70782102, 67084351, 42237907, 30366150, 4978543, 22997281, 6525948, 98130363, 26734892, 67227442, 53393358, 73392814, 89499542, 20486294, 73222868, 22879907, 44844121, 80246713, 64087743, 75135448, 59197747, 88130087, 22108665, 61928316, 62552524, 78561158, 5640302, 68875490, 95395112, 42644903, 65275241, 55850790, 47887470, 17016156, 16097038, 7300047, 53666583, 89804152, 55189057, 82052050, 99729357, 84293052, 26507214, 44847298, 80555751, 62936963, 78909561, 1239555, 83948335, 32590267, 72738685, 6793819, 85116755, 18411915, 51047803, 70372191, 92998591, 66885828, 74724075, 81172706, 70541760, 4099191, 8791066, 22113133, 71316369, 59329511, 16971929, 88047921, 55143724, 72732205, 69355476, 14363867, 50188404, 10264691, 8733068, 56484900, 55247455, 73021291, 61982238, 9603598, 68939068, 83155442, 46540998, 65851721, 88904910, 71083565, 54517921, 24733232, 51588015, 99549373, 35996293, 26139110, 22435353, 45617087, 57241521, 19891772, 81855445, 84684495, 96852321, 25842078, 90013093, 72274002, 43357947, 93359396, 75153252, 11923835, 34698428, 8055981, 66667729, 79922758, 91802888, 3183975, 23569917, 8651647, 1936762, 91831487, 42947632, 55470718, 21919959, 36933038, 1788101, 26397786, 36580610, 13862149, 49236559, 22200378, 64848072, 44915235, 72495719, 34236719, 14723410, 7919588, 70596786, 85695762, 38658347, 21673260, 38009615, 20867149, 70727211, 31733363, 93790285, 76170907, 12571310, 9575954, 69255765, 75407347, 39801351, 37675718, 33553959, 51149804, 85117092, 48260151, 30653863, 65081429, 2331773, 84002370, 48892201, 38365584, 39847321, 92604458, 112651, 43322743, 5073754, 27187213, 44889423, 44245960, 71522968, 7646095, 79136082, 51464002, 25636669, 39373729, 38022834, 29466406, 27041967, 2607799, 14045383, 16054533, 97379790, 82651278, 71920426, 65047700, 86798033, 87720882, 72238278, 72373496, 16567550, 18806856, 12024238, 83083131, 66319530, 99226875, 77072625, 89699445, 31727379, 17037369, 45996863, 77413012, 36396314, 96709982, 76330843, 4064751, 29819940, 79821897, 16380211, 10597197, 97641116, 53888755, 72278539, 62693428, 94911072, 65017137, 9886593, 45049308, 65038678, 61960400, 92530431, 68824981, 10366309, 36139942, 92803766, 59109400, 23432750, 48673079, 66137019, 2891150, 18001617, 8634541, 33431960, 19939935, 3233084, 38510840, 21001913, 16445503, 51715482, 22450468, 84840549, 75128745, 58749, 10961421, 65271999, 62357986, 73235980, 68702632, 53842979, 57248122, 74137926, 26114953, 98943869, 89419466, 75229462, 84406788, 70036438, 4199704, 37957788, 18466635, 32250045, 97011160, 68128438, 15163258, 38008118, 81853704, 53547802, 15902805, 8807940, 12416768, 70004753, 48360198, 67031644, 13348726, 98648327, 45990383, 92071990, 86301513, 63059024, 31161687, 60581278, 77312810, 89217461, 23134715, 94360702, 60102965, 26275734, 17058722, 30543215, 52293995, 76703609, 26063929, 16019925, 80014588, 73617245, 36505482, 30463802, 19101477, 91957544, 49597667, 77620120, 23110625, 16099750, 48658605, 42692881, 6808825, 41245325, 43376279, 31126490, 52060076, 54232247, 18699206, 19272365, 76960303, 16684829, 50668599, 9257405, 39201414, 1431742, 40781854, 67030811, 3294781, 20140249, 56424103, 74452589, 86022504, 87710366, 99333375, 45848907, 12664567, 6986898, 50702367, 17081350, 14349098, 46870723, 21397057, 44060493, 40534591, 40152546, 94076128, 50806615, 66832478, 4985896, 48893685, 9398733, 45306070, 56515456, 95251277, 18833224, 45075471, 82339363, 44481640, 69136837, 40677414, 90158785, 17764950, 20642888, 22405120, 4095116, 83533741, 94516935, 59371804, 93053405, 33797252, 49598724, 94090109, 80251430, 69579137, 58208470, 88698958, 71965942, 33304202, 35092039, 72019362, 40865610, 6221471, 45237957, 30998561, 4515343, 78549759, 30694952, 33628349, 51315460, 85023028, 60106217, 14326617, 61271144, 88444207, 91990218, 63628376, 93515664, 67451935, 17976208, 9860195, 65454636, 34493392, 32159704, 89996536, 64098930, 69848388, 24591705, 61712234, 15015906, 16595436, 6153406, 21070110, 82178706, 40984766, 86460488, 33061250, 3773993, 54263880, 79738755, 37224844, 55753905, 24826575, 65880522, 7423788, 20765474, 62803858, 61728685, 61263068, 37183543, 93933709, 42967683, 43933006, 98653983, 8913721, 13468268, 54427233, 36845587, 73124510, 41380093, 34295794, 75986488, 29188588, 9175338, 71300104, 69786756, 63967300, 47708850, 37620363, 63152504, 33699435, 17146629, 4508700, 89811711, 68644627, 95957797, 77787724, 29834512, 91664334, 85711894, 81774825, 7517032, 30811010, 37659250, 74110882, 33935899, 91938191, 66428795, 63015256, 72777973, 40356877, 95726235, 47298834, 20645197, 77284619, 72357096, 96193415, 34497327, 70420215, 57658654, 77187825, 84187166, 37192445, 45381876, 15536795, 1250437, 5832946, 71657078, 45995325, 37891451, 168541, 30163921, 2717150, 90090964, 44846932, 29065964, 19457589, 36753250, 77300457, 43501211, 32274392, 26664538, 23848565, 38645117, 92787493, 70800879, 91727510, 75820087, 3509435, 69641513, 26863229, 86001008, 83133790, 76315420, 49882705, 68102437, 61623915, 53084256, 3487592, 48395186, 54762643, 44664587, 93270084, 28787861, 33895336, 26776922, 49328608, 82427263, 60430369, 7229550, 14093520, 47792865, 10358899, 9829782, 59405277, 53802686, 14626618, 17857111, 24058273, 53648154, 81898046, 22942635, 62490109, 89046466, 176257, 8729146, 19486173, 44784505, 73168565, 33123618, 29959549, 77301523, 34698463, 4798568, 35192533, 89214616, 96906410, 32426752, 42073124, 70367851, 36808486, 6505939, 23740167, 51426311, 50007421, 56461322, 41442762, 33237508, 18131876, 7182642, 16424199, 4091162, 20122224, 40781449, 4119747, 49271185, 23194618, 29401781, 45919976, 31491938, 1375023, 43152977, 37435892, 64055960, 78300864, 60955663, 14731700, 67474219, 824872, 62496012, 11757872, 55669657, 54606384, 37166608, 11581181, 50567636, 52261574, 48774913, 30771409, 10453030, 94595668, 99971982, 54987042, 44983451, 14220886, 2917920, 57020481, 16387575, 526217, 99917599, 82327024, 9599614, 8115266, 16405341, 9058407, 45667668, 30139692, 92215320, 44348328, 17957593, 8099994, 7104732, 78602717, 9259676, 20568363, 66903004, 59981773, 28734791, 6157724, 92398073, 27325428, 30764367, 59614746, 1197320, 20836893, 20681921, 65338021, 21993752, 30891921, 68000591, 30569392, 10649306, 6111563, 48088883, 66271566, 11543098, 51507425, 72614359, 7955293, 92541302, 15535065, 29635537, 57163802, 54868730, 33249630, 22766820, 71469330, 67281495, 7066775, 86118021, 7011964, 52204879, 92692978, 82532312, 92033260, 4069912, 44479073, 68204242, 98948034, 83269727, 26744917, 56955985, 84127901, 57240218, 17386996, 17385531, 99515901, 22129328, 54058788, 59318837, 34432810, 321665, 62452034, 61141874, 43506672, 74614639, 39553046, 79191827, 44842615, 84349107, 76825057, 72725103, 76540481, 26102057, 85242963, 79942022, 45790169, 29516592, 78218845, 17539625, 90457870, 78785507, 99658235, 58224549, 3088684, 95581843, 54199160, 55602660, 68816413, 51472275, 6497830, 24619760, 44177011, 83789391, 64602895, 52613508, 47213183, 62051033, 92867155, 55615885, 415901, 7685448, 99690194, 59177718, 12303248, 2204165, 73109997, 83747892, 55960386, 5970607, 57359924, 90004325, 99021067, 74441448, 4668450, 59501487, 79322415, 8128637, 26292919, 89078848, 67793644, 92353856, 69697787, 89637706, 5267545, 19376156, 14947650, 75543508, 97281847, 29510992, 21533347, 36468541, 26392416, 37280276, 749283, 51590803, 17365188, 62430984, 65715134, 45794415, 68110330, 87598888, 78589145, 8129978, 75777973, 36685023, 76671482, 42307449, 62740044, 9188443, 18504197, 99297164, 34044787, 78766358, 7687278, 99524975, 24953498, 53632373, 57803235, 80316608, 4434662, 44473167, 88251446, 96735716, 20885148, 15064655, 4204661, 61741594, 36135, 90654836, 79806380, 96318094, 31904591, 32699744, 55770687, 38061439, 58180653, 46851987, 27625735, 38256849, 45407418, 66045587, 40027975, 34667286 +18001617, 92692283, 77620120, 54058788, 82897371, 36580610, 67451935, 40984766, 112651, 43376279, 20645197, 64055960, 9603598, 11581181, 14349098, 23432750, 17957593, 35092039, 93270084, 64848072, 30764367, 24591705, 70596786, 59197747, 92554120, 77312810, 82979980, 40197395, 36845587, 9175338, 98371444, 7685448, 63152504, 92692978, 82532312, 66428795, 55669657, 10453030, 61897582, 72278539, 8696647, 44481640, 38645117, 13470059, 26493119, 92215320, 88698958, 19939935, 83133790, 68102437, 7104732, 68702632, 81805959, 51315460, 75052463, 38494874, 34667286, 22942635, 64602895, 5482538, 45990383, 39801351, 62051033, 874791, 72614359, 72738685, 74724075, 41245325, 96726697, 50668599, 56484900, 6819644, 84187166, 26292919, 77413012, 84127901, 96709982, 48774913, 54987042, 69697787, 88904910, 19457589, 91281584, 74614639, 79942022, 97940276, 45790169, 3509435, 44348328, 99658235, 3487592, 58749, 53842979, 91802888, 78549759, 33628349, 9259676, 14326617, 10358899, 63628376, 47893286, 53802686, 15163258, 89996536, 19898053, 21993752, 89046466, 30090481, 47213183, 69255765, 5640302, 95395112, 42644903, 29932657, 29959549, 23134715, 94360702, 39171895, 76703609, 48260151, 84002370, 73124510, 37560164, 44245960, 47708850, 70367851, 73109997, 47090124, 8791066, 33237508, 89811711, 71316369, 95957797, 61859143, 52060076, 28851716, 91938191, 65074535, 99021067, 29994197, 95726235, 60393039, 24953498, 77072625, 74452589, 87710366, 37166608, 33565483, 57803235, 82779622, 21397057, 46540998, 97057187, 2717150, 11365791, 321665, 94911072, 36812683, 45049308, 71083565, 45075471, 83651211, 35996293, 75543508, 4434662, 33336148, 29510992, 13231279, 75820087, 98462867, 71965942, 21001913, 10961421, 65271999, 6221471, 54762643, 62357986, 508198, 9860968, 42947632, 47792865, 75229462, 83150534, 13862149, 17976208, 6157724, 72495719, 14626618, 6525948, 61712234, 32699744, 65338021, 89499542, 8807940, 3773993, 54263880, 76170907, 12571310, 55753905, 65880522, 68000591, 92867155, 60581278, 569864, 7300047, 26275734, 72793444, 82052050, 66271566, 51149804, 26063929, 16019925, 62740044, 78909561, 7955293, 19101477, 83948335, 91957544, 29188588, 39847321, 57163802, 99690194, 92998591, 22766820, 43322743, 27187213, 36808486, 99297164, 41442762, 39373729, 34044787, 29466406, 52204879, 31126490, 14045383, 49641577, 57359924, 4119747, 54232247, 92033260, 29401781, 9257405, 39201414, 8733068, 94711842, 12024238, 37435892, 83083131, 96193415, 31727379, 37192445, 53632373, 40686254, 30771409, 67793644, 168541, 91255408, 14220886, 9398733, 5111370, 62693428, 62452034, 72358170, 99125126, 43506672, 32161669, 82327024, 23848565, 90310261, 60176618, 72725103, 4806458, 76540481, 85242963, 14947650, 80316608, 45667668, 97561745, 8634541, 3233084, 25842078, 38510840, 27185644, 33304202, 72274002, 16445503, 87160386, 72019362, 26998766, 28550822, 66611759, 88653118, 73235980, 79657802, 96735716, 2208785, 79922758, 23569917, 91831487, 70782102, 67084351, 749283, 27325428, 90061527, 17365188, 62430984, 94539824, 73392814, 21070110, 21673260, 68110330, 33061250, 30787683, 93790285, 40027975, 37224844, 88130087, 13348726, 77377183, 30569392, 13819358, 62803858, 55850790, 1022677, 61263068, 13468268, 46851987, 4798568, 44847298, 30463802, 49597667, 41380093, 6793819, 16099750, 89214616, 59177718, 12303248, 66885828, 12348118, 29029316, 71469330, 6505939, 51464002, 51426311, 18783702, 86118021, 71333116, 16971929, 2607799, 74357852, 37659250, 24314885, 18699206, 33935899, 7687278, 1204161, 65047700, 14363867, 44479073, 85571389, 31491938, 1375023, 43152977, 78300864, 66663942, 70420215, 11757872, 62762857, 79322415, 26744917, 36396314, 82726008, 46870723, 5832946, 40534591, 32151165, 6871053, 37891451, 34432810, 86821229, 4985896, 92353856, 68694897, 89637706, 9886593, 29065964, 57020481, 16387575, 83368048, 82339363, 15148031, 9599614, 76825057, 90158785, 4787945, 51588015, 20642888, 83533741, 66137019, 26139110, 22435353, 8373289, 47361209, 21533347, 69641513, 96852321, 86001008, 17068582, 8099994, 51715482, 75153252, 11923835, 63372756, 45237957, 28796059, 33895336, 30998561, 49328608, 42782652, 82427263, 96420429, 78602717, 26114953, 20568363, 98943869, 85023028, 37280276, 36189527, 44915235, 93515664, 28734791, 15075176, 59405277, 92398073, 15948937, 51590803, 77272628, 97011160, 32159704, 84166196, 64098930, 26734892, 20681921, 44844121, 62490109, 64087743, 30395570, 83789391, 87598888, 31733363, 8729146, 19486173, 48360198, 98648327, 61928316, 44784505, 3235882, 92071990, 86301513, 8129978, 7423788, 43045786, 21289531, 47887470, 77301523, 89217461, 93933709, 98653983, 55189057, 36685023, 52293995, 99729357, 51507425, 79880247, 84293052, 73617245, 84904436, 36505482, 61741594, 415901, 1239555, 92541302, 34295794, 15535065, 75986488, 85116755, 32426752, 71522968, 6808825, 23740167, 7011964, 17146629, 4508700, 58751351, 77787724, 97379790, 32058615, 68316156, 7517032, 30811010, 27625735, 76960303, 16684829, 56153345, 73786944, 16567550, 74441448, 66319530, 67474219, 67030811, 72357096, 99226875, 56424103, 86022504, 36780454, 62428472, 68939068, 67513640, 45848907, 89078848, 6986898, 50702367, 99515901, 52261574, 17727650, 247198, 10597197, 73031054, 44983451, 6521313, 44550764, 65017137, 61141874, 36753250, 18833224, 61960400, 54517921, 79191827, 61859581, 44842615, 36139942, 84349107, 91240048, 92803766, 83210802, 8115266, 41092172, 16405341, 48673079, 4095116, 2891150, 45617087, 90272749, 44473167, 57241521, 33431960, 69579137, 58208470, 57961282, 84684495, 93359396, 88251446, 40865610, 53084256, 9623492, 66250369, 57248122, 30694952, 8651647, 68816413, 60106217, 89419466, 59981773, 21919959, 95290172, 36933038, 54014062, 51472275, 4199704, 37957788, 65454636, 11161731, 18466635, 32250045, 81677380, 22997281, 68128438, 38008118, 98130363, 7919588, 20836893, 15015906, 24058273, 53393358, 35456853, 53648154, 73222868, 12416768, 86460488, 24619760, 61373987, 70004753, 75135448, 9575954, 75407347, 20765474, 65275241, 38341669, 73168565, 86543538, 16097038, 60102965, 48088883, 4204661, 3233569, 53666583, 58180653, 76671482, 85117092, 80555751, 23110625, 29635537, 9188443, 96906410, 54868730, 42073124, 63967300, 2204165, 37620363, 93562257, 79136082, 18504197, 70541760, 7066775, 50007421, 4099191, 68644627, 29834512, 85711894, 36135, 4091162, 40781449, 63015256, 4069912, 50188404, 87720882, 23194618, 51466049, 83302115, 99524975, 60955663, 4668450, 98948034, 3294781, 62496012, 59501487, 34497327, 97783876, 38256849, 41092102, 89699445, 17037369, 8128637, 15536795, 12664567, 17386996, 76330843, 99224392, 4064751, 47738185, 29819940, 7502255, 59318837, 65851721, 67124282, 56531125, 77300457, 39553046, 93566986, 26664538, 69136837, 59109400, 45407418, 22405120, 9058407, 70800879, 94516935, 80251430, 19891772, 17539625, 43357947, 49882705, 8055981, 66667729, 28787861, 3880712, 26776922, 55602660, 62115552, 74137926, 81274566, 95010552, 61271144, 26392416, 42237907, 22200378, 48675329, 70036438, 6497830, 4978543, 20885148, 59614746, 54663246, 17857111, 67227442, 16595436, 53547802, 45794415, 20486294, 62232211, 81898046, 80246713, 38061439, 1569515, 70727211, 76434144, 64157906, 22108665, 31161687, 75777973, 33123618, 37675718, 33553959, 6111563, 89804152, 11543098, 34698463, 63506281, 80014588, 37739481, 35192533, 48892201, 45428665, 5073754, 83747892, 67281495, 27041967, 18131876, 7182642, 55143724, 16054533, 59910624, 41481685, 77898274, 82651278, 20122224, 90654836, 74110882, 19272365, 86798033, 40356877, 10264691, 18806856, 1431742, 30218878, 77187825, 24168411, 99333375, 57240218, 83155442, 22129328, 71657078, 96318094, 45995325, 94595668, 30163921, 53888755, 90090964, 48893685, 2917920, 56515456, 44627776, 526217, 65038678, 43501211, 68824981, 93057697, 19376156, 35512853, 91727510, 68041839, 78218845, 81855445, 26863229, 76315420, 90457870, 84840549, 61623915, 75128745, 91141395, 3088684, 95581843, 66045587, 7229550, 66903004, 34493392, 14723410, 81853704, 65715134, 3633375, 6153406, 78884452, 69605283, 38658347, 176257, 99861373, 79738755, 24826575, 78589145, 52613508, 78561158, 68875490, 10649306, 82886381, 37183543, 42967683, 17058722, 30543215, 8913721, 42307449, 66322959, 55615885, 62936963, 61815223, 32590267, 18411915, 48658605, 51047803, 71300104, 42692881, 44889423, 81172706, 25636669, 38022834, 91664334, 88047921, 81774825, 90004325, 72732205, 27665211, 79806380, 68204242, 47298834, 86242799, 40781854, 73021291, 5822202, 36930650, 54606384, 83269727, 50567636, 39986008, 56955985, 45381876, 17385531, 1250437, 99971982, 66832478, 74743862, 45306070, 95251277, 32274392, 92530431, 5267545, 40677414, 99549373, 17764950, 59371804, 30139692, 29516592, 22450468, 78785507, 48395186, 58224549, 44664587, 60430369, 14093520, 36468541, 88444207, 1788101, 30366150, 9860195, 10309525, 1197320, 55770687, 82178706, 15902805, 44177011, 20867149, 67031644, 62552524, 66116458, 98739783, 17016156, 43933006, 65081429, 2331773, 38365584, 70372191, 69786756, 33249630, 77694700, 18663507, 7646095, 37501808, 99965001, 56461322, 5970607, 24915585, 71920426, 49271185, 72777973, 72373496, 45919976, 55247455, 14731700, 20002147, 77284619, 57658654, 33201905, 45996863, 17081350, 44060493, 97641116, 22721500, 24733232, 10366309, 26102057, 27411561, 33797252, 97281847, 13173644, 17894977, 28928175, 34698428, 55470718, 49236559, 9829782, 69848388, 85695762, 22879907, 30891921, 38009615, 15064655, 42199455, 54427233, 30653863, 26507214, 92604458, 55960386, 22113133, 59329511, 78766358, 16424199, 69355476, 824872, 40152546, 94076128, 16380211, 50806615, 92787493, 93053405, 49598724, 94090109, 36312813, 6038457, 3183975, 91990218, 34236719, 61728685, 56473732, 33699435, 20140249, 61982238, 79821897, 44846932, 31904591, 84406788, 72238278, 34946859, 669105, 90013093, 54199160, 4515343, 1936762, 57393458, 99604946, 63059024, 99917599, 26397786 +15064655, 73124510, 95726235, 45407418, 89078848, 44842615, 1936762, 21993752, 52613508, 78909561, 97379790, 90004325, 79806380, 92033260, 17764950, 76540481, 19939935, 83133790, 78785507, 47893286, 36505482, 42073124, 36808486, 56461322, 82532312, 24314885, 55247455, 45381876, 53632373, 14349098, 40152546, 54517921, 44481640, 2891150, 13173644, 90013093, 35092039, 49328608, 17976208, 8729146, 64602895, 10649306, 95395112, 89217461, 3233569, 72793444, 51149804, 9175338, 89214616, 59177718, 42692881, 40781449, 76960303, 56153345, 12024238, 43152977, 98948034, 72357096, 99226875, 84187166, 99971982, 18833224, 31904591, 93566986, 61859581, 33431960, 81855445, 86001008, 17894977, 40865610, 73235980, 30998561, 7229550, 85023028, 70036438, 27325428, 14626618, 32159704, 54663246, 1197320, 17857111, 32699744, 21070110, 31733363, 12571310, 59197747, 62552524, 30569392, 63059024, 5640302, 42644903, 61728685, 29959549, 77301523, 82052050, 54427233, 2331773, 46851987, 37739481, 415901, 32426752, 18504197, 7011964, 29466406, 16971929, 2607799, 31126490, 32058615, 54232247, 66428795, 4069912, 18806856, 56484900, 59501487, 96193415, 26292919, 6986898, 17386996, 99515901, 96318094, 45995325, 50806615, 44983451, 321665, 9886593, 61960400, 45075471, 92530431, 9599614, 69136837, 40677414, 16405341, 66137019, 26139110, 75543508, 45790169, 45667668, 97281847, 57241521, 29510992, 57961282, 69641513, 87160386, 88653118, 7104732, 9623492, 74137926, 26114953, 9259676, 14093520, 26397786, 36580610, 91990218, 18466635, 34236719, 30891921, 8807940, 38061439, 87598888, 79738755, 40027975, 55753905, 5482538, 64157906, 78561158, 86301513, 31161687, 77312810, 569864, 93933709, 94360702, 66271566, 85117092, 84002370, 44847298, 61815223, 41380093, 39847321, 7685448, 96906410, 99690194, 47708850, 71522968, 18663507, 37620363, 51426311, 99297164, 34044787, 71316369, 29834512, 91664334, 20122224, 90654836, 92692978, 86798033, 65074535, 85571389, 73786944, 45919976, 8733068, 40781854, 14731700, 77284619, 34497327, 74452589, 54606384, 11581181, 68939068, 52261574, 54058788, 59318837, 97057187, 65851721, 67793644, 54987042, 11365791, 82897371, 8696647, 62452034, 29065964, 95251277, 43501211, 24733232, 82327024, 5267545, 76825057, 8115266, 90158785, 4787945, 4806458, 22405120, 4095116, 26102057, 85242963, 97940276, 33797252, 49598724, 33336148, 30139692, 44473167, 72274002, 93359396, 72019362, 88251446, 75153252, 10961421, 63372756, 58224549, 44664587, 28787861, 33895336, 66045587, 508198, 62115552, 55470718, 26392416, 9829782, 51472275, 4199704, 36189527, 22997281, 94539824, 98130363, 24591705, 61712234, 53547802, 6153406, 45794415, 38658347, 53648154, 62490109, 38009615, 99604946, 24619760, 83789391, 70727211, 65880522, 88130087, 68000591, 61928316, 45990383, 44784505, 92071990, 7423788, 68875490, 13819358, 65275241, 92867155, 33123618, 61263068, 37675718, 82979980, 37183543, 7300047, 48088883, 26275734, 58180653, 26063929, 16019925, 13468268, 51507425, 80014588, 83948335, 77620120, 112651, 71469330, 83747892, 7066775, 55960386, 33699435, 78766358, 49641577, 36135, 37659250, 71920426, 27665211, 44479073, 94711842, 60393039, 47298834, 60955663, 824872, 5822202, 57658654, 11757872, 36780454, 83269727, 67513640, 17385531, 5832946, 40534591, 32151165, 73031054, 92353856, 2917920, 67124282, 56531125, 44846932, 88904910, 19457589, 83368048, 99917599, 32161669, 83533741, 14947650, 80316608, 29516592, 8373289, 94090109, 25842078, 17957593, 21001913, 8099994, 51715482, 22450468, 28550822, 53084256, 75128745, 48395186, 45237957, 68702632, 95581843, 96735716, 6038457, 8651647, 9860968, 81274566, 68816413, 14326617, 83150534, 95290172, 70782102, 36933038, 88444207, 54014062, 51590803, 17365188, 62430984, 15015906, 3633375, 65338021, 55770687, 35456853, 44844121, 15902805, 86460488, 54263880, 44177011, 48360198, 78589145, 76434144, 67031644, 30090481, 22108665, 98739783, 20765474, 55850790, 47887470, 23134715, 86543538, 16097038, 30543215, 99729357, 84293052, 73617245, 4798568, 72614359, 80555751, 7955293, 48892201, 92692283, 15535065, 72738685, 98371444, 70372191, 92998591, 12303248, 12348118, 37501808, 51464002, 6808825, 41245325, 67281495, 23740167, 47090124, 41442762, 95957797, 43376279, 81774825, 28851716, 27625735, 74110882, 7687278, 63015256, 16684829, 72238278, 83302115, 20645197, 1431742, 36930650, 37166608, 62428472, 39986008, 50702367, 1250437, 44060493, 6871053, 94076128, 94595668, 61897582, 4985896, 90090964, 48893685, 68694897, 56515456, 5111370, 99125126, 61141874, 36753250, 16387575, 74614639, 82339363, 15148031, 10366309, 23848565, 92803766, 35996293, 91727510, 45617087, 90272749, 47361209, 17539625, 21533347, 26863229, 71965942, 16445503, 49882705, 28928175, 3487592, 91141395, 54762643, 3088684, 42782652, 2208785, 60430369, 91802888, 3183975, 81805959, 78549759, 51315460, 60106217, 21919959, 61271144, 42237907, 57393458, 10358899, 63628376, 49236559, 37280276, 48675329, 28734791, 9860195, 59405277, 65454636, 11161731, 53802686, 32250045, 72495719, 15163258, 89996536, 30764367, 64098930, 6525948, 26734892, 20836893, 81853704, 73222868, 22942635, 3773993, 30787683, 1569515, 99861373, 75135448, 19486173, 13348726, 69255765, 66116458, 8129978, 29932657, 1022677, 33553959, 42967683, 39171895, 60102965, 53666583, 36685023, 76671482, 11543098, 48260151, 63506281, 66322959, 36845587, 62936963, 35192533, 30463802, 38365584, 23110625, 45428665, 29635537, 57163802, 18411915, 51047803, 71300104, 92604458, 63967300, 22766820, 77694700, 27187213, 44889423, 2204165, 6505939, 86118021, 50007421, 58751351, 89811711, 59329511, 96726697, 14045383, 16424199, 68316156, 7517032, 82651278, 57359924, 24915585, 72732205, 4119747, 49271185, 68204242, 29994197, 40356877, 16567550, 1375023, 99524975, 24953498, 4668450, 6819644, 30218878, 3294781, 20140249, 62496012, 66663942, 61982238, 70420215, 77072625, 97783876, 38256849, 33201905, 99333375, 12664567, 36396314, 57803235, 83155442, 22129328, 99224392, 30771409, 71657078, 10453030, 37891451, 10597197, 34432810, 66832478, 14220886, 74743862, 69697787, 45306070, 94911072, 36812683, 57020481, 32274392, 39553046, 90310261, 72725103, 99549373, 92787493, 9058407, 48673079, 79942022, 59371804, 8634541, 78218845, 69579137, 58208470, 92215320, 96852321, 27185644, 90457870, 68102437, 99658235, 61623915, 58749, 34698428, 6221471, 66611759, 53842979, 26776922, 55602660, 78602717, 75052463, 66903004, 91831487, 42947632, 36468541, 1788101, 64848072, 34667286, 97011160, 34493392, 7919588, 16595436, 24058273, 53393358, 89499542, 69605283, 85695762, 22879907, 62232211, 81898046, 21673260, 12416768, 24826575, 98648327, 77377183, 9575954, 3235882, 75407347, 38341669, 62803858, 82886381, 43933006, 4204661, 8913721, 42307449, 76703609, 65081429, 26507214, 62740044, 91957544, 34295794, 6793819, 9188443, 16099750, 54868730, 69786756, 74724075, 81172706, 99965001, 18783702, 4099191, 25636669, 4508700, 33237508, 22113133, 27041967, 18131876, 7182642, 16054533, 52060076, 69355476, 91938191, 19272365, 29401781, 9257405, 72373496, 39201414, 51466049, 31491938, 64055960, 67474219, 73021291, 55669657, 41092102, 86022504, 87710366, 17037369, 56955985, 37192445, 15536795, 96709982, 76330843, 46870723, 21397057, 53888755, 669105, 72278539, 91255408, 6521313, 9398733, 89637706, 72358170, 44627776, 91281584, 77300457, 71083565, 79191827, 68824981, 19376156, 35512853, 20642888, 70800879, 93053405, 44348328, 84684495, 17068582, 76315420, 26998766, 65271999, 62357986, 8055981, 93270084, 79657802, 82427263, 79922758, 67451935, 37957788, 92398073, 15948937, 77272628, 81677380, 68128438, 67227442, 78884452, 20486294, 80246713, 40984766, 33061250, 89046466, 61373987, 70004753, 20867149, 93790285, 43045786, 62051033, 75777973, 17058722, 61741594, 1239555, 37560164, 92541302, 75986488, 85116755, 5073754, 73109997, 56473732, 38022834, 77787724, 71333116, 52204879, 88047921, 55143724, 77898274, 4091162, 33935899, 65047700, 50188404, 99021067, 87720882, 72777973, 10264691, 37435892, 86242799, 67030811, 56424103, 24168411, 31727379, 26744917, 50567636, 77413012, 57240218, 17081350, 82779622, 7502255, 34946859, 16380211, 2717150, 44550764, 65017137, 526217, 65038678, 26664538, 84349107, 91240048, 83210802, 38645117, 60176618, 83651211, 4434662, 3509435, 98462867, 33304202, 66250369, 28796059, 54199160, 57248122, 30694952, 33628349, 38494874, 47792865, 75229462, 22200378, 93515664, 6157724, 90061527, 59614746, 84166196, 38008118, 69848388, 73392814, 82178706, 68110330, 30395570, 176257, 47213183, 73168565, 21289531, 17016156, 6111563, 42199455, 52293995, 34698463, 874791, 84904436, 55615885, 66885828, 79136082, 39373729, 5970607, 85711894, 41481685, 30811010, 18699206, 1204161, 14363867, 50668599, 23194618, 89699445, 40686254, 82726008, 4064751, 48774913, 79821897, 247198, 168541, 22721500, 62693428, 45049308, 13470059, 23432750, 51588015, 41092172, 22435353, 26493119, 19891772, 88698958, 3233084, 36312813, 4515343, 96420429, 59981773, 84406788, 6497830, 44915235, 4978543, 15075176, 10309525, 14723410, 65715134, 19898053, 39801351, 60581278, 89804152, 98653983, 55189057, 19101477, 29188588, 48658605, 33249630, 43322743, 7646095, 93562257, 17146629, 68644627, 61859143, 83083131, 66319530, 9603598, 79322415, 77187825, 33565483, 45848907, 47738185, 29819940, 46540998, 86821229, 43506672, 36139942, 27411561, 68041839, 18001617, 97561745, 75820087, 11923835, 66667729, 3880712, 23569917, 20568363, 95010552, 13862149, 20681921, 70596786, 64087743, 37224844, 76170907, 92554120, 30653863, 49597667, 29029316, 74357852, 59910624, 20002147, 62762857, 45996863, 84127901, 59109400, 94516935, 80251430, 13231279, 38510840, 84840549, 67084351, 30366150, 749283, 20885148, 44245960, 70367851, 63152504, 70541760, 8791066, 74441448, 8128637, 97641116, 43357947, 98943869, 89419466, 30163921, 40197395, 79880247, 32590267, 78300864, 17727650, 93057697 +79880247, 26392416, 66116458, 61728685, 22766820, 79136082, 91664334, 4434662, 59981773, 70727211, 36845587, 49597667, 18411915, 42692881, 10264691, 78300864, 61982238, 4064751, 97057187, 67793644, 45306070, 69579137, 47361209, 63628376, 36189527, 27325428, 81677380, 38341669, 53666583, 2331773, 7066775, 71333116, 27041967, 43376279, 55143724, 92692978, 23194618, 72238278, 39201414, 55669657, 99333375, 67513640, 12664567, 54058788, 2717150, 67124282, 99125126, 20642888, 22435353, 93053405, 68041839, 26493119, 13231279, 33304202, 99658235, 6038457, 98943869, 47792865, 83150534, 84406788, 22200378, 9829782, 65454636, 59614746, 6525948, 67227442, 81898046, 68110330, 61373987, 31733363, 8129978, 13819358, 31161687, 62051033, 6111563, 7300047, 4204661, 58180653, 30463802, 61741594, 37560164, 34295794, 54868730, 43322743, 12348118, 44245960, 51464002, 22113133, 38022834, 91938191, 65047700, 50188404, 50668599, 72373496, 40356877, 12024238, 31491938, 17727650, 68694897, 44627776, 39553046, 70800879, 97561745, 29516592, 13173644, 57961282, 88698958, 3233084, 25842078, 90013093, 76315420, 66611759, 20568363, 91831487, 55470718, 95290172, 57393458, 10358899, 64848072, 17976208, 9860195, 84166196, 20681921, 3633375, 70596786, 22879907, 15902805, 64087743, 76170907, 5482538, 52613508, 47213183, 86301513, 77301523, 65081429, 84002370, 36505482, 48892201, 77620120, 66885828, 37501808, 71316369, 29466406, 29834512, 18131876, 31126490, 33935899, 83083131, 30218878, 73021291, 20140249, 70420215, 77187825, 41092102, 84187166, 15536795, 21397057, 40152546, 247198, 73031054, 6521313, 72358170, 36812683, 57020481, 61859581, 44842615, 93057697, 36139942, 38645117, 2891150, 59371804, 49598724, 57241521, 3509435, 98462867, 96852321, 26863229, 71965942, 21001913, 43357947, 3487592, 91141395, 8055981, 73235980, 49328608, 79922758, 7229550, 30694952, 9259676, 95010552, 88444207, 34667286, 51590803, 72495719, 22997281, 68128438, 89996536, 98130363, 16595436, 32699744, 45794415, 62232211, 40984766, 99604946, 30395570, 89046466, 30787683, 59197747, 24826575, 62552524, 92071990, 92554120, 62803858, 17016156, 23134715, 86543538, 89804152, 98653983, 42199455, 40197395, 11543098, 85117092, 99729357, 62936963, 78909561, 37739481, 415901, 98371444, 92604458, 42073124, 47708850, 7646095, 18504197, 47090124, 7011964, 41442762, 58751351, 77787724, 74357852, 54232247, 18699206, 49271185, 4069912, 16684829, 85571389, 16567550, 60393039, 4668450, 20002147, 67474219, 72357096, 11757872, 36780454, 83269727, 62428472, 26744917, 39986008, 57240218, 17386996, 96709982, 99515901, 48774913, 82779622, 5832946, 30163921, 66832478, 22721500, 45049308, 526217, 43501211, 74614639, 61960400, 54517921, 82339363, 15148031, 24733232, 26664538, 69136837, 76825057, 13470059, 72725103, 99549373, 4806458, 17764950, 41092172, 4095116, 76540481, 35996293, 66137019, 18001617, 29510992, 92215320, 75820087, 72274002, 8099994, 90457870, 93359396, 68102437, 28550822, 75153252, 58749, 34698428, 66667729, 36312813, 30998561, 508198, 78549759, 33628349, 68816413, 75229462, 14326617, 61271144, 36933038, 67084351, 13862149, 47893286, 4978543, 90061527, 14626618, 30764367, 38008118, 64098930, 20836893, 61712234, 53393358, 30891921, 44844121, 33061250, 24619760, 79738755, 93790285, 37224844, 55753905, 19486173, 48360198, 64602895, 78589145, 67031644, 3235882, 20765474, 92867155, 55850790, 60581278, 33123618, 1022677, 89217461, 37183543, 42967683, 43933006, 72793444, 55189057, 82052050, 52293995, 8913721, 874791, 46851987, 72614359, 7955293, 1239555, 38365584, 92541302, 75986488, 39847321, 7685448, 70372191, 59177718, 63967300, 33249630, 81172706, 70367851, 93562257, 73109997, 83747892, 23740167, 17146629, 4508700, 59329511, 95957797, 52204879, 88047921, 49641577, 77898274, 24915585, 90654836, 1204161, 19272365, 68204242, 73786944, 55247455, 96193415, 77072625, 57658654, 86022504, 89699445, 33565483, 17037369, 68939068, 45381876, 45996863, 84127901, 76330843, 99224392, 47738185, 10453030, 37891451, 44983451, 92353856, 69697787, 2917920, 56515456, 62693428, 9886593, 29065964, 95251277, 18833224, 32274392, 10366309, 92803766, 4787945, 35512853, 51588015, 22405120, 26102057, 97940276, 90272749, 8634541, 33431960, 19891772, 17539625, 17894977, 35092039, 16445503, 49882705, 88251446, 61623915, 48395186, 88653118, 62357986, 66250369, 54199160, 3880712, 42782652, 96420429, 60430369, 74137926, 81805959, 51315460, 75052463, 8651647, 66903004, 9860968, 70782102, 36580610, 749283, 48675329, 70036438, 6497830, 44915235, 67451935, 15075176, 10309525, 53802686, 54663246, 14723410, 1197320, 17857111, 7919588, 81853704, 19898053, 78884452, 73222868, 86460488, 54263880, 44177011, 20867149, 13348726, 10649306, 39801351, 95395112, 73168565, 47887470, 16097038, 48088883, 3233569, 36685023, 51149804, 51507425, 55615885, 30653863, 26507214, 83948335, 32590267, 15535065, 45428665, 29188588, 6793819, 29635537, 57163802, 16099750, 112651, 92998591, 12303248, 29029316, 70541760, 25636669, 2607799, 7182642, 85711894, 16424199, 36135, 81774825, 90004325, 4091162, 40781449, 72732205, 69355476, 82532312, 86798033, 65074535, 14363867, 29401781, 45919976, 18806856, 83302115, 1431742, 86242799, 60955663, 77284619, 62496012, 99226875, 36930650, 97783876, 38256849, 24168411, 33201905, 50567636, 26292919, 77413012, 36396314, 40686254, 17081350, 44060493, 7502255, 46540998, 59318837, 94076128, 16380211, 65851721, 669105, 11365791, 82897371, 56531125, 77300457, 71083565, 68824981, 32161669, 5267545, 8115266, 23432750, 45407418, 27411561, 91727510, 75543508, 33797252, 30139692, 44473167, 44348328, 69641513, 19939935, 38510840, 51715482, 72019362, 84840549, 28928175, 75128745, 10961421, 6221471, 9623492, 68702632, 82427263, 4515343, 62115552, 42947632, 42237907, 92398073, 77272628, 97011160, 62430984, 15163258, 32159704, 6153406, 20486294, 82178706, 22942635, 21673260, 62490109, 80246713, 3773993, 87598888, 40027975, 8729146, 22108665, 61928316, 69255765, 7423788, 5640302, 29932657, 82886381, 61263068, 77312810, 569864, 26275734, 66271566, 76703609, 48260151, 16019925, 80014588, 84904436, 44847298, 19101477, 91957544, 9175338, 89214616, 69786756, 5073754, 37620363, 6505939, 41245325, 51426311, 33699435, 16054533, 82651278, 61859143, 57359924, 20122224, 30811010, 27665211, 56153345, 99021067, 51466049, 8733068, 1375023, 43152977, 56484900, 37435892, 20645197, 64055960, 40781854, 98948034, 3294781, 824872, 59501487, 5822202, 34497327, 9603598, 54606384, 37166608, 31727379, 53632373, 17385531, 1250437, 30771409, 6871053, 94595668, 97641116, 61897582, 86821229, 72278539, 4985896, 14220886, 5111370, 91281584, 36753250, 65038678, 99917599, 93566986, 59109400, 90158785, 83651211, 48673079, 85242963, 83533741, 14947650, 26139110, 45617087, 78218845, 84684495, 86001008, 17957593, 27185644, 26998766, 11923835, 65271999, 58224549, 3088684, 28796059, 79657802, 33895336, 66045587, 91802888, 26114953, 1936762, 81274566, 26397786, 91990218, 6157724, 15948937, 18466635, 17365188, 94539824, 34236719, 65715134, 24058273, 89499542, 85695762, 53648154, 38009615, 38061439, 12571310, 75135448, 65880522, 64157906, 30090481, 98648327, 77377183, 45990383, 44784505, 30569392, 43045786, 33553959, 84293052, 62740044, 4798568, 80555751, 85116755, 9188443, 48658605, 51047803, 99690194, 77694700, 71522968, 18663507, 36808486, 55960386, 86118021, 4099191, 56473732, 8791066, 99297164, 34044787, 33237508, 89811711, 68644627, 5970607, 59910624, 97379790, 41481685, 32058615, 27625735, 71920426, 79806380, 7687278, 76960303, 44479073, 9257405, 95726235, 47298834, 24953498, 14731700, 6819644, 66663942, 79322415, 87710366, 37192445, 6986898, 50702367, 83155442, 52261574, 82726008, 14349098, 46870723, 40534591, 32151165, 79821897, 45995325, 99971982, 168541, 53888755, 54987042, 91255408, 90090964, 44550764, 74743862, 8696647, 9398733, 89637706, 62452034, 44846932, 88904910, 61141874, 45075471, 79191827, 44481640, 84349107, 40677414, 60176618, 9058407, 94516935, 80316608, 80251430, 58208470, 83133790, 17068582, 78785507, 53084256, 45237957, 26776922, 2208785, 57248122, 3183975, 23569917, 4199704, 28734791, 20885148, 69848388, 24591705, 65338021, 73392814, 38658347, 1569515, 70004753, 15064655, 9575954, 78561158, 75407347, 42644903, 82979980, 93933709, 94360702, 60102965, 17058722, 30543215, 76671482, 63506281, 13468268, 66322959, 73617245, 72738685, 74724075, 2204165, 71469330, 63152504, 6808825, 67281495, 16971929, 78766358, 92033260, 63015256, 87720882, 29994197, 99524975, 74441448, 66319530, 67030811, 8128637, 89078848, 22129328, 29819940, 71657078, 10597197, 50806615, 94911072, 65017137, 19457589, 92530431, 31904591, 82327024, 83210802, 90310261, 16405341, 33336148, 45667668, 8373289, 94090109, 81855445, 21533347, 22450468, 63372756, 93270084, 28787861, 53842979, 55602660, 78602717, 38494874, 60106217, 89419466, 14093520, 1788101, 30366150, 54014062, 37280276, 26734892, 55770687, 21070110, 176257, 99861373, 76434144, 63059024, 68875490, 98739783, 65275241, 37675718, 42307449, 26063929, 35192533, 61815223, 73124510, 92692283, 96906410, 44889423, 99965001, 50007421, 14045383, 68316156, 28851716, 37659250, 4119747, 74110882, 72777973, 94711842, 62762857, 11581181, 56955985, 34946859, 96318094, 48893685, 321665, 43506672, 83368048, 91240048, 79942022, 97281847, 87160386, 40865610, 44664587, 95581843, 96735716, 21919959, 49236559, 59405277, 32250045, 34493392, 15015906, 53547802, 69605283, 21993752, 35456853, 8807940, 83789391, 88130087, 68000591, 75777973, 21289531, 29959549, 39171895, 34698463, 23110625, 32426752, 27187213, 39373729, 7517032, 24314885, 56424103, 74452589, 16387575, 23848565, 92787493, 54762643, 7104732, 85023028, 36468541, 51472275, 11161731, 12416768, 71300104, 18783702, 96726697, 52060076, 66428795, 45848907, 57803235, 34432810, 9599614, 45790169, 37957788, 54427233, 41380093, 56461322, 19376156, 93515664 +22879907, 39801351, 11365791, 35996293, 21673260, 33553959, 65081429, 56473732, 77898274, 56424103, 36396314, 97641116, 83368048, 15148031, 87160386, 72495719, 63506281, 13468268, 55143724, 86242799, 83155442, 7502255, 168541, 86821229, 92803766, 40677414, 18001617, 19891772, 58224549, 3880712, 3633375, 38658347, 81898046, 94360702, 7300047, 42199455, 52293995, 80555751, 9175338, 7646095, 79136082, 16054533, 59910624, 68204242, 64055960, 66663942, 11757872, 97783876, 61960400, 24733232, 23848565, 36139942, 90158785, 48673079, 97561745, 21001913, 95581843, 53842979, 75052463, 10358899, 61712234, 40984766, 64087743, 176257, 45990383, 3235882, 30569392, 7423788, 31161687, 55850790, 60581278, 47887470, 6111563, 84002370, 46851987, 48658605, 70372191, 63967300, 25636669, 71316369, 52060076, 37659250, 69355476, 31491938, 30218878, 72357096, 37192445, 57803235, 4064751, 44060493, 71657078, 46540998, 40534591, 50806615, 61897582, 44550764, 5111370, 74614639, 82339363, 16405341, 27411561, 94516935, 14947650, 91727510, 96852321, 8099994, 28796059, 47792865, 1788101, 92398073, 10309525, 73392814, 62232211, 30395570, 20867149, 55753905, 19486173, 76434144, 75407347, 82886381, 82979980, 43933006, 3233569, 89804152, 99729357, 84293052, 84904436, 26507214, 36845587, 62936963, 19101477, 23110625, 85116755, 51047803, 77694700, 27187213, 81172706, 18663507, 93562257, 63152504, 2607799, 96726697, 88047921, 97379790, 90004325, 33935899, 86798033, 66428795, 87720882, 10264691, 1375023, 20002147, 5822202, 62428472, 77413012, 50702367, 14349098, 82779622, 6871053, 94595668, 30163921, 2717150, 72358170, 36812683, 526217, 43501211, 32274392, 79191827, 91240048, 26102057, 83533741, 79942022, 97940276, 80316608, 68041839, 90272749, 94090109, 47361209, 17539625, 17957593, 76315420, 28928175, 75153252, 3487592, 45237957, 49328608, 60430369, 79922758, 74137926, 9860968, 14326617, 57393458, 22200378, 36189527, 37957788, 9860195, 6157724, 27325428, 15163258, 59614746, 24058273, 53648154, 89046466, 99861373, 5482538, 13348726, 22108665, 78561158, 98739783, 92554120, 20765474, 38341669, 21289531, 16097038, 53666583, 55189057, 11543098, 48260151, 66322959, 874791, 2331773, 61741594, 6793819, 98371444, 66885828, 5073754, 44245960, 2204165, 99965001, 50007421, 8791066, 22113133, 77787724, 74357852, 41481685, 61859143, 57359924, 24915585, 72732205, 71920426, 49271185, 63015256, 56153345, 23194618, 18806856, 47298834, 67030811, 73021291, 24168411, 36780454, 89699445, 33565483, 17037369, 67513640, 46870723, 99224392, 17727650, 32151165, 97057187, 73031054, 54987042, 22721500, 6521313, 45306070, 321665, 65017137, 44627776, 44481640, 26664538, 32161669, 83210802, 76825057, 35512853, 92787493, 20642888, 9058407, 59371804, 4434662, 45617087, 80251430, 13173644, 21533347, 98462867, 88698958, 3233084, 38510840, 17894977, 72274002, 90457870, 51715482, 72019362, 61623915, 53084256, 91141395, 73235980, 79657802, 26776922, 30998561, 91802888, 62115552, 23569917, 38494874, 8651647, 91831487, 89419466, 26397786, 67084351, 13862149, 67451935, 18466635, 51590803, 77272628, 68128438, 94539824, 89996536, 34236719, 20836893, 69605283, 21070110, 35456853, 22942635, 62490109, 86460488, 30787683, 61373987, 70004753, 93790285, 37224844, 15064655, 78589145, 77377183, 75777973, 73168565, 17016156, 60102965, 4204661, 82052050, 76671482, 42307449, 26063929, 55615885, 62740044, 36505482, 61815223, 1239555, 37560164, 92541302, 34295794, 9188443, 18411915, 89214616, 42073124, 22766820, 12348118, 74724075, 71469330, 47090124, 33237508, 16971929, 18131876, 43376279, 91664334, 7182642, 36135, 68316156, 7517032, 30811010, 74110882, 82532312, 24314885, 65074535, 29994197, 73786944, 12024238, 55247455, 6819644, 67474219, 70420215, 62762857, 79322415, 37166608, 11581181, 56955985, 45996863, 57240218, 17081350, 96709982, 82726008, 29819940, 10453030, 40152546, 247198, 94076128, 669105, 91255408, 82897371, 48893685, 68694897, 62693428, 89637706, 62452034, 91281584, 45049308, 71083565, 92530431, 82327024, 10366309, 5267545, 19376156, 69136837, 90310261, 23432750, 72725103, 51588015, 4095116, 76540481, 75543508, 22435353, 26493119, 57241521, 78218845, 57961282, 75820087, 69641513, 84684495, 27185644, 90013093, 22450468, 84840549, 26998766, 78785507, 68102437, 99658235, 28550822, 58749, 34698428, 48395186, 62357986, 42782652, 508198, 4515343, 26114953, 30694952, 1936762, 81274566, 60106217, 83150534, 21919959, 84406788, 37280276, 6497830, 28734791, 59405277, 65454636, 11161731, 15948937, 97011160, 81677380, 34493392, 54663246, 98130363, 26734892, 65338021, 6153406, 45794415, 53393358, 70596786, 20486294, 30891921, 44844121, 80246713, 99604946, 3773993, 1569515, 70727211, 76170907, 12571310, 98648327, 62552524, 86301513, 68875490, 13819358, 42644903, 65275241, 62051033, 92867155, 1022677, 37675718, 26275734, 98653983, 76703609, 80014588, 79880247, 37739481, 30463802, 48892201, 91957544, 32590267, 77620120, 39847321, 57163802, 92604458, 33249630, 43322743, 42692881, 29029316, 71522968, 73109997, 55960386, 18783702, 99297164, 41442762, 58751351, 39373729, 68644627, 95957797, 29834512, 31126490, 85711894, 32058615, 82651278, 4091162, 27625735, 4119747, 54232247, 27665211, 65047700, 4069912, 44479073, 50188404, 40356877, 83302115, 8733068, 94711842, 60393039, 99524975, 24953498, 40781854, 98948034, 59501487, 9603598, 55669657, 86022504, 87710366, 31727379, 8128637, 6986898, 99515901, 47738185, 54058788, 16380211, 65851721, 53888755, 44983451, 14220886, 2917920, 56515456, 99125126, 9886593, 44846932, 43506672, 93566986, 38645117, 8115266, 13470059, 4787945, 99549373, 41092172, 85242963, 66137019, 49598724, 33336148, 30139692, 44473167, 8373289, 33431960, 69579137, 13231279, 83133790, 11923835, 44664587, 3088684, 66667729, 36312813, 28787861, 66045587, 82427263, 2208785, 6038457, 78602717, 81805959, 33628349, 98943869, 68816413, 14093520, 75229462, 36933038, 42237907, 36580610, 49236559, 64848072, 47893286, 48675329, 70036438, 93515664, 4978543, 15075176, 32250045, 22997281, 32159704, 14723410, 17857111, 16595436, 65715134, 44177011, 83789391, 64602895, 88130087, 9575954, 44784505, 52613508, 92071990, 8129978, 5640302, 93933709, 42967683, 86543538, 39171895, 72793444, 16019925, 30653863, 44847298, 35192533, 41380093, 15535065, 45428665, 29188588, 16099750, 71300104, 99690194, 112651, 92998591, 6808825, 70541760, 7066775, 33699435, 17146629, 38022834, 71333116, 29466406, 27041967, 52204879, 49641577, 81774825, 20122224, 19272365, 14363867, 50668599, 29401781, 72777973, 85571389, 39201414, 45919976, 16567550, 51466049, 43152977, 56484900, 78300864, 14731700, 66319530, 77284619, 96193415, 34497327, 77072625, 26292919, 89078848, 53632373, 76330843, 22129328, 48774913, 10597197, 74743862, 69697787, 9398733, 67124282, 56531125, 36753250, 16387575, 95251277, 65038678, 18833224, 61859581, 44842615, 9599614, 93057697, 59109400, 60176618, 4806458, 2891150, 93053405, 81855445, 26863229, 86001008, 35092039, 17068582, 93359396, 75128745, 10961421, 6221471, 54762643, 7104732, 9623492, 93270084, 96735716, 57248122, 78549759, 51315460, 20568363, 59981773, 42947632, 55470718, 70782102, 88444207, 30366150, 63628376, 9829782, 749283, 44915235, 90061527, 14626618, 6525948, 7919588, 24591705, 81853704, 67227442, 20681921, 32699744, 78884452, 85695762, 12416768, 24619760, 8729146, 75135448, 64157906, 30090481, 63059024, 10649306, 62803858, 29959549, 569864, 23134715, 17058722, 40197395, 30543215, 66271566, 51149804, 8913721, 51507425, 78909561, 83948335, 49597667, 73124510, 72738685, 75986488, 54868730, 69786756, 59177718, 12303248, 47708850, 36808486, 6505939, 41245325, 23740167, 51426311, 4099191, 4508700, 40781449, 90654836, 92692978, 18699206, 91938191, 1204161, 76960303, 72238278, 37435892, 1431742, 83083131, 4668450, 62496012, 61982238, 36930650, 41092102, 54606384, 33201905, 99333375, 45848907, 15536795, 52261574, 45995325, 4985896, 90090964, 8696647, 88904910, 84349107, 22405120, 45667668, 92215320, 25842078, 71965942, 16445503, 40865610, 63372756, 68702632, 95290172, 95010552, 36468541, 4199704, 20885148, 53802686, 38008118, 69848388, 55770687, 8807940, 24826575, 67031644, 61928316, 69255765, 66116458, 95395112, 33123618, 37183543, 58180653, 36685023, 85117092, 72614359, 7955293, 38365584, 37620363, 37501808, 51464002, 67281495, 56461322, 5970607, 16424199, 7687278, 16684829, 9257405, 74441448, 60955663, 20140249, 77187825, 68939068, 50567636, 39986008, 45381876, 40686254, 30771409, 79821897, 59318837, 67793644, 66832478, 72278539, 29065964, 19457589, 57020481, 39553046, 68824981, 45407418, 17764950, 33797252, 8634541, 29510992, 3509435, 44348328, 19939935, 33304202, 43357947, 49882705, 65271999, 88653118, 66250369, 96420429, 7229550, 9259676, 66903004, 85023028, 61271144, 26392416, 51472275, 17976208, 30764367, 84166196, 89499542, 82178706, 38009615, 38061439, 33061250, 54263880, 31733363, 79738755, 68000591, 43045786, 29932657, 77301523, 415901, 29635537, 7685448, 44889423, 83747892, 18504197, 86118021, 7011964, 34044787, 89811711, 14045383, 28851716, 99021067, 95726235, 74452589, 38256849, 83269727, 84187166, 17386996, 21397057, 37891451, 34432810, 99971982, 92353856, 54517921, 45075471, 83651211, 70800879, 26139110, 97281847, 29516592, 88251446, 66611759, 8055981, 91990218, 54014062, 17365188, 62430984, 64098930, 1197320, 15015906, 19898053, 68110330, 40027975, 59197747, 65880522, 47213183, 61728685, 89217461, 34698463, 73617245, 92692283, 96906410, 59329511, 79806380, 92033260, 72373496, 20645197, 3294781, 99226875, 26744917, 12664567, 17385531, 1250437, 5832946, 34946859, 96318094, 94911072, 61141874, 99917599, 31904591, 45790169, 58208470, 54199160, 3183975, 53547802, 21993752, 15902805, 87598888, 4798568, 32426752, 84127901, 55602660, 48360198, 77312810, 78766358, 77300457, 33895336, 34667286, 73222868, 48088883, 70367851, 824872, 57658654, 61263068, 54427233 +16684829, 86242799, 19891772, 10309525, 77272628, 68128438, 59614746, 20486294, 47887470, 48658605, 1250437, 16380211, 5111370, 30139692, 22450468, 79657802, 38658347, 38009615, 43045786, 13819358, 1022677, 52293995, 30653863, 15535065, 27187213, 2204165, 43152977, 66319530, 62762857, 76825057, 13470059, 23432750, 22405120, 49598724, 21001913, 17894977, 9259676, 75229462, 72495719, 64098930, 19898053, 64087743, 62740044, 48892201, 33249630, 7646095, 63152504, 79136082, 51426311, 55143724, 68316156, 40781449, 37659250, 63015256, 56424103, 11757872, 62428472, 50806615, 66832478, 77300457, 32274392, 82339363, 10366309, 97940276, 97281847, 92215320, 51715482, 40865610, 9623492, 26776922, 508198, 62115552, 26114953, 88444207, 15075176, 15163258, 69848388, 65715134, 24058273, 93790285, 88130087, 67031644, 78561158, 68875490, 65275241, 82886381, 33123618, 29959549, 37183543, 7300047, 60102965, 53666583, 98653983, 55615885, 46851987, 36505482, 1239555, 34295794, 89214616, 5073754, 44245960, 47708850, 59329511, 29466406, 88047921, 16054533, 4091162, 72777973, 73786944, 31491938, 1375023, 67030811, 70420215, 57658654, 36930650, 82726008, 71657078, 67793644, 53888755, 72278539, 8696647, 89637706, 18833224, 74614639, 61960400, 54517921, 83368048, 31904591, 93566986, 68824981, 44481640, 40677414, 48673079, 93053405, 4434662, 26493119, 78218845, 33304202, 90013093, 90457870, 99658235, 61623915, 34698428, 63372756, 66611759, 8055981, 44664587, 49328608, 91802888, 81805959, 75052463, 38494874, 89419466, 55470718, 42237907, 54014062, 37280276, 749283, 20885148, 97011160, 22997281, 32159704, 14723410, 26734892, 61712234, 81853704, 3633375, 78884452, 22879907, 30395570, 54263880, 30787683, 70004753, 20867149, 15064655, 45990383, 52613508, 30569392, 73168565, 61728685, 17016156, 17058722, 99729357, 44847298, 61815223, 83948335, 41380093, 23110625, 92541302, 72738685, 75986488, 29188588, 63967300, 71469330, 7066775, 4099191, 89811711, 77787724, 29834512, 16971929, 18131876, 43376279, 31126490, 7517032, 61859143, 27625735, 33935899, 19272365, 66428795, 29401781, 37435892, 54606384, 39986008, 45381876, 8128637, 36396314, 99224392, 21397057, 10453030, 30163921, 61897582, 48893685, 45306070, 9886593, 88904910, 44627776, 57020481, 16387575, 45075471, 39553046, 15148031, 44842615, 99549373, 22435353, 8634541, 81855445, 27185644, 28550822, 91141395, 3088684, 66667729, 53842979, 30998561, 2208785, 60430369, 6038457, 74137926, 60106217, 95010552, 26397786, 57393458, 30366150, 9829782, 6497830, 34667286, 90061527, 62430984, 7919588, 20836893, 16595436, 73392814, 55770687, 80246713, 8807940, 38061439, 176257, 83789391, 79738755, 59197747, 24826575, 78589145, 64157906, 30090481, 98648327, 39801351, 42644903, 92867155, 42967683, 89804152, 72793444, 66271566, 54427233, 84002370, 77620120, 45428665, 57163802, 85116755, 92998591, 77694700, 74724075, 29029316, 44889423, 71522968, 18663507, 83747892, 70541760, 67281495, 56473732, 25636669, 39373729, 22113133, 71333116, 52204879, 90654836, 49271185, 76960303, 14363867, 45919976, 16567550, 95726235, 60393039, 47298834, 30218878, 5822202, 86022504, 36780454, 45996863, 77413012, 96709982, 29819940, 7502255, 54058788, 45995325, 10597197, 97057187, 73031054, 168541, 97641116, 86821229, 6521313, 11365791, 92353856, 69697787, 68694897, 94911072, 72358170, 44846932, 36812683, 19457589, 95251277, 43501211, 99917599, 61859581, 93057697, 36139942, 60176618, 8115266, 51588015, 4806458, 20642888, 83533741, 79942022, 94516935, 68041839, 18001617, 29516592, 21533347, 58208470, 38510840, 87160386, 75153252, 11923835, 6221471, 88653118, 28787861, 7229550, 9860968, 81274566, 59981773, 42947632, 14326617, 83150534, 1788101, 22200378, 37957788, 9860195, 6157724, 65454636, 81677380, 98130363, 32699744, 45794415, 53393358, 44844121, 21673260, 40984766, 12416768, 76170907, 55753905, 5482538, 76434144, 3235882, 92071990, 92554120, 569864, 33553959, 86543538, 6111563, 3233569, 51149804, 34698463, 42307449, 76703609, 48260151, 16019925, 13468268, 72614359, 37739481, 61741594, 32590267, 70372191, 112651, 22766820, 43322743, 66885828, 81172706, 37620363, 6808825, 18504197, 17146629, 99297164, 41442762, 96726697, 78766358, 7182642, 85711894, 59910624, 97379790, 41481685, 36135, 32058615, 77898274, 24915585, 54232247, 92692978, 74110882, 91938191, 86798033, 87720882, 50668599, 23194618, 72373496, 40356877, 10264691, 18806856, 20645197, 4668450, 20002147, 77284619, 98948034, 72357096, 66663942, 61982238, 96193415, 34497327, 37166608, 83269727, 89699445, 31727379, 33565483, 50567636, 53632373, 57240218, 83155442, 17385531, 52261574, 4064751, 47738185, 44060493, 34946859, 40534591, 32151165, 6871053, 65851721, 321665, 65017137, 99125126, 24733232, 32161669, 5267545, 91240048, 35512853, 16405341, 9058407, 14947650, 26139110, 33797252, 90272749, 13231279, 44348328, 84684495, 98462867, 25842078, 35092039, 76315420, 16445503, 84840549, 49882705, 3487592, 28796059, 54199160, 66045587, 79922758, 78602717, 66903004, 21919959, 70782102, 13862149, 10358899, 49236559, 64848072, 47893286, 44915235, 93515664, 11161731, 92398073, 14626618, 34236719, 6525948, 1197320, 17857111, 24591705, 20681921, 85695762, 81898046, 30891921, 86460488, 44177011, 8729146, 12571310, 75135448, 48360198, 77377183, 44784505, 69255765, 8129978, 5640302, 20765474, 38341669, 75777973, 55850790, 29932657, 60581278, 23134715, 39171895, 4204661, 26275734, 55189057, 36685023, 85117092, 80555751, 62936963, 35192533, 19101477, 49597667, 39847321, 29635537, 9175338, 7685448, 92604458, 54868730, 69786756, 32426752, 42692881, 37501808, 99965001, 68644627, 57359924, 20122224, 82532312, 24314885, 7687278, 1204161, 72238278, 85571389, 29994197, 55247455, 14731700, 67474219, 59501487, 79322415, 97783876, 77187825, 24168411, 17037369, 37192445, 15536795, 6986898, 50702367, 22129328, 46870723, 17727650, 46540998, 59318837, 94595668, 669105, 14220886, 2717150, 90090964, 9398733, 92530431, 23848565, 19376156, 69136837, 92787493, 85242963, 2891150, 75543508, 80316608, 33336148, 8373289, 57241521, 69579137, 69641513, 26863229, 19939935, 83133790, 71965942, 8099994, 88251446, 28928175, 53084256, 48395186, 62357986, 7104732, 58224549, 93270084, 68702632, 3880712, 33895336, 42782652, 57248122, 78549759, 33628349, 8651647, 85023028, 68816413, 14093520, 36933038, 84406788, 63628376, 48675329, 4199704, 67451935, 53802686, 32250045, 94539824, 89996536, 84166196, 38008118, 67227442, 65338021, 6153406, 53648154, 82178706, 99604946, 61373987, 99861373, 40027975, 37224844, 64602895, 66116458, 10649306, 31161687, 62051033, 61263068, 43933006, 48088883, 42199455, 51507425, 65081429, 2331773, 26507214, 78909561, 73124510, 37560164, 6793819, 16099750, 51047803, 59177718, 42073124, 36808486, 6505939, 23740167, 47090124, 7011964, 33699435, 58751351, 34044787, 33237508, 71316369, 2607799, 16424199, 81774825, 82651278, 4119747, 27665211, 79806380, 99021067, 9257405, 51466049, 94711842, 56484900, 24953498, 60955663, 20140249, 99226875, 38256849, 99333375, 84187166, 67513640, 56955985, 17386996, 48774913, 82779622, 99971982, 91255408, 4985896, 44550764, 2917920, 67124282, 61141874, 91281584, 36753250, 65038678, 82327024, 9599614, 92803766, 83210802, 4787945, 4095116, 66137019, 91727510, 45790169, 97561745, 33431960, 94090109, 13173644, 47361209, 57961282, 86001008, 17957593, 72274002, 17068582, 43357947, 10961421, 73235980, 96420429, 55602660, 23569917, 20568363, 1936762, 91831487, 36468541, 67084351, 36580610, 51472275, 17976208, 59405277, 27325428, 15015906, 21070110, 73222868, 22942635, 62490109, 68110330, 24619760, 1569515, 70727211, 31733363, 65880522, 22108665, 62552524, 47213183, 86301513, 63059024, 62803858, 89217461, 76671482, 874791, 80014588, 79880247, 4798568, 30463802, 7955293, 38365584, 92692283, 18411915, 71300104, 96906410, 99690194, 12303248, 70367851, 93562257, 73109997, 51464002, 86118021, 50007421, 8791066, 4508700, 38022834, 27041967, 91664334, 14045383, 49641577, 28851716, 69355476, 18699206, 92033260, 65074535, 4069912, 44479073, 56153345, 39201414, 99524975, 1431742, 78300864, 83083131, 40781854, 3294781, 62496012, 41092102, 68939068, 45848907, 26292919, 84127901, 57803235, 40686254, 99515901, 76330843, 14349098, 5832946, 30771409, 79821897, 40152546, 247198, 54987042, 62452034, 45049308, 71083565, 84349107, 90158785, 83651211, 72725103, 41092172, 70800879, 26102057, 45617087, 29510992, 88698958, 96852321, 3233084, 78785507, 65271999, 54762643, 45237957, 95581843, 96735716, 82427263, 4515343, 30694952, 98943869, 47792865, 91990218, 70036438, 4978543, 34493392, 30764367, 70596786, 21993752, 35456853, 15902805, 13348726, 68000591, 9575954, 7423788, 21289531, 77301523, 77312810, 82979980, 93933709, 16097038, 94360702, 82052050, 8913721, 63506281, 36845587, 98371444, 12348118, 41245325, 55960386, 18783702, 56461322, 74357852, 30811010, 72732205, 65047700, 8733068, 74441448, 64055960, 9603598, 87710366, 34432810, 44983451, 22721500, 82897371, 56515456, 56531125, 43506672, 79191827, 90310261, 35996293, 45667668, 44473167, 80251430, 17539625, 75820087, 93359396, 72019362, 26998766, 75128745, 66250369, 36312813, 51315460, 28734791, 51590803, 17365188, 89499542, 69605283, 19486173, 61928316, 75407347, 95395112, 58180653, 40197395, 66322959, 73617245, 415901, 9188443, 95957797, 90004325, 50188404, 68204242, 83302115, 12024238, 6819644, 73021291, 74452589, 55669657, 33201905, 26744917, 12664567, 89078848, 17081350, 37891451, 94076128, 29065964, 526217, 38645117, 45407418, 27411561, 68102437, 3183975, 95290172, 61271144, 36189527, 15948937, 18466635, 54663246, 53547802, 62232211, 33061250, 3773993, 89046466, 87598888, 30543215, 11543098, 84293052, 91957544, 52060076, 824872, 77072625, 11581181, 96318094, 62693428, 26664538, 59109400, 17764950, 58749, 26392416, 98739783, 37675718, 26063929, 84904436, 71920426, 74743862, 59371804, 76540481, 3509435, 5970607 +55470718, 99729357, 80555751, 43152977, 58749, 39801351, 26063929, 415901, 69786756, 50007421, 76330843, 76540481, 69641513, 86001008, 20836893, 53648154, 38009615, 30569392, 72614359, 78909561, 32426752, 6505939, 90004325, 34497327, 36780454, 12664567, 97641116, 61897582, 93566986, 44481640, 36139942, 44473167, 57961282, 17957593, 26998766, 99658235, 26776922, 23569917, 8651647, 36933038, 36580610, 37280276, 35456853, 70004753, 63059024, 93933709, 94360702, 39171895, 45428665, 39847321, 9188443, 74724075, 58751351, 77787724, 7182642, 92033260, 18806856, 1375023, 56484900, 64055960, 11757872, 99515901, 79821897, 96318094, 48893685, 16387575, 45075471, 83368048, 5267545, 79942022, 14947650, 66137019, 94090109, 47361209, 63372756, 88653118, 58224549, 49328608, 9860968, 68816413, 69848388, 53547802, 24058273, 21673260, 75135448, 61728685, 29932657, 33123618, 53666583, 36685023, 63506281, 36845587, 51047803, 96906410, 81172706, 55960386, 34044787, 38022834, 52204879, 16054533, 59910624, 41481685, 50668599, 68204242, 9257405, 16567550, 51466049, 31491938, 99524975, 72357096, 20140249, 70420215, 62762857, 89078848, 14349098, 86821229, 8696647, 2917920, 54517921, 61859581, 92803766, 45407418, 41092172, 85242963, 97281847, 80251430, 84684495, 72019362, 78785507, 66611759, 54762643, 8055981, 9623492, 44664587, 3088684, 96735716, 82427263, 91802888, 81805959, 51315460, 1936762, 95010552, 57393458, 37957788, 6157724, 77272628, 94539824, 26734892, 55770687, 69605283, 82178706, 22879907, 62490109, 15902805, 68110330, 99861373, 15064655, 30090481, 68000591, 77377183, 17016156, 569864, 33553959, 82979980, 26275734, 72793444, 73617245, 2331773, 4798568, 15535065, 75986488, 48658605, 112651, 33249630, 5073754, 6808825, 51426311, 25636669, 8791066, 29466406, 97379790, 36135, 72732205, 24314885, 19272365, 66428795, 65074535, 76960303, 10264691, 83302115, 47298834, 1431742, 67030811, 56424103, 96193415, 9603598, 97783876, 24168411, 39986008, 77413012, 6986898, 7502255, 71657078, 10453030, 94595668, 91255408, 22721500, 82897371, 62693428, 56531125, 321665, 88904910, 29065964, 95251277, 61960400, 92530431, 44842615, 9599614, 23432750, 83651211, 99549373, 27411561, 45790169, 45667668, 18001617, 33431960, 17539625, 92215320, 13231279, 88698958, 38510840, 90013093, 17068582, 61623915, 75153252, 3487592, 91141395, 65271999, 28787861, 3880712, 4515343, 79922758, 6038457, 38494874, 42947632, 21919959, 70782102, 30366150, 47893286, 70036438, 81677380, 89996536, 34236719, 45794415, 53393358, 70596786, 73392814, 3773993, 1569515, 37224844, 76434144, 67031644, 9575954, 95395112, 62051033, 62803858, 29959549, 77312810, 89217461, 23134715, 42967683, 16097038, 7300047, 8913721, 48892201, 83948335, 91957544, 49597667, 92541302, 98371444, 7685448, 71300104, 89214616, 99690194, 70372191, 22766820, 27187213, 93562257, 36808486, 70541760, 4508700, 41442762, 22113133, 95957797, 14045383, 16424199, 90654836, 33935899, 49271185, 86798033, 16684829, 99021067, 87720882, 60393039, 14731700, 20002147, 66319530, 30218878, 3294781, 77072625, 77187825, 33565483, 67513640, 8128637, 84127901, 46870723, 99224392, 4064751, 21397057, 46540998, 34946859, 40534591, 16380211, 34432810, 14220886, 74743862, 45306070, 44627776, 91281584, 57020481, 43506672, 71083565, 31904591, 32161669, 10366309, 23848565, 91240048, 40677414, 90158785, 92787493, 17764950, 20642888, 22405120, 16405341, 4095116, 83533741, 35996293, 94516935, 97940276, 75543508, 59371804, 4434662, 30139692, 13173644, 78218845, 19891772, 58208470, 44348328, 96852321, 19939935, 27185644, 35092039, 72274002, 93359396, 87160386, 84840549, 48395186, 36312813, 95581843, 96420429, 55602660, 74137926, 75052463, 91831487, 60106217, 95290172, 36468541, 26392416, 67084351, 49236559, 18466635, 84166196, 14723410, 98130363, 24591705, 81853704, 65715134, 21993752, 21070110, 24619760, 44177011, 83789391, 76170907, 12571310, 59197747, 24826575, 88130087, 69255765, 86301513, 43045786, 37183543, 86543538, 89804152, 42199455, 82052050, 40197395, 66271566, 11543098, 52293995, 42307449, 874791, 30463802, 19101477, 1239555, 41380093, 23110625, 6793819, 85116755, 18411915, 12303248, 43322743, 42692881, 70367851, 71522968, 37501808, 83747892, 67281495, 56461322, 71333116, 16971929, 31126490, 7517032, 61859143, 57359924, 4119747, 91938191, 7687278, 72238278, 73786944, 55247455, 74441448, 77284619, 5822202, 66663942, 87710366, 11581181, 50567636, 45996863, 57240218, 83155442, 17385531, 52261574, 30771409, 40152546, 94076128, 10597197, 97057187, 50806615, 53888755, 4985896, 11365791, 5111370, 89637706, 62452034, 9886593, 61141874, 19457589, 65038678, 43501211, 82339363, 68824981, 93057697, 19376156, 69136837, 90310261, 13470059, 35512853, 51588015, 26102057, 33336148, 97561745, 90272749, 29516592, 8634541, 83133790, 68102437, 45237957, 66667729, 79657802, 53842979, 42782652, 26114953, 78549759, 33628349, 9259676, 98943869, 81274566, 1788101, 26397786, 42237907, 10358899, 63628376, 22200378, 64848072, 51472275, 67451935, 59405277, 65454636, 32250045, 51590803, 22997281, 62430984, 15163258, 38008118, 1197320, 17857111, 67227442, 38658347, 62232211, 80246713, 8807940, 12416768, 86460488, 64087743, 33061250, 93790285, 55753905, 19486173, 65880522, 5482538, 78589145, 62552524, 45990383, 52613508, 8129978, 7423788, 98739783, 20765474, 42644903, 38341669, 21289531, 47887470, 61263068, 37675718, 3233569, 85117092, 34698463, 16019925, 66322959, 84293052, 36505482, 35192533, 73124510, 77620120, 72738685, 29635537, 9175338, 92604458, 59177718, 77694700, 44889423, 71469330, 73109997, 63152504, 79136082, 18504197, 41245325, 86118021, 4099191, 56473732, 47090124, 7011964, 33699435, 99297164, 33237508, 5970607, 96726697, 52060076, 4091162, 54232247, 72373496, 85571389, 39201414, 8733068, 94711842, 86242799, 4668450, 62496012, 61982238, 36930650, 79322415, 55669657, 38256849, 54606384, 83269727, 84187166, 68939068, 26292919, 40686254, 1250437, 82726008, 47738185, 59318837, 99971982, 44550764, 69697787, 68694897, 94911072, 99125126, 526217, 77300457, 74614639, 15148031, 59109400, 76825057, 72725103, 48673079, 26139110, 33797252, 68041839, 45617087, 57241521, 69579137, 21533347, 3509435, 26863229, 71965942, 90457870, 28928175, 40865610, 28550822, 11923835, 62115552, 20568363, 89419466, 59981773, 14326617, 91990218, 54014062, 13862149, 48675329, 36189527, 11161731, 72495719, 97011160, 7919588, 65338021, 19898053, 89499542, 44844121, 40984766, 54263880, 89046466, 61373987, 176257, 40027975, 8729146, 98648327, 3235882, 78561158, 75407347, 5640302, 13819358, 92554120, 65275241, 60581278, 82886381, 77301523, 43933006, 60102965, 48088883, 55189057, 76671482, 48260151, 84904436, 84002370, 46851987, 26507214, 62936963, 61815223, 7955293, 32590267, 57163802, 63967300, 29029316, 2204165, 51464002, 23740167, 7066775, 18783702, 39373729, 89811711, 68644627, 59329511, 29834512, 27041967, 43376279, 78766358, 88047921, 55143724, 81774825, 68316156, 77898274, 82651278, 20122224, 40781449, 37659250, 82532312, 27665211, 79806380, 18699206, 1204161, 29401781, 72777973, 29994197, 95726235, 24953498, 40781854, 99226875, 57658654, 37166608, 31727379, 17037369, 26744917, 57803235, 17081350, 96709982, 22129328, 17727650, 48774913, 44060493, 32151165, 6871053, 37891451, 669105, 67124282, 65017137, 82327024, 8115266, 4787945, 4806458, 80316608, 93053405, 26493119, 29510992, 81855445, 75820087, 98462867, 33304202, 17894977, 22450468, 53084256, 7104732, 68702632, 28796059, 508198, 2208785, 57248122, 30694952, 85023028, 14093520, 61271144, 44915235, 93515664, 28734791, 17976208, 92398073, 20885148, 15948937, 27325428, 17365188, 34493392, 32159704, 30764367, 64098930, 61712234, 32699744, 6153406, 78884452, 85695762, 22942635, 30891921, 30787683, 79738755, 64157906, 13348726, 44784505, 47213183, 68875490, 92867155, 75777973, 6111563, 58180653, 51149804, 76703609, 13468268, 54427233, 30653863, 62740044, 44847298, 61741594, 37560164, 16099750, 42073124, 92998591, 47708850, 18663507, 37620363, 85711894, 49641577, 32058615, 28851716, 69355476, 4069912, 44479073, 50188404, 23194618, 45919976, 60955663, 98948034, 33201905, 62428472, 99333375, 45381876, 15536795, 36396314, 50702367, 29819940, 45995325, 65851721, 73031054, 168541, 44983451, 72278539, 90090964, 92353856, 44846932, 36753250, 18833224, 79191827, 99917599, 91727510, 21001913, 76315420, 51715482, 49882705, 75128745, 10961421, 34698428, 66250369, 54199160, 3183975, 47792865, 75229462, 83150534, 749283, 6497830, 4978543, 15075176, 9860195, 10309525, 53802686, 90061527, 68128438, 15015906, 20681921, 16595436, 30395570, 70727211, 48360198, 64602895, 22108665, 92071990, 66116458, 10649306, 73168565, 55850790, 4204661, 51507425, 80014588, 92692283, 29188588, 12348118, 7646095, 99965001, 17146629, 18131876, 2607799, 91664334, 74357852, 24915585, 30811010, 27625735, 74110882, 14363867, 12024238, 78300864, 59501487, 74452589, 86022504, 89699445, 37192445, 82779622, 54058788, 247198, 66832478, 56515456, 24733232, 26664538, 84349107, 83210802, 9058407, 70800879, 8373289, 3233084, 25842078, 43357947, 8099994, 88251446, 62357986, 93270084, 66045587, 7229550, 66903004, 9829782, 14626618, 59614746, 54663246, 3633375, 73222868, 81898046, 38061439, 31733363, 31161687, 17058722, 79880247, 55615885, 65081429, 71316369, 92692978, 71920426, 56153345, 37435892, 83083131, 6819644, 56955985, 45848907, 53632373, 30163921, 54987042, 2717150, 6521313, 72358170, 36812683, 45049308, 39553046, 38645117, 60176618, 2891150, 49598724, 16445503, 73235980, 33895336, 30998561, 60430369, 78602717, 34667286, 6525948, 20486294, 99604946, 20867149, 87598888, 61928316, 98653983, 38365584, 34295794, 65047700, 63015256, 40356877, 20645197, 17386996, 5832946, 67793644, 32274392, 6221471, 88444207, 84406788, 1022677, 30543215, 54868730, 67474219, 73021291, 9398733, 4199704, 37739481, 44245960, 824872, 41092102, 22435353, 66885828 +61623915, 29516592, 85695762, 82886381, 57393458, 30787683, 65047700, 168541, 44842615, 26114953, 98943869, 98130363, 13348726, 43045786, 39201414, 57240218, 59318837, 67793644, 669105, 22721500, 18833224, 35996293, 17894977, 91802888, 85023028, 14093520, 51472275, 10309525, 5640302, 63506281, 65081429, 2331773, 73124510, 9188443, 18411915, 42073124, 66885828, 83747892, 22113133, 5970607, 16684829, 40781854, 79322415, 45996863, 29819940, 54058788, 72358170, 4806458, 17764950, 48673079, 3509435, 86001008, 71965942, 90457870, 10961421, 34698428, 4515343, 96420429, 51315460, 36580610, 21993752, 20486294, 48360198, 52613508, 78561158, 65275241, 77312810, 51149804, 26507214, 71522968, 25636669, 91664334, 49641577, 86798033, 50188404, 83302115, 1250437, 52261574, 96318094, 44983451, 32274392, 82339363, 8115266, 99549373, 80316608, 94090109, 38510840, 22450468, 7104732, 33895336, 62115552, 3183975, 9259676, 30366150, 9829782, 70036438, 9860195, 14626618, 64098930, 35456853, 44844121, 20867149, 64157906, 67031644, 44784505, 10649306, 61263068, 89217461, 16097038, 60102965, 53666583, 17058722, 62936963, 48892201, 91957544, 29635537, 57163802, 92998591, 22766820, 42692881, 93562257, 33699435, 58751351, 27625735, 4069912, 72238278, 51466049, 86242799, 59501487, 77072625, 86022504, 39986008, 53632373, 83155442, 96709982, 17385531, 94076128, 2717150, 44550764, 45306070, 92530431, 31904591, 4787945, 45407418, 68041839, 97281847, 8373289, 8634541, 13173644, 81855445, 43357947, 99658235, 28550822, 58749, 66250369, 66667729, 68702632, 33628349, 66903004, 81274566, 68816413, 22200378, 44915235, 28734791, 67451935, 15075176, 11161731, 30764367, 1197320, 19898053, 55770687, 15902805, 38009615, 12416768, 44177011, 76170907, 55753905, 24826575, 5482538, 98739783, 31161687, 29959549, 77301523, 37183543, 7300047, 8913721, 76703609, 99729357, 73617245, 72614359, 7955293, 415901, 72738685, 70372191, 112651, 18663507, 23740167, 56473732, 7011964, 96726697, 52060076, 4091162, 28851716, 71920426, 82532312, 76960303, 14363867, 95726235, 37435892, 55247455, 74441448, 20002147, 67474219, 30218878, 3294781, 61982238, 96193415, 57658654, 77187825, 89699445, 31727379, 17037369, 15536795, 84127901, 76330843, 46540998, 40534591, 94595668, 50806615, 72278539, 56515456, 29065964, 91281584, 65038678, 43501211, 39553046, 83368048, 79191827, 26664538, 82327024, 19376156, 84349107, 69136837, 20642888, 9058407, 27411561, 93053405, 4434662, 49598724, 29510992, 26863229, 93359396, 84840549, 78785507, 88251446, 11923835, 63372756, 6221471, 48395186, 62357986, 58224549, 73235980, 9623492, 79657802, 96735716, 508198, 82427263, 60430369, 74137926, 23569917, 59981773, 55470718, 83150534, 88444207, 13862149, 63628376, 4199704, 36189527, 59405277, 51590803, 81677380, 62430984, 89996536, 84166196, 20836893, 15015906, 20681921, 3633375, 53547802, 45794415, 53393358, 78884452, 69605283, 21070110, 81898046, 30891921, 62490109, 80246713, 33061250, 79738755, 93790285, 12571310, 30090481, 22108665, 92071990, 8129978, 13819358, 38341669, 73168565, 33123618, 21289531, 47887470, 37675718, 569864, 42967683, 72793444, 58180653, 30543215, 54427233, 874791, 79880247, 84002370, 35192533, 19101477, 32590267, 75986488, 6793819, 85116755, 89214616, 43322743, 12348118, 44889423, 79136082, 51464002, 18504197, 4508700, 89811711, 71333116, 74357852, 78766358, 81774825, 30811010, 69355476, 74110882, 56153345, 40356877, 94711842, 12024238, 60393039, 99524975, 43152977, 78300864, 60955663, 67030811, 98948034, 20140249, 62762857, 41092102, 83269727, 26292919, 36396314, 6986898, 22129328, 99224392, 4064751, 17727650, 21397057, 79821897, 37891451, 10597197, 97057187, 61897582, 4985896, 90090964, 48893685, 67124282, 5111370, 88904910, 36753250, 45049308, 526217, 77300457, 45075471, 68824981, 32161669, 9599614, 92803766, 59109400, 76540481, 14947650, 91727510, 2891150, 26139110, 33336148, 80251430, 78218845, 17539625, 13231279, 57961282, 84684495, 96852321, 76315420, 75153252, 3487592, 95581843, 78602717, 81805959, 30694952, 38494874, 1936762, 91831487, 89419466, 42947632, 75229462, 14326617, 21919959, 95290172, 36933038, 1788101, 84406788, 17976208, 92398073, 34667286, 32250045, 27325428, 90061527, 17857111, 7919588, 24591705, 65715134, 6153406, 53648154, 62232211, 21673260, 68110330, 99604946, 38061439, 30395570, 24619760, 70727211, 31733363, 99861373, 75135448, 59197747, 76434144, 77377183, 75407347, 86301513, 7423788, 20765474, 92867155, 62803858, 1022677, 39171895, 98653983, 42199455, 82052050, 11543098, 13468268, 66322959, 51507425, 84904436, 46851987, 4798568, 44847298, 36845587, 37739481, 38365584, 49597667, 15535065, 9175338, 51047803, 71300104, 92604458, 96906410, 29029316, 7646095, 73109997, 7066775, 18783702, 86118021, 56461322, 47090124, 34044787, 71316369, 68644627, 38022834, 27041967, 52204879, 2607799, 31126490, 77898274, 57359924, 90004325, 72732205, 65074535, 68204242, 72777973, 72373496, 45919976, 10264691, 24953498, 64055960, 1431742, 66319530, 97783876, 24168411, 87710366, 37166608, 68939068, 50567636, 89078848, 40686254, 46870723, 71657078, 34432810, 65851721, 97641116, 14220886, 74743862, 82897371, 2917920, 56531125, 89637706, 94911072, 44846932, 36812683, 54517921, 93566986, 24733232, 61859581, 10366309, 40677414, 38645117, 13470059, 23432750, 35512853, 26102057, 85242963, 83533741, 75543508, 59371804, 18001617, 90272749, 30139692, 44473167, 92215320, 44348328, 27185644, 90013093, 17068582, 68102437, 40865610, 54762643, 88653118, 3088684, 28796059, 30998561, 49328608, 2208785, 7229550, 95010552, 37280276, 48675329, 37957788, 72495719, 77272628, 68128438, 34236719, 6525948, 14723410, 81853704, 32699744, 38658347, 73222868, 82178706, 22879907, 54263880, 61373987, 1569515, 40027975, 37224844, 8729146, 19486173, 78589145, 61928316, 45990383, 9575954, 68875490, 82979980, 6111563, 94360702, 48088883, 4204661, 3233569, 89804152, 55189057, 66271566, 52293995, 16019925, 55615885, 78909561, 37560164, 77620120, 34295794, 29188588, 48658605, 99690194, 32426752, 5073754, 74724075, 47708850, 6505939, 99965001, 8791066, 17146629, 59329511, 77787724, 29466406, 7182642, 85711894, 37659250, 4119747, 27665211, 7687278, 19272365, 44479073, 50668599, 23194618, 9257405, 85571389, 29994197, 73021291, 72357096, 824872, 66663942, 56424103, 54606384, 33201905, 11581181, 99333375, 67513640, 17386996, 82726008, 5832946, 10453030, 40152546, 16380211, 73031054, 86821229, 6521313, 92353856, 69697787, 62693428, 321665, 62452034, 9886593, 19457589, 43506672, 61960400, 15148031, 23848565, 60176618, 76825057, 90158785, 92787493, 22405120, 16405341, 70800879, 66137019, 33797252, 26493119, 45667668, 97561745, 33431960, 47361209, 75820087, 33304202, 51715482, 72019362, 28928175, 53084256, 65271999, 66611759, 54199160, 3880712, 26776922, 6038457, 36468541, 26392416, 67084351, 42237907, 54014062, 4978543, 65454636, 53802686, 15948937, 18466635, 32159704, 26734892, 67227442, 65338021, 70596786, 22942635, 8807940, 40984766, 3773993, 89046466, 83789391, 15064655, 98648327, 62552524, 47213183, 63059024, 95395112, 55850790, 60581278, 86543538, 26275734, 76671482, 85117092, 34698463, 42307449, 30653863, 80555751, 61741594, 1239555, 92692283, 23110625, 7685448, 54868730, 63967300, 12303248, 77694700, 44245960, 2204165, 36808486, 41245325, 51426311, 4099191, 99297164, 95957797, 29834512, 18131876, 16054533, 16424199, 41481685, 36135, 92692978, 24314885, 33935899, 91938191, 1204161, 66428795, 99021067, 18806856, 8733068, 47298834, 31491938, 56484900, 77284619, 5822202, 99226875, 34497327, 70420215, 11757872, 62428472, 26744917, 8128637, 12664567, 77413012, 44060493, 34946859, 6871053, 11365791, 9398733, 44627776, 16387575, 99917599, 44481640, 36139942, 72725103, 41092172, 4095116, 97940276, 45790169, 45617087, 57241521, 19891772, 88698958, 19939935, 72274002, 87160386, 26998766, 91141395, 8055981, 44664587, 93270084, 66045587, 75052463, 47792865, 70782102, 61271144, 64848072, 749283, 20885148, 97011160, 22997281, 34493392, 59614746, 38008118, 54663246, 69848388, 16595436, 24058273, 73392814, 64087743, 92554120, 75777973, 61728685, 17016156, 33553959, 48260151, 84293052, 62740044, 36505482, 30463802, 41380093, 45428665, 71469330, 37620363, 37501808, 63152504, 6808825, 55960386, 41442762, 43376279, 55143724, 14045383, 97379790, 32058615, 68316156, 82651278, 61859143, 20122224, 79806380, 92033260, 63015256, 16567550, 20645197, 4668450, 14731700, 6819644, 38256849, 36780454, 56955985, 37192445, 45848907, 45381876, 99515901, 47738185, 82779622, 54987042, 91255408, 8696647, 65017137, 99125126, 61141874, 57020481, 95251277, 5267545, 83210802, 90310261, 79942022, 69641513, 17957593, 83133790, 35092039, 8099994, 16445503, 49882705, 75128745, 45237957, 36312813, 79922758, 78549759, 20568363, 9860968, 26397786, 91990218, 47893286, 17365188, 61712234, 89499542, 70004753, 87598888, 65880522, 88130087, 42644903, 29932657, 40197395, 80014588, 92541302, 39847321, 16099750, 98371444, 70541760, 33237508, 40781449, 29401781, 73786944, 62496012, 9603598, 36930650, 74452589, 55669657, 33565483, 14349098, 7502255, 45995325, 99971982, 91240048, 94516935, 22435353, 69579137, 58208470, 3233084, 25842078, 21001913, 28787861, 55602660, 8651647, 60106217, 6497830, 6157724, 15163258, 94539824, 86460488, 3235882, 69255765, 30569392, 66116458, 39801351, 23134715, 26063929, 61815223, 83948335, 69786756, 33249630, 81172706, 67281495, 16971929, 7517032, 24915585, 54232247, 49271185, 87720882, 1375023, 84187166, 57803235, 48774913, 247198, 30163921, 66832478, 53888755, 68694897, 74614639, 71083565, 93057697, 21533347, 98462867, 42782652, 49236559, 93515664, 176257, 68000591, 93933709, 43933006, 36685023, 59177718, 27187213, 70367851, 50007421, 39373729, 88047921, 18699206, 83083131, 51588015, 53842979, 57248122, 64602895, 90654836, 50702367, 17081350, 83651211, 62051033, 59910624, 30771409, 10358899, 32151165 +86022504, 11923835, 70372191, 7646095, 4069912, 25842078, 54199160, 16595436, 3633375, 7423788, 52060076, 83302115, 94595668, 61960400, 68824981, 6221471, 68816413, 27325428, 98130363, 99861373, 64157906, 84904436, 77694700, 58751351, 18131876, 65047700, 72238278, 5822202, 24168411, 89637706, 92803766, 83210802, 40677414, 92787493, 17764950, 70800879, 88251446, 28928175, 53084256, 30694952, 47792865, 75229462, 59405277, 10309525, 54663246, 81853704, 24058273, 99604946, 52613508, 42644903, 29635537, 42073124, 66885828, 29834512, 16971929, 2607799, 32058615, 45919976, 14731700, 67474219, 67513640, 44060493, 29819940, 99971982, 66832478, 92353856, 56531125, 32274392, 69136837, 4787945, 78218845, 21533347, 98462867, 40865610, 62357986, 14093520, 84406788, 28734791, 92398073, 34236719, 89499542, 62232211, 81898046, 30395570, 15064655, 8729146, 55753905, 76434144, 98739783, 65275241, 82886381, 60102965, 55189057, 11543098, 8913721, 26063929, 63506281, 36505482, 415901, 91957544, 18411915, 48658605, 54868730, 29029316, 99965001, 25636669, 17146629, 91664334, 74357852, 16424199, 68316156, 30811010, 69355476, 71920426, 49271185, 92033260, 1375023, 66663942, 34497327, 41092102, 50567636, 56955985, 50702367, 83155442, 32151165, 59318837, 91255408, 90090964, 67124282, 526217, 18833224, 44481640, 61859581, 26102057, 66137019, 33797252, 97561745, 13173644, 58208470, 90457870, 75128745, 95581843, 96420429, 60430369, 3183975, 75052463, 38494874, 67084351, 10358899, 9829782, 48675329, 44915235, 18466635, 68128438, 15163258, 84166196, 38008118, 7919588, 61712234, 30891921, 62490109, 8807940, 86460488, 20867149, 70727211, 79738755, 78589145, 92071990, 75407347, 33553959, 43933006, 58180653, 48260151, 80014588, 30653863, 65081429, 84002370, 44847298, 37739481, 41380093, 45428665, 85116755, 9188443, 96906410, 33249630, 44889423, 73109997, 18783702, 56461322, 4508700, 41442762, 55143724, 74110882, 7687278, 19272365, 68204242, 29401781, 72777973, 85571389, 39201414, 99524975, 55247455, 86242799, 20002147, 77284619, 38256849, 17037369, 45381876, 99515901, 96318094, 45995325, 40152546, 34432810, 54987042, 86821229, 2717150, 48893685, 56515456, 321665, 9886593, 45049308, 43501211, 71083565, 15148031, 26664538, 10366309, 23848565, 19376156, 13470059, 99549373, 16405341, 9058407, 4095116, 27411561, 79942022, 35996293, 94516935, 68041839, 26493119, 45617087, 8373289, 94090109, 69579137, 47361209, 44348328, 96852321, 26863229, 3233084, 72274002, 93359396, 78785507, 49882705, 28550822, 3088684, 79657802, 42782652, 2208785, 4515343, 78602717, 66903004, 55470718, 95290172, 36468541, 13862149, 63628376, 67451935, 32250045, 90061527, 22997281, 6525948, 1197320, 17857111, 53547802, 65338021, 21993752, 61373987, 31733363, 59197747, 24826575, 5482538, 13348726, 68000591, 45990383, 9575954, 3235882, 78561158, 86301513, 63059024, 39801351, 95395112, 20765474, 62803858, 82979980, 42967683, 86543538, 4204661, 26275734, 82052050, 66271566, 51507425, 874791, 73617245, 55615885, 2331773, 26507214, 37560164, 23110625, 34295794, 72738685, 75986488, 57163802, 89214616, 99690194, 5073754, 71469330, 37620363, 86118021, 47090124, 99297164, 68644627, 5970607, 38022834, 77787724, 96726697, 78766358, 31126490, 97379790, 36135, 7517032, 57359924, 4091162, 27665211, 56153345, 50668599, 23194618, 10264691, 20645197, 40781854, 3294781, 824872, 99226875, 61982238, 11757872, 74452589, 77187825, 99333375, 68939068, 37192445, 45996863, 8128637, 12664567, 36396314, 17386996, 17081350, 52261574, 46870723, 4064751, 17727650, 7502255, 16380211, 67793644, 50806615, 30163921, 61897582, 669105, 44983451, 6521313, 74743862, 45306070, 5111370, 44846932, 44627776, 74614639, 39553046, 83368048, 79191827, 99917599, 24733232, 38645117, 90158785, 72725103, 4806458, 45407418, 48673079, 26139110, 22435353, 93053405, 49598724, 33336148, 30139692, 44473167, 33431960, 13231279, 57961282, 75820087, 3509435, 69641513, 84684495, 17894977, 17068582, 76315420, 8099994, 51715482, 61623915, 88653118, 44664587, 68702632, 26776922, 66045587, 30998561, 49328608, 79922758, 6038457, 81805959, 78549759, 20568363, 98943869, 91831487, 42947632, 30366150, 93515664, 15075176, 6157724, 15948937, 34667286, 81677380, 14626618, 32159704, 55770687, 69605283, 176257, 83789391, 87598888, 37224844, 64602895, 62552524, 69255765, 30569392, 66116458, 13819358, 92554120, 38341669, 31161687, 89804152, 76671482, 85117092, 13468268, 62740044, 72614359, 19101477, 32590267, 92692283, 29188588, 98371444, 43322743, 42692881, 36808486, 70541760, 55960386, 50007421, 33699435, 39373729, 22113133, 29466406, 43376279, 7182642, 16054533, 59910624, 81774825, 40781449, 28851716, 27625735, 72732205, 54232247, 82532312, 33935899, 44479073, 18806856, 24953498, 74441448, 60955663, 4668450, 73021291, 36930650, 55669657, 87710366, 33201905, 62428472, 84187166, 26292919, 57803235, 82726008, 76330843, 14349098, 48774913, 21397057, 10597197, 65851721, 97641116, 14220886, 11365791, 82897371, 62693428, 99125126, 61141874, 44842615, 9599614, 91240048, 59109400, 60176618, 23432750, 83651211, 51588015, 85242963, 97940276, 91727510, 2891150, 59371804, 45667668, 8634541, 19891772, 29510992, 81855445, 38510840, 35092039, 16445503, 22450468, 99658235, 10961421, 48395186, 8055981, 93270084, 66250369, 66667729, 36312813, 91802888, 55602660, 74137926, 33628349, 51315460, 1936762, 83150534, 21919959, 88444207, 26397786, 57393458, 49236559, 37280276, 4978543, 17976208, 53802686, 34493392, 89996536, 24591705, 15015906, 65715134, 6153406, 19898053, 38658347, 22942635, 80246713, 40984766, 30787683, 76170907, 19486173, 65880522, 30090481, 98648327, 5640302, 10649306, 75777973, 61728685, 55850790, 29932657, 21289531, 61263068, 48088883, 42199455, 52293995, 66322959, 54427233, 79880247, 46851987, 48892201, 15535065, 6793819, 47708850, 81172706, 18663507, 2204165, 7066775, 8791066, 95957797, 27041967, 85711894, 41481685, 77898274, 82651278, 20122224, 37659250, 65074535, 63015256, 72373496, 29994197, 16567550, 95726235, 47298834, 37435892, 83083131, 66319530, 67030811, 30218878, 62496012, 56424103, 70420215, 83269727, 11581181, 33565483, 6986898, 47738185, 5832946, 34946859, 6871053, 37891451, 247198, 73031054, 72278539, 68694897, 94911072, 62452034, 36812683, 19457589, 65038678, 82327024, 84349107, 90310261, 76825057, 41092172, 83533741, 4434662, 57241521, 80251430, 17539625, 19939935, 27185644, 72019362, 84840549, 75153252, 91141395, 58224549, 73235980, 96735716, 23569917, 89419466, 14326617, 95010552, 26392416, 22200378, 4199704, 36189527, 9860195, 11161731, 72495719, 97011160, 94539824, 59614746, 26734892, 20836893, 45794415, 78884452, 21070110, 22879907, 21673260, 12416768, 70004753, 93790285, 75135448, 88130087, 67031644, 44784505, 47213183, 8129978, 47887470, 569864, 37183543, 39171895, 40197395, 36685023, 34698463, 36845587, 61815223, 1239555, 92541302, 51047803, 71300104, 32426752, 63967300, 44245960, 63152504, 79136082, 51464002, 67281495, 33237508, 71316369, 90004325, 90654836, 4119747, 24314885, 86798033, 50188404, 40356877, 8733068, 12024238, 43152977, 64055960, 1431742, 78300864, 6819644, 20140249, 59501487, 57658654, 62762857, 79322415, 54606384, 89699445, 26744917, 89078848, 57240218, 22129328, 30771409, 10453030, 97057187, 53888755, 2917920, 29065964, 36753250, 77300457, 95251277, 45075471, 92530431, 31904591, 93566986, 82339363, 93057697, 5267545, 8115266, 76540481, 14947650, 75543508, 80316608, 97281847, 18001617, 83133790, 90013093, 43357947, 63372756, 54762643, 45237957, 53842979, 7229550, 62115552, 81274566, 61271144, 1788101, 54014062, 6497830, 37957788, 51590803, 77272628, 30764367, 67227442, 53393358, 53648154, 82178706, 15902805, 38061439, 48360198, 61928316, 62051033, 73168565, 60581278, 17016156, 77301523, 37675718, 93933709, 16097038, 53666583, 17058722, 30543215, 76703609, 7955293, 77620120, 9175338, 7685448, 92604458, 92998591, 74724075, 70367851, 93562257, 83747892, 6808825, 41245325, 23740167, 56473732, 71333116, 14045383, 49641577, 61859143, 24915585, 18699206, 66428795, 14363867, 87720882, 60393039, 98948034, 31727379, 77413012, 53632373, 82779622, 79821897, 94076128, 168541, 22721500, 69697787, 88904910, 16387575, 54517921, 35512853, 20642888, 22405120, 92215320, 33304202, 34698428, 65271999, 66611759, 9623492, 28787861, 3880712, 33895336, 508198, 82427263, 57248122, 26114953, 9259676, 85023028, 59981773, 70782102, 36933038, 64848072, 51472275, 65454636, 20885148, 17365188, 14723410, 20486294, 35456853, 68110330, 38009615, 1569515, 12571310, 77377183, 22108665, 43045786, 68875490, 92867155, 33123618, 6111563, 3233569, 98653983, 16019925, 84293052, 78909561, 61741594, 83948335, 38365584, 49597667, 16099750, 69786756, 59177718, 112651, 22766820, 71522968, 37501808, 4099191, 7011964, 89811711, 59329511, 92692978, 79806380, 91938191, 76960303, 16684829, 99021067, 73786944, 31491938, 56484900, 96193415, 9603598, 97783876, 36780454, 39986008, 45848907, 84127901, 17385531, 54058788, 4985896, 9398733, 72358170, 57020481, 36139942, 45790169, 29516592, 17957593, 87160386, 26998766, 68102437, 3487592, 58749, 8651647, 9860968, 42237907, 36580610, 91990218, 749283, 70036438, 62430984, 64098930, 20681921, 85695762, 73222868, 33061250, 3773993, 54263880, 44177011, 89046466, 29959549, 77312810, 89217461, 94360702, 7300047, 72793444, 51149804, 99729357, 80555751, 62936963, 35192533, 30463802, 73124510, 39847321, 12303248, 27187213, 6505939, 18504197, 1204161, 9257405, 51466049, 72357096, 37166608, 15536795, 40686254, 1250437, 99224392, 46540998, 40534591, 44550764, 65017137, 91281584, 32161669, 90272749, 88698958, 71965942, 21001913, 47893286, 69848388, 32699744, 64087743, 40027975, 1022677, 42307449, 4798568, 12348118, 51426311, 88047921, 94711842, 77072625, 96709982, 43506672, 7104732, 28796059, 44844121, 24619760, 23134715, 34044787, 52204879, 8696647, 70596786, 73392814, 71657078, 86001008, 60106217 +16019925, 44627776, 65851721, 89046466, 92554120, 6111563, 72357096, 97783876, 72358170, 95251277, 22405120, 8055981, 70782102, 70596786, 3235882, 23134715, 37560164, 51047803, 66319530, 7502255, 247198, 43506672, 10366309, 90272749, 91141395, 28787861, 28796059, 82427263, 4199704, 4978543, 17365188, 14723410, 70727211, 38341669, 3233569, 98653983, 79880247, 61815223, 7955293, 72738685, 16099750, 112651, 27187213, 81172706, 37501808, 18504197, 92692978, 12024238, 3294781, 55669657, 67513640, 57240218, 99224392, 4064751, 11365791, 67124282, 16405341, 4434662, 97281847, 26863229, 17957593, 21001913, 90013093, 87160386, 58749, 3880712, 508198, 30694952, 75052463, 9860968, 1788101, 91990218, 9829782, 69848388, 73222868, 44177011, 8729146, 60581278, 1022677, 84293052, 26507214, 62936963, 38365584, 69786756, 74724075, 2204165, 93562257, 79136082, 51464002, 38022834, 77898274, 82651278, 57359924, 1204161, 87720882, 86242799, 6819644, 79322415, 36780454, 89699445, 45848907, 45996863, 96709982, 82779622, 97057187, 30163921, 53888755, 44550764, 44846932, 39553046, 90158785, 94516935, 57241521, 47361209, 92215320, 98462867, 25842078, 83133790, 71965942, 72019362, 26998766, 68102437, 99658235, 34698428, 65271999, 66667729, 79922758, 7229550, 8651647, 1936762, 89419466, 26392416, 42237907, 84406788, 36189527, 37957788, 18466635, 51590803, 22997281, 94539824, 73392814, 21070110, 22942635, 80246713, 70004753, 93790285, 24826575, 65880522, 76434144, 45990383, 75777973, 17016156, 569864, 86543538, 16097038, 39171895, 4204661, 51149804, 44847298, 61741594, 45428665, 42073124, 63967300, 51426311, 89811711, 22113133, 59329511, 29466406, 91664334, 31126490, 90654836, 79806380, 33935899, 16684829, 56153345, 9257405, 51466049, 31491938, 56424103, 34497327, 36930650, 26744917, 84127901, 17081350, 48774913, 30771409, 71657078, 79821897, 168541, 4985896, 22721500, 82897371, 321665, 36812683, 24733232, 32161669, 36139942, 60176618, 27411561, 97940276, 2891150, 26493119, 18001617, 29516592, 44473167, 94090109, 58208470, 13231279, 57961282, 16445503, 3487592, 58224549, 73235980, 36312813, 42782652, 98943869, 91831487, 60106217, 14093520, 21919959, 95010552, 36933038, 88444207, 57393458, 37280276, 92398073, 68128438, 34236719, 38008118, 15015906, 67227442, 16595436, 32699744, 85695762, 82178706, 21673260, 99604946, 33061250, 3773993, 24619760, 87598888, 37224844, 59197747, 48360198, 77377183, 86301513, 63059024, 43045786, 61728685, 21289531, 37675718, 55189057, 80014588, 65081429, 84002370, 80555751, 49597667, 23110625, 92541302, 39847321, 6793819, 54868730, 12303248, 12348118, 83747892, 23740167, 7066775, 56473732, 52204879, 88047921, 7182642, 55143724, 85711894, 20122224, 40781449, 37659250, 4119747, 18699206, 91938191, 73786944, 40356877, 18806856, 60393039, 24953498, 78300864, 40781854, 98948034, 9603598, 77187825, 83269727, 62428472, 11581181, 99333375, 31727379, 45381876, 8128637, 15536795, 36396314, 40686254, 76330843, 46870723, 17727650, 40534591, 6871053, 73031054, 86821229, 91255408, 6521313, 8696647, 2917920, 9398733, 99125126, 71083565, 83368048, 99917599, 61859581, 44842615, 93057697, 91240048, 40677414, 90310261, 13470059, 4787945, 35512853, 20642888, 26139110, 49598724, 68041839, 78218845, 69579137, 17539625, 81855445, 88698958, 3233084, 27185644, 17068582, 76315420, 93359396, 66611759, 57248122, 55602660, 74137926, 81805959, 42947632, 55470718, 75229462, 14326617, 26397786, 70036438, 44915235, 77272628, 97011160, 34493392, 54663246, 1197320, 7919588, 53393358, 19898053, 78884452, 69605283, 38658347, 22879907, 62232211, 44844121, 62490109, 15902805, 38009615, 64087743, 54263880, 40027975, 76170907, 88130087, 78589145, 9575954, 30569392, 98739783, 62051033, 29932657, 82886381, 77301523, 89217461, 43933006, 89804152, 17058722, 40197395, 52293995, 85117092, 42307449, 874791, 55615885, 72614359, 36505482, 37739481, 83948335, 32590267, 85116755, 18411915, 98371444, 7685448, 71300104, 92998591, 22766820, 47708850, 71469330, 63152504, 6505939, 70541760, 8791066, 17146629, 34044787, 71316369, 71333116, 2607799, 43376279, 14045383, 16054533, 90004325, 4091162, 86798033, 65074535, 99021067, 50668599, 68204242, 23194618, 85571389, 39201414, 45919976, 8733068, 47298834, 43152977, 55247455, 4668450, 20002147, 30218878, 73021291, 62496012, 5822202, 61982238, 33201905, 33565483, 39986008, 53632373, 17385531, 99515901, 40152546, 94595668, 97641116, 669105, 72278539, 2717150, 65017137, 88904910, 61141874, 43501211, 54517921, 15148031, 26664538, 92803766, 72725103, 51588015, 26102057, 85242963, 75543508, 93053405, 45667668, 30139692, 8634541, 19891772, 86001008, 40865610, 28550822, 75128745, 11923835, 45237957, 44664587, 4515343, 96420429, 91802888, 62115552, 23569917, 20568363, 66903004, 81274566, 59981773, 61271144, 49236559, 22200378, 749283, 48675329, 9860195, 65454636, 11161731, 15948937, 59614746, 17857111, 20681921, 53547802, 6153406, 20486294, 35456853, 53648154, 38061439, 30787683, 20867149, 67031644, 13348726, 30090481, 61928316, 44784505, 47213183, 68875490, 39801351, 95395112, 47887470, 37183543, 93933709, 94360702, 53666583, 99729357, 13468268, 73617245, 84904436, 30653863, 2331773, 4798568, 78909561, 30463802, 1239555, 41380093, 92692283, 29188588, 9175338, 99690194, 43322743, 29029316, 7646095, 6808825, 55960386, 4099191, 47090124, 39373729, 68644627, 16971929, 18131876, 96726697, 36135, 52060076, 24915585, 30811010, 69355476, 66428795, 76960303, 29994197, 94711842, 99524975, 56484900, 37435892, 64055960, 83083131, 60955663, 77284619, 20140249, 59501487, 11757872, 74452589, 17037369, 12664567, 26292919, 77413012, 57803235, 14349098, 46540998, 45995325, 10597197, 61897582, 48893685, 68694897, 19457589, 91281584, 57020481, 18833224, 32274392, 92530431, 82339363, 83210802, 38645117, 23432750, 17764950, 83533741, 79942022, 45617087, 33431960, 80251430, 13173644, 69641513, 38510840, 33304202, 35092039, 72274002, 43357947, 63372756, 7104732, 66045587, 30998561, 6038457, 26114953, 85023028, 95290172, 47893286, 51472275, 93515664, 17976208, 15075176, 27325428, 62430984, 15163258, 32159704, 26734892, 45794415, 89499542, 55770687, 40984766, 68110330, 30395570, 61373987, 1569515, 176257, 83789391, 31733363, 99861373, 19486173, 64157906, 78561158, 7423788, 5640302, 29959549, 77312810, 82979980, 42967683, 7300047, 72793444, 36685023, 76671482, 34698463, 76703609, 26063929, 35192533, 73124510, 34295794, 15535065, 29635537, 57163802, 5073754, 44889423, 44245960, 67281495, 7011964, 33699435, 25636669, 95957797, 77787724, 29834512, 74357852, 59910624, 61859143, 28851716, 27625735, 72732205, 63015256, 44479073, 50188404, 72777973, 95726235, 14731700, 57658654, 38256849, 41092102, 37166608, 68939068, 50567636, 83155442, 1250437, 22129328, 47738185, 44060493, 29819940, 94076128, 67793644, 54987042, 62693428, 94911072, 29065964, 36753250, 16387575, 526217, 65038678, 74614639, 61960400, 68824981, 44481640, 82327024, 9599614, 23848565, 8115266, 83651211, 99549373, 9058407, 76540481, 35996293, 14947650, 33336148, 84684495, 19939935, 84840549, 48395186, 88653118, 9623492, 68702632, 26776922, 96735716, 2208785, 3183975, 78602717, 51315460, 9259676, 47792865, 54014062, 63628376, 6497830, 59405277, 20885148, 34667286, 89996536, 30764367, 6525948, 61712234, 81853704, 3633375, 86460488, 75135448, 68000591, 22108665, 62552524, 66116458, 8129978, 13819358, 31161687, 33123618, 61263068, 33553959, 48088883, 58180653, 42199455, 82052050, 11543098, 8913721, 51507425, 46851987, 48892201, 75986488, 9188443, 48658605, 92604458, 70367851, 18663507, 36808486, 50007421, 4508700, 99297164, 33237508, 5970607, 27041967, 16424199, 49641577, 68316156, 7517032, 54232247, 82532312, 49271185, 7687278, 14363867, 72373496, 10264691, 16567550, 1375023, 1431742, 99226875, 66663942, 96193415, 70420215, 62762857, 17386996, 52261574, 21397057, 34946859, 10453030, 34432810, 99971982, 66832478, 14220886, 90090964, 74743862, 92353856, 69697787, 45306070, 56515456, 89637706, 45049308, 45075471, 93566986, 84349107, 59109400, 92787493, 70800879, 59371804, 80316608, 22435353, 97561745, 29510992, 75820087, 96852321, 51715482, 61623915, 75153252, 53084256, 3088684, 54199160, 78549759, 33628349, 38494874, 68816413, 36580610, 30366150, 6157724, 90061527, 81677380, 64098930, 21993752, 81898046, 12416768, 79738755, 12571310, 55753905, 5482538, 92867155, 55850790, 26275734, 66271566, 66322959, 54427233, 91957544, 77620120, 70372191, 33249630, 71522968, 41245325, 99965001, 18783702, 78766358, 97379790, 41481685, 71920426, 24314885, 65047700, 19272365, 4069912, 67474219, 77072625, 54606384, 24168411, 84187166, 37192445, 89078848, 6986898, 5832946, 59318837, 37891451, 16380211, 50806615, 5111370, 62452034, 77300457, 79191827, 31904591, 5267545, 19376156, 76825057, 4806458, 41092172, 48673079, 4095116, 66137019, 45790169, 33797252, 8373289, 17894977, 8099994, 90457870, 49882705, 93270084, 66250369, 95581843, 33895336, 49328608, 83150534, 36468541, 10358899, 67451935, 53802686, 32250045, 72495719, 84166196, 98130363, 20836893, 24058273, 64602895, 98648327, 92071990, 69255765, 10649306, 20765474, 62803858, 73168565, 60102965, 30543215, 36845587, 415901, 59177718, 42692881, 66885828, 77694700, 37620363, 41442762, 81774825, 27665211, 92033260, 29401781, 72238278, 83302115, 74441448, 824872, 86022504, 82726008, 32151165, 96318094, 44983451, 56531125, 9886593, 69136837, 45407418, 21533347, 3509435, 10961421, 6221471, 54762643, 79657802, 60430369, 67084351, 13862149, 28734791, 24591705, 65715134, 65338021, 30891921, 8807940, 52613508, 42644903, 62740044, 19101477, 96906410, 32426752, 73109997, 86118021, 56461322, 58751351, 32058615, 74110882, 20645197, 67030811, 56955985, 50702367, 54058788, 91727510, 53842979, 64848072, 10309525, 15064655, 75407347, 65275241, 48260151, 89214616, 78785507, 88251446, 28928175, 62357986, 87710366, 44348328, 22450468, 14626618, 63506281 +3509435, 85117092, 38365584, 12303248, 59329511, 27041967, 8733068, 29516592, 69579137, 93270084, 66667729, 59981773, 26392416, 42237907, 10358899, 38008118, 62936963, 15535065, 57163802, 54868730, 33699435, 4985896, 36139942, 44915235, 20885148, 54263880, 48360198, 66116458, 6111563, 89804152, 34295794, 70367851, 49271185, 31491938, 79322415, 11581181, 26292919, 96709982, 71657078, 56531125, 26664538, 5267545, 84349107, 76825057, 90158785, 94516935, 98462867, 71965942, 43357947, 3880712, 37280276, 10309525, 17365188, 20486294, 80246713, 44177011, 89046466, 55850790, 60581278, 8913721, 16019925, 874791, 92541302, 51464002, 18504197, 17146629, 22113133, 29466406, 72732205, 7687278, 73021291, 3294781, 57658654, 38256849, 33201905, 26744917, 21397057, 168541, 22721500, 61859581, 23432750, 16405341, 26102057, 30139692, 80251430, 17957593, 35092039, 3487592, 36312813, 95581843, 96735716, 20568363, 8651647, 6497830, 15075176, 14626618, 73392814, 15902805, 3773993, 61373987, 55753905, 78589145, 13819358, 86543538, 26275734, 99729357, 13468268, 84002370, 62740044, 36505482, 37739481, 69786756, 44245960, 73109997, 70541760, 23740167, 52204879, 31126490, 92692978, 63015256, 16684829, 50188404, 99021067, 72373496, 39201414, 78300864, 6819644, 56955985, 4064751, 10453030, 32151165, 79821897, 37891451, 30163921, 14220886, 99125126, 29065964, 18833224, 99917599, 82339363, 24733232, 59109400, 60176618, 41092172, 22435353, 4434662, 68041839, 57241521, 29510992, 47361209, 88698958, 76315420, 65271999, 66250369, 28796059, 49328608, 7229550, 78602717, 81805959, 33628349, 66903004, 30366150, 63628376, 9829782, 4199704, 4978543, 17976208, 51590803, 32159704, 67227442, 55770687, 22879907, 44844121, 40984766, 93790285, 67031644, 77377183, 9575954, 44784505, 8129978, 43045786, 20765474, 38341669, 31161687, 17016156, 61263068, 77301523, 16097038, 48088883, 55189057, 82052050, 36685023, 4798568, 98371444, 42692881, 6505939, 89811711, 38022834, 14045383, 36135, 20122224, 79806380, 85571389, 12024238, 824872, 59501487, 61982238, 83269727, 45381876, 15536795, 17385531, 48774913, 46540998, 59318837, 247198, 97057187, 72278539, 2717150, 74743862, 2917920, 9886593, 43506672, 44842615, 10366309, 13470059, 72725103, 48673079, 4095116, 27411561, 97940276, 8634541, 94090109, 86001008, 17894977, 78785507, 88251446, 28550822, 54762643, 62357986, 7104732, 58224549, 33895336, 3183975, 23569917, 9860968, 36933038, 88444207, 91990218, 54014062, 34667286, 27325428, 90061527, 97011160, 1197320, 61712234, 81853704, 32699744, 78884452, 81898046, 22942635, 38061439, 33061250, 70727211, 31733363, 40027975, 5482538, 88130087, 22108665, 98739783, 61728685, 47887470, 569864, 3233569, 72793444, 98653983, 58180653, 80014588, 72614359, 44847298, 36845587, 78909561, 35192533, 30463802, 7955293, 1239555, 16099750, 18411915, 92604458, 42073124, 22766820, 66885828, 12348118, 74724075, 47708850, 79136082, 51426311, 56473732, 34044787, 95957797, 18131876, 43376279, 78766358, 16424199, 32058615, 61859143, 30811010, 90654836, 18699206, 91938191, 86798033, 50668599, 40356877, 37435892, 24953498, 20645197, 74441448, 14731700, 66319530, 70420215, 62762857, 99333375, 50567636, 39986008, 12664567, 53632373, 22129328, 47738185, 34946859, 6871053, 10597197, 99971982, 65851721, 73031054, 669105, 91255408, 6521313, 69697787, 62693428, 36753250, 45075471, 39553046, 15148031, 82327024, 83210802, 8115266, 51588015, 20642888, 22405120, 59371804, 18001617, 97561745, 17539625, 21533347, 75820087, 44348328, 21001913, 33304202, 90013093, 99658235, 8055981, 9623492, 3088684, 68702632, 26776922, 508198, 82427263, 60430369, 98943869, 47792865, 55470718, 75229462, 83150534, 95010552, 61271144, 1788101, 36580610, 47893286, 51472275, 48675329, 70036438, 36189527, 67451935, 59614746, 84166196, 14723410, 69848388, 98130363, 17857111, 7919588, 20681921, 16595436, 65715134, 24058273, 6153406, 53393358, 82178706, 86460488, 30395570, 30787683, 79738755, 65880522, 98648327, 62552524, 47213183, 78561158, 75407347, 86301513, 63059024, 95395112, 62051033, 43933006, 39171895, 60102965, 4204661, 17058722, 42199455, 66271566, 42307449, 76703609, 26063929, 51507425, 84293052, 73617245, 55615885, 2331773, 32590267, 49597667, 37560164, 45428665, 9175338, 48658605, 71300104, 89214616, 70372191, 63967300, 29029316, 7646095, 63152504, 7066775, 99965001, 86118021, 4099191, 7011964, 4508700, 71316369, 68644627, 74357852, 55143724, 68316156, 52060076, 40781449, 28851716, 69355476, 82532312, 1204161, 65047700, 19272365, 65074535, 72777973, 45919976, 18806856, 86242799, 4668450, 67474219, 5822202, 77072625, 9603598, 11757872, 86022504, 17037369, 84187166, 84127901, 57240218, 83155442, 14349098, 46870723, 30771409, 40534591, 54058788, 90090964, 8696647, 67124282, 88904910, 91281584, 526217, 74614639, 9599614, 99549373, 4806458, 85242963, 2891150, 93053405, 49598724, 33336148, 45667668, 45617087, 58208470, 57961282, 69641513, 27185644, 72274002, 16445503, 93359396, 84840549, 49882705, 11923835, 6221471, 48395186, 28787861, 54199160, 79657802, 30998561, 42782652, 79922758, 55602660, 51315460, 75052463, 68816413, 60106217, 89419466, 42947632, 21919959, 36468541, 749283, 65454636, 77272628, 22997281, 34493392, 62430984, 15163258, 89996536, 30764367, 64098930, 6525948, 26734892, 20836893, 70596786, 21070110, 62490109, 8807940, 37224844, 8729146, 76170907, 19486173, 64157906, 61928316, 3235882, 7423788, 68875490, 92554120, 42644903, 33123618, 21289531, 77312810, 23134715, 7300047, 51149804, 34698463, 79880247, 84904436, 61741594, 73124510, 75986488, 39847321, 29635537, 7685448, 59177718, 33249630, 5073754, 77694700, 81172706, 71522968, 71469330, 37501808, 41245325, 67281495, 47090124, 58751351, 77787724, 71333116, 16054533, 7517032, 77898274, 82651278, 37659250, 24314885, 33935899, 87720882, 95726235, 51466049, 60393039, 47298834, 43152977, 98948034, 62496012, 96193415, 97783876, 36780454, 31727379, 45996863, 8128637, 1250437, 52261574, 82779622, 44060493, 5832946, 16380211, 34432810, 67793644, 66832478, 53888755, 321665, 65017137, 44627776, 36812683, 45049308, 43501211, 61960400, 71083565, 54517921, 79191827, 93566986, 68824981, 91240048, 92803766, 40677414, 90310261, 83651211, 35512853, 92787493, 83533741, 14947650, 91727510, 75543508, 33797252, 26493119, 90272749, 33431960, 13173644, 92215320, 13231279, 19939935, 83133790, 17068582, 75128745, 10961421, 91141395, 66611759, 4515343, 62115552, 30694952, 1936762, 95290172, 70782102, 26397786, 67084351, 28734791, 9860195, 53547802, 19898053, 89499542, 69605283, 73222868, 62232211, 30891921, 21673260, 87598888, 75135448, 64602895, 76434144, 68000591, 30569392, 10649306, 75777973, 29932657, 1022677, 89217461, 82979980, 42967683, 76671482, 11543098, 66322959, 30653863, 65081429, 80555751, 19101477, 48892201, 415901, 83948335, 92692283, 29188588, 6793819, 96906410, 99690194, 92998591, 18663507, 2204165, 83747892, 50007421, 25636669, 99297164, 41442762, 39373729, 29834512, 7182642, 59910624, 57359924, 27625735, 4119747, 74110882, 27665211, 66428795, 76960303, 56153345, 68204242, 23194618, 29994197, 20002147, 66663942, 36930650, 74452589, 41092102, 33565483, 37192445, 77413012, 89078848, 36396314, 57803235, 17386996, 17081350, 99515901, 17727650, 29819940, 40152546, 94076128, 86821229, 44550764, 92353856, 48893685, 45306070, 56515456, 89637706, 72358170, 16387575, 77300457, 95251277, 92530431, 19376156, 38645117, 4787945, 17764950, 19891772, 84684495, 3233084, 25842078, 8099994, 51715482, 72019362, 61623915, 58749, 34698428, 63372756, 88653118, 73235980, 45237957, 66045587, 57248122, 6038457, 78549759, 91831487, 81274566, 14326617, 37957788, 59405277, 6157724, 11161731, 53802686, 72495719, 94539824, 15015906, 3633375, 21993752, 38658347, 53648154, 68110330, 38009615, 1569515, 83789391, 59197747, 13348726, 30090481, 45990383, 52613508, 69255765, 92867155, 62803858, 73168565, 82886381, 29959549, 40197395, 46851987, 77620120, 23110625, 72738685, 85116755, 37620363, 93562257, 18783702, 56461322, 8791066, 2607799, 91664334, 88047921, 49641577, 41481685, 81774825, 24915585, 92033260, 44479073, 29401781, 9257405, 73786944, 16567550, 1375023, 99524975, 1431742, 83083131, 60955663, 40781854, 77284619, 20140249, 87710366, 68939068, 67513640, 50702367, 99224392, 94595668, 82897371, 68694897, 9398733, 5111370, 57020481, 65038678, 32274392, 83368048, 44481640, 32161669, 93057697, 69136837, 79942022, 66137019, 80316608, 97281847, 38510840, 90457870, 26998766, 68102437, 28928175, 75153252, 53084256, 44664587, 2208785, 96420429, 9259676, 14093520, 57393458, 13862149, 84406788, 49236559, 15948937, 81677380, 45794415, 64087743, 24619760, 15064655, 24826575, 93933709, 94360702, 48260151, 63506281, 54427233, 91957544, 112651, 27187213, 36808486, 33237508, 96726697, 85711894, 71920426, 14363867, 4069912, 10264691, 30218878, 72357096, 99226875, 77187825, 55669657, 54606384, 24168411, 37166608, 6986898, 40686254, 82726008, 45995325, 97641116, 62452034, 19457589, 45407418, 9058407, 76540481, 45790169, 44473167, 8373289, 81855445, 22450468, 74137926, 85023028, 22200378, 32250045, 68128438, 34236719, 54663246, 24591705, 65338021, 85695762, 99604946, 92071990, 5640302, 39801351, 37675718, 33553959, 37183543, 52293995, 26507214, 61815223, 41380093, 9188443, 51047803, 32426752, 44889423, 55960386, 4091162, 83302115, 56484900, 56424103, 34497327, 89699445, 50806615, 44983451, 94911072, 44846932, 31904591, 70800879, 26139110, 96852321, 26863229, 87160386, 40865610, 53842979, 91802888, 26114953, 64848072, 93515664, 18466635, 70004753, 176257, 99861373, 12571310, 30543215, 6808825, 5970607, 55247455, 45848907, 61897582, 11365791, 35996293, 38494874, 35456853, 12416768, 20867149, 53666583, 97379790, 90004325, 72238278, 64055960, 67030811, 76330843, 7502255, 96318094, 54987042, 61141874, 23848565, 78218845, 65275241, 43322743, 16971929, 54232247, 94711842, 62428472, 92398073 +14349098, 23432750, 9860968, 54987042, 26734892, 20836893, 70596786, 21289531, 13468268, 89811711, 29466406, 56424103, 97783876, 33565483, 86821229, 33431960, 19891772, 17068582, 45237957, 14326617, 4199704, 36845587, 61741594, 43322743, 63152504, 8791066, 88047921, 85711894, 77898274, 72357096, 8128637, 48774913, 94911072, 72358170, 44627776, 43506672, 38645117, 4095116, 94516935, 18001617, 27185644, 87160386, 75153252, 3880712, 79657802, 57248122, 47792865, 37957788, 89996536, 69848388, 20486294, 63059024, 6111563, 89804152, 17058722, 69786756, 27041967, 74357852, 96726697, 82651278, 61859143, 24314885, 68204242, 40356877, 6986898, 99224392, 21397057, 65851721, 22721500, 11365791, 65017137, 95251277, 65038678, 32161669, 40677414, 20642888, 68041839, 45617087, 57241521, 47361209, 88698958, 25842078, 17957593, 83133790, 38510840, 76315420, 26998766, 62357986, 26776922, 78549759, 8651647, 81274566, 95290172, 88444207, 42237907, 84406788, 47893286, 67451935, 24058273, 82178706, 64087743, 93790285, 76170907, 76434144, 3235882, 37675718, 77312810, 82979980, 23134715, 86543538, 30543215, 42307449, 84293052, 78909561, 23110625, 51047803, 63967300, 81172706, 37501808, 41442762, 55143724, 14045383, 59910624, 41481685, 20122224, 28851716, 92692978, 33935899, 14363867, 87720882, 40781854, 20140249, 66663942, 11757872, 79322415, 74452589, 68939068, 67513640, 46870723, 79821897, 247198, 6521313, 44550764, 2917920, 99125126, 88904910, 36753250, 74614639, 61960400, 71083565, 15148031, 24733232, 82327024, 93057697, 23848565, 92803766, 90310261, 59109400, 4787945, 92787493, 16405341, 35996293, 81855445, 75820087, 96852321, 33304202, 68102437, 53084256, 6221471, 28796059, 49328608, 7229550, 75052463, 38494874, 91831487, 68816413, 60106217, 42947632, 70782102, 49236559, 22200378, 10309525, 72495719, 54663246, 14723410, 32699744, 85695762, 53648154, 73222868, 37224844, 12571310, 55753905, 19486173, 17016156, 33553959, 11543098, 48260151, 55615885, 19101477, 37560164, 92692283, 48658605, 12348118, 7646095, 71469330, 70541760, 67281495, 56473732, 68644627, 16971929, 7182642, 57359924, 90004325, 30811010, 72732205, 66428795, 65074535, 63015256, 99021067, 23194618, 72238278, 73786944, 51466049, 31491938, 24953498, 67030811, 30218878, 37166608, 36780454, 84187166, 26292919, 77413012, 40686254, 30771409, 71657078, 40534591, 6871053, 168541, 97641116, 30163921, 91255408, 45306070, 89637706, 44846932, 61141874, 77300457, 72725103, 41092172, 70800879, 85242963, 27411561, 80316608, 45667668, 44473167, 94090109, 13231279, 57961282, 8099994, 54762643, 44664587, 28787861, 79922758, 81805959, 26114953, 89419466, 67084351, 93515664, 28734791, 27325428, 90061527, 77272628, 34493392, 15163258, 32159704, 38008118, 1197320, 35456853, 62232211, 81898046, 89046466, 83789391, 22108665, 39801351, 75777973, 60581278, 94360702, 43933006, 39171895, 60102965, 4204661, 40197395, 51149804, 874791, 80014588, 84904436, 46851987, 30463802, 32590267, 34295794, 75986488, 6793819, 9175338, 7685448, 92604458, 92998591, 22766820, 27187213, 74724075, 44245960, 71522968, 2204165, 36808486, 7066775, 50007421, 56461322, 33699435, 38022834, 77787724, 29834512, 32058615, 4091162, 90654836, 37659250, 4119747, 4069912, 29401781, 60393039, 99524975, 55247455, 74441448, 1431742, 6819644, 96193415, 9603598, 62762857, 38256849, 24168411, 87710366, 26744917, 84127901, 57240218, 96709982, 22129328, 4064751, 44060493, 7502255, 34946859, 10597197, 50806615, 72278539, 74743862, 48893685, 8696647, 9398733, 67124282, 62452034, 91281584, 57020481, 45049308, 43501211, 79191827, 61859581, 9599614, 83210802, 60176618, 35512853, 48673079, 79942022, 97940276, 29516592, 8634541, 58208470, 92215320, 98462867, 3233084, 43357947, 51715482, 11923835, 65271999, 9623492, 95581843, 82427263, 62115552, 23569917, 66903004, 64848072, 6497830, 9860195, 6157724, 92398073, 15948937, 97011160, 81677380, 22997281, 68128438, 59614746, 24591705, 20681921, 53547802, 6153406, 15902805, 12416768, 3773993, 70727211, 31733363, 40027975, 48360198, 13348726, 45990383, 78561158, 98739783, 92554120, 31161687, 73168565, 29959549, 93933709, 7300047, 3233569, 26275734, 66322959, 79880247, 30653863, 415901, 1239555, 49597667, 92541302, 45428665, 29188588, 9188443, 71300104, 112651, 42692881, 5073754, 93562257, 6505939, 79136082, 51464002, 18504197, 4099191, 47090124, 7011964, 58751351, 71316369, 18131876, 16424199, 36135, 7517032, 52060076, 40781449, 69355476, 74110882, 82532312, 91938191, 1204161, 9257405, 16567550, 8733068, 37435892, 64055960, 78300864, 66319530, 77284619, 3294781, 77072625, 57658654, 36930650, 54606384, 33201905, 89699445, 12664567, 36396314, 17386996, 17081350, 52261574, 29819940, 54058788, 37891451, 97057187, 61897582, 66832478, 53888755, 4985896, 69697787, 68694897, 56515456, 36812683, 32274392, 83368048, 5267545, 76825057, 83651211, 76540481, 14947650, 66137019, 26493119, 97561745, 90272749, 80251430, 13173644, 78218845, 84684495, 17894977, 22450468, 84840549, 78785507, 99658235, 63372756, 88653118, 73235980, 36312813, 68702632, 96735716, 508198, 2208785, 55602660, 9259676, 59981773, 21919959, 36468541, 91990218, 30366150, 10358899, 37280276, 9829782, 32250045, 62430984, 65715134, 45794415, 78884452, 22879907, 22942635, 21673260, 80246713, 68110330, 86460488, 33061250, 30395570, 30787683, 70004753, 79738755, 15064655, 65880522, 9575954, 44784505, 86301513, 8129978, 43045786, 68875490, 13819358, 62051033, 82886381, 47887470, 1022677, 98653983, 55189057, 76671482, 52293995, 8913721, 76703609, 99729357, 51507425, 65081429, 80555751, 37739481, 61815223, 83948335, 91957544, 29635537, 16099750, 89214616, 42073124, 12303248, 18663507, 23740167, 51426311, 86118021, 25636669, 17146629, 4508700, 34044787, 33237508, 97379790, 24915585, 18699206, 49271185, 76960303, 44479073, 50668599, 72777973, 85571389, 29994197, 45919976, 47298834, 86242799, 14731700, 98948034, 73021291, 62496012, 5822202, 83269727, 15536795, 83155442, 1250437, 82726008, 76330843, 17727650, 82779622, 46540998, 10453030, 96318094, 73031054, 669105, 2717150, 82897371, 321665, 29065964, 31904591, 82339363, 84349107, 90158785, 51588015, 99549373, 75543508, 59371804, 22435353, 45790169, 4434662, 97281847, 30139692, 29510992, 17539625, 21533347, 44348328, 26863229, 93359396, 72019362, 28550822, 75128745, 48395186, 7104732, 8055981, 66667729, 53842979, 66045587, 4515343, 78602717, 85023028, 14093520, 36933038, 1788101, 26397786, 36580610, 749283, 59405277, 65454636, 11161731, 34236719, 61712234, 15015906, 16595436, 73392814, 38658347, 30891921, 44844121, 62490109, 38009615, 99604946, 87598888, 99861373, 78589145, 30090481, 98648327, 68000591, 77377183, 75407347, 30569392, 20765474, 38341669, 55850790, 33123618, 569864, 37183543, 42967683, 16097038, 72793444, 36685023, 66271566, 34698463, 72614359, 7955293, 48892201, 85116755, 18411915, 59177718, 29029316, 47708850, 70367851, 73109997, 55960386, 99297164, 59329511, 5970607, 43376279, 49641577, 81774825, 27625735, 71920426, 27665211, 86798033, 50188404, 56153345, 39201414, 83302115, 94711842, 43152977, 61982238, 70420215, 11581181, 31727379, 17037369, 39986008, 56955985, 53632373, 32151165, 94076128, 16380211, 67793644, 526217, 54517921, 93566986, 68824981, 44842615, 10366309, 36139942, 19376156, 69136837, 13470059, 45407418, 22405120, 9058407, 26102057, 83533741, 91727510, 26139110, 93053405, 33336148, 8373289, 86001008, 35092039, 72274002, 90457870, 49882705, 66611759, 3088684, 66250369, 54199160, 42782652, 60430369, 6038457, 74137926, 51315460, 98943869, 1936762, 83150534, 61271144, 57393458, 54014062, 70036438, 36189527, 17365188, 94539824, 6525948, 98130363, 17857111, 7919588, 67227442, 53393358, 19898053, 21070110, 38061439, 24619760, 54263880, 44177011, 176257, 64602895, 88130087, 67031644, 62552524, 66116458, 7423788, 5640302, 58180653, 42199455, 85117092, 63506281, 16019925, 84002370, 26507214, 44847298, 62936963, 36505482, 35192533, 38365584, 41380093, 72738685, 39847321, 98371444, 54868730, 99690194, 70372191, 33249630, 66885828, 83747892, 99965001, 39373729, 22113133, 95957797, 71333116, 16054533, 79806380, 7687278, 19272365, 92033260, 12024238, 1375023, 56484900, 20645197, 83083131, 60955663, 67474219, 59501487, 34497327, 77187825, 55669657, 89078848, 50702367, 59318837, 40152546, 44983451, 14220886, 90090964, 92353856, 39553046, 92530431, 44481640, 8115266, 4806458, 2891150, 69641513, 19939935, 16445503, 28928175, 40865610, 61623915, 58749, 10961421, 34698428, 58224549, 93270084, 96420429, 91802888, 30694952, 75229462, 95010552, 13862149, 63628376, 48675329, 4978543, 17976208, 15075176, 20885148, 53802686, 51590803, 30764367, 84166196, 3633375, 65338021, 89499542, 69605283, 21993752, 8807940, 40984766, 61373987, 1569515, 20867149, 75135448, 59197747, 24826575, 64157906, 52613508, 47213183, 92071990, 42644903, 61728685, 29932657, 48088883, 82052050, 26063929, 77620120, 15535065, 57163802, 77694700, 37620363, 41245325, 68316156, 65047700, 16684829, 72373496, 10264691, 20002147, 824872, 45848907, 45381876, 45996863, 17385531, 99515901, 47738185, 5832946, 94595668, 5111370, 62693428, 56531125, 9886593, 26664538, 17764950, 69579137, 21001913, 88251446, 3487592, 3183975, 33628349, 20568363, 51472275, 44915235, 81853704, 55770687, 8729146, 5482538, 10649306, 92867155, 62803858, 53666583, 4798568, 96906410, 32426752, 44889423, 6808825, 2607799, 91664334, 78766358, 95726235, 18806856, 86022504, 99333375, 37192445, 57803235, 45995325, 34432810, 16387575, 18833224, 33797252, 49598724, 3509435, 71965942, 90013093, 91141395, 55470718, 64098930, 61928316, 69255765, 95395112, 89217461, 54427233, 73124510, 18783702, 52204879, 54232247, 4668450, 99226875, 62428472, 99971982, 19457589, 91240048, 33895336, 30998561, 26392416, 18466635, 14626618, 65275241, 73617245, 2331773, 31126490, 41092102, 45075471, 34667286, 77301523, 62740044, 50567636, 99917599, 61263068 +66250369, 54427233, 66885828, 41481685, 62452034, 44627776, 94090109, 81855445, 22450468, 28928175, 11923835, 6221471, 14093520, 36468541, 93515664, 37675718, 33553959, 4798568, 80555751, 18663507, 5970607, 97379790, 99226875, 11757872, 99224392, 321665, 68824981, 99549373, 44473167, 26863229, 57248122, 74137926, 78549759, 13862149, 49236559, 70036438, 35456853, 48360198, 88130087, 65275241, 47887470, 70372191, 90654836, 72777973, 29994197, 40781854, 56955985, 26292919, 7502255, 44550764, 48893685, 9886593, 526217, 32274392, 15148031, 69136837, 40677414, 8115266, 70800879, 59371804, 17894977, 42782652, 82427263, 1936762, 68816413, 21919959, 84406788, 59405277, 11161731, 32699744, 85695762, 68110330, 99604946, 70004753, 31733363, 29959549, 37183543, 16097038, 4204661, 2331773, 54868730, 71522968, 4099191, 56461322, 68644627, 2607799, 91664334, 69355476, 44479073, 64055960, 86242799, 14731700, 72357096, 62428472, 45381876, 17386996, 83155442, 17081350, 52261574, 4064751, 17727650, 29819940, 94076128, 66832478, 669105, 8696647, 45306070, 44842615, 76825057, 9058407, 26139110, 93053405, 58208470, 69641513, 88251446, 28550822, 58224549, 9623492, 66045587, 6038457, 57393458, 51472275, 65454636, 53802686, 62430984, 15015906, 86460488, 83789391, 15064655, 8729146, 30090481, 7423788, 68875490, 95395112, 62803858, 82886381, 1022677, 6111563, 66271566, 11543098, 8913721, 66322959, 46851987, 19101477, 61741594, 29635537, 32426752, 42073124, 33249630, 22766820, 42692881, 7646095, 37501808, 55960386, 56473732, 41442762, 16971929, 90004325, 20122224, 1204161, 56153345, 39201414, 10264691, 55247455, 30218878, 3294781, 824872, 5822202, 66663942, 34497327, 87710366, 17385531, 53888755, 4985896, 90090964, 19457589, 65038678, 31904591, 32161669, 90310261, 17764950, 85242963, 83533741, 35996293, 33797252, 45617087, 8373289, 29510992, 90013093, 34698428, 79657802, 2208785, 4515343, 55602660, 33628349, 36933038, 26392416, 42237907, 64848072, 749283, 4199704, 27325428, 17365188, 81677380, 84166196, 14723410, 1197320, 20681921, 16595436, 20486294, 12416768, 76434144, 64157906, 67031644, 47213183, 75407347, 30569392, 98739783, 13819358, 20765474, 55850790, 569864, 86543538, 26275734, 72793444, 55189057, 30543215, 42307449, 84293052, 30653863, 415901, 91957544, 32590267, 9188443, 99690194, 112651, 29029316, 47708850, 37620363, 36808486, 83747892, 18504197, 18783702, 50007421, 17146629, 4508700, 43376279, 74357852, 88047921, 36135, 81774825, 77898274, 82651278, 27625735, 54232247, 24314885, 86798033, 14363867, 95726235, 8733068, 99524975, 43152977, 78300864, 62762857, 41092102, 89078848, 1250437, 30771409, 32151165, 45995325, 73031054, 54987042, 6521313, 82897371, 9398733, 89637706, 65017137, 72358170, 91281584, 45049308, 77300457, 74614639, 99917599, 24733232, 10366309, 23848565, 4806458, 4095116, 97940276, 80316608, 33431960, 78218845, 98462867, 19939935, 21001913, 17068582, 43357947, 72019362, 49882705, 40865610, 53084256, 58749, 63372756, 48395186, 53842979, 60430369, 7229550, 51315460, 38494874, 85023028, 47792865, 48675329, 28734791, 67451935, 15075176, 6157724, 92398073, 20885148, 10309525, 59614746, 65715134, 45794415, 89499542, 69605283, 82178706, 15902805, 8807940, 38061439, 24619760, 30787683, 40027975, 76170907, 12571310, 19486173, 98648327, 77377183, 61928316, 62552524, 42644903, 62051033, 60581278, 61263068, 77301523, 82979980, 60102965, 3233569, 53666583, 82052050, 76671482, 48260151, 26063929, 51507425, 79880247, 84002370, 26507214, 36505482, 23110625, 6793819, 9175338, 85116755, 89214616, 74724075, 44889423, 67281495, 99965001, 7011964, 89811711, 71316369, 77787724, 29834512, 78766358, 57359924, 4091162, 63015256, 4069912, 87720882, 72373496, 18806856, 83302115, 1375023, 20645197, 60955663, 66319530, 67474219, 77284619, 59501487, 61982238, 56424103, 97783876, 54606384, 24168411, 83269727, 31727379, 84187166, 68939068, 39986008, 45996863, 8128637, 36396314, 57240218, 50702367, 40686254, 22129328, 47738185, 44060493, 34946859, 50806615, 97641116, 44983451, 72278539, 2717150, 11365791, 68694897, 56515456, 62693428, 56531125, 44846932, 16387575, 95251277, 18833224, 79191827, 93566986, 82339363, 19376156, 92803766, 60176618, 13470059, 83651211, 4787945, 20642888, 22405120, 48673079, 26102057, 27411561, 79942022, 2891150, 22435353, 49598724, 68041839, 97281847, 30139692, 13173644, 57961282, 3509435, 17957593, 83133790, 33304202, 51715482, 68102437, 75128745, 10961421, 73235980, 54199160, 30998561, 96420429, 3183975, 78602717, 9259676, 8651647, 81274566, 75229462, 1788101, 30366150, 9829782, 4978543, 9860195, 18466635, 32250045, 34236719, 98130363, 7919588, 3633375, 65338021, 19898053, 78884452, 55770687, 21993752, 62232211, 38009615, 3773993, 20867149, 70727211, 93790285, 24826575, 13348726, 3235882, 52613508, 92071990, 8129978, 43045786, 5640302, 75777973, 73168565, 33123618, 21289531, 23134715, 94360702, 48088883, 89804152, 98653983, 42199455, 40197395, 63506281, 65081429, 44847298, 35192533, 61815223, 48892201, 38365584, 92692283, 18411915, 7685448, 71300104, 92604458, 43322743, 2204165, 79136082, 86118021, 58751351, 95957797, 16054533, 59910624, 16424199, 32058615, 24915585, 72732205, 71920426, 74110882, 19272365, 92033260, 16684829, 68204242, 29401781, 85571389, 73786944, 60393039, 74441448, 83083131, 6819644, 98948034, 96193415, 57658654, 36930650, 74452589, 77187825, 38256849, 89699445, 17037369, 12664567, 82779622, 46540998, 79821897, 37891451, 65851721, 168541, 86821229, 91255408, 22721500, 92353856, 57020481, 36753250, 71083565, 39553046, 26664538, 61859581, 9599614, 93057697, 91240048, 83210802, 38645117, 51588015, 94516935, 66137019, 91727510, 33336148, 45667668, 18001617, 97561745, 69579137, 75820087, 86001008, 90457870, 87160386, 78785507, 61623915, 91141395, 66611759, 54762643, 44664587, 96735716, 98943869, 91831487, 89419466, 55470718, 14326617, 88444207, 67084351, 54014062, 47893286, 15948937, 97011160, 89996536, 30764367, 54663246, 24591705, 61712234, 81853704, 67227442, 24058273, 70596786, 73392814, 38658347, 44844121, 21673260, 80246713, 33061250, 54263880, 1569515, 79738755, 65880522, 68000591, 22108665, 45990383, 44784505, 69255765, 10649306, 29932657, 42967683, 58180653, 51149804, 76703609, 80014588, 84904436, 36845587, 62936963, 78909561, 37739481, 83948335, 73124510, 41380093, 16099750, 59177718, 63967300, 5073754, 77694700, 81172706, 93562257, 6808825, 33699435, 8791066, 39373729, 33237508, 59329511, 38022834, 71333116, 18131876, 96726697, 7182642, 85711894, 68316156, 52060076, 40781449, 30811010, 28851716, 79806380, 33935899, 50188404, 72238278, 40356877, 45919976, 16567550, 94711842, 73021291, 20140249, 77072625, 37166608, 45848907, 53632373, 96709982, 82726008, 46870723, 40534591, 96318094, 40152546, 16380211, 94595668, 14220886, 69697787, 94911072, 99125126, 29065964, 61960400, 83368048, 44481640, 59109400, 35512853, 14947650, 45790169, 4434662, 8634541, 92215320, 96852321, 25842078, 38510840, 35092039, 93359396, 75153252, 62357986, 7104732, 8055981, 3088684, 66667729, 36312813, 95581843, 28787861, 508198, 91802888, 26114953, 95290172, 91990218, 36189527, 6497830, 37957788, 90061527, 51590803, 72495719, 77272628, 14626618, 68128438, 34493392, 6525948, 17857111, 53547802, 6153406, 21070110, 53648154, 30891921, 62490109, 40984766, 64087743, 44177011, 89046466, 61373987, 99861373, 9575954, 78561158, 66116458, 63059024, 17016156, 93933709, 34698463, 99729357, 62740044, 7955293, 92541302, 34295794, 72738685, 70367851, 63152504, 70541760, 34044787, 27041967, 55143724, 82532312, 27665211, 7687278, 65047700, 65074535, 37435892, 4668450, 20002147, 67030811, 70420215, 9603598, 86022504, 11581181, 50567636, 67513640, 37192445, 77413012, 57803235, 6986898, 99515901, 76330843, 5832946, 71657078, 54058788, 61897582, 74743862, 43501211, 54517921, 45075471, 92530431, 82327024, 5267545, 84349107, 45407418, 92787493, 41092172, 16405341, 75543508, 26493119, 17539625, 44348328, 3233084, 71965942, 27185644, 72274002, 8099994, 45237957, 3880712, 33895336, 26776922, 49328608, 30694952, 9860968, 59981773, 83150534, 61271144, 26397786, 22200378, 44915235, 17976208, 22997281, 94539824, 26734892, 20836893, 176257, 55753905, 64602895, 78589145, 77312810, 89217461, 43933006, 52293995, 85117092, 13468268, 874791, 55615885, 72614359, 1239555, 49597667, 77620120, 75986488, 29188588, 98371444, 48658605, 96906410, 69786756, 27187213, 44245960, 73109997, 41245325, 23740167, 25636669, 99297164, 14045383, 49641577, 7517032, 61859143, 4119747, 92692978, 91938191, 49271185, 66428795, 76960303, 99021067, 50668599, 23194618, 9257405, 51466049, 12024238, 31491938, 56484900, 36780454, 99333375, 59318837, 6871053, 99971982, 67124282, 88904910, 61141874, 36139942, 90158785, 29516592, 57241521, 88698958, 16445503, 65271999, 88653118, 93270084, 79922758, 62115552, 75052463, 66903004, 36580610, 10358899, 34667286, 15163258, 32159704, 38008118, 53393358, 73222868, 22879907, 81898046, 87598888, 37224844, 75135448, 38341669, 92867155, 61728685, 7300047, 17058722, 15535065, 92998591, 71469330, 6505939, 7066775, 51426311, 18699206, 84127901, 14349098, 21397057, 10453030, 34432810, 67793644, 30163921, 5111370, 76540481, 80251430, 19891772, 47361209, 84684495, 76315420, 26998766, 99658235, 68702632, 28796059, 23569917, 20568363, 60106217, 70782102, 63628376, 69848388, 22942635, 59197747, 5482538, 39801351, 31161687, 36685023, 30463802, 51047803, 12303248, 51464002, 47090124, 22113133, 52204879, 37659250, 79322415, 26744917, 48774913, 247198, 10597197, 2917920, 43506672, 72725103, 21533347, 13231279, 84840549, 81805959, 95010552, 37280276, 30395570, 92554120, 39171895, 16019925, 39847321, 12348118, 29466406, 31126490, 47298834, 24953498, 1431742, 55669657, 33565483, 97057187, 36812683, 23432750, 90272749, 3487592, 42947632, 86301513, 73617245, 37560164, 57163802, 62496012, 15536795, 64098930, 45428665, 33201905 +82052050, 72725103, 85242963, 7104732, 30694952, 24314885, 65047700, 4806458, 70782102, 1569515, 31733363, 44784505, 29959549, 49597667, 26744917, 73031054, 90158785, 97281847, 32699744, 61263068, 62936963, 23740167, 71316369, 95957797, 16424199, 65074535, 44479073, 71657078, 10453030, 247198, 34432810, 74743862, 45075471, 59109400, 4434662, 26493119, 98462867, 88698958, 71965942, 78785507, 68102437, 54762643, 89419466, 88444207, 38061439, 48360198, 77377183, 95395112, 17016156, 33553959, 51149804, 26063929, 16019925, 4798568, 35192533, 1239555, 6793819, 57163802, 7685448, 92998591, 43322743, 66885828, 93562257, 67281495, 86118021, 4119747, 69355476, 92033260, 45919976, 4668450, 98948034, 99333375, 50567636, 17386996, 48774913, 32151165, 96318094, 59318837, 8696647, 68694897, 65038678, 18833224, 83210802, 49598724, 90272749, 26863229, 25842078, 93359396, 49882705, 8055981, 53842979, 508198, 85023028, 26397786, 84406788, 17365188, 89996536, 38008118, 70596786, 12416768, 54263880, 87598888, 29932657, 60581278, 33123618, 86543538, 52293995, 2331773, 84002370, 36505482, 72738685, 16099750, 33249630, 77694700, 44889423, 18663507, 34044787, 16971929, 78766358, 20122224, 54232247, 27665211, 18699206, 91938191, 7687278, 1204161, 72373496, 8733068, 73021291, 59501487, 5822202, 61982238, 62762857, 74452589, 37166608, 68939068, 15536795, 89078848, 5832946, 79821897, 44983451, 4985896, 11365791, 9886593, 29065964, 91281584, 57020481, 82339363, 99549373, 14947650, 2891150, 75543508, 80316608, 45790169, 27185644, 90013093, 26998766, 34698428, 9623492, 93270084, 66045587, 79922758, 91831487, 59981773, 61271144, 37280276, 9829782, 53802686, 27325428, 61712234, 67227442, 55770687, 69605283, 30891921, 62490109, 15064655, 59197747, 19486173, 98648327, 62552524, 45990383, 47213183, 30569392, 1022677, 569864, 89217461, 42199455, 36685023, 11543098, 30653863, 80555751, 30463802, 38365584, 29188588, 44245960, 51426311, 4099191, 4508700, 29834512, 74357852, 36135, 77898274, 61859143, 92692978, 33935899, 40356877, 94711842, 83083131, 66319530, 67030811, 20140249, 97783876, 77187825, 83269727, 33565483, 37192445, 45381876, 8128637, 45995325, 37891451, 99971982, 30163921, 54987042, 44550764, 2917920, 9398733, 56515456, 62693428, 88904910, 19457589, 74614639, 39553046, 79191827, 44481640, 19376156, 51588015, 30139692, 33431960, 80251430, 13173644, 96852321, 22450468, 65271999, 73235980, 3880712, 4515343, 91802888, 81805959, 78549759, 14326617, 1788101, 10358899, 64848072, 36189527, 17976208, 15948937, 32250045, 51590803, 64098930, 69848388, 15015906, 20681921, 65715134, 81898046, 40984766, 3773993, 44177011, 79738755, 65880522, 5482538, 13348726, 22108665, 69255765, 98739783, 92554120, 38341669, 92867155, 61728685, 77301523, 82979980, 23134715, 94360702, 43933006, 4204661, 98653983, 76703609, 61741594, 83948335, 73124510, 29635537, 18411915, 51047803, 71522968, 37620363, 63152504, 18504197, 41245325, 18783702, 56473732, 99297164, 52204879, 31126490, 88047921, 14045383, 32058615, 52060076, 24915585, 40781449, 27625735, 82532312, 66428795, 56153345, 68204242, 10264691, 95726235, 47298834, 37435892, 70420215, 57658654, 36930650, 84187166, 67513640, 39986008, 26292919, 6986898, 96709982, 82726008, 76330843, 30771409, 10597197, 94595668, 61897582, 2717150, 6521313, 90090964, 45306070, 44846932, 36812683, 16387575, 526217, 77300457, 54517921, 9599614, 93057697, 10366309, 8115266, 13470059, 45407418, 9058407, 70800879, 83533741, 33336148, 8373289, 69579137, 3233084, 76315420, 16445503, 90457870, 72019362, 99658235, 61623915, 10961421, 48395186, 3088684, 66667729, 68702632, 54199160, 26776922, 30998561, 23569917, 51315460, 75052463, 38494874, 42947632, 47792865, 37957788, 15075176, 9860195, 65454636, 92398073, 20885148, 10309525, 18466635, 14626618, 15163258, 30764367, 84166196, 98130363, 24591705, 16595436, 45794415, 53393358, 73392814, 78884452, 89499542, 22942635, 80246713, 89046466, 30787683, 20867149, 70727211, 93790285, 37224844, 8729146, 75135448, 76434144, 61928316, 92071990, 8129978, 68875490, 10649306, 39801351, 62051033, 62803858, 82886381, 37675718, 37183543, 39171895, 72793444, 17058722, 8913721, 63506281, 66322959, 84293052, 37739481, 39847321, 9175338, 48658605, 71300104, 89214616, 96906410, 12303248, 74724075, 73109997, 41442762, 58751351, 71333116, 91664334, 85711894, 59910624, 82651278, 90004325, 72732205, 76960303, 14363867, 99021067, 23194618, 83302115, 12024238, 99524975, 43152977, 78300864, 30218878, 824872, 62496012, 66663942, 96193415, 34497327, 79322415, 54606384, 83155442, 1250437, 34946859, 6871053, 97641116, 82897371, 92353856, 5111370, 94911072, 65017137, 99125126, 61141874, 83368048, 92530431, 68824981, 36139942, 69136837, 60176618, 76825057, 23432750, 4787945, 41092172, 4095116, 79942022, 97940276, 44473167, 57241521, 19891772, 29510992, 13231279, 3509435, 84684495, 17957593, 35092039, 43357947, 8099994, 28550822, 75153252, 53084256, 66611759, 36312813, 28787861, 33895336, 49328608, 96735716, 82427263, 55602660, 6038457, 26114953, 81274566, 60106217, 36580610, 47893286, 51472275, 6497830, 67451935, 72495719, 77272628, 32159704, 65338021, 6153406, 19898053, 62232211, 68110330, 99604946, 24619760, 61373987, 83789391, 12571310, 64602895, 88130087, 67031644, 9575954, 78561158, 43045786, 13819358, 77312810, 26275734, 58180653, 40197395, 30543215, 80014588, 55615885, 46851987, 26507214, 62740044, 41380093, 23110625, 75986488, 45428665, 9188443, 98371444, 92604458, 59177718, 32426752, 112651, 12348118, 29029316, 70367851, 7646095, 6808825, 55960386, 47090124, 8791066, 27041967, 43376279, 7182642, 16054533, 81774825, 7517032, 57359924, 4091162, 90654836, 28851716, 79806380, 4069912, 16684829, 73786944, 51466049, 60393039, 55247455, 64055960, 9603598, 55669657, 33201905, 62428472, 45848907, 12664567, 57803235, 50702367, 47738185, 82779622, 21397057, 7502255, 54058788, 16380211, 65851721, 67793644, 50806615, 66832478, 669105, 86821229, 91255408, 22721500, 48893685, 67124282, 72358170, 36753250, 61960400, 32274392, 31904591, 24733232, 32161669, 61859581, 23848565, 91240048, 91727510, 59371804, 45667668, 8634541, 94090109, 81855445, 57961282, 44348328, 69641513, 86001008, 19939935, 83133790, 21001913, 33304202, 72274002, 17068582, 87160386, 40865610, 62357986, 58224549, 44664587, 28796059, 42782652, 60430369, 74137926, 33628349, 9259676, 9860968, 14093520, 83150534, 95010552, 36933038, 91990218, 30366150, 49236559, 28734791, 34667286, 81677380, 22997281, 59614746, 6525948, 26734892, 7919588, 3633375, 21070110, 44844121, 86460488, 33061250, 70004753, 176257, 75407347, 42644903, 73168565, 47887470, 93933709, 16097038, 55189057, 76671482, 48260151, 54427233, 79880247, 44847298, 36845587, 78909561, 415901, 91957544, 92692283, 92541302, 34295794, 54868730, 42073124, 63967300, 22766820, 47708850, 6505939, 33699435, 17146629, 39373729, 5970607, 41481685, 71920426, 19272365, 63015256, 87720882, 50668599, 85571389, 29994197, 16567550, 18806856, 1375023, 56484900, 74441448, 1431742, 60955663, 40781854, 77284619, 3294781, 72357096, 99226875, 36780454, 11581181, 56955985, 77413012, 36396314, 84127901, 99515901, 52261574, 22129328, 46870723, 99224392, 4064751, 168541, 321665, 89637706, 62452034, 45049308, 95251277, 43506672, 99917599, 82327024, 38645117, 35512853, 16405341, 26102057, 94516935, 26139110, 33797252, 45617087, 47361209, 17539625, 75128745, 88653118, 45237957, 66250369, 2208785, 57248122, 62115552, 3183975, 20568363, 8651647, 66903004, 26392416, 67084351, 63628376, 48675329, 44915235, 93515664, 6157724, 11161731, 90061527, 97011160, 34236719, 14723410, 85695762, 21993752, 35456853, 73222868, 21673260, 15902805, 30395570, 99861373, 76170907, 78589145, 30090481, 52613508, 86301513, 75777973, 42967683, 6111563, 7300047, 60102965, 48088883, 66271566, 85117092, 99729357, 874791, 73617245, 61815223, 7955293, 48892201, 85116755, 81172706, 36808486, 51464002, 50007421, 7011964, 68644627, 59329511, 18131876, 37659250, 74110882, 29401781, 72777973, 9257405, 14731700, 41092102, 87710366, 31727379, 53632373, 57240218, 17385531, 44060493, 46540998, 94076128, 97057187, 53888755, 69697787, 44627776, 5267545, 40677414, 48673079, 22435353, 93053405, 97561745, 58208470, 92215320, 75820087, 38510840, 3487592, 91141395, 79657802, 78602717, 21919959, 42237907, 749283, 68128438, 62430984, 94539824, 54663246, 1197320, 38658347, 53648154, 82178706, 8807940, 55753905, 24826575, 68000591, 63059024, 7423788, 20765474, 65275241, 55850790, 21289531, 53666583, 42307449, 51507425, 84904436, 65081429, 19101477, 37560164, 77620120, 15535065, 99690194, 69786756, 5073754, 70541760, 7066775, 99965001, 25636669, 89811711, 22113133, 77787724, 96726697, 97379790, 49641577, 68316156, 30811010, 49271185, 50188404, 24953498, 20645197, 56424103, 24168411, 17037369, 45996863, 40686254, 14349098, 29819940, 72278539, 56531125, 71083565, 15148031, 84349107, 92803766, 90310261, 92787493, 17764950, 76540481, 66137019, 68041839, 29516592, 84840549, 63372756, 98943869, 1936762, 55470718, 36468541, 57393458, 70036438, 4978543, 17857111, 20836893, 53547802, 20486294, 22879907, 38009615, 40027975, 3235882, 66116458, 31161687, 3233569, 89804152, 34698463, 71469330, 37501808, 38022834, 2607799, 72238278, 39201414, 31491938, 20002147, 6819644, 77072625, 86022504, 89699445, 17727650, 40152546, 14220886, 43501211, 93566986, 44842615, 22405120, 35996293, 18001617, 78218845, 51715482, 88251446, 28928175, 11923835, 6221471, 95581843, 7229550, 68816413, 75229462, 95290172, 54014062, 13862149, 4199704, 59405277, 34493392, 81853704, 24058273, 5640302, 13468268, 72614359, 42692881, 27187213, 2204165, 83747892, 86798033, 86242799, 67474219, 17081350, 40534591, 26664538, 83651211, 20642888, 27411561, 22200378, 64087743, 64157906, 79136082, 56461322, 11757872, 38256849, 21533347, 58749, 96420429, 32590267, 70372191, 33237508, 29466406, 55143724, 17894977 +24591705, 45794415, 37183543, 52060076, 17976208, 53666583, 22766820, 66611759, 62115552, 15064655, 8913721, 84904436, 2331773, 29635537, 42073124, 23740167, 83302115, 47298834, 96318094, 94595668, 2917920, 26102057, 26863229, 86001008, 17068582, 78785507, 48395186, 30694952, 70782102, 15948937, 62232211, 52613508, 55189057, 52293995, 49597667, 70367851, 94711842, 67030811, 61982238, 79322415, 67793644, 168541, 72278539, 48893685, 56515456, 82339363, 85242963, 35996293, 28787861, 33628349, 51315460, 68816413, 47792865, 14326617, 36580610, 92398073, 15163258, 1197320, 7919588, 22942635, 92071990, 5640302, 61728685, 82886381, 16097038, 76703609, 80555751, 19101477, 18411915, 7685448, 29029316, 18783702, 14045383, 90004325, 30811010, 54232247, 4069912, 10264691, 45996863, 89078848, 17385531, 14349098, 48774913, 10453030, 61897582, 91255408, 11365791, 92353856, 8696647, 45306070, 44842615, 23848565, 4787945, 16405341, 49598724, 45617087, 33431960, 80251430, 81855445, 87160386, 72019362, 61623915, 3487592, 65271999, 66045587, 26397786, 22200378, 72495719, 14626618, 84166196, 61712234, 20681921, 55770687, 62490109, 40027975, 48360198, 67031644, 13348726, 30090481, 68875490, 569864, 82979980, 93933709, 92541302, 83747892, 55960386, 47090124, 4508700, 68644627, 36135, 77898274, 90654836, 82532312, 92033260, 99021067, 23194618, 31491938, 64055960, 70420215, 77072625, 77187825, 33201905, 96709982, 1250437, 99515901, 45995325, 50806615, 97641116, 82897371, 43501211, 38645117, 13470059, 23432750, 4806458, 93053405, 13231279, 11923835, 7104732, 3088684, 33895336, 60430369, 91802888, 81805959, 26114953, 85023028, 81274566, 95290172, 88444207, 57393458, 63628376, 70036438, 34667286, 90061527, 38008118, 98130363, 20836893, 65715134, 89499542, 24619760, 76170907, 59197747, 5482538, 73168565, 29932657, 17016156, 77312810, 23134715, 94360702, 39171895, 11543098, 63506281, 874791, 44847298, 48892201, 1239555, 23110625, 29188588, 69786756, 112651, 66885828, 74724075, 7646095, 93562257, 73109997, 36808486, 79136082, 50007421, 56473732, 58751351, 89811711, 22113133, 52204879, 91664334, 85711894, 16424199, 49641577, 61859143, 57359924, 19272365, 16684829, 50668599, 51466049, 1375023, 43152977, 20645197, 5822202, 34497327, 62762857, 97783876, 86022504, 24168411, 11581181, 31727379, 33565483, 36396314, 53632373, 40686254, 17727650, 247198, 34432810, 73031054, 22721500, 2717150, 5111370, 61141874, 19457589, 71083565, 93566986, 93057697, 83210802, 41092172, 48673079, 14947650, 33336148, 44473167, 8373289, 29510992, 47361209, 17539625, 58208470, 75820087, 3509435, 88698958, 27185644, 43357947, 22450468, 93359396, 68102437, 73235980, 45237957, 66250369, 30998561, 508198, 96420429, 74137926, 9259676, 66903004, 75229462, 9829782, 47893286, 67451935, 37957788, 51590803, 17365188, 97011160, 81677380, 54663246, 64098930, 32699744, 65338021, 19898053, 73392814, 38658347, 81898046, 21673260, 40984766, 38009615, 99604946, 33061250, 44177011, 89046466, 1569515, 176257, 79738755, 12571310, 19486173, 24826575, 64602895, 76434144, 64157906, 98648327, 68000591, 77377183, 61928316, 47213183, 78561158, 86301513, 7423788, 92554120, 38341669, 62051033, 92867155, 1022677, 61263068, 86543538, 4204661, 72793444, 51149804, 26063929, 26507214, 36845587, 30463802, 91957544, 73124510, 37560164, 34295794, 15535065, 45428665, 6793819, 48658605, 89214616, 96906410, 54868730, 70372191, 92998591, 43322743, 77694700, 12348118, 44245960, 47708850, 6808825, 7011964, 99297164, 71333116, 16971929, 27041967, 78766358, 59910624, 7517032, 4119747, 74110882, 91938191, 1204161, 76960303, 50188404, 68204242, 72238278, 73786944, 1431742, 60955663, 14731700, 98948034, 30218878, 9603598, 62428472, 26744917, 50567636, 56955985, 37192445, 26292919, 77413012, 84127901, 83155442, 52261574, 46870723, 71657078, 94076128, 30163921, 74743862, 68694897, 9398733, 67124282, 99125126, 88904910, 91281584, 526217, 43506672, 39553046, 92530431, 15148031, 26664538, 69136837, 59109400, 83651211, 35512853, 9058407, 76540481, 70800879, 27411561, 79942022, 97940276, 26139110, 75543508, 4434662, 45667668, 97281847, 29516592, 19891772, 92215320, 69641513, 98462867, 96852321, 71965942, 38510840, 90013093, 17894977, 35092039, 90457870, 88251446, 99658235, 10961421, 54762643, 62357986, 8055981, 3880712, 53842979, 57248122, 55602660, 78602717, 20568363, 75052463, 38494874, 1936762, 14093520, 83150534, 61271144, 36933038, 26392416, 48675329, 28734791, 6157724, 10309525, 32250045, 30764367, 26734892, 17857111, 70596786, 78884452, 21070110, 20486294, 35456853, 68110330, 38061439, 30395570, 70004753, 93790285, 75135448, 55753905, 30569392, 39801351, 65275241, 29959549, 37675718, 33553959, 42967683, 43933006, 89804152, 58180653, 40197395, 66271566, 34698463, 16019925, 13468268, 55615885, 65081429, 84002370, 415901, 38365584, 77620120, 57163802, 85116755, 98371444, 42692881, 81172706, 37620363, 37501808, 63152504, 67281495, 17146629, 39373729, 59329511, 5970607, 38022834, 55143724, 97379790, 41481685, 68316156, 33935899, 49271185, 7687278, 86798033, 66428795, 63015256, 9257405, 45919976, 8733068, 12024238, 37435892, 86242799, 59501487, 11757872, 74452589, 54606384, 89699445, 84187166, 12664567, 57240218, 57803235, 6986898, 82779622, 21397057, 29819940, 30771409, 54058788, 99971982, 65851721, 86821229, 69697787, 89637706, 72358170, 44627776, 36812683, 36753250, 45049308, 77300457, 95251277, 65038678, 32274392, 45075471, 31904591, 32161669, 82327024, 36139942, 92803766, 40677414, 72725103, 45407418, 4095116, 33797252, 26493119, 90272749, 8634541, 57241521, 94090109, 84684495, 21001913, 40865610, 28550822, 75153252, 53084256, 44664587, 36312813, 95581843, 54199160, 79657802, 96735716, 42782652, 2208785, 4515343, 78549759, 59981773, 21919959, 67084351, 91990218, 13862149, 36189527, 6497830, 44915235, 4978543, 9860195, 65454636, 53802686, 22997281, 62430984, 89996536, 81853704, 16595436, 6153406, 53393358, 85695762, 21993752, 53648154, 22879907, 30891921, 44844121, 15902805, 86460488, 3773993, 54263880, 70727211, 31733363, 37224844, 9575954, 69255765, 75407347, 62803858, 33123618, 47887470, 6111563, 7300047, 82052050, 99729357, 48260151, 54427233, 80014588, 84293052, 73617245, 46851987, 62936963, 61741594, 92692283, 39847321, 71300104, 32426752, 41245325, 86118021, 4099191, 41442762, 34044787, 29466406, 43376279, 96726697, 31126490, 16054533, 81774825, 4091162, 20122224, 40781449, 28851716, 71920426, 24314885, 44479073, 29401781, 72777973, 29994197, 60393039, 24953498, 55247455, 67474219, 77284619, 73021291, 824872, 99226875, 56424103, 57658654, 36930650, 41092102, 37166608, 17037369, 67513640, 45381876, 8128637, 17386996, 17081350, 82726008, 99224392, 4064751, 5832946, 79821897, 59318837, 40152546, 16380211, 10597197, 4985896, 14220886, 6521313, 94911072, 57020481, 16387575, 18833224, 74614639, 61960400, 54517921, 44481640, 61859581, 9599614, 76825057, 90158785, 59371804, 80316608, 45790169, 18001617, 30139692, 78218845, 57961282, 19939935, 25842078, 17957593, 33304202, 84840549, 49882705, 28928175, 63372756, 6221471, 66667729, 26776922, 79922758, 23569917, 98943869, 60106217, 89419466, 95010552, 42237907, 49236559, 64848072, 59405277, 11161731, 77272628, 14723410, 69848388, 53547802, 24058273, 73222868, 82178706, 8807940, 30787683, 61373987, 99861373, 88130087, 3235882, 66116458, 20765474, 42644903, 75777973, 21289531, 77301523, 60102965, 36685023, 42307449, 66322959, 51507425, 79880247, 62740044, 72614359, 78909561, 7955293, 41380093, 72738685, 9188443, 12303248, 5073754, 71469330, 6505939, 7066775, 99965001, 33699435, 71316369, 95957797, 77787724, 32058615, 72732205, 37659250, 92692978, 27665211, 18699206, 65047700, 72373496, 78300864, 40781854, 66319530, 72357096, 55669657, 87710366, 68939068, 39986008, 15536795, 76330843, 22129328, 46540998, 65017137, 9886593, 79191827, 68824981, 5267545, 19376156, 84349107, 91240048, 90310261, 8115266, 17764950, 20642888, 22405120, 94516935, 97561745, 13173644, 69579137, 72274002, 8099994, 51715482, 26998766, 58749, 91141395, 34698428, 88653118, 58224549, 49328608, 82427263, 3183975, 8651647, 42947632, 55470718, 54014062, 10358899, 749283, 20885148, 18466635, 27325428, 68128438, 94539824, 59614746, 67227442, 3633375, 69605283, 8729146, 22108665, 62552524, 45990383, 43045786, 95395112, 55850790, 89217461, 48088883, 26275734, 17058722, 30653863, 37739481, 61815223, 9175338, 51047803, 99690194, 18663507, 2204165, 25636669, 18131876, 82651278, 69355476, 65074535, 56153345, 87720882, 39201414, 16567550, 95726235, 99524975, 56484900, 83083131, 20002147, 20140249, 62496012, 66663942, 38256849, 50702367, 44060493, 34946859, 97057187, 66832478, 53888755, 44550764, 56531125, 321665, 44846932, 83368048, 10366309, 99549373, 91727510, 2891150, 22435353, 21533347, 3233084, 83133790, 75128745, 9623492, 6038457, 91831487, 36468541, 30366150, 84406788, 51472275, 93515664, 34493392, 34236719, 6525948, 12416768, 78589145, 63059024, 10649306, 98739783, 13819358, 3233569, 30543215, 4798568, 35192533, 83948335, 32590267, 75986488, 92604458, 71522968, 51464002, 18504197, 51426311, 56461322, 7182642, 24915585, 79806380, 85571389, 18806856, 6819644, 3294781, 96193415, 83269727, 99333375, 47738185, 40534591, 32151165, 6871053, 54987042, 44983451, 90090964, 62693428, 62452034, 29065964, 51588015, 83533741, 68041839, 44348328, 68702632, 7229550, 37280276, 4199704, 15075176, 15015906, 80246713, 64087743, 83789391, 44784505, 8129978, 31161687, 42199455, 85117092, 36505482, 63967300, 70541760, 33237508, 29834512, 2607799, 27625735, 40356877, 74441448, 36780454, 45848907, 7502255, 37891451, 669105, 99917599, 60176618, 92787493, 16445503, 93270084, 28796059, 9860968, 32159704, 20867149, 87598888, 60581278, 98653983, 76671482, 33249630, 8791066, 88047921, 14363867, 24733232, 66137019, 65880522, 16099750, 59177718, 44889423, 74357852, 76315420, 1788101, 4668450, 27187213 +23432750, 84187166, 68939068, 67513640, 30139692, 21919959, 48360198, 61928316, 48260151, 75986488, 71316369, 59910624, 54987042, 82897371, 94911072, 8099994, 75153252, 81274566, 89996536, 77377183, 82532312, 63015256, 11581181, 77413012, 17386996, 4787945, 63372756, 53842979, 68816413, 95010552, 67451935, 37957788, 15948937, 84166196, 53547802, 65880522, 68875490, 92554120, 8913721, 84293052, 55615885, 92692283, 85116755, 41245325, 70541760, 68644627, 16971929, 28851716, 24314885, 91938191, 7687278, 92033260, 85571389, 8733068, 62762857, 54606384, 82726008, 66832478, 92353856, 99125126, 88904910, 91240048, 22405120, 70800879, 69579137, 51715482, 93359396, 66611759, 62357986, 26776922, 23569917, 75052463, 93515664, 10309525, 24591705, 67227442, 32699744, 176257, 55753905, 76434144, 64157906, 68000591, 63059024, 73168565, 569864, 37183543, 93933709, 94360702, 39171895, 63506281, 72614359, 415901, 23110625, 34295794, 69786756, 59177718, 43322743, 29029316, 44889423, 47708850, 71522968, 71469330, 50007421, 4099191, 58751351, 29466406, 18131876, 97379790, 61859143, 49271185, 65047700, 76960303, 33565483, 26744917, 39986008, 84127901, 46870723, 4064751, 79821897, 37891451, 97641116, 669105, 69697787, 2917920, 67124282, 72358170, 61141874, 45049308, 74614639, 71083565, 54517921, 45075471, 44481640, 15148031, 92803766, 83210802, 76825057, 4806458, 45407418, 92787493, 16405341, 33336148, 45667668, 8634541, 57241521, 80251430, 98462867, 88698958, 26863229, 54762643, 54199160, 57248122, 79922758, 81805959, 42947632, 9829782, 90061527, 54663246, 1197320, 19898053, 8807940, 1569515, 99861373, 12571310, 64602895, 22108665, 52613508, 62051033, 77312810, 58180653, 66271566, 874791, 4798568, 36845587, 19101477, 15535065, 29188588, 29635537, 70367851, 36808486, 63152504, 51464002, 56461322, 85711894, 32058615, 68316156, 30811010, 71920426, 79806380, 1204161, 50668599, 16567550, 43152977, 1431742, 66319530, 72357096, 66663942, 70420215, 9603598, 36930650, 97783876, 38256849, 56955985, 8128637, 26292919, 30771409, 7502255, 32151165, 34432810, 61897582, 91255408, 6521313, 44550764, 89637706, 62452034, 19457589, 16387575, 65038678, 23848565, 5267545, 14947650, 26139110, 59371804, 97561745, 44473167, 94090109, 58208470, 87160386, 26998766, 78785507, 3880712, 49328608, 82427263, 33628349, 9860968, 89419466, 70782102, 67084351, 30366150, 54014062, 64848072, 47893286, 17976208, 15075176, 18466635, 27325428, 72495719, 68128438, 15163258, 69848388, 26734892, 20836893, 61712234, 24058273, 70596786, 69605283, 38061439, 93790285, 15064655, 76170907, 88130087, 13348726, 47213183, 78561158, 42644903, 38341669, 75777973, 1022677, 33553959, 82979980, 53666583, 55189057, 42307449, 66322959, 84002370, 36505482, 91957544, 77620120, 92541302, 45428665, 7685448, 71300104, 33249630, 66885828, 99965001, 56473732, 47090124, 34044787, 27041967, 31126490, 14045383, 16054533, 16424199, 41481685, 81774825, 4119747, 69355476, 18699206, 86798033, 66428795, 72238278, 40356877, 45919976, 83302115, 47298834, 1375023, 99524975, 56484900, 74441448, 4668450, 14731700, 67030811, 99226875, 34497327, 86022504, 87710366, 33201905, 99333375, 17037369, 5832946, 40534591, 247198, 73031054, 168541, 30163921, 4985896, 8696647, 62693428, 9886593, 61960400, 90310261, 60176618, 8115266, 13470059, 83651211, 27411561, 26493119, 45617087, 47361209, 13231279, 19939935, 17957593, 17894977, 76315420, 16445503, 68102437, 40865610, 28550822, 53084256, 88653118, 45237957, 44664587, 36312813, 28796059, 66045587, 78549759, 9259676, 36580610, 37280276, 65454636, 11161731, 22997281, 30764367, 59614746, 14723410, 81853704, 20486294, 35456853, 53648154, 82178706, 15902805, 38009615, 3773993, 89046466, 30787683, 20867149, 5482538, 75407347, 30569392, 47887470, 37675718, 43933006, 89804152, 82052050, 40197395, 30543215, 51149804, 26063929, 79880247, 61815223, 48658605, 112651, 92998591, 12303248, 42692881, 12348118, 2204165, 37620363, 37501808, 73109997, 18504197, 23740167, 18783702, 25636669, 17146629, 99297164, 33237508, 89811711, 96726697, 88047921, 7517032, 82651278, 57359924, 4091162, 20122224, 40781449, 90654836, 27625735, 19272365, 14363867, 50188404, 99021067, 23194618, 73786944, 18806856, 55247455, 83083131, 6819644, 30218878, 3294781, 20140249, 62496012, 56424103, 74452589, 24168411, 36780454, 62428472, 50567636, 45381876, 12664567, 36396314, 57803235, 6986898, 76330843, 14349098, 29819940, 59318837, 94076128, 53888755, 86821229, 11365791, 68694897, 321665, 65017137, 44627776, 29065964, 91281584, 526217, 77300457, 18833224, 83368048, 79191827, 93566986, 24733232, 32161669, 82327024, 9599614, 93057697, 36139942, 40677414, 59109400, 99549373, 20642888, 85242963, 83533741, 94516935, 93053405, 18001617, 90272749, 13173644, 19891772, 17539625, 57961282, 96852321, 35092039, 84840549, 99658235, 3487592, 11923835, 34698428, 65271999, 7104732, 93270084, 30998561, 96735716, 55602660, 62115552, 8651647, 85023028, 75229462, 36468541, 63628376, 49236559, 44915235, 6157724, 34667286, 77272628, 94539824, 32159704, 38008118, 16595436, 3633375, 73392814, 89499542, 21993752, 21070110, 62232211, 62490109, 80246713, 40984766, 68110330, 12416768, 54263880, 70004753, 83789391, 59197747, 67031644, 30090481, 45990383, 44784505, 3235882, 92071990, 69255765, 66116458, 7423788, 39801351, 29932657, 60581278, 33123618, 21289531, 17016156, 61263068, 23134715, 42967683, 7300047, 60102965, 48088883, 4204661, 72793444, 98653983, 76671482, 11543098, 85117092, 76703609, 99729357, 54427233, 80014588, 73617245, 84904436, 61741594, 48892201, 83948335, 38365584, 41380093, 39847321, 6793819, 9175338, 89214616, 96906410, 99690194, 32426752, 63967300, 27187213, 44245960, 7646095, 6505939, 79136082, 83747892, 67281495, 86118021, 71333116, 43376279, 78766358, 77898274, 90004325, 37659250, 54232247, 27665211, 33935899, 44479073, 68204242, 29401781, 72373496, 95726235, 51466049, 94711842, 24953498, 64055960, 73021291, 5822202, 96193415, 11757872, 41092102, 83269727, 53632373, 57240218, 83155442, 17081350, 17727650, 47738185, 82779622, 96318094, 6871053, 45995325, 10597197, 97057187, 65851721, 44983451, 14220886, 2717150, 48893685, 56515456, 5111370, 56531125, 36812683, 57020481, 43506672, 68824981, 61859581, 44842615, 10366309, 84349107, 9058407, 48673079, 4095116, 79942022, 66137019, 80316608, 45790169, 97281847, 8373289, 33431960, 75820087, 84684495, 3233084, 25842078, 83133790, 27185644, 33304202, 72274002, 17068582, 90457870, 10961421, 3088684, 66250369, 66667729, 28787861, 33895336, 42782652, 508198, 2208785, 96420429, 7229550, 78602717, 98943869, 60106217, 47792865, 14326617, 83150534, 26397786, 26392416, 91990218, 4199704, 6497830, 9860195, 92398073, 81677380, 15015906, 65715134, 65338021, 6153406, 45794415, 30891921, 44844121, 86460488, 33061250, 87598888, 79738755, 40027975, 24826575, 78589145, 9575954, 98739783, 95395112, 20765474, 65275241, 92867155, 86543538, 6111563, 26275734, 17058722, 51507425, 30653863, 62936963, 78909561, 37739481, 30463802, 7955293, 1239555, 73124510, 9188443, 51047803, 22766820, 81172706, 18663507, 7066775, 51426311, 55960386, 7011964, 8791066, 39373729, 38022834, 95957797, 29834512, 91664334, 74357852, 7182642, 49641577, 72732205, 74110882, 65074535, 4069912, 16684829, 29994197, 39201414, 12024238, 31491938, 59501487, 77072625, 79322415, 31727379, 45848907, 89078848, 50702367, 40686254, 96709982, 99515901, 99224392, 72278539, 90090964, 9398733, 43501211, 38645117, 72725103, 35512853, 51588015, 35996293, 97940276, 22435353, 49598724, 68041839, 21533347, 44348328, 69641513, 86001008, 71965942, 43357947, 28928175, 61623915, 75128745, 91141395, 58224549, 95581843, 79657802, 4515343, 60430369, 6038457, 74137926, 30694952, 38494874, 1936762, 14093520, 1788101, 13862149, 84406788, 10358899, 22200378, 70036438, 36189527, 20885148, 32250045, 17365188, 64098930, 6525948, 20681921, 78884452, 64087743, 70727211, 31733363, 19486173, 8129978, 5640302, 62803858, 61728685, 55850790, 36685023, 52293995, 62740044, 44847298, 80555751, 32590267, 37560164, 98371444, 5073754, 77694700, 93562257, 4508700, 77787724, 55143724, 52060076, 24915585, 56153345, 9257405, 10264691, 37435892, 40781854, 67474219, 77284619, 57658654, 37166608, 37192445, 15536795, 17385531, 1250437, 52261574, 22129328, 48774913, 34946859, 54058788, 10453030, 16380211, 99971982, 50806615, 22721500, 74743862, 44846932, 36753250, 95251277, 31904591, 26664538, 19376156, 69136837, 17764950, 76540481, 26102057, 91727510, 75543508, 4434662, 33797252, 78218845, 81855445, 92215320, 3509435, 38510840, 21001913, 72019362, 8055981, 51315460, 66903004, 91831487, 59981773, 95290172, 88444207, 42237907, 749283, 48675329, 4978543, 53802686, 51590803, 97011160, 14626618, 34236719, 98130363, 7919588, 55770687, 81898046, 22942635, 21673260, 24619760, 37224844, 8729146, 10649306, 13819358, 31161687, 29959549, 3233569, 42199455, 16019925, 13468268, 65081429, 2331773, 26507214, 49597667, 57163802, 16099750, 92604458, 70372191, 42073124, 74724075, 6808825, 33699435, 41442762, 22113133, 52204879, 87720882, 72777973, 78300864, 86242799, 55669657, 44060493, 71657078, 46540998, 40152546, 45306070, 39553046, 92530431, 6221471, 48395186, 73235980, 9623492, 68702632, 26114953, 20568363, 55470718, 36933038, 51472275, 28734791, 34493392, 17857111, 53393358, 22879907, 99604946, 30395570, 61373987, 75135448, 98648327, 62552524, 86301513, 77301523, 35192533, 18411915, 54868730, 36135, 60393039, 20645197, 824872, 61982238, 89699445, 45996863, 21397057, 67793644, 32274392, 99917599, 82339363, 90158785, 41092172, 2891150, 90013093, 22450468, 49882705, 88251446, 61271144, 59405277, 62430984, 73222868, 44177011, 43045786, 82886381, 89217461, 16097038, 72738685, 59329511, 60955663, 77187825, 94595668, 29516592, 58749, 91802888, 85695762, 38658347, 34698463, 2607799, 92692978, 20002147, 29510992, 3183975, 46851987, 5970607, 98948034, 57393458 +48658605, 47090124, 68644627, 91938191, 96318094, 14093520, 67031644, 42967683, 55189057, 71469330, 7011964, 95957797, 4119747, 68204242, 64055960, 34497327, 11581181, 7502255, 6521313, 48893685, 71083565, 44842615, 85242963, 44664587, 63628376, 78561158, 43933006, 92541302, 29635537, 85116755, 71300104, 66885828, 45919976, 51466049, 20140249, 55669657, 77413012, 96709982, 99515901, 91255408, 32274392, 23432750, 79942022, 80251430, 47361209, 28550822, 9623492, 53842979, 26776922, 42947632, 49236559, 27325428, 20836893, 78884452, 80246713, 48360198, 13819358, 65275241, 86543538, 26063929, 2331773, 9188443, 12348118, 74724075, 27041967, 41481685, 69355476, 86242799, 67030811, 62496012, 76330843, 14349098, 40534591, 79821897, 61859581, 33797252, 69579137, 98462867, 17068582, 61623915, 54199160, 81805959, 21919959, 95010552, 64848072, 9829782, 69605283, 38658347, 73222868, 21673260, 8807940, 87598888, 37224844, 15064655, 68000591, 92554120, 60581278, 65081429, 61815223, 43322743, 18663507, 70541760, 56473732, 33699435, 99297164, 58751351, 16054533, 61859143, 52060076, 4091162, 18699206, 65047700, 14363867, 56153345, 72373496, 16567550, 1375023, 99524975, 61982238, 56424103, 70420215, 54606384, 33201905, 6871053, 94595668, 97641116, 14220886, 65017137, 61141874, 44627776, 36753250, 18833224, 31904591, 84349107, 92803766, 27411561, 35996293, 14947650, 68041839, 45617087, 13173644, 58208470, 26863229, 17957593, 72274002, 93359396, 88251446, 40865610, 53084256, 58749, 11923835, 34698428, 45237957, 3880712, 82427263, 30694952, 75052463, 89419466, 36933038, 30366150, 51472275, 10309525, 18466635, 34493392, 89996536, 67227442, 24058273, 6153406, 21993752, 82178706, 22879907, 30891921, 70004753, 8729146, 77377183, 22108665, 61928316, 44784505, 63059024, 98739783, 569864, 33553959, 94360702, 26275734, 36685023, 26507214, 44847298, 80555751, 415901, 34295794, 45428665, 54868730, 81172706, 37620363, 93562257, 99965001, 86118021, 38022834, 77787724, 18131876, 68316156, 57359924, 82532312, 33935899, 72238278, 39201414, 43152977, 30218878, 73021291, 66663942, 41092102, 68939068, 67513640, 44060493, 59318837, 247198, 10597197, 97057187, 669105, 44550764, 11365791, 92353856, 8696647, 67124282, 321665, 74614639, 61960400, 44481640, 15148031, 24733232, 82327024, 93057697, 69136837, 83210802, 76825057, 13470059, 4806458, 45407418, 92787493, 41092172, 91727510, 26139110, 59371804, 45667668, 90272749, 44473167, 57241521, 94090109, 78218845, 19891772, 81855445, 86001008, 25842078, 71965942, 38510840, 21001913, 84840549, 3487592, 10961421, 93270084, 42782652, 79922758, 91802888, 26114953, 23569917, 33628349, 51315460, 91831487, 47792865, 95290172, 36468541, 70782102, 26397786, 67084351, 57393458, 10358899, 47893286, 67451935, 37957788, 51590803, 68128438, 15163258, 30764367, 38008118, 14723410, 3633375, 53547802, 65338021, 55770687, 21070110, 62232211, 40984766, 12416768, 86460488, 30395570, 83789391, 19486173, 30090481, 52613508, 5640302, 95395112, 62803858, 75777973, 21289531, 23134715, 3233569, 99729357, 16019925, 73617245, 78909561, 83948335, 23110625, 72738685, 6793819, 57163802, 32426752, 77694700, 70367851, 2204165, 7646095, 73109997, 23740167, 50007421, 33237508, 91664334, 77898274, 30811010, 86798033, 44479073, 50188404, 10264691, 83302115, 24953498, 96193415, 62762857, 24168411, 87710366, 84187166, 39986008, 8128637, 36396314, 34946859, 67793644, 90090964, 74743862, 82897371, 68694897, 62693428, 56531125, 44846932, 43501211, 93566986, 82339363, 68824981, 26664538, 4787945, 35512853, 20642888, 16405341, 83533741, 66137019, 75543508, 80316608, 8634541, 33431960, 29510992, 21533347, 84684495, 3233084, 83133790, 90013093, 78785507, 7104732, 8055981, 95581843, 28787861, 4515343, 57248122, 3183975, 74137926, 78602717, 9259676, 38494874, 98943869, 1936762, 1788101, 11161731, 72495719, 1197320, 61712234, 15015906, 20681921, 65715134, 19898053, 73392814, 35456853, 81898046, 44844121, 62490109, 24619760, 70727211, 5482538, 88130087, 76434144, 13348726, 30569392, 86301513, 7423788, 10649306, 38341669, 33123618, 47887470, 7300047, 60102965, 4204661, 53666583, 11543098, 34698463, 63506281, 13468268, 54427233, 874791, 84904436, 84002370, 46851987, 62936963, 38365584, 37560164, 92692283, 98371444, 92998591, 33249630, 47708850, 51464002, 18504197, 7066775, 55960386, 41442762, 71333116, 29466406, 55143724, 85711894, 14045383, 97379790, 90004325, 92692978, 79806380, 65074535, 23194618, 1431742, 20002147, 5822202, 57658654, 97783876, 83269727, 31727379, 50567636, 45996863, 57240218, 17385531, 17727650, 48774913, 71657078, 61897582, 54987042, 22721500, 72358170, 29065964, 43506672, 54517921, 45075471, 79191827, 5267545, 90310261, 59109400, 60176618, 17764950, 48673079, 76540481, 94516935, 45790169, 93053405, 33336148, 30139692, 8373289, 75820087, 33304202, 17894977, 90457870, 51715482, 87160386, 99658235, 63372756, 62357986, 73235980, 49328608, 96420429, 6038457, 66903004, 9860968, 68816413, 42237907, 91990218, 84406788, 6497830, 28734791, 9860195, 59405277, 32250045, 17365188, 22997281, 14626618, 59614746, 34236719, 69848388, 98130363, 32699744, 70596786, 85695762, 20486294, 15902805, 38009615, 44177011, 30787683, 99861373, 79738755, 12571310, 75135448, 78589145, 45990383, 92071990, 66116458, 39801351, 82886381, 82979980, 37183543, 89804152, 72793444, 85117092, 8913721, 66322959, 84293052, 36845587, 19101477, 91957544, 32590267, 29188588, 39847321, 51047803, 92604458, 99690194, 63967300, 5073754, 44245960, 71522968, 37501808, 36808486, 63152504, 83747892, 25636669, 39373729, 89811711, 71316369, 59329511, 29834512, 43376279, 96726697, 88047921, 16424199, 49641577, 7517032, 82651278, 20122224, 40781449, 37659250, 71920426, 27665211, 7687278, 92033260, 16684829, 87720882, 50668599, 9257405, 85571389, 29994197, 95726235, 8733068, 94711842, 60393039, 31491938, 4668450, 14731700, 66319530, 3294781, 9603598, 86022504, 62428472, 84127901, 6986898, 83155442, 4064751, 30771409, 37891451, 34432810, 65851721, 73031054, 53888755, 86821229, 4985896, 2917920, 9398733, 45306070, 19457589, 91281584, 45049308, 39553046, 10366309, 19376156, 38645117, 8115266, 90158785, 72725103, 51588015, 99549373, 22405120, 4095116, 70800879, 26102057, 2891150, 4434662, 18001617, 29516592, 69641513, 88698958, 96852321, 27185644, 35092039, 76315420, 72019362, 28928175, 65271999, 58224549, 3088684, 36312813, 96735716, 508198, 7229550, 8651647, 81274566, 14326617, 13862149, 36189527, 17976208, 94539824, 84166196, 54663246, 6525948, 24591705, 16595436, 89499542, 99604946, 54263880, 20867149, 31733363, 76170907, 59197747, 62552524, 9575954, 20765474, 31161687, 55850790, 29932657, 29959549, 1022677, 61263068, 37675718, 77312810, 93933709, 6111563, 48088883, 40197395, 51149804, 48260151, 80014588, 79880247, 72614359, 36505482, 61741594, 73124510, 41380093, 77620120, 15535065, 96906410, 70372191, 12303248, 44889423, 41245325, 18783702, 4099191, 17146629, 4508700, 5970607, 52204879, 31126490, 7182642, 72732205, 24314885, 49271185, 1204161, 19272365, 66428795, 63015256, 99021067, 74441448, 72357096, 824872, 59501487, 99226875, 99333375, 33565483, 26292919, 17386996, 40686254, 22129328, 99224392, 47738185, 29819940, 99971982, 50806615, 2717150, 89637706, 88904910, 57020481, 65038678, 92530431, 99917599, 23848565, 91240048, 40677414, 9058407, 97561745, 22450468, 75153252, 75128745, 91141395, 66250369, 66667729, 28796059, 79657802, 60106217, 55470718, 75229462, 83150534, 88444207, 26392416, 48675329, 70036438, 4199704, 92398073, 15948937, 90061527, 77272628, 97011160, 81677380, 62430984, 32159704, 64098930, 17857111, 7919588, 64087743, 38061439, 33061250, 176257, 93790285, 98648327, 47213183, 8129978, 68875490, 42644903, 17016156, 39171895, 98653983, 58180653, 76671482, 66271566, 52293995, 76703609, 55615885, 30653863, 35192533, 48892201, 7685448, 89214616, 69786756, 42073124, 112651, 29029316, 6505939, 79136082, 51426311, 59910624, 28851716, 27625735, 54232247, 4069912, 18806856, 47298834, 40781854, 79322415, 38256849, 37166608, 36780454, 45381876, 12664567, 21397057, 5832946, 46540998, 10453030, 94076128, 16380211, 168541, 30163921, 66832478, 69697787, 56515456, 94911072, 99125126, 526217, 95251277, 83368048, 32161669, 9599614, 36139942, 22435353, 97281847, 3509435, 19939935, 8099994, 16445503, 49882705, 66611759, 54762643, 88653118, 33895336, 66045587, 2208785, 55602660, 59981773, 36580610, 54014062, 37280276, 22200378, 15075176, 6157724, 20885148, 34667286, 26734892, 53393358, 22942635, 68110330, 3773993, 55753905, 24826575, 65880522, 64157906, 69255765, 75407347, 92867155, 73168565, 61728685, 89217461, 16097038, 17058722, 30543215, 42307449, 51507425, 49597667, 75986488, 59177718, 42692881, 27187213, 56461322, 8791066, 22113133, 16971929, 24915585, 29401781, 72777973, 73786944, 40356877, 56484900, 55247455, 20645197, 83083131, 6819644, 98948034, 74452589, 17037369, 26744917, 37192445, 15536795, 89078848, 52261574, 82726008, 32151165, 40152546, 5111370, 9886593, 16387575, 77300457, 97940276, 17539625, 92215320, 57961282, 26998766, 68102437, 48395186, 60430369, 62115552, 78549759, 20568363, 93515664, 4978543, 65454636, 89046466, 61373987, 3235882, 82052050, 62740044, 4798568, 37739481, 7955293, 9175338, 18411915, 67281495, 74357852, 78766358, 32058615, 81774825, 90654836, 60955663, 77284619, 11757872, 36930650, 89699445, 56955985, 45848907, 57803235, 1250437, 46870723, 44983451, 62452034, 49598724, 13231279, 6221471, 68702632, 85023028, 61271144, 749283, 81853704, 45794415, 1569515, 40027975, 64602895, 43045786, 62051033, 42199455, 30463802, 22766820, 6808825, 34044787, 2607799, 74110882, 12024238, 78300864, 77072625, 77187825, 50702367, 45995325, 83651211, 44348328, 43357947, 44915235, 53802686, 53648154, 1239555, 16099750, 36135, 37435892, 17081350, 82779622, 54058788, 72278539, 36812683, 26493119, 30998561, 77301523, 76960303, 67474219, 53632373 +36505482, 75052463, 42967683, 72738685, 70596786, 37224844, 39171895, 74357852, 86821229, 53084256, 15015906, 76434144, 44784505, 75407347, 77301523, 16019925, 23110625, 23740167, 95726235, 12024238, 62762857, 37192445, 67793644, 5111370, 9886593, 36139942, 4434662, 26493119, 76315420, 9259676, 55470718, 9860195, 68128438, 55770687, 43045786, 92554120, 38341669, 1022677, 52293995, 48892201, 9175338, 51426311, 4091162, 69355476, 1204161, 65047700, 20002147, 77187825, 62428472, 15536795, 59318837, 97057187, 95251277, 83368048, 16405341, 27185644, 78785507, 28550822, 79922758, 1788101, 36189527, 17857111, 3633375, 21070110, 20486294, 80246713, 54263880, 1569515, 13348726, 29959549, 61263068, 37183543, 4204661, 26275734, 17058722, 76703609, 54427233, 84002370, 4798568, 57163802, 4099191, 92692978, 7687278, 66428795, 24953498, 66319530, 96709982, 1250437, 44060493, 6871053, 168541, 72278539, 11365791, 88904910, 61960400, 82339363, 15148031, 61859581, 13470059, 26102057, 49598724, 30139692, 71965942, 38510840, 90013093, 75153252, 10961421, 34698428, 66667729, 66045587, 82427263, 81805959, 42947632, 77272628, 94539824, 59614746, 98130363, 7919588, 20836893, 73392814, 81898046, 61373987, 93790285, 76170907, 67031644, 78561158, 92071990, 5640302, 98653983, 42307449, 80014588, 44847298, 35192533, 37560164, 15535065, 29188588, 54868730, 70372191, 66885828, 7646095, 33699435, 18131876, 91664334, 31126490, 81774825, 57359924, 76960303, 29994197, 98948034, 83269727, 50567636, 82779622, 32151165, 34432810, 94911072, 44627776, 32274392, 31904591, 68824981, 32161669, 44842615, 94516935, 14947650, 97940276, 97281847, 29516592, 29510992, 17539625, 21533347, 88698958, 26863229, 25842078, 93359396, 87160386, 49882705, 99658235, 40865610, 61623915, 7104732, 4515343, 60430369, 91802888, 26114953, 60106217, 14093520, 75229462, 83150534, 70782102, 15075176, 17365188, 14723410, 69848388, 62490109, 55753905, 48360198, 5482538, 78589145, 77377183, 61928316, 45990383, 9575954, 98739783, 42644903, 77312810, 93933709, 51149804, 874791, 79880247, 73617245, 30653863, 46851987, 26507214, 51047803, 112651, 92998591, 12303248, 27187213, 29029316, 71469330, 37620363, 70541760, 8791066, 71316369, 29466406, 43376279, 68316156, 40781449, 27625735, 4119747, 18699206, 33935899, 65074535, 87720882, 85571389, 45919976, 18806856, 83302115, 99524975, 40781854, 72357096, 34497327, 86022504, 36780454, 39986008, 45848907, 57803235, 83155442, 4064751, 21397057, 46540998, 40152546, 16380211, 94595668, 30163921, 91255408, 2917920, 72358170, 16387575, 43506672, 44481640, 9599614, 10366309, 91240048, 40677414, 4787945, 99549373, 4806458, 41092172, 22405120, 48673079, 79942022, 26139110, 80316608, 68041839, 78218845, 13231279, 98462867, 17957593, 72274002, 48395186, 58224549, 8055981, 44664587, 93270084, 66250369, 36312813, 28787861, 3880712, 96735716, 51315460, 38494874, 8651647, 66903004, 59981773, 47792865, 26397786, 30366150, 49236559, 37280276, 749283, 67451935, 37957788, 10309525, 32250045, 14626618, 32159704, 30764367, 26734892, 81853704, 16595436, 65715134, 32699744, 38658347, 22879907, 30891921, 12416768, 20867149, 64602895, 88130087, 30569392, 39801351, 92867155, 62803858, 33123618, 37675718, 23134715, 7300047, 3233569, 72793444, 42199455, 55189057, 76671482, 8913721, 34698463, 48260151, 2331773, 62740044, 72614359, 34295794, 75986488, 45428665, 29635537, 48658605, 89214616, 33249630, 5073754, 81172706, 73109997, 86118021, 50007421, 56473732, 47090124, 25636669, 59329511, 2607799, 85711894, 97379790, 72732205, 82532312, 91938191, 92033260, 63015256, 56153345, 43152977, 14731700, 59501487, 57658654, 79322415, 97783876, 89699445, 99333375, 17037369, 68939068, 67513640, 77413012, 36396314, 6986898, 50702367, 52261574, 22129328, 99224392, 34946859, 37891451, 97641116, 66832478, 54987042, 44983451, 14220886, 82897371, 67124282, 89637706, 44846932, 36812683, 45049308, 526217, 65038678, 54517921, 92530431, 24733232, 26664538, 23848565, 84349107, 23432750, 70800879, 2891150, 93053405, 8634541, 57241521, 33431960, 80251430, 81855445, 92215320, 86001008, 35092039, 26998766, 68102437, 75128745, 58749, 26776922, 30998561, 74137926, 95010552, 9829782, 44915235, 72495719, 89996536, 54663246, 24591705, 61712234, 20681921, 53547802, 24058273, 65338021, 45794415, 53393358, 85695762, 21993752, 21673260, 8807940, 40984766, 99604946, 89046466, 70004753, 176257, 70727211, 79738755, 52613508, 8129978, 65275241, 31161687, 62051033, 29932657, 60581278, 89217461, 86543538, 43933006, 26063929, 65081429, 62936963, 37739481, 61815223, 19101477, 1239555, 38365584, 73124510, 41380093, 39847321, 16099750, 96906410, 43322743, 44245960, 63152504, 79136082, 18504197, 7066775, 55960386, 18783702, 7011964, 17146629, 68644627, 77787724, 29834512, 16971929, 96726697, 78766358, 7182642, 16424199, 27665211, 49271185, 86798033, 14363867, 16684829, 50668599, 68204242, 94711842, 60393039, 47298834, 37435892, 55247455, 86242799, 4668450, 30218878, 56424103, 96193415, 77072625, 11757872, 55669657, 26292919, 84127901, 76330843, 46870723, 48774913, 29819940, 79821897, 10597197, 99971982, 50806615, 22721500, 6521313, 48893685, 56515456, 62693428, 74614639, 79191827, 5267545, 19376156, 59109400, 60176618, 51588015, 92787493, 85242963, 66137019, 91727510, 33336148, 45667668, 13173644, 19891772, 21001913, 16445503, 22450468, 84840549, 62357986, 9623492, 28796059, 54199160, 33895336, 508198, 55602660, 6038457, 78602717, 30694952, 98943869, 68816413, 89419466, 36933038, 26392416, 84406788, 22200378, 4199704, 4978543, 17976208, 59405277, 65454636, 34667286, 27325428, 90061527, 34236719, 1197320, 19898053, 69605283, 15902805, 44177011, 87598888, 99861373, 12571310, 75135448, 59197747, 62552524, 47213183, 69255765, 68875490, 73168565, 55850790, 47887470, 569864, 6111563, 82052050, 36685023, 85117092, 99729357, 51507425, 84293052, 415901, 32590267, 92692283, 77620120, 6793819, 98371444, 71300104, 99690194, 69786756, 42073124, 63967300, 77694700, 44889423, 47708850, 2204165, 6808825, 58751351, 39373729, 33237508, 22113133, 52204879, 16054533, 20122224, 4069912, 23194618, 9257405, 16567550, 1375023, 74441448, 64055960, 1431742, 78300864, 67474219, 3294781, 61982238, 9603598, 37166608, 11581181, 45996863, 8128637, 17386996, 99515901, 71657078, 45995325, 247198, 73031054, 53888755, 2717150, 74743862, 68694897, 99125126, 91281584, 36753250, 43501211, 71083565, 83210802, 8115266, 35512853, 83533741, 27411561, 18001617, 90272749, 44473167, 8373289, 44348328, 69641513, 84684495, 19939935, 83133790, 33304202, 17894977, 43357947, 8099994, 90457870, 3487592, 63372756, 65271999, 6221471, 66611759, 95581843, 49328608, 62115552, 3183975, 78549759, 23569917, 33628349, 9860968, 91831487, 81274566, 14326617, 61271144, 88444207, 63628376, 64848072, 47893286, 51472275, 48675329, 28734791, 20885148, 53802686, 15948937, 51590803, 22997281, 62430984, 64098930, 6525948, 78884452, 53648154, 62232211, 86460488, 64087743, 15064655, 19486173, 24826575, 65880522, 3235882, 86301513, 63059024, 10649306, 75777973, 21289531, 33553959, 16097038, 48088883, 89804152, 40197395, 30543215, 13468268, 55615885, 36845587, 80555751, 61741594, 18411915, 32426752, 12348118, 93562257, 83747892, 41245325, 56461322, 99297164, 41442762, 34044787, 89811711, 71333116, 88047921, 55143724, 36135, 32058615, 7517032, 61859143, 52060076, 54232247, 24314885, 19272365, 44479073, 72238278, 31491938, 6819644, 67030811, 73021291, 62496012, 5822202, 66663942, 70420215, 74452589, 41092102, 33565483, 26744917, 12664567, 10453030, 96318094, 65851721, 61897582, 90090964, 44550764, 8696647, 9398733, 56531125, 321665, 29065964, 19457589, 77300457, 18833224, 45075471, 99917599, 82327024, 90310261, 38645117, 83651211, 72725103, 9058407, 35996293, 75543508, 22435353, 97561745, 94090109, 69579137, 47361209, 3509435, 3233084, 51715482, 72019362, 88251446, 91141395, 88653118, 3088684, 79657802, 42782652, 1936762, 21919959, 42237907, 36580610, 57393458, 54014062, 6497830, 92398073, 97011160, 38008118, 6153406, 89499542, 73222868, 68110330, 38009615, 30787683, 83789391, 31733363, 40027975, 68000591, 66116458, 95395112, 20765474, 61728685, 82886381, 60102965, 53666583, 66271566, 11543098, 66322959, 84904436, 30463802, 9188443, 7685448, 92604458, 42692881, 74724075, 37501808, 6505939, 51464002, 67281495, 99965001, 5970607, 38022834, 95957797, 27041967, 59910624, 41481685, 82651278, 24915585, 37659250, 99021067, 72777973, 39201414, 40356877, 10264691, 51466049, 8733068, 83083131, 77284619, 824872, 99226875, 38256849, 33201905, 84187166, 45381876, 57240218, 17385531, 82726008, 17727650, 5832946, 40534591, 94076128, 669105, 4985896, 61141874, 39553046, 93566986, 92803766, 90158785, 45407418, 17764950, 45617087, 58208470, 57961282, 11923835, 54762643, 68702632, 53842979, 20568363, 85023028, 13862149, 70036438, 11161731, 18466635, 81677380, 35456853, 22942635, 38061439, 33061250, 3773993, 30395570, 8729146, 64157906, 7423788, 94360702, 58180653, 63506281, 7955293, 83948335, 91957544, 49597667, 92541302, 85116755, 22766820, 71522968, 14045383, 90004325, 71920426, 79806380, 50188404, 29401781, 73786944, 56484900, 60955663, 36930650, 24168411, 87710366, 31727379, 17081350, 47738185, 30771409, 54058788, 69697787, 45306070, 62452034, 65017137, 57020481, 76825057, 20642888, 4095116, 76540481, 28928175, 45237957, 2208785, 96420429, 7229550, 67084351, 91990218, 93515664, 34493392, 84166196, 82178706, 24619760, 98648327, 22108665, 17016156, 82979980, 59177718, 70367851, 18663507, 72373496, 20645197, 54606384, 89078848, 53632373, 14349098, 7502255, 92353856, 93057697, 69136837, 59371804, 33797252, 75820087, 96852321, 73235980, 95290172, 36468541, 6157724, 44844121, 78909561, 4508700, 30811010, 90654836, 28851716, 56955985, 40686254, 45790169, 10358899, 15163258, 67227442, 30090481, 36808486, 49641577, 77898274, 74110882, 57248122, 13819358, 20140249, 17068582 +6038457, 91727510, 77272628, 91664334, 13819358, 37183543, 41481685, 91938191, 83651211, 28550822, 52613508, 47213183, 61263068, 7685448, 7646095, 8115266, 66611759, 9623492, 4515343, 74137926, 91990218, 49236559, 47893286, 44177011, 70004753, 64602895, 68000591, 42644903, 92867155, 1022677, 92692283, 29188588, 29029316, 37501808, 50668599, 1375023, 4668450, 57658654, 39986008, 96709982, 92353856, 36139942, 22435353, 8373289, 44348328, 33304202, 34698428, 96420429, 57248122, 8651647, 36933038, 67084351, 9860195, 6157724, 59614746, 6525948, 65715134, 80246713, 99604946, 176257, 93790285, 76434144, 8129978, 92554120, 55850790, 29932657, 72793444, 35192533, 16099750, 12303248, 4091162, 54232247, 10264691, 83083131, 66319530, 824872, 41092102, 33565483, 50702367, 37891451, 6521313, 90090964, 94911072, 77300457, 32161669, 93057697, 69136837, 93053405, 30139692, 71965942, 38510840, 8055981, 53842979, 60430369, 91802888, 51315460, 9259676, 59981773, 42237907, 36580610, 64848072, 22997281, 62430984, 84166196, 1197320, 20836893, 61712234, 32699744, 24058273, 53393358, 73222868, 44844121, 12416768, 86460488, 77377183, 43045786, 68875490, 65275241, 73168565, 77301523, 569864, 33553959, 48088883, 40197395, 54427233, 79880247, 80555751, 48892201, 1239555, 83948335, 77620120, 15535065, 70372191, 43322743, 66885828, 74724075, 18663507, 37620363, 7066775, 4099191, 17146629, 5970607, 77787724, 14363867, 50188404, 9257405, 83302115, 20645197, 5822202, 34497327, 77072625, 77413012, 1250437, 6871053, 97057187, 73031054, 30163921, 2717150, 44550764, 11365791, 9398733, 36753250, 65038678, 32274392, 31904591, 82339363, 38645117, 59371804, 80316608, 49598724, 45617087, 84684495, 19939935, 83133790, 21001913, 22450468, 78785507, 88251446, 40865610, 6221471, 44664587, 66250369, 79657802, 96735716, 79922758, 7229550, 62115552, 78549759, 60106217, 55470718, 83150534, 61271144, 13862149, 97011160, 14626618, 68128438, 45794415, 38658347, 64087743, 3773993, 92071990, 31161687, 33123618, 47887470, 17016156, 93933709, 23134715, 7300047, 30543215, 76671482, 52293995, 34698463, 16019925, 62740044, 6793819, 42692881, 77694700, 79136082, 18783702, 86118021, 7011964, 33237508, 59329511, 71333116, 52204879, 18131876, 96726697, 31126490, 88047921, 90654836, 76960303, 60393039, 43152977, 56484900, 64055960, 73021291, 3294781, 66663942, 61982238, 56424103, 77187825, 54606384, 62428472, 45848907, 45381876, 12664567, 82726008, 22129328, 4064751, 82779622, 34946859, 16380211, 72278539, 8696647, 68694897, 61141874, 71083565, 99917599, 44481640, 26664538, 60176618, 4787945, 35512853, 51588015, 4806458, 41092172, 70800879, 97940276, 4434662, 90272749, 29510992, 27185644, 90013093, 35092039, 72274002, 17068582, 51715482, 49882705, 75128745, 58749, 91141395, 66667729, 36312813, 30998561, 49328608, 2208785, 55602660, 23569917, 38494874, 9860968, 95290172, 95010552, 36468541, 1788101, 57393458, 84406788, 10358899, 51472275, 4199704, 67451935, 92398073, 20885148, 53802686, 89996536, 14723410, 69848388, 24591705, 15015906, 20681921, 70596786, 19898053, 76170907, 75135448, 48360198, 62552524, 9575954, 30569392, 62051033, 61728685, 42967683, 6111563, 89804152, 98653983, 42199455, 36685023, 8913721, 48260151, 30653863, 2331773, 46851987, 26507214, 78909561, 37739481, 38365584, 32590267, 39847321, 9188443, 71300104, 96906410, 112651, 63967300, 33249630, 22766820, 27187213, 70367851, 36808486, 63152504, 51464002, 99965001, 47090124, 58751351, 39373729, 89811711, 2607799, 43376279, 55143724, 85711894, 20122224, 40781449, 69355476, 27665211, 19272365, 29994197, 51466049, 94711842, 14731700, 77284619, 62496012, 96193415, 86022504, 24168411, 87710366, 83269727, 89699445, 17037369, 84187166, 50567636, 8128637, 17386996, 40686254, 17727650, 47738185, 29819940, 5832946, 30771409, 54058788, 168541, 66832478, 54987042, 22721500, 2917920, 19457589, 57020481, 45049308, 43501211, 45075471, 84349107, 91240048, 76825057, 13470059, 99549373, 20642888, 22405120, 29516592, 80251430, 78218845, 81855445, 92215320, 98462867, 17894977, 43357947, 87160386, 53084256, 10961421, 11923835, 63372756, 3088684, 28796059, 33895336, 26114953, 98943869, 75229462, 21919959, 22200378, 749283, 48675329, 44915235, 93515664, 4978543, 65454636, 11161731, 18466635, 34667286, 32250045, 34493392, 15163258, 54663246, 64098930, 7919588, 16595436, 3633375, 73392814, 89499542, 20486294, 30891921, 68110330, 54263880, 15064655, 19486173, 65880522, 67031644, 61928316, 75407347, 66116458, 98739783, 95395112, 20765474, 60581278, 82886381, 37675718, 3233569, 26275734, 58180653, 55189057, 11543098, 85117092, 76703609, 61741594, 415901, 91957544, 37560164, 29635537, 98371444, 89214616, 54868730, 32426752, 42073124, 47708850, 81172706, 71522968, 6505939, 67281495, 23740167, 56473732, 25636669, 22113133, 29834512, 14045383, 16054533, 16424199, 32058615, 77898274, 66428795, 92033260, 16684829, 72777973, 72238278, 72373496, 39201414, 73786944, 40356877, 16567550, 74441448, 1431742, 20002147, 6819644, 30218878, 72357096, 9603598, 36930650, 62762857, 99333375, 31727379, 45996863, 57803235, 46870723, 50806615, 86821229, 44983451, 91255408, 4985896, 14220886, 74743862, 5111370, 62693428, 62452034, 72358170, 9886593, 526217, 43506672, 39553046, 82327024, 61859581, 83210802, 90310261, 72725103, 48673079, 94516935, 44473167, 8634541, 57241521, 13231279, 57961282, 75820087, 88698958, 26863229, 8099994, 28928175, 65271999, 88653118, 68702632, 28787861, 82427263, 30694952, 75052463, 91831487, 85023028, 89419466, 42947632, 26392416, 30366150, 54014062, 70036438, 59405277, 10309525, 15948937, 51590803, 94539824, 32159704, 98130363, 26734892, 81853704, 67227442, 55770687, 8807940, 40984766, 30787683, 70727211, 31733363, 40027975, 5482538, 88130087, 64157906, 30090481, 98648327, 69255765, 29959549, 86543538, 16097038, 60102965, 17058722, 82052050, 66271566, 84293052, 4798568, 44847298, 62936963, 61815223, 73124510, 34295794, 99690194, 59177718, 44245960, 2204165, 73109997, 41245325, 50007421, 68644627, 38022834, 95957797, 29466406, 27041967, 74357852, 68316156, 82651278, 4119747, 79806380, 24314885, 7687278, 65047700, 65074535, 56153345, 99021067, 68204242, 23194618, 45919976, 95726235, 99524975, 78300864, 60955663, 40781854, 67474219, 59501487, 99226875, 70420215, 36780454, 37192445, 36396314, 84127901, 6986898, 17081350, 99515901, 48774913, 21397057, 40534591, 32151165, 79821897, 40152546, 99971982, 56515456, 56531125, 44846932, 88904910, 54517921, 93566986, 68824981, 9599614, 23432750, 16405341, 26102057, 79942022, 35996293, 26139110, 75543508, 33797252, 18001617, 33431960, 94090109, 47361209, 17539625, 96852321, 86001008, 76315420, 68102437, 75153252, 3487592, 54762643, 58224549, 73235980, 93270084, 54199160, 3183975, 33628349, 68816413, 14326617, 63628376, 36189527, 15075176, 30764367, 34236719, 53547802, 35456853, 82178706, 38061439, 30395570, 24619760, 61373987, 20867149, 79738755, 55753905, 13348726, 22108665, 78561158, 63059024, 38341669, 75777973, 21289531, 89217461, 53666583, 99729357, 63506281, 66322959, 55615885, 84002370, 36845587, 30463802, 19101477, 49597667, 9175338, 48658605, 92604458, 44889423, 71469330, 6808825, 70541760, 8791066, 41442762, 16971929, 78766358, 49641577, 81774825, 7517032, 61859143, 52060076, 57359924, 30811010, 27625735, 72732205, 37659250, 92692978, 18699206, 85571389, 37435892, 86242799, 67030811, 11757872, 97783876, 74452589, 37166608, 33201905, 11581181, 68939068, 56955985, 53632373, 52261574, 99224392, 71657078, 10453030, 96318094, 247198, 94076128, 94595668, 67793644, 53888755, 669105, 48893685, 69697787, 45306070, 99125126, 91281584, 92530431, 24733232, 44842615, 5267545, 17764950, 9058407, 83533741, 68041839, 45667668, 97281847, 3509435, 25842078, 17957593, 26998766, 45237957, 26776922, 66045587, 42782652, 20568363, 1936762, 66903004, 14093520, 26397786, 37957788, 27325428, 17365188, 65338021, 78884452, 85695762, 22879907, 81898046, 21673260, 62490109, 89046466, 1569515, 83789391, 87598888, 37224844, 12571310, 24826575, 78589145, 45990383, 86301513, 7423788, 5640302, 39801351, 77312810, 82979980, 43933006, 39171895, 4204661, 42307449, 26063929, 13468268, 73617245, 84904436, 36505482, 41380093, 23110625, 85116755, 69786756, 92998591, 18504197, 51426311, 56461322, 71316369, 97379790, 90004325, 71920426, 82532312, 1204161, 63015256, 29401781, 18806856, 8733068, 98948034, 38256849, 26744917, 67513640, 17385531, 44060493, 46540998, 67124282, 321665, 89637706, 29065964, 16387575, 18833224, 61960400, 83368048, 79191827, 15148031, 10366309, 19376156, 85242963, 66137019, 45790169, 26493119, 58208470, 69641513, 93359396, 72019362, 84840549, 61623915, 48395186, 508198, 70782102, 9829782, 6497830, 17976208, 38008118, 17857111, 62232211, 38009615, 8729146, 10649306, 51149804, 51507425, 874791, 65081429, 72614359, 72738685, 75986488, 45428665, 57163802, 5073754, 93562257, 83747892, 55960386, 4508700, 99297164, 24915585, 28851716, 74110882, 4069912, 44479073, 87720882, 47298834, 55247455, 15536795, 89078848, 57240218, 83155442, 14349098, 7502255, 59318837, 45995325, 10597197, 97641116, 82897371, 65017137, 44627776, 95251277, 23848565, 45407418, 27411561, 14947650, 21533347, 3233084, 16445503, 90457870, 99658235, 62357986, 7104732, 95581843, 3880712, 81805959, 88444207, 37280276, 28734791, 90061527, 81677380, 69605283, 21993752, 21070110, 53648154, 22942635, 15902805, 99861373, 3235882, 92541302, 51047803, 33699435, 34044787, 7182642, 33935899, 49271185, 12024238, 20140249, 55669657, 34432810, 65851721, 36812683, 74614639, 59109400, 90158785, 92787493, 4095116, 2891150, 33336148, 13173644, 69579137, 78602717, 81274566, 47792865, 6153406, 94360702, 7955293, 18411915, 59910624, 86798033, 79322415, 26292919, 92803766, 76540481, 33061250, 59197747, 44784505, 62803858, 12348118, 24953498, 76330843, 61897582, 40677414, 97561745, 72495719, 80014588, 36135, 31491938, 19891772 +17857111, 86022504, 91255408, 30139692, 1788101, 3633375, 99604946, 42967683, 74357852, 71920426, 5822202, 29065964, 9599614, 33431960, 28550822, 34698428, 74137926, 38494874, 15075176, 80246713, 22108665, 75407347, 77301523, 63506281, 37739481, 71316369, 45919976, 40781854, 89699445, 52261574, 67124282, 57961282, 27185644, 85023028, 83150534, 61928316, 98739783, 1022677, 37675718, 36505482, 29188588, 32426752, 92998591, 18663507, 25636669, 17146629, 55143724, 69355476, 50188404, 40356877, 95726235, 18806856, 1431742, 6871053, 37891451, 73031054, 44550764, 89637706, 36812683, 36753250, 95251277, 71083565, 83368048, 72725103, 17539625, 58224549, 54199160, 14093520, 57393458, 9829782, 89996536, 32699744, 53393358, 44177011, 20867149, 31733363, 76434144, 95395112, 82979980, 42199455, 51507425, 46851987, 32590267, 57163802, 70372191, 5073754, 63152504, 88047921, 27625735, 4119747, 94711842, 74441448, 56424103, 97783876, 99333375, 50702367, 7502255, 61897582, 669105, 65038678, 31904591, 24733232, 82327024, 19376156, 13470059, 41092172, 35996293, 2891150, 22435353, 68041839, 26493119, 8634541, 71965942, 95581843, 3880712, 26776922, 4515343, 66903004, 84406788, 49236559, 4199704, 69848388, 81853704, 1569515, 93790285, 76170907, 48360198, 45990383, 92071990, 55850790, 60581278, 33553959, 4204661, 53666583, 98653983, 73124510, 72738685, 6793819, 7685448, 92604458, 5970607, 27041967, 16424199, 54232247, 56153345, 29994197, 6819644, 62762857, 83269727, 39986008, 56955985, 44060493, 5832946, 247198, 54987042, 86821229, 5111370, 99125126, 57020481, 61960400, 15148031, 26664538, 44842615, 76825057, 48673079, 4434662, 97281847, 90272749, 29510992, 13231279, 93359396, 26998766, 65271999, 6221471, 7104732, 93270084, 33895336, 91802888, 55602660, 6038457, 54014062, 70036438, 93515664, 67451935, 59405277, 17365188, 59614746, 6525948, 20681921, 19898053, 21993752, 40984766, 24619760, 89046466, 30787683, 61373987, 75135448, 55753905, 69255765, 66116458, 86301513, 65275241, 38341669, 92867155, 29932657, 82886381, 89217461, 6111563, 39171895, 17058722, 30543215, 52293995, 99729357, 65081429, 62740044, 4798568, 44847298, 80555751, 75986488, 99690194, 44245960, 67281495, 4099191, 77787724, 71333116, 85711894, 27665211, 7687278, 65047700, 86798033, 39201414, 60393039, 43152977, 37435892, 14731700, 66319530, 67030811, 98948034, 30218878, 66663942, 96193415, 37166608, 45848907, 83155442, 99224392, 65851721, 168541, 53888755, 22721500, 14220886, 11365791, 62452034, 72358170, 9886593, 16387575, 54517921, 32274392, 79191827, 23848565, 83210802, 76540481, 26102057, 91727510, 93053405, 78218845, 88698958, 96852321, 86001008, 22450468, 49882705, 53084256, 88653118, 44664587, 49328608, 7229550, 78602717, 42947632, 55470718, 88444207, 30366150, 63628376, 749283, 36189527, 37957788, 14626618, 34236719, 20836893, 61712234, 65715134, 6153406, 70596786, 55770687, 21070110, 20486294, 35456853, 73222868, 82178706, 62232211, 15902805, 12416768, 70004753, 65880522, 77377183, 62552524, 78561158, 68875490, 10649306, 13819358, 31161687, 569864, 93933709, 23134715, 43933006, 3233569, 82052050, 76671482, 51149804, 48260151, 26063929, 16019925, 66322959, 30653863, 26507214, 62936963, 61815223, 48892201, 415901, 1239555, 77620120, 15535065, 45428665, 9175338, 85116755, 16099750, 48658605, 71300104, 54868730, 112651, 63967300, 66885828, 71469330, 6505939, 83747892, 7066775, 47090124, 89811711, 95957797, 18131876, 96726697, 14045383, 59910624, 41481685, 32058615, 52060076, 40781449, 90654836, 72732205, 74110882, 33935899, 44479073, 50668599, 72777973, 8733068, 12024238, 56484900, 78300864, 20002147, 77284619, 73021291, 72357096, 20140249, 62496012, 9603598, 55669657, 38256849, 33565483, 17037369, 8128637, 84127901, 96709982, 82726008, 29819940, 30771409, 71657078, 46540998, 34946859, 59318837, 97057187, 50806615, 97641116, 4985896, 61141874, 44627776, 526217, 43506672, 92530431, 40677414, 90158785, 4787945, 35512853, 92787493, 4095116, 66137019, 80316608, 13173644, 26863229, 19939935, 17957593, 21001913, 17894977, 35092039, 43357947, 87160386, 78785507, 99658235, 75128745, 45237957, 36312813, 57248122, 26114953, 91831487, 81274566, 68816413, 59981773, 36468541, 67084351, 4978543, 92398073, 10309525, 27325428, 68128438, 34493392, 15163258, 84166196, 38008118, 14723410, 78884452, 89499542, 69605283, 68110330, 70727211, 99861373, 40027975, 8729146, 12571310, 19486173, 24826575, 78589145, 9575954, 44784505, 7423788, 92554120, 62803858, 17016156, 61263068, 86543538, 94360702, 60102965, 26275734, 66271566, 34698463, 42307449, 76703609, 874791, 79880247, 84293052, 84904436, 2331773, 61741594, 38365584, 49597667, 42692881, 77694700, 27187213, 12348118, 44889423, 81172706, 71522968, 2204165, 7646095, 73109997, 18504197, 41245325, 51426311, 56473732, 4508700, 59329511, 16971929, 91664334, 49641577, 77898274, 57359924, 90004325, 37659250, 79806380, 18699206, 91938191, 72373496, 86242799, 4668450, 67474219, 824872, 59501487, 99226875, 36930650, 41092102, 50567636, 37192445, 45996863, 12664567, 36396314, 57240218, 17386996, 22129328, 17727650, 10453030, 67793644, 2717150, 45306070, 65017137, 39553046, 99917599, 44481640, 93057697, 45407418, 83533741, 27411561, 94516935, 59371804, 18001617, 69579137, 81855445, 38510840, 90013093, 72274002, 8099994, 72019362, 40865610, 61623915, 10961421, 66611759, 73235980, 28796059, 42782652, 60430369, 79922758, 33628349, 8651647, 21919959, 22200378, 64848072, 51472275, 48675329, 6497830, 65454636, 34667286, 97011160, 32159704, 98130363, 81898046, 44844121, 21673260, 54263880, 59197747, 5482538, 67031644, 30090481, 3235882, 47213183, 63059024, 5640302, 75777973, 47887470, 16097038, 48088883, 89804152, 58180653, 55189057, 40197395, 36685023, 85117092, 8913721, 55615885, 35192533, 83948335, 41380093, 9188443, 18411915, 98371444, 69786756, 33249630, 43322743, 29029316, 37620363, 93562257, 18783702, 86118021, 50007421, 56461322, 33699435, 8791066, 41442762, 33237508, 68644627, 29466406, 29834512, 2607799, 78766358, 31126490, 16054533, 81774825, 68316156, 7517032, 61859143, 82532312, 65074535, 76960303, 14363867, 4069912, 68204242, 83302115, 61982238, 77072625, 79322415, 74452589, 77187825, 87710366, 26744917, 77413012, 17081350, 4064751, 47738185, 48774913, 82779622, 40534591, 79821897, 45995325, 94076128, 16380211, 34432810, 99971982, 30163921, 44983451, 90090964, 92353856, 8696647, 44846932, 88904910, 77300457, 43501211, 93566986, 36139942, 91240048, 69136837, 90310261, 38645117, 51588015, 99549373, 4806458, 17764950, 85242963, 97940276, 75543508, 97561745, 44473167, 94090109, 19891772, 75820087, 44348328, 3233084, 83133790, 33304202, 76315420, 90457870, 51715482, 84840549, 28928175, 75153252, 58749, 91141395, 8055981, 3088684, 66667729, 68702632, 66045587, 96735716, 508198, 2208785, 96420429, 78549759, 9259676, 75052463, 9860968, 14326617, 26397786, 42237907, 47893286, 44915235, 28734791, 20885148, 18466635, 32250045, 51590803, 72495719, 77272628, 62430984, 54663246, 1197320, 7919588, 15015906, 67227442, 24058273, 85695762, 38658347, 22879907, 62490109, 38061439, 3773993, 83789391, 87598888, 37224844, 88130087, 43045786, 73168565, 33123618, 7300047, 72793444, 13468268, 54427233, 80014588, 36845587, 91957544, 89214616, 59177718, 74724075, 37501808, 51464002, 55960386, 38022834, 97379790, 36135, 82651278, 4091162, 24915585, 28851716, 92692978, 66428795, 63015256, 99021067, 87720882, 72238278, 85571389, 73786944, 31491938, 1375023, 99524975, 55247455, 20645197, 83083131, 3294781, 34497327, 70420215, 62428472, 11581181, 31727379, 68939068, 89078848, 53632373, 1250437, 46870723, 54058788, 32151165, 94595668, 66832478, 6521313, 82897371, 48893685, 2917920, 56515456, 56531125, 321665, 94911072, 45049308, 18833224, 10366309, 84349107, 59109400, 60176618, 8115266, 23432750, 83651211, 20642888, 14947650, 45790169, 33797252, 45667668, 47361209, 21533347, 3509435, 98462867, 3487592, 63372756, 54762643, 9623492, 53842979, 82427263, 23569917, 30694952, 1936762, 89419466, 70782102, 26392416, 36580610, 91990218, 37280276, 11161731, 15948937, 94539824, 64098930, 24591705, 16595436, 30891921, 8807940, 86460488, 64087743, 33061250, 30395570, 176257, 64602895, 8129978, 20765474, 29959549, 37183543, 73617245, 84002370, 72614359, 78909561, 30463802, 19101477, 37560164, 92541302, 34295794, 29635537, 51047803, 42073124, 12303248, 22766820, 70367851, 36808486, 79136082, 6808825, 70541760, 23740167, 39373729, 34044787, 22113133, 52204879, 20122224, 1204161, 92033260, 16567550, 47298834, 60955663, 57658654, 54606384, 24168411, 36780454, 67513640, 6986898, 40686254, 17385531, 76330843, 74743862, 68694897, 9398733, 62693428, 91281584, 82339363, 68824981, 32161669, 92803766, 16405341, 9058407, 70800879, 79942022, 49598724, 45617087, 58208470, 92215320, 84684495, 17068582, 11923835, 48395186, 66250369, 30998561, 62115552, 47792865, 95290172, 95010552, 61271144, 13862149, 53802686, 30764367, 26734892, 53547802, 53648154, 38009615, 79738755, 15064655, 64157906, 13348726, 68000591, 30569392, 39801351, 62051033, 61728685, 21289531, 7955293, 92692283, 39847321, 96906410, 47708850, 99965001, 99297164, 58751351, 7182642, 30811010, 24314885, 49271185, 23194618, 29401781, 9257405, 10264691, 51466049, 24953498, 11757872, 21397057, 96318094, 72278539, 69697787, 22405120, 26139110, 29516592, 8373289, 57241521, 80251430, 69641513, 16445503, 68102437, 88251446, 79657802, 81805959, 51315460, 98943869, 75229462, 10358899, 90061527, 73392814, 22942635, 52613508, 42644903, 11543098, 19272365, 33201905, 15536795, 26292919, 57803235, 19457589, 74614639, 45075471, 61859581, 5267545, 25842078, 28787861, 20568363, 60106217, 36933038, 9860195, 6157724, 22997281, 77312810, 23110625, 7011964, 43376279, 64055960, 84187166, 45381876, 99515901, 14349098, 40152546, 33336148, 62357986, 3183975, 81677380, 65338021, 98648327, 16684829, 17976208, 45794415, 10597197 +44784505, 7104732, 81898046, 5482538, 67031644, 86798033, 14220886, 54517921, 45407418, 45237957, 28734791, 20867149, 99861373, 86301513, 77301523, 4508700, 31126490, 7182642, 97783876, 17727650, 10453030, 44983451, 72278539, 14947650, 3509435, 88698958, 72019362, 21919959, 13862149, 30764367, 65338021, 69605283, 62552524, 92071990, 73617245, 65081429, 69786756, 59177718, 18504197, 8791066, 95957797, 16424199, 12024238, 40781854, 33565483, 46540998, 77300457, 13470059, 51588015, 92787493, 97561745, 57241521, 29510992, 49882705, 63372756, 48395186, 91831487, 64848072, 65454636, 34236719, 21673260, 88130087, 66116458, 7423788, 95395112, 62803858, 89217461, 33553959, 43933006, 48088883, 76671482, 32590267, 6793819, 85116755, 92604458, 12303248, 43322743, 82651278, 28851716, 27625735, 79806380, 65047700, 74441448, 4668450, 77072625, 55669657, 12664567, 57240218, 76330843, 79821897, 59318837, 54987042, 45306070, 89637706, 92530431, 93566986, 82339363, 75543508, 44473167, 17539625, 58208470, 13231279, 78785507, 61623915, 34698428, 79657802, 96735716, 57248122, 6038457, 26392416, 22200378, 47893286, 92398073, 15015906, 67227442, 6153406, 8807940, 78589145, 10649306, 37183543, 7300047, 42199455, 36685023, 26063929, 44847298, 36845587, 9175338, 51047803, 5073754, 71522968, 99297164, 33237508, 74357852, 88047921, 36135, 32058615, 81774825, 52060076, 57359924, 90004325, 4091162, 4119747, 27665211, 18699206, 33935899, 92033260, 76960303, 14731700, 62496012, 9603598, 99333375, 89078848, 40686254, 34946859, 247198, 94595668, 86821229, 44550764, 74743862, 69697787, 99125126, 88904910, 93057697, 5267545, 84349107, 91240048, 99549373, 20642888, 83533741, 27411561, 35996293, 33336148, 26493119, 80251430, 21533347, 21001913, 26998766, 88251446, 75128745, 91141395, 65271999, 58224549, 28787861, 3183975, 51315460, 38494874, 55470718, 36933038, 57393458, 49236559, 70036438, 93515664, 34667286, 77272628, 62430984, 94539824, 17857111, 20836893, 85695762, 35456853, 80246713, 24619760, 87598888, 79738755, 76170907, 19486173, 65880522, 22108665, 9575954, 30569392, 63059024, 92554120, 65275241, 29959549, 77312810, 94360702, 82052050, 42307449, 72614359, 78909561, 35192533, 61815223, 19101477, 1239555, 92692283, 92541302, 29188588, 48658605, 96906410, 92998591, 66885828, 70367851, 93562257, 79136082, 67281495, 51426311, 18783702, 41442762, 68644627, 91664334, 14045383, 54232247, 92692978, 69355476, 99021067, 29994197, 73786944, 18806856, 78300864, 98948034, 34497327, 62762857, 74452589, 38256849, 87710366, 83269727, 11581181, 26744917, 45848907, 14349098, 54058788, 96318094, 94076128, 34432810, 50806615, 669105, 2717150, 6521313, 67124282, 56515456, 5111370, 62452034, 16387575, 65038678, 71083565, 39553046, 31904591, 44481640, 61859581, 9599614, 36139942, 90158785, 17764950, 4095116, 76540481, 26102057, 79942022, 2891150, 59371804, 4434662, 33797252, 49598724, 45667668, 18001617, 86001008, 3233084, 38510840, 33304202, 53084256, 10961421, 54762643, 88653118, 8055981, 3880712, 33895336, 42782652, 91802888, 81805959, 23569917, 1936762, 68816413, 95010552, 61271144, 30366150, 10358899, 63628376, 37957788, 4978543, 9860195, 6157724, 53802686, 72495719, 22997281, 6525948, 7919588, 24591705, 81853704, 16595436, 3633375, 70596786, 19898053, 53648154, 22879907, 15902805, 30395570, 31733363, 12571310, 48360198, 64602895, 30090481, 68000591, 77377183, 45990383, 5640302, 86543538, 16097038, 39171895, 4204661, 3233569, 17058722, 58180653, 55189057, 99729357, 54427233, 30653863, 62740044, 62936963, 61741594, 83948335, 38365584, 49597667, 75986488, 99690194, 32426752, 63967300, 22766820, 44889423, 36808486, 63152504, 6505939, 83747892, 86118021, 4099191, 56461322, 47090124, 5970607, 85711894, 16054533, 49641577, 61859143, 91938191, 7687278, 1204161, 14363867, 4069912, 72373496, 16567550, 8733068, 94711842, 60393039, 37435892, 64055960, 1431742, 67030811, 99226875, 66663942, 41092102, 89699445, 39986008, 15536795, 84127901, 83155442, 17081350, 52261574, 47738185, 29819940, 5832946, 7502255, 37891451, 61897582, 53888755, 4985896, 48893685, 68694897, 2917920, 62693428, 56531125, 72358170, 91281584, 57020481, 526217, 95251277, 74614639, 32274392, 24733232, 26664538, 40677414, 76825057, 66137019, 30139692, 19891772, 17957593, 8099994, 40865610, 28550822, 75153252, 58749, 62357986, 73235980, 3088684, 54199160, 26776922, 66045587, 55602660, 78549759, 33628349, 85023028, 14326617, 95290172, 88444207, 67084351, 42237907, 36580610, 749283, 36189527, 67451935, 90061527, 81677380, 68128438, 59614746, 38008118, 64098930, 1197320, 21993752, 21070110, 20486294, 73222868, 62490109, 44177011, 1569515, 37224844, 15064655, 8729146, 75135448, 24826575, 61928316, 98739783, 42644903, 60581278, 17016156, 93933709, 23134715, 42967683, 60102965, 89804152, 52293995, 874791, 80014588, 79880247, 84293052, 84904436, 46851987, 80555751, 36505482, 415901, 37560164, 41380093, 29635537, 9188443, 7685448, 71300104, 112651, 12348118, 81172706, 2204165, 37620363, 70541760, 23740167, 99965001, 56473732, 33699435, 34044787, 89811711, 71316369, 71333116, 27041967, 43376279, 55143724, 59910624, 20122224, 37659250, 71920426, 19272365, 66428795, 16684829, 50668599, 72238278, 9257405, 51466049, 31491938, 99524975, 83083131, 60955663, 59501487, 61982238, 96193415, 86022504, 36780454, 17037369, 50567636, 56955985, 77413012, 57803235, 96709982, 17385531, 21397057, 44060493, 45995325, 65851721, 91255408, 9886593, 61141874, 44627776, 18833224, 43501211, 44842615, 69136837, 23432750, 83651211, 4787945, 9058407, 70800879, 80316608, 22435353, 45617087, 33431960, 69579137, 47361209, 57961282, 69641513, 26863229, 25842078, 71965942, 43357947, 51715482, 87160386, 99658235, 28928175, 11923835, 66250369, 66667729, 82427263, 96420429, 26114953, 98943869, 9860968, 47792865, 75229462, 26397786, 84406788, 51472275, 10309525, 15948937, 27325428, 51590803, 17365188, 14626618, 32159704, 89996536, 84166196, 54663246, 14723410, 69848388, 98130363, 78884452, 55770687, 82178706, 62232211, 44844121, 40984766, 12416768, 33061250, 3773993, 30787683, 93790285, 59197747, 3235882, 52613508, 75407347, 8129978, 68875490, 39801351, 13819358, 92867155, 75777973, 33123618, 61263068, 37675718, 53666583, 72793444, 98653983, 30543215, 11543098, 85117092, 63506281, 55615885, 26507214, 4798568, 30463802, 48892201, 91957544, 39847321, 57163802, 16099750, 18411915, 70372191, 33249630, 42692881, 47708850, 71469330, 41245325, 50007421, 7011964, 22113133, 38022834, 52204879, 78766358, 97379790, 41481685, 68316156, 24314885, 44479073, 50188404, 87720882, 85571389, 39201414, 83302115, 24953498, 55247455, 6819644, 3294781, 72357096, 20140249, 77187825, 54606384, 24168411, 62428472, 68939068, 37192445, 53632373, 48774913, 30771409, 97641116, 82897371, 94911072, 65017137, 44846932, 36812683, 29065964, 43506672, 68824981, 23848565, 92803766, 38645117, 60176618, 8115266, 4806458, 85242963, 94516935, 97940276, 45790169, 90272749, 94090109, 92215320, 44348328, 98462867, 96852321, 19939935, 27185644, 90013093, 35092039, 72274002, 22450468, 93359396, 44664587, 28796059, 53842979, 30998561, 79922758, 74137926, 30694952, 9259676, 75052463, 60106217, 59981773, 83150534, 91990218, 54014062, 9829782, 4199704, 18466635, 32250045, 97011160, 34493392, 53547802, 24058273, 38658347, 64087743, 54263880, 70004753, 83789391, 76434144, 13348726, 43045786, 20765474, 31161687, 55850790, 29932657, 21289531, 47887470, 40197395, 66271566, 16019925, 13468268, 66322959, 51507425, 37739481, 73124510, 77620120, 72738685, 89214616, 74724075, 44245960, 37501808, 51464002, 17146629, 29834512, 30811010, 49271185, 65074535, 56153345, 23194618, 29401781, 45919976, 95726235, 20645197, 77284619, 30218878, 824872, 79322415, 67513640, 45996863, 8128637, 36396314, 6986898, 50702367, 17386996, 99515901, 46870723, 71657078, 32151165, 10597197, 97057187, 67793644, 66832478, 90090964, 11365791, 92353856, 321665, 45049308, 61960400, 45075471, 79191827, 99917599, 15148031, 59109400, 72725103, 41092172, 91727510, 68041839, 97281847, 83133790, 6221471, 66611759, 93270084, 508198, 78602717, 66903004, 42947632, 14093520, 70782102, 44915235, 20885148, 15163258, 65715134, 45794415, 22942635, 30891921, 68110330, 61373987, 55753905, 64157906, 78561158, 69255765, 38341669, 73168565, 26275734, 8913721, 7955293, 23110625, 34295794, 15535065, 98371444, 54868730, 29029316, 18663507, 7646095, 55960386, 58751351, 59329511, 77787724, 16971929, 18131876, 2607799, 40781449, 90654836, 72777973, 10264691, 43152977, 56484900, 20002147, 66319530, 67474219, 56424103, 70420215, 33201905, 82726008, 4064751, 82779622, 40152546, 16380211, 73031054, 22721500, 19457589, 83368048, 32161669, 82327024, 19376156, 83210802, 90310261, 35512853, 16405341, 48673079, 26139110, 29516592, 75820087, 84684495, 76315420, 16445503, 90457870, 9623492, 36312813, 68702632, 95581843, 2208785, 60430369, 62115552, 20568363, 81274566, 1788101, 37280276, 48675329, 6497830, 59405277, 20681921, 73392814, 89499542, 38061439, 89046466, 98648327, 62051033, 61728685, 82886381, 1022677, 569864, 82979980, 51149804, 34698463, 48260151, 77694700, 73109997, 6808825, 25636669, 96726697, 7517032, 24915585, 68204242, 40356877, 1375023, 86242799, 73021291, 5822202, 37166608, 31727379, 26292919, 6871053, 99971982, 168541, 8696647, 9398733, 36753250, 10366309, 93053405, 81855445, 17894977, 84840549, 68102437, 3487592, 49328608, 17976208, 15075176, 11161731, 61712234, 32699744, 38009615, 86460488, 70727211, 6111563, 2331773, 84002370, 45428665, 42073124, 27187213, 7066775, 29466406, 72732205, 82532312, 63015256, 47298834, 57658654, 36930650, 84187166, 1250437, 22129328, 99224392, 40534591, 30163921, 22405120, 8634541, 13173644, 78218845, 4515343, 26734892, 53393358, 99604946, 39373729, 11757872, 45381876, 8373289, 17068582, 7229550, 8651647, 89419466, 36468541, 40027975, 47213183, 77898274, 74110882, 176257, 76703609 diff --git a/src/test/resources/data/test_ground_truth_l2_100.csv b/src/test/resources/data/test_ground_truth_l2_100.csv new file mode 100644 index 000000000..c1986261f --- /dev/null +++ b/src/test/resources/data/test_ground_truth_l2_100.csv @@ -0,0 +1,100 @@ +76960303,76703609,62740044,29188588,66428795,37435892,60106217,71469330,9575954,6986898,2607799,66045587,39847321,48774913,16097038,61263068,75229462,39801351,247198,44627776,7182642,40677414,29834512,95957797,30771409,84293052,16099750,38658347,86001008,76434144,9860968,94076128,77187825,67793644,30998561,50806615,96193415,42967683,4095116,16595436,4099191,54987042,56424103,61271144,74137926,2208785,24915585,43376279,92033260,21993752,70596786,24058273,71083565,9623492,6808825,7687278,51472275,35192533,97011160,38645117,34236719,86460488,56153345,61859143,43933006,99224392,17081350,7646095,37675718,66667729,8733068,32250045,35456853,44889423,14947650,96726697,62452034,83210802,86022504,79136082,73235980,1569515,62496012,22108665,92803766,7919588,92787493,40865610,72738685,75407347,63967300,23848565,33237508,51047803,6153406,69786756,55247455,24826575,6793819,5111370,63059024,64157906,42307449,3233084,75986488,38061439,80014588,72373496,14349098,72357096,20885148,53842979,23740167,17764950,32161669,98943869,33935899,65851721,43506672,54199160,7423788,16971929,60176618,9599614,36753250,9860195,97783876,83948335,53632373,79922758,94711842,176257,86301513,4064751,26863229,5640302,79821897,17385531,68694897,94539824,57241521,40781854,29994197,8634541,60430369,19376156,30218878,7685448,58749,57163802,60955663,44847298,30543215,19939935,75777973,26493119,62693428,39171895,20002147,92692283,57248122,70372191,40781449,78549759,22879907,62232211,73786944,14731700,3233569,72274002,41092172,76315420,72725103,85116755,9603598,93933709,22942635,1431742,46870723,57359924,15015906,20568363,77620120,44177011,17386996,749283,40984766,76671482,21001913,49236559,36396314,33061250,76540481,54427233,44550764,54762643,88653118,95581843,31904591,48893685,78589145,27625735,36780454,17037369,63152504,47792865,2917920,84840549,39553046,21070110,4668450,36812683,32426752,45848907,81853704,29029316,73392814,37620363,50702367,34698463,26664538,77413012,8129978,59371804,84904436,61373987,27665211,16019925,54663246,68939068,48675329,80316608,70004753,53802686,1788101,87720882,37891451,64602895,74110882,53666583,18131876,71316369,13231279,16054533,66271566,29466406,55189057,12024238,96735716,19101477,42199455,64848072,45794415,15064655,68000591,78602717,3487592,68875490,67030811,85242963,26998766,77272628,18699206,99515901,18783702,55960386,89811711,13468268,19486173,66903004,41481685,93270084,90272749,1197320,91802888,73617245,17146629,15536795,18806856,17957593,89214616,28796059,23569917,29819940,71300104,72777973,50668599,36685023,48260151,93515664,3509435,2204165,37192445,24168411,11365791,99690194,3773993,36312813,8807940,77284619,82779622,91240048,15075176,49882705,74452589,61960400,5267545,12571310,90457870,16387575,99333375,86821229,36505482,36933038,508198,569864,33797252,70036438,65880522,62803858,32159704,69136837,21289531,7066775,73124510,16380211,49598724,8913721,9175338,55602660,63372756,14326617,91664334,54058788,16405341,62430984,52060076,38365584,54517921,8651647,7011964,30569392,35092039,71657078,59981773,99658235,68644627,19457589,90004325,78218845,29510992,92692978,24591705,83302115,7955293,51464002,9058407,91990218,874791,56484900,62051033,39986008,88047921,57803235,45996863,53547802,36845587,83533741,82427263,9188443,23194618,45995325,27185644,85571389,91957544,51149804,4508700,31126490,27325428,42073124,6505939,30764367,415901,34497327,12348118,67451935,29959549,77694700,26744917,40027975,29932657,48892201,41245325,69355476,22129328,38341669,68824981,55669657,9398733,3880712,26392416,34698428,52261574,41092102,43357947,526217,20140249,36189527,45075471,3294781,66250369,112651,11543098,44915235,89217461,38009615,79880247,40534591,45237957,26507214,44060493,87710366,80555751,33565483,92071990,83150534,59910624,15948937,57961282,61859581,34946859,2891150,25636669,49271185,84187166,19272365,17365188,81805959,96420429,97057187,92604458,87598888,62936963,20122224,40152546,2717150,97281847,66832478,15535065,83269727,28787861,17539625,95251277,10358899,99604946,82178706,13470059,68204242,84127901,36930650,77377183,6497830,3235882,36580610,92554120,30090481,4204661,87160386,34044787,30787683,36808486,168541,669105,38494874,90013093,34295794,73222868,91141395,99971982,90061527,1022677,55753905,68102437,4434662,10366309,69641513,43322743,33304202,45790169,17068582,55143724,98948034,18833224,64087743,20642888,9257405,79806380,8128637,97940276,4787945,11757872,30811010,75135448,74743862,32274392,99549373,78909561,16684829,30891921,82897371,37224844,77300457,45919976,98739783,59177718,99917599,92867155,62490109,72019362,45049308,99021067,10961421,61741594,24953498,94516935,18001617,58208470,4119747,61982238,25842078,47708850,59501487,45667668,41442762,53648154,83368048,96852321,57240218,37501808,44348328,68316156,67281495,5970607,65338021,56955985,58180653,20836893,3633375,11923835,64055960,40686254,79322415,68110330,66663942,44983451,19891772,77072625,99861373,89046466,90654836,66319530,79191827,86242799,7517032,83133790,11581181,8791066,59614746,13348726,92541302,47361209,26139110,13173644,39373729,44846932,65081429,45407418,84684495,37183543,89419466,60102965,97641116,49641577,56515456,90310261,33249630,71333116,46851987,78766358,61141874,54014062,21919959,99297164,9886593,31161687,50007421,82532312,43501211,23110625,99965001,30694952,78884452,98371444,93057697,65454636,26292919,6871053,95010552,67513640,76170907,30139692,53084256,92215320,44784505,22450468,28928175,33628349,91727510,58751351,5832946,44479073,18411915,77312810,12664567,71920426,94911072,82726008,94360702,30463802,16445503,82651278,48673079,81898046,51426311,24314885,37659250,6038457,58224549,91281584,79942022,1204161,93566986,20486294,27041967,39201414,73031054,32590267,94090109,30163921,50188404,57020481,35996293,33123618,15148031,74357852,4806458,76330843,30395570,37739481,31491938,70420215,70367851,1250437,26114953,26776922,80246713,88698958,56531125,67227442,48088883,52613508,75153252,4515343,42644903,73168565,77301523,26102057,32699744,7104732,7300047,6521313,9829782,40197395,6111563,75128745,2331773,57658654,56461322,78785507,26063929,83083131,45381876,82886381,8373289,22721500,92398073,91255408,321665,42947632,51715482,68128438,83747892,17727650,10649306,83155442,72278539,83789391,82339363,49597667,73021291,36135,63015256,43045786,16424199,27187213,60581278,5073754,21533347,82979980,33895336,55470718,93359396,68041839,72495719,52204879,47887470,72614359,86118021,74724075,74614639,14093520,22997281,54868730,88904910,79657802,67474219,95290172,22766820,6819644,72238278,51507425,38022834,51588015,94595668,3183975,75543508,98462867,61728685,99524975,27411561,65275241,66611759,45617087,97379790,83651211,63506281,66885828,47213183,53393358,22435353,96709982,18663507,53888755,81774825,28851716,20765474,29065964,4091162,36139942,8099994,35512853,52293995,4069912,61897582,81677380,44473167,47893286,59109400,89699445,7502255,42782652,84406788,65715134,97561745,59197747,12416768,6157724,18504197,69579137,33699435,26734892,4798568,51466049,1936762,24619760,22200378,61815223,82327024,86543538,43152977,68702632,85711894,33201905,37560164,26397786,86798033,65271999,48658605,55770687,65047700,19898053,28734791,36468541,92998591,72793444,72358170,15902805,61623915,93790285,98130363,81274566,32058615,12303248,14220886,10597197,84002370,21673260,88444207,89499542,11161731,33431960,34493392,42237907,75052463,82052050,93562257,98653983,37957788,70727211,6221471,47298834,74441448,93053405,15163258,34432810,49328608,81172706,65074535,88251446,48395186,46540998,70800879,40356877,54232247,14626618,29635537,5822202,4985896,59329511,66322959,21397057,8115266,66116458,33553959,4199704,59318837,54263880,76825057,90090964,38510840,33336148,95726235,20645197,62762857,63628376,89637706,9259676,31727379,98648327,78300864,95395112,44844121,44664587,44245960,73109997,89996536,13862149,45990383,77787724,44481640,32151165,78561158,62357986,34667286,92530431,85023028,18466635,57393458,99226875,55615885,70782102,29401781,89078848,75820087,45428665,30653863,68816413,51590803,77898274,81855445,38008118,23134715,28550822,64098930,5482538,6525948,8696647,71522968,10309525,30366150,50567636,31733363,84349107,61928316,69848388,4978543,41380093,17016156,8729146,56473732,38256849,62552524,47090124,26275734,44842615,17058722,47738185,69605283,17894977,91938191,67031644,3088684,65017137,71965942,62428472,14723410,20867149,17976208,65038678,89804152,10264691,10453030,85117092,99729357,60393039,42692881,62115552,51315460,48360198,45306070,91831487,92353856,8055981,23432750,70541760,29516592,69697787,7229550,69255765,16567550,55850790,72732205,61712234,17857111,14363867,96318094,88130087,37166608,13819358,66137019,85695762,14045383,67084351,54606384,1375023,824872,67124282,84166196,90158785,20681921,22405120,22113133,99125126,24733232,79738755,37280276,80251430,1239555,59405277,96906410 +84840549,73124510,14326617,99658235,9623492,89214616,68702632,30787683,97940276,5267545,76434144,35092039,40534591,19376156,23569917,29819940,5111370,80316608,98943869,83368048,1788101,41245325,15536795,6986898,37620363,80251430,22942635,64087743,42644903,36930650,13173644,18663507,63967300,72495719,247198,21070110,16684829,30694952,79136082,45996863,17016156,74614639,93053405,18466635,17764950,36685023,80555751,27665211,48774913,76703609,36505482,79922758,18131876,94076128,18783702,40027975,22879907,72373496,64098930,38061439,30543215,1022677,1431742,43501211,9886593,32250045,62936963,60955663,99917599,20642888,44889423,72738685,44348328,40865610,16424199,73235980,65047700,13468268,66885828,38365584,45995325,16097038,97783876,73392814,63372756,33304202,39553046,168541,83302115,6808825,47213183,13231279,15535065,31904591,62115552,65074535,3509435,10961421,16445503,81677380,81898046,7502255,26744917,33123618,40152546,62496012,77312810,96726697,35456853,93933709,11365791,36780454,37659250,39986008,92033260,43933006,78766358,34432810,61263068,82052050,9603598,68204242,56515456,34295794,7919588,48675329,29029316,66045587,38658347,82897371,61373987,78884452,48260151,10366309,53842979,54762643,32161669,51715482,73031054,2917920,40677414,92787493,55770687,90272749,71469330,17727650,71657078,63628376,57803235,55753905,37166608,96193415,34497327,79322415,56484900,43376279,84684495,75986488,669105,30139692,15902805,43045786,90090964,45075471,23194618,83150534,62490109,50668599,23848565,47738185,71333116,72777973,4668450,8115266,19891772,69136837,54199160,16054533,3294781,3233084,24591705,7646095,62803858,65338021,33628349,68041839,78602717,51047803,91664334,91802888,19898053,36139942,6157724,92803766,2717150,10358899,62693428,7517032,90457870,24915585,76960303,98371444,7300047,69355476,40984766,22766820,70004753,77377183,112651,98130363,29959549,29994197,38645117,51149804,63506281,33249630,52293995,61897582,79880247,14731700,4099191,7687278,41481685,6153406,59197747,16380211,20486294,55615885,75229462,86022504,34698428,8634541,17894977,26292919,14626618,88047921,4434662,60430369,62357986,39373729,99515901,30163921,24953498,874791,29188588,78909561,43322743,92215320,30366150,83083131,95581843,29466406,3233569,19457589,87710366,30463802,73222868,95726235,74137926,83210802,19486173,10597197,87720882,12348118,39801351,62452034,22129328,60102965,92398073,18833224,66663942,97057187,569864,50806615,17068582,24058273,85242963,12416768,29065964,36753250,5822202,8696647,48893685,85571389,78300864,26392416,33895336,26114953,17957593,66322959,59981773,88698958,85116755,21533347,88653118,89419466,48892201,36933038,93562257,30764367,22108665,98653983,43152977,77272628,66271566,50188404,6521313,10309525,45667668,4064751,42073124,73168565,95957797,25636669,11543098,78589145,61271144,72274002,36580610,47298834,52613508,79657802,42307449,82726008,68816413,77187825,75777973,44844121,66428795,55143724,37435892,56955985,30090481,50702367,67030811,70596786,9398733,67227442,96852321,33061250,16099750,31727379,49328608,6871053,26507214,47361209,62552524,99297164,45919976,69255765,69641513,61728685,53802686,60581278,44550764,2891150,37739481,76315420,95395112,99226875,30998561,46851987,99971982,9188443,53393358,14093520,98462867,46870723,65851721,70727211,29834512,4985896,36812683,28796059,56473732,75153252,32590267,8913721,53084256,89699445,16387575,86001008,42967683,26139110,35192533,16971929,20122224,13348726,66667729,68316156,99021067,74110882,74743862,38008118,28851716,58751351,82532312,79191827,11161731,30891921,39171895,64157906,65017137,81805959,21993752,72732205,70541760,91727510,99333375,45237957,61141874,16567550,36845587,71316369,66611759,61982238,68644627,99965001,37183543,49598724,57658654,81172706,44245960,4806458,97641116,8791066,67281495,98648327,22997281,7955293,20568363,45306070,44983451,526217,59501487,40781449,57163802,18411915,61623915,56531125,53547802,41442762,3487592,70036438,92692978,37192445,83533741,84293052,48673079,54058788,15064655,98948034,1569515,94516935,78549759,321665,73021291,9860968,59371804,92692283,69848388,54014062,37224844,508198,99729357,30811010,14947650,82339363,7066775,43506672,22435353,64055960,47090124,2607799,30395570,97561745,9860195,36312813,3633375,28550822,96420429,89217461,92071990,3183975,74357852,39847321,30218878,81274566,22450468,95290172,8651647,99549373,37675718,17976208,33797252,3880712,8733068,4095116,64602895,67793644,12571310,89637706,27187213,60106217,49641577,92604458,34493392,64848072,36808486,99690194,1197320,51466049,53666583,9599614,47708850,19939935,19272365,68694897,44915235,66903004,62740044,24619760,65454636,86242799,42947632,20002147,42782652,66319530,65715134,3773993,40356877,34946859,9257405,51588015,93566986,17386996,13470059,7423788,55247455,21001913,83155442,20765474,415901,36396314,61928316,50007421,16595436,72725103,50567636,71300104,83133790,66250369,59177718,26397786,72019362,18001617,61859143,44481640,30569392,30771409,62428472,2208785,12024238,20885148,33553959,2204165,99524975,87598888,33201905,60393039,79821897,63015256,14723410,86460488,76170907,38009615,81853704,6793819,15015906,91831487,47792865,67084351,44177011,85117092,76540481,40197395,79942022,44060493,70800879,749283,29635537,8729146,91141395,77413012,45848907,78785507,75128745,77898274,72358170,8373289,91957544,4787945,52060076,75543508,49271185,3088684,55470718,66832478,77284619,5640302,48658605,62762857,90310261,56424103,67451935,44842615,27185644,7685448,19101477,24314885,55189057,9058407,77072625,4119747,42199455,12303248,38341669,57248122,75135448,67124282,62051033,70420215,82886381,57020481,77620120,99604946,23740167,83789391,68102437,24826575,45049308,18504197,73786944,93270084,59614746,26734892,5832946,40686254,31161687,68875490,8129978,89046466,55602660,91281584,97011160,54987042,94711842,94911072,70372191,29932657,44664587,9175338,95010552,69786756,45990383,84349107,33237508,38494874,94595668,21673260,60176618,92554120,92541302,76330843,54427233,89811711,31126490,63059024,59910624,61960400,98739783,55669657,24168411,77301523,93057697,71920426,88904910,51472275,17037369,63152504,90654836,18699206,76671482,91990218,22200378,44479073,32159704,54868730,7182642,39201414,10649306,61859581,80014588,96906410,24733232,59109400,49236559,9259676,81855445,69605283,57961282,88444207,76825057,17365188,53648154,90061527,56153345,78218845,97379790,8055981,62232211,86821229,35512853,10264691,65038678,18806856,7229550,84187166,83948335,14349098,62430984,53632373,57359924,58224549,6525948,85711894,32274392,83651211,65081429,26063929,72357096,94090109,6497830,41092172,54606384,75407347,49882705,84904436,34236719,6505939,37280276,52204879,69579137,33336148,51507425,30653863,9575954,86301513,79806380,5073754,72278539,66137019,57241521,70782102,20645197,59318837,32426752,23432750,84166196,13862149,4069912,51464002,37501808,56461322,57240218,52261574,93359396,99861373,87160386,16019925,15948937,8807940,93790285,72614359,7011964,94539824,23110625,26493119,84002370,25842078,6038457,21919959,58749,34044787,37560164,16405341,55850790,34698463,48088883,8099994,47887470,78561158,11923835,38510840,8128637,77694700,65271999,57393458,48395186,27325428,74724075,74441448,45428665,22721500,96709982,49597667,35996293,15075176,17385531,1204161,36468541,45794415,51590803,82779622,26275734,73617245,84127901,13819358,71522968,824872,34667286,22113133,28928175,44473167,90013093,36189527,17146629,29516592,4508700,51426311,91240048,26998766,22405120,91938191,54517921,44784505,4515343,54663246,92867155,83747892,68000591,67513640,86118021,61712234,9829782,44627776,43357947,33935899,75052463,85023028,7104732,17058722,94360702,41092102,48360198,46540998,92998591,95251277,1375023,38256849,82979980,44847298,2331773,67474219,31491938,86798033,74452589,17539625,90004325,32058615,26664538,59329511,6819644,33565483,37891451,65275241,68824981,72793444,14045383,15148031,11581181,75820087,21397057,97281847,54232247,29510992,1250437,82178706,27411561,71083565,5482538,54263880,96318094,93515664,61741594,99125126,4798568,38022834,82427263,27625735,44846932,33699435,58180653,45381876,99224392,32151165,36135,33431960,28734791,67031644,6221471,14363867,20867149,61815223,96735716,89078848,45617087,45790169,4199704,71965942,29401781,17081350,3235882,45407418,17857111,176257,88251446,82651278,6111563,4978543,68128438,20681921,77300457,69697787,79738755,58208470,26102057,26863229,14220886,53888755,65880522,82327024,32699744,83269727,88130087,70367851,66116458,15163258,10453030,77787724,73109997,21289531,55960386,90158785,80246713,41380093,42692881,27041967,4091162,86543538,68110330,26776922,20140249,81774825,72238278,89804152,4204661,5970607,31733363,85695762,37957788,11757872,92353856,47893286,20836893,42237907,23134715,40781854,1936762,91255408,51315460,59405277,89499542,1239555,68939068,92530431,28787861,12664567,89996536,84406788 +56461322,63015256,74614639,48260151,91664334,54987042,92803766,4119747,6153406,12348118,33237508,7011964,75153252,88047921,33553959,9398733,72777973,4069912,67793644,89214616,62452034,15064655,19891772,21070110,73168565,97641116,34493392,30218878,87710366,3294781,80316608,62496012,68644627,669105,26114953,68204242,45306070,62936963,44846932,42967683,51507425,35092039,415901,62428472,62115552,54199160,73392814,57240218,45237957,22450468,55143724,54427233,65715134,1431742,65454636,48675329,13468268,92692978,68816413,81855445,59177718,99965001,70541760,29188588,48893685,34236719,29466406,9175338,78549759,26776922,97783876,93933709,45995325,99690194,83368048,99297164,90061527,57163802,79806380,6986898,93566986,5832946,10961421,95290172,874791,29819940,60430369,76671482,31904591,47738185,77377183,79136082,88653118,99226875,70004753,78785507,49598724,32161669,36468541,19272365,19376156,33628349,56473732,33797252,77284619,40781449,38658347,26139110,44983451,37183543,14093520,36812683,4787945,76540481,50806615,4099191,68316156,34698428,14326617,35192533,45919976,14731700,55753905,60102965,66137019,20765474,92071990,96193415,40152546,40677414,47708850,53084256,48673079,72238278,64087743,16445503,6793819,90654836,9886593,8807940,66322959,44889423,27665211,70372191,59318837,47792865,30891921,15015906,8913721,16097038,61263068,90090964,71469330,74357852,11161731,38061439,91281584,22129328,93053405,24733232,94911072,36808486,31161687,3233084,7687278,60955663,96726697,21289531,20486294,43045786,749283,17058722,77072625,12571310,68824981,62430984,92787493,46851987,32426752,70596786,62232211,85116755,54606384,67030811,63967300,24619760,9623492,81172706,79821897,32699744,9188443,45407418,81853704,30139692,94076128,51047803,36753250,43501211,91727510,22108665,17386996,4806458,63059024,66832478,50702367,40356877,48395186,83210802,68694897,22879907,3633375,8099994,508198,80246713,81274566,96420429,33249630,5267545,30395570,28550822,21673260,99917599,17957593,72738685,54663246,72278539,75135448,13231279,73617245,76330843,7517032,81898046,38009615,12024238,95395112,72373496,23569917,18131876,44550764,26102057,32590267,78909561,77620120,73031054,71657078,42644903,78602717,76960303,7919588,44245960,80555751,55615885,18783702,82327024,53842979,569864,83302115,56424103,57803235,7229550,9860968,83083131,48088883,37739481,168541,88444207,92033260,49271185,28928175,69641513,34497327,30787683,42307449,36396314,16595436,86022504,59197747,35996293,22200378,33123618,92604458,70800879,42199455,49641577,14626618,63506281,16424199,78218845,92554120,90272749,112651,59614746,36580610,56531125,74137926,77312810,16971929,61271144,71316369,53632373,82532312,7423788,81677380,28851716,34295794,247198,29029316,98371444,16380211,12416768,59501487,67281495,33304202,7502255,78589145,66663942,5970607,36505482,43506672,37620363,43933006,23110625,32274392,29994197,50007421,5111370,40027975,20140249,36189527,74441448,8373289,20122224,89811711,30543215,66611759,63628376,18699206,5640302,3235882,51590803,43322743,86821229,18466635,70420215,37166608,2891150,72357096,16019925,95957797,2917920,41481685,45996863,83150534,27625735,69355476,51472275,94360702,89637706,66271566,65047700,30569392,25842078,10649306,93515664,1204161,30764367,38645117,6871053,49328608,57020481,55602660,78561158,79657802,20885148,86242799,54058788,3233569,38008118,53547802,48360198,89078848,22942635,64157906,92215320,50188404,38494874,39847321,83789391,3509435,75986488,78766358,29510992,56515456,7104732,82052050,18663507,1375023,85242963,37501808,76434144,67474219,76703609,36930650,4985896,99333375,7955293,33895336,59910624,99658235,73124510,7300047,39171895,17037369,54762643,57241521,72019362,33431960,4668450,40781854,92353856,98739783,43152977,61623915,65275241,61741594,96318094,9860195,40686254,7685448,24953498,55770687,80014588,51715482,15163258,50668599,71333116,40865610,97940276,48892201,61897582,99861373,1022677,65017137,88130087,73235980,76170907,66903004,59371804,75543508,83948335,82779622,51588015,61859581,24168411,92998591,42692881,37659250,77413012,79191827,35456853,6808825,63372756,33201905,26063929,43376279,92692283,72732205,84187166,81774825,13173644,24591705,70727211,3487592,75052463,99549373,22997281,89419466,71300104,64602895,37957788,8696647,37675718,1250437,45667668,74743862,30653863,72495719,67031644,50567636,64098930,24915585,17764950,3183975,20836893,99524975,14947650,35512853,17385531,31491938,86301513,71522968,70036438,2208785,82726008,36685023,95726235,9603598,16099750,6221471,32159704,65338021,57658654,79922758,52613508,17539625,15148031,10597197,10358899,34432810,59109400,48658605,15948937,72274002,11365791,92398073,68102437,27325428,42237907,15535065,36845587,29959549,93359396,23432750,321665,25636669,45428665,75777973,61859143,94539824,24058273,23740167,66116458,77694700,69136837,55247455,36780454,96852321,98130363,19101477,21993752,73222868,41092102,74110882,97561745,91957544,8634541,77300457,26998766,8651647,82339363,8733068,62740044,23194618,99021067,52060076,98462867,54014062,8129978,86543538,49236559,3880712,87160386,39986008,66319530,20002147,33699435,67227442,13348726,86001008,85023028,62803858,68939068,44842615,1197320,73786944,4798568,20568363,2204165,42073124,79738755,176257,19486173,55189057,23848565,59981773,79880247,7066775,83155442,94711842,15536795,90013093,17146629,42947632,45794415,88698958,84293052,45617087,37891451,11581181,47298834,30998561,45848907,67084351,73021291,19898053,13862149,66250369,47361209,45790169,83651211,39553046,64848072,93562257,56955985,41245325,65271999,82178706,17068582,65880522,68875490,29834512,10366309,99125126,40534591,26493119,53666583,61728685,16567550,83533741,14045383,29635537,91240048,87720882,97011160,97057187,26292919,60581278,85711894,84904436,34698463,4434662,24314885,17976208,20642888,85571389,47090124,4091162,65074535,44664587,17016156,16684829,11543098,33935899,86798033,9575954,61141874,54868730,83747892,62357986,4064751,89046466,11923835,69697787,2717150,32250045,68128438,69786756,63152504,32151165,91802888,21533347,61928316,38365584,71920426,4095116,26734892,99224392,84349107,40197395,18411915,526217,26275734,20681921,6525948,27411561,95581843,61815223,96735716,80251430,84127901,62693428,15075176,89699445,61982238,47213183,77301523,52261574,8791066,9829782,61373987,48774913,17894977,23134715,17727650,75407347,21001913,58751351,61960400,24826575,84002370,38256849,90310261,10309525,58180653,44060493,46870723,82651278,96709982,22435353,99971982,84406788,45381876,58749,19457589,77187825,52293995,21919959,37435892,67124282,41092172,1936762,4199704,6819644,4515343,19939935,31733363,61712234,41380093,68041839,81805959,30771409,77787724,31126490,14220886,97379790,62490109,27041967,30163921,30463802,51466049,34946859,51464002,44847298,59405277,29065964,85117092,7646095,44784505,72358170,26392416,89804152,28734791,91938191,37192445,68702632,74452589,75128745,26863229,56153345,82427263,47887470,42782652,44348328,66045587,65038678,66885828,43357947,26397786,70367851,14349098,92541302,30366150,83133790,26507214,15902805,83269727,30811010,98648327,8115266,99604946,95251277,86460488,29932657,84684495,44177011,22721500,39373729,85695762,18504197,69605283,14723410,72793444,75229462,4204661,29516592,39201414,69579137,53648154,57248122,72725103,52204879,67451935,4508700,5822202,54517921,6497830,22766820,18806856,58208470,90004325,55669657,4978543,44481640,59329511,69848388,28796059,44627776,57393458,84840549,44915235,91990218,17857111,18001617,82897371,17081350,39801351,99729357,6505939,88904910,9058407,46540998,6038457,79942022,91831487,30694952,76315420,87598888,54232247,82886381,27187213,9599614,69255765,79322415,37224844,6521313,94516935,75820087,38022834,9257405,88251446,40984766,98948034,62051033,16054533,6157724,62552524,73109997,66667729,93057697,5482538,89217461,92867155,36139942,57359924,74724075,56484900,51426311,90158785,86118021,14363867,55850790,90457870,98653983,78884452,5073754,60176618,20645197,76825057,44844121,66428795,51315460,91141395,55470718,53802686,28787861,92530431,18833224,38510840,8128637,33336148,10264691,33565483,41442762,93270084,49597667,20867149,97281847,71083565,65851721,3088684,7182642,12303248,94090109,34667286,11757872,72614359,93790285,68000591,16405341,82979980,68110330,26664538,10453030,45075471,94595668,44479073,16387575,1788101,57961282,26744917,2607799,98943869,51149804,31727379,36135,22405120,55960386,45990383,34044787,78300864,22113133,36312813,27185644,77272628,3773993,60106217,17365188,2331773,30090481,89996536,824872,9259676,32058615,91255408,77898274,58224549,65081429,29401781,37560164,44473167,70782102,64055960,49882705,6111563,13470059,89499542,71965942,12664567,99515901,54263880,47893286,96906410,62762857,1569515,13819358,95010552,8055981,8729146,45049308,53888755,67513640,36933038,84166196,38341669,60393039,1239555,33061250,37280276,53393358,21397057 +9623492,75153252,19101477,39201414,14326617,3183975,63967300,38658347,33304202,22108665,6497830,70596786,68702632,35456853,67030811,6793819,24591705,30463802,69136837,12348118,74441448,73168565,10366309,44245960,89699445,34698428,68102437,76540481,20836893,39986008,17957593,10961421,71333116,2717150,59501487,67793644,78884452,31904591,49882705,71657078,9058407,10358899,99125126,8115266,97057187,3487592,62430984,5832946,60430369,30543215,20002147,6871053,51047803,569864,48892201,77072625,91664334,13231279,8099994,98371444,76170907,61373987,55143724,40152546,51472275,83302115,86821229,47361209,77312810,69355476,91802888,112651,3233084,22450468,59109400,70420215,91141395,93933709,96318094,38494874,85116755,98739783,70800879,3509435,99549373,8129978,43322743,49597667,37675718,11543098,20122224,47090124,27411561,70727211,5970607,38510840,96420429,37620363,93562257,35092039,62232211,83083131,415901,96193415,9603598,31733363,77620120,9175338,10649306,12024238,92692283,36396314,38645117,7300047,20885148,99658235,72738685,36812683,6819644,90272749,53084256,52293995,23194618,61982238,26275734,29994197,44348328,96709982,40984766,36933038,63628376,23569917,67451935,9860968,37183543,12571310,33628349,12416768,35996293,99604946,27665211,98130363,85242963,2891150,61859581,59910624,72732205,37166608,19939935,30569392,65851721,53393358,66885828,70541760,508198,73786944,33699435,26493119,44983451,73031054,21673260,61263068,60581278,86022504,87598888,29819940,73222868,66250369,55753905,526217,7502255,95957797,44550764,73235980,18833224,48360198,72725103,22129328,80316608,48774913,90061527,9188443,82339363,33061250,40197395,7646095,29510992,80555751,42237907,78300864,25842078,59197747,15535065,79657802,45990383,28796059,49641577,4515343,45049308,61741594,4668450,99297164,45790169,62452034,91281584,3088684,65047700,6521313,32426752,54663246,89419466,36580610,39847321,81677380,47213183,55247455,6153406,6986898,61859143,9259676,57803235,89214616,90004325,33553959,89811711,7011964,23848565,66663942,74137926,97940276,32159704,71469330,60955663,51315460,36685023,16380211,68644627,20642888,36753250,43357947,41092102,37224844,99690194,5111370,79880247,26292919,14626618,54232247,92398073,26063929,98948034,86543538,57020481,81898046,8373289,83210802,81172706,54199160,64087743,30787683,91727510,51464002,49236559,55960386,29959549,87710366,17727650,75543508,72019362,99333375,52261574,65038678,50188404,61623915,91831487,15536795,4787945,77284619,99021067,16019925,93053405,45237957,39171895,31491938,77301523,44844121,1250437,62803858,9886593,44479073,18001617,62428472,66611759,57248122,4099191,68110330,45996863,64098930,33935899,17058722,13468268,1431742,54868730,14349098,36808486,43045786,40781854,73124510,35192533,33123618,56153345,30163921,93057697,7955293,55189057,55770687,59371804,64848072,43376279,32250045,92998591,92692978,98462867,45794415,82178706,1569515,30090481,69579137,42782652,72357096,33797252,33895336,84840549,53648154,33565483,73109997,88698958,68939068,54058788,94090109,77413012,33201905,5267545,62936963,57359924,42967683,37560164,9860195,56531125,66322959,2917920,17764950,1204161,16567550,29466406,31161687,65017137,53666583,98648327,29029316,56473732,65074535,72373496,84684495,99515901,33237508,89046466,20867149,75229462,72495719,75135448,27041967,67031644,86301513,80251430,321665,20140249,78602717,29834512,3235882,12303248,88047921,70004753,95395112,44842615,20681921,22942635,22997281,24058273,6038457,57163802,61728685,66116458,51588015,8696647,92541302,94516935,24619760,824872,4978543,72274002,17068582,22721500,33249630,16684829,40865610,77787724,26664538,62552524,56515456,78909561,84187166,16445503,52204879,1022677,16097038,76671482,92071990,54606384,22435353,7423788,85023028,44915235,57658654,87720882,78785507,30694952,36189527,74357852,95010552,14723410,83133790,82897371,89078848,17365188,72777973,30764367,45075471,8634541,14220886,28851716,86001008,82052050,81853704,37501808,66137019,57240218,65081429,48893685,7182642,75777973,37435892,2208785,2204165,29932657,97783876,57393458,95581843,34667286,59177718,19272365,8055981,3633375,46851987,4119747,57961282,53842979,4095116,73021291,81855445,96735716,68204242,82726008,34236719,24915585,68875490,61271144,91957544,84166196,43152977,874791,89804152,99965001,62496012,33336148,77187825,27325428,59614746,42307449,99226875,168541,96852321,84127901,67124282,5482538,24826575,49328608,44473167,7919588,34698463,15948937,45919976,82779622,6808825,36468541,82427263,55669657,15064655,44481640,22879907,39801351,28734791,44177011,59329511,42644903,24168411,82327024,56424103,38365584,72278539,83533741,61815223,32161669,30218878,55850790,15015906,92554120,52060076,65338021,79922758,16387575,21993752,30395570,66832478,38008118,40027975,11161731,4798568,69255765,69786756,56484900,22766820,20486294,11923835,91938191,61960400,18806856,30998561,67084351,15075176,30366150,4064751,247198,3880712,38061439,66903004,40686254,13862149,14093520,26507214,65275241,44060493,68041839,10309525,84349107,37739481,59981773,30653863,26102057,53888755,4985896,90013093,99917599,90457870,3773993,47792865,10453030,9575954,94539824,4508700,67227442,84406788,36505482,34044787,99524975,65715134,34295794,80014588,18783702,23110625,9398733,17976208,28550822,76434144,32590267,42692881,4806458,18504197,68824981,13173644,76330843,22200378,9257405,68816413,68316156,26734892,26863229,77300457,90090964,37659250,41481685,58749,99861373,44889423,12664567,72614359,69641513,71083565,51715482,71300104,72358170,21533347,44846932,30139692,93790285,26114953,62490109,669105,88653118,79136082,46870723,95290172,54427233,19376156,40677414,20765474,17037369,79738755,73392814,97011160,92604458,87160386,16971929,34946859,31126490,34432810,45848907,14363867,10597197,18466635,36312813,45617087,92033260,50567636,75128745,16595436,45407418,98943869,23134715,34497327,42199455,97561745,94076128,7685448,76315420,42073124,8729146,1197320,83368048,95726235,86460488,17146629,66428795,6221471,47887470,51426311,8791066,17386996,24733232,64055960,83150534,92803766,63506281,44784505,62357986,74743862,50702367,82979980,93566986,1375023,53632373,26392416,3233569,36780454,65880522,43506672,6505939,31727379,22405120,26397786,74110882,37192445,84293052,98653983,85117092,13470059,33431960,13819358,44627776,18663507,78766358,17385531,5822202,21001913,46540998,15902805,4204661,81274566,94711842,89996536,88251446,11581181,29516592,62115552,22113133,83155442,89637706,38341669,19486173,14045383,75986488,4199704,78549759,80246713,26744917,2607799,77272628,14731700,70036438,89499542,99224392,1788101,7104732,27625735,48658605,17857111,61897582,5073754,88444207,18411915,97641116,45306070,63059024,65454636,15148031,50668599,749283,70367851,73617245,69848388,75820087,8733068,48675329,75407347,78589145,51507425,21397057,18131876,57241521,38022834,67281495,72793444,54014062,52613508,43933006,96726697,90158785,35512853,4069912,30891921,7229550,91990218,26998766,54762643,81805959,64157906,68694897,7517032,51149804,77898274,4434662,15163258,67474219,48395186,29188588,63152504,77694700,72238278,77377183,37891451,8807940,79806380,47893286,45428665,4091162,47738185,47708850,32699744,83948335,37957788,10264691,78218845,50007421,41092172,40781449,60106217,92215320,58751351,61712234,48673079,55470718,55602660,84904436,34493392,88904910,91255408,58208470,49271185,36845587,42947632,79322415,45667668,39553046,79942022,7687278,17016156,53802686,17081350,84002370,71965942,68128438,54987042,20568363,62740044,8651647,40534591,43501211,30771409,26139110,16405341,16054533,53547802,94360702,11757872,51466049,83269727,76703609,70782102,58180653,19891772,27185644,1239555,6111563,63015256,8913721,23740167,89217461,41442762,83651211,29401781,92353856,74724075,71920426,51590803,93359396,66667729,44847298,70372191,11365791,65271999,36135,79821897,9829782,48088883,90654836,19457589,5640302,54263880,69605283,6525948,18699206,92787493,93270084,83789391,66319530,21919959,92867155,64602895,32274392,17539625,40356877,48260151,85571389,76825057,16099750,21070110,71316369,17894977,8128637,81774825,93515664,62693428,95251277,50806615,6157724,61928316,97281847,90310261,97379790,86798033,3294781,45381876,14947650,62051033,85711894,63372756,56955985,23432750,59318837,82886381,60102965,25636669,66271566,99971982,58224549,86118021,26776922,74614639,69697787,7066775,79191827,45995325,16424199,28928175,29065964,68000591,47298834,82532312,86242799,54517921,59405277,60176618,92530431,24953498,66045587,78561158,74452589,44664587,2331773,55615885,41245325,71522968,29635537,76960303,60393039,85695762,99729357,32151165,20645197,13348726,36930650,94911072,82651278,21289531,83747892,61141874,24314885,56461322,37280276,28787861,38009615,19898053,94595668,62762857,41380093,49598724,96906410,27187213,9599614,30811010,75052463,176257,38256849,32058615,36139942,39373729,91240048,67513640,88130087,1936762 +73031054,81853704,39171895,65851721,26998766,26863229,92692283,10366309,98739783,23110625,54427233,54663246,38061439,6793819,4985896,22108665,29932657,16971929,61271144,40677414,38008118,77284619,15535065,56473732,33628349,87710366,12348118,44889423,68702632,17957593,2717150,30543215,55602660,24591705,58180653,75153252,60176618,70727211,83210802,36505482,23569917,3633375,98462867,52293995,17146629,56484900,84187166,63967300,3509435,77377183,37560164,15948937,96193415,38341669,32250045,25636669,54014062,8807940,28851716,53084256,29819940,8129978,44348328,59109400,77312810,77272628,52060076,17068582,20122224,94911072,61263068,66611759,74110882,63015256,20765474,29510992,77187825,37739481,25842078,81274566,88047921,11161731,93270084,29834512,45990383,16019925,48088883,26493119,29188588,83651211,55770687,91664334,16445503,22450468,49882705,72777973,81898046,15075176,66832478,9398733,57803235,13231279,54987042,89811711,72495719,97940276,18833224,61373987,29029316,30653863,9623492,72732205,35456853,78561158,2891150,16099750,7423788,48360198,35192533,68041839,55143724,89046466,43933006,32159704,79821897,43322743,34432810,29466406,92541302,3773993,4069912,74452589,36685023,14723410,17037369,74724075,99965001,66319530,98130363,4099191,62232211,92215320,55247455,95290172,55669657,75820087,17764950,37675718,4668450,61897582,72793444,86821229,44060493,72738685,64848072,42947632,8115266,91990218,91240048,36468541,68816413,38365584,64602895,79136082,22435353,79191827,43376279,69136837,17386996,19376156,68824981,10358899,24915585,16424199,78549759,24058273,26102057,66663942,99333375,1431742,31161687,57248122,69355476,59197747,14093520,85023028,19891772,29065964,81805959,7955293,64087743,22942635,61928316,9860968,51472275,38494874,67793644,12024238,35092039,23194618,69848388,49236559,47298834,85571389,87598888,37891451,73222868,98948034,508198,68875490,99917599,64157906,89419466,66250369,1022677,89214616,62430984,27665211,72274002,112651,33304202,3880712,68204242,14626618,62936963,87720882,61859143,62496012,55753905,10597197,168541,85242963,54762643,66903004,91281584,23848565,68644627,1788101,2204165,51588015,86022504,99658235,47090124,3088684,31727379,50567636,84406788,40152546,67084351,30694952,12303248,6505939,79880247,60955663,4787945,61815223,74137926,7646095,4119747,92554120,26776922,71300104,15015906,28787861,8696647,48892201,7300047,57359924,38658347,51715482,75229462,61141874,67030811,82979980,16387575,83789391,96906410,66137019,99524975,51149804,24733232,69697787,35512853,84002370,19457589,16097038,32590267,71316369,8733068,96726697,96852321,30366150,92398073,9603598,81172706,70596786,14349098,91938191,39373729,526217,63059024,33201905,10961421,45995325,83155442,8128637,17539625,17058722,38256849,44473167,95395112,62115552,33237508,13468268,44842615,40865610,86001008,86460488,24826575,16595436,19939935,74743862,50702367,40781449,95581843,6153406,47708850,17894977,99297164,13173644,50188404,47361209,96709982,49271185,91802888,95726235,40356877,48774913,3294781,3183975,2917920,63628376,415901,3233084,46870723,93790285,16054533,76540481,71333116,88444207,32161669,70004753,6521313,46851987,93933709,62452034,33431960,6525948,11581181,33336148,37183543,76703609,7687278,75407347,47792865,41245325,33797252,77898274,65038678,30569392,92071990,85116755,30771409,10309525,45075471,48673079,16380211,42692881,30163921,824872,38645117,3487592,54058788,6819644,65074535,27625735,22879907,6871053,74357852,61859581,90061527,99549373,9058407,99971982,96318094,44550764,36580610,62740044,73235980,41481685,72373496,36396314,81855445,4806458,94539824,30395570,11365791,669105,77301523,66116458,42782652,99729357,49641577,6808825,72278539,75986488,23432750,37501808,67451935,69786756,42307449,1204161,99224392,23740167,39201414,92033260,30090481,68102437,90158785,34667286,66322959,30787683,45428665,6986898,33123618,70800879,82886381,21289531,77413012,247198,99604946,50668599,40686254,61741594,31733363,21533347,12416768,48675329,20645197,65715134,33553959,16684829,3233569,87160386,51426311,44479073,78602717,73021291,72725103,28796059,93515664,8099994,62552524,34698428,59910624,4095116,80014588,49597667,9259676,31904591,37280276,40197395,60430369,37435892,84293052,75777973,18001617,88130087,97379790,23134715,61728685,5640302,92803766,62357986,59371804,20885148,45790169,27325428,874791,68939068,15536795,99226875,38510840,97641116,17976208,27041967,43506672,44847298,60102965,39986008,79942022,56531125,76825057,53632373,78589145,66045587,9886593,93053405,14947650,73392814,33895336,45848907,45381876,84840549,5111370,67227442,97561745,70372191,7517032,14326617,44177011,37957788,17016156,42644903,92998591,82052050,26275734,36312813,80246713,20867149,66428795,30218878,8913721,91255408,93057697,78300864,37166608,83368048,27185644,95010552,8791066,62693428,42237907,26664538,63506281,73168565,32426752,76960303,55189057,56461322,9188443,59405277,57240218,93359396,7685448,65047700,34236719,22129328,77072625,10649306,99515901,36753250,95957797,33249630,79922758,70036438,67124282,37620363,84684495,47213183,90457870,34493392,321665,79806380,90090964,40027975,83133790,55470718,89699445,55960386,2208785,69641513,51464002,29401781,90013093,9257405,76330843,83150534,80251430,18131876,57241521,21993752,94090109,17727650,18411915,26744917,56515456,98371444,76434144,74614639,51047803,40984766,9175338,36780454,51466049,39553046,18663507,53842979,15148031,38022834,66271566,65454636,78785507,36845587,19101477,80316608,99861373,47738185,34497327,83302115,44844121,4199704,20836893,11543098,42199455,81677380,43045786,27411561,75052463,17365188,82339363,75135448,66667729,30764367,86798033,36812683,77300457,6221471,22721500,34295794,65275241,44245960,45617087,73617245,60106217,33935899,83533741,71657078,45919976,61960400,99021067,36189527,5832946,62803858,65017137,26063929,7011964,30998561,83083131,82897371,45667668,73124510,65338021,4798568,33061250,61623915,92604458,56153345,97783876,60393039,65880522,89499542,18699206,32058615,34698463,1569515,20642888,44627776,68316156,13348726,68128438,9575954,44915235,41442762,45237957,57658654,9599614,92353856,60581278,89996536,44784505,94076128,20568363,64098930,36930650,59501487,79657802,37224844,76671482,45306070,53393358,44983451,1197320,75128745,43501211,83948335,34946859,77620120,91831487,70367851,1375023,18466635,68000591,8373289,16405341,92692978,53648154,37192445,78884452,85711894,29994197,45996863,5267545,30891921,22997281,4091162,89804152,73109997,75543508,5482538,32151165,30463802,19486173,7502255,82327024,10453030,96420429,12571310,42073124,96735716,15064655,95251277,49598724,70541760,68110330,73786944,89637706,93562257,74441448,41380093,36808486,52613508,24168411,5073754,88653118,39801351,80555751,12664567,1250437,56424103,51507425,21001913,49328608,91727510,18783702,43357947,59614746,86242799,65271999,4508700,70782102,55615885,26507214,52204879,31126490,6038457,17081350,82726008,28734791,42967683,92787493,85117092,30811010,6497830,18504197,82651278,53547802,47893286,48260151,41092172,52261574,86543538,7066775,58208470,76170907,59318837,76315420,48658605,67281495,7182642,26734892,97057187,9829782,54199160,54868730,4515343,50806615,71469330,63372756,90272749,16567550,749283,39847321,61982238,72357096,86118021,2607799,24953498,62051033,14731700,82532312,26139110,36139942,1239555,569864,82427263,43152977,54232247,91957544,3235882,54517921,11757872,4434662,35996293,22405120,9860195,33699435,90654836,99125126,26392416,33565483,58224549,28928175,56955985,79738755,24314885,93566986,22766820,20140249,40781854,63152504,17857111,13470059,20486294,70420215,17385531,45407418,51315460,94360702,22113133,78218845,7919588,69579137,84349107,4064751,48893685,14045383,26114953,27187213,31491938,7104732,36933038,90310261,8651647,30139692,71522968,88698958,57393458,71083565,54263880,28550822,4978543,53802686,78766358,45794415,22200378,67474219,18806856,69255765,57163802,34044787,26397786,26292919,58751351,13819358,50007421,77694700,21070110,59329511,82178706,6111563,83269727,44846932,91141395,72358170,94516935,19272365,84127901,62762857,89078848,57961282,79322415,88904910,88251446,58749,13862149,54606384,99690194,32699744,71920426,98943869,97281847,57020481,38009615,44664587,68694897,86301513,59177718,89217461,97011160,62490109,8055981,7229550,14220886,84904436,15902805,29959549,85695762,78909561,41092102,21397057,53666583,62428472,11923835,92530431,8729146,84166196,36135,29516592,5822202,21919959,37659250,92867155,72019362,46540998,51590803,59981773,44481640,15163258,69605283,176257,94595668,45049308,98653983,53888755,66885828,24619760,67513640,40534591,47887470,6157724,1936762,90004325,19898053,20002147,64055960,61712234,98648327,55850790,82779622,77787724,94711842,32274392,8634541,29635537,48395186,20681921,5970607,10264691,14363867,4204661,67031644,71965942,72614359,72238278,65081429,21673260,81774825,83747892,2331773 +33565483,33304202,92692978,44983451,62740044,874791,26292919,68939068,7300047,57803235,51047803,67793644,40677414,55247455,33336148,7229550,66045587,72614359,12348118,12024238,36189527,8115266,30998561,89214616,40356877,66832478,73021291,65338021,40152546,60430369,7423788,62430984,22200378,9886593,64602895,59197747,60106217,3487592,80014588,54663246,46870723,569864,96735716,76540481,94539824,65851721,63967300,112651,37183543,78589145,62452034,526217,13468268,54058788,68102437,37435892,54517921,57241521,65454636,62357986,3233569,77284619,1197320,99021067,9175338,37560164,26392416,92692283,37620363,99297164,16445503,68875490,63015256,22766820,79880247,34493392,3235882,70372191,55615885,56484900,31126490,36685023,18411915,7955293,47361209,27187213,73617245,64157906,66611759,96193415,44784505,9860195,83210802,26063929,3773993,66667729,97011160,18833224,1788101,44889423,81898046,87720882,15535065,70004753,71920426,29834512,38645117,72274002,45428665,98948034,20122224,71316369,83533741,5640302,45381876,92554120,89217461,54427233,22450468,87598888,35092039,72357096,34667286,11365791,14349098,11923835,1239555,94911072,65275241,41380093,3509435,98943869,4199704,29994197,73124510,42307449,8696647,39847321,38365584,85117092,60955663,31491938,14045383,89699445,43501211,55602660,62232211,2208785,83789391,83155442,13470059,75543508,81677380,99515901,23740167,92033260,47213183,20568363,58224549,69579137,99224392,79738755,45237957,2917920,89046466,6986898,83083131,53842979,1022677,97940276,22405120,8128637,50702367,70800879,23848565,29188588,81172706,9058407,30653863,81853704,57240218,39201414,21993752,34295794,13819358,78549759,38510840,38658347,76960303,31161687,65880522,16595436,61373987,7919588,72793444,17068582,36312813,72278539,84127901,62496012,57359924,9259676,40027975,22435353,90013093,98130363,6808825,27665211,88251446,99226875,66271566,44627776,86001008,9398733,21001913,53802686,43933006,16405341,67281495,46540998,65271999,15015906,168541,69697787,76671482,49597667,2717150,51464002,82779622,30787683,67451935,59501487,19939935,75153252,30771409,20002147,33895336,93933709,21289531,99549373,89078848,75128745,9860968,29029316,61928316,20642888,36780454,30764367,13348726,15148031,1204161,81855445,86460488,4069912,77301523,4064751,82532312,43376279,8807940,95010552,36505482,29401781,21533347,93270084,61741594,62051033,9188443,88904910,49641577,68316156,47298834,20867149,55143724,95290172,72725103,88653118,56153345,30463802,86301513,26664538,18783702,16097038,17764950,19376156,9575954,43045786,72777973,61263068,83948335,3233084,22997281,73392814,26114953,25842078,34698463,14326617,34044787,68816413,29959549,66322959,36753250,32590267,9603598,36808486,17365188,36580610,321665,26493119,2204165,29819940,38022834,62490109,92541302,52261574,57961282,55753905,74357852,27325428,54232247,78766358,20486294,24168411,77620120,6505939,44550764,31904591,48658605,44060493,61859581,65074535,60176618,99861373,98371444,669105,4668450,33628349,8129978,53632373,52613508,37739481,91240048,61271144,18663507,14220886,5832946,20140249,99125126,55470718,59177718,98739783,11581181,38009615,9257405,99965001,99917599,45667668,85023028,30543215,97783876,58749,50806615,63059024,40534591,70596786,5970607,82726008,98648327,29510992,56461322,8099994,82979980,10366309,53393358,56515456,55960386,68702632,64848072,72238278,77300457,17037369,14626618,37659250,1569515,45995325,85242963,8373289,68128438,66885828,77312810,53648154,94516935,749283,68824981,22108665,5267545,10453030,37501808,78602717,45075471,91802888,19898053,74137926,75777973,52204879,47887470,51149804,96726697,45049308,54762643,58208470,54014062,87160386,45617087,4798568,55189057,94595668,15948937,76170907,56955985,20765474,40686254,44847298,24915585,56531125,1431742,96906410,72738685,75986488,48088883,27625735,16019925,74724075,77413012,97379790,62936963,82651278,53084256,70420215,75229462,83302115,57248122,28796059,61982238,53666583,89637706,26734892,15064655,16099750,24826575,6521313,50188404,75820087,64087743,49882705,6793819,81274566,93790285,64098930,65081429,65017137,17976208,38256849,92867155,66116458,82427263,3880712,34497327,99690194,28851716,65715134,17894977,51426311,65047700,74452589,2891150,28928175,67124282,61815223,34236719,71333116,69255765,24591705,47090124,1250437,66663942,6038457,26275734,14731700,4204661,38341669,31733363,66903004,44846932,44915235,3088684,85571389,56473732,5111370,77187825,95726235,19101477,13862149,94076128,35192533,90457870,52060076,38061439,24058273,90158785,86798033,21673260,39171895,63372756,38494874,19486173,83651211,27411561,33431960,54868730,17081350,43506672,88130087,247198,44842615,35512853,23569917,48360198,88444207,40781854,18806856,18131876,78218845,74743862,85116755,28787861,68694897,37675718,6221471,44348328,87710366,40197395,32161669,77694700,97561745,84840549,99971982,69848388,8729146,44844121,508198,4978543,10358899,41481685,53547802,51466049,63628376,91727510,42782652,28550822,36933038,10961421,30366150,77272628,4787945,51315460,33201905,15536795,91831487,91664334,59318837,36135,16684829,93057697,96420429,55770687,55669657,66250369,22129328,73222868,17727650,96852321,21919959,78300864,29466406,54199160,73786944,11543098,42237907,41442762,50668599,92353856,7646095,7104732,45790169,26507214,29516592,84904436,57163802,84187166,70541760,6497830,12571310,92530431,76703609,8634541,54987042,76315420,6819644,51715482,75135448,19457589,95395112,17957593,68000591,3294781,77072625,22942635,91255408,40865610,49271185,44481640,91141395,55850790,33237508,24619760,11161731,90004325,45848907,33797252,99658235,78909561,18699206,70782102,65038678,30891921,17539625,80555751,16971929,29635537,18504197,43322743,73235980,85695762,72495719,9623492,96318094,67030811,39801351,16387575,98462867,6153406,42073124,59614746,14947650,60102965,90272749,23134715,79136082,4091162,37166608,32699744,32159704,23194618,14093520,5822202,7011964,84406788,86821229,92398073,78561158,34698428,7687278,33123618,20645197,4099191,93562257,45794415,66137019,10264691,84684495,29932657,92215320,48395186,69355476,47708850,20885148,13231279,18466635,71300104,45996863,61728685,48675329,6525948,51472275,69605283,62803858,66428795,89499542,38008118,82052050,71657078,44177011,17146629,4508700,53888755,57020481,33699435,91990218,30139692,26744917,10597197,30569392,68110330,16054533,46851987,86242799,73168565,93515664,61897582,88047921,61623915,98653983,97057187,89996536,71469330,12303248,15163258,92998591,30694952,60581278,15075176,77787724,82339363,3183975,52293995,84349107,40781449,61141874,93566986,69641513,30163921,39553046,89811711,48673079,44473167,69786756,92787493,5482538,29065964,41245325,45990383,15902805,36812683,4119747,35996293,58180653,44479073,97281847,8651647,17385531,47738185,50567636,81805959,76330843,24953498,16567550,7502255,76434144,78785507,2607799,7066775,40984766,88698958,62552524,68041839,94090109,42199455,30395570,90310261,82897371,19272365,4434662,80316608,42967683,67474219,24314885,85711894,83747892,48774913,35456853,61859143,79922758,47893286,20836893,59405277,68644627,95581843,73031054,26102057,63152504,95251277,39986008,31727379,11757872,80246713,19891772,10309525,89419466,26139110,82886381,7685448,63506281,84293052,45407418,27185644,57658654,415901,7182642,78884452,21070110,79806380,50007421,93359396,36930650,26998766,56424103,59981773,22879907,61712234,33935899,66319530,824872,4985896,2331773,86022504,51507425,70727211,45306070,81774825,5073754,58751351,61960400,34946859,59910624,48260151,54606384,21397057,26863229,71965942,90090964,91281584,43357947,99729357,4515343,74110882,59371804,62115552,90061527,54263880,83368048,64055960,17386996,92604458,14363867,75052463,59109400,93053405,26397786,74614639,69136837,10649306,36396314,51590803,41092102,41092172,3633375,49598724,48892201,83133790,8791066,62693428,70367851,82327024,33249630,4095116,99524975,22721500,36468541,76825057,44245960,86543538,30811010,32274392,71083565,24733232,73109997,96709982,72358170,72019362,7517032,23432750,28734791,4806458,95957797,36139942,77377183,90654836,14723410,47792865,23110625,18001617,79191827,17857111,37280276,12416768,57393458,79657802,89804152,42644903,60393039,42947632,70036438,6871053,32058615,32151165,37957788,9829782,72732205,8055981,51588015,48893685,33061250,26776922,67084351,77898274,17016156,62762857,30218878,27041967,34432810,79821897,92071990,94711842,1936762,22113133,44664587,74441448,1375023,92803766,37891451,67031644,37192445,30090481,49328608,91957544,32250045,72373496,80251430,6157724,13173644,49236559,62428472,45919976,83150534,39373729,8733068,16380211,37224844,6111563,9599614,67513640,99604946,68204242,17058722,20681921,82178706,42692881,94360702,16424199,33553959,71522968,43152977,84166196,36845587,99333375,84002370,83269727,59329511,91938191,79942022,12664567,8913721,25636669,79322415,67227442,32426752,97641116,75407347,86118021,176257 +54663246,63967300,62452034,34295794,75543508,6525948,19457589,11581181,99965001,60430369,66045587,44784505,99524975,98948034,40781449,89078848,30764367,62740044,6808825,26507214,87720882,9398733,43376279,17764950,33797252,168541,48675329,16019925,14947650,73392814,5111370,75777973,9829782,26392416,14093520,35092039,78549759,90158785,90457870,97011160,2717150,98130363,81805959,67281495,67793644,57803235,38645117,88047921,98462867,95726235,31904591,32250045,34667286,6793819,8807940,28550822,70596786,22721500,80316608,77413012,14220886,81855445,18466635,44481640,526217,93270084,48260151,16405341,99226875,69355476,31161687,18663507,4064751,19486173,96735716,44842615,50702367,16445503,10366309,89214616,64087743,71333116,48893685,73031054,74110882,39373729,2891150,45428665,22129328,10358899,37659250,33201905,36808486,30998561,22942635,7011964,81853704,18504197,26664538,17365188,44889423,38365584,72793444,9257405,79880247,70727211,35456853,40152546,37620363,60102965,27665211,66667729,8696647,60955663,32161669,93515664,76540481,31491938,26863229,14626618,92215320,30811010,5640302,28787861,18833224,51426311,20002147,37891451,6153406,80014588,98371444,65454636,13468268,37183543,30891921,5832946,61263068,57241521,96193415,26292919,43501211,63628376,66903004,85117092,73235980,34698463,53648154,112651,15148031,93933709,54427233,66885828,61373987,45995325,67474219,40197395,62428472,39801351,45990383,22200378,91831487,84684495,68128438,874791,66250369,73124510,58749,61712234,72274002,92554120,49328608,78884452,91240048,72373496,508198,70372191,28734791,30694952,247198,47361209,94076128,65038678,89499542,36780454,99658235,78561158,48360198,9886593,16971929,82979980,93562257,64848072,2208785,84840549,4099191,72278539,56515456,57240218,749283,4204661,29819940,85116755,30163921,33249630,69255765,40686254,51047803,47738185,44060493,99297164,76703609,2607799,33237508,21001913,40677414,98653983,86242799,11923835,83150534,75229462,81172706,11365791,79942022,70004753,65074535,62936963,47213183,92692283,41481685,47090124,55143724,62490109,44245960,78589145,81774825,3880712,19939935,92530431,53842979,84904436,2331773,77694700,18699206,68824981,2917920,85571389,78766358,83533741,51507425,15536795,24733232,99515901,6221471,68102437,16054533,70036438,12571310,32699744,38061439,35192533,7646095,81677380,92033260,48774913,45919976,92398073,36845587,87598888,79191827,62051033,42307449,89419466,83133790,47887470,53666583,94911072,19101477,69605283,3773993,9058407,47298834,36812683,94360702,29834512,83302115,7300047,74614639,93053405,63015256,54014062,15902805,42947632,88904910,91957544,8129978,77300457,68702632,34236719,74724075,88698958,31126490,55247455,98648327,55615885,7955293,83155442,51590803,13173644,75153252,10309525,72614359,59177718,33123618,91990218,9175338,37192445,70782102,49598724,22879907,49641577,38022834,56153345,7423788,77377183,3088684,1022677,21673260,16380211,54517921,80555751,9860195,64055960,36505482,55189057,17068582,16567550,14349098,37501808,94516935,91664334,69579137,43933006,40356877,99021067,38658347,81898046,72019362,30771409,569864,6521313,75820087,44479073,47792865,71965942,8099994,85242963,63059024,44983451,29510992,66832478,18783702,84187166,45667668,64098930,53632373,27041967,39986008,92803766,7182642,13862149,21993752,24619760,3294781,41380093,65081429,11161731,99971982,45790169,9623492,77284619,24058273,59614746,55960386,20645197,52293995,45049308,10649306,54762643,62357986,36685023,65338021,92692978,669105,40781854,24168411,17957593,33565483,36753250,45794415,22108665,6497830,86821229,20568363,70420215,43152977,30395570,40027975,91727510,26275734,59318837,61623915,13231279,55753905,61741594,29029316,89637706,28796059,55770687,68939068,28851716,29466406,58751351,4668450,824872,62430984,91141395,95957797,8634541,23740167,23432750,22766820,12416768,19376156,3509435,89811711,5267545,73786944,66611759,18131876,59109400,24591705,51149804,56473732,18411915,35996293,4985896,70367851,53888755,9188443,69697787,56531125,54058788,51464002,36312813,96906410,77272628,76825057,84166196,26998766,10264691,77620120,29188588,17857111,29959549,24915585,20140249,22450468,96726697,58180653,26776922,54987042,19272365,36396314,58208470,54199160,86798033,55470718,33628349,65275241,21289531,96709982,88251446,97379790,86022504,15075176,17976208,73168565,23569917,77187825,23194618,79806380,33304202,20642888,68644627,61928316,56955985,95290172,98739783,54868730,50188404,77312810,16099750,20486294,27187213,14045383,30090481,94595668,8913721,99333375,49271185,79657802,10453030,66322959,26734892,84293052,86001008,8791066,68000591,56484900,92604458,99604946,45075471,12024238,79738755,3233084,72357096,44844121,39847321,89699445,76330843,5073754,62552524,61815223,14731700,74137926,82726008,43357947,82532312,1569515,20765474,83368048,24826575,13470059,29635537,45617087,61982238,23110625,75135448,87710366,2204165,79136082,8115266,71300104,92353856,12348118,31727379,3233569,80246713,73222868,4069912,72495719,95395112,20122224,68316156,56424103,47708850,29994197,30218878,99690194,90272749,70800879,72725103,22405120,82427263,20836893,15064655,66137019,55669657,77301523,78602717,30366150,57359924,72738685,17727650,76671482,73021291,48892201,1788101,72238278,65880522,44550764,71522968,17894977,57658654,9575954,5970607,26063929,32159704,43506672,92787493,97641116,84127901,33895336,51466049,42644903,4515343,98943869,30569392,36930650,33935899,51588015,30653863,37435892,4787945,39553046,67451935,21533347,86543538,89046466,45407418,67513640,71469330,92998591,20885148,52613508,34497327,53393358,85023028,42782652,1204161,83789391,71083565,15948937,64602895,49236559,321665,29516592,61960400,79922758,31733363,45381876,1375023,34698428,38008118,55602660,14363867,40984766,30543215,8373289,81274566,61271144,11543098,54232247,32151165,23848565,42967683,37560164,90310261,59501487,45306070,38009615,62803858,27185644,27325428,415901,16595436,83651211,17539625,6986898,53802686,69641513,66271566,50806615,91802888,93566986,4508700,16684829,83210802,57163802,72777973,66428795,75407347,25842078,1250437,24953498,13819358,32590267,52204879,74743862,99917599,29932657,42692881,70541760,32274392,68694897,49882705,61728685,66319530,82886381,90090964,99125126,62693428,38341669,1197320,74357852,7919588,26102057,95010552,52261574,68875490,6505939,41245325,80251430,42237907,32058615,26114953,37166608,28928175,84406788,17081350,46870723,15015906,76170907,51715482,48673079,33336148,67084351,45996863,6157724,10597197,96852321,34493392,34946859,21070110,17385531,69136837,33553959,4798568,73617245,97057187,94090109,3487592,40534591,7502255,13348726,7685448,18001617,57961282,7229550,63506281,16097038,85711894,77787724,61859581,59981773,65851721,83269727,71657078,69848388,48395186,33699435,32426752,59329511,77898274,51315460,54606384,65271999,53547802,87160386,8729146,55850790,1936762,7104732,36468541,50567636,6871053,8055981,83948335,75052463,78300864,62496012,71316369,67030811,66116458,68816413,68041839,37739481,43322743,48088883,82327024,21919959,26139110,43045786,60393039,19891772,67031644,9259676,99549373,76434144,1431742,68204242,97940276,65047700,97281847,17037369,66663942,61897582,60106217,39201414,85695762,75128745,99861373,3235882,90061527,50007421,46851987,16424199,42073124,15535065,14723410,4095116,45848907,94539824,76960303,50668599,51472275,35512853,36135,62762857,91938191,52060076,37675718,83083131,22997281,77072625,40865610,60176618,60581278,59197747,97783876,46540998,45237957,36189527,4806458,88444207,56461322,3633375,82897371,53084256,90013093,74452589,30463802,27625735,4091162,44627776,82779622,38510840,29401781,10961421,89217461,82178706,29065964,59371804,59910624,75986488,27411561,8651647,34432810,176257,5482538,90654836,69786756,22435353,39171895,30787683,67227442,38494874,37957788,15163258,96420429,44473167,96318094,4199704,26744917,76315420,41092172,65017137,14326617,33431960,78218845,20681921,99729357,86460488,93057697,74441448,36580610,71920426,78785507,44846932,26493119,4119747,44177011,57393458,25636669,48658605,61141874,9603598,4434662,8128637,6819644,58224549,33061250,86301513,44664587,16387575,5822202,62115552,44348328,92867155,6038457,88653118,62232211,41092102,86118021,57248122,9860968,42199455,4978543,34044787,12664567,79821897,1239555,73109997,94711842,82339363,90004325,68110330,23134715,7517032,63372756,82651278,21397057,54263880,7687278,93359396,20867149,91281584,82052050,72358170,3183975,7066775,37280276,61859143,36139942,8733068,91255408,72732205,95581843,41442762,63152504,12303248,49597667,89804152,84349107,78909561,64157906,99224392,59405277,44915235,9599614,18806856,97561745,22113133,65715134,37224844,83747892,6111563,26397786,95251277,47893286,17058722,84002370,88130087,79322415,44847298,67124282,17386996,30139692,57020481,89996536,24314885,92541302,11757872,93790285,36933038,17016156,17146629,38256849,19898053,92071990 +62496012,74357852,19891772,29819940,95010552,83210802,88047921,49641577,34493392,50702367,92787493,21533347,66832478,45848907,36753250,70541760,17764950,40356877,16099750,37620363,62803858,62693428,44481640,12348118,93515664,13468268,3233084,45990383,63967300,66137019,77284619,87160386,95957797,61928316,41245325,65271999,96193415,2917920,18783702,16019925,68000591,55247455,68204242,71920426,63152504,64848072,58749,6808825,64055960,71522968,18699206,88653118,18001617,8129978,45407418,86022504,37739481,35996293,9623492,71333116,17727650,89078848,84840549,8115266,62428472,86543538,9257405,75153252,1788101,37166608,97940276,41092102,57241521,26114953,83789391,17539625,21993752,44784505,66045587,61859581,83368048,44060493,36780454,28550822,92803766,77413012,9603598,55143724,51047803,96726697,94090109,88904910,3880712,42782652,6221471,74452589,99524975,30787683,53547802,39801351,99224392,92554120,50668599,91255408,91240048,8807940,4204661,27625735,32590267,44889423,53842979,45996863,79821897,37957788,57163802,47893286,22108665,73124510,93933709,26998766,71469330,7687278,36685023,73031054,85117092,83651211,66885828,98371444,54987042,52261574,16387575,45428665,17857111,34236719,64602895,5111370,749283,67451935,7502255,64087743,6505939,53802686,6793819,55189057,93270084,39847321,78300864,69641513,40686254,24733232,6986898,27325428,61815223,79136082,36580610,26863229,32159704,65275241,33237508,81898046,68816413,30764367,50188404,45667668,70800879,98943869,33628349,42307449,34295794,4119747,99515901,62357986,14947650,69579137,59197747,50007421,51472275,61859143,37192445,38494874,93053405,78218845,68128438,67124282,67793644,3294781,79657802,65047700,71316369,51426311,8128637,81853704,75135448,29834512,28796059,60955663,47738185,40027975,62936963,20867149,43501211,38061439,23569917,72793444,82726008,7182642,8696647,54762643,44177011,90090964,97783876,83533741,84127901,97561745,67030811,53084256,4787945,34044787,63506281,43376279,78602717,62452034,61141874,55753905,52293995,54263880,95581843,60102965,79942022,59177718,68316156,9575954,15015906,76671482,32161669,76434144,28928175,10358899,78766358,14220886,29065964,61271144,10961421,48673079,19898053,94539824,99861373,4099191,70727211,61897582,33336148,22450468,9860195,66611759,36808486,20642888,91664334,70004753,17037369,73392814,84904436,9259676,44245960,96852321,23194618,36312813,19486173,247198,76960303,58751351,38022834,89699445,90272749,95395112,45995325,65880522,45919976,74614639,45075471,29188588,68824981,57803235,63372756,11365791,68041839,82779622,34432810,669105,27665211,99604946,58224549,83948335,4668450,39986008,30366150,99297164,22942635,4064751,43933006,3487592,76315420,41442762,83155442,48675329,46540998,75777973,26392416,95290172,33935899,99917599,3233569,92692978,6521313,31126490,47361209,15075176,20568363,93057697,86001008,40197395,72495719,26664538,59614746,18466635,20486294,9829782,73617245,33201905,70036438,14045383,16097038,76540481,26507214,63059024,47090124,11543098,19272365,55770687,98948034,23110625,5267545,66322959,4434662,9188443,11581181,72274002,21070110,20002147,37891451,66271566,23848565,90457870,36505482,19457589,69697787,874791,80555751,38365584,30543215,73168565,89046466,27187213,7423788,33304202,75543508,48658605,508198,91727510,66663942,62740044,55615885,10366309,84684495,48088883,48260151,40865610,70596786,89996536,92867155,42947632,47708850,18663507,54427233,65715134,33895336,3773993,78589145,83083131,15535065,24619760,7955293,1431742,66667729,68644627,38645117,85695762,74110882,87598888,81172706,5073754,13348726,56461322,78561158,88698958,51315460,62051033,22879907,98462867,23740167,68939068,70372191,2717150,96709982,82979980,66116458,49236559,80246713,65338021,82897371,44844121,73235980,39171895,27411561,48892201,53393358,56531125,92692283,69355476,84293052,44842615,92998591,72358170,56473732,90013093,5832946,45237957,80316608,79738755,2204165,10597197,4798568,83133790,89419466,95726235,11161731,15163258,56484900,9599614,55669657,92071990,72614359,40152546,22435353,26063929,54014062,5640302,73222868,66428795,42073124,94711842,53648154,74441448,44479073,526217,36189527,28734791,99965001,25636669,42644903,61741594,74743862,47792865,82651278,59910624,9886593,38658347,59109400,56424103,12024238,92604458,79191827,79806380,43045786,53888755,90310261,168541,14731700,40984766,92033260,76330843,83150534,18806856,39201414,93562257,30139692,62430984,86301513,99729357,98653983,81677380,16971929,57359924,81274566,7104732,94911072,29994197,58180653,24915585,82427263,66250369,60581278,18504197,98739783,91831487,4199704,24058273,69605283,3183975,29029316,71657078,24168411,33797252,7300047,8913721,46851987,14093520,30771409,57020481,16567550,34946859,59318837,86798033,93359396,16405341,36139942,62490109,77377183,99125126,72357096,44664587,30163921,81805959,45790169,85571389,77072625,46870723,36930650,55470718,36812683,77312810,65081429,92541302,72019362,44550764,37560164,20140249,51466049,20885148,5482538,34698463,40677414,61728685,69848388,64157906,94360702,87720882,33249630,30694952,77694700,69786756,99658235,77272628,75407347,30891921,77620120,43322743,55960386,1375023,57248122,14723410,64098930,61982238,86821229,9398733,43357947,32250045,35512853,17068582,91141395,35192533,81855445,99549373,59501487,6525948,60106217,1569515,54606384,55602660,1204161,31491938,20765474,112651,47213183,35092039,56153345,72238278,67227442,16595436,8634541,53666583,16380211,3235882,52060076,2891150,415901,67474219,29932657,21289531,45617087,89214616,2607799,96906410,97057187,31161687,93790285,85116755,62762857,7011964,19376156,32058615,33061250,50567636,16054533,21919959,40534591,36468541,34497327,61263068,82339363,70367851,30218878,92398073,13231279,30653863,3633375,31727379,3088684,37501808,92353856,17957593,24314885,37224844,176257,18833224,62232211,53632373,44983451,26275734,72777973,26493119,77301523,32151165,17081350,4515343,54199160,12571310,74724075,82532312,23134715,96318094,65454636,73786944,18131876,15536795,17385531,60430369,8099994,72373496,82052050,54663246,71965942,37435892,57658654,49271185,16445503,61623915,69255765,39553046,50806615,4069912,57240218,18411915,36396314,21673260,31904591,11923835,76170907,30998561,28851716,824872,7229550,66903004,13470059,37675718,78785507,26139110,29401781,89811711,25842078,79922758,38341669,36933038,86242799,13173644,84349107,17058722,51464002,38009615,37183543,22200378,89637706,59371804,87710366,29959549,63015256,30569392,79880247,14363867,47298834,82886381,57961282,65038678,88444207,52613508,3509435,81774825,24953498,54517921,24826575,6153406,56955985,91802888,66319530,36135,54232247,44915235,76703609,35456853,85711894,85023028,83302115,41092172,91281584,1022677,89804152,49598724,96420429,51507425,62552524,22405120,90004325,34698428,96735716,60176618,72725103,99690194,76825057,78909561,7517032,88130087,8373289,15148031,70420215,57393458,80251430,10309525,39373729,29510992,77187825,93566986,56515456,75052463,19939935,15064655,34667286,12416768,7066775,28787861,9058407,67513640,31733363,14349098,37659250,71300104,14626618,85242963,40781854,80014588,51715482,67281495,91938191,33123618,65017137,32699744,68875490,26734892,26292919,19101477,98130363,51588015,38008118,26776922,30090481,23432750,22129328,29466406,10649306,42199455,90061527,8791066,4091162,30811010,9860968,24591705,48360198,82178706,51149804,4508700,51590803,62115552,8733068,30395570,48893685,89217461,42967683,1936762,68102437,22766820,52204879,58208470,15948937,82327024,60393039,4095116,49882705,65851721,84187166,47887470,90158785,97281847,48774913,44473167,92215320,92530431,75229462,44846932,7646095,6497830,6157724,99971982,71083565,17365188,26397786,30463802,55850790,8651647,97379790,91990218,45049308,45306070,15902805,68110330,54868730,12303248,99333375,74137926,2331773,32426752,43506672,88251446,7685448,86460488,33553959,6038457,61960400,72278539,75986488,94076128,6871053,40781449,98648327,72732205,29635537,45794415,89499542,5970607,59329511,49328608,63628376,14326617,33565483,99226875,33699435,97011160,91957544,4806458,6819644,10453030,20645197,7919588,67031644,20122224,41481685,26744917,27185644,99021067,54058788,42692881,65074535,38256849,1197320,59405277,68702632,77787724,73109997,9175338,94595668,61373987,42237907,77898274,22997281,26102057,17976208,86118021,83269727,90654836,20836893,16424199,59981773,77300457,10264691,68694897,2208785,20681921,4985896,43152977,72738685,49597667,70782102,37280276,21001913,17016156,1250437,569864,44627776,41380093,75128745,321665,44348328,73021291,13862149,17146629,44847298,78549759,36845587,97641116,95251277,67084351,17894977,38510840,84166196,75820087,78884452,33431960,79322415,45381876,8729146,22113133,84002370,17386996,69136837,22721500,83747892,48395186,11757872,16684829,1239555,21397057,61712234,27041967,94516935,6111563,5822202,4978543,84406788,8055981,13819358,32274392,29516592,12664567 +45381876,96735716,66322959,3509435,81853704,57803235,64157906,33895336,35192533,29188588,23569917,69355476,63506281,56531125,65271999,6808825,44915235,26392416,52204879,62693428,415901,65275241,29029316,86001008,168541,48088883,66250369,70004753,95010552,35092039,99965001,67030811,48675329,8807940,9188443,70372191,10309525,1022677,66319530,7423788,66271566,63372756,22997281,80246713,17957593,53842979,73617245,62936963,37739481,28928175,65715134,24826575,84904436,9257405,66045587,22129328,62430984,41092102,54232247,569864,34295794,11161731,76170907,99690194,29994197,30366150,63967300,31126490,91240048,44983451,20002147,4668450,26863229,7646095,15535065,66667729,33304202,61928316,51047803,62232211,4515343,97011160,16097038,8129978,96709982,83651211,18663507,10264691,69641513,38494874,59981773,72274002,1431742,13231279,27411561,60102965,53666583,50806615,71333116,9886593,40197395,50188404,52613508,66832478,68816413,75135448,84127901,61263068,61815223,72278539,55189057,65338021,56424103,67474219,92554120,93933709,75986488,25842078,54517921,9575954,66611759,86301513,30787683,55247455,84166196,71920426,62740044,85116755,9860195,73124510,42307449,176257,19272365,47738185,61982238,83210802,62051033,39847321,97561745,62452034,43501211,63015256,71316369,24168411,36780454,33565483,99549373,88130087,89699445,92692283,43045786,7182642,112651,76330843,16054533,19376156,29819940,7955293,26114953,34698463,44479073,31727379,41380093,36505482,60430369,34493392,32426752,24619760,44348328,54263880,97940276,19939935,56473732,77377183,96193415,68875490,14731700,95395112,13348726,12348118,48892201,3773993,70800879,65454636,8696647,20642888,99226875,72793444,51466049,6221471,51464002,30764367,44177011,17365188,83948335,73031054,69255765,98648327,93790285,3233569,20765474,36930650,16099750,2204165,65017137,29466406,47887470,66137019,66116458,86242799,64087743,41481685,68102437,22879907,60106217,6038457,36685023,7685448,78589145,67084351,1788101,64602895,1569515,9623492,98739783,45075471,8115266,749283,44889423,33201905,30569392,52060076,77694700,30998561,67031644,38645117,2208785,17727650,15075176,49597667,5111370,38365584,76703609,67451935,59910624,16595436,874791,86022504,35512853,61373987,26507214,76434144,73021291,89214616,26275734,61623915,99515901,15902805,36396314,59177718,50007421,12416768,75229462,99729357,99125126,17037369,86460488,33123618,37183543,47090124,18783702,58224549,43376279,3294781,87710366,36753250,72373496,39553046,17081350,17386996,76540481,3633375,81172706,68128438,46540998,32161669,21533347,61141874,89046466,526217,45848907,53802686,51472275,59371804,78766358,3088684,55470718,11923835,99604946,82178706,90272749,57240218,22435353,40781449,77284619,98130363,6505939,33249630,33797252,30090481,77312810,37435892,90013093,26664538,15163258,18833224,4508700,17068582,98371444,39171895,92033260,54762643,3880712,669105,23194618,91802888,71657078,26998766,82979980,6793819,57248122,18466635,42967683,17539625,51507425,59197747,78785507,52293995,37620363,56955985,91664334,77301523,36189527,20645197,32151165,83150534,9259676,79136082,72725103,94911072,13862149,45617087,83302115,6986898,82726008,13173644,89637706,22200378,45306070,61741594,60955663,30139692,37957788,18806856,96726697,8651647,98943869,48360198,68694897,508198,68939068,77072625,824872,42237907,49641577,40677414,34497327,7300047,2717150,16019925,94360702,24733232,35456853,84187166,92541302,68644627,54663246,83083131,21993752,10366309,37501808,54014062,44842615,33237508,54427233,38658347,4119747,83155442,63152504,72358170,19101477,70036438,55850790,5970607,78602717,49598724,53547802,45996863,56153345,55753905,72777973,7919588,42692881,78909561,19457589,10453030,83789391,14947650,90654836,92787493,99861373,68041839,30653863,33628349,48893685,92867155,69697787,84349107,76671482,7229550,16380211,92353856,94539824,39801351,54058788,44473167,19486173,62496012,44847298,38061439,22942635,51715482,89419466,57241521,20681921,24591705,99224392,1250437,68204242,29834512,28550822,27325428,77187825,17894977,55143724,61271144,6157724,64098930,9829782,3233084,42199455,66663942,1239555,14220886,7687278,33061250,17146629,41245325,52261574,79880247,90090964,38341669,14093520,51588015,57961282,85571389,321665,75153252,40356877,77413012,51426311,40865610,10358899,70727211,76960303,98948034,50668599,36933038,37675718,4099191,13470059,44627776,48260151,99971982,73168565,75543508,29635537,80555751,22405120,95726235,34044787,83269727,38022834,32699744,35996293,1197320,8373289,59318837,81677380,64055960,47298834,38009615,9599614,22108665,43506672,9398733,57393458,44784505,58749,16971929,36808486,89811711,10597197,65851721,40534591,73222868,26102057,69786756,86798033,27665211,69605283,78561158,92692978,5482538,97379790,68000591,30771409,70420215,49328608,36580610,58180653,77272628,4806458,61859581,64848072,21070110,94076128,22450468,99297164,47361209,16445503,62428472,83533741,43322743,49271185,39201414,71083565,54199160,29959549,88904910,1204161,77620120,57359924,4091162,15148031,15015906,36312813,51149804,95290172,2607799,84002370,71469330,75820087,55615885,61859143,92398073,26139110,44844121,8913721,90061527,30395570,65038678,16567550,84684495,6819644,81274566,44060493,60393039,73235980,11581181,45995325,42782652,47708850,79821897,30218878,94090109,88251446,74724075,63628376,96420429,20122224,54606384,49882705,16424199,9058407,89078848,30543215,99917599,20486294,76825057,56515456,48673079,14349098,9860968,39986008,59329511,89499542,28787861,5073754,9175338,20885148,74137926,7011964,93270084,53888755,79806380,81855445,5267545,78549759,82532312,23848565,82886381,4798568,79191827,85242963,14626618,88047921,98462867,8634541,32590267,34667286,10649306,44481640,30891921,46870723,47792865,2891150,67793644,54987042,73392814,84406788,74614639,91727510,71522968,77300457,91831487,17764950,34946859,7104732,44550764,67227442,33336148,4787945,83368048,53632373,48395186,18411915,68824981,96318094,69579137,12571310,93053405,45237957,71300104,59501487,62357986,61728685,21001913,72495719,2917920,36139942,83133790,38256849,96852321,70596786,74357852,20140249,18131876,72614359,17976208,50567636,86821229,31491938,31904591,78218845,24058273,13468268,87160386,2331773,65074535,82052050,73109997,68316156,82339363,38510840,47213183,34432810,79657802,40152546,72357096,90457870,26292919,65047700,24915585,40984766,79322415,29065964,29516592,91957544,55669657,65880522,31161687,66885828,36468541,32250045,11757872,29510992,7517032,53648154,60581278,67281495,91990218,37560164,27625735,74452589,8128637,51315460,66903004,62762857,9603598,67124282,30463802,1375023,20836893,12024238,14363867,72238278,3487592,74743862,55960386,60176618,38008118,4064751,33553959,36845587,82897371,93359396,21919959,29932657,48658605,6525948,21673260,70367851,92604458,5640302,8729146,18001617,247198,19891772,70541760,88653118,54868730,82327024,77787724,15064655,91141395,66428795,92071990,11543098,45790169,7502255,40686254,89217461,81774825,53084256,42073124,94516935,97057187,55770687,45407418,93566986,39373729,4095116,4069912,30694952,62552524,45428665,56484900,11365791,93057697,45049308,90004325,26776922,32058615,61897582,78884452,93515664,57163802,37659250,85711894,69136837,43152977,59109400,22766820,48774913,79942022,87720882,15948937,23110625,34236719,23432750,14326617,28796059,34698428,18699206,95581843,92215320,69848388,80014588,8099994,55602660,99524975,42947632,4985896,8791066,3235882,62803858,91255408,90310261,46851987,23740167,85023028,45919976,19898053,3183975,26744917,26063929,79922758,73786944,28734791,16387575,88444207,30163921,27185644,45794415,99333375,15536795,12664567,92998591,74110882,82427263,6871053,28851716,43357947,37891451,32274392,8733068,4978543,20867149,31733363,1936762,43933006,33699435,62490109,99658235,17058722,7066775,32159704,20568363,8055981,96906410,84840549,97281847,30811010,84293052,61712234,22721500,14723410,68702632,49236559,94711842,77898274,95957797,91281584,92803766,10961421,75407347,12303248,26493119,6521313,98653983,17385531,72019362,25636669,97783876,75777973,99021067,21289531,6153406,50702367,59405277,87598888,5832946,18504197,85695762,71965942,80251430,13819358,45667668,36812683,81898046,82779622,93562257,37166608,36135,57658654,85117092,72732205,4199704,5822202,40027975,14045383,56461322,62115552,4204661,16684829,6111563,26397786,27041967,33935899,79738755,65081429,67513640,81805959,63059024,6497830,44664587,16405341,41092172,88698958,94595668,86118021,59614746,42644903,37192445,40781854,58208470,53393358,86543538,91938191,92530431,58751351,27187213,51590803,72738685,33431960,75052463,26734892,90158785,22113133,24314885,61960400,80316608,37224844,44846932,44245960,89804152,82651278,78300864,95251277,29401781,68110330,83747892,89996536,47893286,4434662,37280276,17016156,23134715,70782102,74441448,75128745,45990383,24953498,57020481,76315420,41442762,17857111,97641116,21397057 +77312810,96726697,61741594,4064751,51047803,112651,17365188,63967300,53084256,66319530,44177011,17957593,31727379,71083565,7423788,55247455,57803235,69605283,81853704,62452034,23569917,40984766,7182642,22108665,44348328,168541,4099191,51464002,77272628,99333375,62936963,98371444,10358899,69355476,94076128,16387575,44915235,48892201,96193415,35192533,88904910,49641577,91990218,23432750,21533347,44481640,66250369,10366309,30395570,55770687,7646095,89419466,34698428,9829782,76671482,61271144,60581278,53648154,55143724,95581843,39553046,77787724,90272749,93270084,14220886,16097038,56473732,39847321,63152504,15535065,9860195,37675718,84293052,27411561,3880712,54762643,29959549,50188404,79136082,4095116,84904436,22129328,26392416,9886593,19939935,66832478,9599614,27041967,73617245,24733232,76170907,95957797,91802888,8115266,14093520,39171895,83083131,95010552,82726008,67281495,18663507,74357852,59177718,84684495,81898046,10309525,26664538,70004753,30771409,57240218,34946859,34432810,73031054,73786944,6819644,45617087,53666583,29819940,70036438,75229462,40865610,14731700,33201905,44842615,94516935,9603598,51588015,27665211,31126490,18001617,89046466,26998766,99965001,53632373,31733363,77284619,77301523,176257,73168565,62490109,33304202,69641513,33895336,56515456,7685448,12571310,42692881,69786756,11581181,89637706,40686254,59371804,26114953,93790285,27625735,79922758,64087743,14349098,3294781,51315460,96420429,29994197,50702367,56424103,6793819,60106217,91240048,247198,3088684,18699206,72373496,8651647,57163802,29932657,99658235,8913721,79322415,64157906,38494874,24591705,33565483,8733068,8634541,93562257,12348118,15075176,83533741,17037369,51472275,62740044,20645197,36780454,41092102,82779622,10649306,58751351,33061250,75543508,73021291,18806856,43933006,68702632,65038678,16684829,89217461,76540481,97011160,75986488,21070110,37501808,33431960,38022834,34044787,96735716,34236719,59501487,29188588,7919588,55753905,6157724,72238278,508198,83368048,61263068,29834512,78549759,44784505,20885148,4199704,44550764,54232247,58208470,35456853,73124510,65851721,3487592,26292919,59109400,48675329,415901,97561745,29510992,90090964,749283,20867149,72357096,824872,78602717,37739481,12416768,669105,8128637,51426311,27185644,33797252,8807940,42967683,34698463,98943869,56484900,70727211,37891451,9257405,59329511,6497830,51466049,16595436,5267545,33123618,52060076,59910624,13470059,28796059,88047921,74441448,58749,11923835,89078848,60430369,65271999,99021067,54014062,22997281,37957788,70596786,86022504,54987042,17894977,93053405,65081429,75153252,4434662,67227442,66885828,43357947,32159704,75135448,58180653,65715134,32161669,33237508,6221471,49882705,8129978,48088883,21993752,98739783,68128438,83269727,12664567,67451935,68644627,16019925,98648327,17146629,9259676,63372756,9398733,5970607,60102965,64848072,39201414,88653118,71657078,97783876,38658347,86301513,45995325,4798568,45794415,79806380,19101477,23194618,93933709,24826575,99861373,88698958,40027975,42237907,42199455,30653863,81805959,24168411,73392814,42307449,68102437,37435892,36845587,37280276,45790169,98462867,43506672,82979980,4668450,3233084,59981773,31904591,86821229,30463802,66667729,86798033,65047700,94360702,78218845,38061439,65880522,52293995,54517921,44245960,36580610,1431742,81172706,9623492,99604946,62693428,9860968,87160386,53842979,30543215,25842078,26776922,30218878,3773993,5111370,26863229,95395112,30764367,90654836,76434144,14045383,51507425,65338021,75820087,3509435,66428795,55470718,2717150,92530431,66045587,43376279,1204161,56531125,24058273,1569515,49271185,55189057,63506281,6153406,77072625,39986008,60393039,25636669,17058722,73235980,86001008,40534591,42644903,47090124,90457870,36753250,84349107,84127901,68939068,5482538,76703609,6986898,54606384,71965942,6505939,67474219,29466406,97057187,84187166,22450468,50007421,68816413,54058788,67793644,17385531,15015906,55669657,16054533,34295794,1239555,44844121,30787683,37620363,65454636,1022677,77620120,90013093,61712234,28787861,70372191,44473167,30366150,81274566,7011964,11543098,77694700,5832946,92541302,23848565,75052463,36312813,21673260,70800879,20681921,53802686,64602895,6525948,16099750,24619760,63628376,43045786,37659250,61982238,72793444,47708850,6808825,2204165,77300457,82339363,40781854,84406788,76330843,13231279,46540998,38341669,99524975,55960386,13468268,90004325,51149804,45848907,26734892,47887470,87710366,72019362,96709982,42782652,43152977,77377183,43322743,526217,36808486,82052050,52204879,44983451,40197395,26063929,86543538,76315420,57961282,80316608,321665,75407347,49598724,67084351,68875490,92692283,62496012,85571389,36505482,78561158,4508700,62430984,14723410,16405341,91957544,72614359,4091162,22435353,20836893,61859143,13173644,7104732,70367851,95251277,14947650,9575954,19272365,12024238,77187825,74452589,37183543,89214616,98653983,40356877,74110882,53393358,91664334,61960400,874791,1250437,57359924,9188443,92604458,13862149,26493119,63015256,78589145,7229550,78909561,32058615,20140249,61623915,53888755,11757872,48360198,62762857,18131876,20122224,8696647,69697787,26744917,92554120,45075471,16445503,90310261,72738685,44846932,79191827,98948034,70541760,77413012,15148031,99549373,7300047,62803858,69136837,14326617,46851987,7955293,61373987,55850790,78785507,83302115,92803766,33249630,10597197,2331773,82886381,88444207,49236559,87598888,68041839,66322959,71469330,92353856,60955663,72278539,72274002,69848388,47361209,28851716,54427233,29029316,89499542,19486173,68204242,16380211,5073754,97379790,61728685,45919976,33628349,36930650,30090481,79942022,74724075,4806458,89811711,2917920,6871053,39801351,62051033,32274392,99690194,569864,30569392,18833224,32250045,49597667,87720882,82427263,20486294,20002147,1375023,2891150,6111563,21001913,62428472,22113133,37192445,68000591,56153345,17764950,17016156,6038457,56461322,54663246,61859581,42947632,36812683,35092039,8729146,30163921,27325428,4985896,18466635,3233569,64098930,4515343,51715482,50806615,71300104,14363867,30998561,99917599,71333116,37224844,38256849,65074535,97940276,44847298,91831487,32699744,30694952,41481685,46870723,73222868,17081350,8373289,84840549,38645117,57241521,60176618,20765474,91141395,35996293,91281584,8099994,51590803,7687278,40152546,89699445,47738185,85116755,2607799,92787493,85023028,36189527,22721500,99297164,80251430,1936762,82178706,90158785,40677414,21289531,15536795,28550822,99515901,1788101,93359396,17539625,35512853,66663942,82897371,22942635,61815223,93515664,95726235,15902805,34493392,45407418,78766358,33699435,71522968,44479073,92867155,11365791,33935899,71316369,83150534,74743862,17386996,16567550,52261574,55615885,48673079,71920426,59318837,99125126,54263880,99224392,57248122,15064655,2208785,4978543,80555751,26507214,8055981,18783702,33553959,42073124,52613508,48893685,22766820,36933038,90061527,85242963,9175338,70420215,17068582,92071990,48658605,12303248,29065964,65275241,80014588,93566986,45996863,24314885,94539824,80246713,41380093,30139692,39373729,48774913,1197320,40781449,54868730,10264691,31161687,88251446,29516592,26275734,83210802,4787945,92033260,84002370,4069912,10453030,21397057,38510840,44060493,18504197,78300864,32426752,17976208,91255408,47792865,14626618,17727650,43501211,7502255,36468541,16424199,89996536,95290172,56955985,22405120,83133790,37166608,8791066,45667668,34497327,24915585,92692978,88130087,7517032,37560164,91727510,19891772,3183975,3235882,34667286,19457589,41442762,24953498,81855445,11161731,68824981,50567636,97281847,82327024,72725103,69255765,20642888,57658654,75777973,94711842,99226875,13819358,5640302,47213183,59197747,59614746,9058407,44889423,54199160,3633375,83789391,57393458,94911072,68694897,92398073,83651211,79880247,94595668,89804152,86460488,79657802,45428665,92215320,28734791,94090109,69579137,85711894,17857111,97641116,29635537,30811010,47298834,62115552,74614639,45049308,45237957,21919959,45381876,57020481,61141874,66137019,72358170,67030811,82651278,75128745,98130363,77898274,33336148,50668599,74137926,19376156,72777973,13348726,53547802,36139942,66903004,48395186,15163258,45306070,76960303,93057697,38009615,83948335,67513640,7066775,86118021,67124282,18411915,68316156,85117092,30891921,45990383,48260151,68110330,72495719,26102057,62552524,96852321,22879907,64055960,15948937,79821897,47893286,4119747,41245325,36685023,99729357,79738755,20568363,66271566,26397786,16971929,63059024,38365584,83747892,6521313,32590267,61897582,91938191,85695762,58224549,99971982,29401781,67031644,44627776,41092172,28928175,31491938,92998591,49328608,55602660,65017137,66611759,96318094,81677380,23740167,38008118,23110625,27187213,23134715,36396314,86242799,5822202,26139110,78884452,4204661,83155442,96906410,22200378,62232211,32151165,72732205,36135,44664587,70782102,62357986,61928316,59405277,66116458,19898053,84166196,82532312,81774825,10961421,73109997,76825057 +72274002,89214616,59197747,67793644,112651,77284619,45428665,95290172,60430369,44348328,20765474,70420215,81898046,99658235,50668599,55753905,16387575,84840549,96906410,99549373,19939935,29029316,83150534,12024238,30395570,29834512,66250369,54058788,93933709,74137926,99971982,53547802,3633375,3233084,55470718,69255765,20568363,66322959,55247455,14626618,13862149,71333116,17068582,50702367,47708850,24058273,95957797,6793819,6808825,71316369,72725103,9259676,81853704,15536795,6525948,8129978,82339363,54663246,53802686,30998561,5640302,4508700,33797252,98371444,78589145,63506281,40781449,43152977,77620120,62452034,10961421,74724075,44481640,15535065,86543538,37739481,97379790,37166608,39847321,64087743,2717150,8807940,38645117,65047700,73124510,49882705,54014062,29188588,20486294,33935899,62496012,29819940,415901,29065964,9603598,63628376,7687278,36189527,27411561,40356877,54868730,51047803,3088684,54427233,37620363,26744917,15015906,96193415,86301513,66832478,7919588,65338021,84002370,18699206,35192533,16019925,18411915,92033260,73222868,3183975,75407347,3233569,62490109,19891772,65275241,69136837,72278539,31904591,69848388,34236719,88653118,33201905,84684495,6221471,9257405,96318094,20642888,42967683,14326617,47090124,35092039,98648327,83083131,77272628,68102437,40865610,1431742,48892201,6871053,79806380,37560164,61728685,91664334,26275734,98130363,4119747,32590267,24915585,21533347,7685448,40677414,61263068,96726697,54263880,92803766,9623492,9886593,90013093,86460488,6153406,34044787,66137019,62740044,26493119,62430984,62357986,824872,10309525,25842078,72777973,46851987,38008118,19376156,30366150,22108665,19486173,36812683,26114953,74452589,32159704,42073124,14093520,42199455,92692978,51590803,34493392,61960400,83533741,5111370,61897582,99226875,44889423,53084256,91938191,14947650,66611759,29510992,76170907,67124282,94516935,5832946,68816413,65715134,24826575,33431960,16445503,7300047,12348118,73617245,8651647,73235980,31161687,44060493,92353856,57163802,29466406,31126490,64602895,58224549,36753250,33237508,22766820,65851721,72738685,55189057,67030811,66663942,23848565,99515901,9188443,34698463,30764367,508198,73109997,39373729,16567550,65454636,82052050,50567636,16380211,49328608,59177718,43376279,73168565,12416768,85116755,91281584,61982238,37891451,89046466,14220886,13231279,61271144,72732205,7011964,64098930,95395112,45306070,37183543,79738755,3509435,5970607,38658347,17976208,22450468,3294781,30787683,39171895,65038678,75229462,85023028,55770687,50007421,72358170,40686254,75052463,69641513,16971929,95581843,26734892,84127901,77694700,56484900,22942635,51472275,68204242,94595668,17385531,95726235,23569917,40197395,61373987,26139110,51588015,48260151,27325428,41245325,30891921,27665211,40984766,56531125,91831487,83948335,526217,34497327,40781854,55669657,9575954,44842615,45848907,45049308,12664567,43933006,30139692,86022504,11543098,66271566,54199160,78218845,36580610,99917599,99297164,80014588,23194618,66116458,93053405,16595436,15148031,56461322,7955293,60106217,14349098,41092102,91957544,82327024,35996293,48675329,57803235,13173644,22879907,72495719,22129328,4668450,18806856,5073754,17037369,65017137,49641577,15163258,47792865,83210802,57658654,70372191,39553046,97057187,88904910,11365791,42692881,48774913,16099750,29516592,11161731,16684829,69355476,59501487,10366309,33628349,61141874,10453030,69579137,32426752,44245960,81677380,30694952,2891150,67281495,29994197,42307449,24591705,41092172,20836893,85242963,45407418,34295794,78785507,58749,38061439,7423788,83651211,59371804,33123618,39801351,34667286,84187166,60581278,11581181,62803858,45237957,38494874,99604946,64157906,96420429,33249630,90061527,86821229,55850790,74743862,83269727,90090964,36139942,72373496,3880712,52261574,62552524,4099191,73031054,71657078,57248122,44983451,8733068,71083565,92692283,57020481,75777973,80555751,43045786,68824981,49598724,76330843,43501211,4787945,84406788,79821897,51507425,51715482,26063929,30771409,51464002,17365188,17857111,12303248,39201414,85571389,54987042,62232211,26102057,45990383,62051033,1204161,70596786,4064751,6986898,46540998,92998591,79880247,36396314,36685023,73786944,97561745,3487592,57961282,4515343,7229550,60955663,26507214,70541760,33304202,18504197,38365584,68041839,92787493,84904436,89637706,20002147,99965001,98462867,77301523,86001008,15075176,44479073,82897371,54517921,54606384,77787724,9398733,17146629,10649306,50806615,60102965,36468541,99224392,98739783,20122224,77187825,75543508,76315420,44177011,47738185,68000591,59614746,21993752,6505939,50188404,22200378,28734791,37224844,20140249,37501808,26664538,7517032,20645197,18833224,99690194,10358899,48088883,92604458,30569392,16405341,26392416,47361209,19272365,66428795,77312810,44473167,63967300,1250437,87598888,88047921,47298834,48395186,82979980,97940276,16054533,7182642,44844121,39986008,78602717,66667729,2208785,70367851,33699435,61859581,83368048,63152504,17764950,67474219,11757872,45794415,63015256,77413012,1197320,38256849,29959549,17957593,53632373,23134715,19101477,81855445,1022677,18783702,98653983,51426311,87720882,67031644,75153252,45790169,72614359,321665,76540481,44550764,96709982,70727211,36505482,92215320,34432810,62428472,5822202,53842979,3235882,8729146,4204661,8696647,35456853,10597197,55143724,75128745,32058615,32161669,75986488,53666583,82726008,66045587,74357852,77377183,88251446,45617087,26776922,23740167,61741594,34946859,86118021,669105,42782652,40152546,78561158,90272749,99861373,92071990,45995325,31491938,15902805,176257,91990218,1569515,70004753,7646095,41380093,28796059,61623915,89811711,65074535,96735716,28928175,17386996,34698428,83133790,5482538,93790285,56153345,91255408,36780454,62693428,53648154,31727379,2917920,94911072,36312813,71469330,72019362,57359924,78909561,87710366,1239555,53393358,52613508,67084351,59109400,18663507,2607799,26998766,22997281,89499542,89078848,21001913,26397786,52293995,88130087,68644627,569864,44784505,15948937,59329511,92554120,30653863,47213183,16424199,99021067,78766358,79136082,89804152,22405120,17081350,48893685,4798568,84166196,9058407,37435892,8055981,14731700,58751351,8913721,99333375,16097038,51466049,2204165,44847298,33895336,18131876,94076128,60176618,97641116,32151165,30543215,83155442,21070110,42947632,61712234,78300864,91727510,58208470,10264691,77300457,30218878,14045383,59405277,78549759,22113133,87160386,44664587,9860195,76960303,93515664,18466635,54232247,1375023,70800879,17727650,12571310,22435353,55602660,45075471,27187213,86798033,75135448,74110882,68316156,168541,63372756,72357096,7066775,8115266,45667668,24168411,36808486,68110330,24953498,83302115,29932657,68939068,36135,8099994,13348726,42644903,62936963,51149804,91802888,71920426,20867149,80251430,56515456,28550822,4434662,13819358,4978543,79191827,72793444,43322743,18001617,11923835,84349107,93057697,20885148,95251277,4095116,97011160,17894977,81172706,65271999,89419466,32699744,63059024,47887470,45381876,69697787,97783876,55615885,80316608,52060076,79657802,56955985,33565483,59910624,71522968,6038457,49271185,78884452,57393458,61859143,65880522,17539625,49236559,73392814,74614639,13468268,38510840,83789391,47893286,68702632,97281847,35512853,8373289,58180653,749283,4069912,89699445,88698958,64848072,59318837,90310261,90457870,93359396,98943869,32250045,24314885,64055960,1788101,80246713,38009615,48360198,96852321,52204879,92541302,37659250,9175338,29401781,75820087,79922758,31733363,88444207,76434144,92867155,85117092,44627776,60393039,49597667,92398073,33336148,44915235,27625735,62762857,94711842,4806458,66319530,99524975,8791066,30463802,9599614,6521313,61815223,69786756,73021291,40027975,54762643,38341669,20681921,48673079,76825057,8634541,45919976,29635537,21919959,74441448,37957788,82532312,90004325,28787861,82178706,37280276,26292919,22721500,21397057,93566986,56424103,93270084,6111563,66903004,89217461,95010552,69605283,7104732,77072625,36930650,68694897,55960386,874791,5267545,57241521,24619760,6497830,19898053,247198,28851716,7502255,82779622,14723410,3773993,81805959,21289531,61928316,71300104,4199704,23110625,76671482,68128438,99729357,26863229,89996536,17058722,84293052,66885828,94539824,59981773,81774825,48658605,45996863,46870723,30090481,57240218,9860968,70036438,40534591,65081429,33061250,44846932,85695762,81274566,43506672,83747892,99125126,24733232,98948034,14363867,13470059,67451935,27041967,36933038,30163921,6157724,82886381,91240048,27185644,72238278,91141395,6819644,67513640,8128637,94090109,42237907,90654836,30811010,86242799,9829782,94360702,21673260,2331773,4985896,67227442,43357947,32274392,23432750,90158785,1936762,25636669,4091162,19457589,33553959,37675718,15064655,37192445,38022834,85711894,41442762,68875490,82651278,79322415,17016156,62115552,76703609,93562257,41481685,77898274,79942022,53888755,92530431,82427263,56473732,71965942,36845587,51315460,70782102 +99861373,62740044,36580610,38494874,96318094,77620120,40984766,99524975,53648154,67281495,54517921,1022677,65880522,5970607,61373987,40197395,37659250,50806615,91990218,3088684,12416768,112651,62452034,93057697,8791066,19939935,84406788,97783876,61141874,29834512,7955293,526217,74441448,26664538,48360198,55470718,91802888,85242963,34698428,70004753,19101477,39986008,82651278,44481640,50188404,4978543,84840549,19376156,415901,46870723,27665211,37435892,15535065,6808825,49641577,78589145,89637706,49882705,99515901,30543215,30771409,39553046,9860195,35092039,7182642,43933006,5267545,68102437,59501487,1431742,79880247,1204161,29932657,66045587,69355476,18783702,37675718,64848072,14220886,94516935,56515456,38061439,63967300,29994197,24058273,87598888,95957797,2204165,1788101,96726697,26392416,29029316,55602660,39847321,69136837,64098930,57359924,29959549,4668450,12571310,91727510,10366309,52060076,20002147,25636669,54762643,42782652,75135448,92554120,89811711,99965001,28550822,91831487,53842979,99658235,22200378,34698463,6111563,68939068,33565483,45075471,36845587,35456853,29510992,66611759,60955663,38008118,66663942,57163802,80251430,83210802,44844121,95581843,36780454,99604946,71657078,99917599,45919976,64087743,24591705,96735716,5822202,569864,2917920,9188443,60106217,20140249,52293995,8696647,20765474,95726235,62496012,44915235,92541302,12664567,42199455,88047921,27411561,508198,28851716,44479073,18504197,42237907,50702367,65715134,77312810,91957544,93933709,88653118,77272628,64602895,13470059,33249630,83269727,37183543,72278539,15015906,92692283,321665,98462867,66428795,7685448,77413012,53632373,26998766,31733363,78884452,3233084,8055981,71083565,26744917,84127901,34295794,4199704,4204661,45790169,39801351,27185644,51426311,33237508,17146629,5111370,99549373,14947650,49597667,7423788,48892201,60430369,44177011,10358899,19272365,56424103,17081350,3235882,53802686,47213183,79657802,63015256,48774913,32274392,80014588,22405120,85711894,30998561,66667729,81172706,57240218,26776922,39201414,94595668,32250045,27041967,44348328,65074535,83083131,76703609,67451935,44784505,30569392,26275734,33431960,98371444,71300104,30764367,35192533,68204242,8128637,77284619,76330843,71522968,37192445,70372191,9259676,2717150,70367851,11365791,66832478,41481685,54058788,91240048,57803235,85023028,84904436,20681921,31904591,21397057,36312813,16971929,97011160,68000591,76315420,37620363,65038678,70596786,54868730,6038457,66250369,84349107,45428665,34946859,24826575,82726008,30653863,35996293,8129978,99021067,84187166,44842615,23134715,9257405,17068582,21001913,96906410,5482538,7300047,57961282,87160386,80555751,1239555,55753905,69579137,4434662,13231279,63628376,23740167,61741594,53666583,669105,96193415,80316608,49236559,36808486,33123618,61859143,96420429,74110882,68824981,20836893,65851721,86821229,35512853,60581278,99333375,75543508,76540481,61960400,53393358,62490109,33797252,61263068,60102965,45996863,10649306,92787493,51590803,18806856,21993752,30139692,37891451,72725103,82339363,40781854,2891150,82886381,86001008,30395570,76671482,77898274,57658654,30463802,36505482,44473167,21919959,90004325,15148031,12348118,74357852,93562257,93790285,65081429,90090964,48893685,17365188,92033260,95395112,81677380,67084351,73222868,78300864,40027975,86460488,77300457,4099191,7919588,47090124,74743862,50668599,9398733,33201905,247198,69786756,70800879,6525948,26063929,73021291,47708850,40686254,3880712,6153406,9058407,84684495,16380211,65338021,55770687,22129328,18833224,23569917,9599614,20867149,29065964,4091162,81898046,9603598,13173644,18411915,54199160,14326617,82979980,73786944,38365584,22997281,32590267,92215320,65047700,62803858,45848907,34493392,90457870,97940276,6505939,79191827,81853704,62693428,70420215,51149804,23848565,52204879,9575954,93515664,48675329,6157724,47298834,18663507,82052050,73124510,4985896,3294781,8807940,29819940,77187825,75820087,4508700,4064751,62552524,11923835,61982238,54606384,59109400,42947632,66137019,53084256,20885148,21289531,9886593,24915585,32161669,68128438,66903004,43501211,97641116,98943869,14731700,19898053,67793644,54427233,23194618,72793444,89214616,40677414,21673260,61623915,89419466,98653983,40356877,77301523,67474219,98130363,30218878,13468268,16054533,75986488,56484900,51047803,45407418,17857111,7104732,36930650,4515343,18131876,45049308,91664334,33061250,30163921,34497327,36685023,22721500,95010552,65017137,45990383,94076128,89078848,3633375,22450468,52613508,72357096,29188588,39171895,40152546,78549759,15064655,62430984,73617245,22766820,24733232,43322743,19457589,78561158,8115266,17727650,20486294,168541,39373729,72373496,79942022,49328608,53888755,77787724,32699744,33304202,26734892,5073754,3509435,83368048,65271999,41380093,75407347,72732205,63059024,27325428,89804152,77377183,37957788,24619760,88444207,20642888,28787861,13862149,78218845,5832946,59910624,82427263,57393458,26493119,66885828,89046466,11581181,44060493,85117092,55189057,17037369,8634541,67031644,11543098,52261574,58224549,31727379,48395186,33935899,69641513,48673079,20645197,31161687,79922758,56461322,3183975,96852321,46851987,93270084,92604458,75229462,30811010,13348726,7517032,6986898,48088883,20122224,36753250,92867155,22108665,36139942,91141395,69255765,2607799,99224392,50007421,26507214,83150534,91255408,44245960,40865610,21533347,4119747,72777973,62936963,74724075,76170907,22879907,14363867,90158785,75777973,57248122,98739783,73031054,74137926,68041839,1197320,47887470,99971982,33628349,38022834,7687278,89699445,1250437,31126490,58751351,9175338,88251446,79136082,36189527,61728685,68702632,30891921,4787945,43506672,58180653,55247455,45794415,6871053,63372756,69848388,16387575,51466049,6521313,80246713,44550764,16424199,99297164,88904910,38510840,19486173,76434144,72358170,44846932,63506281,45306070,4798568,42692881,65454636,47361209,72738685,30694952,36933038,38256849,68644627,56153345,16445503,68875490,68816413,97561745,17016156,34432810,61271144,15948937,29466406,30366150,56473732,69697787,61815223,32151165,34236719,83533741,46540998,73168565,86118021,41245325,10597197,44983451,59329511,25842078,98948034,14093520,54663246,17385531,75153252,18001617,94711842,17764950,32159704,90654836,36468541,6221471,8099994,73109997,53547802,14349098,44664587,92998591,2331773,51507425,72274002,3233569,59318837,36135,97281847,42073124,66319530,59371804,92398073,26114953,62762857,98648327,17957593,99125126,61928316,6793819,17539625,86301513,79821897,71469330,56955985,55960386,8651647,71920426,82178706,70727211,749283,42967683,90272749,73235980,83651211,9623492,14045383,75128745,1569515,67030811,78766358,51715482,34044787,79738755,85116755,90013093,22435353,86543538,26102057,86022504,17894977,93053405,84002370,58749,63152504,62051033,68110330,71333116,16099750,824872,14626618,51464002,31491938,26292919,30787683,77072625,37280276,33336148,32426752,69605283,10309525,38645117,64157906,55143724,42307449,94360702,83155442,72238278,5640302,92530431,9860968,22113133,44889423,47738185,28796059,62115552,36396314,15902805,8733068,26139110,95251277,82897371,62357986,21070110,78602717,41442762,26863229,55669657,34667286,60176618,28734791,32058615,58208470,24953498,72019362,16405341,23432750,15536795,18699206,33553959,29401781,36812683,41092102,41092172,97057187,70036438,54232247,14723410,45237957,11757872,55615885,85695762,38658347,59197747,3487592,16097038,51588015,67227442,90310261,92071990,92353856,85571389,84293052,3773993,7502255,51315460,48260151,94911072,17976208,60393039,99729357,72495719,2208785,40534591,37739481,43045786,94090109,16019925,77694700,45667668,37224844,17058722,18466635,76960303,64055960,94539824,89499542,10961421,12024238,86242799,33699435,93359396,92803766,50567636,83789391,93566986,57241521,28928175,15075176,71316369,79322415,65275241,13819358,51472275,47893286,45617087,874791,42644903,89996536,24168411,8373289,37560164,78909561,59614746,176257,6497830,96709982,43152977,88698958,83948335,56531125,86798033,90061527,17386996,43376279,62232211,83302115,73392814,66322959,59981773,62428472,29516592,92692978,91938191,4069912,61712234,66116458,71965942,45995325,55850790,70541760,61859581,74452589,10264691,89217461,44847298,87720882,87710366,83747892,7011964,95290172,76825057,59177718,4095116,54014062,7066775,49271185,12303248,72614359,37501808,81774825,6819644,20568363,16595436,79806380,8729146,45381876,23110625,82532312,38009615,4806458,37166608,61897582,68694897,74614639,81805959,9829782,1375023,81274566,27187213,38341669,29635537,7229550,83133790,15163258,57020481,11161731,10453030,78785507,54263880,7646095,8913721,44627776,19891772,40781449,88130087,97379790,67124282,16684829,30090481,22942635,91281584,33895336,67513640,99690194,75052463,27625735,99226875,84166196,48658605,26397786,54987042,1936762,49598724,82327024,59405277,16567550,68316156,24314885,47792865,66271566,70782102,81855445,82779622,43357947 +91957544,60102965,44842615,56424103,66250369,39801351,62803858,29959549,73168565,53666583,97379790,85023028,35192533,88047921,14093520,24619760,95957797,38061439,89637706,98648327,59177718,25842078,81172706,415901,16971929,74452589,99524975,84349107,4204661,34044787,62496012,14349098,45848907,39553046,89078848,47708850,23134715,40197395,19939935,6153406,90004325,12416768,9886593,74110882,8807940,71083565,42644903,72357096,77620120,35996293,70596786,4099191,59910624,67474219,77284619,9575954,82979980,30395570,84904436,33237508,93933709,20140249,33201905,10649306,29819940,46540998,7685448,55143724,6221471,24733232,42782652,20765474,66832478,68000591,65454636,48260151,55753905,14220886,79657802,89804152,51588015,15075176,20681921,78218845,22942635,77300457,16097038,26776922,2717150,19891772,96726697,58180653,18001617,19272365,95581843,49641577,94516935,90457870,8651647,55669657,74357852,89419466,73031054,6525948,75986488,19486173,58208470,53888755,93515664,60955663,3235882,92215320,34295794,40781854,57248122,14947650,77312810,32426752,16380211,99965001,81853704,76330843,13862149,43322743,9599614,18699206,6111563,75777973,42073124,64087743,34698463,24591705,34698428,71522968,70036438,90310261,18411915,89046466,76170907,44983451,99658235,59318837,34946859,12664567,61741594,8913721,176257,8733068,79821897,72274002,8055981,32699744,2208785,3773993,99861373,90090964,66428795,29994197,749283,8099994,91831487,40027975,29466406,7517032,67227442,43045786,85571389,53648154,21533347,8791066,88653118,73222868,83150534,10309525,78602717,69697787,82897371,4798568,36780454,75543508,58749,31904591,67281495,36505482,112651,7955293,70727211,85242963,36808486,62452034,37891451,4119747,39201414,26507214,44481640,7011964,41442762,30998561,28734791,74614639,36753250,7229550,12571310,56531125,73235980,76540481,54663246,50188404,5832946,35456853,32274392,11543098,76671482,69641513,22405120,83269727,18466635,30811010,38494874,45995325,45667668,97561745,30090481,6497830,3233084,508198,49236559,68644627,92604458,29065964,61271144,34236719,6986898,73786944,51315460,9257405,5111370,5970607,93053405,7182642,64848072,54517921,44348328,77272628,33797252,45790169,3183975,59371804,17386996,94360702,80316608,54868730,95290172,17058722,26114953,50668599,94711842,29834512,37957788,98653983,87710366,97783876,45919976,17037369,247198,50702367,61960400,66319530,30764367,24826575,89699445,51047803,95395112,27625735,82178706,44177011,2204165,98948034,28550822,89214616,98943869,70367851,84187166,40356877,81677380,82052050,92787493,14326617,36845587,26392416,4806458,20486294,30891921,63015256,8696647,29510992,27411561,47738185,83747892,32161669,13470059,81898046,48893685,9058407,73124510,49597667,46870723,23848565,68102437,20836893,526217,83651211,69255765,4091162,16387575,20568363,72278539,51507425,45049308,17539625,824872,31733363,28851716,49882705,15536795,68702632,62490109,45306070,89996536,67793644,18783702,30139692,55602660,33249630,23569917,40686254,68204242,74441448,44245960,80555751,43933006,37183543,18504197,47298834,40781449,45407418,66322959,70420215,17857111,669105,33304202,75229462,37739481,20642888,50007421,72725103,93566986,99224392,92803766,42692881,30771409,10961421,94076128,57020481,59329511,92398073,55850790,26493119,78561158,26744917,30543215,13231279,87598888,61859143,86543538,90272749,31126490,55247455,54762643,34493392,48774913,36580610,10358899,92530431,71657078,83789391,68041839,50806615,6505939,75820087,38008118,92692283,57359924,70004753,62693428,6871053,68824981,72777973,75153252,84406788,99549373,51426311,91664334,98739783,17016156,6157724,24058273,27185644,7502255,74724075,36812683,2607799,88904910,86001008,1204161,71333116,91802888,55470718,30163921,6808825,9860195,70541760,15064655,1375023,98371444,33553959,99125126,21397057,23110625,12348118,27325428,84684495,55615885,44844121,72495719,49271185,27041967,59501487,27665211,48360198,33431960,40984766,9603598,43501211,86118021,63372756,79191827,62740044,16595436,92692978,76315420,78589145,74743862,40677414,68816413,57658654,94090109,38645117,17764950,32590267,9398733,29029316,52204879,5267545,48892201,20885148,4434662,66663942,69786756,22721500,63506281,60176618,321665,53632373,168541,83155442,3233569,9623492,1250437,62051033,86242799,41380093,91938191,70800879,90061527,16405341,61712234,72019362,11757872,17957593,26734892,1936762,23194618,74137926,1431742,44915235,83083131,17894977,22766820,79806380,44889423,54987042,85117092,85116755,53802686,51472275,4095116,82886381,99333375,78300864,86821229,3633375,97281847,47213183,31727379,3880712,66045587,13819358,54014062,48673079,11161731,4199704,66137019,26102057,99604946,3294781,67084351,55189057,52293995,68694897,61373987,30653863,88698958,37435892,21070110,92033260,45428665,99971982,43357947,4668450,77787724,6793819,66667729,49328608,99729357,38009615,39986008,54606384,57240218,87720882,37280276,18833224,15163258,39847321,80014588,1022677,26292919,17068582,63967300,93562257,16684829,88251446,33123618,62430984,44784505,51715482,9175338,60430369,48675329,28796059,47361209,48088883,42967683,36312813,54199160,45996863,35092039,77072625,62936963,43152977,67451935,79942022,44473167,4978543,72358170,8729146,44479073,86301513,82726008,71920426,20122224,33628349,42307449,9829782,70372191,53547802,91141395,78766358,65880522,37675718,54263880,63152504,54427233,72238278,41245325,84293052,26863229,4064751,26998766,97057187,4985896,65017137,99021067,10366309,66903004,13173644,45617087,83133790,77377183,43376279,59109400,4508700,48395186,99917599,16445503,90013093,85711894,37192445,24915585,38256849,30569392,65074535,68110330,33061250,43506672,64098930,96193415,80246713,77301523,47887470,22200378,15148031,45237957,61982238,35512853,92998591,71300104,85695762,44627776,89499542,71469330,14626618,17727650,36933038,31161687,7919588,24953498,28787861,30218878,46851987,82327024,17081350,42237907,16019925,99690194,20645197,45381876,59197747,9188443,26139110,94539824,30366150,45075471,61263068,89811711,99226875,26664538,12024238,73392814,83948335,37659250,32159704,64055960,84166196,8128637,45794415,20867149,95726235,29635537,82779622,60106217,2331773,63059024,38341669,10453030,51590803,81855445,55770687,79136082,29188588,47792865,76434144,61859581,18663507,11923835,84127901,36135,92353856,66116458,2917920,22435353,16424199,36685023,61141874,88444207,91990218,62552524,67031644,76960303,72373496,72614359,77898274,31491938,22108665,19101477,50567636,52261574,60581278,61728685,18131876,36396314,82339363,5822202,75407347,63628376,90654836,92554120,5073754,34667286,54058788,4069912,44846932,38022834,19457589,33699435,1197320,97011160,87160386,17365188,39373729,98462867,48658605,56515456,37501808,40152546,22450468,68939068,22129328,39171895,36930650,5482538,65081429,41481685,18806856,86460488,69848388,51464002,44550764,29401781,65275241,99297164,82532312,65715134,96318094,14723410,17385531,67124282,17976208,6038457,64602895,77187825,21673260,9259676,7687278,33935899,14045383,84840549,96906410,78909561,4515343,21993752,10597197,59614746,75052463,42199455,1239555,82427263,56955985,96709982,34432810,51466049,1569515,83210802,14363867,93270084,56473732,15015906,29516592,73617245,62115552,21289531,22879907,22113133,66611759,84002370,72738685,81805959,16567550,99515901,30463802,59405277,76703609,15535065,96852321,7646095,15948937,58751351,83368048,21001913,72732205,77413012,67513640,86022504,83533741,47090124,7104732,8115266,49598724,36468541,11581181,41092102,65038678,97940276,57803235,69355476,92541302,68128438,56461322,44060493,89217461,76825057,3088684,77694700,61815223,78884452,66885828,33895336,69605283,75135448,79738755,95251277,52613508,26397786,91281584,71316369,67030811,93057697,91240048,32058615,72793444,56484900,57961282,79922758,56153345,20002147,25636669,96420429,41092172,2891150,5640302,14731700,37560164,88130087,16099750,82651278,55960386,33565483,30787683,15902805,1788101,7423788,6521313,60393039,3509435,93790285,3487592,29932657,86798033,47893286,17146629,96735716,6819644,57241521,91255408,8634541,874791,52060076,57393458,62762857,80251430,9860968,94911072,92071990,61897582,97641116,65271999,69136837,36139942,19898053,44664587,79322415,53084256,65047700,54232247,28928175,53842979,32250045,79880247,94595668,8129978,95010552,90158785,37166608,30694952,98130363,13348726,62428472,91727510,21919959,32151165,37620363,26275734,19376156,81274566,81774825,24168411,37224844,23432750,59981773,64157906,26063929,40865610,36189527,68875490,7300047,569864,51149804,65851721,69579137,13468268,75128745,66271566,11365791,23740167,57163802,38658347,22997281,44847298,65338021,27187213,58224549,78549759,10264691,16054533,62232211,83302115,42947632,61623915,62357986,92867155,45990383,34497327,93359396,7066775,8373289,68316156,40534591,24314885,38510840,33336148,73021291,4787945,53393358,12303248,78785507,71965942,73109997,38365584,61928316,70782102 +3233084,112651,42199455,53084256,81853704,33304202,97561745,24733232,99965001,3235882,56515456,38061439,26063929,29188588,75135448,31161687,73222868,45407418,5267545,38645117,6221471,1022677,55143724,76671482,30653863,62496012,59109400,92554120,38256849,321665,51047803,669105,17146629,63628376,74357852,15535065,92692978,86022504,44060493,45790169,33895336,33237508,89637706,96193415,65047700,60430369,67793644,42967683,42782652,60581278,8129978,30366150,62232211,30543215,37675718,29466406,26664538,7011964,10358899,59981773,83789391,92692283,37183543,81677380,15015906,55753905,33249630,12024238,61741594,8115266,45381876,21001913,75229462,44983451,33123618,526217,57961282,73617245,88653118,4199704,168541,7423788,72725103,54232247,85117092,50188404,1431742,29029316,6808825,13173644,84349107,13470059,34295794,63967300,10597197,824872,99658235,569864,68939068,67281495,36580610,16595436,29834512,3633375,80246713,81898046,83210802,66319530,7646095,57803235,33565483,66832478,7229550,30139692,55770687,35192533,3088684,69697787,68204242,62452034,98462867,66116458,76315420,51472275,72614359,65271999,44889423,44550764,31126490,78218845,83302115,41380093,83533741,99690194,93270084,48658605,24953498,84904436,39847321,98130363,17365188,72274002,72738685,28550822,43933006,15163258,37659250,36685023,36812683,55615885,56531125,14731700,415901,66611759,77787724,23194618,53842979,63015256,85571389,18806856,78884452,89214616,26998766,59318837,36505482,38658347,749283,77312810,92071990,39201414,4099191,7300047,92604458,54058788,43045786,95581843,1788101,92787493,86301513,65454636,32590267,22435353,77694700,61141874,43501211,88444207,29994197,16097038,34698428,76170907,70004753,10961421,4064751,30395570,55247455,54199160,66667729,82726008,27185644,16971929,72278539,96709982,16424199,37435892,63152504,36930650,39171895,18131876,3509435,17894977,52613508,45617087,61263068,70420215,1204161,70036438,66045587,99549373,61623915,82339363,94516935,1250437,70800879,82178706,46540998,9860968,62357986,93933709,69641513,14093520,22942635,57240218,16445503,80316608,61271144,88047921,65851721,62740044,93057697,68041839,77300457,80251430,10366309,72373496,50567636,9398733,50668599,98371444,17727650,78549759,4069912,5482538,59197747,9058407,90457870,16684829,23432750,19101477,47090124,32159704,24058273,69355476,54517921,4508700,65715134,9886593,36189527,77284619,20002147,68644627,90013093,33797252,17957593,98653983,12303248,2891150,30569392,83269727,19939935,9175338,96726697,29065964,30218878,57241521,61728685,91831487,44847298,12348118,54014062,72495719,7066775,75407347,64602895,78589145,44784505,45306070,55189057,26102057,71333116,25842078,70596786,49236559,75153252,4985896,55470718,91802888,22450468,40152546,84684495,78909561,24619760,8634541,18699206,66428795,93053405,66137019,90654836,20642888,40534591,22108665,34044787,84187166,11365791,33336148,247198,38008118,36780454,34493392,21070110,45237957,75986488,17539625,49271185,15148031,4668450,69786756,39986008,85116755,48673079,35092039,8696647,13231279,77072625,32274392,4119747,36753250,40356877,44627776,874791,8807940,82979980,46851987,38365584,33628349,90272749,37739481,52060076,27411561,43322743,87710366,9623492,14363867,64848072,57359924,51466049,29819940,56424103,61373987,21533347,9259676,74110882,53648154,83150534,14626618,21993752,27665211,92998591,54868730,13468268,15064655,14723410,68694897,66885828,30998561,23848565,22129328,48088883,63059024,35996293,37891451,15536795,22997281,40677414,63372756,45667668,58224549,56461322,60102965,90061527,24915585,508198,99917599,48774913,68875490,50806615,13819358,3880712,22113133,17058722,93790285,77620120,42237907,70372191,8128637,50702367,61859143,79657802,70541760,68000591,3773993,37166608,30463802,62936963,15075176,62430984,68702632,91664334,34236719,7955293,37620363,34946859,65038678,49641577,27325428,22879907,67031644,24591705,22200378,40865610,99226875,49597667,99125126,79191827,74743862,86001008,79922758,27625735,97011160,61982238,62693428,21289531,97940276,5111370,67227442,88251446,10309525,95290172,99297164,28851716,82427263,18504197,82651278,31733363,70727211,31904591,62552524,20122224,66250369,76434144,55602660,78300864,19891772,71469330,6793819,40197395,20867149,92541302,47738185,2204165,12416768,79738755,1239555,69136837,7502255,26114953,44481640,14349098,61859581,79821897,5970607,96735716,38341669,72777973,34497327,13862149,29510992,76540481,59614746,12664567,65338021,33201905,59405277,83133790,19376156,57163802,95010552,74724075,83083131,94595668,17016156,36135,50007421,18663507,86460488,73124510,6986898,1197320,92803766,62428472,95395112,41481685,86821229,44245960,92215320,26776922,4095116,48360198,67451935,73168565,73021291,26292919,36845587,16099750,99861373,64157906,64087743,59177718,9188443,20568363,44842615,86798033,41092172,66663942,30163921,8099994,26392416,45075471,61897582,23569917,63506281,73031054,78785507,51426311,24168411,98739783,91727510,56955985,26493119,9860195,72732205,4091162,84127901,85711894,65275241,94090109,54987042,48675329,87598888,38494874,20645197,59910624,46870723,19272365,54663246,36468541,41092102,6111563,52261574,65081429,22721500,37501808,57658654,59329511,54427233,44348328,45919976,33431960,26275734,8055981,20486294,36312813,40686254,53547802,82052050,99604946,40781449,69848388,8791066,67084351,66271566,45848907,10649306,51507425,77301523,99971982,11923835,16019925,29401781,34432810,30811010,73786944,12571310,65880522,39553046,14045383,43152977,69579137,52293995,24826575,57248122,45996863,59501487,6038457,9603598,36808486,74452589,44915235,90090964,14326617,3487592,82532312,99515901,94076128,71657078,29932657,6153406,59371804,95726235,92033260,60393039,40027975,61815223,53888755,88698958,17037369,78602717,54762643,78766358,16380211,71300104,13348726,95957797,47213183,43376279,76330843,28787861,18783702,18411915,60955663,34667286,25636669,73392814,75820087,7919588,47708850,14220886,93566986,97057187,44846932,32161669,97783876,93515664,85023028,35512853,91990218,20836893,3294781,62051033,55669657,68816413,18833224,72358170,15948937,28734791,44479073,96318094,28928175,19486173,53632373,86543538,47361209,71316369,88904910,28796059,52204879,98943869,55960386,6157724,31491938,51588015,89217461,81805959,99224392,51464002,58749,83368048,36396314,81855445,77187825,17386996,37192445,83651211,74614639,61928316,37560164,56484900,7182642,80014588,2917920,95251277,176257,87720882,7104732,66322959,30764367,77272628,89811711,86242799,1375023,70367851,74137926,72793444,37280276,84406788,26744917,45995325,6521313,32426752,69255765,6871053,33553959,62803858,81274566,84840549,5822202,43506672,99021067,42947632,94539824,89046466,86118021,17764950,89078848,7687278,68824981,7685448,76703609,33699435,69605283,89419466,62490109,5832946,74441448,6497830,68128438,18001617,91281584,4787945,44177011,91255408,34698463,17385531,81774825,4515343,3233569,65017137,49598724,40984766,32699744,96906410,9599614,58180653,2208785,17068582,93359396,20681921,1569515,71083565,99524975,43357947,5073754,20765474,16387575,30787683,8373289,76825057,72238278,97281847,16405341,73235980,8651647,6819644,30771409,4806458,47298834,4434662,89699445,42307449,53802686,48892201,4978543,45428665,67474219,49882705,81172706,87160386,96420429,94911072,23110625,68102437,39801351,83155442,7517032,48893685,38022834,48395186,73109997,15902805,23740167,19898053,11543098,79322415,2717150,26139110,64098930,91240048,44473167,66903004,3183975,8729146,60176618,36933038,10264691,26863229,83747892,99333375,27187213,38009615,77377183,85242963,22405120,90310261,77413012,9829782,45990383,98648327,62762857,75777973,35456853,96852321,67030811,11161731,20885148,78561158,47792865,16567550,37224844,72357096,22766820,51149804,91938191,31727379,94360702,58751351,4798568,29516592,29635537,45794415,92867155,27041967,49328608,54263880,40781854,92530431,39373729,93562257,94711842,71965942,14947650,70782102,85695762,41442762,82886381,82327024,32250045,51315460,29959549,30891921,5640302,56473732,57020481,92353856,54606384,6505939,6525948,26397786,42073124,10453030,9257405,20140249,76960303,90004325,92398073,33061250,56153345,84002370,26734892,4204661,36139942,71522968,44664587,21919959,75543508,65074535,26507214,62115552,84293052,17976208,18466635,42644903,23134715,11581181,51590803,2607799,79880247,33935899,19457589,98948034,57393458,16054533,71920426,21673260,2331773,42692881,60106217,24314885,67124282,51715482,68316156,97641116,97379790,89804152,21397057,83948335,88130087,91957544,79136082,55850790,41245325,99729357,79942022,17857111,37957788,90158785,91141395,64055960,17081350,80555751,47887470,53393358,9575954,68110330,1936762,32058615,38510840,75052463,89499542,8913721,82779622,44844121,72019362,58208470,30694952,8733068,47893286,61960400,32151165,79806380,53666583,11757872,77898274,82897371,61712234,75128745,30090481,89996536,48260151,84166196,45049308,67513640 +20002147,99965001,51047803,37183543,60581278,96193415,59177718,36780454,45617087,38645117,61982238,98462867,4064751,60430369,9623492,53084256,57961282,74357852,72274002,7300047,36753250,27665211,85116755,29834512,34236719,72725103,21993752,1431742,82979980,43376279,10358899,45407418,6793819,90090964,17764950,98371444,99549373,88047921,14626618,88698958,33249630,77072625,44983451,58751351,31126490,22450468,17068582,39847321,98130363,97011160,65338021,79922758,83210802,2917920,29029316,67793644,40356877,14220886,45237957,22129328,77787724,95290172,44550764,92554120,55189057,86022504,5970607,72238278,63059024,64848072,94090109,3233084,40984766,57803235,14045383,38022834,47090124,75543508,36505482,38658347,45428665,91802888,83269727,73786944,22997281,12664567,15015906,36580610,4069912,14731700,40152546,59371804,65271999,73222868,81898046,53842979,56153345,67281495,96852321,31904591,40781449,7229550,84684495,45794415,20642888,28796059,30395570,99524975,93270084,54058788,8807940,37659250,55753905,50668599,83789391,64087743,13173644,82726008,44479073,15148031,93057697,36312813,14947650,34698428,72732205,569864,66667729,49236559,99515901,55850790,57393458,24058273,62496012,7646095,78602717,14363867,96906410,10309525,27411561,72357096,16567550,88653118,50702367,8099994,37501808,63967300,77272628,67451935,25636669,9603598,92353856,12348118,24168411,30764367,62740044,22435353,68875490,30218878,20568363,15535065,76671482,32590267,6986898,23194618,52060076,92803766,59614746,59109400,72373496,30463802,18699206,87720882,13468268,49641577,21289531,415901,96735716,68204242,53648154,4806458,82339363,62936963,39171895,70004753,18131876,91831487,19939935,18806856,44889423,10264691,56461322,61728685,96318094,37435892,96726697,62430984,81274566,30771409,29188588,89214616,69697787,30569392,65047700,54014062,8129978,41092102,95957797,37620363,65715134,76315420,30653863,99971982,824872,16445503,38365584,93562257,27041967,32161669,7919588,34493392,71333116,53802686,16595436,73617245,61623915,77694700,68041839,11923835,40534591,73031054,61263068,51472275,69641513,84840549,22200378,3088684,48260151,15163258,81677380,69355476,9058407,71965942,91990218,43152977,89078848,66271566,46870723,38061439,1197320,30163921,33797252,63152504,44844121,33431960,56473732,9257405,32250045,42644903,10366309,37166608,80316608,90457870,20122224,75153252,77284619,44348328,33123618,51590803,3487592,36812683,34497327,93053405,176257,55143724,63628376,27185644,35192533,669105,70367851,62452034,65275241,61859581,43322743,51588015,66885828,96420429,45995325,6808825,91664334,66832478,35092039,13819358,45381876,66903004,29959549,3233569,42967683,1936762,79738755,70727211,84904436,57241521,56515456,4099191,20645197,49598724,12416768,63015256,74110882,94516935,7011964,89804152,5832946,55615885,78561158,93933709,70036438,85117092,99917599,54232247,112651,9886593,7182642,72793444,92215320,99297164,26998766,39553046,55470718,83150534,8373289,14093520,7423788,3880712,4095116,91727510,20140249,33304202,24314885,19101477,20486294,46851987,29994197,1204161,77312810,72278539,61815223,18466635,64602895,41481685,23432750,40197395,8115266,74743862,74614639,87710366,65038678,80251430,75135448,23569917,9188443,73021291,66250369,54987042,70420215,54606384,21001913,37891451,43933006,90310261,96709982,91255408,78218845,11161731,39373729,19486173,28550822,7502255,90272749,33336148,97783876,32159704,33201905,77413012,15536795,2204165,42199455,68644627,74724075,4434662,93515664,44481640,26292919,47738185,92033260,44784505,6521313,99604946,78589145,526217,54663246,4668450,42782652,15064655,6157724,90013093,43045786,66663942,33553959,78909561,26063929,74441448,39986008,92604458,85242963,59318837,48893685,14326617,36685023,42237907,71083565,59981773,321665,11365791,42073124,66322959,71316369,31733363,66428795,65081429,92398073,10597197,44664587,26139110,67474219,80014588,40781854,54517921,57248122,16684829,6221471,8128637,5073754,53632373,86460488,16405341,78884452,6819644,29932657,83533741,2717150,68000591,17365188,78785507,11543098,70800879,16099750,31161687,59501487,20765474,89419466,16971929,33237508,16019925,84406788,8791066,21673260,33565483,40686254,45306070,61859143,51464002,83083131,79821897,52613508,22108665,92692978,84349107,17539625,70596786,16380211,40027975,24619760,84293052,6497830,56531125,51466049,38510840,45990383,33699435,17016156,76960303,90061527,87598888,49271185,9860968,26392416,28928175,508198,44245960,27187213,42947632,83302115,26275734,34946859,85711894,53547802,84127901,82427263,44060493,45996863,47361209,36808486,48892201,50567636,67084351,50188404,77620120,69605283,1250437,69848388,29819940,49328608,72019362,12024238,79657802,77377183,75229462,3294781,16097038,13231279,92787493,16424199,5267545,44846932,99333375,53393358,82052050,73235980,68694897,6153406,65017137,48673079,45790169,26397786,76434144,71657078,15902805,30694952,55770687,26493119,36135,76703609,30543215,82178706,44842615,18663507,66137019,85571389,53888755,7955293,20867149,43501211,71300104,56424103,89217461,66045587,16387575,58224549,60393039,54762643,22721500,81853704,24591705,50007421,62693428,61897582,26664538,31491938,97561745,62552524,37560164,73168565,92541302,80555751,76540481,68702632,42307449,30139692,38008118,61741594,5111370,65851721,95395112,89637706,17727650,35996293,78549759,22766820,4515343,48088883,12571310,82886381,47893286,99861373,8696647,1788101,43357947,88251446,95010552,24733232,32058615,23110625,90654836,26776922,247198,30787683,69579137,4787945,72777973,86242799,61271144,14723410,97057187,4204661,97641116,40865610,32274392,37192445,83368048,82779622,68816413,75407347,98648327,91141395,60102965,69136837,8651647,22113133,17385531,874791,16054533,86543538,24826575,36930650,59197747,48774913,72738685,9259676,62762857,3509435,8634541,98739783,43506672,19376156,41092172,55247455,44627776,9829782,42692881,20885148,62428472,75777973,68128438,26114953,88444207,71469330,168541,58208470,31727379,62115552,85695762,34432810,61373987,70541760,3773993,94076128,36933038,83651211,61141874,69786756,99125126,94595668,45848907,66116458,1022677,58180653,68939068,18504197,22879907,57240218,29466406,4119747,24915585,70372191,81805959,56484900,2891150,88904910,36189527,82651278,57163802,92071990,81172706,5482538,44177011,54199160,3633375,18833224,5640302,17976208,77300457,7066775,1239555,84187166,20681921,55960386,64055960,79136082,92692283,45919976,26102057,92530431,40677414,39201414,13348726,17957593,92867155,57658654,62051033,75128745,67030811,71920426,36468541,9398733,86798033,82897371,12303248,8733068,25842078,64098930,17894977,21070110,8729146,86001008,50806615,41245325,38494874,71522968,79880247,48658605,92998591,26507214,74137926,90004325,29516592,1375023,52261574,99690194,27625735,4091162,19891772,62490109,36396314,5822202,89996536,36139942,79806380,94911072,9175338,81855445,79942022,2607799,78766358,2208785,28734791,6505939,63372756,76825057,21397057,26734892,99226875,89046466,93566986,35512853,99021067,41380093,94539824,46540998,32699744,8055981,58749,21533347,77898274,68110330,86118021,64157906,54427233,73124510,7685448,55602660,30891921,14349098,89811711,37739481,78300864,32426752,17857111,6038457,17386996,91281584,10649306,32151165,9575954,83948335,3183975,29065964,66611759,4199704,1569515,65880522,91240048,68824981,60955663,73392814,61960400,44473167,94711842,7517032,66319530,88130087,35456853,22405120,98943869,47887470,13470059,99224392,47298834,56955985,17146629,68102437,65454636,79191827,77187825,9860195,83155442,17081350,76170907,11581181,23848565,72358170,89699445,91938191,97940276,26863229,30366150,80246713,45075471,30811010,38341669,75820087,27325428,6525948,30998561,72614359,75986488,6871053,62803858,34698463,81774825,93359396,83133790,20836893,57020481,13862149,82327024,18783702,99658235,17058722,86301513,49597667,15075176,51426311,62232211,22942635,3235882,34667286,60106217,95581843,79322415,90158785,73109997,93790285,48675329,11757872,63506281,7104732,29635537,74452589,2331773,59910624,749283,19457589,86821229,4508700,29401781,61928316,52293995,33895336,34295794,62357986,29510992,23134715,49882705,37957788,84002370,45667668,69255765,38009615,97379790,19272365,98653983,26744917,47792865,18001617,18411915,94360702,65074535,59329511,53666583,85023028,67227442,9599614,61712234,41442762,15948937,95251277,76330843,68316156,10453030,34044787,28787861,75052463,77301523,38256849,4985896,84166196,52204879,70782102,48360198,72495719,33628349,67513640,37224844,89499542,47213183,59405277,36845587,39801351,7687278,87160386,67124282,37675718,47708850,97281847,99729357,24953498,10961421,23740167,95726235,51507425,28851716,51715482,67031644,33935899,55669657,44847298,17037369,8913721,37280276,4798568,57359924,54868730,60176618,48395186,51149804,19898053,98948034,82532312,54263880,4978543,33061250,44915235,91957544,6111563,30090481,45049308,21919959,83747892,51315460 +21993752,99515901,14731700,47361209,37620363,77312810,14220886,6986898,55247455,32159704,66832478,55753905,10309525,59197747,52261574,86543538,76960303,66250369,15015906,1569515,96193415,26139110,62552524,53666583,67793644,45237957,9886593,14626618,29834512,83150534,77284619,15902805,17764950,78602717,1204161,4064751,48892201,67281495,77413012,14947650,51047803,24619760,62496012,50702367,30463802,55470718,54606384,59177718,92033260,9257405,93933709,27665211,68939068,67124282,48260151,40865610,91664334,83651211,75543508,669105,18504197,70420215,54014062,82178706,48675329,99524975,20486294,35192533,77072625,41245325,42692881,80014588,6808825,96726697,33935899,12571310,23848565,95726235,84684495,73786944,33249630,44842615,31904591,92787493,79821897,45848907,66322959,45381876,54199160,99604946,1197320,62936963,98130363,72373496,82726008,38494874,3487592,96420429,66045587,17068582,69355476,16405341,56153345,42644903,55850790,26292919,85242963,48893685,66137019,49641577,12664567,36930650,10366309,47090124,78909561,81677380,70036438,65038678,7011964,50007421,53632373,53802686,73124510,69848388,89214616,74137926,77272628,88653118,78766358,40781854,3233569,90272749,34497327,36753250,7300047,569864,38009615,36780454,30764367,16595436,88904910,75128745,32151165,81855445,20002147,32161669,88047921,24915585,89217461,14349098,68816413,71316369,63372756,11757872,26998766,44177011,17386996,45428665,60430369,74614639,22997281,20122224,96735716,29959549,81274566,59910624,85116755,16445503,51464002,45617087,68875490,24058273,99917599,74357852,4119747,16387575,35092039,44889423,76330843,40356877,64848072,37501808,21533347,57248122,33061250,42307449,74110882,98371444,66116458,37224844,92692978,50567636,99965001,51426311,75153252,22766820,53393358,95251277,79136082,72357096,6819644,415901,53084256,7182642,66667729,44983451,62428472,43376279,1250437,44847298,247198,94090109,46870723,21673260,18699206,71333116,69786756,8634541,78589145,64087743,78884452,36808486,6221471,86460488,19101477,99125126,60102965,42199455,28550822,12024238,93053405,44784505,37675718,68644627,176257,3235882,61728685,65715134,4508700,38658347,5832946,74724075,20140249,8807940,69255765,60106217,37183543,29932657,58751351,65017137,87598888,44479073,14093520,90310261,90013093,44473167,3294781,83948335,62452034,26863229,44481640,22942635,53648154,82339363,84127901,38061439,526217,17385531,64602895,99021067,20645197,24591705,56515456,22450468,17365188,82052050,68694897,77300457,34044787,72614359,84840549,6793819,10358899,88444207,13348726,37891451,62803858,40534591,61859581,36312813,79922758,50806615,30163921,92541302,3233084,28796059,17081350,86001008,42967683,39171895,94911072,93566986,27187213,29065964,51466049,54663246,31733363,66428795,39553046,73168565,62051033,49328608,68102437,21289531,61373987,57803235,18466635,9398733,63015256,85571389,30395570,824872,34236719,55143724,34493392,38645117,76315420,40781449,63059024,34946859,73235980,26734892,508198,73031054,65851721,78218845,2204165,57241521,89046466,69641513,19939935,22129328,66319530,8729146,68000591,54427233,32058615,99226875,99224392,92353856,33201905,7104732,4099191,11543098,4515343,54517921,65271999,43152977,16971929,40677414,26102057,55189057,13173644,82897371,97783876,39801351,42237907,48774913,74452589,5111370,17894977,5073754,92071990,321665,43501211,50188404,35512853,26275734,70596786,59371804,61982238,96318094,49236559,40197395,99861373,8651647,6871053,32699744,63967300,86301513,9259676,80316608,30366150,99549373,61263068,42073124,26392416,9603598,76170907,66903004,94711842,95290172,31491938,36505482,1431742,62430984,15535065,75777973,30139692,1936762,75229462,8696647,2891150,82979980,91802888,74743862,66611759,6505939,78561158,67451935,68702632,97011160,29029316,112651,55960386,33304202,6153406,69579137,23194618,92215320,76434144,81898046,76703609,76540481,73222868,22405120,56531125,64157906,39847321,16567550,81805959,24826575,45407418,22113133,73392814,42782652,62693428,30891921,77620120,18411915,26776922,30653863,4069912,57961282,57393458,2917920,68316156,57240218,56461322,53547802,49271185,79657802,40027975,75986488,44060493,29819940,40152546,85711894,77694700,30694952,45075471,33628349,24953498,14045383,5970607,47792865,26397786,32426752,30787683,57020481,48360198,83789391,80555751,69697787,16684829,46851987,94516935,44627776,83747892,8733068,85023028,44550764,68824981,59981773,29994197,12348118,47738185,31126490,19898053,82651278,16054533,45306070,20836893,58749,63506281,4091162,72274002,64098930,7517032,7687278,3880712,91831487,70541760,18806856,70800879,14723410,24168411,98739783,62232211,9623492,48395186,91240048,14363867,70727211,17957593,29188588,8373289,98648327,58208470,75135448,27041967,72732205,89637706,12416768,72495719,38022834,66271566,3183975,33699435,57658654,36812683,49598724,41092172,40686254,97057187,37659250,82327024,9829782,20568363,90654836,18833224,28928175,96906410,71657078,7919588,15163258,90090964,69136837,61141874,10649306,60581278,20765474,76671482,46540998,36933038,15948937,6038457,71920426,99297164,34295794,8115266,73021291,32250045,4204661,35456853,44846932,91990218,30998561,16019925,24314885,36139942,2331773,58180653,27411561,35996293,72793444,4095116,9175338,17727650,4434662,92998591,7229550,72019362,68128438,34698463,92554120,83210802,4798568,57359924,22200378,90457870,71469330,81853704,18131876,86118021,61712234,1788101,92692283,94539824,56424103,26507214,19376156,1022677,79880247,48088883,37560164,54232247,49882705,83368048,60176618,47298834,18783702,95395112,79806380,39373729,44844121,93057697,22435353,89499542,92604458,15148031,7423788,56484900,68041839,50668599,8099994,51149804,13819358,72238278,44664587,58224549,42947632,8129978,38365584,80246713,45990383,25842078,99690194,16097038,72738685,61271144,67030811,99971982,21070110,37166608,70004753,17857111,69605283,2208785,30811010,84349107,30543215,2717150,4787945,61623915,83155442,31161687,32590267,84293052,41442762,29516592,83533741,55770687,77787724,36135,52204879,68110330,3088684,7685448,19486173,19272365,79322415,26114953,70372191,59501487,7502255,79738755,71522968,4668450,91141395,47893286,34698428,94360702,16380211,37739481,73617245,16099750,17037369,18663507,47708850,13468268,90004325,87720882,3633375,89804152,71965942,87160386,52293995,15075176,65275241,48658605,8913721,67474219,72358170,3509435,5482538,20867149,84166196,97281847,168541,7646095,10961421,45794415,61741594,37435892,33431960,98462867,37957788,98653983,9575954,11161731,6497830,65338021,84904436,92398073,75407347,19891772,89419466,84187166,49597667,17976208,89811711,61928316,92530431,38256849,72777973,97561745,26063929,97940276,29510992,27625735,36580610,68204242,65081429,89996536,36845587,99658235,93515664,23134715,85117092,89078848,51715482,55615885,45049308,98948034,11365791,71300104,45996863,31727379,18001617,65454636,87710366,45995325,27185644,61815223,30090481,77301523,43045786,8128637,4806458,59614746,63152504,95581843,30569392,33123618,59109400,56473732,86022504,33565483,97641116,96852321,39986008,66885828,96709982,13862149,38341669,17016156,84002370,86242799,78549759,86798033,7955293,90061527,29401781,95957797,62740044,36468541,47887470,17058722,5267545,23432750,17146629,51472275,21919959,52613508,53842979,14326617,40984766,88698958,93790285,15064655,45667668,86821229,30771409,84406788,27325428,77377183,43357947,65880522,98943869,24733232,67084351,26493119,22721500,52060076,60955663,63628376,11581181,79942022,59405277,91727510,65047700,20642888,39201414,83083131,59318837,26744917,99333375,23110625,93270084,6157724,54987042,89699445,81172706,9860968,33336148,95010552,67227442,91938191,82886381,72725103,13231279,51590803,79191827,91957544,33797252,66663942,82779622,62357986,54263880,62762857,54058788,57163802,94595668,38008118,33895336,55669657,22879907,51507425,10597197,45790169,67513640,51588015,23569917,53888755,85695762,41481685,78300864,92803766,29466406,65074535,93359396,19457589,77187825,61960400,36685023,82427263,15536795,54868730,47213183,9058407,56955985,78785507,61859143,83302115,7066775,59329511,71083565,20885148,48673079,70367851,10453030,72278539,28734791,874791,6525948,43506672,9599614,41092102,43933006,97379790,5640302,94076128,75052463,28851716,6111563,33237508,74441448,30218878,55602660,22108665,37192445,80251430,62115552,9188443,44348328,43322743,16424199,749283,8055981,28787861,83269727,11923835,23740167,20681921,26664538,81774825,88251446,76825057,91255408,36396314,13470059,5822202,34667286,82532312,60393039,44245960,29635537,44915235,4985896,37280276,12303248,34432810,9860195,36189527,21397057,33553959,2607799,75820087,83133790,25636669,4199704,10264691,3773993,1239555,45919976,54762643,88130087,73109997,21001913,93562257,17539625,91281584,4978543,90158785,70782102,64055960,32274392,8791066,61897582,67031644,41380093,92867155,77898274,99729357,1375023,38510840,6521313,51315460,62490109 +6505939,68816413,40197395,89214616,78766358,34493392,35092039,61728685,71920426,67084351,99515901,22879907,36930650,99917599,26397786,48260151,67124282,85242963,17016156,83789391,65017137,70004753,68000591,27665211,62496012,20140249,28851716,13173644,74110882,12348118,99965001,53842979,36753250,50806615,83210802,9603598,75128745,57020481,59371804,20122224,57248122,73124510,8651647,14326617,97783876,44177011,69579137,18783702,98943869,11161731,10366309,47298834,86118021,72495719,73031054,59910624,35512853,45667668,72725103,96852321,92998591,6986898,96906410,26275734,29994197,23848565,66322959,51047803,53547802,62936963,669105,19457589,67030811,63506281,9623492,46870723,54517921,50007421,99524975,98739783,84840549,90457870,67281495,5073754,33123618,8696647,39171895,32151165,74441448,44842615,74614639,42073124,62430984,60102965,91281584,33201905,55247455,65715134,415901,7687278,71657078,95957797,6808825,75777973,33249630,50188404,85711894,51464002,62693428,247198,5111370,9188443,95395112,61859143,41092172,99604946,89419466,79880247,64098930,82897371,17058722,22766820,34295794,92787493,2331773,526217,14045383,82979980,10597197,53802686,9398733,6153406,48893685,56955985,88904910,13468268,86460488,76330843,61141874,88653118,63372756,95010552,1197320,55753905,30366150,3294781,13231279,44473167,66428795,14947650,69848388,59501487,30771409,88047921,3233084,45919976,89078848,90310261,75543508,54014062,24591705,36780454,99549373,29834512,70036438,49328608,45996863,32274392,45995325,54606384,89804152,76434144,64087743,37739481,8913721,16380211,19272365,6521313,9575954,82886381,3183975,74137926,30090481,26744917,43933006,64602895,99861373,92692978,7502255,49597667,33895336,508198,33553959,99971982,75986488,5822202,95581843,63967300,27185644,54199160,22129328,77312810,88444207,40356877,26114953,19376156,62232211,47738185,68875490,56515456,33797252,59197747,36505482,30395570,61982238,67227442,8634541,18411915,66903004,84293052,83155442,6221471,44479073,79821897,99125126,33237508,18466635,55615885,44889423,68644627,12571310,39553046,31126490,38008118,17764950,36812683,66045587,50702367,80555751,18131876,24826575,51149804,31727379,18699206,48673079,66611759,76540481,35192533,83747892,69786756,83651211,3233569,45407418,43357947,9599614,99658235,79738755,70367851,54762643,75820087,96735716,44060493,78218845,90272749,4069912,10961421,6111563,67793644,569864,21919959,70596786,16405341,62452034,92398073,39801351,70420215,26392416,14349098,92215320,76671482,6871053,53666583,62803858,22942635,61897582,40534591,18663507,23194618,37620363,37957788,23432750,22997281,11757872,91664334,81677380,1431742,97940276,72777973,17068582,7423788,71316369,1022677,37183543,84349107,20642888,29819940,24058273,92071990,49271185,16971929,55189057,61960400,749283,45790169,80251430,90090964,40865610,71300104,45428665,26507214,55602660,44983451,30998561,59177718,26998766,51715482,85571389,68204242,48774913,92530431,70727211,43376279,81172706,94090109,30811010,62357986,95290172,83368048,50668599,14093520,68694897,4119747,83269727,70782102,48088883,86001008,72732205,76960303,32058615,73617245,38022834,176257,66137019,56153345,49641577,52293995,78884452,91831487,84904436,42307449,41442762,59109400,21070110,77377183,22108665,72373496,9175338,69255765,73235980,37166608,95726235,17976208,21533347,22113133,3880712,79322415,46851987,70800879,10309525,71522968,54263880,61623915,82532312,7517032,874791,62051033,61373987,99297164,38494874,30569392,64055960,36135,91957544,74357852,42967683,43506672,98653983,19939935,68102437,81774825,2607799,19486173,7300047,85116755,49236559,66271566,39373729,13348726,20486294,49882705,77620120,9058407,38341669,16445503,72274002,44245960,43045786,98948034,63059024,92604458,65271999,68824981,43322743,5267545,53084256,3509435,98130363,75407347,57241521,74724075,53632373,77187825,55850790,32161669,53648154,34698428,29959549,7955293,23569917,52261574,29466406,31904591,90004325,77413012,28928175,2204165,56424103,73021291,23134715,97057187,61815223,53888755,42782652,5970607,81274566,60955663,56473732,40686254,85023028,16019925,65338021,89046466,79191827,47213183,68041839,21993752,36685023,36845587,94711842,51507425,37675718,30891921,45848907,65038678,30463802,45306070,39847321,29188588,92353856,78602717,26493119,54868730,4091162,168541,38009615,17727650,29401781,43501211,99224392,97011160,61712234,88698958,19898053,91990218,20002147,34698463,1204161,79136082,4787945,26292919,82178706,16097038,35456853,92541302,47792865,73168565,91240048,73222868,27041967,40781854,23740167,60106217,47887470,48360198,1788101,33565483,99021067,57803235,8791066,76170907,26102057,18833224,30543215,93566986,17957593,66832478,86301513,77694700,48675329,66250369,27187213,20681921,56484900,89811711,84187166,39986008,94076128,6157724,69605283,33628349,3235882,33304202,69641513,2917920,88130087,99729357,40152546,79657802,8099994,30139692,14220886,15064655,4515343,54663246,36312813,24953498,42237907,9257405,17081350,7182642,98648327,321665,17857111,26664538,29635537,41481685,67031644,15148031,44550764,80316608,72238278,84127901,2208785,91802888,45237957,87598888,37280276,90013093,28550822,90654836,82339363,22200378,66667729,38365584,19891772,20765474,17037369,29065964,30787683,40781449,82726008,4095116,38061439,20885148,96193415,22450468,44348328,77898274,37224844,42199455,69697787,99226875,92033260,65275241,96726697,15163258,30694952,36139942,93790285,87160386,78589145,65851721,31491938,97561745,66319530,62740044,94595668,52613508,13470059,92554120,26063929,85695762,85117092,84684495,9860195,7685448,26139110,31161687,6819644,89217461,30764367,15015906,23110625,71083565,93515664,32250045,78300864,93053405,68939068,96318094,77072625,78909561,18806856,70541760,84002370,61271144,24168411,29932657,18001617,72357096,92692283,65047700,1375023,24733232,77272628,24619760,13819358,62552524,55470718,36396314,73392814,22721500,95251277,96420429,27411561,67513640,9259676,37659250,82052050,68316156,40984766,56531125,74743862,11581181,93933709,65880522,76703609,55143724,36933038,7919588,44784505,19101477,44915235,14626618,41092102,51426311,1250437,15535065,30163921,15948937,71333116,34497327,7229550,89637706,93562257,47708850,38645117,61928316,67474219,77300457,6497830,83150534,45381876,57393458,69355476,8373289,29029316,40677414,33061250,72019362,21001913,72793444,37501808,17385531,28796059,43152977,94360702,69136837,11543098,7104732,67451935,18504197,82327024,79806380,65074535,37192445,60430369,28787861,1239555,92867155,5832946,89499542,44846932,16054533,66885828,56461322,65081429,74452589,99333375,45075471,32699744,58751351,63015256,41245325,50567636,45990383,82651278,4204661,78561158,6793819,824872,45617087,10453030,47090124,11365791,21289531,86821229,80014588,35996293,45049308,97379790,83533741,44481640,84166196,17386996,65454636,8733068,59405277,83948335,7011964,81898046,31733363,92803766,76825057,52060076,36468541,64848072,75229462,75135448,21673260,26734892,47893286,3773993,33935899,32426752,8729146,57240218,90061527,1936762,57163802,15902805,21397057,98371444,94516935,36808486,8807940,10649306,86543538,42644903,112651,15075176,4806458,36189527,41380093,76315420,83133790,34432810,14363867,72614359,26863229,37435892,16387575,5640302,83083131,4508700,91938191,48658605,4798568,49598724,72278539,10358899,6525948,48892201,42692881,51588015,16567550,77787724,34044787,86798033,7646095,34946859,47361209,81805959,87710366,58224549,91255408,51472275,55669657,42947632,14731700,44844121,93359396,79942022,8129978,62115552,53393358,63628376,62762857,97641116,44847298,73109997,99690194,17539625,12416768,61859581,37891451,68702632,72738685,54427233,20836893,37560164,30653863,16099750,3633375,40027975,33336148,5482538,59981773,4668450,57961282,72358170,66116458,55960386,48395186,64157906,17365188,81855445,54058788,2717150,4434662,77284619,71469330,16595436,38256849,32590267,25842078,71965942,3487592,51466049,51590803,58749,34236719,8128637,86242799,12664567,27325428,54987042,89699445,84406788,86022504,39201414,44627776,3088684,8055981,75153252,13862149,61263068,66663942,63152504,10264691,14723410,60581278,91727510,32159704,27625735,20645197,78549759,51315460,97281847,52204879,75052463,83302115,4064751,15536795,89996536,4099191,2891150,20568363,6038457,29516592,44664587,36580610,58208470,60393039,24314885,9886593,45794415,12024238,78785507,59614746,28734791,11923835,68110330,81853704,33699435,94539824,54232247,88251446,16684829,94911072,4985896,9829782,8115266,20867149,17146629,93057697,25636669,87720882,68128438,93270084,46540998,1569515,4199704,17894977,79922758,16424199,24915585,90158785,7066775,73786944,22405120,59318837,57658654,59329511,22435353,29510992,57359924,96709982,61741594,12303248,70372191,82779622,38658347,77301523,38510840,55770687,30218878,80246713,91141395,33431960,58180653,82427263,26776922,98462867,62490109,4978543,60176618,9860968,34667286,62428472 +83789391,7687278,3235882,32159704,76540481,29466406,24058273,10366309,20885148,46870723,38061439,70596786,33336148,99658235,68000591,38658347,65074535,36812683,3773993,33628349,6871053,72495719,61373987,4119747,66271566,67793644,63059024,81853704,42782652,85116755,39847321,45848907,76434144,63967300,75229462,39201414,60430369,79942022,48893685,36396314,53084256,36312813,61263068,81805959,68644627,84904436,66045587,37659250,48360198,3487592,9058407,95957797,94711842,96193415,44627776,40984766,3233084,83533741,17058722,72732205,74357852,34432810,52261574,59197747,168541,24591705,45667668,2917920,16971929,33201905,9175338,24619760,60106217,80014588,70420215,33061250,96726697,29932657,29029316,74441448,93057697,99524975,4798568,62051033,60581278,66667729,78766358,15015906,75153252,62452034,86118021,39801351,88047921,31491938,62936963,12348118,76315420,69355476,35092039,93933709,70800879,23848565,9603598,95581843,59109400,89699445,71316369,55247455,69697787,8729146,70727211,17539625,44842615,15535065,14626618,27185644,72019362,72614359,69579137,76671482,72738685,75777973,77272628,85023028,55770687,44177011,66428795,8129978,19272365,10358899,36753250,99125126,65851721,36808486,36685023,26392416,44060493,62496012,66250369,6819644,88653118,13470059,71657078,15163258,26292919,63628376,247198,7229550,81274566,17068582,25842078,62803858,40677414,73222868,6793819,7955293,71469330,19939935,33565483,20002147,68316156,82427263,92787493,19891772,45407418,86821229,91255408,46851987,32151165,99515901,71083565,33935899,49328608,54663246,874791,48774913,9623492,50188404,2717150,77284619,83210802,55753905,53802686,75543508,19376156,88444207,80246713,72274002,70367851,30463802,68816413,82339363,93359396,415901,36135,68824981,2204165,89419466,53547802,89214616,91802888,34493392,72725103,16405341,5073754,70541760,97940276,72777973,62357986,4064751,59177718,45919976,77620120,15148031,82726008,4668450,9259676,45790169,31727379,81677380,28796059,15948937,44889423,40865610,22108665,27325428,29834512,85711894,5970607,23569917,89046466,6808825,29994197,37560164,569864,47792865,49236559,112651,40197395,37620363,30569392,98371444,7502255,89078848,58749,90457870,8128637,66663942,45990383,98739783,76703609,17365188,34295794,67031644,77312810,21993752,51047803,11543098,43376279,92692978,65338021,16387575,99021067,23194618,39553046,21533347,82897371,73124510,32590267,16019925,92998591,99297164,57359924,22721500,17957593,74743862,95010552,4099191,25636669,53666583,71333116,73617245,39171895,70782102,45075471,87720882,36468541,38365584,20765474,44847298,57803235,16097038,54199160,42307449,77413012,47213183,11365791,27665211,91831487,4199704,78909561,8733068,66611759,61859581,78300864,86022504,53632373,18806856,94090109,6497830,61859143,40152546,92803766,1204161,26998766,17081350,21070110,92554120,66137019,37224844,18504197,2607799,1431742,84840549,84293052,40534591,35192533,37183543,35512853,49641577,86460488,71920426,50702367,28851716,56531125,4515343,1022677,15075176,59318837,84127901,8099994,5111370,17764950,47361209,72278539,92604458,17857111,36933038,5267545,36580610,61982238,6986898,321665,93515664,81855445,41092172,68204242,30366150,4204661,48673079,43322743,73109997,21001913,14326617,30218878,91727510,53842979,50567636,14093520,40027975,18783702,17894977,11581181,37192445,83133790,55850790,90061527,34698463,37675718,90310261,17146629,82532312,92541302,6153406,87160386,30787683,64848072,98130363,83155442,96318094,7423788,50668599,30811010,61712234,30163921,31733363,84187166,67124282,97641116,44664587,34698428,56153345,9575954,87598888,15064655,43501211,79922758,51472275,59981773,67030811,70372191,66885828,80316608,4069912,49271185,94360702,54606384,20486294,18466635,54014062,26275734,99224392,38008118,96709982,5832946,61928316,18833224,22435353,98948034,19457589,7919588,36189527,37166608,30653863,44983451,29959549,20836893,75128745,77377183,16445503,80555751,64602895,73168565,29065964,51315460,26493119,73235980,42947632,34946859,38494874,75820087,79821897,26734892,34497327,73786944,43152977,95726235,69786756,26863229,63015256,3088684,67474219,77301523,26063929,93053405,73392814,99690194,59329511,95251277,71300104,526217,66903004,79136082,92215320,3183975,10961421,51590803,14045383,33249630,78884452,24168411,92692283,70036438,9188443,51464002,55669657,33123618,50007421,68875490,99549373,72357096,65880522,66832478,56424103,45049308,79191827,3880712,30139692,84349107,98462867,97379790,33237508,23432750,8651647,669105,29819940,85117092,31904591,94516935,97561745,46540998,13173644,52060076,70004753,43933006,83302115,61741594,1375023,67451935,37501808,86301513,29635537,78602717,9599614,35996293,60102965,32250045,55189057,83651211,82779622,3633375,75986488,52293995,83083131,508198,39986008,13468268,76960303,90654836,61815223,6221471,17037369,82979980,9257405,14349098,45237957,35456853,33431960,92033260,2891150,82651278,87710366,85242963,77072625,16380211,4091162,48088883,51149804,76330843,93270084,53888755,97057187,1569515,16595436,40356877,65271999,41481685,17385531,68041839,93790285,99333375,49597667,4787945,86543538,58224549,19101477,78561158,93562257,40686254,37739481,22129328,61271144,54427233,15536795,69848388,8373289,56484900,23110625,3294781,9886593,57241521,69641513,77300457,1788101,1197320,27187213,69255765,28928175,68110330,21919959,36505482,22942635,96735716,64157906,68702632,7685448,42692881,65715134,10453030,30771409,61960400,44479073,20568363,62740044,99861373,7104732,17976208,40781449,96906410,86242799,77787724,43357947,91938191,37891451,78785507,61141874,14220886,83150534,60176618,2208785,44550764,44245960,92398073,18131876,9398733,26776922,62762857,53393358,81898046,55602660,44915235,59501487,8696647,99604946,26139110,7011964,43506672,78218845,22405120,9860968,91664334,19486173,11161731,57020481,44481640,824872,21673260,45996863,68694897,4434662,62693428,28550822,88698958,54263880,33797252,51426311,79806380,20140249,22450468,51507425,44784505,33895336,59371804,24953498,11757872,10649306,74137926,7646095,53648154,30764367,34236719,47887470,84684495,48892201,44846932,9829782,12303248,38256849,65017137,72373496,79880247,68102437,94911072,23134715,29188588,29401781,74110882,89811711,61728685,69136837,89217461,94539824,67227442,75052463,49598724,20642888,62232211,47893286,91990218,52613508,6111563,27625735,9860195,11923835,33553959,32426752,42199455,37957788,45428665,85571389,58208470,31126490,5482538,55960386,89804152,59614746,55143724,65047700,77187825,38645117,28734791,90004325,14731700,24915585,16054533,56955985,18699206,22766820,43045786,57658654,7517032,34044787,64055960,90013093,41245325,91281584,26744917,20867149,3509435,8807940,68939068,30543215,1250437,80251430,6038457,48260151,14363867,84406788,24733232,85695762,36780454,42967683,38022834,31161687,23740167,44844121,33304202,97011160,22997281,24826575,55470718,3233569,19898053,50806615,99226875,47298834,62430984,93566986,89637706,10597197,82052050,176257,66319530,68128438,26397786,56461322,30891921,97783876,12664567,5822202,16684829,21289531,8634541,57240218,81172706,88904910,89996536,76170907,74614639,58751351,59910624,91957544,97281847,57163802,17016156,14723410,20645197,48658605,59405277,45306070,63152504,18001617,49882705,27411561,62490109,84166196,5640302,82178706,29516592,67084351,33699435,749283,30395570,78589145,32161669,38341669,94595668,41442762,26114953,22200378,47090124,4978543,36930650,4985896,6525948,6521313,30694952,26507214,77694700,7182642,88130087,92353856,95395112,21397057,89499542,66322959,54987042,52204879,74724075,75407347,12024238,1239555,67281495,41092102,36139942,54868730,88251446,86001008,57961282,99917599,42073124,32699744,96420429,54232247,83368048,30090481,79657802,8791066,26102057,73031054,20681921,54762643,57248122,74452589,82327024,26664538,72358170,29510992,24314885,54517921,48395186,56515456,61623915,37435892,6505939,65275241,16567550,98943869,44348328,63506281,64087743,99965001,86798033,61897582,16099750,40781854,18411915,96852321,60955663,36845587,10309525,28787861,75135448,71965942,17727650,90272749,22879907,42644903,14947650,16424199,62552524,95290172,90158785,79322415,44473167,83948335,81774825,4095116,41380093,39373729,7066775,12416768,13862149,62428472,56473732,79738755,47738185,45995325,48675329,45794415,51715482,22113133,20122224,15902805,54058788,8055981,77898274,27041967,65081429,12571310,2331773,63372756,55615885,78549759,58180653,8913721,51588015,57393458,45381876,13231279,98648327,62115552,67513640,4508700,65038678,65454636,32058615,32274392,7300047,34667286,6157724,38009615,66116458,82886381,72793444,30998561,90090964,42237907,76825057,91141395,84002370,92867155,69605283,8115266,92071990,51466049,47708850,60393039,83269727,73021291,91240048,4806458,99971982,45617087,64098930,71522968,94076128,18663507,83747892,1936762,13348726,98653983,92530431,38510840,37280276,10264691,17386996,13819358,99729357,72238278 +45990383,21993752,16019925,30543215,90272749,59371804,63059024,91664334,45237957,27665211,77413012,16099750,73031054,68110330,3233084,45428665,3487592,44889423,66137019,51472275,75153252,62496012,39986008,45919976,62936963,68204242,53393358,26863229,92398073,85116755,70004753,87598888,59177718,86001008,90090964,61928316,6793819,68041839,53547802,79821897,22108665,30764367,69848388,88653118,77301523,12348118,2917920,55247455,11543098,99333375,47738185,14947650,36812683,7687278,7502255,63967300,2891150,6153406,86022504,50567636,29188588,54427233,16054533,74110882,89699445,13468268,62490109,66611759,89804152,42782652,98130363,29819940,19891772,4119747,40865610,17857111,7955293,62452034,44784505,3088684,4787945,73235980,26139110,61815223,33237508,77284619,99658235,99917599,9575954,50188404,86543538,69579137,13231279,18466635,4204661,31733363,36685023,60955663,31126490,45407418,45667668,30771409,95290172,61859581,64848072,74614639,17764950,97783876,54014062,55753905,57240218,99861373,15015906,53648154,82327024,72357096,10366309,34236719,93933709,36780454,96726697,3183975,20140249,78300864,53666583,71469330,47361209,61271144,33935899,62428472,23194618,88047921,7182642,65047700,25636669,78589145,6986898,37435892,62051033,18504197,28550822,89811711,91990218,43501211,8733068,55143724,65271999,16445503,91255408,35092039,749283,23848565,93053405,415901,8696647,2717150,3294781,60581278,45848907,41092102,40197395,17068582,61728685,20867149,6505939,82979980,9886593,71316369,12416768,20486294,42967683,56153345,8128637,27411561,8807940,80555751,10649306,32590267,72274002,22942635,91141395,40356877,84904436,45996863,99224392,81898046,18833224,39847321,95726235,96193415,67793644,66903004,47893286,93562257,6525948,48675329,77187825,18783702,15902805,3880712,34493392,32250045,68644627,32159704,4798568,40152546,89046466,29834512,71333116,17058722,77072625,31904591,35456853,57393458,17539625,6808825,99524975,66832478,49641577,3233569,508198,92554120,38022834,65454636,89217461,70596786,49328608,81774825,34295794,31491938,86242799,92998591,78602717,83651211,82726008,49236559,59318837,24058273,85242963,14045383,22997281,32161669,79136082,71920426,70541760,48088883,72358170,91957544,26744917,89214616,32426752,39171895,82532312,19898053,57163802,9603598,88698958,72019362,12024238,57961282,48774913,14626618,99515901,82339363,80014588,14220886,91240048,55470718,83789391,62803858,73168565,97379790,51507425,26292919,75777973,9829782,92033260,57241521,23110625,37957788,37501808,20122224,5970607,98948034,42073124,82427263,74357852,26507214,44550764,65074535,9398733,19457589,73786944,84406788,67451935,63628376,1788101,37166608,22766820,61373987,36312813,91938191,11161731,11581181,247198,4985896,37620363,96318094,73222868,669105,20765474,96852321,14093520,68102437,57803235,59197747,66663942,29065964,38365584,44481640,92692978,51047803,18699206,569864,92867155,86460488,37183543,33797252,83269727,44177011,54663246,52060076,36933038,36930650,93057697,29510992,26063929,82178706,36753250,30694952,26776922,96420429,79880247,30163921,83533741,4434662,65851721,80316608,33628349,8634541,77620120,38494874,18001617,75135448,76540481,30787683,89637706,66667729,62232211,86821229,97641116,27325428,5482538,83210802,44479073,34698463,55189057,38008118,17727650,30998561,90004325,61263068,51590803,15075176,19939935,89419466,24591705,83155442,82651278,40686254,27625735,2607799,90457870,78766358,40677414,54762643,93515664,68875490,69786756,51588015,112651,77312810,84684495,5832946,43045786,99297164,64098930,26664538,8115266,47298834,44842615,82897371,63372756,55669657,55602660,9257405,84293052,93359396,84127901,74441448,98462867,47792865,40781449,85023028,54199160,67030811,43152977,95957797,99125126,38061439,8129978,37739481,48260151,9188443,79738755,26392416,71657078,51715482,68816413,36189527,87160386,65081429,82779622,57658654,16424199,6221471,4064751,40781854,48360198,5822202,47708850,70367851,10961421,73021291,26998766,7229550,66322959,24826575,70727211,89078848,7011964,45075471,1569515,68824981,79322415,81855445,20642888,22129328,36505482,39553046,9623492,82886381,58749,72238278,33201905,98371444,4806458,321665,37560164,9058407,64157906,28928175,79942022,81172706,17957593,34698428,34667286,35192533,16387575,5073754,45306070,67124282,40027975,94516935,66116458,24915585,56473732,4508700,90654836,78549759,4668450,63015256,40984766,67227442,59614746,69255765,58180653,47887470,61982238,85571389,99604946,52261574,65017137,17081350,63506281,3633375,76825057,68000591,58751351,92787493,68702632,84840549,77377183,81677380,57020481,72777973,94539824,19486173,95581843,4515343,95010552,99549373,76330843,5111370,23569917,73617245,76315420,51464002,6497830,54987042,76960303,79806380,29466406,65880522,85695762,85117092,17365188,43376279,526217,4099191,36468541,27041967,44473167,24733232,72725103,99965001,30218878,18806856,11923835,70420215,16097038,43506672,97940276,89996536,9860968,23134715,29932657,30891921,34497327,16595436,20568363,6819644,14723410,38645117,62552524,1197320,36808486,29994197,83368048,874791,10358899,54058788,45790169,17386996,50668599,81805959,57359924,50007421,84002370,21289531,53632373,26493119,59501487,37891451,59109400,42692881,33061250,14349098,76434144,69136837,67513640,88904910,62740044,92803766,6111563,36135,1250437,87720882,88444207,7423788,50702367,30139692,97281847,43322743,35996293,44844121,43933006,42644903,74137926,45995325,64602895,94090109,99971982,97057187,86798033,72373496,66250369,28796059,51466049,49597667,80251430,51149804,92541302,44915235,44664587,1936762,63152504,57248122,58224549,83150534,29029316,65038678,95251277,99729357,38658347,6038457,99021067,17037369,68694897,94911072,78218845,14363867,19376156,13173644,78561158,96709982,84187166,8913721,72732205,30569392,78909561,15163258,824872,38009615,49271185,91802888,24314885,9259676,68316156,66885828,82052050,17146629,4069912,7104732,44060493,74452589,54868730,54606384,71522968,39801351,13470059,68128438,1431742,37675718,16380211,66428795,14326617,42307449,11365791,28851716,30090481,28734791,17385531,73124510,33699435,56424103,14731700,34044787,48892201,21673260,28787861,62430984,43357947,24619760,17976208,18663507,26114953,64087743,75407347,75128745,70372191,69605283,69697787,73392814,44627776,77787724,33431960,98653983,24168411,9860195,67474219,9599614,78785507,7685448,98739783,2204165,86118021,93270084,70036438,22200378,48893685,93790285,97561745,83302115,36580610,37224844,37192445,7300047,94076128,6871053,30366150,1022677,65275241,53842979,81274566,41245325,88130087,76671482,20885148,91727510,97011160,176257,33895336,90158785,29959549,95395112,66271566,36139942,69355476,99690194,86301513,20836893,1375023,26734892,22450468,76170907,3773993,61960400,30395570,47213183,55770687,56461322,94360702,45617087,10309525,15536795,61623915,68939068,51315460,90013093,22721500,74743862,67281495,8729146,20681921,45794415,44348328,32699744,30811010,92604458,15535065,33249630,92692283,79657802,47090124,4091162,56531125,9175338,3235882,72278539,96735716,17016156,11757872,61859143,56484900,89499542,16405341,4095116,60176618,92215320,81853704,54517921,42199455,99226875,59910624,12571310,20002147,77272628,26102057,61141874,83083131,83948335,60102965,75543508,55960386,42947632,71083565,32151165,54263880,24953498,3509435,65715134,94711842,55615885,77694700,22405120,23740167,59981773,31161687,44846932,41092172,32058615,53802686,77898274,60430369,44983451,15948937,16567550,44245960,61712234,49598724,4978543,62115552,12664567,8099994,16971929,58208470,33565483,70800879,37659250,48658605,168541,72793444,30653863,32274392,27187213,2208785,25842078,74724075,53888755,54232247,50806615,33336148,18411915,34432810,8651647,55850790,36396314,64055960,38256849,92530431,13348726,61741594,48673079,29516592,62693428,70782102,66319530,92353856,66045587,96906410,22879907,79191827,5640302,10597197,23432750,46851987,30463802,7066775,10453030,53084256,69641513,72614359,71300104,21533347,41481685,67084351,34946859,75986488,21919959,61897582,87710366,77300457,59405277,80246713,48395186,75820087,71965942,75229462,39373729,44847298,83133790,45049308,56955985,21001913,67031644,45381876,91281584,85711894,7517032,51426311,10264691,15064655,15148031,2331773,35512853,12303248,41380093,84349107,98943869,26275734,40534591,62762857,78884452,1204161,26397786,8373289,90061527,22435353,7646095,8791066,38510840,13862149,19272365,73109997,52293995,72738685,22113133,41442762,91831487,65338021,76703609,83747892,27185644,42237907,39201414,33553959,8055981,31727379,33123618,56515456,6521313,38341669,21070110,79922758,92071990,20645197,94595668,62357986,46870723,6157724,46540998,37280276,33304202,1239555,36845587,17894977,29635537,84166196,60393039,59329511,7919588,75052463,49882705,93566986,72495719,21397057,5267545,19101477,29401781,60106217,52204879,13819358,98648327,88251446,90310261,4199704,18131876,52613508,16684829 +17764950,4099191,90013093,55189057,60430369,38061439,34497327,66667729,26998766,16405341,49236559,22405120,68041839,14093520,18806856,29834512,11923835,79821897,76540481,3509435,20765474,72274002,61859581,68204242,15535065,91240048,51472275,50702367,97561745,95581843,28928175,52204879,2331773,20002147,40197395,2208785,2607799,96735716,72357096,8099994,16097038,99965001,51047803,45428665,66045587,71300104,44627776,9058407,59501487,22721500,35512853,73392814,80316608,80246713,90310261,87598888,39553046,49271185,71920426,34236719,98130363,415901,69355476,99226875,57803235,89046466,99515901,7646095,38658347,87720882,6497830,61271144,30366150,55247455,73617245,8733068,20140249,68000591,58749,18466635,33336148,46540998,94090109,33895336,65271999,99971982,4515343,70800879,38365584,47361209,54663246,62936963,36753250,36780454,73235980,44784505,54987042,82178706,76170907,99524975,29029316,73168565,62803858,40356877,53632373,9623492,37192445,91664334,72793444,36312813,2717150,96906410,34432810,89214616,76703609,50188404,98943869,33061250,78589145,99917599,99224392,30764367,10358899,65074535,3183975,20642888,37739481,14349098,94539824,76671482,29188588,44842615,82651278,18783702,45794415,62740044,99549373,4668450,9599614,44844121,96193415,60176618,38645117,26493119,26664538,68644627,89996536,93562257,17539625,36933038,34493392,10597197,73124510,32274392,49641577,84002370,16445503,47893286,30463802,85242963,95957797,86460488,62496012,90158785,53547802,28796059,25636669,78602717,76434144,78909561,6521313,95726235,46870723,61373987,96852321,35996293,51466049,526217,43152977,66322959,73021291,59371804,77272628,7955293,14220886,85116755,81898046,21993752,67793644,40781449,36505482,27325428,3487592,39201414,16099750,79922758,63506281,70372191,42073124,84904436,89078848,85117092,18131876,1204161,168541,99297164,18833224,64602895,38022834,33201905,7687278,77620120,77413012,78218845,17081350,73786944,508198,93933709,44177011,34946859,92803766,92215320,80014588,95395112,1431742,35192533,26744917,59109400,81853704,33249630,44889423,83651211,85571389,824872,38341669,10366309,77694700,68102437,66250369,29994197,61263068,73222868,44481640,86118021,82979980,18001617,84187166,37501808,43376279,20486294,31161687,34044787,45848907,26507214,32161669,31126490,84684495,42692881,70596786,9603598,4798568,67451935,22113133,53666583,62232211,66885828,8373289,22450468,61623915,26397786,26275734,74357852,83083131,16567550,55470718,29065964,22942635,7502255,98371444,59981773,69641513,26863229,45996863,4119747,5267545,63967300,5482538,77072625,50806615,67474219,26139110,54014062,71333116,65047700,83133790,56531125,48088883,3633375,23569917,19486173,72725103,88130087,19272365,24058273,79136082,26102057,44479073,71965942,90457870,17727650,29959549,67124282,36468541,19376156,24619760,30787683,12024238,26392416,42199455,5970607,44983451,33699435,75543508,29819940,61741594,91281584,14626618,1569515,52261574,75777973,91831487,16054533,8651647,79322415,70004753,61728685,27411561,28851716,11581181,21289531,98948034,93057697,32151165,95010552,59197747,21070110,61982238,97281847,37620363,30163921,10309525,9575954,29635537,3880712,18411915,56955985,55770687,81855445,18504197,76315420,62490109,4199704,63059024,82897371,7229550,51590803,38256849,62452034,33797252,8791066,11757872,57248122,5111370,53802686,77312810,96318094,43045786,97783876,58180653,9829782,89699445,37675718,14947650,74743862,3088684,22879907,9398733,17146629,36580610,92604458,68816413,9860968,68316156,42782652,72732205,86242799,45075471,16595436,45617087,37891451,6505939,42237907,77284619,61815223,9257405,72738685,14326617,45407418,96726697,91802888,39171895,67030811,98648327,83789391,37183543,86798033,57240218,40152546,6153406,13231279,8807940,70727211,77377183,19457589,37560164,51464002,67227442,17016156,85023028,88251446,30543215,16971929,17957593,1197320,45667668,89217461,62693428,70036438,44915235,61960400,874791,36396314,99861373,92692978,56461322,15536795,49328608,40027975,93270084,88904910,10264691,13468268,41442762,99658235,11543098,56424103,19939935,61928316,32590267,82339363,55850790,6221471,4787945,19101477,32426752,58751351,98462867,86022504,49598724,17068582,41481685,7011964,6819644,24168411,12348118,91141395,83302115,6871053,1936762,92554120,17386996,6793819,33237508,57658654,12303248,15148031,81805959,18663507,8129978,60581278,54427233,4806458,53648154,83155442,23134715,40984766,64055960,26063929,27041967,73031054,4091162,69605283,89804152,75128745,96420429,66428795,9175338,14723410,22997281,74452589,7919588,48360198,96709982,56515456,2891150,43501211,3235882,92787493,4064751,87710366,43357947,20122224,19891772,41245325,31904591,78884452,1022677,2917920,92398073,50567636,55753905,8913721,4434662,63015256,65081429,72373496,40865610,92033260,4985896,61712234,35092039,36189527,18699206,30811010,31491938,67084351,112651,57241521,23110625,84840549,39801351,81677380,91990218,69786756,54606384,69255765,75052463,33628349,52060076,82427263,38008118,71083565,99729357,74724075,62051033,97940276,9188443,13862149,13470059,92071990,55960386,1788101,20885148,30139692,31733363,36135,74137926,95290172,55602660,59177718,79191827,29466406,48675329,34698463,1250437,36808486,30771409,59405277,55669657,33304202,569864,90090964,8055981,88653118,19898053,2204165,83210802,59910624,92353856,30653863,92998591,47792865,60955663,49597667,70420215,48260151,62552524,30998561,77187825,86001008,60102965,66137019,39847321,17037369,60106217,20568363,9886593,13173644,1375023,27665211,3773993,81172706,30569392,94516935,98739783,65715134,58224549,16684829,38009615,29932657,7423788,23194618,27625735,15163258,26114953,32250045,68875490,66116458,50668599,53842979,45381876,70541760,32159704,93359396,15015906,63628376,93790285,75407347,247198,72278539,669105,10453030,38494874,93053405,57393458,30891921,44847298,36139942,78785507,54232247,64848072,68702632,48892201,82886381,68939068,65017137,38510840,84293052,21673260,22766820,22108665,76960303,37435892,99604946,41092172,71316369,16387575,93515664,36930650,71469330,84349107,80555751,12416768,72358170,20645197,33123618,54058788,81274566,5832946,3294781,57163802,57961282,66319530,64098930,74614639,66903004,321665,72019362,65338021,36812683,54263880,41092102,62762857,23740167,99333375,27187213,5640302,54199160,86301513,71657078,93566986,82532312,15075176,94076128,77898274,44060493,67513640,54868730,11161731,15064655,37166608,44846932,59318837,39986008,92692283,6986898,62430984,72614359,92541302,66832478,45995325,29510992,3233084,30090481,44664587,69136837,51507425,6038457,16019925,74110882,66271566,47090124,72495719,47738185,78766358,37659250,40686254,53084256,12664567,82726008,55143724,21533347,3233569,83150534,89419466,34698428,69579137,54762643,48774913,48673079,24314885,72777973,97379790,99690194,63372756,94595668,45237957,13348726,90061527,31727379,4069912,75229462,88047921,30694952,6111563,88698958,97057187,56153345,24915585,90654836,176257,56473732,49882705,26292919,17365188,52613508,36685023,16380211,83368048,45919976,62115552,64157906,40781854,91957544,32699744,40534591,58208470,8696647,51315460,5822202,17894977,8115266,97011160,46851987,89499542,54517921,91727510,71522968,34667286,86543538,6525948,75135448,23848565,7182642,75820087,43322743,15902805,80251430,28734791,69848388,8634541,34295794,7517032,20836893,22129328,44550764,79806380,10649306,77787724,33553959,86821229,26734892,42307449,69697787,83747892,43506672,87160386,48893685,79657802,4978543,41380093,53888755,84127901,6808825,72238278,4204661,11365791,44245960,68128438,65275241,81774825,7066775,25842078,78561158,97641116,45790169,8729146,4095116,42644903,30395570,76330843,24733232,40677414,51426311,39373729,61859143,8128637,47213183,57359924,42967683,57020481,15948937,99021067,75986488,99125126,44473167,7685448,65454636,79880247,56484900,47298834,82052050,90004325,35456853,91255408,20867149,94360702,48658605,9860195,68824981,83533741,47708850,29401781,36845587,51715482,75153252,4508700,24953498,17385531,84166196,17857111,37280276,21919959,84406788,22200378,67281495,45990383,37224844,66611759,10961421,59614746,94911072,43933006,27185644,66663942,82779622,17976208,65880522,59329511,70367851,14045383,16424199,32058615,28550822,48395186,26776922,12571310,44348328,5073754,33431960,24591705,78300864,42947632,89637706,14731700,749283,67031644,29516592,14363867,62428472,45306070,89811711,79942022,74441448,60393039,13819358,65038678,68694897,63152504,68110330,33935899,51588015,76825057,78549759,7300047,94711842,33565483,22435353,7104732,52293995,77300457,90272749,21397057,50007421,28787861,21001913,45049308,20681921,30218878,91938191,73109997,88444207,61897582,62357986,83948335,77301523,85711894,61141874,24826575,53393358,1239555,92530431,98653983,95251277,51149804,17058722,47887470,79738755,92867155,85695762,55615885,64087743,6157724,65851721,37957788,70782102,83269727,9259676,82327024,23432750 +4099191,14626618,72373496,89214616,27665211,75543508,65074535,50567636,81855445,7687278,9623492,526217,73124510,60430369,508198,66250369,83302115,35092039,29994197,54199160,61263068,38061439,30090481,44481640,51047803,8913721,15535065,29029316,63628376,86821229,68644627,17764950,168541,5832946,95726235,61373987,70800879,19939935,32250045,82052050,47090124,6871053,89078848,10366309,9886593,59371804,98739783,66137019,4064751,42967683,67793644,43501211,55247455,61271144,99658235,63506281,13173644,72614359,56515456,30891921,10649306,93933709,47792865,70036438,15536795,68824981,43376279,16424199,7011964,97783876,96318094,34432810,91664334,80316608,31904591,74137926,6986898,55770687,26744917,26292919,79922758,33123618,16445503,68204242,26392416,34698428,70372191,247198,2917920,24591705,54663246,50188404,68694897,24058273,71657078,13470059,78766358,16971929,54606384,20122224,69355476,95290172,33797252,10358899,93053405,6153406,45075471,99965001,74743862,62452034,22108665,11365791,93562257,10309525,81805959,65038678,4668450,47708850,34295794,32161669,5111370,34946859,75229462,74724075,39986008,29065964,90457870,7517032,17385531,21993752,3633375,34497327,4515343,43322743,53666583,14093520,1569515,87598888,63059024,76703609,59318837,33201905,92554120,18833224,22879907,98371444,1022677,26139110,95395112,54868730,52613508,6793819,35192533,63967300,26734892,8634541,7229550,15064655,59109400,36812683,75777973,30395570,18466635,99861373,31161687,37659250,58751351,47213183,18783702,26063929,98130363,42947632,33304202,8651647,68702632,66667729,16567550,36930650,85117092,37675718,61728685,78785507,95957797,44784505,30139692,8733068,32159704,79738755,11581181,824872,46870723,60102965,25842078,90013093,11923835,57240218,79821897,39171895,42782652,91957544,1431742,32699744,9575954,88047921,70367851,3509435,81898046,26863229,40865610,45995325,67281495,56484900,26507214,94711842,98462867,321665,72019362,1204161,47738185,84904436,66611759,96193415,8696647,40781449,72274002,96420429,41380093,76540481,67227442,90272749,75986488,19101477,8373289,20002147,20885148,43152977,44889423,30764367,61960400,51149804,70420215,73031054,37183543,55753905,70004753,59177718,62740044,99297164,85116755,38341669,75153252,112651,63372756,4787945,81853704,18411915,93359396,7646095,48774913,85023028,33565483,19272365,42644903,38494874,69848388,569864,77377183,55143724,62803858,48260151,84840549,7423788,4119747,18131876,36780454,82339363,54987042,44847298,99226875,45407418,48892201,44842615,94516935,77312810,17016156,14363867,54014062,79191827,92033260,669105,83368048,56531125,415901,18699206,2331773,30366150,29834512,33553959,36580610,85242963,8055981,98948034,62936963,76170907,49328608,72725103,93566986,8729146,80014588,57658654,75407347,49236559,94076128,81677380,9398733,66885828,59197747,44245960,17068582,28796059,91240048,6808825,45794415,28851716,74614639,56153345,77300457,39201414,34236719,95010552,86301513,93790285,30998561,31733363,45049308,55470718,28928175,27185644,88653118,57163802,18806856,28734791,99604946,29819940,27041967,67513640,3773993,30787683,99333375,69579137,6497830,34698463,33249630,73168565,76671482,50702367,6525948,97940276,14326617,49641577,23569917,86460488,57803235,21673260,61982238,68939068,30218878,92692978,37192445,82886381,90004325,53802686,3294781,98648327,96906410,43045786,71300104,98943869,17957593,38658347,47361209,53648154,77787724,35456853,8807940,24314885,55960386,74110882,29959549,7919588,92215320,45667668,23848565,45919976,36753250,74357852,65271999,66903004,99125126,33061250,62693428,46851987,19891772,87710366,36312813,88444207,69605283,65338021,36808486,67031644,69136837,77694700,53547802,66045587,9829782,29635537,51472275,79136082,86001008,91831487,65715134,33895336,72495719,86543538,37891451,29510992,84684495,32426752,6819644,4798568,56955985,15948937,62490109,51588015,22450468,37739481,17365188,37957788,54232247,39553046,16380211,62357986,15075176,12571310,16019925,97281847,24733232,17146629,73222868,72738685,19486173,99524975,52204879,83150534,36505482,66663942,57248122,32058615,67124282,22997281,62115552,24619760,82897371,11161731,8129978,72238278,17727650,57359924,45617087,77284619,18663507,40027975,91281584,3235882,92398073,44348328,44479073,16595436,48893685,92998591,73392814,2607799,30694952,42199455,16097038,16054533,80251430,60955663,49597667,54058788,53632373,17539625,68000591,17976208,31126490,71333116,37166608,30569392,12416768,4806458,40152546,7685448,66319530,54427233,84166196,48360198,26114953,3088684,88904910,20836893,77620120,7955293,34044787,78884452,37620363,5822202,91938191,77072625,4985896,48088883,59501487,42307449,74441448,99224392,34493392,85571389,22721500,70596786,53842979,50668599,15902805,61859581,91990218,47887470,40534591,69255765,40197395,12664567,61859143,18001617,22129328,92803766,49598724,16387575,14349098,62496012,20681921,82726008,36685023,72732205,56424103,41092102,17386996,78561158,30463802,12348118,33336148,32274392,97379790,30543215,15148031,41481685,67084351,38008118,35996293,58224549,80246713,5970607,76330843,52293995,99515901,29466406,61623915,51466049,99021067,62430984,80555751,3487592,40984766,20765474,37501808,6221471,13862149,92071990,83651211,97561745,99917599,84187166,11543098,68816413,79942022,63015256,38365584,10961421,92604458,2208785,92530431,86118021,36396314,21533347,1936762,92692283,84349107,61141874,81274566,9599614,45237957,44177011,26397786,62051033,89499542,26664538,48658605,19376156,69641513,17894977,84127901,23194618,26998766,40781854,44550764,4434662,3183975,67030811,10597197,92787493,78909561,73617245,40677414,8128637,93515664,29516592,22405120,54762643,33628349,33699435,31727379,86022504,4204661,71920426,57241521,51464002,55189057,49271185,96726697,21070110,78549759,1788101,3880712,93270084,5267545,12024238,65081429,73021291,13231279,79880247,96735716,60106217,19457589,7066775,22200378,43357947,20486294,68128438,23740167,22766820,41092172,87720882,45381876,30653863,23432750,65047700,9603598,29932657,66428795,79657802,82979980,20568363,76960303,84293052,36139942,2717150,95581843,82532312,18504197,27325428,60176618,9188443,39801351,22942635,77413012,91727510,64098930,30771409,90310261,28550822,88251446,20642888,73235980,44473167,54263880,42073124,78218845,14731700,39847321,85711894,55602660,3233084,89699445,50806615,98653983,20140249,53084256,7300047,59910624,64157906,60581278,71316369,77272628,24168411,99549373,4978543,51315460,37435892,70727211,83083131,94090109,44915235,66271566,65017137,7502255,51590803,41442762,37224844,65275241,9257405,66832478,7104732,4199704,66322959,79806380,72777973,59329511,68102437,17058722,75128745,97057187,94360702,45996863,95251277,24953498,76434144,97641116,78589145,82178706,9058407,51426311,71083565,94539824,82427263,58180653,67474219,45428665,77187825,33431960,30811010,26102057,89637706,99690194,13348726,25636669,26493119,14723410,22113133,89419466,83269727,44664587,86242799,94595668,44983451,58749,84002370,51507425,72358170,1197320,14045383,89217461,16684829,83789391,36933038,44846932,15163258,47893286,2891150,8099994,90061527,71965942,71522968,30163921,4091162,78602717,99971982,28787861,48395186,27625735,23134715,89996536,6505939,94911072,50007421,68875490,56461322,9259676,10264691,1375023,73109997,17081350,58208470,93057697,84406788,33237508,81774825,4069912,64087743,73786944,9175338,45848907,77898274,89046466,32151165,5482538,68316156,40686254,57961282,26776922,66116458,55615885,52261574,26275734,14947650,83533741,62232211,27187213,42692881,35512853,56473732,45306070,61712234,75820087,20867149,16405341,14220886,21919959,62552524,176257,13819358,8115266,29188588,32590267,83210802,9860968,71469330,89811711,90654836,19898053,76315420,9860195,874791,57020481,4095116,23110625,48675329,38645117,38009615,4508700,74452589,64602895,36845587,61897582,62428472,70541760,68041839,3233569,6038457,11757872,43933006,10453030,36468541,38256849,5640302,20645197,96709982,86798033,44627776,17037369,39373729,31491938,65454636,1250437,54517921,16099750,22435353,91141395,82779622,60393039,53888755,90090964,78300864,72357096,82651278,57393458,43506672,59981773,6157724,63152504,44060493,53393358,59405277,52060076,48673079,37560164,40356877,6521313,34667286,65851721,97011160,5073754,24915585,59614746,72793444,21397057,49882705,87160386,24826575,92353856,55669657,88130087,15015906,29401781,62762857,70782102,83948335,92867155,42237907,88698958,83155442,36189527,45790169,749283,45990383,83747892,44844121,8791066,77301523,41245325,51715482,61928316,72278539,67451935,64055960,69697787,91802888,12303248,75052463,75135448,81172706,47298834,64848072,7182642,92541302,90158785,65880522,79322415,17857111,38022834,33935899,37280276,83133790,6111563,1239555,82327024,89804152,21289531,13468268,27411561,69786756,36135,61815223,76825057,68110330,99729357,21001913,46540998,96852321,2204165,55850790,61741594,38510840,91255408,85695762 +14220886,24058273,17068582,82327024,67793644,49236559,66250369,96193415,44889423,21993752,29834512,66832478,96735716,78785507,95957797,93933709,1197320,22108665,16595436,4204661,81172706,44784505,66116458,6986898,74743862,29819940,60430369,37620363,29510992,74724075,44348328,6793819,50806615,96726697,67030811,26292919,78561158,20568363,68000591,14626618,58180653,55770687,69355476,78218845,57241521,5073754,19457589,15015906,53084256,89214616,9599614,54427233,10358899,29029316,66045587,7646095,74110882,16971929,92787493,33628349,14947650,71300104,73124510,45237957,16054533,16019925,72274002,72777973,35092039,6871053,99604946,55247455,86543538,81853704,99524975,55470718,26114953,69136837,99549373,20122224,93515664,75229462,38658347,47213183,30764367,76434144,32250045,74357852,17764950,21289531,23194618,32151165,61982238,44481640,17539625,55753905,6505939,37891451,73235980,36808486,36753250,35192533,65715134,55850790,72019362,88047921,6525948,39171895,60581278,66428795,91255408,94090109,77284619,98130363,14326617,4064751,29994197,77312810,93270084,1431742,2204165,13468268,8807940,4515343,73222868,98739783,62452034,52261574,5111370,41092172,415901,9175338,9575954,2717150,45790169,26139110,62430984,20140249,50702367,8733068,92554120,5832946,29466406,72738685,28928175,70372191,39201414,67451935,6038457,40197395,12416768,11543098,11581181,8129978,12303248,53648154,29959549,90310261,73617245,53802686,57359924,6153406,92604458,16099750,91938191,48360198,29188588,43501211,70596786,6808825,80555751,33201905,12348118,29635537,33431960,24168411,45407418,4787945,9886593,19101477,56153345,7502255,84406788,4099191,59910624,83155442,83269727,42237907,34236719,66663942,62232211,36468541,79821897,72725103,34497327,79191827,33336148,40152546,58749,17365188,64602895,55960386,67281495,32159704,78909561,16097038,31126490,4806458,27665211,68824981,85242963,79922758,39801351,59501487,25842078,68816413,89804152,20885148,33935899,97783876,44842615,58208470,44479073,27625735,26998766,51426311,34946859,80014588,77272628,76960303,61960400,91990218,33797252,32058615,55602660,99515901,9860195,23848565,23569917,59371804,91727510,41245325,3294781,92033260,22450468,59405277,31491938,72373496,93566986,30090481,87598888,18833224,17037369,30543215,15902805,44177011,55669657,15535065,74452589,8099994,92803766,94595668,34044787,77413012,4668450,40781854,65271999,10309525,58224549,84127901,82651278,26863229,8729146,3088684,9623492,69641513,57658654,4091162,36685023,66667729,78884452,28734791,508198,71920426,78589145,18504197,17957593,54868730,44983451,4508700,64848072,37435892,79942022,90158785,43376279,48088883,98948034,1204161,86118021,51047803,50567636,38494874,94911072,569864,66885828,31161687,38645117,48673079,36312813,83533741,7423788,72357096,99125126,49271185,96420429,90457870,89996536,32161669,70800879,56955985,77694700,61263068,90013093,80246713,60176618,85023028,28787861,40984766,45919976,63059024,66903004,40677414,45990383,62740044,7300047,32590267,44847298,82339363,86242799,65338021,99965001,13862149,1569515,83789391,22942635,32426752,68644627,64055960,61712234,76540481,2917920,18699206,71522968,45428665,42644903,78602717,57240218,83210802,19939935,18411915,54263880,22405120,34667286,9259676,31904591,50668599,15075176,72495719,168541,15948937,72278539,51588015,57803235,33699435,5970607,99226875,78766358,48774913,3509435,86001008,22129328,12664567,26734892,66611759,99021067,31733363,51464002,82979980,96906410,9058407,41442762,36505482,92398073,4069912,40356877,61859581,37192445,4434662,63967300,52204879,74137926,70004753,94711842,80316608,65275241,4798568,321665,45848907,19376156,60106217,77898274,3880712,39373729,44060493,81898046,44844121,74441448,90061527,36396314,92215320,34432810,16405341,20002147,95581843,1788101,91664334,6819644,61373987,47893286,82897371,40686254,34698428,69697787,14363867,77377183,73031054,41481685,17081350,824872,30653863,11161731,46540998,8128637,66271566,1936762,30787683,19891772,68875490,26392416,24619760,91802888,82427263,68102437,56461322,20867149,22113133,61271144,84349107,89046466,46851987,26102057,96318094,56531125,96852321,47738185,62051033,72732205,54517921,16380211,68204242,1250437,87710366,44473167,98943869,54663246,75543508,669105,66319530,52060076,26063929,8791066,86022504,93359396,92692978,43152977,94539824,8373289,14093520,24953498,3183975,71316369,38061439,3233569,2208785,29932657,49328608,7182642,27041967,68939068,14723410,37957788,37659250,48893685,33061250,16684829,70367851,57393458,86821229,33304202,47361209,16445503,11757872,75777973,10453030,89811711,7687278,53547802,15148031,75153252,14731700,4119747,14349098,36580610,17386996,30998561,77301523,9257405,62490109,83150534,91831487,84684495,92692283,34698463,85695762,90004325,61859143,90090964,57163802,44846932,65017137,28550822,71965942,37183543,749283,526217,17976208,36933038,28796059,54987042,7011964,2891150,53842979,48675329,8651647,37224844,82178706,42199455,95290172,65038678,69786756,40865610,50007421,82726008,54762643,59197747,36139942,43322743,84166196,3233084,51472275,92353856,98371444,63015256,94360702,62693428,45794415,81677380,11923835,49598724,62936963,86301513,9188443,47887470,11365791,22435353,26776922,45381876,47792865,95726235,89419466,30366150,91957544,97379790,58751351,77072625,45667668,66322959,85571389,20765474,74614639,59109400,44627776,39553046,38341669,26744917,66137019,98462867,65880522,79880247,22997281,22766820,14045383,30139692,76703609,57020481,19486173,69579137,17727650,22721500,48260151,3487592,30463802,84904436,23134715,61728685,94516935,21673260,68128438,82779622,39847321,86460488,68110330,10597197,26275734,67124282,62762857,51315460,54058788,78549759,99971982,60102965,1239555,70782102,43357947,84293052,70541760,13819358,99690194,20836893,82052050,9603598,84840549,75820087,88251446,76315420,53632373,18001617,15163258,32274392,39986008,54014062,49641577,73109997,47708850,9860968,97561745,75135448,33237508,59318837,65851721,4199704,76671482,59329511,2607799,874791,76330843,12571310,79806380,71083565,99658235,83651211,89499542,72238278,37675718,36812683,89078848,65081429,35512853,88130087,81274566,68316156,13348726,61928316,73392814,62552524,87160386,82886381,13173644,64087743,13470059,10366309,19272365,83368048,92530431,64157906,87720882,40781449,75986488,30891921,3633375,16387575,68041839,89217461,27185644,37280276,53666583,3235882,38256849,37166608,46870723,7229550,35456853,53888755,29516592,48892201,93057697,10264691,26493119,68694897,54606384,62357986,91240048,42782652,72793444,1022677,15536795,86798033,97011160,61141874,42967683,99861373,7919588,56484900,4095116,5267545,6221471,7685448,24733232,90272749,67474219,88444207,27325428,60955663,63628376,26664538,38365584,18783702,20681921,57248122,51466049,88904910,33553959,99333375,89637706,18131876,23110625,5482538,67084351,36135,61623915,71469330,43933006,76170907,15064655,21919959,81805959,51507425,30395570,17016156,63372756,16424199,10649306,45306070,24591705,42073124,88653118,43045786,81855445,44245960,30811010,30163921,63152504,247198,50188404,29401781,84002370,45075471,51590803,95395112,75128745,79657802,92998591,70036438,77620120,98653983,83133790,3773993,59614746,69605283,34493392,55189057,65074535,55143724,48658605,49882705,30771409,32699744,27187213,17385531,77187825,41092102,36845587,6497830,1375023,89699445,17894977,49597667,36930650,85116755,21533347,38022834,77787724,98648327,67031644,21397057,33565483,28851716,12024238,52613508,37560164,42947632,18663507,7955293,33249630,62496012,97057187,44550764,47298834,83083131,44664587,8696647,79136082,7517032,24314885,65454636,61897582,29065964,59177718,99297164,92541302,42692881,23740167,62803858,92867155,24915585,76825057,70420215,92071990,91141395,65047700,44915235,30694952,16567550,18466635,36780454,20642888,99224392,37739481,17146629,9829782,19898053,8913721,94076128,72614359,54232247,56515456,40027975,47090124,24826575,60393039,13231279,99917599,97940276,176257,35996293,84187166,91281584,38510840,20486294,51149804,93562257,61815223,37501808,45617087,22200378,69848388,97281847,77300457,27411561,40534591,30569392,71333116,52293995,81774825,69255765,6521313,34295794,56473732,112651,95251277,9398733,4978543,23432750,83948335,54199160,96709982,8115266,17857111,5640302,73786944,73168565,55615885,83302115,42307449,2331773,30218878,48395186,38009615,21070110,38008118,45995325,36189527,20645197,82532312,63506281,21001913,25636669,7104732,41380093,71657078,61741594,75407347,33123618,8055981,26507214,45049308,88698958,4985896,22879907,93053405,95010552,99729357,45996863,56424103,51715482,79322415,43506672,68702632,93790285,10961421,67513640,73021291,85711894,8634541,97641116,59981773,53393358,31727379,80251430,17058722,62428472,70727211,85117092,79738755,62115552,78300864,75052463,6157724,5822202,57961282,72358170,33895336,67227442,6111563,18806856,64098930,7066775,26397786,90654836,83747892 +37183543,24591705,69786756,53842979,73168565,30366150,99965001,29819940,92787493,27185644,20642888,9398733,73617245,59177718,60430369,97561745,14045383,65047700,19101477,61859143,39801351,92554120,26664538,76671482,50188404,34493392,1022677,64602895,28787861,78218845,16595436,66322959,57803235,8913721,62740044,39847321,32161669,874791,68128438,67084351,3880712,526217,34295794,1431742,40152546,99297164,77413012,55770687,49641577,112651,38061439,40197395,14093520,84187166,51715482,65338021,77272628,62936963,73222868,1204161,92215320,28550822,79821897,176257,42782652,60955663,29188588,5482538,48360198,17016156,36780454,36468541,63015256,62693428,89214616,84840549,50702367,18131876,21289531,1375023,15535065,18001617,28851716,80014588,27041967,98130363,53648154,21533347,15064655,10264691,45667668,66903004,51047803,47298834,75407347,86022504,41092172,96906410,89046466,39553046,79922758,65454636,43933006,92803766,62496012,4091162,18783702,68000591,70596786,45790169,23569917,29029316,44983451,11365791,36685023,70367851,89419466,66832478,62357986,22997281,77620120,36580610,43501211,99861373,69355476,68939068,67031644,168541,83083131,74441448,86821229,31126490,38365584,53084256,85242963,37560164,6521313,6986898,56473732,71083565,92033260,6153406,92541302,6793819,9058407,7955293,27411561,77300457,61728685,24168411,45995325,33123618,46870723,37435892,98462867,33249630,66116458,96852321,66045587,96709982,72793444,42073124,26998766,19891772,10358899,75229462,90061527,26397786,41481685,68204242,16971929,45996863,33237508,82979980,30694952,38645117,81172706,14349098,3509435,67474219,36753250,23110625,4099191,54762643,67793644,88653118,55189057,89078848,44550764,62051033,71300104,36135,43322743,30163921,3235882,36139942,77284619,67281495,72278539,4806458,4787945,71333116,40781449,88904910,95957797,19939935,63967300,33201905,69697787,83368048,11923835,90090964,66271566,38341669,8099994,37192445,96735716,33565483,83651211,77694700,91957544,13862149,34698428,42199455,90457870,36505482,91990218,20765474,24058273,3233084,29932657,38022834,3183975,2208785,9623492,99658235,17539625,18806856,78589145,68816413,2917920,99515901,88698958,247198,82339363,94911072,76960303,51472275,78561158,84904436,51464002,29635537,28796059,89811711,50007421,33336148,24619760,52204879,62490109,20681921,84349107,72357096,82886381,7919588,35192533,53666583,23432750,27665211,25842078,26114953,14947650,82726008,13348726,93933709,72725103,4069912,60106217,55615885,23194618,80251430,38658347,90272749,62452034,26063929,19486173,43152977,49597667,16684829,20002147,16099750,19272365,42644903,89804152,63506281,99524975,34432810,16387575,73021291,39373729,91938191,30395570,38008118,52261574,86118021,7011964,64098930,44479073,90310261,50668599,72495719,85711894,66319530,40534591,70420215,60102965,92998591,95010552,29834512,66250369,22200378,8373289,12348118,91802888,14220886,27625735,93790285,47090124,8807940,74357852,97011160,99333375,91664334,66667729,76434144,20122224,43506672,61815223,99917599,78549759,73392814,12024238,6808825,21001913,65271999,33304202,70004753,3088684,76540481,3487592,67030811,52613508,98648327,74614639,24826575,7646095,88130087,49236559,1197320,70800879,45617087,82779622,32274392,32250045,77072625,29959549,85117092,12571310,57241521,33628349,54014062,77312810,36845587,81274566,9603598,18699206,14326617,96420429,5970607,59910624,54058788,61741594,13468268,70727211,61982238,8055981,10309525,44177011,75153252,59614746,2717150,31904591,38256849,40677414,70372191,35996293,55143724,61263068,73031054,82651278,38494874,99971982,86001008,26863229,20486294,9599614,55753905,37739481,59109400,45075471,15015906,3294781,15948937,7229550,88047921,14731700,2204165,99549373,91141395,43045786,48673079,68875490,64087743,4064751,42947632,67227442,415901,16380211,54606384,53888755,79191827,96726697,30653863,40356877,30139692,43376279,47708850,61623915,98943869,1239555,81898046,95395112,52060076,44842615,22405120,72274002,17146629,20867149,61897582,52293995,56424103,15536795,94539824,17081350,68316156,22129328,16405341,53632373,92692283,20140249,97641116,54427233,68102437,8634541,7687278,92398073,46540998,34946859,22879907,4119747,21070110,63628376,44889423,77377183,6111563,29401781,51466049,83789391,22721500,28928175,97783876,20836893,83210802,70541760,10366309,94516935,94595668,42237907,22108665,29994197,36312813,37659250,58751351,64848072,92353856,76330843,99224392,93515664,23848565,20645197,23740167,63059024,81677380,26776922,86242799,48088883,79136082,91240048,9257405,10649306,30811010,4508700,59371804,75135448,5073754,79806380,89996536,60581278,47738185,25636669,57961282,32151165,72777973,22942635,44915235,9860968,63372756,55247455,36812683,51149804,26102057,47893286,84293052,45428665,54199160,44844121,12416768,48893685,71469330,45237957,74452589,26744917,37891451,91255408,47361209,58749,92530431,7066775,45306070,8696647,65880522,39171895,37675718,73786944,50806615,13231279,34698463,94090109,57240218,15163258,17037369,80316608,83302115,79322415,93270084,31733363,87710366,10453030,44245960,87160386,6505939,71920426,19898053,62803858,50567636,55850790,51590803,68824981,96193415,16054533,19376156,88251446,92604458,9575954,99226875,5111370,41380093,68644627,56461322,55602660,67451935,97281847,37501808,95581843,59981773,59501487,62115552,93053405,57248122,5267545,81774825,32159704,17386996,824872,85023028,49271185,84002370,4095116,7423788,17068582,69641513,58208470,19457589,57658654,11543098,61859581,3633375,89637706,49598724,77187825,72732205,49882705,64157906,79880247,99021067,69136837,17058722,77787724,79657802,6871053,26734892,34236719,6157724,34667286,62430984,91831487,81805959,34497327,51588015,76170907,93562257,4434662,87598888,45848907,85116755,40686254,64055960,26493119,90004325,75820087,40984766,65017137,7104732,76703609,42307449,54868730,22435353,93566986,45990383,86301513,4798568,35456853,35512853,66611759,54663246,80246713,33797252,38510840,34044787,22766820,40027975,9860195,68041839,44481640,92692978,71316369,21919959,86460488,10597197,61960400,99604946,33895336,90158785,40865610,17727650,7502255,55669657,22450468,89699445,7300047,87720882,85571389,79738755,8129978,73235980,57020481,86543538,44784505,4199704,31161687,33699435,99125126,15148031,98371444,66885828,76315420,6497830,30764367,1569515,61141874,32699744,569864,84684495,56515456,8791066,75777973,43357947,16445503,74743862,4668450,75543508,94360702,20885148,46851987,48774913,74110882,4204661,16424199,88444207,36396314,65851721,48395186,65275241,48675329,6221471,53393358,16097038,41442762,8128637,78785507,65715134,82178706,31491938,62232211,48892201,54263880,13470059,59318837,26392416,70036438,7685448,80555751,21993752,48658605,24953498,44060493,44348328,17385531,56531125,69848388,18411915,1936762,73109997,45919976,61373987,22113133,83269727,59197747,4985896,12303248,44627776,11161731,81853704,54987042,37957788,76825057,75052463,61928316,37620363,97379790,95251277,321665,90654836,45794415,99690194,21673260,30771409,29466406,71657078,26139110,24915585,94076128,7517032,56153345,26275734,78766358,92071990,44846932,83133790,9188443,55470718,60176618,37280276,12664567,15902805,58180653,45407418,42692881,96318094,36808486,86798033,98948034,9886593,36933038,30463802,32590267,1788101,71522968,30998561,66137019,30543215,35092039,23134715,69255765,83155442,51507425,30218878,77898274,83533741,669105,30569392,37166608,9259676,17365188,13173644,66663942,55960386,94711842,39986008,53802686,65038678,33061250,78300864,57393458,5640302,36930650,10961421,93057697,81855445,24733232,29510992,62762857,53547802,9175338,4515343,18466635,91281584,61271144,65081429,95290172,18833224,3233569,69579137,68110330,31727379,16567550,65074535,17957593,83747892,3773993,8733068,91727510,97940276,66428795,48260151,8651647,69605283,1250437,14723410,28734791,508198,72614359,79942022,54517921,32426752,11581181,62428472,7182642,77301523,5822202,49328608,47887470,83150534,14626618,2891150,18663507,56484900,15075176,72373496,29516592,78884452,82427263,30891921,67124282,89499542,82052050,16019925,41245325,90013093,73124510,68702632,26507214,67513640,84127901,44473167,68694897,17857111,75128745,32058615,13819358,63152504,72358170,8115266,27325428,36189527,33935899,6038457,44847298,2607799,17976208,2331773,99729357,30787683,6819644,54232247,40781854,82532312,59329511,38009615,74137926,37224844,92867155,57359924,30090481,11757872,82897371,72738685,47213183,9829782,51315460,26292919,61712234,33431960,72019362,5832946,56955985,62552524,33553959,42967683,97057187,75986488,27187213,17894977,60393039,72238278,20568363,39201414,59405277,93359396,21397057,17764950,70782102,74724075,4978543,47792865,749283,29065964,78909561,84406788,71965942,78602717,83948335,14363867,57163802,44664587,45049308,24314885,51426311,84166196,89217461,98739783,6525948,58224549,95726235,45381876,41092102,85695762,82327024,8729146,18504197,98653983 +61859143,17037369,43376279,66832478,8913721,64055960,12348118,3233084,4119747,89214616,44889423,16097038,16595436,19891772,95957797,15015906,76540481,99224392,34497327,62693428,30787683,37957788,32161669,98948034,9623492,83789391,88653118,92803766,18699206,62496012,86001008,65454636,62740044,92998591,7502255,63967300,65275241,83948335,55247455,92692978,70596786,19457589,34698428,40197395,68939068,57803235,28851716,62936963,669105,36580610,14349098,96735716,75153252,19486173,81677380,22942635,60955663,43506672,45848907,29959549,61741594,42782652,38022834,38658347,54427233,89046466,99226875,33553959,39801351,20140249,76434144,73235980,93359396,39847321,44550764,68644627,45790169,41481685,27665211,62357986,21993752,5970607,6808825,59614746,17385531,89078848,749283,508198,29188588,70800879,12571310,43357947,2891150,9188443,49641577,34493392,53842979,70004753,9860195,18001617,96709982,47792865,46851987,26063929,36753250,49236559,52261574,34698463,27325428,33797252,62803858,15064655,9398733,20642888,68816413,56515456,37739481,57248122,70367851,64848072,26114953,30694952,42307449,14947650,61897582,22766820,92604458,40677414,20885148,26998766,9603598,23848565,76960303,97940276,77620120,33336148,74614639,50188404,88047921,18783702,2717150,7687278,36845587,26392416,77301523,62430984,51715482,43045786,65851721,37435892,4515343,72019362,47708850,24591705,65338021,59177718,1431742,37620363,90272749,91255408,50702367,1204161,86543538,9860968,21533347,6986898,3294781,82779622,99515901,30569392,92554120,51047803,75407347,33201905,95395112,33935899,29994197,62232211,33237508,79821897,30163921,96193415,35996293,36812683,74452589,66250369,1375023,84187166,50668599,70036438,44060493,54663246,66903004,4099191,71920426,23569917,92541302,3183975,99524975,23740167,6505939,68824981,72725103,28734791,65074535,42199455,54987042,31733363,99965001,13231279,26275734,45049308,77284619,48088883,67031644,83083131,54762643,96318094,76671482,79136082,20486294,1022677,38365584,94539824,26493119,39171895,15536795,58749,93566986,71083565,33628349,62452034,72777973,61982238,56424103,44245960,25842078,52060076,38341669,75777973,8696647,30395570,11543098,11161731,50806615,5267545,98371444,22879907,69786756,97561745,52293995,97281847,79191827,54199160,168541,33249630,10366309,51472275,83302115,6153406,91664334,72793444,97783876,36468541,55189057,64087743,67281495,29834512,44177011,61859581,66116458,86798033,43933006,46540998,7104732,48260151,30543215,81853704,16387575,90013093,63059024,73617245,66885828,63372756,78589145,78300864,11923835,89699445,45237957,36135,47361209,65880522,71333116,85571389,45995325,7955293,74441448,112651,39201414,17068582,3633375,57241521,50007421,874791,1250437,23194618,71316369,30771409,78549759,86821229,24915585,22450468,51588015,91281584,68102437,9575954,4508700,3509435,8055981,79657802,77413012,28796059,32250045,82427263,68694897,66271566,24733232,4798568,48360198,23134715,8115266,24826575,57359924,73031054,38061439,29819940,57393458,16019925,17365188,56153345,5640302,94090109,57020481,20122224,85711894,13470059,22108665,59371804,98130363,48673079,247198,28550822,70541760,32426752,17058722,18806856,38494874,72274002,83533741,51464002,3773993,14093520,10961421,53802686,88444207,96852321,40686254,66663942,83155442,73124510,61271144,38008118,66137019,33565483,83210802,6793819,36933038,22129328,7919588,4204661,6111563,13348726,17764950,26102057,98648327,36505482,74724075,45996863,98462867,36780454,93933709,55143724,45667668,69641513,39373729,68316156,51590803,34236719,84904436,91727510,44983451,9175338,35092039,36189527,38510840,87160386,92692283,90310261,8791066,74357852,98739783,45990383,36930650,61815223,44473167,33304202,3487592,78766358,39553046,77377183,67474219,60581278,51426311,99658235,71657078,87720882,35456853,87710366,88130087,85242963,81172706,91831487,38645117,58208470,29932657,67084351,37183543,40152546,21397057,80014588,93270084,29466406,69579137,92033260,54517921,44915235,19376156,34295794,42237907,6221471,74137926,86301513,77312810,53888755,68128438,42073124,76703609,30090481,55602660,82979980,82532312,7517032,72373496,73392814,66045587,12024238,83368048,48774913,29029316,68204242,92787493,9829782,7066775,16099750,78602717,57961282,45306070,45075471,99549373,21001913,19101477,8733068,18833224,8634541,38009615,66611759,73786944,97057187,90654836,8807940,79880247,44348328,68875490,2607799,97011160,415901,57240218,84293052,80555751,8128637,67793644,40984766,33895336,34946859,9599614,91957544,41442762,59197747,51507425,27625735,56531125,39986008,84840549,89811711,83651211,53547802,61623915,40781854,40356877,59109400,61373987,69255765,4095116,55669657,26776922,1569515,45407418,59501487,16380211,85116755,6521313,54014062,33123618,37192445,4668450,31904591,20568363,569864,7300047,7423788,99729357,75052463,65047700,14326617,45428665,45919976,55470718,44784505,3880712,92215320,64098930,4069912,99917599,63506281,72614359,37675718,71469330,14723410,67030811,79942022,91802888,62051033,70420215,56473732,79806380,76315420,77187825,24058273,77272628,44627776,94711842,94911072,29635537,9058407,69697787,61728685,85695762,27185644,78218845,17957593,84127901,92867155,57163802,59981773,98943869,30139692,97641116,19272365,63152504,80316608,17146629,41092172,15163258,33431960,53632373,4434662,53084256,24168411,99861373,4806458,26863229,46870723,73222868,21070110,82651278,99971982,74110882,59910624,49271185,89804152,6497830,14045383,29510992,93515664,20681921,90457870,99297164,75543508,10358899,29401781,2208785,96726697,62552524,9886593,76330843,66322959,30463802,30366150,71522968,68702632,17727650,83133790,83269727,33061250,58751351,32699744,37166608,24314885,55615885,55960386,95251277,16971929,63015256,82886381,26139110,48395186,27411561,77694700,28928175,60176618,15948937,75986488,47298834,67124282,20765474,19939935,42967683,36685023,43322743,54868730,13468268,34432810,5482538,77300457,42947632,57658654,65715134,70727211,24619760,15075176,28787861,43501211,36139942,5073754,40781449,69355476,75229462,99125126,84406788,88904910,26507214,72495719,71300104,44842615,1788101,48892201,26734892,3233569,35192533,95290172,68000591,80246713,67451935,7182642,41092102,15535065,12416768,41380093,4985896,53666583,93053405,91240048,51466049,51315460,47213183,85023028,99333375,96420429,82339363,11365791,4787945,17386996,82052050,24953498,89996536,68041839,10453030,49597667,4064751,60106217,94076128,93562257,30218878,58224549,99604946,21289531,53648154,62490109,95726235,81855445,78884452,19898053,84002370,72357096,72732205,9259676,32274392,1239555,94516935,47090124,77787724,65271999,8651647,15148031,48675329,52613508,13862149,47738185,74743862,65017137,93790285,75135448,44479073,40865610,8129978,48893685,73021291,6157724,11757872,91141395,14731700,32159704,7011964,35512853,58180653,61263068,49882705,31126490,82726008,87598888,62428472,59318837,526217,21673260,99690194,60430369,16054533,29065964,26744917,67227442,83150534,62762857,54606384,88251446,26397786,36808486,94360702,72738685,7229550,14363867,69605283,78909561,18466635,49598724,76170907,70372191,37280276,10649306,60102965,55753905,30764367,22721500,824872,37224844,97379790,89419466,18663507,36396314,8099994,92398073,38256849,30998561,18131876,20002147,67513640,1936762,20867149,93057697,92530431,17081350,16684829,45794415,17976208,80251430,44846932,72278539,78561158,54232247,82178706,20645197,91938191,92071990,90061527,90090964,37501808,31491938,75128745,22435353,55770687,66319530,36312813,69848388,61928316,56461322,40027975,6525948,47887470,51149804,99021067,54263880,66428795,63628376,95010552,54058788,5832946,88698958,7685448,53393358,23110625,77072625,86460488,17016156,89637706,8373289,44664587,56484900,10309525,30653863,81805959,3235882,23432750,16445503,69136837,4199704,79922758,9257405,72358170,52204879,37659250,41245325,2917920,2204165,16567550,84349107,44847298,33699435,6038457,25636669,89499542,31161687,50567636,12664567,62115552,30811010,34044787,20836893,17539625,73168565,7646095,48658605,79738755,26292919,5111370,16424199,42692881,27041967,32151165,1197320,72238278,6871053,94595668,21919959,66667729,81898046,44844121,90004325,79322415,64602895,30891921,17857111,61141874,37891451,78785507,86022504,40534591,70782102,17894977,75820087,14626618,47893286,22200378,32590267,83747892,71965942,44481640,12303248,84684495,64157906,68110330,92353856,18411915,26664538,82897371,15902805,76825057,86242799,321665,91990218,65081429,59329511,6819644,45617087,13173644,59405277,14220886,77898274,16405341,37560164,22997281,27187213,86118021,96906410,85117092,81774825,55850790,29516592,49328608,81274566,176257,10597197,22405120,5822202,42644903,98653983,60393039,84166196,11581181,8729146,89217461,73109997,45381876,56955985,43152977,32058615,82327024,18504197,4978543,90158785,22113133,4091162,95581843,13819358,3088684,34667286,31727379,2331773,61960400,61712234,10264691,65038678 +66045587,70420215,2717150,77284619,54199160,30998561,82339363,27665211,10309525,62740044,88653118,55470718,33201905,89214616,93933709,62552524,35092039,37620363,37183543,16445503,47090124,54663246,26392416,20765474,13862149,77272628,71333116,81898046,15535065,79657802,17764950,29834512,13173644,67793644,49641577,89637706,43501211,64087743,9257405,10366309,27325428,61263068,20122224,63059024,21533347,73786944,89804152,80014588,76960303,1197320,62357986,39847321,50668599,84840549,49328608,89078848,31904591,14626618,84187166,74137926,40356877,8807940,5111370,1431742,92787493,44983451,27411561,92033260,55753905,10453030,91957544,93566986,99515901,53802686,5822202,30764367,54762643,18504197,83651211,71657078,45428665,6525948,64098930,24915585,39373729,63967300,27041967,75777973,36580610,18833224,94516935,37739481,49271185,3509435,72274002,60430369,64602895,91664334,67474219,65081429,20836893,67451935,34044787,40677414,9860195,67030811,39801351,49882705,20486294,65851721,33431960,94595668,52613508,75986488,12348118,24058273,17081350,7919588,84904436,82726008,54606384,85116755,59329511,22997281,75820087,321665,32590267,96318094,59177718,69136837,31161687,92604458,9259676,35192533,54014062,2917920,26507214,66250369,34698463,36780454,29994197,55247455,66903004,88047921,16054533,22108665,50702367,4204661,53666583,3880712,78785507,44844121,99021067,30163921,37501808,57803235,66667729,40152546,66116458,508198,247198,62051033,3487592,51464002,53547802,3233084,4099191,526217,87160386,66271566,8129978,45049308,47792865,47887470,43376279,68816413,19376156,19891772,77787724,99549373,92398073,70372191,44481640,52204879,14349098,71316369,14326617,12571310,42199455,5482538,96193415,76825057,10649306,3088684,62490109,85117092,60106217,38061439,49598724,73235980,33935899,65038678,36808486,29516592,61960400,65338021,65275241,2204165,45794415,42782652,7229550,66428795,77620120,56461322,78589145,19101477,10961421,47361209,88251446,16097038,43322743,56515456,68000591,48260151,96735716,80316608,69848388,68644627,18783702,74110882,99917599,58224549,60176618,33237508,4064751,46851987,6986898,45306070,30366150,50567636,73124510,89046466,99971982,66611759,62452034,4787945,73168565,19486173,4119747,20140249,14045383,30811010,94360702,72373496,65715134,18663507,90457870,16387575,69355476,4095116,8651647,28734791,18131876,1569515,95290172,43933006,60102965,51149804,1239555,46870723,84406788,72357096,9886593,45407418,55615885,73617245,59910624,61271144,66832478,70800879,99965001,30891921,99658235,91727510,30395570,40027975,43357947,30090481,669105,20002147,78602717,29065964,33699435,44479073,95726235,14220886,44842615,5832946,76315420,9058407,75135448,99125126,90004325,51047803,18806856,84684495,35996293,35456853,22766820,55143724,84002370,26397786,16380211,45075471,68939068,30139692,7300047,28928175,29029316,61373987,82897371,9575954,72278539,98648327,22200378,12664567,4515343,89699445,14947650,53393358,38022834,6793819,80251430,51507425,9175338,22879907,3633375,7685448,22405120,92998591,62693428,45237957,56484900,57240218,62496012,23848565,81853704,17037369,15536795,14731700,59109400,67031644,83150534,8696647,32151165,75543508,80555751,20867149,79806380,18411915,91938191,63628376,22450468,93057697,7687278,61141874,8733068,15148031,4668450,22113133,16567550,98130363,40865610,32161669,61712234,77694700,86022504,34497327,53648154,61741594,70727211,5073754,50188404,22721500,75153252,3233569,8115266,36139942,40781854,46540998,52261574,30787683,97940276,69786756,37891451,37659250,77300457,32159704,72238278,33565483,79136082,27625735,22435353,2891150,68204242,68875490,98653983,44784505,31126490,79738755,89996536,824872,90013093,19939935,86242799,71920426,40984766,99729357,34295794,26139110,88444207,86001008,33797252,82886381,96726697,83210802,89419466,83155442,54058788,77898274,38256849,16099750,6808825,51426311,87598888,20568363,99226875,16405341,42692881,25636669,6505939,76170907,56955985,39986008,62803858,6153406,17068582,65880522,4508700,79821897,47213183,89217461,16019925,6521313,569864,36753250,42073124,59371804,2208785,41245325,45617087,40781449,36312813,82052050,62936963,9599614,30771409,94911072,15902805,63506281,81172706,51588015,48892201,98371444,7182642,74441448,62430984,17386996,96906410,5640302,6157724,29466406,23569917,45996863,24826575,39553046,61982238,32699744,5970607,81677380,74743862,81805959,95957797,92554120,5267545,58208470,38008118,33249630,84166196,84127901,168541,75128745,58180653,99604946,99524975,17385531,86118021,48395186,91831487,41380093,68128438,76330843,31491938,70367851,88904910,67281495,17016156,13348726,44889423,52293995,36135,72358170,21070110,78300864,66137019,48088883,15015906,72725103,42644903,61623915,26102057,4985896,94090109,37560164,47708850,63015256,61859581,71469330,42237907,90061527,17976208,83789391,48774913,72614359,30543215,26275734,1788101,112651,12416768,3183975,55189057,54427233,42307449,67227442,19272365,18699206,92692283,85571389,1204161,71083565,79880247,47738185,44664587,45848907,76434144,57163802,44473167,62762857,55770687,76540481,32058615,86460488,33061250,28851716,26493119,26292919,7066775,95010552,95251277,99297164,92803766,97057187,86821229,53632373,86798033,47298834,45990383,29932657,1250437,81855445,92692978,78218845,54517921,34698428,70004753,31733363,38658347,79922758,23194618,7646095,55602660,9188443,68702632,93053405,24619760,77187825,92071990,30653863,59197747,16595436,32250045,93790285,99861373,11543098,7955293,83747892,50007421,18001617,41092102,16971929,69579137,36930650,33304202,57248122,73109997,66663942,84349107,20885148,33628349,51590803,8099994,97011160,59318837,56473732,8913721,60955663,67124282,30463802,93562257,78561158,63372756,49597667,6111563,57241521,45381876,75229462,61859143,82979980,83083131,71522968,64848072,44348328,77312810,16684829,91990218,44846932,44847298,98943869,49236559,81774825,78909561,43506672,69697787,88130087,64157906,72777973,55669657,77413012,14363867,95581843,25842078,36396314,43045786,62115552,24314885,68694897,85023028,176257,65047700,29635537,85242963,56424103,69641513,21397057,8729146,4434662,7011964,48673079,74614639,77072625,66322959,36685023,94711842,57020481,68041839,69605283,40197395,78884452,24168411,7517032,40686254,99333375,92541302,38009615,37957788,37435892,90272749,71300104,59501487,75052463,10597197,22942635,37192445,21919959,60581278,70036438,34432810,36812683,61728685,37675718,95395112,97641116,34493392,58751351,99224392,65017137,17539625,69255765,50806615,26998766,73031054,93359396,24733232,89811711,86301513,2607799,36505482,43152977,53888755,874791,54868730,83368048,54263880,86543538,35512853,23740167,57393458,61928316,2331773,59405277,12303248,38365584,26744917,7423788,24591705,44245960,11757872,17857111,29188588,44177011,65074535,83302115,72495719,97379790,97561745,11581181,62232211,88698958,92215320,56531125,53842979,78766358,36468541,17058722,44060493,70541760,6221471,29819940,26114953,90090964,19898053,87710366,87720882,42947632,29401781,82327024,4091162,17727650,48675329,15948937,67513640,28796059,57359924,82532312,21993752,68824981,30694952,51715482,48360198,749283,82651278,41442762,67084351,4069912,82178706,74724075,21673260,38494874,19457589,39171895,6038457,85695762,9603598,57658654,26863229,47893286,10358899,59614746,8373289,15163258,76671482,28787861,28550822,23432750,75407347,54987042,3773993,36189527,17894977,22129328,90310261,82427263,4199704,63152504,36933038,74357852,26776922,65271999,38645117,13819358,79191827,97281847,13468268,26734892,91802888,20642888,61815223,93515664,6871053,72732205,15064655,92867155,37224844,68102437,37166608,24953498,23134715,91281584,98462867,23110625,32274392,55960386,44550764,55850790,9398733,44915235,16424199,56153345,1936762,59981773,68110330,96852321,70596786,42967683,83133790,37280276,73021291,33336148,8128637,10264691,415901,15075176,20681921,77301523,68316156,89499542,13231279,4806458,38341669,85711894,34236719,77377183,98948034,9623492,60393039,33895336,79322415,45995325,18466635,14093520,34667286,97783876,92530431,40534591,82779622,48658605,79942022,21001913,29510992,12024238,92353856,34946859,52060076,33553959,39201414,30218878,54232247,17957593,83948335,36845587,4978543,66319530,72019362,20645197,78549759,73392814,91141395,81274566,83533741,99690194,94076128,11365791,91255408,29959549,61897582,1375023,11161731,41481685,72793444,26063929,17365188,98739783,31727379,48893685,57961282,17146629,96420429,64055960,4798568,26664538,7104732,83269727,3235882,65454636,30569392,74452589,3294781,7502255,27185644,44627776,1022677,51466049,8055981,45667668,94539824,41092172,8791066,33123618,90654836,53084256,21289531,32426752,93270084,84293052,80246713,58749,96709982,45919976,51472275,66885828,91240048,9829782,71965942,9860968,11923835,90158785,76703609,13470059,45790169,73222868,27187213,6497830,72738685,8634541,14723410,6819644,70782102,62428472,51315460,38510840 +168541,61741594,83789391,90457870,50188404,44177011,55189057,84349107,14093520,88444207,73168565,20486294,4069912,46870723,4119747,24591705,91240048,38061439,669105,36753250,39171895,29466406,22435353,48360198,57803235,2208785,30694952,94090109,53632373,75543508,31904591,40027975,42692881,29188588,1197320,81677380,69355476,71333116,67793644,8099994,62936963,53842979,51047803,7104732,5832946,33201905,24058273,12348118,66667729,96735716,66250369,56424103,34295794,68644627,57240218,8128637,48673079,67451935,51590803,33304202,5970607,48088883,70727211,30163921,19376156,76703609,11161731,48892201,15535065,61859143,9058407,41481685,43376279,81805959,26863229,43501211,96709982,89214616,63967300,62232211,28734791,57393458,4806458,56473732,7229550,34044787,93053405,66045587,66832478,94711842,46851987,29029316,67474219,22108665,99524975,52060076,66611759,19457589,65454636,18833224,61712234,60106217,30463802,21533347,44983451,63015256,14349098,30998561,84293052,86242799,78218845,22766820,55247455,13468268,74357852,49598724,21993752,30139692,79191827,90310261,66319530,66903004,38494874,37183543,77312810,15064655,38341669,85711894,60581278,73031054,62430984,29819940,36812683,20765474,10366309,47887470,6819644,99604946,37192445,40534591,10309525,17081350,6808825,39553046,32274392,80316608,75153252,52204879,37739481,72725103,84406788,1239555,247198,85117092,66885828,92398073,71920426,26392416,16097038,27665211,44627776,18411915,91727510,96318094,66271566,2717150,53666583,76330843,59318837,7423788,36312813,35192533,3880712,89804152,50668599,37659250,40781854,68939068,16971929,60430369,81898046,31126490,68875490,71965942,38022834,55753905,52293995,38658347,92692978,4204661,47361209,48774913,20645197,24826575,92033260,98371444,6153406,37620363,56515456,75820087,73235980,70372191,79821897,47090124,60102965,70004753,53084256,96193415,59501487,112651,44847298,55850790,19486173,4668450,33249630,81855445,92554120,56153345,32161669,99965001,69697787,8115266,51472275,91802888,17539625,62496012,71300104,17037369,92353856,77694700,11365791,87710366,83302115,65074535,54199160,4515343,92787493,7011964,50567636,45919976,44915235,98130363,6221471,59614746,508198,67513640,76315420,64602895,19891772,40984766,7955293,15148031,82726008,51464002,44842615,35996293,61373987,73021291,72495719,2204165,61960400,76434144,56531125,77300457,23110625,44245960,43357947,29959549,97641116,55770687,74110882,77620120,76671482,84002370,526217,5482538,40152546,80014588,99333375,38365584,20140249,13348726,99549373,22942635,46540998,57658654,91957544,71316369,62740044,89046466,98648327,321665,89078848,27625735,84840549,43322743,44473167,68000591,90004325,87598888,95010552,90090964,91831487,84166196,35512853,84684495,24733232,72614359,4091162,2917920,52261574,29994197,34698428,26664538,21001913,81274566,30787683,8696647,34493392,29834512,68816413,47213183,93790285,45848907,9829782,30891921,26114953,40356877,14220886,43045786,83651211,45049308,79942022,80555751,98943869,23134715,27325428,3233084,98739783,83210802,18783702,30218878,42782652,65271999,33237508,43506672,31733363,82886381,7687278,72274002,72357096,45794415,45407418,44784505,75229462,91664334,8729146,53648154,3487592,6986898,3294781,29516592,71083565,77284619,24619760,1022677,32250045,63059024,62693428,23569917,17727650,17058722,73124510,30395570,81172706,68204242,85023028,68316156,87720882,15075176,36505482,9175338,61928316,83133790,52613508,72732205,82532312,54058788,26292919,76960303,5267545,874791,12416768,14731700,67281495,39847321,57241521,55602660,39801351,17365188,34667286,17957593,9603598,51507425,7919588,44846932,12024238,37675718,54263880,75777973,70420215,64087743,61815223,3773993,44889423,32159704,86460488,44060493,77187825,19272365,66322959,5822202,70800879,33431960,65715134,27041967,9886593,59981773,3633375,21673260,6871053,85242963,569864,6525948,89419466,415901,18131876,89499542,57359924,82339363,84904436,99515901,9398733,18466635,85116755,36396314,70596786,30811010,37560164,1936762,13470059,34432810,22997281,4199704,14947650,77272628,79136082,34698463,54606384,30569392,1431742,38645117,9599614,3088684,54232247,9575954,34236719,62051033,14626618,82052050,26102057,45790169,88047921,79806380,86301513,40686254,70367851,5073754,27185644,78909561,81853704,44844121,77301523,72793444,35092039,87160386,22405120,42644903,78589145,20642888,93359396,17386996,6497830,59177718,57248122,68694897,16405341,37435892,53802686,17894977,99125126,9860195,10649306,18663507,75128745,20885148,59329511,34497327,18504197,94076128,80251430,1375023,92541302,72278539,63628376,26998766,20867149,96852321,26275734,89811711,23848565,29635537,67031644,94516935,20002147,8634541,26397786,31161687,16595436,65047700,7646095,83083131,49641577,4099191,91990218,17764950,1788101,15948937,48260151,74724075,75986488,824872,77377183,42073124,96906410,45237957,92215320,54427233,16684829,64157906,16445503,60176618,90158785,92867155,58208470,55615885,94360702,9623492,6038457,83155442,24915585,47792865,23740167,99224392,97281847,83150534,28796059,39201414,2331773,6111563,90654836,73392814,86001008,62490109,30764367,11923835,66663942,61263068,19101477,99297164,50007421,13819358,94539824,33336148,18806856,28928175,94911072,47708850,95395112,54987042,78766358,12571310,72777973,89699445,62803858,83533741,24953498,28550822,60393039,17068582,21289531,64055960,7300047,64848072,65081429,30653863,176257,47298834,26139110,38009615,93933709,58224549,71657078,65338021,37891451,22129328,3235882,41442762,99226875,8733068,78561158,74137926,8807940,69848388,1204161,68041839,8129978,45075471,96726697,78300864,33895336,7502255,40781449,20568363,36808486,32699744,83747892,57961282,95957797,19939935,68128438,70036438,65851721,39986008,48893685,17146629,10961421,91141395,55143724,45667668,93562257,98948034,8791066,21070110,33797252,86022504,61728685,9188443,17976208,73786944,65017137,3509435,37957788,59197747,59371804,64098930,53393358,99658235,74441448,58180653,68702632,97379790,40197395,7517032,79922758,92692283,68102437,19898053,77072625,89637706,23194618,97057187,82779622,72373496,8373289,54663246,15015906,90013093,78785507,74743862,55669657,75135448,82651278,76540481,92071990,49597667,79738755,65275241,65880522,45306070,62115552,31727379,49271185,50806615,36933038,32426752,33628349,45995325,92803766,5111370,49328608,45996863,6505939,85695762,86821229,86543538,44550764,63152504,36139942,4434662,1250437,55960386,51315460,62552524,25636669,25842078,69136837,4798568,2891150,10358899,95726235,38008118,88130087,92998591,73222868,20836893,47738185,36468541,59109400,86798033,72238278,23432750,88251446,36780454,60955663,82897371,33061250,6793819,93566986,78602717,33565483,14723410,77898274,14045383,57020481,49236559,749283,26507214,56955985,63506281,93515664,51466049,29401781,92530431,34946859,20681921,42967683,54517921,91938191,68824981,93270084,67030811,79657802,96420429,82178706,28851716,18001617,7066775,58749,98462867,17857111,40865610,61271144,12664567,29932657,40677414,73617245,71522968,61982238,79880247,68110330,4787945,37224844,67084351,76170907,3183975,84127901,17385531,49882705,61623915,61141874,90272749,83269727,80246713,45617087,39373729,61897582,44481640,30543215,11581181,99917599,16099750,59910624,78884452,63372756,51149804,41380093,9860968,2607799,16387575,36580610,85571389,45381876,26063929,9257405,77787724,43152977,13862149,10597197,36135,22450468,75407347,33935899,66137019,33699435,42307449,8651647,58751351,69605283,95581843,88653118,8913721,55470718,31491938,98653983,30366150,26493119,43933006,66428795,83368048,3233569,82427263,26734892,82327024,38256849,89217461,22879907,7685448,41245325,69641513,99021067,82979980,17016156,20122224,84187166,29065964,91281584,16019925,28787861,32151165,8055981,42947632,54868730,88904910,67124282,95290172,61859581,62357986,72358170,94595668,22113133,51426311,41092102,92604458,77413012,72738685,45990383,97011160,4095116,72019362,6521313,30090481,70782102,36930650,42199455,99861373,74452589,93057697,10453030,16424199,47893286,65038678,67227442,48658605,70541760,71469330,13173644,35456853,56461322,4978543,62452034,33553959,15163258,54762643,69579137,51715482,99690194,95251277,4985896,59405277,27187213,89996536,81774825,36845587,86118021,75052463,78549759,37280276,44479073,24168411,97561745,27411561,14326617,41092172,26744917,9259676,22200378,15902805,97940276,53888755,14363867,42237907,18699206,91255408,33123618,30771409,88698958,50702367,26776922,66116458,79322415,76825057,16380211,36189527,21919959,51588015,62428472,54014062,15536795,13231279,24314885,16054533,32590267,99971982,53547802,4064751,29510992,97783876,83948335,56484900,11543098,44664587,21397057,69255765,45428665,1569515,36685023,44348328,11757872,22721500,4508700,48395186,74614639,90061527,99729357,73109997,16567550,48675329,5640302,38510840,37501808,6157724,57163802,12303248,69786756,32058615,37166608,10264691,62762857,7182642 +4515343,61373987,17764950,29065964,9623492,74357852,93933709,55247455,39201414,24058273,11923835,66885828,68644627,97783876,60430369,75777973,96193415,29994197,77312810,20885148,44983451,75543508,20002147,14626618,40197395,69355476,415901,55753905,44915235,67451935,43357947,75986488,6497830,29029316,9603598,77787724,10309525,16445503,95957797,321665,168541,34698463,70596786,98371444,20140249,17727650,4099191,70004753,4668450,62430984,88653118,46870723,10366309,93057697,83533741,81853704,70800879,48892201,39553046,34698428,9886593,71657078,7517032,79821897,53648154,96726697,65275241,38494874,5832946,67793644,44784505,86022504,44844121,2717150,31904591,99658235,7955293,76434144,65047700,66667729,29834512,89046466,57240218,73222868,92604458,20765474,9188443,37224844,53547802,7502255,50806615,90457870,72725103,71965942,66428795,74110882,15015906,34044787,99861373,72373496,77620120,3088684,15148031,35192533,1431742,77272628,82897371,45996863,16595436,76960303,37620363,99690194,4787945,72738685,97057187,54663246,32590267,57163802,27665211,69786756,65715134,43045786,56531125,61982238,44473167,6221471,2204165,85116755,96318094,31126490,40027975,47792865,52261574,86001008,30463802,39986008,40865610,1569515,36685023,76703609,93515664,30090481,74441448,14363867,77284619,82052050,62452034,60581278,19101477,32426752,89499542,18833224,30163921,86543538,98648327,78909561,4064751,49882705,91141395,98739783,42073124,67084351,34946859,59371804,95010552,5970607,80316608,8651647,3509435,56424103,71522968,24168411,17539625,54199160,31733363,89214616,749283,94090109,85023028,50567636,80246713,8128637,23848565,18411915,59501487,4434662,99549373,45794415,38658347,55470718,99917599,4204661,8634541,28796059,66319530,94711842,1197320,32250045,66137019,27187213,49271185,18131876,33061250,82651278,54987042,79191827,37183543,26493119,30543215,6505939,5111370,14093520,6793819,35092039,54058788,73124510,63059024,17957593,67124282,8129978,93270084,874791,2607799,67474219,93562257,22405120,29510992,99021067,30771409,8807940,21993752,34236719,45790169,37739481,569864,96420429,39171895,36468541,68000591,8791066,72777973,57658654,2208785,83150534,66832478,58208470,38022834,26664538,45049308,84406788,98653983,47361209,91802888,96906410,26863229,69641513,82979980,3294781,54868730,30787683,68102437,84684495,22721500,18504197,50702367,63628376,38645117,67281495,75229462,26063929,26998766,36505482,87710366,66663942,35996293,29466406,40781854,73168565,92398073,14326617,44348328,3183975,45407418,11581181,27325428,669105,51047803,36812683,78766358,83368048,84349107,44177011,70036438,36312813,66903004,9575954,56153345,7687278,63152504,78602717,42237907,28734791,68702632,22113133,91831487,59197747,30139692,76170907,83210802,508198,50668599,33797252,66611759,35456853,82427263,61263068,31161687,8729146,68204242,8055981,47213183,62490109,63372756,58180653,4798568,4119747,16380211,44842615,20867149,30366150,1788101,89637706,89078848,50188404,12348118,30891921,42782652,69136837,15163258,76315420,15075176,43376279,16097038,92353856,65074535,89419466,7646095,72357096,49236559,29516592,25636669,84840549,99125126,90272749,40686254,6038457,29819940,92803766,26507214,8099994,10597197,87598888,824872,82726008,43506672,1204161,57803235,24619760,96735716,48893685,78218845,41092102,51315460,44481640,57241521,9058407,42967683,22450468,94539824,20122224,27411561,91938191,72274002,13231279,52204879,80014588,48774913,44479073,59318837,98130363,33431960,45919976,86301513,84127901,61960400,23569917,96709982,62762857,25842078,88047921,38008118,51472275,44846932,36396314,84293052,83302115,53666583,74137926,24733232,1250437,37192445,91727510,53888755,33628349,83083131,86821229,21397057,21289531,70372191,15535065,44889423,56484900,33237508,84904436,18806856,8733068,24953498,11543098,19486173,95581843,29188588,64157906,82886381,38061439,15536795,6153406,78589145,61623915,89699445,59405277,10961421,55770687,68824981,61712234,36780454,69579137,43322743,58749,66250369,26139110,16684829,73786944,14731700,52613508,92071990,45428665,1375023,90158785,92692283,18663507,76671482,6525948,40781449,17037369,81677380,79922758,60102965,11757872,41092172,58224549,44550764,39373729,14723410,59109400,10358899,36930650,33553959,48360198,72614359,61859143,36753250,55189057,79738755,37675718,26114953,19376156,68041839,72238278,12571310,94516935,36933038,72495719,61141874,20681921,62936963,33895336,51590803,15902805,40534591,65880522,99297164,71333116,92998591,47738185,4508700,99524975,526217,80555751,58751351,9829782,14045383,57248122,87720882,48088883,74743862,40984766,59177718,98948034,33249630,65271999,7229550,21070110,98462867,20645197,61741594,34497327,16424199,63967300,96852321,89811711,14947650,13173644,92554120,5267545,30764367,3233084,43933006,99604946,61271144,90310261,68110330,91957544,90654836,12664567,22200378,16971929,91990218,16567550,42644903,62232211,6871053,53802686,30218878,9257405,62552524,67513640,34432810,83651211,90061527,59614746,33699435,2917920,95726235,37166608,77187825,37891451,89996536,17385531,33201905,16405341,17068582,20486294,91664334,95290172,17081350,9599614,19939935,72019362,62740044,67030811,78300864,46851987,71300104,24826575,29959549,26292919,47090124,46540998,56955985,43152977,14220886,40152546,85242963,97641116,95395112,79806380,92215320,76540481,4978543,44060493,99515901,91255408,77898274,6986898,91240048,54517921,19272365,18783702,32274392,51588015,22942635,88251446,26392416,45848907,28928175,6111563,61897582,44245960,54606384,66116458,76330843,66271566,83269727,64087743,19457589,59910624,30811010,54762643,5482538,72732205,78561158,17976208,33565483,65338021,65038678,73392814,21673260,37435892,75052463,64848072,26744917,93053405,62693428,3235882,83789391,68816413,27185644,81172706,73031054,70727211,22879907,39801351,8373289,18699206,49328608,59329511,82339363,93790285,77072625,85571389,26776922,38510840,56461322,99965001,80251430,10649306,45075471,77413012,38256849,74724075,83133790,20836893,70541760,38341669,99971982,22766820,6808825,81774825,86460488,30569392,4199704,68316156,34667286,20568363,40356877,27625735,79657802,79942022,7919588,18466635,34493392,12416768,73617245,37957788,28550822,81898046,32699744,88130087,112651,1022677,85117092,37280276,99333375,97379790,49641577,62051033,98943869,79322415,41442762,32159704,66322959,16054533,51464002,64098930,2331773,54263880,75407347,41481685,84187166,90013093,33304202,26734892,79880247,71920426,42947632,62496012,45237957,37560164,36189527,41245325,5822202,62803858,75128745,21919959,56473732,48260151,65017137,7423788,60955663,41380093,17386996,26275734,7104732,57961282,38009615,34295794,78549759,37659250,45667668,8696647,82327024,79136082,16019925,75153252,44664587,90090964,28851716,29635537,1239555,82178706,70367851,24915585,69605283,57393458,4069912,5640302,64602895,84002370,97281847,36808486,55850790,99224392,13862149,57359924,82779622,78884452,31727379,77694700,4095116,97561745,23194618,11365791,55960386,81805959,3880712,97011160,7011964,49598724,61728685,52060076,36580610,30998561,4806458,23110625,30395570,78785507,56515456,92787493,9175338,45617087,60106217,16099750,71083565,54427233,17016156,73235980,16387575,61859581,94911072,17058722,57020481,21533347,92692978,3233569,26397786,81855445,4985896,42307449,64055960,55602660,17365188,68939068,48395186,45990383,47708850,30653863,32151165,13468268,13470059,53393358,5073754,71469330,65851721,3487592,15064655,54014062,32161669,97940276,22435353,7685448,44847298,23740167,26102057,7182642,52293995,1936762,2891150,55143724,94076128,72793444,6521313,84166196,33123618,67227442,19898053,86118021,77377183,10453030,13348726,69848388,23134715,24591705,33935899,63015256,76825057,69697787,51426311,88444207,55615885,74452589,8115266,94360702,20642888,44627776,75820087,65454636,4091162,60176618,92541302,68875490,27041967,65081429,69255765,45995325,53084256,72358170,29401781,19891772,88698958,38365584,42199455,247198,22129328,30694952,33336148,42692881,6819644,48673079,83155442,73109997,176257,85695762,7300047,66045587,49597667,22997281,15948937,48658605,9259676,77301523,9860195,47298834,39847321,90004325,17894977,81274566,70420215,22108665,53632373,93566986,40677414,9398733,50007421,99226875,62357986,35512853,67031644,94595668,59981773,17857111,47887470,12024238,68694897,92033260,51715482,93359396,3773993,8913721,61815223,31491938,82532312,45306070,29932657,83948335,51507425,77300457,6157724,73021291,43501211,9860968,62115552,51149804,23432750,63506281,47893286,91281584,92867155,89217461,17146629,21001913,62428472,87160386,71316369,53842979,86798033,92530431,83747892,7066775,51466049,86242799,54232247,75135448,48675329,24314885,13819358,55669657,3633375,95251277,89804152,36139942,74614639,68128438,10264691,11161731,12303248,18001617,36135,45381876,88904910,60393039,37501808,36845587,32058615,28787861,14349098,61928316,70782102,99729357,85711894,72278539 +37183543,24619760,29994197,89214616,92033260,5267545,45306070,88653118,99690194,17764950,9188443,91664334,90090964,9886593,80316608,68644627,30569392,57803235,89078848,90310261,81853704,72777973,35092039,76671482,78602717,69641513,8807940,36808486,96726697,3487592,59177718,29029316,18466635,47738185,58224549,21993752,34493392,38061439,96735716,60430369,98371444,44983451,51047803,83789391,36753250,54663246,82979980,22942635,57961282,33249630,68204242,4119747,526217,45848907,62936963,83150534,84904436,70036438,24058273,27665211,66250369,36312813,36468541,91802888,29819940,55753905,70420215,55615885,30653863,16097038,45428665,43501211,14947650,8099994,40677414,62740044,86001008,10358899,20568363,15163258,99549373,33797252,75543508,4064751,72274002,6221471,72373496,83155442,7300047,55602660,58749,61263068,99965001,97281847,65715134,96193415,168541,23194618,17539625,39847321,78589145,34236719,8733068,44889423,93933709,874791,4668450,36812683,39553046,20002147,72732205,28796059,6808825,16595436,5832946,22450468,8115266,3233084,50702367,59981773,45919976,62051033,56531125,99917599,68000591,30218878,67451935,56424103,55850790,30998561,11543098,80555751,38009615,32590267,99524975,18699206,90272749,95957797,96852321,9257405,67474219,44627776,17037369,61271144,75777973,29188588,20885148,22129328,92215320,22435353,415901,73392814,89804152,7517032,63015256,63967300,14326617,13173644,35512853,55189057,42644903,59371804,76434144,669105,16445503,15064655,98462867,12024238,90654836,36135,20765474,99515901,51588015,62496012,72725103,94711842,67793644,45617087,56515456,38022834,36780454,74614639,32161669,34044787,2208785,1197320,48893685,61728685,66271566,61815223,21001913,98948034,45407418,16387575,18783702,31161687,49271185,11161731,569864,72019362,85571389,66045587,44481640,98739783,16019925,33123618,19376156,45996863,2891150,77312810,54762643,508198,73124510,40356877,53802686,1788101,32699744,26392416,92692978,72614359,78909561,20486294,71333116,41245325,44550764,53842979,48260151,4204661,31904591,38658347,65454636,73031054,26139110,41092102,97379790,70372191,19939935,65338021,7104732,43376279,87720882,7502255,44177011,33336148,30764367,70004753,4806458,26292919,16567550,31491938,6819644,89046466,36505482,10366309,23848565,2204165,69848388,45794415,11365791,74357852,4099191,8651647,52261574,3235882,2717150,76540481,45075471,29635537,77694700,42237907,73617245,48673079,6986898,70800879,29834512,9575954,57393458,97057187,88047921,83533741,14220886,85023028,41092172,78766358,72738685,56473732,66667729,14626618,40781449,7646095,17068582,11923835,10649306,93057697,47361209,66832478,48892201,99224392,30787683,37620363,99729357,66663942,76703609,82052050,6497830,42967683,68816413,96318094,12664567,14349098,77377183,1375023,55470718,9623492,50567636,74724075,7229550,55143724,17976208,78884452,69255765,32426752,77072625,24826575,36580610,12348118,43506672,57248122,85242963,5970607,59197747,86460488,99021067,48395186,9175338,20645197,17365188,92803766,15535065,69786756,176257,80246713,36930650,6505939,5111370,62430984,84293052,13862149,80014588,62115552,74110882,22766820,33553959,38365584,99125126,59501487,68694897,90004325,27185644,93515664,47213183,30811010,83948335,19457589,48774913,55960386,19486173,50188404,5073754,8055981,53547802,59405277,29401781,89699445,40027975,52204879,76170907,8696647,43357947,51590803,77301523,42199455,18833224,7011964,1250437,43045786,23134715,97940276,18663507,51472275,56461322,45237957,14093520,79191827,23569917,65275241,84349107,79880247,97011160,19272365,45790169,89637706,1431742,70541760,4515343,42073124,82427263,50668599,37739481,35456853,98943869,30395570,67281495,40534591,5822202,40197395,9603598,34698428,14731700,3509435,34497327,73222868,87598888,85116755,95395112,4069912,2917920,99971982,13470059,64602895,71920426,62452034,30463802,68875490,40781854,89217461,47298834,83368048,39201414,54606384,14045383,87710366,38256849,4508700,36685023,81855445,49641577,45381876,40152546,84840549,58180653,81898046,54427233,79136082,29959549,26102057,9829782,1936762,66137019,6157724,96420429,21070110,3183975,30771409,90457870,7685448,33628349,74441448,58208470,47090124,61982238,78218845,66611759,77187825,45995325,24733232,66428795,73168565,46851987,37957788,15075176,36139942,90061527,83210802,52613508,26734892,83651211,29932657,96906410,70596786,15015906,84127901,92998591,6153406,30139692,3088684,10453030,30163921,16405341,82779622,3880712,9398733,36396314,64848072,94911072,37166608,27041967,53648154,13819358,78785507,69697787,92530431,62232211,7182642,95251277,43933006,19101477,11757872,65271999,20681921,73021291,83269727,56153345,98648327,58751351,26063929,39801351,6793819,13348726,85117092,53084256,41380093,79922758,49236559,62803858,42782652,9058407,28550822,20642888,22879907,247198,33201905,82327024,1022677,21289531,91240048,77284619,16971929,86798033,99861373,63059024,82726008,60176618,78300864,82339363,35996293,77898274,55247455,15536795,21673260,10961421,68316156,45667668,62552524,49597667,24915585,66903004,6038457,18806856,10309525,89811711,63628376,62490109,76330843,50007421,77413012,24314885,2331773,93053405,27625735,64157906,71083565,8913721,17957593,37891451,92604458,71965942,63372756,3233569,7423788,62693428,72238278,81677380,30891921,12303248,55770687,92554120,97783876,95290172,16099750,72357096,54232247,74137926,22200378,93270084,71316369,98653983,35192533,61928316,23740167,65081429,91727510,83747892,38645117,94076128,92353856,76960303,77272628,17146629,44784505,48675329,67084351,40865610,34946859,91255408,34667286,63506281,25636669,61859143,7687278,57020481,15148031,59318837,79806380,33431960,37192445,8634541,53666583,59109400,18411915,19898053,61712234,72495719,64087743,68824981,18504197,22997281,51507425,95726235,12416768,16054533,4798568,98130363,92787493,17058722,1569515,99604946,3773993,30694952,97561745,86301513,61897582,36933038,80251430,61373987,65017137,7955293,46870723,16684829,65880522,75153252,92398073,824872,4434662,29466406,23110625,24168411,7919588,29065964,47792865,37435892,16380211,34295794,52060076,86022504,46540998,66322959,69355476,82178706,31733363,48658605,54014062,40984766,57658654,47887470,3294781,8729146,54058788,65074535,13468268,72278539,29510992,29516592,83302115,94090109,48360198,1204161,43152977,85711894,33237508,30366150,4985896,40686254,84684495,9599614,44060493,78549759,11581181,4095116,20122224,81774825,37501808,44348328,33895336,66116458,65047700,28928175,9860968,47708850,36189527,68702632,84187166,77787724,60581278,24953498,39171895,82532312,4199704,83133790,69579137,93562257,51426311,96709982,50806615,87160386,91957544,64098930,99658235,18131876,27187213,49328608,15902805,66885828,44844121,68102437,71300104,44842615,9860195,22721500,41481685,30543215,83083131,19891772,93566986,26776922,85695762,2607799,73235980,8791066,75407347,54199160,79738755,99226875,62357986,81172706,57241521,88130087,20867149,17894977,39986008,16424199,64055960,97641116,32151165,749283,34432810,8373289,49598724,72358170,112651,53632373,26744917,75229462,79657802,53393358,68041839,92692283,59910624,79821897,39373729,76315420,31126490,13231279,61859581,56484900,63152504,52293995,26664538,99333375,26275734,61741594,4091162,88698958,45990383,3633375,8128637,17727650,57359924,76825057,59614746,95581843,77620120,44846932,94516935,74743862,42307449,17385531,33935899,66319530,33699435,75052463,91938191,26493119,22108665,82897371,37280276,57240218,71469330,42692881,90013093,26397786,91831487,75128745,82886381,37659250,92541302,68110330,68128438,4787945,27325428,26114953,82651278,7066775,5482538,89419466,86543538,75135448,38008118,43322743,32250045,26998766,62428472,81274566,51715482,72793444,17016156,67030811,14723410,88251446,32274392,24591705,91281584,86242799,67124282,37675718,69136837,54987042,1239555,70367851,60393039,26507214,57163802,73109997,44479073,12571310,6111563,17857111,65038678,91141395,33304202,54517921,73786944,26863229,8129978,55669657,28734791,5640302,33565483,89996536,38510840,21533347,84406788,321665,71657078,75820087,92071990,14363867,92867155,44245960,94595668,88444207,37224844,86118021,56955985,79942022,38494874,44473167,34698463,25842078,70727211,74452589,91990218,75986488,77300457,10264691,17386996,78561158,48088883,61960400,86821229,94539824,6871053,38341669,65851721,21397057,6525948,61623915,9259676,79322415,60106217,51464002,27411561,60955663,22405120,30090481,36845587,31727379,32159704,20836893,15948937,44847298,20140249,81805959,61141874,42947632,44664587,18001617,68939068,44915235,10597197,4978543,32058615,93790285,95010552,60102965,45049308,54868730,99297164,69605283,51315460,28851716,93359396,67031644,51466049,90158785,89499542,59329511,84166196,67227442,71522968,84002370,53888755,21919959,49882705,22113133,47893286,94360702,67513640,70782102,28787861,37560164,23432750,33061250,62762857,88904910,17081350,6521313,51149804,41442762,54263880 +99297164,70541760,3294781,20642888,70596786,53084256,20486294,51047803,19486173,78589145,75543508,40152546,90310261,34698428,33304202,62496012,64848072,34493392,68816413,6521313,79806380,59177718,60430369,32161669,83083131,8115266,68644627,45667668,15535065,46870723,61741594,67793644,92541302,15536795,75777973,43376279,79136082,72357096,74357852,39201414,13468268,37620363,68000591,73124510,669105,14731700,30787683,874791,72278539,7687278,11923835,81853704,64055960,59614746,42307449,33336148,54427233,65275241,18131876,36753250,14045383,62430984,77620120,30395570,5640302,21533347,83651211,61859143,49641577,40356877,32159704,70800879,44842615,749283,65017137,19891772,7423788,22766820,12348118,3233084,92692978,9623492,2717150,26392416,66271566,75229462,7502255,87160386,94090109,62803858,3183975,89046466,66250369,72495719,96318094,54058788,63967300,10358899,8373289,55247455,26507214,23569917,56424103,99524975,9188443,53842979,15015906,28734791,20867149,38645117,23134715,55189057,40197395,40984766,67281495,44550764,77072625,73168565,7011964,62936963,88047921,46851987,68939068,57803235,168541,22997281,81855445,42782652,83789391,78602717,38658347,12416768,96420429,57359924,64087743,1375023,70004753,66832478,89637706,96906410,98943869,55143724,4119747,4204661,9886593,824872,99965001,16097038,59501487,45237957,17037369,4434662,98462867,52261574,68316156,99515901,79657802,22879907,29819940,14626618,44060493,21673260,55753905,94539824,6153406,45996863,16405341,8099994,37957788,14349098,26102057,71316369,80246713,85116755,86798033,53547802,21001913,7300047,58749,75128745,23848565,97057187,71469330,50007421,40027975,74614639,39847321,44481640,91831487,54987042,71522968,30366150,22405120,62740044,74441448,14093520,99861373,19939935,75153252,18001617,30764367,24953498,82897371,44844121,34236719,45790169,84684495,36580610,93053405,8128637,55615885,19101477,96726697,99226875,29029316,54606384,82178706,92353856,27411561,26139110,36505482,81898046,99549373,46540998,89214616,66885828,40677414,18699206,83533741,77413012,57241521,81677380,59109400,19272365,26063929,65338021,91664334,45049308,20836893,60106217,65715134,1197320,62357986,80316608,63372756,20122224,1204161,40781449,30891921,30463802,96193415,51715482,88653118,82339363,82532312,95957797,16567550,15064655,26114953,17727650,68824981,77312810,33628349,87720882,31727379,40686254,20765474,8913721,93515664,48260151,33895336,92803766,57248122,95290172,50702367,89078848,44245960,44784505,4515343,79738755,53802686,6793819,98648327,35092039,72732205,48893685,93057697,25842078,32274392,96709982,69641513,20002147,91255408,68128438,72019362,29510992,59371804,36812683,86460488,39171895,95395112,4064751,55850790,13470059,6986898,26664538,44889423,30139692,91938191,53632373,78218845,34698463,30694952,54263880,54199160,5970607,84904436,50806615,44983451,29834512,37224844,26734892,9860968,99917599,14220886,98130363,14947650,31126490,40865610,89804152,67030811,7229550,9603598,10264691,72274002,64157906,78884452,42073124,80555751,38494874,61897582,73617245,17539625,26292919,76825057,77284619,16387575,5482538,47361209,76540481,14326617,82726008,39801351,82779622,6808825,37192445,5267545,1431742,45428665,69355476,77377183,65047700,21070110,18466635,73031054,10309525,78766358,41245325,45794415,94911072,86543538,84840549,9860195,97940276,63059024,7646095,27625735,58751351,45407418,95581843,4985896,33237508,24168411,68694897,15163258,86301513,94516935,65038678,37659250,33797252,23432750,9259676,73786944,60581278,61712234,13862149,83210802,21289531,29466406,36396314,6505939,26863229,83133790,14723410,40534591,4798568,6871053,36845587,18783702,60102965,53888755,92787493,66116458,68204242,88130087,26275734,79821897,74110882,96735716,87710366,29188588,61859581,90013093,97561745,76170907,69786756,81172706,8791066,9398733,6221471,61623915,37560164,7517032,43501211,3233569,63015256,28550822,66322959,95726235,44177011,76330843,88251446,2208785,112651,24058273,17764950,62232211,8807940,99690194,54014062,74724075,65454636,84293052,32590267,57020481,56473732,33553959,4668450,48673079,44348328,94076128,97011160,29959549,75407347,84349107,49271185,39986008,38510840,43933006,415901,30163921,27665211,53666583,9058407,85242963,8634541,28851716,35996293,55470718,4787945,22129328,70420215,26397786,41442762,95010552,41092102,34432810,92604458,17058722,43045786,3235882,62428472,10961421,50668599,59318837,9829782,43322743,14363867,62693428,24619760,63152504,64098930,92554120,66428795,33201905,29994197,81805959,49236559,94360702,28928175,32151165,19376156,2607799,67451935,16054533,85711894,69579137,22200378,30653863,90090964,34497327,43152977,9175338,85695762,62452034,48774913,35192533,54663246,34946859,99021067,4099191,68102437,38256849,72358170,88904910,16595436,38061439,42644903,6819644,22450468,83150534,73392814,51590803,44846932,7104732,8696647,96852321,36685023,30811010,58208470,61960400,93566986,92867155,47708850,36933038,5832946,93933709,44473167,57240218,51588015,38365584,2204165,88698958,77272628,33123618,7919588,13231279,51464002,56515456,72793444,92692283,43506672,42199455,16971929,21993752,526217,50567636,20140249,75052463,66045587,51472275,76315420,37435892,33935899,29401781,4069912,11161731,48658605,47213183,36930650,47090124,81274566,33249630,56461322,31904591,6157724,98371444,78561158,8651647,67124282,76960303,83948335,65074535,33699435,31733363,17365188,72614359,13348726,3880712,20568363,39553046,17146629,10453030,12571310,15148031,11543098,87598888,18663507,11581181,4508700,93270084,56955985,91240048,76671482,17068582,44664587,98739783,37891451,69605283,76434144,31491938,61982238,41481685,91281584,176257,86022504,80251430,71920426,36780454,54868730,47887470,27325428,70367851,97783876,44479073,79880247,19457589,30998561,18504197,70727211,89699445,12024238,56531125,61815223,89996536,1250437,10597197,79922758,569864,69697787,24591705,52293995,36189527,77787724,45381876,65081429,20885148,38341669,6497830,58180653,67474219,41380093,49598724,26493119,98653983,79942022,16019925,7955293,36312813,9257405,53393358,77301523,99658235,22435353,61728685,84002370,5073754,92998591,62490109,61373987,59405277,4199704,27187213,23194618,68875490,69255765,38022834,30543215,17857111,67031644,69848388,7066775,52060076,82979980,51149804,50188404,45995325,74452589,92071990,17976208,68041839,62051033,74137926,36135,49328608,2891150,247198,91802888,79191827,32250045,72238278,29065964,82427263,84187166,18411915,93790285,64602895,22113133,86242799,59197747,62762857,71333116,89217461,59981773,8129978,33061250,42947632,86821229,61141874,66137019,86001008,76703609,66663942,71300104,68702632,17385531,65851721,80014588,34295794,62552524,57961282,16380211,53648154,33565483,67513640,3773993,17894977,66903004,20645197,77694700,97379790,78785507,89419466,47792865,78549759,1788101,19898053,61263068,12664567,42967683,22108665,73222868,1022677,44915235,92398073,7685448,57658654,91141395,27185644,71965942,11365791,40781854,93562257,17016156,62115552,72738685,30569392,37183543,90061527,30771409,58224549,82327024,91727510,83155442,5111370,3509435,61928316,48088883,86118021,90457870,99224392,45990383,89499542,47298834,9575954,88444207,48395186,85023028,90272749,85571389,45919976,82886381,36468541,16684829,29635537,66611759,48892201,45848907,72777973,56153345,13819358,24733232,75135448,32699744,65880522,3633375,37166608,26998766,4091162,15948937,73109997,73235980,12303248,92215320,6038457,74743862,47893286,39373729,321665,60955663,57163802,11757872,4978543,24826575,44847298,18833224,78909561,65271999,55960386,9599614,17957593,28796059,37739481,84127901,83302115,63628376,42692881,44627776,84166196,10649306,52613508,52204879,60393039,54517921,79322415,99729357,78300864,85117092,30090481,55770687,1569515,70036438,51315460,31161687,23110625,94595668,90654836,24314885,21919959,71657078,70372191,72725103,3487592,18806856,99971982,99125126,37501808,36808486,90004325,32058615,51426311,92033260,17081350,55602660,37280276,45617087,20681921,92530431,63506281,34044787,66667729,77300457,29516592,59910624,15902805,67227442,10366309,42237907,67084351,49597667,91957544,59329511,8733068,48360198,35512853,2917920,508198,71083565,1239555,54762643,7182642,57393458,77898274,69136837,8055981,35456853,82651278,16099750,22942635,8729146,54232247,43357947,3088684,33431960,6525948,66319530,29932657,16424199,51507425,16445503,77187825,37675718,91990218,49882705,6111563,93359396,99604946,61271144,1936762,94711842,83747892,45075471,28787861,51466049,75820087,98948034,81774825,26776922,47738185,48675329,26744917,99333375,4095116,27041967,17386996,82052050,34667286,5822202,30218878,23740167,83368048,60176618,21397057,24915585,83269727,73021291,36139942,89811711,55669657,70782102,38009615,38008118,75986488,84406788,45306070,56484900,41092172,32426752,22721500,68110330,90158785,72373496,15075176,2331773,97641116,4806458,95251277,13173644,25636669,97281847 +71920426,33628349,77413012,62740044,62496012,81677380,34493392,1250437,30787683,39986008,89811711,96735716,5970607,36505482,55470718,59177718,99729357,98462867,35092039,79136082,36933038,14947650,49236559,30569392,44842615,44889423,4119747,7955293,42782652,78602717,85116755,2607799,17037369,79657802,64848072,75986488,94090109,3487592,99524975,60955663,9575954,37435892,23194618,89699445,11161731,62762857,97011160,18783702,65017137,5073754,29819940,13468268,23848565,42947632,66832478,45848907,8791066,8696647,95581843,53547802,61859581,22942635,94595668,44177011,15015906,78589145,40197395,73617245,13231279,93053405,99861373,22721500,55753905,48673079,50668599,63967300,66137019,65271999,13173644,45306070,68041839,92692978,13470059,7229550,16019925,415901,48675329,34295794,92554120,15535065,45237957,54517921,54762643,61928316,91664334,44479073,65715134,34698463,81853704,65081429,9175338,71469330,8634541,52261574,41481685,9188443,88653118,47090124,18806856,50188404,68128438,12303248,45919976,40677414,17146629,14349098,63059024,47361209,77300457,9257405,1431742,77187825,91727510,36396314,17058722,74137926,26776922,26139110,4668450,37501808,11543098,43501211,58749,48260151,28928175,17068582,61373987,67124282,86022504,49328608,54014062,51315460,94516935,62115552,21993752,70036438,29834512,33123618,83155442,78300864,64087743,508198,68816413,80246713,23569917,50702367,3294781,86798033,30764367,5111370,4204661,72357096,77620120,95726235,53888755,45996863,24591705,29959549,66663942,33797252,49271185,42307449,30218878,669105,6871053,99549373,61982238,92787493,26392416,72777973,36580610,96726697,78218845,53393358,7182642,29466406,48360198,57163802,20885148,3183975,65880522,58180653,28851716,65275241,77694700,51466049,14093520,20002147,78766358,9886593,92803766,77284619,73222868,70541760,25636669,89214616,66428795,98943869,4806458,21673260,79322415,10366309,63506281,83789391,56424103,3633375,31491938,38008118,92215320,68000591,24826575,68204242,84127901,76170907,93057697,38022834,63372756,37183543,45407418,38061439,33237508,63152504,4064751,38645117,30163921,72793444,36753250,72278539,39847321,27665211,89637706,57803235,19272365,9398733,68824981,32161669,27325428,56515456,98948034,99515901,32250045,20765474,21070110,1788101,82178706,44473167,38494874,99690194,78909561,13819358,57359924,36780454,38256849,75135448,91255408,20486294,62430984,16380211,82532312,62803858,44983451,29994197,749283,13348726,37675718,53842979,11923835,55602660,46851987,70367851,77072625,59197747,9259676,17957593,7687278,72732205,19376156,24619760,59981773,6505939,54199160,76540481,3235882,6221471,48892201,86001008,93515664,16099750,95290172,29635537,94539824,92604458,37659250,55143724,29065964,22766820,91802888,14326617,42692881,41380093,6986898,39801351,112651,69579137,72373496,61623915,30139692,84904436,49598724,7011964,51590803,7300047,67031644,59614746,26493119,40686254,55850790,65047700,57393458,8913721,4434662,61263068,569864,82979980,33553959,15064655,36468541,82327024,92867155,99125126,3233569,90310261,48088883,26998766,15163258,19101477,22435353,66322959,82339363,69697787,28550822,40865610,29188588,83210802,98371444,68939068,55669657,43045786,60102965,8129978,68644627,26102057,34497327,6808825,40781449,83269727,17894977,35996293,4099191,8099994,33336148,20867149,55189057,6111563,33201905,68702632,58224549,5267545,96709982,4095116,19898053,27411561,30653863,68102437,68875490,8115266,4515343,18001617,76825057,39201414,63628376,62232211,42073124,57658654,88251446,76671482,65851721,95395112,39171895,67227442,28796059,8651647,74110882,83133790,86118021,20642888,45990383,44664587,75407347,90457870,66250369,15075176,43933006,85571389,47738185,79821897,35512853,96193415,51507425,82726008,51472275,9860968,44847298,1022677,77377183,30771409,7685448,29029316,92692283,82651278,68110330,69136837,80251430,89996536,9599614,57248122,44550764,83302115,92071990,38365584,69255765,53632373,78884452,70596786,11757872,36845587,83368048,55960386,67451935,9623492,96852321,52613508,92541302,4069912,66116458,83533741,99917599,80014588,51464002,66885828,92353856,62936963,49641577,3233084,29510992,42237907,47887470,70727211,6793819,93933709,70800879,45995325,88047921,50567636,60430369,92033260,88698958,17081350,83150534,42644903,9058407,40152546,14220886,36312813,35456853,10453030,70372191,87710366,2204165,55247455,72274002,32590267,47213183,48893685,53666583,59371804,38341669,65454636,70004753,21533347,34698428,64157906,52293995,17539625,44245960,64055960,18504197,17857111,84684495,9603598,56484900,76960303,247198,37620363,71083565,51047803,99965001,74357852,97281847,83747892,70420215,93562257,36930650,7104732,6153406,59318837,30395570,2717150,86242799,24058273,15902805,38510840,74724075,22405120,44784505,66611759,22200378,86301513,15148031,26114953,76330843,95957797,97940276,27625735,78561158,44060493,42967683,78785507,33249630,38658347,89419466,63015256,81274566,87720882,82779622,97057187,81172706,45381876,67084351,76434144,52060076,54427233,50007421,54987042,71333116,6157724,321665,79922758,74441448,71300104,99658235,17764950,53802686,62693428,4978543,18411915,47792865,16054533,8807940,72358170,44481640,61815223,90004325,57240218,62452034,67281495,26063929,12348118,66271566,7423788,32159704,53084256,98130363,85711894,12416768,22129328,2208785,43376279,97561745,62428472,72725103,40984766,30090481,43322743,23110625,10264691,4508700,73786944,19457589,88444207,82427263,88130087,84349107,3880712,7919588,72614359,168541,99333375,73031054,99226875,99224392,97783876,1197320,19939935,45075471,4091162,73392814,30998561,44348328,31126490,44844121,56153345,74614639,36135,83651211,4798568,77787724,41245325,75543508,8373289,30891921,26292919,23432750,33431960,12664567,33565483,4199704,67030811,6525948,60581278,69605283,9829782,79880247,50806615,13862149,26507214,67793644,31727379,72738685,49597667,1204161,8128637,66903004,94076128,18466635,91957544,14731700,67474219,62552524,10961421,17727650,10358899,95010552,10309525,86460488,61859143,87160386,90090964,39553046,30463802,6497830,42199455,72019362,41092102,61141874,3509435,56955985,82052050,69641513,93270084,6521313,59501487,8055981,37957788,24953498,20140249,77272628,58751351,71657078,29401781,18833224,36189527,5822202,90013093,62490109,81898046,33304202,24915585,65038678,33699435,30694952,64098930,37280276,526217,75128745,5640302,35192533,18131876,84166196,30543215,28734791,86543538,33895336,31904591,24168411,20681921,88904910,55615885,47708850,32274392,72495719,16445503,15536795,83083131,45428665,92530431,98648327,91831487,34946859,39373729,24733232,16971929,96906410,22108665,90061527,16595436,73124510,74743862,73109997,92398073,62051033,90272749,60176618,29932657,7502255,57961282,85695762,32699744,14045383,37739481,19891772,21001913,22997281,59329511,44627776,91240048,75052463,53648154,75229462,38009615,51426311,79942022,68316156,89217461,27041967,40781854,51149804,95251277,37192445,96420429,19486173,33935899,2891150,26744917,26734892,7646095,31161687,37166608,66667729,20568363,36808486,59910624,98739783,56531125,97641116,73168565,98653983,32151165,21397057,85242963,89804152,51715482,47298834,89046466,17976208,72238278,1239555,80555751,37891451,69786756,73235980,21919959,18663507,56473732,82897371,89078848,56461322,9860195,99971982,4985896,83948335,17386996,48774913,58208470,57241521,54232247,46540998,85117092,44915235,70782102,34044787,2331773,2917920,87598888,48395186,71522968,93359396,84002370,40027975,45667668,41092172,30366150,6038457,7066775,99604946,80316608,66319530,61960400,99297164,93566986,78549759,57020481,29516592,60106217,7517032,84840549,8733068,75820087,176257,874791,26664538,14626618,96318094,33061250,26275734,82886381,61897582,12024238,45617087,32426752,86821229,36812683,71316369,20122224,45790169,69355476,61728685,30811010,75153252,16097038,3088684,49882705,48658605,40534591,65338021,17385531,54606384,69848388,20645197,8729146,43506672,10649306,59109400,54868730,1569515,3773993,34432810,14363867,36139942,44846932,1936762,91281584,51588015,55770687,61271144,34667286,77312810,90654836,5832946,93790285,23134715,84406788,34236719,91141395,17365188,16387575,22450468,77301523,15948937,77898274,18699206,21289531,40356877,26863229,76315420,36685023,43152977,45794415,75777973,81805959,24314885,10597197,84293052,46870723,81774825,65074535,11365791,32058615,31733363,84187166,20836893,16567550,66045587,76703609,43357947,68694897,12571310,17016156,5482538,4787945,64602895,22113133,16405341,45049308,60393039,92998591,14723410,73021291,54663246,94711842,79806380,23740167,94911072,28787861,99021067,54263880,91990218,16684829,67513640,47893286,824872,27185644,22879907,89499542,91938191,61741594,52204879,41442762,11581181,81855445,59405277,79191827,90158785,71965942,16424199,26397786,74452589,85023028,1375023,54058788,37224844,97379790,6819644,61712234,25842078,62357986,79738755,27187213,94360702,37560164 +93933709,70004753,10366309,5073754,68041839,99658235,64602895,88047921,92787493,19891772,9623492,71083565,63059024,72373496,38022834,42692881,61271144,36685023,55247455,69848388,89811711,20140249,16424199,75986488,36312813,67227442,47738185,57163802,15148031,5111370,62430984,10309525,89214616,48360198,30787683,78766358,54014062,72274002,32590267,29188588,42967683,33237508,749283,20122224,83789391,56153345,34946859,31904591,33201905,87720882,75153252,37739481,96193415,72777973,168541,59371804,37501808,37620363,81677380,29466406,90272749,68644627,74110882,4668450,40197395,97783876,34493392,89419466,34295794,63152504,77187825,23569917,95010552,48260151,85571389,77787724,94516935,27665211,36780454,81853704,15535065,38061439,87598888,17068582,44889423,50188404,64848072,44784505,70800879,30764367,45428665,36753250,77272628,54762643,6793819,4099191,53648154,73222868,43045786,63506281,30395570,66250369,73031054,77312810,88904910,60102965,2204165,95395112,71657078,16099750,55753905,30139692,79657802,28851716,65715134,7517032,54427233,22942635,92398073,92998591,22108665,526217,77284619,17386996,24058273,78602717,3088684,42782652,22879907,14326617,65074535,35092039,66137019,98371444,25842078,91281584,30366150,15075176,18466635,95290172,99861373,32151165,83651211,33797252,59109400,9575954,93359396,34698463,62452034,55470718,70036438,95957797,48892201,71920426,96726697,99125126,2917920,82886381,43501211,49328608,321665,20642888,72725103,19101477,27625735,63015256,40865610,53547802,79821897,16445503,99524975,20486294,61859581,44473167,7955293,75543508,55770687,13862149,49641577,80251430,63967300,43357947,4095116,83269727,62496012,88653118,48675329,51588015,98739783,83210802,13468268,47792865,19898053,24733232,16567550,93790285,93515664,71522968,17764950,54663246,49236559,83150534,74357852,25636669,33304202,48658605,7646095,66667729,67084351,10358899,27187213,6525948,66832478,37183543,5482538,44245960,57658654,66611759,86001008,45617087,26493119,74441448,8129978,24591705,9603598,36505482,89637706,60581278,14093520,4798568,44177011,76170907,29834512,824872,44348328,57241521,12664567,82427263,72732205,72358170,93562257,61728685,73235980,32161669,42307449,48893685,79136082,24314885,97940276,4508700,54199160,6808825,80014588,45075471,17081350,17016156,508198,5832946,77072625,40677414,43933006,60176618,32426752,8696647,47298834,13231279,8791066,3509435,6505939,32058615,86022504,669105,19376156,72614359,16405341,20885148,95581843,40027975,10453030,59177718,59197747,98130363,44479073,10597197,38008118,13173644,47361209,92803766,14626618,74743862,30090481,28796059,96852321,66903004,38658347,17957593,50668599,4119747,3233084,21533347,53888755,39373729,27041967,43322743,96735716,44983451,16595436,3487592,61960400,8128637,3294781,6871053,90654836,97057187,43376279,69605283,86821229,63628376,30543215,19939935,9188443,61373987,6038457,45407418,8807940,29819940,10961421,61897582,28928175,62936963,70367851,56484900,41481685,16684829,18833224,79942022,8733068,24915585,84684495,91990218,68875490,82726008,61815223,56955985,32159704,9398733,67124282,7687278,44481640,77300457,18663507,54263880,45667668,68824981,6153406,47887470,31126490,1250437,29994197,18001617,37659250,58208470,68702632,11923835,56515456,37435892,53802686,64087743,26998766,79322415,23194618,49271185,74724075,88698958,26114953,76315420,415901,6521313,3633375,21993752,29932657,68816413,66319530,70372191,4064751,16387575,75777973,66663942,35456853,16380211,92604458,22721500,50702367,54606384,31161687,874791,7300047,11161731,27185644,90310261,45237957,74452589,68204242,68110330,12416768,30463802,16019925,80555751,33565483,60955663,96709982,83368048,61982238,59910624,3880712,65454636,78561158,84904436,99917599,75135448,86543538,69355476,57020481,69136837,82651278,51590803,27411561,41092172,13819358,30771409,1431742,49598724,6986898,78300864,54058788,72019362,62051033,92554120,69579137,62693428,7502255,58224549,39201414,87710366,50806615,73021291,8099994,17727650,73168565,14349098,56531125,55143724,3235882,70596786,91831487,30811010,38009615,36812683,78589145,2208785,78909561,81898046,39801351,89078848,33699435,30218878,67281495,11757872,65271999,69641513,1569515,99965001,26776922,71333116,61928316,33431960,83533741,67474219,35192533,26292919,18699206,1375023,74137926,39986008,23432750,67513640,51472275,94911072,64157906,14723410,91255408,88251446,9599614,66322959,88444207,45790169,14947650,30653863,67031644,71316369,7919588,77620120,52613508,99729357,18131876,6221471,54987042,51047803,24168411,66271566,72357096,65038678,7229550,39171895,6111563,7685448,76960303,92692978,33553959,86118021,30569392,2717150,9829782,93057697,39553046,247198,60393039,90061527,12348118,73617245,65338021,9259676,22200378,40781854,37560164,16097038,20002147,37166608,1239555,8651647,87160386,66428795,90090964,22435353,67030811,91957544,92353856,51464002,78884452,1197320,29401781,68939068,97011160,20765474,91802888,43506672,52261574,38365584,72793444,20568363,57248122,82178706,30163921,22129328,50007421,18806856,46540998,36396314,94090109,9058407,65851721,65047700,11581181,73786944,31491938,77301523,34497327,99021067,89996536,74614639,36139942,48088883,31733363,9257405,84002370,99297164,4069912,17365188,84187166,72238278,9886593,61741594,94076128,82897371,84293052,40534591,84840549,95251277,44915235,77694700,51315460,24826575,26744917,21397057,66885828,86242799,44664587,88130087,99333375,92033260,70782102,57393458,76434144,41092102,45848907,569864,68102437,55189057,11365791,57803235,84127901,59318837,44060493,62232211,47708850,34236719,30998561,85116755,80316608,77413012,97641116,22113133,17037369,17857111,9175338,99549373,59614746,83302115,70541760,51507425,61263068,34698428,26275734,79880247,11543098,62740044,60430369,5267545,67793644,35996293,34432810,7011964,53842979,36933038,82979980,78785507,20681921,86798033,40152546,16054533,33061250,33628349,18504197,66045587,9860968,89217461,27325428,44844121,52060076,50567636,8913721,45306070,40781449,94539824,20836893,98948034,14220886,38494874,82052050,19486173,41442762,91141395,91240048,34667286,79738755,99604946,73124510,78549759,85242963,40356877,14045383,81855445,85117092,45995325,42237907,26734892,45919976,36468541,56461322,29065964,48673079,79191827,22450468,72738685,56473732,91938191,95726235,15163258,10649306,86460488,29516592,4985896,38645117,82779622,22997281,43152977,47893286,20645197,59501487,62357986,2607799,90004325,37192445,47213183,36930650,29635537,84406788,62803858,26102057,28734791,97281847,81805959,12571310,52204879,92215320,38510840,30891921,29510992,8055981,57961282,59981773,45794415,82327024,81774825,1022677,93270084,76703609,92071990,99226875,83155442,46870723,75820087,96420429,44627776,51715482,5970607,37891451,38341669,37957788,90457870,61712234,98462867,93053405,76330843,13348726,3183975,7423788,75128745,23848565,53084256,15536795,67451935,61141874,82339363,59329511,16971929,81274566,29029316,22405120,97561745,98653983,76671482,24953498,30694952,3233569,37675718,36135,65275241,33935899,55850790,48774913,77377183,70420215,65880522,68128438,71300104,26863229,55602660,6497830,97379790,2891150,73392814,69786756,89499542,68000591,22766820,84349107,19272365,37224844,59405277,61859143,41245325,71469330,58751351,62762857,89804152,23110625,17058722,14731700,54868730,54232247,29959549,26139110,75052463,6819644,32250045,18783702,1788101,8634541,38256849,42644903,1204161,4091162,17146629,89699445,44847298,35512853,4787945,85695762,36808486,94711842,3773993,34044787,15015906,8373289,54517921,69697787,40686254,78218845,56424103,32699744,86301513,83133790,5822202,96906410,55669657,31727379,58180653,14363867,26392416,98648327,57359924,85023028,70727211,65017137,7182642,21289531,1936762,64098930,33123618,96318094,53632373,66116458,92692283,68694897,26507214,19457589,51426311,71965942,23134715,92867155,72495719,99224392,84166196,90013093,9860195,8115266,42947632,53393358,13470059,53666583,49882705,15902805,93566986,33336148,26397786,81172706,94360702,7104732,45381876,21001913,90158785,99690194,7066775,47090124,18411915,21673260,79806380,5640302,62552524,26063929,33249630,15948937,63372756,20867149,17385531,46851987,21919959,83948335,91727510,92541302,69255765,45049308,21070110,17894977,45996863,79922758,2331773,62115552,91664334,75407347,15064655,4199704,39847321,40984766,76540481,45990383,36580610,99515901,36189527,62490109,37280276,28550822,60106217,4806458,98943869,44842615,89046466,28787861,83747892,42199455,42073124,49597667,80246713,62428472,12303248,41380093,76825057,51149804,52293995,4515343,55960386,33895336,8729146,36845587,82532312,48395186,17976208,61623915,44550764,23740167,17539625,92530431,72278539,73109997,24619760,112651,55615885,4204661,58749,68316156,94595668,57240218,83083131,99971982,4978543,85711894,26664538,64055960,51466049,176257,75229462,10264691,65081429,77898274,12024238,4434662,44846932,32274392,6157724 +96193415,58749,37891451,98371444,18699206,64087743,95957797,33249630,50188404,51047803,30218878,5970607,10358899,97641116,81274566,35996293,49328608,47090124,44889423,83651211,68204242,80316608,20002147,31904591,44245960,26998766,62496012,50702367,40197395,29834512,15064655,74743862,66137019,31161687,88047921,46851987,66611759,77413012,24733232,4515343,7502255,61623915,44550764,1431742,74357852,14093520,247198,45919976,48673079,78589145,67281495,61859143,82327024,42199455,28734791,70036438,86022504,48260151,99524975,27325428,86301513,35092039,70727211,57020481,6505939,33553959,30463802,59177718,43376279,82726008,8696647,62452034,40356877,45996863,77620120,19101477,38494874,60430369,15535065,56424103,73392814,84840549,71333116,79191827,83210802,39847321,29188588,76540481,89214616,4787945,86543538,65454636,55143724,84684495,53648154,8634541,15015906,26392416,38061439,50668599,55753905,44473167,61141874,18783702,79657802,73168565,4434662,57240218,83150534,81677380,57359924,56531125,55669657,27665211,34698428,1788101,30569392,79821897,53842979,55470718,77312810,12348118,22108665,17727650,4668450,22435353,78549759,69355476,81898046,28928175,40152546,62803858,72777973,45407418,43501211,4099191,40686254,42782652,92803766,94595668,33628349,26507214,44177011,56473732,41092172,76330843,87160386,8913721,13231279,71300104,96726697,32699744,32250045,22879907,76960303,83302115,9188443,89078848,76434144,65715134,92398073,29994197,19486173,3880712,35192533,46870723,98739783,61897582,51464002,75153252,45990383,75543508,54987042,16099750,70800879,39801351,34295794,24619760,62740044,8651647,13173644,65047700,51426311,53666583,70596786,61960400,99333375,5111370,20486294,20885148,76671482,98948034,47361209,28550822,874791,36753250,77377183,93566986,50806615,81805959,99965001,39171895,59614746,43357947,63506281,82779622,26063929,42967683,19272365,75407347,9623492,4119747,77072625,48360198,92604458,66116458,36580610,8373289,99604946,3633375,65271999,89811711,55247455,85711894,37957788,42692881,57393458,88653118,86118021,37659250,62051033,94090109,96852321,45306070,77284619,6808825,50007421,25636669,65038678,24915585,62490109,18001617,29819940,79136082,51590803,57241521,70004753,36312813,43506672,86821229,59910624,92530431,90457870,66319530,21993752,71920426,81855445,56515456,62936963,63967300,97783876,36139942,20836893,112651,91727510,60581278,26664538,26139110,6793819,99861373,89046466,73617245,23194618,7104732,53888755,10961421,37183543,58751351,68644627,21533347,99125126,57961282,98648327,8791066,51588015,40677414,33304202,53084256,52261574,6157724,36930650,9603598,43933006,83789391,52293995,99549373,68939068,36780454,26102057,48892201,74110882,30395570,2917920,93515664,10264691,7011964,22200378,53632373,3294781,96906410,83083131,64848072,14349098,2717150,15948937,29959549,44983451,69605283,47708850,57163802,54663246,18411915,55189057,20642888,12024238,89996536,69579137,32159704,67227442,21070110,80251430,91802888,669105,92215320,49641577,32161669,415901,18806856,55602660,66903004,97561745,82886381,3233084,84293052,56484900,68816413,48774913,30764367,44481640,24591705,62693428,51472275,91831487,33237508,9860195,17016156,92692283,24058273,1569515,99515901,61263068,6153406,77694700,43322743,42644903,39373729,69255765,17764950,4064751,47792865,12571310,41481685,89804152,17058722,37620363,38341669,44842615,61373987,6221471,55960386,41245325,33201905,21001913,63152504,73031054,93057697,45848907,58180653,22942635,84349107,85116755,91957544,11365791,11161731,17539625,95395112,1204161,83269727,39201414,44060493,68102437,91664334,72274002,13348726,75229462,30163921,9886593,77300457,57658654,8099994,31491938,47213183,89499542,94711842,51507425,54014062,36808486,28851716,65081429,84166196,6871053,68824981,68316156,27041967,26776922,14220886,83368048,99658235,68000591,51315460,16380211,44846932,80555751,95290172,35456853,78218845,7685448,9599614,80246713,33123618,16019925,45428665,74137926,20568363,14045383,33565483,79922758,89637706,32274392,83133790,69641513,81172706,21289531,66045587,64098930,91240048,89419466,70372191,99690194,36812683,9860968,60102965,67793644,70541760,99021067,508198,64055960,34497327,68041839,17146629,8129978,45617087,69136837,50567636,15536795,84187166,72614359,9829782,15148031,74724075,83533741,8115266,81853704,7687278,90158785,38008118,26734892,70782102,83155442,59981773,63372756,98462867,4806458,56461322,42947632,57803235,19939935,47738185,30694952,49597667,67084351,60393039,54868730,88698958,16971929,79880247,29029316,66428795,53547802,37166608,62428472,36468541,24826575,4069912,14731700,29466406,92071990,824872,66271566,7919588,37192445,3773993,98130363,41092102,82178706,19898053,66885828,82052050,69848388,26493119,17081350,73124510,88444207,96709982,31126490,16054533,13468268,40781449,74614639,9257405,36685023,92692978,1022677,4204661,11581181,22450468,55850790,52613508,29510992,38645117,75986488,75777973,68128438,93790285,48675329,91281584,77787724,61741594,45790169,27187213,18131876,71083565,23569917,90272749,72725103,16387575,74441448,56153345,23848565,94911072,79806380,36845587,66667729,13470059,8807940,7300047,69786756,77272628,72495719,19891772,59318837,4091162,14947650,99297164,7646095,28796059,95581843,34493392,93270084,29401781,23110625,61982238,5832946,36505482,23134715,93053405,34236719,99224392,40781854,96735716,71316369,45995325,71657078,29065964,92554120,94516935,59197747,66250369,60106217,85571389,16405341,37675718,70420215,39986008,22405120,59501487,6986898,11757872,82339363,94360702,54517921,176257,93359396,85242963,22766820,92033260,91255408,9175338,38365584,14626618,61712234,41442762,33895336,66322959,90654836,67031644,31733363,79738755,21673260,72019362,46540998,168541,49271185,20140249,36135,63059024,59109400,33699435,22997281,92787493,79942022,93933709,77898274,53802686,569864,41380093,37224844,67030811,33797252,87720882,60955663,72373496,45237957,59371804,7066775,526217,55770687,15075176,62232211,26275734,20681921,749283,91938191,70367851,44627776,90004325,32590267,76703609,37280276,36189527,30139692,72793444,17857111,89217461,93562257,67451935,10366309,98653983,63628376,53393358,62552524,20765474,73222868,7423788,55615885,19457589,38009615,49598724,10597197,44844121,75135448,37560164,80014588,94076128,98943869,23432750,35512853,16567550,20122224,6525948,40534591,62430984,86798033,96318094,49236559,6819644,30771409,15902805,52204879,17037369,16595436,88251446,66832478,12303248,61728685,67124282,54606384,18663507,5822202,77301523,82979980,84406788,37739481,29932657,12416768,82651278,65275241,17068582,81774825,84904436,90310261,99917599,26114953,90090964,34698463,30891921,85117092,30998561,51466049,72357096,44479073,68702632,18504197,9398733,44348328,3183975,36396314,78300864,6038457,76170907,97281847,2204165,16097038,77187825,4985896,8128637,75052463,95726235,9575954,62357986,48395186,43045786,11543098,321665,52060076,54232247,61271144,61815223,18466635,22129328,65017137,72278539,71965942,91141395,7229550,42307449,6497830,65851721,22721500,2331773,78766358,37435892,5267545,72238278,30366150,75820087,99729357,90061527,72732205,38256849,10309525,2891150,71522968,14723410,54199160,54762643,44784505,61859581,57248122,30543215,20867149,6111563,4508700,5073754,92867155,34667286,2607799,34432810,86001008,92998591,68694897,73786944,59405277,48893685,4095116,31727379,21919959,3235882,83948335,15163258,78909561,42073124,17385531,99226875,61928316,56955985,38022834,37501808,26863229,76315420,27625735,10649306,84002370,12664567,48658605,85023028,82427263,19376156,65074535,48088883,64157906,87710366,45667668,6521313,17957593,47298834,2208785,84127901,90013093,73235980,30653863,1197320,4798568,76825057,26292919,9259676,32151165,78561158,43152977,33431960,8733068,92353856,95010552,58208470,71469330,27185644,99971982,62762857,45794415,3509435,13862149,8055981,40027975,40865610,54058788,96420429,3233569,72358170,24168411,34044787,13819358,42237907,86460488,16445503,51715482,17894977,38658347,1239555,3487592,7955293,23740167,97940276,65880522,67513640,28787861,33935899,39553046,3088684,65338021,30787683,5482538,30811010,78602717,60176618,94539824,11923835,24953498,40984766,66663942,82532312,68110330,69697787,10453030,26744917,44847298,75128745,14363867,33336148,7182642,47893286,44664587,33061250,14326617,95251277,4199704,29516592,1250437,59329511,72738685,83747892,25842078,78785507,86242799,64602895,67474219,5640302,30090481,27411561,51149804,1375023,73021291,82897371,54427233,45075471,32058615,92541302,79322415,74452589,78884452,97011160,1936762,17365188,62115552,34946859,9058407,7517032,68875490,87598888,73109997,36933038,29635537,17386996,91990218,49882705,16424199,47887470,32426752,17976208,58224549,22113133,24314885,16684829,89699445,38510840,88130087,63015256,8729146,20645197,97379790,26397786,88904910,21397057,85695762,54263880,45049308,97057187,4978543,45381876,44915235,18833224 +10358899,99021067,33797252,15535065,54014062,62490109,92803766,30463802,26292919,24058273,19376156,95957797,62452034,84840549,38658347,31904591,89078848,37659250,40197395,55770687,33304202,96193415,93933709,44245960,18806856,48360198,62740044,72777973,34946859,63967300,92787493,62496012,5267545,9599614,27665211,41245325,66045587,43501211,61741594,2717150,78602717,66832478,89637706,46851987,14349098,48892201,26744917,14220886,36685023,28928175,11923835,23569917,96726697,45306070,3088684,17081350,49271185,94516935,50007421,14731700,17068582,22129328,27411561,38365584,70004753,99549373,29834512,67793644,40677414,33201905,72373496,4508700,44889423,3487592,60581278,8733068,72274002,77284619,55470718,9398733,44847298,51472275,569864,6153406,4064751,65271999,5111370,24953498,16097038,67281495,247198,64098930,20568363,78589145,88653118,5073754,80251430,32590267,65715134,92604458,22942635,12664567,20885148,99658235,69355476,36753250,80014588,72738685,8099994,58751351,68824981,36468541,35092039,36580610,31491938,29466406,47361209,61815223,28796059,77787724,1431742,59981773,38061439,92692978,77312810,61897582,80316608,17037369,33431960,51590803,52060076,42782652,33628349,78909561,73617245,20486294,82339363,34698463,22108665,1788101,71083565,40534591,38256849,19898053,8128637,59614746,61373987,28851716,20642888,17365188,40152546,77694700,58749,57020481,81853704,77272628,19939935,61712234,37620363,7687278,42967683,80555751,81898046,72732205,17016156,14045383,20002147,83150534,60430369,66250369,56473732,77301523,91957544,98739783,48893685,56515456,22435353,84293052,61960400,23194618,36312813,61982238,47792865,50702367,30366150,29994197,24591705,69786756,68816413,5970607,98462867,44473167,38022834,82178706,76330843,10366309,71300104,21001913,16387575,55850790,83789391,63059024,2917920,36808486,13862149,53084256,168541,34044787,89214616,73235980,70367851,16595436,88698958,22997281,321665,68000591,40027975,68694897,54199160,60106217,98371444,33237508,91802888,72357096,79657802,39801351,49598724,35456853,21070110,38645117,87720882,37183543,9886593,68644627,4099191,35192533,19891772,44844121,16424199,4434662,83651211,68102437,84904436,33699435,1250437,91727510,36812683,90310261,6819644,53648154,67030811,79136082,4668450,3294781,30569392,74110882,63152504,54427233,75128745,43322743,34497327,70420215,81677380,59501487,4095116,90090964,78785507,72725103,7919588,57240218,47090124,70596786,3880712,8129978,47213183,27325428,61859143,69136837,65338021,43506672,18783702,6808825,44983451,66611759,14626618,21397057,8651647,36505482,22766820,16099750,45790169,45237957,26392416,86301513,43376279,90272749,7955293,83083131,13468268,29932657,82327024,37891451,74614639,30395570,88047921,97011160,81172706,12303248,68316156,17539625,29819940,17386996,63628376,37675718,8807940,16445503,51047803,62232211,13470059,20765474,41481685,66903004,57359924,66428795,93566986,92398073,874791,98948034,51588015,34432810,58224549,82726008,55753905,81805959,99861373,19486173,55247455,76703609,70541760,56461322,32159704,55189057,99515901,76434144,61263068,92554120,11161731,26275734,30787683,56153345,24915585,43152977,63506281,77413012,85571389,30090481,14326617,88444207,85711894,48395186,9603598,41092172,14093520,49236559,59910624,45428665,30653863,26493119,44060493,6871053,8634541,86001008,17146629,98943869,44664587,40781449,53393358,112651,62430984,59109400,8729146,8115266,61271144,3233084,65454636,33565483,93053405,26998766,6038457,15015906,98648327,415901,26102057,43357947,91664334,29401781,65851721,23432750,42237907,5482538,79191827,1239555,8696647,39847321,17957593,75407347,36396314,83533741,6986898,30998561,7502255,62936963,84349107,34698428,71469330,78884452,67084351,94911072,1197320,50668599,36139942,72278539,97641116,7646095,76315420,4985896,7517032,99965001,96709982,50806615,56955985,86543538,79942022,72019362,59197747,73222868,8791066,9058407,99125126,15064655,84684495,49328608,59371804,11581181,14947650,28550822,98130363,79821897,23848565,62762857,68702632,16405341,93790285,87160386,8913721,97561745,20122224,86821229,73786944,55669657,83133790,73392814,10309525,62051033,78218845,77072625,70372191,15536795,75135448,32250045,7011964,91281584,17764950,48673079,59177718,43933006,47887470,45667668,42199455,526217,84166196,91255408,45919976,73031054,61623915,83210802,89804152,10453030,824872,24733232,86118021,94711842,96735716,90061527,34493392,79922758,46540998,44177011,39171895,54868730,93359396,70036438,50188404,86022504,39553046,21993752,25636669,72614359,39201414,2607799,6793819,56531125,57658654,70800879,65081429,48774913,7685448,75229462,36930650,19101477,11543098,58208470,44481640,76540481,57393458,4978543,20140249,9860195,44627776,7423788,44348328,4119747,40356877,97783876,60102965,24826575,10961421,16684829,30771409,16054533,38008118,67474219,89217461,73124510,74357852,50567636,30163921,85242963,29188588,26734892,53842979,68110330,33061250,26664538,68939068,3235882,92033260,89811711,29510992,69848388,508198,9829782,12348118,94595668,7229550,36135,68041839,71965942,33336148,45075471,93057697,69641513,55602660,35996293,15148031,13173644,749283,3233569,32426752,18001617,95581843,55615885,82532312,1375023,82779622,75153252,89419466,45990383,2204165,24168411,6111563,74743862,1569515,3509435,27625735,94090109,64087743,54762643,21919959,86460488,70727211,7066775,93515664,34295794,9860968,12416768,45407418,82052050,26139110,69579137,33249630,4798568,12571310,30694952,66137019,96852321,26063929,42692881,77300457,66885828,44842615,51464002,99524975,42644903,62803858,2208785,6157724,59318837,71657078,91990218,64157906,44479073,6525948,56484900,9575954,86242799,52261574,30218878,89996536,61859581,96420429,48088883,44784505,36189527,59329511,20836893,54606384,83155442,45794415,91938191,74441448,5832946,40984766,9623492,59405277,15902805,75543508,49641577,75986488,68875490,99971982,29959549,6497830,76671482,32151165,84406788,7182642,16380211,61728685,60955663,17727650,77377183,68204242,78300864,66667729,67227442,65074535,4515343,79806380,37739481,37192445,21533347,83302115,4204661,74452589,64848072,48658605,81855445,40686254,82886381,52204879,3773993,42073124,38510840,32161669,77898274,57803235,58180653,1204161,669105,93270084,44550764,95395112,97057187,57163802,19272365,86798033,96318094,62693428,19457589,69605283,51507425,30764367,92215320,43045786,2891150,53547802,42947632,18504197,48260151,83269727,17894977,99729357,41092102,66271566,14723410,92998591,47738185,79322415,30811010,29065964,66663942,67031644,54263880,4806458,40781854,75820087,34667286,79880247,10649306,92541302,31161687,51715482,18131876,69255765,62357986,99690194,57961282,23740167,85116755,22405120,26776922,84127901,4787945,41442762,47708850,91831487,6505939,21289531,3183975,94539824,37224844,84002370,89046466,65047700,32058615,24314885,48675329,75777973,63015256,90004325,53802686,36845587,40865610,92353856,31727379,6221471,22200378,33935899,84187166,24619760,73021291,78549759,91240048,36933038,64602895,34236719,62552524,97281847,22113133,9175338,87598888,72495719,99224392,36780454,38009615,82651278,74137926,28734791,47298834,37435892,82979980,83747892,95010552,37957788,27041967,77620120,18663507,99333375,53888755,97940276,71333116,73168565,91141395,38341669,30139692,45995325,45996863,45049308,55143724,13819358,10597197,65880522,85117092,83368048,37166608,71316369,65017137,4069912,18411915,30543215,89499542,76960303,16019925,39373729,54232247,31126490,32699744,20681921,81274566,18699206,78766358,65038678,15948937,13348726,67451935,99917599,49597667,9188443,9257405,29516592,26114953,46870723,22721500,99226875,12024238,31733363,56424103,11757872,82427263,39986008,72238278,2331773,72358170,20645197,4091162,63372756,85023028,37501808,15163258,88251446,92692283,61141874,32274392,7300047,45617087,29635537,37280276,81774825,3633375,38494874,64055960,15075176,96906410,54663246,52613508,16567550,33895336,29029316,26863229,1022677,9259676,94360702,66116458,57241521,90158785,17058722,28787861,16971929,93562257,95290172,44846932,26397786,18466635,90654836,87710366,7104732,33123618,11365791,60176618,53666583,30891921,69697787,99604946,95251277,90013093,27187213,54058788,66322959,66319530,55960386,67124282,41380093,51466049,98653983,51315460,89699445,8055981,70782102,80246713,27185644,76825057,176257,5822202,18833224,73109997,25842078,99297164,4199704,78561158,51149804,42307449,77187825,13231279,45848907,53632373,33553959,22450468,51426311,6521313,79738755,94076128,82897371,17976208,26507214,23134715,35512853,71522968,88904910,71920426,14363867,44915235,54987042,85695762,88130087,95726235,75052463,1936762,17385531,62428472,22879907,5640302,21673260,92071990,97379790,68128438,52293995,90457870,49882705,47893286,61928316,83948335,54517921,65275241,62115552,76170907,60393039,92867155,72793444,74724075,67513640,8373289,17857111,10264691,45381876,23110625,37560164,92530431,20867149,57248122 +44481640,36780454,16097038,83210802,88653118,77072625,66667729,16099750,61263068,53393358,69641513,36933038,247198,96193415,77312810,16445503,68000591,35456853,53547802,90457870,55189057,39801351,83651211,40027975,2917920,38645117,95726235,29834512,36312813,15535065,61141874,6986898,9257405,60106217,96735716,79942022,99549373,78766358,7919588,99515901,26114953,64087743,49641577,68875490,32590267,66250369,43376279,6808825,73124510,6505939,92554120,64602895,40865610,63967300,33237508,64848072,95957797,62051033,19376156,96726697,18783702,88444207,63059024,81805959,79136082,99524975,56484900,49236559,2204165,70596786,85116755,56531125,70004753,9575954,39373729,36505482,92692283,17764950,7955293,37192445,68110330,75543508,29932657,31126490,4668450,8129978,12348118,31904591,85117092,55470718,59501487,86022504,5111370,58751351,10309525,36808486,37620363,61271144,59109400,54762643,32159704,38365584,5267545,73786944,9259676,18504197,22997281,63015256,45237957,48892201,16054533,90013093,33895336,51047803,18699206,54263880,82339363,40152546,44664587,76434144,61373987,13231279,76960303,67451935,87710366,33699435,52261574,79657802,54427233,5482538,9603598,26507214,66428795,59177718,7300047,55669657,8696647,39847321,59371804,98943869,22942635,61859143,53632373,55753905,17857111,94539824,99729357,86001008,70036438,82897371,62430984,5640302,30366150,22766820,74743862,47738185,61960400,65038678,81898046,16567550,45794415,34044787,45407418,29959549,94360702,48088883,14947650,84127901,78785507,89499542,72357096,50668599,96906410,9886593,43322743,30771409,54014062,67793644,88904910,32250045,98648327,51464002,92033260,61859581,8807940,72238278,68316156,68644627,8651647,1431742,16387575,49328608,55770687,81274566,66271566,93933709,4806458,95290172,90310261,76703609,44842615,60393039,3509435,81677380,27665211,88698958,57803235,44784505,10358899,33249630,62496012,73392814,53888755,30764367,31727379,8128637,84187166,70367851,83150534,78602717,35192533,94911072,62452034,4798568,4099191,37501808,18131876,74110882,62552524,44983451,21533347,73031054,51149804,24058273,27411561,38061439,65338021,40984766,22113133,41092102,93270084,77300457,28928175,34493392,26493119,68702632,77187825,61928316,20002147,94090109,84349107,47887470,97940276,30891921,41442762,10597197,77377183,19939935,88130087,92541302,7011964,53084256,32161669,93057697,62693428,17068582,3880712,95010552,42947632,18663507,26392416,71333116,19457589,39986008,21070110,74357852,6157724,72495719,65275241,66045587,874791,24915585,4064751,72738685,22108665,91664334,66832478,66903004,40686254,21919959,22435353,51426311,52060076,61623915,72278539,36753250,22405120,71657078,72373496,53666583,78300864,84684495,29466406,51507425,45919976,68204242,43045786,26292919,65081429,94076128,44889423,415901,20486294,29994197,39171895,14220886,33797252,19891772,50702367,52204879,98130363,97011160,30543215,33201905,91802888,14723410,168541,62936963,321665,38008118,65715134,78218845,83789391,64055960,78589145,17957593,65851721,95581843,44348328,6221471,71469330,60430369,54517921,62803858,48893685,37166608,92867155,76315420,31491938,2331773,11161731,38022834,23848565,41245325,22450468,69355476,19101477,80014588,62740044,69697787,65017137,97561745,526217,16019925,13173644,749283,26139110,36189527,75153252,74452589,22129328,9188443,42644903,82726008,18833224,17727650,68816413,14093520,37659250,35092039,26998766,20765474,25636669,79821897,2607799,72777973,29065964,19272365,81774825,72274002,66611759,37224844,97379790,17539625,34295794,17365188,45990383,75986488,69848388,69255765,37280276,99297164,72793444,90004325,47090124,38494874,71522968,44245960,84293052,17016156,30787683,83533741,99658235,80316608,82052050,40356877,62490109,90090964,41481685,97783876,64157906,93053405,71300104,18001617,64098930,99971982,12416768,23432750,85695762,17386996,77284619,29819940,57658654,89217461,83302115,91255408,69136837,89214616,15075176,86118021,87160386,60955663,10366309,73235980,11543098,32426752,19898053,60102965,40197395,3633375,50806615,5832946,11581181,49598724,37739481,99226875,56424103,112651,29029316,669105,86460488,49882705,95395112,30218878,3088684,43506672,89078848,14045383,19486173,16380211,9860195,47213183,1197320,29401781,82327024,23569917,17976208,1250437,48260151,26397786,41380093,33628349,51315460,75135448,89419466,85242963,98462867,54199160,68694897,79191827,20885148,40781449,20568363,57359924,54058788,37675718,79880247,70541760,37957788,9599614,71965942,77301523,13348726,10453030,67227442,49597667,1239555,92787493,63372756,56515456,34698428,52293995,84904436,45996863,36139942,37183543,59329511,94516935,13468268,14626618,77787724,77272628,4069912,45667668,7182642,65271999,67030811,81853704,14326617,44844121,80555751,63152504,30163921,4787945,44473167,28796059,68041839,81172706,99917599,76671482,91957544,21397057,75407347,27625735,43933006,91990218,56461322,42307449,78561158,30998561,77898274,44060493,18411915,54987042,57961282,55960386,94711842,98948034,44177011,70800879,36930650,76330843,20645197,61741594,89637706,99604946,2891150,53802686,87598888,51472275,29188588,49271185,7517032,66322959,71920426,57248122,47893286,34236719,5970607,73222868,92998591,66137019,76825057,46851987,7104732,75128745,33431960,99965001,88047921,91240048,92071990,82886381,8373289,3233084,9175338,45075471,72019362,7685448,20836893,91281584,63628376,8099994,5073754,13862149,85023028,99861373,26863229,34667286,26275734,48673079,38510840,43501211,16971929,65880522,5822202,91141395,2208785,68824981,20140249,90272749,569864,21673260,48675329,40534591,54663246,93566986,6793819,59910624,77620120,4095116,55247455,14349098,58208470,54606384,8115266,56955985,32151165,58749,55615885,84840549,91727510,82178706,75229462,67281495,50007421,26664538,17081350,42692881,61815223,31733363,79922758,37435892,88251446,76540481,57393458,46540998,28787861,45995325,15015906,98739783,80246713,59197747,66663942,52613508,48774913,54232247,86821229,8729146,7502255,72725103,10961421,12303248,14731700,62762857,26776922,58180653,44915235,86242799,98371444,61982238,2717150,87720882,57241521,21993752,11757872,92604458,15902805,96420429,51715482,99125126,1788101,7229550,35512853,83368048,60581278,96709982,62428472,48360198,30090481,62232211,59614746,31161687,24314885,61897582,176257,48658605,1936762,24591705,6525948,42967683,46870723,26734892,99224392,77413012,65074535,36812683,71083565,50188404,53648154,33935899,44847298,80251430,3294781,53842979,75777973,73168565,6153406,33336148,20867149,79806380,30139692,14363867,28851716,28550822,16405341,25842078,33304202,44479073,9623492,29635537,95251277,34497327,28734791,8634541,71316369,55602660,18806856,7687278,74441448,29510992,38009615,27187213,94595668,17058722,16595436,56153345,93790285,34946859,78909561,60176618,56473732,18466635,70727211,15536795,3773993,1375023,51466049,1204161,89046466,73109997,34432810,82979980,17385531,39553046,3233569,20122224,43152977,4091162,72614359,24826575,4204661,38341669,4434662,22721500,7646095,37560164,47298834,33061250,33123618,17894977,36845587,26063929,45848907,74614639,44550764,4119747,84166196,45381876,36685023,92215320,50567636,67474219,97641116,66319530,12664567,22200378,67031644,16424199,69579137,27041967,6521313,61712234,35996293,10649306,92530431,92803766,93562257,32058615,45790169,75052463,27325428,4515343,55850790,8733068,45617087,34698463,92398073,15148031,82779622,83269727,70372191,30694952,37891451,10264691,72732205,82651278,83133790,57240218,17037369,20642888,74137926,78549759,24168411,6497830,6819644,89811711,30653863,41092172,3183975,99333375,84002370,47361209,96852321,70420215,47792865,93359396,92692978,508198,6038457,77694700,23740167,12571310,68939068,68102437,51590803,99690194,1569515,7066775,59318837,42782652,45428665,15163258,13470059,38256849,24953498,44846932,73617245,42073124,16684829,36580610,69605283,85571389,3487592,90061527,81855445,93515664,36135,66885828,38658347,82427263,27185644,23110625,11365791,83948335,62115552,824872,51588015,82532312,86301513,26744917,47708850,9829782,59405277,39201414,67124282,67513640,92353856,20681921,23134715,79738755,6871053,86543538,83155442,30463802,9058407,30811010,57020481,99021067,21001913,98653983,89996536,83083131,76170907,24733232,30395570,55143724,30569392,74724075,63506281,48395186,9398733,33565483,45049308,69786756,8791066,65047700,86798033,12024238,66116458,91938191,4508700,36468541,89804152,57163802,8913721,40781854,96318094,15064655,43357947,97281847,26102057,75820087,13819358,90654836,22879907,15948937,33553959,61728685,70782102,4985896,11923835,7423788,29516592,97057187,83747892,23194618,40677414,32699744,36396314,4978543,54868730,91831487,24619760,58224549,85711894,73021291,68128438,90158785,45306070,72358170,32274392,62357986,1022677,84406788,42199455,65454636,89699445,59981773,79322415,78884452,9860968,44627776,21289531,17146629,3235882,42237907,6111563,8055981,67084351,4199704 +88653118,66832478,1431742,24058273,669105,19101477,7919588,54199160,50668599,36753250,76960303,4099191,88047921,30139692,75986488,52613508,84349107,77620120,74137926,29029316,77284619,36580610,92787493,29834512,66667729,27665211,73222868,6986898,19939935,31904591,7517032,72614359,112651,51047803,80316608,67793644,43322743,20568363,40865610,5111370,60430369,65275241,60102965,95581843,29994197,74357852,8129978,67281495,16971929,37620363,48260151,17081350,48673079,39171895,5832946,38061439,19891772,62496012,17727650,27625735,15948937,24619760,91957544,16099750,99658235,40534591,36808486,40027975,72777973,29466406,30163921,34493392,11365791,90457870,37192445,27187213,84904436,53666583,53547802,5482538,81677380,38341669,71333116,59318837,44889423,74441448,76540481,64087743,90310261,40197395,66045587,321665,62803858,72238278,44983451,86001008,7011964,8128637,59329511,25636669,26392416,3294781,93566986,78218845,38645117,3509435,46870723,1788101,99224392,16595436,58751351,17894977,78884452,94711842,73168565,80246713,57020481,36812683,9623492,176257,66250369,20002147,77272628,52261574,71657078,38658347,16684829,21533347,75135448,97561745,34236719,37891451,66903004,55753905,50702367,63059024,39553046,63152504,5267545,45995325,9599614,37166608,57248122,95957797,84840549,8807940,12664567,26493119,57359924,99604946,71469330,72373496,65715134,22435353,38494874,15535065,44842615,84127901,21070110,45790169,26139110,98653983,4787945,33628349,63372756,83651211,43501211,8099994,29065964,16424199,23848565,29188588,18783702,89214616,39801351,22108665,3233084,34432810,48892201,20486294,92554120,4119747,66137019,68204242,34295794,93359396,95290172,97783876,41442762,34044787,18131876,76315420,40984766,34946859,59614746,87598888,77300457,61859143,62693428,73786944,55247455,37659250,54517921,32590267,76434144,32161669,13862149,59910624,98943869,40686254,70372191,51472275,64602895,82427263,18001617,83302115,86242799,45075471,37675718,8634541,77413012,58224549,9058407,78549759,89637706,70596786,43376279,247198,26114953,14220886,65017137,46540998,34497327,53842979,33304202,79657802,66611759,62430984,81853704,83789391,6153406,49328608,2208785,69136837,68644627,67227442,91727510,94090109,93933709,87160386,81898046,36505482,28550822,33237508,54987042,65880522,30395570,83210802,93270084,13468268,7687278,39201414,72274002,8696647,55770687,45237957,44481640,97281847,15148031,20885148,82897371,74743862,4069912,93515664,42692881,55470718,28734791,10961421,99861373,92692283,62452034,3633375,30787683,37739481,57240218,85571389,64848072,57961282,30543215,73124510,96709982,18699206,93053405,73617245,24953498,80014588,29819940,33201905,30811010,83083131,16097038,53648154,61141874,4199704,96726697,12348118,13470059,43506672,82726008,79191827,4204661,90090964,29932657,37435892,72738685,98130363,17385531,69255765,47708850,76170907,22879907,79922758,749283,92033260,70420215,94516935,22766820,2204165,55143724,68816413,3773993,45848907,45428665,22405120,26734892,508198,77694700,7502255,22942635,36780454,12571310,23134715,42199455,51464002,99021067,88251446,50806615,7685448,67451935,89419466,99549373,1204161,27185644,74110882,53888755,33431960,12416768,3487592,34698463,71300104,7229550,82052050,65338021,42782652,30463802,10366309,33797252,78602717,43933006,60581278,24733232,14731700,2917920,38008118,39847321,62740044,76703609,26664538,4064751,28928175,50188404,85116755,75153252,58749,32159704,33061250,16445503,6505939,55189057,72358170,33249630,98371444,30366150,48360198,86543538,54014062,30891921,17037369,85242963,31161687,3880712,61373987,44846932,47090124,54762643,61960400,65851721,86821229,70004753,79136082,72725103,40677414,26507214,22200378,45919976,17068582,60176618,37183543,97641116,49641577,41245325,77312810,66428795,82979980,62051033,4091162,19486173,92604458,91802888,72495719,874791,95395112,78766358,99524975,93057697,6525948,36933038,28796059,10597197,96735716,40356877,20140249,61897582,54868730,7646095,56424103,22721500,26063929,36312813,72357096,89811711,526217,35512853,91281584,20645197,61271144,59405277,59177718,69355476,47738185,57163802,6871053,36189527,35192533,9886593,28851716,1022677,56531125,1375023,68939068,93790285,83155442,52204879,71920426,2717150,34698428,7955293,7066775,24591705,18466635,96193415,88904910,3235882,10358899,6038457,60955663,62232211,77072625,86022504,99965001,18411915,22450468,44348328,16405341,36139942,4668450,11543098,4798568,17386996,37501808,18806856,6793819,42237907,49236559,27325428,9257405,92803766,47361209,54427233,26776922,73235980,4806458,30998561,46851987,55602660,30218878,38022834,62552524,99690194,415901,84684495,84166196,32426752,66271566,34667286,63506281,70541760,82779622,75820087,69697787,78909561,41481685,61741594,21993752,92541302,87710366,15075176,39986008,92692978,75543508,44915235,19457589,53084256,68824981,75229462,20642888,53632373,1197320,6221471,65271999,57393458,79806380,45306070,15536795,56461322,81855445,95010552,19898053,63628376,59981773,62762857,99917599,65038678,24915585,9860968,63967300,54663246,42967683,88444207,85117092,51507425,74724075,69786756,50007421,85711894,91938191,55669657,36685023,64098930,77187825,14363867,94911072,83150534,9175338,84293052,6808825,96906410,53802686,33699435,67513640,44847298,29401781,94076128,56955985,44479073,84187166,31126490,9860195,70036438,44473167,68000591,65074535,92215320,7423788,45407418,35092039,56515456,92398073,3088684,569864,17146629,19376156,9603598,80555751,35996293,8733068,8791066,51590803,89804152,8729146,75407347,66663942,14626618,21673260,49882705,83368048,16019925,57241521,66319530,96852321,76671482,20765474,61712234,4515343,8651647,55850790,86118021,44844121,23432750,25842078,54606384,60106217,87720882,8115266,44784505,96318094,21289531,23110625,55615885,79942022,68875490,2607799,70800879,6497830,81805959,14947650,14326617,69848388,75777973,68041839,36930650,39373729,85023028,4095116,29959549,59197747,84406788,57803235,44245960,61982238,91664334,71083565,92071990,43045786,7300047,81172706,1569515,42947632,92998591,16380211,42644903,44627776,94595668,168541,33565483,86301513,38009615,76825057,72278539,29635537,90272749,33553959,99297164,52060076,17016156,52293995,91831487,21919959,16387575,17764950,77787724,69641513,61928316,35456853,45617087,54058788,98739783,99515901,68102437,18663507,68694897,96420429,5970607,42307449,40781449,9398733,11757872,76330843,97940276,21397057,1250437,36468541,91240048,17539625,40781854,36135,45996863,78561158,45990383,74452589,20122224,56153345,23569917,67031644,99125126,5073754,70727211,9188443,59109400,33895336,48658605,98648327,71316369,95726235,824872,48088883,65454636,61859581,41380093,26744917,62115552,92867155,27041967,62490109,13819358,51466049,44177011,67084351,89078848,81274566,24168411,62357986,20867149,18504197,47213183,30569392,63015256,69605283,49271185,89699445,43357947,10309525,48774913,82327024,70367851,30653863,48675329,9575954,64157906,37957788,90013093,71522968,48893685,82532312,78589145,44664587,14093520,15064655,57658654,59501487,26292919,20836893,73392814,89499542,67474219,36396314,19272365,41092172,82178706,17857111,10649306,4985896,26998766,8913721,44060493,93562257,38365584,40152546,7104732,6111563,91990218,51715482,17957593,32274392,3233569,13231279,32250045,22997281,18833224,11923835,26275734,89996536,45381876,67030811,43152977,44550764,83948335,82886381,58208470,79821897,72019362,21001913,33935899,14349098,68702632,6157724,56484900,45667668,26863229,55960386,88698958,51588015,54263880,75052463,78785507,85695762,28787861,30764367,90654836,49598724,91255408,90004325,86798033,13173644,62936963,89046466,14723410,14045383,61728685,23194618,73109997,64055960,95251277,4508700,59371804,60393039,89217461,82651278,11161731,83133790,20681921,66885828,42073124,54232247,47792865,97379790,27411561,16054533,8055981,15015906,26397786,61263068,36845587,37280276,53393358,65047700,24826575,66116458,51426311,23740167,83269727,37224844,32699744,97057187,78300864,15902805,79738755,5822202,74614639,15163258,56473732,99226875,72793444,68128438,70782102,67124282,73031054,45049308,80251430,99729357,83747892,97011160,31727379,84002370,22113133,94539824,13348726,17058722,16567550,32058615,24314885,94360702,77377183,99333375,22129328,38510840,99971982,41092102,12024238,2331773,72732205,11581181,33123618,62428472,91141395,82339363,9259676,29510992,38256849,6521313,90061527,61815223,3183975,58180653,4434662,68110330,30090481,37560164,50567636,51315460,98948034,12303248,86460488,88130087,61623915,51149804,6819644,4978543,79880247,98462867,31733363,1239555,47893286,45794415,8373289,65081429,66322959,30771409,17365188,5640302,69579137,92353856,29516592,2891150,81774825,49597667,71965942,68316156,32151165,47887470,30694952,9829782,33336148,73021291,47298834,17976208,48395186,7182642,1936762,92530431,77898274,79322415,26102057,75128745,83533741,31491938,77301523,10453030,10264691,90158785 +89214616,77301523,92033260,43501211,21993752,66611759,72373496,19376156,55470718,8099994,5970607,3294781,61263068,50188404,1197320,17037369,9175338,96735716,91727510,669105,22129328,45237957,50567636,22766820,6808825,75128745,35092039,37183543,72777973,66667729,99515901,40152546,44177011,62430984,11161731,19898053,31904591,56515456,42692881,90272749,57240218,65715134,415901,70372191,52261574,62936963,56461322,24591705,63059024,61373987,44983451,88653118,36468541,72614359,54199160,48360198,67084351,63015256,49328608,51590803,6153406,24953498,76540481,24619760,6038457,36139942,24058273,78602717,72019362,56153345,73124510,8729146,9886593,10649306,96193415,34698428,76960303,247198,36753250,20486294,41481685,23134715,35192533,99917599,66903004,34698463,96318094,13862149,34497327,8055981,94516935,48395186,68816413,78589145,91664334,81853704,7011964,77300457,4668450,62740044,48892201,44889423,78766358,9575954,99965001,55753905,54606384,36930650,57241521,47361209,57393458,59371804,94911072,16445503,63967300,48675329,68875490,59177718,4119747,98130363,20645197,7300047,22997281,34493392,42967683,8634541,61859581,37435892,20885148,5267545,8696647,62552524,53084256,4069912,45306070,77787724,91957544,8733068,80555751,68644627,92554120,26292919,46870723,29932657,31161687,10366309,67281495,32250045,24826575,26139110,16595436,52613508,76434144,34295794,91802888,99524975,43045786,40865610,17068582,99125126,90310261,30366150,77312810,87598888,26114953,48673079,1250437,569864,61982238,68939068,15535065,62496012,6793819,17764950,40677414,83269727,92215320,76671482,67031644,51047803,78785507,45407418,11365791,55770687,40534591,26392416,68204242,82532312,874791,92692978,43506672,1431742,70367851,57658654,2917920,99604946,6505939,39847321,37620363,33431960,27665211,77072625,77620120,47213183,168541,20002147,49271185,5073754,68702632,33237508,66250369,93933709,1204161,14731700,79136082,38009615,57803235,9259676,59197747,82651278,76330843,95290172,6986898,1788101,81677380,1022677,53632373,83789391,28734791,47738185,17058722,26734892,7423788,4787945,38008118,27041967,3509435,66045587,92541302,58749,73617245,8128637,97641116,45428665,8651647,30395570,40027975,17386996,29959549,46851987,85242963,4508700,53802686,73392814,97783876,80251430,21001913,68824981,95726235,38658347,9188443,14093520,38022834,14045383,59614746,82327024,96420429,36312813,50806615,97057187,44473167,55602660,19939935,33201905,66137019,30891921,83651211,4515343,7229550,38256849,83155442,87710366,14626618,47887470,18833224,23848565,33249630,17365188,7955293,15902805,29188588,17539625,80316608,28550822,8807940,29516592,19457589,71083565,94076128,78561158,79806380,54427233,51507425,44348328,90457870,5832946,39553046,3487592,16380211,62803858,82427263,3233569,17146629,99021067,75820087,75543508,75986488,79922758,14220886,9623492,38061439,89811711,3633375,75153252,51315460,33565483,92787493,45667668,55615885,27325428,76703609,526217,47708850,2891150,53393358,53648154,65338021,51464002,28928175,30764367,74614639,7687278,13348726,7919588,31491938,62357986,28796059,31733363,55247455,71965942,60176618,36580610,54058788,79821897,79657802,66319530,63372756,1936762,49236559,72732205,14723410,18783702,84349107,95395112,7685448,77187825,47792865,85695762,96906410,82779622,22435353,50668599,32161669,79942022,10453030,64157906,81855445,15015906,29466406,76315420,89804152,31126490,67793644,32151165,62693428,44847298,52204879,22942635,84840549,10961421,36505482,27185644,30543215,21070110,26776922,55189057,65275241,82886381,94360702,32426752,98653983,72738685,91938191,88444207,29065964,36808486,80014588,71333116,94595668,71920426,60430369,78300864,15148031,37675718,22113133,2204165,99690194,42237907,12348118,98739783,17976208,24733232,19891772,68694897,69848388,73109997,20765474,74743862,98371444,99333375,40781854,29635537,16684829,86001008,79191827,57020481,62051033,55960386,98943869,85116755,30694952,57961282,71657078,72357096,54014062,37659250,65851721,68128438,39171895,58180653,26275734,67474219,13468268,83210802,66116458,3233084,54868730,41092172,83302115,508198,99729357,26102057,99861373,14349098,65454636,8129978,93359396,62232211,92071990,33304202,65271999,29994197,83150534,37957788,48893685,60106217,4434662,112651,3088684,66832478,43376279,48088883,73222868,61859143,16054533,43933006,70036438,50007421,32058615,70420215,6525948,78218845,20122224,30998561,66271566,77284619,2717150,83533741,39986008,59329511,70541760,93566986,82052050,94711842,63628376,9398733,1375023,18699206,39201414,321665,33336148,10358899,96726697,77694700,69579137,78884452,72278539,21673260,44842615,70004753,18411915,56531125,90004325,21397057,69355476,7104732,72725103,77272628,81774825,59501487,78909561,39801351,74441448,75229462,4064751,17957593,13470059,38365584,30787683,84684495,9829782,30139692,34236719,2208785,45919976,90654836,19101477,45848907,49641577,88251446,43357947,53547802,35456853,4806458,15075176,88047921,15064655,83747892,72793444,54663246,37280276,62452034,74724075,18806856,5822202,33935899,84904436,3183975,51466049,51472275,34432810,18131876,99658235,61141874,16567550,69136837,61623915,88130087,98948034,6819644,30771409,42782652,61728685,66663942,44481640,30163921,94090109,74357852,6871053,23194618,66322959,26744917,34946859,74137926,92398073,43152977,18466635,83948335,92530431,48774913,86543538,70596786,40197395,30811010,12664567,71316369,86460488,14363867,99226875,26063929,90013093,77898274,36685023,79880247,88904910,90090964,47090124,37501808,29819940,97281847,75135448,13819358,44784505,70800879,69697787,13173644,65074535,16424199,25636669,32159704,40356877,25842078,60102965,17894977,84187166,95957797,58224549,176257,42307449,72274002,73021291,60581278,21533347,34044787,89419466,36933038,16097038,68000591,85117092,99549373,22108665,89637706,5111370,9603598,36812683,59109400,38510840,92998591,84406788,82339363,40984766,68316156,23569917,91831487,81172706,76170907,34667286,67513640,30463802,8115266,89217461,4798568,29029316,35512853,29834512,49598724,11581181,36845587,67227442,78549759,83368048,93270084,54263880,57163802,16971929,20681921,61271144,40686254,18001617,20140249,88698958,51588015,18663507,67451935,46540998,59981773,14947650,26397786,18504197,22200378,55669657,56424103,50702367,53666583,54232247,91990218,92803766,73786944,6221471,55143724,7502255,95251277,89046466,36780454,85023028,30218878,45996863,92604458,62115552,36135,86301513,77413012,42644903,33123618,89699445,19272365,2331773,90061527,17385531,59318837,80246713,17016156,23740167,64848072,53888755,15948937,38494874,10309525,14326617,54517921,72238278,48260151,13231279,64087743,33699435,84127901,66885828,85711894,44550764,61928316,33797252,45794415,21919959,44627776,82178706,9860195,4204661,81805959,92692283,61897582,4199704,97011160,98462867,89078848,45790169,1239555,39373729,9599614,22450468,71522968,65017137,4099191,16405341,56484900,37739481,11923835,20568363,45995325,45075471,27625735,56473732,8373289,93562257,98648327,92353856,49597667,6157724,57248122,86242799,74110882,84293052,15536795,59910624,19486173,91240048,40781449,32590267,51715482,42947632,20642888,20836893,749283,30569392,42073124,53842979,69641513,70727211,12571310,30090481,94539824,93053405,16099750,58208470,69255765,38645117,3235882,26998766,43322743,96709982,33061250,72358170,77377183,33628349,75052463,12416768,86798033,75777973,62490109,72495719,52060076,68110330,1569515,85571389,4091162,41092102,66428795,32699744,3880712,12303248,73168565,97940276,824872,92867155,64098930,15163258,65081429,82979980,61712234,58751351,73031054,55850790,73235980,82897371,68102437,6111563,59405277,97379790,67124282,86118021,12024238,45990383,24168411,32274392,35996293,81898046,67030811,11543098,68041839,23432750,60393039,9058407,29401781,37192445,42199455,22405120,26507214,47298834,65038678,71300104,4978543,7517032,22721500,69786756,21289531,5482538,29510992,44060493,97561745,33895336,60955663,38341669,49882705,51149804,71469330,86821229,61741594,84166196,3773993,8913721,54987042,17857111,93790285,30653863,4985896,44915235,99224392,96852321,54762643,2607799,84002370,24915585,99971982,51426311,79322415,6497830,79738755,17081350,26664538,28787861,89996536,95010552,93057697,48658605,82726008,61960400,17727650,27187213,52293995,63506281,16387575,87720882,20867149,4095116,37166608,37224844,69605283,57359924,44664587,65047700,44846932,7646095,89499542,91141395,16019925,36189527,41245325,83133790,62762857,56955985,7066775,33553959,65880522,22879907,87160386,5640302,95581843,93515664,81274566,75407347,64602895,44844121,76825057,24314885,41380093,28851716,83083131,86022504,91255408,27411561,7182642,8791066,37891451,11757872,44245960,10597197,45381876,70782102,36396314,90158785,9860968,41442762,47893286,26863229,45617087,9257405,45049308,63152504,74452589,23110625,10264691,31727379,64055960,62428472,99297164,44479073,26493119,61815223,6521313,91281584,37560164 +98371444,55960386,99333375,91990218,53842979,89214616,59109400,41092172,33201905,7011964,35192533,98462867,72495719,30395570,415901,6871053,94516935,10358899,73124510,6793819,24058273,50188404,15535065,72738685,42782652,77413012,51047803,77694700,10366309,66663942,60430369,74724075,57658654,94076128,23848565,22129328,15536795,15015906,47792865,78589145,44550764,23569917,29065964,6153406,29029316,35092039,68824981,77187825,30694952,84684495,56531125,73168565,48893685,95581843,61960400,62740044,32250045,68644627,55753905,26392416,63015256,47298834,92033260,17146629,43152977,92803766,51472275,18131876,508198,84002370,14326617,79136082,17068582,65074535,16097038,35996293,9886593,29994197,9188443,54868730,43376279,39847321,9603598,50567636,11581181,73392814,70367851,21993752,53648154,99297164,84293052,96193415,84840549,40152546,87710366,14220886,40984766,62693428,39801351,53084256,93933709,34698428,92692978,4668450,26063929,58749,98943869,45790169,17365188,67793644,13348726,16054533,97641116,35456853,38658347,63628376,20765474,26734892,86460488,96420429,48892201,5111370,38061439,66250369,23134715,5267545,36580610,16387575,67281495,33628349,70420215,112651,43501211,7646095,44245960,8634541,3509435,85242963,80316608,3183975,9623492,33249630,6521313,89046466,84187166,69136837,91831487,80251430,21673260,31727379,62490109,99524975,51464002,83150534,77272628,42967683,99658235,20568363,93270084,75229462,34698463,76671482,26139110,80014588,87598888,33336148,7423788,63506281,51149804,46851987,59177718,65017137,6808825,57803235,99021067,51590803,42307449,11923835,31161687,4064751,83368048,22997281,59981773,24591705,33237508,2717150,4508700,75543508,39986008,74441448,168541,92787493,72019362,37891451,69355476,72725103,17957593,81853704,60955663,17385531,37659250,72793444,77284619,96726697,95010552,27041967,93053405,44177011,7687278,29819940,49641577,57241521,91957544,83533741,98130363,22108665,54762643,90272749,73235980,40865610,18466635,64055960,1022677,14093520,44348328,14626618,36505482,15075176,79821897,4099191,13470059,40677414,99515901,86301513,30764367,62452034,23194618,97379790,24168411,30366150,60102965,96852321,89078848,76170907,47213183,4985896,68041839,18699206,55189057,77301523,89996536,34432810,8913721,44481640,71333116,75777973,26998766,8055981,61623915,21533347,39171895,3088684,669105,33797252,3487592,75407347,9860968,16445503,71657078,29466406,14045383,69579137,79657802,48360198,3233084,30569392,30787683,63967300,65047700,20642888,72777973,44889423,56484900,18783702,13819358,86798033,92530431,45848907,8733068,55850790,17016156,26664538,32161669,20885148,56515456,55470718,70541760,59371804,78549759,48774913,95395112,82532312,17539625,68816413,36753250,4434662,50668599,6986898,17976208,44842615,81172706,26776922,68204242,61859143,67513640,69255765,72614359,12664567,95957797,14349098,5073754,3880712,18663507,45237957,13173644,47090124,65715134,53666583,72373496,247198,88444207,45049308,69848388,38365584,52060076,39373729,30218878,73031054,37435892,43933006,7300047,47738185,92215320,54058788,8791066,22450468,57163802,76825057,569864,51507425,99965001,76330843,8807940,68702632,99917599,16380211,23740167,21001913,94595668,22766820,9575954,1197320,29188588,26397786,53802686,10649306,38256849,88130087,27665211,65454636,20867149,92398073,81274566,90158785,97561745,31491938,72274002,12024238,95726235,77377183,526217,79806380,4091162,7955293,32590267,70372191,99549373,40534591,54427233,34295794,75153252,55669657,36468541,80555751,29510992,7685448,91255408,27411561,15064655,72732205,64087743,4798568,44847298,7502255,72278539,60581278,5970607,62430984,53632373,78218845,22113133,61859581,16424199,31904591,19376156,19101477,47893286,54606384,82726008,55770687,81898046,76960303,19939935,36312813,83302115,49598724,19891772,56424103,49328608,45919976,6111563,83651211,66137019,16405341,68102437,31126490,13862149,48395186,65081429,42692881,30463802,91938191,33565483,25842078,39201414,17894977,12348118,47708850,36812683,74614639,92604458,93057697,61263068,67084351,73617245,56153345,54663246,37166608,70036438,93515664,96906410,44915235,50007421,26863229,22942635,81855445,47887470,55247455,2917920,10961421,17058722,51588015,90457870,4095116,67474219,36933038,60106217,20836893,50702367,34044787,89811711,21397057,13231279,81805959,9257405,14947650,68000591,5832946,5822202,26275734,86821229,91141395,33431960,33123618,74137926,51715482,16019925,39553046,45990383,4978543,24733232,78300864,98739783,28787861,4806458,64157906,82886381,38008118,36780454,61728685,86118021,59197747,93790285,18833224,45428665,61815223,38022834,66667729,26114953,66885828,56461322,40781854,22879907,47361209,42947632,32274392,44784505,8651647,84904436,64098930,4204661,67227442,37957788,75820087,18504197,99226875,68939068,19272365,4069912,96318094,30998561,13468268,62115552,76703609,43506672,3294781,5482538,321665,36930650,61741594,2891150,4787945,28734791,40027975,53393358,70596786,38494874,9175338,34236719,26292919,61982238,92692283,78909561,31733363,15148031,40686254,95290172,54014062,79322415,69605283,10309525,33304202,6525948,94539824,91240048,48088883,79880247,30139692,41380093,77312810,749283,71469330,65851721,65038678,26744917,12303248,89419466,91281584,83269727,92554120,45996863,3633375,90090964,62496012,71300104,99729357,3235882,63059024,70004753,44627776,49236559,54232247,54263880,42199455,97940276,49597667,85116755,45075471,59329511,11161731,89699445,21070110,33895336,55143724,9398733,1250437,36685023,60176618,90061527,34497327,40781449,73222868,61141874,61373987,67124282,52261574,59405277,91664334,94911072,82327024,49271185,36845587,18411915,87720882,56473732,62051033,51315460,10264691,37675718,1431742,58751351,1936762,5640302,89804152,11365791,72358170,99971982,64602895,94360702,91727510,62232211,2607799,52613508,82651278,89217461,44983451,85117092,65880522,46870723,76540481,61897582,34493392,8373289,54199160,90654836,18806856,64848072,14731700,21289531,9829782,44479073,11543098,61271144,19486173,92541302,1204161,62936963,59501487,69641513,84406788,30771409,57359924,33061250,88698958,1788101,28928175,77620120,65338021,8129978,57248122,45794415,79942022,75128745,79738755,7104732,52204879,43045786,77072625,9058407,66045587,66322959,73786944,37280276,42644903,8696647,81677380,20486294,57393458,37192445,6819644,70727211,97783876,35512853,86543538,54987042,23110625,824872,93562257,68128438,23432750,20122224,17386996,54517921,78602717,66271566,82979980,59318837,77787724,22405120,44664587,92998591,29635537,82339363,57961282,2331773,53547802,96709982,79191827,82052050,14363867,37560164,4515343,63372756,80246713,74357852,94711842,83133790,92353856,66832478,32159704,37183543,45667668,8099994,99690194,37620363,19457589,62803858,86242799,45407418,55602660,8115266,77898274,28550822,61928316,9599614,73109997,16684829,83210802,97281847,24953498,57240218,33935899,12571310,68316156,29959549,65271999,75135448,69786756,70800879,99125126,15902805,20002147,20645197,99604946,68694897,10597197,86001008,24826575,29834512,71316369,48260151,72357096,40197395,98648327,16971929,36139942,30090481,58208470,97057187,14723410,11757872,98948034,88047921,86022504,88904910,27185644,56955985,44846932,96735716,36135,34946859,1569515,874791,28796059,44473167,88653118,83083131,45995325,26507214,45306070,82897371,57020481,58180653,41481685,78884452,7517032,37739481,41245325,83948335,28851716,88251446,24619760,93566986,98653983,22721500,48673079,71920426,26102057,67030811,94090109,68110330,7919588,52293995,38009615,34667286,99861373,71965942,20681921,89637706,90004325,60393039,84166196,33699435,30891921,44844121,38341669,74110882,91802888,1239555,17037369,43322743,7066775,6505939,45617087,17764950,75986488,17727650,30811010,79922758,66116458,75052463,71083565,37501808,93359396,42073124,76315420,82427263,49882705,8128637,90310261,6497830,67031644,84349107,24314885,41092102,59614746,66319530,30653863,22435353,9860195,1375023,51426311,27325428,29932657,27187213,36189527,78785507,9259676,66903004,50806615,32151165,62552524,29516592,51466049,22200378,3773993,7182642,12416768,20140249,83747892,89499542,85711894,66428795,74743862,78561158,63152504,15163258,76434144,87160386,69697787,78766358,62762857,32058615,82178706,4119747,10453030,65275241,32699744,17081350,36396314,67451935,41442762,48675329,32426752,45381876,7229550,73021291,15948937,83789391,90013093,92071990,58224549,99224392,19898053,62428472,40356877,66611759,30543215,85023028,18001617,37224844,74452589,61712234,2208785,77300457,59910624,6221471,3233569,16567550,26493119,17857111,55615885,92867155,62357986,6038457,25636669,21919959,29401781,4199704,30163921,85571389,24915585,16595436,71522968,95251277,8729146,48658605,38510840,70782102,82779622,44060493,2204165,83155442,46540998,38645117,53888755,42237907,36808486,97011160,16099750,85695762,72238278,68875490,81774825,6157724,176257,27625735,43357947,33553959,84127901 +61373987,44348328,65851721,112651,3509435,31733363,29959549,25842078,8733068,98462867,8055981,98739783,62693428,15536795,22997281,10358899,30395570,97561745,66322959,53084256,49641577,27325428,3233084,33304202,12416768,81853704,96193415,526217,48774913,54058788,59109400,85023028,99549373,63059024,54427233,3183975,19939935,99965001,4508700,415901,91938191,4985896,79922758,95581843,27411561,83210802,43933006,2917920,8807940,6793819,35092039,72274002,35192533,36505482,23569917,12348118,12664567,72725103,13231279,56531125,23848565,22108665,17146629,19101477,69355476,27041967,55189057,4095116,92033260,60581278,33201905,36580610,55753905,62936963,59371804,18833224,73031054,80251430,40984766,66250369,45996863,9188443,4668450,9575954,30218878,3294781,5822202,65074535,91957544,321665,29029316,20765474,51472275,39553046,61263068,68204242,63628376,4806458,45237957,62740044,79806380,4199704,84187166,61859143,65038678,62496012,10366309,22721500,7011964,34698428,92398073,90090964,89637706,51466049,9886593,71920426,71333116,84002370,569864,99333375,94516935,44846932,82886381,24591705,13173644,14093520,73109997,38494874,66663942,26776922,17365188,24168411,93562257,50668599,83269727,2204165,77312810,93270084,3880712,10309525,38061439,75543508,78884452,13862149,30366150,26063929,82979980,19486173,17957593,26292919,59197747,6153406,26744917,8115266,17068582,50567636,30139692,1204161,91802888,10453030,9623492,45790169,508198,62232211,51047803,54762643,33431960,43322743,42073124,30543215,55602660,27665211,92604458,78561158,65047700,60430369,4978543,79880247,11161731,61982238,87598888,95290172,6111563,50188404,99524975,63967300,98943869,72738685,65715134,57240218,85116755,85242963,44550764,669105,27185644,68041839,26664538,14045383,60106217,70800879,16595436,37435892,14326617,39986008,6521313,38008118,36312813,81172706,68102437,34295794,74743862,72278539,17058722,43501211,41380093,65454636,77300457,14626618,15015906,62452034,1431742,51715482,18411915,69136837,71657078,55143724,33628349,7300047,68128438,34236719,20122224,68702632,67793644,73786944,89214616,12303248,2891150,7687278,68816413,60102965,40152546,24826575,4119747,68644627,37891451,80014588,69579137,48360198,48893685,7423788,72777973,37675718,69786756,33797252,21001913,91255408,3088684,66116458,42967683,3235882,44983451,8129978,77284619,44842615,22129328,61859581,33237508,39373729,54199160,6986898,76330843,70541760,77620120,67031644,30787683,84840549,99604946,64848072,39847321,59501487,86460488,4064751,11543098,24058273,96726697,45306070,14731700,32590267,6497830,72373496,30653863,24915585,90272749,70004753,37739481,34432810,75229462,7646095,45794415,61741594,74357852,91990218,55470718,1569515,65017137,96318094,20486294,58751351,65271999,73168565,54014062,68824981,10961421,16424199,20681921,72358170,92803766,68694897,68000591,18783702,44844121,33699435,30694952,95957797,78589145,99658235,84349107,21993752,83302115,5111370,9058407,75153252,96735716,78218845,92554120,15535065,45428665,37183543,34044787,44784505,47792865,13470059,80555751,36780454,92541302,37501808,2717150,874791,93933709,84904436,40781449,76170907,29834512,67474219,66832478,24733232,97011160,57658654,87710366,5970607,1022677,66428795,33123618,81677380,37957788,6525948,86543538,77694700,40197395,17976208,51588015,66137019,168541,52060076,92215320,76540481,30891921,36845587,36930650,16380211,89046466,49597667,20645197,95395112,6038457,56153345,47090124,4099191,23194618,13348726,52293995,67084351,4787945,43045786,89419466,39201414,99224392,28928175,35996293,55669657,824872,42947632,62803858,86821229,77187825,60176618,69605283,76671482,26392416,57393458,17081350,61960400,9860968,19376156,79821897,75128745,31126490,33336148,92692283,73235980,82726008,62490109,29819940,68316156,40677414,32159704,26139110,77072625,8651647,93053405,78785507,71083565,64087743,10649306,10597197,51464002,41481685,64157906,62051033,77787724,66319530,67513640,66667729,81898046,64055960,29466406,26998766,67281495,57961282,36753250,5482538,8128637,43506672,82327024,15075176,53842979,92998591,92692978,1250437,56424103,24314885,60955663,91664334,11923835,247198,62357986,54663246,42782652,99861373,40356877,18806856,68875490,78602717,84127901,93057697,47298834,97940276,84406788,9398733,10264691,90061527,85695762,90004325,8373289,23432750,48673079,69848388,38645117,78549759,68939068,44481640,36468541,7919588,89699445,53648154,47213183,8696647,38022834,45848907,95726235,86301513,93566986,94360702,65338021,59981773,1788101,44479073,82052050,34497327,89078848,82339363,71522968,57241521,78300864,16099750,20867149,18663507,98648327,18131876,55850790,67227442,53802686,44245960,8791066,14363867,15902805,20836893,13819358,65081429,69641513,20002147,31727379,6871053,54868730,49236559,32058615,32426752,45381876,9599614,16684829,86242799,67451935,42644903,4798568,46870723,29510992,1197320,29635537,98130363,59318837,78909561,9259676,57803235,16097038,79136082,35456853,75777973,83533741,32161669,84684495,89804152,30771409,22435353,32250045,73222868,71300104,42237907,72793444,29932657,33249630,58208470,47361209,31904591,53547802,97641116,71469330,61141874,55770687,62430984,47887470,66903004,29188588,38365584,28851716,85711894,15948937,72732205,79657802,9257405,88444207,29516592,25636669,99297164,89811711,26114953,41092172,29994197,9603598,99515901,16971929,1239555,26863229,99729357,46540998,96709982,1936762,99125126,33565483,6819644,74441448,70367851,22113133,70372191,47708850,83083131,57248122,40865610,73617245,80246713,59910624,44177011,33061250,61815223,82897371,71316369,51149804,70727211,88251446,73021291,99021067,67030811,38658347,90013093,41092102,55960386,68110330,38256849,74110882,59329511,28550822,16019925,30764367,58180653,51590803,86118021,53393358,45617087,63015256,77377183,9829782,90310261,34667286,33895336,66611759,30090481,24619760,82651278,15148031,90158785,8729146,44473167,83651211,89499542,36812683,53666583,70596786,27625735,48892201,17857111,51507425,26275734,20642888,72614359,53888755,7955293,9175338,17386996,7685448,7517032,91831487,81274566,87720882,26102057,64098930,6157724,56461322,1375023,39171895,28734791,17727650,7229550,88698958,89217461,32151165,92787493,75407347,82532312,34493392,3633375,72357096,63372756,72238278,91281584,44847298,52261574,37192445,86001008,30998561,96420429,61623915,94539824,84166196,29401781,77301523,40686254,82178706,39801351,81855445,19891772,24953498,66045587,99690194,7066775,54987042,77898274,83150534,49328608,82427263,21533347,72019362,46851987,28787861,47738185,75052463,76315420,83789391,36135,7502255,11581181,52204879,38009615,96906410,45995325,61928316,17764950,45667668,50806615,14349098,4515343,69255765,76825057,20140249,45407418,64602895,26734892,86798033,31161687,77413012,30811010,94076128,48675329,17539625,8913721,20885148,4434662,65275241,37620363,14220886,48088883,6808825,34946859,21289531,22200378,16387575,8634541,12571310,50007421,66885828,65880522,14723410,55247455,19272365,43152977,56515456,59177718,83948335,80316608,23110625,44627776,41245325,14947650,36189527,44889423,40781854,4204661,74452589,51426311,62552524,75820087,75135448,70036438,43376279,84293052,86022504,22766820,88130087,31491938,79191827,19898053,98371444,26493119,97379790,56473732,43357947,66271566,5640302,37560164,88653118,3773993,6221471,49882705,7104732,11365791,49271185,77272628,22942635,32274392,26507214,95251277,95010552,5073754,36685023,97783876,44060493,30569392,15064655,29065964,79738755,61271144,17385531,21673260,16567550,44915235,45990383,85117092,57020481,36933038,54232247,74724075,16054533,93359396,42199455,54606384,32699744,57359924,4069912,9860195,30463802,40534591,67124282,74614639,92353856,34698463,93515664,90457870,45919976,42307449,52613508,57163802,94911072,83368048,45075471,18699206,89996536,35512853,83155442,33935899,70782102,3233569,81805959,74137926,49598724,96852321,38510840,63506281,45049308,7182642,12024238,23134715,91240048,58749,42692881,26397786,75986488,176257,40027975,94090109,48395186,62762857,56955985,20568363,91141395,21397057,21070110,79322415,38341669,99971982,62115552,87160386,69697787,70420215,37280276,72495719,61728685,22405120,16445503,58224549,28796059,17894977,17037369,78766358,16405341,54263880,83747892,37659250,3487592,92071990,8099994,88047921,73392814,73124510,76434144,2607799,37166608,99917599,22450468,93790285,85571389,18504197,2208785,81774825,5267545,99226875,56484900,97057187,48658605,91727510,22879907,33553959,92867155,5832946,76960303,98948034,36396314,61712234,41442762,94595668,76703609,97281847,18001617,92530431,83133790,55615885,48260151,88904910,19457589,21919959,90654836,60393039,98653983,54517921,17016156,4091162,37224844,36808486,47893286,749283,15163258,2331773,59405277,23740167,61897582,53632373,30163921,27187213,50702367,18466635,79942022,13468268,62428472,51315460,71965942,82779622,63152504,94711842,59614746,36139942,6505939,44664587,11757872 +33304202,62452034,89214616,47361209,61960400,35192533,83210802,15535065,47090124,4668450,92398073,93933709,6793819,38494874,4099191,77413012,86460488,78884452,54987042,55143724,53842979,22450468,76825057,29834512,29188588,7423788,34497327,7646095,56515456,9398733,30543215,80316608,96193415,45407418,99297164,43376279,93515664,11365791,59614746,73235980,37620363,14326617,74137926,74614639,97783876,16567550,51472275,66045587,53084256,112651,54663246,95957797,4064751,63506281,68816413,69136837,8807940,78602717,8099994,17764950,15064655,26664538,37675718,36808486,57240218,34236719,59910624,78785507,62232211,66832478,8129978,99917599,38658347,85242963,81855445,75153252,70004753,61897582,55247455,38061439,63152504,63967300,59501487,35996293,67030811,99971982,247198,60430369,70372191,55770687,99515901,72274002,73392814,96318094,19376156,48892201,26275734,45237957,73031054,1375023,9175338,14731700,67793644,71469330,52060076,98371444,56955985,26863229,61623915,4119747,12024238,31904591,62430984,81898046,32699744,73168565,19939935,99658235,14349098,99604946,51464002,24915585,82427263,23432750,68939068,56424103,45990383,66428795,85116755,48360198,415901,20122224,70727211,87160386,38645117,51047803,23848565,36580610,14947650,17386996,63372756,80246713,86022504,84840549,26063929,84187166,10366309,19891772,44481640,62496012,57803235,18663507,23194618,99021067,68824981,66137019,61859581,7011964,78589145,47708850,20836893,3235882,9623492,79922758,30463802,16099750,57241521,19101477,39801351,36812683,15148031,45075471,50007421,55189057,7182642,43501211,54232247,5111370,98462867,40865610,26493119,16445503,65017137,99965001,57359924,48675329,26292919,36685023,22129328,26744917,9058407,50702367,44245960,95726235,45617087,72738685,42199455,23110625,83133790,74724075,62357986,78766358,57248122,3294781,33699435,17957593,34698463,58224549,43152977,40686254,526217,49328608,72725103,94090109,89046466,88047921,26102057,98130363,89637706,51507425,84684495,874791,52613508,40677414,48893685,91664334,30771409,13468268,36930650,36505482,13348726,67281495,569864,17068582,1431742,26507214,22108665,91727510,59371804,65851721,92541302,33249630,77284619,46851987,11581181,61741594,72614359,34295794,92071990,75135448,77072625,65081429,3773993,27325428,16054533,54199160,3088684,44889423,2917920,77694700,42967683,4515343,94076128,18001617,49236559,65038678,24591705,3880712,82327024,57163802,99226875,40984766,38365584,62740044,77300457,37659250,13470059,8128637,20568363,14220886,42073124,7685448,88698958,65715134,8696647,95581843,4199704,20642888,98653983,86301513,55960386,39373729,35092039,14626618,70596786,99524975,30998561,33237508,79657802,70800879,78218845,54868730,22942635,83948335,53632373,1204161,82726008,8115266,44473167,29819940,77787724,40781854,2208785,71333116,5267545,1022677,18131876,10358899,15163258,30218878,61263068,91240048,4508700,9259676,90457870,4787945,78549759,86001008,73124510,64157906,321665,70420215,32161669,29029316,87598888,34432810,79738755,3233084,27665211,82532312,73786944,68702632,61271144,82897371,59109400,42692881,18783702,83155442,38341669,16405341,73617245,98739783,92033260,95395112,18699206,1569515,45428665,54427233,6505939,37183543,27625735,42307449,90272749,64848072,99690194,54014062,65271999,71657078,2717150,45790169,55753905,13231279,48088883,37501808,97940276,48260151,82339363,15948937,72777973,44842615,63628376,16971929,6525948,75543508,75407347,7517032,6221471,82178706,23569917,89419466,88904910,50806615,32151165,39171895,33061250,21533347,70036438,72373496,69579137,8634541,176257,62490109,16019925,56461322,53648154,45667668,64087743,26392416,92998591,30366150,93790285,77620120,92692978,89996536,20765474,45995325,32159704,54762643,35456853,34698428,92554120,76170907,76703609,66885828,93359396,56473732,76434144,45919976,48658605,76540481,9860968,88653118,44847298,51466049,68644627,28796059,77312810,31161687,30139692,824872,81853704,37166608,29994197,18466635,98943869,18806856,44479073,65047700,27411561,49641577,44060493,31126490,31733363,92692283,5822202,24733232,32250045,74452589,20486294,96735716,79821897,61982238,44983451,17365188,30764367,11161731,3633375,99861373,77377183,90090964,63059024,6986898,44550764,66250369,2331773,85571389,12571310,50567636,93053405,57658654,24058273,89499542,6153406,53802686,66611759,33565483,38510840,95290172,28550822,84127901,22879907,34493392,10309525,65338021,72358170,30090481,91141395,43322743,56531125,44664587,17146629,52261574,52204879,16097038,45794415,41380093,6819644,58180653,84406788,71316369,51588015,44627776,12416768,5640302,59318837,38008118,21993752,94911072,34667286,11543098,16595436,65275241,72793444,68875490,71083565,46540998,9829782,68041839,8651647,37891451,7502255,20002147,3487592,26397786,26734892,33797252,30395570,22997281,7955293,60581278,57020481,42947632,67451935,33553959,37435892,81805959,22766820,73021291,99549373,80251430,47298834,5970607,62428472,32426752,37739481,92353856,75229462,54606384,6808825,36753250,92803766,97281847,1788101,79880247,47792865,33336148,31727379,72495719,9603598,68102437,75777973,44348328,79191827,86242799,32590267,28928175,1197320,3509435,7919588,99729357,42782652,54058788,6871053,41442762,61928316,40534591,73222868,28851716,44846932,95010552,64602895,4204661,94516935,14093520,26114953,69355476,80555751,34946859,72357096,91938191,21001913,49598724,37957788,89078848,60102965,40197395,19272365,16387575,33201905,43933006,66903004,69641513,28787861,6521313,33628349,61712234,50188404,30891921,7687278,40027975,26998766,52293995,45996863,20885148,98948034,40781449,26139110,56153345,4069912,53393358,36845587,10961421,68316156,28734791,10597197,86543538,61373987,83150534,2891150,61141874,22113133,7104732,8373289,29932657,94711842,15015906,84293052,60106217,96709982,61728685,57961282,68128438,4985896,508198,58749,29959549,68204242,87720882,37280276,51149804,59197747,36396314,63015256,66322959,81774825,7300047,82979980,46870723,92787493,55615885,24826575,96726697,79136082,48673079,77272628,84002370,77898274,64098930,90013093,2204165,48774913,9188443,6157724,82886381,91281584,36933038,11757872,17385531,36189527,4434662,97011160,38009615,83368048,81274566,65454636,30653863,16380211,83083131,36780454,27187213,45381876,72019362,71300104,1250437,40152546,94360702,61859143,47213183,669105,29635537,88444207,15075176,14045383,81677380,18504197,78561158,62552524,30811010,30163921,36135,30569392,13819358,94539824,66271566,66667729,44784505,48395186,99333375,32058615,51426311,55602660,73109997,12303248,41092102,86821229,49271185,62115552,53547802,5482538,23740167,29510992,24953498,43506672,86118021,68110330,96420429,13862149,22200378,97641116,62936963,44177011,25636669,65880522,39201414,85023028,4798568,78300864,1239555,66319530,83302115,58751351,91802888,30694952,49597667,19486173,9860195,20140249,17016156,82651278,22405120,82052050,17037369,49882705,98648327,91255408,72238278,17727650,20867149,18833224,97561745,57393458,72278539,71965942,9886593,11923835,24168411,39986008,91990218,36468541,36312813,83651211,76315420,88251446,33123618,32274392,93566986,31491938,69255765,168541,34044787,90654836,45848907,74110882,29065964,3233569,99125126,42237907,17081350,90310261,13173644,65074535,20645197,80014588,38256849,84904436,69848388,77301523,12348118,56484900,75052463,25842078,76671482,76960303,29466406,16684829,23134715,33935899,2607799,50668599,42644903,55669657,83269727,749283,17857111,93562257,90061527,37192445,68000591,90158785,55470718,4095116,81172706,71522968,39553046,12664567,21070110,72732205,19457589,4806458,75128745,91957544,14723410,59177718,47738185,10264691,62803858,54517921,45049308,59405277,75986488,40356877,6038457,96852321,69605283,96906410,85117092,17976208,44844121,8913721,33431960,67084351,41481685,43357947,62051033,97379790,77187825,68694897,10649306,51715482,74357852,45306070,93057697,76330843,8729146,60955663,41245325,79806380,15536795,18411915,94595668,91831487,89699445,6111563,16424199,21673260,62762857,58208470,92604458,51590803,35512853,47887470,92215320,7066775,47893286,64055960,67124282,66663942,37224844,9599614,89217461,5832946,30787683,8733068,33895336,75820087,43045786,39847321,4978543,3183975,83747892,53888755,87710366,71920426,83789391,9257405,70367851,26776922,92867155,60176618,61815223,59329511,9575954,70782102,4091162,10453030,21289531,55850790,95251277,70541760,78909561,99224392,17894977,86798033,67513640,92530431,19898053,67474219,8055981,74743862,7229550,93270084,90004325,1936762,14363867,29401781,85711894,89804152,89811711,54263880,6497830,79322415,79942022,20681921,66116458,21919959,53666583,62693428,37560164,22435353,38022834,69697787,44915235,27041967,24619760,17539625,82779622,69786756,97057187,5073754,83533741,51315460,24314885,27185644,36139942,67031644,8791066,41092172,59981773,15902805,84166196,74441448,17058722,67227442,21397057,60393039,29516592,84349107,22721500,88130087,85695762 +47738185,75543508,67124282,78909561,17068582,77413012,59177718,21673260,22942635,44784505,80014588,6153406,53666583,45667668,85116755,57803235,83533741,39847321,71657078,99297164,7919588,18699206,35092039,29029316,99125126,92692978,72373496,9886593,37739481,9603598,75777973,77284619,30090481,81898046,15536795,44889423,98371444,62496012,88047921,40781449,81853704,21070110,55247455,95395112,4064751,72274002,29834512,27665211,9257405,30787683,10453030,36505482,9623492,94090109,88130087,2717150,73124510,16099750,36933038,5073754,37501808,29635537,79136082,86001008,70036438,75986488,67084351,61373987,62936963,66116458,55753905,29065964,96735716,4119747,33797252,33123618,44177011,51590803,64848072,24058273,23569917,20885148,30163921,69641513,16971929,92554120,168541,58749,40686254,3233084,67227442,5970607,16019925,15163258,22721500,65074535,38645117,82779622,26063929,34236719,18411915,99226875,72777973,97561745,33628349,98462867,99524975,66322959,51047803,26392416,89214616,15535065,5832946,44060493,48260151,7687278,61271144,90013093,30139692,28796059,66667729,669105,7229550,28851716,5111370,33336148,30764367,79806380,41380093,99658235,48893685,55850790,96193415,86798033,73031054,36753250,71920426,77377183,45428665,33201905,94595668,29819940,74357852,44481640,8913721,65275241,34493392,73392814,45996863,77272628,51464002,56531125,14093520,53547802,87598888,41245325,14045383,45995325,99690194,55470718,80246713,8651647,38365584,36780454,68816413,36396314,16445503,34497327,31126490,86301513,36312813,50702367,56955985,82339363,83651211,96709982,96852321,74724075,11923835,16405341,91831487,3487592,6793819,14326617,29959549,59197747,18783702,60102965,6221471,18833224,8733068,30395570,71333116,61982238,76434144,66271566,74441448,21919959,93270084,11543098,34946859,64087743,86543538,45237957,70367851,89217461,20002147,76540481,73222868,17037369,47893286,4099191,45794415,62740044,33249630,79821897,17857111,83789391,68204242,44842615,38658347,28928175,38008118,62452034,34432810,40984766,1936762,17727650,64157906,63628376,45848907,6808825,17539625,36808486,83210802,76330843,62490109,60955663,9829782,68000591,6505939,51315460,72019362,63967300,6871053,62762857,93933709,40027975,22766820,43376279,14349098,3509435,92803766,48088883,91664334,90272749,61859581,74452589,30694952,44479073,48675329,40534591,65038678,73786944,78766358,33895336,45617087,2208785,98948034,53842979,9175338,26139110,57248122,65338021,63506281,36139942,11757872,61623915,17957593,24591705,49236559,29466406,92353856,35996293,66250369,63059024,77312810,73617245,70596786,3233569,62430984,99515901,19486173,17016156,62552524,46870723,2204165,8634541,40197395,33553959,30463802,16097038,65880522,66611759,43045786,4204661,9058407,68644627,66137019,53802686,16380211,57240218,88653118,75128745,17764950,67281495,8129978,84187166,10309525,22113133,4787945,30569392,13231279,35512853,98130363,40677414,55143724,70800879,50806615,52261574,86022504,62232211,42947632,88444207,67793644,112651,44983451,29994197,89078848,54517921,60430369,59109400,23848565,96906410,62428472,16595436,68102437,69255765,24619760,50188404,93562257,83368048,71469330,91727510,84002370,58208470,90457870,19891772,14220886,14626618,66832478,69579137,4508700,59371804,50567636,22108665,89699445,55615885,70004753,21993752,36685023,91240048,10597197,65715134,82979980,10264691,93566986,65271999,28550822,69786756,65851721,7104732,20681921,34295794,54014062,53632373,749283,98943869,29516592,3088684,22200378,99917599,97379790,9860195,99965001,2331773,53393358,55770687,60581278,57393458,12416768,18001617,59501487,43933006,1197320,39801351,61712234,49271185,14363867,41092172,92604458,8099994,57163802,90158785,98648327,508198,84904436,55189057,99971982,81677380,99333375,31491938,95726235,30998561,88251446,44915235,83083131,321665,51715482,6157724,81805959,31727379,10358899,52613508,52204879,80555751,30366150,57020481,26507214,64055960,78561158,7300047,3633375,47090124,30653863,23134715,68316156,35192533,97011160,26292919,50668599,7955293,34698428,81172706,77694700,36580610,45919976,4095116,90090964,6986898,20568363,10366309,15948937,24826575,15075176,65017137,44473167,52293995,91141395,91281584,75820087,1431742,15015906,26998766,88698958,26664538,39986008,54606384,9188443,66903004,76671482,11581181,79322415,79880247,99549373,55669657,79922758,77620120,20122224,37620363,26744917,49597667,7423788,40152546,36189527,3294781,16054533,71300104,24168411,37224844,89811711,39201414,89804152,30218878,55960386,40865610,7182642,40356877,18504197,19101477,14947650,37166608,59981773,38022834,19376156,26397786,87160386,42782652,74110882,13468268,45790169,68702632,46540998,34698463,16424199,89046466,78602717,92071990,66428795,61263068,59614746,20765474,32590267,79657802,71316369,61859143,62693428,95957797,90654836,13173644,73235980,86460488,49641577,91255408,85571389,92033260,1250437,20140249,526217,25842078,99021067,94516935,28734791,12664567,92215320,61141874,3773993,8807940,15148031,12024238,6525948,32699744,33565483,26114953,66663942,97940276,64602895,56484900,65047700,76170907,2917920,70420215,94360702,30891921,20642888,47213183,49328608,1788101,87710366,47792865,57961282,27325428,33935899,53648154,93790285,43501211,47361209,4434662,18466635,66885828,2607799,7646095,67513640,58751351,7011964,19898053,78300864,20486294,61897582,415901,83155442,46851987,24733232,98739783,84293052,54058788,72357096,38494874,93359396,68128438,75153252,70541760,17976208,32161669,62051033,52060076,72278539,73168565,61728685,49598724,65081429,69848388,54663246,78218845,13862149,74743862,58224549,3183975,76960303,95581843,93053405,41092102,90310261,38256849,569864,67030811,60176618,32058615,11365791,69697787,58180653,42967683,9860968,4806458,7685448,47298834,36135,37183543,17385531,51588015,48360198,30771409,72732205,92541302,59910624,82427263,15064655,85242963,61741594,50007421,21533347,38341669,17894977,81855445,18806856,60106217,29188588,72793444,74614639,96726697,76825057,19272365,54199160,88904910,247198,27041967,44844121,54987042,11161731,3235882,36468541,7517032,26776922,22405120,44847298,77187825,53888755,37675718,176257,43357947,22879907,56461322,80251430,1204161,42307449,54263880,72725103,45306070,47708850,99861373,86242799,19457589,39553046,84684495,8791066,83150534,38061439,85117092,68939068,4985896,4091162,62357986,8729146,6497830,3880712,81274566,32159704,97783876,18131876,32250045,78785507,32426752,2891150,99729357,42073124,54427233,89637706,13348726,44245960,72238278,5640302,39373729,87720882,85711894,29401781,824872,18663507,95290172,12348118,82651278,874791,61815223,17365188,83302115,84349107,95010552,59318837,75135448,56153345,64098930,74137926,99604946,12303248,27187213,95251277,53084256,42237907,26275734,66319530,48673079,82726008,57359924,13470059,43152977,71083565,79191827,37659250,35456853,86118021,37435892,42692881,23740167,68824981,62803858,8115266,7502255,92398073,19939935,54762643,1375023,61928316,82532312,26493119,71522968,31904591,13819358,78589145,57658654,47887470,90004325,45075471,9575954,42199455,68875490,17386996,51466049,57241521,63152504,8055981,4515343,1022677,75229462,16684829,44664587,80316608,39171895,77787724,48395186,37280276,5482538,96420429,68694897,89419466,67031644,8373289,16387575,68041839,51426311,37192445,44348328,34044787,85695762,94076128,27411561,8696647,6521313,69605283,79738755,5267545,33431960,97641116,22450468,67451935,1239555,84840549,55602660,69136837,83269727,92530431,41481685,72614359,85023028,15902805,17081350,36845587,91990218,70372191,51472275,24314885,30811010,72495719,97281847,84166196,17146629,10961421,76315420,48774913,22997281,4668450,94711842,65454636,54232247,82327024,54868730,34667286,37560164,48892201,29510992,83133790,66045587,93515664,31161687,42644903,61960400,9398733,71965942,75052463,82897371,72738685,4798568,8128637,78549759,32151165,70727211,69355476,20867149,45990383,96318094,92787493,12571310,45381876,99224392,48658605,28787861,27185644,56515456,6111563,63015256,51149804,45407418,73021291,37891451,43506672,41442762,77300457,16567550,36930650,22129328,33304202,79942022,75407347,82886381,4978543,21001913,44627776,24915585,62115552,49882705,33699435,23194618,45049308,23432750,97057187,4069912,24953498,84127901,26734892,56473732,92998591,68110330,37957788,17058722,43322743,56424103,29932657,25636669,92867155,82052050,36812683,22435353,92692283,98653983,93057697,60393039,91938191,44550764,51507425,10649306,7066775,94911072,44846932,33237508,82178706,59405277,90061527,76703609,91957544,72358170,77301523,33061250,20836893,77898274,81774825,9259676,38510840,26863229,9599614,63372756,91802888,30543215,21289531,5822202,40781854,77072625,78884452,1569515,14731700,67474219,6038457,59329511,89996536,84406788,73109997,4199704,26102057,27625735,83948335,32274392,38009615,94539824,89499542,86821229,21397057,31733363,14723410,6819644,20645197,70782102,23110625,83747892 +47090124,38494874,79922758,64087743,85711894,15064655,29834512,27665211,65338021,67281495,92554120,35192533,98648327,38008118,55602660,61271144,20122224,91831487,88251446,32699744,61141874,33565483,96193415,62452034,16099750,36930650,9623492,36685023,33304202,70004753,1204161,65851721,75543508,69641513,88047921,43322743,86001008,98371444,80316608,95957797,80014588,39801351,16595436,54199160,1569515,78549759,84187166,58208470,31904591,24591705,45995325,7104732,74743862,62430984,4064751,60955663,34698428,50806615,4099191,14731700,77272628,52293995,57803235,1239555,25842078,90457870,30463802,64848072,76960303,61982238,16971929,69355476,17016156,45617087,83651211,8696647,33553959,57248122,2208785,32161669,99861373,60102965,29959549,26063929,53842979,42644903,62357986,57240218,97783876,95395112,50702367,46851987,33628349,9398733,47708850,55143724,2917920,247198,59177718,26392416,99524975,43376279,15535065,46870723,1431742,6793819,18699206,71657078,34493392,78561158,36780454,42947632,68702632,66903004,29029316,89419466,82427263,60106217,23569917,70800879,36580610,33249630,67084351,41481685,30163921,8913721,30090481,26998766,40027975,66611759,526217,4515343,34698463,72777973,40984766,63372756,40677414,66832478,41442762,89637706,37891451,82052050,34497327,79738755,55470718,22200378,37620363,52613508,31126490,43501211,55247455,28550822,48892201,45996863,63967300,93790285,54987042,76671482,56473732,99965001,82651278,17068582,4985896,71333116,19891772,30139692,77300457,4069912,33123618,51149804,52204879,44479073,99297164,77787724,37192445,95010552,30891921,62496012,14363867,35092039,77620120,94911072,58180653,64602895,10597197,28851716,28787861,92033260,37280276,112651,57241521,68644627,65880522,92215320,10358899,18806856,24058273,97641116,89078848,4119747,90272749,52060076,79191827,30543215,66319530,51047803,24915585,73617245,65038678,18411915,30218878,321665,72725103,37183543,34295794,83210802,93562257,74724075,68204242,92398073,84406788,4787945,61623915,99549373,1788101,48360198,26114953,86821229,29188588,74357852,51464002,51426311,28796059,41245325,90654836,89214616,54606384,56484900,176257,79657802,44473167,44889423,8128637,40686254,76434144,38658347,11365791,86242799,70596786,24826575,55770687,12571310,65074535,168541,54663246,22435353,7517032,21533347,4668450,10309525,3773993,66271566,62428472,51588015,10453030,99021067,72019362,81898046,68939068,76330843,38061439,77413012,2717150,81805959,53666583,98462867,42199455,44784505,87160386,19939935,30694952,75777973,3880712,8651647,23134715,14093520,40534591,75153252,55753905,93053405,44550764,6157724,64098930,43933006,50567636,47213183,28928175,9860968,6111563,43506672,96709982,37224844,74614639,6153406,16380211,62740044,76825057,69786756,75986488,50007421,56531125,59318837,80251430,66667729,8129978,66137019,73031054,68128438,14045383,19486173,47361209,61859143,94090109,79880247,17727650,62051033,77377183,669105,61373987,79821897,1375023,77284619,51590803,75229462,44983451,12416768,30569392,70036438,73392814,29819940,42782652,29932657,32159704,10366309,14947650,53648154,26776922,68694897,62693428,60581278,81677380,45428665,49882705,67793644,96906410,87598888,37957788,53888755,57393458,32250045,67227442,24733232,26507214,79806380,23432750,91938191,30771409,99658235,84840549,78589145,55669657,70367851,73168565,94360702,98948034,70372191,98130363,91802888,83083131,17976208,94595668,73786944,61263068,57359924,39373729,99690194,22766820,38365584,45790169,3509435,13348726,78218845,62936963,69136837,3233084,66045587,50188404,15948937,63152504,82726008,7066775,55960386,82979980,75820087,7919588,99125126,20681921,96726697,75135448,74110882,33797252,47298834,35456853,91727510,83302115,61741594,55615885,27185644,92692978,20885148,5970607,6525948,66322959,19101477,29065964,51466049,89499542,86460488,1197320,16019925,41380093,21673260,9257405,26139110,20002147,9886593,27325428,26664538,22997281,88653118,36808486,71083565,11923835,6221471,48260151,34236719,18504197,6808825,81855445,83948335,21070110,72614359,17081350,9599614,45237957,72373496,81274566,42307449,3235882,54427233,62762857,68816413,99515901,26863229,22942635,82779622,68102437,20645197,65017137,7687278,7229550,38341669,48774913,16097038,26292919,12348118,36505482,97561745,65715134,40781449,15075176,40152546,824872,569864,15163258,93566986,98739783,33895336,16424199,12664567,89996536,37659250,49328608,73222868,71300104,51472275,77312810,13231279,38645117,40197395,62115552,7182642,63628376,79942022,9259676,91990218,28734791,99604946,17764950,89804152,81172706,3294781,78766358,25636669,88698958,63059024,22405120,54762643,85116755,66428795,68824981,91141395,48658605,44177011,49597667,61712234,91664334,65271999,14326617,66116458,62490109,61897582,7502255,6038457,48088883,93359396,23848565,30395570,48673079,83789391,64055960,37560164,93933709,47893286,92692283,83368048,29994197,91957544,93515664,44348328,40356877,49641577,17365188,34946859,83155442,8055981,5482538,94711842,6986898,88444207,13173644,19376156,20642888,80246713,44060493,64157906,57658654,4091162,97940276,89046466,4806458,7955293,60430369,39553046,59109400,44846932,29516592,79136082,24314885,3233569,53632373,14349098,16054533,44915235,84349107,36135,15015906,65275241,52261574,20140249,13470059,62803858,59910624,20765474,8115266,74452589,34667286,15536795,69848388,53802686,84293052,43357947,96318094,56515456,9860195,22450468,34432810,77898274,33699435,53084256,16445503,37675718,93270084,42073124,87710366,87720882,29635537,18663507,45075471,44844121,55189057,85571389,66885828,10649306,19457589,67513640,37166608,99226875,54868730,70541760,51315460,29510992,67030811,86543538,58751351,85242963,32058615,99917599,80555751,47738185,10961421,4095116,54517921,94516935,7685448,30366150,4204661,37435892,749283,18833224,68875490,92530431,23740167,53393358,21919959,82886381,5111370,61815223,29401781,83533741,8373289,33431960,95290172,38022834,78785507,17385531,97281847,99971982,9188443,22879907,4434662,45049308,31733363,8729146,59371804,36312813,39201414,2331773,92803766,3633375,15902805,27187213,84002370,44842615,9058407,24953498,90013093,30653863,45848907,18001617,99224392,24619760,78300864,65047700,68000591,62552524,45919976,54014062,21993752,7646095,26275734,58224549,45306070,72793444,45407418,83269727,1022677,44245960,61960400,73124510,67474219,91281584,93057697,45990383,89811711,17386996,92353856,84684495,71316369,33336148,26493119,90090964,415901,56461322,11757872,70727211,70782102,58749,59614746,37739481,5822202,8807940,86022504,91240048,18131876,26744917,12024238,97011160,43045786,48893685,7011964,32426752,30764367,95251277,45381876,39847321,47792865,6505939,96735716,90158785,30811010,83133790,85117092,14626618,88904910,92071990,54232247,77187825,35996293,20486294,7423788,38009615,44664587,5640302,4508700,90310261,76540481,22113133,32274392,48675329,3487592,1250437,38256849,59501487,8634541,9175338,16567550,31161687,63506281,49236559,72274002,20836893,4199704,26734892,82339363,37501808,45667668,36139942,2607799,75052463,36753250,18783702,17037369,48395186,36812683,44481640,98943869,95726235,15148031,61728685,874791,29466406,68110330,32590267,49598724,21289531,92541302,51715482,60176618,74441448,56153345,42237907,92787493,81774825,92867155,11543098,61859581,69579137,33237508,10264691,70420215,42967683,78909561,27625735,50668599,9603598,57163802,67451935,92604458,66663942,23194618,82532312,71522968,19272365,56955985,16684829,59197747,9829782,17957593,72495719,13819358,1936762,6819644,26397786,26102057,19898053,36933038,30998561,69605283,63015256,9575954,18466635,17857111,2204165,20568363,6521313,31727379,89217461,57020481,94539824,68316156,77301523,7300047,66250369,39171895,74137926,49271185,17058722,69255765,97379790,86301513,59981773,5267545,41092172,41092102,47887470,76703609,27041967,90004325,73235980,81853704,92998591,6871053,62232211,99333375,4798568,89699445,16405341,24168411,33061250,8099994,96852321,57961282,21001913,39986008,94076128,65454636,83747892,23110625,3088684,75407347,53547802,22108665,14723410,98653983,67031644,2891150,54058788,71965942,59329511,72357096,69697787,17146629,51507425,5073754,3183975,36845587,45794415,13468268,88130087,33935899,20867149,42692881,40865610,73021291,22721500,84127901,77694700,99729357,17539625,77072625,13862149,27411561,22129328,38510840,55850790,34044787,72238278,32151165,79322415,82897371,35512853,96420429,82178706,44627776,36468541,21397057,46540998,85023028,30787683,60393039,4978543,16387575,56424103,14220886,75128745,65081429,85695762,68041839,72358170,8733068,71469330,40781854,86118021,11161731,78884452,78602717,72278539,76170907,72732205,44847298,43152977,91255408,6497830,8791066,95581843,71920426,84166196,59405277,12303248,5832946,11581181,33201905,83150534,67124282,508198,76315420,82327024,84904436,36396314,72738685,54263880,73109997,36189527,97057187,86798033,90061527,61928316,31491938,17894977 +73168565,68816413,10358899,67793644,63967300,16097038,3183975,40865610,47792865,53084256,49598724,5111370,4434662,17058722,26102057,70596786,20765474,35192533,31904591,82327024,72495719,4508700,76671482,70541760,15015906,79806380,54199160,75543508,37620363,77312810,71657078,18131876,8129978,6986898,415901,24168411,96726697,79136082,33628349,14731700,89637706,30395570,26392416,50188404,96193415,30891921,37957788,92398073,28928175,19101477,1197320,56461322,39201414,9886593,19272365,6793819,60106217,59371804,38658347,61141874,8913721,65715134,64055960,59981773,69786756,68824981,24058273,89214616,8128637,112651,27625735,8115266,2717150,98739783,19376156,99524975,93057697,43357947,72777973,77072625,8807940,21533347,8696647,26063929,81677380,44842615,14349098,78602717,56531125,55960386,82339363,62936963,55247455,50567636,38061439,9398733,17081350,29834512,68644627,39553046,53802686,23848565,25842078,45996863,98943869,7517032,16405341,37659250,3509435,40152546,42644903,98462867,44846932,77300457,20486294,24619760,72357096,35996293,10366309,51047803,91802888,97641116,17764950,77787724,84127901,69605283,33304202,93515664,22435353,10597197,15064655,19486173,4119747,76170907,15535065,7687278,39171895,7646095,61263068,92692978,84349107,13468268,21001913,42782652,30463802,8651647,99917599,97561745,84904436,40197395,52060076,6505939,9623492,68694897,51472275,43506672,95957797,53632373,94076128,64087743,68204242,83789391,84684495,89078848,92071990,36468541,5970607,16099750,53547802,26507214,46870723,9575954,82052050,66045587,43501211,22450468,49641577,50668599,95581843,77272628,20002147,49271185,4069912,72373496,92604458,37675718,66903004,83533741,4985896,51315460,49882705,61373987,72732205,4515343,81898046,22997281,14626618,11581181,57241521,31733363,37891451,14326617,35512853,55470718,508198,55143724,78766358,42967683,69355476,65851721,67451935,93053405,74441448,17365188,11757872,58180653,81855445,70800879,80555751,34493392,1569515,68000591,46851987,92787493,47298834,31727379,78218845,68875490,62232211,74452589,71469330,81274566,88653118,57020481,48893685,77284619,48774913,38022834,45407418,57240218,89419466,55189057,6497830,66250369,31491938,78589145,66885828,4668450,24591705,59109400,44473167,36930650,65081429,65017137,63372756,19457589,1204161,62051033,60430369,10309525,526217,26292919,43376279,2204165,62693428,45237957,91664334,95395112,62496012,13862149,91990218,56424103,82726008,75153252,30998561,32590267,81853704,33201905,76960303,89046466,32159704,77187825,49236559,29510992,99658235,44983451,19939935,27041967,45381876,62430984,31126490,99021067,61271144,48892201,44847298,4798568,86118021,17068582,99515901,69697787,22942635,23194618,56955985,57248122,70004753,14093520,26998766,29819940,35092039,52261574,87160386,55753905,41481685,44889423,59177718,85695762,29466406,21673260,4204661,16019925,81805959,30653863,42692881,73617245,30090481,17146629,94595668,3294781,37183543,53393358,73124510,669105,45995325,66832478,60102965,3233084,67281495,34432810,30811010,72238278,24733232,50007421,7423788,23432750,45794415,67030811,30366150,54427233,56515456,78549759,7919588,79657802,97940276,53666583,55602660,36753250,62452034,51466049,93933709,23110625,57658654,40781854,34044787,749283,17037369,33249630,66611759,16595436,37166608,73786944,67227442,83302115,39801351,12416768,94090109,91938191,38494874,58749,77413012,70782102,66271566,82178706,5640302,68110330,89996536,4978543,38008118,6221471,36812683,71333116,99690194,17016156,2917920,33237508,15948937,99297164,16380211,24953498,88444207,83747892,22108665,10649306,68102437,52293995,13231279,79821897,26863229,13173644,87710366,36933038,61982238,32250045,66428795,61859581,43045786,79942022,85116755,62803858,27325428,51507425,82897371,1250437,2208785,89217461,39373729,84840549,3088684,44479073,54987042,62552524,44664587,14947650,9058407,40677414,30163921,12348118,99333375,59501487,59614746,168541,33797252,45990383,7685448,17539625,56473732,72278539,65454636,29959549,6157724,77694700,73392814,57803235,3487592,16445503,6153406,49597667,7011964,83210802,87598888,56153345,48088883,38645117,7182642,12664567,27411561,94516935,34698463,65047700,30764367,33061250,34698428,30787683,40781449,54058788,42307449,62490109,85711894,74357852,68316156,86460488,22129328,94711842,66322959,18466635,94911072,40027975,90004325,71920426,64848072,43322743,65038678,68939068,8634541,73031054,79738755,62357986,86543538,38256849,92692283,84406788,95726235,1431742,47361209,51715482,49328608,874791,75128745,45790169,54014062,41380093,4064751,53842979,92033260,21289531,57393458,9257405,7229550,60955663,98648327,59197747,45049308,74110882,54762643,16387575,58751351,26734892,70036438,87720882,82532312,89699445,63059024,78909561,37192445,99965001,35456853,71300104,99861373,18663507,64157906,33935899,5482538,98130363,27665211,90310261,91957544,18806856,82779622,4095116,64098930,61728685,76434144,98371444,84187166,45848907,8373289,97011160,86301513,83651211,9188443,70372191,1375023,80251430,18783702,88047921,92803766,74743862,75229462,54663246,20645197,58208470,77377183,62740044,12303248,63628376,8729146,77301523,84002370,26139110,43933006,97783876,50806615,29188588,95290172,8733068,66663942,24915585,15902805,3233569,30694952,569864,23569917,44177011,2607799,67031644,33895336,36135,76540481,9860195,9603598,54606384,28734791,85023028,48395186,42073124,71316369,51149804,86242799,25636669,21993752,16684829,14045383,59910624,69579137,16567550,57163802,20122224,72738685,17727650,78300864,33123618,20885148,80316608,80014588,34295794,45667668,81172706,18504197,13348726,69136837,7955293,62115552,47090124,73235980,44784505,79922758,47738185,44348328,92541302,37560164,94360702,22766820,74137926,86022504,70420215,37224844,28851716,92215320,70367851,85242963,20642888,61815223,5267545,68041839,61960400,61623915,36808486,33553959,78884452,30569392,44550764,92867155,88698958,63015256,321665,86798033,29029316,59405277,48675329,83083131,11161731,10961421,247198,40686254,16424199,19891772,68702632,26114953,47213183,88251446,8099994,36396314,4787945,22405120,76703609,5822202,89499542,30218878,51426311,6808825,90654836,6521313,32161669,26275734,65338021,71083565,98653983,79880247,72019362,88904910,4199704,99729357,21070110,44844121,90457870,67084351,22113133,1239555,40356877,99224392,86001008,17976208,79322415,16971929,16054533,12571310,91141395,70727211,9175338,33565483,32274392,89804152,18411915,15075176,6525948,59318837,92353856,55770687,30771409,91255408,42237907,53648154,45919976,30543215,96318094,91727510,14220886,26664538,23134715,76330843,37501808,11923835,83948335,48360198,78785507,83368048,26397786,66137019,39986008,32058615,31161687,6038457,41092102,83150534,82886381,37739481,91831487,72725103,176257,4099191,75986488,44627776,90272749,36312813,96420429,75052463,44245960,55615885,3773993,82979980,24314885,99549373,99125126,96906410,17857111,6819644,824872,82427263,36189527,17894977,44481640,66667729,74614639,44060493,60581278,29065964,77898274,93270084,21919959,41245325,4806458,91281584,68128438,63506281,90061527,59329511,6111563,5073754,40534591,2891150,41092172,15536795,45306070,90090964,84293052,6871053,69848388,32426752,3235882,64602895,47708850,36685023,29994197,18833224,77620120,86821229,53888755,48260151,10264691,34497327,69255765,61741594,63152504,61928316,81774825,74724075,22879907,39847321,52613508,95010552,15163258,34667286,37280276,36580610,9259676,92998591,75777973,7104732,1788101,27185644,30139692,92554120,72793444,48658605,21397057,91240048,51588015,44915235,26776922,45075471,13470059,28787861,48673079,55850790,66319530,65275241,37435892,33699435,95251277,92530431,76825057,85117092,20140249,93359396,36845587,61897582,41442762,96709982,42199455,2331773,93566986,50702367,67474219,62762857,54868730,62428472,28550822,26744917,73222868,99604946,56484900,61859143,9599614,54263880,72614359,1022677,20836893,24826575,67513640,38365584,93562257,79191827,54517921,66116458,38341669,38510840,33431960,45428665,7300047,65271999,20681921,3633375,36780454,17385531,96735716,23740167,11543098,89811711,83155442,1936762,78561158,67124282,98948034,57359924,14723410,17386996,9829782,60176618,20568363,51590803,75407347,32699744,88130087,18001617,65880522,10453030,51464002,18699206,72358170,97281847,97379790,80246713,94539824,75820087,8791066,75135448,22200378,7502255,29932657,83133790,36505482,34236719,69641513,4091162,3880712,97057187,93790285,55669657,20867149,29401781,34946859,17957593,43152977,73021291,40984766,47893286,85571389,57961282,58224549,8055981,27187213,38009615,32151165,22721500,19898053,72274002,12024238,5832946,71522968,83269727,42947632,61712234,46540998,15148031,11365791,96852321,99226875,7066775,47887470,26493119,28796059,9860968,90158785,73109997,33336148,82651278,52204879,29635537,76315420,71965942,99971982,45617087,65074535,29516592,90013093,54232247,14363867,60393039,84166196,36139942,13819358 +16097038,73392814,14947650,7011964,7300047,99549373,43152977,90457870,32699744,95957797,45617087,89214616,91802888,95290172,68644627,22450468,44550764,88698958,65271999,96726697,96193415,44889423,89996536,74614639,4806458,45407418,67793644,63372756,37183543,21289531,86242799,66667729,48893685,4069912,14363867,55247455,89078848,51472275,44664587,61815223,62430984,57248122,73617245,89419466,29834512,70004753,51464002,17957593,31126490,51047803,569864,99226875,14093520,57241521,16019925,13819358,1250437,59177718,44983451,51588015,62452034,2331773,36780454,8807940,92554120,37166608,85242963,74724075,69255765,82178706,45790169,98130363,15015906,99965001,14349098,24058273,50702367,33237508,22129328,14326617,98371444,91281584,72725103,749283,54517921,70036438,76671482,33797252,97783876,21001913,45381876,22766820,33249630,112651,73235980,83302115,34667286,29994197,10358899,73222868,97379790,77072625,415901,83747892,18504197,3487592,93566986,72793444,83789391,4064751,91990218,37891451,22942635,15064655,18833224,38658347,36812683,72777973,26139110,9257405,16684829,80555751,74137926,47361209,58749,5111370,52204879,20140249,51426311,29959549,3183975,31904591,4798568,9623492,59318837,44842615,20568363,68102437,90272749,62936963,64098930,86001008,61982238,17068582,41481685,61623915,43322743,33628349,60102965,92692283,51466049,85571389,32426752,43045786,7955293,6793819,59405277,48260151,8099994,34493392,99224392,1431742,65454636,36580610,60430369,27665211,43376279,45990383,47708850,23134715,59981773,16445503,66885828,12348118,22721500,83269727,16099750,63967300,40197395,54427233,62496012,60955663,82886381,8913721,83368048,90061527,50188404,59614746,29029316,247198,49641577,26776922,85116755,32161669,26998766,38510840,21070110,97641116,65338021,95395112,36933038,68939068,53842979,7182642,17016156,5640302,22113133,4099191,88047921,58208470,17365188,10597197,2717150,78218845,508198,13468268,17764950,15163258,91831487,7646095,20642888,48360198,54762643,6153406,14220886,46851987,60581278,30998561,23110625,80316608,70596786,321665,72738685,99515901,76960303,55850790,82532312,88904910,76540481,56473732,73031054,68694897,99729357,29188588,39553046,39986008,6986898,1204161,3233084,49328608,57240218,78909561,35192533,84684495,78766358,10309525,44627776,26292919,67451935,51315460,62740044,94595668,59501487,77272628,54663246,20122224,15075176,78589145,35996293,67281495,6497830,53632373,98948034,33699435,96735716,51590803,34698428,29819940,55960386,72278539,71657078,29466406,69697787,84187166,64087743,93515664,58751351,2891150,83651211,90158785,31161687,92692978,16405341,3233569,66045587,47893286,93270084,14626618,18466635,55602660,77694700,42073124,65047700,18806856,4668450,98739783,9175338,1197320,72357096,52060076,13231279,93053405,24314885,55189057,59197747,37620363,27041967,84293052,17386996,92353856,45995325,526217,25842078,44479073,33304202,45919976,82779622,9058407,61859581,35456853,90090964,88653118,26275734,44844121,71083565,91727510,40781449,63015256,43357947,57961282,40027975,59371804,54987042,89046466,92033260,82427263,72274002,98462867,9188443,87710366,27625735,16380211,6505939,5482538,19939935,89804152,39171895,41442762,9599614,55669657,82979980,94539824,5822202,4985896,5970607,79821897,40534591,77312810,57020481,42237907,12303248,44245960,67227442,39847321,92530431,89217461,18663507,66250369,23432750,98943869,56531125,82339363,54014062,38645117,26114953,18699206,45237957,99524975,92604458,35512853,86118021,38009615,83150534,82052050,10366309,7502255,84002370,61263068,14045383,29065964,57163802,4508700,92803766,63059024,14723410,47792865,92398073,22200378,36930650,39201414,62232211,81172706,62762857,4787945,40984766,66611759,49597667,72373496,90013093,33895336,38061439,30764367,99917599,78549759,10453030,41092102,77413012,14731700,41380093,36135,94090109,26392416,30569392,73786944,61712234,18131876,22108665,11161731,99690194,30653863,30463802,40781854,76330843,42307449,3509435,91664334,45306070,28796059,80014588,29635537,99658235,66319530,31733363,51507425,73168565,45428665,10649306,20486294,97561745,72019362,36753250,68204242,40677414,51715482,9259676,6111563,71333116,7423788,56153345,61960400,68816413,64848072,16595436,84406788,6808825,52261574,26063929,72614359,84349107,61271144,27187213,64055960,12416768,30891921,47090124,36505482,13862149,55470718,93562257,63506281,96709982,99021067,81677380,20002147,874791,87720882,669105,92787493,44348328,15535065,69136837,53084256,98648327,39801351,68824981,71469330,12024238,29510992,29516592,15148031,11757872,77187825,1375023,68041839,92998591,50806615,30771409,4091162,68000591,53666583,45794415,66832478,74452589,61728685,36189527,23194618,74357852,77787724,63628376,2208785,48675329,56515456,19891772,11581181,4515343,78561158,43501211,8651647,61741594,34044787,85711894,44847298,4434662,91938191,55753905,3880712,62803858,81805959,16971929,40686254,66137019,83210802,24168411,40152546,17894977,20645197,78785507,65275241,86543538,61859143,67030811,2917920,83948335,28787861,72732205,99297164,73124510,75543508,81855445,81898046,78884452,28550822,71300104,82897371,75777973,30218878,25636669,32590267,24619760,37435892,91141395,18001617,75820087,26493119,64602895,32274392,48088883,3773993,5267545,96420429,89811711,8373289,46540998,42692881,36808486,12571310,95726235,79136082,70420215,84840549,75229462,94076128,42644903,55143724,97940276,92215320,8129978,27411561,88251446,34295794,16054533,42967683,7919588,62115552,38008118,24591705,7517032,45667668,44177011,81774825,77620120,82726008,79942022,42199455,68110330,30366150,97281847,49271185,1239555,47887470,45848907,57658654,4095116,8115266,49598724,52613508,77300457,65081429,30395570,84127901,66663942,40356877,82651278,28851716,30543215,30090481,53648154,62051033,8791066,8733068,17385531,38022834,62490109,94911072,70727211,10264691,83155442,9603598,18783702,81274566,85117092,43506672,19457589,48658605,75153252,12664567,85695762,37560164,13470059,17539625,69605283,30787683,83533741,55770687,36685023,41245325,54606384,93933709,66903004,57803235,20765474,76825057,58180653,77898274,78602717,34236719,44473167,7685448,22879907,26744917,8696647,6525948,38365584,26507214,3294781,31491938,77377183,72495719,90654836,95010552,33553959,37280276,19486173,74743862,59109400,98653983,22997281,76170907,79322415,38341669,86798033,26863229,45075471,7229550,63152504,28928175,65880522,54058788,47213183,21993752,32159704,9575954,60176618,76703609,83133790,70367851,86022504,54868730,10961421,13173644,11923835,96852321,22405120,56484900,17727650,69641513,88444207,41092172,91957544,50567636,72238278,37224844,11543098,19376156,99333375,56461322,59910624,74110882,9398733,33336148,65715134,70541760,68875490,53802686,27325428,94360702,79880247,48892201,2607799,47298834,79191827,34497327,29932657,51149804,26734892,168541,35092039,93359396,79922758,99971982,42947632,8634541,62693428,68128438,69786756,18411915,23740167,50007421,1569515,32250045,33565483,6157724,48395186,176257,6038457,42782652,56424103,44784505,99861373,82327024,1022677,79657802,13348726,72358170,31727379,69355476,30694952,6819644,67474219,44481640,24953498,87160386,34698463,23569917,94516935,15902805,36312813,97057187,75135448,75986488,70800879,80246713,96906410,75052463,36468541,53547802,86460488,69579137,66428795,66322959,58224549,40865610,39373729,71522968,99125126,21397057,84904436,15536795,38494874,76434144,26664538,44846932,87598888,2204165,21673260,5073754,54232247,90310261,26397786,57393458,7104732,54263880,6221471,89499542,45996863,91240048,9860968,62552524,8729146,77284619,43933006,66271566,79806380,17976208,19272365,65074535,24826575,78300864,36845587,9886593,17146629,33935899,37659250,46870723,29401781,19101477,68316156,65851721,67084351,90004325,11365791,50668599,49236559,3088684,89637706,85023028,37675718,70372191,4119747,65017137,36139942,47738185,33123618,4204661,20681921,24733232,88130087,33201905,57359924,17058722,3633375,23848565,37957788,89699445,68702632,30811010,37501808,20885148,38256849,93057697,20867149,94711842,62428472,86821229,1788101,66116458,44915235,71920426,30163921,48774913,62357986,71316369,61141874,34946859,70782102,33431960,67124282,1936762,15948937,77301523,86301513,36396314,24915585,92867155,71965942,99604946,91255408,61897582,95251277,76315420,8128637,97011160,53393358,55615885,17037369,96318094,56955985,27185644,59329511,54199160,19898053,6871053,9860195,93790285,3235882,60393039,7066775,49882705,61928316,32058615,9829782,64157906,81853704,17081350,67513640,80251430,21919959,92071990,824872,22435353,6521313,79738755,44060493,83083131,16424199,48673079,8055981,65038678,74441448,73021291,5832946,67031644,52293995,16567550,60106217,92541302,20836893,32151165,33061250,37739481,34432810,45049308,4199704,21533347,84166196,26102057,17857111,69848388,61373987,16387575,4978543,95581843,75407347,28734791,7687278,37192445,75128745,53888755,30139692,73109997 +64087743,33237508,73031054,77312810,26734892,20122224,40197395,27665211,6871053,65074535,87160386,57020481,31904591,29834512,80555751,6505939,71657078,51588015,80316608,67793644,35092039,73168565,18699206,17957593,16445503,94360702,4787945,89046466,55669657,37659250,69136837,78549759,98130363,14326617,14045383,98371444,14349098,36812683,47090124,7011964,99658235,22108665,40152546,16405341,96193415,70596786,97641116,12348118,51047803,96709982,84349107,91802888,31161687,69848388,569864,87598888,97783876,42692881,41481685,44844121,74110882,11581181,49328608,53648154,51507425,45790169,99021067,47361209,17016156,72732205,85242963,39171895,43376279,55143724,65454636,14220886,16019925,75153252,81805959,77072625,66611759,247198,32699744,29994197,43506672,55753905,83150534,73124510,112651,60581278,30463802,17058722,70782102,8696647,68816413,29959549,12571310,92692978,47298834,66903004,67281495,86301513,12416768,28734791,77284619,89996536,21533347,37183543,83651211,21289531,49641577,83155442,66137019,24058273,69255765,74743862,874791,87720882,26275734,95251277,46851987,8128637,79657802,61741594,68102437,96726697,36780454,82327024,99333375,9175338,17068582,93933709,93790285,56461322,321665,59614746,54199160,14723410,13231279,82339363,59501487,22129328,62051033,94911072,81855445,53393358,52204879,68702632,73786944,92998591,10358899,40356877,63967300,40781449,38061439,61859581,80014588,35192533,76170907,43357947,43501211,81274566,73392814,56473732,88047921,89214616,9603598,54987042,43045786,38494874,60430369,6793819,65038678,76540481,40686254,79806380,16380211,70800879,4095116,22997281,77187825,3773993,44481640,79136082,19939935,56515456,26998766,20140249,508198,57241521,44847298,42967683,54606384,9623492,33699435,1431742,51464002,95290172,13468268,68204242,33553959,17386996,36685023,37166608,14093520,34698428,22450468,93359396,86821229,68644627,44983451,77620120,10597197,44177011,29188588,84002370,76703609,90272749,74614639,37280276,51715482,36468541,67227442,39373729,91957544,16099750,61960400,19101477,18131876,63059024,7919588,4099191,79191827,53547802,33304202,93562257,749283,30764367,39801351,28851716,36312813,49597667,72738685,61728685,13862149,70727211,6986898,75777973,7300047,91281584,15148031,73235980,50188404,99524975,95395112,70036438,62452034,86242799,65851721,83083131,47893286,65338021,78218845,7685448,10309525,9398733,5970607,22766820,30543215,8651647,54663246,37501808,35996293,1197320,95726235,6525948,44889423,27041967,2891150,32590267,62430984,71469330,66250369,74724075,89811711,75128745,53084256,65715134,81898046,98739783,32159704,87710366,44846932,5832946,18783702,45237957,9886593,44842615,4119747,90310261,31491938,3233084,18001617,19891772,19486173,84684495,32250045,15535065,24591705,72725103,92398073,95957797,50007421,79942022,29466406,71300104,77413012,55770687,84187166,23848565,36845587,27187213,44348328,57248122,60955663,61623915,56424103,37891451,20486294,47792865,45617087,59197747,84293052,83210802,23432750,68316156,71083565,8099994,6497830,74137926,57359924,61271144,17764950,54058788,76960303,89804152,46870723,20765474,45667668,26392416,42947632,25636669,18663507,88653118,3880712,82886381,52261574,91938191,55247455,6819644,2204165,50567636,99125126,16097038,30787683,94076128,37675718,30218878,78602717,44550764,17365188,75543508,92692283,24733232,66319530,99515901,73617245,40781854,64848072,73222868,5482538,79322415,15015906,48395186,23134715,44784505,22942635,48774913,83789391,75135448,93566986,60102965,81677380,43152977,26102057,57163802,98648327,26063929,48360198,8129978,13348726,23740167,68041839,77787724,68694897,1239555,20002147,85571389,92530431,78766358,94090109,59329511,77301523,30366150,40534591,52293995,8634541,99729357,9860195,80251430,77300457,9259676,9188443,70541760,58749,37620363,26292919,50702367,15902805,7423788,10366309,59177718,72357096,37957788,79738755,38008118,96735716,16567550,34432810,23569917,59109400,3487592,4668450,59371804,88251446,44245960,69355476,45428665,29510992,62115552,36580610,33201905,48892201,78785507,38645117,77898274,33628349,76330843,11161731,62936963,62496012,56153345,36753250,99549373,68000591,20681921,4798568,75986488,16424199,63152504,8791066,48675329,83133790,42073124,82726008,18504197,44473167,55960386,2917920,26744917,26397786,63628376,824872,2717150,14626618,168541,82178706,61141874,82779622,53888755,66832478,8807940,57803235,21993752,73109997,31126490,37192445,4434662,99604946,43322743,55850790,96852321,61859143,70004753,26664538,61712234,44664587,40027975,66271566,61815223,69605283,36139942,34497327,91141395,48260151,72777973,5111370,48658605,23110625,76315420,45990383,72019362,68875490,81172706,91990218,65017137,31733363,62490109,37560164,79821897,42199455,59910624,53632373,90457870,77272628,19376156,67030811,41092172,76434144,3233569,7502255,22721500,66116458,99297164,24619760,6808825,55189057,82532312,40677414,54232247,26139110,63015256,67451935,15064655,33797252,20568363,88904910,72614359,16595436,7104732,98948034,85023028,57240218,40865610,61897582,12303248,26114953,71333116,54014062,79922758,1250437,4064751,96318094,7517032,7066775,1204161,62232211,93270084,33123618,94516935,63506281,51590803,57658654,86022504,47708850,96906410,4091162,72373496,99226875,30395570,56531125,13819358,69786756,22879907,77377183,12024238,90654836,49236559,89419466,89217461,89637706,14731700,17539625,97057187,33431960,14363867,4806458,49271185,29401781,6153406,3509435,20867149,65271999,74452589,32161669,3088684,415901,6038457,28787861,33249630,4985896,84127901,65047700,88444207,67124282,88698958,20885148,91255408,66667729,33895336,99965001,68939068,76825057,26507214,11543098,78909561,48088883,86001008,32274392,86543538,65081429,26776922,42307449,50806615,47213183,32151165,1022677,50668599,84904436,70420215,91664334,51426311,45306070,20642888,62693428,3183975,70372191,71920426,60393039,3294781,21001913,34493392,8115266,24915585,59405277,24953498,22113133,19457589,72278539,39847321,24168411,13173644,62552524,7229550,13470059,9860968,14947650,9599614,5640302,34295794,64602895,68110330,43933006,10649306,64055960,42237907,71965942,53842979,95581843,54868730,47738185,22435353,30891921,66428795,4515343,2208785,72793444,6521313,69641513,1375023,18466635,92071990,27185644,61982238,3235882,55602660,74357852,53666583,45996863,91831487,90090964,75229462,49598724,61373987,91727510,55470718,22405120,56484900,52613508,34698463,36930650,30163921,42644903,89078848,93057697,4069912,11757872,92803766,84166196,36505482,51466049,28796059,89499542,96420429,51472275,63372756,72495719,9058407,97561745,11365791,67513640,72274002,27411561,7955293,30653863,59981773,57961282,48893685,5267545,16054533,29516592,38658347,90158785,98943869,85117092,36933038,92787493,16971929,94711842,60106217,29029316,20836893,36189527,86460488,33935899,26493119,99917599,6221471,64098930,1569515,38341669,27625735,99224392,30090481,30771409,84406788,25842078,38022834,97281847,92604458,51149804,8373289,54427233,79880247,75820087,51315460,78561158,42782652,34946859,21919959,32058615,669105,84840549,68128438,30694952,6111563,90061527,19272365,45407418,54263880,5822202,29065964,97379790,66663942,526217,92554120,65880522,31727379,21070110,85116755,71522968,39201414,69579137,4508700,36396314,45794415,73021291,90004325,39986008,76671482,18411915,81853704,54762643,52060076,35456853,18806856,34236719,45848907,58180653,45995325,16684829,10961421,92215320,93053405,34044787,28928175,8733068,38510840,36808486,93515664,9829782,98653983,81774825,59318837,9257405,19898053,75407347,45075471,58224549,35512853,65275241,37739481,83302115,99690194,28550822,21673260,17081350,66885828,17857111,72358170,78884452,62357986,98462867,29932657,95010552,36135,94539824,83368048,15536795,74441448,62803858,10453030,48673079,44060493,82979980,61263068,8055981,37224844,58751351,7687278,9575954,20645197,16387575,67084351,30998561,41442762,69697787,33061250,72238278,77694700,5073754,82651278,29819940,83747892,8913721,82427263,99861373,6157724,78589145,23194618,11923835,12664567,44915235,38365584,97940276,57393458,41092102,30569392,18833224,67031644,45381876,38009615,88130087,66045587,10264691,68824981,3633375,86118021,66322959,89699445,82052050,86798033,30811010,33565483,24826575,40984766,92541302,53802686,71316369,82897371,62740044,26863229,30139692,85695762,85711894,4204661,60176618,70367851,46540998,75052463,62428472,7646095,58208470,1788101,41245325,22200378,90013093,44627776,92353856,15075176,80246713,17727650,47887470,83533741,7182642,54517921,2607799,45919976,99971982,78300864,27325428,92033260,29635537,17146629,83948335,17894977,4199704,92867155,38256849,39553046,176257,41380093,94595668,91240048,56955985,15948937,97011160,1936762,44479073,34667286,32426752,62762857,49882705,15163258,61928316,45049308,67474219,83269727,8729146,17976208,64157906,37435892,21397057,24314885,4978543,2331773,33336148,17385531,55615885,17037369 +44889423,98948034,44550764,96726697,44844121,99549373,51047803,20486294,78602717,1788101,68644627,74110882,92398073,76960303,14723410,43322743,81677380,68041839,77072625,17894977,39986008,43357947,72777973,1197320,60430369,14731700,30787683,30543215,48673079,79821897,67451935,56473732,93053405,4099191,82886381,85116755,65081429,37183543,10961421,247198,31904591,77898274,29994197,21993752,52261574,19457589,44842615,93057697,94711842,42073124,63059024,49641577,66667729,11161731,2917920,14626618,25636669,37620363,80316608,56424103,40781854,88698958,93933709,35192533,67281495,8099994,4668450,53648154,72725103,87720882,17764950,63967300,74743862,5822202,94090109,29959549,70036438,72019362,41481685,35456853,36505482,72357096,84684495,92604458,17727650,88653118,7104732,82979980,62430984,62552524,45407418,32590267,14326617,63372756,97783876,77620120,38658347,20867149,57241521,28796059,57393458,72278539,57961282,79191827,45237957,65271999,1431742,59614746,17016156,66428795,24619760,86242799,67793644,33431960,59371804,66611759,8729146,91255408,43376279,59177718,81274566,168541,36580610,45306070,97641116,26998766,69848388,47090124,80555751,93562257,9575954,34698463,32274392,13231279,22108665,96193415,59501487,10309525,91802888,43933006,98130363,18504197,15148031,84293052,62740044,49236559,6819644,21070110,61623915,53632373,93515664,36812683,63152504,55189057,46851987,68702632,68694897,26392416,65275241,8791066,39801351,13470059,4119747,22766820,38645117,85023028,54014062,92692283,89078848,8115266,23194618,34236719,13468268,84166196,83155442,36930650,62115552,51590803,61373987,54517921,90457870,99965001,22721500,83150534,69786756,5970607,19486173,874791,8373289,69605283,93566986,30998561,48260151,75777973,29188588,5111370,7300047,7502255,5640302,34698428,86022504,55143724,57248122,99226875,51472275,44846932,3233084,40984766,12348118,54427233,9603598,40677414,669105,39171895,36753250,26139110,16387575,62803858,6157724,33304202,73786944,4985896,19898053,46540998,26114953,59329511,30569392,43045786,94539824,13819358,95290172,43501211,46870723,95957797,72358170,89217461,54762643,62496012,43152977,40686254,99224392,48774913,23848565,49271185,5482538,17539625,91957544,64848072,36780454,49328608,16097038,59318837,2204165,47887470,9829782,12664567,82897371,43506672,2717150,40781449,10366309,41092102,83789391,90061527,27041967,91727510,91831487,19891772,44348328,18783702,28928175,80251430,22129328,99658235,14093520,69355476,22200378,60581278,22879907,73235980,7646095,47213183,9188443,6808825,9259676,7229550,53393358,15536795,11543098,38365584,82427263,66885828,83210802,61859581,84904436,18466635,68204242,61859143,8696647,50188404,30764367,44983451,56515456,4069912,58224549,38061439,29819940,23110625,59405277,45848907,4515343,58751351,27665211,44245960,29834512,90272749,31161687,86001008,45794415,70782102,28734791,2208785,73031054,58180653,20642888,13862149,90004325,61271144,81853704,73168565,37957788,99333375,86301513,19939935,6986898,64087743,22435353,3773993,38009615,74137926,7955293,33237508,49598724,81898046,65017137,15902805,88047921,16380211,39553046,81855445,61741594,89996536,89811711,54199160,12416768,47361209,30218878,65454636,85695762,26507214,70004753,42782652,33895336,37435892,76315420,32161669,54663246,73124510,96318094,71920426,50702367,9599614,75153252,66271566,6497830,49597667,18411915,82726008,97011160,17857111,33565483,54987042,62936963,77187825,79657802,40534591,65038678,67030811,17058722,89699445,4978543,67474219,36685023,99021067,55615885,86798033,88251446,44664587,98371444,17068582,2607799,76434144,62051033,66045587,98462867,42644903,97940276,77272628,9623492,68000591,69136837,41245325,51464002,8651647,85711894,78589145,52293995,34493392,70727211,75986488,99604946,1569515,36808486,38494874,54058788,321665,22450468,79880247,29029316,749283,34497327,20140249,71333116,20568363,9886593,4787945,34667286,72738685,93790285,78884452,9175338,82327024,14363867,16054533,45996863,68875490,33699435,98739783,4434662,73617245,35092039,48892201,508198,72373496,92998591,55850790,33628349,76703609,82178706,16971929,75229462,76540481,17146629,11365791,31126490,61815223,58749,95726235,26863229,7517032,77284619,569864,28550822,62452034,40356877,98653983,2891150,66250369,72495719,40152546,35996293,73222868,45049308,6153406,24826575,26292919,42307449,4091162,9860195,61982238,55669657,1239555,6038457,60955663,71965942,37739481,27625735,4199704,91990218,24591705,26102057,7687278,72274002,48395186,1250437,30694952,62490109,6525948,33553959,176257,86821229,97057187,92033260,96852321,29065964,12571310,75543508,3633375,92541302,21397057,6793819,18663507,31733363,54232247,44177011,45428665,67124282,51315460,50806615,89046466,77312810,56153345,90013093,22997281,94076128,14045383,65338021,63628376,94360702,24168411,27411561,3509435,70596786,57020481,20645197,89214616,45075471,92071990,59910624,8634541,61263068,30891921,75135448,67031644,77300457,82651278,36312813,92530431,72614359,66903004,17037369,1936762,44627776,19376156,72238278,64157906,55247455,16019925,86460488,19101477,58208470,8055981,55470718,28851716,23569917,24953498,30139692,15064655,50007421,79922758,24733232,37224844,26776922,8807940,71522968,55753905,33797252,66663942,50668599,32699744,37659250,77413012,90090964,12303248,77787724,32250045,5832946,78785507,4064751,26493119,61897582,42947632,14947650,3233569,824872,70420215,47298834,89637706,84406788,15535065,51466049,77301523,99729357,27185644,68316156,47708850,9058407,44473167,92803766,55602660,53547802,41442762,87160386,24058273,8733068,63015256,22113133,99861373,34044787,53084256,2331773,99515901,17081350,34946859,65715134,24915585,91664334,83651211,83747892,84840549,71300104,90310261,7011964,18833224,74724075,96735716,66116458,51507425,10649306,78218845,78561158,11923835,68816413,62232211,85571389,36396314,22942635,95010552,83302115,96906410,30463802,3235882,29932657,16424199,45919976,42199455,3183975,48658605,91240048,13173644,29635537,38510840,12024238,67227442,8913721,37891451,94516935,66319530,76825057,14349098,20885148,40197395,23134715,64098930,67513640,89419466,30090481,51588015,22405120,54606384,91938191,61712234,45790169,30811010,92215320,91141395,61960400,16445503,70372191,68128438,75128745,6111563,37501808,92554120,52204879,36135,93270084,82779622,3294781,83133790,47792865,15015906,69641513,84349107,38341669,79806380,41380093,98648327,32058615,53888755,76330843,53666583,40865610,88904910,65851721,33201905,79136082,57803235,82339363,30771409,44479073,26734892,8128637,20002147,57359924,74357852,89499542,27187213,89804152,99917599,71657078,44784505,1022677,51149804,4806458,92692978,94911072,31491938,20122224,45990383,75820087,80246713,66137019,33249630,47893286,31727379,7066775,86118021,48360198,36933038,71083565,87710366,24314885,3487592,32159704,37675718,68110330,10264691,50567636,78300864,53802686,59197747,65047700,79942022,94595668,33061250,78766358,99125126,92867155,99690194,60176618,112651,63506281,9860968,83533741,62762857,53842979,34295794,21289531,6505939,30653863,21001913,51715482,35512853,18806856,29516592,20681921,4508700,16567550,32426752,415901,81172706,21673260,80014588,56461322,10358899,99971982,17365188,34432810,85117092,68102437,78549759,5267545,26275734,36139942,77694700,11757872,74441448,6871053,74614639,97561745,42692881,61141874,95395112,29401781,42967683,51426311,40027975,73021291,38022834,73392814,29510992,64055960,91281584,62693428,18131876,1204161,85242963,77377183,70800879,4095116,71316369,78909561,10453030,30163921,14220886,84127901,81805959,39201414,69579137,57240218,82532312,9257405,68824981,65880522,65074535,79322415,97281847,26744917,48893685,69255765,20765474,71469330,92787493,16405341,72793444,62428472,59981773,82052050,33336148,96420429,7919588,36468541,16684829,87598888,3880712,81774825,4798568,45617087,70541760,5073754,7685448,52613508,18699206,97379790,90654836,39373729,26063929,6221471,96709982,56484900,86543538,17957593,76170907,21533347,37166608,45381876,37560164,7182642,61728685,92353856,15948937,526217,36189527,99524975,7423788,76671482,88444207,19272365,60102965,60393039,93359396,52060076,44847298,64602895,98943869,57163802,44481640,42237907,79738755,47738185,75407347,55960386,66322959,72732205,45667668,55770687,25842078,37192445,75052463,83368048,16099750,4204661,95251277,67084351,83083131,66832478,70367851,21919959,61928316,17385531,13348726,88130087,57658654,83948335,9398733,45995325,83269727,56531125,20836893,95581843,29466406,15163258,23432750,73109997,3088684,36845587,16595436,69697787,99297164,48088883,74452589,48675329,18001617,38008118,62357986,15075176,23740167,68939068,39847321,49882705,26397786,8129978,6521313,30395570,17386996,27325428,10597197,56955985,41092172,33935899,17976208,38256849,33123618,54868730,30366150,11581181,1375023,90158785,59109400,60106217,84002370,37280276,28787861,32151165,26664538,44060493,54263880,84187166,44915235 +24733232,35192533,76671482,57658654,99658235,6505939,98371444,77620120,7685448,99021067,99333375,35092039,4064751,15536795,45848907,26998766,37659250,4985896,79821897,29466406,44846932,54762643,30764367,94911072,14045383,44915235,29029316,79880247,17365188,37739481,44844121,73031054,49641577,80316608,82886381,62740044,1431742,90272749,57241521,73124510,62430984,39847321,14220886,4099191,57803235,29188588,19939935,16445503,96726697,8651647,59329511,55753905,78589145,15015906,62452034,65074535,508198,4668450,89046466,112651,1204161,70596786,60102965,51047803,83150534,36780454,2717150,29834512,99690194,91831487,51464002,32250045,64098930,80014588,36930650,81172706,16019925,68702632,42199455,59501487,65880522,89419466,64087743,75229462,90090964,71657078,80246713,37675718,59197747,72358170,66250369,88653118,15075176,53802686,51590803,10358899,60581278,50567636,34698428,96852321,74357852,81774825,30395570,36312813,72732205,89214616,8791066,60430369,81853704,69641513,39373729,85242963,43501211,48360198,20002147,28796059,30463802,86301513,11581181,5267545,40356877,57163802,36812683,9575954,62936963,18504197,5970607,13470059,59177718,52060076,7955293,39171895,32151165,54058788,77413012,29994197,34698463,73392814,45996863,21289531,98648327,63967300,29819940,31727379,89699445,78602717,30163921,20885148,83155442,19272365,48675329,83651211,48774913,26392416,33237508,30218878,59318837,15535065,85023028,40781449,32161669,79657802,70727211,57240218,7687278,22450468,6153406,14626618,48893685,56424103,73617245,31126490,74614639,61982238,9257405,22129328,20568363,92803766,78909561,67793644,72777973,30139692,89078848,51507425,67474219,95581843,4199704,3233084,77284619,44784505,61859581,66663942,70036438,61741594,8807940,17016156,3183975,46870723,99965001,8129978,68939068,92554120,4434662,38256849,44550764,15148031,96193415,24619760,415901,65715134,6871053,71920426,31161687,84187166,36753250,76960303,26292919,48088883,40152546,9188443,51149804,1788101,86821229,98948034,99549373,96735716,56531125,669105,8099994,62490109,50806615,83948335,55850790,66137019,66667729,1022677,56461322,6521313,71469330,54868730,4508700,10366309,3509435,5822202,53084256,18663507,53547802,61263068,46540998,12303248,78549759,7423788,61815223,2204165,8733068,49328608,35456853,27041967,4798568,5111370,70004753,72357096,75407347,43045786,90158785,39986008,89637706,45075471,96420429,79942022,84904436,16380211,6808825,97783876,7011964,23134715,21070110,45306070,73222868,98130363,18833224,18783702,99604946,749283,72278539,88047921,32274392,82779622,93562257,14731700,65454636,92215320,63015256,45790169,13231279,93933709,44842615,30366150,17068582,50188404,92998591,23432750,62693428,36580610,62115552,61712234,51426311,33628349,77898274,89217461,60176618,44177011,12348118,20486294,88444207,44664587,33895336,51588015,98739783,55143724,19457589,91727510,76703609,55470718,39801351,60955663,48395186,61623915,9860195,5073754,77312810,86543538,54014062,77301523,52204879,1197320,97561745,76170907,95395112,47738185,59981773,55602660,23740167,48260151,23569917,99524975,67281495,17957593,7104732,68102437,31904591,247198,79136082,73168565,69136837,53648154,77187825,40686254,42692881,70372191,94360702,77300457,10649306,83789391,36505482,29510992,84293052,63059024,53632373,77377183,18131876,44847298,12416768,87710366,59109400,40984766,90654836,59614746,4095116,77272628,69355476,26397786,66045587,9623492,9886593,52613508,40197395,43506672,29516592,60106217,34236719,44481640,44348328,10597197,37183543,40027975,20765474,72274002,55189057,38022834,5832946,91664334,168541,78300864,42307449,176257,51472275,9175338,43357947,44479073,77787724,79322415,56153345,62496012,19376156,78561158,47213183,526217,43152977,54606384,54987042,73786944,37435892,45995325,72725103,72019362,17764950,75135448,29959549,321665,72495719,9599614,75543508,11365791,13862149,12571310,3880712,33797252,26744917,89804152,99917599,24915585,39553046,37957788,91141395,45407418,65271999,66319530,4119747,22405120,6793819,58180653,91802888,35512853,90061527,47298834,52261574,17385531,37501808,49236559,83368048,94711842,87720882,44983451,38494874,13468268,55247455,4091162,32058615,20122224,75777973,69579137,78766358,32699744,22942635,88130087,73235980,22108665,34295794,92787493,98462867,86242799,16405341,3088684,99226875,49598724,23194618,55615885,65275241,28550822,67030811,26114953,9603598,53666583,7182642,4515343,30653863,10309525,48673079,47887470,24168411,7229550,92692283,91938191,44889423,68000591,33061250,17146629,33201905,15902805,75820087,55960386,86001008,2607799,13348726,67451935,80555751,43933006,20867149,84406788,65038678,99224392,36845587,76434144,25842078,69848388,76315420,16097038,30771409,93057697,82726008,71333116,19891772,20645197,17857111,36808486,30787683,61960400,88251446,84840549,3235882,78884452,37891451,21673260,83210802,4069912,92692978,7919588,45428665,93515664,11161731,54517921,26863229,68644627,41380093,27665211,24826575,3294781,28734791,95290172,6525948,33699435,87160386,50668599,70420215,1250437,99971982,34044787,97940276,66885828,91990218,82178706,77072625,92604458,22721500,6497830,27411561,14093520,93790285,8128637,15163258,72373496,74110882,8696647,28787861,569864,68824981,4204661,69255765,3233569,93053405,874791,32590267,17058722,71965942,83269727,77694700,95957797,34432810,66832478,17539625,43376279,26664538,26507214,97011160,79191827,6038457,13173644,75153252,42073124,10453030,6819644,82427263,26776922,27185644,97641116,18001617,69786756,14947650,81898046,17976208,37280276,21533347,66611759,50702367,68041839,80251430,67227442,29635537,81677380,31733363,9829782,22879907,67084351,75986488,3487592,74452589,76540481,42967683,18411915,66271566,45381876,26734892,42947632,90457870,39201414,90013093,41245325,67031644,23110625,70800879,85695762,78218845,33123618,83133790,30543215,22997281,18699206,92530431,92541302,68128438,58749,49882705,17081350,16099750,22113133,16595436,38341669,59910624,75128745,38365584,8913721,97057187,92398073,28851716,66116458,10264691,26139110,61271144,34946859,79738755,17894977,8634541,33304202,56515456,6111563,93270084,92071990,89499542,52293995,71083565,89811711,12024238,14326617,68875490,4806458,91957544,95251277,58224549,14723410,34493392,21993752,40677414,29065964,30569392,24058273,29932657,9860968,19101477,30811010,48892201,72738685,51315460,1239555,45919976,66903004,64602895,81274566,64157906,62357986,63628376,86118021,74724075,44245960,16054533,99297164,47090124,7300047,34667286,99729357,62803858,824872,70541760,94516935,85117092,41092172,4787945,62762857,57020481,26063929,75052463,47893286,98943869,98653983,79806380,25636669,7646095,54199160,44627776,84684495,84349107,27625735,68316156,72614359,79922758,35996293,22435353,1569515,99125126,36189527,41092102,88904910,89996536,31491938,63152504,87598888,70367851,93566986,21001913,33431960,56473732,81805959,97379790,94539824,38008118,53393358,2891150,84127901,69697787,16387575,38061439,82339363,33565483,61859143,38645117,74441448,20681921,65851721,86022504,82532312,45617087,33249630,42237907,76825057,26493119,40781854,38009615,11923835,20642888,56484900,8373289,94076128,36135,36685023,22200378,82979980,88698958,99515901,47792865,71522968,36468541,62051033,6157724,38510840,66428795,54663246,65017137,83747892,37560164,71316369,45667668,99861373,95726235,58751351,30694952,12664567,13819358,16424199,61373987,83083131,20836893,17037369,92353856,5482538,67513640,74743862,33935899,65047700,5640302,23848565,36396314,34497327,85711894,6986898,55770687,84002370,82327024,59405277,2331773,60393039,82651278,48658605,76330843,16971929,91255408,54427233,2917920,27187213,21919959,55669657,20140249,96906410,19486173,72793444,86460488,47361209,19898053,42644903,45794415,46851987,8729146,14349098,1936762,49271185,91240048,61728685,85116755,32159704,53888755,47708850,38658347,27325428,32426752,51715482,70782102,9398733,92033260,2208785,16684829,24953498,11543098,14363867,43322743,26102057,63372756,57393458,51466049,96318094,82052050,95010552,37192445,58208470,30891921,53842979,7066775,66322959,64848072,22766820,17386996,63506281,42782652,56955985,94090109,49597667,96709982,3633375,40865610,8055981,45049308,57248122,73021291,54263880,59371804,4978543,6221471,91281584,8115266,54232247,68204242,41442762,7502255,86798033,1375023,28928175,45237957,61928316,57359924,41481685,61141874,83533741,62428472,71300104,74137926,9259676,37166608,36139942,30090481,18466635,18806856,15948937,68816413,78785507,69605283,82897371,37620363,29401781,26275734,30998561,44473167,81855445,92867155,90004325,9058407,15064655,83302115,33336148,67124282,68694897,50007421,45990383,3773993,40534591,65081429,97281847,65338021,17727650,61897582,24591705,73109997,85571389,68110330,72238278,64055960,7517032,84166196,44060493,94595668,62552524,36933038,37224844,90310261,62232211,93359396,57961282,33553959,16567550,11757872,24314885,21397057,10961421 +669105,67281495,75153252,49236559,30139692,57803235,31904591,26507214,44784505,29188588,9886593,6986898,30543215,4099191,76434144,29029316,65715134,76960303,70596786,38494874,63967300,50806615,66832478,68644627,15535065,10309525,26863229,80014588,72274002,29932657,11365791,45407418,89214616,45794415,96726697,77312810,62430984,6038457,33553959,34044787,29834512,92554120,24058273,66667729,68204242,62452034,52261574,48360198,37620363,34236719,23848565,48673079,93933709,91664334,52293995,4515343,81853704,35192533,61263068,97281847,65038678,7104732,55470718,55753905,45237957,5970607,90272749,7687278,34497327,87720882,168541,4119747,14220886,62552524,44177011,47213183,29959549,62936963,50188404,415901,50007421,40197395,4787945,64602895,74743862,4091162,53648154,86001008,94539824,27665211,3294781,51047803,12664567,31126490,85711894,38658347,66250369,8128637,35092039,58224549,38645117,83651211,43506672,44889423,15015906,6221471,61373987,44983451,61728685,2917920,24953498,64848072,55615885,14045383,88047921,66045587,92071990,96318094,66611759,68000591,19939935,54427233,33895336,37183543,59318837,96193415,526217,34698428,1250437,44473167,91990218,33431960,749283,1569515,6153406,62051033,16099750,60430369,34295794,59177718,1204161,53084256,84349107,40356877,30891921,34946859,92033260,59197747,72357096,61982238,36312813,86022504,78766358,98130363,90090964,55247455,37675718,8807940,16595436,15064655,32426752,28550822,32151165,20486294,18699206,66428795,77301523,81898046,57240218,99524975,30163921,57241521,63059024,92353856,6808825,61741594,57393458,39373729,89699445,74357852,29819940,90457870,16424199,55189057,75229462,79821897,14731700,4798568,85116755,99549373,83083131,72019362,74441448,69848388,79922758,65338021,98371444,94516935,91802888,57359924,95726235,94911072,61712234,94090109,76315420,55770687,8696647,80316608,8055981,40984766,44550764,1022677,81677380,47090124,65017137,9829782,93359396,11923835,26776922,77187825,40781449,3088684,32161669,4064751,65851721,69641513,44915235,69255765,6525948,70004753,48675329,99515901,73222868,62428472,72738685,30694952,96906410,75543508,32159704,54199160,22113133,65271999,34493392,84127901,37739481,58751351,53547802,7646095,24591705,61623915,92530431,49328608,321665,82726008,82427263,35456853,21993752,37192445,99658235,20140249,99297164,48892201,8634541,62496012,17857111,99965001,54232247,93053405,60102965,70367851,60581278,84684495,91938191,77272628,17068582,68702632,82339363,9575954,19457589,84904436,75777973,72777973,44348328,30395570,33336148,40865610,42692881,34432810,30463802,54014062,5111370,48260151,81855445,33249630,92398073,88444207,26102057,56515456,99021067,42782652,17037369,8729146,71333116,54517921,69355476,26392416,26063929,78602717,5832946,4668450,61141874,78549759,73786944,22942635,38365584,98948034,17365188,54987042,48774913,88653118,93562257,10649306,4806458,22766820,83789391,4204661,19486173,80246713,89637706,78909561,96709982,73031054,82886381,44479073,98462867,14947650,1431742,67793644,44481640,36930650,92998591,96735716,89811711,45848907,42073124,1197320,247198,83533741,79942022,99125126,77620120,27185644,89804152,48893685,26292919,26493119,9599614,15148031,49598724,14626618,66116458,27411561,569864,53666583,75407347,12416768,46870723,30998561,91727510,9175338,66137019,5482538,38061439,26998766,43322743,32590267,42199455,79191827,1239555,49271185,176257,89499542,32699744,18504197,30569392,65454636,50668599,83302115,29466406,99861373,46851987,12571310,56424103,45075471,78589145,76330843,8651647,43045786,83210802,79136082,13231279,24619760,92787493,36505482,6111563,70372191,33628349,68816413,22997281,36812683,9623492,24826575,38341669,36933038,95957797,26139110,90654836,17058722,77413012,1936762,71083565,97641116,30653863,63015256,68875490,15948937,77787724,45428665,68694897,42967683,9398733,36685023,12024238,70800879,22200378,97561745,81172706,40027975,16019925,16971929,17386996,62740044,74614639,65275241,79806380,70036438,37435892,74110882,85242963,99226875,824872,56461322,94711842,90004325,36753250,74724075,7685448,39801351,10453030,8373289,2891150,43376279,99604946,11543098,77072625,15902805,75986488,92692978,77284619,16380211,17539625,47887470,30764367,90310261,86301513,80251430,31733363,16097038,1375023,65047700,3235882,20002147,29510992,62693428,4434662,75135448,39171895,45919976,87710366,72725103,48088883,14093520,6157724,44842615,53888755,71920426,36808486,22405120,20836893,45306070,89217461,51466049,36139942,98739783,9603598,61859581,38008118,26734892,9860195,6871053,3233084,18466635,79738755,69786756,71469330,79880247,9259676,40686254,1788101,55960386,17727650,40534591,22435353,74137926,65074535,6793819,47738185,72614359,97379790,83269727,77300457,72238278,19101477,23194618,68102437,7919588,99917599,20885148,9188443,73617245,37166608,99690194,51472275,29065964,66271566,73124510,67030811,73235980,33304202,89046466,81774825,7300047,55143724,20867149,45667668,7955293,3880712,55602660,43501211,72373496,91957544,71657078,24733232,58749,66322959,94360702,63372756,13468268,41442762,18806856,59371804,52060076,14326617,27187213,70541760,4069912,61859143,76170907,6505939,21673260,92215320,59614746,508198,41092172,38009615,59501487,31161687,7502255,21070110,23569917,36580610,37224844,18783702,39553046,33797252,72278539,82651278,97011160,67031644,32250045,24168411,2717150,84293052,62490109,12348118,62232211,28734791,97783876,45381876,91141395,11581181,17385531,17016156,19891772,52204879,67474219,2208785,16445503,86543538,17976208,68316156,82052050,112651,8115266,29635537,21533347,63152504,67084351,26114953,45790169,69136837,51590803,15536795,30366150,2204165,85023028,40152546,64087743,84166196,21919959,25842078,80555751,51715482,19376156,8099994,45990383,92604458,67451935,37501808,20765474,40677414,76540481,93057697,39847321,47792865,23432750,82327024,95290172,86821229,41481685,43152977,59910624,27625735,78785507,29401781,28928175,37957788,88130087,44060493,50702367,44847298,73021291,15163258,66319530,51588015,30218878,43933006,98648327,73168565,78218845,99333375,39986008,90013093,87598888,50567636,22721500,22129328,61960400,70782102,41380093,37659250,23134715,51426311,71965942,46540998,7517032,44846932,52613508,96420429,81274566,75128745,13348726,3633375,14723410,87160386,7229550,29994197,68041839,19898053,95251277,82979980,40781854,61897582,17957593,30771409,13470059,34698463,51464002,75052463,23110625,3487592,91831487,3509435,53842979,54663246,51507425,38510840,76671482,33935899,20122224,71316369,94076128,92867155,45617087,9257405,95581843,84187166,7011964,88251446,36845587,84002370,42307449,56955985,2607799,4508700,57961282,92541302,16054533,84406788,78884452,57658654,69697787,86460488,95010552,6497830,88698958,41245325,56531125,9058407,17894977,33565483,78561158,36189527,67124282,82897371,98653983,47708850,42947632,26397786,44627776,10366309,68939068,68128438,43357947,58180653,97940276,60106217,93515664,8129978,66663942,6819644,48395186,33123618,20681921,78300864,25636669,66903004,45995325,97057187,28787861,54058788,10358899,59109400,79657802,53632373,83150534,77694700,47361209,28796059,89419466,21289531,26744917,66885828,49597667,93270084,26664538,86798033,73392814,14363867,95395112,874791,54606384,36468541,20645197,74452589,51149804,33061250,82532312,36780454,53802686,99971982,20568363,18411915,47298834,37560164,36135,4985896,57020481,13862149,49641577,98943869,8913721,33237508,83155442,9860968,68824981,22450468,63506281,93566986,60955663,26275734,72358170,62115552,17081350,69579137,32274392,5822202,13173644,45996863,68110330,85571389,42644903,63628376,94595668,72495719,64157906,70727211,85695762,39201414,31727379,4978543,55850790,56473732,37891451,5267545,56153345,77377183,83133790,55669657,91255408,91281584,92692283,61928316,8791066,64098930,93790285,88904910,49882705,70420215,48658605,30787683,76703609,4199704,61271144,21001913,41092102,57163802,91240048,35512853,53393358,86118021,38256849,3233569,83747892,33699435,54263880,84840549,51315460,83368048,33201905,29516592,90061527,37280276,12303248,89078848,10961421,5640302,23740167,62762857,82779622,57248122,16387575,36396314,2331773,30811010,72732205,15075176,11161731,79322415,18131876,3183975,5073754,60393039,22108665,19272365,8733068,75820087,67227442,32058615,16567550,17764950,81805959,59329511,24314885,71300104,11757872,16684829,73109997,30090481,35996293,64055960,24915585,44245960,65880522,96852321,54868730,7423788,69605283,14349098,76825057,60176618,59405277,62357986,18833224,10264691,28851716,82178706,72793444,18001617,16405341,85117092,27041967,17146629,42237907,3773993,71522968,77898274,92803766,7182642,59981773,67513640,38022834,4095116,99224392,62803858,31491938,83948335,86242799,21397057,20642888,56484900,89996536,58208470,7066775,27325428,6521313,13819358,65081429,22879907,61815223,34667286,47893286,10597197,44664587,54762643,18663507,45049308,44844121,90158785,99729357 +47090124,71300104,39171895,12348118,8807940,27665211,97783876,4099191,98739783,44846932,51047803,65715134,26063929,67281495,29959549,16445503,26392416,508198,81274566,1431742,98462867,6819644,85023028,96193415,4787945,96726697,6153406,80251430,61263068,569864,34698428,56473732,78549759,43933006,1204161,45995325,56461322,35092039,37957788,86821229,17016156,21289531,68644627,8696647,96420429,66137019,3509435,77312810,5832946,92692283,77620120,51426311,47708850,61859143,168541,55753905,70004753,33304202,7919588,43501211,6793819,18699206,57163802,69641513,32250045,78589145,57240218,19939935,98130363,89078848,31491938,80316608,95290172,46870723,88653118,54058788,69355476,36580610,85242963,112651,22997281,17764950,35192533,75543508,89046466,76671482,83302115,4806458,20486294,38494874,21993752,74743862,67793644,44348328,9623492,40865610,44844121,43376279,7502255,4798568,12024238,15535065,29834512,43322743,57803235,3233569,34432810,20122224,20765474,42947632,55770687,5970607,62430984,74724075,14093520,46851987,76703609,29819940,62936963,7517032,13348726,99297164,24058273,36930650,321665,29994197,16387575,3294781,45996863,65074535,29510992,33553959,60955663,24619760,31161687,26734892,50702367,83210802,9398733,36780454,26664538,6505939,95395112,91240048,88047921,96318094,60581278,92215320,9860968,26292919,33431960,90004325,68702632,90013093,32161669,83083131,28851716,12416768,59371804,63628376,99333375,49641577,62452034,69605283,99549373,37659250,33797252,68102437,56424103,55143724,48088883,53084256,4119747,19486173,83789391,31733363,4069912,74357852,7423788,37620363,44245960,97641116,40534591,94911072,71657078,40152546,62740044,93562257,9603598,61623915,14045383,70541760,8099994,49597667,30395570,69136837,10358899,29029316,76540481,29065964,99604946,99861373,58208470,22450468,30569392,92398073,36505482,51466049,20645197,40356877,79922758,38061439,75229462,72738685,54762643,36753250,85711894,24733232,81805959,11923835,87710366,34493392,60430369,77377183,70800879,16097038,84349107,19101477,65038678,48360198,14349098,61728685,99965001,71920426,11161731,54199160,15015906,4095116,11581181,51588015,65271999,25842078,6986898,73124510,92554120,45237957,9886593,79821897,74110882,22108665,29932657,70372191,94595668,78561158,74441448,66903004,84293052,18783702,23194618,73617245,86301513,33895336,84187166,92803766,4199704,8651647,10597197,59910624,26493119,83150534,4985896,16595436,55960386,82886381,14326617,64087743,69579137,68204242,45407418,39201414,62490109,22435353,40686254,91802888,34497327,2917920,82052050,33123618,30764367,41380093,37435892,66250369,59501487,65047700,47213183,16019925,57961282,42967683,93933709,67451935,36468541,72777973,31126490,65338021,85117092,15948937,98648327,81898046,20867149,874791,32590267,76330843,38365584,72238278,99021067,70036438,37675718,9599614,8634541,91281584,53648154,52261574,33249630,89214616,26102057,45667668,30771409,51464002,50188404,30463802,87160386,21673260,17539625,15075176,61982238,20002147,66611759,6808825,40197395,83651211,89811711,65017137,54987042,53632373,55602660,98371444,19376156,83533741,72373496,81853704,61815223,29188588,4434662,89637706,28796059,60106217,24953498,45617087,95726235,73392814,72495719,16424199,10366309,59109400,43045786,53842979,78766358,669105,63967300,50567636,31904591,99226875,79880247,41481685,66319530,18833224,34044787,81855445,93270084,18131876,19272365,95957797,26776922,8373289,44889423,6871053,1022677,1197320,52060076,79191827,30218878,68824981,68939068,21070110,54014062,29466406,45790169,40781854,80555751,73168565,48774913,5267545,18806856,6497830,16971929,77898274,59197747,61960400,415901,49328608,44842615,32159704,62693428,64157906,51715482,79136082,28550822,2717150,41092102,4978543,15902805,32274392,17146629,99224392,7011964,14731700,23432750,47738185,30090481,36312813,88130087,14626618,30787683,84002370,49236559,64098930,47298834,97561745,59177718,61141874,35512853,66885828,40984766,56515456,92692978,3880712,82897371,51472275,23569917,86460488,88444207,86001008,84684495,76170907,82327024,44481640,55470718,37739481,1239555,38008118,73031054,71316369,33628349,90457870,54427233,79942022,97940276,24591705,8115266,78909561,68875490,82726008,61897582,50668599,42644903,72019362,78785507,96735716,58180653,61271144,7300047,48673079,15064655,13862149,53547802,5111370,62803858,75820087,17976208,99658235,78218845,93053405,3773993,98948034,65275241,93566986,37224844,8791066,40781449,37183543,57248122,43506672,27411561,92353856,95251277,47361209,526217,30139692,26114953,12571310,67084351,28787861,65081429,39986008,18663507,63015256,33201905,58749,83368048,44983451,39847321,16054533,96906410,44479073,76960303,13231279,67227442,20885148,39373729,247198,8129978,91727510,7104732,67124282,81172706,38658347,8913721,55615885,17727650,66428795,77187825,90654836,79738755,75986488,57020481,37192445,7955293,62232211,39553046,59318837,69848388,26744917,83269727,4064751,11365791,28928175,10961421,60102965,30653863,4091162,66271566,77072625,21533347,36685023,67513640,52293995,40677414,16380211,47792865,32699744,45075471,89804152,9188443,66322959,72725103,79657802,13173644,14723410,3487592,36189527,13468268,17058722,41245325,97011160,65851721,55247455,5482538,82779622,53802686,62115552,9575954,2891150,26139110,19457589,44550764,13470059,33699435,92033260,2204165,66832478,85116755,33237508,71333116,59329511,45919976,81677380,56531125,63059024,77301523,20681921,72732205,75407347,8733068,19891772,90272749,58751351,79806380,9259676,57393458,36812683,35996293,21001913,15163258,4515343,94516935,17957593,84840549,75052463,92071990,70596786,78602717,93790285,37166608,57359924,54663246,48395186,17068582,3183975,9257405,73235980,26998766,14947650,7646095,19898053,5640302,77272628,68000591,92541302,28734791,55189057,22942635,3235882,42237907,22879907,33565483,96852321,30163921,17365188,67031644,38510840,82178706,21919959,65454636,77284619,20642888,40027975,27625735,31727379,54606384,77300457,80246713,56484900,42199455,59405277,14363867,82532312,27041967,72274002,35456853,70367851,23740167,45428665,7229550,84406788,3088684,18411915,68816413,63152504,93057697,48658605,61373987,72793444,23848565,73786944,20836893,63372756,30891921,41092172,57241521,52204879,54232247,30998561,43152977,68128438,27185644,70727211,77787724,94711842,18001617,20140249,94076128,79322415,36808486,65880522,49598724,34236719,10309525,80014588,42073124,89419466,824872,16099750,4668450,7687278,23134715,38022834,94539824,88251446,5822202,48260151,82339363,7182642,54517921,30366150,38256849,91957544,33336148,44177011,64602895,48892201,3633375,99125126,74137926,66045587,47887470,69697787,89217461,99515901,75153252,99690194,36139942,26275734,3233084,6157724,50806615,87598888,43357947,86798033,82651278,83948335,90310261,39801351,89499542,24314885,34698463,42307449,27325428,44473167,30694952,53888755,8128637,30543215,73222868,67474219,24168411,90090964,60176618,57658654,44847298,53666583,86242799,10649306,86543538,92998591,72357096,37891451,59614746,9175338,72614359,4204661,27187213,85695762,90158785,38645117,62428472,75777973,62496012,82979980,2208785,45794415,67030811,91938191,16684829,56153345,1250437,36135,36845587,71522968,15536795,69786756,64848072,45306070,15148031,29401781,66663942,93515664,37560164,70420215,98653983,95581843,53393358,81774825,22129328,8055981,24826575,51149804,94090109,77413012,68694897,72278539,49271185,29516592,75135448,91664334,84127901,7685448,61859581,51590803,42782652,97281847,54868730,38009615,94360702,86022504,23110625,82427263,92604458,9860195,95010552,59981773,18466635,20568363,99524975,6111563,12303248,22405120,56955985,73021291,12664567,90061527,71469330,91990218,51507425,33061250,76434144,25636669,8729146,84904436,91255408,92867155,18504197,1788101,44915235,749283,83155442,75128745,37280276,17894977,17385531,6221471,45848907,49882705,11543098,60393039,74614639,11757872,88904910,33935899,61712234,2607799,62051033,45381876,22766820,16567550,71083565,70782102,99917599,68316156,44664587,63506281,29635537,52613508,10264691,6521313,17386996,17081350,66667729,55669657,78884452,14220886,16405341,73109997,38341669,48893685,44627776,1569515,44784505,78300864,32426752,87720882,46540998,85571389,44060493,64055960,34667286,9829782,26863229,98943869,92530431,96709982,32151165,32058615,77694700,91831487,34295794,72358170,22200378,13819358,26507214,89996536,68041839,97057187,86118021,62357986,74452589,76315420,93359396,7066775,99971982,92787493,30811010,51315460,37501808,99729357,55850790,22721500,9058407,88698958,71965942,48675329,4508700,6525948,66116458,1936762,176257,50007421,5073754,36933038,24915585,61741594,61928316,54263880,10453030,41442762,62762857,17037369,45990383,1375023,83133790,17857111,22113133,62552524,58224549,6038457,91141395,68110330,84166196,97379790,45049308,34946859,69255765,42692881,89699445,83747892,21397057,36396314,76825057,47893286,2331773,26397786 +66667729,72373496,2917920,69136837,14731700,4668450,22879907,35192533,52204879,61373987,93933709,1197320,93053405,18411915,66250369,59329511,14723410,36505482,12664567,62496012,66611759,97783876,33304202,34946859,83210802,76330843,99549373,45794415,526217,30543215,41092102,69355476,64157906,247198,1788101,48893685,72777973,37620363,81853704,40152546,24591705,95395112,58180653,112651,73786944,61859581,91240048,55770687,30891921,18783702,85242963,54232247,55189057,22129328,79821897,36780454,26292919,17386996,29029316,68824981,51047803,19272365,57248122,58751351,99917599,69641513,29834512,63372756,68102437,95010552,49271185,54606384,40984766,84684495,90061527,63059024,44177011,8055981,29188588,61960400,77312810,35456853,36933038,60430369,98943869,40027975,52613508,82897371,6808825,20486294,44842615,40781449,26776922,42692881,81898046,17894977,49641577,5267545,30463802,19891772,26139110,78785507,72278539,54427233,70004753,30998561,73031054,50702367,26744917,22200378,4508700,1204161,7011964,76315420,25842078,44983451,66137019,59501487,17146629,92033260,51472275,67281495,48774913,34698428,3294781,15535065,48360198,48892201,39553046,50007421,18504197,54199160,39986008,53648154,8099994,59197747,13470059,55753905,88698958,26664538,53632373,34044787,77072625,63015256,8129978,92692283,87598888,38061439,96193415,99658235,95290172,66319530,19376156,99515901,78884452,43376279,75777973,46870723,22997281,44481640,49598724,7300047,73124510,33237508,61271144,7955293,10358899,14326617,4099191,65074535,59910624,66045587,62430984,55247455,176257,75128745,68694897,51464002,61263068,62803858,68316156,34295794,874791,15902805,95957797,65038678,6038457,68000591,62490109,65851721,92398073,2204165,98130363,34493392,4787945,56461322,36312813,65338021,43501211,61741594,84293052,9886593,44348328,35512853,90013093,3509435,40534591,415901,62452034,57241521,96726697,35092039,99965001,45790169,93566986,45381876,60106217,89078848,47708850,60102965,29994197,23569917,83651211,23194618,55470718,55850790,1375023,37501808,91664334,3233084,32426752,61712234,98948034,36685023,56515456,168541,21070110,68644627,1431742,33699435,7646095,30218878,3088684,5111370,21919959,10366309,64602895,55669657,669105,5832946,65271999,321665,59318837,17976208,59614746,72738685,19898053,61815223,4978543,43152977,24915585,65715134,54517921,79922758,72274002,97379790,17957593,91990218,29819940,26392416,38645117,7423788,14626618,92787493,41442762,78766358,47792865,4064751,62115552,86460488,44889423,95581843,50567636,30569392,43933006,67451935,30771409,13862149,77787724,83269727,86301513,7919588,77620120,50188404,82427263,83150534,18663507,68204242,11365791,54663246,99604946,94076128,58224549,67793644,8729146,36139942,53666583,9599614,47213183,64087743,30366150,98371444,54868730,74357852,86821229,8634541,33797252,31126490,47361209,29065964,48675329,60176618,41245325,38658347,72357096,44844121,9829782,79191827,24058273,12303248,22405120,80251430,3235882,26863229,53084256,32250045,88047921,96735716,98462867,70800879,82178706,91727510,15064655,40686254,89217461,22113133,10597197,98739783,31904591,16445503,99690194,86001008,82651278,42644903,53802686,22450468,5822202,17081350,96318094,84840549,77694700,14947650,30139692,19101477,44473167,25636669,73392814,58749,29959549,93790285,99524975,64098930,57240218,24826575,8651647,52261574,73021291,45667668,9259676,47893286,96420429,51149804,70372191,72725103,17764950,97641116,69786756,98648327,81677380,70036438,75229462,62051033,92692978,62740044,70596786,90272749,42073124,27665211,72614359,85117092,8733068,74743862,60955663,10961421,83302115,16405341,33628349,99297164,61141874,82339363,6153406,16424199,15148031,91141395,42947632,39801351,80246713,51588015,40677414,85116755,77898274,67227442,63628376,24168411,66832478,78602717,42307449,44784505,33249630,2331773,75986488,10649306,62552524,61982238,97561745,99226875,99333375,11757872,65275241,12024238,45617087,29466406,23848565,57020481,72793444,33061250,36930650,19939935,5482538,45237957,62232211,85711894,51590803,54058788,49328608,81774825,90090964,42967683,9175338,26397786,17016156,44664587,55143724,37183543,24733232,33431960,64848072,94539824,86022504,44245960,45075471,94090109,89419466,17385531,20122224,34667286,43506672,15536795,80316608,56955985,67513640,3773993,22766820,94360702,93057697,74724075,81855445,36808486,749283,59371804,78909561,18131876,62428472,43322743,9623492,69848388,66428795,26275734,76825057,68939068,17365188,26998766,91938191,17727650,88904910,92071990,80014588,67084351,59109400,8807940,91831487,90310261,13231279,69255765,93270084,76703609,9603598,3880712,16684829,39171895,86242799,22435353,37891451,77284619,86543538,28796059,47090124,76434144,99971982,55960386,44479073,92604458,18699206,88653118,73222868,48673079,65017137,30764367,68041839,68110330,37224844,82886381,5640302,70727211,9575954,47887470,73168565,71657078,22108665,43045786,37659250,4806458,57658654,31727379,85571389,34698463,84187166,44627776,56531125,91281584,16099750,7687278,78549759,77187825,63967300,56473732,85695762,2717150,55615885,70420215,89214616,44847298,33565483,92541302,84349107,20568363,86118021,8373289,68816413,99861373,14363867,45306070,80555751,48260151,48395186,88251446,2208785,49882705,40197395,71469330,77300457,39847321,71083565,99021067,54263880,95726235,7517032,84166196,77377183,16595436,36189527,45428665,33895336,84904436,32274392,36845587,15948937,29510992,75135448,78589145,27411561,26063929,17539625,81274566,90004325,59981773,4515343,34236719,6986898,20885148,62693428,23110625,71522968,71333116,76540481,32699744,75153252,93515664,1936762,54762643,90654836,12416768,44915235,72019362,68702632,61859143,93359396,82979980,38494874,29516592,24953498,9257405,97940276,17068582,41481685,4069912,60581278,1250437,83368048,48658605,6871053,3183975,28928175,83747892,30694952,54014062,78300864,36753250,92554120,12348118,569864,35996293,13173644,53842979,77301523,5073754,62936963,28550822,21993752,63506281,30090481,20140249,74137926,12571310,46851987,62762857,14220886,42782652,33336148,13468268,79657802,96906410,93562257,21673260,56484900,11161731,70367851,16971929,22721500,71316369,16054533,66885828,72732205,94516935,37675718,38008118,72238278,67474219,17037369,42199455,27041967,16567550,31161687,67031644,99729357,50806615,66322959,33201905,92803766,4798568,3233569,90457870,40781854,4091162,66903004,37435892,57163802,4095116,67030811,32058615,5970607,89811711,89637706,6157724,36396314,38365584,37739481,76960303,76170907,53393358,36812683,8791066,20002147,82726008,27185644,45049308,75820087,10309525,78218845,6497830,22942635,97057187,38022834,72495719,36135,73235980,1022677,46540998,10453030,4985896,20642888,57961282,56153345,53547802,11923835,88444207,49236559,45996863,14093520,97011160,20681921,26734892,16380211,20645197,24619760,6793819,53888755,16097038,30395570,18833224,36580610,7182642,6525948,91802888,39373729,38510840,52293995,73617245,28787861,23134715,61728685,6111563,26493119,34497327,87710366,82779622,51315460,91957544,39201414,9860195,84002370,47738185,55602660,29635537,82532312,8128637,99224392,58208470,15015906,8913721,37192445,87160386,8115266,79136082,42237907,99125126,75543508,94911072,32161669,59177718,1569515,6819644,45407418,34432810,71965942,83948335,28734791,72358170,11543098,30653863,14045383,45919976,68128438,18466635,83083131,66116458,27325428,6505939,38341669,4204661,32590267,79880247,38009615,40865610,13819358,84406788,57359924,23432750,7066775,74614639,18806856,20867149,26507214,98653983,96709982,38256849,33123618,4119747,74441448,45995325,57803235,9058407,28851716,78561158,74110882,81805959,51507425,23740167,61623915,81172706,88130087,50668599,52060076,6221471,7502255,19486173,49597667,65454636,824872,16019925,69579137,21001913,65880522,44550764,60393039,17058722,92530431,27187213,26102057,66663942,9188443,48088883,89046466,17857111,51715482,79738755,65047700,8696647,59405277,77413012,24314885,31733363,26114953,44060493,86798033,75052463,20836893,20765474,83155442,62357986,14349098,83533741,75407347,71920426,51426311,10264691,83133790,87720882,9860968,30163921,21397057,41380093,54987042,77272628,19457589,2607799,27625735,73109997,89499542,41092172,15163258,30811010,71300104,2891150,94595668,15075176,4199704,37280276,3633375,7104732,7685448,4434662,95251277,3487592,21533347,89996536,44846932,47298834,13348726,40356877,32151165,45848907,65081429,57393458,32159704,21289531,29401781,16387575,508198,69697787,79942022,33553959,92353856,11581181,79806380,51466049,56424103,84127901,91255408,9398733,94711842,31491938,6521313,74452589,29932657,45990383,96852321,82052050,61928316,70541760,92215320,68875490,37166608,79322415,37957788,18001617,43357947,83789391,7229550,1239555,85023028,69605283,92998591,66271566,70782102,67124282,61897582,63152504,37560164,90158785,64055960,76671482,89804152,82327024,33935899,36468541,30787683,92867155,97281847,89699445 +81855445,24591705,98739783,36753250,45428665,97281847,8733068,9058407,37739481,68204242,30569392,10366309,72274002,93562257,6793819,42073124,8696647,98948034,92398073,30771409,42782652,50702367,11161731,61728685,16971929,49271185,18663507,93933709,66322959,19939935,66045587,89214616,56473732,34295794,33895336,86001008,27625735,93053405,85116755,62496012,20486294,34698463,89699445,19486173,95290172,38061439,18806856,88653118,16445503,9623492,54427233,96193415,73168565,35996293,68816413,33553959,52293995,91664334,83210802,65715134,99604946,45995325,36312813,57248122,72777973,3880712,15535065,4119747,1788101,16380211,61859143,73031054,2208785,17539625,8807940,63967300,6497830,2917920,40027975,72357096,63506281,77620120,51472275,39553046,4099191,67281495,15064655,62552524,71657078,22942635,40781449,11365791,31904591,54014062,29188588,40686254,37620363,92033260,70727211,54663246,87720882,61897582,70004753,71920426,14349098,61263068,59501487,37183543,9398733,62452034,30543215,36685023,20765474,75135448,98371444,16097038,62740044,26392416,53666583,3088684,20140249,18833224,70372191,62430984,45617087,68041839,18783702,70036438,38009615,19891772,30366150,50188404,8115266,7955293,60102965,24619760,9599614,59910624,98648327,77272628,88047921,43501211,36812683,12348118,9829782,47298834,67030811,90013093,23134715,33201905,55247455,95395112,22721500,51047803,44842615,14947650,78766358,61271144,30998561,92867155,54987042,55960386,17764950,77284619,39171895,73786944,30891921,2331773,40677414,97379790,89804152,74452589,44889423,28796059,95010552,79738755,24826575,34493392,80316608,62803858,76434144,9603598,17037369,43933006,88904910,65851721,47090124,3233569,2717150,526217,29959549,53648154,41442762,26776922,76540481,81853704,73392814,87160386,55602660,5111370,47708850,36505482,26493119,3487592,29819940,11923835,82979980,86460488,99861373,35192533,89078848,67513640,44177011,26863229,48260151,96318094,72358170,49882705,53547802,59197747,75153252,69786756,2607799,14093520,29932657,41092102,27665211,22405120,99917599,99658235,47887470,67124282,33431960,15948937,18466635,97783876,62693428,68644627,75543508,32590267,3773993,49641577,24058273,57961282,47361209,59109400,112651,73222868,48360198,64098930,84840549,6505939,42692881,2204165,4515343,56424103,89046466,6111563,91802888,29994197,99515901,62936963,33304202,58180653,3235882,92353856,7502255,54762643,32426752,69848388,94595668,78561158,9886593,96726697,99549373,55770687,69255765,38645117,71083565,79136082,38365584,85023028,99333375,72793444,63152504,82897371,247198,99125126,37166608,83155442,16099750,36930650,57803235,27185644,76330843,508198,46870723,65338021,27411561,13470059,49236559,55189057,55753905,22450468,51466049,26664538,38658347,42307449,4668450,34044787,37501808,9259676,83789391,1239555,17058722,45306070,61960400,5822202,56955985,1204161,26397786,80555751,4199704,8651647,37675718,39986008,73617245,61859581,37957788,71522968,90004325,14326617,90457870,36468541,1375023,26507214,61815223,51507425,40356877,41245325,95726235,37435892,51590803,55143724,17016156,96709982,33628349,43322743,30653863,83368048,53842979,56461322,4787945,176257,27325428,43045786,17068582,8099994,22435353,99729357,90090964,29834512,92215320,57393458,9860968,67451935,98130363,10649306,5482538,78218845,94090109,71469330,13468268,6153406,65271999,66903004,32699744,71333116,70367851,84187166,38022834,17727650,72725103,49597667,24314885,66137019,30764367,74110882,66663942,45667668,47792865,18001617,77187825,40984766,34698428,23569917,21533347,82178706,77312810,16054533,24733232,36780454,26102057,92998591,8055981,77787724,67474219,26734892,62490109,94539824,68128438,82886381,15163258,6819644,6521313,8634541,16405341,96852321,77694700,10961421,62115552,75052463,40781854,7423788,68824981,64087743,72738685,29065964,45919976,58749,79657802,21001913,69136837,99965001,63372756,824872,66832478,51464002,92071990,3233084,25636669,12416768,30218878,19898053,23110625,21070110,6808825,99021067,32161669,49328608,67793644,91957544,54232247,68102437,84684495,59177718,68875490,81805959,59329511,9575954,98462867,13231279,36580610,5970607,67227442,8128637,16019925,60955663,83651211,79806380,52261574,17857111,96906410,91727510,58208470,72495719,78602717,45407418,45790169,9257405,76960303,3633375,415901,19376156,62232211,93515664,34432810,669105,84293052,80246713,68316156,168541,4978543,56515456,60430369,70800879,78300864,28928175,53802686,15148031,34497327,95581843,6525948,63059024,41481685,44481640,1569515,65017137,22766820,34236719,30163921,79821897,569864,33797252,26275734,89811711,57241521,30090481,53888755,15075176,33249630,87598888,1431742,55470718,85711894,20122224,33123618,35456853,81898046,59371804,97940276,84904436,66250369,45996863,44983451,44784505,59318837,71300104,91831487,90158785,18699206,51426311,38494874,30395570,16387575,93057697,80251430,23740167,92787493,51149804,28734791,61712234,42237907,85117092,86242799,32159704,64157906,19101477,66611759,91141395,73124510,89637706,97011160,82726008,32250045,70420215,30694952,74724075,2891150,4806458,40197395,83133790,22997281,8373289,7104732,99224392,76671482,45237957,4095116,68702632,19457589,3294781,35512853,22200378,17081350,75986488,89996536,91281584,31126490,13173644,50668599,89419466,7229550,10358899,88444207,874791,99971982,45075471,67084351,83533741,39847321,98653983,23194618,48395186,72278539,12571310,47213183,92803766,72238278,48892201,30139692,90310261,75777973,19272365,44479073,29029316,52060076,10453030,65880522,86301513,48658605,61141874,78785507,32274392,40865610,16567550,77413012,4798568,10309525,63015256,1197320,77301523,51315460,91990218,73235980,37891451,3509435,36189527,44550764,77072625,54058788,7517032,95957797,37560164,56484900,83150534,9860195,43357947,33061250,20002147,92692283,89217461,99297164,31491938,90272749,52204879,74137926,61982238,5832946,84406788,45848907,61741594,84166196,54517921,97561745,58224549,6038457,38008118,45990383,49598724,8129978,10597197,94711842,7011964,23848565,44473167,66667729,70596786,77898274,34946859,96735716,75407347,56531125,29635537,89499542,17957593,64602895,47738185,75820087,22879907,6986898,7182642,16595436,71316369,69605283,84002370,72614359,50567636,48088883,86022504,7685448,78589145,51588015,94076128,36933038,53393358,64848072,46851987,22113133,45381876,7687278,56153345,76703609,62762857,68939068,57163802,36808486,83302115,18504197,48675329,57240218,14220886,20642888,31733363,20836893,92692978,57020481,66319530,30463802,99524975,4069912,38510840,41092172,85242963,33699435,39801351,27187213,50806615,39201414,41380093,61623915,42967683,51715482,65074535,43376279,16424199,69641513,44627776,30787683,4204661,1250437,45794415,11543098,31161687,83083131,54263880,91240048,65454636,87710366,30811010,21397057,76170907,97641116,20885148,90654836,82532312,21993752,74441448,80014588,26114953,77300457,91255408,61928316,48673079,54199160,83948335,53084256,94911072,26998766,85571389,66271566,86821229,54606384,7646095,33935899,60393039,65275241,85695762,17386996,48893685,40534591,44846932,14045383,24168411,58751351,55615885,50007421,20645197,17146629,98943869,26744917,14626618,59614746,33237508,93790285,6221471,69697787,73021291,26063929,81172706,82779622,42644903,92541302,76825057,37192445,22108665,72019362,74743862,5267545,38341669,65038678,70541760,42199455,94360702,8913721,36139942,7066775,66885828,46540998,37280276,93566986,20867149,79191827,60176618,69579137,5640302,18131876,92604458,99226875,13819358,78884452,43152977,20681921,65047700,48774913,57359924,76315420,53632373,28851716,61373987,35092039,8729146,57658654,20568363,3183975,82427263,25842078,82339363,60106217,94516935,43506672,28550822,55669657,13348726,29401781,15536795,36135,33336148,66116458,63628376,17894977,14723410,44664587,81774825,88251446,99690194,62428472,44245960,65081429,75229462,6871053,88698958,18411915,33565483,21673260,9188443,4091162,40152546,92554120,66428795,88130087,44348328,68000591,92530431,36396314,83269727,24915585,47893286,64055960,44847298,4508700,54868730,22129328,75128745,91938191,45049308,1022677,86543538,59405277,74357852,73109997,28787861,72732205,78909561,69355476,10264691,12024238,9175338,17976208,11581181,86798033,52613508,72373496,83747892,95251277,34667286,62357986,42947632,93359396,27041967,79880247,60581278,26139110,17385531,29466406,81677380,32058615,1936762,14731700,84349107,14363867,62051033,24953498,321665,15015906,44060493,82651278,31727379,55850790,38256849,79322415,84127901,86118021,17365188,68694897,7919588,8791066,96420429,82052050,44844121,77377183,749283,29510992,82327024,79922758,78549759,4434662,97057187,5073754,4985896,32151165,12303248,71965942,79942022,68110330,37224844,26292919,90061527,37659250,93270084,67031644,70782102,74614639,16684829,81274566,4064751,39373729,21919959,12664567,21289531,23432750,15902805,29516592,44915235,59981773,11757872,7300047,6157724,13862149,36845587 +4668450,60430369,54199160,14326617,19376156,70420215,89214616,67793644,56955985,59501487,50567636,96193415,32590267,84684495,77072625,68644627,78602717,91957544,13470059,54606384,36812683,22108665,99549373,33699435,66611759,7685448,88444207,92692978,44889423,17386996,4119747,59177718,38365584,73168565,73617245,27625735,5970607,6819644,19939935,31904591,79821897,93933709,60581278,67227442,92398073,48892201,20002147,91727510,72373496,94076128,70372191,59371804,48260151,42692881,569864,93515664,15535065,54427233,75153252,33237508,20765474,20122224,37659250,94711842,95957797,81677380,68110330,874791,526217,16380211,48360198,72274002,13862149,26139110,53393358,60102965,6793819,99021067,86460488,39553046,59614746,63015256,89637706,71316369,19272365,43045786,34295794,99658235,88047921,20642888,14626618,75986488,47361209,78766358,62452034,18783702,29188588,6038457,35192533,83210802,61623915,8099994,74743862,16445503,63152504,93790285,20568363,30998561,68041839,99297164,24058273,30543215,44983451,57020481,24591705,56461322,18833224,49598724,73124510,18663507,30764367,67030811,17976208,27665211,70541760,36685023,26102057,69136837,2208785,36505482,63967300,18466635,51507425,44245960,62430984,75128745,51047803,94516935,11161731,54762643,53802686,33304202,17081350,1431742,99333375,66271566,16424199,19891772,71657078,31126490,77787724,28928175,68316156,30787683,49236559,31733363,62496012,2917920,48088883,37620363,44847298,55753905,321665,73222868,76960303,6153406,14093520,38061439,38494874,73235980,37675718,37183543,20885148,9575954,16567550,33061250,3233569,82178706,36930650,6871053,92787493,59910624,61960400,50188404,17764950,85116755,72777973,44348328,33895336,54058788,7229550,39986008,92604458,79806380,72614359,3633375,74110882,53084256,5111370,72732205,82327024,67513640,6808825,15075176,33628349,30891921,26744917,89804152,31161687,26063929,85242963,95010552,28796059,68875490,72278539,43322743,88653118,45049308,30463802,4798568,72738685,30218878,95395112,13231279,68702632,12416768,22450468,99861373,12348118,59109400,71920426,11581181,92071990,47213183,87598888,88904910,26493119,44481640,78549759,75777973,8733068,86242799,82897371,97783876,79880247,74441448,15536795,63628376,51464002,80316608,45790169,77620120,7300047,78785507,77272628,44550764,47887470,20140249,20867149,84127901,61712234,55850790,14220886,99515901,3235882,43501211,26392416,59197747,20486294,7687278,1022677,33797252,64087743,36753250,82532312,97641116,84293052,72357096,29834512,65074535,93053405,4806458,60176618,14731700,3183975,65017137,52204879,49328608,66667729,65454636,6986898,77694700,37891451,54263880,56484900,95290172,70004753,40781449,53632373,40197395,21919959,29994197,68816413,57248122,4978543,22405120,95726235,21673260,68824981,36780454,35092039,98130363,72019362,67124282,80014588,66250369,56153345,98371444,51472275,30139692,64098930,72725103,66903004,3880712,87720882,18806856,61373987,42307449,81853704,54014062,34946859,87160386,22435353,77312810,55247455,66663942,55189057,41481685,61263068,75135448,47738185,89699445,99226875,51590803,22766820,10309525,17068582,36139942,66045587,3088684,44844121,84406788,51466049,46870723,75543508,99917599,5073754,93566986,1239555,1250437,6505939,34667286,69848388,70367851,34236719,63506281,4515343,48673079,9603598,78884452,17146629,91255408,34698428,65880522,18504197,74357852,9175338,9259676,89046466,29466406,4204661,30366150,1788101,54987042,34698463,23569917,2891150,62936963,61859581,91990218,97011160,76434144,41245325,9623492,83155442,77300457,19898053,7423788,1197320,508198,90090964,38658347,11365791,41380093,93359396,65338021,78300864,39171895,61897582,17857111,30771409,8128637,26114953,17539625,43506672,70800879,20681921,8634541,15148031,49597667,56531125,46851987,7011964,62051033,44473167,44784505,71083565,80555751,67281495,68204242,91281584,71300104,10597197,74137926,40677414,14947650,66322959,7955293,168541,32699744,59318837,63059024,57393458,52261574,21993752,91664334,53888755,86798033,76671482,4099191,13468268,64157906,73031054,71469330,90457870,10366309,79136082,48893685,92554120,51149804,36312813,17058722,18131876,83150534,45996863,21001913,84166196,76170907,67031644,27411561,8373289,4069912,84349107,99965001,6221471,9398733,77301523,82779622,73786944,50007421,29959549,59981773,92353856,56515456,51715482,7919588,82886381,40865610,93562257,247198,65271999,10358899,40781854,90061527,62115552,61271144,98739783,96906410,3294781,82726008,81274566,73021291,56473732,10961421,48658605,32161669,1569515,86118021,65038678,55143724,55602660,99729357,65851721,61141874,66137019,749283,69605283,53648154,15015906,6525948,62803858,49271185,70596786,83789391,1204161,44842615,59329511,24619760,5822202,72358170,79657802,54517921,36580610,34432810,20645197,78589145,41092102,24826575,65047700,47792865,45407418,9058407,29635537,824872,85023028,73392814,53547802,48395186,87710366,45919976,17894977,38008118,96726697,6111563,10453030,92998591,28851716,72495719,14349098,44177011,23134715,669105,16099750,71965942,6497830,98462867,91938191,4091162,29516592,24168411,36933038,40356877,34493392,23194618,77284619,50702367,79738755,9886593,8651647,23740167,3487592,27041967,112651,22113133,37560164,86821229,43357947,82427263,62740044,45075471,8129978,8115266,29401781,75407347,92803766,68939068,40686254,94090109,86543538,96735716,69579137,98943869,37501808,17016156,4064751,83368048,28734791,26734892,49641577,99125126,68128438,77187825,415901,32274392,39801351,99524975,95581843,44664587,43376279,51588015,18699206,13348726,89217461,83747892,80246713,58224549,3233084,77413012,4199704,32058615,45381876,57163802,12664567,8696647,57241521,16684829,65081429,22200378,66428795,17037369,91141395,98948034,92215320,32426752,83651211,44846932,40027975,27185644,45848907,33565483,3509435,24915585,4434662,16405341,40152546,99690194,15163258,78218845,77377183,82052050,60106217,15064655,44627776,42967683,50806615,13819358,96318094,89419466,65715134,12024238,14045383,64602895,61982238,32159704,9860968,16971929,47893286,76330843,83948335,70727211,57240218,71522968,14363867,19457589,81805959,76540481,81898046,42782652,90013093,47090124,34497327,81774825,2717150,97281847,70036438,55770687,23110625,32250045,63372756,85117092,8807940,26863229,39201414,86001008,82339363,68694897,91240048,72238278,31727379,47708850,92692283,16097038,75229462,2331773,57359924,45995325,45237957,94360702,7502255,90654836,33249630,69255765,54868730,12303248,53842979,52060076,99604946,36189527,32151165,29029316,78909561,58751351,88698958,86301513,96420429,39847321,43152977,66832478,36468541,33201905,57803235,97561745,97940276,37280276,25842078,25636669,4095116,35456853,29932657,37435892,58208470,61928316,30163921,7104732,76825057,22721500,98653983,17727650,51315460,57658654,64848072,60955663,84840549,92033260,48675329,62232211,21533347,93057697,16054533,26292919,26998766,89996536,92867155,38645117,56424103,74452589,79191827,47298834,8729146,40984766,24314885,54663246,22129328,26507214,69355476,88130087,62490109,15948937,5267545,26275734,55470718,36396314,4508700,4985896,37166608,5832946,90272749,27187213,91802888,43933006,97379790,37739481,96852321,44479073,7517032,40534591,21289531,98648327,42947632,82651278,11923835,17365188,38022834,55669657,1375023,29510992,30090481,2204165,37192445,57961282,76703609,22997281,19486173,35512853,16387575,36135,73109997,37957788,95251277,3773993,51426311,89078848,85571389,46540998,22879907,33123618,80251430,74724075,69697787,5640302,59405277,99224392,18001617,62762857,6521313,90004325,70782102,90310261,42199455,94595668,61815223,55960386,45428665,68000591,45667668,94911072,45617087,50668599,36808486,83133790,2607799,97057187,30653863,78561158,30569392,33336148,85695762,81855445,61728685,26776922,86022504,67084351,9829782,18411915,15902805,45990383,31491938,89499542,83533741,28787861,83269727,29819940,21397057,85711894,66319530,4787945,83083131,5482538,8913721,41092172,88251446,33431960,76315420,84002370,13173644,58749,61741594,9860195,53666583,58180653,26397786,96709982,11543098,8791066,92530431,68102437,38009615,23432750,92541302,84187166,23848565,9599614,84904436,65275241,10649306,19101477,71333116,39373729,27325428,37224844,9188443,45306070,89811711,8055981,35996293,62693428,52613508,38510840,26664538,45794415,33935899,52293995,62428472,22942635,49882705,90158785,17957593,94539824,75820087,10264691,79922758,16019925,62552524,34044787,24953498,81172706,69786756,30694952,74614639,38256849,54232247,75052463,99971982,91831487,66885828,20836893,67451935,30811010,79322415,44915235,79942022,42237907,77898274,38341669,30395570,9257405,62357986,7182642,93270084,6157724,1936762,11757872,42644903,48774913,69641513,29065964,67474219,72793444,21070110,55615885,66116458,83302115,28550822,61859143,12571310,42073124,7066775,7646095,60393039,33553959,16595436,176257,41442762,44060493,82979980,64055960,17385531,24733232,14723410,36845587 +31161687,27665211,2717150,40152546,60430369,82979980,55753905,91664334,12571310,86821229,37183543,83083131,3183975,47361209,92554120,51715482,38494874,70800879,22721500,13862149,35092039,43322743,5970607,49641577,86022504,40356877,77620120,4099191,12348118,29029316,75820087,26493119,75229462,98948034,85571389,75777973,48675329,43357947,96420429,45996863,47090124,66116458,79657802,61373987,90061527,14626618,61982238,33565483,31126490,81677380,11543098,67793644,36580610,17727650,569864,526217,85116755,4434662,91727510,68644627,92215320,88251446,45617087,50702367,99965001,89499542,34698428,22450468,9188443,1022677,76960303,14045383,13231279,77300457,4806458,75153252,66611759,90013093,62496012,4787945,26998766,80014588,7687278,40197395,20867149,87710366,8373289,9623492,15064655,44889423,78549759,49597667,73786944,77284619,19101477,30163921,168541,15536795,54663246,10453030,99224392,39847321,17386996,51047803,70727211,28851716,59177718,66250369,49236559,16380211,37435892,824872,38008118,74357852,22766820,59501487,60102965,78589145,61859143,66832478,98130363,29401781,55615885,32161669,76315420,1250437,45237957,1569515,63059024,65275241,34698463,73617245,82886381,79922758,89811711,96193415,6038457,6986898,75407347,20836893,2208785,44842615,36780454,73235980,73124510,54606384,60955663,8055981,89214616,41481685,68316156,47708850,27325428,15015906,67474219,25842078,39986008,35456853,79136082,72725103,19486173,65454636,77072625,50567636,38341669,20122224,30090481,19939935,26507214,89699445,37620363,6808825,62452034,44844121,54232247,76540481,55247455,91957544,74743862,62740044,57240218,29510992,99549373,99226875,36808486,95290172,74110882,81855445,45306070,38256849,58751351,85023028,43045786,92398073,53888755,94539824,96735716,64098930,83155442,84166196,33628349,11923835,79821897,62357986,71920426,96726697,23848565,36753250,65047700,55143724,92998591,39201414,29819940,33336148,28928175,5832946,88653118,8913721,97561745,81172706,68000591,71333116,88047921,50806615,1239555,30543215,83651211,67124282,67281495,72274002,64848072,69255765,74137926,3487592,22435353,26776922,89078848,61741594,54517921,20140249,78602717,45407418,57803235,21993752,22997281,10309525,67084351,92692283,74614639,70596786,9886593,44983451,53802686,63372756,46870723,16971929,79880247,61712234,24058273,1431742,38645117,54199160,52261574,4668450,9860968,29932657,53547802,92071990,61263068,28550822,84187166,47213183,39801351,99604946,33304202,73031054,92033260,14220886,62051033,61271144,65081429,34236719,15535065,52293995,82339363,54427233,26063929,59614746,66322959,83210802,99861373,14326617,14947650,76170907,42644903,18806856,30653863,54014062,73222868,54058788,56473732,98648327,8807940,96318094,24915585,8099994,96906410,2891150,44348328,17539625,40781449,70541760,20002147,78218845,8791066,32699744,14093520,46540998,82726008,17016156,92353856,26114953,33201905,38365584,29834512,49882705,84840549,32590267,69641513,7300047,77272628,20642888,247198,14363867,72357096,39373729,29994197,9398733,82779622,66885828,51464002,30764367,68128438,6497830,36505482,80251430,50188404,90272749,7066775,48360198,18783702,53648154,63967300,78785507,61623915,33553959,9575954,34295794,12024238,45794415,77187825,89804152,6157724,6793819,74441448,37192445,81774825,66428795,95957797,91831487,14731700,4064751,36468541,5111370,48892201,33237508,99971982,32159704,40677414,93057697,93562257,36812683,36139942,19898053,93566986,17976208,75543508,26102057,40984766,37739481,28734791,71657078,68875490,53666583,20885148,91802888,33431960,17764950,15948937,93515664,23110625,67031644,56461322,86301513,65880522,99658235,90457870,49271185,4119747,70420215,9603598,31733363,65271999,14349098,44550764,89046466,51588015,82897371,37560164,45428665,62115552,77413012,16445503,9860195,70367851,20765474,26392416,33797252,6871053,21919959,89419466,10264691,60581278,36685023,55470718,29516592,87160386,874791,99125126,19376156,34667286,22879907,4798568,59318837,66045587,76671482,76703609,3233569,8115266,23569917,73109997,82052050,97281847,31904591,6521313,96852321,75128745,48658605,59197747,44245960,30998561,80555751,90004325,62430984,22108665,10649306,7011964,81898046,508198,44473167,7229550,48260151,65715134,15163258,56955985,24591705,34044787,59371804,9175338,23194618,69136837,36135,26292919,36845587,64602895,75986488,65338021,20568363,38061439,85242963,13819358,2917920,99297164,7423788,17068582,321665,44784505,34497327,61728685,84406788,55602660,5640302,99021067,4095116,76330843,71300104,29635537,83302115,17037369,84684495,68702632,32151165,54868730,72614359,24826575,6153406,3233084,94090109,67030811,91938191,72278539,83133790,24619760,73168565,36312813,62936963,1197320,56515456,70372191,57020481,1375023,29959549,55669657,2607799,92604458,52613508,18699206,47792865,72373496,90310261,9599614,4069912,43933006,12416768,57248122,5482538,80316608,1204161,66667729,3633375,30771409,42199455,79322415,57658654,32250045,45848907,21673260,45075471,63015256,30694952,7685448,88444207,76825057,20486294,43376279,32426752,30787683,92803766,95010552,27411561,26744917,18833224,44481640,62803858,11365791,58224549,35512853,21289531,44479073,16019925,97011160,3088684,20681921,42073124,84904436,82532312,86543538,66271566,53842979,3880712,29466406,65017137,415901,44177011,85117092,92541302,52204879,53632373,67451935,68694897,10961421,57163802,57961282,71083565,62490109,56424103,64157906,93270084,85711894,4091162,9259676,47738185,54762643,69579137,97940276,86460488,27625735,29188588,3294781,94711842,44846932,81274566,83368048,81853704,72019362,36396314,46851987,68824981,7955293,61859581,6221471,7646095,3509435,98739783,97057187,94516935,1788101,65851721,17146629,45990383,10358899,26734892,3235882,17081350,22200378,71316369,36930650,11757872,58180653,33895336,54987042,36189527,6525948,95726235,60106217,30569392,15075176,9058407,62552524,72732205,30463802,35192533,30891921,4515343,39553046,58749,41092102,51472275,29065964,69355476,83150534,60176618,73021291,19457589,69697787,35996293,22113133,77301523,51466049,97783876,37675718,26863229,669105,64087743,57359924,32274392,86001008,52060076,78766358,65074535,90158785,58208470,92692978,56531125,99515901,84293052,68939068,83747892,91255408,84127901,59329511,66903004,48774913,48088883,63506281,93790285,78561158,82651278,6505939,51426311,68102437,37224844,94911072,18663507,17894977,57241521,4204661,43501211,18411915,7502255,33935899,45790169,55189057,63628376,37166608,16595436,26139110,44060493,84349107,17857111,19891772,16684829,11161731,24953498,112651,79806380,33061250,67227442,89996536,22405120,88904910,83533741,8733068,45667668,16424199,76434144,17385531,25636669,57393458,99917599,68204242,8651647,45995325,49328608,4508700,34432810,48673079,44627776,47298834,34493392,47887470,90090964,89637706,48893685,83269727,42237907,93933709,44915235,66137019,6819644,38658347,59910624,8129978,7182642,41442762,78300864,80246713,30811010,71469330,18001617,33249630,3773993,5822202,30366150,61141874,91281584,7919588,23740167,83948335,38510840,81805959,62428472,72793444,71522968,75135448,16567550,15902805,98462867,8634541,40865610,87598888,27187213,56484900,68816413,69786756,79191827,83789391,50668599,17058722,77312810,94595668,99524975,61815223,24168411,40781854,72777973,55770687,88130087,38009615,38022834,40686254,92530431,82178706,92787493,9257405,98371444,31491938,93359396,70004753,43506672,68110330,86798033,55960386,62762857,95251277,97379790,91990218,75052463,82427263,77787724,86118021,93053405,21070110,94076128,70782102,61928316,51590803,45919976,77898274,73392814,5267545,42967683,18131876,2204165,30218878,95395112,63152504,19272365,7104732,21533347,33123618,59109400,15148031,92867155,69848388,45049308,51315460,22942635,4199704,749283,55850790,53393358,13348726,32058615,6111563,84002370,16405341,72358170,34946859,77694700,28796059,98943869,42307449,42782652,91141395,26664538,4985896,16099750,9829782,18504197,70036438,49598724,61960400,16097038,72238278,50007421,27185644,62693428,8696647,37891451,41245325,37659250,13468268,37501808,21001913,13173644,59405277,24314885,72738685,65038678,68041839,39171895,74452589,59981773,24733232,11581181,51507425,40027975,27041967,20645197,42692881,56153345,16054533,71965942,77377183,33699435,8128637,79942022,97641116,51149804,16387575,61897582,30139692,23134715,89217461,12664567,28787861,87720882,42947632,78884452,99690194,86242799,40534591,18466635,48395186,31727379,44847298,10597197,41092172,26397786,10366309,47893286,13470059,14723410,17365188,41380093,62232211,37280276,96709982,94360702,88698958,8729146,54263880,30395570,66663942,176257,17957593,5073754,72495719,2331773,22129328,43152977,78909561,64055960,99333375,7517032,36933038,66319530,99729357,26275734,95581843,12303248,37957788,79738755,60393039,85695762,1936762,98653983,90654836,4978543,91240048,67513640,74724075,21397057,45381876,23432750,44664587,69605283,82327024,53084256 +62051033,53802686,65851721,37620363,89046466,52204879,92398073,31904591,43376279,33628349,29834512,61373987,20486294,62936963,99658235,3509435,26392416,59197747,65338021,57248122,96193415,82339363,86118021,89214616,73617245,24915585,74724075,52261574,91802888,99515901,99917599,69355476,37560164,34493392,78766358,66667729,66611759,38658347,69786756,90457870,84127901,51464002,71316369,67793644,69255765,85116755,39373729,64602895,874791,84349107,73222868,4099191,10366309,49328608,98948034,16054533,35092039,2204165,57803235,22766820,2917920,73235980,57020481,39847321,16099750,11365791,66271566,33935899,55247455,4119747,7300047,3233084,45990383,66137019,59614746,66903004,57240218,7687278,4668450,71333116,64087743,10309525,84684495,23194618,92033260,29188588,17081350,20867149,87598888,29994197,27187213,65271999,66250369,1022677,44889423,75229462,44550764,35192533,36930650,6505939,73168565,81274566,69848388,12348118,61263068,26744917,24168411,1431742,53393358,97641116,68000591,51047803,78549759,66045587,40865610,34295794,49641577,60430369,37739481,71657078,89217461,14326617,3880712,71920426,12303248,39553046,77187825,79806380,37659250,1204161,45848907,63967300,21533347,22879907,45237957,96726697,6793819,247198,24058273,87160386,33304202,37675718,44844121,8129978,93053405,44842615,96906410,68875490,67030811,56153345,61141874,72373496,78884452,28796059,8729146,65074535,36139942,30463802,55143724,74137926,38494874,40534591,22113133,99297164,68816413,77272628,55470718,36812683,1569515,55850790,47298834,26734892,76434144,60106217,78909561,112651,73021291,40677414,83651211,52293995,49597667,66319530,39171895,24619760,68316156,90272749,1239555,62693428,29819940,72777973,20568363,37183543,94711842,29029316,69136837,26998766,79821897,19486173,75777973,53842979,38061439,43357947,18699206,16387575,61623915,95957797,6808825,60581278,26863229,92867155,44627776,38341669,57241521,2891150,88653118,5073754,91938191,48673079,21001913,71083565,53666583,97379790,15948937,18783702,53632373,59371804,34497327,55669657,70782102,3487592,47361209,63372756,93790285,73392814,46851987,65275241,93515664,37891451,91240048,60176618,33699435,73124510,98943869,45996863,21289531,40686254,6525948,17058722,30998561,16019925,14626618,61960400,92530431,86301513,29510992,669105,57359924,37224844,22108665,34044787,82726008,43501211,22942635,508198,50188404,74110882,19376156,15015906,76960303,18663507,51715482,64055960,75543508,44784505,81805959,67451935,6153406,86460488,91990218,47090124,84002370,6986898,19457589,27665211,59910624,168541,16445503,36468541,98130363,89699445,30218878,4515343,26063929,34667286,79922758,7104732,35512853,83155442,68204242,14093520,65454636,17764950,30891921,33249630,81677380,92604458,54199160,61271144,44847298,18833224,83789391,20885148,75128745,34236719,10358899,36312813,31161687,81898046,9860195,41481685,15535065,66832478,56531125,58224549,44177011,92215320,83368048,51149804,91281584,65017137,81855445,17365188,93566986,92787493,6038457,86001008,83210802,3183975,93270084,45075471,92541302,72019362,30366150,20645197,88047921,10453030,99021067,46540998,33895336,64098930,66116458,13862149,61728685,12024238,82327024,66885828,96735716,82886381,6497830,44473167,73031054,20002147,95726235,31727379,62452034,76540481,48774913,77694700,43506672,1788101,98653983,62430984,78218845,90090964,17727650,32274392,68702632,88444207,52613508,7502255,53648154,22450468,49882705,67124282,8651647,57393458,24591705,77284619,77413012,49598724,79738755,47887470,16971929,99224392,21993752,65047700,83083131,94090109,30694952,42199455,7919588,85023028,9259676,42782652,44983451,89419466,77301523,7646095,97281847,72614359,33237508,99125126,46870723,22997281,18466635,61712234,30787683,37166608,13468268,80014588,72725103,32159704,95251277,33565483,30771409,34698428,72278539,95010552,36780454,26493119,39801351,41245325,20122224,14045383,45381876,97561745,69697787,65038678,4806458,87720882,50668599,13470059,47708850,19891772,8696647,77312810,99226875,76315420,23432750,10597197,5111370,72738685,43045786,3233569,92998591,48088883,55189057,54663246,91664334,82779622,62762857,68128438,75052463,45995325,51466049,51588015,55753905,9188443,44060493,88130087,78785507,23110625,47213183,8373289,2717150,526217,20765474,97940276,99604946,30653863,16405341,92692978,98371444,54987042,36580610,45306070,40781854,16097038,13231279,79880247,56484900,3633375,76330843,98648327,68041839,5822202,23134715,76170907,34432810,60955663,38365584,95395112,86022504,29959549,44846932,92554120,1375023,54606384,86821229,55602660,68694897,77072625,75153252,89499542,26776922,40356877,77620120,91727510,6819644,21070110,84406788,28787861,83533741,59501487,63628376,8128637,36135,16595436,70727211,33061250,94076128,23569917,63059024,54517921,54014062,65880522,62496012,79191827,45794415,41380093,85711894,98739783,67227442,1250437,99524975,54762643,58751351,47792865,97783876,74743862,86242799,824872,27325428,67281495,5482538,44481640,43322743,84840549,72357096,7423788,16567550,9829782,13348726,30139692,78589145,9575954,81853704,93933709,14220886,66322959,47738185,5970607,30163921,50702367,22129328,14723410,11161731,40152546,24826575,83302115,36685023,1197320,30543215,6157724,22721500,82651278,26139110,16684829,36505482,44245960,62232211,4434662,32250045,54058788,51472275,26507214,96852321,54263880,95290172,19939935,3088684,92692283,48360198,77377183,40197395,68644627,56461322,88904910,61859581,60102965,78300864,62803858,2208785,4069912,10961421,47893286,9886593,79136082,14349098,4204661,26275734,13173644,3235882,28928175,21673260,40781449,17037369,31126490,7685448,15902805,72495719,70420215,9257405,41092102,28550822,8791066,70372191,76825057,82427263,81774825,61859143,69579137,35996293,9603598,99971982,62490109,23848565,26664538,27625735,70596786,54427233,99333375,62357986,61741594,48675329,72274002,4064751,44664587,23740167,42307449,32161669,58749,37501808,17068582,18504197,70800879,4798568,3773993,94911072,78561158,17976208,63015256,94595668,65715134,67474219,69605283,18806856,22435353,25636669,6871053,55960386,61928316,17386996,38645117,74614639,24953498,26397786,56424103,17146629,13819358,63506281,66428795,14731700,8099994,8913721,9398733,749283,29065964,37957788,59329511,4199704,26292919,6221471,53084256,38008118,9599614,33553959,71300104,99861373,42947632,45428665,93359396,9058407,62552524,3294781,29635537,82532312,7955293,21919959,57163802,176257,57658654,59318837,91141395,82897371,55770687,17957593,38009615,30764367,84166196,50806615,19101477,74357852,85242963,44348328,94539824,34698463,89804152,16424199,29932657,56473732,41092172,84293052,321665,2607799,43933006,7066775,18411915,8807940,33797252,37435892,73786944,29401781,80555751,4508700,92353856,61982238,9623492,4091162,36753250,93057697,89637706,53888755,65081429,96709982,24733232,59981773,80246713,75820087,64848072,42692881,42073124,80316608,70004753,19898053,7229550,14947650,8055981,68102437,12664567,9860968,89811711,43152977,91831487,59177718,30811010,83133790,17857111,72358170,94516935,36189527,96318094,45407418,62740044,37192445,51426311,40027975,37280276,71965942,15075176,30090481,86543538,70036438,82052050,569864,32426752,75135448,36396314,30395570,20140249,4787945,89996536,83150534,12416768,82178706,67084351,82979980,40984766,51590803,7182642,99690194,18131876,22405120,77300457,5640302,71469330,45667668,68824981,38022834,9175338,98462867,99965001,42967683,89078848,83948335,88251446,50567636,61897582,56515456,48260151,63152504,48395186,74452589,90654836,15148031,17539625,7517032,88698958,93562257,11581181,48892201,85571389,33201905,67513640,77898274,90158785,26114953,31491938,54232247,78602717,5832946,27411561,38256849,77787724,90013093,22200378,15064655,28734791,91255408,64157906,44479073,99549373,97011160,58208470,27041967,83747892,74441448,12571310,61815223,39986008,70367851,48893685,29466406,75407347,53547802,45049308,20642888,62115552,11757872,48658605,8634541,33123618,39201414,50007421,41442762,87710366,62428472,49271185,8115266,33336148,7011964,31733363,69641513,91957544,8733068,76703609,17894977,19272365,6521313,36933038,10649306,6111563,72238278,35456853,99729357,44915235,17385531,56955985,58180653,42237907,86798033,60393039,10264691,15163258,26102057,68939068,59405277,95581843,4095116,4985896,5267545,72793444,25842078,67031644,97057187,36808486,66663942,96420429,32151165,57961282,27185644,85117092,20681921,17016156,51507425,49236559,32590267,55615885,54868730,2331773,32699744,84187166,45919976,68110330,45790169,11543098,76671482,16380211,51315460,70541760,28851716,42644903,90310261,73109997,75986488,79657802,81172706,18001617,415901,72732205,32058615,38510840,52060076,85695762,1936762,80251430,92071990,21397057,94360702,15536795,79322415,84904436,92803766,29516592,4978543,30569392,36845587,24314885,45617087,90061527,59109400,20836893,79942022,14363867,34946859,33431960,11923835,90004325,83269727,71522968 +9398733,63967300,62740044,85023028,42237907,62936963,56515456,77272628,37183543,66045587,19486173,10366309,62430984,78602717,97011160,29834512,73617245,4099191,45428665,28796059,40356877,33797252,38365584,99965001,95957797,38022834,87598888,54606384,48395186,17764950,90457870,99515901,64098930,36930650,55602660,79191827,55247455,7182642,8696647,26744917,52613508,36580610,7229550,77413012,9257405,99917599,89046466,30764367,37501808,62357986,29959549,14093520,45075471,27041967,526217,11543098,61741594,70800879,29932657,34698428,112651,99861373,83155442,47298834,8099994,95251277,4119747,62452034,68875490,61271144,1204161,98948034,69786756,89078848,20765474,43357947,80251430,92215320,60430369,34497327,51590803,98371444,80316608,37739481,54762643,16387575,61982238,33336148,8651647,28851716,13819358,99524975,14349098,1239555,44479073,18504197,43152977,33304202,70372191,89996536,32058615,96193415,66903004,61623915,73222868,92554120,44550764,36685023,50188404,16595436,7955293,5640302,4064751,43933006,74614639,66832478,26397786,55470718,20486294,20645197,7104732,89214616,18663507,77284619,32699744,15148031,1431742,87720882,43506672,97783876,68128438,69605283,65880522,6505939,68644627,74110882,57803235,68702632,83789391,9058407,9829782,45306070,93270084,99224392,65074535,68102437,99021067,82979980,33699435,77898274,168541,73392814,22942635,35512853,86001008,93933709,69579137,76671482,18833224,84187166,33123618,38009615,90272749,65454636,81172706,8733068,35092039,90090964,80014588,98653983,20885148,16567550,4199704,72357096,24733232,97281847,43501211,73168565,48360198,70367851,51464002,90013093,24619760,65081429,824872,36753250,47090124,10597197,16019925,10309525,37620363,31904591,73124510,12024238,60176618,17365188,16445503,10453030,20642888,8807940,508198,73235980,5267545,30771409,68939068,19376156,50806615,60106217,42947632,53802686,2891150,70727211,29065964,77787724,8913721,53084256,78909561,3233084,53632373,54199160,56153345,98739783,26392416,49597667,19939935,55143724,99549373,30163921,92353856,85571389,38061439,45995325,93562257,53393358,874791,85242963,30694952,36808486,1022677,13231279,1788101,40677414,14947650,71083565,45848907,50702367,4985896,2208785,7685448,40197395,30463802,92604458,26998766,77377183,36505482,39553046,44842615,29401781,92541302,54014062,65338021,77300457,27325428,72373496,27665211,6157724,44784505,17037369,36312813,21397057,91727510,76540481,11581181,60581278,27411561,13173644,18466635,24058273,44844121,38256849,69848388,67084351,45407418,78589145,75777973,67793644,46851987,36780454,56461322,78549759,44245960,35996293,20122224,37280276,10649306,62803858,15948937,30787683,12348118,91281584,79806380,88653118,39801351,99729357,57393458,20140249,89637706,48892201,17068582,73786944,19457589,2717150,44889423,62496012,51426311,91831487,73021291,2204165,33249630,38341669,20002147,96318094,59197747,89699445,6793819,93566986,57248122,61897582,86460488,75052463,75543508,67474219,68041839,54058788,21289531,51047803,3509435,14045383,38008118,247198,3773993,61141874,79880247,84127901,22129328,7517032,49271185,71333116,2607799,62051033,9860195,23134715,30543215,18783702,91240048,67281495,69136837,78766358,72777973,29819940,6808825,66250369,84293052,34432810,54987042,32151165,84904436,3233569,6221471,34698463,13862149,44177011,68816413,22721500,56473732,58208470,35456853,98462867,68824981,83150534,44481640,38658347,94090109,82651278,40781854,66667729,75128745,63059024,33553959,84406788,52204879,32161669,41092102,11923835,23740167,11365791,48675329,16684829,28787861,99658235,33935899,99604946,66611759,44627776,62762857,4798568,29635537,71657078,24826575,30998561,70420215,21070110,96735716,81898046,79922758,77312810,71300104,77301523,74724075,64087743,43376279,33565483,7919588,74452589,92692978,71316369,12303248,57240218,54427233,26292919,29994197,49882705,34946859,65038678,59981773,91957544,26275734,35192533,31733363,8115266,4668450,20681921,6111563,72274002,30653863,98648327,42199455,41245325,59177718,97057187,19898053,34236719,37675718,81677380,13348726,669105,97561745,97940276,88047921,28928175,17857111,64602895,49236559,48088883,9603598,51466049,57241521,45667668,9623492,89419466,71965942,44060493,3088684,68316156,91990218,48673079,4515343,16099750,79738755,1250437,5970607,34493392,6525948,30218878,89811711,7423788,98943869,65017137,24168411,46540998,78561158,84840549,83133790,25636669,6497830,62490109,90061527,79136082,39171895,3880712,24915585,42307449,94911072,66137019,79942022,68000591,71522968,6986898,26493119,2917920,69641513,10358899,4095116,18806856,32159704,19272365,18699206,64848072,40027975,6153406,52293995,82726008,3235882,95726235,15075176,51588015,8729146,83651211,76315420,55615885,83948335,21001913,14220886,51472275,6819644,42782652,41481685,41380093,62693428,70036438,37659250,44348328,94516935,31491938,85116755,88904910,93790285,8373289,15536795,84349107,33628349,61263068,81853704,22108665,92530431,88251446,17386996,415901,98130363,23569917,58224549,2331773,9175338,7066775,1569515,9188443,97641116,96906410,76170907,30139692,33237508,63152504,11161731,37891451,96852321,72732205,55189057,61373987,43322743,31161687,56424103,19891772,55669657,30366150,16405341,83747892,18001617,54663246,49328608,75820087,50567636,36468541,51715482,7687278,3294781,47887470,22879907,29188588,53648154,48260151,1936762,91802888,9259676,40686254,45790169,36189527,76330843,3487592,67227442,91938191,88698958,44846932,61728685,43045786,31727379,30569392,54517921,28550822,73031054,27625735,29516592,48774913,22405120,65047700,15535065,62552524,18411915,65851721,15015906,38494874,72725103,88130087,17016156,47792865,59501487,57020481,33201905,16380211,45381876,32250045,57359924,44983451,17146629,34295794,47361209,29510992,85117092,36812683,45996863,67031644,82427263,60955663,45617087,76434144,49598724,26102057,99125126,83210802,63506281,99226875,72614359,96726697,12416768,72238278,92998591,82886381,99690194,58749,58180653,16054533,34044787,6521313,39373729,40152546,24953498,52261574,71920426,79657802,61960400,32426752,95395112,97379790,66885828,90654836,82178706,30090481,92033260,93515664,49641577,69255765,45919976,81274566,37957788,67513640,20867149,99297164,47893286,22435353,16971929,8055981,68694897,569864,8791066,59910624,78218845,83083131,47738185,13470059,23194618,40534591,42073124,48658605,82532312,67124282,94711842,15902805,60102965,4508700,14731700,20568363,5832946,14723410,17058722,66428795,33431960,45990383,78300864,26734892,59109400,14626618,77187825,67030811,61815223,53842979,89217461,23432750,39847321,68110330,65275241,31126490,5111370,45237957,59318837,6038457,17976208,22200378,77620120,38510840,94539824,61712234,78884452,39201414,62115552,7300047,92803766,56484900,22450468,63628376,81855445,21993752,91255408,40781449,21919959,32590267,99333375,4806458,8634541,59614746,92398073,52060076,36845587,66116458,56531125,53888755,84002370,72793444,83533741,66322959,14326617,90158785,60393039,74137926,16424199,72495719,82779622,47213183,10961421,7011964,69355476,9575954,9599614,25842078,86798033,55960386,75407347,30395570,4978543,57961282,26507214,75986488,47708850,4091162,5822202,7646095,66663942,66319530,82052050,95010552,4434662,53666583,92692283,24591705,99971982,70004753,69697787,81805959,90310261,17539625,70596786,79821897,59371804,39986008,84684495,14363867,72358170,321665,9886593,22997281,26139110,33061250,30891921,4204661,45794415,6871053,92867155,86242799,74743862,26114953,23110625,26063929,17957593,55770687,12571310,40984766,37224844,74357852,76703609,30811010,15163258,4787945,86543538,1197320,24314885,75135448,91141395,76960303,94360702,3633375,53547802,18131876,66271566,54232247,51507425,68204242,80246713,15064655,95581843,96709982,89499542,87160386,17727650,48893685,44473167,7502255,5073754,38645117,46870723,89804152,44664587,83302115,40865610,57658654,5482538,21533347,42692881,91664334,16097038,11757872,23848565,67451935,65271999,74441448,8129978,72019362,50668599,12664567,93359396,13468268,58751351,90004325,61859581,17081350,92071990,29029316,36135,83269727,33895336,92787493,64157906,27185644,86301513,3183975,75153252,176257,37166608,70541760,749283,51315460,61859143,57163802,22766820,85695762,87710366,50007421,63372756,64055960,32274392,37435892,63015256,22113133,54868730,59405277,51149804,54263880,76825057,77694700,9860968,26863229,65715134,93053405,29466406,37560164,70782102,71469330,34667286,4069912,95290172,80555751,72738685,41092172,82339363,83368048,85711894,59329511,82897371,36139942,8128637,44847298,77072625,94076128,62232211,36933038,45049308,84166196,56955985,72278539,26664538,1375023,19101477,62428472,55753905,36396314,82327024,79322415,61928316,42644903,26776922,86118021,88444207,20836893,42967683,86821229,27187213,86022504,96420429,17385531,21673260,55850790,37192445,44915235,78785507,73109997,75229462,93057697,17894977,94595668,41442762,81774825,28734791,10264691 +84349107,68644627,26139110,66250369,77312810,79821897,70367851,14626618,73222868,4099191,91938191,24058273,1204161,31904591,98739783,321665,74137926,60430369,55753905,10597197,4787945,6986898,1569515,26734892,32426752,74110882,49328608,85242963,92398073,23194618,99515901,72777973,42782652,824872,33699435,66832478,66611759,83651211,41092172,17146629,85116755,55602660,96193415,72373496,99861373,60176618,55470718,10309525,63059024,44348328,89214616,76170907,52613508,54014062,48893685,37501808,27665211,86821229,29188588,77272628,79191827,50702367,99917599,99549373,37675718,37891451,65074535,65038678,49236559,68000591,78561158,39171895,9623492,29510992,7517032,20122224,65017137,5111370,10366309,62430984,32058615,95581843,26063929,65275241,30395570,70800879,78766358,34497327,93270084,68816413,20885148,99524975,20486294,75777973,32250045,18783702,85023028,16019925,22879907,74724075,68824981,97561745,23110625,99297164,99021067,7011964,96726697,82886381,51047803,32159704,29994197,61859143,80014588,6153406,44889423,77620120,40865610,71333116,82327024,50567636,57359924,66116458,60102965,7646095,43501211,8807940,59371804,36580610,52204879,6221471,95957797,15535065,526217,12664567,57248122,34946859,69136837,18699206,88047921,50188404,37739481,4798568,92692283,5822202,55247455,54199160,29029316,5832946,15148031,669105,33237508,97641116,29466406,17058722,68204242,57803235,75407347,28787861,99604946,35092039,81855445,90457870,19939935,16054533,48260151,94090109,20867149,62051033,74614639,37166608,56515456,68875490,89811711,6525948,67281495,44177011,1431742,48673079,30366150,569864,37183543,26507214,62496012,21919959,66667729,91990218,88653118,42199455,54263880,61373987,43506672,29819940,39553046,8696647,36933038,15948937,19898053,92554120,44983451,9599614,74743862,70372191,16099750,38061439,34432810,52261574,79922758,34236719,83789391,30998561,16684829,37620363,8129978,12303248,39373729,88444207,3509435,28796059,86001008,16971929,86118021,28550822,4985896,46870723,56153345,45617087,26776922,36312813,78300864,75128745,48360198,67793644,50806615,35456853,90090964,54868730,70004753,68316156,50668599,70036438,6505939,86543538,99965001,93933709,17068582,32590267,33565483,43045786,99224392,98130363,90061527,44473167,54427233,24619760,83155442,55143724,77072625,9886593,62936963,7687278,3233084,34698428,51472275,15075176,34295794,80246713,91957544,96318094,59197747,36930650,7229550,63967300,61141874,20002147,38008118,8651647,53666583,24733232,81853704,60106217,12348118,7685448,74441448,17037369,92604458,29834512,84684495,47792865,66903004,55960386,61271144,69786756,66428795,8128637,44784505,23848565,48658605,4668450,40686254,65851721,24953498,34493392,32151165,45990383,97783876,16424199,15015906,76434144,16595436,45790169,64087743,8913721,17727650,59910624,93515664,90310261,28928175,30543215,4119747,2208785,49271185,11365791,56424103,38341669,63372756,99658235,40197395,78549759,21993752,7502255,6111563,43357947,48892201,29065964,48774913,98462867,508198,95395112,45919976,73168565,23134715,25842078,84127901,29516592,31126490,77694700,9058407,81677380,40781854,54987042,53084256,56461322,26292919,94516935,14326617,19101477,3294781,47893286,43376279,38658347,17764950,22942635,95726235,4515343,63506281,77787724,43322743,82427263,42237907,45848907,81172706,83302115,78602717,82726008,81274566,42644903,33553959,64602895,45306070,3235882,9257405,35192533,60581278,45995325,94711842,61263068,11543098,20140249,9575954,76315420,92998591,16567550,14947650,19891772,71316369,78884452,45075471,24168411,75229462,67124282,61859581,51464002,44842615,57658654,7423788,59109400,1936762,33249630,38494874,92033260,73124510,18504197,64848072,84904436,8373289,3183975,87598888,36685023,24591705,40534591,62357986,21289531,45667668,30163921,83150534,33797252,27187213,37957788,84293052,66137019,79738755,82339363,55770687,87160386,42073124,33123618,44847298,56531125,168541,45237957,91727510,75153252,6038457,66663942,2917920,53547802,20836893,80316608,95290172,54663246,26744917,30139692,112651,42947632,11923835,4064751,19272365,39986008,21397057,66045587,71083565,69697787,49598724,30764367,51588015,57020481,76960303,82979980,4204661,68041839,9603598,8733068,874791,45381876,92530431,68128438,93053405,73392814,23569917,9188443,71657078,75543508,32699744,89046466,13173644,8634541,20642888,42692881,96735716,94911072,8099994,44245960,16405341,22766820,86460488,83133790,62452034,69579137,65715134,26863229,68110330,17857111,70420215,77413012,11581181,36505482,77284619,11757872,44550764,40027975,1197320,97940276,6871053,85571389,44481640,30891921,76540481,30463802,45996863,13231279,88130087,54606384,70596786,89419466,38256849,13470059,65271999,68102437,72274002,92787493,33628349,6497830,97011160,5970607,80251430,20645197,18411915,8055981,98371444,4091162,22129328,6808825,35512853,22108665,62803858,14220886,7919588,58749,96906410,26392416,92692978,67227442,10358899,75986488,33895336,40152546,82651278,31161687,50007421,89804152,89996536,14045383,91802888,74452589,47213183,30811010,83210802,10961421,68939068,30090481,57240218,22721500,95010552,7955293,26664538,16445503,15163258,82897371,65338021,39847321,15064655,41245325,10649306,28851716,42967683,96420429,97281847,68702632,32161669,33304202,82779622,72738685,63152504,54058788,39801351,74357852,20568363,15536795,73235980,7300047,31733363,67031644,9175338,21533347,97379790,93566986,11161731,58208470,27041967,7104732,5073754,67084351,78909561,29932657,16387575,25636669,67474219,49597667,9860195,33431960,47708850,57241521,17539625,15902805,72495719,17894977,82178706,87710366,12416768,40677414,93790285,51466049,64157906,17385531,99971982,59981773,29959549,41380093,86242799,1022677,62232211,72357096,89499542,58224549,83533741,23432750,47361209,8729146,14731700,61982238,86022504,55615885,6819644,61712234,90013093,62552524,53888755,1375023,33336148,69848388,46851987,87720882,91281584,31727379,69355476,96709982,22450468,91664334,44479073,98648327,81805959,66319530,62762857,99226875,79136082,16380211,98943869,18833224,37659250,71920426,94539824,70541760,68694897,13348726,72725103,17081350,6793819,749283,53842979,61815223,43152977,33935899,61960400,36812683,45407418,72732205,4508700,41442762,38365584,14093520,84166196,44915235,18663507,72614359,95251277,29401781,36845587,73031054,17976208,26102057,12024238,59177718,35996293,26114953,98948034,62693428,1788101,57393458,3880712,53632373,52293995,41092102,20681921,26397786,88251446,3088684,71522968,20765474,78589145,36396314,22435353,93562257,3773993,77377183,24915585,72278539,28734791,85711894,48675329,33201905,37560164,83368048,53648154,79657802,73617245,71300104,13468268,90272749,58751351,24314885,39201414,83948335,19486173,77301523,66322959,53393358,46540998,72358170,22405120,24826575,41481685,17016156,26998766,22200378,93057697,53802686,22113133,2717150,66885828,84840549,59614746,45428665,36139942,59329511,44060493,247198,59405277,1250437,13862149,14723410,61728685,67030811,59318837,415901,44627776,36780454,18466635,61623915,48395186,6157724,19376156,61897582,14349098,63628376,9398733,83269727,27325428,88698958,86798033,54762643,51507425,58180653,78218845,44844121,84406788,22997281,47738185,79880247,3633375,37280276,2891150,33061250,4095116,91240048,73786944,43933006,79806380,89699445,81898046,47298834,36808486,40356877,13819358,92541302,89217461,30218878,14363867,38009615,76330843,79942022,18806856,81774825,67451935,42307449,77300457,49882705,17957593,72019362,8791066,77898274,21673260,26493119,94595668,3233569,9259676,80555751,89078848,40984766,82532312,34698463,65047700,55669657,2204165,84002370,34044787,85695762,73109997,57163802,84187166,5482538,78785507,92803766,30787683,72238278,47090124,40781449,16097038,36753250,71965942,38645117,98653983,4806458,21001913,45794415,94076128,62740044,37224844,97057187,26275734,77187825,19457589,52060076,56473732,56484900,59501487,91831487,30694952,27185644,21070110,64055960,17365188,66271566,4199704,9829782,37435892,30653863,30569392,76825057,91255408,75820087,63015256,65454636,4434662,83747892,51315460,4069912,90654836,92353856,99690194,18001617,73021291,99125126,5640302,36189527,69255765,51426311,176257,18131876,96852321,69641513,89637706,75052463,47887470,37192445,3487592,34667286,55850790,51149804,90004325,17386996,94360702,54517921,69605283,32274392,92215320,1239555,76671482,2607799,82052050,12571310,85117092,76703609,88904910,8115266,51715482,7066775,86301513,44846932,70727211,27411561,92071990,48088883,62115552,71469330,51590803,93359396,49641577,38510840,10453030,5267545,30771409,60393039,99333375,56955985,70782102,29635537,36135,55189057,62428472,36468541,64098930,67513640,9860968,57961282,61741594,6521313,99729357,61928316,92867155,38022834,4978543,65081429,62490109,23740167,91141395,2331773,27625735,45049308,83083131,60955663,7182642,44664587,54232247,75135448,79322415,72793444,65880522,31491938,90158785,10264691 +77620120,89499542,57803235,67281495,29029316,10366309,669105,4099191,66250369,99604946,36312813,321665,35192533,43376279,16097038,19939935,55753905,65074535,14626618,55470718,6153406,39801351,68644627,34497327,247198,39171895,5970607,24058273,68702632,37620363,75229462,51464002,26292919,67793644,73235980,76315420,38658347,90013093,48892201,59109400,40984766,99549373,824872,35092039,95726235,26392416,61373987,12664567,5267545,29834512,30163921,62430984,69355476,29932657,63059024,99515901,20836893,19101477,66667729,5822202,74110882,17365188,98739783,55247455,34698428,28734791,52261574,49882705,15535065,71657078,27665211,32159704,70596786,56515456,83302115,75543508,54427233,77272628,54199160,65038678,79880247,83651211,86821229,96735716,8651647,16445503,85242963,36505482,65851721,26063929,33061250,12348118,70004753,44481640,569864,77312810,98371444,22405120,4985896,40865610,30463802,61728685,30395570,75777973,66045587,74137926,80014588,98130363,48360198,95251277,23848565,44889423,31904591,49236559,91727510,47090124,168541,45075471,22113133,69579137,89046466,54663246,84406788,80316608,4119747,1204161,91990218,87160386,95581843,65715134,66832478,99965001,39373729,56424103,52060076,62496012,65017137,14220886,37675718,61712234,85116755,34698463,92554120,93933709,78884452,99226875,60106217,5482538,4064751,53632373,55143724,40781854,31126490,32151165,68939068,22766820,26507214,18504197,7919588,62232211,20122224,69136837,96193415,30998561,37739481,68875490,15948937,94516935,1569515,55960386,33249630,43506672,6111563,72274002,20486294,43933006,6871053,55602660,86460488,16387575,73786944,96726697,51047803,77187825,31727379,65338021,36930650,6819644,10358899,10309525,93790285,16054533,78549759,4515343,7685448,90090964,67451935,82897371,40197395,83150534,52293995,45790169,29188588,62740044,26275734,7687278,69605283,25842078,22108665,38494874,59910624,51149804,49641577,16971929,37659250,79942022,71333116,61741594,59329511,26139110,47361209,54014062,88444207,4668450,1239555,30139692,8099994,16019925,72777973,46851987,20002147,94360702,15075176,12571310,57240218,75128745,29466406,84840549,32161669,42199455,63967300,37192445,76960303,2717150,66428795,93053405,73392814,74743862,60430369,87598888,82427263,94911072,99524975,96318094,44842615,92033260,31733363,33431960,176257,62552524,77694700,29994197,26863229,36580610,98462867,64602895,61960400,22997281,9058407,83210802,78589145,48260151,37224844,75407347,55770687,50806615,75153252,46870723,75820087,3509435,72738685,21993752,30366150,33237508,9175338,9188443,84349107,61263068,48673079,53393358,3487592,72614359,526217,32250045,99658235,62936963,88047921,86001008,57359924,53842979,62051033,33797252,74724075,1250437,75986488,24953498,17385531,76434144,58751351,14731700,32426752,33565483,21533347,38341669,73124510,85023028,18699206,48774913,44846932,20765474,51472275,50702367,99021067,9886593,61982238,15148031,8128637,56531125,3233084,89214616,82726008,28796059,98648327,71316369,83789391,17894977,60176618,38061439,26493119,91957544,19376156,29959549,44473167,78602717,1431742,26114953,94711842,40152546,73031054,12416768,18833224,55850790,44915235,93057697,92541302,82979980,81805959,47792865,48893685,53666583,29065964,44784505,69641513,96709982,66611759,35456853,23134715,33201905,27041967,64848072,68694897,88251446,82886381,4787945,77284619,81677380,58224549,88653118,39201414,98948034,62693428,63628376,84187166,7502255,45617087,7646095,72019362,23432750,62452034,15536795,84684495,77072625,4798568,91664334,80246713,6986898,62803858,38365584,94090109,14093520,44844121,44177011,33628349,36812683,112651,7517032,66137019,54517921,5111370,3633375,44348328,19457589,33304202,81855445,89078848,91802888,18783702,30694952,52613508,5832946,9599614,2331773,24733232,26998766,40686254,4508700,90272749,8729146,7104732,33699435,18131876,68102437,73109997,71300104,80251430,47213183,17068582,45237957,16405341,70800879,61859143,17857111,79821897,89804152,34295794,11581181,92692283,60581278,26734892,73617245,45848907,9860195,8807940,34236719,40534591,61271144,20645197,89419466,44550764,45049308,57658654,77787724,88904910,14947650,89637706,26664538,90457870,6808825,67474219,42782652,415901,60102965,17081350,97561745,52204879,99861373,99333375,66319530,51590803,51426311,97940276,42947632,59501487,84904436,7300047,72373496,42692881,4069912,68816413,6793819,9860968,53084256,17764950,14326617,7423788,3088684,70541760,32699744,92398073,36808486,45407418,99297164,76825057,6525948,78766358,37957788,68824981,11365791,1788101,64087743,70372191,61815223,47708850,59371804,77301523,3294781,749283,71083565,15163258,17957593,49328608,95957797,63015256,42967683,22129328,44479073,17037369,50668599,93515664,69255765,31161687,26776922,93270084,15902805,50567636,72495719,24591705,20140249,30543215,21001913,77413012,55189057,90310261,82339363,18663507,81172706,2891150,1197320,45428665,17016156,76703609,28787861,17146629,91831487,16380211,46540998,84166196,72725103,36780454,76330843,7011964,91938191,7955293,8373289,39847321,14045383,30764367,86543538,76540481,66903004,18411915,55669657,65880522,54868730,6505939,10649306,40027975,84293052,22721500,16424199,85711894,35512853,21919959,78561158,70036438,94595668,5073754,84002370,59177718,34432810,6038457,2208785,95290172,23569917,37183543,41092102,50188404,9829782,8115266,24915585,63506281,36189527,83133790,68000591,59405277,71469330,61623915,67084351,57393458,20568363,59614746,83948335,17976208,77377183,33895336,59981773,14363867,44245960,508198,81274566,92787493,16595436,41245325,13470059,13862149,6221471,93566986,54263880,37280276,44983451,74441448,8129978,97011160,36753250,43152977,1375023,20885148,13173644,78785507,40781449,54606384,29516592,3773993,95395112,38022834,87710366,7066775,34946859,96906410,26397786,14349098,30569392,53888755,57961282,24619760,85571389,44847298,3183975,53648154,38008118,70420215,33935899,82327024,83155442,86118021,69848388,57248122,15015906,24826575,70367851,9623492,53802686,92604458,78300864,16099750,28550822,81853704,27411561,30891921,32274392,48088883,79922758,22450468,30787683,6497830,22942635,27185644,45794415,57241521,73222868,79657802,76170907,73168565,22879907,4095116,62357986,39553046,37891451,41442762,99224392,44060493,3235882,77300457,85695762,92530431,12024238,14723410,34493392,50007421,51466049,4978543,45381876,4199704,36933038,75052463,40356877,65275241,34044787,44664587,36845587,66885828,36135,45667668,21070110,38009615,97281847,45919976,91281584,47887470,9257405,99971982,13348726,54762643,1022677,4091162,16684829,86301513,43501211,67227442,43322743,18466635,69786756,2917920,67513640,83083131,9259676,79191827,2607799,56153345,54058788,85117092,28851716,47298834,78218845,61141874,66271566,45995325,64055960,11757872,74357852,97783876,87720882,17727650,26102057,23194618,75135448,6157724,80555751,22435353,93359396,8696647,68204242,33336148,19272365,37166608,19486173,63372756,8733068,8634541,96852321,97057187,70727211,74452589,18806856,82532312,54987042,88130087,23110625,92215320,95010552,72732205,10597197,53547802,97641116,89217461,29635537,79738755,78909561,41092172,30653863,17386996,73021291,38645117,86798033,65047700,11543098,58208470,40677414,81898046,90654836,27325428,10453030,41380093,45306070,59318837,76671482,61928316,45990383,13231279,68316156,21673260,41481685,86022504,27187213,19898053,37435892,57163802,67031644,71920426,42307449,38256849,42073124,30811010,36139942,79136082,10961421,29819940,21397057,56473732,92692978,62762857,77898274,37560164,91240048,99125126,28928175,82052050,3880712,90158785,66322959,15064655,71965942,11923835,19891772,68041839,99917599,12303248,30771409,35996293,92998591,72357096,51507425,874791,39986008,30090481,89811711,33553959,64157906,74614639,65271999,4806458,8913721,93562257,5640302,8055981,61859581,32590267,42237907,70782102,97379790,17058722,55615885,29510992,66116458,20681921,4204661,99690194,7182642,69697787,26744917,83533741,20867149,56484900,94076128,61897582,9398733,13819358,56461322,62490109,2204165,72238278,13468268,66663942,8791066,29401781,38510840,59197747,9603598,92353856,43357947,64098930,25636669,72278539,72358170,23740167,86242799,62115552,48395186,56955985,96420429,91141395,24168411,49598724,84127901,18001617,1936762,83747892,49271185,16567550,92071990,89699445,32058615,67030811,92867155,57020481,54232247,44627776,79806380,65081429,30218878,94539824,11161731,37501808,58749,62428472,82651278,51315460,67124282,9575954,98653983,45996863,6521313,51715482,88698958,48675329,42644903,34667286,48658605,91255408,22200378,58180653,47738185,90004325,98943869,49597667,79322415,92803766,7229550,63152504,83269727,60393039,20642888,68128438,89996536,51588015,21289531,3233569,43045786,60955663,31491938,36468541,47893286,4434662,36685023,72793444,71522968,83368048,17539625,82178706,81774825,65454636,82779622,27625735,24314885,33123618,68110330,36396314,90061527,10264691,99729357 +53547802,73617245,41245325,17385531,40781449,86022504,6808825,44889423,64157906,61263068,41380093,29065964,7955293,55753905,85116755,27411561,99971982,35456853,92554120,8807940,66428795,53802686,15535065,92787493,79821897,4064751,60102965,96193415,4978543,17727650,45237957,40984766,32590267,36505482,37620363,72777973,34044787,67793644,14947650,52060076,4099191,16097038,45617087,38008118,66250369,51464002,82897371,97561745,72738685,41092102,92033260,54517921,19939935,51472275,93933709,33797252,65851721,54762643,89811711,8634541,17068582,55247455,31126490,9599614,91255408,29819940,59329511,96852321,88653118,97379790,93515664,56955985,10309525,6986898,89419466,93270084,73235980,5111370,58751351,44842615,91664334,21919959,15075176,94595668,22113133,62740044,63372756,38645117,50668599,40865610,59318837,82979980,2891150,62496012,26744917,29029316,89214616,36753250,6793819,31904591,96726697,9886593,26493119,57240218,68204242,53842979,1431742,83210802,21993752,77413012,7182642,90457870,21070110,78602717,71657078,6505939,15536795,16445503,80014588,36808486,44348328,44915235,62430984,72373496,97783876,84187166,79322415,24058273,49641577,4787945,73786944,75229462,78589145,44784505,65275241,30764367,55669657,30771409,3509435,5970607,98948034,84684495,65271999,38494874,18806856,16099750,77284619,54014062,95290172,60955663,62452034,50806615,36580610,99515901,8129978,99658235,71920426,29994197,72278539,247198,91990218,98943869,51047803,65017137,17764950,51149804,19101477,7646095,61859143,67030811,67451935,33061250,9188443,415901,98371444,25842078,58224549,77072625,35996293,94090109,1197320,65715134,78561158,66903004,99690194,69697787,39373729,84127901,24953498,37435892,18699206,39986008,68644627,82726008,3633375,44481640,28550822,22942635,48260151,56531125,10366309,71469330,66832478,54987042,83269727,27665211,63059024,34493392,23569917,9257405,45996863,36780454,21289531,74137926,98130363,82178706,18833224,4091162,66667729,81853704,95581843,18131876,9575954,83651211,26664538,32161669,81274566,92692283,42782652,72274002,30163921,55189057,30366150,22997281,13231279,36312813,669105,56424103,26998766,38658347,7502255,16019925,1250437,66885828,72019362,10264691,73222868,78766358,37675718,37891451,28928175,44983451,35192533,45407418,62762857,8913721,39801351,14731700,1788101,74110882,47090124,15015906,89699445,45075471,77694700,17365188,72238278,62552524,73124510,37957788,28851716,76960303,29510992,6221471,29834512,7066775,96735716,22405120,3487592,82886381,62490109,57961282,55615885,83533741,7104732,4798568,99297164,26392416,4508700,44550764,40197395,89637706,71300104,79657802,90090964,63015256,14093520,86001008,4515343,30090481,41442762,99549373,99965001,86118021,70036438,42073124,50702367,45428665,68875490,14220886,34946859,45990383,48675329,98653983,68000591,50188404,83368048,37560164,66663942,63152504,30694952,16054533,27625735,43376279,65338021,74441448,4204661,76434144,4434662,24591705,61982238,66116458,76315420,89804152,90013093,56515456,94911072,30787683,54058788,20867149,569864,50567636,39847321,11543098,4806458,57658654,94076128,23194618,8651647,68824981,71083565,42967683,4095116,98462867,7300047,24168411,26776922,17037369,10597197,9259676,26863229,52261574,92215320,176257,58749,99021067,67281495,91957544,83302115,8128637,75986488,76330843,64848072,62115552,12664567,70727211,36933038,749283,62693428,45794415,77272628,70372191,57803235,67084351,59405277,9829782,96906410,61960400,55143724,73392814,29635537,63967300,87160386,16380211,24314885,22721500,7423788,72358170,60106217,20568363,69641513,45381876,32699744,13173644,11757872,67227442,32058615,33628349,26114953,34295794,68102437,55602660,11581181,34236719,54427233,14326617,18783702,57241521,98739783,33431960,39171895,74743862,57359924,83150534,12348118,20885148,21673260,69255765,71333116,22108665,81898046,96420429,56484900,63506281,4069912,68041839,8373289,55960386,69355476,33895336,17976208,39553046,38365584,53888755,53648154,26063929,92604458,59197747,7687278,92692978,84840549,90272749,59371804,10961421,39201414,65047700,11923835,4668450,34497327,55770687,78909561,6525948,40781854,30569392,9623492,3183975,44479073,82339363,12416768,32159704,2331773,47738185,72357096,25636669,34667286,45848907,48360198,78218845,93053405,14045383,77620120,99861373,45919976,59177718,33237508,19272365,30395570,37501808,95957797,75407347,18663507,15064655,30218878,95395112,82327024,74357852,77898274,86301513,65081429,29188588,95726235,37280276,3294781,22129328,65880522,73168565,69605283,81172706,29466406,58180653,30998561,97281847,3088684,13348726,20002147,64055960,57163802,66045587,84349107,5482538,60430369,45790169,7919588,17146629,68816413,74452589,79880247,88444207,53632373,80246713,45667668,18411915,92867155,66322959,14363867,66611759,58208470,321665,40534591,38510840,92398073,10358899,99524975,51715482,32426752,43501211,53393358,93790285,34698463,47708850,61928316,65038678,74724075,22450468,40686254,75135448,59614746,26139110,66319530,22200378,99729357,508198,70420215,69136837,33336148,87598888,3773993,37739481,59501487,61271144,16424199,64602895,24619760,53666583,2208785,33201905,2204165,48893685,47792865,90004325,31491938,1204161,37183543,77787724,34432810,84002370,18001617,49236559,28787861,45995325,2717150,41092172,13468268,93359396,76825057,44177011,20140249,49271185,21397057,61373987,44847298,62232211,43357947,17539625,51315460,40152546,90061527,69786756,84904436,50007421,82651278,9860195,16971929,26507214,8791066,168541,91802888,37166608,11161731,99604946,56473732,72725103,1936762,35092039,10453030,54868730,47361209,34698428,5073754,31727379,84406788,52204879,54199160,99226875,7517032,43933006,29516592,1569515,15902805,40677414,67474219,7685448,79942022,17016156,76703609,112651,32274392,79191827,824872,61859581,6871053,2607799,92541302,77312810,44844121,85242963,8729146,72614359,89217461,26292919,91727510,99224392,71316369,29959549,77187825,51426311,79922758,6111563,15163258,95010552,94539824,5822202,70367851,99125126,19486173,55470718,89996536,14349098,91831487,17386996,32250045,37192445,51466049,60581278,69579137,49328608,23432750,36930650,89499542,2917920,29932657,12303248,42947632,9603598,9058407,7011964,23848565,71522968,89046466,38009615,66137019,19898053,30543215,12571310,40356877,36396314,42307449,38256849,43322743,83747892,9860968,75543508,92353856,88698958,33699435,80555751,26734892,6157724,48395186,30891921,38061439,92530431,526217,42199455,37224844,61141874,7229550,6521313,4119747,37659250,70004753,62051033,43506672,44664587,98648327,36812683,6153406,70541760,36685023,76540481,61623915,53084256,64098930,8115266,93566986,15948937,6497830,17894977,44627776,92803766,17957593,3233569,30653863,68939068,63628376,20642888,20122224,19457589,13470059,47893286,49597667,20486294,48088883,62803858,16387575,22435353,93057697,51588015,48673079,92071990,60393039,96318094,20765474,8696647,8733068,44245960,97940276,75128745,87710366,44473167,85117092,88904910,3880712,67124282,56153345,9398733,75777973,91281584,24733232,88047921,36139942,33249630,33565483,14626618,55850790,52293995,42237907,54663246,38341669,44846932,83789391,99917599,77300457,43045786,90310261,26102057,72495719,31161687,68110330,94516935,61728685,40027975,22766820,24826575,83083131,48892201,78785507,97011160,20681921,36468541,66271566,4985896,20836893,86460488,28734791,51590803,19891772,42644903,75153252,14723410,49882705,56461322,79136082,57393458,6038457,72732205,3233084,70596786,43152977,69848388,36135,86242799,96709982,42692881,16595436,27187213,23110625,83948335,68702632,89078848,88130087,33304202,16684829,22879907,874791,68694897,47298834,17857111,71965942,57248122,91141395,5640302,62936963,87720882,82427263,64087743,12024238,27325428,99333375,76170907,38022834,26397786,97641116,44060493,94711842,83155442,4199704,78300864,46851987,80316608,62428472,33553959,82052050,59910624,80251430,18466635,51507425,59109400,60176618,78884452,74614639,61897582,83133790,5832946,81774825,52613508,86543538,94360702,61815223,54232247,13862149,13819358,46870723,97057187,92998591,30139692,81855445,45049308,10649306,21533347,76671482,15148031,20645197,5267545,81805959,84293052,75052463,88251446,85571389,48774913,82779622,79806380,1022677,84166196,47213183,27185644,33123618,18504197,81677380,11365791,21001913,73109997,17058722,28796059,73031054,82532312,3235882,79738755,75820087,17081350,54263880,23740167,31733363,24915585,93562257,54606384,30811010,35512853,33935899,36189527,8055981,85711894,1239555,86821229,61741594,77377183,19376156,59981773,85695762,72793444,68316156,86798033,36845587,29401781,57020481,91240048,26275734,67031644,65074535,6819644,85023028,16405341,91938191,46540998,9175338,61712234,70800879,68128438,27041967,45306070,47887470,30463802,67513640,78549759,23134715,95251277,41481685,48658605,73021291,1375023,49598724,90654836,65454636,70782102,32151165,8099994,90158785,16567550,77301523,62357986 +63967300,59177718,93933709,87598888,54427233,78602717,33304202,42307449,89078848,79806380,30090481,99965001,14626618,63628376,54762643,19376156,27625735,27665211,16097038,4985896,7011964,99226875,18504197,22108665,49882705,95395112,75543508,44844121,54199160,10366309,26392416,25842078,2208785,65338021,1204161,247198,93053405,37620363,61263068,19486173,18833224,51464002,41481685,73235980,43376279,68694897,51149804,75135448,38022834,46540998,67793644,68702632,19939935,14349098,66271566,34698463,37183543,9398733,49598724,99549373,26275734,14731700,22997281,3509435,20885148,22129328,7104732,1250437,89637706,40152546,39801351,7229550,64602895,98462867,24314885,84187166,95726235,33061250,15536795,26863229,32590267,4668450,85116755,38061439,30998561,44842615,22450468,51590803,69355476,16019925,55669657,42782652,66045587,38658347,82427263,60955663,59318837,64848072,81898046,73031054,73786944,92541302,94516935,36780454,64087743,93270084,26998766,3773993,4119747,38365584,8651647,86001008,32159704,44784505,17068582,2204165,10309525,60102965,93562257,3233084,99297164,70420215,5640302,18663507,96193415,32161669,42237907,60430369,4434662,6793819,80246713,72614359,16445503,62740044,78785507,34493392,21993752,54606384,56531125,99917599,63372756,63059024,57803235,27041967,18806856,88047921,1936762,58180653,91831487,20568363,62762857,35192533,12664567,9259676,60581278,7955293,55602660,82339363,33699435,41380093,71657078,15015906,99333375,33797252,82897371,83150534,17386996,49641577,31733363,82726008,63152504,47887470,16054533,44479073,65851721,83210802,51588015,40984766,76825057,44847298,34667286,50567636,47090124,9175338,37891451,39553046,93057697,81855445,10358899,79922758,68644627,12416768,89419466,99224392,53802686,321665,34432810,70036438,63015256,66250369,5482538,62357986,8729146,86242799,88698958,92692978,79880247,72725103,73124510,94539824,824872,29510992,70800879,95290172,65454636,61815223,17764950,89996536,81805959,45381876,57241521,17365188,56153345,59371804,64098930,80014588,77284619,87720882,58208470,23848565,48892201,95957797,34295794,92033260,77272628,62936963,14093520,27185644,61982238,85571389,68128438,8099994,36685023,91990218,70596786,93566986,40781449,35512853,38008118,9058407,36580610,88904910,67474219,62452034,30764367,24826575,73392814,40027975,3487592,70367851,9623492,77072625,1197320,65074535,112651,24591705,6153406,31727379,2717150,81274566,15902805,61271144,9886593,51466049,91727510,89046466,20765474,45049308,2607799,30891921,25636669,47361209,84406788,55189057,37501808,53084256,7919588,62115552,1569515,61741594,56473732,44983451,62430984,45075471,36812683,44550764,42947632,4099191,569864,79821897,30787683,2891150,508198,20122224,7423788,12571310,73617245,37435892,55143724,89214616,50188404,13173644,43506672,2917920,66322959,17081350,86798033,94076128,21533347,91240048,81677380,30771409,57248122,56955985,17976208,54987042,83368048,36505482,68939068,10649306,17957593,75777973,74110882,526217,62051033,19891772,78549759,6808825,34698428,82052050,68824981,43152977,16099750,65880522,54663246,669105,88653118,39171895,45617087,75820087,78589145,78766358,20642888,4064751,72732205,66667729,85242963,55470718,48088883,69786756,749283,79657802,37659250,16684829,68110330,99515901,20486294,76671482,91664334,93515664,77787724,30694952,14363867,83155442,92998591,33201905,71316369,91141395,36930650,97561745,62552524,94090109,3183975,4798568,29932657,60176618,4978543,78884452,14947650,48360198,28928175,10961421,78561158,23432750,26102057,87710366,91281584,8807940,45237957,36135,18411915,72793444,74357852,11543098,76315420,87160386,26292919,69605283,76703609,75153252,5267545,96735716,85117092,40677414,53393358,17385531,72278539,31126490,74137926,51507425,29959549,98130363,67227442,28851716,49271185,10453030,45848907,42692881,73168565,48658605,35092039,7517032,62803858,79738755,53842979,66903004,74724075,28796059,9603598,82178706,59197747,4508700,39986008,88251446,68816413,82979980,46851987,16567550,90013093,77413012,51715482,64055960,74743862,42644903,15535065,37560164,14326617,66116458,31904591,22721500,92867155,13862149,76540481,75986488,22766820,53632373,59981773,94911072,70372191,91938191,6986898,44889423,62496012,92554120,32426752,29834512,11161731,26397786,71333116,68041839,56461322,53648154,43501211,30163921,51472275,65081429,38256849,7687278,97940276,24915585,43322743,59109400,18131876,23740167,8733068,84002370,49328608,26114953,33431960,12348118,12024238,66663942,65017137,66137019,83789391,12303248,20867149,30543215,61373987,98371444,19457589,73222868,4095116,874791,21673260,66319530,16971929,77620120,1431742,97783876,8696647,29029316,54058788,92692283,44245960,9860195,38645117,5111370,86543538,64157906,55247455,82532312,77377183,86460488,79322415,68000591,77300457,79191827,59329511,67281495,77187825,168541,68875490,9257405,90310261,3880712,70727211,82779622,40534591,10264691,21001913,6497830,45407418,33237508,31491938,76330843,99524975,23569917,86118021,20645197,16380211,5073754,6525948,17016156,11581181,35996293,30395570,92353856,8129978,18466635,59501487,22942635,52261574,43933006,51047803,9575954,92604458,5822202,72373496,15064655,69255765,84840549,15948937,51426311,33628349,72357096,45996863,47893286,76960303,29994197,67031644,84684495,33336148,75052463,40781854,33123618,4515343,99658235,72274002,53888755,26139110,3233569,54014062,61859581,44664587,29466406,42199455,48395186,44348328,46870723,4806458,92398073,54868730,62428472,88444207,29516592,45990383,32151165,22200378,3294781,95251277,75128745,43045786,7685448,43357947,1788101,98948034,59614746,7300047,6819644,31161687,18699206,10597197,16424199,71083565,66611759,8913721,81853704,57359924,74614639,83083131,40356877,80555751,60106217,13470059,70541760,94360702,96709982,81172706,98648327,22435353,20140249,8055981,16387575,45794415,61623915,78909561,32250045,17146629,19101477,92787493,13348726,22405120,86821229,74441448,33565483,83533741,40197395,69579137,34497327,4199704,39373729,98943869,38341669,65271999,92215320,37739481,99125126,57240218,40686254,44627776,36933038,8373289,61712234,18783702,67451935,37192445,29065964,1239555,65275241,99729357,97011160,37166608,71965942,97641116,91957544,96726697,71522968,86301513,9829782,77312810,53547802,14045383,35456853,99971982,6111563,47213183,66428795,9188443,23194618,91802888,36189527,99690194,26507214,78218845,57961282,99861373,26063929,30811010,26744917,80316608,29188588,52204879,92803766,5970607,44481640,50702367,79136082,44060493,77301523,70004753,14723410,55753905,82886381,66885828,98739783,62232211,79942022,8128637,36753250,56484900,16405341,54263880,71469330,7066775,1022677,30463802,28734791,92530431,48893685,7182642,2331773,34946859,28787861,90158785,4204661,56515456,32699744,44473167,84127901,90457870,59405277,72777973,49597667,38494874,37957788,89217461,17539625,59910624,17037369,69848388,30366150,89804152,39201414,83747892,69697787,95010552,90272749,67030811,86022504,30653863,36139942,91255408,68316156,14220886,72019362,96906410,65038678,45790169,90004325,3088684,42967683,24058273,89499542,47708850,27411561,61960400,42073124,21070110,51315460,69136837,15148031,30218878,22113133,36845587,20002147,37675718,99021067,26493119,17894977,47738185,61859143,6038457,90090964,62693428,61928316,83133790,30569392,13231279,52293995,4069912,48774913,67513640,45995325,49236559,57658654,6157724,57393458,82651278,65715134,29635537,37224844,66832478,55770687,6871053,76170907,80251430,22879907,73109997,83651211,16595436,41442762,84293052,54517921,52060076,37280276,33935899,39847321,20836893,17857111,40865610,7646095,53666583,34236719,85023028,88130087,71300104,36468541,36312813,27325428,3235882,29819940,83948335,45667668,48260151,13468268,72495719,48675329,21919959,58224549,8115266,68102437,85711894,6505939,71920426,44846932,69641513,17058722,3633375,56424103,30139692,72238278,48673079,8791066,75229462,47298834,72738685,74452589,52613508,36808486,81774825,26734892,50007421,90654836,15075176,18001617,84904436,23110625,24168411,26776922,26664538,78300864,62490109,44915235,5832946,9599614,11757872,98653983,77694700,57163802,83269727,73021291,6521313,92071990,55960386,60393039,33249630,41245325,1375023,77898274,97379790,20681921,93359396,19272365,21397057,415901,54232247,90061527,176257,97057187,93790285,96318094,58751351,67124282,99604946,58749,83302115,85695762,44177011,45428665,61728685,61897582,17727650,24733232,9860968,65047700,32274392,27187213,28550822,75407347,84349107,21289531,55850790,63506281,11365791,41092172,89699445,47792865,4787945,82327024,34044787,29401781,70782102,6221471,33895336,24619760,72358170,61141874,68204242,50668599,41092102,55615885,23134715,84166196,76434144,36396314,11923835,24953498,7502255,94595668,45306070,57020481,19898053,89811711,94711842,67084351,45919976,15163258,32058615,33553959,95581843,4091162,97281847,96420429,50806615,38510840,38009615,13819358,96852321,8634541 +12348118,96193415,75153252,44245960,70367851,33201905,74110882,20885148,23194618,35092039,22108665,61859143,75407347,53842979,57803235,81677380,97561745,86821229,27665211,6986898,21993752,99524975,70541760,9623492,52293995,75543508,9860195,46851987,62496012,17068582,83533741,32159704,17058722,77312810,10309525,55753905,15535065,39801351,93270084,15536795,85571389,37192445,41092172,99297164,77284619,14349098,62693428,6808825,80014588,61271144,55247455,50702367,26114953,84293052,33628349,74614639,38008118,9398733,20642888,68644627,24591705,28851716,17976208,61373987,62936963,91281584,67793644,8913721,22129328,16971929,5111370,64087743,84349107,94539824,69355476,63967300,21533347,30694952,6793819,33797252,18699206,79821897,97011160,19891772,16019925,64848072,77272628,95957797,66322959,47792865,96726697,59177718,3233084,26139110,89214616,99515901,68204242,79136082,34432810,44784505,31904591,39847321,89804152,47893286,3880712,23848565,75777973,29819940,50806615,6153406,96852321,88653118,70727211,87720882,80251430,20140249,60581278,26998766,33237508,30787683,91727510,31126490,93790285,19486173,50007421,92541302,91957544,79806380,99224392,60106217,7104732,38658347,44842615,40984766,82779622,18833224,56473732,45919976,24314885,8696647,89419466,44060493,17957593,13173644,44348328,50188404,76540481,2208785,79657802,98739783,36580610,40356877,68824981,16380211,17539625,68875490,3509435,3183975,25842078,61982238,88047921,97641116,71083565,36753250,74441448,65851721,30569392,71657078,72725103,51715482,67474219,98371444,7919588,81898046,13862149,40152546,9603598,57241521,45790169,32058615,61741594,58751351,45990383,99965001,56531125,48892201,60430369,7687278,83210802,34295794,20836893,11161731,53888755,6505939,98462867,78549759,61897582,16424199,81274566,24733232,81172706,4095116,34236719,11543098,83789391,32161669,10366309,73168565,16054533,75128745,37560164,73124510,94516935,7423788,22435353,36139942,38365584,92554120,38341669,53084256,22879907,37280276,1431742,22766820,62357986,27325428,7685448,54263880,62740044,71333116,44889423,88444207,40677414,73222868,64055960,27041967,19101477,92998591,43376279,65271999,18783702,78602717,14220886,62051033,38061439,37891451,34946859,67030811,91831487,32250045,44177011,68316156,72373496,321665,91990218,90310261,17727650,92803766,65047700,94360702,13468268,19939935,17037369,26776922,97940276,37620363,39986008,45237957,168541,28928175,37739481,69848388,47708850,91664334,29516592,43357947,89499542,31727379,82979980,70596786,526217,65454636,77620120,2917920,36930650,4798568,11923835,81805959,415901,22997281,66663942,77377183,54014062,54762643,23569917,60102965,70800879,82339363,61623915,14093520,9886593,29401781,1204161,91802888,61815223,10358899,97057187,73031054,30395570,44983451,42644903,40197395,2717150,51047803,85116755,7502255,33249630,8128637,9188443,89811711,26734892,64602895,59614746,26292919,28796059,6525948,29510992,10597197,42947632,49236559,21070110,36396314,44550764,83133790,36685023,55669657,63152504,53632373,49598724,6157724,87160386,15064655,72019362,96709982,84840549,71316369,54199160,37224844,17365188,5832946,68816413,37183543,42692881,40865610,54868730,6871053,18663507,72278539,4434662,62552524,95395112,59910624,49271185,45428665,10961421,44844121,16387575,61141874,39373729,28734791,82532312,20486294,40781449,96906410,93562257,23110625,21673260,73235980,508198,72738685,29466406,65275241,48260151,8651647,3088684,68102437,62452034,57658654,84684495,43322743,72732205,27411561,42199455,99549373,52613508,17081350,77413012,7066775,99658235,36780454,24058273,88130087,30163921,54606384,56153345,63015256,43506672,99604946,78909561,42307449,92867155,55189057,4119747,71965942,47090124,45995325,43933006,83651211,8129978,54058788,48893685,14947650,54987042,93057697,4668450,6521313,20645197,42782652,78785507,79942022,87710366,29994197,72777973,73109997,36505482,24915585,7229550,67281495,11365791,66271566,65074535,36845587,14626618,99021067,59197747,98948034,91255408,96420429,24168411,76315420,86543538,8373289,6497830,69697787,61859581,69786756,34493392,30218878,55143724,4099191,33935899,21397057,69255765,3233569,66832478,77301523,51464002,66250369,90090964,29932657,75135448,669105,14045383,61263068,66116458,59109400,53547802,20002147,56484900,60955663,94711842,45794415,26397786,89046466,68702632,34497327,58749,89078848,26102057,56461322,81853704,84002370,8055981,48658605,35192533,62803858,94090109,80555751,87598888,44473167,65017137,26392416,46540998,74743862,9860968,18806856,45049308,33431960,95010552,75986488,67031644,38645117,66611759,33123618,2891150,53802686,90061527,77787724,67227442,70420215,39201414,22942635,92398073,63506281,99861373,33304202,41481685,1788101,39171895,98943869,79922758,38494874,12571310,59501487,75229462,30543215,26744917,45075471,22721500,44847298,7517032,6221471,37501808,13231279,64098930,75052463,92033260,8733068,93566986,98648327,28787861,76170907,45848907,37166608,16445503,90272749,92604458,65715134,3773993,29959549,50567636,30366150,51590803,78589145,40534591,30891921,73021291,1022677,83155442,54427233,78561158,82726008,44481640,62428472,19376156,31161687,51472275,42967683,49882705,3294781,8791066,569864,50668599,874791,3633375,59371804,83083131,4069912,85242963,20122224,26063929,45306070,12416768,86118021,52060076,40686254,55770687,98130363,49597667,36312813,1569515,69641513,47738185,30998561,67124282,92692978,36812683,11581181,15163258,36808486,64157906,16684829,53393358,66137019,82052050,16595436,48774913,55615885,68041839,6038457,84187166,21919959,76330843,10453030,15948937,32590267,17764950,72274002,90013093,86798033,68939068,53666583,51426311,33699435,16405341,57961282,749283,99729357,24826575,34667286,4064751,9575954,66428795,26507214,9259676,15902805,92787493,28550822,30764367,68128438,65081429,78218845,55470718,57359924,41245325,7955293,10649306,20568363,9829782,16567550,4787945,57240218,65038678,74357852,5073754,70782102,20867149,67084351,85711894,92692283,95290172,79880247,1197320,62430984,93359396,21289531,5970607,41092102,69579137,37675718,85695762,40027975,76434144,20765474,66885828,23740167,17857111,81855445,77694700,57248122,73617245,92353856,34698428,7011964,52261574,29834512,75820087,12024238,43045786,63628376,1250437,47213183,30811010,79191827,3487592,63372756,37659250,26493119,42073124,26863229,72357096,14731700,65338021,84166196,54663246,73392814,33565483,824872,15015906,45667668,39553046,93053405,76960303,86022504,80316608,8099994,72495719,33336148,91141395,7300047,247198,48675329,11757872,33061250,32426752,86460488,33895336,26275734,90004325,29065964,1375023,17894977,47361209,19457589,24953498,15148031,54232247,99971982,84127901,94911072,35456853,69605283,12303248,77072625,51588015,49641577,51315460,48088883,56424103,88251446,52204879,91240048,69136837,76703609,4985896,63059024,73786944,35512853,92530431,36189527,17386996,53648154,56515456,97281847,16097038,66045587,94076128,19272365,74137926,112651,38022834,9257405,95581843,32151165,48673079,14326617,24619760,61960400,12664567,22200378,42237907,1239555,34698463,8634541,58208470,4204661,176257,54517921,51466049,14723410,71522968,51507425,60176618,88904910,93933709,90457870,41380093,31491938,59318837,68000591,84904436,55602660,89996536,83368048,70036438,82327024,26664538,36468541,66319530,99917599,86001008,13470059,18466635,32274392,62490109,8115266,17016156,6111563,92215320,31733363,99333375,30463802,30139692,89699445,78300864,45617087,33553959,18001617,83150534,17146629,27185644,68694897,88698958,68110330,74452589,7646095,77898274,96735716,59329511,13819358,44479073,74724075,37957788,22450468,82886381,96318094,45996863,76671482,29188588,37435892,71469330,97379790,62762857,46870723,85117092,5267545,9599614,15075176,9058407,44846932,48360198,77300457,61728685,43501211,30771409,62232211,79322415,29029316,66903004,72358170,95726235,21001913,20681921,77187825,83302115,18504197,83269727,71300104,8807940,94595668,8729146,44915235,97783876,16099750,98653983,85023028,76825057,9175338,7182642,57020481,72238278,35996293,86301513,61712234,4508700,47298834,30653863,4806458,66667729,14363867,4091162,49328608,59981773,36135,30090481,19898053,23432750,43152977,80246713,90158785,38256849,18411915,6819644,99226875,72793444,47887470,25636669,93515664,57393458,17385531,67513640,40781854,82427263,3235882,1936762,22113133,5640302,32699744,71920426,84406788,27187213,82651278,82178706,34044787,91938191,27625735,2331773,90654836,70372191,89637706,81774825,48395186,57163802,45407418,78766358,99125126,95251277,83948335,70004753,23134715,2607799,44627776,13348726,82897371,4515343,18131876,86242799,79738755,60393039,10264691,55850790,62115552,41442762,5482538,38510840,89217461,92071990,72614359,61928316,51149804,4199704,38009615,22405120,67451935,2204165,45381876,78884452,65880522,56955985,5822202,44664587,99690194,36933038,4978543,58180653,55960386,59405277,58224549,83747892,29635537 +65851721,94516935,65454636,58208470,4119747,67281495,33628349,20885148,55247455,89637706,41380093,56531125,91255408,80251430,70800879,28851716,82427263,54427233,97281847,54762643,24733232,99861373,17727650,26392416,44784505,29994197,61741594,168541,30653863,45790169,66045587,97940276,96193415,3233084,70036438,86001008,38061439,77300457,92398073,89078848,29959549,43045786,87160386,69786756,89214616,30218878,37739481,9860195,4508700,81677380,19101477,53842979,15064655,321665,27325428,88047921,33201905,45996863,77312810,99658235,112651,35456853,22721500,59501487,18783702,7919588,62693428,46540998,5970607,99524975,96726697,17081350,44473167,30543215,21673260,70004753,66832478,1022677,57240218,30139692,20122224,89699445,61373987,63372756,70596786,57248122,43933006,18663507,97561745,75543508,60955663,1239555,50806615,52060076,5111370,71657078,43357947,80555751,43322743,64848072,54517921,98948034,33565483,44245960,33797252,81853704,40677414,36930650,93790285,1788101,17037369,65715134,93515664,85571389,68644627,57803235,48260151,12348118,94076128,12664567,56515456,29635537,53888755,45306070,98943869,40865610,65880522,37957788,669105,62452034,91957544,78549759,77413012,34698463,14093520,48360198,92033260,51047803,49236559,14326617,50188404,29401781,55753905,97011160,30811010,526217,20140249,38494874,26493119,62496012,88130087,7502255,93566986,38022834,53666583,4668450,30569392,84904436,9886593,88444207,9398733,48673079,24591705,15015906,99965001,13231279,47708850,749283,10366309,35092039,95581843,80014588,15536795,34946859,75986488,57359924,75153252,62490109,93933709,37183543,98739783,47298834,8696647,27665211,42782652,34698428,99125126,34295794,66250369,9860968,74110882,36780454,874791,96735716,6111563,2204165,79657802,44889423,4099191,39801351,21533347,65038678,3235882,90457870,16405341,33553959,7104732,82886381,6153406,16097038,95957797,61271144,83210802,27041967,94711842,64087743,53648154,40686254,6221471,74743862,75777973,80316608,18466635,69641513,68816413,24619760,90004325,69848388,91802888,36505482,9188443,89811711,1569515,99604946,11923835,90090964,53632373,77272628,58749,86543538,23110625,56473732,32161669,13470059,65047700,67031644,28734791,45428665,6986898,21001913,63967300,29819940,79191827,30787683,62936963,29834512,68102437,20642888,8791066,89046466,16684829,5267545,88653118,79136082,93359396,59910624,53802686,59109400,54606384,56955985,9058407,15535065,22108665,84349107,24826575,44479073,64098930,20867149,88251446,51315460,57393458,17058722,26292919,86798033,51464002,3088684,48893685,45919976,1431742,60581278,37192445,60430369,37435892,40534591,93562257,39847321,19486173,59318837,73617245,67124282,75820087,13862149,61141874,63506281,59371804,39201414,43506672,7685448,36685023,76671482,11365791,44842615,82651278,14349098,61897582,40152546,20486294,62051033,90272749,41481685,96709982,37280276,8115266,88904910,55850790,16424199,24915585,82779622,37675718,20681921,28928175,38008118,9175338,83651211,66611759,77377183,55470718,78300864,47887470,73021291,58751351,78602717,38365584,44983451,68041839,12416768,69605283,26114953,38009615,10453030,14731700,48675329,30771409,70782102,72777973,85711894,72274002,91831487,15948937,17068582,8733068,26998766,26063929,49597667,52293995,49641577,50007421,82979980,1204161,96906410,3880712,21993752,3773993,54868730,17894977,12571310,8913721,8128637,31733363,18699206,3509435,84187166,2717150,26102057,73222868,45049308,1250437,84127901,16971929,45667668,66903004,91281584,89499542,66885828,46870723,4515343,18833224,4985896,72725103,98130363,72357096,81898046,45848907,94595668,57961282,37620363,71920426,23569917,62740044,42199455,61859143,84840549,71300104,16595436,63059024,73031054,7182642,77284619,32699744,4199704,78589145,9599614,85116755,92530431,31904591,55602660,3183975,76825057,9623492,36468541,19457589,48774913,40027975,98648327,30090481,98653983,15075176,54199160,2331773,81172706,8651647,19939935,86821229,22942635,36933038,94090109,97641116,52261574,33123618,84406788,68939068,53547802,29188588,35512853,78884452,33431960,46851987,10309525,51507425,22450468,95726235,83155442,29029316,97783876,95010552,92554120,45237957,76170907,13173644,72373496,52613508,20836893,47213183,64157906,73168565,68702632,39171895,65271999,74357852,56484900,99297164,72738685,44481640,65081429,62762857,44627776,83789391,44915235,95251277,91240048,76540481,48892201,51426311,47738185,6157724,43501211,23848565,30463802,21070110,60106217,66319530,76315420,62428472,5073754,4798568,73235980,78766358,8055981,60102965,44060493,39986008,82052050,77620120,79880247,29466406,38341669,17539625,44846932,6808825,73124510,66137019,85695762,16567550,54663246,95395112,21397057,6505939,35996293,67227442,18806856,33061250,86301513,67793644,85023028,51472275,77301523,2208785,84293052,19891772,93057697,63015256,65074535,17365188,31727379,44550764,69355476,43376279,92604458,37659250,63152504,99224392,61623915,30764367,9829782,7229550,72614359,40356877,68128438,76330843,92215320,27185644,7687278,65017137,55189057,11757872,39553046,62357986,26776922,66271566,35192533,57658654,36808486,79806380,39373729,68204242,83533741,53084256,28550822,4064751,63628376,4091162,10358899,44348328,90310261,86460488,74441448,75052463,99690194,44177011,56461322,22405120,26139110,98462867,45794415,38658347,18001617,55669657,247198,65275241,19898053,569864,19272365,20765474,1197320,93053405,40197395,23194618,59329511,66667729,56424103,84684495,48088883,33336148,47361209,36580610,66322959,33304202,24058273,44844121,824872,67513640,83269727,89217461,51715482,49271185,4806458,10597197,84166196,33249630,2917920,36312813,44847298,20002147,27625735,68316156,99021067,42307449,73786944,25842078,26744917,37891451,62803858,34667286,10961421,99226875,9603598,87598888,34044787,68875490,68824981,55770687,92803766,78218845,22200378,27411561,69136837,67084351,66428795,34432810,71083565,86118021,3487592,11161731,8373289,32159704,99549373,91990218,89419466,17857111,18131876,38645117,42237907,82726008,96318094,65338021,59177718,508198,26664538,68694897,30366150,96852321,92692978,17957593,92787493,61263068,18411915,61859581,45995325,14363867,69255765,33237508,4095116,17386996,14220886,17016156,50668599,30694952,21919959,58180653,80246713,92867155,22766820,36189527,15148031,6525948,72278539,79821897,1375023,42692881,40781449,8129978,28796059,17976208,45617087,61928316,15902805,53393358,36812683,90654836,5822202,23432750,47090124,8634541,83302115,7517032,55615885,37501808,86242799,89804152,4069912,98371444,16099750,16387575,6038457,415901,66116458,51466049,24168411,71965942,45990383,20568363,81274566,22113133,76960303,34236719,74452589,7646095,41092102,61982238,75135448,54058788,81855445,94360702,9257405,36135,42073124,14947650,6521313,9259676,92692283,30395570,75229462,11543098,62232211,92998591,36396314,99729357,51588015,7423788,70541760,97379790,67451935,24314885,77187825,33895336,71333116,176257,54014062,58224549,91141395,74724075,59197747,41245325,47792865,50702367,76434144,31126490,51149804,70727211,64602895,3294781,13348726,61960400,82339363,81774825,77072625,91727510,86022504,34493392,83747892,48395186,55960386,99917599,36753250,5832946,66663942,84002370,70367851,6497830,36845587,31161687,55143724,68110330,33699435,72732205,49882705,74614639,72793444,70420215,92541302,26507214,12024238,14045383,77787724,57241521,83368048,40984766,2891150,16380211,15163258,26734892,72019362,59614746,8807940,72238278,90158785,20645197,72495719,64055960,17764950,4204661,22997281,10264691,62430984,91664334,32590267,49598724,78785507,81805959,2607799,45075471,92071990,48658605,83083131,28787861,82327024,78909561,22879907,71316369,54987042,25636669,69697787,29932657,29516592,71522968,3633375,36139942,5482538,61712234,60176618,13819358,73392814,7955293,69579137,30163921,30998561,74137926,78561158,67474219,6871053,61815223,38256849,85242963,32058615,7066775,16054533,52204879,18504197,57020481,34497327,8729146,83150534,87720882,59405277,99333375,29510992,14626618,19376156,32274392,22435353,70372191,16019925,4978543,26863229,61728685,42967683,68000591,79942022,23740167,6793819,45381876,4787945,90013093,67030811,33935899,51590803,41442762,26275734,79922758,1936762,83133790,54232247,91938191,54263880,4434662,23134715,3233569,75407347,37224844,79322415,94539824,94911072,79738755,72358170,95290172,71469330,17146629,13468268,76703609,5640302,30891921,62115552,26397786,82532312,97057187,32426752,40781854,99971982,83948335,92353856,7011964,50567636,62552524,77898274,11581181,88698958,99515901,9575954,82897371,17385531,32151165,24953498,57163802,16445503,82178706,45407418,42947632,59981773,87710366,37166608,49328608,6819644,7300047,56153345,90061527,44664587,93270084,32250045,27187213,60393039,10649306,77694700,14723410,8099994,75128745,37560164,21289531,31491938,89996536,22129328,42644903,43152977,85117092,96420429,47893286,38510840,41092172,12303248,29065964,73109997 +19939935,19891772,29819940,78602717,8807940,45075471,68816413,33061250,39171895,92541302,26392416,97783876,63967300,73235980,3773993,99658235,40197395,12348118,55602660,28851716,669105,66832478,96726697,25842078,39801351,2717150,81172706,97561745,18783702,49641577,66250369,38022834,17068582,60102965,1431742,8696647,99224392,56461322,91957544,23848565,62496012,51715482,38061439,35192533,7517032,42782652,77312810,19457589,44889423,4119747,60955663,62051033,6505939,95395112,29959549,22108665,69697787,8099994,16971929,30998561,2204165,30764367,83155442,43322743,4099191,66045587,79880247,65074535,91727510,44842615,72357096,12416768,6153406,20486294,67474219,43506672,65275241,23569917,9860195,69786756,35512853,93562257,73392814,79821897,27665211,85023028,99515901,37620363,99861373,57240218,34493392,62430984,14349098,4985896,26507214,61815223,17016156,59910624,75777973,76540481,67281495,22942635,7955293,15948937,97379790,43933006,73124510,40686254,44479073,93270084,98948034,65017137,1204161,48260151,73031054,84293052,14947650,85695762,77284619,36189527,4806458,5832946,93515664,77898274,8733068,82886381,58180653,37501808,68824981,85242963,72495719,66903004,29834512,29029316,40356877,45667668,90090964,36753250,69605283,73168565,45237957,55247455,11923835,55189057,62428472,92398073,87598888,4978543,51464002,56473732,59197747,32590267,29994197,19376156,13468268,4798568,48673079,21919959,25636669,11543098,2607799,7502255,69255765,36930650,68102437,81855445,73786944,71920426,45990383,81898046,99524975,78909561,23194618,97011160,90272749,86242799,98739783,61271144,63372756,569864,3233084,32161669,81274566,45996863,18411915,70004753,79806380,13470059,74614639,53547802,18699206,17037369,4204661,33553959,26734892,65454636,70036438,7229550,42073124,76960303,54987042,48088883,75820087,168541,36808486,33304202,78549759,7919588,26292919,4668450,28928175,64055960,56955985,60430369,16445503,67227442,82979980,62803858,51047803,112651,29188588,47708850,30811010,84406788,55960386,83210802,508198,45794415,29065964,70800879,53802686,89078848,64602895,79136082,42237907,99549373,59318837,55669657,68644627,70372191,64157906,20568363,77377183,58749,81677380,74743862,82897371,65880522,9886593,43376279,33201905,89419466,72777973,89637706,10366309,68694897,84684495,95290172,89811711,67513640,16019925,16595436,38365584,19486173,67451935,98943869,66116458,20140249,91990218,40677414,70727211,80014588,8115266,4434662,45995325,55615885,4069912,80555751,99965001,53648154,30139692,8729146,77272628,64087743,70596786,16387575,21289531,52613508,27411561,96852321,26863229,5111370,53632373,33336148,22129328,42644903,91240048,51466049,87720882,71316369,36505482,16424199,62740044,54762643,50007421,31904591,66137019,9599614,73617245,88653118,9398733,7423788,17386996,36312813,92692283,6986898,36580610,30543215,83789391,54014062,17081350,10358899,99226875,247198,37192445,45790169,99604946,49597667,49598724,30395570,54058788,19898053,66322959,99021067,13862149,8791066,40781449,27041967,57803235,91141395,24733232,22997281,37435892,55143724,83083131,8651647,52204879,62452034,76330843,86821229,24953498,75153252,44844121,23134715,54199160,22435353,97641116,65038678,33431960,50188404,31491938,92803766,3233569,82532312,61859143,77620120,3294781,89214616,92554120,19272365,73021291,88047921,51149804,17146629,89996536,37739481,57241521,96193415,83533741,62693428,69136837,34236719,38645117,70367851,71083565,20765474,80251430,28550822,91664334,71657078,13231279,526217,51315460,20867149,17539625,16097038,41481685,84349107,90004325,5640302,35092039,45306070,76703609,61728685,74137926,49271185,46540998,39553046,16099750,79191827,44983451,17058722,16054533,37280276,74357852,54606384,29466406,8913721,67793644,4787945,50702367,1788101,40152546,46851987,54427233,18833224,26275734,45848907,12571310,71333116,64098930,29932657,20885148,68316156,10649306,34698428,38341669,44846932,62936963,89804152,94911072,56515456,75052463,2208785,93053405,11365791,92215320,72278539,94360702,99971982,415901,94539824,85117092,87160386,71522968,34295794,76315420,36845587,39847321,94076128,24619760,78766358,85571389,84840549,33628349,34497327,17727650,53666583,48675329,36685023,3509435,98648327,1936762,46870723,66611759,22879907,65047700,68875490,68204242,91281584,52060076,7687278,35456853,99729357,4064751,64848072,874791,86001008,95726235,66319530,53393358,62490109,44550764,99297164,66885828,74452589,8634541,32058615,33237508,54517921,84904436,32274392,30163921,41092102,57393458,44481640,85711894,41245325,23110625,31126490,65338021,61960400,96318094,30090481,9058407,47361209,56531125,7104732,35996293,38008118,88904910,30694952,27625735,92787493,32159704,55753905,9257405,79657802,50567636,85116755,32250045,21673260,94090109,90061527,18504197,49236559,78561158,56153345,74110882,30569392,44627776,77072625,51507425,88251446,99333375,44784505,90310261,14093520,26744917,72019362,20642888,76170907,20122224,6111563,45919976,70541760,67030811,26397786,24168411,59177718,40781854,749283,63015256,97281847,43501211,23432750,72274002,98462867,13348726,6793819,56424103,22721500,36933038,59614746,55770687,91802888,7011964,33797252,86460488,14045383,63628376,61897582,34946859,1197320,26493119,17976208,53842979,3880712,3183975,61373987,88130087,8055981,11581181,58224549,22405120,42199455,84166196,32426752,44915235,92071990,22450468,77413012,7685448,60581278,54263880,47213183,97940276,76825057,92604458,39201414,21070110,37659250,92353856,37183543,93933709,38494874,47792865,72732205,63152504,36468541,41442762,6525948,37957788,5822202,83948335,67031644,47090124,29401781,17894977,10309525,72614359,41380093,79942022,17764950,78589145,78785507,31727379,9575954,78884452,72738685,51472275,10961421,14731700,43045786,45407418,59501487,75229462,53084256,26998766,21993752,15536795,65851721,39986008,321665,34698463,14220886,48360198,10597197,71300104,68041839,48658605,43152977,3235882,36396314,21533347,31161687,59329511,81853704,15163258,92692978,80316608,52261574,59405277,57020481,84127901,15075176,78218845,54868730,76434144,5482538,51590803,75543508,92998591,98130363,93057697,68000591,40984766,60106217,68939068,92033260,93359396,30787683,11161731,36780454,2917920,48892201,62232211,48395186,8373289,6497830,62357986,47298834,37166608,69848388,51588015,15148031,6808825,7066775,94516935,4199704,95957797,24591705,75128745,28787861,65715134,14626618,15535065,91831487,17857111,88444207,72238278,68702632,34432810,61263068,9623492,30653863,43357947,18466635,19101477,45428665,26776922,34044787,26139110,33123618,39373729,57248122,82427263,95581843,61928316,82726008,93566986,24915585,49328608,69641513,38510840,84002370,26664538,4091162,6521313,14326617,20002147,45381876,26102057,29510992,84187166,36812683,81805959,61741594,86301513,89046466,82178706,65271999,38658347,71469330,23740167,44847298,99917599,40534591,42692881,87710366,4095116,40865610,16684829,1375023,72373496,63059024,83651211,61623915,3487592,52293995,90158785,4508700,31733363,60176618,30366150,30771409,44348328,69579137,74441448,30463802,18001617,65081429,90457870,30891921,47887470,98371444,75986488,33935899,61141874,59109400,24826575,58751351,66663942,93790285,9829782,8128637,57359924,62115552,99125126,59371804,44664587,37675718,90013093,44473167,6819644,15064655,62552524,41092172,83150534,18806856,15902805,91938191,18663507,66667729,11757872,6871053,27185644,42307449,74724075,9603598,37560164,83302115,24058273,17385531,28796059,9175338,99690194,76671482,12024238,5267545,63506281,61859581,37224844,61712234,38256849,82651278,2331773,29516592,82779622,83747892,71965942,4515343,96735716,176257,16380211,38009615,77694700,7182642,15015906,66428795,86022504,22200378,9860968,6157724,69355476,47738185,98653983,44245960,27187213,824872,73222868,16567550,90654836,88698958,1250437,50806615,28734791,36139942,57961282,7646095,48774913,1569515,20681921,62762857,89699445,89217461,75407347,29635537,42947632,72358170,92530431,79322415,54663246,49882705,22766820,54232247,21397057,16405341,24314885,55470718,40027975,57163802,82339363,8129978,45049308,9188443,66271566,2891150,33895336,33699435,26063929,80246713,32699744,70782102,5970607,50668599,14723410,30218878,53888755,18131876,92867155,70420215,61982238,97057187,14363867,6038457,79738755,42967683,96420429,44177011,86798033,91255408,82052050,83368048,45617087,17957593,1239555,75135448,77300457,77301523,95010552,20645197,47893286,6221471,27325428,67084351,58208470,67124282,36135,26114953,86543538,68128438,13173644,48893685,3633375,94595668,12303248,81774825,73109997,79922758,12664567,96709982,10264691,1022677,72725103,33249630,68110330,10453030,72793444,7300047,95251277,57658654,56484900,17365188,33565483,21001913,32151165,82327024,37891451,55850790,83133790,77187825,96906410,94711842,3088684,22113133,78300864,44060493,9259676,51426311,86118021,77787724,60393039,34667286,83269727,59981773,5073754,89499542,13819358,20836893 +42782652,44473167,62496012,66250369,7502255,29188588,10358899,17081350,69605283,84684495,10366309,38494874,569864,70004753,77694700,98371444,29994197,65851721,16097038,824872,24733232,49641577,63152504,34295794,13231279,4099191,23569917,8791066,6808825,77284619,73021291,93057697,29510992,56424103,44245960,56531125,71920426,82178706,81677380,51047803,72777973,72274002,9886593,55850790,18783702,77312810,33201905,34698463,73222868,36505482,59371804,20486294,88444207,14220886,98648327,45237957,90090964,95290172,3633375,14326617,88047921,23848565,26292919,61982238,68041839,73235980,96193415,4119747,93515664,13470059,75543508,38061439,41245325,33061250,9188443,45790169,24915585,63506281,44842615,40197395,17037369,93933709,27411561,79821897,86460488,74110882,32699744,26863229,50806615,29959549,55470718,66832478,34946859,81172706,18663507,35996293,65880522,18806856,20140249,7229550,37620363,79657802,48892201,92398073,44550764,56153345,9860195,73392814,12348118,41481685,83150534,94516935,86022504,9575954,24168411,30771409,44177011,24619760,72725103,98130363,44889423,68816413,87160386,6111563,81855445,98739783,82886381,54517921,14093520,98948034,55753905,64087743,61741594,57803235,77787724,55247455,99515901,83789391,63967300,44627776,6525948,66116458,30395570,46540998,84904436,17068582,54014062,53842979,70800879,78602717,49236559,81853704,33304202,70036438,68644627,89078848,52060076,83651211,31904591,82726008,63015256,58208470,93053405,96726697,36753250,28928175,34493392,68875490,33237508,15535065,43045786,66322959,59981773,40686254,72495719,63372756,30218878,33797252,89637706,91957544,24826575,43506672,38658347,20836893,77072625,47213183,68204242,55143724,8055981,57359924,50668599,32161669,31126490,74743862,35456853,21533347,60393039,321665,82979980,70596786,6038457,85116755,87720882,16019925,89214616,35192533,19486173,60430369,95395112,67281495,56515456,56955985,29029316,17727650,94539824,91831487,42199455,66428795,37739481,50702367,22942635,92787493,33431960,99917599,29466406,79322415,69848388,99125126,8696647,5970607,37675718,1239555,99224392,89419466,3233084,38009615,22721500,51472275,50188404,29819940,92692978,74452589,71083565,72019362,30543215,26998766,41380093,80251430,247198,27325428,65715134,76540481,48893685,82339363,1569515,45306070,1788101,33565483,68102437,11543098,16099750,7011964,1204161,36312813,92604458,75153252,65038678,77300457,83133790,44784505,4508700,53393358,13468268,78766358,26734892,86118021,42237907,45381876,88251446,70367851,25636669,36780454,57393458,27665211,41092172,62051033,61271144,61141874,14947650,89499542,7066775,20867149,54762643,29635537,83302115,48673079,30787683,88653118,749283,55189057,83533741,68694897,82327024,669105,14349098,10453030,62693428,19101477,68000591,73031054,29834512,99021067,62740044,9623492,90310261,60102965,32590267,1250437,49328608,18466635,40152546,4434662,12664567,39801351,15015906,10309525,39201414,26102057,526217,51588015,42967683,77620120,58749,15064655,32274392,44847298,7646095,60581278,6221471,69355476,97561745,96852321,26392416,67474219,80555751,61815223,62430984,77413012,37891451,99971982,9603598,4515343,16054533,43501211,30366150,38341669,34698428,64055960,41442762,22200378,92071990,36580610,62762857,63059024,42644903,33628349,69579137,86001008,59109400,65271999,30653863,20122224,35092039,84166196,44479073,9860968,99524975,86798033,8128637,48260151,82651278,45407418,95581843,37501808,15148031,45428665,30463802,99861373,3294781,72793444,2204165,65017137,77272628,21993752,7517032,43376279,508198,96709982,55770687,24314885,74357852,44983451,47298834,45848907,91990218,8115266,49271185,59177718,30998561,33699435,82427263,16971929,8729146,40781854,67124282,2331773,62452034,90272749,36930650,4668450,51464002,17365188,49597667,39553046,9398733,4985896,99549373,48360198,81274566,30811010,59318837,72357096,70541760,97940276,72358170,62936963,61623915,91938191,16405341,5073754,36135,78300864,59501487,92530431,71469330,62232211,36685023,64602895,5482538,50007421,76434144,53648154,54987042,65081429,66663942,99297164,7104732,26139110,9599614,71316369,1375023,59910624,83269727,82897371,3487592,16595436,39373729,38022834,45617087,40677414,73168565,20885148,90013093,53666583,80246713,8913721,2917920,92033260,86543538,61859143,3183975,30569392,63628376,47361209,31727379,84127901,40984766,20642888,22405120,26114953,78884452,47792865,22113133,91255408,7687278,44060493,64848072,84349107,57658654,45996863,97011160,8373289,48774913,40356877,39847321,99965001,19272365,3088684,61373987,4064751,65074535,26507214,28851716,3880712,23194618,66885828,78589145,54427233,95010552,85023028,89699445,85711894,6871053,94711842,45794415,92554120,2891150,95957797,22129328,79136082,62490109,91802888,11923835,94595668,46851987,22108665,11161731,37183543,94090109,91141395,6986898,87598888,41092102,93790285,58224549,14626618,89811711,874791,60106217,20681921,19898053,99658235,24058273,54199160,8651647,10597197,19891772,51590803,16380211,76170907,81898046,26776922,67031644,76315420,24591705,75407347,69255765,12416768,69641513,3235882,76960303,38365584,67030811,99729357,53802686,62552524,79922758,7423788,47708850,38256849,66137019,30139692,66611759,86301513,80014588,98943869,57240218,38645117,96318094,7955293,95726235,83210802,61712234,69697787,16424199,64157906,8733068,54868730,49598724,61960400,15163258,2208785,75777973,4978543,32426752,14045383,28550822,39171895,34236719,78785507,5267545,16567550,97281847,73617245,55602660,16445503,66319530,33553959,17957593,53888755,91281584,13173644,68702632,20568363,62428472,56484900,53547802,79880247,42073124,91240048,96906410,71657078,88698958,96735716,17764950,5822202,40781449,28787861,32151165,67227442,5111370,52261574,89046466,98462867,68939068,1431742,68316156,91664334,18699206,168541,22879907,37560164,67793644,34044787,65047700,92803766,48658605,33249630,57163802,30694952,34497327,21289531,76703609,83747892,45919976,10961421,62803858,415901,96420429,72373496,56473732,47887470,54232247,26493119,51426311,93359396,93562257,30764367,43933006,58751351,71522968,84840549,67084351,71333116,79191827,42307449,69136837,69786756,42692881,65454636,92998591,11581181,28734791,17385531,26275734,55669657,75820087,75128745,17894977,20645197,74614639,14731700,70727211,37192445,37659250,6153406,61928316,19376156,65338021,70372191,74724075,11757872,22435353,19939935,93270084,53084256,57020481,21070110,49882705,59197747,50567636,89804152,91727510,78549759,27185644,81805959,78909561,79738755,35512853,55960386,29065964,23432750,1936762,90004325,13348726,84002370,14723410,43322743,31733363,79806380,85242963,27187213,6793819,16684829,76825057,20002147,97641116,32159704,37224844,21673260,44481640,15075176,43357947,75135448,23134715,7919588,45667668,10649306,66271566,37435892,7685448,54263880,4204661,40027975,32058615,68110330,17146629,9175338,82052050,75052463,15536795,26063929,98653983,3509435,45990383,89217461,45075471,51507425,79942022,51315460,44664587,93566986,54663246,78218845,18411915,92867155,70782102,1197320,2717150,36812683,22997281,44844121,13819358,68128438,29932657,72732205,61728685,6157724,33895336,45049308,21397057,59329511,53632373,66667729,61859581,48675329,99604946,88904910,85571389,52204879,76330843,52293995,68824981,45995325,84293052,8634541,36808486,9829782,7300047,4095116,99226875,70420215,29401781,83948335,85695762,87710366,77377183,77301523,33123618,72278539,73109997,60955663,97783876,12571310,40865610,90654836,84406788,32250045,29516592,4798568,89996536,6497830,30090481,92353856,59405277,46870723,19457589,38008118,58180653,9058407,27625735,6505939,112651,10264691,36933038,74137926,17386996,80316608,54606384,6521313,2607799,94360702,5832946,24953498,92541302,22450468,97057187,36396314,57961282,51149804,83083131,90457870,44348328,82532312,8807940,40534591,1022677,72738685,21001913,25842078,36189527,77898274,72238278,86821229,20765474,78561158,92692283,23110625,65275241,31161687,4091162,74441448,51715482,17539625,4199704,22766820,39986008,18131876,3773993,26397786,6819644,66045587,95251277,55615885,61263068,17976208,44846932,51466049,4069912,37957788,90158785,47090124,27041967,42947632,26664538,52613508,57248122,17857111,67451935,17016156,86242799,88130087,7182642,66903004,72614359,71965942,34432810,26744917,71300104,47893286,75986488,30163921,61897582,18504197,17058722,18001617,92215320,56461322,83368048,83155442,33935899,99690194,37166608,9257405,16387575,15948937,30891921,18833224,36468541,94076128,11365791,13862149,34667286,44915235,12024238,99333375,33336148,36845587,9259676,73786944,176257,5640302,64098930,14363867,8129978,28796059,77187825,97379790,84187166,15902805,62115552,47738185,59614746,73124510,90061527,4806458,3233569,43152977,31491938,57241521,54058788,76671482,82779622,4787945,94911072,60176618,48395186,12303248,48088883,8099994,62357986,23740167,37280276,36139942,75229462,38510840,81774825,85117092,21919959,67513640 +45919976,22942635,56515456,86001008,44481640,88653118,35456853,95957797,8651647,8634541,526217,61373987,50188404,57961282,90310261,97783876,29932657,97281847,112651,11543098,8696647,56424103,61859143,68000591,84293052,83210802,26392416,59109400,73124510,98371444,62452034,73222868,36780454,36933038,19272365,36808486,70367851,36189527,2917920,5970607,31733363,73168565,39801351,4668450,62803858,31904591,8807940,36312813,7011964,33249630,35996293,6808825,85117092,68204242,92554120,16380211,64157906,53547802,70420215,62496012,93933709,79657802,60106217,61263068,36812683,18783702,26114953,54199160,57163802,24733232,48892201,56531125,69641513,76703609,53888755,5640302,99125126,26063929,20002147,37183543,53393358,14093520,77898274,63059024,39847321,99861373,65081429,55189057,9886593,14947650,44177011,82178706,50702367,39986008,66611759,36685023,34698463,68644627,48893685,61141874,49328608,82979980,95726235,66667729,19939935,73786944,88698958,42782652,91255408,24591705,29994197,38008118,415901,96318094,71522968,81853704,21397057,36580610,68110330,5482538,93270084,70372191,43376279,36505482,17857111,62490109,16019925,64098930,81855445,17016156,7104732,52293995,45790169,75543508,6221471,53802686,29065964,76170907,32159704,16445503,83533741,37620363,34295794,44842615,6153406,40865610,94076128,14349098,247198,7182642,59501487,49597667,17764950,19486173,69136837,84904436,41380093,91957544,5111370,85242963,32426752,15536795,9829782,77284619,60102965,4798568,44550764,45407418,18806856,72614359,40027975,84349107,569864,61897582,51047803,94711842,43501211,9603598,72738685,4099191,27665211,99604946,71657078,14220886,17976208,98462867,83302115,3773993,43322743,74357852,80555751,33895336,16387575,34698428,71965942,36753250,72373496,44060493,96193415,80014588,24058273,26664538,77312810,85023028,18699206,75407347,53084256,23134715,51472275,5832946,38061439,61982238,67281495,55770687,71333116,17365188,3880712,76434144,37739481,77187825,76330843,78766358,10358899,44889423,27187213,80246713,65038678,14045383,1788101,84187166,21070110,3088684,94539824,65017137,39171895,7517032,30771409,47090124,37957788,62552524,71300104,12024238,13348726,28787861,31491938,99690194,72777973,98739783,58749,90013093,55470718,95395112,28928175,28550822,13173644,59329511,92803766,7955293,25842078,76540481,77694700,38365584,11161731,49641577,34493392,29819940,61960400,66832478,77413012,70036438,18131876,13470059,39553046,92033260,72793444,15064655,33123618,45848907,99515901,93359396,58208470,34432810,82052050,41092172,99021067,89078848,11923835,16567550,13819358,30218878,23740167,3294781,45428665,18663507,41092102,9575954,83368048,45667668,49882705,99549373,21993752,15535065,1375023,93566986,48774913,67793644,90457870,32590267,34044787,23569917,88444207,22129328,60430369,63967300,14731700,26744917,64055960,3235882,96906410,2331773,61271144,13231279,74441448,77787724,874791,12416768,37166608,89499542,92604458,16405341,55669657,68824981,6793819,45996863,6986898,38494874,84840549,78589145,30694952,74137926,23848565,37192445,64602895,47213183,93057697,16097038,13862149,9188443,57359924,33336148,99917599,43933006,53648154,70541760,36930650,12664567,69848388,59318837,37891451,15148031,70004753,66137019,6505939,19376156,94516935,79738755,77300457,64087743,10649306,17727650,86821229,26139110,66250369,29401781,96726697,16971929,58751351,96735716,30764367,48360198,90004325,90061527,40686254,54663246,6819644,30366150,66428795,89214616,9257405,11581181,45990383,54014062,65338021,40152546,22405120,33797252,6038457,98653983,77301523,52060076,88047921,53666583,68816413,8055981,65880522,87598888,64848072,3633375,18833224,33237508,72238278,57248122,73031054,2607799,91990218,81898046,51315460,97561745,95290172,62430984,18504197,93790285,95581843,67451935,10366309,10961421,24619760,81805959,59177718,38645117,41442762,32161669,79806380,97379790,54762643,3233084,97011160,33304202,35092039,33565483,21919959,17385531,53842979,44245960,73235980,20885148,30395570,65074535,78300864,40677414,85116755,42967683,8129978,86022504,15948937,29834512,32250045,79191827,82779622,31126490,27325428,95010552,28734791,18411915,49236559,18466635,69355476,72357096,50668599,7687278,1569515,76671482,48675329,66885828,4434662,50007421,80316608,67124282,33201905,72019362,37501808,44983451,83133790,71083565,10597197,43045786,60393039,47738185,96420429,51426311,19101477,78218845,55602660,89637706,7919588,15163258,1022677,61815223,27411561,9623492,98648327,92530431,20867149,69255765,57803235,86460488,42307449,82651278,72495719,92787493,19898053,97940276,33628349,79942022,66045587,98943869,4806458,6157724,90272749,4204661,9398733,7646095,74452589,1204161,22879907,98130363,46870723,87710366,34236719,73392814,62051033,4787945,12348118,82339363,89046466,70596786,34667286,48658605,17058722,7502255,29959549,94090109,83789391,51590803,54232247,1250437,89419466,72725103,37659250,75777973,45995325,28796059,50806615,2891150,1936762,29466406,63152504,43152977,24953498,17037369,66663942,22113133,22450468,4508700,99524975,83083131,23194618,61859581,26493119,824872,37675718,97057187,98948034,55247455,19891772,36139942,99658235,39373729,30139692,87160386,77272628,26507214,20681921,15015906,83150534,37280276,75986488,70727211,44348328,30569392,55960386,61623915,92692283,97641116,508198,26998766,54606384,62936963,321665,9259676,87720882,44473167,52261574,70800879,45075471,60581278,21533347,8128637,29510992,30998561,17146629,91141395,7300047,7423788,91831487,5267545,63506281,79922758,65275241,48395186,40534591,11757872,94911072,16424199,29635537,7685448,1431742,9599614,57020481,45617087,80251430,91727510,91802888,37224844,83948335,81677380,51464002,81274566,28851716,2717150,62693428,71920426,33431960,96709982,11365791,20486294,43506672,26292919,78602717,24915585,30163921,94595668,90090964,62762857,4064751,1197320,69786756,56484900,42073124,68939068,84127901,63372756,47298834,35512853,59197747,39201414,8733068,31161687,51507425,68128438,8729146,5822202,26397786,17068582,20765474,72274002,4119747,22997281,51588015,84684495,21001913,57658654,1239555,61928316,73617245,669105,35192533,85571389,6497830,66903004,14626618,5073754,55753905,68102437,72732205,55143724,2208785,54427233,72278539,8913721,42692881,82327024,77072625,4199704,93562257,92692978,83269727,25636669,59910624,59371804,57393458,4515343,93053405,95251277,63015256,59981773,54263880,88904910,53632373,57241521,9058407,24826575,68316156,17957593,168541,75135448,68702632,52204879,45237957,62232211,74743862,93515664,22766820,79136082,43357947,45794415,68875490,88251446,22435353,54868730,8791066,40781449,42644903,82726008,29516592,67227442,56473732,76960303,30787683,75229462,79821897,91664334,40356877,78909561,84166196,99729357,20836893,44627776,17539625,81172706,26776922,40781854,34497327,16099750,38009615,14326617,8115266,20568363,32699744,44846932,3509435,33553959,92215320,15075176,71316369,67031644,51715482,99297164,44784505,74110882,26734892,41245325,8099994,76825057,77620120,62740044,74724075,89811711,58224549,52613508,60955663,91938191,68041839,6521313,71469330,38341669,4095116,61728685,42947632,33699435,27625735,18001617,22108665,69605283,65715134,82897371,99965001,38022834,3487592,749283,51149804,9860195,45306070,82427263,55615885,72358170,89699445,4978543,36396314,86301513,47361209,20645197,29029316,8373289,16595436,30090481,19457589,92998591,96852321,45049308,40197395,20642888,67030811,99333375,6871053,14363867,83155442,38658347,90654836,37560164,60176618,33061250,3183975,92071990,44844121,30463802,56461322,48673079,89804152,30811010,56153345,59405277,10309525,176257,92398073,86798033,36135,69579137,21673260,50567636,48260151,40984766,89996536,36468541,4091162,86543538,22200378,92541302,65047700,77377183,67084351,44915235,59614746,65851721,13468268,23432750,89217461,83651211,82886381,7229550,32058615,10453030,47708850,99226875,79880247,34946859,83747892,74614639,86118021,44479073,33935899,65271999,22721500,67513640,62357986,38256849,56955985,47887470,84002370,3233569,91240048,16054533,42237907,27041967,62428472,15902805,30891921,63628376,4069912,48088883,82532312,78561158,38510840,27185644,9860968,66322959,26863229,46540998,54058788,92867155,10264691,54987042,47893286,92353856,66319530,17386996,47792865,65454636,30543215,67474219,12571310,66116458,46851987,85695762,75153252,84406788,91281584,54517921,78785507,66271566,26102057,75128745,94360702,61741594,29188588,24314885,68694897,49598724,78884452,99224392,31727379,20140249,44664587,37435892,20122224,88130087,85711894,69697787,78549759,2204165,99971982,76315420,49271185,21289531,81774825,6525948,70782102,23110625,86242799,9175338,90158785,6111563,55850790,75052463,58180653,26275734,17894977,32274392,62115552,41481685,73109997,79322415,14723410,4985896,30653863,44847298,57240218,36845587,61712234,16684829,7066775,51466049,12303248,42199455,17081350,73021291,24168411,75820087,45381876,32151165 +72725103,91727510,98371444,27665211,84406788,96193415,44889423,95957797,50702367,22450468,29065964,95395112,44348328,88653118,37891451,77787724,29834512,89419466,82052050,51464002,15015906,44983451,62452034,4668450,90457870,10649306,9259676,28796059,33431960,49882705,6793819,47213183,46851987,70420215,7955293,23848565,22129328,16445503,83155442,23194618,60102965,83210802,6505939,77284619,56484900,68824981,84684495,8129978,53802686,99515901,66663942,40984766,62740044,16054533,2717150,35192533,82726008,45407418,1431742,42692881,95290172,73124510,19939935,99549373,669105,7423788,6525948,321665,75135448,42199455,9575954,61263068,99971982,18833224,14220886,81853704,30543215,20568363,32250045,25636669,96318094,69136837,99917599,415901,47090124,4515343,34698463,61960400,39801351,65851721,63152504,45790169,33699435,74724075,31904591,62552524,17068582,35456853,93053405,37435892,9623492,79191827,89214616,37620363,88047921,81898046,69355476,49328608,18806856,13470059,44479073,32590267,45990383,57163802,61982238,91802888,18663507,74137926,62762857,48892201,11543098,6808825,83150534,10366309,20140249,16971929,20002147,44481640,59614746,4798568,61271144,33304202,76434144,77272628,15535065,78602717,93270084,94516935,42782652,72274002,54987042,45919976,21993752,65275241,18411915,68939068,22108665,39986008,59318837,61623915,39201414,508198,45049308,98648327,11161731,75986488,9886593,72738685,55247455,87710366,64087743,62803858,3633375,18504197,59371804,99524975,68204242,33797252,99658235,5970607,77620120,54663246,40197395,10309525,44245960,75407347,14947650,68702632,91990218,52613508,40677414,76540481,3773993,96726697,55753905,39553046,15148031,87720882,38645117,82178706,30891921,55470718,74110882,33237508,83368048,89996536,72732205,38008118,65017137,749283,51588015,34698428,89637706,67793644,17764950,54762643,8729146,37659250,43376279,55770687,44784505,40686254,44842615,55669657,33628349,72373496,65038678,247198,56515456,74441448,66137019,31161687,44473167,12571310,70367851,59197747,84187166,26139110,35092039,5111370,26776922,66250369,42947632,30771409,26863229,65715134,58180653,12416768,45848907,80246713,82979980,4069912,33249630,53547802,99333375,29959549,14626618,91664334,79821897,93057697,13173644,29932657,54199160,84840549,83533741,7685448,78589145,4099191,53393358,76960303,8807940,43322743,51426311,20885148,6038457,9188443,43933006,20867149,44177011,59910624,63967300,34493392,7646095,62496012,18699206,97783876,50188404,38494874,21397057,93933709,53084256,71083565,26392416,36580610,98462867,16019925,29510992,1197320,33061250,46540998,1788101,65338021,91957544,51149804,32274392,81677380,14349098,17957593,92787493,85116755,42967683,82339363,57803235,19376156,68816413,5073754,3294781,44550764,51507425,30395570,75153252,67030811,48088883,28734791,27411561,29188588,7011964,60430369,1569515,45075471,78218845,7919588,51047803,3880712,52293995,77187825,91831487,94090109,19272365,96906410,30569392,54517921,79942022,87598888,62430984,89046466,85117092,29466406,78561158,3233569,60176618,36812683,56153345,36468541,94911072,3487592,92803766,77301523,50668599,22942635,94076128,40027975,67451935,12348118,97379790,3183975,8128637,61897582,71657078,31733363,92692283,78785507,52261574,61859143,37675718,9058407,69255765,15064655,63506281,98653983,55960386,53648154,59177718,97641116,36685023,16380211,83789391,72019362,51590803,77694700,32159704,92554120,6153406,47361209,61373987,57241521,82897371,27185644,72614359,68000591,75229462,99021067,79922758,3233084,21533347,44844121,24314885,99861373,70372191,37280276,40781854,66045587,4064751,8099994,24591705,26063929,88251446,25842078,64055960,57359924,39373729,83269727,73222868,61141874,4204661,64157906,31727379,39847321,86118021,8791066,55189057,66885828,94595668,66832478,14363867,99729357,8696647,97561745,29819940,17365188,54868730,75543508,48360198,28928175,63015256,37183543,20765474,26998766,50567636,82886381,98948034,92998591,73617245,33201905,65074535,92530431,26744917,73235980,63628376,1204161,41245325,84349107,72495719,51315460,99226875,18466635,37560164,12664567,22766820,4434662,85242963,65081429,29994197,78300864,9599614,59329511,98739783,9860195,65880522,76671482,78766358,90013093,65271999,70727211,6497830,3509435,7182642,22435353,92692978,71920426,49598724,26734892,71469330,49597667,89078848,38061439,82327024,30218878,65047700,66428795,36505482,48260151,7229550,56461322,59501487,15075176,34667286,9603598,44847298,61815223,17894977,64602895,49236559,30463802,34044787,85571389,24058273,77312810,41092172,1250437,14045383,92604458,59405277,18783702,41380093,92071990,30163921,16387575,70541760,30787683,93359396,81805959,48658605,79880247,54014062,9257405,73168565,38341669,17976208,92398073,34432810,4978543,60581278,97281847,88444207,29029316,22113133,71333116,23134715,77898274,70800879,33553959,54232247,74357852,51472275,84127901,2607799,61859581,34295794,43152977,89804152,2891150,62490109,49271185,16595436,13468268,49641577,67474219,10961421,56424103,68041839,42644903,72777973,37166608,67227442,17037369,57658654,67281495,80316608,64848072,47708850,59109400,41442762,14093520,50007421,98943869,66611759,44060493,93562257,38658347,82427263,69697787,20642888,24915585,84293052,80014588,46870723,10358899,30998561,29516592,71316369,40356877,19891772,53842979,51715482,6986898,5832946,21289531,51466049,45995325,53888755,66319530,39171895,56955985,27625735,93515664,7502255,79657802,57248122,99604946,94711842,7300047,17146629,16099750,42307449,70036438,83948335,90272749,62936963,55143724,26507214,24168411,5640302,26275734,99297164,6221471,2208785,95726235,66322959,88698958,24733232,86821229,89499542,44915235,76703609,26114953,14326617,80251430,16097038,17058722,80555751,33935899,69579137,91141395,15536795,55602660,89217461,40781449,2917920,95581843,43501211,31126490,74743862,24826575,63372756,58749,28851716,20645197,68110330,9175338,76315420,11365791,8651647,72357096,52204879,86001008,58224549,37192445,45428665,56531125,36753250,41481685,15948937,48673079,12024238,53632373,7104732,60106217,4806458,2204165,30139692,83083131,79806380,34946859,72278539,36312813,35996293,97057187,14731700,57961282,68644627,50806615,8373289,91255408,36930650,17016156,60955663,27187213,3088684,94360702,33895336,45617087,36808486,4095116,34497327,76170907,23740167,99965001,72238278,83133790,95010552,43357947,1022677,18131876,44846932,68128438,30764367,5822202,569864,85023028,99224392,68102437,20486294,90090964,27325428,112651,38365584,75128745,94539824,26664538,41092102,82779622,3235882,63059024,81855445,66903004,56473732,70004753,91281584,26493119,14723410,86301513,19486173,92033260,45237957,36135,32161669,17385531,24619760,15163258,70596786,86022504,76330843,17539625,96852321,98130363,8634541,61728685,16567550,8115266,20122224,526217,57020481,81774825,23432750,37739481,37501808,4119747,71300104,78909561,6111563,17857111,4199704,81274566,54606384,7687278,73031054,8055981,61712234,81172706,16684829,30090481,8733068,69641513,13862149,57240218,32426752,5267545,24953498,64098930,77072625,73109997,74614639,84904436,21070110,9860968,37224844,87160386,92353856,45381876,82651278,52060076,82532312,75820087,34236719,33565483,47893286,6871053,75777973,96420429,90004325,10264691,66271566,44664587,36933038,45794415,71522968,13819358,30366150,36189527,2331773,43045786,6157724,78549759,61741594,42237907,4508700,58751351,17386996,90310261,85695762,42073124,75052463,72793444,66667729,20836893,79738755,68694897,11581181,33123618,92541302,76825057,1375023,176257,86460488,4985896,15902805,54058788,62357986,84166196,37957788,62693428,88904910,54427233,38256849,32699744,6819644,23569917,32058615,19457589,21919959,13348726,26102057,92867155,12303248,67084351,29401781,55615885,66116458,26292919,84002370,23110625,22721500,9398733,72358170,48893685,73392814,19101477,77413012,28787861,78884452,40865610,7517032,99690194,30694952,73786944,6521313,85711894,1239555,22200378,16405341,93566986,40152546,36780454,18001617,53666583,77377183,11757872,45667668,47792865,77300457,28550822,48395186,69605283,32151165,89699445,31491938,91240048,71965942,91938191,10597197,60393039,47738185,69848388,83747892,67124282,36845587,36396314,35512853,86543538,27041967,62232211,30653863,83302115,97011160,21001913,62051033,4787945,90061527,874791,45306070,9829782,90654836,68875490,16424199,22405120,43506672,96735716,11923835,93790285,90158785,10453030,824872,99125126,74452589,8913721,48675329,48774913,45996863,65454636,30811010,38022834,38009615,47887470,73021291,1936762,62428472,5482538,86242799,83651211,57393458,67031644,58208470,33336148,29635537,38510840,44627776,26397786,40534591,70782102,21673260,13231279,22997281,4091162,17727650,62115552,22879907,168541,20681921,7066775,89811711,61928316,17081350,68316156,36139942,59981773,67513640,79136082,47298834,69786756,86798033,95251277,79322415,92215320,55850790,97940276,96709982,88130087,54263880,19898053 +4668450,68702632,96193415,66250369,8807940,77312810,85571389,9886593,17764950,55470718,29834512,57241521,89046466,43376279,64087743,94539824,70800879,32159704,61263068,81805959,26734892,15015906,44784505,29959549,13231279,79821897,44348328,87710366,51047803,26292919,81677380,99658235,78589145,14349098,47361209,89637706,63967300,75543508,9860968,38061439,25842078,40152546,96726697,89214616,62051033,29510992,39373729,16019925,6793819,39801351,4099191,69641513,2717150,76960303,65851721,4119747,54427233,45794415,45237957,4434662,35092039,77272628,30764367,61859581,65275241,81853704,43501211,12348118,16595436,72274002,14947650,65074535,20140249,48774913,77300457,88653118,34698428,26392416,99515901,76315420,77301523,77620120,54058788,92692283,53802686,8696647,78561158,72019362,79136082,58208470,21533347,48675329,40356877,6525948,12416768,20486294,44473167,33431960,12024238,16445503,1204161,68128438,18504197,82886381,84187166,19486173,19939935,31126490,80014588,6808825,53547802,64848072,112651,22766820,73031054,53632373,56531125,20765474,34497327,57240218,17957593,93933709,80316608,89499542,75777973,62936963,70596786,4095116,92554120,54517921,95957797,85116755,74743862,2891150,14723410,44889423,47090124,51588015,83948335,49236559,82427263,8129978,67793644,61373987,95726235,30891921,80251430,68939068,92215320,1569515,49641577,68041839,4798568,74724075,9623492,17365188,89217461,5832946,51426311,57803235,91664334,17058722,61271144,72357096,82339363,91957544,75229462,31161687,94911072,16567550,93562257,73235980,98943869,98462867,36780454,78909561,16097038,18699206,13862149,57658654,51464002,34236719,569864,85023028,86821229,79942022,9575954,33304202,40686254,4064751,75153252,79880247,40027975,88251446,55753905,47792865,10309525,43045786,46851987,97940276,247198,63059024,59197747,8115266,30787683,62693428,47708850,97561745,63372756,7685448,9259676,27665211,28734791,168541,26102057,24915585,10358899,71333116,27325428,28851716,92604458,32161669,91802888,14045383,54987042,30694952,39201414,53393358,90310261,44481640,8729146,98130363,83533741,92692978,8055981,6986898,44844121,36930650,68644627,70541760,9860195,98648327,83789391,98371444,92398073,96318094,90013093,24058273,26998766,21993752,50668599,33201905,55669657,66116458,8791066,44842615,58180653,40781449,33237508,52293995,22405120,80246713,60430369,39847321,72725103,66271566,4985896,68204242,22942635,41481685,749283,55247455,16054533,45996863,74452589,45848907,16099750,60955663,60106217,74357852,62740044,72614359,29635537,20836893,78549759,82651278,35192533,65454636,92998591,14731700,56424103,37957788,49597667,66319530,54232247,94090109,46540998,66137019,14093520,17068582,5640302,99524975,55189057,33699435,9175338,9829782,20867149,37620363,73786944,65880522,46870723,42644903,17037369,32590267,91938191,669105,57359924,69697787,62552524,16684829,30543215,99861373,73392814,66322959,59501487,42782652,39986008,74614639,61741594,7011964,51315460,99549373,13470059,1239555,6153406,45407418,10453030,36580610,59177718,10366309,99226875,87720882,90457870,31733363,26507214,1022677,30771409,1250437,55770687,58751351,49598724,59318837,78785507,29819940,59109400,81898046,37659250,16387575,78300864,76170907,95290172,7300047,79657802,22997281,29466406,51715482,26863229,78766358,79922758,19376156,65081429,40865610,30090481,67474219,32250045,69579137,9188443,37739481,64055960,26139110,44177011,15536795,29401781,6521313,508198,18783702,54663246,87598888,51466049,7502255,7955293,84684495,40197395,60581278,88444207,68694897,88130087,45428665,24733232,8128637,94516935,2917920,23569917,61859143,36808486,77787724,33797252,62452034,36685023,4069912,66428795,12571310,49271185,90272749,35456853,59614746,92033260,53888755,72738685,50567636,2204165,26776922,98948034,70004753,30139692,36505482,61623915,62428472,43322743,5111370,81172706,18833224,71316369,58749,93057697,30218878,87160386,20122224,8651647,31904591,874791,78602717,84293052,60102965,52261574,37192445,4204661,40677414,37891451,26493119,71083565,62490109,22113133,6505939,7066775,30811010,56515456,38256849,66832478,38341669,83150534,34295794,84904436,3294781,38022834,15535065,17976208,37501808,42692881,67281495,54762643,13348726,55602660,96735716,47213183,67031644,79806380,6497830,30395570,1197320,97379790,77284619,23194618,18001617,7646095,80555751,7229550,30366150,321665,5970607,75052463,78218845,8373289,84127901,33628349,36312813,31491938,38494874,37280276,30653863,3183975,65047700,76671482,28550822,61960400,65338021,22450468,65715134,1375023,99224392,29029316,19101477,52204879,69355476,66611759,8733068,82052050,3233569,49328608,20002147,68824981,74137926,73124510,24619760,50188404,56461322,90004325,19891772,92803766,47887470,3235882,70036438,53648154,54014062,11161731,73222868,33565483,29516592,28796059,38008118,81855445,33336148,62430984,14326617,85695762,29932657,91831487,77187825,69136837,99297164,91727510,97783876,2208785,69255765,45075471,85242963,43506672,82897371,4508700,20885148,14363867,96709982,71469330,89078848,34493392,83651211,16971929,69848388,56484900,37224844,48892201,54606384,44846932,40781854,48088883,61141874,68875490,51472275,45381876,43933006,76540481,34698463,68102437,86001008,23848565,4806458,7919588,61982238,54199160,71522968,36845587,98739783,50806615,29188588,82779622,30163921,83083131,60393039,78884452,95251277,37435892,29065964,3633375,38645117,15148031,83133790,63628376,21673260,11365791,99021067,70367851,66667729,97057187,88047921,72793444,29994197,82532312,19272365,20642888,52613508,83210802,42199455,9603598,84166196,97011160,86301513,37560164,33935899,11923835,28928175,55143724,45990383,6038457,92530431,4515343,72278539,66885828,93053405,77072625,62803858,17386996,40984766,41092102,34044787,27625735,26114953,41380093,68316156,14220886,73617245,63015256,10597197,19457589,76330843,50702367,88904910,3487592,42307449,17727650,24953498,4199704,68816413,93515664,38365584,75986488,3088684,43357947,68000591,44983451,3233084,38009615,86460488,90158785,91141395,93270084,59329511,89996536,30463802,94711842,9398733,11581181,99965001,71300104,36468541,67513640,48360198,53666583,70372191,66045587,3773993,45790169,76434144,85117092,62232211,37675718,99604946,66903004,74110882,97281847,71920426,45667668,64157906,7104732,20645197,35996293,4787945,86543538,415901,65271999,30998561,57961282,9257405,12664567,50007421,82178706,48893685,57393458,81274566,54868730,49882705,66663942,5267545,44550764,13173644,36753250,61712234,18131876,45306070,98653983,91990218,824872,55960386,526217,21070110,97641116,89699445,77413012,39171895,44847298,92541302,33061250,60176618,90090964,14626618,32151165,84002370,4978543,59371804,17539625,33553959,32274392,15902805,63152504,21289531,94360702,82327024,71965942,37183543,95010552,84406788,38658347,26744917,71657078,21001913,9599614,82726008,62357986,44627776,62496012,83302115,48658605,48673079,47893286,7182642,45049308,5482538,1431742,95395112,15064655,76703609,39553046,70782102,26063929,92071990,85711894,45919976,23432750,51149804,83269727,72358170,72373496,99333375,94076128,72732205,52060076,2607799,77377183,36933038,27411561,6871053,53084256,11543098,91240048,42073124,35512853,89419466,13819358,61728685,44245960,24314885,6221471,93359396,47298834,45617087,70727211,17894977,51507425,99729357,7687278,32699744,31727379,47738185,10649306,18411915,28787861,57248122,22129328,93566986,19898053,57163802,44479073,67124282,72495719,20568363,42947632,13468268,33249630,61815223,65038678,6111563,48260151,22721500,86242799,10961421,30569392,73168565,75820087,58224549,3880712,94595668,44060493,23134715,7517032,83155442,96906410,18663507,92787493,77898274,6819644,24826575,79191827,67227442,72238278,27041967,18466635,3509435,53842979,65017137,54263880,91281584,48395186,76825057,59910624,41245325,82979980,7423788,17857111,18806856,36135,68110330,9058407,23110625,89804152,34667286,17385531,5822202,16424199,32426752,86022504,51590803,8913721,15075176,90061527,17016156,33123618,89811711,75407347,43152977,67451935,86118021,69605283,36812683,16405341,77694700,1788101,72777973,8099994,16380211,34432810,25636669,44915235,96420429,33895336,75128745,15163258,79738755,42237907,27185644,22435353,91255408,23740167,70420215,84349107,90654836,1936762,20681921,44664587,64602895,24591705,73109997,17146629,99125126,92353856,92867155,21919959,83368048,42967683,21397057,55615885,27187213,75135448,99971982,11757872,62115552,26275734,93790285,84840549,99917599,86798033,22108665,36139942,81774825,61897582,12303248,56473732,6157724,41442762,69786756,56153345,176257,15948937,95581843,59981773,88698958,32058615,74441448,26664538,96852321,45995325,57020481,79322415,99690194,61928316,64098930,4091162,59405277,38510840,36189527,22879907,36396314,37166608,34946859,62762857,24168411,10264691,73021291,17081350,67030811,83747892,26397786,5073754,63506281,8634541,67084351,55850790,56955985,22200378,41092172,40534591,2331773 +9623492,34493392,22450468,28928175,99917599,5111370,2917920,39171895,74357852,16097038,20885148,94911072,70596786,59197747,19939935,6986898,12348118,95010552,57803235,97561745,17764950,99965001,92554120,83150534,52261574,4787945,29188588,51047803,68875490,63967300,93933709,85242963,7919588,37501808,1204161,78602717,37166608,56531125,37739481,48673079,25636669,74614639,86022504,54987042,71333116,61859581,73392814,13231279,33895336,66885828,27665211,81898046,8807940,89046466,66903004,65271999,68702632,38658347,26114953,95581843,76703609,66045587,3509435,8129978,7517032,49236559,96709982,87710366,84904436,94090109,97783876,73222868,10366309,24619760,42307449,1022677,36580610,34236719,86821229,99604946,98130363,29029316,81274566,20642888,32590267,98739783,35192533,1569515,37620363,48892201,89214616,80246713,73168565,37192445,88047921,61897582,16445503,28796059,29466406,43376279,78561158,77272628,29819940,40356877,17058722,89078848,24733232,36685023,90090964,64087743,15535065,26275734,4199704,64848072,51472275,247198,4099191,43045786,16099750,34432810,92998591,6221471,6793819,8696647,61982238,66271566,16595436,36312813,16019925,20645197,49598724,20002147,7646095,79922758,65338021,7955293,44479073,30891921,16424199,53084256,78766358,46870723,93270084,5832946,62115552,9603598,1431742,70800879,36780454,14326617,6808825,77377183,54014062,41092102,88653118,17068582,81855445,69355476,71657078,18833224,71316369,83210802,6505939,34946859,52613508,70372191,79191827,47738185,36189527,72777973,67030811,33061250,56473732,53802686,77620120,61859143,8099994,65715134,74743862,62430984,54199160,3773993,59329511,68041839,77413012,6819644,77072625,23194618,36812683,38494874,71920426,76540481,10597197,88251446,72238278,79942022,19898053,67474219,22108665,14626618,32426752,76434144,92692978,44889423,38022834,9257405,8729146,32159704,50668599,26397786,68204242,30163921,40865610,55602660,54762643,98371444,40027975,67793644,29994197,824872,26734892,82427263,58749,38008118,38341669,19457589,21993752,73031054,99549373,2717150,5267545,18699206,82886381,63059024,61271144,60430369,64602895,85116755,66322959,93515664,66611759,52060076,40197395,39553046,38645117,97940276,93562257,89419466,43357947,38510840,66667729,9599614,8651647,76960303,18131876,31904591,77312810,61928316,80316608,35512853,17727650,7229550,90272749,99971982,64055960,72495719,33336148,56461322,4119747,24058273,37675718,38061439,26507214,8733068,44784505,9575954,14220886,112651,72358170,94711842,23740167,91802888,31727379,5482538,83302115,19891772,81172706,55247455,33304202,55960386,43506672,26998766,26493119,33237508,92398073,79806380,20486294,86001008,99125126,73235980,22879907,59910624,45075471,34698463,10358899,18466635,68816413,88698958,32250045,62428472,73124510,29510992,48260151,26863229,88130087,36753250,40152546,53632373,30463802,95957797,72373496,32151165,81853704,7502255,41245325,96193415,66137019,44842615,50188404,65017137,62693428,50806615,55143724,9886593,87720882,99861373,28550822,31733363,21533347,72732205,15148031,9398733,90457870,62452034,70367851,49641577,88904910,44983451,47090124,78884452,9058407,95395112,89699445,49882705,93053405,68316156,82779622,42199455,53842979,73617245,78300864,70541760,31126490,99224392,77187825,55753905,19376156,87160386,68128438,11161731,28734791,15163258,57241521,72738685,62357986,29932657,30395570,18783702,83155442,65074535,74110882,9188443,36505482,63506281,66250369,75153252,45237957,94539824,16971929,92692283,33565483,37957788,36930650,45996863,38365584,30787683,26392416,34698428,22129328,61815223,60955663,62936963,22997281,76671482,83083131,6153406,77301523,20867149,95290172,32161669,1788101,9860195,35996293,66832478,1375023,17976208,17037369,93057697,99226875,23569917,33553959,51507425,79136082,4668450,54427233,6497830,37435892,83789391,61263068,669105,99515901,11581181,69786756,61373987,4095116,74452589,30139692,91281584,27411561,53666583,68644627,13468268,41092172,65275241,15075176,72019362,56153345,874791,83651211,16567550,27625735,76170907,49328608,70036438,40534591,30764367,16054533,18001617,45428665,54263880,98943869,12024238,34044787,92215320,54663246,37183543,54606384,17539625,94076128,61728685,29959549,42782652,15948937,72357096,62803858,68000591,77284619,84127901,80014588,30366150,51466049,84293052,3487592,44915235,18806856,72274002,75135448,44245960,30090481,53547802,85695762,83948335,1936762,3633375,44348328,50702367,40984766,42073124,90310261,20765474,45990383,65038678,45995325,88444207,36808486,81677380,69579137,72725103,62496012,7687278,26102057,44844121,42692881,64157906,60106217,48675329,34497327,15902805,14947650,87598888,42237907,82897371,67451935,9175338,96735716,48360198,30543215,98948034,63152504,72278539,92604458,46851987,37659250,95726235,73786944,75820087,11365791,92541302,44550764,99297164,55770687,70727211,22942635,46540998,1197320,21070110,71300104,84187166,67031644,19272365,29834512,26292919,45848907,39373729,79738755,55189057,35456853,43501211,53888755,47893286,10649306,98462867,79880247,43322743,96726697,51464002,90013093,3088684,86242799,23110625,8128637,4064751,69641513,66116458,57248122,49271185,62051033,58180653,74441448,16387575,37891451,78909561,5073754,98648327,84406788,30653863,2607799,17016156,92071990,36933038,91727510,56515456,44177011,176257,6038457,91664334,33123618,45790169,56424103,16684829,82726008,7685448,569864,20568363,89811711,34295794,82052050,19101477,89637706,749283,84840549,19486173,65851721,92033260,61623915,17365188,85023028,91831487,17385531,79657802,78549759,29065964,4515343,99524975,42947632,60102965,14723410,33699435,14731700,97641116,67084351,4806458,79821897,3183975,71083565,36139942,1250437,82979980,59371804,66428795,23848565,75543508,62232211,42644903,40686254,8373289,84684495,85571389,508198,13173644,13348726,54232247,74724075,75128745,92353856,25842078,76315420,7104732,3235882,68824981,22200378,10961421,18663507,61141874,41481685,12303248,2204165,35092039,90061527,23432750,415901,91240048,91938191,26139110,20140249,84349107,59318837,17386996,60176618,68694897,54058788,73021291,82532312,69697787,59109400,52293995,11543098,39801351,33201905,41442762,3294781,63628376,17894977,3233084,22405120,20122224,78785507,45667668,48658605,93359396,59177718,45306070,23134715,51315460,18411915,80555751,24591705,4069912,33797252,15064655,70004753,86301513,7423788,63372756,27185644,89804152,97057187,93790285,14045383,526217,71469330,2208785,77898274,45919976,28851716,48088883,60581278,75986488,62740044,84166196,85117092,99658235,57020481,51588015,97011160,85711894,26063929,21919959,4798568,6521313,96318094,56484900,33628349,91957544,54868730,52204879,86460488,55470718,96906410,7011964,17146629,75407347,48395186,64098930,91255408,95251277,37560164,99021067,5822202,27325428,92867155,67227442,53648154,78218845,6525948,30771409,42967683,89996536,78589145,17957593,26744917,51149804,30569392,47792865,15536795,31491938,44664587,57393458,11923835,8115266,30218878,40677414,45407418,30694952,77787724,6871053,86798033,83368048,92803766,58751351,71965942,48774913,57163802,91990218,17081350,57658654,63015256,86118021,96852321,55669657,3233569,9829782,97379790,5640302,43152977,82339363,74137926,81805959,27041967,54517921,13470059,44627776,15015906,24915585,7182642,57240218,45381876,76825057,10309525,30811010,21397057,27187213,91141395,89499542,33249630,59501487,168541,51590803,99690194,75229462,98653983,99729357,68939068,36468541,69255765,62762857,90004325,75052463,44060493,33431960,26664538,1239555,39986008,47298834,24826575,44847298,66663942,2331773,4091162,31161687,59981773,4978543,51426311,69136837,93566986,5970607,8913721,89217461,47708850,17857111,57961282,83133790,22435353,65047700,9860968,4204661,21673260,40781449,9259676,2891150,58224549,72793444,18504197,48893685,45617087,21289531,28787861,77300457,67124282,39201414,73109997,61960400,47887470,44481640,92787493,53393358,22113133,79322415,61741594,50007421,62552524,94360702,3880712,4434662,82327024,84002370,80251430,49597667,11757872,82651278,59614746,40781854,16380211,29401781,8634541,41380093,66319530,12416768,32058615,75777973,51715482,90654836,81774825,65880522,44846932,67281495,24953498,36845587,7300047,47213183,14093520,39847321,36396314,97281847,45794415,62490109,96420429,56955985,72614359,55615885,22721500,94516935,4985896,69848388,36135,38256849,32699744,38009615,8055981,24314885,43933006,92530431,68102437,69605283,34667286,29635537,47361209,8791066,7066775,57359924,26776922,22766820,99333375,14363867,12664567,24168411,14349098,55850790,70420215,59405277,37224844,12571310,10453030,44473167,4508700,86543538,65454636,21001913,60393039,50567636,13862149,16405341,321665,10264691,58208470,77694700,20681921,20836893,33935899,70782102,67513640,65081429,83533741,82178706,32274392,68110330,83269727,13819358,76330843,94595668,30998561,61712234,71522968,37280276,6111563,45049308,6157724,83747892,29516592,90158785 +37183543,86022504,33237508,65271999,1431742,31904591,56515456,81853704,53888755,67030811,66611759,23194618,63967300,29994197,13468268,8696647,53547802,73222868,4099191,83210802,13173644,15535065,67451935,68816413,37501808,23110625,6808825,247198,36780454,40686254,3233084,17365188,37166608,34493392,61263068,70036438,68204242,9599614,98462867,65851721,72777973,92554120,33553959,57241521,94911072,92692283,93053405,112651,69355476,77072625,30218878,69697787,6505939,66663942,54014062,41380093,98371444,96193415,65275241,7300047,26998766,32590267,49328608,12348118,81274566,42782652,72278539,55189057,30764367,12571310,65338021,77284619,95395112,11161731,82979980,78909561,62452034,45407418,99658235,93562257,19101477,83533741,93933709,81898046,96726697,99125126,29029316,89214616,90457870,91802888,89811711,57803235,66885828,2891150,51588015,70800879,31491938,38061439,39847321,22129328,7955293,57961282,3880712,83789391,33895336,61859581,39171895,87160386,55143724,73617245,63372756,44889423,66137019,56461322,9829782,33123618,47090124,38645117,95290172,66116458,66045587,24591705,43933006,25636669,61859143,93515664,37659250,68644627,84349107,70727211,88653118,29819940,30569392,23432750,45919976,39986008,83133790,23569917,43322743,39373729,15902805,9058407,53802686,1022677,59197747,61815223,73031054,16445503,64087743,36396314,72725103,43376279,19939935,33431960,99690194,61141874,29834512,36685023,68875490,43501211,89699445,54762643,10597197,10366309,19891772,54987042,6793819,69641513,44842615,62430984,41481685,65715134,14093520,44550764,9603598,59614746,49641577,14731700,84127901,45617087,44983451,9259676,4064751,45237957,37620363,5073754,50668599,84904436,61623915,83651211,20002147,17068582,55753905,97281847,14326617,8651647,92803766,71083565,66322959,99729357,65038678,34432810,77898274,62693428,45995325,92530431,61897582,58224549,45794415,53842979,93359396,53084256,61728685,91831487,15148031,17058722,29466406,45990383,76170907,9623492,72373496,42073124,8807940,93057697,97940276,56473732,16380211,9188443,69786756,66832478,78218845,75135448,73786944,63015256,95726235,84840549,7104732,81677380,51472275,2917920,36930650,90061527,40677414,74614639,2204165,76434144,64602895,85116755,60393039,51507425,31727379,42307449,53648154,79657802,17764950,74743862,99226875,13231279,44664587,32161669,16019925,60581278,90654836,12416768,37739481,7502255,89046466,68041839,36580610,77413012,78561158,669105,96709982,72793444,1204161,8129978,63628376,7229550,38658347,31161687,6521313,34295794,8791066,64157906,71333116,36753250,22108665,34497327,7011964,29635537,86001008,29188588,59318837,63152504,21070110,18001617,16567550,21533347,35456853,72738685,17957593,18783702,69255765,65017137,98948034,15064655,41092102,45306070,55960386,88047921,96735716,82339363,16099750,415901,95581843,45075471,36933038,92787493,4668450,55602660,874791,79821897,96318094,52261574,99549373,52613508,37675718,49271185,40781449,50188404,97561745,99604946,87720882,36468541,59910624,37957788,10264691,91255408,36808486,17539625,33304202,62803858,54058788,74137926,67793644,4069912,65047700,61928316,38494874,22942635,3509435,1788101,77312810,97641116,7066775,66903004,99965001,26114953,4095116,42692881,59177718,65454636,40197395,749283,52293995,72274002,94076128,89637706,35092039,7182642,83368048,92033260,80555751,24733232,81805959,6221471,45428665,55770687,20486294,90090964,79942022,30653863,63059024,60430369,33628349,47738185,73235980,56424103,71469330,44784505,48673079,40152546,57240218,11365791,16387575,92998591,71920426,3633375,569864,93270084,88904910,30366150,90004325,45790169,26664538,3487592,83302115,30543215,66271566,74110882,93566986,13348726,18806856,63506281,21919959,70782102,94090109,75229462,48088883,77300457,38008118,62762857,33797252,7919588,14045383,49236559,61271144,74724075,20681921,168541,92604458,73124510,34667286,29932657,52060076,99917599,84187166,16097038,92215320,71657078,62051033,33336148,78884452,19457589,27665211,88698958,38341669,78602717,6497830,24619760,8128637,5267545,26493119,28796059,9886593,88130087,95010552,75153252,82178706,26292919,98130363,67031644,89419466,19272365,66250369,99971982,51464002,50702367,81855445,55247455,91990218,32250045,21993752,80014588,62936963,43152977,94539824,36505482,76540481,16684829,80251430,50007421,6153406,7423788,36312813,14723410,99515901,62115552,28928175,79922758,61982238,8099994,20867149,52204879,44481640,5482538,51047803,321665,61741594,18699206,7646095,26392416,59501487,16971929,72357096,29065964,20885148,44348328,17016156,28550822,18663507,64848072,4787945,30771409,62232211,6525948,38365584,35996293,26734892,33249630,3235882,26863229,82886381,55669657,62496012,77620120,20642888,22450468,32159704,60955663,57658654,34044787,3773993,37560164,22879907,22766820,97783876,10309525,6157724,67227442,40356877,73168565,71522968,78589145,54232247,72019362,62357986,10358899,48260151,86118021,26776922,79136082,4199704,25842078,44245960,67281495,1250437,40027975,44060493,15163258,78766358,96852321,72732205,22405120,3088684,92353856,79806380,4985896,8729146,97379790,81774825,88444207,57393458,82726008,87710366,40534591,69848388,48360198,5822202,57163802,67474219,99524975,81172706,54199160,72495719,50567636,14947650,9257405,68316156,66428795,26063929,37435892,86798033,50806615,43357947,77187825,93790285,24168411,98648327,68000591,96906410,7685448,14349098,27625735,54606384,70004753,72238278,42199455,76315420,18466635,20568363,54663246,6986898,55850790,83083131,72614359,55615885,34236719,51149804,78785507,90310261,46870723,83150534,95957797,28851716,28734791,508198,74441448,26275734,59329511,824872,8373289,40984766,18411915,97011160,70541760,1375023,91664334,36812683,43506672,45667668,12024238,9860195,44844121,85117092,51466049,18131876,34698463,61960400,74357852,19486173,69136837,4508700,48395186,45996863,32699744,49597667,64055960,85711894,27411561,56531125,85023028,62740044,15536795,99333375,15075176,1197320,24058273,56955985,92541302,86242799,15015906,83155442,59109400,27187213,26397786,45848907,57248122,54517921,70420215,30163921,40781854,12303248,16424199,79191827,97057187,37280276,98943869,69605283,48675329,42947632,20122224,17727650,22721500,27041967,59405277,89217461,71300104,13470059,98739783,45381876,43045786,58208470,92398073,22435353,68702632,34946859,92692978,5970607,76671482,17894977,76825057,41442762,88251446,44473167,17857111,18833224,57359924,71316369,34698428,10453030,32058615,5111370,33201905,85242963,26744917,44627776,36135,99297164,99861373,91281584,29401781,4119747,86301513,77787724,77694700,39801351,22200378,42237907,21001913,46851987,41245325,20140249,42644903,4806458,29510992,91938191,30787683,54427233,75407347,8634541,77272628,9575954,68102437,8115266,16595436,66667729,30395570,36845587,22997281,86821229,73392814,59371804,17385531,85571389,16054533,79322415,526217,70596786,79738755,38009615,62552524,24826575,62490109,10961421,3233569,76960303,65880522,38022834,48658605,70372191,32426752,53632373,23848565,12664567,19376156,35192533,14220886,1936762,83269727,30139692,77301523,90272749,44846932,92071990,4798568,82427263,47361209,65074535,23740167,80246713,18504197,8913721,11923835,46540998,176257,95251277,30463802,91727510,51715482,41092172,72358170,66319530,1569515,75820087,21289531,64098930,94360702,70367851,11543098,76703609,60102965,78549759,99021067,42967683,24314885,58180653,38510840,54868730,82651278,26139110,58749,28787861,24915585,38256849,84166196,47298834,69579137,5832946,75777973,7517032,44915235,89078848,75986488,33699435,77377183,17037369,99224392,20765474,48893685,80316608,9175338,53393358,55470718,40865610,44177011,74452589,29959549,47708850,54263880,75543508,37192445,14626618,76330843,31126490,9398733,4091162,7687278,84406788,24953498,49882705,89499542,27325428,44479073,26507214,62428472,90158785,15948937,89804152,31733363,4204661,68939068,56484900,94711842,39553046,37891451,22113133,48774913,82327024,53666583,32274392,84684495,51590803,98653983,47792865,36139942,51426311,57020481,68110330,30998561,1239555,79880247,2607799,68824981,86543538,6038457,10649306,26102057,33061250,13862149,78300864,47213183,39201414,6819644,2208785,32151165,14363867,33565483,58751351,92867155,67124282,13819358,73109997,67084351,84293052,96420429,8055981,65081429,47893286,68128438,20645197,16405341,35512853,2717150,89996536,94595668,3294781,61373987,59981773,17081350,5640302,9860968,87598888,48892201,82779622,82052050,90013093,83747892,30090481,4515343,60176618,82897371,68694897,6111563,36189527,85695762,19898053,11581181,30811010,23134715,33935899,51315460,27185644,21673260,94516935,56153345,37224844,17146629,49598724,20836893,8733068,2331773,47887470,6871053,21397057,91141395,44847298,71965942,60106217,4434662,73021291,30694952,67513640,82532312,91240048,3183975,29516592,61712234,83948335,84002370,75052463,17976208,11757872,17386996,4978543,75128745,30891921,91957544,86460488,45049308 +26392416,45381876,36780454,71920426,16971929,36505482,60393039,508198,98130363,55753905,39171895,60102965,73031054,22721500,55143724,24733232,19939935,98739783,5822202,669105,77620120,6871053,41481685,81805959,18833224,49328608,16097038,75543508,49641577,33553959,90013093,10358899,30395570,58180653,24591705,99604946,37620363,37501808,33201905,69605283,82979980,13231279,32161669,57359924,56515456,51464002,27665211,31126490,77284619,2717150,4099191,26493119,15075176,65038678,67281495,65715134,74110882,18663507,29029316,9175338,51047803,74724075,96735716,73235980,93562257,44889423,34295794,96726697,95290172,569864,55470718,37659250,53666583,27325428,71333116,39847321,83210802,68875490,62693428,44842615,73392814,5832946,67227442,81855445,30764367,82726008,54014062,96193415,43376279,3773993,35092039,75820087,75135448,88444207,98462867,59329511,33304202,64848072,83789391,42782652,68939068,4095116,95726235,26734892,3233084,35996293,94090109,73786944,79657802,61859143,30653863,54427233,89217461,31904591,93270084,12416768,68694897,8696647,29834512,95581843,77312810,28851716,11161731,23848565,55669657,20486294,48774913,49271185,39201414,83302115,51466049,3633375,92398073,5111370,41442762,17068582,16019925,16445503,56955985,84166196,77694700,29466406,26863229,28928175,54517921,57248122,81677380,30787683,93053405,13470059,70782102,98948034,14349098,68702632,29959549,74137926,65851721,63967300,10366309,87598888,44479073,321665,2208785,31161687,9623492,247198,72238278,22997281,36312813,62496012,99965001,36580610,4064751,54762643,50668599,52060076,61373987,99524975,14626618,61271144,55602660,59981773,60176618,38061439,32159704,7502255,91664334,65074535,62936963,4806458,37192445,16099750,33565483,66045587,91831487,59910624,72777973,9886593,44473167,22942635,61712234,2607799,19101477,23432750,33628349,37739481,75986488,63059024,74743862,53802686,21673260,59197747,53632373,30463802,59109400,824872,36812683,95395112,92033260,9860968,17146629,84684495,30163921,6153406,82886381,87160386,8651647,12348118,40781449,40984766,68824981,64087743,46870723,22108665,70004753,72358170,68041839,54058788,70036438,69136837,30569392,38022834,66832478,93566986,98943869,37675718,6986898,22435353,36930650,99125126,89499542,22405120,6808825,42692881,90457870,35192533,62740044,47361209,45237957,15948937,112651,97011160,43933006,92692978,67474219,45617087,85116755,75153252,54232247,7300047,62430984,9398733,3487592,92692283,13819358,38494874,45996863,52293995,56531125,26998766,67084351,15535065,30694952,66250369,57241521,1022677,50188404,8115266,67513640,43501211,57240218,8807940,70727211,24314885,91281584,17016156,55960386,29188588,9058407,67793644,84002370,62115552,17957593,91802888,73124510,77187825,53888755,53393358,22879907,68644627,29994197,8055981,16380211,56153345,82178706,39553046,69848388,89811711,45407418,78561158,68204242,2331773,20122224,40197395,78602717,79322415,28550822,5482538,526217,55247455,61982238,6521313,40686254,77898274,66428795,8733068,49236559,28734791,76540481,66663942,43322743,36753250,65017137,86460488,72793444,17081350,86242799,24058273,8128637,45995325,29932657,34698428,47887470,63152504,17058722,30771409,22113133,2204165,83150534,39986008,85711894,37435892,14363867,31733363,42644903,88047921,44348328,71657078,89214616,76825057,65880522,61623915,48675329,749283,85242963,72373496,42199455,48360198,24826575,51507425,10309525,90158785,76703609,61728685,25842078,8129978,30139692,27041967,77300457,82897371,6038457,30998561,33431960,27625735,92353856,74357852,26063929,7011964,34698463,4119747,6525948,32274392,58751351,30218878,93933709,13468268,61141874,48260151,90272749,59371804,9599614,47090124,21993752,23134715,16054533,1569515,84904436,30090481,80246713,75229462,70367851,3294781,33797252,44177011,76960303,57803235,21001913,99297164,78766358,29510992,51426311,9829782,78884452,65275241,17764950,5970607,7229550,56424103,67451935,62803858,92215320,44245960,69697787,81172706,41380093,22450468,69255765,40677414,25636669,64055960,87720882,99226875,92554120,92867155,26776922,44784505,48673079,9860195,53648154,42967683,49597667,83651211,51149804,168541,16405341,82427263,45306070,51315460,61263068,33061250,99333375,89699445,32699744,4787945,97561745,45848907,37891451,73617245,2917920,17976208,31491938,98371444,33249630,7646095,54199160,87710366,91957544,66319530,34236719,18504197,20568363,1788101,20836893,62232211,1197320,72738685,17727650,77787724,8373289,14093520,47738185,71083565,93790285,4199704,64098930,44846932,75052463,89419466,65081429,89637706,50702367,3235882,14947650,7066775,86022504,88904910,3880712,23569917,33895336,7685448,47213183,47792865,10597197,77272628,58208470,78549759,76330843,69579137,59318837,47708850,24619760,20140249,66116458,66903004,86821229,72357096,12571310,14220886,51588015,45428665,8791066,8729146,72278539,26292919,44060493,92541302,19376156,74452589,6819644,83368048,62051033,85023028,18699206,60955663,36845587,34497327,94516935,7423788,6505939,94595668,57393458,61928316,75128745,19898053,72019362,86798033,86001008,71316369,51472275,84406788,99861373,89078848,80316608,66667729,30811010,67030811,99224392,45990383,83083131,63015256,9575954,29819940,40865610,62552524,65454636,50806615,63372756,30543215,44481640,39373729,77072625,99549373,99658235,40027975,63628376,72614359,33237508,38341669,53842979,54606384,17386996,9188443,89804152,61815223,41092102,18783702,98653983,39801351,18466635,46540998,3509435,61741594,64602895,54663246,62452034,13173644,26102057,50567636,79136082,20645197,40152546,57020481,91727510,63506281,38008118,16424199,32426752,86118021,66322959,4668450,85695762,70596786,88653118,91240048,78909561,4069912,21070110,18411915,36189527,86543538,67031644,29065964,19457589,47298834,44627776,65271999,57163802,80014588,18806856,29635537,76315420,66885828,69355476,42237907,38658347,38365584,10264691,14326617,88251446,15536795,18131876,70420215,61859581,23194618,90090964,52613508,32058615,57961282,83155442,98648327,66137019,2891150,9259676,48893685,97940276,71300104,17894977,1936762,26507214,84187166,81853704,94911072,38009615,51590803,7919588,93515664,68128438,36135,40356877,415901,53547802,17857111,68816413,78785507,93057697,60430369,66611759,70372191,43357947,1204161,94360702,88130087,26139110,60581278,71522968,6793819,57658654,34493392,48088883,24168411,44847298,53084256,83269727,32250045,59177718,5640302,44844121,15015906,5073754,44983451,20867149,30891921,82779622,55189057,72274002,70800879,36808486,20885148,96420429,27185644,56484900,72495719,73168565,93359396,34432810,97281847,79191827,26275734,16595436,54987042,32151165,73222868,40534591,45919976,90310261,8913721,7687278,37957788,21533347,3233569,34044787,14723410,33935899,83948335,49882705,49598724,89996536,76434144,81898046,45667668,82532312,68000591,79922758,78589145,79738755,1250437,91938191,94539824,38645117,16684829,33123618,4985896,12664567,37560164,45794415,94076128,55850790,66271566,37183543,32590267,92604458,71469330,68102437,7955293,26114953,96852321,79821897,61960400,41092172,61897582,6497830,45075471,9257405,41245325,52204879,15163258,75407347,44550764,65338021,92071990,58749,24915585,77413012,7182642,77377183,81274566,59501487,10961421,1239555,92803766,79942022,80251430,85571389,44664587,6111563,40781854,45790169,15148031,35456853,27187213,82339363,43152977,17365188,92787493,19891772,4204661,7104732,43506672,20681921,17037369,11923835,71965942,75777973,34667286,95251277,79880247,99917599,91990218,26664538,176257,69641513,76170907,43045786,18001617,46851987,24953498,7517032,64157906,58224549,91255408,92998591,1375023,38256849,35512853,3088684,76671482,30366150,70541760,62762857,22129328,22200378,1431742,11365791,80555751,14731700,48658605,28796059,51715482,42073124,79806380,78300864,4508700,82052050,44915235,33699435,97783876,88698958,83133790,99971982,19486173,54263880,72725103,8099994,82327024,90004325,99729357,38510840,4091162,37280276,62428472,4515343,56473732,97379790,13862149,74614639,78218845,13348726,11757872,83533741,99515901,12303248,54868730,874791,42307449,73109997,84127901,29401781,48892201,10453030,99021067,59614746,28787861,60106217,6157724,22766820,84293052,99690194,8634541,50007421,86301513,92530431,59405277,52261574,29516592,3183975,4978543,11543098,96906410,23740167,89046466,23110625,4434662,20002147,96318094,55770687,37166608,82651278,17385531,33336148,15064655,62357986,14045383,74441448,96709982,16387575,84349107,26744917,11581181,84840549,72732205,97057187,73021291,10649306,97641116,94711842,90654836,31727379,9603598,68316156,69786756,17539625,5267545,19272365,67124282,36685023,36468541,36139942,65047700,21397057,36933038,20765474,42947632,95957797,62490109,56461322,21919959,47893286,20642888,6221471,12024238,26397786,4798568,95010552,91141395,48395186,27411561,90061527,77301523,21289531,85117092,16567550,55615885,15902805,36396314,81774825,37224844,34946859,68110330,45049308,83747892 +73168565,99549373,64157906,76170907,55753905,53084256,49641577,247198,34295794,9623492,38494874,51472275,25842078,29029316,99125126,54058788,59197747,2204165,31161687,74357852,92692283,83210802,81677380,87710366,37183543,41092102,92033260,14093520,63628376,415901,86543538,12416768,29959549,3183975,62232211,168541,57961282,95395112,10309525,41481685,31733363,83789391,669105,52261574,85116755,18833224,51715482,47708850,99333375,86022504,66319530,20486294,66250369,33237508,96193415,4119747,35996293,99224392,50567636,38061439,76315420,66322959,4806458,22108665,69355476,12571310,83533741,65454636,9259676,71920426,74743862,55602660,86301513,67451935,99604946,61263068,61741594,29188588,66885828,35192533,68939068,20642888,78909561,34236719,85695762,69579137,22450468,82726008,68694897,10358899,40781854,51464002,50188404,77284619,1197320,44060493,18411915,45407418,36753250,5970607,92353856,30218878,16595436,23848565,60430369,59501487,71333116,45996863,19898053,9603598,7300047,81898046,9058407,15075176,83150534,1204161,81853704,48360198,68110330,87598888,82651278,59371804,62430984,21993752,3233084,66428795,57658654,67793644,60176618,7104732,96735716,77187825,16097038,4204661,55143724,3235882,61859581,39986008,61373987,55247455,6221471,37435892,67031644,65074535,31491938,66116458,7229550,33304202,6808825,89811711,62936963,24591705,19939935,39201414,9886593,36312813,29994197,73235980,91664334,87160386,36933038,61271144,95290172,76703609,45667668,22997281,70596786,30998561,54199160,20140249,28851716,70800879,6038457,62496012,5832946,44983451,9188443,65715134,53547802,36808486,7011964,77620120,99965001,82339363,18783702,32590267,78766358,68875490,112651,53632373,14723410,53648154,73031054,85023028,70036438,38645117,35092039,4668450,59109400,34698428,73786944,36135,78218845,93515664,61859143,15015906,55669657,26063929,68824981,20867149,54427233,55189057,824872,40686254,29834512,75153252,65271999,66611759,5822202,3633375,77300457,26102057,30764367,53802686,10597197,98462867,16380211,44889423,91802888,62452034,44550764,42967683,20765474,26275734,93270084,51047803,37620363,20122224,71316369,65880522,81274566,76671482,60955663,66667729,90654836,73392814,54762643,98739783,19272365,95726235,12348118,10453030,78589145,33201905,526217,68316156,54868730,80555751,10961421,71657078,92998591,56153345,80246713,56531125,74137926,84002370,58180653,64848072,68816413,40197395,84349107,92692978,93053405,51588015,6793819,62357986,23110625,56424103,30139692,55850790,61728685,88251446,54663246,83651211,96726697,57241521,71083565,36780454,84684495,10366309,45794415,6111563,33935899,68644627,13470059,92554120,48774913,90004325,65047700,20681921,32151165,49597667,89214616,44177011,72373496,54606384,34667286,60102965,79821897,78785507,82897371,19891772,10264691,83083131,6497830,71522968,72777973,62428472,38008118,7687278,2208785,30395570,59981773,36812683,7955293,8129978,43357947,64055960,51590803,8733068,93057697,62051033,17068582,4508700,22129328,45237957,99658235,24619760,48673079,68102437,84406788,99226875,74452589,91990218,14363867,15902805,47090124,44479073,76960303,8696647,43322743,63015256,90457870,45617087,72614359,40984766,45790169,92398073,569864,6819644,92604458,94090109,98648327,2917920,73617245,96709982,11543098,45075471,73109997,89499542,51507425,62803858,31904591,2891150,17727650,75128745,17857111,17081350,82979980,72278539,92215320,9829782,30569392,3294781,84187166,57803235,65038678,6153406,1250437,26664538,77072625,63967300,1936762,54987042,72732205,41092172,27325428,73222868,46870723,97561745,4787945,19486173,32426752,14349098,91831487,24826575,44481640,58208470,4064751,30787683,17365188,89996536,57393458,43045786,9175338,40781449,29819940,20836893,17385531,90013093,11161731,6525948,47213183,31126490,68000591,27041967,72738685,74724075,89804152,13231279,15148031,8651647,92541302,36468541,85242963,7502255,50702367,66903004,3487592,14626618,56461322,39847321,57020481,79880247,57248122,54263880,70541760,33249630,74110882,47361209,30366150,46540998,26776922,1022677,75543508,70727211,45848907,13862149,56955985,5482538,66137019,9860195,20002147,33431960,57359924,94076128,93933709,19101477,90090964,33628349,53842979,508198,47893286,82779622,58749,50806615,67474219,15064655,23740167,14326617,61712234,24915585,99690194,874791,65338021,32159704,26734892,79806380,5640302,26392416,42073124,33061250,17386996,77377183,23194618,33797252,95010552,63152504,37560164,10649306,70004753,67227442,66832478,5073754,37739481,29065964,3233569,50007421,61982238,72019362,4798568,74441448,6521313,64602895,7919588,69697787,65851721,44844121,55960386,17764950,69786756,99297164,3509435,44784505,17058722,99524975,42644903,98130363,17539625,36505482,17957593,59177718,49882705,61928316,22435353,77312810,90272749,34044787,48893685,48088883,33336148,4069912,50668599,1431742,85117092,84166196,75229462,9398733,78884452,39553046,70372191,2717150,79738755,97641116,33699435,79136082,37957788,24058273,29510992,88904910,53888755,96318094,45381876,9575954,72725103,4099191,21919959,37501808,49271185,8373289,26493119,90061527,27411561,40152546,30771409,57240218,88130087,48395186,59329511,71469330,13468268,62693428,61141874,4095116,12024238,38022834,76330843,40356877,5267545,86118021,22721500,83155442,99861373,88653118,26292919,16387575,30653863,91141395,8055981,4978543,94516935,27185644,33565483,14731700,29635537,67030811,44627776,88444207,29401781,45306070,26744917,6986898,8099994,97783876,60581278,75135448,73124510,27665211,67281495,68204242,26998766,31727379,8807940,26507214,30543215,46851987,68041839,8128637,19376156,83302115,16971929,39801351,44915235,78300864,3773993,69641513,79191827,86821229,72793444,43376279,11923835,72495719,63372756,20885148,43152977,24168411,53666583,82327024,78602717,18663507,66271566,20568363,72238278,98948034,59614746,78549759,7066775,77413012,96852321,28734791,38365584,42237907,16424199,36396314,56515456,44842615,48892201,95957797,55470718,20645197,28550822,38658347,47887470,83269727,21289531,89637706,67513640,36845587,77694700,72274002,66045587,44473167,77272628,72357096,86242799,76540481,44348328,69255765,4515343,2607799,44245960,86460488,63059024,77301523,34493392,35456853,70420215,41380093,55770687,39373729,81855445,76434144,66663942,44847298,51315460,53393358,22942635,84127901,4434662,22405120,16445503,92787493,12303248,1239555,97379790,80014588,61815223,94911072,91938191,36580610,15163258,47738185,14947650,14220886,22879907,45428665,42782652,49328608,43933006,37192445,97940276,21001913,17976208,40677414,72358170,64087743,81805959,75407347,44846932,89217461,76825057,75820087,16099750,62740044,7423788,18699206,24314885,321665,17894977,82178706,59318837,32250045,45919976,95581843,68128438,34497327,98371444,94360702,18504197,28928175,61623915,4199704,91240048,7646095,45990383,15535065,98653983,23134715,54232247,52060076,34946859,38009615,65081429,69605283,89419466,65017137,47792865,57163802,73021291,9257405,7182642,85571389,33895336,92071990,51466049,79942022,27625735,16019925,91255408,4091162,23569917,69848388,79657802,26863229,8634541,43501211,33123618,15536795,88047921,94539824,18806856,29466406,99971982,82886381,29932657,67124282,8729146,70367851,19457589,8913721,40027975,22113133,17146629,71300104,75777973,49236559,93790285,62115552,12664567,81774825,37891451,42692881,30463802,82532312,48675329,21673260,16684829,79922758,59910624,41442762,17037369,16567550,93562257,38256849,6505939,99917599,92803766,9860968,29516592,3088684,49598724,90310261,32274392,91957544,32161669,6871053,81172706,22766820,22200378,47298834,85711894,84904436,8115266,37659250,40865610,60106217,89078848,36189527,40534591,18131876,84293052,64098930,15948937,88698958,13348726,30891921,26139110,1375023,87720882,8791066,97057187,14045383,43506672,26114953,37675718,83948335,54517921,67084351,21533347,75986488,89046466,749283,3880712,78561158,33553959,74614639,52204879,61960400,42307449,65275241,75052463,51149804,69136837,25636669,82427263,93566986,91727510,39171895,92867155,32699744,99515901,1569515,4985896,68702632,90158785,91281584,60393039,32058615,6157724,36685023,23432750,37224844,13173644,17016156,52293995,97281847,18001617,54014062,48658605,82052050,42199455,13819358,5111370,24733232,44664587,96906410,36930650,7685448,79322415,97011160,80251430,28787861,51426311,94711842,35512853,63506281,42947632,98943869,26397786,30811010,30090481,7517032,56473732,95251277,30694952,83368048,89699445,37166608,56484900,41245325,52613508,16054533,11757872,86001008,30163921,21070110,92530431,86798033,45049308,18466635,28796059,58224549,77898274,59405277,83747892,62490109,48260151,62552524,45995325,62762857,58751351,1788101,84840549,80316608,21397057,99021067,77787724,34698463,11365791,55615885,83133790,11581181,71965942,38341669,96420429,94595668,24953498,34432810,176257,93359396,2331773,27187213,37280276,16405341,70782102,9599614,61897582,99729357,36139942,38510840 +168541,6221471,69641513,20002147,10309525,44481640,96193415,30366150,66319530,33797252,18806856,92554120,62936963,14093520,6793819,66667729,10358899,19486173,48892201,48675329,45407418,57393458,84002370,70372191,53648154,37620363,95957797,40534591,1431742,60430369,48360198,24058273,17365188,51047803,15015906,85116755,1022677,45996863,44784505,15535065,68644627,22997281,72373496,78589145,97783876,79191827,96906410,90654836,44844121,86301513,31161687,2204165,77377183,57240218,66250369,99125126,4668450,874791,38061439,66045587,5267545,3880712,53842979,73124510,82726008,60102965,21070110,31733363,3294781,75543508,3088684,91990218,30218878,34236719,78218845,6808825,99549373,53084256,44060493,84127901,16380211,46540998,34497327,9398733,20642888,415901,7300047,17539625,77272628,5970607,96735716,35192533,38494874,20765474,60581278,39553046,94090109,73617245,75135448,61741594,57803235,47887470,35092039,62452034,56531125,16099750,72019362,78766358,37183543,19376156,526217,34946859,11923835,4064751,55770687,824872,57961282,29029316,62740044,16097038,508198,55470718,61712234,99604946,55189057,38365584,26664538,53632373,43152977,51590803,76671482,3633375,62051033,79136082,11581181,26102057,86022504,73222868,38645117,73392814,92353856,27665211,71083565,4434662,7229550,72793444,38008118,36312813,31126490,2208785,69355476,99658235,68204242,59501487,56955985,51466049,80246713,33336148,88047921,92604458,65338021,62490109,4515343,40984766,93933709,20486294,93270084,87720882,94516935,47361209,11365791,52204879,84684495,72278539,71300104,81853704,48893685,569864,21001913,71333116,83083131,26392416,70004753,90013093,34698428,98653983,62428472,83150534,50806615,95010552,67281495,12571310,33699435,41481685,61263068,19101477,24826575,7955293,26998766,92215320,40027975,9623492,68000591,27411561,39201414,84406788,19457589,112651,77413012,73168565,58749,29188588,2917920,33123618,89214616,17727650,1250437,42237907,48088883,72274002,82651278,36580610,96318094,66322959,4099191,44550764,6111563,72358170,3509435,47738185,14220886,91831487,39847321,90272749,79922758,76330843,3233569,51426311,47090124,62430984,32151165,63967300,67793644,17764950,19939935,10366309,42967683,98371444,28796059,36468541,59371804,5111370,60106217,99297164,32590267,8129978,73786944,30764367,78602717,36189527,45617087,33628349,9188443,9603598,59109400,16684829,90457870,65081429,99524975,26114953,86543538,79806380,38658347,50188404,16567550,78909561,66116458,71522968,80316608,83210802,84349107,56424103,44245960,22113133,30771409,55753905,63152504,26275734,34432810,33249630,83368048,83533741,85117092,34044787,96709982,13348726,86460488,27325428,12303248,13231279,61982238,32159704,22721500,20645197,82339363,17957593,44177011,92787493,8128637,22942635,10597197,54606384,15064655,37435892,91664334,37891451,7687278,74724075,9886593,88653118,57241521,7919588,91802888,96726697,5073754,1936762,14731700,83269727,22450468,11161731,96420429,68041839,63059024,70727211,55247455,34698463,84840549,51472275,83651211,5832946,34295794,49598724,10453030,54263880,98462867,33237508,97561745,21919959,44479073,3233084,65271999,32426752,37192445,71316369,76540481,75229462,30653863,36135,80555751,37166608,57658654,52261574,15148031,29065964,13173644,2607799,77787724,39373729,4199704,63015256,39801351,59329511,6153406,72732205,30139692,3235882,10264691,45990383,39986008,24915585,70367851,33935899,669105,31491938,72357096,36753250,8696647,7104732,22435353,34667286,33304202,8733068,30998561,45075471,30787683,35996293,98648327,99917599,15075176,54199160,26507214,97011160,29834512,80014588,29959549,14723410,68316156,89811711,94711842,46851987,98130363,36505482,87598888,99690194,61373987,15536795,4798568,41092102,61141874,29635537,21673260,77694700,91727510,78549759,75153252,79821897,7646095,20681921,57359924,68875490,18833224,62496012,29932657,76434144,50567636,47708850,95395112,99965001,65880522,82427263,70800879,22108665,78785507,36812683,36780454,84187166,52060076,92033260,71657078,93562257,99861373,26744917,40356877,32161669,7011964,53888755,66903004,68128438,65038678,85023028,23569917,74110882,91141395,38009615,24168411,1788101,35456853,64157906,50668599,49328608,45794415,38022834,43501211,90158785,66611759,59177718,9829782,14626618,16054533,87710366,17386996,24619760,28928175,82178706,45919976,93515664,9259676,13862149,92803766,31904591,51715482,40197395,56461322,65454636,31727379,6525948,88444207,2331773,70036438,42199455,50007421,5482538,76703609,29819940,17068582,61897582,40686254,24733232,18663507,21993752,61928316,56484900,81898046,58224549,37957788,99515901,18504197,70596786,77312810,66885828,96852321,10649306,37659250,18783702,57248122,99226875,11543098,7502255,65715134,8099994,38256849,2891150,17857111,8807940,99971982,83155442,54058788,43933006,9257405,16595436,90090964,2717150,41245325,37280276,85242963,26063929,30163921,70420215,42692881,48673079,16445503,89078848,6819644,61623915,82052050,79657802,20122224,63372756,73109997,20568363,92867155,93790285,29994197,93053405,247198,7423788,37739481,36139942,7182642,49271185,98943869,78300864,84293052,30891921,67474219,95290172,94076128,321665,45790169,37675718,23134715,63628376,77187825,75986488,20140249,98739783,59981773,72725103,88251446,20885148,23740167,44889423,64087743,9058407,30569392,88904910,30395570,3773993,92541302,81677380,81774825,3183975,18411915,72614359,61859143,64055960,49597667,43045786,50702367,33565483,89499542,61960400,75407347,83133790,91240048,60955663,55615885,69579137,90310261,749283,49641577,16387575,35512853,70541760,9860195,16424199,68694897,53802686,17037369,94360702,66428795,77284619,21289531,18466635,42644903,76960303,62115552,14947650,90061527,33201905,36685023,44983451,36930650,77072625,69786756,30811010,66663942,28787861,30543215,58208470,4204661,1204161,28550822,4806458,91938191,8791066,46870723,69848388,32250045,83789391,19898053,55960386,13819358,59614746,8913721,12416768,44473167,30463802,74357852,64098930,14045383,80251430,8651647,26776922,44842615,38341669,65074535,89637706,22879907,4119747,32699744,65047700,60393039,4978543,99729357,55602660,66271566,21533347,22405120,88698958,68102437,74452589,76170907,19891772,18699206,18001617,43506672,54232247,79942022,9175338,99333375,22200378,77620120,48395186,20867149,1239555,67084351,5640302,91255408,19272365,36808486,69255765,65851721,40781449,14363867,47298834,41380093,27625735,23194618,51464002,87160386,7685448,42782652,97641116,38510840,24953498,55850790,44847298,8634541,86242799,1197320,8055981,33895336,66832478,72495719,29466406,36396314,34493392,16971929,55143724,59197747,51588015,95251277,72777973,92692978,43357947,33431960,86798033,99224392,61271144,22766820,74743862,42947632,9575954,12024238,17385531,57163802,69697787,22129328,47893286,1375023,64602895,7517032,6038457,59318837,26139110,8115266,75777973,15902805,79738755,75052463,9599614,82327024,8373289,92692283,95581843,29510992,67451935,27185644,12664567,54663246,40865610,37560164,86001008,65017137,81855445,47213183,62762857,6505939,28851716,84904436,91281584,58751351,64848072,44348328,58180653,95726235,32058615,5822202,27187213,63506281,67030811,176257,45428665,27041967,69136837,40152546,45848907,25636669,30090481,78561158,39171895,16019925,81172706,43376279,51507425,76315420,15163258,61728685,67031644,89804152,82897371,73031054,94595668,56515456,45667668,52293995,45381876,82532312,56153345,36933038,6157724,68110330,4787945,79880247,42307449,33061250,86118021,78884452,62357986,91957544,73235980,56473732,52613508,89046466,44915235,67227442,37224844,79322415,92998591,6871053,81805959,14326617,94911072,1569515,45237957,72738685,20836893,82886381,3487592,26397786,67124282,13468268,53547802,81274566,12348118,24314885,55669657,18131876,82979980,26493119,71965942,97379790,24591705,40781854,62232211,83302115,92071990,4985896,68702632,8729146,23110625,40677414,26292919,16405341,17081350,11757872,66137019,54868730,74137926,61859581,49236559,6986898,29516592,89419466,77301523,10961421,69605283,54762643,67513640,9860968,98948034,17976208,4508700,51315460,26863229,45995325,93057697,42073124,17894977,76825057,59910624,60176618,53393358,37501808,97940276,48658605,85571389,75128745,54987042,54427233,68939068,54517921,77300457,26734892,68816413,83747892,25842078,4091162,13470059,45306070,88130087,84166196,7066775,53666583,23432750,97281847,68824981,73021291,82779622,89699445,92530431,71469330,62693428,30694952,61815223,14349098,44627776,43322743,74614639,62552524,75820087,17146629,93566986,41092172,4069912,32274392,28734791,44664587,17016156,6497830,57020481,21397057,23848565,54014062,51149804,86821229,89996536,71920426,6521313,90004325,48260151,99021067,72238278,47792865,33553959,97057187,44846932,92398073,49882705,85711894,93359396,36845587,15948937,62803858,65275241,77898274,48774913,94539824,83948335,45049308,89217461,74441448,59405277,29401781,4095116,41442762,17058722,85695762,70782102 +4099191,37560164,17764950,57803235,38022834,29029316,37620363,48892201,31126490,47090124,65047700,12348118,78785507,95010552,44479073,30694952,60430369,37739481,38658347,92554120,3183975,73617245,62430984,49598724,86022504,70800879,93933709,61982238,98130363,29834512,87710366,83302115,8129978,77272628,77312810,67474219,68204242,75052463,45996863,81274566,51047803,23569917,69355476,56531125,92071990,24733232,55189057,92398073,39171895,22879907,22435353,16099750,35512853,10597197,3509435,29510992,17976208,35996293,40865610,3088684,26998766,4119747,80316608,4515343,8791066,63628376,62428472,73168565,64602895,45407418,80251430,10366309,36753250,17081350,36580610,49882705,73392814,55247455,98371444,2917920,15535065,10309525,95957797,52060076,44915235,94090109,79821897,69136837,5111370,37183543,168541,59109400,83210802,92033260,36808486,67793644,16445503,55753905,76434144,55602660,4064751,42199455,72274002,5267545,4787945,89078848,45075471,22942635,21533347,35192533,16019925,30163921,66137019,62496012,79922758,45237957,65715134,50188404,63152504,43506672,37192445,32250045,7955293,88444207,22997281,38008118,11581181,61141874,52613508,64087743,96709982,91664334,26392416,30395570,16097038,13231279,53632373,2717150,9257405,54987042,5640302,1204161,17727650,35092039,60581278,74357852,8807940,16595436,76960303,92803766,4668450,18131876,1431742,71333116,38061439,96193415,73222868,49641577,22450468,2204165,59318837,36312813,65338021,36780454,60106217,67030811,33201905,44784505,85711894,44177011,70596786,20867149,65271999,82339363,91240048,38494874,17146629,61271144,51472275,27411561,824872,66832478,86821229,41245325,30891921,98739783,53842979,98462867,93057697,99515901,47708850,26507214,17037369,9398733,76671482,20645197,86460488,84002370,44627776,79942022,9575954,12024238,75543508,20765474,70367851,88251446,13173644,78602717,30764367,66250369,62693428,27665211,9860195,29819940,99297164,15015906,14326617,33336148,51590803,40356877,83083131,40152546,93562257,7687278,33304202,14626618,63059024,69605283,29188588,84684495,99965001,34497327,59501487,87720882,83651211,61373987,29466406,247198,21993752,65081429,44844121,48675329,34493392,4806458,34236719,53802686,78909561,73235980,26102057,10649306,30543215,6793819,61741594,92353856,64848072,9175338,62051033,71657078,81172706,99524975,67451935,77187825,9188443,62490109,87598888,80014588,18699206,24915585,67031644,40984766,94711842,66116458,23432750,96735716,38256849,37224844,26863229,81898046,30653863,23194618,53648154,3233084,57163802,44473167,78549759,19486173,31161687,41092102,91281584,44245960,72373496,26664538,91141395,19101477,77284619,57240218,54663246,4434662,47361209,62936963,70036438,66667729,76315420,55770687,66319530,58180653,76170907,56461322,63967300,89811711,45617087,59329511,66428795,5970607,57359924,60955663,14947650,38645117,34295794,92787493,24058273,68041839,6038457,7919588,77620120,21070110,48774913,69697787,88653118,7646095,95581843,17385531,30771409,53547802,21289531,73021291,61263068,24826575,70004753,38365584,61623915,44983451,20486294,55470718,44842615,54762643,56473732,8099994,79880247,40027975,23110625,54199160,74743862,25842078,28928175,30463802,30787683,90272749,33061250,6871053,55669657,96318094,60102965,13348726,90090964,32161669,19376156,53084256,68939068,4069912,27325428,97011160,93515664,42782652,34698463,56515456,79136082,75820087,81677380,84904436,24168411,10358899,75153252,42947632,17386996,30366150,59197747,47298834,97940276,42073124,86001008,88047921,17957593,76703609,16567550,59981773,56153345,89214616,35456853,74441448,26114953,90013093,28851716,569864,22200378,50806615,31904591,62115552,93053405,7517032,50567636,25636669,36505482,52293995,78561158,77377183,52204879,39373729,87160386,63015256,82427263,13862149,69641513,84127901,22721500,27041967,18806856,3633375,1569515,24619760,61928316,508198,20642888,75777973,13468268,79322415,45848907,63506281,85116755,36685023,44846932,68000591,22108665,9623492,66903004,59371804,66271566,92541302,26063929,669105,62740044,99917599,3233569,48893685,42307449,3880712,50668599,33895336,69786756,91990218,874791,28734791,82726008,97561745,68875490,91802888,76330843,1022677,55143724,39847321,5482538,6808825,81855445,49236559,6505939,7011964,72238278,48360198,26493119,96906410,46851987,43322743,92692283,33699435,526217,62452034,33249630,5832946,14220886,51466049,43376279,78300864,29994197,4095116,71920426,71316369,34698428,68816413,84349107,91957544,65880522,75986488,70372191,55960386,20885148,64055960,67124282,3487592,72495719,8651647,72358170,86798033,32590267,91831487,48673079,11543098,1197320,70420215,94516935,82897371,83150534,77787724,47792865,31733363,89046466,38341669,24953498,18504197,33237508,32274392,4199704,6153406,30811010,96726697,20836893,74452589,33628349,26275734,60176618,68644627,83789391,37891451,17068582,89419466,99604946,62232211,68128438,19457589,41092172,8913721,54263880,99658235,30090481,36135,71300104,30569392,14349098,20140249,98653983,56955985,14093520,40781449,66045587,321665,31727379,9058407,54232247,30218878,73124510,65851721,10961421,82651278,36189527,15948937,77072625,48260151,37166608,36812683,1250437,39553046,40686254,18833224,52261574,62357986,51426311,39201414,70727211,37435892,88698958,69848388,95290172,42237907,46540998,41481685,94911072,44664587,67227442,32159704,22405120,44060493,71083565,34432810,83948335,65017137,20002147,23848565,94539824,40197395,72357096,39801351,36139942,71469330,81805959,95726235,79738755,59910624,29635537,99224392,68702632,14731700,80246713,77413012,65275241,18783702,96852321,1936762,78589145,63372756,34946859,90004325,54058788,68102437,42692881,12303248,71965942,112651,98648327,72777973,42644903,94595668,20568363,91727510,67084351,82979980,93270084,79191827,68316156,20122224,58751351,32426752,93359396,4978543,72019362,42967683,89499542,23740167,29932657,65074535,99549373,9886593,21673260,27625735,79657802,78766358,8696647,70541760,73031054,57241521,66322959,36933038,15148031,51464002,61815223,37675718,90457870,40534591,99690194,51588015,37659250,15163258,45995325,64157906,22113133,9860968,84293052,32699744,74110882,58749,26397786,4798568,4985896,92998591,415901,1788101,96420429,11923835,84840549,44481640,6986898,44550764,69579137,57658654,8373289,26139110,16684829,75407347,27185644,14045383,19891772,76540481,97057187,45428665,18411915,89637706,89699445,73786944,43357947,7502255,37501808,43501211,99333375,92604458,97783876,9603598,28550822,6521313,56424103,48088883,92215320,19898053,22129328,61859581,45794415,30139692,4204661,67513640,46870723,15902805,57393458,29516592,66611759,72738685,66885828,28796059,34044787,7104732,17539625,38510840,81853704,83269727,44889423,16971929,6525948,44847298,24591705,59177718,82532312,12664567,82779622,61859143,47213183,72725103,48658605,88904910,18466635,74614639,54517921,749283,77300457,55615885,17365188,30998561,53393358,8729146,98948034,64098930,92692978,29401781,54014062,50702367,33797252,84187166,61728685,60393039,75128745,29065964,23134715,18663507,97281847,15064655,77694700,58208470,62552524,82886381,72793444,36396314,99021067,43045786,16380211,41380093,81774825,3294781,84406788,99125126,58224549,54606384,33431960,99861373,85242963,45990383,86301513,92867155,90158785,1375023,74724075,85571389,7685448,57248122,50007421,43933006,19272365,44348328,15536795,95251277,72278539,32151165,97641116,26734892,98943869,33123618,176257,91938191,61712234,38009615,54427233,1239555,34667286,67281495,75135448,15075176,7300047,49271185,82052050,83133790,74137926,7229550,89804152,93790285,90061527,8115266,6221471,7182642,12571310,85117092,55850790,56484900,72732205,36468541,53666583,2331773,78884452,51507425,89996536,6111563,86242799,11365791,45919976,53888755,80555751,99971982,72614359,88130087,29959549,10453030,75229462,4508700,33565483,73109997,78218845,33935899,84166196,28787861,95395112,18001617,45306070,16424199,8634541,26776922,82327024,4091162,83533741,90654836,86118021,83368048,13470059,41442762,33553959,86543538,9599614,26292919,37957788,51149804,66663942,90310261,49597667,49328608,10264691,45049308,54868730,7066775,51315460,19939935,31491938,51715482,62762857,61960400,82178706,65038678,26744917,47738185,99226875,69255765,47893286,68824981,16054533,91255408,2891150,45381876,12416768,2208785,40677414,79806380,3235882,16387575,77301523,14363867,21919959,3773993,45790169,59614746,8055981,47887470,48395186,22766820,62803858,21001913,36845587,14723410,17058722,61897582,8733068,7423788,17857111,8128637,16405341,83155442,9259676,37280276,94076128,85695762,70782102,11757872,76825057,68694897,6819644,21397057,17016156,9829782,94360702,77898274,85023028,93566986,5822202,20681921,59405277,39986008,99729357,36930650,2607799,6157724,17894977,89217461,40781854,24314885,27187213,65454636,83747892,57961282,45667668,92530431,5073754,57020481,6497830,71522968,43152977,68110330,11161731,32058615,97379790,13819358 +31733363,38494874,31126490,4204661,36505482,57248122,82979980,15075176,49236559,89214616,36812683,74357852,26493119,13470059,92692978,92554120,16595436,38022834,7502255,66903004,27185644,30139692,71333116,70596786,33237508,27325428,86543538,91664334,83210802,65081429,18131876,40865610,6221471,28851716,99965001,63015256,12348118,57359924,40781854,91255408,45237957,18783702,6521313,97783876,90457870,68939068,68816413,6505939,67793644,92692283,93515664,70004753,66832478,65017137,56955985,55753905,508198,874791,40197395,53084256,27665211,23432750,18699206,1022677,59371804,72238278,26063929,29994197,60102965,26863229,57241521,23110625,99524975,94090109,89046466,80246713,85242963,13231279,4064751,17957593,48088883,96193415,19101477,55189057,47361209,76671482,16971929,37560164,54663246,26664538,14093520,415901,79821897,1431742,3633375,99861373,81172706,47893286,26114953,34295794,24591705,82897371,72725103,3773993,88444207,44846932,1375023,61373987,98739783,37957788,99125126,60430369,20002147,50668599,38008118,18833224,95010552,54987042,44060493,22766820,16097038,112651,20765474,26397786,84127901,36780454,46870723,78218845,18001617,37183543,67451935,83155442,84406788,22450468,4069912,37192445,43376279,76703609,247198,68000591,99917599,29029316,43045786,33304202,27625735,37675718,35192533,44983451,3509435,17058722,4806458,62496012,21533347,98943869,79657802,62232211,71300104,5111370,32161669,59109400,44915235,17727650,76434144,45990383,18663507,73235980,6986898,83789391,2717150,41380093,37620363,77284619,20836893,8128637,93359396,98653983,27041967,30163921,26102057,3880712,28734791,95957797,7955293,61859143,89637706,71920426,76825057,14220886,14626618,6793819,84187166,35092039,33201905,42782652,93933709,67124282,39201414,26275734,93566986,56424103,44847298,20122224,22113133,23134715,54199160,569864,66428795,70036438,57240218,59501487,3235882,73031054,68824981,51464002,74724075,7229550,30771409,44842615,74137926,42967683,64055960,28787861,9058407,53842979,99297164,66250369,73222868,15163258,8696647,34236719,17764950,88047921,7423788,7919588,73168565,89419466,92398073,87598888,54868730,29834512,77694700,84904436,95290172,98130363,37739481,92541302,21993752,19457589,55143724,61141874,26998766,3294781,22108665,97281847,9575954,61271144,43357947,33565483,68644627,44245960,1197320,49641577,77620120,39171895,25842078,26392416,749283,59614746,89996536,91957544,88653118,82886381,62452034,72274002,81677380,47090124,10309525,30366150,1204161,33553959,92998591,44473167,50567636,77377183,6525948,20885148,17081350,4515343,8807940,70727211,19272365,69355476,83083131,32250045,64848072,28796059,96735716,28550822,30787683,36580610,14363867,35996293,82339363,34497327,75153252,62740044,60106217,5482538,75229462,22997281,45919976,15015906,28928175,24168411,17976208,80014588,36933038,23848565,47738185,70800879,44177011,80555751,75407347,99729357,90272749,53632373,24058273,93790285,66885828,77300457,48673079,47298834,61859581,66667729,44481640,63628376,74452589,44889423,71657078,38645117,45381876,3233084,52060076,14947650,27411561,71316369,9623492,39801351,77072625,78589145,9603598,40686254,11581181,66045587,3183975,23569917,2208785,65271999,72793444,38341669,78300864,669105,65454636,65851721,61960400,26744917,14349098,6871053,23740167,95726235,33935899,50702367,49597667,7300047,54263880,67084351,16405341,30653863,13348726,45428665,81805959,63059024,66663942,97011160,67030811,32274392,99549373,91240048,29188588,77787724,64602895,51047803,37435892,78602717,34493392,74441448,4091162,4119747,85116755,57803235,2204165,88904910,526217,84349107,56153345,50188404,99604946,55770687,70541760,9398733,9175338,1239555,54058788,45049308,90013093,40984766,92803766,14045383,68875490,29959549,38658347,50806615,78766358,82532312,75543508,33249630,65047700,7646095,8099994,85023028,51507425,75128745,61982238,59910624,99515901,68041839,69697787,14326617,73021291,20642888,86301513,1936762,11161731,79136082,69786756,48360198,51472275,17857111,95251277,70372191,53666583,18806856,45996863,15535065,53888755,9860195,82726008,76960303,34698463,54762643,81853704,48893685,72278539,42237907,29466406,72495719,15148031,79880247,77413012,60581278,45667668,321665,17068582,73786944,94516935,79922758,33797252,90310261,8129978,24733232,90061527,30764367,53802686,30090481,16019925,69641513,68694897,42073124,87160386,16054533,55247455,85571389,14731700,4668450,31904591,68204242,76315420,44784505,48892201,39553046,4434662,92604458,4508700,20140249,41481685,91802888,82651278,37891451,6038457,89699445,69579137,71965942,66319530,19891772,42947632,62936963,72614359,4199704,47708850,32590267,19939935,6808825,93053405,37659250,20867149,33699435,36753250,36845587,94911072,36930650,99224392,83269727,86821229,81274566,78561158,29819940,17539625,86242799,56515456,65880522,63967300,77187825,69605283,66322959,50007421,68128438,42644903,40356877,88130087,42307449,4798568,56531125,29635537,34432810,21673260,30395570,62430984,40152546,96420429,7182642,12024238,53393358,33061250,19486173,83651211,46851987,40027975,53547802,24619760,10961421,55850790,5970607,77312810,45617087,77272628,17385531,2331773,17386996,84002370,82327024,81898046,176257,3088684,83948335,65715134,61263068,8055981,86118021,95581843,44550764,65275241,30543215,96709982,73124510,47792865,49882705,89078848,55669657,32159704,32058615,1569515,36312813,32151165,51715482,2917920,86022504,66611759,75986488,98462867,67281495,49271185,89804152,30218878,73617245,8373289,10366309,98948034,96906410,86460488,10358899,36808486,45848907,78884452,89499542,82052050,70420215,61897582,76170907,62693428,85117092,92071990,69848388,13862149,39373729,11923835,15064655,63152504,44627776,17146629,83368048,41092102,97057187,67474219,61741594,57020481,52261574,71083565,4099191,41092172,88251446,68316156,62762857,16445503,44844121,97561745,68102437,18466635,49598724,5073754,84684495,12416768,80316608,21001913,26507214,85711894,39847321,67513640,63372756,13468268,38061439,65338021,7104732,54427233,168541,26734892,44348328,74110882,72732205,90158785,40534591,30891921,70367851,10597197,99971982,36189527,22435353,17037369,54606384,43933006,6153406,54517921,12664567,75777973,64087743,13819358,89811711,92215320,62357986,54232247,75052463,77898274,74614639,71469330,58208470,88698958,65038678,9860968,1250437,79738755,9188443,4787945,33628349,19376156,83302115,38365584,72738685,61815223,53648154,94711842,43322743,52204879,40781449,46540998,42199455,2607799,37501808,45995325,82779622,45790169,97379790,14723410,56473732,64157906,48260151,8651647,18411915,16387575,6497830,93270084,33431960,36468541,11365791,61928316,8634541,31491938,54014062,94360702,76540481,98371444,91990218,32699744,62803858,23194618,38510840,78549759,99690194,56484900,87710366,51315460,90004325,86798033,39986008,76330843,52613508,29401781,83150534,52293995,21289531,96726697,97940276,51466049,3487592,6111563,7517032,62490109,8913721,43506672,69136837,57393458,9886593,98648327,99658235,59329511,33895336,3233569,27187213,16380211,99333375,82178706,16099750,5267545,37280276,45407418,24915585,74743862,30463802,79942022,84293052,8115266,79191827,92787493,84166196,9259676,93562257,94076128,16567550,58180653,57961282,43501211,44479073,92033260,25636669,63506281,43152977,31727379,48774913,31161687,38256849,90654836,42692881,5822202,72019362,21397057,61728685,26139110,30811010,22129328,8733068,86001008,45794415,91727510,67227442,60955663,55960386,30998561,78785507,11757872,4978543,7011964,96852321,59177718,55602660,72373496,59318837,40677414,66271566,15902805,55470718,48658605,9829782,44664587,91938191,91141395,82427263,51149804,15948937,51588015,2891150,67031644,10264691,22200378,22405120,93057697,73392814,36396314,68110330,7685448,17016156,84840549,70782102,51426311,94595668,35512853,16684829,73109997,92530431,95395112,6819644,57658654,58751351,6157724,11543098,59197747,20681921,69255765,47213183,99226875,96318094,66137019,60176618,22879907,99021067,1788101,75135448,55615885,71522968,16424199,57163802,66116458,20568363,12571310,72358170,33336148,41442762,33123618,20486294,824872,61623915,7066775,24826575,34946859,9257405,9599614,17894977,21070110,8791066,20645197,36685023,34044787,5640302,7687278,72777973,37166608,92867155,62428472,47887470,58224549,34698428,36135,38009615,72357096,58749,49328608,17365188,91831487,75820087,77301523,59405277,21919959,29932657,13173644,15536795,87720882,22942635,83133790,61712234,62051033,29510992,81855445,59981773,94539824,79806380,24314885,62552524,92353856,29516592,65074535,26292919,81774825,85695762,30694952,5832946,80251430,35456853,24953498,79322415,62115552,45306070,34667286,10453030,29065964,32426752,64098930,48675329,22721500,26776922,10649306,48395186,91281584,37224844,68702632,36139942,19898053,45075471,56461322,4095116,12303248,83533741,18504197,8729146,60393039,90090964,83747892,97641116,4985896,78909561,41245325,30569392,89217461,51590803 +55470718,55753905,9886593,83210802,89214616,62496012,84840549,64087743,91664334,76960303,36930650,30139692,23848565,65715134,30366150,27665211,83150534,6153406,72495719,16971929,80246713,29029316,91727510,48893685,3233569,50567636,569864,15535065,70004753,34698428,112651,20002147,415901,61982238,66322959,85116755,71333116,33628349,19376156,11365791,99861373,15075176,78218845,34493392,83155442,78785507,71657078,65275241,44983451,78766358,5111370,31904591,70372191,86001008,33249630,96193415,35192533,54058788,66832478,63372756,26292919,79806380,29819940,88653118,72777973,65038678,14947650,50806615,39847321,72274002,78589145,9623492,26139110,40152546,91938191,22997281,31126490,97783876,18663507,16595436,99549373,49328608,43501211,30998561,19939935,92692978,36780454,4806458,90457870,54606384,45919976,88444207,35092039,32426752,71522968,33123618,87710366,51047803,53842979,29188588,79136082,82726008,28928175,32590267,15163258,5822202,56531125,17539625,83269727,68824981,67793644,78549759,29466406,526217,68875490,73392814,28787861,30891921,66428795,36812683,16054533,4668450,62936963,39171895,92398073,78561158,64098930,13348726,68316156,80555751,8651647,45237957,55770687,61373987,73222868,30653863,28796059,26114953,85023028,76825057,26392416,90013093,34295794,86460488,60106217,88047921,61263068,96726697,14731700,74357852,38645117,45848907,6808825,50668599,10961421,7502255,16099750,89637706,176257,61623915,11161731,45996863,15148031,321665,16445503,72725103,90272749,53393358,37435892,13173644,4508700,7300047,67281495,20486294,8696647,18833224,32250045,15015906,57240218,82052050,27325428,49598724,95581843,46851987,62552524,12348118,40677414,99604946,28550822,42199455,63967300,38365584,69136837,71469330,45306070,32159704,81677380,61141874,30787683,62232211,36505482,40865610,62693428,55247455,1204161,50702367,24058273,73031054,40356877,24619760,77272628,34236719,94595668,71920426,18504197,54427233,48892201,7423788,39201414,47298834,8807940,83302115,45990383,61928316,17764950,4787945,76671482,68702632,42644903,34698463,26734892,65017137,75229462,4064751,62357986,85695762,38494874,74137926,45428665,1375023,24915585,51472275,93057697,77377183,68128438,29834512,86543538,30218878,59371804,874791,83533741,3235882,6793819,16380211,26063929,29065964,77620120,7955293,99515901,1569515,98648327,73124510,44889423,59197747,99917599,26493119,83789391,9599614,38061439,94539824,69641513,29994197,56461322,83368048,60102965,18131876,62452034,3088684,37166608,80316608,66885828,81774825,86821229,45667668,8129978,77694700,20765474,81853704,51149804,69579137,97561745,84904436,21001913,33304202,66271566,57803235,33565483,52613508,94711842,62762857,65454636,44550764,95726235,77300457,17037369,7182642,58224549,8373289,91141395,30395570,1788101,53547802,61859143,19898053,55602660,75543508,99690194,4515343,1431742,59501487,669105,78909561,53666583,58749,68816413,45995325,44481640,9398733,15948937,24733232,8733068,18806856,36753250,66137019,4099191,95395112,92803766,66667729,43045786,51464002,61960400,52261574,6505939,79922758,67031644,27187213,77413012,95010552,39801351,59981773,5832946,57658654,56424103,47090124,73168565,18783702,99224392,3233084,46870723,60430369,62430984,57393458,10358899,16684829,82651278,72373496,37659250,77072625,82532312,54014062,26998766,30764367,92554120,45407418,38008118,52293995,76170907,8634541,37739481,99297164,47887470,47738185,36189527,2917920,92033260,19101477,62051033,69255765,56515456,20642888,84127901,7104732,2208785,79880247,63059024,48658605,13470059,42967683,82427263,54517921,70036438,96906410,36685023,95290172,20122224,26664538,20885148,49641577,5073754,4069912,59177718,6986898,38256849,65880522,19457589,70596786,58751351,78884452,63628376,83948335,72614359,54199160,7011964,54663246,2331773,71316369,86022504,47708850,7229550,20568363,70541760,42947632,1239555,17058722,55615885,3294781,22766820,79738755,97011160,61815223,56484900,39553046,33797252,38341669,66663942,6871053,30463802,4119747,22405120,22108665,8099994,92867155,97281847,17727650,81805959,13819358,77787724,23569917,34497327,26863229,22942635,37891451,18411915,1022677,2607799,72738685,9175338,41380093,61859581,5970607,82979980,69697787,65338021,48360198,33895336,64602895,57241521,57359924,32161669,77312810,99125126,6038457,70800879,5267545,27625735,13468268,17146629,85571389,14326617,7517032,45790169,49882705,62740044,78300864,68204242,99658235,30163921,82886381,76330843,41092172,94516935,49597667,93053405,76434144,1250437,4434662,29959549,12416768,86798033,44177011,90310261,92998591,19891772,43376279,59109400,41245325,17068582,14626618,85242963,81855445,66319530,15902805,59910624,55143724,50188404,65851721,61897582,98130363,7687278,77301523,48088883,68644627,75986488,3487592,10264691,6221471,99971982,33201905,29932657,12024238,33699435,75135448,92787493,16097038,40686254,89419466,35512853,37183543,76703609,39986008,7066775,7646095,44842615,90090964,8115266,22129328,45075471,82339363,17976208,82327024,67124282,36135,16387575,31161687,4095116,71300104,77284619,73617245,72357096,9860195,26397786,81274566,25842078,39373729,46540998,66116458,58180653,77898274,67451935,66611759,88698958,66250369,89217461,10366309,95957797,92215320,94911072,52060076,51466049,36139942,86242799,35456853,68694897,13231279,32058615,7685448,57248122,47361209,28734791,24591705,99965001,79821897,70727211,1197320,75407347,38658347,60955663,63506281,45381876,85117092,51588015,9259676,29635537,76315420,91240048,92604458,14363867,44348328,85711894,17386996,69786756,33336148,70367851,30543215,30771409,3633375,64055960,14093520,64157906,86301513,73786944,67030811,54263880,3509435,247198,65074535,17957593,37192445,9603598,92071990,29516592,37280276,36580610,22450468,30569392,22200378,5482538,91831487,59318837,89499542,30811010,48673079,9860968,61728685,34432810,17894977,73235980,43933006,37620363,96709982,2204165,9188443,74743862,36312813,98653983,65047700,55850790,10597197,72732205,54232247,26507214,66903004,69848388,99524975,54868730,15536795,65271999,21993752,92692283,56955985,22435353,93515664,55960386,18699206,94076128,20836893,20140249,99729357,53084256,44846932,22879907,59329511,40781449,76540481,51715482,90158785,48675329,66045587,14220886,31491938,10309525,68939068,98462867,168541,16019925,17016156,98371444,94090109,26776922,47213183,91802888,34044787,62803858,93790285,12303248,40027975,44627776,2717150,19486173,43152977,62115552,40197395,14045383,44844121,81898046,34946859,80014588,59614746,21070110,64848072,53802686,7919588,60393039,31727379,21919959,33237508,65081429,89046466,49271185,40534591,67513640,51315460,53648154,97379790,70420215,91281584,42073124,97641116,51507425,98948034,20867149,62428472,24826575,9257405,98943869,56153345,44060493,93270084,24168411,42307449,57961282,6157724,77187825,82779622,4978543,48260151,83651211,62490109,3183975,16567550,72019362,22113133,49236559,16424199,68110330,8055981,52204879,32151165,63015256,43322743,95251277,15064655,51590803,2891150,84166196,93566986,17385531,74614639,44479073,60581278,30090481,82178706,55669657,55189057,38009615,9575954,87160386,10649306,45794415,8128637,99333375,72793444,29510992,73109997,98739783,47792865,35996293,69355476,87598888,82897371,75153252,3880712,96735716,74724075,508198,68000591,22721500,91990218,23110625,68102437,44473167,36468541,41481685,33553959,53888755,37957788,44784505,84187166,16405341,8913721,84002370,97940276,42237907,57020481,86118021,92530431,40984766,4204661,72278539,75052463,61712234,88904910,4798568,75777973,81172706,71965942,91957544,61271144,33935899,23432750,19272365,9058407,42692881,33431960,54762643,12571310,6525948,6497830,58208470,37675718,6111563,25636669,83083131,44664587,24314885,42782652,17365188,32699744,20645197,74110882,90654836,93359396,96318094,70782102,4199704,84406788,34667286,32274392,89078848,21673260,71083565,79657802,80251430,48774913,67474219,8729146,31733363,59405277,21289531,88251446,89811711,99021067,96420429,37501808,44245960,10453030,44915235,83133790,44847298,6521313,26102057,93933709,18466635,36396314,11923835,84684495,41092102,99226875,92541302,5640302,88130087,50007421,41442762,36808486,75128745,79942022,89996536,54987042,40781854,17857111,36933038,94360702,11757872,13862149,749283,3773993,9829782,72358170,1936762,53632373,28851716,43506672,56473732,36845587,63152504,51426311,824872,17081350,97057187,45049308,21533347,78602717,11543098,11581181,74452589,93562257,57163802,23134715,84293052,27041967,38022834,72238278,67227442,26744917,90004325,30694952,20681921,23740167,89699445,33061250,83747892,89804152,24953498,68041839,96852321,29401781,84349107,48395186,8791066,79191827,27185644,23194618,45617087,26275734,12664567,47893286,92353856,4985896,18001617,14723410,14349098,74441448,4091162,87720882,61741594,37224844,69605283,75820087,73021291,67084351,91255408,38510840,79322415,90061527,27411561,60176618,43357947,21397057,6819644,37560164 +874791,67793644,14093520,62936963,55669657,51464002,73617245,54058788,49641577,20765474,30764367,43933006,33628349,99965001,13470059,77787724,63059024,65851721,67281495,72274002,51047803,9860968,40686254,40677414,36812683,18663507,36312813,38365584,96193415,39171895,93566986,12348118,59109400,40152546,7423788,86301513,91255408,82979980,31733363,3233084,24733232,2717150,68644627,47361209,64098930,65454636,84840549,93562257,99549373,45428665,27325428,75135448,36505482,33565483,83210802,61741594,56531125,14220886,68128438,66903004,38022834,83155442,96726697,81898046,95290172,1788101,54663246,75052463,97011160,55143724,89078848,30218878,76540481,56473732,43357947,61859143,66045587,77620120,62740044,20486294,59501487,39201414,78589145,33797252,44245960,96906410,4069912,58208470,68041839,60430369,72738685,60102965,53084256,44481640,8373289,45306070,40356877,83083131,52060076,77413012,44844121,10358899,11161731,3880712,84406788,71920426,62430984,22721500,36580610,33201905,84904436,9188443,70727211,9058407,39553046,57359924,26114953,69641513,30395570,54427233,80014588,87720882,44842615,73222868,1431742,78549759,92398073,80251430,20642888,60581278,45790169,83150534,92541302,19939935,71300104,24619760,3294781,97561745,30787683,85116755,70372191,61373987,62496012,75543508,6793819,96318094,27665211,37560164,17146629,36685023,44348328,77284619,95251277,15148031,22997281,43501211,27625735,20867149,62803858,66250369,89637706,4806458,526217,99333375,78561158,7229550,57240218,35092039,6871053,29466406,88653118,32590267,72614359,33431960,44550764,72732205,14731700,37183543,12024238,85023028,37891451,14349098,86242799,3509435,40984766,65017137,65074535,61960400,44784505,47090124,26392416,21001913,59329511,88251446,11365791,81172706,5640302,51590803,79806380,80316608,76330843,16054533,64157906,84293052,6497830,49597667,19891772,26493119,10366309,45237957,76825057,81274566,77187825,45617087,65338021,99297164,32161669,69786756,4119747,34698463,3487592,38645117,33699435,80246713,27411561,69605283,69136837,29994197,23110625,48893685,58749,64848072,94360702,29959549,25842078,98739783,5970607,26776922,6038457,20568363,30771409,10264691,6525948,86798033,4668450,13231279,88047921,80555751,14045383,247198,27185644,53632373,19486173,2208785,43152977,30139692,97783876,85242963,37620363,84166196,17857111,75229462,33304202,14626618,52293995,22450468,34667286,18833224,58751351,66667729,90158785,16405341,44846932,23432750,70782102,78602717,15948937,83789391,89214616,78218845,61982238,46851987,62115552,69355476,35996293,98462867,81855445,59405277,91727510,68316156,65081429,99021067,81853704,72777973,5111370,10453030,93053405,22108665,82886381,74110882,14326617,68939068,55470718,17058722,36753250,4508700,24826575,11923835,27041967,82726008,68102437,15535065,25636669,28928175,20645197,37957788,90013093,7502255,5073754,93515664,91831487,112651,89046466,30694952,31161687,4199704,72278539,55753905,33336148,24168411,91802888,57803235,94516935,60106217,20140249,21993752,51472275,68000591,20002147,42199455,92215320,99524975,9398733,5822202,68816413,60176618,17727650,12416768,45996863,48088883,50188404,17764950,99861373,38494874,86821229,47298834,34236719,69848388,44889423,95957797,13819358,31126490,37739481,43322743,5482538,72495719,3183975,63628376,39847321,29188588,36930650,62762857,50567636,57393458,77377183,74743862,40781449,83948335,48774913,40027975,44627776,32274392,71657078,93057697,70004753,60955663,84187166,91664334,29834512,30163921,53393358,81805959,3773993,18783702,51507425,61712234,44983451,55850790,87710366,63152504,42967683,20122224,98130363,93933709,3633375,6153406,96735716,15015906,2204165,14947650,72725103,93270084,17068582,63015256,33061250,86543538,46540998,76671482,15075176,90061527,98648327,29029316,38658347,3233569,26397786,7955293,77312810,73786944,3235882,18466635,69579137,8733068,49271185,41092102,168541,59318837,15064655,55189057,7646095,41380093,98948034,12571310,48658605,73235980,96852321,14363867,70596786,95010552,28851716,1197320,7687278,48360198,59177718,66137019,53648154,68204242,61271144,19272365,88444207,44847298,7104732,28734791,89811711,26664538,67124282,74357852,77272628,35456853,57241521,38008118,70800879,24591705,72019362,82779622,89996536,6521313,32250045,61897582,92692978,87160386,6819644,68824981,95581843,89804152,42947632,22435353,74724075,99224392,41481685,91938191,26292919,43506672,81677380,93790285,7685448,44060493,28787861,22200378,55602660,17539625,92530431,37435892,43376279,46870723,30463802,92803766,99604946,30569392,61859581,19457589,4099191,77898274,55247455,26063929,2917920,79191827,63967300,34497327,11581181,90090964,36780454,87598888,9603598,28796059,85117092,86460488,9175338,92353856,54762643,72358170,79136082,64602895,19101477,75820087,8128637,23194618,17081350,824872,51588015,53842979,6111563,22942635,27187213,7011964,54606384,77300457,67031644,54987042,65880522,59197747,4978543,4095116,13862149,51149804,9829782,37659250,7919588,20885148,75128745,91957544,12303248,21289531,31727379,52613508,86022504,48673079,15536795,29401781,86001008,84684495,2891150,19376156,32159704,9259676,40534591,16019925,26744917,18504197,33237508,64087743,85695762,669105,92787493,415901,73168565,73021291,20836893,70541760,93359396,97940276,13468268,61623915,98943869,7300047,56955985,92692283,38256849,21673260,51715482,18131876,61815223,90457870,98371444,36468541,34295794,16097038,42073124,62452034,1204161,71083565,92033260,67030811,26102057,83651211,42237907,79738755,61928316,92554120,79880247,82327024,76315420,26139110,6808825,29516592,73392814,84349107,38009615,569864,77072625,30543215,34698428,83302115,54199160,18806856,75153252,45407418,73109997,72357096,97641116,89699445,3088684,51466049,45919976,22766820,12664567,53547802,36189527,321665,29932657,85711894,92604458,65038678,35512853,39801351,59614746,1375023,74137926,21070110,71316369,16380211,66271566,23134715,89217461,89499542,24058273,2331773,53802686,6505939,30653863,29635537,17976208,4064751,33935899,26998766,6221471,73124510,49236559,10961421,10649306,24915585,44479073,34493392,18699206,70367851,99226875,67513640,39986008,69697787,94595668,66663942,31491938,21397057,9257405,49598724,47887470,83747892,37501808,57248122,76170907,99658235,11543098,99917599,37192445,94911072,36808486,62357986,17016156,91990218,33249630,65275241,58224549,4515343,4787945,54014062,8791066,40781854,57961282,49882705,15902805,9886593,86118021,22129328,62490109,38341669,90272749,88904910,82651278,16567550,82427263,17957593,47708850,55770687,1022677,23848565,88698958,47792865,50702367,75986488,16595436,23569917,47893286,60393039,71333116,45667668,7066775,52261574,29819940,1569515,67084351,76960303,33553959,94076128,18411915,99515901,8055981,13348726,97057187,75777973,79657802,53666583,66322959,508198,17365188,65047700,99729357,91281584,96709982,4434662,47213183,92867155,59371804,4798568,14723410,54232247,66319530,56424103,72793444,79922758,19898053,42692881,21533347,66611759,72373496,73031054,56461322,68694897,64055960,95395112,66832478,44177011,66116458,45990383,15163258,85571389,22879907,54517921,59910624,50668599,9599614,16971929,69255765,26863229,48395186,57658654,76703609,5267545,42307449,31904591,8099994,8115266,16445503,56515456,78300864,65715134,32699744,70420215,34946859,71469330,94090109,71965942,8807940,95726235,22405120,61141874,17894977,8651647,32058615,58180653,65271999,78766358,91141395,59981773,66885828,53888755,35192533,43045786,92998591,749283,61263068,48675329,7182642,24314885,67451935,34432810,92071990,63372756,4985896,76434144,40865610,45848907,30366150,99690194,88130087,42644903,18001617,97281847,55960386,89419466,16684829,55615885,30090481,62051033,33123618,84127901,48892201,90310261,45794415,10309525,78884452,57020481,26275734,9575954,24953498,97379790,41245325,1936762,70036438,16387575,32151165,1239555,99125126,72238278,50806615,30811010,23740167,8634541,9623492,4091162,39373729,48260151,57163802,68702632,10597197,82178706,56153345,2607799,38510840,8129978,45049308,21919959,5832946,38061439,77301523,32426752,33895336,82339363,26734892,49328608,51426311,63506281,16424199,56484900,62693428,41092172,54868730,83133790,13173644,83533741,83368048,74441448,36135,96420429,37224844,6986898,82897371,42782652,8696647,51315460,40197395,176257,1250437,41442762,37280276,29510992,44915235,6157724,29065964,68110330,36845587,66428795,62552524,44473167,22113133,17037369,84002370,36139942,8729146,74614639,20681921,4204661,61728685,50007421,75407347,94539824,47738185,67474219,94711842,45381876,30891921,9860195,90654836,79821897,68875490,77694700,91240048,78909561,17386996,62428472,62232211,78785507,11757872,99971982,67227442,82052050,81774825,37675718,44664587,37166608,28550822,54263880,79322415,34044787,79942022,71522968,82532312,8913721,83269727,90004325,45075471,30998561,45995325,98653983,36933038,74452589,52204879,16099750,7517032,26507214,36396314,17385531 +70800879,60106217,83155442,7423788,44983451,90013093,55770687,81853704,6505939,61373987,34698428,34497327,97783876,6497830,8651647,12348118,62762857,77620120,8099994,26998766,15535065,1250437,27411561,55247455,44177011,76170907,26744917,9599614,49236559,10366309,77301523,85023028,1431742,76540481,72274002,69848388,44842615,76434144,26493119,75128745,22129328,53666583,40152546,98948034,89214616,72738685,12303248,4668450,78766358,70596786,50806615,62552524,52261574,48892201,29466406,14723410,44889423,73786944,62496012,415901,37280276,33431960,99729357,55470718,26114953,93057697,18663507,44846932,99965001,26776922,94539824,112651,62452034,99549373,98648327,30891921,31126490,81677380,50702367,89419466,29959549,68816413,65454636,6038457,39171895,38658347,35092039,73021291,9603598,44481640,98739783,77284619,33201905,40356877,29834512,71657078,30366150,59329511,96193415,62232211,48658605,67124282,42947632,62803858,39801351,61741594,99515901,5267545,65851721,37675718,66832478,50188404,1197320,91802888,63967300,29188588,70372191,11543098,34432810,33237508,68875490,69786756,1239555,86301513,81172706,43933006,52293995,22766820,57240218,22108665,1788101,24915585,91664334,84349107,39201414,63059024,669105,30787683,18783702,96726697,65074535,67281495,47792865,73124510,42692881,19272365,3233569,19486173,68102437,82178706,88653118,38341669,70541760,99021067,86821229,33895336,77072625,30543215,79136082,76330843,29932657,53888755,13862149,49882705,78549759,17365188,73617245,97057187,20836893,75052463,48088883,38494874,66319530,95581843,20885148,60581278,59501487,99224392,31904591,53648154,78884452,37891451,45306070,1569515,4515343,22721500,57658654,1204161,42237907,55602660,51426311,71300104,82532312,20122224,18504197,60955663,14220886,39986008,10649306,22435353,54427233,36930650,86001008,12416768,53547802,17957593,91727510,32250045,4978543,24619760,18699206,78589145,51590803,61960400,45848907,95395112,8791066,44847298,9188443,4798568,38008118,72278539,66611759,2891150,56461322,77413012,17894977,34946859,33797252,68702632,43045786,19101477,46851987,67793644,32161669,33628349,34493392,70036438,89699445,3183975,5111370,2607799,45237957,44348328,9886593,78785507,27325428,95957797,95251277,56515456,15902805,42967683,37739481,30771409,99524975,9259676,74110882,51047803,89046466,13470059,10358899,51464002,4099191,28734791,63628376,42307449,5073754,64087743,87598888,71920426,54014062,3509435,35192533,824872,62740044,28796059,27625735,73222868,38256849,62490109,19939935,85117092,99658235,60430369,6871053,41442762,34044787,85571389,8115266,23848565,33304202,97561745,9860195,8634541,2204165,72725103,54987042,79922758,55143724,69355476,45667668,16445503,80316608,36505482,16054533,9575954,8733068,20002147,62936963,22405120,36753250,17037369,19898053,59614746,29510992,65715134,6111563,7955293,87160386,21993752,10453030,77787724,168541,40865610,59910624,99604946,44784505,94516935,47213183,27665211,17764950,8055981,37620363,17539625,54517921,45428665,49641577,49597667,68316156,72777973,77694700,95726235,749283,40677414,10961421,6521313,77377183,75135448,11365791,54263880,83747892,66428795,56473732,20765474,48360198,4199704,61728685,4434662,21289531,64602895,64848072,62430984,18131876,77300457,874791,76671482,51472275,44245960,72357096,93566986,33123618,508198,73235980,88904910,14326617,6793819,92692283,78218845,93053405,80251430,87720882,72614359,78300864,91957544,7687278,37435892,71522968,66250369,7104732,24953498,18833224,69579137,91281584,33935899,29401781,99226875,57020481,83789391,526217,42199455,92692978,80555751,83651211,55189057,30139692,16019925,33699435,39553046,16595436,52204879,70727211,30764367,55669657,92867155,9860968,37183543,88047921,14093520,7011964,66322959,20486294,68939068,76315420,71965942,99917599,54606384,67084351,75986488,16405341,6153406,40781854,97940276,43501211,40984766,84406788,67227442,64098930,17146629,17016156,30998561,14349098,43357947,4806458,569864,36312813,93562257,93933709,51715482,84840549,99861373,4985896,24733232,35456853,92998591,65271999,55753905,24058273,84127901,74743862,92604458,26397786,20140249,49598724,40197395,57803235,89996536,23569917,45790169,90310261,44627776,21070110,15536795,99333375,6986898,84187166,77312810,36812683,96735716,38510840,68824981,97281847,34295794,98130363,91990218,81274566,5482538,9257405,59197747,34236719,36189527,27041967,61263068,24591705,83150534,75407347,74441448,61815223,75153252,6525948,3773993,15948937,61271144,17857111,5970607,66667729,54868730,95010552,47893286,71083565,78909561,85242963,19457589,54058788,16380211,70004753,50668599,36396314,68644627,38022834,61859581,93270084,86798033,9829782,9058407,93790285,16097038,51149804,33553959,92541302,97011160,63015256,19376156,8696647,7919588,92530431,15064655,4119747,30163921,32426752,43322743,61859143,51466049,95290172,18806856,89804152,17058722,56424103,19891772,92398073,68000591,77272628,82726008,48675329,89499542,53802686,4508700,71469330,15015906,12571310,3294781,38645117,15148031,32159704,65017137,37957788,23194618,6221471,85695762,7182642,2331773,42782652,69641513,4095116,73168565,28851716,37166608,9175338,17068582,30463802,3233084,36139942,9398733,29994197,17386996,29819940,43152977,33249630,77187825,99125126,50567636,53084256,61623915,6808825,33061250,83533741,46540998,66885828,69255765,96906410,69136837,34698463,72793444,96852321,92215320,48260151,69605283,83083131,79657802,14947650,59371804,90272749,13819358,36845587,41092102,22997281,44550764,79806380,30653863,76703609,32590267,32058615,7229550,83269727,60176618,51507425,80014588,26102057,88130087,16387575,78602717,89811711,28787861,92803766,52613508,67451935,49328608,7502255,20867149,27187213,51315460,10597197,18001617,32274392,44844121,27185644,93359396,3088684,94076128,98943869,31161687,55615885,8128637,81855445,26392416,16567550,7300047,61982238,73109997,92554120,94360702,88251446,12664567,43506672,72373496,74137926,82979980,38009615,36685023,13231279,13173644,9623492,38365584,91255408,26292919,60102965,82327024,1022677,8913721,41245325,98653983,26063929,79191827,7685448,49271185,68041839,75229462,83133790,24314885,45990383,48673079,17727650,54762643,21919959,90158785,35512853,68204242,15075176,54199160,22113133,58751351,54663246,3880712,73031054,82779622,91831487,58224549,67474219,7646095,26734892,11161731,3235882,2208785,11923835,96420429,79821897,78561158,62693428,75543508,89078848,31733363,98371444,4091162,82897371,74357852,10309525,14626618,44915235,22942635,72732205,8807940,40686254,247198,71316369,5832946,64157906,91141395,89637706,84684495,17385531,59177718,30569392,16099750,63506281,57248122,45075471,82886381,80246713,89217461,47887470,82651278,45995325,2717150,81898046,83302115,66137019,62051033,16424199,81774825,91938191,31491938,31727379,21397057,56484900,94595668,96709982,5640302,37501808,84904436,93515664,44473167,47361209,72495719,13348726,99297164,74724075,38061439,57241521,11581181,85116755,18411915,4069912,45919976,68128438,34667286,321665,25636669,70782102,12024238,26139110,30395570,17976208,58749,17081350,40534591,39847321,75820087,56531125,1375023,79942022,59109400,44664587,25842078,79880247,36580610,65275241,57163802,67030811,59318837,33336148,16684829,94711842,65038678,23134715,53393358,69697787,71333116,23432750,54232247,90004325,53842979,26275734,73392814,40781449,3487592,55960386,63152504,6819644,47090124,20645197,43376279,21533347,14045383,16971929,99690194,92787493,8129978,90090964,5822202,86118021,21001913,57359924,20568363,37224844,36468541,81805959,22879907,97379790,58208470,84293052,92071990,28928175,75777973,79322415,30090481,67031644,3633375,97641116,51588015,48893685,33565483,32151165,24168411,45049308,47298834,61141874,99971982,14731700,66663942,82427263,44060493,86543538,41092172,52060076,40027975,59981773,59405277,7517032,41481685,23740167,36135,61712234,4064751,66045587,91240048,20642888,4787945,176257,26863229,72238278,62428472,29065964,8373289,22450468,83210802,55850790,56153345,83948335,62115552,53632373,41380093,70367851,61928316,88444207,86242799,68110330,74614639,45381876,90654836,94911072,29516592,11757872,29635537,45794415,6157724,72019362,82052050,87710366,42073124,26507214,70420215,47708850,48774913,76960303,7066775,13468268,23110625,74452589,2917920,28550822,56955985,36933038,92033260,30694952,72358170,82339363,65880522,77898274,66271566,47738185,66903004,90457870,65047700,36780454,48395186,96318094,88698958,24826575,94090109,84002370,37560164,45407418,22200378,45996863,18466635,4204661,57393458,63372756,21673260,35996293,58180653,29029316,42644903,39373729,37659250,86460488,57961282,98462867,79738755,30218878,65338021,66116458,92353856,8729146,68694897,32699744,62357986,83368048,37192445,44479073,36808486,86022504,61897582,85711894,26664538,65081429,45617087,14363867,67513640,1936762,76825057,84166196,46870723,50007421,20681921,15163258,64055960,90061527,30811010,10264691,60393039 +80555751,53842979,53802686,90310261,54199160,94076128,52261574,81853704,66250369,78766358,32159704,65715134,3294781,89214616,63967300,82886381,37891451,34493392,96906410,46870723,54427233,7687278,72732205,28928175,68875490,79191827,36812683,37192445,83155442,45790169,68000591,61373987,669105,1197320,34432810,79657802,48360198,93515664,9623492,91255408,66667729,95581843,7423788,44177011,10366309,96193415,77413012,59371804,71657078,86821229,87160386,88904910,99604946,24591705,63059024,29819940,98130363,36753250,51472275,92033260,67793644,18783702,43357947,60106217,6986898,99917599,96318094,94516935,54014062,53547802,66045587,526217,57658654,99861373,56515456,67281495,6793819,70372191,44784505,9398733,99524975,4787945,20885148,66832478,49236559,92787493,24619760,5970607,55753905,1250437,63506281,7104732,14731700,68824981,36312813,40865610,15064655,80014588,11161731,19272365,74110882,11581181,59329511,45428665,6221471,44889423,49641577,3509435,19939935,57803235,15948937,8115266,68204242,75128745,85242963,85117092,34295794,4515343,8733068,62740044,15535065,81855445,69355476,56424103,17068582,54987042,78589145,64848072,39986008,3183975,4798568,9599614,6808825,73168565,34044787,48675329,26063929,58751351,99965001,37739481,84840549,48673079,61859581,38008118,38061439,38658347,6521313,42692881,59501487,57248122,8807940,13819358,20642888,84349107,28734791,71920426,3235882,68316156,44983451,17857111,62452034,12664567,3773993,63015256,36930650,90457870,34698428,77300457,36468541,30891921,72278539,88047921,12348118,22450468,14947650,43045786,33565483,56461322,52293995,40781449,21993752,74137926,79942022,16019925,77694700,20486294,4668450,45667668,89804152,72793444,94090109,92398073,34698463,55247455,99226875,8373289,29959549,68816413,73031054,9188443,76170907,28851716,58224549,62496012,76703609,16405341,40677414,66611759,55770687,54606384,19376156,17764950,1239555,53632373,82339363,69579137,29510992,61263068,33237508,8129978,33797252,14220886,81677380,74743862,72738685,48774913,60102965,99021067,11757872,4978543,43501211,50668599,92554120,5482538,64157906,415901,50702367,82651278,8696647,5111370,14349098,95726235,91990218,72725103,50188404,19101477,168541,86118021,89419466,50007421,72614359,26998766,42782652,83651211,75543508,17058722,79922758,15148031,79821897,18131876,21919959,33123618,31904591,42307449,89046466,32161669,20140249,27625735,72495719,93270084,7011964,66885828,88251446,84187166,29029316,17727650,86242799,93359396,20645197,78561158,98739783,19486173,39201414,30787683,54663246,53084256,26734892,47708850,8128637,52204879,11365791,8634541,38365584,44844121,35456853,26744917,87710366,37183543,42073124,20765474,80251430,77301523,65338021,17037369,7646095,38645117,13862149,40197395,824872,24058273,32058615,45848907,6871053,38494874,36845587,8913721,32151165,67124282,2717150,39373729,16971929,95957797,10961421,86022504,16387575,71333116,54762643,6038457,94539824,35192533,10453030,82726008,85023028,65271999,92692283,17365188,95395112,96726697,44550764,4985896,43376279,23110625,42947632,61859143,27665211,78218845,1375023,84684495,94911072,48893685,18806856,18466635,96709982,89078848,26392416,73392814,66428795,50806615,31733363,83789391,37675718,18833224,65851721,79806380,70727211,23569917,7955293,97783876,59614746,60430369,81274566,26275734,44842615,29994197,30366150,72373496,17539625,97940276,91802888,22108665,96735716,99515901,3088684,67451935,1569515,75407347,6497830,5073754,22766820,83210802,66663942,23134715,81898046,47213183,26664538,65038678,36933038,10309525,99658235,33553959,40356877,68128438,112651,1022677,83302115,73124510,21533347,98943869,14723410,12416768,75153252,28550822,65454636,4508700,37620363,33628349,77187825,62430984,45990383,68939068,62803858,62936963,83150534,89499542,18663507,4119747,48892201,91664334,247198,29932657,35092039,37659250,62357986,97057187,749283,23848565,49328608,36580610,32699744,69136837,97281847,9860968,2331773,77284619,5822202,51464002,36396314,4099191,26292919,83747892,41380093,70596786,30218878,74357852,2208785,62693428,30395570,92530431,50567636,30694952,89699445,40534591,874791,75777973,24915585,35512853,71316369,34236719,9829782,22113133,47298834,72357096,321665,53666583,64602895,41245325,69848388,98948034,47792865,58749,14045383,14093520,29188588,61928316,26507214,91240048,33304202,80246713,4069912,82052050,57163802,59318837,56473732,5267545,40027975,77377183,45075471,64055960,61960400,73021291,62552524,49271185,65275241,26114953,5832946,33249630,7685448,55850790,34497327,65017137,97561745,84293052,56531125,14326617,61741594,6525948,9175338,89637706,33201905,1204161,16424199,17976208,4434662,93933709,29466406,1788101,77072625,90090964,22997281,17016156,3487592,78300864,73617245,70782102,77620120,93053405,9603598,26776922,69697787,61728685,56484900,40781854,62115552,30771409,96420429,73222868,79136082,35996293,89996536,70541760,33061250,9058407,76825057,29834512,45996863,16097038,21673260,39801351,74441448,62232211,33895336,21001913,9575954,95010552,18001617,44481640,62051033,31727379,41481685,45306070,26102057,75229462,40984766,10358899,83533741,44915235,17146629,84166196,21289531,508198,68644627,67084351,78884452,72274002,67031644,99549373,66322959,30543215,74724075,18411915,54517921,39171895,86460488,26863229,11923835,82779622,79880247,17081350,8099994,15015906,33336148,30653863,78602717,6505939,57241521,82979980,81774825,20836893,13470059,98648327,16380211,27041967,60176618,67030811,92692978,82178706,31126490,32250045,75820087,30764367,53888755,93790285,82427263,90013093,99971982,71965942,10649306,52060076,89811711,97641116,48260151,22942635,27411561,37280276,93566986,30090481,76540481,6153406,25636669,71083565,91938191,44348328,37501808,29065964,66903004,12303248,94360702,59109400,20002147,55143724,3880712,4806458,44847298,61897582,26139110,73235980,61982238,90004325,55189057,53648154,88444207,17894977,7502255,65074535,66137019,12024238,45919976,71522968,80316608,51047803,84904436,92998591,76315420,9886593,82532312,14626618,73786944,2891150,30139692,38341669,10597197,30811010,8651647,27325428,72019362,36135,84002370,70800879,71300104,45617087,77272628,84406788,29401781,91957544,88653118,87720882,51507425,7517032,44245960,90272749,7919588,45794415,57961282,8055981,6819644,63372756,49597667,54232247,57020481,13468268,47090124,27185644,47893286,13231279,61271144,97379790,28796059,86001008,36808486,24953498,15536795,54058788,82897371,16054533,91831487,91727510,45049308,46851987,19457589,16099750,47361209,68102437,87598888,77898274,86543538,23432750,9259676,34946859,40152546,23194618,66271566,88130087,19898053,62490109,3633375,23740167,44473167,24314885,32426752,83368048,2204165,76960303,20122224,27187213,46540998,74452589,22129328,51315460,99125126,43933006,47887470,55602660,17385531,99224392,2607799,85116755,22721500,33431960,64087743,15075176,569864,176257,96852321,37435892,68694897,24733232,67227442,62762857,22200378,56153345,2917920,81172706,56955985,94595668,58180653,51466049,65081429,39553046,84127901,67474219,1431742,93057697,90654836,41442762,33699435,60955663,94711842,95290172,70036438,54868730,81805959,19891772,76434144,4199704,20867149,4091162,43152977,68041839,21070110,92215320,85695762,90158785,65047700,29635537,72777973,86301513,36685023,68110330,36139942,85571389,54263880,22435353,34667286,69641513,83083131,30569392,44846932,16445503,42199455,59405277,37166608,15163258,78549759,77312810,65880522,51715482,57359924,37560164,37957788,92604458,83269727,78785507,74614639,53393358,72358170,58208470,69255765,98653983,21397057,30163921,13348726,32274392,76671482,15902805,28787861,68702632,61712234,30998561,98462867,66319530,45237957,7300047,70004753,70420215,16567550,3233084,82327024,41092172,69786756,38022834,24168411,92071990,18504197,7229550,43322743,16595436,22405120,70367851,98371444,92541302,44479073,22879907,51149804,99297164,8729146,45381876,4064751,36780454,4204661,32590267,26493119,48088883,92353856,11543098,99729357,8791066,18699206,60581278,69605283,38510840,42644903,4095116,45407418,59197747,12571310,5640302,26397786,17957593,99333375,33935899,63628376,93562257,20568363,85711894,42237907,88698958,36505482,45995325,42967683,44060493,75986488,38009615,1936762,48658605,57240218,48395186,75135448,52613508,30463802,51590803,61141874,89217461,66116458,73109997,16684829,40686254,90061527,92803766,59910624,77787724,61815223,55470718,41092102,51426311,59177718,9860195,86798033,72238278,39847321,49598724,55960386,92867155,6111563,31491938,83948335,44627776,95251277,13173644,55615885,83133790,61623915,57393458,91281584,71469330,7182642,78909561,49882705,31161687,36189527,9257405,38256849,47738185,17386996,79322415,43506672,51588015,76330843,91141395,64098930,97011160,10264691,3233569,24826575,99690194,25842078,7066775,29516592,44664587,20681921,6157724,79738755,59981773,14363867,63152504,55669657,75052463,62428472,67513640,37224844,60393039 +5111370,52613508,45407418,43376279,89078848,70036438,24058273,51047803,47090124,44481640,47792865,24168411,95957797,54663246,63059024,30090481,84904436,44983451,60102965,10453030,20765474,1431742,4806458,55247455,35996293,29065964,90457870,98371444,67474219,33201905,93566986,96735716,20002147,29994197,19101477,168541,68644627,71333116,2717150,669105,5482538,88653118,68000591,75543508,29834512,2607799,94595668,76330843,92604458,99524975,99549373,9058407,51464002,9257405,89214616,73786944,83302115,3509435,18411915,66667729,45617087,49598724,16445503,62430984,85116755,60430369,49641577,55753905,72373496,66045587,75777973,71657078,18783702,68816413,35192533,30463802,36808486,53666583,38061439,96193415,17727650,70800879,18699206,43322743,61623915,22721500,56531125,8807940,65338021,39801351,48088883,83651211,6793819,34236719,415901,34044787,45794415,52261574,65275241,55960386,66250369,43357947,62693428,76540481,14093520,4204661,4099191,29029316,7646095,33553959,78766358,41092102,13348726,73124510,27411561,50702367,97561745,66832478,26392416,7919588,82979980,92998591,66903004,16097038,21070110,92803766,36780454,86001008,92787493,61859143,45306070,14326617,38658347,34698428,13862149,75986488,84349107,78909561,72614359,79191827,76434144,36312813,15148031,67084351,61263068,4668450,84840549,10309525,93515664,508198,62452034,32426752,99604946,44842615,6525948,22942635,27665211,74137926,47361209,91281584,4095116,83368048,17764950,95395112,92692283,24619760,30366150,69136837,42644903,66137019,9599614,99965001,99226875,14626618,49271185,81853704,13173644,33797252,83747892,61373987,30891921,94711842,39373729,84166196,19272365,25842078,73617245,67793644,63506281,18131876,90310261,98648327,91802888,29819940,60955663,58751351,45075471,86301513,79657802,37620363,46851987,35092039,33237508,53547802,247198,84187166,69355476,17016156,26507214,14220886,2331773,5640302,71522968,44479073,52204879,92215320,57240218,2204165,67513640,16054533,98739783,67281495,35456853,89499542,39553046,57248122,54199160,9175338,90061527,75229462,99125126,93562257,54058788,95010552,48260151,95290172,321665,32590267,49328608,77272628,37183543,77787724,76671482,77284619,44889423,22113133,92353856,36580610,72777973,55470718,18504197,4119747,7011964,4508700,99861373,28928175,59197747,34493392,10358899,38008118,4064751,30395570,94360702,62357986,61982238,40984766,47738185,33249630,85571389,2917920,94090109,71920426,53802686,93933709,31161687,92398073,26397786,77187825,76170907,46870723,8129978,37739481,1204161,7423788,72738685,14947650,62936963,19486173,8651647,15064655,26493119,36505482,74357852,9188443,16380211,4787945,67031644,84684495,73031054,71083565,14363867,57020481,61897582,61271144,5832946,1375023,82897371,15163258,22450468,57393458,10597197,30764367,16567550,40197395,62740044,20140249,38022834,48893685,15535065,7517032,47708850,41442762,82886381,8099994,77694700,50188404,81898046,40781449,66271566,61141874,42967683,54762643,68939068,88251446,65047700,59910624,30139692,16405341,11543098,74743862,26776922,12664567,7955293,96726697,87710366,6505939,85117092,9829782,22879907,69255765,19376156,43152977,61815223,32250045,4515343,67124282,97940276,74441448,80014588,74614639,39171895,99333375,56473732,31126490,90272749,176257,10649306,86118021,51590803,40865610,62803858,65271999,19939935,71300104,83210802,72725103,74110882,3773993,18663507,59177718,48892201,32151165,86022504,89811711,86242799,61960400,98943869,69786756,5970607,53632373,97641116,10366309,96906410,54987042,11923835,81855445,76825057,53648154,92554120,6221471,29188588,79136082,18833224,70367851,80316608,59329511,15948937,99021067,8733068,85023028,69641513,37659250,19891772,48395186,526217,70727211,824872,20568363,54232247,16099750,51588015,4798568,6157724,40686254,68694897,10961421,12571310,66428795,99515901,67227442,56424103,91831487,44473167,99658235,68824981,33061250,15015906,28851716,3183975,874791,34497327,40534591,62232211,65715134,73168565,37675718,51507425,56515456,73235980,54517921,30811010,29635537,8791066,64602895,82052050,30218878,60176618,2208785,51426311,45237957,42073124,17386996,57163802,14045383,89419466,93270084,8055981,84293052,48673079,33895336,36812683,32161669,38494874,51472275,70372191,20885148,42947632,13819358,61712234,62051033,41380093,44177011,1197320,38365584,24733232,55770687,9603598,73392814,16595436,28734791,83150534,12416768,45995325,37192445,77072625,83533741,85242963,25636669,77620120,82339363,38341669,40027975,50806615,36933038,76960303,75820087,58749,29466406,6497830,78218845,83083131,44915235,9623492,66322959,27325428,33336148,20486294,3233084,77312810,99971982,112651,66885828,29959549,88047921,62762857,43501211,91957544,5822202,59109400,79821897,3880712,30998561,35512853,61728685,86821229,45996863,56153345,49882705,72238278,6153406,34946859,33628349,89996536,92071990,30569392,38645117,63372756,52293995,43045786,80251430,33699435,91141395,91727510,91990218,65074535,55143724,59614746,6521313,62115552,81677380,18466635,20645197,80246713,98462867,42237907,55189057,45667668,97011160,23569917,27187213,75135448,97783876,62552524,99297164,87598888,95726235,16684829,44844121,65038678,17539625,67451935,66611759,57803235,49236559,21993752,97379790,72274002,15075176,65017137,7182642,67030811,64098930,91664334,17081350,57241521,44348328,26063929,64848072,23848565,42199455,92033260,26275734,34295794,37166608,36135,6986898,8729146,68204242,79922758,89046466,54606384,82427263,70004753,63628376,45919976,8634541,78602717,36396314,31904591,50668599,27041967,9575954,30163921,90013093,88130087,1239555,77377183,77413012,50007421,39847321,58224549,36468541,42692881,75052463,56461322,89637706,8373289,52060076,17385531,41245325,74724075,48360198,17857111,33935899,90090964,4069912,47298834,98130363,39201414,22435353,38009615,99690194,54014062,64157906,96318094,7687278,82726008,83133790,7300047,40152546,21673260,3294781,61741594,22108665,33565483,36930650,43933006,81805959,37891451,26998766,94076128,1569515,65880522,36753250,95581843,88444207,64087743,20867149,22200378,70596786,5267545,21533347,68316156,81774825,40781854,88904910,31733363,37957788,77300457,76703609,17068582,20681921,90158785,83948335,65851721,26114953,21001913,31491938,93053405,72495719,37501808,48675329,36845587,44846932,45790169,96709982,34432810,30787683,57961282,56955985,37224844,51715482,63967300,17957593,12348118,17037369,44784505,83155442,78884452,53888755,8913721,26139110,44847298,28796059,39986008,70420215,23194618,45428665,49597667,89217461,72278539,41092172,26863229,44245960,62496012,22766820,30653863,1250437,16971929,71965942,36189527,21919959,82532312,83269727,92541302,84002370,9886593,73222868,55850790,16019925,26664538,83789391,47887470,79806380,92530431,97281847,59981773,60581278,11757872,55669657,97057187,78589145,41481685,88698958,68702632,54868730,69605283,16387575,63152504,46540998,21397057,82779622,60106217,34698463,4091162,6111563,53842979,64055960,24591705,66319530,17058722,7685448,75407347,14731700,68102437,29401781,33431960,65081429,72793444,99917599,3235882,22997281,16424199,2891150,78561158,6871053,66116458,62490109,5073754,18806856,61859581,14349098,77898274,33304202,87160386,40356877,59501487,33123618,78785507,75128745,89699445,69697787,79738755,18001617,85695762,78300864,91938191,44664587,81274566,749283,99729357,80555751,31727379,68128438,98948034,96420429,27185644,82178706,6819644,50567636,59405277,7502255,1936762,32058615,51466049,54427233,76315420,6808825,3088684,19457589,11365791,26744917,3233569,40677414,82651278,13231279,15902805,96852321,22405120,89804152,44060493,20122224,59318837,15536795,12024238,70541760,84406788,84127901,94516935,37435892,38256849,24915585,78549759,57658654,58180653,36685023,81172706,17894977,569864,59371804,3487592,11581181,72019362,28550822,26102057,7066775,86543538,55602660,51315460,23432750,43506672,53084256,24953498,42307449,66663942,91240048,58208470,17976208,45381876,20642888,90004325,8115266,48774913,65454636,68875490,71469330,79942022,94911072,4985896,32699744,27625735,72357096,45848907,38510840,73109997,56484900,48658605,17146629,51149804,86460488,23110625,54263880,7104732,23740167,44550764,24826575,19898053,8128637,74452589,36139942,55615885,75153252,45990383,53393358,90654836,10264691,26292919,93359396,34667286,87720882,85711894,98653983,86798033,93057697,45049308,23134715,29932657,6038457,29516592,9860195,1788101,17365188,32159704,92692978,69579137,28787861,93790285,30543215,30694952,21289531,62428472,42782652,20836893,9398733,1022677,69848388,72358170,79322415,94539824,44627776,7229550,13470059,3633375,91255408,72732205,95251277,79880247,22129328,82327024,9259676,47893286,24314885,57359924,73021291,37280276,99224392,13468268,92867155,12303248,30771409,68041839,60393039,71316369,4434662,11161731,8696647,32274392,61928316,14723410,4199704,47213183,26734892,29510992,77301523,4978543,70782102,63015256,37560164,68110330,9860968 +67451935,63967300,4095116,13173644,55753905,36505482,56424103,17764950,9886593,29466406,62496012,12348118,44983451,88698958,76960303,64087743,40152546,48673079,85116755,33237508,24733232,60102965,31126490,54987042,9575954,75986488,10597197,68875490,17037369,569864,40356877,57163802,13231279,83210802,89637706,82327024,29188588,22997281,72373496,45996863,78602717,71469330,73235980,87598888,78589145,51047803,87710366,59177718,13468268,29819940,57803235,92398073,71333116,14947650,30764367,90090964,7423788,8791066,20002147,86022504,9623492,14220886,2204165,94516935,99965001,6153406,36780454,89419466,92787493,27665211,5832946,30395570,65715134,80316608,9860195,8099994,61982238,7182642,35092039,14731700,79322415,78909561,68041839,3487592,42644903,16567550,66319530,1788101,29834512,40027975,8807940,66832478,65851721,20486294,63372756,4099191,68702632,20140249,83155442,79657802,18663507,38645117,18806856,15015906,5111370,48260151,73031054,44915235,54762643,68128438,9188443,35456853,82427263,97057187,55247455,23569917,10366309,74357852,67227442,99549373,247198,71657078,96726697,98371444,63152504,79821897,45848907,61960400,18783702,92033260,70372191,88047921,49882705,25636669,45237957,35192533,37739481,81172706,321665,44889423,99971982,78766358,66428795,7646095,20122224,72357096,55143724,48892201,96852321,26114953,45995325,88653118,43501211,21070110,66885828,98653983,22942635,45407418,30139692,41092102,7955293,93933709,85117092,33699435,66667729,57240218,59329511,7011964,56515456,7685448,51464002,65275241,32426752,26292919,94090109,8115266,77694700,79922758,9599614,82178706,41245325,30771409,21993752,3509435,67474219,95581843,32699744,27325428,59981773,168541,83302115,91802888,73124510,92803766,60955663,92071990,29994197,71920426,81898046,34698428,38008118,669105,29959549,56531125,1197320,96193415,61928316,749283,45617087,95290172,16019925,72274002,88251446,98648327,76170907,80251430,10309525,19898053,33895336,82886381,29516592,43045786,58180653,40677414,34295794,20867149,90272749,59197747,68816413,55602660,42237907,77620120,34236719,28550822,44784505,84684495,53547802,30998561,45428665,61263068,32058615,6221471,4091162,81774825,78785507,48658605,82897371,7919588,40781854,99917599,96709982,71300104,15535065,28928175,53632373,18504197,55850790,83150534,1431742,15064655,73786944,66663942,415901,62803858,70541760,30787683,4119747,37659250,89078848,40865610,17386996,44479073,93515664,44844121,37620363,34698463,70036438,55669657,70004753,56473732,9860968,70727211,96735716,5267545,50188404,54199160,508198,16445503,7517032,42967683,91141395,22129328,42073124,89811711,83133790,78300864,30543215,17957593,73222868,874791,68204242,28734791,36753250,48675329,4668450,98462867,54517921,77312810,6793819,10649306,47361209,27411561,63015256,26507214,5822202,92692283,9175338,15075176,73617245,61815223,84904436,55189057,86242799,31904591,2891150,84349107,38022834,35996293,81805959,7502255,26139110,26664538,34493392,49641577,63506281,2208785,81274566,99604946,51472275,67793644,91831487,69605283,5640302,83368048,59318837,37183543,38009615,8733068,59910624,90013093,5970607,16684829,71316369,77284619,32590267,14093520,64602895,92554120,8651647,30163921,79136082,69579137,6808825,44348328,66903004,30090481,99729357,89214616,83269727,65880522,99515901,24619760,64157906,97783876,22405120,45667668,99690194,32159704,65271999,16099750,62936963,37435892,21533347,65047700,11923835,15902805,76315420,55470718,40781449,75820087,69697787,67281495,12416768,43933006,8634541,18131876,83747892,32250045,33797252,47708850,19272365,66116458,50806615,3233569,39847321,77072625,70800879,99861373,76434144,6986898,19891772,47298834,30366150,3633375,54868730,86001008,93562257,49271185,61859581,58208470,66271566,73021291,8129978,3088684,82979980,47887470,43506672,81853704,52060076,26493119,33553959,91727510,33431960,98130363,73168565,18833224,16097038,23194618,23848565,99125126,32274392,16054533,85571389,55615885,45075471,77272628,42692881,38256849,59501487,84127901,72725103,43322743,66322959,51149804,45919976,14326617,32161669,16405341,2717150,24826575,11161731,3233084,51507425,3183975,31161687,53842979,61623915,51588015,23110625,93053405,1204161,2607799,65017137,42307449,98739783,34044787,82726008,89699445,63059024,17539625,75135448,62430984,26776922,10358899,62693428,86460488,99658235,71522968,57658654,63628376,99524975,1569515,5482538,94911072,47090124,84840549,7300047,52613508,39171895,44550764,40984766,64098930,99021067,1250437,30463802,3294781,95395112,22450468,10453030,21289531,18699206,17058722,93790285,6038457,28796059,14349098,83651211,95726235,89046466,99297164,22435353,36930650,69355476,33123618,22879907,91664334,76540481,824872,24058273,56153345,92867155,4434662,9829782,39986008,38061439,56461322,37166608,72238278,53888755,6521313,52204879,4985896,60106217,61728685,73392814,36580610,9058407,44481640,76825057,5073754,92692978,16971929,76330843,16424199,40686254,68102437,92530431,61859143,83948335,66250369,50668599,78218845,50702367,52261574,13348726,18411915,30811010,90004325,22108665,66611759,69641513,44177011,95010552,44060493,87720882,79738755,49328608,19376156,93270084,85695762,30694952,48088883,4787945,98943869,79191827,61741594,44473167,27625735,62552524,77787724,45790169,18001617,80555751,77300457,46540998,72777973,36685023,65038678,18466635,43376279,39201414,19101477,88904910,45306070,7229550,10961421,94539824,29510992,15148031,44847298,13819358,11543098,72614359,56484900,56955985,7066775,92215320,37501808,8128637,34497327,90457870,44846932,4069912,28787861,54663246,91990218,84293052,72358170,89804152,20765474,34432810,20836893,84187166,65338021,94595668,80246713,38658347,78561158,94076128,50567636,69136837,15536795,46870723,55770687,68000591,54606384,72278539,57241521,68694897,72793444,59371804,83533741,62740044,9398733,54232247,97940276,55960386,43357947,74614639,82532312,75229462,19486173,87160386,82052050,6525948,68939068,17727650,11581181,8696647,49236559,86301513,82339363,4806458,85242963,16595436,71965942,9257405,12571310,54014062,92353856,61271144,70420215,62232211,74137926,6505939,12024238,59109400,80014588,79806380,44627776,75777973,62115552,93057697,75543508,45381876,24953498,30653863,112651,26392416,57393458,97561745,9603598,44842615,48774913,4204661,75153252,33304202,62762857,71083565,15163258,33201905,6871053,17068582,7104732,68644627,14045383,36808486,77377183,30891921,75128745,3773993,58749,39553046,92541302,91957544,33565483,4515343,8913721,62490109,27187213,17146629,37891451,77898274,14626618,53666583,76671482,59614746,30218878,86821229,81855445,97011160,38494874,25842078,46851987,72738685,54058788,21673260,24915585,26998766,39801351,62428472,42782652,91240048,31727379,49598724,67124282,47792865,60581278,36468541,36312813,89217461,53802686,58224549,8729146,65454636,47213183,79880247,48893685,66137019,4199704,42947632,77187825,60430369,37192445,17081350,24168411,53648154,69848388,20568363,37280276,88444207,72019362,94360702,17894977,72732205,27041967,6497830,17365188,29065964,8373289,82651278,26397786,40534591,67084351,23432750,74110882,77413012,32151165,16380211,53393358,31491938,98948034,47738185,95957797,34667286,20885148,41481685,38341669,21919959,34946859,47893286,65074535,36189527,79942022,19939935,78884452,84002370,57961282,35512853,92604458,30569392,176257,70596786,22721500,37957788,84406788,41442762,37675718,97641116,36396314,90158785,28851716,19457589,61373987,3880712,57248122,66045587,33249630,36845587,72495719,88130087,4978543,65081429,67031644,57359924,90310261,74743862,96318094,68316156,99333375,33061250,51315460,83083131,20642888,83789391,78549759,26275734,33628349,17976208,93359396,81677380,44245960,48360198,2917920,90061527,10264691,76703609,1936762,64055960,9259676,4508700,26744917,14723410,51466049,6819644,29635537,13470059,54263880,45794415,4064751,61141874,61897582,26734892,51426311,62452034,85711894,6157724,36933038,29029316,69786756,45990383,91938191,99226875,36812683,60393039,74441448,22766820,84166196,26063929,61712234,41380093,2331773,11757872,22200378,64848072,12664567,26102057,50007421,89996536,1022677,58751351,75052463,36135,97379790,67030811,60176618,85023028,96420429,53084256,42199455,91281584,67513640,68824981,68110330,54427233,17016156,51715482,90654836,86798033,7687278,62051033,26863229,20645197,74724075,1239555,17385531,39373729,24591705,77301523,24314885,82779622,49597667,40197395,73109997,69255765,74452589,97281847,14363867,51590803,93566986,21001913,44664587,70367851,27185644,99224392,37560164,52293995,43152977,13862149,4798568,37224844,33336148,23134715,62357986,36139942,29932657,94711842,86118021,31733363,38365584,8055981,526217,89499542,92998591,22113133,57020481,86543538,23740167,70782102,20681921,91255408,33935899,45049308,38510840,15948937,1375023,17857111,75407347,95251277,12303248,48395186,41092172,96906410,16387575,3235882,59405277,6111563,11365791,29401781,21397057 +10358899,2891150,8634541,47792865,72373496,53666583,19272365,415901,77620120,29834512,7011964,98943869,76330843,11923835,31491938,37620363,14220886,99524975,55960386,22129328,24058273,6808825,68824981,29959549,62936963,68102437,56515456,26776922,6497830,69255765,37659250,99515901,76170907,59981773,28928175,73124510,27665211,26744917,96420429,68816413,96735716,45790169,95957797,96193415,66885828,93270084,21001913,93057697,52261574,79136082,49271185,12348118,49641577,17365188,22766820,18663507,9829782,91802888,35996293,26734892,39553046,14093520,75543508,95395112,2717150,40865610,51047803,19376156,85242963,76540481,40197395,34698428,97641116,21993752,569864,69355476,74614639,33249630,33201905,47361209,95726235,38658347,67793644,49597667,43501211,77694700,29994197,52204879,50188404,54199160,55247455,4668450,32161669,79806380,16380211,9623492,44847298,8729146,40781854,83083131,6986898,5970607,78884452,65074535,62452034,71300104,22997281,16445503,33237508,508198,85116755,15064655,48893685,90310261,45237957,50806615,89214616,14947650,34493392,13862149,40152546,60430369,53632373,72738685,80014588,65715134,8807940,57163802,60955663,17957593,53842979,20765474,18806856,69579137,35092039,96726697,44245960,98948034,84002370,92604458,98462867,44473167,41092102,98648327,60581278,42967683,30463802,46851987,40984766,67030811,54517921,68644627,44844121,48892201,38061439,36753250,45794415,55470718,26392416,82178706,65017137,38494874,14326617,3294781,53802686,81805959,9188443,63967300,76703609,92692978,83533741,1250437,66045587,44889423,7423788,81855445,6871053,59501487,69136837,8055981,98739783,65851721,5111370,73235980,44481640,91727510,9886593,4508700,67084351,40534591,58749,23848565,33431960,33797252,62496012,39201414,96709982,30395570,88653118,51426311,36468541,40686254,73786944,78602717,92353856,78218845,99021067,93053405,61741594,14349098,3487592,61960400,44348328,44842615,62740044,51590803,67281495,61623915,112651,78766358,61373987,70541760,93359396,68204242,84904436,8129978,68000591,20002147,15535065,99917599,33304202,39801351,75128745,99658235,51507425,20140249,83150534,27325428,66832478,64098930,9575954,19101477,31904591,80251430,64055960,71920426,37501808,50702367,48675329,39847321,63059024,55850790,72238278,17058722,1375023,86301513,44177011,25636669,31161687,54232247,68694897,90090964,40027975,44846932,7687278,62232211,23569917,89046466,61982238,65271999,57240218,7300047,55189057,5832946,38256849,80555751,20645197,6525948,89499542,72777973,48360198,18783702,11543098,20681921,93933709,81172706,74357852,72019362,87598888,30998561,4204661,36933038,15902805,96318094,98371444,34432810,67227442,7919588,77312810,73392814,47090124,36812683,11757872,91664334,45996863,68316156,9398733,36930650,83133790,58751351,23194618,99226875,41481685,89699445,7517032,30218878,34044787,8651647,81853704,86001008,39986008,9259676,64848072,18131876,69641513,94090109,50567636,19939935,51715482,26114953,95581843,62430984,59109400,4434662,54014062,68110330,65338021,43376279,82726008,54606384,14045383,83747892,90013093,2607799,30694952,57020481,8733068,24619760,3509435,3088684,45306070,6038457,29401781,4095116,99125126,34497327,57961282,53084256,66250369,42692881,59197747,3880712,4119747,59371804,74441448,84127901,70372191,49598724,73031054,40781449,36312813,85023028,5482538,1431742,18466635,48774913,89419466,4787945,61728685,24591705,57803235,72725103,98130363,61815223,84187166,53648154,26139110,53393358,71333116,57393458,6793819,36580610,3233084,99861373,73617245,3183975,70800879,97561745,77787724,26998766,58208470,9058407,59614746,52613508,59177718,8128637,54058788,9257405,10366309,32274392,92398073,36845587,43933006,20885148,62803858,54427233,72278539,36808486,247198,61859143,82532312,13231279,1204161,21070110,93562257,48395186,12416768,44983451,27411561,60106217,26275734,56424103,94595668,66322959,79657802,5073754,99604946,74137926,62693428,8791066,10453030,34698463,10649306,16595436,63015256,1197320,18833224,57658654,70596786,17764950,13819358,15163258,5640302,14363867,71469330,42073124,49328608,84349107,77301523,45995325,66903004,89217461,16684829,32159704,72732205,85711894,97783876,26102057,56531125,31733363,30787683,78909561,33895336,29466406,81677380,83948335,47738185,49236559,33061250,9599614,71965942,33628349,18699206,526217,77284619,66137019,42237907,64157906,84684495,16405341,42947632,40677414,37166608,44550764,11161731,91990218,51464002,33553959,29819940,79922758,89078848,32590267,61271144,61263068,81898046,30653863,38008118,6153406,8696647,36505482,76671482,22405120,90061527,75229462,2204165,45428665,4806458,66611759,14626618,7646095,4515343,39373729,8115266,45919976,21289531,86798033,77300457,99965001,56473732,66667729,8099994,88251446,38510840,4064751,17386996,874791,55770687,92803766,35512853,20486294,35456853,17068582,90457870,38022834,6505939,56153345,70004753,91831487,38365584,22721500,71522968,99297164,29932657,53888755,1022677,6819644,52293995,29029316,77898274,1569515,33565483,4069912,22450468,66663942,12664567,45075471,54263880,70367851,44664587,99729357,8373289,19898053,62552524,96852321,56461322,29516592,19486173,37435892,88444207,68128438,89811711,71657078,84406788,16424199,16054533,97011160,83210802,24168411,64087743,29510992,35192533,72793444,86118021,13468268,82327024,43152977,32699744,28851716,82651278,37891451,70420215,44784505,7955293,9603598,70727211,749283,95010552,17146629,5822202,72357096,55669657,60102965,61141874,21673260,4091162,4099191,15015906,29188588,37675718,79322415,84840549,99224392,82339363,71083565,22942635,3773993,19891772,94516935,30569392,29065964,27187213,669105,45667668,82052050,25842078,17016156,67124282,37192445,99549373,77072625,50668599,97057187,45990383,86242799,69697787,47213183,46870723,67474219,4798568,48658605,57359924,54762643,59405277,29635537,7182642,61859581,69605283,36780454,5267545,30891921,34946859,32426752,16097038,65081429,13470059,61712234,42782652,31727379,86022504,83789391,45407418,78589145,18504197,38009615,16019925,48088883,73168565,32250045,22113133,3235882,93566986,73222868,65454636,95290172,32151165,70036438,43045786,24826575,83368048,26397786,6221471,84166196,52060076,33699435,57241521,97281847,168541,68875490,55753905,92541302,18001617,45381876,63628376,77187825,94539824,24915585,36189527,62490109,7229550,65047700,72495719,45848907,33935899,87720882,75135448,17081350,65275241,54663246,48260151,8913721,34667286,79821897,41442762,26292919,51588015,79191827,7685448,91281584,2331773,49882705,88047921,99971982,82886381,62762857,26493119,38645117,78300864,30090481,89637706,18411915,24953498,21533347,22108665,20867149,87710366,75777973,91957544,33123618,34295794,15536795,84293052,77413012,90004325,99690194,14731700,92554120,62115552,53547802,16971929,37739481,68939068,71316369,15148031,74110882,74724075,41245325,78561158,20122224,62051033,19457589,17727650,30764367,85571389,12571310,39171895,36135,92215320,17894977,321665,94911072,86460488,68702632,80316608,90272749,3233569,91255408,75407347,9860195,3633375,41092172,59329511,58180653,72614359,23134715,22879907,77272628,92787493,94360702,54868730,9175338,30139692,28734791,1936762,55143724,2208785,31126490,57248122,47298834,17385531,91141395,37224844,14723410,26063929,17976208,92998591,10597197,58224549,43357947,92867155,82979980,6111563,40356877,92530431,78549759,75052463,7066775,1788101,75820087,33336148,21919959,4985896,16099750,83269727,26507214,83651211,34236719,73109997,24733232,66116458,66271566,30366150,75986488,89996536,37957788,9860968,63506281,87160386,74743862,80246713,30771409,27625735,43506672,56955985,59910624,74452589,56484900,92071990,72274002,88904910,16567550,23432750,67513640,22435353,78785507,64602895,79942022,61928316,51472275,7502255,83302115,67451935,20568363,51315460,69786756,88698958,1239555,76434144,20642888,73021291,70782102,176257,26664538,94076128,89804152,28550822,6157724,86543538,16387575,90158785,22200378,51466049,91240048,43322743,76315420,10309525,44479073,63372756,51149804,65038678,55602660,91938191,92033260,12303248,2917920,11581181,66319530,36396314,42307449,4199704,6521313,17857111,44060493,37183543,54987042,75153252,21397057,48673079,15075176,97940276,86821229,68041839,41380093,93515664,85695762,824872,17539625,63152504,17037369,60176618,62428472,32058615,69848388,99333375,47887470,47708850,85117092,95251277,36139942,27041967,83155442,13173644,98653983,24314885,67031644,82897371,92692283,65880522,7104732,30163921,81274566,4978543,82779622,90654836,79880247,45617087,12024238,44627776,47893286,81774825,13348726,36685023,97379790,66428795,20836893,42644903,42199455,30543215,37280276,94711842,96906410,59318837,50007421,30811010,76825057,44915235,61897582,72358170,26863229,79738755,45049308,27185644,93790285,23740167,76960303,10264691,38341669,60393039,88130087,28787861,82427263,46540998,28796059,10961421,77377183,55615885,37560164,11365791,23110625,15948937,62357986 +13231279,99965001,98739783,99549373,61859143,59197747,54014062,73786944,27665211,28796059,247198,95290172,112651,44889423,51047803,6819644,12348118,86821229,36812683,4806458,28851716,66322959,5111370,47090124,82979980,19939935,37183543,51464002,36505482,9623492,30395570,18783702,569864,49641577,36930650,30787683,35456853,37739481,508198,7502255,90272749,36580610,83368048,36780454,62936963,43376279,98462867,45428665,99861373,96726697,61373987,669105,40197395,59910624,92554120,15148031,68939068,29994197,92692283,83210802,8696647,71657078,90061527,65017137,79657802,99125126,77284619,33565483,85116755,6793819,11161731,88653118,7300047,1431742,8807940,39986008,18833224,90013093,26998766,93566986,57961282,44550764,10309525,26493119,24591705,74743862,16445503,99224392,43045786,68316156,39171895,73031054,98371444,1022677,19101477,97783876,55247455,9886593,26063929,68041839,60102965,13173644,98130363,63506281,62496012,29959549,1569515,83302115,61897582,9603598,40781449,89214616,82726008,50702367,17068582,15536795,74137926,70727211,20002147,61859581,66319530,22450468,42199455,64087743,60430369,3294781,64157906,31733363,37675718,14093520,67227442,45617087,65038678,63059024,40677414,39847321,33553959,92803766,45996863,55753905,8733068,81853704,71333116,3633375,1204161,85242963,91802888,59329511,69255765,54762643,99604946,82779622,10597197,36189527,45990383,80014588,48260151,72738685,48675329,4099191,14349098,68702632,29932657,51588015,27411561,35092039,73392814,30218878,6521313,81274566,76434144,83155442,18663507,37501808,65851721,22721500,65275241,13819358,84166196,61815223,29819940,34236719,56515456,22879907,66611759,62452034,67124282,72358170,13470059,30366150,51466049,16380211,97057187,7423788,28928175,75407347,68875490,84187166,56424103,81677380,20486294,57248122,3233569,24733232,4787945,69355476,76960303,22942635,44348328,68644627,32161669,10366309,65074535,18806856,93562257,53547802,72793444,87710366,7955293,85571389,874791,12571310,12416768,83651211,15948937,44245960,77312810,12024238,14045383,73222868,87720882,50188404,3235882,32159704,91664334,18411915,28550822,36312813,32058615,16971929,22997281,70036438,89811711,77413012,38494874,84293052,8791066,2717150,67084351,53666583,77377183,95395112,23848565,97561745,75128745,63967300,6808825,51715482,6986898,42073124,33201905,91957544,29834512,69605283,56473732,58751351,22129328,83150534,15163258,55143724,89419466,43357947,62428472,38008118,96193415,17016156,89499542,8651647,14326617,74357852,5822202,77620120,73168565,54663246,94539824,99333375,30463802,88904910,5640302,66250369,44844121,4095116,33304202,15015906,24915585,61263068,24314885,22108665,77694700,26397786,8634541,44983451,6153406,61982238,42947632,76540481,34295794,41092102,26776922,69786756,38061439,42644903,47708850,59109400,92998591,97940276,16595436,43152977,67281495,70596786,62693428,16097038,17727650,82178706,62803858,42237907,95726235,8055981,79880247,39801351,77272628,48892201,23194618,65338021,68816413,76170907,33797252,50668599,57393458,62762857,9175338,94076128,90310261,94911072,49597667,40356877,90090964,82886381,67793644,7919588,99515901,56531125,83083131,19891772,93790285,70800879,11365791,40686254,9398733,36753250,68102437,26507214,84684495,76703609,44481640,99658235,53084256,81855445,75153252,34497327,40152546,45306070,17957593,16567550,3183975,92604458,71316369,20140249,54427233,66116458,33699435,15075176,88444207,57241521,33237508,66903004,23569917,96709982,62115552,18699206,89078848,14731700,75986488,63372756,17146629,41092172,72357096,73235980,30771409,70541760,24826575,19272365,8115266,26734892,7011964,53888755,74614639,80555751,55470718,71920426,79821897,3233084,72274002,52293995,80246713,33895336,27041967,87160386,88698958,95957797,66832478,4119747,25842078,3880712,32250045,37560164,29029316,23110625,43933006,57240218,8373289,66667729,78909561,76330843,72732205,77072625,74110882,77898274,9860968,78549759,56484900,73617245,34698428,44842615,5832946,70420215,14723410,68128438,55669657,99226875,85023028,97281847,33336148,38658347,79922758,2917920,27185644,17764950,78218845,1250437,94516935,88130087,37435892,47887470,4668450,72278539,45995325,62357986,97011160,53648154,19898053,6871053,10358899,64098930,21070110,38009615,10961421,91831487,52060076,70004753,40865610,38365584,24058273,50007421,56153345,3773993,66137019,65454636,93270084,89046466,9058407,63628376,82052050,93933709,4064751,62740044,17058722,43322743,66428795,68694897,64848072,66663942,82897371,13862149,19486173,44060493,52261574,3509435,38645117,88047921,55189057,37620363,67030811,41245325,30543215,30090481,48893685,17894977,20122224,92541302,83948335,6505939,92033260,74724075,54987042,17037369,27325428,13348726,6221471,35996293,99971982,48395186,2607799,70372191,84840549,46870723,63015256,14947650,31126490,59177718,67474219,47738185,51472275,45237957,16099750,62490109,34493392,75229462,62232211,47361209,37957788,53393358,9575954,35192533,33431960,21001913,96852321,78884452,15535065,84904436,97641116,89699445,38022834,53802686,57803235,34698463,58749,61728685,7104732,65880522,415901,21993752,10453030,78602717,49328608,99917599,11543098,90457870,81898046,68204242,24619760,81172706,91255408,81805959,71300104,48673079,73021291,29466406,33628349,2891150,32590267,51590803,20642888,43501211,18131876,54199160,26139110,95010552,41380093,92215320,68000591,51149804,75777973,5482538,62552524,78561158,20765474,46540998,52204879,85117092,20867149,44846932,32151165,91281584,41481685,72777973,45848907,72019362,79191827,27187213,86022504,60176618,30653863,94360702,39373729,61623915,98648327,30694952,42782652,53842979,92692978,21919959,72725103,26114953,60955663,23740167,50806615,1788101,76671482,67513640,168541,69136837,6497830,4199704,73124510,54058788,38341669,9257405,89804152,94090109,7646095,17386996,61271144,47792865,67451935,96735716,31161687,33249630,39201414,18001617,65271999,98653983,2208785,36845587,59501487,65715134,79136082,86301513,30163921,17976208,60106217,23432750,526217,82651278,74452589,93053405,29188588,91240048,32426752,83789391,75543508,71083565,19376156,29401781,72495719,37166608,47893286,4798568,14626618,8099994,4515343,77300457,25636669,30569392,77787724,72373496,42692881,60581278,6038457,54606384,70367851,91727510,58208470,5970607,16387575,99729357,42967683,51507425,8913721,26744917,40534591,98948034,61960400,7182642,26392416,55602660,49271185,69579137,89637706,80251430,44847298,45667668,42307449,52613508,21289531,26292919,99021067,11923835,26275734,99297164,97379790,20645197,59614746,14220886,10264691,16019925,96906410,75052463,9860195,33935899,75135448,48360198,85711894,96420429,72238278,39553046,16405341,57163802,90158785,92398073,45790169,78589145,72614359,321665,77187825,91990218,98943869,31904591,44915235,23134715,30891921,30764367,75820087,37192445,96318094,31491938,40984766,61928316,35512853,86798033,55770687,33061250,7229550,48774913,44177011,36139942,82339363,22405120,86543538,94711842,91141395,15902805,45919976,20885148,86242799,68824981,34432810,1936762,37891451,29065964,67031644,21673260,2204165,95251277,1197320,18466635,50567636,31727379,17857111,66045587,64055960,84002370,33123618,20568363,11581181,4985896,176257,49598724,93515664,79806380,71522968,49236559,61141874,99690194,16054533,57020481,44479073,30139692,6525948,32274392,59371804,69641513,69848388,54517921,22113133,37659250,43506672,7517032,61712234,76315420,65047700,79738755,95581843,38510840,44784505,36396314,54232247,37280276,93359396,16684829,84406788,21533347,65081429,36685023,86001008,26664538,45075471,40027975,92787493,34044787,92530431,66885828,44627776,9829782,78300864,45381876,62430984,30998561,824872,1375023,62051033,90654836,28787861,5073754,2331773,17081350,82532312,13468268,15064655,9188443,10649306,49882705,63152504,85695762,9599614,16424199,24168411,83533741,53632373,89996536,55850790,70782102,92353856,24953498,84349107,84127901,4508700,99524975,56461322,34946859,36468541,26102057,47213183,51315460,4978543,28734791,36808486,86118021,92071990,78785507,22766820,93057697,57658654,87598888,47298834,1239555,6157724,82327024,88251446,17385531,79942022,22200378,21397057,22435353,45794415,86460488,7687278,48658605,44473167,3088684,14363867,12303248,73109997,45407418,8129978,46851987,66271566,83133790,17365188,44664587,60393039,20836893,64602895,57359924,9259676,19457589,8729146,4434662,89217461,55960386,78766358,7066775,40781854,91938191,4069912,51426311,12664567,36135,59981773,3487592,26863229,36933038,11757872,41442762,55615885,61741594,71469330,5267545,7685448,79322415,54868730,80316608,6111563,76825057,69697787,32699744,82427263,45049308,8128637,29516592,56955985,90004325,83269727,17539625,59318837,29510992,74441448,94595668,83747892,27625735,81774825,77301523,20681921,48088883,71965942,18504197,4091162,37224844,68110330,749283,58224549,54263880,4204661,38256849,29635537,30811010,58180653,92867155,34667286,59405277 +39801351,17957593,35092039,43376279,64848072,30764367,79657802,92692283,8807940,96726697,38061439,70596786,97940276,99658235,85571389,14349098,75153252,74724075,91802888,73124510,13231279,40197395,5111370,64602895,13470059,2717150,112651,77312810,65074535,98371444,58749,93933709,16019925,36580610,96193415,77620120,49641577,51315460,26292919,26734892,66428795,95726235,35192533,15535065,10358899,89996536,69255765,55669657,25842078,89419466,47090124,83083131,13862149,65454636,72738685,69641513,95957797,23569917,89214616,6793819,47708850,19939935,75229462,44348328,17068582,62452034,44481640,91831487,64055960,53842979,90272749,29994197,83133790,46540998,9259676,19891772,9886593,4064751,78218845,17365188,74614639,84187166,38494874,60102965,59197747,66250369,36845587,32161669,99021067,54232247,48774913,62051033,96709982,62430984,73031054,168541,73392814,415901,64087743,68102437,11581181,55753905,29029316,61741594,61859143,51464002,77413012,75820087,14045383,14947650,29065964,20645197,89046466,79922758,22942635,569864,18699206,29401781,53802686,51715482,4668450,48360198,3233084,42644903,23194618,77272628,50668599,45075471,10309525,62740044,72278539,10961421,9175338,76825057,15148031,61859581,12416768,30090481,89811711,7517032,22766820,34432810,43506672,37891451,874791,73235980,97379790,9603598,47792865,4119747,29834512,99861373,6986898,749283,7104732,24591705,86821229,67793644,10453030,77301523,33565483,93270084,33628349,87598888,29466406,40686254,36505482,84002370,28851716,54762643,21993752,57163802,47361209,19101477,26998766,78549759,61897582,16445503,29959549,61271144,39171895,48675329,1204161,18001617,54663246,48892201,87710366,3773993,67084351,29510992,14326617,61373987,4798568,7011964,76170907,72358170,75052463,44844121,74357852,69579137,26275734,38658347,91957544,75543508,68644627,77377183,26392416,15536795,83651211,89637706,57359924,9257405,4434662,34698428,72274002,92692978,84684495,76540481,3487592,53666583,45428665,40984766,19898053,5832946,39201414,45667668,92033260,76703609,9623492,38008118,45790169,70541760,50188404,54987042,62693428,26776922,8696647,82532312,93790285,16387575,33304202,83150534,23848565,6871053,9398733,4099191,30787683,49597667,51588015,89078848,30694952,28550822,6819644,92215320,80251430,79880247,57803235,66885828,70367851,63967300,12348118,4985896,61263068,68000591,7955293,51426311,44842615,81853704,45848907,92541302,44784505,43152977,66137019,6525948,247198,62357986,13348726,70800879,37192445,33201905,29188588,15015906,94360702,78602717,35996293,63152504,53632373,69355476,36753250,17976208,98462867,54427233,44245960,27325428,27185644,8733068,8913721,19486173,43322743,65338021,5640302,8791066,20765474,30395570,9860968,57658654,14093520,34295794,63372756,23134715,83533741,72793444,65271999,78589145,7687278,40027975,12303248,29819940,68702632,1250437,95395112,74452589,60430369,99729357,70004753,14626618,92554120,91281584,15163258,72495719,33336148,54058788,6808825,36808486,67451935,29932657,40677414,44847298,78909561,59177718,38645117,24058273,37620363,52204879,3509435,42967683,58180653,68816413,37280276,80014588,89217461,50007421,16054533,36685023,91938191,22129328,26744917,8055981,26493119,82897371,18131876,83948335,79942022,88251446,99297164,27665211,56153345,80316608,8115266,73617245,2204165,96735716,36189527,85023028,94911072,1022677,82052050,10366309,77284619,42307449,71657078,80555751,99524975,3880712,7300047,51590803,14220886,81172706,86001008,79191827,72725103,21001913,45996863,9188443,82779622,55470718,99515901,62803858,508198,23432750,57241521,86022504,99965001,56424103,87160386,88653118,50702367,85242963,97561745,44889423,72614359,8129978,1431742,321665,7685448,17539625,16971929,17386996,71965942,34497327,83789391,36780454,58208470,48260151,32699744,30998561,2208785,13819358,22108665,3294781,5482538,79136082,21533347,17727650,30463802,42782652,94539824,97783876,92398073,92998591,46851987,88698958,82979980,62496012,41092102,33237508,66319530,76330843,52293995,20885148,8099994,81898046,34698463,55189057,45990383,37560164,52613508,90310261,26114953,16424199,26063929,36930650,22997281,32159704,8373289,11757872,73168565,40865610,99549373,70727211,44177011,45049308,94516935,37435892,41245325,60106217,91255408,54517921,66271566,42073124,68128438,71083565,6505939,95290172,47213183,90090964,21673260,24953498,11365791,37183543,45237957,71333116,81677380,16097038,52261574,9860195,87720882,66832478,90061527,51149804,39373729,65275241,74110882,81805959,41481685,84127901,51047803,54263880,56531125,47893286,30569392,75777973,12664567,56484900,24733232,89499542,11543098,61982238,2917920,98943869,37224844,98739783,70420215,42692881,36812683,44479073,53084256,78561158,96420429,37166608,16380211,44983451,93515664,9575954,31126490,51472275,8651647,99226875,31727379,76960303,2607799,45306070,55247455,74137926,70036438,82339363,61712234,53547802,71316369,55602660,46870723,42947632,40534591,68694897,5267545,49328608,26139110,75986488,92867155,86118021,73222868,19376156,35456853,93057697,64098930,11923835,34044787,82327024,66611759,77187825,73786944,99125126,1197320,75135448,20568363,4787945,63628376,22450468,43045786,57248122,59318837,7919588,40356877,9829782,43501211,1375023,65851721,45617087,65017137,78884452,15075176,30218878,21070110,60955663,84349107,20122224,36312813,37659250,526217,18504197,7502255,34493392,18466635,81274566,17146629,86301513,67281495,67474219,30771409,68824981,96852321,32590267,41092172,34667286,39847321,11161731,77300457,85711894,65715134,88047921,30811010,21397057,66663942,76671482,12571310,62490109,2891150,14363867,33431960,7423788,44627776,59109400,55143724,36135,51466049,69697787,98948034,38256849,16405341,62762857,57020481,38341669,20486294,30543215,16684829,74743862,55960386,98130363,74441448,3235882,4204661,52060076,63506281,54014062,31733363,62428472,61141874,14723410,60176618,44473167,40152546,22200378,18783702,24619760,16595436,20836893,72777973,73109997,10597197,63015256,3233569,27187213,22435353,18663507,88130087,20681921,86460488,59614746,47887470,38022834,99333375,70782102,33553959,23740167,39986008,94076128,4806458,60581278,68939068,66667729,28787861,98653983,86242799,84293052,77072625,20642888,68316156,66322959,61623915,53648154,22405120,84166196,22879907,82726008,90654836,32426752,83155442,93359396,59910624,1569515,97057187,66045587,45794415,6157724,57240218,18833224,51507425,32250045,86543538,88904910,7066775,55770687,92803766,44550764,65047700,64157906,83210802,48893685,45407418,36396314,33123618,30366150,40781449,85116755,79821897,68041839,3088684,69786756,83368048,47738185,24826575,80246713,13468268,98648327,72357096,89699445,99604946,67227442,6038457,54606384,8634541,66903004,44915235,32151165,65880522,38510840,33935899,93053405,61960400,16099750,7646095,4091162,12024238,93562257,19457589,31491938,10649306,82427263,95010552,42237907,72732205,17058722,84840549,40781854,83302115,14731700,4069912,6153406,1239555,26507214,62232211,60393039,67124282,824872,55850790,6221471,27411561,17764950,76315420,39553046,71469330,36933038,81855445,37739481,6497830,95581843,33797252,8729146,76434144,89804152,92353856,79322415,13173644,45381876,35512853,91240048,69848388,78785507,6521313,31161687,77787724,62115552,43933006,37501808,56473732,26863229,44060493,91727510,47298834,62936963,59501487,25636669,50806615,18411915,71920426,57961282,59981773,4095116,61928316,72019362,99224392,69136837,17857111,48088883,17016156,45995325,48673079,54868730,4508700,99690194,28734791,90158785,27041967,84406788,68110330,37957788,96906410,9058407,44664587,70372191,37675718,24314885,26664538,56515456,9599614,16567550,24915585,32058615,20867149,61815223,90457870,17894977,15948937,91664334,50567636,53888755,56461322,97011160,36139942,49236559,82178706,97641116,71300104,10264691,30653863,96318094,43357947,67030811,58751351,53393358,36468541,17037369,92071990,78766358,33061250,82886381,18806856,45919976,85117092,33895336,65081429,71522968,93566986,38009615,176257,65038678,8128637,28796059,48395186,91141395,15064655,4199704,30139692,79738755,48658605,86798033,27625735,49598724,4978543,72373496,41380093,31904591,24168411,5073754,79806380,75407347,23110625,83269727,84904436,34946859,82651278,33249630,20140249,67513640,94711842,94595668,15902805,26102057,90004325,88444207,7182642,29635537,21289531,59371804,75128745,91990218,69605283,5970607,669105,66116458,61728685,17385531,3633375,3183975,94090109,49271185,34236719,6111563,41442762,62552524,32274392,97281847,22721500,22113133,30891921,54199160,67031644,19272365,56955985,33699435,59329511,1788101,63059024,78300864,42199455,77694700,38365584,58224549,55615885,29516592,59405277,99971982,92604458,92787493,4515343,28928175,5822202,92530431,7229550,90013093,26397786,49882705,17081350,21919959,73021291,99917599,44846932,83747892,68204242,30163921,81774825,68875490,2331773,57393458,85695762,20002147,77898274,1936762,95251277,72238278 +73124510,67793644,45407418,17764950,36505482,13173644,9886593,21993752,18833224,95726235,51149804,76540481,96193415,92033260,40152546,44842615,19939935,16971929,89214616,38061439,32426752,54663246,56461322,55247455,20765474,26139110,37620363,1936762,61859581,42073124,44481640,49641577,77284619,415901,14093520,40781449,15064655,97379790,86001008,82532312,53632373,31733363,61263068,72357096,59501487,76960303,63059024,54987042,52613508,73235980,75543508,8651647,78909561,30764367,44784505,91664334,55960386,99524975,72793444,59177718,35092039,73031054,8807940,45381876,14947650,90004325,34044787,24619760,84187166,45995325,88653118,91990218,10649306,14626618,62452034,81855445,89078848,15015906,55753905,99690194,73786944,99226875,90013093,16097038,93933709,4099191,84002370,35192533,29466406,32699744,71316369,1197320,36780454,44889423,55470718,47893286,24591705,18806856,7229550,85023028,56153345,17386996,18663507,2891150,66045587,66137019,99297164,92692978,63372756,9599614,88444207,31126490,95395112,29065964,26392416,87598888,71522968,18504197,22942635,57241521,68875490,26292919,41380093,79806380,44479073,51047803,64157906,20122224,93566986,62496012,53802686,508198,14349098,18466635,36808486,82979980,2917920,40027975,83133790,66903004,21070110,92215320,44664587,54232247,27665211,99917599,95290172,25842078,88130087,59197747,3233569,17976208,17068582,62430984,9175338,71920426,7011964,89217461,37675718,17539625,17365188,92867155,2204165,33123618,8733068,49328608,45667668,32159704,54427233,80014588,30569392,70036438,77620120,26863229,86460488,68816413,81274566,53547802,16445503,94360702,9623492,47708850,36135,6986898,66428795,10366309,7955293,66271566,36580610,87710366,26114953,37183543,78785507,62803858,22450468,66322959,55602660,45075471,2331773,39553046,42307449,56484900,24058273,92803766,39847321,27325428,50806615,45794415,16405341,80316608,76825057,57803235,74614639,30998561,99604946,58180653,19486173,28928175,44983451,43376279,61728685,68939068,51464002,82052050,18411915,22997281,40677414,98739783,12348118,69697787,6153406,66832478,37891451,1431742,82897371,64602895,97940276,72278539,48088883,36753250,99515901,82339363,92530431,321665,15535065,44177011,29959549,32058615,4095116,5640302,13348726,94539824,60430369,83533741,59318837,99965001,83269727,70420215,45919976,569864,64848072,54517921,65074535,65851721,46851987,91831487,78561158,75777973,29834512,85116755,90090964,30771409,51426311,91802888,31904591,60106217,17385531,33431960,16019925,40984766,65271999,18131876,78589145,16099750,87160386,74110882,40356877,53666583,71300104,32161669,41442762,69641513,34236719,31491938,72738685,38009615,71469330,56531125,49882705,37501808,41245325,1250437,70596786,26397786,4064751,26998766,11161731,42692881,9829782,36933038,8729146,43152977,75153252,54606384,36930650,22405120,62936963,54058788,77301523,4199704,10358899,85242963,72274002,83789391,77312810,58749,9259676,78218845,30891921,29994197,95957797,23134715,74137926,32250045,6808825,60102965,35456853,63015256,42644903,8373289,24314885,34295794,45790169,51507425,72373496,24826575,68000591,96906410,47792865,33699435,98948034,98130363,61928316,21673260,72732205,17957593,669105,112651,77072625,43322743,7687278,66663942,4798568,27625735,33201905,61141874,65275241,99971982,54868730,92787493,27187213,53648154,88251446,30543215,79922758,81898046,20867149,44846932,3633375,9575954,94076128,71657078,11365791,49236559,15536795,51472275,26063929,62552524,80246713,31161687,33336148,90310261,38645117,14731700,92554120,4806458,2208785,76330843,97281847,48260151,62740044,80555751,17857111,54762643,42782652,61982238,98462867,90654836,57240218,39171895,7517032,40781854,86118021,59371804,4119747,15902805,6505939,83210802,8634541,63967300,68644627,68316156,54263880,10309525,61623915,96726697,96318094,61960400,78602717,98648327,40534591,21001913,7104732,6793819,94711842,37739481,72614359,2607799,73392814,69255765,17058722,37659250,83150534,61859143,39986008,22108665,13468268,67513640,40865610,76703609,36396314,60955663,36812683,72019362,84293052,68694897,38658347,9058407,94090109,85117092,50188404,33061250,41092102,92353856,22129328,66667729,69136837,81853704,10961421,23194618,92604458,26734892,9257405,67474219,60176618,12571310,62693428,19891772,93515664,96735716,33237508,18699206,30090481,86242799,81805959,16380211,61271144,79136082,67451935,90457870,56424103,99333375,68204242,85571389,89996536,24953498,33797252,33895336,53842979,68128438,72725103,69355476,61373987,3088684,66611759,47361209,77413012,47887470,9188443,45990383,93053405,1022677,824872,99224392,8115266,70372191,30139692,99861373,14220886,99549373,34698428,10597197,99658235,38008118,55189057,64098930,70727211,44847298,73617245,36312813,50007421,39801351,93270084,526217,97561745,16054533,34497327,8696647,17894977,89046466,3773993,72777973,48360198,37166608,33628349,83948335,31727379,52261574,30787683,66116458,76434144,12416768,74724075,83368048,4204661,75128745,34493392,53888755,78766358,5482538,81677380,35996293,92998591,15163258,36189527,98371444,90272749,67030811,67281495,5111370,4668450,29188588,20140249,168541,44844121,43501211,10264691,45617087,87720882,73222868,5832946,54014062,4787945,26776922,28550822,72495719,50567636,20645197,50702367,49598724,3294781,65880522,66885828,65715134,76315420,58208470,9398733,79821897,51590803,61815223,247198,6038457,48892201,72358170,45848907,57658654,50668599,28787861,8129978,22200378,78549759,3183975,7423788,64055960,65017137,99125126,99729357,91281584,47090124,11543098,52204879,57248122,30218878,44627776,7300047,45237957,48675329,44550764,55669657,66250369,79738755,7182642,7685448,23848565,82327024,97011160,14326617,57961282,82178706,8099994,61741594,36468541,94911072,26275734,12664567,75135448,37435892,3509435,45428665,12024238,18783702,76671482,82427263,30395570,62232211,34946859,29635537,29510992,23110625,36685023,51466049,82651278,85695762,30463802,90158785,44060493,89811711,97783876,15148031,5970607,48893685,27411561,93359396,68702632,38022834,97057187,51715482,68102437,37192445,47298834,30366150,40686254,43045786,75986488,6871053,67227442,70004753,77272628,88047921,3233084,70800879,77187825,94595668,16595436,74452589,30653863,26102057,19457589,52293995,5267545,91141395,71333116,20568363,22879907,27185644,78884452,68824981,3487592,1788101,9603598,4069912,20486294,91957544,92398073,77694700,58224549,61712234,89699445,95581843,54199160,73168565,56473732,10453030,7919588,65338021,77787724,55850790,63628376,7066775,48395186,49271185,89637706,22766820,71965942,81172706,33553959,42967683,55143724,1204161,46870723,75407347,89804152,83651211,62115552,38341669,99021067,89419466,42947632,38494874,56955985,62051033,92071990,6525948,16567550,6221471,14045383,19272365,81774825,82726008,13819358,52060076,28734791,33935899,34667286,57393458,72238278,32151165,47213183,24733232,11757872,24915585,86798033,84349107,34432810,93562257,73109997,83155442,21919959,3880712,92692283,16387575,67124282,41092172,55770687,4508700,30811010,84406788,82886381,11581181,58751351,14363867,15948937,51315460,68041839,69786756,5822202,62428472,29932657,65454636,53084256,51588015,57359924,92541302,56515456,19101477,30694952,65038678,17081350,17037369,35512853,22721500,86301513,70367851,64087743,60581278,26507214,83302115,85711894,7502255,65081429,2717150,59405277,95251277,27041967,6497830,37957788,44348328,3235882,90061527,20642888,75229462,39373729,44473167,38365584,74357852,96709982,16424199,29029316,6111563,9860195,20836893,5073754,89499542,91727510,84684495,68110330,8913721,1569515,7646095,57020481,83747892,4091162,62490109,57163802,33304202,6521313,69605283,26493119,75052463,12303248,23740167,61897582,84904436,4985896,96852321,29819940,26744917,66319530,69579137,59109400,17016156,96420429,77300457,19898053,63506281,19376156,82779622,88904910,79191827,22113133,91938191,9860968,29516592,86543538,84127901,28851716,42237907,48774913,34698463,59981773,13231279,98943869,47738185,98653983,37280276,22435353,26664538,84840549,77377183,59910624,13470059,21397057,45996863,46540998,24168411,25636669,15075176,20002147,70541760,48673079,75820087,43933006,23569917,79657802,74743862,65047700,69848388,71083565,16684829,79942022,62762857,176257,93790285,13862149,33249630,28796059,62357986,59329511,21289531,95010552,45049308,42199455,86022504,32590267,53393358,21533347,11923835,4978543,8791066,91255408,44915235,32274392,55615885,39201414,49597667,37560164,17146629,20885148,74441448,77898274,6819644,874791,91240048,4434662,73021291,44245960,67031644,36845587,6157724,1375023,94516935,76170907,14723410,40197395,63152504,70782102,60393039,45306070,749283,38256849,8128637,59614746,4515343,23432750,93057697,17727650,79880247,97641116,86821229,37224844,1239555,29401781,30163921,8055981,84166196,79322415,48658605,88698958,41481685,83083131,18001617,20681921,36139942,67084351,43357947,43506672,80251430,33565483,78300864,38510840 +50188404,26392416,63628376,92692978,12024238,55189057,81677380,81898046,3233084,93053405,71333116,76170907,91664334,69579137,42692881,77620120,4434662,23194618,34295794,7011964,31126490,35092039,12348118,13231279,7300047,72274002,22879907,79880247,65454636,73235980,33201905,62496012,60581278,37560164,20642888,33304202,30395570,53666583,70596786,98943869,66611759,66116458,12664567,44245960,61982238,37501808,62803858,85571389,64098930,95290172,33249630,83150534,34667286,55247455,526217,67793644,93057697,31161687,18699206,95010552,99658235,29834512,47792865,79136082,55143724,22766820,79821897,39847321,27041967,86301513,99515901,66137019,247198,61373987,30764367,86543538,6525948,508198,41092102,74357852,70727211,17727650,749283,5832946,4064751,62232211,49597667,64848072,112651,57241521,73031054,16019925,4204661,78300864,38008118,48892201,78884452,74614639,68041839,96193415,39201414,2717150,45996863,51464002,43501211,81172706,26493119,39986008,50668599,72725103,67124282,89214616,40152546,98653983,25842078,59614746,88047921,65047700,17976208,6153406,67227442,66667729,98371444,22108665,6808825,4668450,68816413,83269727,669105,1022677,59371804,76315420,27325428,22942635,64087743,47361209,14626618,98462867,85116755,22200378,60955663,47090124,45794415,9623492,37435892,97057187,59197747,72373496,93933709,53802686,62452034,97561745,88444207,38022834,50567636,18833224,62552524,88698958,14947650,32590267,55470718,44627776,90013093,84406788,5111370,20836893,40356877,30998561,7919588,70036438,27665211,6505939,63967300,61960400,78909561,86798033,26102057,43933006,47738185,45237957,3880712,62051033,70420215,24733232,66885828,91831487,45306070,8099994,86022504,8696647,18411915,42967683,99333375,48774913,86001008,51590803,44983451,43322743,1197320,46540998,20140249,22113133,92398073,90457870,98130363,95395112,52613508,54517921,92554120,16595436,8129978,61271144,43376279,415901,15902805,97783876,31733363,20568363,84840549,62428472,26744917,54868730,95726235,99224392,65081429,23134715,55753905,59981773,38645117,10961421,18504197,30163921,70367851,73222868,67031644,73168565,20002147,17764950,13862149,9860195,31491938,44842615,62936963,40984766,88653118,62430984,35996293,51047803,30463802,65271999,4069912,17385531,3487592,7229550,66250369,10358899,53632373,95957797,29516592,80014588,76540481,37620363,6497830,30139692,68204242,2891150,40027975,57240218,36189527,37224844,68939068,2917920,77284619,5970607,61741594,66903004,34236719,74137926,54199160,57961282,42199455,36780454,99524975,29466406,60102965,33237508,30891921,77312810,24915585,98648327,22435353,92071990,56531125,87598888,22129328,16445503,7066775,37739481,48088883,49641577,20681921,81853704,55669657,8651647,99125126,79657802,19272365,15148031,70004753,45919976,7685448,82779622,89078848,27411561,36505482,29029316,77072625,37891451,70800879,64602895,30218878,77187825,61623915,91957544,54263880,15535065,54232247,90004325,29188588,61859581,75153252,42073124,96709982,83210802,14349098,83083131,26664538,89637706,45428665,8807940,3509435,72238278,17068582,13470059,20122224,36753250,30694952,21993752,59910624,99917599,42782652,82886381,17081350,29510992,2331773,89217461,84127901,33797252,49328608,58180653,84684495,33061250,75543508,61728685,40197395,68102437,92998591,85117092,69255765,26275734,9829782,56515456,59109400,89804152,78589145,15536795,29401781,31904591,36808486,91802888,82339363,14093520,19486173,77413012,39801351,96420429,4508700,19891772,49598724,39171895,60430369,76330843,57020481,69355476,67030811,17957593,91938191,80251430,51588015,54014062,99604946,19939935,77301523,36845587,57163802,72358170,6111563,6793819,78602717,59318837,22721500,97011160,53393358,6038457,82427263,30787683,40686254,48260151,44473167,94516935,18131876,10366309,9257405,46851987,90272749,11543098,72614359,16380211,3633375,8055981,48395186,32159704,84187166,16684829,1204161,53648154,58749,93359396,3233569,48675329,36933038,84166196,98948034,79738755,83789391,99297164,9886593,77787724,94090109,8729146,57393458,75135448,94595668,65074535,54058788,89699445,54663246,13173644,57248122,10453030,75820087,19101477,4099191,61263068,67451935,59177718,99965001,51715482,14363867,17146629,14045383,17016156,73786944,38365584,22997281,43152977,44784505,17365188,4095116,10309525,15075176,6521313,9259676,36139942,74441448,96735716,74743862,57803235,41092172,47213183,26507214,10264691,10649306,78766358,66322959,44348328,24591705,9603598,65715134,24826575,7955293,38341669,51507425,18001617,16099750,55960386,569864,18806856,55602660,8115266,48360198,29994197,82052050,78549759,90654836,16567550,43357947,80555751,14326617,67474219,91727510,20486294,4985896,81774825,14220886,87720882,37183543,6986898,68644627,44060493,44844121,26998766,36312813,36812683,77300457,77694700,66832478,28550822,68702632,9188443,21673260,65851721,92803766,59501487,44889423,32161669,62490109,71316369,96852321,92541302,4806458,75986488,17894977,88251446,3183975,83533741,57658654,29819940,16097038,44177011,89046466,7423788,76825057,28734791,73124510,30771409,91141395,99971982,89996536,5073754,82178706,9058407,76671482,45049308,18783702,69641513,39553046,7502255,69136837,92604458,72019362,1431742,92692283,26863229,7646095,74724075,93562257,99549373,49236559,43045786,38061439,33895336,47708850,21001913,45617087,58751351,64055960,57359924,72357096,33628349,33935899,48658605,24619760,36685023,85242963,17386996,21533347,47893286,29065964,79922758,17857111,99226875,49271185,99729357,4119747,62693428,68824981,45407418,66045587,30543215,8913721,33431960,23740167,69697787,89419466,85711894,94076128,84904436,53842979,73021291,97641116,80316608,71657078,43506672,34698463,26292919,8634541,45995325,49882705,53084256,90061527,48893685,66271566,75052463,36580610,44847298,65275241,62740044,34698428,40534591,9575954,92353856,89499542,30653863,69786756,34497327,55850790,38658347,72732205,36930650,71083565,95251277,91990218,16971929,16424199,67513640,68128438,4978543,14723410,5822202,22450468,44915235,62357986,42307449,83302115,67281495,93270084,75777973,35192533,75229462,15163258,23848565,33565483,35512853,50806615,1250437,42237907,23432750,36135,84349107,40781854,56153345,4515343,44481640,83155442,33123618,41481685,26397786,71965942,72278539,29635537,13819358,20765474,15064655,86242799,83651211,63506281,73392814,45667668,17037369,61859143,24168411,71920426,50702367,79806380,34493392,52204879,75128745,21397057,40781449,76703609,13468268,23569917,44479073,86460488,6871053,11923835,99861373,66319530,18663507,90158785,91240048,26139110,69848388,32699744,1936762,28928175,46870723,6221471,55770687,98739783,82532312,5482538,63059024,45381876,38256849,44550764,70541760,29959549,45848907,92033260,29932657,8791066,30569392,81855445,5640302,12303248,88904910,32274392,3088684,40677414,72793444,30811010,28851716,93566986,56424103,3294781,78561158,824872,84293052,59329511,54606384,19898053,9398733,67084351,80246713,45990383,82651278,3773993,92215320,2204165,86821229,61815223,95581843,51472275,61928316,40865610,17058722,75407347,87710366,68694897,51426311,21070110,97940276,7687278,4787945,97379790,1788101,16054533,65038678,58208470,51149804,168541,52261574,68000591,77272628,73617245,22405120,20867149,26063929,37659250,50007421,26734892,56955985,83747892,47887470,3235882,72495719,53888755,89811711,81805959,55615885,45075471,86118021,874791,44664587,70372191,12571310,52293995,79322415,1569515,91255408,9175338,24058273,26114953,63015256,74110882,20885148,4798568,61712234,87160386,71469330,83948335,84002370,9860968,94539824,93790285,78218845,63152504,44846932,41442762,2208785,48673079,176257,76434144,90090964,68110330,8733068,65017137,82897371,64157906,10597197,60106217,11581181,96726697,35456853,28796059,58224549,82979980,76960303,54427233,17539625,53547802,11757872,38009615,65880522,13348726,37192445,96318094,91281584,51315460,99021067,78785507,42947632,7182642,97281847,14731700,12416768,7104732,62115552,38494874,1239555,321665,30090481,69605283,63372756,39373729,19457589,19376156,92867155,27625735,52060076,51466049,60176618,5267545,30366150,41380093,61141874,33699435,15948937,41245325,32250045,45790169,11365791,70782102,36396314,54987042,33553959,99690194,37166608,56484900,82726008,25636669,54762643,60393039,18466635,27185644,85695762,81274566,73109997,6157724,31727379,61897582,72777973,79942022,32426752,56461322,77377183,71522968,66663942,85023028,15015906,20645197,34946859,26776922,8373289,1375023,16387575,82327024,56473732,72738685,34044787,21919959,4091162,47298834,79191827,83368048,62762857,7517032,37675718,66428795,16405341,11161731,74452589,6819644,4199704,59405277,2607799,28787861,71300104,92530431,42644903,8128637,65338021,94711842,33336148,96906410,21289531,68875490,38510840,36468541,77898274,37280276,90310261,32151165,24953498,23110625,94360702,94911072,32058615,68316156,88130087,83133790,92787493,93515664,37957788,34432810,24314885,27187213,9599614 +55143724,39801351,40677414,63967300,81898046,42199455,96726697,51047803,87160386,22879907,46851987,56424103,56473732,53648154,43933006,29994197,7423788,4099191,29819940,46540998,39847321,2717150,16097038,80316608,5822202,47090124,16054533,35996293,80555751,64087743,7502255,84293052,36580610,33553959,8099994,40984766,43501211,19891772,59910624,9175338,97641116,71083565,7300047,68204242,65081429,21533347,77413012,83155442,66663942,29834512,14349098,89804152,53842979,91727510,81172706,87720882,37659250,34236719,27411561,38658347,45237957,66428795,79136082,94360702,9860195,63506281,11365791,61960400,77898274,26507214,62740044,75820087,14326617,23110625,57803235,83368048,31161687,94090109,99965001,15064655,7182642,60581278,88047921,74614639,89046466,99224392,13468268,13862149,22108665,50188404,30139692,65454636,9860968,86821229,6157724,16971929,99515901,21070110,18466635,85116755,14947650,3235882,69355476,50007421,95957797,44550764,83210802,3633375,72357096,72495719,97783876,99524975,62452034,39986008,23194618,47887470,71657078,1431742,77284619,17539625,44844121,70727211,9603598,31491938,30163921,22129328,23848565,45990383,98462867,12348118,83789391,176257,61859143,57241521,66045587,59177718,3773993,77272628,18806856,62936963,33249630,44060493,82339363,70372191,67793644,60430369,75777973,5111370,89214616,91831487,79657802,96193415,44842615,61741594,65017137,37192445,15535065,32274392,3088684,526217,58224549,92398073,86301513,6986898,86460488,29959549,73392814,26998766,30787683,58749,48673079,30569392,54987042,30218878,76434144,93562257,6793819,43322743,75153252,52293995,62490109,24058273,37183543,94076128,33237508,91664334,7646095,96852321,20642888,41481685,50806615,65338021,20765474,29466406,90158785,62051033,20836893,40534591,68816413,85571389,73168565,55753905,32161669,92803766,24733232,17016156,17081350,99658235,36505482,89078848,19376156,91802888,36845587,67474219,72019362,38494874,19486173,12024238,49641577,31126490,90457870,1788101,30463802,53666583,59614746,82979980,25636669,57359924,74137926,508198,50702367,69136837,42782652,52204879,95581843,36780454,21673260,44983451,70036438,71316369,61623915,69605283,20486294,6808825,70596786,37739481,9623492,32159704,20002147,8651647,62232211,874791,40152546,94711842,21001913,88904910,35192533,77377183,16405341,23569917,6153406,55247455,89699445,73235980,3880712,2208785,98371444,5482538,68041839,28796059,168541,4095116,247198,19101477,53084256,3233569,83533741,39201414,27665211,62552524,76671482,37675718,74743862,78589145,63628376,88653118,34295794,22997281,1022677,73786944,57240218,43376279,33304202,8115266,80251430,45919976,76540481,5267545,91957544,67451935,93566986,17068582,4064751,37620363,3233084,20867149,51588015,9058407,47361209,17764950,30998561,4668450,49271185,35456853,86022504,74110882,91240048,94516935,26664538,89419466,36312813,33565483,78218845,18001617,15148031,15163258,45306070,68875490,57393458,99549373,4119747,51590803,93933709,26776922,2917920,61815223,63372756,69786756,68644627,8791066,13173644,98948034,40356877,17957593,34497327,36930650,42644903,67030811,76315420,36753250,82651278,669105,2891150,36139942,16387575,1239555,26275734,91141395,90004325,71333116,64848072,36396314,97561745,47792865,12571310,66322959,24591705,45407418,57248122,10366309,84904436,6521313,77787724,86242799,75407347,62115552,85242963,49328608,97379790,98130363,81853704,62496012,14220886,53632373,32590267,75052463,16380211,18699206,18663507,75543508,84684495,44627776,72274002,99729357,52060076,26102057,22721500,6871053,26493119,44481640,98739783,4434662,64055960,18833224,43152977,82427263,36808486,34698428,26392416,71300104,44177011,72278539,37957788,7104732,72777973,112651,68000591,6525948,4806458,10358899,92604458,82052050,57961282,49236559,45794415,58751351,46870723,79806380,72725103,7919588,24915585,1569515,93790285,78785507,94539824,8807940,77620120,33935899,18783702,61712234,99861373,53802686,90272749,5073754,70004753,61897582,84840549,76170907,82726008,89637706,2607799,66271566,28734791,97940276,14731700,79922758,72732205,22435353,99604946,92998591,30653863,81274566,35092039,55770687,82897371,29029316,55615885,48658605,66667729,97011160,4199704,45848907,78561158,10309525,19272365,70541760,79942022,13231279,63152504,75229462,4204661,39171895,6111563,94595668,75135448,44889423,9188443,33201905,45996863,97057187,27325428,53393358,5970607,65851721,78602717,86798033,569864,66319530,49597667,36685023,8696647,92541302,42307449,96735716,77694700,47738185,44245960,92787493,63059024,55470718,64602895,2331773,81805959,50668599,43357947,65275241,98648327,13819358,92554120,74724075,11757872,10961421,48675329,52613508,13470059,22200378,19457589,7685448,28851716,45617087,37166608,31904591,55669657,51715482,77187825,84406788,76330843,415901,22942635,12416768,14093520,99125126,55189057,56515456,42237907,62357986,48395186,74357852,82886381,76960303,44664587,73031054,83083131,95290172,47893286,11543098,7687278,8055981,8128637,68102437,40197395,15075176,38341669,48260151,59329511,17037369,21919959,66611759,22450468,66137019,2204165,60106217,30366150,30395570,99297164,38365584,51472275,54058788,20885148,81677380,44846932,85117092,76825057,32151165,96709982,88698958,77072625,99917599,33336148,61373987,22766820,9398733,47298834,18411915,9886593,11581181,93053405,1375023,63015256,21289531,28928175,78909561,59371804,70420215,5640302,68702632,80014588,30694952,82532312,60102965,34493392,19939935,89996536,72358170,92215320,67281495,14723410,22113133,48893685,99333375,26397786,36812683,18131876,24826575,51426311,68694897,56531125,16445503,54762643,52261574,26114953,38061439,32250045,38645117,91281584,30891921,92033260,28550822,26734892,20140249,59501487,4069912,3509435,24314885,4091162,68128438,90013093,73021291,3487592,321665,49598724,41380093,69848388,30543215,56153345,26292919,79821897,27625735,90090964,82178706,34432810,16019925,82779622,37435892,17894977,37224844,17386996,3294781,23134715,73124510,78884452,59318837,1197320,98653983,54663246,51466049,18504197,66116458,61859581,37280276,26063929,67084351,65074535,36189527,29188588,7229550,32699744,33431960,42073124,79191827,35512853,6497830,59981773,61263068,60955663,40781854,38256849,16099750,67227442,7955293,33797252,23432750,69641513,8373289,36135,71469330,55850790,10453030,83302115,4515343,10649306,65715134,65880522,54014062,6819644,55960386,83150534,75986488,26863229,51464002,56461322,80246713,44479073,17058722,24619760,15948937,3183975,6221471,59405277,11161731,84002370,44847298,95010552,98943869,20568363,34044787,1204161,59109400,93057697,33628349,57163802,40781449,42692881,64098930,45428665,70782102,95251277,50567636,73617245,62693428,78549759,44348328,92692978,82327024,45667668,62762857,65271999,54199160,95395112,9257405,4787945,17727650,8733068,93359396,31727379,21993752,72738685,86001008,88251446,12303248,99971982,87710366,62803858,85711894,20122224,66250369,61271144,66885828,9599614,16567550,69255765,9575954,99690194,81855445,69697787,42947632,79880247,95726235,38022834,90654836,27041967,68939068,84349107,33699435,45049308,83651211,48774913,7517032,93270084,71920426,33061250,6038457,48892201,43045786,4985896,93515664,14363867,29510992,14045383,23740167,90310261,44915235,91990218,45075471,74441448,10264691,78300864,81774825,41092102,11923835,38510840,68316156,70800879,37501808,48088883,54517921,16595436,85023028,51507425,83133790,15536795,37891451,17857111,44784505,47213183,29065964,67513640,40027975,86118021,65047700,30771409,92530431,39553046,61982238,83747892,62428472,76703609,62430984,72614359,53888755,13348726,99226875,73109997,34698463,749283,96906410,53547802,77312810,40686254,28787861,66903004,16424199,96318094,4508700,88444207,6505939,30811010,84166196,4978543,77300457,61728685,84187166,42967683,65038678,9259676,39373729,87598888,37560164,67031644,8634541,47708850,33123618,30090481,55602660,29932657,43506672,54232247,90061527,72793444,61928316,64157906,15015906,27185644,20681921,69579137,7011964,41442762,73222868,29401781,89811711,8913721,99021067,61141874,83948335,41092172,40865610,45995325,45381876,51149804,14626618,56955985,97281847,31733363,24953498,75128745,32058615,67124282,45790169,36468541,8729146,4798568,85695762,58180653,77301523,91255408,74452589,66832478,26139110,34946859,25842078,71522968,92692283,17146629,38008118,44473167,72373496,79322415,57020481,51315460,96420429,16684829,92867155,36933038,15902805,5832946,89499542,24168411,7066775,8129978,88130087,54606384,29635537,92071990,56484900,30764367,60176618,10597197,48360198,54868730,58208470,71965942,22405120,27187213,94911072,32426752,41245325,92353856,54427233,49882705,1250437,91938191,60393039,29516592,68824981,84127901,38009615,79738755,72238278,17385531,26744917,59197747,17365188,33895336,824872,19898053,34667286,86543538,54263880,83269727,1936762,78766358,9829782,70367851,17976208,89217461,21397057,12664567,57658654,20645197,68110330 +15535065,1022677,17894977,64087743,59614746,92554120,70004753,68128438,77300457,10309525,37659250,65715134,77284619,34295794,51715482,71657078,5111370,8696647,26493119,37183543,79136082,55770687,71333116,38494874,89214616,73168565,48893685,78218845,9623492,73031054,36505482,81853704,83789391,68644627,88653118,99658235,29834512,54606384,19898053,96726697,80246713,36780454,99917599,64098930,30653863,16380211,30787683,93790285,34698428,86242799,86022504,4434662,33628349,30139692,20486294,68875490,38658347,53666583,88047921,75229462,52293995,40865610,31904591,66832478,24058273,48658605,4091162,67793644,55143724,39801351,68824981,61960400,47887470,7300047,43045786,16684829,22879907,73786944,56424103,1250437,22405120,87160386,48892201,83368048,37739481,4064751,7687278,20867149,61623915,6986898,62740044,16595436,36396314,82886381,81677380,19891772,7229550,62762857,43322743,7646095,39553046,4099191,19272365,26114953,33304202,38009615,55470718,99125126,46851987,63015256,91938191,35092039,35192533,60430369,98653983,27665211,63628376,90013093,15075176,18131876,43152977,72373496,66667729,71469330,3633375,79738755,44889423,77694700,42237907,2208785,89419466,39373729,90457870,27625735,33797252,7919588,49236559,92692978,61373987,39986008,15148031,36930650,97641116,40677414,508198,53888755,30998561,96193415,52261574,42782652,59197747,44842615,24915585,54199160,30366150,66428795,13470059,99549373,66611759,65275241,82726008,91240048,21070110,70372191,43376279,63967300,50806615,59501487,4668450,75986488,2204165,99297164,47708850,97281847,27187213,76825057,6808825,1431742,168541,51426311,36812683,52204879,33249630,45428665,14626618,93057697,38008118,81855445,76703609,77620120,48673079,31733363,85116755,37620363,95010552,23432750,77787724,44245960,20885148,34698463,9259676,72278539,76434144,9886593,1239555,5482538,82339363,33237508,8634541,41380093,30218878,10366309,112651,29188588,49271185,68316156,43501211,34497327,8807940,21533347,874791,49328608,70596786,69848388,78589145,14326617,78549759,45075471,44983451,5640302,8128637,44060493,93053405,70420215,24826575,29819940,29959549,92215320,56515456,29994197,79657802,97940276,54014062,66271566,99224392,18663507,4119747,60102965,49598724,33123618,1375023,9860195,88130087,23110625,95726235,72495719,86001008,48360198,79922758,1204161,86821229,86543538,2607799,54263880,68000591,69786756,44847298,66319530,18783702,44177011,67031644,72777973,57240218,77413012,70036438,18806856,78766358,54427233,75543508,40027975,62430984,25636669,54868730,67451935,20836893,3487592,20122224,42307449,22450468,83150534,34493392,26776922,93933709,30395570,9829782,6038457,6505939,74614639,94516935,19101477,7517032,28928175,59329511,63152504,40781449,81805959,47298834,32159704,50702367,32161669,45990383,74110882,29466406,16971929,84002370,16054533,7066775,99861373,7955293,32590267,72614359,53842979,77187825,40152546,74137926,55189057,7104732,65454636,89699445,5073754,55247455,62936963,15064655,22200378,22997281,20765474,10358899,58208470,30163921,12348118,92604458,13231279,13819358,98130363,20002147,68041839,14093520,44550764,88904910,92353856,61897582,61859581,45995325,67281495,30090481,14723410,6221471,49641577,14731700,61741594,23740167,3088684,57803235,99965001,61859143,42967683,47090124,4204661,40534591,87720882,8651647,36135,62452034,26998766,83155442,72358170,73392814,91664334,76170907,569864,73235980,83210802,84127901,91802888,85023028,11757872,77272628,39171895,55615885,17016156,78884452,3235882,15163258,57163802,43933006,6521313,40356877,51047803,14947650,46870723,79191827,60106217,89637706,65851721,64602895,23569917,26734892,98462867,97783876,44481640,44664587,17068582,176257,57658654,5822202,50188404,19939935,62496012,29029316,526217,27325428,16445503,71522968,99690194,8913721,3294781,32274392,38256849,22129328,55753905,749283,75135448,52613508,2717150,67474219,69641513,61982238,80014588,6157724,75777973,60176618,26507214,70727211,26397786,17857111,65880522,9398733,16567550,64157906,17764950,20140249,6871053,66885828,4806458,59177718,85571389,247198,19457589,37280276,13862149,63372756,4515343,98948034,50668599,45381876,31161687,60955663,28796059,78300864,59910624,66903004,94539824,75153252,45306070,1197320,74724075,65038678,93566986,94090109,97379790,47738185,57359924,38061439,44846932,76960303,38341669,76671482,95957797,28550822,44627776,1788101,53802686,34236719,321665,33553959,62490109,32699744,26863229,68939068,65017137,99021067,62115552,66045587,34432810,70800879,86301513,45794415,79942022,45996863,26139110,43357947,42947632,81274566,53632373,32426752,42644903,61263068,18833224,78602717,89811711,34667286,16099750,20645197,98371444,73124510,6525948,12416768,30891921,92398073,27185644,2917920,7502255,72738685,13468268,67124282,92692283,81898046,90061527,7182642,90272749,47213183,89499542,95251277,69355476,96709982,5267545,61728685,15536795,28851716,5832946,80316608,82178706,30463802,37957788,6153406,68694897,41481685,79806380,23848565,66663942,77377183,61928316,82427263,55669657,10453030,18699206,12571310,26292919,21993752,31727379,16424199,24591705,96735716,56473732,64848072,88444207,30764367,29932657,62357986,63506281,76330843,33565483,97011160,17385531,9575954,3880712,59981773,93515664,45919976,94911072,57020481,48088883,17386996,2891150,80555751,99729357,37501808,51588015,14220886,55602660,94711842,18504197,51507425,17037369,9603598,54987042,10597197,31126490,61141874,51464002,36580610,415901,60581278,15015906,97057187,91831487,4985896,62803858,85711894,92998591,2331773,58751351,67227442,16097038,71083565,41092102,21001913,34044787,79821897,67030811,11923835,74357852,89217461,82052050,669105,68816413,46540998,24733232,18001617,56484900,72238278,73222868,19376156,43506672,62051033,98648327,3233569,58224549,22435353,23194618,17058722,7011964,70782102,44473167,33336148,83533741,75128745,16405341,8129978,42692881,11365791,32151165,69579137,29510992,77312810,82779622,8115266,99524975,85242963,22113133,33699435,82651278,47792865,68204242,33935899,84904436,61712234,40781854,75052463,98943869,84187166,36189527,16019925,91141395,10961421,86798033,17727650,44915235,90654836,17146629,62428472,45848907,28787861,92071990,56461322,54517921,53648154,71316369,92787493,55850790,69255765,17539625,39847321,51149804,44844121,48675329,22108665,72274002,13348726,16387575,68110330,68702632,72357096,30771409,34946859,92541302,66250369,41442762,26102057,49882705,23134715,82532312,10649306,51472275,6111563,30569392,88251446,57248122,50567636,8055981,15902805,77072625,85117092,65074535,21673260,53084256,68102437,21289531,3773993,97561745,8373289,39201414,36139942,88698958,92033260,90310261,9860968,93359396,45790169,95581843,8099994,29401781,45237957,26063929,6497830,3233084,33895336,47893286,92803766,66137019,45407418,65338021,87710366,63059024,9175338,19486173,35456853,20681921,78561158,66116458,7423788,61271144,47361209,17081350,59318837,62232211,26744917,33431960,83651211,38022834,37166608,81172706,26392416,77898274,1569515,69136837,44348328,61815223,99971982,84349107,90090964,56153345,11543098,95395112,4199704,89046466,48260151,17976208,91990218,57393458,99333375,96420429,22766820,25842078,53393358,83083131,54058788,54762643,9257405,86460488,7685448,82897371,57241521,11161731,40984766,13173644,89078848,54232247,91255408,31491938,96906410,20642888,42073124,56531125,65271999,37675718,3183975,73617245,9599614,84684495,27041967,37224844,78909561,36685023,36808486,17957593,1936762,54663246,45667668,71300104,49597667,83948335,41092172,37192445,93562257,14349098,99604946,71920426,44784505,27411561,70367851,50007421,21919959,36468541,4787945,40197395,48774913,4798568,84840549,14363867,82979980,72725103,44479073,58749,72793444,51590803,4095116,84406788,12303248,4069912,53547802,86118021,36753250,89804152,14045383,59371804,89996536,32058615,6793819,94360702,71965942,24168411,91281584,29635537,38510840,42199455,37435892,37891451,35996293,17365188,67513640,70541760,3509435,99226875,36845587,9188443,58180653,37560164,8791066,93270084,65081429,55960386,76315420,83269727,22721500,69697787,10264691,73021291,51315460,51466049,65047700,80251430,94076128,824872,83302115,29516592,62693428,72732205,92530431,38365584,83747892,38645117,45617087,32250045,83133790,56955985,36312813,76540481,81774825,75407347,33201905,24619760,79880247,4508700,30543215,74441448,21397057,66322959,5970607,77301523,82327024,40686254,79322415,9058407,78785507,85695762,91727510,95290172,74743862,36933038,64055960,99515901,8733068,69605283,67084351,12024238,33061250,75820087,92867155,4978543,18411915,8729146,22942635,12664567,59405277,26275734,29065964,94595668,35512853,59109400,28734791,15948937,62552524,57961282,98739783,20568363,24953498,30694952,24314885,73109997,45049308,41245325,96318094,87598888,18466635,91957544,6819644,72019362,11581181,52060076,84166196,26664538,90158785,30811010,90004325,74452589,60393039,48395186,84293052,96852321 +55470718,76540481,51047803,58749,30569392,26776922,39801351,17068582,50007421,18806856,77413012,36580610,76330843,49328608,43152977,8696647,34497327,68204242,66137019,6986898,63372756,99515901,88653118,68816413,72614359,45428665,85242963,26998766,44481640,15902805,78909561,93933709,44177011,14349098,21993752,53666583,8651647,20140249,65715134,96193415,26734892,89996536,63059024,35092039,38009615,58751351,74724075,69786756,63506281,7687278,86001008,61728685,9860968,95290172,9188443,72274002,31491938,53547802,77787724,27665211,96735716,4798568,57359924,34044787,82979980,62452034,29959549,36933038,3487592,30366150,93359396,97783876,92033260,16054533,62430984,14947650,3294781,95957797,43501211,49597667,69848388,77272628,20002147,80555751,29834512,6808825,9599614,89078848,77312810,99224392,96906410,99524975,57393458,61859143,65271999,50188404,2917920,9623492,35456853,75543508,75135448,38494874,50702367,36685023,83789391,4119747,89214616,415901,81172706,68000591,70367851,97281847,39847321,40677414,61859581,99861373,74743862,20836893,70596786,94516935,72357096,33336148,47361209,47090124,44348328,44889423,24058273,9603598,67281495,29466406,23194618,57163802,1431742,91802888,53648154,26063929,29932657,36780454,569864,50668599,59910624,77284619,72777973,86301513,46540998,72793444,874791,19486173,10366309,37957788,51426311,72019362,29065964,60106217,95395112,59177718,23569917,83651211,87720882,59197747,56424103,17957593,94539824,4515343,95010552,19939935,61263068,99729357,63967300,12571310,91957544,23848565,62490109,62936963,81853704,57961282,24591705,9886593,46851987,76960303,42782652,22721500,70036438,71657078,79136082,55960386,82178706,92787493,51466049,22129328,40197395,61897582,70420215,93566986,48893685,56484900,54014062,84127901,36505482,38008118,84684495,96318094,62496012,38022834,79942022,98943869,40027975,48675329,39986008,15064655,8055981,36312813,61623915,17539625,11757872,83948335,60102965,26392416,10358899,99658235,22997281,1569515,26275734,42307449,96726697,97641116,90310261,92803766,86821229,6038457,69641513,66428795,75986488,73392814,61982238,7502255,16380211,97561745,18411915,45407418,18466635,4099191,55247455,45075471,82427263,56531125,78589145,92604458,4668450,12664567,16567550,66271566,7011964,6505939,35996293,91938191,36930650,51472275,65017137,41092172,68041839,32161669,98371444,70372191,65074535,70541760,64087743,64055960,55602660,55753905,21533347,46870723,90004325,44983451,40152546,66667729,50806615,82052050,40534591,62740044,62051033,20765474,30463802,44473167,57241521,3880712,83155442,72725103,4508700,13173644,37183543,69579137,4095116,59371804,11543098,64848072,33249630,37891451,43376279,8807940,33061250,44842615,21673260,80246713,27411561,43322743,13231279,14220886,62762857,34432810,3509435,5111370,95581843,94090109,3773993,61960400,42692881,89217461,83368048,52204879,15015906,98948034,81677380,6793819,99549373,80316608,80251430,16099750,70782102,38658347,65338021,16097038,9575954,34493392,15535065,22942635,33431960,19891772,45990383,71469330,4064751,39171895,75777973,65454636,55669657,66611759,74137926,67030811,89046466,70004753,61373987,53842979,28851716,3088684,55770687,18699206,84187166,49271185,33935899,68644627,72495719,23432750,33628349,526217,79806380,20486294,77187825,16595436,1250437,14731700,37435892,17764950,62803858,5073754,91664334,37501808,88904910,32426752,48892201,42947632,5970607,33797252,1204161,33201905,27187213,34698428,48673079,49641577,73617245,92530431,3235882,14723410,9259676,92692978,43933006,5267545,73124510,38061439,33123618,88047921,13470059,21070110,32590267,669105,61141874,66319530,91240048,69605283,24826575,41481685,65851721,44479073,8791066,88698958,72732205,48774913,99125126,112651,25636669,36845587,40781449,42237907,10453030,4787945,51464002,47887470,99226875,7182642,16019925,33553959,29819940,26102057,85116755,57248122,37620363,53393358,78766358,75153252,35512853,90457870,30139692,84293052,51588015,33565483,71333116,63628376,43506672,26863229,16405341,54762643,22879907,30218878,64098930,18001617,66663942,17385531,97011160,54427233,45790169,68939068,82532312,45237957,18504197,27185644,72278539,48260151,32151165,30787683,59109400,87160386,79821897,48360198,51590803,1197320,7685448,11161731,91141395,44550764,53888755,41245325,98739783,79922758,93562257,19272365,6525948,13468268,58224549,68110330,38341669,508198,68824981,92998591,9257405,6157724,36468541,69136837,33699435,5640302,48395186,99297164,77072625,42199455,57803235,57020481,90013093,62693428,17016156,36139942,98462867,76434144,45667668,35192533,65880522,24314885,11923835,22766820,7300047,78561158,67227442,69255765,45381876,26493119,19376156,94595668,23134715,71300104,96420429,17037369,97379790,91990218,18783702,82651278,14045383,95251277,57240218,52293995,45996863,51315460,42967683,78785507,81805959,7955293,4434662,92554120,67793644,21919959,84166196,39201414,83533741,8733068,93270084,95726235,47298834,48658605,81274566,34295794,99021067,90272749,78218845,30771409,94360702,98648327,45306070,247198,58208470,40686254,44060493,9175338,28796059,49236559,97940276,20642888,1022677,21397057,47213183,76671482,30090481,14093520,91255408,8634541,84840549,2717150,92215320,22200378,26139110,31904591,99965001,1788101,51507425,10649306,321665,37280276,77620120,29029316,22405120,20885148,80014588,2891150,68102437,76825057,86798033,83269727,33304202,63152504,3233569,32159704,60955663,54517921,71083565,19101477,47893286,85711894,41442762,37659250,54606384,40356877,89804152,168541,96709982,6111563,90654836,8913721,55143724,6153406,99690194,15948937,77300457,67084351,8115266,82897371,62232211,45848907,29994197,59981773,30395570,12303248,31727379,97057187,8128637,87598888,94911072,66832478,22113133,9398733,6497830,98130363,4091162,24168411,2208785,73222868,7517032,59318837,45794415,30998561,36135,66250369,78549759,29188588,44627776,36753250,31126490,8129978,34236719,72738685,82726008,15148031,71920426,65038678,36808486,12416768,16971929,93057697,27325428,32250045,77694700,51715482,10961421,55189057,22450468,74614639,83150534,96852321,74441448,20568363,75052463,26292919,74110882,40865610,76170907,3233084,54232247,83302115,19457589,66045587,91727510,39553046,53802686,12348118,54199160,41380093,61271144,68694897,85571389,52261574,66322959,72373496,24619760,73109997,89419466,5822202,62552524,1375023,93053405,32699744,44784505,73168565,16387575,37166608,10309525,37675718,52613508,56461322,99333375,74357852,75229462,89811711,72238278,9829782,33237508,47792865,70800879,83210802,78300864,26397786,12024238,82339363,91831487,81855445,99917599,20122224,26507214,53084256,68128438,10597197,17857111,87710366,13348726,45995325,4985896,50567636,45617087,56515456,11581181,75407347,60581278,67451935,23740167,19898053,94076128,69355476,60430369,78884452,89637706,1936762,28550822,36189527,28787861,44664587,54987042,31161687,66116458,83747892,34946859,83133790,54663246,82886381,34698463,86460488,81898046,88130087,77377183,17386996,30764367,14626618,11365791,23110625,16445503,92541302,2204165,52060076,29635537,73235980,42073124,31733363,61741594,29516592,7229550,2607799,40984766,68316156,7423788,79738755,5832946,6221471,749283,92398073,79191827,58180653,37739481,84904436,79322415,7919588,43045786,92867155,64157906,76315420,86543538,61815223,2331773,82327024,64602895,68702632,38510840,77301523,28928175,17976208,26664538,8099994,91281584,65275241,68875490,24953498,9058407,1239555,73786944,67124282,71316369,51149804,33895336,99971982,44847298,7104732,74452589,22108665,30543215,44846932,85023028,30694952,7646095,83083131,89699445,89499542,94711842,84002370,26114953,79657802,29510992,5482538,14326617,67513640,6871053,44844121,90061527,176257,18131876,92692283,92353856,57658654,38365584,18833224,42644903,17081350,56473732,45919976,17727650,90158785,82779622,76703609,15536795,38256849,16424199,55850790,39373729,59329511,62115552,59501487,48088883,16684829,3633375,20867149,92071990,44245960,47738185,4806458,30891921,90090964,55615885,61712234,3183975,59405277,17365188,65081429,59614746,47708850,17894977,27041967,54263880,40781854,32058615,18663507,29401781,60176618,86118021,26744917,85117092,30653863,10264691,24733232,67474219,37560164,99604946,49882705,17058722,53632373,4978543,84349107,71522968,38645117,77898274,25842078,21289531,44915235,56153345,63015256,17146629,9860195,13862149,4069912,75128745,41092102,78602717,36812683,60393039,21001913,20645197,36396314,71965942,86022504,15163258,67031644,66903004,15075176,30163921,86242799,20681921,27625735,6819644,6521313,4204661,75820087,8729146,14363867,93790285,69697787,93515664,81774825,85695762,79880247,7066775,98653983,37224844,54868730,62428472,43357947,62357986,13819358,73031054,30811010,28734791,34667286,88444207,61928316,37192445,8373289,4199704,56955985,65047700,84406788,88251446,70727211,73021291,22435353,72358170,32274392,49598724,54058788,24915585,824872,45049308,66885828 +14093520,44842615,29819940,61623915,55247455,12416768,31904591,21993752,77312810,81677380,20486294,52261574,30787683,18833224,90457870,98130363,66885828,33699435,17727650,66250369,65047700,85023028,29516592,48892201,4515343,99549373,17764950,84127901,91957544,18411915,26114953,73124510,94090109,22108665,67031644,82886381,27665211,44550764,51047803,57240218,44983451,59318837,1197320,31733363,7955293,60430369,96420429,9188443,62936963,67793644,98943869,44889423,56531125,91802888,2331773,1250437,19939935,38494874,28851716,14626618,69355476,85695762,34698428,3509435,80316608,5970607,44784505,68816413,6793819,8913721,48360198,17058722,52060076,44844121,55753905,3183975,65038678,22721500,32161669,53547802,96318094,68644627,12348118,18663507,73168565,10309525,72738685,415901,73222868,69136837,9575954,33061250,68204242,59501487,97783876,669105,88444207,22450468,17976208,55770687,99524975,35092039,30366150,43357947,45919976,13348726,91664334,56515456,78766358,20885148,20002147,75543508,20867149,58751351,53632373,53393358,26998766,53666583,43045786,7104732,72373496,14220886,49597667,5111370,92803766,39986008,81805959,66903004,96193415,6505939,50567636,16684829,97641116,22766820,9829782,15015906,29466406,26863229,18504197,57393458,39553046,7011964,44473167,4806458,74357852,33628349,99604946,33895336,77072625,15535065,70036438,28734791,81898046,27041967,89419466,99658235,34236719,72019362,81274566,67030811,32250045,86001008,19101477,99861373,15902805,75153252,97057187,36580610,35456853,64087743,37183543,8791066,78602717,86798033,13862149,65275241,82178706,74137926,17385531,81853704,9623492,53084256,73617245,76434144,23569917,22113133,85242963,6153406,29065964,66137019,76330843,79322415,1431742,4798568,8634541,62430984,97281847,72614359,33304202,55470718,54058788,56473732,28928175,84349107,8129978,74441448,10366309,16097038,7685448,37675718,45990383,4787945,10649306,2717150,1022677,59329511,43152977,20642888,78589145,65271999,4064751,45996863,71965942,83150534,63506281,78561158,71333116,168541,83302115,83747892,44245960,6808825,39171895,55189057,61373987,77284619,74110882,38341669,10961421,98371444,68939068,95395112,72357096,30218878,51472275,59197747,50188404,64098930,25636669,29510992,19376156,79821897,4668450,20140249,48893685,29959549,49641577,27411561,60955663,60102965,42692881,36812683,78909561,93562257,50806615,3633375,4508700,80251430,21070110,81855445,54199160,7300047,7919588,90272749,65081429,44177011,24733232,94711842,70800879,92604458,80246713,91727510,13470059,76170907,15075176,37957788,64157906,89214616,80555751,32590267,61263068,40865610,82427263,93270084,4099191,22942635,93566986,72732205,85116755,63628376,3294781,83155442,7229550,95957797,17146629,18783702,37620363,45407418,91990218,39201414,65715134,76540481,51466049,70420215,44348328,1788101,68041839,4434662,59371804,35996293,38365584,72278539,72358170,13231279,4069912,51315460,88653118,98739783,26392416,33935899,99021067,40356877,89637706,12024238,71522968,79880247,34493392,67451935,82339363,44915235,24619760,37435892,3880712,70596786,5073754,28796059,91255408,77272628,92787493,86022504,35192533,77787724,26102057,42967683,72495719,40027975,47090124,29635537,17037369,26292919,11923835,93359396,96726697,33797252,8115266,51464002,93053405,21289531,749283,69605283,874791,67474219,92530431,84684495,91831487,48673079,15536795,48260151,112651,6221471,51590803,57359924,98948034,12664567,1936762,33431960,27625735,40781854,84166196,54762643,20568363,66667729,54014062,14947650,79191827,13468268,84904436,84840549,61982238,27325428,5832946,37224844,12571310,95726235,72238278,30764367,73235980,40152546,82979980,40534591,44481640,68702632,53648154,247198,31126490,96735716,97561745,17957593,98653983,18131876,70727211,28550822,42073124,72777973,99690194,16971929,43322743,98648327,59405277,82052050,5482538,33237508,26139110,90013093,36468541,60581278,569864,84406788,21673260,77377183,69848388,36780454,23194618,57658654,89811711,77300457,91281584,9599614,2891150,37891451,78785507,89078848,92998591,38008118,66322959,99917599,31491938,508198,34667286,99224392,17857111,99729357,29029316,57241521,96906410,62803858,24058273,34946859,63372756,13173644,90090964,11161731,38510840,31161687,92554120,33201905,4119747,84293052,88904910,63059024,33249630,75986488,68875490,68824981,52293995,82726008,90004325,14731700,23848565,95010552,6871053,74743862,17068582,61897582,77413012,9257405,7646095,23432750,66611759,36753250,23740167,4095116,66116458,1569515,45794415,19486173,8729146,89699445,47361209,83368048,71300104,78884452,79136082,62490109,32426752,6986898,5640302,8733068,50668599,79806380,24591705,38022834,9259676,76703609,73031054,22129328,30694952,88251446,7502255,57163802,66271566,59614746,38658347,36189527,17365188,20645197,45790169,72793444,60176618,18001617,53802686,49598724,71657078,37560164,40686254,92033260,30139692,94076128,79942022,37501808,46540998,8696647,83210802,30543215,41245325,9603598,36808486,29834512,30090481,54868730,57803235,61859581,49328608,95581843,99125126,57248122,26493119,63967300,66832478,75407347,16445503,65880522,62452034,51149804,526217,88698958,87598888,48395186,1204161,54606384,46851987,77898274,86821229,32159704,9886593,31727379,72725103,51507425,34497327,92071990,71920426,91141395,73392814,56484900,79657802,17894977,82532312,18806856,77620120,61741594,26507214,86242799,22405120,24826575,321665,83789391,45049308,67124282,46870723,88047921,52613508,62762857,38645117,36505482,70372191,34698463,76960303,63152504,26734892,95290172,19272365,49882705,30163921,16019925,29994197,80014588,16424199,32274392,65074535,44627776,65017137,14326617,42947632,44479073,78300864,38009615,16380211,10597197,36312813,77694700,7423788,45306070,43501211,56424103,96852321,30395570,16387575,54517921,66319530,75777973,68316156,99965001,34295794,26063929,40984766,21397057,47213183,4978543,93515664,33123618,9860195,21919959,86301513,70541760,33336148,19457589,59109400,69579137,39801351,17016156,65851721,30891921,99515901,17386996,30653863,11581181,37166608,62552524,17539625,86543538,24168411,54427233,2917920,824872,77187825,37739481,62115552,96709982,93790285,11543098,2204165,24953498,99297164,34044787,30463802,55602660,62496012,92353856,6497830,40197395,14723410,45237957,42199455,54987042,29932657,22200378,64055960,36933038,66663942,9058407,60106217,85117092,92215320,8373289,41481685,18699206,26744917,87710366,56153345,41092102,48774913,94595668,81172706,94911072,26664538,57961282,5267545,58749,98462867,16054533,55143724,67227442,62740044,29188588,13819358,92398073,18466635,89804152,32699744,37659250,42237907,36396314,99333375,59981773,93933709,87720882,72274002,55669657,30811010,64848072,90310261,8807940,35512853,14349098,89996536,4199704,42644903,19898053,48088883,79738755,75135448,10358899,82327024,8099994,4985896,15064655,58224549,61859143,40677414,20836893,61960400,7687278,99226875,25842078,60393039,14363867,42307449,90061527,51426311,49236559,62428472,44847298,3487592,62232211,74614639,9175338,42782652,20681921,94516935,10264691,89217461,74724075,75229462,83269727,63015256,48658605,54263880,36930650,50702367,41380093,26397786,89499542,43376279,52204879,83533741,41442762,15148031,69641513,6038457,3773993,68110330,37280276,39847321,45428665,26275734,44846932,3233084,1239555,38061439,89046466,4091162,57020481,43506672,8651647,51588015,34432810,47298834,69786756,92867155,47887470,16405341,36685023,37192445,86118021,40781449,62357986,91938191,75052463,6521313,77301523,6819644,65454636,51715482,8128637,22997281,97379790,90158785,45617087,84002370,75128745,85571389,45848907,48675329,44060493,66045587,82897371,61815223,47708850,62693428,67281495,20765474,76671482,36845587,2208785,69697787,59177718,97011160,69255765,9398733,61141874,78218845,94539824,23134715,71469330,8055981,68102437,50007421,81774825,82779622,68000591,78549759,92692978,47893286,20122224,92692283,5822202,92541302,68128438,29401781,15948937,3088684,4204661,79922758,44664587,47738185,30569392,27185644,99971982,3233569,66428795,14045383,73786944,76315420,21533347,87160386,36135,56955985,58180653,19891772,7182642,67513640,16595436,54232247,45075471,56461322,65338021,11365791,53888755,38256849,93057697,30998561,61271144,26776922,6525948,70004753,84187166,55615885,7517032,74452589,55960386,73109997,59910624,45667668,71316369,76825057,53842979,10453030,58208470,73021291,7066775,23110625,47792865,2607799,3235882,61728685,83651211,55850790,95251277,27187213,45995325,70367851,16567550,32058615,41092172,61928316,39373729,49271185,11757872,82651278,85711894,22879907,33553959,75820087,24915585,94360702,83133790,24314885,30771409,83083131,91240048,22435353,68694897,83948335,36139942,6111563,71083565,54663246,9860968,43933006,67084351,97940276,33565483,70782102,16099750,88130087,90654836,61712234,45381876,62051033,28787861,12303248,1375023,64602895,86460488,6157724,21001913,17081350,15163258,176257,32151165 +415901,98462867,55753905,86022504,29834512,17764950,68816413,68204242,67793644,68000591,91664334,54199160,28928175,3183975,33249630,89637706,72274002,4069912,69355476,11923835,25842078,64157906,4787945,30395570,99965001,81898046,52613508,3633375,43501211,13173644,24058273,39201414,24168411,81853704,5111370,14093520,77284619,70372191,2717150,67084351,18131876,95290172,9058407,29635537,84904436,8807940,20765474,67474219,92398073,55189057,33797252,36505482,96193415,52060076,83210802,59318837,97561745,99524975,9188443,48774913,38658347,20002147,82979980,87710366,85117092,38645117,63628376,70800879,45919976,49271185,21993752,29819940,6221471,7423788,40781449,83302115,49236559,92803766,1197320,10309525,99515901,47792865,55770687,35092039,43322743,98739783,5822202,65047700,55143724,26102057,3088684,21533347,10358899,62740044,10366309,98130363,40865610,94595668,72238278,36468541,66832478,37739481,19376156,27411561,92033260,85116755,55602660,6525948,4119747,14731700,50567636,75543508,78602717,68644627,90090964,15535065,33237508,32250045,40677414,23848565,15064655,61960400,60102965,66885828,66663942,98371444,60430369,508198,16971929,75820087,95581843,78218845,35456853,44842615,92787493,526217,37620363,61263068,74357852,59197747,54868730,54663246,72373496,57803235,63967300,85242963,27325428,96318094,62490109,30694952,30569392,50702367,8729146,91957544,26139110,9623492,44479073,72777973,53084256,6793819,33553959,65338021,41092102,17037369,75229462,7646095,3509435,92554120,36753250,42967683,66428795,76434144,18833224,80014588,45237957,92692283,40152546,25636669,30139692,47361209,95395112,29466406,16595436,44889423,23110625,45428665,9575954,63506281,70004753,58751351,22997281,29029316,55615885,68824981,15148031,30811010,80251430,99690194,9886593,38008118,55470718,99658235,99549373,29994197,42782652,669105,38494874,45306070,42644903,61373987,70727211,99604946,44846932,70541760,26063929,3294781,82886381,89214616,88251446,54987042,95726235,44473167,66116458,85571389,33431960,18411915,67451935,6986898,14326617,1788101,96726697,34497327,4204661,39553046,50188404,35996293,45617087,44983451,50668599,45996863,56424103,78589145,44060493,47090124,76170907,55669657,42073124,4508700,34295794,5970607,7955293,62496012,84187166,83789391,60955663,31126490,26664538,22942635,62452034,77694700,15163258,86460488,28550822,91831487,2891150,81172706,19272365,86301513,92071990,66903004,321665,43933006,98943869,28734791,31904591,38061439,72738685,68702632,46851987,14220886,62803858,30366150,30653863,51507425,17727650,16424199,46870723,51047803,37183543,96735716,81677380,16054533,9829782,81855445,7502255,83155442,82052050,73021291,48673079,97940276,39373729,2208785,65275241,66137019,30891921,77300457,82339363,12416768,56531125,18783702,72725103,51472275,69579137,86242799,80316608,56515456,16405341,61728685,92353856,48260151,2204165,13862149,56153345,61982238,40781854,71333116,1375023,44915235,92215320,76671482,23569917,69136837,18806856,68041839,36808486,6038457,53547802,37192445,73109997,17081350,4064751,13468268,14626618,66250369,16380211,61859581,6153406,27665211,9860195,93053405,78561158,24733232,71469330,34493392,8129978,22450468,26863229,61712234,64602895,24826575,26292919,71920426,72495719,34236719,8913721,7517032,79880247,68316156,7919588,73235980,87598888,57240218,4668450,24591705,44348328,32590267,61623915,93933709,15075176,75052463,3233569,16445503,8128637,62232211,62357986,26392416,824872,77787724,14947650,32058615,18466635,82726008,47708850,43376279,20486294,58180653,7687278,11581181,91802888,40984766,17068582,66045587,3233084,52293995,13231279,4806458,73617245,80555751,89078848,75777973,30163921,44481640,66322959,7685448,32274392,29401781,93057697,84406788,88653118,73168565,90457870,76330843,99971982,74441448,59614746,99297164,79738755,33628349,37224844,56461322,30787683,5640302,54058788,38256849,112651,19101477,89046466,34946859,41481685,73031054,6808825,54014062,83533741,4199704,42947632,79657802,20122224,58208470,874791,79922758,67513640,10597197,67031644,45995325,72019362,17146629,95957797,9257405,29065964,168541,34698463,31161687,2607799,84349107,78785507,26275734,82897371,38341669,37659250,17539625,74110882,7229550,40197395,30218878,62693428,83651211,9398733,94090109,67030811,41245325,56955985,99333375,90061527,57163802,71083565,12348118,62552524,34667286,89699445,51715482,28851716,33304202,82178706,77898274,65715134,77187825,76825057,48892201,62430984,63015256,31491938,77620120,22129328,36930650,45407418,8115266,14349098,67124282,48088883,52261574,72732205,75128745,8055981,74743862,12664567,8696647,79821897,1431742,75986488,59405277,93562257,61897582,74137926,23194618,20140249,3235882,17386996,7182642,44847298,78909561,70420215,89811711,93515664,36396314,4095116,54427233,55960386,65038678,19939935,5482538,54606384,48658605,19898053,50806615,79191827,34432810,37891451,1204161,84002370,98648327,74614639,77272628,92604458,59371804,17857111,65851721,53802686,90310261,40356877,21070110,49328608,8099994,569864,33336148,56473732,97057187,30998561,68939068,15948937,55247455,10961421,96906410,66667729,19891772,45794415,4515343,76315420,96852321,83368048,68128438,45381876,90013093,33201905,17957593,22879907,84840549,89804152,66271566,43357947,7011964,59109400,16567550,89499542,30090481,9599614,17976208,48893685,39801351,32151165,38022834,82532312,65081429,11543098,4099191,77072625,30764367,31727379,41092172,99917599,6521313,59177718,86821229,41380093,15015906,57961282,33699435,91240048,78549759,20836893,57359924,42692881,99861373,18504197,81805959,69255765,5073754,99021067,27625735,27041967,23432750,47298834,26493119,18663507,19486173,61141874,29510992,29932657,8651647,4091162,64055960,20645197,84166196,8791066,37675718,32159704,16099750,35192533,97379790,99125126,3487592,94516935,11161731,20867149,59501487,92692978,7066775,33123618,36780454,57393458,69605283,13470059,72278539,27185644,88130087,32426752,94911072,78884452,98948034,67281495,24619760,8634541,36685023,5267545,6871053,61815223,59910624,69697787,48360198,76540481,93270084,16019925,30771409,79136082,61271144,47738185,48675329,85695762,26507214,49882705,38365584,4978543,42199455,49641577,82651278,55850790,85711894,22200378,47213183,26776922,29188588,37957788,86001008,95010552,43152977,22435353,74724075,16097038,34698428,2917920,51590803,63059024,54762643,39171895,71300104,84684495,64098930,45790169,20642888,79806380,40027975,75407347,6157724,50007421,53842979,83150534,18001617,26397786,36312813,99226875,57658654,43506672,19457589,10264691,68102437,29516592,44784505,57248122,97641116,1936762,91727510,86543538,80246713,86118021,5832946,88047921,69641513,93359396,72614359,2331773,43045786,53632373,8373289,22405120,22108665,10453030,29959549,88444207,36135,13348726,176257,71316369,71522968,62428472,87720882,37560164,18699206,73222868,26114953,93566986,53888755,36580610,65074535,60176618,15902805,62051033,26734892,77312810,65017137,78766358,53666583,45667668,44627776,6505939,58224549,247198,71657078,91255408,90004325,94539824,96420429,70036438,92530431,74452589,91938191,70367851,65454636,41442762,61859143,21289531,24953498,53648154,78300864,6111563,97011160,37501808,12024238,17365188,61741594,76960303,62115552,14363867,33565483,66611759,8733068,44245960,99729357,90654836,63372756,42237907,4434662,81274566,76703609,62936963,21919959,20681921,65271999,61928316,83133790,16684829,79942022,39847321,17058722,30543215,17385531,33895336,72357096,97783876,54517921,47887470,77413012,77377183,82327024,83269727,31733363,13819358,45075471,45990383,81774825,57241521,45848907,51464002,37280276,12571310,85023028,21673260,67227442,48395186,9860968,65880522,6497830,15536795,94076128,68875490,36812683,91141395,69786756,53393358,83747892,64087743,66319530,22113133,60393039,9259676,14045383,75135448,10649306,64848072,39986008,9603598,32161669,92998591,44177011,89419466,28787861,1239555,22721500,73124510,51466049,75153252,82427263,38009615,51315460,17894977,44550764,54232247,37435892,3880712,26998766,98653983,58749,749283,22766820,20568363,63152504,91990218,51588015,59981773,84127901,4985896,91281584,9175338,14723410,84293052,17016156,32699744,96709982,36845587,6819644,11757872,83083131,60106217,35512853,94360702,34044787,62762857,92541302,49598724,1022677,1569515,88904910,77301523,79322415,68110330,21001913,72358170,90158785,20885148,72793444,26744917,93790285,7104732,45049308,97281847,83948335,90272749,24915585,36933038,40686254,99224392,70596786,37166608,3773993,30463802,69848388,1250437,33061250,73786944,59329511,51149804,68694897,86798033,11365791,95251277,71965942,89996536,7300047,44844121,44664587,16387575,60581278,49597667,42307449,36189527,33935899,12303248,94711842,51426311,52204879,73392814,24314885,4798568,87160386,92867155,23740167,38510840,23134715,88698958,21397057,40534591,46540998,56484900,89217461,70782102,28796059,54263880,57020481,36139942,47893286,27187213,82779622 +97783876,65851721,66319530,63059024,51047803,87598888,36505482,57240218,90272749,57241521,44627776,24733232,112651,4119747,82427263,93933709,87720882,34497327,4199704,1788101,8128637,63967300,13231279,60581278,55189057,68041839,16019925,55669657,93562257,89699445,95251277,28796059,66667729,75777973,10366309,80014588,12024238,92554120,247198,89046466,39171895,65880522,43506672,84293052,6793819,8055981,45848907,69848388,508198,30139692,80246713,17068582,45990383,4434662,43376279,93790285,3773993,97281847,16405341,35092039,40677414,30787683,72019362,68702632,73222868,3233084,18783702,44889423,98943869,8651647,30163921,33304202,59197747,77413012,22942635,34236719,30771409,21070110,37501808,55470718,3880712,85242963,83210802,13470059,23134715,99125126,98653983,68204242,81172706,4668450,62496012,1022677,11365791,79821897,9829782,91255408,98462867,36189527,3233569,57359924,42782652,92692978,14045383,38022834,70782102,40686254,26493119,68644627,72357096,55602660,91831487,62936963,45996863,27325428,14349098,26392416,44842615,40356877,7955293,94090109,48360198,19486173,17957593,94516935,74724075,67281495,70004753,16595436,38365584,60955663,44784505,69786756,7919588,14093520,15015906,79880247,90013093,37957788,88047921,50188404,3294781,70596786,39986008,59329511,85711894,6871053,17365188,2917920,8696647,73786944,72738685,11161731,98948034,52060076,43501211,83789391,57393458,71657078,21993752,38008118,18699206,28787861,40152546,85116755,569864,26744917,39553046,43933006,47361209,4204661,7104732,72614359,62490109,36812683,9860968,98739783,34698463,7502255,86301513,14723410,75052463,23740167,47090124,40027975,38061439,76434144,9575954,6153406,71333116,51472275,71300104,7685448,49597667,7229550,75986488,29188588,18833224,82779622,2717150,51464002,26998766,6808825,55247455,24826575,73617245,14731700,72358170,37560164,70727211,1204161,32590267,73031054,15536795,18806856,84127901,17058722,39847321,78300864,56424103,53648154,96709982,60106217,44473167,65074535,9175338,45428665,98371444,16097038,5970607,36312813,99297164,66611759,31126490,874791,89811711,70036438,28851716,88698958,58208470,64087743,80316608,669105,41380093,19891772,67124282,2204165,30694952,53084256,55143724,61815223,37739481,16099750,96726697,16971929,99224392,73235980,77787724,40781449,62740044,72274002,91990218,37183543,38658347,58224549,78589145,4064751,6111563,26863229,57961282,97011160,81805959,68102437,99021067,99658235,78602717,91664334,37675718,58749,92541302,38341669,92215320,36780454,44844121,76315420,23194618,22405120,62232211,17037369,61859581,99549373,71316369,22997281,97057187,78884452,34295794,9886593,34698428,44550764,67793644,63152504,12348118,83155442,27665211,749283,33431960,23848565,45237957,79806380,29994197,17146629,99604946,79738755,77284619,39801351,89637706,6505939,99515901,97940276,35512853,44177011,24058273,3235882,68939068,46870723,18466635,20122224,7300047,16445503,20486294,99333375,71965942,51588015,6521313,67513640,52293995,57248122,48774913,61859143,29466406,21001913,97641116,1431742,89419466,51590803,66903004,28550822,17764950,65017137,26397786,87160386,99524975,88653118,84406788,26507214,82651278,29932657,84187166,93359396,94539824,32159704,45381876,77272628,59614746,95957797,4985896,72777973,95290172,91802888,27187213,50668599,74357852,29834512,53632373,76960303,47893286,32161669,18504197,9398733,36930650,30569392,76540481,91957544,9603598,20002147,4515343,36685023,13173644,17016156,90654836,91141395,90158785,26102057,30395570,89078848,20885148,45995325,26292919,19939935,30218878,14326617,29959549,86242799,22721500,55770687,22108665,33336148,57803235,20140249,34493392,23569917,78909561,92803766,80555751,66137019,4091162,30463802,33935899,76330843,61741594,35192533,88444207,16684829,1569515,77377183,53393358,81898046,31733363,44664587,36139942,4099191,11581181,40781854,85695762,76671482,65454636,40534591,321665,81853704,71083565,73392814,3487592,99690194,21673260,4978543,86798033,35996293,168541,56484900,75543508,21533347,34432810,54517921,30366150,85117092,51466049,31491938,66663942,61373987,2891150,89214616,27411561,8129978,7423788,6819644,33201905,44847298,1197320,33565483,36580610,74137926,59177718,63628376,92398073,31904591,45919976,96852321,68000591,40865610,91240048,5111370,40984766,8733068,11923835,25842078,47213183,48088883,37620363,42307449,37280276,9623492,6525948,75135448,77898274,53547802,81855445,2607799,82532312,9257405,77187825,15535065,42237907,26664538,42967683,98130363,93053405,78218845,62452034,95010552,82178706,47887470,61897582,17385531,69605283,59910624,10597197,61263068,33061250,1936762,35456853,50007421,30764367,29635537,22129328,8099994,72725103,3633375,45617087,48893685,79322415,66045587,526217,4798568,92787493,8729146,83083131,86543538,36135,37659250,63015256,83269727,70541760,44846932,30543215,5073754,60102965,89804152,37224844,44060493,86821229,42073124,68128438,52204879,61960400,53888755,62051033,43045786,85023028,93057697,54987042,46851987,67227442,84904436,13468268,88904910,17386996,32699744,27185644,99729357,31727379,54199160,7687278,5822202,86001008,99861373,96735716,17539625,51149804,76825057,17727650,22766820,92033260,37435892,68875490,9188443,21289531,10358899,43152977,75128745,39201414,33237508,49236559,7066775,22200378,29510992,92692283,17857111,65271999,99965001,7182642,33249630,9860195,4069912,71469330,83747892,40197395,80251430,64157906,30653863,9058407,8791066,77312810,83302115,90310261,30090481,78549759,91938191,59501487,68816413,8115266,62803858,82979980,66832478,49598724,85571389,93515664,8807940,62428472,61728685,1250437,56531125,52261574,18001617,89217461,38009615,59109400,24953498,45790169,15148031,38256849,38494874,69255765,41481685,27625735,55753905,4787945,70800879,44915235,66322959,73168565,2208785,61982238,30998561,95395112,77620120,48675329,61271144,97561745,70420215,99917599,20642888,33628349,75153252,38645117,42947632,70372191,48892201,11543098,43357947,15075176,12664567,79922758,59318837,69579137,25636669,48673079,63372756,78766358,33699435,38510840,18663507,69641513,8634541,54868730,74452589,33797252,65081429,47298834,16424199,16567550,12416768,83651211,83948335,62693428,24915585,24619760,95726235,51426311,49328608,54014062,54427233,50702367,21919959,89996536,20867149,82897371,15948937,19457589,56153345,90090964,43322743,54762643,76170907,26063929,23110625,81274566,29516592,67030811,18131876,72278539,83133790,13348726,62115552,15902805,29819940,41092102,1239555,48395186,59981773,32274392,17976208,28734791,45794415,36396314,6221471,33123618,34044787,57163802,49882705,58751351,4806458,94711842,6986898,54663246,93270084,5640302,66116458,34946859,92604458,23432750,47792865,92998591,65715134,62430984,68824981,42692881,36933038,26776922,96193415,94911072,69355476,97379790,45049308,19898053,4095116,79942022,29029316,56515456,33553959,18411915,73021291,6038457,44245960,7011964,61928316,72793444,60393039,56473732,65038678,82886381,20836893,55960386,84684495,24591705,4508700,46540998,64055960,53842979,36753250,42199455,12303248,53666583,17081350,66271566,70367851,90457870,88130087,51315460,82327024,3088684,77694700,62762857,84002370,50567636,71920426,65338021,27041967,73124510,94595668,65047700,5832946,45407418,33895336,90004325,54232247,77300457,13819358,61141874,60176618,9599614,83368048,51715482,7646095,44983451,96318094,84840549,22113133,62552524,92530431,176257,14947650,49641577,26139110,44481640,15064655,48260151,96906410,67451935,16380211,64098930,12571310,61623915,66250369,64848072,99971982,45667668,68694897,79657802,92353856,16054533,60430369,93566986,5482538,31161687,58180653,56955985,415901,69136837,44348328,75229462,67474219,79136082,90061527,65275241,48658605,59371804,82339363,44479073,14220886,10453030,2331773,3509435,20568363,74614639,47738185,37166608,86460488,72373496,47708850,49271185,74110882,11757872,91281584,72732205,19272365,39373729,45306070,26114953,57658654,26734892,55615885,22879907,16387575,37891451,91727510,77301523,19376156,92867155,41092172,64602895,20765474,79191827,82726008,19101477,9259676,10649306,45075471,82052050,81677380,74743862,75820087,32250045,13862149,99226875,30811010,53802686,66428795,52613508,76703609,22435353,95581843,51507425,54263880,1375023,94360702,36468541,824872,5267545,57020481,36808486,8913721,83150534,24314885,30891921,37192445,72238278,41245325,26275734,67031644,83533741,22450468,10309525,84349107,6497830,10961421,20681921,29065964,32058615,66885828,15163258,69697787,21397057,72495719,3183975,89499542,14363867,54058788,67084351,68110330,41442762,78561158,73109997,28928175,77072625,86118021,14626618,29401781,74441448,55850790,75407347,68316156,88251446,63506281,50806615,7517032,56461322,10264691,17894977,71522968,87710366,42644903,6157724,24168411,84166196,94076128,34667286,96420429,86022504,20645197,92071990,81774825,61712234,98648327,59405277,62357986,32426752,54606384,32151165,36845587,78785507,8373289 +16019925,66667729,59329511,94516935,62936963,27041967,89046466,48360198,8733068,3880712,79821897,4985896,8651647,78602717,20486294,55753905,17365188,49271185,68041839,38008118,3509435,71657078,247198,93270084,66250369,20885148,35192533,36312813,42237907,22108665,10453030,35092039,33699435,10358899,34295794,85117092,26392416,3487592,168541,17146629,81853704,29466406,15535065,8913721,11581181,61373987,62740044,5267545,37739481,12303248,10309525,14349098,62496012,17037369,56531125,55143724,36139942,1250437,84349107,55770687,14220886,73392814,33304202,29516592,44245960,57658654,50188404,44844121,69355476,31491938,61263068,85242963,51464002,38256849,9829782,30366150,63628376,93790285,6793819,54263880,89419466,4798568,4434662,43357947,42307449,16405341,78589145,69848388,95581843,47361209,96193415,99021067,30163921,41092172,79322415,75986488,63967300,44842615,98371444,82339363,55189057,57163802,79657802,54014062,3773993,7687278,24733232,68644627,84904436,59109400,17058722,65715134,26102057,37891451,69579137,10366309,12348118,29065964,26292919,62430984,44177011,43933006,72373496,18504197,27665211,15075176,34698463,26664538,12664567,17957593,77377183,60430369,63015256,14045383,80014588,4668450,60176618,874791,59981773,5111370,26744917,65271999,54199160,9599614,14626618,89078848,96735716,89804152,54868730,14093520,38061439,43506672,44348328,3294781,36780454,93933709,92541302,27325428,55850790,60102965,24058273,97561745,60581278,98462867,67227442,22997281,22942635,7955293,99125126,40027975,749283,99549373,50567636,68000591,67451935,8129978,92604458,72777973,36812683,14731700,86301513,70727211,33237508,81898046,19939935,508198,14947650,23569917,31126490,62051033,36505482,57241521,56473732,20568363,2717150,38022834,61982238,78766358,32159704,29510992,76825057,73617245,70367851,63059024,6497830,52204879,5482538,96709982,41092102,99658235,44784505,86821229,81805959,70372191,59614746,17539625,70541760,68204242,16445503,71333116,72614359,48774913,98739783,36753250,97783876,71083565,69786756,57240218,92787493,66319530,86001008,22879907,89214616,1788101,5970607,86543538,23432750,42967683,47708850,72278539,91255408,7300047,60106217,26275734,30764367,52261574,1197320,4095116,112651,6505939,34493392,51472275,6153406,27411561,88698958,39201414,37183543,77787724,66116458,33797252,72732205,8807940,28796059,37280276,94090109,90272749,73031054,76170907,22129328,46870723,4978543,67793644,61741594,15536795,50668599,87598888,46540998,3088684,48673079,55602660,67281495,88653118,19272365,77694700,67474219,13819358,98943869,90061527,92692283,9575954,7919588,42782652,88904910,93053405,5073754,3233084,70420215,22113133,73222868,4508700,83210802,75229462,17976208,83651211,22766820,45075471,8128637,83155442,88251446,93562257,92692978,31733363,17857111,96726697,15902805,54762643,67031644,65081429,36580610,19376156,669105,72274002,80246713,30395570,22721500,40781449,50702367,6871053,99965001,33201905,34044787,82532312,38365584,6111563,43045786,18131876,39553046,80251430,13173644,59318837,19891772,4204661,73168565,91938191,40677414,33061250,69641513,99333375,86118021,89811711,84187166,29819940,45617087,59910624,77620120,18806856,78909561,92554120,13862149,40197395,66903004,22435353,77413012,91990218,44550764,80555751,71300104,39986008,61859581,83150534,47792865,16097038,85571389,36468541,20836893,76315420,95957797,7011964,18783702,43501211,74357852,79191827,18699206,4099191,45790169,19101477,61271144,6819644,9886593,11161731,23194618,4064751,62490109,7229550,35512853,32699744,42692881,57803235,47738185,30090481,89637706,74110882,5822202,7685448,1204161,45428665,42073124,65074535,2917920,49641577,95010552,58224549,49328608,27625735,74743862,90013093,24591705,84840549,824872,62803858,16380211,48658605,29834512,81172706,99729357,86242799,61960400,75820087,59371804,48088883,27185644,20681921,36933038,72725103,88130087,47887470,13470059,36135,89217461,66045587,4515343,26776922,79136082,77300457,61712234,29188588,15148031,5832946,70004753,86022504,30139692,53888755,16424199,7646095,68702632,20642888,526217,59197747,78300864,34432810,415901,33628349,71965942,66322959,1239555,21533347,53547802,52060076,87710366,59501487,569864,77187825,29029316,77312810,28851716,97011160,53632373,73124510,59177718,99917599,70596786,40984766,89996536,14723410,18001617,18833224,71469330,9188443,64602895,91957544,45996863,82327024,36189527,97940276,20002147,68824981,68816413,37659250,38658347,33431960,95395112,47090124,21993752,62693428,74441448,3183975,61859143,71316369,44847298,37501808,56424103,83302115,36685023,44983451,55470718,10961421,66611759,20140249,12024238,62452034,45919976,50007421,17764950,98653983,65047700,51047803,95726235,88444207,93359396,77898274,82178706,8099994,33895336,6986898,21070110,14326617,21397057,21919959,69255765,31904591,64098930,6221471,43376279,97057187,77272628,83083131,43152977,82886381,39171895,29994197,3235882,74614639,51588015,77072625,8055981,40356877,8115266,84293052,321665,16684829,36930650,37224844,62357986,88047921,47298834,78785507,34698428,7066775,8696647,65851721,33123618,20122224,66663942,99690194,55669657,62762857,44915235,58180653,25842078,77284619,73786944,69697787,16387575,81274566,36845587,85116755,28734791,92033260,68875490,51590803,65017137,7517032,30998561,48892201,70036438,51507425,2204165,61815223,82427263,45237957,48260151,8634541,57248122,74724075,83747892,9175338,44481640,37192445,66428795,84127901,4119747,99515901,23134715,1022677,17894977,33565483,76703609,9623492,18411915,77301523,26493119,55615885,68694897,53393358,23740167,39373729,7423788,26998766,1375023,32151165,83269727,66832478,84166196,26063929,90158785,48893685,29959549,39801351,53842979,13231279,33249630,78561158,87160386,16054533,87720882,48675329,4806458,44060493,30787683,58751351,63506281,10597197,6521313,31161687,1431742,20765474,92215320,6525948,26114953,93515664,33336148,9603598,56955985,30811010,95290172,83789391,17386996,45667668,53084256,45306070,83533741,75543508,46851987,53802686,17016156,92998591,28550822,23848565,53648154,68939068,13468268,10649306,7104732,91240048,30463802,67084351,76434144,72357096,73235980,44473167,56153345,40152546,34497327,22405120,51715482,40865610,98948034,24619760,99604946,58749,84406788,98648327,44889423,92803766,79806380,75777973,63152504,19486173,69136837,79922758,31727379,49236559,41380093,86798033,57961282,47213183,90457870,82726008,11757872,76330843,60955663,75128745,37560164,41481685,25636669,32161669,89499542,98130363,65454636,82779622,56515456,73021291,49597667,78884452,17068582,30771409,84684495,80316608,9860195,93566986,55247455,67124282,20645197,4069912,17081350,90310261,17385531,66885828,84002370,75135448,47893286,42644903,34667286,64157906,38341669,76671482,51315460,32426752,9257405,54606384,78218845,9058407,99971982,24953498,39847321,57020481,86460488,21001913,7182642,34946859,36396314,57359924,82897371,81855445,15064655,42947632,82979980,73109997,18663507,11923835,54663246,28787861,33935899,3233569,61728685,1569515,45381876,30569392,22450468,16567550,15015906,32590267,30891921,32274392,37620363,74137926,76540481,37957788,45407418,72019362,71920426,24168411,72358170,53666583,94539824,72495719,9860968,99524975,54058788,24826575,66137019,9398733,37435892,16595436,30694952,68102437,11543098,28928175,2891150,94076128,66271566,83368048,99226875,6808825,17727650,52613508,38645117,62552524,64087743,2208785,34236719,41245325,62232211,82052050,29635537,81677380,65880522,36808486,40781854,70800879,38494874,90090964,4199704,99297164,45990383,26734892,97379790,35996293,96906410,63372756,64848072,75153252,94595668,72793444,79942022,24915585,91802888,44627776,58208470,48395186,68316156,1936762,3633375,91664334,4787945,94911072,67030811,90654836,6157724,95251277,92530431,32250045,99861373,75052463,15163258,75407347,38009615,61928316,42199455,79880247,2331773,93057697,65338021,40534591,23110625,97641116,26139110,8791066,92867155,65038678,91831487,78549759,7502255,2607799,6038457,38510840,45848907,29932657,37166608,26507214,51426311,68128438,69605283,19898053,8729146,21289531,64055960,13348726,51149804,89699445,9259676,54517921,51466049,52293995,26397786,91727510,22200378,59405277,81774825,29401781,54427233,4091162,56484900,79738755,74452589,35456853,45794415,12571310,15948937,62115552,40686254,55960386,30218878,45995325,96318094,99224392,21673260,49882705,30653863,56461322,85023028,94360702,83133790,41442762,91141395,19457589,43322743,72738685,92353856,83948335,76960303,96420429,60393039,14363867,91281584,176257,49598724,96852321,92398073,33553959,44664587,82651278,44846932,71522968,24314885,20867149,68110330,44479073,32058615,67513640,54232247,45049308,72238278,12416768,85711894,16099750,92071990,65275241,90004325,61897582,61623915,30543215,37675718,54987042,26863229,16971929,8373289,70782102,27187213,57393458,11365791,85695762,50806615,5640302,97281847,10264691,94711842,61141874,18466635,62428472 +55753905,72357096,20486294,69786756,96726697,45237957,3233084,70596786,46870723,89811711,68644627,19891772,53084256,77312810,88047921,48774913,64087743,247198,65017137,55143724,83789391,23432750,97783876,17068582,97641116,48260151,4119747,61859143,6793819,40677414,56424103,12571310,54987042,6986898,48893685,44627776,20140249,22129328,27665211,40356877,29466406,89214616,25842078,63059024,33304202,23848565,74724075,42307449,75986488,63152504,67281495,4199704,75153252,14731700,14349098,50007421,78218845,74357852,8651647,1204161,59910624,73168565,37435892,88698958,95290172,69848388,4099191,74110882,26734892,14326617,67451935,26292919,94516935,39986008,569864,79657802,79821897,75777973,9860968,17058722,38008118,4095116,81274566,8128637,89804152,57241521,10366309,43322743,86821229,8696647,57248122,38494874,68816413,29959549,28851716,85242963,24733232,32161669,85711894,60102965,94090109,60581278,92604458,26776922,88904910,77300457,32590267,23194618,112651,6153406,10309525,33431960,34236719,15148031,26998766,21289531,40781854,92787493,77413012,43376279,9623492,94911072,65038678,99524975,89996536,89419466,43506672,45428665,65851721,72777973,30543215,46851987,37957788,59109400,71657078,14045383,20122224,13231279,93790285,64848072,82651278,32274392,3235882,22721500,55602660,3880712,27185644,68204242,41481685,43357947,36845587,7502255,1431742,89046466,41092172,80014588,93515664,93933709,84406788,76434144,3294781,62452034,73222868,23110625,20836893,51047803,14723410,82979980,76960303,53547802,19101477,87160386,40027975,92998591,874791,7517032,44550764,16971929,9175338,99658235,8791066,78300864,89637706,4064751,77787724,33553959,30218878,13468268,50806615,82178706,31904591,4069912,82327024,87720882,33935899,32159704,68041839,66832478,68128438,7685448,65715134,85695762,69355476,40534591,6111563,36753250,14947650,38645117,40686254,32699744,11543098,55247455,22766820,47298834,669105,1197320,16595436,62496012,84293052,24058273,54762643,33565483,99965001,78909561,49328608,61741594,45617087,66319530,73786944,92541302,84127901,7182642,87598888,13470059,33249630,43933006,81172706,78561158,63967300,52261574,98653983,37183543,47361209,55615885,59177718,83210802,39171895,72358170,29994197,74441448,91255408,77898274,67513640,20642888,98739783,77272628,92692978,36812683,74614639,79806380,35996293,48673079,15163258,70004753,93562257,58208470,96852321,7300047,34493392,70800879,17957593,1250437,54663246,60430369,29834512,37224844,66428795,49236559,90272749,65880522,88653118,92803766,90310261,72495719,18699206,90457870,22108665,92554120,85116755,61960400,19486173,66250369,2917920,47090124,73235980,99021067,27041967,36580610,62232211,28796059,37166608,76315420,99224392,56515456,4515343,42782652,30163921,2204165,22450468,70367851,45306070,42237907,38009615,30366150,61859581,84904436,23134715,62357986,29819940,9259676,80316608,72738685,35092039,10597197,76170907,49597667,72238278,79322415,6521313,45790169,3233569,14363867,99549373,81855445,91831487,6221471,70541760,82427263,31733363,75052463,77284619,27325428,44846932,66137019,59329511,15015906,7229550,80555751,6871053,77072625,78884452,86118021,45667668,62430984,70036438,96735716,37675718,8913721,30811010,71469330,68939068,26114953,6808825,66903004,83651211,77377183,47792865,57961282,63015256,33201905,91957544,94360702,29516592,59197747,48658605,75543508,44245960,56461322,24953498,7646095,168541,78602717,71300104,78549759,9398733,40197395,13173644,54199160,96193415,68102437,4787945,5073754,57240218,23569917,4091162,30463802,11161731,15064655,28787861,2717150,12416768,50702367,51464002,12348118,79922758,59614746,7104732,99125126,17016156,91938191,75135448,8807940,92692283,4204661,18131876,74452589,90061527,88444207,6505939,74137926,71333116,45990383,30787683,97011160,16097038,37501808,43045786,95957797,34698428,98462867,34497327,12664567,8099994,81898046,36930650,34698463,61141874,33237508,90654836,44784505,81853704,1022677,24591705,53648154,28550822,44060493,26397786,72278539,7919588,71083565,7011964,48360198,57020481,37560164,67793644,21397057,93057697,35192533,80251430,9603598,40781449,10961421,34295794,44348328,8733068,7066775,24314885,62803858,76671482,17386996,38658347,30395570,22879907,23740167,83155442,95251277,85571389,77187825,18504197,36468541,30653863,50188404,80246713,79136082,6157724,63372756,45407418,97379790,21673260,73392814,71316369,11365791,72725103,21070110,42947632,86543538,4798568,66611759,1569515,51715482,39847321,26493119,14093520,75820087,29029316,75229462,67474219,84187166,67030811,74743862,81805959,38061439,19898053,57359924,99297164,68000591,29401781,11757872,15902805,16019925,29188588,66322959,42967683,26744917,27187213,56473732,44177011,53842979,51507425,62762857,9257405,67124282,21533347,51588015,26863229,77620120,91802888,36505482,76825057,16405341,96318094,11923835,7687278,81677380,17365188,66116458,42692881,86022504,31161687,62051033,87710366,83150534,30771409,70420215,6525948,38022834,20867149,20885148,15948937,39553046,41442762,93359396,35456853,97281847,37891451,36808486,44473167,22997281,91990218,92215320,89078848,68875490,54058788,13819358,33336148,84840549,61271144,4985896,53888755,9575954,44842615,66271566,83302115,32058615,5970607,43501211,9188443,21993752,89699445,78589145,71522968,5822202,36780454,749283,51466049,16567550,1239555,415901,16445503,30139692,78766358,42199455,62936963,61373987,36312813,38256849,93270084,86242799,31491938,18001617,39801351,70782102,92033260,97057187,72373496,96709982,79942022,28734791,44664587,66045587,16684829,46540998,48395186,24168411,14220886,53802686,50668599,64602895,8129978,18833224,13348726,57658654,40984766,83368048,8055981,51472275,45919976,92398073,15075176,69641513,6819644,15536795,92353856,39373729,82532312,84349107,98371444,34946859,44983451,33699435,69136837,45794415,99515901,54427233,76330843,42644903,19939935,54014062,508198,15535065,79738755,27411561,58751351,83269727,82886381,4434662,79191827,70727211,20681921,30764367,65047700,26275734,95726235,5111370,65454636,57163802,66663942,17037369,42073124,99971982,8634541,62115552,84684495,21919959,54606384,26507214,9599614,72019362,95581843,82897371,16380211,62693428,49641577,9860195,98943869,38341669,65271999,55189057,526217,82779622,17146629,9058407,17385531,53632373,83948335,72614359,68110330,68702632,18663507,24826575,20765474,99861373,54517921,76540481,45381876,45996863,3773993,72732205,86301513,12024238,94076128,82726008,68694897,94711842,65074535,29635537,17894977,66885828,37280276,90004325,61897582,66667729,10358899,59318837,40865610,47893286,30891921,19376156,70372191,26102057,99604946,99729357,5832946,86460488,85023028,67084351,24619760,55669657,40152546,37620363,43152977,7955293,37659250,56531125,51590803,29065964,7423788,16424199,83133790,44889423,22200378,321665,6038457,62552524,32250045,83083131,49271185,176257,44844121,61982238,24915585,44479073,69697787,54868730,98130363,38510840,4668450,33628349,29510992,59501487,47708850,36685023,97940276,45848907,60176618,9886593,55470718,17764950,62490109,52060076,60106217,19457589,47887470,93566986,33123618,27625735,55770687,30090481,67227442,73617245,64055960,60393039,56955985,20002147,57393458,58749,6497830,1788101,18783702,65275241,68824981,25636669,75128745,22942635,73124510,75407347,52293995,10649306,4508700,65338021,30569392,18411915,48892201,55960386,26063929,45995325,51149804,72274002,59371804,96420429,96906410,36396314,83533741,72793444,3487592,93053405,94539824,16099750,52613508,78785507,61623915,20645197,95010552,34044787,60955663,13862149,71920426,31727379,97561745,79880247,91281584,63506281,49598724,17857111,17081350,3509435,95395112,91664334,26139110,73031054,53666583,14626618,2208785,35512853,44481640,36933038,10453030,47213183,22405120,36135,41380093,30694952,824872,9829782,57803235,69605283,44915235,12303248,53393358,86798033,26392416,55850790,30998561,26664538,98948034,50567636,91727510,45049308,32151165,17727650,29932657,90090964,99917599,56153345,8373289,58224549,16387575,58180653,51426311,82052050,88251446,47738185,85117092,63628376,22113133,99333375,61815223,36139942,69579137,51315460,34432810,33061250,82339363,19272365,61728685,61263068,1375023,5640302,11581181,39201414,99690194,65081429,1936762,99226875,5267545,84166196,83747892,17539625,88130087,49882705,3183975,3088684,62740044,92071990,76703609,91240048,48675329,2891150,8115266,86001008,31126490,89217461,61712234,84002370,48088883,59405277,59981773,20568363,3633375,67031644,17976208,37739481,94595668,81774825,56484900,98648327,61928316,4806458,64098930,32426752,90158785,37192445,36189527,73109997,41092102,38365584,77694700,8729146,54263880,41245325,16054533,28928175,18806856,45075471,18466635,4978543,64157906,71965942,33797252,89499542,68316156,73021291,33895336,52204879,92530431,21001913,44847298,62428472,69255765,2607799,10264691,22435353,92867155,5482538,90013093,2331773,91141395,54232247,34667286,77301523 +62452034,99604946,66250369,44842615,74137926,14093520,48360198,99549373,54427233,28928175,16097038,5970607,37675718,94090109,62430984,9188443,44473167,98739783,52261574,80555751,31733363,31904591,68816413,3294781,65275241,81855445,82886381,96193415,15064655,42782652,51472275,22450468,72777973,68644627,26863229,69355476,669105,60430369,36468541,54868730,16971929,84406788,17081350,9623492,20486294,48893685,35456853,32426752,4099191,37620363,46851987,33553959,19101477,17727650,26392416,43357947,99524975,38494874,19939935,57248122,66045587,47887470,44479073,85023028,41442762,43322743,70004753,66832478,37183543,69136837,49236559,55602660,68824981,72357096,91664334,415901,41481685,14731700,44550764,526217,77072625,70800879,98130363,29959549,40677414,2331773,99224392,71333116,70596786,98648327,85116755,61741594,59371804,10309525,10366309,4515343,29819940,321665,98948034,65715134,26139110,67474219,33628349,93515664,24591705,8913721,34698428,1250437,70036438,50806615,77284619,48892201,67031644,15015906,99226875,88251446,68875490,35192533,17385531,40781854,30653863,18663507,33304202,27325428,9058407,55247455,84166196,26507214,50007421,19486173,26275734,33797252,53802686,1197320,29029316,44627776,2204165,78549759,77300457,27665211,7919588,71522968,29994197,81677380,7502255,91802888,20122224,72373496,62803858,95957797,86001008,59405277,56531125,92215320,83155442,82427263,6111563,89419466,22721500,66885828,89499542,91957544,824872,57393458,18833224,33249630,4798568,84002370,54199160,21673260,32250045,24826575,29635537,4064751,56473732,53632373,58224549,50188404,73031054,63967300,32699744,30366150,18806856,99690194,67793644,86242799,93566986,40197395,97561745,78589145,53666583,6038457,17386996,88444207,48260151,20642888,73235980,3509435,65038678,32161669,80316608,79136082,24619760,11923835,56424103,59501487,57240218,4787945,20885148,54014062,64087743,53084256,569864,18504197,66322959,30218878,42692881,45306070,60102965,87160386,45919976,1936762,87710366,4199704,80014588,54987042,93053405,7011964,26292919,95395112,8651647,66903004,112651,2607799,28550822,61373987,84293052,17764950,95726235,38658347,39553046,12416768,82178706,39986008,64157906,77272628,79880247,27625735,7646095,3183975,6221471,73124510,15075176,81172706,86460488,34946859,45617087,20140249,90310261,90654836,24058273,58208470,36396314,37891451,91281584,22108665,17068582,28734791,72358170,56153345,75407347,60106217,47792865,2717150,16595436,9599614,4806458,52060076,7104732,168541,55960386,85695762,38645117,33061250,69641513,1022677,66663942,20681921,22129328,92604458,34236719,92398073,1569515,5822202,96318094,70372191,50702367,15535065,67281495,19376156,36505482,34497327,11757872,76434144,30090481,21919959,39201414,18783702,66271566,12664567,82052050,65017137,47708850,83789391,65454636,85242963,29834512,57359924,92033260,99917599,66137019,78766358,13862149,34295794,48088883,1239555,62051033,8733068,64055960,57803235,90457870,77377183,33431960,15948937,75820087,79191827,8115266,24915585,40984766,64848072,89214616,82979980,91255408,53888755,83651211,97641116,35996293,61623915,10358899,56955985,72278539,4508700,61263068,45790169,29510992,55753905,58751351,20002147,97281847,27411561,70727211,28851716,66611759,97379790,17146629,77187825,1204161,23848565,4069912,49641577,92554120,79657802,72738685,8373289,39373729,86543538,72732205,9398733,52613508,11161731,8634541,63372756,24733232,40534591,59318837,37501808,66116458,90013093,58749,63059024,43376279,60176618,77301523,79806380,34698463,71920426,7955293,96735716,33123618,14363867,12571310,47213183,25842078,99965001,9886593,76825057,77413012,78602717,73392814,30764367,7229550,61982238,16099750,17365188,22766820,20765474,8729146,68316156,38341669,67030811,65851721,55143724,36808486,21993752,80246713,68000591,43045786,48675329,51149804,7423788,19457589,23110625,45794415,11543098,2891150,44889423,61859143,73168565,51715482,6986898,83210802,3633375,9175338,4668450,37957788,6793819,14626618,74614639,74441448,38061439,42307449,30569392,78884452,21001913,97940276,38365584,45996863,16387575,84187166,86022504,15148031,63506281,75153252,9860195,18411915,26102057,32159704,56515456,75543508,23134715,33237508,40781449,37739481,6521313,749283,91831487,71657078,78218845,30139692,90090964,30787683,53842979,2208785,8129978,7685448,94076128,36930650,89078848,62740044,74357852,22435353,73222868,99125126,83302115,82897371,4204661,48395186,72019362,49882705,20867149,45995325,36135,8807940,82339363,55770687,62936963,81853704,32590267,29188588,89811711,49328608,33699435,46870723,1431742,59614746,66667729,90004325,88047921,76170907,30771409,44983451,9257405,55189057,51590803,36933038,17857111,73786944,72495719,5640302,97783876,96726697,88130087,44846932,9829782,91990218,44245960,89637706,42199455,29401781,31727379,71316369,76671482,51507425,26114953,18131876,68102437,26493119,30543215,75777973,14947650,53648154,55470718,6153406,68041839,26998766,99658235,17957593,77620120,44844121,74110882,20836893,42073124,99861373,62232211,45990383,56461322,61897582,72793444,92787493,33935899,77312810,10961421,27041967,99515901,247198,17894977,36312813,85571389,44847298,41092102,44060493,1788101,26063929,37435892,92803766,84904436,42237907,68204242,82651278,51464002,84349107,69255765,12348118,21070110,17016156,36753250,40027975,79821897,38022834,13348726,29065964,67451935,36780454,4095116,98943869,16380211,68110330,49597667,76960303,44784505,37166608,8696647,89046466,62693428,44348328,62762857,89804152,61712234,83747892,93562257,76703609,89699445,50567636,62357986,6808825,30811010,81274566,29932657,508198,16054533,36845587,35092039,48673079,53547802,59329511,45848907,83269727,14723410,71300104,94711842,92353856,86118021,40152546,58180653,43152977,22200378,5073754,95581843,57658654,91727510,81774825,38009615,51047803,93933709,50668599,75128745,62428472,13231279,4978543,45049308,45407418,22942635,78561158,14326617,65338021,17037369,99021067,40865610,83533741,49598724,94360702,98462867,38008118,8791066,60581278,77787724,69697787,54606384,3235882,30891921,17058722,8128637,29466406,38256849,18466635,6505939,39171895,15902805,54517921,71083565,82726008,68128438,47090124,54058788,3880712,49271185,51466049,55850790,84840549,60955663,36812683,14220886,22405120,52293995,41380093,22997281,65081429,95251277,96709982,19898053,32151165,9575954,4985896,59109400,34493392,6497830,31161687,34667286,95290172,38510840,61859581,16684829,54663246,5111370,76540481,40356877,5482538,69605283,64098930,72238278,40686254,13470059,68939068,78300864,65047700,11365791,93790285,42644903,92692283,86821229,66428795,37224844,25636669,26664538,92998591,67084351,4119747,83368048,65271999,62552524,94911072,79922758,51426311,65880522,39847321,44481640,23569917,61141874,10649306,16019925,3773993,45237957,24314885,68694897,30163921,17976208,32058615,83150534,99297164,75052463,52204879,69786756,75229462,98371444,87720882,45381876,85711894,29516592,3233084,83133790,22879907,4091162,47361209,10264691,1375023,13819358,77694700,61815223,46540998,36189527,76330843,7517032,99971982,13173644,9259676,7182642,83083131,79942022,72614359,51588015,6525948,98653983,94516935,61928316,7300047,66319530,61271144,61960400,59910624,64602895,44177011,15536795,93270084,31126490,72274002,81898046,74452589,37192445,6819644,74724075,30395570,47738185,24168411,96420429,42967683,3088684,84127901,36580610,54232247,89996536,54263880,43501211,92541302,54762643,63015256,874791,70420215,67513640,35512853,65074535,7687278,67227442,94539824,6871053,20568363,7066775,6157724,33336148,78909561,57961282,97011160,82327024,73617245,77898274,30694952,99729357,19272365,90158785,78785507,30998561,22113133,33895336,44915235,51315460,32274392,39801351,8055981,41245325,16424199,48774913,3233569,8099994,26734892,5267545,9603598,93057697,176257,72725103,43506672,18001617,59177718,75986488,42947632,67124282,18699206,33565483,61728685,36685023,44664587,73109997,57020481,90272749,30463802,83948335,91938191,31491938,33201905,26397786,82779622,93359396,74743862,11581181,69579137,5832946,62496012,59981773,94595668,90061527,86301513,70541760,82532312,28796059,16405341,4434662,62490109,88653118,20645197,87598888,57241521,91141395,92692978,19891772,86798033,23740167,47298834,14349098,92530431,80251430,41092172,96852321,91240048,43933006,23432750,55669657,26776922,81805959,63152504,53393358,59197747,34044787,76315420,99333375,55615885,45667668,63628376,28787861,9860968,14045383,79738755,62115552,95010552,10453030,34432810,37659250,92867155,12303248,96906410,89217461,68702632,70367851,16567550,92071990,47893286,60393039,75135448,21289531,27185644,73021291,23194618,71965942,16445503,48658605,17539625,21533347,12024238,27187213,2917920,85117092,88904910,24953498,45075471,13468268,56484900,15163258,26744917,69848388,21397057,37280276,10597197,71469330,84684495,37560164,88698958,70782102,36139942,45428665,97057187,57163802,79322415,3487592 +72725103,71657078,7104732,73031054,30694952,9623492,87598888,4668450,29959549,35192533,44479073,44784505,49597667,59109400,98462867,26744917,95957797,26998766,31126490,52293995,9398733,53842979,16971929,10309525,32699744,44550764,69355476,27665211,85242963,6793819,65074535,91831487,40984766,62452034,62496012,75543508,526217,90272749,20122224,54762643,65047700,1204161,15536795,75153252,83210802,38494874,33123618,22108665,47213183,47090124,7687278,93562257,34432810,89214616,10358899,44889423,62936963,1022677,16019925,61982238,96193415,33249630,45790169,29834512,89419466,59318837,38008118,34698428,17365188,88047921,99549373,34295794,1569515,67281495,29029316,8733068,30569392,99297164,7685448,66885828,70596786,45919976,18833224,36812683,16380211,73235980,13173644,30139692,38061439,77377183,99524975,36505482,60581278,16424199,50567636,20140249,69579137,79922758,57803235,17068582,99658235,35092039,82052050,80251430,40356877,14947650,62693428,66428795,24314885,96318094,415901,65038678,99333375,65715134,3233084,92398073,45075471,70782102,4806458,37620363,48360198,73124510,64098930,12571310,61373987,3880712,90457870,17957593,8055981,26063929,6153406,67793644,62552524,72373496,26863229,63967300,72274002,55753905,88444207,66322959,96906410,23848565,71965942,19939935,68694897,4099191,45990383,84406788,90090964,247198,44481640,49882705,61859143,64848072,6871053,15015906,37192445,74357852,91990218,98739783,54517921,79821897,17976208,31733363,36685023,39986008,39553046,98948034,44983451,12348118,41380093,37891451,81805959,61623915,59501487,19486173,14326617,82979980,51149804,27325428,55247455,1788101,25842078,6521313,57658654,92033260,83083131,93790285,36780454,15064655,51588015,51472275,38365584,47298834,71333116,80316608,12416768,52060076,4119747,57241521,53802686,30764367,59177718,16445503,10597197,61271144,6986898,12664567,40781449,48893685,48088883,83269727,68702632,44348328,31904591,51047803,69255765,65851721,7182642,68204242,32159704,30543215,62740044,94090109,91664334,33628349,17386996,4064751,71316369,19457589,1239555,15535065,26493119,39801351,33553959,66045587,37280276,97783876,98371444,36930650,14363867,9886593,90158785,61728685,70800879,85711894,37183543,98648327,66832478,20885148,5111370,65017137,45407418,33304202,9860968,90013093,30787683,321665,50806615,30163921,36753250,8128637,91938191,18504197,97281847,80555751,95726235,99125126,63628376,81898046,3088684,69641513,61263068,93933709,55770687,80014588,23569917,8115266,30218878,44245960,30653863,70727211,43322743,34044787,84187166,54427233,38658347,70367851,7955293,78766358,59197747,33201905,11543098,28734791,50188404,77272628,92692978,62232211,64602895,53084256,49236559,4515343,95395112,82726008,48774913,83533741,92554120,44177011,22450468,85023028,38341669,59371804,65338021,60102965,99515901,72738685,78884452,14626618,93270084,26292919,74743862,91802888,37166608,168541,10366309,5970607,18663507,78785507,6497830,74441448,32426752,71300104,99604946,4095116,91281584,66611759,26392416,66663942,69605283,75777973,24591705,44844121,9829782,56955985,68102437,33797252,89499542,28851716,82427263,60430369,54232247,85116755,45848907,92998591,6808825,99971982,61741594,33431960,87720882,60955663,86118021,58180653,508198,43376279,89078848,17016156,15948937,76315420,17058722,10264691,93359396,77187825,9188443,56153345,70004753,18466635,22997281,37659250,67030811,72732205,68644627,20681921,56515456,23194618,76330843,79738755,4508700,61141874,69848388,21673260,78589145,45428665,32250045,70541760,55602660,59910624,77284619,44846932,73786944,18699206,89996536,68875490,9058407,82339363,53393358,45381876,69136837,5822202,78549759,84684495,76960303,4985896,86543538,75986488,23740167,30463802,29635537,64055960,30998561,3773993,2717150,24915585,57248122,29932657,96852321,86001008,9259676,569864,72777973,77312810,36135,4798568,21993752,669105,81677380,8373289,2331773,8651647,29188588,24733232,57359924,71522968,86460488,45995325,56531125,32058615,96726697,29065964,36580610,79880247,44473167,2917920,91727510,42947632,28787861,74724075,79942022,40197395,92541302,99965001,4434662,11365791,81274566,30771409,6038457,39847321,42199455,22129328,7300047,96709982,45617087,14045383,47792865,5832946,2891150,83368048,1250437,63152504,33565483,99021067,79657802,34236719,99861373,56484900,46851987,10453030,93515664,19272365,92353856,51426311,84002370,76825057,83789391,75407347,73222868,88698958,14093520,13231279,33336148,54058788,112651,79191827,38645117,51466049,27185644,75135448,63059024,39171895,15163258,84840549,37435892,92867155,58751351,4069912,93053405,26114953,70420215,45306070,22942635,40677414,54987042,92215320,84293052,86821229,54663246,37739481,26776922,94516935,76671482,66667729,65880522,14731700,16099750,40865610,43933006,67451935,34497327,37224844,74110882,54606384,78909561,11161731,5482538,44060493,15075176,62430984,64087743,17539625,87710366,61960400,65271999,89699445,57961282,28550822,93057697,3509435,24058273,29510992,57163802,55189057,68824981,77898274,10649306,8913721,39201414,22721500,18783702,55470718,97561745,62051033,89637706,29819940,89046466,4204661,63015256,26102057,51590803,77694700,8129978,65454636,42307449,53666583,19376156,18806856,66319530,77413012,1197320,45794415,72495719,94711842,41245325,45237957,42782652,7502255,45996863,9860195,24619760,27625735,54263880,96735716,77787724,3487592,10961421,81172706,9257405,32161669,21001913,73109997,50702367,77620120,8729146,84127901,30090481,33895336,4787945,53648154,73168565,84349107,19101477,45667668,94360702,13470059,62490109,74452589,9175338,67084351,95010552,67227442,30891921,76434144,98130363,7646095,74614639,8807940,28796059,41442762,69697787,78561158,22435353,9603598,29994197,51464002,26397786,9575954,47738185,39373729,83948335,824872,62357986,26275734,50668599,48658605,57020481,72019362,82532312,75229462,8696647,54199160,49641577,46870723,83150534,79322415,73392814,18411915,68128438,50007421,83155442,27041967,47893286,6505939,32151165,97011160,53547802,3183975,42073124,52204879,92803766,30395570,59981773,55669657,23110625,61897582,60106217,92692283,68939068,2208785,44842615,73617245,63506281,56424103,11581181,68000591,35456853,17385531,78300864,88653118,55615885,6525948,62762857,26507214,1431742,62803858,26139110,58224549,89811711,47708850,73021291,42967683,88904910,72793444,89217461,68316156,61815223,82897371,6111563,24826575,34946859,98653983,16097038,58208470,20642888,16595436,87160386,48892201,8791066,71920426,91240048,47887470,6221471,80246713,12303248,36312813,16387575,57240218,84166196,36845587,52613508,79806380,55143724,85571389,81853704,52261574,44627776,22879907,8099994,35512853,69786756,749283,22766820,66271566,86301513,92071990,77300457,66250369,43152977,53888755,61859581,54868730,3633375,7517032,92604458,20568363,37675718,36189527,83302115,20002147,26664538,98943869,16405341,23134715,63372756,92530431,67513640,32274392,66137019,18131876,35996293,7423788,94911072,20765474,1375023,31727379,56473732,176257,51315460,74137926,2204165,89804152,53632373,78218845,82651278,51715482,14220886,19891772,81774825,83133790,86022504,65081429,3235882,20645197,99917599,97940276,57393458,43357947,42644903,12024238,76703609,6157724,31161687,90004325,16567550,23432750,17857111,27411561,68110330,85117092,26734892,4091162,33061250,20836893,36933038,30366150,43506672,14723410,17764950,41092172,40152546,48260151,22113133,70036438,46540998,97641116,20867149,34698463,49328608,34667286,76170907,72357096,7011964,9599614,15902805,32590267,55960386,71083565,7919588,92787493,41092102,5267545,62428472,13348726,44915235,97057187,5073754,82886381,59329511,47361209,94076128,40781854,95581843,75820087,67124282,3233569,16054533,66903004,14349098,17037369,33935899,29401781,24168411,21397057,40027975,62115552,61712234,41481685,45049308,60393039,13819358,3294781,91255408,21070110,88251446,874791,16684829,4978543,71469330,36139942,17146629,91141395,78602717,72278539,67031644,97379790,94539824,37560164,91957544,49598724,75128745,60176618,40686254,48673079,43045786,59614746,7066775,36808486,94595668,77301523,11923835,84904436,30811010,85695762,76540481,95290172,20486294,54014062,36396314,99226875,8634541,95251277,44847298,61928316,2607799,66116458,99729357,81855445,37957788,37501808,67474219,75052463,15148031,55850790,29516592,42237907,33699435,79136082,48675329,99224392,82178706,72358170,17727650,68816413,29466406,21919959,28928175,7229550,82327024,40534591,88130087,68041839,4199704,1936762,82779622,22200378,51507425,38022834,31491938,13468268,72614359,90654836,21533347,27187213,77072625,22405120,33237508,21289531,6819644,83651211,25636669,13862149,99690194,59405277,49271185,19898053,38510840,72238278,43501211,17081350,34493392,42692881,93566986,86798033,44664587,70372191,38256849,38009615,48395186,58749,65275241,24953498,18001617,86242799,36468541,90061527,90310261,96420429,56461322,17894977,5640302,64157906,11757872,83747892 +42073124,4787945,24591705,15535065,52060076,17068582,53666583,99604946,29029316,45794415,80555751,67030811,17016156,5111370,14326617,8913721,92398073,82886381,37183543,94595668,18783702,52293995,93933709,1197320,49597667,112651,35996293,19101477,2917920,5822202,36580610,68816413,22766820,40027975,526217,74137926,61859143,48774913,5970607,46870723,48360198,47090124,8696647,65271999,73168565,15064655,23194618,77898274,32161669,11923835,66045587,13231279,50007421,30395570,23848565,89214616,80251430,61897582,62232211,72278539,16097038,99021067,33201905,86022504,35092039,50806615,98130363,67793644,97783876,16971929,14045383,92353856,12571310,17976208,98462867,44842615,45996863,26392416,55770687,415901,14349098,62115552,168541,53632373,569864,50188404,36808486,97641116,17727650,53842979,85711894,68644627,89078848,77620120,47298834,66611759,70367851,19891772,91957544,85242963,22997281,1022677,61982238,47708850,39171895,2717150,65715134,48893685,64157906,40984766,29994197,77413012,17539625,96318094,4064751,45306070,76703609,43152977,12416768,70596786,4434662,26102057,7919588,56515456,247198,5640302,92033260,91802888,26292919,56424103,77312810,81898046,91664334,96726697,96420429,33431960,53084256,86001008,67281495,97940276,6808825,22942635,72495719,89811711,84840549,30090481,3509435,96735716,38008118,60430369,63506281,2331773,62430984,61623915,55753905,8651647,81855445,26863229,92554120,6153406,62740044,18833224,19376156,82979980,38494874,59501487,51047803,75543508,9603598,16380211,29819940,64098930,82339363,16424199,34497327,76170907,34432810,90004325,15148031,28851716,43322743,9398733,45617087,26744917,58208470,19939935,14626618,70036438,74724075,43501211,30998561,39801351,50567636,71657078,37957788,57359924,10366309,55189057,54232247,23740167,93053405,20486294,33565483,29635537,40686254,68204242,34295794,61263068,57803235,78766358,87160386,30764367,72793444,99658235,3487592,51507425,73031054,66667729,38061439,26114953,11161731,14731700,86242799,6986898,62357986,77300457,34698463,19486173,62490109,41481685,30694952,26734892,38645117,18411915,31126490,83210802,48395186,67451935,32699744,54427233,57248122,45428665,79136082,97281847,27665211,14947650,70372191,26397786,57240218,7011964,80014588,7104732,75229462,72738685,68102437,54058788,71333116,11757872,95290172,47792865,50668599,68875490,59197747,37192445,63152504,90654836,44473167,98943869,91990218,77284619,81677380,19457589,68939068,85023028,26493119,48892201,21070110,5482538,49598724,38341669,83155442,92071990,75135448,62452034,52261574,1204161,45995325,22450468,70004753,94711842,9623492,78785507,30463802,30569392,65081429,13348726,31727379,12348118,93790285,82779622,83302115,874791,92803766,6793819,51466049,11365791,73617245,29188588,36780454,7517032,20836893,88698958,60106217,4069912,73235980,24058273,56473732,7685448,17365188,61728685,508198,1239555,10264691,91831487,2208785,22200378,67031644,66885828,76540481,44844121,99965001,55247455,66903004,7955293,72358170,99861373,54762643,47213183,84904436,95010552,36812683,79922758,4119747,61141874,69641513,84187166,54199160,3088684,54606384,9259676,27411561,78218845,70420215,4099191,38658347,57393458,29932657,19898053,89996536,73124510,21673260,16019925,59910624,68000591,93359396,66428795,51149804,48675329,75820087,9575954,32426752,8807940,92998591,44664587,5832946,55602660,88444207,86798033,17385531,23569917,72019362,88904910,13470059,24826575,30771409,98948034,55960386,37435892,99549373,37675718,50702367,71083565,69579137,44245960,9058407,17957593,92541302,12664567,70727211,43357947,22108665,79880247,86821229,26664538,35192533,38365584,15163258,96709982,39986008,14093520,77694700,81805959,90013093,55143724,37560164,49271185,90457870,26063929,84002370,83083131,95726235,94076128,3233084,79322415,4508700,33304202,31904591,35456853,45667668,84127901,90272749,42967683,9188443,66250369,90061527,31491938,45919976,99297164,4806458,51590803,95395112,176257,1569515,33336148,67124282,64602895,86301513,23110625,63628376,13468268,96852321,15948937,30218878,61960400,69136837,75986488,63372756,98371444,6505939,1375023,7300047,60955663,45407418,33628349,63967300,36312813,16054533,77787724,36396314,77272628,32590267,22879907,40677414,20642888,86460488,72777973,12024238,10453030,59371804,36139942,97011160,75153252,74614639,42644903,59109400,82897371,99125126,57163802,21993752,47361209,89637706,82726008,17146629,31733363,26507214,78589145,96906410,13862149,11581181,53547802,57658654,53648154,44915235,7423788,69355476,65338021,40865610,15902805,49641577,51464002,91255408,28928175,77072625,67084351,99224392,30787683,6157724,22129328,28550822,9829782,53802686,43506672,65454636,36135,8055981,37166608,91938191,93562257,61271144,92692283,22405120,16445503,66322959,99226875,7687278,73392814,93057697,64848072,26998766,64087743,20122224,36753250,85116755,7646095,10309525,17081350,93566986,90310261,4515343,89419466,8128637,37501808,25636669,8634541,84166196,19272365,48673079,66271566,74452589,72725103,36930650,54263880,84293052,46851987,58751351,46540998,94911072,75777973,28787861,33895336,5073754,16595436,1936762,83651211,41380093,80316608,34493392,61859581,70800879,8099994,30139692,79738755,61373987,29466406,1431742,71300104,20645197,69848388,83150534,57961282,99971982,20681921,89046466,22113133,43376279,3880712,61741594,94090109,77187825,56955985,38009615,37620363,54014062,37739481,76825057,78561158,82052050,33553959,89499542,82532312,23432750,44177011,34044787,92215320,39553046,76960303,34667286,34698428,29510992,36933038,3773993,30811010,83789391,71920426,3235882,95581843,88251446,94360702,87710366,39847321,89699445,60102965,39373729,76330843,81853704,17857111,94539824,40534591,17764950,45990383,98648327,55615885,57241521,26139110,47893286,81274566,33123618,18806856,40152546,79821897,30366150,47887470,18663507,18001617,30653863,72732205,76434144,97561745,54663246,69255765,72373496,61712234,52204879,29959549,55470718,72238278,21533347,88653118,81172706,44348328,20765474,74110882,40197395,45237957,44847298,4204661,73222868,29834512,62496012,20140249,34236719,44479073,32250045,70782102,24619760,36189527,49236559,42692881,27185644,62051033,54868730,40781449,29516592,23134715,45049308,44627776,7229550,9860195,94516935,73109997,69786756,9886593,73786944,17037369,78884452,89804152,24733232,98739783,72274002,33249630,27041967,82178706,99524975,62936963,321665,79657802,43045786,3294781,16405341,8373289,64055960,6111563,48658605,9599614,37659250,65851721,68694897,68110330,18131876,92867155,99515901,43933006,10597197,3633375,1250437,72614359,61928316,45075471,41092172,16387575,59329511,62803858,77377183,68316156,67474219,6871053,86543538,79942022,44889423,48260151,4985896,51588015,22721500,27325428,65038678,72357096,79191827,49882705,33061250,87720882,51715482,11543098,2607799,9175338,52613508,17894977,78602717,51472275,99333375,44481640,21001913,79806380,84684495,83533741,41442762,9257405,95957797,8115266,6819644,30891921,21919959,59614746,68824981,3233569,66319530,66832478,65017137,56461322,6525948,49328608,53393358,75052463,4199704,4095116,20885148,15536795,36845587,83747892,14220886,33237508,33797252,29401781,93515664,41092102,37224844,68128438,63015256,15075176,62693428,85695762,28734791,42307449,25842078,8129978,10358899,34946859,44550764,55669657,96193415,4091162,88047921,36685023,29065964,26275734,91727510,8733068,44983451,44846932,75128745,13173644,57020481,10649306,75407347,92692978,45790169,39201414,59981773,89217461,10961421,38510840,85571389,27625735,73021291,80246713,74441448,63059024,97379790,6521313,36505482,26776922,33935899,24168411,40781854,35512853,99917599,71965942,60581278,6497830,69697787,7502255,4668450,824872,41245325,30163921,84406788,91281584,95251277,82427263,30543215,58749,40356877,99729357,45848907,84349107,38022834,6221471,62552524,3183975,16684829,76671482,42782652,91141395,2204165,74357852,58180653,31161687,59318837,78300864,32151165,32058615,99690194,42947632,78909561,98653983,68702632,67513640,65275241,59177718,24953498,58224549,32274392,20002147,62428472,66663942,20568363,71316369,6038457,20867149,17058722,78549759,97057187,48088883,83269727,18466635,13819358,51315460,54517921,61815223,92530431,74743862,4978543,8729146,1788101,81774825,62762857,5267545,4798568,65880522,65047700,82327024,83368048,71469330,71522968,56531125,24314885,83133790,14723410,42199455,12303248,66116458,22435353,2891150,17386996,21397057,24915585,60393039,65074535,91240048,70541760,56484900,69605283,42237907,38256849,90090964,18699206,37891451,45381876,44060493,87598888,86118021,47738185,82651278,749283,7182642,32159704,7066775,55850790,90158785,77301523,67227442,51426311,53888755,59405277,85117092,88130087,16567550,92787493,669105,36468541,16099750,37280276,8791066,68041839,18504197,92604458,44784505,56153345,66137019,28796059,14363867,33699435,54987042,21289531,15015906,9860968,83948335,93270084,76315420,27187213,60176618 +82897371,84187166,55615885,43322743,66832478,53547802,76330843,8807940,92554120,68939068,48360198,168541,8099994,24591705,67513640,75153252,45667668,28851716,4064751,29029316,84293052,37183543,59177718,67451935,92692283,51715482,64157906,62452034,3773993,38061439,68816413,47090124,55247455,48260151,99965001,62496012,12348118,112651,50188404,415901,33201905,82979980,85571389,26744917,4099191,1431742,70420215,98462867,68644627,33336148,27665211,69786756,8913721,45075471,53666583,46870723,92803766,14349098,54606384,70800879,23432750,77377183,15535065,43933006,39801351,3233084,15948937,63372756,99524975,16971929,82532312,34044787,58751351,669105,7955293,53842979,62693428,2717150,26392416,93515664,44889423,34432810,39847321,16445503,94911072,82726008,29466406,176257,6986898,61859143,62051033,77413012,96726697,17386996,40197395,50007421,21919959,23848565,4787945,61928316,8651647,76434144,71316369,30366150,70367851,83651211,24058273,29819940,9257405,66322959,69697787,32699744,18783702,73786944,75986488,874791,44983451,3294781,70541760,14947650,89046466,69848388,85116755,51047803,40984766,23110625,39986008,22108665,91664334,34295794,67084351,64848072,59910624,29834512,52613508,81274566,84840549,35092039,92033260,97379790,68000591,66611759,1197320,92353856,33553959,26863229,7687278,93933709,73168565,72614359,62357986,9599614,67227442,40781449,34698428,19939935,23194618,4119747,66045587,36505482,75777973,10309525,54014062,55753905,569864,91957544,4806458,77312810,63967300,88653118,36580610,99861373,79821897,39171895,62936963,21070110,33249630,38008118,92398073,84904436,89996536,47708850,13348726,23569917,7517032,12571310,9398733,95957797,526217,49271185,53648154,44479073,34497327,16405341,30569392,81805959,70596786,57248122,8696647,26998766,61141874,45407418,76960303,77898274,94360702,36780454,75229462,92541302,89419466,89078848,19898053,73617245,87160386,70036438,91938191,85711894,73124510,80014588,63015256,96193415,19457589,10366309,99658235,66428795,17539625,1569515,16595436,247198,57961282,66319530,31126490,5832946,1022677,44842615,79136082,99515901,65454636,83210802,44481640,68316156,61263068,80251430,93790285,93359396,29635537,73031054,10961421,22405120,26063929,11161731,71333116,27325428,95010552,63059024,35456853,41245325,64602895,89214616,96906410,61815223,32161669,51464002,55770687,43152977,15163258,50668599,82178706,36808486,78766358,54427233,71522968,27411561,60106217,15064655,63506281,56473732,54762643,77072625,54987042,85242963,59371804,99549373,47361209,13862149,69579137,85117092,37620363,83155442,91240048,8115266,49641577,54663246,30764367,76671482,65880522,35192533,84127901,9259676,19272365,68875490,6157724,16387575,19101477,33628349,57240218,50702367,37280276,30771409,17976208,36845587,7423788,75820087,94090109,20765474,57241521,11543098,40534591,68204242,65017137,45617087,14731700,99125126,29065964,57803235,45428665,37957788,61741594,8733068,62232211,58180653,75052463,71469330,77272628,42692881,22997281,68102437,12416768,47792865,18699206,47298834,26776922,36753250,62740044,74441448,26292919,8128637,55189057,38494874,18131876,40677414,20486294,54199160,65038678,65271999,83302115,72373496,22721500,38365584,40356877,50567636,13470059,1375023,56515456,98943869,4668450,47213183,18833224,44915235,36312813,18466635,97561745,56424103,70727211,81853704,1204161,67474219,90310261,14045383,41092102,72274002,77284619,16567550,72358170,60102965,30139692,9058407,17068582,33123618,8634541,99297164,51149804,51466049,7011964,48658605,26139110,30543215,62490109,59318837,86821229,88904910,37501808,88047921,19891772,59614746,43506672,53802686,32058615,35996293,43376279,90654836,17037369,15536795,38022834,83789391,38341669,59109400,62803858,89811711,42947632,84002370,15015906,96735716,33565483,93566986,56461322,33304202,37891451,7300047,36930650,45995325,85023028,28796059,98653983,40865610,15075176,90061527,36396314,45990383,72495719,78785507,33237508,37675718,92787493,97783876,3487592,6153406,61373987,93053405,4798568,42782652,90272749,79191827,87598888,7646095,12664567,33797252,74614639,9829782,28550822,80555751,95726235,36812683,65338021,96318094,83083131,59197747,3880712,67281495,86001008,40027975,42307449,20140249,55602660,5482538,81677380,79922758,65047700,76540481,27041967,24168411,4204661,66667729,91255408,30163921,68694897,82427263,63628376,2917920,48893685,16380211,28787861,11581181,9603598,19486173,48673079,69355476,47738185,321665,83533741,68824981,26493119,96852321,44473167,71300104,88130087,91990218,61897582,6521313,508198,42644903,2204165,3235882,61960400,66663942,25842078,49328608,27625735,9860968,7182642,29994197,31904591,66250369,49597667,68128438,72357096,86022504,30694952,24733232,98739783,99224392,74110882,84349107,22129328,99690194,6819644,94711842,71083565,90004325,4069912,30090481,7502255,30891921,48892201,86460488,90457870,45919976,33431960,76825057,44846932,79806380,9886593,61712234,42073124,65715134,32426752,18504197,84406788,34493392,78561158,56484900,23740167,10649306,77620120,7685448,33895336,99021067,31733363,99729357,45790169,44177011,64098930,84166196,88698958,21993752,99333375,48774913,70004753,4091162,4515343,20642888,17764950,81855445,22766820,20122224,73021291,15148031,66885828,38645117,29188588,37739481,75543508,65074535,78589145,77300457,65851721,60581278,41481685,66271566,13231279,53084256,26734892,17894977,69255765,75407347,24826575,99226875,31491938,5111370,38256849,51507425,29932657,3233569,17058722,44844121,71657078,60430369,54517921,2208785,30811010,36685023,61728685,9623492,24619760,80316608,53632373,54058788,14363867,38009615,76315420,52204879,45237957,63152504,94539824,95581843,17957593,17365188,45794415,48088883,26664538,97641116,41380093,28928175,9188443,11923835,13173644,24314885,55143724,6793819,4095116,29959549,94516935,95290172,62762857,14723410,72278539,86798033,45996863,89637706,23134715,22942635,93057697,30998561,25636669,53888755,89804152,18806856,97281847,26397786,15902805,98130363,7229550,44627776,60176618,65275241,93562257,73392814,46540998,20867149,16019925,58224549,69605283,9175338,29401781,55960386,79942022,6505939,71920426,6808825,77787724,67793644,84684495,39201414,11757872,17727650,20836893,6111563,27185644,7919588,44550764,44784505,22879907,96420429,11365791,72793444,49236559,80246713,17385531,67030811,55669657,9575954,76170907,90090964,78549759,64087743,70372191,16054533,78602717,69641513,67124282,83368048,22435353,36189527,17146629,14326617,20885148,96709982,30787683,24953498,51588015,74724075,3509435,30463802,89699445,45848907,58208470,4199704,78218845,31727379,86118021,1936762,17016156,74137926,45049308,67031644,81774825,18663507,26507214,36468541,5640302,61859581,30653863,4985896,52293995,81172706,14093520,30218878,37192445,43501211,94076128,51472275,10358899,32590267,93270084,62430984,44664587,91802888,14220886,1788101,69136837,56955985,59329511,70782102,72238278,76703609,83269727,92998591,73109997,57359924,40152546,45381876,9860195,5822202,59501487,18411915,10597197,97011160,16424199,5267545,92604458,39553046,19376156,43357947,3088684,31161687,44245960,5970607,2331773,749283,92692978,91727510,88444207,66903004,37560164,51590803,99971982,83133790,58749,78909561,68041839,14626618,40686254,95395112,50806615,74357852,78884452,36135,32151165,61271144,92215320,37435892,7104732,47893286,79880247,89499542,44060493,57020481,97057187,99604946,41442762,72777973,86242799,73235980,87710366,52261574,62428472,74743862,83150534,36139942,32159704,26275734,81898046,91281584,21533347,42199455,48675329,74452589,37166608,17081350,36933038,75128745,16097038,6871053,21289531,6497830,12303248,77694700,79657802,38658347,51426311,32250045,97940276,16099750,66137019,34236719,82651278,87720882,61623915,2607799,52060076,35512853,91831487,6221471,98948034,8373289,71965942,98371444,82779622,8129978,72725103,33935899,82052050,62552524,98648327,54263880,99917599,55850790,56531125,20681921,45306070,13819358,83948335,75135448,16684829,39373729,53393358,44348328,30395570,47887470,10453030,59405277,8055981,33061250,24915585,18001617,66116458,49598724,37224844,20568363,86301513,51315460,17857111,37659250,27187213,85695762,79322415,78300864,61982238,21673260,1239555,34946859,92867155,59981773,82886381,13468268,12024238,20002147,46851987,68110330,42237907,54232247,68702632,34667286,44847298,4978543,22450468,88251446,83747892,29516592,62115552,824872,90013093,4434662,6038457,3633375,72738685,28734791,77301523,6525948,43045786,32274392,26102057,10264691,73222868,20645197,1250437,77187825,72732205,57658654,92071990,56153345,34698463,65081429,8791066,42967683,60955663,5073754,82327024,57163802,7066775,55470718,3183975,64055960,38510840,22200378,26114953,8729146,89217461,4508700,21001913,48395186,54868730,94595668,40781854,90158785,86543538,79738755,2891150,72019362,82339363,21397057,49882705,33699435,95251277,22113133,91141395,41092172,29510992,60393039,92530431,57393458 +99515901,96318094,48893685,98462867,68204242,4119747,68644627,47090124,12348118,18699206,85116755,4787945,62496012,65338021,55189057,53842979,34497327,8807940,33797252,55770687,15535065,61623915,68816413,44842615,96709982,63628376,96193415,48360198,83210802,14093520,99524975,9623492,26664538,30366150,33249630,20486294,35996293,95957797,43933006,98371444,7011964,24058273,72274002,6153406,80555751,99965001,81855445,29834512,43501211,45617087,71469330,69355476,80251430,84349107,66137019,5970607,415901,80246713,4515343,6871053,92554120,11581181,16054533,65275241,15064655,20122224,45237957,82339363,35092039,42967683,99297164,27411561,82178706,37183543,49236559,34698428,20836893,65017137,86001008,34946859,56424103,7502255,26139110,55143724,68000591,42947632,4099191,59318837,51466049,74724075,50188404,77413012,42782652,32699744,34295794,26063929,48658605,98130363,55669657,76330843,69136837,47361209,10366309,78561158,73222868,27665211,77787724,14220886,31904591,76434144,67031644,56473732,71657078,73392814,14947650,61982238,94090109,72373496,96906410,64055960,59371804,86543538,62936963,40984766,14349098,70004753,78785507,44481640,19101477,29959549,63506281,7300047,10649306,22997281,22108665,18663507,16405341,38658347,65715134,56153345,17068582,21070110,91938191,10961421,28550822,38365584,88047921,18783702,4806458,96726697,40781449,51472275,66250369,36753250,18131876,33123618,6793819,24591705,69605283,21993752,42237907,14045383,68128438,92033260,30395570,91664334,75543508,43152977,22129328,71083565,61815223,61263068,77377183,58751351,10309525,34493392,65081429,569864,33699435,7423788,53084256,97783876,47708850,67793644,78589145,44847298,68939068,9188443,40534591,40865610,3294781,54606384,7517032,43322743,10597197,62762857,14363867,29635537,44664587,85242963,93566986,80316608,44060493,30694952,29994197,84840549,84187166,78884452,70420215,94595668,33336148,61859143,16019925,95726235,52613508,59109400,10358899,83789391,33553959,93933709,50567636,19898053,37620363,37224844,63372756,35192533,45790169,51047803,20140249,7104732,24733232,97641116,77898274,30764367,2204165,23569917,87598888,53547802,66322959,86460488,68102437,9257405,60581278,16567550,61960400,44844121,96735716,20765474,60430369,3233569,91727510,3880712,69579137,65047700,45667668,9398733,1197320,508198,8634541,36505482,44479073,41481685,26397786,73617245,56531125,71333116,90090964,67030811,13862149,91990218,52060076,83302115,12416768,24619760,9259676,66663942,45428665,50007421,77312810,18833224,75777973,64848072,57240218,17764950,40197395,45919976,27325428,18504197,73031054,44177011,62740044,63967300,84904436,41092102,58749,84684495,21289531,92398073,54868730,1431742,57359924,6521313,54427233,5482538,92787493,33628349,99690194,32274392,14326617,38494874,99549373,66611759,14626618,26392416,67281495,33304202,43506672,71300104,90457870,51464002,33237508,21919959,14723410,66428795,22879907,70541760,95395112,81898046,42073124,47298834,32426752,64098930,5640302,66667729,76825057,321665,66116458,44889423,55850790,38008118,51715482,1204161,99125126,8099994,29065964,67124282,99861373,48774913,36685023,44784505,20002147,86242799,29819940,44983451,60102965,91831487,88130087,77620120,55247455,39171895,75052463,88251446,49328608,57803235,66885828,72738685,6986898,93359396,89419466,3633375,49641577,1788101,62232211,36580610,15148031,3183975,24168411,13468268,98739783,669105,83155442,29029316,59177718,68316156,74137926,17957593,82979980,7685448,62803858,73124510,21001913,22721500,7919588,168541,26776922,47792865,94516935,69848388,30139692,74614639,40152546,94360702,15015906,65074535,39847321,55753905,79821897,30891921,79738755,30787683,45407418,36468541,76540481,4091162,67451935,15163258,5111370,26863229,19891772,30543215,3233084,41092172,92353856,82427263,53666583,14731700,70800879,39801351,33201905,87160386,26275734,62490109,52204879,54014062,19486173,89811711,91802888,33431960,83150534,90272749,26507214,63059024,44550764,86022504,13348726,18806856,8696647,88698958,38061439,13173644,30569392,66832478,89996536,20681921,23432750,91240048,25842078,13470059,78766358,50702367,8115266,20642888,62693428,70372191,82726008,58208470,46851987,79942022,37957788,31126490,64087743,32250045,77694700,3509435,11365791,824872,99021067,95290172,90013093,97561745,6505939,61928316,88653118,11923835,79191827,76170907,23134715,98943869,16380211,95581843,4064751,11543098,83533741,89214616,874791,93057697,5267545,97379790,52261574,45306070,64157906,69786756,18466635,17146629,28787861,4204661,84002370,89804152,24953498,57248122,16971929,62552524,27041967,67227442,80014588,59614746,69641513,65038678,29466406,92541302,72495719,56515456,3088684,78602717,21673260,63015256,68824981,16595436,7646095,8651647,66319530,30771409,24826575,61373987,93053405,93562257,39553046,44627776,1375023,55960386,70367851,62357986,5822202,67084351,2331773,86798033,247198,81274566,95010552,50668599,44473167,15902805,21533347,43376279,79806380,78218845,82897371,70727211,70036438,16097038,59405277,48260151,29510992,9829782,74441448,65851721,29188588,96852321,48088883,23848565,39373729,77072625,29516592,99333375,40356877,40677414,28928175,59197747,48892201,26114953,72777973,37166608,22405120,8055981,72793444,93790285,176257,47893286,92692283,83651211,50806615,2891150,28734791,35456853,41380093,79922758,6808825,36780454,17386996,77272628,6111563,55470718,81805959,36933038,54987042,55615885,66903004,61897582,85571389,39201414,11161731,59329511,54199160,37659250,45075471,89078848,85711894,8373289,57163802,1022677,19939935,45990383,61141874,66271566,81172706,68041839,40686254,30090481,46540998,32161669,55602660,28796059,29932657,32590267,10264691,7955293,48673079,45996863,37501808,60106217,99658235,47213183,90310261,1936762,4668450,62430984,36312813,72732205,2917920,2717150,30218878,8128637,25636669,9860968,15948937,71522968,68875490,3487592,84293052,75135448,12571310,9058407,83269727,22942635,77284619,60393039,98653983,526217,5073754,92692978,17539625,85695762,81853704,63152504,34698463,85117092,45995325,31733363,39986008,91255408,70596786,93270084,32159704,73235980,99917599,65271999,112651,87720882,8129978,59501487,36930650,40781854,74743862,3773993,38645117,26292919,54263880,17385531,53393358,36396314,69255765,75153252,8729146,66045587,86118021,57393458,89637706,82886381,73021291,73109997,58224549,34236719,17976208,77300457,20867149,8733068,87710366,4069912,13819358,71920426,74110882,51588015,34432810,73168565,92803766,23194618,2208785,62452034,37675718,78909561,72614359,60176618,57961282,72358170,86301513,56461322,51590803,64602895,36808486,94711842,82651278,51426311,9175338,31727379,73786944,36139942,98648327,92071990,49271185,17365188,7229550,61859581,22200378,83948335,19272365,30811010,8913721,9575954,17727650,62428472,6157724,44245960,57241521,27185644,16424199,67513640,9860195,30998561,12664567,83368048,57658654,36812683,42692881,82327024,89046466,84127901,54663246,7687278,9886593,72725103,97011160,82532312,23110625,35512853,72019362,38510840,99604946,9599614,91957544,54517921,83133790,99729357,97057187,37739481,86821229,91281584,68694897,4095116,88444207,75128745,26744917,61712234,31161687,51149804,41245325,40027975,49597667,79657802,4434662,38009615,37192445,79880247,99226875,36189527,42307449,52293995,96420429,37560164,6038457,33895336,45848907,75820087,9603598,7182642,30653863,53888755,17894977,6525948,19376156,48395186,54058788,20885148,97281847,37891451,44846932,22766820,79136082,47738185,10453030,88904910,90061527,78549759,16445503,89499542,46870723,26102057,84166196,53648154,69697787,4199704,99971982,23740167,85023028,33935899,1569515,43045786,16684829,17037369,42199455,17857111,72357096,47887470,31491938,38022834,4978543,58180653,81677380,28851716,13231279,7066775,84406788,75229462,4798568,6819644,99224392,71965942,30163921,44348328,749283,89699445,90004325,93515664,22450468,92998591,4508700,24314885,65880522,94911072,59981773,76703609,12303248,15536795,51315460,6497830,5832946,27625735,17016156,37280276,92530431,18001617,19457589,59910624,33565483,61728685,38256849,32058615,82052050,41442762,20645197,76315420,22113133,61741594,76671482,60955663,62051033,48675329,38341669,94539824,72278539,91141395,32151165,26998766,72238278,54762643,26734892,15075176,4985896,6221471,1250437,98948034,34667286,1239555,70782102,3235882,65454636,92215320,76960303,53632373,97940276,92604458,16099750,94076128,12024238,42644903,75986488,62115552,68702632,30463802,89217461,17058722,36135,75407347,56955985,45794415,29401781,79322415,44915235,49598724,49882705,77301523,22435353,53802686,71316369,95251277,61271144,74357852,24915585,92867155,45049308,81774825,51507425,90654836,36845587,45381876,17081350,83083131,43357947,8791066,83747892,27187213,20568363,54232247,33061250,67474219,57020481,2607799,11757872,90158785,16387575,74452589,26493119,34044787,21397057,18411915,56484900,82779622,77187825,37435892,68110330,78300864 +70596786,9860195,37620363,76434144,67793644,1431742,78218845,55770687,14093520,80014588,26114953,36505482,21070110,1204161,15148031,70004753,57803235,37183543,29959549,99549373,52293995,15015906,5111370,53084256,49236559,112651,75407347,9259676,47213183,76315420,29188588,79922758,1250437,68644627,31904591,75052463,54427233,44983451,1788101,92554120,66832478,87720882,61859581,74357852,72738685,52261574,70800879,27665211,34497327,99658235,89214616,65047700,75153252,3633375,26102057,12416768,91664334,38494874,55470718,91802888,18131876,92692978,59614746,95726235,60430369,11365791,28550822,20486294,66885828,39171895,30139692,42967683,4798568,97783876,1022677,10309525,30543215,62428472,62740044,4091162,43045786,87160386,77620120,15535065,20002147,32161669,38658347,74137926,45919976,26493119,68128438,16595436,35192533,77187825,86821229,42073124,31126490,8696647,54868730,50806615,749283,35092039,36189527,15536795,76330843,1569515,27185644,22405120,9886593,9623492,44784505,83155442,77312810,13470059,7104732,39801351,83368048,18806856,39986008,16380211,3880712,43933006,13231279,43152977,44889423,72278539,96726697,80246713,66319530,50702367,88653118,33237508,76170907,82779622,61373987,13348726,80316608,62936963,168541,62496012,874791,44550764,62490109,4099191,40152546,4204661,68204242,415901,93933709,62452034,23740167,61263068,21533347,68041839,44842615,12348118,29819940,8128637,79880247,14731700,10366309,67281495,79821897,4064751,88047921,90013093,66045587,50188404,18783702,12571310,40865610,34946859,38341669,96193415,9398733,68102437,88904910,72358170,55247455,48260151,63015256,33431960,68939068,7687278,55189057,83083131,19101477,78549759,70372191,57359924,27625735,30653863,42782652,7955293,36396314,526217,23110625,69786756,14947650,96709982,4515343,37739481,62762857,48360198,59501487,72357096,24953498,17058722,76703609,85711894,51590803,29994197,73168565,34432810,82427263,98739783,44915235,39553046,33628349,81853704,93562257,94516935,67451935,94539824,20122224,9058407,17957593,26275734,43506672,6871053,247198,68316156,30366150,97057187,71333116,38510840,30764367,24591705,34698428,37435892,33304202,84349107,32274392,8634541,33249630,85571389,36812683,69355476,76960303,26998766,19939935,23569917,37560164,75229462,99965001,17857111,3233084,29029316,61960400,9599614,72274002,71316369,60106217,37675718,29932657,78884452,8913721,40534591,79136082,40197395,97281847,65715134,95957797,85242963,7646095,18699206,55602660,4119747,65074535,64087743,78589145,32250045,14723410,61741594,19457589,76671482,6221471,78561158,81677380,55753905,3294781,58224549,3233569,86242799,75986488,26139110,16019925,98130363,54199160,16405341,17365188,62430984,66903004,5482538,54987042,48673079,66667729,65454636,81172706,37192445,20836893,66428795,57241521,4787945,99224392,53547802,30998561,5640302,2204165,77301523,2208785,90310261,99861373,70541760,54263880,48892201,45428665,36580610,92998591,84187166,15064655,51047803,98462867,44481640,42307449,1197320,73031054,45996863,98648327,40027975,9603598,34698463,93053405,19376156,40677414,77284619,58749,40984766,92692283,89699445,31733363,55669657,60955663,23134715,44847298,41442762,42199455,77272628,89419466,34295794,38008118,37224844,6153406,65338021,44177011,66250369,73786944,59197747,77413012,23194618,64848072,33123618,95010552,9575954,84002370,33797252,57658654,57163802,98943869,85116755,43322743,68816413,4434662,46851987,36312813,81898046,17037369,11923835,37501808,9829782,77072625,86001008,29834512,26392416,22129328,16971929,99524975,22450468,44627776,56424103,78602717,97940276,8791066,6521313,53666583,40781449,87598888,46870723,10597197,19272365,43376279,92071990,48658605,59318837,44060493,6986898,62803858,98948034,86543538,78785507,25636669,36930650,98653983,73124510,65038678,48893685,83302115,7919588,73109997,89046466,99690194,2891150,21993752,38645117,68824981,9175338,47090124,40781854,669105,78766358,4199704,73617245,47708850,6525948,4668450,10961421,30163921,26776922,42644903,20867149,83210802,23432750,56515456,14220886,52613508,30787683,99297164,73222868,49598724,94595668,92604458,65275241,72777973,3235882,36139942,22435353,15075176,64602895,26507214,14326617,95251277,81774825,91831487,21673260,39847321,51315460,9257405,51507425,99333375,92215320,82897371,92867155,33895336,321665,46540998,569864,50668599,74441448,86798033,89811711,96735716,76540481,44348328,63967300,8099994,40356877,95581843,88698958,44846932,24058273,71522968,62232211,8055981,56473732,27325428,75543508,82532312,33699435,27041967,25842078,2331773,1375023,81805959,19486173,19898053,26664538,93790285,5970607,89637706,22721500,6793819,35456853,24826575,22879907,32159704,83789391,68000591,37891451,72732205,44473167,57240218,99604946,30218878,26734892,17727650,84406788,98371444,77694700,93057697,55615885,508198,14626618,97641116,45848907,62693428,74614639,51426311,97379790,92803766,53888755,41092172,77787724,28787861,80555751,176257,20140249,23848565,7300047,51149804,34236719,26863229,16099750,56484900,84904436,39373729,62552524,18001617,59109400,3183975,84840549,75135448,71469330,84127901,62115552,78300864,5267545,6111563,42947632,30891921,13468268,26744917,93270084,79657802,8651647,16684829,93359396,67513640,4806458,20885148,38061439,28796059,82726008,24915585,96318094,36933038,55143724,8115266,20765474,36135,26063929,17539625,12024238,33553959,32590267,16097038,71083565,90090964,26397786,63059024,69255765,61982238,73392814,61728685,33935899,45790169,73021291,26292919,4069912,36780454,72614359,8129978,99917599,91938191,7229550,24733232,6808825,90272749,59177718,22200378,30395570,42692881,77377183,29466406,2917920,22766820,28734791,34493392,45995325,67031644,70727211,53648154,83150534,60581278,74743862,9188443,72793444,32699744,85695762,69848388,72495719,51472275,18411915,49882705,85023028,18663507,52204879,36753250,43501211,91957544,45990383,19891772,27411561,40686254,47792865,99515901,51715482,73235980,61141874,16424199,96906410,4978543,6038457,50007421,16387575,61859143,60102965,70420215,7502255,91727510,88444207,1936762,86022504,61928316,75777973,47887470,61623915,37957788,54663246,94911072,45049308,8807940,99125126,81274566,58208470,27187213,55960386,99729357,75128745,94360702,65851721,7423788,77300457,69641513,67124282,92398073,66116458,99021067,94076128,63152504,91240048,28851716,63372756,36685023,97561745,79322415,82339363,88251446,56153345,49641577,15902805,11543098,37280276,67474219,66663942,66611759,12664567,65017137,48675329,80251430,68875490,90457870,20681921,16445503,17386996,54517921,83133790,2607799,79191827,92787493,7011964,2717150,90061527,59910624,14363867,49271185,12303248,99226875,79738755,33565483,3487592,53842979,45667668,17385531,45794415,86118021,18504197,57020481,35996293,58751351,5832946,30463802,71920426,92033260,83269727,17146629,95395112,32151165,29635537,71300104,24168411,22108665,64098930,86301513,54014062,43357947,31161687,72725103,63628376,20642888,47893286,66322959,37166608,7517032,1239555,94090109,30694952,79942022,48774913,37659250,9860968,10649306,61712234,8733068,89996536,21289531,99971982,66271566,66137019,60176618,8373289,70782102,50567636,49328608,82651278,64157906,89499542,34044787,24619760,91990218,48088883,3509435,36845587,83747892,44245960,81855445,13173644,17764950,57961282,44664587,6505939,5822202,53632373,47361209,62357986,83533741,53802686,65271999,61815223,51466049,71657078,41245325,30569392,85117092,62051033,61897582,6497830,82886381,70367851,58180653,32058615,71965942,39201414,91255408,41092102,4985896,61271144,41481685,64055960,21001913,17081350,88130087,83651211,30771409,59329511,17976208,97011160,38009615,33336148,65880522,82979980,13862149,55850790,56461322,69136837,72373496,44479073,29510992,21397057,69605283,93515664,70036438,5073754,16054533,89804152,17894977,15948937,87710366,89217461,72019362,84293052,4508700,82327024,41380093,38022834,68694897,93566986,54762643,18833224,57248122,32426752,67030811,54058788,38365584,52060076,51588015,53393358,92541302,68702632,22997281,59981773,63506281,17016156,45237957,48395186,90004325,59405277,74452589,67084351,11757872,91281584,34667286,68110330,28928175,45075471,20645197,29516592,14349098,96852321,45617087,91141395,69579137,10358899,7685448,49597667,47738185,3773993,74724075,59371804,21919959,33201905,42237907,69697787,47298834,10453030,10264691,57393458,92530431,11161731,7066775,96420429,35512853,84684495,95290172,33061250,45306070,51464002,82052050,54606384,824872,65081429,72238278,22942635,14045383,16567550,22113133,3088684,29065964,45407418,11581181,76825057,31491938,44844121,86460488,31727379,7182642,94711842,79806380,75820087,56531125,29401781,82178706,20568363,15163258,6819644,78909561,90654836,36468541,90158785,74110882,18466635,83948335,89078848,45381876,30811010,17068582,54232247,92353856,38256849,67227442,24314885,8729146,56955985,36808486,84166196,60393039,77898274,6157724,4095116,30090481,13819358 +4668450,49236559,9623492,31904591,29029316,77272628,6038457,33304202,91664334,91802888,29188588,47213183,41481685,64848072,44177011,30139692,66832478,59329511,91727510,34698428,66667729,73031054,48892201,37620363,69136837,19939935,37183543,36312813,66611759,77620120,5970607,36580610,96726697,93053405,23569917,22435353,96709982,4099191,15535065,66250369,75407347,35192533,61271144,2717150,76434144,68000591,60430369,83083131,7646095,61263068,29994197,39986008,69355476,38658347,10358899,79922758,48774913,30787683,7066775,28550822,91990218,8651647,51464002,43322743,92692283,37891451,62430984,93057697,8129978,60102965,84684495,60581278,47893286,31161687,4515343,8055981,40197395,80246713,70004753,44844121,6871053,38645117,7685448,1197320,53393358,50188404,57803235,52204879,24058273,26998766,95957797,96193415,22450468,47090124,36753250,67474219,16971929,37501808,29819940,8115266,62452034,57658654,6521313,16019925,22129328,32161669,66885828,34295794,9257405,59614746,63152504,30653863,17068582,73168565,33249630,61741594,81172706,52613508,55602660,42644903,12303248,51047803,44550764,26392416,8373289,76315420,35092039,526217,4787945,34497327,64087743,70596786,1431742,76703609,71333116,17146629,51472275,9860195,1239555,29834512,61373987,11923835,45794415,16099750,65880522,31126490,61623915,99965001,11365791,74137926,88251446,77187825,53842979,7229550,77300457,12664567,29932657,86022504,40781449,55850790,42692881,6808825,53802686,68204242,63967300,71657078,44348328,85711894,78549759,55753905,9575954,9860968,64602895,62496012,73222868,53084256,99549373,24591705,86460488,9188443,20642888,14220886,43376279,8807940,76960303,60106217,59371804,6525948,74441448,48673079,66319530,56153345,36780454,96735716,824872,22405120,54427233,74357852,77284619,98948034,90272749,73124510,48360198,65715134,93790285,84406788,39847321,72274002,14626618,44889423,92554120,71083565,55470718,61141874,13819358,98739783,39553046,62740044,77072625,4119747,52293995,45617087,37659250,43045786,56424103,38494874,4064751,569864,46870723,80555751,30163921,59501487,20645197,71965942,83150534,17727650,19486173,75153252,4434662,77694700,67281495,54058788,7687278,91957544,61712234,98130363,49328608,61982238,76671482,91938191,19457589,54987042,19101477,37675718,40356877,43357947,84349107,8128637,112651,92604458,62693428,2208785,99604946,30764367,33565483,22997281,33237508,89811711,1022677,20885148,75229462,66663942,12416768,67793644,92353856,21070110,50567636,98648327,74724075,82339363,40984766,55143724,14723410,3773993,16424199,30771409,8099994,16097038,7011964,91831487,51715482,78785507,49641577,59981773,32250045,93270084,22766820,68128438,13470059,40534591,54232247,83133790,72777973,36468541,75128745,8696647,71920426,30694952,10309525,32274392,13862149,98943869,3294781,77413012,19376156,54014062,50702367,32699744,49598724,89996536,47708850,72373496,70367851,7300047,26734892,50668599,7919588,40781854,38061439,77787724,2607799,58180653,67451935,9259676,73392814,39373729,17081350,98462867,25636669,72278539,30998561,56461322,79657802,1788101,33895336,247198,4806458,6986898,20836893,45996863,415901,80316608,29510992,83210802,71469330,39201414,30569392,90013093,90090964,78218845,176257,60393039,38510840,65038678,68702632,57241521,27665211,67084351,82886381,23848565,2204165,22108665,79821897,65275241,87710366,49882705,48088883,82651278,42782652,96420429,66271566,89214616,44664587,26863229,93933709,64055960,28928175,92033260,37739481,69641513,73235980,24168411,76170907,79136082,59177718,72725103,23432750,54663246,66903004,44842615,45848907,36139942,49597667,29466406,61859143,62115552,19898053,61859581,51315460,81677380,33431960,62936963,55189057,84166196,29959549,57248122,56484900,33123618,26744917,14363867,40027975,72793444,88047921,16595436,1569515,32590267,86821229,30218878,88653118,2917920,37560164,13231279,81853704,70800879,54263880,74743862,11161731,55770687,57020481,53632373,99524975,54199160,36505482,58751351,27185644,68875490,60176618,90310261,92867155,30463802,95010552,30366150,64157906,15148031,94911072,84293052,26507214,24915585,99515901,98371444,92071990,5832946,68316156,89499542,33201905,57163802,15948937,92692978,10649306,32159704,669105,28851716,9398733,99971982,51590803,18504197,18131876,75543508,58749,4091162,25842078,28796059,168541,54606384,8733068,5111370,79880247,41245325,75820087,15064655,20765474,23134715,17386996,47361209,32426752,82779622,14045383,84187166,44915235,75052463,44481640,20486294,48675329,83302115,53547802,92398073,36930650,6221471,97281847,88698958,87598888,34236719,8791066,17016156,6505939,35996293,41092102,40152546,83789391,20122224,39171895,97011160,83269727,97057187,22113133,89699445,67227442,59318837,39801351,16380211,10264691,82726008,70372191,54517921,70727211,42947632,17385531,27411561,14731700,77312810,23740167,4204661,51466049,37192445,75777973,38341669,99333375,9599614,4069912,6793819,99224392,63628376,72357096,67030811,7955293,18663507,41092172,3633375,1375023,34698463,63372756,20002147,1250437,64098930,508198,8634541,69848388,99917599,23194618,51588015,43501211,87160386,90654836,37435892,74452589,68939068,8729146,36812683,43506672,17058722,45381876,15163258,30543215,36808486,65271999,82979980,36396314,68644627,45075471,89419466,6157724,3088684,94090109,42199455,63059024,69255765,97379790,26114953,95726235,44473167,11543098,47887470,56515456,83651211,33553959,68102437,97783876,36933038,15536795,42073124,3233084,91240048,9886593,53666583,65047700,96906410,52060076,34493392,84002370,18806856,21919959,4199704,30891921,79806380,50806615,76825057,66116458,85117092,46851987,93359396,61728685,34432810,44479073,44846932,22721500,69786756,34667286,85116755,63015256,55960386,38365584,14093520,40865610,24733232,78561158,17037369,31733363,57393458,20867149,5822202,14349098,10961421,62428472,73021291,26776922,75135448,92215320,94711842,66428795,81855445,27625735,62552524,99658235,65074535,749283,72732205,62051033,13468268,16684829,65454636,43152977,65338021,54762643,77898274,35512853,26275734,31727379,22879907,77301523,27041967,70541760,78766358,1204161,78300864,33628349,9603598,47738185,98653983,33797252,38008118,72614359,81898046,59109400,6497830,42967683,95395112,21673260,21001913,17957593,44245960,96852321,30395570,28787861,74110882,15015906,19272365,30090481,69697787,90457870,70036438,4978543,72738685,17764950,18833224,15075176,65017137,47298834,71522968,32058615,83948335,71300104,17894977,12571310,40686254,38022834,78884452,44983451,72238278,8913721,88130087,57359924,61815223,66322959,3509435,60955663,89804152,94516935,20140249,5482538,86118021,73617245,16445503,3487592,34946859,79942022,3880712,4508700,26664538,78909561,44627776,86242799,6111563,29516592,92803766,84127901,89078848,16054533,86543538,68824981,20681921,21993752,27325428,11581181,44784505,3183975,24826575,14326617,48260151,78589145,42307449,73786944,26139110,45990383,57961282,86301513,27187213,10366309,62762857,94076128,44847298,83533741,55669657,49271185,56531125,82178706,41442762,96318094,53648154,95290172,21533347,66045587,18783702,4095116,55247455,62490109,82427263,28734791,72358170,70420215,45919976,10597197,36685023,45428665,99861373,85023028,65081429,36189527,99226875,48893685,68694897,80251430,2891150,45237957,90061527,84840549,29065964,17365188,93515664,7423788,18001617,22200378,72019362,32151165,42237907,82052050,82532312,93562257,59197747,87720882,46540998,99021067,56955985,45790169,88444207,85571389,3233569,48395186,22942635,85695762,4798568,26063929,83368048,89637706,93566986,9829782,24619760,70782102,14947650,45306070,47792865,7104732,15902805,29401781,63506281,9058407,9175338,34044787,95581843,67031644,26102057,66137019,12348118,50007421,37224844,16405341,321665,99690194,99125126,54868730,56473732,77377183,5640302,61960400,89046466,89217461,29635537,85242963,68816413,90004325,58224549,6153406,17857111,26397786,7517032,11757872,35456853,91141395,65851721,79191827,45049308,81805959,91255408,18699206,83155442,44060493,16387575,69605283,68041839,86001008,81274566,18466635,94539824,23110625,6819644,17539625,43933006,71316369,21289531,5073754,45407418,88904910,33699435,78602717,73109997,51149804,94360702,38256849,92787493,84904436,21397057,55615885,5267545,36845587,51507425,80014588,4985896,59405277,16567550,24314885,62232211,1936762,57240218,17976208,52261574,99297164,48658605,30811010,12024238,97561745,37166608,7182642,75986488,45995325,13348726,97641116,40677414,58208470,79738755,62803858,33061250,92998591,97940276,18411915,37957788,92541302,51426311,37280276,2331773,26493119,82897371,24953498,62357986,67124282,94595668,10453030,69579137,7502255,53888755,90158785,91281584,67513640,874791,61897582,45667668,33336148,41380093,72495719,68110330,76330843,26292919,82327024,20568363,99729357,81774825,19891772,61928316,86798033,74614639,76540481,95251277,36135,33935899,83747892,13173644,3235882,59910624,92530431,79322415,38009615,31491938 +33431960,1788101,27665211,17857111,86022504,99604946,74357852,55143724,95726235,247198,38494874,42967683,56424103,50188404,14093520,97783876,30139692,55753905,74137926,34698428,93270084,36812683,70372191,73222868,5822202,23848565,98739783,45919976,18806856,67281495,85023028,5111370,84406788,15535065,49236559,61373987,62496012,35092039,18663507,22108665,39986008,71920426,19939935,26998766,95581843,52261574,68644627,48360198,89637706,5970607,1569515,1022677,29188588,45990383,37739481,34236719,25636669,91255408,1431742,28550822,81853704,44889423,68041839,32590267,17146629,96193415,16019925,77620120,5073754,5832946,74441448,4119747,43933006,58224549,2891150,83155442,40781854,39171895,83150534,9599614,55470718,71333116,67124282,63967300,19376156,88653118,50702367,6871053,86821229,80555751,72725103,74110882,37891451,26063929,91727510,3633375,88047921,57961282,15148031,75135448,14731700,29994197,36312813,83368048,82979980,71316369,55602660,82726008,18504197,43357947,37675718,65715134,415901,8733068,65038678,66663942,17058722,16424199,68204242,31904591,99125126,77284619,85116755,83210802,29065964,60581278,63152504,29932657,75407347,93057697,27625735,91802888,93933709,73031054,69355476,44177011,47090124,69848388,9188443,22435353,99658235,76434144,36505482,62552524,33553959,8634541,3880712,4668450,96726697,35456853,30543215,11365791,10366309,14220886,7104732,80246713,13470059,30653863,72738685,24591705,49328608,89699445,26493119,7685448,46851987,95395112,68000591,6038457,53393358,4434662,61897582,42782652,62452034,4787945,40984766,9603598,44348328,30787683,48892201,2204165,569864,63059024,24733232,17365188,9829782,6793819,4199704,88698958,67451935,12416768,32426752,36580610,21993752,9886593,37435892,75543508,33237508,44481640,20486294,86301513,77301523,66903004,62936963,57393458,54014062,44842615,84904436,78218845,22450468,81898046,66611759,62762857,83269727,4099191,87598888,77072625,26776922,89811711,17539625,62740044,63506281,508198,78589145,44550764,112651,68816413,42199455,82327024,55770687,8651647,48673079,29510992,51507425,78549759,16971929,54232247,75986488,669105,98371444,27185644,89996536,30218878,23134715,67793644,7502255,44784505,57359924,45848907,72777973,36685023,33304202,78602717,6221471,48774913,4798568,52293995,92998591,87160386,53084256,99333375,66428795,90272749,24058273,3235882,20885148,1197320,88444207,79806380,29819940,7423788,50806615,51047803,56153345,4204661,40027975,6153406,81172706,13231279,86798033,50668599,59614746,84840549,77187825,61928316,40356877,76170907,50567636,3233084,66885828,95251277,68875490,43152977,40197395,65081429,14045383,15075176,45306070,26392416,45996863,38008118,99861373,92071990,66319530,46540998,65851721,98948034,63628376,77413012,44060493,61141874,70800879,57163802,31733363,32159704,48260151,36930650,73124510,18131876,16387575,35996293,29834512,51472275,72274002,20002147,60430369,44473167,3088684,59329511,45237957,62430984,92604458,46870723,9623492,53888755,37183543,7687278,62803858,64848072,8055981,33895336,60393039,34497327,70596786,97281847,52060076,54517921,13173644,65017137,59318837,7229550,89046466,874791,19898053,36189527,34432810,59910624,77787724,48658605,79922758,38341669,89217461,24619760,2717150,90457870,99549373,47361209,92530431,97641116,12348118,11161731,96318094,28851716,41092172,8115266,42947632,62232211,36753250,96852321,93053405,78561158,38256849,26114953,29466406,43501211,54199160,20867149,53666583,6819644,56955985,65271999,31161687,61263068,75052463,80316608,56473732,87720882,69255765,70004753,6525948,61623915,8807940,92398073,9175338,44847298,34493392,81855445,33565483,95957797,99021067,91664334,168541,94711842,71657078,93359396,12024238,61960400,94090109,93790285,83789391,67030811,20681921,6808825,92554120,54987042,82886381,61859143,78884452,44245960,28928175,76825057,91990218,65275241,71083565,72614359,71300104,93562257,12571310,66116458,47213183,49597667,99224392,19486173,37166608,74724075,6505939,14326617,20836893,61741594,19101477,82052050,18699206,18783702,83533741,14723410,1239555,43322743,4095116,55669657,72732205,8791066,57020481,6986898,30366150,49641577,59197747,89214616,45995325,87710366,61815223,38061439,99965001,56484900,84127901,3233569,95290172,72357096,59501487,91831487,28734791,73235980,68702632,17764950,4069912,10649306,15064655,41092102,2917920,17727650,17957593,41481685,4978543,61271144,12664567,21001913,3773993,53547802,34698463,14626618,27187213,27325428,90013093,67227442,38365584,10961421,82178706,65880522,17037369,45407418,30569392,80014588,13819358,65454636,26102057,9575954,22129328,69136837,21919959,29029316,17016156,89419466,26863229,77300457,93515664,40677414,7300047,8128637,29959549,77272628,66271566,27041967,4806458,81274566,79880247,42692881,76671482,71965942,33699435,99690194,90310261,84166196,76315420,37192445,51590803,36468541,40686254,38645117,98653983,33249630,53648154,22721500,90090964,79191827,35192533,76703609,78785507,5482538,59177718,8696647,70036438,55247455,90004325,75777973,96709982,76540481,73617245,824872,72373496,37659250,30998561,56515456,72495719,72019362,66322959,79821897,84293052,82532312,47887470,38658347,31491938,86001008,3294781,53842979,32699744,40152546,69786756,64602895,30764367,54762643,17894977,78300864,85117092,45617087,8099994,57248122,23569917,47893286,40781449,94516935,4985896,69579137,99917599,83083131,43045786,44844121,3509435,16380211,54058788,4515343,56531125,99971982,92867155,84349107,44627776,24826575,1250437,83302115,78766358,66045587,77377183,83747892,59405277,16097038,89078848,98462867,23194618,9259676,68316156,15015906,99729357,71522968,32250045,31126490,62490109,37620363,43506672,8129978,66667729,321665,83133790,749283,16099750,64087743,21070110,45428665,10309525,51588015,78909561,9257405,33628349,68128438,18411915,72358170,95010552,2607799,526217,51464002,16054533,92692978,30463802,44846932,92692283,74743862,4064751,26744917,68824981,37957788,91957544,41442762,48675329,82651278,11543098,49882705,6497830,9398733,97940276,70727211,75820087,22766820,18833224,66832478,71469330,70420215,97057187,76330843,16445503,66137019,94076128,20122224,57803235,55960386,73168565,57241521,70367851,76960303,75153252,79657802,39553046,54868730,40865610,80251430,92215320,6521313,94595668,32161669,26664538,60102965,45790169,54427233,93566986,72793444,13862149,9058407,33935899,32274392,33797252,44983451,50007421,33123618,89499542,36396314,22879907,47738185,82339363,30771409,13468268,79136082,34946859,27411561,90158785,65047700,28796059,92787493,35512853,97379790,55189057,89804152,48088883,77898274,42644903,99515901,33201905,90654836,37501808,34295794,61982238,48893685,34667286,81677380,56461322,14947650,66250369,73786944,91240048,99226875,77694700,75128745,11581181,47298834,42307449,99524975,20140249,36780454,10358899,7066775,53802686,55850790,26507214,85242963,47792865,7646095,98130363,86242799,10453030,18466635,70782102,73109997,86543538,15902805,4508700,26275734,77312810,62115552,23432750,51315460,9860968,51149804,92803766,21533347,54663246,92033260,39847321,24915585,36808486,14349098,7517032,20642888,68110330,72278539,59109400,52613508,34044787,61712234,85711894,51426311,62051033,94539824,96735716,19891772,97561745,39201414,31727379,72238278,97011160,17068582,88251446,69641513,17386996,15948937,44479073,84684495,92541302,70541760,30395570,45667668,30090481,7919588,39373729,52204879,88904910,65338021,73392814,5640302,57240218,30163921,30694952,36139942,85695762,22405120,51715482,29401781,83651211,67474219,11923835,17081350,65074535,91938191,41245325,60955663,44664587,42073124,20765474,22997281,11757872,68694897,64157906,96906410,54263880,15163258,86460488,61859581,58749,45075471,63015256,59371804,67031644,16405341,21673260,86118021,85571389,8729146,58180653,64098930,16595436,82779622,79942022,20568363,40534591,18001617,22942635,88130087,84187166,57658654,61728685,1936762,26292919,19457589,82897371,39801351,44915235,98943869,54606384,20645197,74452589,43376279,24953498,8913721,32058615,49271185,2208785,36135,38510840,67084351,7011964,26734892,81805959,30891921,94360702,4091162,37560164,7955293,37224844,6111563,22200378,74614639,69605283,3487592,99297164,82427263,12303248,53632373,2331773,41380093,58751351,75229462,92353856,96420429,68939068,38022834,58208470,83948335,32151165,25842078,49598724,22113133,60106217,55615885,16567550,176257,30811010,26397786,9860195,94911072,59981773,19272365,51466049,5267545,14363867,26139110,73021291,23110625,48395186,1204161,38009615,69697787,13348726,42237907,67513640,33061250,81774825,29635537,17385531,28787861,79738755,24314885,1375023,62693428,15536795,91141395,62428472,63372756,79322415,6157724,33336148,60176618,8373289,37280276,64055960,24168411,21289531,21397057,47708850,7182642,90061527,45794415,62357986,36933038,68102437,91281584,3183975,17976208,45049308,98648327,10597197,36845587,84002370,23740167,29516592,10264691,16684829,45381876 +30764367,44784505,74357852,31126490,81898046,54517921,62430984,51047803,9603598,45237957,62496012,49641577,88653118,7104732,96193415,63967300,89214616,99861373,54058788,4508700,28851716,14947650,13862149,77300457,86798033,72278539,5482538,59177718,14731700,77301523,99965001,68816413,65271999,62452034,76960303,95395112,92692283,97561745,89217461,24591705,92554120,82651278,247198,34497327,40781854,34698428,34236719,4668450,61859143,44983451,57241521,95726235,15015906,3509435,64848072,91831487,13231279,65081429,78589145,44889423,73617245,18504197,5640302,65454636,33237508,1197320,55189057,33565483,89637706,42307449,99549373,65338021,92692978,3183975,48088883,61859581,66428795,93566986,10453030,97783876,88444207,64087743,99226875,67227442,55247455,54762643,14349098,63059024,21993752,74441448,70367851,95957797,86001008,18001617,7300047,61271144,51315460,92398073,10961421,38022834,68939068,77272628,2717150,31733363,74452589,51588015,27625735,55470718,37435892,71920426,90004325,8129978,81853704,44177011,79136082,32058615,45407418,20642888,9886593,29834512,76671482,50806615,54987042,36685023,82339363,30787683,26998766,35192533,92998591,3233084,9575954,29188588,53084256,69579137,30395570,61741594,17764950,40686254,45848907,35456853,44473167,51464002,67281495,96735716,92071990,26063929,20867149,20122224,54427233,87710366,85116755,37183543,17727650,6793819,63372756,34946859,29994197,67031644,88698958,95010552,38494874,65275241,67793644,15535065,54014062,42692881,43322743,8807940,27665211,36580610,62740044,32590267,6986898,62693428,50188404,53648154,6153406,112651,53802686,5970607,52060076,79806380,75543508,61373987,12416768,4119747,64602895,69786756,17037369,92033260,29959549,37957788,874791,92787493,42199455,94595668,12348118,66663942,68128438,86543538,18833224,70036438,97940276,14093520,52261574,49236559,13470059,56515456,47893286,90272749,99021067,4064751,18806856,36808486,26493119,60955663,79821897,17539625,50007421,77284619,22108665,51472275,62552524,28734791,22766820,80555751,16387575,63506281,9175338,99729357,38645117,64098930,12664567,19101477,71657078,95290172,36780454,37675718,47090124,77072625,17385531,93270084,53632373,17058722,36505482,87598888,18699206,27041967,79880247,61623915,14045383,36933038,7423788,99515901,32250045,19939935,38365584,65715134,77312810,83789391,1204161,78561158,67124282,65047700,92604458,33304202,99125126,53547802,60102965,19891772,77413012,415901,2917920,38256849,10309525,669105,84904436,81677380,56461322,26114953,68875490,88047921,74743862,92541302,68644627,78766358,33935899,44550764,76170907,73786944,62936963,9860195,36845587,58208470,39986008,44481640,77620120,45790169,3633375,56531125,85695762,2204165,6505939,8791066,9188443,91141395,69848388,59318837,30139692,2891150,22200378,64157906,92530431,57393458,8733068,57359924,21533347,14220886,75986488,72725103,39171895,30569392,49597667,59371804,44348328,66611759,60581278,41442762,82052050,25842078,79657802,72738685,23569917,61141874,38008118,80014588,65880522,62803858,35996293,57803235,34295794,46540998,99524975,4095116,10649306,26392416,33895336,59501487,54232247,51590803,20765474,8913721,72777973,99917599,70800879,91664334,34667286,22942635,749283,38341669,78785507,99658235,35092039,20486294,29510992,57163802,21919959,5073754,21070110,85571389,91957544,57248122,50668599,33201905,168541,44842615,31904591,41380093,89996536,27325428,40677414,12024238,39847321,15536795,9257405,18131876,6038457,526217,74614639,80316608,16595436,70596786,43357947,86301513,66045587,55753905,72238278,39801351,7011964,73124510,14363867,40152546,46870723,27411561,88251446,72373496,24953498,99333375,70004753,68000591,6808825,48260151,69605283,3233569,5111370,78300864,24619760,93933709,4434662,88904910,13819358,94090109,7687278,44479073,1239555,77377183,71522968,22997281,47738185,34493392,4787945,72274002,23432750,94539824,91802888,18783702,41092102,24314885,16097038,86821229,26102057,40197395,58751351,3294781,91281584,6525948,69697787,94076128,16054533,47361209,21001913,19376156,63152504,30366150,48675329,29635537,21673260,66137019,45306070,33249630,42967683,54663246,46851987,7685448,72614359,33123618,59197747,68824981,36812683,58749,8099994,72357096,23134715,83651211,57020481,89804152,9599614,94516935,19898053,55602660,55669657,10366309,30694952,40356877,98371444,28787861,24058273,40984766,4091162,72019362,51715482,30543215,58180653,71333116,29065964,72793444,12303248,43045786,81805959,51149804,17365188,66832478,4069912,23848565,508198,76330843,56473732,68694897,10358899,50702367,65851721,1431742,62762857,91240048,50567636,37620363,76540481,93562257,16019925,17857111,37739481,66250369,29932657,89078848,98739783,8115266,19486173,9259676,66667729,4806458,79738755,60430369,42947632,93359396,37891451,15148031,69136837,24733232,44847298,48360198,62357986,33628349,22113133,7955293,83210802,85242963,54199160,61263068,17957593,81172706,43933006,94360702,85023028,75153252,96709982,16099750,20002147,83269727,78909561,61982238,53842979,26139110,53666583,96906410,7182642,43376279,77787724,30218878,13348726,99297164,11581181,90061527,6157724,29819940,3088684,34044787,96318094,98130363,18411915,31491938,57961282,61815223,32161669,48658605,92803766,45667668,42644903,40781449,40865610,84127901,99690194,91255408,44060493,49882705,11161731,30998561,26664538,4978543,83533741,86022504,47708850,68041839,80251430,73222868,74137926,51426311,71083565,16424199,17016156,81274566,22450468,87160386,65038678,83150534,43506672,54606384,55143724,36189527,7919588,22879907,48395186,28550822,42782652,75407347,69641513,37659250,84349107,20885148,1936762,21397057,76434144,4099191,49598724,60106217,79942022,93057697,32159704,27185644,66903004,63628376,36753250,33797252,36135,45996863,26507214,321665,26397786,82897371,45075471,90457870,81774825,1569515,73235980,29401781,26275734,82178706,37501808,4985896,59109400,83948335,13468268,89046466,5267545,78549759,38061439,77187825,20140249,96726697,85711894,78602717,59614746,56153345,29466406,6521313,57240218,8696647,3235882,33699435,14326617,67451935,76315420,92215320,70420215,62051033,12571310,55960386,60393039,28796059,67084351,86118021,64055960,66322959,66271566,97011160,17146629,4204661,6221471,33336148,9623492,48892201,17386996,75128745,34432810,26776922,33431960,89811711,93790285,88130087,39553046,66116458,98653983,14626618,45990383,84187166,83747892,28928175,51507425,43501211,84684495,45049308,68316156,73031054,33553959,52204879,41481685,56484900,68110330,5832946,98943869,99224392,48893685,94711842,72495719,36312813,93515664,91938191,85117092,55615885,17068582,84293052,83155442,45428665,20568363,4798568,74724075,9398733,67030811,48774913,30090481,82886381,16380211,9058407,30463802,82779622,70541760,90013093,75052463,29029316,54263880,1250437,68204242,53888755,70372191,20836893,91990218,47792865,92867155,8055981,58224549,37192445,74110882,10597197,15902805,97641116,78884452,94911072,59910624,3880712,15064655,15948937,45919976,84406788,75777973,16567550,66319530,35512853,56424103,95581843,61897582,21289531,62232211,90090964,73392814,98648327,8128637,68702632,30891921,11365791,73168565,7517032,98462867,38510840,79922758,96420429,72358170,3773993,89419466,22435353,8634541,44844121,54868730,52613508,22129328,82979980,78218845,30653863,19272365,16445503,90158785,42237907,1022677,69355476,36139942,56955985,8729146,65074535,3487592,68102437,71300104,98948034,67513640,26292919,86242799,26744917,34698463,19457589,71965942,76825057,24826575,824872,42073124,99604946,39201414,52293995,75135448,36396314,23740167,18466635,66885828,14723410,29516592,80246713,97379790,8651647,11543098,18663507,89699445,53393358,9829782,44664587,45995325,49271185,6497830,1788101,82726008,47887470,30771409,16971929,84166196,4199704,55770687,60176618,37560164,17081350,61928316,69255765,49328608,17976208,24915585,84840549,45617087,93053405,71469330,79191827,38658347,16405341,62490109,51466049,63015256,16684829,44846932,82427263,82327024,25636669,45794415,96852321,84002370,71316369,24168411,61960400,23194618,37224844,6111563,2208785,26863229,36930650,87720882,33061250,55850790,7502255,17894977,31161687,72732205,95251277,44245960,5822202,59981773,91727510,75229462,7229550,75820087,48673079,97057187,41092172,82532312,86460488,22721500,569864,6819644,7646095,30811010,44915235,59329511,32151165,31727379,11757872,9860968,83133790,83083131,37280276,62428472,176257,40027975,92353856,39373729,83302115,83368048,6871053,90654836,57658654,2607799,43152977,32274392,37166608,8373289,32426752,2331773,81855445,1375023,47298834,65017137,89499542,13173644,47213183,41245325,10264691,30163921,97281847,15163258,90310261,11923835,26734892,77694700,15075176,73021291,23110625,38009615,36468541,27187213,61728685,20645197,70782102,79322415,70727211,22405120,76703609,99971982,32699744,45381876,62115552,7066775,20681921,61712234,59405277,40534591,67474219,44627776,4515343,77898274,73109997 \ No newline at end of file diff --git a/src/test/resources/data/test_queries_binary_100x128.csv b/src/test/resources/data/test_queries_binary_100x128.csv new file mode 100644 index 000000000..0740c6fd4 --- /dev/null +++ b/src/test/resources/data/test_queries_binary_100x128.csv @@ -0,0 +1,100 @@ +-53,97,109,87,117,-42,116,-90,-17,-5,62,-66,2,109,-78,-52 +9,65,-21,87,-58,-75,-29,1,-73,124,12,-102,-86,18,83,91 +-72,76,-21,-100,66,-83,-65,61,-71,39,-81,106,27,5,-125,99 +34,-54,5,-3,127,45,13,29,24,-60,-90,-40,69,30,-23,-100 +-91,-51,43,78,81,120,-59,-100,-35,-68,-56,-69,-40,16,-110,-28 +-123,-123,-15,-25,-16,17,-52,2,86,12,-98,-73,33,78,110,5 +-77,-115,-13,-84,4,-13,-32,75,-128,24,-17,-78,28,-57,111,-20 +79,-71,-5,127,-46,-72,118,123,-7,33,-85,95,-61,-121,95,-61 +-52,40,-5,11,112,-69,11,-36,-26,78,-16,-33,59,-105,102,-51 +43,48,70,85,-111,-21,34,14,112,98,0,-126,73,127,46,-52 +84,47,-109,-29,72,68,-34,84,-105,64,-73,-71,-111,-35,-79,89 +-57,21,69,33,-65,-42,78,106,-18,92,51,29,-109,60,101,100 +-20,-36,-126,-22,-122,110,50,47,38,-42,99,32,21,-13,61,-38 +-83,36,27,5,127,-115,-87,127,-12,-60,-105,108,60,112,-50,109 +113,34,-57,105,127,-122,70,-86,-111,-95,-112,-75,-72,127,-33,124 +121,-53,-111,113,-87,-106,87,-30,-9,-128,-62,-91,-91,75,-61,83 +-4,-110,55,-119,-114,123,95,-9,-28,-8,-55,-117,-77,-96,88,83 +63,-109,107,82,125,24,-72,-89,-1,64,38,70,117,72,1,-27 +38,95,-87,-87,-49,-104,-35,-126,57,-95,-30,-35,-114,-41,-33,-46 +-95,6,30,71,-16,-112,83,-90,-118,87,-29,53,-96,-68,125,-73 +58,-47,25,-44,-4,-125,-30,5,-16,-42,107,-42,-68,-97,17,-51 +-125,-113,108,-29,57,-48,102,-76,-34,0,-81,-21,113,-13,95,65 +-11,-73,38,47,-6,-80,91,39,-4,-26,-43,30,63,12,30,52 +-76,124,85,126,-106,-23,52,104,-81,-87,-66,-68,81,40,-99,7 +-108,47,-76,-47,110,-28,-49,46,70,25,72,-73,-122,-41,-47,117 +19,27,48,74,-70,-85,-87,-107,-30,-44,92,11,113,-7,42,-16 +35,114,75,-30,-69,115,-94,-116,22,81,-41,120,-64,-115,97,-43 +-111,8,83,-62,0,15,-10,-83,63,-96,92,-44,-23,85,78,123 +110,78,-46,105,-40,-63,40,122,-69,-96,23,-90,81,46,122,51 +21,25,-2,51,-115,28,-111,-8,-125,-126,-79,4,99,48,78,-17 +125,-2,57,82,-66,77,-125,-49,16,33,-88,-6,-74,-75,23,1 +82,28,-38,28,23,-26,-9,-1,-68,85,-92,78,100,-118,-120,-9 +90,55,72,67,-50,20,-9,-57,-66,9,-74,-128,81,125,11,37 +109,49,-23,61,89,-12,7,70,64,-111,110,-89,-21,-95,95,118 +31,55,-44,118,50,87,-90,-91,-2,-44,117,109,6,-96,-105,45 +82,13,109,3,-83,-99,36,-115,55,-124,-36,-126,-10,-55,-47,96 +102,-20,84,-41,-59,-112,-54,117,-70,52,31,-118,-70,-108,-80,-27 +-42,96,3,6,-17,-56,-113,31,86,2,25,25,91,58,-48,-84 +-120,-121,87,-44,121,-27,95,-46,-79,-51,-86,-20,-100,70,51,-88 +118,111,-82,115,48,-108,122,16,66,37,-93,52,106,-59,73,83 +21,-10,101,-68,-73,-42,-25,88,-40,-35,16,62,126,6,17,-109 +-49,68,-10,93,107,88,91,-116,-101,-102,77,-82,117,-119,1,102 +-92,92,70,-67,17,59,-9,-111,19,-21,-60,-111,-88,-2,61,111 +63,14,92,-76,71,101,87,11,24,69,-116,71,22,-22,-94,114 +122,72,-32,-54,91,14,-59,-87,-97,-45,46,99,-118,-21,124,-82 +44,-101,91,15,-25,-17,109,63,-86,50,-76,-32,-86,-46,-91,70 +89,114,101,-96,57,-27,-71,8,-1,-112,98,-90,90,-111,-50,-110 +58,16,-55,-88,83,-59,-26,-83,92,54,-97,79,108,120,32,68 +-39,69,0,109,-39,6,-57,85,-94,-35,107,-127,-54,-43,112,9 +90,88,-25,-58,-14,-23,-46,-37,101,-3,107,81,-52,3,61,-63 +125,9,-4,-55,-56,69,-7,-113,27,69,72,8,-93,97,49,32 +-112,20,-68,-14,-68,101,-19,45,28,66,-10,-68,6,6,-8,-105 +-89,2,-12,112,48,2,-45,0,-1,25,4,-37,35,-54,16,-44 +-92,-85,7,65,-94,121,-26,-53,-23,75,-104,86,-88,74,-83,18 +-9,-34,89,-55,41,86,-30,-110,-1,37,67,99,-42,-63,-63,29 +58,-70,81,80,57,-43,-59,6,-22,-36,6,-96,51,-64,-95,54 +-24,113,-83,-25,29,-44,89,32,36,7,81,121,75,-10,23,-83 +-99,-27,-78,-35,101,-112,0,78,75,-38,-34,-111,-65,110,27,8 +87,-98,45,91,6,64,100,19,-7,51,-78,51,25,13,-78,92 +-126,54,-96,-38,-105,91,-93,-61,118,95,60,28,123,0,105,10 +-56,95,-60,78,2,65,-28,10,-19,-6,-49,101,-85,-80,98,-124 +-62,50,-39,-48,31,-50,-117,-46,-82,-81,34,16,-15,-83,-114,-117 +98,-75,121,117,-127,70,82,113,97,99,119,-56,-52,5,45,58 +3,-12,-33,-41,77,22,-92,120,-119,-112,-85,41,-107,123,53,-11 +-121,90,-55,48,-32,-12,-91,108,23,18,10,-92,-3,26,58,-126 +89,-77,-33,110,115,41,99,-68,-3,37,-52,-43,-39,-76,-107,31 +-68,-72,-2,63,91,-113,-109,41,-11,65,-71,-37,-1,82,70,-88 +-70,-47,-11,18,53,-49,-63,-10,66,-65,-125,65,118,-70,-34,-24 +-63,105,-112,34,91,-119,45,127,76,35,11,121,39,-52,-118,-7 +97,32,-102,-96,42,-94,106,77,-30,69,-92,-100,104,29,127,-31 +-119,35,81,124,50,-8,-53,60,-99,118,-128,29,-31,-123,-27,76 +39,-108,-12,78,-100,-21,10,-12,21,-27,-56,124,113,-30,89,-76 +-39,30,-21,-77,107,-95,-18,-44,115,-32,108,100,-84,0,-45,85 +34,4,50,-58,-20,74,-60,-77,-117,66,-36,115,24,34,-88,32 +63,4,-56,19,104,105,21,18,108,-104,63,50,-61,50,-11,21 +-105,-122,-67,-53,-7,73,11,-61,-71,24,57,-120,82,-31,55,-81 +-66,112,86,-2,48,-9,55,-49,-58,67,109,82,-84,-47,-15,117 +-8,32,-101,83,103,-10,-125,-69,32,-93,-51,8,6,-12,-10,-39 +-70,20,-52,48,-91,-108,26,-116,-117,8,123,-127,69,-35,56,76 +114,90,-80,38,5,43,67,115,-36,103,-56,49,-88,58,-45,-115 +4,-43,-33,114,20,113,-76,83,26,118,-106,-57,123,126,50,-82 +-72,-51,-53,-41,113,-126,-35,-63,65,-94,43,-123,-67,-59,-13,18 +30,93,-126,52,-114,-124,45,125,19,93,-64,-112,36,-34,-52,-100 +-45,30,102,-60,22,-82,74,115,-78,-15,-116,114,1,54,99,-26 +117,-117,-90,-74,35,-81,-85,-16,115,-71,112,102,-105,26,50,41 +119,126,76,-75,85,-74,86,-63,39,-14,113,-25,-17,61,69,-125 +48,64,-118,-97,-101,-79,105,-103,7,6,44,75,-128,-72,-27,-20 +-126,55,67,70,12,-59,-117,-10,-43,-126,75,58,20,-107,65,-15 +47,-37,32,32,-93,26,80,32,39,-1,-60,28,104,126,-22,-128 +53,-83,26,67,-17,-56,33,69,48,31,-60,-112,106,-8,8,109 +-65,-34,36,96,-49,110,38,59,20,-9,-104,111,81,46,-125,98 +-128,88,-124,-56,-47,3,58,-50,-38,-117,42,-88,-44,-128,103,-85 +-106,-93,29,-112,-13,-93,-12,25,73,-8,10,88,122,27,25,30 +16,-107,36,-12,90,-11,91,-103,103,126,122,-45,120,69,-64,-82 +-36,-5,58,80,-40,99,-73,-29,121,118,-39,109,111,7,83,-27 +-26,43,32,-76,30,-110,18,-103,-79,-26,-101,1,-36,-82,65,-15 +85,-117,83,53,-111,-96,-79,52,125,-112,100,118,82,56,-31,0 +65,73,-12,1,57,103,47,-100,-38,-27,48,-108,-2,-114,-68,-67 +26,-120,-37,-126,-101,14,24,-2,124,-31,-84,73,74,-77,-123,-75 +-34,100,-44,-5,-102,-98,57,90,29,98,15,-90,110,114,93,0 diff --git a/src/test/resources/data/test_vectors_binary_1000x128.json b/src/test/resources/data/test_vectors_binary_1000x128.json new file mode 100644 index 000000000..a9eab4098 --- /dev/null +++ b/src/test/resources/data/test_vectors_binary_1000x128.json @@ -0,0 +1,1000 @@ +{"id": 91240048, "vector": [-61, 47, -14, 41, -112, -101, -49, -58, -7, 127, -21, 126, -22, 121, 54, -58]} +{"id": 69136837, "vector": [18, 67, 100, -107, 79, 69, -50, -41, -119, 67, 35, -120, -35, -46, 38, 68]} +{"id": 48088883, "vector": [46, 41, -43, -102, 88, 120, 47, -120, -64, 19, -31, -84, 63, -16, -104, -64]} +{"id": 8729146, "vector": [10, -77, -121, -117, 32, 2, -127, -128, -105, -37, 15, -127, -51, -37, 2, -62]} +{"id": 4204661, "vector": [-121, -103, -42, -88, -98, -48, 56, 41, 82, -103, 126, 108, 96, -43, -45, 81]} +{"id": 83210802, "vector": [-64, -77, -7, 110, 125, 65, 78, 123, 99, -31, -54, 57, 74, 8, 8, 96]} +{"id": 3233569, "vector": [-83, 9, -29, -21, 68, 58, 124, 64, 118, -31, -120, -111, 36, 40, -16, 65]} +{"id": 53666583, "vector": [82, 93, -120, -66, -78, 58, 27, -94, 106, 124, 114, 99, 5, -41, -60, -33]} +{"id": 26275734, "vector": [113, -82, -57, 113, -12, -24, 22, 95, -51, -99, -50, -115, -97, -68, 97, -71]} +{"id": 89804152, "vector": [114, -98, 36, -55, 15, -55, 125, 51, 62, -105, 80, -107, 71, -48, 31, -15]} +{"id": 92803766, "vector": [-69, 126, 10, 30, -60, -10, -6, 117, -109, 107, -73, 30, -48, 101, 83, -27]} +{"id": 97057187, "vector": [59, 79, 51, 51, 20, 40, 37, -87, 61, 97, 20, -112, -111, -3, 114, -107]} +{"id": 76170907, "vector": [-81, -114, 30, 113, -127, 89, 58, 58, 118, 68, 24, -29, 38, 20, -28, -99]} +{"id": 65851721, "vector": [-113, -14, -93, -42, 125, 90, 1, 66, 7, -115, 56, 25, 73, 82, -126, 0]} +{"id": 168541, "vector": [57, 24, 50, -56, -78, -8, -31, 23, 102, 69, 60, 70, 114, -99, 98, -25]} +{"id": 73031054, "vector": [126, -38, 1, 6, -45, -31, 93, -33, -46, -23, -19, 20, -38, 83, 26, -2]} +{"id": 67793644, "vector": [63, -123, -109, -3, 10, 49, 125, -75, -45, 8, 86, 97, 16, -17, -96, 48]} +{"id": 99971982, "vector": [-128, -93, -49, -125, 64, -92, 65, 34, -91, -99, -45, -67, -98, -27, -103, 31]} +{"id": 12571310, "vector": [-7, 96, -110, -10, -26, -77, 117, 47, 76, -32, -78, -85, 23, 103, -30, -89]} +{"id": 75135448, "vector": [11, 36, -27, 23, 127, -73, 84, 122, -22, 22, -31, 81, -98, 121, -97, 33]} +{"id": 72793444, "vector": [21, 77, -107, 55, 112, -86, -125, -17, 0, -84, -79, -101, -24, 8, 127, -90]} +{"id": 98653983, "vector": [75, -39, -107, -111, 2, 91, -128, 117, 53, -99, 4, 108, -62, -53, 127, 124]} +{"id": 40677414, "vector": [-108, 30, 35, -45, -59, 78, -40, 105, -9, -82, 11, -2, 5, 48, -90, 33]} +{"id": 17058722, "vector": [-1, -48, -75, 27, -17, 40, -31, 60, -65, -93, 77, 71, 93, 17, -96, 124]} +{"id": 59109400, "vector": [38, 38, 17, 13, -44, -8, -32, 63, 2, 125, 75, -23, -14, 1, -117, 101]} +{"id": 58180653, "vector": [-119, 125, -125, -57, -5, 92, -25, -36, -117, 30, -51, 69, 86, 23, 92, -42]} +{"id": 90310261, "vector": [-101, 22, 56, 123, -95, -63, 50, 103, -114, -27, -3, -127, -47, 45, 122, -77]} +{"id": 42199455, "vector": [-120, 110, 30, -103, 63, -81, 76, 118, -1, 81, -123, 52, -80, 49, -115, 15]} +{"id": 38645117, "vector": [9, 103, -81, 98, 94, 53, 55, 50, -128, -59, 111, -3, 80, -18, -50, 62]} +{"id": 61897582, "vector": [82, -1, -33, -18, 86, 21, 84, -13, 103, 127, -67, 106, -104, 27, 86, 48]} +{"id": 59197747, "vector": [77, -9, -97, -74, 96, 29, -43, 123, 87, 42, 70, 73, -71, -38, -64, -45]} +{"id": 19486173, "vector": [22, 104, -98, 104, 118, 64, -22, 29, -28, -13, 63, 22, 4, 111, 79, 54]} +{"id": 24826575, "vector": [98, 113, 35, -118, -90, -82, -3, -2, 71, -50, -11, -97, -61, 65, 70, -120]} +{"id": 48360198, "vector": [92, -37, 32, -126, -5, 17, -77, -43, 40, 6, -86, 9, 118, 72, 13, 36]} +{"id": 55753905, "vector": [-103, -6, -6, -29, 29, -76, -19, -26, 48, 54, 0, 89, 19, 84, -64, 123]} +{"id": 50806615, "vector": [-24, 22, 111, -125, -77, -2, 41, -112, -11, 89, 58, 35, 33, 4, -41, 27]} +{"id": 97641116, "vector": [-41, 18, 36, -100, 67, 22, -70, 70, -87, 85, -3, 123, -114, 93, -127, -26]} +{"id": 30163921, "vector": [17, 123, 55, 109, 46, 109, -32, -96, -31, -11, -62, 124, 66, -62, 12, 54]} +{"id": 60176618, "vector": [-121, -23, 112, 70, -79, 91, -30, 35, -53, 23, -126, -93, -126, 90, -108, 113]} +{"id": 13470059, "vector": [6, -128, 102, 103, -116, -125, -96, 82, 127, -4, 107, 68, -86, 98, 4, -87]} +{"id": 55189057, "vector": [-63, 59, 49, 126, 72, -37, 27, 95, 49, -47, 122, 29, 104, -6, 100, -22]} +{"id": 82052050, "vector": [17, -75, -39, -44, -13, 63, -84, 69, -98, -62, 73, -104, 44, 59, 83, 94]} +{"id": 90158785, "vector": [-77, -82, -50, -89, -9, -37, -52, 23, -128, 122, -57, -102, 104, 19, 43, -80]} +{"id": 40197395, "vector": [76, 94, 85, 32, 15, 113, 114, -82, 12, 76, -77, -12, 33, -69, 90, -65]} +{"id": 23432750, "vector": [56, -25, 126, 60, 78, -5, -89, -78, 53, -66, -112, -32, 127, -86, 3, -28]} +{"id": 76825057, "vector": [-121, -87, -10, -38, -33, -122, 13, -45, 34, -97, -59, -19, -98, -126, 82, 63]} +{"id": 8115266, "vector": [-32, 9, -111, 85, -15, -19, -19, -117, -109, -115, 29, -116, 77, 59, 110, -89]} +{"id": 65880522, "vector": [-125, -21, -9, 50, -53, 83, -19, 103, 8, -54, 21, 12, -85, 101, 15, -127]} +{"id": 66832478, "vector": [-104, -1, -44, 50, -127, -16, -81, -94, -16, -119, 81, 40, -47, 67, 78, 93]} +{"id": 64602895, "vector": [-119, -11, -31, 81, 58, 66, -97, 19, -8, 80, 96, -9, -77, -44, 30, 20]} +{"id": 5482538, "vector": [84, 118, -48, 54, 122, -24, -119, 65, 72, 96, 70, 83, -100, -10, 95, 36]} +{"id": 53888755, "vector": [127, -80, 104, 111, 39, 67, 16, -22, 100, 89, -93, 36, 70, 2, 118, -70]} +{"id": 54987042, "vector": [-99, -30, -49, 68, -48, -49, -73, 58, -103, -9, -97, 46, -48, -25, -73, -108]} +{"id": 76434144, "vector": [17, 123, 103, 71, 1, 98, 17, 0, 126, -11, 10, 78, -32, 3, -59, 113]} +{"id": 88130087, "vector": [-28, 62, -75, -54, -48, -101, 49, -32, 82, 59, -107, 55, -6, -112, -16, 19]} +{"id": 78589145, "vector": [-64, -118, -64, 3, -2, -100, -27, -20, -95, 50, -59, -82, -126, 86, 0, 50]} +{"id": 64157906, "vector": [65, 119, -85, 30, -55, 75, 73, 112, 110, 6, 24, -86, -83, 69, 98, 121]} +{"id": 67031644, "vector": [-44, 59, 52, -69, 110, -120, 41, -23, 127, 106, -89, 6, -4, -87, -27, 40]} +{"id": 72278539, "vector": [20, 69, -4, 107, 74, 43, 27, 54, -59, 30, 98, -26, 91, 124, -2, 104]} +{"id": 13348726, "vector": [-64, -30, 93, 61, -126, -32, 124, -7, -62, -106, -36, 30, -80, 64, -61, -28]} +{"id": 44983451, "vector": [-103, 102, 93, -97, -67, 41, -68, -69, 15, -123, 79, 50, 101, -58, 119, 28]} +{"id": 91255408, "vector": [38, -67, 97, -26, 89, 64, 26, -13, -66, 33, -3, 93, 122, 43, 77, 56]} +{"id": 30090481, "vector": [10, -32, 38, -2, 52, 112, 67, -56, 86, -17, -21, -114, -57, -5, 19, -113]} +{"id": 669105, "vector": [-69, 112, -112, -103, 40, -53, 33, 105, -17, -68, -116, 41, -117, -112, 71, -15]} +{"id": 86821229, "vector": [-105, -102, 96, -88, -41, 108, 69, 114, -36, 102, 103, -30, 22, 60, -79, -123]} +{"id": 30543215, "vector": [-55, 68, 35, -59, -1, 121, -91, 46, -98, -59, -85, 77, -118, 3, -41, 26]} +{"id": 72725103, "vector": [-33, -74, 31, -114, -1, 71, -116, -107, 21, -47, -66, 29, 56, 127, 40, -97]} +{"id": 36685023, "vector": [79, 111, 111, -17, -10, 70, -128, -119, 52, 72, -126, 87, -2, 20, 28, 25]} +{"id": 4787945, "vector": [30, 95, 104, -32, 49, -31, -53, 42, 93, 4, -115, 15, 76, -116, -48, 47]} +{"id": 83651211, "vector": [108, 126, 80, 105, 116, 125, 39, -14, -50, -107, 98, -105, 106, -61, -106, -77]} +{"id": 76671482, "vector": [-119, -79, 79, -113, -76, -102, 63, 46, 90, -28, 93, -66, 45, 42, -83, 99]} +{"id": 66271566, "vector": [21, 123, -5, -38, 112, -60, 24, -69, 86, 27, -36, -114, -67, -119, 22, -61]} +{"id": 35512853, "vector": [-106, 1, 50, 120, 37, 29, 78, -67, -19, -11, -32, -121, -87, -96, 47, 71]} +{"id": 11543098, "vector": [-60, 101, 100, -64, 75, -60, 36, 33, -54, 2, -21, -38, -63, 31, -49, -38]} +{"id": 52293995, "vector": [87, -128, 127, 116, -94, -19, 93, 120, -26, -15, 35, 59, 93, 10, 100, -116]} +{"id": 51588015, "vector": [-50, 110, 86, -84, 83, 107, -32, 108, 20, 33, 86, -110, -10, 119, 26, -39]} +{"id": 99549373, "vector": [84, 120, 6, -119, 105, -125, -27, 122, 75, -63, 79, -85, -63, 60, 72, 57]} +{"id": 4806458, "vector": [-112, 67, 4, -82, 16, -95, -9, 97, 72, -62, -40, 1, -30, -112, -104, 124]} +{"id": 51149804, "vector": [60, -15, -93, -109, 73, -78, -48, 59, -64, -100, 76, -52, -77, -94, -126, -122]} +{"id": 45407418, "vector": [-6, -16, 91, -12, 122, -2, -67, 105, -127, -62, 39, -49, -28, -55, 95, -16]} +{"id": 85117092, "vector": [55, -19, -101, 46, -18, 33, 71, 32, -16, -45, 79, -122, -82, -28, 47, 53]} +{"id": 8913721, "vector": [-80, -4, 2, 89, 14, 96, 115, -124, -7, -86, 105, -38, 98, 15, -124, -49]} +{"id": 92787493, "vector": [-97, 107, 53, 103, -82, -106, -5, -29, -84, 66, 37, 43, 19, -91, 31, 97]} +{"id": 98648327, "vector": [113, 50, -114, -77, -28, 110, -62, 27, -54, -98, 95, -102, 53, -62, 32, 31]} +{"id": 4985896, "vector": [-82, -60, 26, -119, 99, -40, -115, 15, -70, -66, 56, 60, -18, 123, 67, -81]} +{"id": 22721500, "vector": [-73, -56, -90, -112, 61, 124, 68, 11, -60, 27, 101, 49, 66, -83, -51, -20]} +{"id": 14220886, "vector": [67, -94, 94, -13, -116, -74, 59, -110, 56, 10, -29, -95, -23, -10, -82, 112]} +{"id": 2717150, "vector": [2, 109, 38, 18, -10, 53, -23, 94, -114, -66, -20, -11, 68, -42, 57, 116]} +{"id": 6521313, "vector": [-19, -55, -108, 47, -10, -59, 25, 51, -112, 122, -103, 56, -48, 14, 121, -75]} +{"id": 68000591, "vector": [-17, -27, -4, 96, 56, 34, 31, -93, -18, 2, 83, -122, -46, -19, 88, -9]} +{"id": 77377183, "vector": [5, 111, -77, -104, -64, -86, 118, -112, -24, 127, 12, -90, -22, 53, -47, -3]} +{"id": 90090964, "vector": [-30, 35, 75, 105, -93, 1, -1, -97, -82, -89, 0, -79, -97, -105, 14, 104]} +{"id": 17764950, "vector": [-85, 64, 75, 95, 11, -79, -58, -20, 18, 43, 75, -67, -32, -28, 85, -112]} +{"id": 41092172, "vector": [83, -20, 109, -73, 13, -104, 62, 55, -4, 115, -46, -40, -16, -87, -124, 95]} +{"id": 20642888, "vector": [77, -54, -114, -19, -50, 28, 108, -29, -77, 102, -120, -65, 52, 44, 60, 57]} +{"id": 42307449, "vector": [93, -19, 61, 105, -47, -98, -104, -22, -15, 30, -68, -2, -127, 38, -83, 75]} +{"id": 34698463, "vector": [2, -82, -11, -1, -83, 120, -126, -84, -27, 127, -74, -44, -106, -87, -76, -61]} +{"id": 76703609, "vector": [25, 1, 109, 62, -111, -23, 32, -91, 108, 89, -34, -43, -76, 45, 35, -2]} +{"id": 22405120, "vector": [96, -101, 98, 113, -31, 0, -59, -88, 34, 95, 119, 37, 37, -110, 63, -95]} +{"id": 99729357, "vector": [68, 108, -2, 23, -122, 14, 78, -123, 33, -97, -24, 67, -53, 118, 85, 11]} +{"id": 48260151, "vector": [80, -33, -9, -40, -124, 103, -107, -77, -80, 21, 122, 45, 34, -89, 37, 91]} +{"id": 22108665, "vector": [-54, -65, -88, -36, 95, -118, 124, -122, 88, 101, -83, 43, -117, -5, -104, 108]} +{"id": 74743862, "vector": [-9, 55, -8, -23, 125, -49, -9, 41, -36, 78, 14, 51, -88, -71, -119, -102]} +{"id": 44550764, "vector": [-32, -62, -100, -119, 25, -86, -108, 107, -65, -21, 28, -88, 8, 14, -114, -28]} +{"id": 61928316, "vector": [-56, -119, 57, 52, -46, 24, -104, -78, -56, 4, -24, -107, 111, -95, 95, -20]} +{"id": 62552524, "vector": [88, 78, -75, 83, 41, -80, 12, 64, -108, -103, 11, 49, -52, 83, 13, 22]} +{"id": 45990383, "vector": [-121, -97, -106, 30, 15, 18, -36, 66, 53, -95, 6, -1, -37, 19, -5, -4]} +{"id": 11365791, "vector": [-46, -99, 97, -90, 56, 3, -26, 11, -74, -16, -96, -34, 56, 2, -93, -128]} +{"id": 82897371, "vector": [88, 17, -121, 120, 93, -45, -112, 69, 79, -10, -59, 121, -31, 102, 115, -37]} +{"id": 9575954, "vector": [-41, -71, 121, -57, -28, 122, 3, 12, -98, -94, 106, -50, 34, -1, -44, -56]} +{"id": 26063929, "vector": [-93, 52, 81, -27, 117, -127, 80, 83, 121, -20, -105, 46, 110, 73, 69, -73]} +{"id": 16405341, "vector": [-25, -57, 50, 83, -11, 103, 91, -93, 49, -56, -28, 23, 112, -27, 96, 98]} +{"id": 9058407, "vector": [-44, 101, 102, -21, -24, 57, -48, -77, -61, -35, 15, -7, 21, 16, -81, -71]} +{"id": 63506281, "vector": [-38, -84, 31, -107, -44, 61, -33, -45, -18, 103, 41, 26, -127, -73, 5, -19]} +{"id": 16019925, "vector": [23, -117, 121, 24, 35, -40, 85, 48, 65, -90, -91, -12, -40, -10, 40, -113]} +{"id": 48673079, "vector": [-15, -105, -93, 74, 87, -46, 35, -7, -1, -108, 60, 66, -93, -16, -113, -73]} +{"id": 4095116, "vector": [-65, 120, 39, -42, -25, -28, -16, -33, -44, 35, -37, -86, 79, -12, -122, 30]} +{"id": 76540481, "vector": [-70, 12, 79, 126, 84, 90, 12, 43, 39, -116, -89, -57, 45, 45, -35, -105]} +{"id": 70800879, "vector": [-122, -16, 26, 124, -56, 97, -82, -82, -43, 77, 58, 22, -27, 22, -23, 19]} +{"id": 92353856, "vector": [90, -95, 2, -118, 104, 115, -65, -8, -61, 99, -45, -109, 115, -115, 46, -16]} +{"id": 44784505, "vector": [-73, -30, -69, -13, -66, -110, -31, 26, 57, 18, -85, 52, 82, -77, 111, 10]} +{"id": 48893685, "vector": [-49, -62, 97, -18, -83, -73, 122, -105, 35, 42, -30, -95, -38, 4, 65, 107]} +{"id": 3235882, "vector": [-65, -41, 99, -86, -19, 75, 90, 119, -11, 69, 22, 100, 33, 84, 110, -117]} +{"id": 52613508, "vector": [-99, 125, 70, 95, 58, 55, -81, -79, -29, 6, 24, -44, -100, -127, -32, 21]} +{"id": 47213183, "vector": [66, -39, -8, -112, -32, 101, -28, 45, 30, -104, -13, -118, -14, -25, 104, -3]} +{"id": 69697787, "vector": [-71, 23, -100, -5, 58, 56, -84, 34, -127, 72, -63, -27, 119, 68, 6, -19]} +{"id": 8696647, "vector": [68, -104, -83, 35, 71, 75, -45, 73, 115, -68, -102, -27, -4, 41, 47, -98]} +{"id": 78561158, "vector": [-79, -49, -21, -82, -69, -4, -14, -76, -31, 18, -104, 49, -35, -94, 81, -52]} +{"id": 26102057, "vector": [81, -115, 94, -120, 94, -52, 89, -51, -67, -113, 94, 61, 52, 53, -24, 2]} +{"id": 66322959, "vector": [-45, 106, -117, -93, -28, 40, 30, 30, -32, 70, -87, 59, -31, 1, 90, -82]} +{"id": 54427233, "vector": [-24, -51, -95, -87, -15, -47, -13, -106, -33, 8, 126, 32, -33, -87, -33, 33]} +{"id": 51507425, "vector": [13, 28, -30, -102, -49, -5, -112, 102, -55, 5, -23, -53, 31, -11, -87, 58]} +{"id": 27411561, "vector": [110, 99, 4, -87, 92, 104, 27, 75, -92, 3, -55, 120, 9, 116, -28, 37]} +{"id": 85242963, "vector": [-26, -121, 79, -72, -19, -85, 126, -119, -21, -10, -54, -87, 82, -12, 64, 58]} +{"id": 874791, "vector": [-79, 7, 123, 110, -58, -45, -124, -105, 63, 71, -20, -30, 104, 14, 70, 5]} +{"id": 13468268, "vector": [-67, 31, -25, 103, -49, -33, 65, -112, -72, -91, -114, -21, -77, -19, 106, 102]} +{"id": 83533741, "vector": [-57, 66, -85, -77, 87, 8, 60, 104, -47, 33, 31, -6, -19, 89, 106, -25]} +{"id": 92071990, "vector": [90, 65, -73, -41, 26, -11, -101, -68, -104, -63, -53, -52, -30, 89, -62, 2]} +{"id": 68694897, "vector": [-34, 87, -75, -108, 68, -126, 5, -116, -54, 105, 94, 100, 50, -1, -38, 107]} +{"id": 69255765, "vector": [76, -50, -44, 50, 68, 49, -36, 70, -27, 65, 99, 81, 27, -109, 14, 127]} +{"id": 2917920, "vector": [95, -91, 101, 108, -5, -122, -9, 5, 54, 7, 106, -33, -85, 15, -39, -28]} +{"id": 75407347, "vector": [-15, -72, 95, 13, -100, 100, 121, 86, -4, -80, -77, -120, 0, 33, -125, -116]} +{"id": 8129978, "vector": [-97, 63, -17, 12, 123, 16, 103, 84, -110, 5, 33, -51, 86, 92, 61, 76]} +{"id": 30569392, "vector": [-33, 121, 6, -54, 85, 31, -35, 80, -23, -48, 50, -44, 61, 60, 104, 99]} +{"id": 66116458, "vector": [-10, -18, -70, -93, 46, -124, 13, 124, -4, 15, -49, -75, 119, -61, -50, -113]} +{"id": 86301513, "vector": [111, 102, -102, -26, 78, -113, 29, -16, 110, 70, 15, -42, 8, -17, -88, 18]} +{"id": 79942022, "vector": [79, -69, 77, -20, 16, -4, -127, -127, -120, 122, 58, 38, 116, -6, 39, -13]} +{"id": 80014588, "vector": [-105, -66, 105, -13, -84, -46, -5, 38, -55, 31, -36, 33, 24, 80, -89, 18]} +{"id": 79880247, "vector": [-44, 91, -31, 40, -115, 85, 77, 62, 59, 28, -52, -68, -88, 42, 7, -100]} +{"id": 35996293, "vector": [-62, -36, -41, 116, -74, -68, 91, 91, -122, 99, 127, 73, 68, -116, -89, -28]} +{"id": 84293052, "vector": [127, 91, 69, 88, -48, 74, 101, 49, -24, 99, -35, -118, -56, -63, 43, -90]} +{"id": 94516935, "vector": [71, -90, 32, -127, -28, 92, -94, 98, 118, -41, 45, -96, 118, 61, -93, 124]} +{"id": 14947650, "vector": [-47, 56, 23, -71, -117, -12, -80, -47, -95, -22, 75, -43, 69, 118, 55, 70]} +{"id": 73617245, "vector": [44, 54, 100, 23, 56, -111, -47, -1, -83, 2, -123, 23, 111, 105, -67, 73]} +{"id": 97940276, "vector": [46, 13, 99, 88, 119, -103, 47, 99, -34, -77, 96, -38, -5, 54, -16, 11]} +{"id": 45306070, "vector": [-98, 30, -123, -126, -56, -98, -1, -37, -121, 31, 52, -30, -100, 18, -56, 95]} +{"id": 67124282, "vector": [46, -114, -69, -14, -120, -48, 31, -30, 87, 101, -55, 26, -32, 43, -23, 23]} +{"id": 56515456, "vector": [-104, -76, 113, 21, -55, -27, -56, 106, -83, 109, 27, 2, -12, 88, -17, 110]} +{"id": 5111370, "vector": [75, -127, -13, -10, 36, 60, 119, -79, 94, 49, 105, 98, 14, -79, 121, 46]} +{"id": 62693428, "vector": [-58, 56, 93, 31, -80, -16, 118, 103, -110, -69, 95, 57, 63, -86, -52, -52]} +{"id": 56531125, "vector": [2, 98, 25, 103, 68, -70, -23, -42, -78, 23, 77, -95, -17, -124, -119, 91]} +{"id": 321665, "vector": [-83, -10, 105, -39, 55, 19, -114, -63, -46, -45, -25, 41, 86, 112, 98, -72]} +{"id": 9398733, "vector": [-119, -125, -75, -127, -90, 121, 86, -53, -47, 12, -74, 36, 16, 12, -98, -122]} +{"id": 89637706, "vector": [0, -74, -13, -67, -21, 85, -87, -18, 54, -14, 9, 104, 31, 118, 7, 35]} +{"id": 94911072, "vector": [12, 123, 121, 11, -31, -9, -25, -72, -35, 52, -36, -25, 85, -54, -105, -94]} +{"id": 62452034, "vector": [-115, -16, -49, -115, -63, 83, 62, 99, -44, 109, 35, -30, 13, -89, 47, 36]} +{"id": 63059024, "vector": [-66, 99, 120, 104, -19, -114, -113, -127, -121, -93, -11, 53, 78, -95, -41, 98]} +{"id": 7423788, "vector": [-114, 20, -121, -43, 105, -53, 12, -46, -30, 36, -33, -70, 72, 116, 117, -63]} +{"id": 43045786, "vector": [103, 70, -65, -102, -63, 41, -93, -88, 22, -89, 116, 22, -78, -7, -23, 15]} +{"id": 5640302, "vector": [48, 5, 115, -68, 80, 112, -104, 123, 77, 43, 61, -89, -128, -18, -22, -55]} +{"id": 68875490, "vector": [-7, -117, 45, -111, 114, 41, 9, -126, -36, 43, 88, -24, 103, 100, -42, -68]} +{"id": 10649306, "vector": [74, -28, 3, -124, -66, -102, -95, 13, -120, -86, 111, -57, -123, 83, -73, 93]} +{"id": 65017137, "vector": [-8, -96, -12, -24, -20, 18, 0, 113, -86, -20, -46, 109, -63, -112, 2, 90]} +{"id": 66137019, "vector": [31, -2, -39, 36, -108, -103, -57, -5, -106, -126, -93, -60, -113, -128, 6, 80]} +{"id": 84904436, "vector": [-18, 20, 115, 114, -50, -5, 66, -89, -42, 98, 110, 116, -40, -43, -52, -18]} +{"id": 55615885, "vector": [-24, 23, -77, -54, -82, -89, 45, 32, 101, -100, -112, 110, -54, 71, 78, 103]} +{"id": 30653863, "vector": [-114, -128, 0, -77, 48, 45, 78, -82, -43, 9, 65, 116, 115, 26, 15, 105]} +{"id": 91727510, "vector": [-37, 45, -27, 85, 60, 37, 100, -72, -120, -127, -74, 0, -85, -128, -87, -27]} +{"id": 2891150, "vector": [-72, 8, 11, 113, 55, -99, -104, 64, 28, 9, -53, 89, 28, 90, -65, -110]} +{"id": 75543508, "vector": [59, -62, -5, -6, 50, -110, 78, -50, 19, 62, 19, -78, 114, 47, 41, 86]} +{"id": 26139110, "vector": [96, 94, 41, -39, -127, 17, -122, -80, -5, 18, -29, 13, 52, 98, -48, 58]} +{"id": 65081429, "vector": [-62, -128, -32, -35, -118, -100, 74, 100, 85, 66, -127, 68, 0, 106, 107, -2]} +{"id": 98739783, "vector": [90, 24, 89, -60, -75, 122, 103, -106, -43, -81, 15, -39, 73, -72, 105, 81]} +{"id": 72358170, "vector": [108, -38, 53, 6, -70, 14, 113, 123, 27, 30, 4, -35, -112, 39, -9, -24]} +{"id": 99125126, "vector": [50, -1, -72, -14, -84, 11, 59, 125, 76, -51, -60, -41, 103, 85, 87, 114]} +{"id": 9886593, "vector": [66, 108, -37, -15, -127, 3, -21, 77, 112, -104, 78, 117, 119, 16, -126, -37]} +{"id": 44846932, "vector": [41, 18, -24, -100, -29, -17, -98, 52, -35, -66, 23, 40, 13, -22, -23, 82]} +{"id": 88904910, "vector": [-65, -93, -74, 35, -35, -15, -73, 34, 113, -18, 44, -93, -93, 119, -75, -118]} +{"id": 61141874, "vector": [79, -32, -19, 57, 86, 82, -113, -32, -59, 109, 19, 77, 112, 3, -128, -16]} +{"id": 39801351, "vector": [71, -65, 32, -66, 4, 100, 68, 83, -82, -47, 30, 102, 39, -107, 57, -122]} +{"id": 95395112, "vector": [-98, 125, -38, 10, 67, 19, 90, 90, 40, -124, 3, -111, 62, 57, 48, -109]} +{"id": 2331773, "vector": [18, -80, -94, -105, -104, -94, 86, -105, -29, -99, 66, -112, -88, -83, 101, 58]} +{"id": 59371804, "vector": [-7, 112, -24, -56, -36, -56, 26, -123, 27, -27, -127, -72, 54, 41, 84, -34]} +{"id": 80316608, "vector": [49, 75, 79, -36, -10, 87, -25, -65, 58, 112, 6, 14, -40, -94, -3, 114]} +{"id": 84002370, "vector": [68, -26, 14, 80, 54, -128, -45, 85, 74, -71, -63, -99, 16, -104, -13, -28]} +{"id": 22435353, "vector": [93, 17, -16, 70, 62, 76, -20, 95, -40, -119, -16, 11, 108, 82, 38, 89]} +{"id": 45790169, "vector": [-57, -18, 79, 62, 79, 123, -36, -125, 8, 108, 51, 73, 28, -31, 62, 11]} +{"id": 93053405, "vector": [59, 113, -126, -83, -56, 12, -109, -8, -5, -76, 97, -40, -15, -126, -88, -17]} +{"id": 4434662, "vector": [7, 1, -113, -48, 11, -128, 125, 60, 63, -69, -44, -90, -30, 31, 41, -42]} +{"id": 46851987, "vector": [-117, -120, 78, -36, 90, -108, -68, 92, -106, -87, -96, 23, 29, 56, -78, 51]} +{"id": 33797252, "vector": [-20, 48, -95, -17, 78, -17, 115, -59, 75, -120, -77, 0, 24, -12, 31, -111]} +{"id": 49598724, "vector": [25, 99, -12, 20, -22, -126, -5, -34, -61, -70, -20, 54, -80, 81, -72, 76]} +{"id": 68041839, "vector": [101, 72, 39, 84, -49, 28, -127, -9, -75, 87, -128, 41, -110, -77, 45, -102]} +{"id": 33336148, "vector": [-28, -91, 34, 106, 116, -127, -8, 107, -59, 36, 94, -26, -23, 24, -33, -57]} +{"id": 26493119, "vector": [-72, -105, 7, -74, 84, 79, -79, -10, 15, 81, 70, 124, 98, 58, -97, -100]} +{"id": 45667668, "vector": [-18, 85, 43, -93, -64, 96, 50, -109, 36, -84, 47, -10, 111, -56, -37, -13]} +{"id": 26507214, "vector": [76, -102, -16, -116, 55, -111, 97, 10, -27, -27, 99, -82, 0, -73, -23, -2]} +{"id": 4798568, "vector": [12, -56, 92, -83, -16, 1, 36, 7, 105, 3, -30, 104, -49, 89, 41, -121]} +{"id": 62740044, "vector": [-44, -83, -57, -121, -68, -35, -20, -28, -22, -37, -65, 54, 10, 107, 52, 45]} +{"id": 13819358, "vector": [38, -52, 37, -110, 45, 7, -113, -47, -80, 67, 25, -128, -96, -70, 62, 61]} +{"id": 91281584, "vector": [-4, -42, 61, -4, 86, 67, 86, -41, 27, 21, 46, 26, -23, 56, -68, 73]} +{"id": 92554120, "vector": [-122, -77, 109, 60, 24, 126, -23, 49, 121, -57, -44, 52, 122, -2, -1, -91]} +{"id": 44627776, "vector": [-116, 10, 32, 102, -12, 10, 56, -74, -73, -37, -62, 79, -28, 92, -57, 11]} +{"id": 36812683, "vector": [27, -42, 83, -82, 40, -87, -40, 98, 25, 121, 116, -55, 0, 111, -101, 124]} +{"id": 20765474, "vector": [103, 4, 94, -94, 66, -56, -1, 77, -118, 82, 88, -68, -123, -64, -93, -11]} +{"id": 29065964, "vector": [30, -20, -99, -77, -71, 16, -60, 77, -28, -45, -127, 105, -126, -44, 16, -9]} +{"id": 19457589, "vector": [96, 33, 46, 34, 6, 115, -12, -124, -37, -68, -18, -18, -67, -63, 95, -122]} +{"id": 42644903, "vector": [-47, -63, -41, 127, -113, 98, -57, 4, -38, -94, 69, -78, -3, 23, 82, -5]} +{"id": 36753250, "vector": [60, 11, -91, 103, -92, -56, 124, -65, -12, -28, 62, -55, -30, -83, 46, 119]} +{"id": 65275241, "vector": [-80, 26, -15, -1, 50, 113, 8, -6, -105, -128, -69, 100, -50, -128, 112, -99]} +{"id": 45049308, "vector": [66, -54, 49, 93, -116, 81, 45, 79, 18, -40, 126, 61, 17, 103, 49, -51]} +{"id": 16387575, "vector": [100, -86, -63, -37, -14, 4, 86, 102, -19, 112, 53, -22, -27, 89, 18, -84]} +{"id": 526217, "vector": [-57, -112, 63, -115, -72, 5, -18, 79, 114, -54, 67, -69, 96, -76, -49, 36]} +{"id": 38341669, "vector": [-2, -97, 30, 94, 18, 66, -31, -108, -27, -44, -12, -104, 32, 58, -58, -105]} +{"id": 31161687, "vector": [-5, -116, -6, -124, 52, -58, -27, 61, 20, 1, -105, -97, -92, -112, -86, -28]} +{"id": 57020481, "vector": [122, -57, 84, 54, -62, 79, 126, -121, 27, -71, -121, 75, -57, -53, -100, -101]} +{"id": 77300457, "vector": [-34, -121, -107, -98, -83, 127, 47, -124, 20, 74, 57, 108, -26, 72, -98, -86]} +{"id": 72614359, "vector": [57, -42, 80, 54, 124, -111, -91, 99, 35, 66, 85, -50, -58, -29, 47, -95]} +{"id": 45617087, "vector": [-107, -12, 17, -27, 56, -57, 87, -86, -64, 103, -64, 19, -44, 102, 47, 107]} +{"id": 97561745, "vector": [-18, 116, 15, 94, 70, 1, 43, 82, -1, 83, -59, 113, 13, 34, 12, 6]} +{"id": 97281847, "vector": [-98, -88, 28, -121, -77, 85, -14, -96, 37, -77, 114, 76, -79, 17, -81, -104]} +{"id": 62936963, "vector": [-80, 64, 58, 8, -78, -70, 0, 65, 59, -87, 0, -77, 40, 9, 8, 84]} +{"id": 44847298, "vector": [13, 19, 24, -41, 60, -97, 27, -107, 94, -117, -53, 5, 26, 114, -94, 0]} +{"id": 18001617, "vector": [4, -97, -114, 111, -100, -3, 66, 35, 58, -27, -95, -114, 69, 122, 31, -94]} +{"id": 36845587, "vector": [-100, 92, 78, 53, -83, -28, 44, -53, -84, 124, -53, -90, 27, 106, 71, -2]} +{"id": 80555751, "vector": [79, 12, -84, -118, -41, -89, 94, 1, -66, -24, 108, -79, 126, 109, 99, -69]} +{"id": 78909561, "vector": [-71, 106, -52, 101, 38, 25, 37, -39, 82, 115, 3, -123, -23, 93, -121, 83]} +{"id": 90272749, "vector": [74, -22, 97, -74, -76, 122, 79, 9, 33, -31, -124, 92, 58, -37, -42, 111]} +{"id": 30139692, "vector": [81, -21, -102, 18, -6, 70, 42, -32, 114, -14, 61, 104, 59, -112, -56, 17]} +{"id": 29516592, "vector": [49, -51, 43, 25, -49, -10, -24, 24, 6, 22, 116, 32, -115, -24, -24, 89]} +{"id": 36505482, "vector": [124, 105, -61, 66, -111, -122, -116, -11, 104, -102, -63, 80, 82, 48, 82, 18]} +{"id": 44473167, "vector": [-57, -46, 76, 89, -112, -42, -99, -56, 6, -18, -95, -71, 68, -117, 66, 34]} +{"id": 37739481, "vector": [-70, -82, -53, 70, -74, 105, 11, 119, 75, -81, 32, 61, -54, -62, -88, 20]} +{"id": 8373289, "vector": [-32, -46, -67, -59, 61, -63, -122, -66, -117, 6, 106, 82, -87, 10, -22, 55]} +{"id": 35192533, "vector": [65, -30, 21, -117, -9, -94, 127, 28, -108, -35, 7, 100, -65, -15, -80, -60]} +{"id": 62051033, "vector": [-123, 63, 61, 40, 76, 2, -3, 71, -9, 89, 68, -57, 119, -121, -82, -114]} +{"id": 92867155, "vector": [80, 17, -24, 113, 54, -119, 12, -38, 105, -111, 53, -35, 51, 39, -68, 92]} +{"id": 62803858, "vector": [66, 28, -109, 126, -48, -34, -108, 15, -87, 90, 99, 74, -124, 122, 85, 59]} +{"id": 95251277, "vector": [63, -104, 1, -108, -119, 76, -46, -92, 79, 107, -124, -90, 31, 96, -121, 18]} +{"id": 65038678, "vector": [-37, -6, -71, -7, -53, 71, 70, 90, 78, -116, 1, -26, 90, -101, -31, -4]} +{"id": 73168565, "vector": [65, -18, -10, 92, -22, 9, -5, 29, 122, 109, 85, 107, 37, 13, -75, -6]} +{"id": 75777973, "vector": [-89, -54, 50, -24, -76, 69, -23, 14, -89, 121, -1, 14, -53, -25, 113, 115]} +{"id": 18833224, "vector": [33, -23, 41, -110, 50, -85, -32, 82, 19, 10, 91, -103, 5, -40, 85, 116]} +{"id": 43506672, "vector": [-23, 110, 113, -36, -121, 123, 4, -114, 45, -33, -12, 71, 122, 116, -88, 118]} +{"id": 61815223, "vector": [-34, -38, 49, 59, 71, 26, -37, -110, -56, -95, -2, 21, -115, 86, 104, 33]} +{"id": 30463802, "vector": [123, -106, 86, 117, -29, 23, -19, -49, -102, 124, -110, -39, 33, 121, -40, 83]} +{"id": 8634541, "vector": [122, 67, 75, 55, -115, 81, 58, 97, 96, -19, -38, 78, 78, -119, -90, 111]} +{"id": 57241521, "vector": [-3, -3, 116, 39, -81, -125, 37, -67, -79, 50, -98, -65, 40, -61, 123, -126]} +{"id": 7955293, "vector": [-20, -11, -115, 47, -24, 56, 68, 96, 9, 127, 102, 88, -107, -11, 38, -28]} +{"id": 33431960, "vector": [16, 44, -64, -110, 111, 70, -108, 44, 84, 114, -72, 97, -48, 85, -117, -126]} +{"id": 94090109, "vector": [-13, -5, -52, 72, 122, -92, 80, -63, -123, -121, 72, 45, 66, -128, 110, -21]} +{"id": 19101477, "vector": [-47, 80, 16, -9, 40, -41, 2, -81, 24, 92, -73, -1, 69, 16, 67, 46]} +{"id": 61741594, "vector": [-82, 38, 6, 88, -45, 46, -83, 95, 34, 89, 54, 61, 89, 78, -18, -79]} +{"id": 43501211, "vector": [30, 12, 96, 69, -81, -67, -5, -89, -30, -122, -67, 71, -76, -42, 24, -126]} +{"id": 61728685, "vector": [126, -100, -122, -124, 104, -19, 85, 9, -27, 56, 65, -100, -73, -35, -114, -103]} +{"id": 29932657, "vector": [0, -65, -19, 20, 41, 89, 118, 103, 109, -31, 50, -93, -24, 24, 28, -108]} +{"id": 55850790, "vector": [115, -118, -58, 107, -107, 66, -101, -73, -110, 72, 36, -111, 43, -58, 11, 127]} +{"id": 74614639, "vector": [-116, -97, 79, -104, -124, -66, 55, 3, -79, 11, -38, -28, -110, 22, 90, 122]} +{"id": 61960400, "vector": [-62, -102, -62, -10, 73, 70, -121, -75, 32, 81, 75, -54, 11, -68, 35, 16]} +{"id": 71083565, "vector": [-52, -102, 37, 38, 28, 78, 50, -54, 101, 67, -106, -79, 127, -3, -75, -12]} +{"id": 60581278, "vector": [63, 19, 30, -128, 31, -126, -70, 6, -78, 67, -106, 57, -5, 90, 47, 84]} +{"id": 54517921, "vector": [-2, -101, -123, -13, 25, -78, -115, -30, 5, 92, -87, -91, 42, -93, 60, 9]} +{"id": 48892201, "vector": [91, -71, 64, -34, 11, 32, -89, -36, 40, 100, 112, -106, -127, 17, -89, -120]} +{"id": 80251430, "vector": [86, 32, 0, 73, 126, 116, -125, 3, -83, 116, -71, 80, -18, 24, 104, 98]} +{"id": 13173644, "vector": [124, -72, -85, 85, 39, -110, -42, 125, -60, 19, -120, 12, -108, 21, 69, -34]} +{"id": 415901, "vector": [76, 100, 77, 51, 77, 48, 62, -19, 81, -38, 57, -23, 41, -108, -23, -15]} +{"id": 78218845, "vector": [-89, -78, -30, -27, -86, 1, 30, -76, -62, -64, -11, -49, 20, 57, -10, -77]} +{"id": 1239555, "vector": [22, -98, -28, -112, -5, 105, -85, -46, -60, 73, 84, 22, -85, 125, 29, 22]} +{"id": 83948335, "vector": [-26, 9, 89, -50, -75, -1, 52, 120, -126, -24, -48, -83, -83, 70, -112, 9]} +{"id": 38365584, "vector": [-2, 9, 33, -117, -32, -64, 105, 66, -87, -59, -2, -99, 104, 8, 8, 44]} +{"id": 91957544, "vector": [66, 93, 108, -40, -124, 115, -17, 7, 36, -62, 87, 9, 21, -79, -99, -84]} +{"id": 32274392, "vector": [-94, 66, -30, -59, -86, -125, -103, 60, -68, -8, -6, 6, 20, -88, 52, -22]} +{"id": 45075471, "vector": [-19, -93, 76, 86, 31, -111, -27, 73, -22, -18, 98, -3, -81, 89, 55, -35]} +{"id": 39553046, "vector": [57, 97, -57, 42, -86, 10, -6, -114, 35, 126, 66, -102, -108, 55, 16, 92]} +{"id": 21289531, "vector": [-87, 70, 1, 41, 45, -30, -6, 9, -68, 35, -37, 127, 32, 106, 38, -25]} +{"id": 82886381, "vector": [-30, -42, -119, -114, 63, -25, 11, -118, -25, 58, 72, 88, -110, -6, -90, -90]} +{"id": 83368048, "vector": [-77, -46, -9, 39, -107, -74, -38, 101, 91, -89, -27, 91, -118, 19, 35, 71]} +{"id": 33123618, "vector": [-73, -62, -113, -105, -62, 106, -2, 124, -29, 76, 49, -106, -2, 44, 71, 93]} +{"id": 79191827, "vector": [-125, 36, 11, -52, 25, 114, -93, 105, -22, 93, -83, 95, 84, -5, 23, 82]} +{"id": 92530431, "vector": [-13, -12, 24, -1, 13, -69, -37, -88, 109, -111, 13, -126, 59, -94, -82, -106]} +{"id": 19891772, "vector": [22, 63, 36, 62, 66, -120, -86, 123, -95, -11, 126, 111, -125, -13, -102, 67]} +{"id": 32590267, "vector": [77, -24, -8, -86, 75, 85, 42, 46, -105, 65, -58, 115, -116, 52, 15, 33]} +{"id": 29510992, "vector": [-125, -51, -55, -15, 123, 88, -7, 124, 60, 2, -69, -110, -44, -66, 4, 14]} +{"id": 49597667, "vector": [-122, -96, -116, -80, -32, 108, 76, 59, 38, 77, 66, -25, 44, 25, 8, -100]} +{"id": 73124510, "vector": [56, -128, -40, -57, 17, 48, 32, 65, -30, 124, 47, 77, -78, -41, -16, 83]} +{"id": 69579137, "vector": [118, -71, 11, 32, -17, -71, -100, -58, -71, 56, -117, -96, 37, -26, 72, -51]} +{"id": 37560164, "vector": [5, -50, -71, 37, 62, -40, -39, 50, -99, 119, 64, -69, 49, 78, 36, -48]} +{"id": 41380093, "vector": [-18, -90, -5, -81, 16, 75, -54, 104, -57, -105, 115, 108, 126, 116, 35, 62]} +{"id": 92692283, "vector": [-19, 101, -43, 43, 89, 121, -123, 49, -36, -41, -54, 15, -4, 70, 115, -83]} +{"id": 31904591, "vector": [-8, -118, -11, -101, -66, 6, -9, -120, 53, -79, 119, -85, -44, -55, -91, -72]} +{"id": 47887470, "vector": [112, -119, -92, -24, -12, 59, -81, -9, 98, -117, 4, -126, -97, -65, 75, -60]} +{"id": 17016156, "vector": [58, 119, 44, -123, -21, 68, 123, 35, -50, -7, -36, -62, -82, 26, 24, 51]} +{"id": 93566986, "vector": [-66, 92, -92, -33, 21, -89, -61, -31, -1, 11, -9, -125, -21, 98, -103, 49]} +{"id": 29959549, "vector": [112, 0, 7, -85, -31, -92, -78, 3, 75, -18, 43, 102, 115, -22, 43, -102]} +{"id": 99917599, "vector": [-29, -57, -97, 13, -21, 27, -118, -60, -81, -83, -62, -108, -126, -63, 84, 81]} +{"id": 77301523, "vector": [74, 92, 73, 11, -13, 40, -71, 85, 61, 8, 44, -106, -2, 83, -92, -108]} +{"id": 1022677, "vector": [-117, -119, 51, -90, -47, 51, 40, -91, -3, 109, -80, 4, 50, 18, -10, 46]} +{"id": 61263068, "vector": [-128, 65, -71, -103, 117, -111, 55, 52, 77, -125, 116, -48, -18, 89, 16, -12]} +{"id": 72738685, "vector": [43, 67, 77, -74, 17, 17, -44, 54, 53, 17, 79, 64, 88, 56, -78, 57]} +{"id": 77620120, "vector": [116, 29, -53, 14, 63, 119, -108, -6, 58, 116, -77, 53, 7, -64, -8, -114]} +{"id": 23110625, "vector": [-19, -119, 30, -20, 84, 59, -57, -80, -67, -46, 64, 59, 86, 68, -21, 106]} +{"id": 92541302, "vector": [-28, 61, 6, 106, 122, -100, 12, -24, -31, 126, -42, -32, -53, 43, 66, 88]} +{"id": 47361209, "vector": [-77, 13, 84, 54, -84, -108, -59, -117, 55, -57, -64, -71, 77, 78, 19, -57]} +{"id": 17539625, "vector": [79, 60, -50, -117, 90, 8, 109, -83, 125, -45, 111, 87, 114, 119, -22, -57]} +{"id": 75986488, "vector": [126, -9, 119, -46, -105, 55, -91, -20, 67, 87, -27, 47, 6, 33, 7, 93]} +{"id": 34295794, "vector": [-43, -71, -95, 39, -68, -54, -87, 75, 113, 94, -29, 90, -105, -90, -118, -18]} +{"id": 15535065, "vector": [76, -70, 74, 23, 122, 5, 72, -64, 112, 21, 112, -122, -13, 52, -104, 101]} +{"id": 37675718, "vector": [-86, -40, 77, 21, -15, 55, 91, 14, -36, -107, -109, 72, 73, 96, -57, 36]} +{"id": 77312810, "vector": [-23, 78, 75, 63, -37, 113, -91, 10, 30, 126, 67, -61, -7, 75, -61, 72]} +{"id": 569864, "vector": [-80, 2, -3, -111, -51, 3, -94, -80, 98, -18, -58, -107, -19, 93, -61, -83]} +{"id": 89217461, "vector": [-22, 68, -63, -97, -13, 19, -47, 55, 22, 24, 32, -59, 46, 123, -104, -45]} +{"id": 82339363, "vector": [-105, 7, 60, -11, 91, -88, 123, 20, 14, 35, 38, -108, -105, 90, 66, -4]} +{"id": 68824981, "vector": [-10, -47, 65, 12, -51, 25, 110, -44, -65, 10, 107, 96, 86, 28, 50, 89]} +{"id": 33553959, "vector": [-110, 84, -20, -48, 112, 111, -70, 105, -88, -23, 41, 118, 92, -70, 73, -47]} +{"id": 82979980, "vector": [-80, -76, -73, 2, -43, 82, 76, -45, -60, -14, -34, 113, -61, 95, -58, -69]} +{"id": 44481640, "vector": [103, 57, -38, -12, -88, -57, 70, 83, 74, -62, 103, 70, -5, -105, 115, 48]} +{"id": 21533347, "vector": [103, -81, -41, 95, 54, -52, -105, 78, -11, 36, -3, -85, 21, 98, 40, -62]} +{"id": 58208470, "vector": [34, 56, 34, -5, -78, 78, -110, -90, 21, 46, 83, -85, 118, 76, 27, -117]} +{"id": 92215320, "vector": [53, 81, -122, -94, 32, -100, -9, -83, -44, 90, -43, -50, -13, 52, -66, -78]} +{"id": 13231279, "vector": [118, 66, -17, 52, 79, 125, -85, 118, 13, 111, -52, -4, 106, 53, -44, -102]} +{"id": 45428665, "vector": [102, -82, 35, 35, 97, -47, -58, -5, 53, 56, -16, 117, 8, -9, -35, -77]} +{"id": 57961282, "vector": [123, 124, -45, 34, 89, -113, 68, 99, 85, -49, 124, -55, 44, 29, 47, -70]} +{"id": 29188588, "vector": [-64, 99, 87, 48, 75, -49, -69, -102, 57, -11, -76, 95, 48, -15, -98, -123]} +{"id": 81855445, "vector": [-70, -58, -112, -100, 97, 0, -13, 26, -10, -81, 110, -41, -44, -113, -94, -29]} +{"id": 39847321, "vector": [-90, 30, -67, 55, 116, -82, -4, 38, 98, 32, 21, 21, -17, 76, -56, -52]} +{"id": 37183543, "vector": [-44, -31, 52, -123, -102, 39, -1, -87, 93, 43, 116, -74, -12, 101, -26, -72]} +{"id": 32161669, "vector": [48, -40, 96, 25, 52, -30, 12, 42, -70, -74, -7, -3, 48, 24, -65, 10]} +{"id": 93933709, "vector": [75, -23, 113, -63, 93, -76, -109, -19, 78, 119, -1, 113, -9, 87, -81, 2]} +{"id": 23134715, "vector": [102, -36, 20, 34, -46, 94, -4, 53, -87, -67, 80, -123, -14, -29, -15, -78]} +{"id": 42967683, "vector": [72, -64, 121, -76, -108, -118, 23, -100, 117, -63, -75, 96, 22, -3, -64, 105]} +{"id": 15148031, "vector": [68, -66, 99, -16, -127, -53, 8, 107, -48, 81, -23, -31, -80, 104, -29, -91]} +{"id": 24733232, "vector": [-118, 60, 27, 82, -89, 42, -53, 121, -80, -65, -91, 100, -36, -126, -19, 64]} +{"id": 26664538, "vector": [-53, 44, -57, -83, -65, 81, 65, -73, 112, 69, 103, -100, 74, -28, 66, -20]} +{"id": 86543538, "vector": [55, -65, -112, -89, 15, -39, 126, -49, 91, -14, -94, 56, 88, -54, -37, 67]} +{"id": 6793819, "vector": [23, -22, -43, -84, 104, -24, 112, -99, -107, -25, 14, -24, 44, 23, 54, -92]} +{"id": 29635537, "vector": [-92, 43, 39, -38, -120, -9, -70, -75, 69, 86, 61, -111, 112, -120, 78, -125]} +{"id": 3509435, "vector": [-92, 100, 29, 75, 118, -107, 3, 92, 26, 22, -59, -37, 42, -64, 69, 12]} +{"id": 75820087, "vector": [-110, -73, -92, 8, 116, 120, -84, -65, 20, 92, -111, -109, -114, -15, -90, -29]} +{"id": 57163802, "vector": [51, -91, -33, 52, -73, -51, -109, 57, 41, 42, -89, -40, -126, -1, 33, 101]} +{"id": 9175338, "vector": [-100, -57, 118, -63, 53, -70, -108, 119, 18, 4, -84, 38, -30, 81, -25, 58]} +{"id": 44348328, "vector": [64, 100, 79, 77, 109, 125, -92, 101, 87, 65, 48, 43, -33, -74, 24, 28]} +{"id": 85116755, "vector": [-12, 98, -82, -16, 40, 29, 16, -42, -89, -61, -50, 101, 14, 4, 91, -4]} +{"id": 69641513, "vector": [36, 48, 9, 110, 112, -26, 82, 108, 18, 80, 35, -28, -87, -108, 74, 3]} +{"id": 6111563, "vector": [105, -36, 38, -112, -81, -54, -76, 78, 102, -43, 16, 24, 1, -32, 104, -89]} +{"id": 16097038, "vector": [40, 112, -16, -43, 83, -88, 88, 14, 70, -65, 68, -79, 49, 116, 29, -21]} +{"id": 94360702, "vector": [110, 126, 63, -39, 22, -86, 94, 11, 2, 90, -18, 99, 123, -61, -13, -79]} +{"id": 61859581, "vector": [103, 31, 110, 127, -31, -119, 23, 49, 17, -122, -118, -75, 30, -40, -35, -93]} +{"id": 82327024, "vector": [98, -120, 123, -23, 103, 82, -97, -111, 23, -123, -114, -21, 103, -89, -126, 124]} +{"id": 44842615, "vector": [60, -119, -122, -3, -119, 64, 72, -47, 119, -110, -25, 1, -3, -94, 100, -14]} +{"id": 9599614, "vector": [-104, -16, 78, 19, -49, 106, -45, -92, -26, -30, 38, 7, 83, -14, 21, 23]} +{"id": 43933006, "vector": [-14, 45, -31, -106, 78, 78, -76, 47, -17, -6, -103, 50, 74, -62, 104, 64]} +{"id": 93057697, "vector": [79, 48, 56, -78, 75, 29, -122, -70, -98, -7, 90, -75, -28, 6, 12, 76]} +{"id": 9188443, "vector": [-28, 96, 73, 24, 81, 63, 12, -71, -121, 50, -108, -102, 23, -92, 77, -13]} +{"id": 84684495, "vector": [67, 33, -36, 61, 125, 76, -29, -101, 39, -43, -61, 42, -71, -97, -77, -57]} +{"id": 96852321, "vector": [2, 74, 13, 18, 14, 21, 93, -69, -128, -79, -39, -5, 35, -124, 31, 6]} +{"id": 98462867, "vector": [-25, -71, 67, 96, 65, 52, -24, 107, -39, -65, -101, 16, 126, 24, 72, -20]} +{"id": 16099750, "vector": [-115, 127, -1, 45, 35, -50, -9, 6, 74, -81, -92, 30, 72, -69, 31, -98]} +{"id": 18411915, "vector": [-110, -11, -118, -83, -125, 110, 69, 93, -57, 92, 111, 58, 33, -7, 66, -69]} +{"id": 98371444, "vector": [66, -3, 81, 119, 103, -13, -104, 91, -68, 85, -96, 16, -87, 93, 38, -45]} +{"id": 88698958, "vector": [-40, -25, 115, 121, -49, 50, 22, 59, 17, -23, -28, -30, 106, 92, 29, -82]} +{"id": 48658605, "vector": [-33, -123, 9, -106, 78, -17, -110, -112, -14, -25, 94, 98, -122, 42, 76, 65]} +{"id": 23848565, "vector": [-55, -9, 116, -106, -63, 48, -116, 114, 118, -24, 63, -85, 65, 33, -62, -2]} +{"id": 39171895, "vector": [58, -125, 92, 127, 91, 105, -44, -10, -17, -9, -59, -9, -2, 56, -61, 68]} +{"id": 5267545, "vector": [109, 45, 65, -43, -26, -81, 39, -27, 44, 84, 31, -124, -93, 64, 79, 99]} +{"id": 84349107, "vector": [-17, -92, -103, 77, 58, 98, 51, 9, -2, 71, 85, 3, 23, -16, 104, 120]} +{"id": 36139942, "vector": [85, 61, 60, 65, -111, -92, -2, 32, -74, 117, -28, -110, -82, 48, 20, 80]} +{"id": 7300047, "vector": [-3, -116, -111, -75, 24, -105, -116, 97, -126, 37, 28, -78, -29, 30, -42, 100]} +{"id": 60102965, "vector": [25, -28, -67, -18, -84, -14, -21, -37, -30, -77, 67, 41, 23, -70, -87, -31]} +{"id": 19376156, "vector": [-109, 33, 125, 43, -32, 32, -56, -34, -89, -56, -66, -55, -125, 29, 3, 97]} +{"id": 10366309, "vector": [118, -61, 1, 80, 107, -101, -63, 70, -121, -103, -63, -40, -105, 36, 39, -123]} +{"id": 26863229, "vector": [-117, -37, -79, -8, -36, -78, 121, -98, 99, -102, 67, 120, -40, -97, -42, -92]} +{"id": 7685448, "vector": [-114, -101, -35, -61, 65, 119, 125, -113, 35, 119, 16, -124, 26, 23, -124, 56]} +{"id": 86001008, "vector": [48, -50, 105, -1, -46, -14, 16, -47, 110, -49, 48, -43, -114, -30, 76, 70]} +{"id": 51047803, "vector": [-5, 19, 18, -8, -11, -65, -28, 54, -90, -92, 13, 47, -84, 127, -56, 2]} +{"id": 71300104, "vector": [113, 40, -24, 104, -14, -42, 86, -92, -32, 70, -117, 79, 116, 56, 57, 53]} +{"id": 89214616, "vector": [-46, -114, 94, -41, 60, -37, -66, 4, 23, 21, 120, -56, -79, -108, -109, 11]} +{"id": 92604458, "vector": [-9, 76, -2, -6, -113, -126, -125, 44, -57, 105, -125, -24, 9, -23, -99, 39]} +{"id": 96906410, "vector": [67, -122, -102, -21, 88, 83, -49, 64, -123, 80, 81, 23, -14, -119, 93, 50]} +{"id": 54868730, "vector": [70, -99, 2, -125, -42, -45, -88, 52, -12, 31, -17, -104, -42, -26, -96, 27]} +{"id": 99690194, "vector": [41, 125, 99, -101, 7, 3, -104, -3, 62, -58, 23, -2, 107, -61, 103, 91]} +{"id": 19939935, "vector": [72, -33, -110, -114, 109, -59, 116, 4, 104, 99, 22, -27, 61, -75, 55, -104]} +{"id": 3233084, "vector": [-122, 116, 51, 59, 70, 9, -114, 50, -77, -15, -121, -39, 30, 75, -49, 72]} +{"id": 25842078, "vector": [-116, -4, 33, 110, 5, -64, -96, 60, -63, -18, 66, 123, 61, 30, 57, 16]} +{"id": 69786756, "vector": [-17, -26, -90, 67, -53, -38, 87, 79, 127, -5, 85, -9, 77, 73, -60, 5]} +{"id": 17957593, "vector": [45, -17, 109, 80, -44, -70, 20, -36, 20, 51, -91, -47, 93, 50, 106, -53]} +{"id": 83133790, "vector": [-66, -39, 94, 75, 39, 6, -109, -109, -128, -44, -74, -114, -39, 12, -6, -109]} +{"id": 71965942, "vector": [51, 3, 88, 91, -111, -111, 60, 88, 32, 57, -44, 81, -4, -85, -8, -76]} +{"id": 70372191, "vector": [-125, 28, 99, -58, 41, -96, -22, -52, -4, 71, 120, -30, 38, -12, -91, -127]} +{"id": 59177718, "vector": [124, 126, -73, -37, -28, -126, -67, 42, 26, -29, -9, 2, 47, -123, 30, 86]} +{"id": 38510840, "vector": [-8, 67, 69, -90, -36, 125, 38, -104, -115, -123, -12, -14, 73, -66, 116, 32]} +{"id": 21001913, "vector": [-52, 12, -13, -92, 55, 31, 88, -57, -79, 121, 20, 4, 116, 72, 122, 44]} +{"id": 32426752, "vector": [-8, 66, 9, -26, 116, 60, -76, -36, 106, -29, 119, -23, -33, -15, -35, -71]} +{"id": 27185644, "vector": [23, -59, 39, -114, 29, -14, 0, 47, 92, -16, 104, 92, -69, 107, -122, 33]} +{"id": 42073124, "vector": [16, 83, 74, -61, -85, -24, 87, 41, -105, -85, 99, -97, 16, -60, 98, -66]} +{"id": 90013093, "vector": [60, 9, 0, -64, 49, -96, 74, 48, 97, 73, -93, -80, -27, 57, -4, 19]} +{"id": 33304202, "vector": [-38, -27, -32, 57, -25, 5, -115, 92, -15, -41, -112, -21, -15, 106, 60, -87]} +{"id": 112651, "vector": [36, 20, -57, 61, -23, 43, -10, 86, 119, -20, 53, 13, 108, 118, -29, -82]} +{"id": 35092039, "vector": [0, -120, -7, -48, -92, -61, 69, 85, 30, -69, -79, -121, 59, 84, 64, 81]} +{"id": 63967300, "vector": [-1, -103, -93, 81, 109, -67, -21, 74, -67, -87, -100, -114, 77, -66, 47, -106]} +{"id": 72274002, "vector": [100, 47, -45, -50, -68, 96, -10, -79, 54, -43, 43, -80, -71, 118, -113, -111]} +{"id": 33249630, "vector": [-25, -86, -57, 24, -111, -89, -2, 102, -79, -39, 83, -85, 44, 8, 12, 125]} +{"id": 92998591, "vector": [-3, -46, 120, -78, -78, -39, 44, 123, 117, -128, -18, -35, -100, 123, -111, -66]} +{"id": 12303248, "vector": [-19, -119, 76, 69, 107, 12, -9, 84, -117, 86, 7, -42, 107, -22, -35, 101]} +{"id": 22766820, "vector": [20, -44, -60, 97, -24, -105, 101, 16, 51, 113, -8, -118, -127, -63, 78, -82]} +{"id": 17894977, "vector": [105, -116, -126, 70, 73, -105, -87, -123, -42, 60, -23, 33, -115, 34, -25, 111]} +{"id": 43322743, "vector": [-98, 55, -92, -100, -46, 86, -91, 59, -34, -61, 81, 79, -10, 62, -7, -68]} +{"id": 42692881, "vector": [89, 60, 12, 95, 105, 71, 93, -33, 17, 21, -32, -127, 53, -110, -89, -64]} +{"id": 17068582, "vector": [99, -1, -84, -28, 105, 93, 108, -101, -58, 46, -38, 57, -7, -1, 7, -5]} +{"id": 43357947, "vector": [51, 4, 62, 94, -70, 105, 117, 46, 30, 11, -48, -6, -52, -53, -119, -117]} +{"id": 76315420, "vector": [-1, 51, -126, 65, -67, -91, -91, 36, 53, 113, -122, 121, -105, -24, -85, -42]} +{"id": 8099994, "vector": [-40, -65, 66, 10, 104, 47, -20, -65, 25, -43, -33, -62, -29, 68, 119, 84]} +{"id": 66885828, "vector": [-48, 51, -126, 115, -101, 1, 50, -72, -40, 40, -114, -46, 52, -118, 105, -17]} +{"id": 5073754, "vector": [115, -65, -94, 58, -84, 14, -40, -34, 46, 32, -71, -6, 51, -8, 85, 35]} +{"id": 16445503, "vector": [-82, 99, 127, -70, -128, 33, -122, 36, 0, -67, -57, -79, -92, 81, -78, 100]} +{"id": 77694700, "vector": [-13, -82, 85, 7, 12, -127, -37, 29, -62, -87, 115, 72, 32, 39, 27, -83]} +{"id": 90457870, "vector": [-94, -111, -122, -67, 56, -67, -96, -27, -95, -47, 73, 111, 0, -55, 31, -2]} +{"id": 27187213, "vector": [123, -46, -7, 38, 49, 18, -90, 2, -78, -79, -27, -1, -125, 70, 42, 49]} +{"id": 51715482, "vector": [-51, 108, -90, -114, 0, 68, 57, 65, 124, 42, -55, 95, -105, 14, -70, -9]} +{"id": 22450468, "vector": [-95, 37, -105, -20, 81, -81, 79, -72, 92, 40, -20, -55, -107, 10, 45, 43]} +{"id": 93359396, "vector": [28, 54, 56, 84, -107, -61, -104, -128, -79, 76, -88, 125, 57, -7, -107, -112]} +{"id": 87160386, "vector": [-37, -102, 114, 37, 82, -49, 88, 3, 78, 46, 0, 85, -41, 102, -77, -94]} +{"id": 12348118, "vector": [-76, 24, -119, -69, 86, -24, -112, 41, -67, 109, -40, 3, 33, -26, -38, 84]} +{"id": 74724075, "vector": [35, -52, 16, -32, 53, -45, -46, -36, -93, -75, -103, -65, -70, 122, 20, -22]} +{"id": 72019362, "vector": [24, 122, 108, -14, 68, -113, 64, 40, 61, 32, 6, -118, 121, -27, 124, -118]} +{"id": 29029316, "vector": [-122, -13, 50, -22, 89, -27, -22, 52, -54, -12, 116, -75, 42, -112, -58, -23]} +{"id": 26998766, "vector": [-65, -74, 26, 35, -107, 38, 95, -34, -115, 120, -42, 63, -23, 43, -65, -122]} +{"id": 44889423, "vector": [118, 103, 120, 10, 17, 5, -11, -96, 13, -120, -113, 104, -123, 3, 30, -87]} +{"id": 78785507, "vector": [-103, 47, 108, -47, 90, -92, -123, 29, 84, -126, 46, -44, -21, 1, -47, -55]} +{"id": 84840549, "vector": [-54, 58, -109, -73, -18, -12, 102, -94, -73, 88, -11, -101, -55, 40, -77, 125]} +{"id": 44245960, "vector": [-6, -75, 26, -4, -114, 45, -94, -12, -70, 57, -94, 27, 121, 42, -72, 60]} +{"id": 49882705, "vector": [-42, -79, -105, 99, 115, 86, 1, 92, 12, -24, -61, -70, -105, 0, -91, 28]} +{"id": 47708850, "vector": [-52, -100, 0, -70, 18, -63, -25, 97, 27, -81, -95, -100, 31, 97, -80, -23]} +{"id": 68102437, "vector": [78, 78, 15, 33, -107, 83, -41, 11, 75, -36, -70, 122, 97, -44, -86, 78]} +{"id": 70367851, "vector": [70, -75, 60, -24, -50, 116, -88, 86, -31, 63, -13, -123, -2, 105, -99, -51]} +{"id": 71522968, "vector": [-4, -50, -97, -85, -125, -32, 46, 73, -62, 107, 47, 43, -26, 15, 123, -27]} +{"id": 81172706, "vector": [-1, -71, -60, -51, -121, 60, -114, -80, -20, 62, -48, -96, 121, -74, -23, -109]} +{"id": 88251446, "vector": [4, -90, -58, 30, 62, 70, -93, -120, -112, 14, 15, -75, -106, -27, -20, 43]} +{"id": 18663507, "vector": [-93, 32, -13, 17, -41, 3, 74, -38, 0, -5, -72, 17, -124, -125, 51, 13]} +{"id": 2204165, "vector": [-51, 67, -94, 98, 34, -2, -102, -17, 6, -104, 45, -35, -49, 106, 98, 113]} +{"id": 7646095, "vector": [-62, -62, 102, 84, 107, 70, 35, -75, -10, -128, -54, -2, 24, -98, 108, 45]} +{"id": 71469330, "vector": [-19, 105, 99, 118, 78, -27, 16, -96, 56, -22, 35, 69, 3, 98, -85, -15]} +{"id": 99658235, "vector": [45, 95, 14, -90, 62, 21, -79, 23, 37, 88, 36, 108, -113, -33, 1, -122]} +{"id": 37620363, "vector": [120, 33, -12, 49, 29, 69, -126, -23, -39, -72, -122, -39, -43, -63, 121, 0]} +{"id": 28928175, "vector": [-46, 54, -20, 31, 44, 0, 67, -20, -38, 43, -54, -28, 17, -95, -33, -85]} +{"id": 37501808, "vector": [-3, -5, -124, 5, -31, 77, 10, 42, -62, 5, -127, -59, -4, 103, -60, -120]} +{"id": 40865610, "vector": [108, 7, -19, 116, 57, -35, 18, -32, 95, -92, 4, 44, -36, -127, 115, 92]} +{"id": 93562257, "vector": [-98, 98, 38, -98, -58, -5, -62, 42, -45, 119, 6, -47, -54, 57, -20, -110]} +{"id": 28550822, "vector": [-80, -103, -57, 41, -77, 48, 43, -15, -12, -92, -122, 36, 126, -81, 69, -67]} +{"id": 73109997, "vector": [93, 41, 11, -102, 108, -31, 12, 53, -116, -53, -105, -45, 87, 37, 64, -84]} +{"id": 63152504, "vector": [73, -13, 92, 82, 82, -2, -96, 122, 50, -29, -78, 91, 6, 43, -118, 41]} +{"id": 36808486, "vector": [-88, -101, 101, 78, 19, -41, 45, 97, 14, 70, 35, -27, -19, 71, 15, 123]} +{"id": 6505939, "vector": [110, -99, 92, -113, 8, 107, 84, -56, 36, 55, -95, -2, -25, -38, 4, 86]} +{"id": 61623915, "vector": [-12, -94, -20, -99, 82, -76, 33, -6, 23, 10, -93, 74, -88, 24, -32, -12]} +{"id": 75153252, "vector": [87, -38, -63, 84, 124, -88, 109, 11, 25, 53, -30, 111, 31, 1, -33, -32]} +{"id": 79136082, "vector": [127, 88, -84, 119, -126, -36, 46, -60, -5, 44, 72, -106, 2, 22, -22, -26]} +{"id": 53084256, "vector": [79, -121, 71, 5, 89, -127, 26, 126, -39, -124, 29, -21, -7, 74, -53, 96]} +{"id": 51464002, "vector": [-22, -1, 4, 31, 41, 82, -91, 114, 34, 22, -88, -39, 47, -22, -88, -43]} +{"id": 75128745, "vector": [91, -31, 65, 1, -88, 71, 93, 66, -98, 41, -53, 34, -45, -24, -48, -46]} +{"id": 3487592, "vector": [-93, -69, -73, -41, 92, -100, -43, -127, 59, 44, -12, -47, 36, 126, -82, -25]} +{"id": 83747892, "vector": [-100, -63, 18, -75, -84, 31, 31, -120, 43, 11, 93, -117, -127, 113, -90, -93]} +{"id": 6808825, "vector": [91, -8, -87, 41, 52, -91, -56, -32, -84, 72, -5, -38, -39, 93, 54, 10]} +{"id": 18504197, "vector": [-65, -55, 2, -7, -21, 114, -108, 82, 67, 91, 6, -91, -118, -46, 109, 113]} +{"id": 10961421, "vector": [-48, -59, -71, -5, 22, 104, -107, 77, -65, -108, 10, 90, -116, 44, -11, 57]} +{"id": 41245325, "vector": [72, 107, -53, -5, -124, 100, -9, -79, -86, 61, 3, 25, 105, 99, 21, 39]} +{"id": 70541760, "vector": [127, -87, -24, 49, -60, -23, 34, 58, -72, 2, -43, 40, -59, 93, 88, -93]} +{"id": 34698428, "vector": [-46, -61, 66, -72, -69, 1, 9, 44, 40, -23, 61, 2, -20, 26, -73, -59]} +{"id": 11923835, "vector": [34, 112, 8, -29, -52, 81, -85, -102, -14, -85, -37, 118, 24, 28, 110, -95]} +{"id": 91141395, "vector": [67, 83, 106, -2, -87, -72, 79, 74, 56, -5, 111, -72, -81, 94, -82, -111]} +{"id": 58749, "vector": [99, 120, -50, -75, -108, 24, 118, -77, -82, -122, 70, -122, 76, -20, -82, -105]} +{"id": 67281495, "vector": [58, 70, -44, -64, -21, -41, -26, 106, 117, -40, -75, 35, 122, -127, 58, 27]} +{"id": 54762643, "vector": [-91, -7, 85, 15, 83, -9, -125, 75, 12, 58, -104, -56, 15, 37, 29, 78]} +{"id": 63372756, "vector": [-7, 112, -106, -31, -97, -37, -27, -16, 127, -62, 117, -115, -67, 34, 82, -53]} +{"id": 65271999, "vector": [31, -74, 77, 35, -38, -101, -45, -40, -44, 55, -30, -35, 107, 106, -4, -5]} +{"id": 88653118, "vector": [62, 114, -37, 7, 10, 127, 4, 97, 95, -93, 114, -59, -124, 68, 25, 7]} +{"id": 6221471, "vector": [49, -76, -101, 32, -120, 105, 42, -38, -42, -123, 3, -82, 25, -44, -89, 59]} +{"id": 23740167, "vector": [23, -87, 117, -89, -46, 99, 125, 17, 105, 30, -49, -44, -112, 76, -115, -32]} +{"id": 66611759, "vector": [86, 29, -71, 32, 89, 63, -19, -119, -41, 93, -14, -34, -17, -47, -46, -95]} +{"id": 48395186, "vector": [24, 109, 84, -95, -31, -67, 105, 127, -81, 31, 31, 82, -99, 65, 65, 2]} +{"id": 51426311, "vector": [125, -119, 83, -100, 55, -50, 54, 112, 78, 26, -29, 15, -23, 90, 61, 66]} +{"id": 55960386, "vector": [-30, -104, 76, -4, 81, -100, -10, 62, -32, -116, 89, -35, 101, -47, -24, -61]} +{"id": 62357986, "vector": [-108, -66, 51, 103, -2, -15, 44, 83, -33, 14, -99, -2, -123, 74, -47, 123]} +{"id": 7104732, "vector": [-108, -78, -71, -37, 59, -102, 29, 91, 105, 54, 30, 86, 74, 104, 39, 26]} +{"id": 99965001, "vector": [-16, 52, 34, 73, 36, -69, -55, -22, -4, -42, -37, -16, -70, -86, -1, 121]} +{"id": 18783702, "vector": [-58, 25, -43, 3, -52, -13, -53, 10, -17, 103, 111, 82, -30, 97, 115, 63]} +{"id": 58224549, "vector": [-99, 58, 115, -45, -6, -121, 88, -63, -122, -55, 4, -11, -62, -12, 39, -127]} +{"id": 7066775, "vector": [-99, -8, -121, 23, 10, -124, -123, -92, -96, 111, 52, 94, -99, -82, -118, -105]} +{"id": 8055981, "vector": [-41, -32, 4, 34, -91, 13, -114, 76, 37, -10, 119, 80, -4, -101, -8, -120]} +{"id": 73235980, "vector": [-88, 56, -92, 76, 39, -124, -67, -110, 37, 106, -114, -39, -77, -10, -69, -98]} +{"id": 86118021, "vector": [-26, -26, -18, 49, 62, 2, 55, -74, -115, 18, 34, -40, -14, -95, 16, 126]} +{"id": 50007421, "vector": [67, 31, 117, -39, -52, -126, 117, 79, -27, 97, -15, 110, -37, -27, 66, -85]} +{"id": 9623492, "vector": [115, -38, -121, -106, 117, -87, 32, -115, -111, -27, 27, -99, -58, -84, 56, 11]} +{"id": 4099191, "vector": [-72, -86, 30, -12, -79, 86, -93, 46, -2, -9, 35, 116, -30, -56, 23, -116]} +{"id": 56461322, "vector": [-69, 54, -51, -55, 65, 65, -1, 46, 61, 3, -71, 7, -81, -64, -29, 97]} +{"id": 56473732, "vector": [84, 4, 100, -100, -45, -86, 65, -117, -4, -105, -56, 26, -51, 6, 42, 69]} +{"id": 44664587, "vector": [45, -69, -64, 49, 93, 62, -37, -83, -109, -21, 63, 117, 50, -93, 49, 113]} +{"id": 45237957, "vector": [-102, 70, -76, -7, 76, -100, -98, -128, 52, -89, -98, 117, 107, -110, -38, 114]} +{"id": 47090124, "vector": [26, -87, 86, -72, 60, -108, -41, 121, -44, -58, -40, 23, -52, 104, 50, -12]} +{"id": 7011964, "vector": [-66, -19, 24, -67, 41, -14, -73, 31, -14, -92, -1, -128, -108, 108, -18, -22]} +{"id": 33699435, "vector": [-11, -18, 66, 109, 110, -110, -61, -52, 56, 7, 44, 73, 16, 105, 9, 24]} +{"id": 25636669, "vector": [85, 117, 15, 22, 51, 78, -125, -86, -113, -63, -19, 81, -121, -67, -121, -28]} +{"id": 8791066, "vector": [-50, 70, 94, 106, -123, 118, -127, 59, -124, 121, 51, -4, -30, 56, 39, -64]} +{"id": 4508700, "vector": [-34, 100, 18, 43, -59, -35, 94, -40, 7, 106, 22, -8, 28, -45, 3, 47]} +{"id": 93270084, "vector": [-77, -91, 91, 98, 23, -80, -96, 6, -23, 9, -109, 97, 106, -82, 54, 125]} +{"id": 3088684, "vector": [103, 38, 47, -31, 126, 21, -21, -43, 84, 122, 99, -36, -51, -109, -81, 36]} +{"id": 17146629, "vector": [-124, 69, 64, 68, -23, 76, 72, -10, -5, 108, -58, 76, -33, 57, -124, 109]} +{"id": 99297164, "vector": [-19, -25, -62, -3, 110, -125, -104, -38, -94, 54, -125, 127, 26, 0, 24, -106]} +{"id": 41442762, "vector": [-72, 87, -58, 118, -33, -2, -108, 19, -46, -103, 67, -85, -46, -94, 28, -109]} +{"id": 66250369, "vector": [-124, 90, -112, 104, 72, -32, 67, 15, 110, 4, 34, -96, -14, -48, 39, 77]} +{"id": 58751351, "vector": [-78, 19, -63, 116, 93, -91, 103, -61, -101, 18, 113, 57, -56, -81, 28, 100]} +{"id": 66667729, "vector": [117, 39, -87, 96, -71, -77, 35, 35, -109, -37, 68, -65, 10, -15, -83, 61]} +{"id": 36312813, "vector": [118, 107, 121, 36, -33, 13, 71, 6, -18, 90, 72, -48, 99, -60, -81, 113]} +{"id": 39373729, "vector": [-63, -93, 125, 60, 7, 77, 114, 100, -122, 50, -15, -71, -102, -62, 50, -74]} +{"id": 68702632, "vector": [123, -25, -53, 26, 101, 117, -63, 78, 18, -105, 30, 10, -71, 26, -108, -42]} +{"id": 34044787, "vector": [-18, 49, 30, -13, -13, 46, -9, 69, 38, -106, 95, -19, 65, -39, -66, -98]} +{"id": 33237508, "vector": [-17, -4, 93, 57, 30, 127, -97, 6, -71, 65, -67, 0, -105, -12, -14, -88]} +{"id": 95581843, "vector": [-23, -73, 75, -57, -17, 92, 82, -41, -14, 116, 41, 33, 67, -80, -28, 122]} +{"id": 89811711, "vector": [-20, -99, -92, -30, -83, -34, -75, -56, 12, 39, -76, 95, 92, 25, -26, 111]} +{"id": 22113133, "vector": [59, -10, -84, -123, 57, -43, 11, 84, 37, 29, 69, 49, -93, -38, -7, -26]} +{"id": 68644627, "vector": [-4, -8, 48, -4, 26, 73, 32, -114, -69, -57, 82, 3, 26, 21, 105, 99]} +{"id": 59329511, "vector": [27, 41, 20, 38, 111, 106, 65, 52, 34, 31, 103, 1, -33, -64, -75, 9]} +{"id": 28787861, "vector": [-17, -68, 36, 105, 35, -14, -6, 16, 104, -118, -23, -58, 60, 100, -112, -124]} +{"id": 28796059, "vector": [49, -6, 121, 79, -22, 55, 98, 99, 57, -11, -122, 52, -119, 106, -76, -40]} +{"id": 71316369, "vector": [-107, 74, -101, 72, -42, 0, -72, 48, -31, 104, -120, -127, 35, 23, -109, -92]} +{"id": 5970607, "vector": [66, 16, -59, -119, -51, 37, 88, -86, -121, 71, 55, 40, 118, -23, -83, 118]} +{"id": 77787724, "vector": [99, -16, 67, 53, 124, 47, -56, 78, 19, -45, -80, 96, -44, -17, 4, 37]} +{"id": 54199160, "vector": [31, -93, -67, -103, -114, 69, 3, -24, -5, -54, 79, 41, 6, -51, -127, 49]} +{"id": 33895336, "vector": [56, -32, -1, 15, 120, -8, -79, 24, 112, 45, 99, 15, 8, 66, 68, -97]} +{"id": 3880712, "vector": [-107, -86, 84, 47, -46, -50, 18, 51, 32, 83, -80, -119, 8, 27, 74, -60]} +{"id": 38022834, "vector": [39, 113, -124, 14, -98, 92, -61, -49, 65, -8, -44, -108, -31, -83, -57, 65]} +{"id": 95957797, "vector": [-26, -75, 74, -84, 54, 66, 54, -51, -103, 97, 47, -118, -56, 127, 25, 12]} +{"id": 79657802, "vector": [54, -26, -12, 111, 39, -19, 47, -5, -116, -105, 122, -124, -58, 18, 115, -22]} +{"id": 53842979, "vector": [-64, -102, 125, -68, 62, -125, 30, -45, -8, 124, -103, -98, 31, 12, 63, 79]} +{"id": 71333116, "vector": [-108, -104, -107, 30, 102, 60, 10, 80, -61, 116, -60, 109, -50, -52, -35, -99]} +{"id": 26776922, "vector": [-9, 100, 14, -96, -75, 12, -73, -72, 32, -78, -1, 33, 31, 19, -128, -27]} +{"id": 66045587, "vector": [-55, -75, -105, 1, -10, -122, -13, -81, -25, 88, 106, -49, 80, -62, 123, 32]} +{"id": 29466406, "vector": [31, 95, -22, 118, 101, 106, -87, -80, 32, 84, -15, -115, -15, -12, -127, 97]} +{"id": 29834512, "vector": [-89, 23, 74, 68, 32, -10, -75, -8, 88, -55, 41, 45, -96, -38, 6, 90]} +{"id": 30998561, "vector": [-48, -115, -14, -63, 38, -127, -3, -122, 78, -3, 75, -86, -125, 93, -6, -52]} +{"id": 16971929, "vector": [22, -33, -81, 82, 113, 54, -18, 54, -20, -6, 75, 61, 21, 33, 86, -72]} +{"id": 49328608, "vector": [-6, -49, -2, 10, 109, 78, -73, 81, 35, -111, -96, -61, -74, -99, 65, -10]} +{"id": 96735716, "vector": [-79, 43, 84, 1, -71, -33, 60, -62, 66, 6, -1, -41, 83, -4, 93, 70]} +{"id": 508198, "vector": [59, -99, 29, -14, 36, -101, -5, 109, 75, -65, -71, 93, -112, 122, 121, -52]} +{"id": 52204879, "vector": [-123, -18, 92, 25, 50, 60, -53, 13, 34, 93, 114, 3, 43, -5, 112, -103]} +{"id": 82427263, "vector": [-127, -6, 19, -8, 117, 57, 36, -85, -77, -98, 4, 4, -50, 36, -51, -128]} +{"id": 2208785, "vector": [-15, 82, -89, -121, 40, 73, 96, -33, 74, -41, -10, 47, 28, 85, 27, -101]} +{"id": 27041967, "vector": [118, -116, 32, -33, -114, -53, 6, -81, 89, -33, -123, -95, 123, -35, -123, 124]} +{"id": 4515343, "vector": [3, 106, 82, -127, 126, -29, 17, 104, -113, -51, 120, 94, -64, -119, -91, -41]} +{"id": 18131876, "vector": [-113, 3, -29, 93, -119, -91, 18, -27, 16, 61, 93, 61, 54, -90, 32, 53]} +{"id": 2607799, "vector": [2, -47, -58, 7, 100, 90, -79, -26, -62, -55, 47, -10, -128, -68, -5, -42]} +{"id": 42782652, "vector": [70, -42, -75, 87, -50, -102, -104, 70, -28, -65, -109, 20, -12, -109, 103, -113]} +{"id": 43376279, "vector": [-110, -21, -43, 124, 16, -127, 33, -30, -126, -35, -44, -122, -12, -36, 18, 70]} +{"id": 96420429, "vector": [42, 80, 5, -85, -67, 101, 11, -108, -69, 38, -9, -97, -96, 31, 86, -59]} +{"id": 74357852, "vector": [19, -41, -37, 105, -111, -127, -86, 62, -108, 96, -56, -24, 66, 58, -113, 83]} +{"id": 91664334, "vector": [-119, 89, -96, -125, -104, -95, 93, 20, -117, -96, 66, -108, 68, 22, -60, 116]} +{"id": 96726697, "vector": [107, -112, -31, 3, 29, 98, -9, 43, 23, 35, 22, 119, -104, -109, 34, -25]} +{"id": 60430369, "vector": [117, -113, -10, -51, 72, -57, -88, 47, -43, 64, 54, 88, -96, -107, -22, 47]} +{"id": 91802888, "vector": [1, 81, -99, -89, 51, 63, 21, -101, -73, -96, 86, 19, 53, 59, 101, 52]} +{"id": 57248122, "vector": [37, -36, -76, 104, 120, 79, 22, -34, -13, -17, 29, 45, 10, 66, 93, 90]} +{"id": 79922758, "vector": [-103, -94, 15, 125, 49, 70, 2, 53, -101, -34, -112, 22, 102, 63, -45, -107]} +{"id": 55602660, "vector": [-128, -115, 73, -24, -57, 95, -11, 62, -17, -69, 81, -97, -17, -96, -99, -95]} +{"id": 7229550, "vector": [41, -99, -70, -117, -18, 2, -86, 36, 67, -89, -100, 37, 53, -83, -114, -101]} +{"id": 78766358, "vector": [-71, 83, -103, -94, 104, -32, -125, -118, 99, -120, 109, -54, 98, 64, 29, 91]} +{"id": 7182642, "vector": [-18, -96, 39, 59, -59, -6, 126, -32, 4, -61, 2, -10, 7, 86, 77, -56]} +{"id": 6038457, "vector": [-53, 8, 108, 5, 57, 15, -83, -39, 30, 106, 64, -80, -41, -37, -49, -111]} +{"id": 31126490, "vector": [121, 117, 91, -77, 56, -22, -21, -110, 65, -6, -60, 32, 102, -10, 120, -104]} +{"id": 88047921, "vector": [31, -58, -75, 8, 3, -114, -2, -85, 56, -11, -95, -96, -110, 66, 31, -63]} +{"id": 3183975, "vector": [-26, 0, 8, -17, 77, 121, 73, 12, -99, -90, 71, -104, 7, 72, -7, 91]} +{"id": 62115552, "vector": [57, -63, 118, -27, 43, -87, -49, -85, -118, 62, 120, 19, 14, 20, -106, -62]} +{"id": 85711894, "vector": [24, -110, 98, 56, -76, 94, -17, 58, -43, -4, 40, 13, 95, 104, 76, 103]} +{"id": 14045383, "vector": [36, -66, 69, -19, -17, 119, 25, 11, 84, 34, -35, 68, -22, 75, -102, -73]} +{"id": 78602717, "vector": [105, -95, 71, -38, 70, 69, 65, -82, 57, -121, -38, 65, -62, -13, 98, 74]} +{"id": 81805959, "vector": [-74, 123, -110, 59, 100, -10, -63, 16, 113, 112, -50, -57, 116, 41, 2, -24]} +{"id": 26114953, "vector": [48, -96, -19, 55, -110, -86, 6, -72, 83, 102, -76, 42, -111, -91, -8, 10]} +{"id": 55143724, "vector": [-87, -116, -77, -12, 7, -115, 77, 106, -10, -25, -111, 114, 71, -42, -116, -95]} +{"id": 78549759, "vector": [-115, 68, -60, -116, 7, 67, -90, -105, 89, 90, -128, -82, 58, 5, -122, 7]} +{"id": 16054533, "vector": [-57, 106, -19, -112, 74, -46, 66, -14, 107, -105, -92, -14, -67, -46, 80, 69]} +{"id": 74137926, "vector": [-103, -51, -44, -79, 50, 87, 80, -91, -41, -31, -22, -24, -122, -76, -93, -67]} +{"id": 23569917, "vector": [4, -22, -20, 75, -105, -28, -61, 31, -93, 108, -111, 46, -21, 61, 89, -59]} +{"id": 33628349, "vector": [7, -4, -78, -106, 74, 81, -13, -43, -70, 10, 34, 3, 75, 2, 76, -58]} +{"id": 59910624, "vector": [-21, -2, 54, -56, 66, -81, -42, -9, -4, 95, -29, -21, 103, 51, 64, 28]} +{"id": 97379790, "vector": [-60, -34, -89, -28, 80, 2, -3, 6, 100, 1, 89, -31, -103, -42, 117, -61]} +{"id": 36135, "vector": [-107, -90, 70, -6, 79, 8, 92, 103, 6, -70, 34, 94, 36, 65, 92, 43]} +{"id": 16424199, "vector": [31, 103, -51, -64, 67, 4, 35, -119, 124, 104, 91, 120, 62, -79, -36, 96]} +{"id": 30694952, "vector": [82, 3, 33, 68, -86, -76, -44, -62, -115, 58, 90, 26, 120, -98, 44, -34]} +{"id": 49641577, "vector": [40, 48, -94, 126, -65, -83, 94, 75, -116, 44, 79, -27, -93, -17, -80, -76]} +{"id": 41481685, "vector": [-105, 72, -28, 0, 93, 55, -94, -52, -8, 107, -71, 19, -74, 41, 78, -30]} +{"id": 32058615, "vector": [124, -68, 15, 67, 45, -49, 111, 82, -40, 8, 9, 106, -6, 103, -45, -104]} +{"id": 81774825, "vector": [-104, 114, 59, -83, 108, 59, 47, 51, 93, -111, 111, -60, 70, -64, 100, 6]} +{"id": 68316156, "vector": [-31, 51, -70, -25, -92, -87, -121, -42, -7, 105, 111, 7, 73, 74, -64, -93]} +{"id": 7517032, "vector": [89, -106, 65, -14, 34, 108, -126, -115, -21, -81, 95, -3, 15, -68, 19, 95]} +{"id": 77898274, "vector": [-22, -98, 44, 6, -61, 103, -56, 104, -126, -37, 28, 113, -56, -50, 80, -2]} +{"id": 51315460, "vector": [67, -43, -20, 119, 24, 57, 85, 29, 0, -114, 2, 98, 126, -120, 109, -57]} +{"id": 75052463, "vector": [16, -93, 50, 16, 20, 120, -106, 86, 51, -77, 1, 55, 32, 14, -95, 0]} +{"id": 9259676, "vector": [77, -35, -77, -95, 89, -128, -108, 120, 71, 4, 56, -61, -105, 10, 120, -24]} +{"id": 20568363, "vector": [5, -11, 80, -18, 96, -83, -28, -79, -74, 25, -41, -71, -94, -25, 12, -40]} +{"id": 61859143, "vector": [-14, 56, 44, 110, 90, -86, -92, 115, 111, 53, -41, -97, 92, 42, 16, 63]} +{"id": 38494874, "vector": [6, -96, -58, -66, -107, 111, 10, -40, -4, -60, 2, 47, 99, 61, -110, -78]} +{"id": 8651647, "vector": [121, -68, -22, 0, 117, 98, 20, 104, -42, -1, 85, -16, -26, -84, -95, 90]} +{"id": 98943869, "vector": [-61, 81, -46, 31, -120, 53, 102, 36, -93, 31, 25, -117, 73, -70, 72, -56]} +{"id": 1936762, "vector": [-15, -16, 0, -49, -125, -86, 90, 68, -61, -73, -33, -91, -78, -59, -49, -112]} +{"id": 52060076, "vector": [0, 53, 65, 127, 63, 111, 89, -110, -96, 106, 30, 121, 120, 52, 9, -30]} +{"id": 57359924, "vector": [-83, -107, 80, -15, -76, -42, -32, -16, 111, 103, 46, -8, 85, 42, 70, -16]} +{"id": 90004325, "vector": [-38, -48, 40, -51, 95, -100, -2, -91, 68, -14, 58, -124, 69, 119, 111, -102]} +{"id": 82651278, "vector": [-117, -122, 44, 33, -117, -101, -114, 107, 25, 19, 19, 62, -20, -32, -69, -121]} +{"id": 4091162, "vector": [-30, -111, 36, 25, 25, -110, -65, -71, 94, -60, 65, 102, 18, -10, 46, 65]} +{"id": 66903004, "vector": [33, 71, -106, 46, -86, -108, -63, -81, 40, -117, -40, 95, 95, -31, -127, 52]} +{"id": 20122224, "vector": [-96, -49, 28, -55, 95, -33, -12, 13, 82, -119, -55, 30, 54, 38, -46, 1]} +{"id": 9860968, "vector": [-93, 74, -20, 70, -11, -52, -11, -79, 56, 118, -112, -84, 13, 62, -114, 3]} +{"id": 24915585, "vector": [-127, 10, 57, 62, -57, 46, -33, 70, -125, 89, -109, -72, -127, 99, -109, 28]} +{"id": 40781449, "vector": [-99, 75, -65, -114, 9, -124, -47, 106, 0, -6, 113, 52, 90, -49, -94, 123]} +{"id": 90654836, "vector": [41, -20, 13, 104, 127, 11, -69, 111, 99, -7, 48, -98, 44, 69, 6, -77]} +{"id": 30811010, "vector": [-119, 87, 56, 91, -82, -35, 34, -69, -26, -102, -59, -123, -104, 7, 81, -18]} +{"id": 91831487, "vector": [30, -14, -106, 35, 63, 22, -24, 127, -127, -40, 30, -69, -3, -66, -7, -61]} +{"id": 54232247, "vector": [28, 0, 69, 116, 112, 7, -49, -2, -92, 96, -94, 68, 124, 83, 126, -97]} +{"id": 81274566, "vector": [89, -126, -88, 124, -63, -118, -35, -22, -59, 86, -120, 107, -29, 76, 2, -1]} +{"id": 68816413, "vector": [-100, 124, -58, 81, -53, 81, 27, -45, -9, -115, 88, -17, -13, -75, 64, -48]} +{"id": 28851716, "vector": [-72, -57, 62, 27, -114, 85, 69, 66, 76, 110, -85, 46, 13, 40, 27, -85]} +{"id": 27625735, "vector": [-115, 67, -105, -41, -78, -23, -40, -82, 19, -61, -19, -54, 71, -85, 31, 32]} +{"id": 72732205, "vector": [23, 95, 26, -127, 76, 44, 72, -83, -120, 8, 73, -7, -5, 120, -93, 115]} +{"id": 85023028, "vector": [-80, -62, -55, -126, -22, 106, -50, -113, 111, 106, 39, -17, -32, 82, -79, -68]} +{"id": 37659250, "vector": [-69, -109, -4, 111, -25, 23, -53, -30, -95, 96, -66, 68, 55, -2, 32, 100]} +{"id": 4119747, "vector": [-106, 43, 50, -128, 18, -99, -107, -54, -66, -26, 54, 77, 59, 104, -119, 82]} +{"id": 60106217, "vector": [-63, -87, 41, 125, -27, 106, 110, 16, -26, 121, 21, -89, 101, 106, -79, 20]} +{"id": 89419466, "vector": [-124, -13, 33, 29, 111, 59, 50, 25, -124, -72, -28, 120, -57, 55, 50, -3]} +{"id": 92692978, "vector": [-23, -59, -43, 51, -58, -120, -8, 53, 19, -116, -102, 68, 112, -2, -118, 3]} +{"id": 59981773, "vector": [61, 108, 87, 17, -89, 2, -85, -99, 106, 104, -46, 90, 36, 124, -116, 111]} +{"id": 69355476, "vector": [-101, 42, 48, 13, -79, -94, 48, 124, -104, 82, 46, 27, 29, -105, 64, 37]} +{"id": 71920426, "vector": [-38, -128, 125, 6, -120, -55, -38, -86, 18, 34, -87, 89, 39, -32, -49, -41]} +{"id": 42947632, "vector": [63, -87, 73, 56, 9, -80, -122, -14, -119, -42, 57, 22, 11, 106, -125, -3]} +{"id": 74110882, "vector": [51, -64, 107, -56, 15, -51, -59, -62, 110, 91, -77, 59, -111, -125, 86, 126]} +{"id": 82532312, "vector": [28, -59, 126, 56, 68, -95, -116, -127, 113, -66, -98, 4, 1, -95, -15, -9]} +{"id": 27665211, "vector": [-112, -12, -11, -104, 14, -90, -64, -128, 92, 32, -56, -108, -122, 41, -101, 85]} +{"id": 14093520, "vector": [32, -64, 42, 59, -110, -110, -102, -105, -27, -61, -87, 56, 84, -87, 34, -78]} +{"id": 47792865, "vector": [-66, 28, 69, 112, 28, -72, -77, 31, -109, 27, -38, 120, 80, -43, -80, 71]} +{"id": 55470718, "vector": [118, 44, -62, 33, -75, 22, -57, -44, 103, -109, 4, -89, -74, 17, -52, -123]} +{"id": 75229462, "vector": [36, 18, -30, -22, 125, -27, 49, 52, -9, 88, 71, -82, 61, -38, -14, -27]} +{"id": 79806380, "vector": [-2, -18, -22, -37, 98, 74, 120, 76, 59, 42, 93, -99, 94, -115, -70, -48]} +{"id": 24314885, "vector": [-112, 125, -67, -43, 125, 10, -26, 2, 81, 50, 58, 120, -31, 63, -101, 75]} +{"id": 18699206, "vector": [-28, -74, 80, -72, 20, -64, 54, 42, 96, 117, -51, 4, -62, -45, -104, 24]} +{"id": 33935899, "vector": [54, 9, 36, 48, -10, -50, 60, 86, 47, 32, -121, -1, 17, 91, 67, -39]} +{"id": 14326617, "vector": [-114, -62, 102, -13, 73, 5, -98, 41, 18, 127, 12, 19, -109, 53, -40, 42]} +{"id": 83150534, "vector": [89, -23, -37, -78, 9, 36, -61, -1, 46, 112, -54, -110, 52, -22, -89, 127]} +{"id": 91938191, "vector": [-42, -21, 58, -32, 75, 122, -66, -107, -69, -60, 20, -125, -10, -34, -40, -108]} +{"id": 49271185, "vector": [-1, 29, 70, -45, 124, 96, -125, 108, 47, 25, -39, 113, 12, -39, 90, 99]} +{"id": 7687278, "vector": [22, -93, 122, 66, -8, -120, 61, 5, -5, 106, 51, -18, 2, -67, -112, -61]} +{"id": 1204161, "vector": [-60, -110, -98, 105, -15, -45, 22, 56, 125, 93, -42, 32, -92, 88, -61, 6]} +{"id": 65047700, "vector": [-42, 99, 27, -105, -38, -63, -65, 58, -99, 98, -124, 24, 25, 24, 100, -36]} +{"id": 21919959, "vector": [-3, 49, -88, -63, 11, 55, 113, 98, -22, 102, -117, -82, 79, -56, 97, -95]} +{"id": 19272365, "vector": [102, 89, 31, 47, 73, -17, 11, 41, -85, 72, 91, 38, 101, -43, 41, 47]} +{"id": 86798033, "vector": [120, -84, 50, 121, -105, -114, -40, -58, 19, 46, -65, 39, -96, 16, 79, -92]} +{"id": 66428795, "vector": [-55, -95, 109, 115, 55, 106, 113, -31, 86, -48, 3, 102, -61, 108, -45, -66]} +{"id": 92033260, "vector": [-108, 99, -75, -57, -11, -105, -9, -9, 61, -78, 86, -97, 127, -59, -43, -38]} +{"id": 65074535, "vector": [-91, 126, 89, -32, -91, 10, -31, 19, 74, 124, 43, 83, -73, -68, -127, -108]} +{"id": 63015256, "vector": [-115, -115, 57, -50, -35, -69, -117, 32, 56, -99, -40, 33, 51, 5, 118, 104]} +{"id": 76960303, "vector": [121, 123, -3, -41, -71, -10, -19, -83, 29, -6, 76, -81, 34, 64, -78, -121]} +{"id": 14363867, "vector": [46, 114, -73, -103, -99, -46, -82, 56, 82, 65, -34, 117, -98, -86, -5, 107]} +{"id": 4069912, "vector": [-71, 85, -123, 14, -34, -43, -99, -80, -111, -128, 76, 43, 56, 5, 6, 6]} +{"id": 95290172, "vector": [10, 92, -99, -20, 76, -49, -121, 24, 5, -58, 117, -111, -96, -82, -38, 99]} +{"id": 70782102, "vector": [63, -127, 96, 52, 119, 67, -43, 19, -115, 56, -8, 106, 124, -21, 74, -122]} +{"id": 61271144, "vector": [-93, 116, 121, 24, -46, -36, 101, 20, 93, 109, 32, -16, -68, 117, -70, 91]} +{"id": 95010552, "vector": [111, -77, 56, 60, -34, -69, -95, -44, -111, 101, 64, -97, -113, 37, 124, -123]} +{"id": 36933038, "vector": [101, 98, -28, 101, -35, -99, 32, 1, -61, -27, 106, -123, 102, -12, 107, 31]} +{"id": 36468541, "vector": [-95, 44, 44, -122, -32, 117, 62, -17, -68, -118, -123, -35, -35, -119, 38, -47]} +{"id": 16684829, "vector": [-75, -29, -126, -105, 42, 63, -94, -123, 51, 111, 101, -69, -12, 88, 118, 14]} +{"id": 50188404, "vector": [90, 12, 43, 85, -66, 27, 62, -33, -20, 39, -48, 27, -82, -40, -84, -26]} +{"id": 88444207, "vector": [-70, -101, -67, -2, -81, -114, -83, 16, 71, 45, 66, 8, 112, 33, 6, 102]} +{"id": 44479073, "vector": [77, -77, -121, -56, -10, -42, -48, -5, -56, -86, -125, -51, 48, -102, -27, 79]} +{"id": 56153345, "vector": [-43, -61, -63, 6, -76, 27, 88, -51, -40, -85, -122, 91, -76, -17, -61, 68]} +{"id": 1788101, "vector": [123, 43, -45, 7, -73, 66, -60, -104, -2, -37, -84, 72, -54, 22, 102, 67]} +{"id": 99021067, "vector": [111, -58, 69, 91, -29, -9, -3, 11, 60, 31, -46, -62, -52, 103, 0, -75]} +{"id": 26397786, "vector": [124, -77, 48, -41, -30, -89, 72, 22, -83, -15, 89, -48, -103, -64, -53, 19]} +{"id": 87720882, "vector": [109, 74, 113, -58, 102, 14, -16, 10, -103, 27, -7, -13, 80, 61, 46, -126]} +{"id": 26392416, "vector": [10, 28, -44, -39, -25, -64, 48, 100, 99, 30, 69, -107, -66, -58, 77, -60]} +{"id": 67084351, "vector": [114, 116, 39, 2, -68, 115, 27, -33, -100, -36, -111, 82, -25, -106, -108, 67]} +{"id": 50668599, "vector": [23, 121, -55, 115, 126, 37, 26, -27, -81, 53, 17, 63, 118, -74, -104, -84]} +{"id": 68204242, "vector": [-41, 76, 109, 4, 82, -24, -42, 40, -77, 71, 1, 9, 80, -99, 68, 114]} +{"id": 42237907, "vector": [-95, -116, -74, -61, 43, -5, 38, 77, 44, -117, -38, -108, -27, 62, -58, 1]} +{"id": 23194618, "vector": [-53, -104, 22, 12, 79, 4, -11, -72, -73, 54, -110, -38, -107, 87, 88, 76]} +{"id": 29401781, "vector": [-123, -74, 104, 14, 24, 93, -66, -29, 80, 25, 91, -121, -127, 26, -114, 26]} +{"id": 36580610, "vector": [22, -60, 94, 69, 119, -11, -18, 105, -17, 39, 22, 5, -81, 108, -43, -84]} +{"id": 72777973, "vector": [-8, 123, 103, -62, -89, 15, -114, -108, -113, 15, 102, 123, -53, -64, 4, 43]} +{"id": 72238278, "vector": [-22, 117, -59, 29, -120, -89, -61, -68, 85, 63, -7, -31, 5, -81, -49, 120]} +{"id": 9257405, "vector": [109, -23, -32, 120, -91, 54, -42, 26, -58, 2, 113, -41, 40, 118, 45, 127]} +{"id": 91990218, "vector": [-21, -20, 0, -91, 107, 82, 61, 0, -52, 105, 81, -104, -69, -91, -70, -28]} +{"id": 72373496, "vector": [-1, 32, 45, 22, 0, 6, 98, -55, 27, 13, -53, -120, 94, -39, -108, 77]} +{"id": 85571389, "vector": [-123, -98, -85, 80, -123, 71, 53, -63, -43, 80, -49, 21, -115, 48, -4, 73]} +{"id": 29994197, "vector": [100, -102, -4, 115, -111, 27, -116, 62, 126, -61, 63, -48, 52, -12, 67, 79]} +{"id": 57393458, "vector": [112, 56, -124, -20, 35, -98, 73, -34, -105, -59, 52, -53, -88, -103, -53, -112]} +{"id": 39201414, "vector": [45, 92, -101, -10, 61, 89, 110, -3, 59, -50, 14, 57, 101, -72, 108, -15]} +{"id": 30366150, "vector": [-27, 106, 30, -77, -84, 17, 3, 95, 100, -18, 101, 122, -63, -126, 21, 37]} +{"id": 73786944, "vector": [124, 116, -76, 38, 105, -50, -5, 107, 66, -39, 22, 37, -54, 87, -13, -38]} +{"id": 54014062, "vector": [56, -17, -87, 27, 69, 29, -13, -30, -12, 33, -79, -28, -64, -29, 24, -84]} +{"id": 13862149, "vector": [-124, 14, 52, 12, 12, 21, 106, 79, 30, 66, 55, 38, -90, -45, 118, 44]} +{"id": 40356877, "vector": [74, -88, -117, 2, -30, -29, 108, 43, 21, 49, -64, 14, -63, -122, -114, 55]} +{"id": 45919976, "vector": [-30, -101, -5, -65, -117, 90, -84, -75, 97, -13, -86, -40, 92, 21, 73, 121]} +{"id": 10264691, "vector": [64, 110, -90, -81, 93, -125, 94, 60, -62, 90, -63, 82, 54, 15, 78, -66]} +{"id": 10358899, "vector": [103, 53, 74, 118, 102, -50, 111, 79, -104, -24, 7, -48, 125, -98, 26, 103]} +{"id": 16567550, "vector": [-25, 40, -102, -8, 64, 85, -122, 107, -79, -59, 124, -122, -27, 31, -47, 24]} +{"id": 63628376, "vector": [57, 45, -126, -68, 28, 24, -116, -51, 93, 126, -105, 22, -78, 119, 1, -3]} +{"id": 95726235, "vector": [58, -119, -5, -79, -79, -48, -108, 70, 93, -83, 106, 7, -73, -80, 99, -2]} +{"id": 51466049, "vector": [-5, 45, -123, -91, 108, -71, 115, 10, -61, -42, -108, 13, -23, 43, -44, -24]} +{"id": 18806856, "vector": [25, 50, -97, -107, -10, 14, 24, -58, 109, -61, 65, -107, 5, 124, 51, -123]} +{"id": 84406788, "vector": [-126, 119, -108, -88, -85, 10, -27, -68, 73, -55, -40, -72, 17, -73, -91, -32]} +{"id": 83302115, "vector": [-110, 66, -15, 117, 84, -67, -89, -96, -64, -26, 111, -38, 96, -115, -59, -83]} +{"id": 94711842, "vector": [123, -109, 121, -2, 24, 58, 124, -107, -2, 115, 62, 12, -80, 67, -107, -114]} +{"id": 12024238, "vector": [-70, -113, 19, -19, 80, -96, -3, 102, 63, 102, -116, -98, 40, 112, -82, 24]} +{"id": 49236559, "vector": [-21, -119, 36, -126, 80, 100, 51, -94, -100, -21, -77, 36, -34, -80, -51, 22]} +{"id": 60393039, "vector": [72, 77, -79, 51, 55, -121, -64, 102, 4, 115, 56, -61, -16, -102, 14, -62]} +{"id": 8733068, "vector": [-48, -21, -114, 66, -32, -56, 102, -123, 26, 123, 79, -127, 72, 115, -111, -61]} +{"id": 37280276, "vector": [37, -66, 14, -121, 63, 101, -28, 64, 45, -99, 85, 10, 111, -77, 115, 82]} +{"id": 22200378, "vector": [93, -58, -29, -19, -97, 31, -49, 59, 15, 79, 114, -10, 32, -90, 70, 53]} +{"id": 64848072, "vector": [68, 101, -95, 60, -107, 12, 53, -38, -110, 112, -102, 117, -9, -93, 95, 62]} +{"id": 47298834, "vector": [100, -128, 112, -94, -25, -28, -33, -63, -31, 120, -51, 126, -79, 69, -124, -58]} +{"id": 31491938, "vector": [127, -115, -82, -86, -60, -68, 90, 101, 96, 27, -98, -25, 101, 92, 72, -8]} +{"id": 9829782, "vector": [-66, -72, 3, 78, -95, -127, 49, -40, 101, 125, 60, 64, 12, -113, 74, -16]} +{"id": 1375023, "vector": [-125, 126, 102, 62, 62, 99, -70, -59, -79, -20, 100, -15, -33, 21, -72, -77]} +{"id": 47893286, "vector": [117, -35, 12, 117, 106, 2, 13, -109, 88, 97, -117, 55, -5, -26, -67, -117]} +{"id": 749283, "vector": [13, -12, -15, -71, 28, 107, 17, -84, 50, 57, -95, 106, 18, -46, 46, 11]} +{"id": 99524975, "vector": [-25, 17, 66, 24, -67, -8, 118, 78, -117, -80, 121, 15, 121, -112, 71, 112]} +{"id": 43152977, "vector": [32, -49, 6, -103, 104, -82, 52, -27, 111, -22, -75, -29, -70, -97, 99, -71]} +{"id": 51472275, "vector": [-19, 32, -57, -49, -16, 25, 117, -107, -95, -57, 54, 72, -52, -68, -101, -85]} +{"id": 36189527, "vector": [20, 127, -15, 38, 81, 0, 76, 27, 105, 73, -123, -124, 0, 53, -23, 76]} +{"id": 48675329, "vector": [-115, 5, -81, 36, 37, 125, 83, 93, -28, 2, -96, -88, 46, -109, -21, -53]} +{"id": 56484900, "vector": [7, -45, -5, -105, 85, 22, -57, 77, 14, 45, -71, -82, 41, 39, 54, -107]} +{"id": 70036438, "vector": [121, -48, -54, -34, 81, -34, 54, 90, 102, -53, -17, 20, -20, -56, -49, -18]} +{"id": 37435892, "vector": [-128, 101, -82, -22, -17, -99, -124, -74, 117, -76, -66, -4, 34, 57, -74, 12]} +{"id": 55247455, "vector": [-118, -38, 2, 53, 25, -87, 68, -53, 87, 41, 3, 62, 33, -63, -73, 67]} +{"id": 20645197, "vector": [9, 1, 87, -86, 34, 73, 6, -58, -112, 38, 82, -125, 53, 73, -108, 46]} +{"id": 4199704, "vector": [-87, -99, -29, 82, -21, 11, 66, 62, 84, 22, 21, -116, -55, -88, -65, -96]} +{"id": 24953498, "vector": [56, 101, 105, 48, -82, 124, -15, 37, -75, -112, -105, 70, 81, -32, 115, -53]} +{"id": 44915235, "vector": [41, 119, 127, 39, -1, 121, 41, 96, 106, 110, 64, 58, 27, -48, 101, -35]} +{"id": 6497830, "vector": [-1, 16, 14, -26, -30, 125, -112, -113, 121, -111, -45, -16, 13, -22, -88, 127]} +{"id": 1431742, "vector": [28, 2, -50, 61, 30, 78, 6, -87, 111, -57, -67, -67, 59, 81, -111, 85]} +{"id": 74441448, "vector": [127, -16, -58, 73, -66, 81, 24, 39, 28, 36, 50, 106, -49, 25, 13, 117]} +{"id": 93515664, "vector": [-51, -80, -48, 106, 73, -41, -6, -30, -116, -5, -115, -24, -45, 7, -65, -87]} +{"id": 64055960, "vector": [-63, 120, -11, 108, 22, -80, 114, 80, -117, 122, 25, -51, 85, -84, 104, -74]} +{"id": 28734791, "vector": [19, 62, -40, -6, -52, -51, -64, -104, -124, 82, 15, 6, 86, -62, -9, 106]} +{"id": 67451935, "vector": [-127, 124, -13, 98, 91, -96, -105, -71, 24, -2, 94, -17, 67, -38, 101, -3]} +{"id": 37957788, "vector": [116, -104, 112, 76, -61, -5, 84, -24, 119, 37, 31, 127, 95, -74, 25, -62]} +{"id": 4978543, "vector": [73, -64, -31, -28, -22, 89, 77, 100, -116, -33, 79, 48, 79, 124, 37, -115]} +{"id": 78300864, "vector": [-29, -15, 31, 40, -52, -100, -82, 90, 31, 61, 100, -120, 64, 82, -83, -49]} +{"id": 17976208, "vector": [17, -111, 33, -39, 68, -31, -8, 84, 1, 30, -48, -127, -87, 71, -47, 88]} +{"id": 83083131, "vector": [10, 34, -42, 105, -10, 97, 28, 40, -54, 116, -61, -76, 5, 27, -102, -71]} +{"id": 60955663, "vector": [9, 87, -126, -66, -28, -4, 95, 8, 76, -53, -81, 31, 30, -108, 62, 79]} +{"id": 86242799, "vector": [37, 91, 100, -70, 98, -120, -53, -77, -14, -53, -20, 3, 22, -5, 103, -23]} +{"id": 15075176, "vector": [-92, -72, -117, -66, -75, 73, 98, -12, 121, -31, -64, 123, 99, -48, -67, -19]} +{"id": 9860195, "vector": [-64, 63, -20, 81, -105, -29, 109, 56, 125, 29, 45, 20, -107, 110, -19, 72]} +{"id": 59405277, "vector": [44, 86, 18, -125, 55, 85, -40, -75, -34, -127, -49, -5, -35, -92, 36, 35]} +{"id": 6157724, "vector": [-84, 52, -25, 72, 13, -73, 106, 64, -2, -45, 49, -2, -17, 78, 88, 126]} +{"id": 4668450, "vector": [-49, 41, 88, -114, -87, 7, -123, 92, -125, 81, 71, -52, -1, 22, 57, -60]} +{"id": 14731700, "vector": [9, 44, 103, 89, -55, -121, -55, -24, -12, 98, 84, -88, 94, -17, 67, 11]} +{"id": 20002147, "vector": [99, 26, -5, 63, 108, -105, 102, -68, -20, 73, 68, -32, 0, 124, 97, -80]} +{"id": 40781854, "vector": [-14, -40, -107, 75, -85, -121, 31, -116, 13, -48, -114, -17, 0, -12, -93, -127]} +{"id": 6819644, "vector": [34, 88, 65, 73, 97, 75, 17, 119, -8, 69, -50, -57, -87, 123, -102, -32]} +{"id": 66319530, "vector": [1, 75, 26, 0, 11, -69, -105, 60, 102, -5, 5, 42, 127, 41, 54, -55]} +{"id": 65454636, "vector": [-35, 12, -128, -61, -42, 11, -65, -53, 111, 80, -43, -70, 60, 26, -82, -102]} +{"id": 11161731, "vector": [-48, 56, -67, 10, -59, 27, -61, -12, -93, -122, -87, -37, -3, 42, -6, -22]} +{"id": 67030811, "vector": [-98, -85, 108, -97, 94, 57, 90, -7, -98, 101, 114, -21, 19, 11, 80, 48]} +{"id": 92398073, "vector": [-53, 82, 70, -56, -90, 93, -37, -110, 23, -16, 106, 75, 118, -58, -71, -116]} +{"id": 20885148, "vector": [7, -13, 60, -37, 100, -49, 32, -52, 63, 25, -74, 82, -49, -128, -85, 93]} +{"id": 77284619, "vector": [11, 111, -43, 94, -29, 110, 77, 3, 71, 11, -77, 112, -45, -105, -58, -79]} +{"id": 10309525, "vector": [-32, -22, 62, -54, 42, -92, 35, 89, 85, 17, 64, 35, 18, -117, 67, -19]} +{"id": 67474219, "vector": [-103, -8, -121, 10, -18, 112, -115, 118, -10, 35, 118, 82, -111, -43, 125, -52]} +{"id": 53802686, "vector": [-126, -13, -99, 111, 112, 69, 120, 118, -105, 56, 24, -48, -25, -54, -75, 74]} +{"id": 15948937, "vector": [-38, -33, -76, 108, 112, 65, -51, -109, -17, 94, 7, -6, 104, 43, -121, 117]} +{"id": 98948034, "vector": [-71, -103, -52, -58, -107, 28, -63, 24, 77, -104, -85, 82, 73, -54, 60, 2]} +{"id": 18466635, "vector": [110, 67, -45, 65, -44, 31, -46, -127, -85, 96, -56, 26, -68, -107, 126, -14]} +{"id": 34667286, "vector": [-119, -121, 27, -65, 10, 31, -91, -106, 83, 26, -9, -37, 110, -58, 60, -88]} +{"id": 30218878, "vector": [71, 90, -85, -84, 19, -113, -113, 79, -119, 83, 56, 64, 72, 77, -125, -56]} +{"id": 32250045, "vector": [17, 104, 77, 47, 76, -71, -30, -80, -56, -6, -85, -34, -42, 66, -125, -44]} +{"id": 73021291, "vector": [-31, 7, 41, 80, -36, -117, 15, 10, -8, -100, -116, 24, -87, -1, -124, 28]} +{"id": 27325428, "vector": [-122, 61, -125, 19, -10, -118, -122, -43, 3, 3, -126, -20, 80, 6, -47, -12]} +{"id": 90061527, "vector": [-12, -99, 54, 94, -51, 61, -85, 45, -115, 67, -57, -21, -104, 95, 82, 13]} +{"id": 51590803, "vector": [16, 47, 110, 104, -86, -102, -44, 87, 11, 60, 54, 16, -84, -83, -83, -30]} +{"id": 3294781, "vector": [42, 69, -123, 0, -23, -61, 36, 65, -81, -90, -27, -66, -62, -87, 90, -31]} +{"id": 59501487, "vector": [-4, 0, 78, -57, -109, 67, 69, 52, 51, 78, 111, -104, -45, 75, 43, 54]} +{"id": 72357096, "vector": [-86, 91, 38, -31, -32, 2, -3, -65, 32, -77, 36, -91, 71, 63, -26, -122]} +{"id": 17365188, "vector": [8, -86, 89, 112, 79, 26, 48, 4, 96, 8, 61, -104, 120, 95, 102, 42]} +{"id": 72495719, "vector": [94, -119, -90, -2, 94, -72, -24, 119, -103, -4, 77, -21, -79, 4, 2, 7]} +{"id": 77272628, "vector": [85, -21, -28, 81, 35, 116, 96, -78, 84, 113, 1, -94, -67, 24, -67, -91]} +{"id": 20140249, "vector": [-1, 42, -116, -78, -66, 123, 4, -85, 1, 91, -21, 41, -107, -35, -115, -105]} +{"id": 824872, "vector": [56, -82, 19, 104, 127, -64, -21, -118, -52, 5, 23, -104, -12, -93, -68, -101]} +{"id": 62496012, "vector": [-35, 119, -103, 7, -114, 28, -35, -110, -87, -19, 51, 0, 75, 46, -40, 115]} +{"id": 56424103, "vector": [-30, -119, 89, -32, 3, 46, 27, -85, -90, -9, 117, 46, 23, 46, 39, -2]} +{"id": 97011160, "vector": [-67, 33, -25, -117, -84, 77, 98, -5, -30, -21, -14, -92, 121, 61, -32, 12]} +{"id": 5822202, "vector": [-16, -111, 81, -127, 111, 47, -98, -14, -61, -5, 76, -103, 14, -76, -127, -69]} +{"id": 81677380, "vector": [85, 20, -128, 58, 78, 45, -31, -31, -101, -38, -101, -86, -95, 9, -49, -102]} +{"id": 22997281, "vector": [72, 102, -128, 0, 111, -111, -49, -16, -64, 115, 68, -122, 95, 14, -38, 64]} +{"id": 99226875, "vector": [-88, -49, -114, -104, 64, -115, -109, 98, -50, 68, -2, -87, 62, -56, 125, -109]} +{"id": 66663942, "vector": [-9, 121, -3, -45, 95, 57, -116, 113, -38, -29, 121, 56, 80, -42, 7, -32]} +{"id": 70420215, "vector": [-11, -83, 55, -35, 68, 7, 24, 117, 34, 108, 82, 77, -76, 95, 5, -51]} +{"id": 61982238, "vector": [18, -96, 45, -15, 111, -71, 42, -7, -47, -54, -32, 48, -32, -52, -36, -112]} +{"id": 32159704, "vector": [63, -17, -87, 5, 121, -36, -83, -14, -15, 3, 6, -91, 81, 59, 59, -34]} +{"id": 96193415, "vector": [-25, 80, -53, -97, 53, -60, 54, -40, 81, 81, -72, -55, 105, 74, -85, 65]} +{"id": 34497327, "vector": [-80, 109, 76, 16, 40, -111, -45, 0, -1, -15, -78, 46, -12, 102, 69, -111]} +{"id": 68128438, "vector": [-57, -120, 39, 36, -96, -42, -127, -6, 83, 6, -19, 70, 22, 14, -65, -123]} +{"id": 34493392, "vector": [-103, -106, -31, 43, 104, 25, 18, 72, -74, -92, -119, 84, -119, -84, 28, 115]} +{"id": 14626618, "vector": [48, -59, -37, -62, 93, -112, -21, -103, -6, 120, -10, -78, -106, -21, -39, 116]} +{"id": 62430984, "vector": [120, -63, 76, -18, -70, -33, -92, 92, -118, -117, 16, -5, -1, -20, -26, 1]} +{"id": 15163258, "vector": [42, -66, 63, -94, 63, -83, -70, -78, -109, -25, 82, -60, 41, 69, 80, 127]} +{"id": 94539824, "vector": [-33, 95, 105, -13, -11, 57, -119, 112, -3, -125, -9, -77, -24, 46, 126, -106]} +{"id": 11757872, "vector": [-95, -42, 7, 112, 89, 0, 75, -31, 26, -39, -16, -90, 5, -75, 38, 75]} +{"id": 77072625, "vector": [-39, 84, -40, -3, -55, -67, 29, -90, -120, -57, 68, -45, -5, 110, -44, 110]} +{"id": 57658654, "vector": [-26, -39, 72, -89, -21, 40, 39, -72, 0, 20, 118, -72, -66, -119, -80, 123]} +{"id": 30764367, "vector": [64, 9, -103, -5, -50, -47, -14, 59, 11, 90, -127, -10, -2, -10, -9, 112]} +{"id": 59614746, "vector": [-105, 11, -42, 108, 74, 39, 61, -94, 49, -51, -76, 112, 14, -56, 54, 48]} +{"id": 9603598, "vector": [117, 119, -1, 112, 73, 27, 30, 79, 127, 116, -98, -69, -30, 114, -88, -45]} +{"id": 84166196, "vector": [22, 43, -68, 122, -52, -9, -31, -41, -50, 83, 104, -87, -19, -105, -24, -67]} +{"id": 36930650, "vector": [42, -48, -87, 1, 39, -23, 39, -30, -9, -39, 89, -56, -86, 3, 82, 18]} +{"id": 89996536, "vector": [-116, -49, 84, -42, -37, 48, 102, -1, -119, -109, -40, 67, 103, -65, -71, -75]} +{"id": 34236719, "vector": [-77, 10, 67, -14, 96, -97, 109, 38, -73, 99, 3, 46, -54, -91, -33, -104]} +{"id": 79322415, "vector": [9, -14, 38, 27, -1, -20, -103, -120, 36, 62, -122, 83, 2, 61, 2, -54]} +{"id": 38008118, "vector": [-107, -76, 29, -94, 66, 92, 9, 64, -127, -34, -40, 25, 78, 28, -111, 89]} +{"id": 54663246, "vector": [-100, -50, 113, 102, 17, 90, -57, 43, 22, 68, 106, -68, 20, 69, -7, -11]} +{"id": 64098930, "vector": [104, 7, -74, -97, -90, 61, -3, -37, 96, 26, 114, -35, -114, 62, -104, 84]} +{"id": 6525948, "vector": [-102, -23, -34, -96, 14, 91, -89, 84, -106, 12, 32, 51, -98, -9, -69, -108]} +{"id": 62762857, "vector": [22, -90, -66, 117, 9, 50, -78, -38, 110, -101, 48, 112, -30, 34, 81, 101]} +{"id": 14723410, "vector": [125, 73, 0, 96, 91, 63, 7, 100, 45, 24, -122, 123, -36, -86, 126, -77]} +{"id": 69848388, "vector": [119, -126, 48, -109, -56, 78, -105, 74, 38, -72, 48, 125, -22, -98, -122, 80]} +{"id": 97783876, "vector": [-103, 85, 12, -28, -117, 75, 86, 40, 41, -27, -84, -26, 40, 50, 81, 1]} +{"id": 98130363, "vector": [50, 71, 23, -116, 125, -32, -23, -16, 28, -84, -1, 23, 36, -104, 69, -86]} +{"id": 1197320, "vector": [11, 73, 32, -53, -5, 99, 29, -112, 85, -108, -35, -89, 36, -51, 64, 43]} +{"id": 26734892, "vector": [-15, -98, 76, -29, -59, 125, -26, 36, 61, 52, 9, 85, -75, -117, -126, -88]} +{"id": 17857111, "vector": [55, -115, -61, -82, -84, 76, 25, 98, 105, 96, 32, -21, -54, -31, -23, -78]} +{"id": 74452589, "vector": [37, -17, -37, 98, -10, 117, 123, 22, -51, -21, 11, 41, 113, 103, -97, 74]} +{"id": 7919588, "vector": [22, 39, -107, 28, 66, 71, 34, -96, 110, 39, 109, -74, 57, -16, -32, 56]} +{"id": 20836893, "vector": [-13, -54, 0, -111, 92, -36, 45, -3, 52, 54, 50, -25, 87, 38, -87, -107]} +{"id": 77187825, "vector": [23, 96, 117, -92, -64, 93, 85, -76, 79, 82, 64, -38, -106, -104, -18, 54]} +{"id": 81853704, "vector": [15, -18, -5, 70, 65, -49, 41, -20, 55, 4, 51, 114, -56, 81, 86, -123]} +{"id": 55669657, "vector": [-50, -91, 32, 54, 22, 18, -44, 118, 15, 65, -57, 3, 95, -10, -84, -38]} +{"id": 38256849, "vector": [-84, -28, 78, 67, -2, 20, -114, -10, 109, 94, -11, -92, -60, -110, -118, 99]} +{"id": 41092102, "vector": [66, 48, -30, 104, 24, -87, -107, 92, -88, 15, 9, 93, -116, -42, -8, 57]} +{"id": 24591705, "vector": [81, -43, 37, 95, -36, 53, -40, -37, 93, -4, 49, 19, 127, 64, -109, -24]} +{"id": 67227442, "vector": [124, 117, 60, 28, 34, -98, -13, 7, 24, -17, 9, 8, 103, -37, 70, -35]} +{"id": 54606384, "vector": [-3, -76, 0, 27, 74, 99, -54, -20, 59, 108, 26, 64, -65, -114, -9, 83]} +{"id": 61712234, "vector": [-8, -121, 102, -128, 6, 99, -31, -9, 114, 48, 34, -111, 98, -49, 41, -77]} +{"id": 15015906, "vector": [5, 96, -77, -15, -11, 114, 59, -78, -51, -128, -121, 12, -16, 90, -23, 67]} +{"id": 24168411, "vector": [-81, 98, -12, -25, 12, -31, 10, -53, -37, -101, 33, 123, 57, 79, -60, -59]} +{"id": 86022504, "vector": [10, -93, -85, -25, 27, -91, -101, -10, -99, 67, -91, -41, 70, -107, -118, -19]} +{"id": 87710366, "vector": [-51, 108, -19, -34, 80, 53, -128, 109, -112, 102, 72, -85, 86, 40, -98, -9]} +{"id": 53547802, "vector": [109, 114, -8, 35, 112, -63, 53, 32, 4, 34, -7, 125, -60, -27, 65, -6]} +{"id": 20681921, "vector": [-108, -102, 55, -23, -97, 17, 58, -83, 66, 77, -75, 19, 31, -101, -120, -118]} +{"id": 16595436, "vector": [-127, 127, 15, -106, -70, -51, 34, 68, -33, -86, -51, 62, 112, 78, -42, 25]} +{"id": 65715134, "vector": [-38, 48, 92, -128, 123, -43, -97, -3, 54, 60, 48, 116, -46, -88, 34, 89]} +{"id": 3633375, "vector": [80, 10, -69, 22, 28, -98, -53, 100, 89, -63, -118, 40, 48, 52, -108, -96]} +{"id": 32699744, "vector": [-100, -39, 27, -127, -77, -26, -2, -87, 56, -49, -22, 4, 83, -49, 36, -86]} +{"id": 24058273, "vector": [119, 93, 70, 96, 44, -10, -69, -123, 114, 65, 29, 63, -39, -103, 23, 113]} +{"id": 33201905, "vector": [106, 59, 50, 18, -18, -40, 79, 5, -124, 52, -5, 19, 125, 33, -71, 76]} +{"id": 37166608, "vector": [61, -106, -33, -76, 98, 16, -10, 42, -47, -27, 38, -37, -63, -106, -97, 111]} +{"id": 36780454, "vector": [-67, -45, -107, 45, -107, -90, -62, 99, 82, 107, -27, 53, -81, 60, -36, 114]} +{"id": 65338021, "vector": [-108, 38, 119, -23, 48, -107, -13, 74, 73, 76, 29, -77, -107, -18, 2, -128]} +{"id": 6153406, "vector": [-6, -2, -88, -21, 34, -42, -16, -65, 113, -82, -89, -96, 2, 88, -111, -10]} +{"id": 45794415, "vector": [-46, -62, 46, 121, 82, -59, 111, 44, 7, -111, 123, -12, -7, -35, -22, -82]} +{"id": 53393358, "vector": [119, 11, -13, 45, -115, 116, 76, -62, 66, -87, 12, 89, -17, 89, -128, -66]} +{"id": 70596786, "vector": [47, 91, -78, -80, -39, 114, 48, 31, -100, -4, -60, -26, 115, 53, -23, 30]} +{"id": 19898053, "vector": [64, 49, -87, 34, -85, 21, -9, -45, 127, 111, -50, 70, -57, -102, -44, 35]} +{"id": 73392814, "vector": [-7, 15, 84, -76, -79, -72, -14, 22, -86, 31, -51, -48, -21, 12, -110, -21]} +{"id": 83269727, "vector": [-36, -48, 66, -91, -65, -54, -66, -76, 4, -118, -80, 8, 126, 121, 127, 93]} +{"id": 89699445, "vector": [46, -120, -25, -61, -76, 61, 25, 42, -66, 45, 112, 95, 0, -106, -35, -126]} +{"id": 78884452, "vector": [-71, 11, -126, 56, 45, -97, 80, -97, -76, -100, 91, -55, -78, 114, 91, 45]} +{"id": 62428472, "vector": [65, 81, -94, -72, 0, -88, -13, 122, -7, -84, -90, 116, 61, 21, -17, -101]} +{"id": 55770687, "vector": [1, 47, 8, -2, -5, -92, -66, 4, 100, -52, 37, 83, -36, 113, 57, -95]} +{"id": 11581181, "vector": [111, -83, 25, -32, 52, -13, 123, -121, 57, 22, -113, -122, -24, -25, 35, -28]} +{"id": 89499542, "vector": [-94, -78, 89, -96, 13, 123, 21, -41, -41, 26, 118, -79, 100, -125, -70, -74]} +{"id": 99333375, "vector": [78, 105, 7, -17, -119, -53, -43, 111, 91, -8, -91, 26, -82, 91, -119, -12]} +{"id": 69605283, "vector": [122, 34, -48, 85, -105, -37, -46, 74, -55, 106, -123, 122, -19, -87, 75, -88]} +{"id": 31727379, "vector": [108, -79, -54, 120, 69, 27, 75, -116, -25, 116, -120, -70, -101, 106, 38, -2]} +{"id": 33565483, "vector": [-74, -73, 117, 97, -67, 62, -17, -6, 86, 108, -100, -26, 44, 47, -103, -115]} +{"id": 17037369, "vector": [-46, 95, -78, 67, 24, -7, -91, -20, -91, -83, 36, 40, -63, 117, 20, -122]} +{"id": 84187166, "vector": [-92, 117, 50, 20, 101, -32, 62, 109, 72, 20, -39, -51, 60, 70, 55, 51]} +{"id": 21070110, "vector": [58, -62, -1, 18, 18, 50, 23, 37, -31, 19, 39, 2, 72, 100, 96, 67]} +{"id": 85695762, "vector": [81, 90, -95, 57, -61, 25, 108, 26, 7, -86, 14, 97, -111, -32, -36, 97]} +{"id": 21993752, "vector": [19, -128, 75, -38, -99, -111, 20, -64, -3, 34, -110, 15, -99, -19, -42, 72]} +{"id": 38658347, "vector": [-69, -93, -28, 103, 52, -120, 26, -103, -69, -83, 52, 50, -103, 93, -9, 37]} +{"id": 68939068, "vector": [-42, -33, 25, 53, -39, -13, 69, 106, 91, -114, -101, 40, 53, 107, -89, 117]} +{"id": 20486294, "vector": [-48, 10, -76, 98, -22, 40, -95, -22, 115, -74, 36, -27, -128, -24, -128, 104]} +{"id": 35456853, "vector": [-24, 96, 104, -8, 21, 62, 97, 99, 44, -50, 40, -85, -36, 23, -89, -113]} +{"id": 26744917, "vector": [108, -122, 15, 35, -56, 70, 119, 0, 15, 91, -101, 88, 98, -41, 25, 76]} +{"id": 50567636, "vector": [29, -87, 24, -123, -66, -79, -56, -74, -101, -64, 83, 58, -18, 81, -112, -53]} +{"id": 67513640, "vector": [-6, 123, 68, 44, -124, -64, -28, -9, 83, -2, 73, 93, 42, -53, -13, -32]} +{"id": 53648154, "vector": [123, -42, 78, -20, -1, 90, -1, 99, 68, 80, 62, 55, -25, -99, 110, -56]} +{"id": 39986008, "vector": [119, 72, -77, -79, -35, 15, 45, 32, -63, 114, -5, -40, -50, -71, -5, -83]} +{"id": 73222868, "vector": [45, -62, -27, 35, 106, 20, -96, -79, -67, 103, -77, -103, -72, -94, -113, 24]} +{"id": 56955985, "vector": [-120, -95, -10, -49, 124, 71, -22, -18, 36, 125, -22, -88, 99, -79, 75, 107]} +{"id": 82178706, "vector": [-85, 78, 40, 49, -20, 102, 24, -6, 40, -113, 119, 73, -28, 123, 84, 107]} +{"id": 37192445, "vector": [3, 19, 125, 61, 54, -64, 105, 110, 120, 112, 111, 117, 81, -79, 122, 62]} +{"id": 22879907, "vector": [-7, 12, 50, -20, 22, -114, 64, 117, -75, 125, 35, -34, -90, -68, 112, 71]} +{"id": 45848907, "vector": [73, -102, 49, 74, 33, 56, -98, 58, -81, 0, 55, 44, 110, 114, -67, -34]} +{"id": 62232211, "vector": [-110, 78, 121, 32, 90, -103, 29, -43, -57, -51, -37, 59, 65, 116, -53, 47]} +{"id": 45381876, "vector": [-83, -63, -73, 1, 117, -125, -17, -44, -29, -97, 74, 57, 61, -9, 78, 75]} +{"id": 81898046, "vector": [-46, -89, -126, 117, 90, 12, -23, 24, 30, 80, 9, -74, 8, -78, 10, 114]} +{"id": 22942635, "vector": [40, 79, 61, 102, 34, 124, -13, 81, -93, -109, -113, -33, 112, 81, 14, -34]} +{"id": 30891921, "vector": [-33, 69, 105, -84, 104, -30, -13, -35, 83, -108, 79, 16, 1, -85, -75, 123]} +{"id": 44844121, "vector": [41, 47, -104, -22, -85, 54, 71, -51, -118, 114, -84, 17, -121, 78, 112, 44]} +{"id": 62490109, "vector": [2, 39, 106, -27, -50, -93, -5, 7, 76, -66, 53, 4, 8, 95, 37, -126]} +{"id": 21673260, "vector": [51, 1, -124, -3, 20, -97, 72, 106, -110, 52, -59, 68, 122, 39, 97, 67]} +{"id": 45996863, "vector": [33, 32, -22, -84, -90, 57, 11, -66, -89, -85, 77, -35, 102, 12, 64, -114]} +{"id": 8128637, "vector": [111, 55, 93, 50, 91, 47, 100, -118, -45, -97, 124, 104, 86, -89, 10, -32]} +{"id": 12664567, "vector": [22, -10, 24, 105, 41, 70, 93, -18, 22, -44, 17, -95, -98, 93, 77, 12]} +{"id": 15536795, "vector": [118, -20, 59, -33, -89, 0, -27, 62, -121, 124, 119, 112, 66, 78, -7, 74]} +{"id": 77413012, "vector": [103, -85, -74, -16, -115, -48, 119, -15, -70, 36, -69, -2, 46, 38, -89, -74]} +{"id": 26292919, "vector": [-118, -38, 11, 121, -17, -127, -27, -123, 82, -102, -114, -30, -5, 100, 66, -21]} +{"id": 80246713, "vector": [-125, -22, -14, 2, 41, -110, 97, 125, 51, -26, -26, 100, -38, 32, 13, 53]} +{"id": 89078848, "vector": [-98, -76, -110, -52, -10, 50, -49, -53, 70, -86, 111, -42, -7, -19, 61, 7]} +{"id": 15902805, "vector": [59, 105, -118, 41, -49, -109, 18, 73, 93, 90, -4, -65, 31, -48, -58, 87]} +{"id": 8807940, "vector": [-64, 47, -39, -20, 32, 120, 51, -19, 21, -94, 83, -27, -52, -46, 51, 108]} +{"id": 36396314, "vector": [-59, -49, -86, 90, 82, 15, 89, 49, -106, 73, -77, 86, 80, 53, 99, -35]} +{"id": 53632373, "vector": [49, -121, -113, 92, -8, 94, 77, -26, -117, 126, 44, 15, 45, -48, -70, 106]} +{"id": 84127901, "vector": [-83, 17, -5, 48, -18, 83, 30, -120, 61, 71, 116, -17, 51, 123, 77, -52]} +{"id": 57240218, "vector": [-86, 52, -122, -67, -88, -69, 109, -120, 54, -97, -122, 108, 111, -77, -62, -32]} +{"id": 40984766, "vector": [28, 49, 7, -14, 61, -126, 0, 118, -70, 60, 48, 19, -53, 119, 121, -2]} +{"id": 57803235, "vector": [-127, -78, 31, 89, 48, -84, -64, 18, -2, -66, -67, -74, 63, 68, 110, -44]} +{"id": 6986898, "vector": [-1, -59, -83, -35, 93, -91, 20, -95, -2, -126, 80, 47, 48, -74, -101, 94]} +{"id": 50702367, "vector": [-111, 29, -41, -111, -83, 71, -10, 114, -4, -31, -25, 51, -128, 69, 60, 55]} +{"id": 17386996, "vector": [-55, -17, 12, 92, -80, 37, -67, -51, 72, -49, -54, 9, 18, -105, -106, 64]} +{"id": 40686254, "vector": [34, 120, -26, 57, 106, 91, -26, 90, -122, 100, -21, -27, 66, 64, -118, -120]} +{"id": 83155442, "vector": [-127, -116, 8, -126, 70, 99, 121, -45, -81, -124, 76, 114, 6, 98, 71, 19]} +{"id": 17081350, "vector": [15, 19, -28, 73, -10, 94, -17, -58, -34, 123, -29, -71, 3, -31, -53, -128]} +{"id": 96709982, "vector": [119, 47, 26, 48, -77, -71, 49, -101, -112, -27, 126, -108, 89, -28, -90, -84]} +{"id": 82726008, "vector": [-23, -9, -48, 84, 47, 41, -38, -22, 74, 80, 48, 99, 34, 11, 31, -3]} +{"id": 68110330, "vector": [101, 85, 42, -71, -53, 1, -98, -50, 10, -63, -46, -21, 70, 93, -113, -98]} +{"id": 17385531, "vector": [48, -7, -120, -4, -65, -37, 24, 12, -27, 60, 118, -66, -115, -28, 23, 79]} +{"id": 1250437, "vector": [19, -32, 32, 49, 96, -87, -67, -88, 69, -123, 114, -64, -125, 32, -90, 47]} +{"id": 99515901, "vector": [-27, 75, 77, 113, 44, -16, 92, -14, -91, -36, 90, -128, -115, -64, 64, -106]} +{"id": 38009615, "vector": [32, 40, 33, -106, -57, -65, -73, -59, -91, 63, 112, 111, -91, -29, -2, -125]} +{"id": 52261574, "vector": [55, -66, -62, 42, 121, -76, 24, -54, 93, -105, 14, -49, 2, -127, -10, 63]} +{"id": 12416768, "vector": [6, 74, -29, 41, -13, -31, 67, 26, 106, -64, 63, 41, -9, 63, 23, 120]} +{"id": 76330843, "vector": [-34, 118, -88, 58, -122, -78, 84, -36, -49, -60, 67, 67, -61, 93, 107, 65]} +{"id": 22129328, "vector": [-24, -20, 13, 13, -55, 51, 12, 116, -76, -88, -2, 82, -9, -83, -86, 108]} +{"id": 86460488, "vector": [100, 7, 45, -32, -11, -60, -116, -34, -6, -127, -55, -100, -83, 44, -48, -22]} +{"id": 14349098, "vector": [-90, -52, 36, 87, 86, 108, 85, 82, 52, -26, -37, -121, 53, 109, 43, 122]} +{"id": 46870723, "vector": [115, -41, 127, 107, -15, -25, 12, 46, -73, 126, 72, 15, 38, 72, 99, -9]} +{"id": 99224392, "vector": [-67, 25, 8, -96, 22, 10, 108, 38, 95, -86, -6, -3, -127, 40, -90, -113]} +{"id": 64087743, "vector": [85, -34, -25, 61, 3, 102, -62, 90, -110, 61, -35, -116, -121, 34, -114, -40]} +{"id": 99604946, "vector": [123, -103, 7, 12, 43, 65, 71, -2, -28, -31, 46, -103, 116, -109, -25, -104]} +{"id": 4064751, "vector": [41, -5, 66, -79, -99, -121, 110, 6, -45, 27, -124, 116, 96, 79, 71, -19]} +{"id": 38061439, "vector": [-51, -123, 8, -46, -13, -119, -77, -81, -121, -35, 37, 13, -67, 17, 26, -2]} +{"id": 33061250, "vector": [11, 123, 14, 126, -33, -40, 0, 10, -82, 94, 66, -55, -89, 97, -77, 28]} +{"id": 3773993, "vector": [22, -59, 92, 74, 87, 98, 100, 3, -25, -51, -87, 116, 109, -28, 60, -55]} +{"id": 48774913, "vector": [23, 1, 33, 96, -25, -4, -107, 118, -66, -2, 59, -49, 56, -34, 9, 94]} +{"id": 17727650, "vector": [2, 80, -96, 104, -66, -107, 47, -48, -66, 113, -57, 123, 76, 32, 94, 9]} +{"id": 30395570, "vector": [88, 118, 9, 102, 38, -37, 75, 122, -93, -92, 17, -96, 18, 122, -112, 124]} +{"id": 47738185, "vector": [101, 124, -73, -94, -122, 45, 90, -123, -46, 20, -49, -4, -18, -109, 31, 88]} +{"id": 82779622, "vector": [3, 30, 99, 45, -48, 73, 120, 35, 91, 39, -92, 114, 102, 79, -70, 31]} +{"id": 24619760, "vector": [11, 8, -115, -5, -88, -102, -6, -23, -82, -2, 5, 65, 117, 70, -60, 31]} +{"id": 44177011, "vector": [87, -128, 108, 67, -101, -50, 81, -51, 99, -92, -94, 30, -23, -38, -84, -37]} +{"id": 89046466, "vector": [-89, -98, 94, 88, -45, 121, 16, -32, 118, 84, -52, -104, 41, -50, -92, 22]} +{"id": 21397057, "vector": [103, 117, 22, 103, -113, 92, -92, 65, 85, -103, 90, -57, -80, 41, -75, 60]} +{"id": 44060493, "vector": [-101, 45, 51, 39, 22, 6, 56, -48, 108, 9, -125, -2, -111, 40, -53, -13]} +{"id": 30787683, "vector": [57, 105, -71, 82, -126, -103, 76, -31, 27, 39, 56, 58, 17, 58, 85, -65]} +{"id": 54263880, "vector": [109, -69, -104, 43, -46, -32, -88, 21, 99, -21, -126, -109, 119, -79, -77, 13]} +{"id": 29819940, "vector": [-126, -45, -94, 43, -54, -27, 10, 19, -3, -94, 37, -18, -55, -112, 46, -51]} +{"id": 61373987, "vector": [19, 4, 105, 50, -80, 72, 69, 68, -44, 89, -61, -32, -125, 24, -63, -68]} +{"id": 5832946, "vector": [-69, 23, -103, -6, -86, 37, -124, 61, 122, 44, 47, -91, 110, -107, -69, -35]} +{"id": 20867149, "vector": [-30, 3, 30, -8, 27, 14, -120, 122, 95, 2, 77, 92, -121, 18, -2, -44]} +{"id": 1569515, "vector": [-47, -54, -40, 85, -9, 118, -48, 74, 127, 72, 86, 121, 32, 19, -47, 31]} +{"id": 30771409, "vector": [-92, 49, -83, -27, -51, 107, -74, -18, 38, -71, -125, -108, 106, 103, -105, -80]} +{"id": 7502255, "vector": [106, 90, 0, -86, 22, -117, -126, -30, -67, -5, -122, 73, -11, 22, 95, -105]} +{"id": 71657078, "vector": [127, -92, -68, -28, 115, -71, 92, 0, 48, -5, 0, 120, 15, -24, -95, 127]} +{"id": 46540998, "vector": [-63, 38, -110, -29, 54, 44, 32, 95, 111, 55, -26, -23, -93, 106, 109, -126]} +{"id": 70004753, "vector": [40, 90, 97, -91, -106, -37, -98, -114, -14, -19, 96, -18, -2, 63, 121, 39]} +{"id": 34946859, "vector": [109, 47, 9, -13, -113, 15, 99, -118, -34, 69, 107, 48, -37, 41, -99, 3]} +{"id": 176257, "vector": [72, 51, 39, 9, 40, -17, 86, -75, -14, -48, 117, 116, 77, -61, -77, -50]} +{"id": 54058788, "vector": [-20, 68, -113, -85, -2, -31, -20, 126, 19, 115, 60, -13, 45, -36, -78, 60]} +{"id": 83789391, "vector": [61, 63, -7, 10, 30, -117, -72, -121, -61, -69, 26, 111, -11, 104, 47, -45]} +{"id": 70727211, "vector": [44, -101, -105, 6, 77, -88, 124, -55, -124, 81, 66, 26, 76, -122, -22, -48]} +{"id": 87598888, "vector": [-21, -123, 20, 112, -96, 16, -124, -97, 9, -116, 7, 29, -114, -9, 19, 80]} +{"id": 31733363, "vector": [34, -60, 29, -124, -117, -54, -116, 79, 91, -54, -54, -84, -15, -37, -47, 60]} +{"id": 99861373, "vector": [-58, -106, -63, -117, -65, 87, 3, -22, 41, 98, 67, 118, -91, 49, -45, -60]} +{"id": 40534591, "vector": [59, -94, -22, -116, -100, 18, 38, -125, -30, -75, -108, -121, 9, -52, 115, 73]} +{"id": 10453030, "vector": [-106, -84, 46, -78, 48, -35, -87, 91, 14, -117, 49, -46, 105, 127, 25, 7]} +{"id": 32151165, "vector": [-36, -69, 95, -128, 101, -5, 3, -25, -38, 16, -118, -78, 115, -40, -37, 19]} +{"id": 79821897, "vector": [-9, 122, 24, -53, -22, -76, 55, -72, -83, -9, -30, 40, -22, -56, 17, -62]} +{"id": 96318094, "vector": [-30, 38, 29, -8, -6, -15, -52, -87, -123, -60, 59, -123, -42, 93, -63, -96]} +{"id": 59318837, "vector": [-100, 103, -103, -1, -65, 33, -127, -71, -11, -110, 99, 64, 42, -74, 45, 98]} +{"id": 6871053, "vector": [62, -117, 112, -126, 61, -96, 94, 38, -104, 100, -95, 66, 115, -76, -94, 89]} +{"id": 40152546, "vector": [-6, 85, 10, 109, 85, 36, -51, -124, -92, 8, -52, 22, -74, 6, -22, 19]} +{"id": 79738755, "vector": [112, -73, -45, -23, 107, -125, -35, 91, 81, 63, 91, -2, -108, -86, -116, -64]} +{"id": 93790285, "vector": [38, 62, 53, 83, -101, 2, 109, -22, 112, 117, 96, 94, -13, -76, -80, -88]} +{"id": 45995325, "vector": [-72, -107, -41, -52, 67, 29, -14, 23, -31, -86, 113, 60, 34, 67, 86, -104]} +{"id": 40027975, "vector": [41, -43, -21, 125, -14, 54, 4, -121, 114, 63, 120, -119, 82, 81, -82, 115]} +{"id": 15064655, "vector": [-48, 4, 78, -44, 127, 7, 61, -87, -31, -82, 123, 22, 29, 4, 11, 3]} +{"id": 37891451, "vector": [-105, -60, -38, 77, -117, 114, -10, -12, -72, 16, 1, 9, 124, -81, -99, 45]} +{"id": 247198, "vector": [-102, -79, 52, 36, -117, -106, -123, 39, 45, -19, -114, -106, 42, 107, 19, -5]} +{"id": 94076128, "vector": [-53, 68, -32, -19, -47, -49, 112, 47, 97, 28, -39, -118, -82, -79, -9, -18]} +{"id": 37224844, "vector": [119, 111, -127, 48, 26, 116, -80, 52, -68, 83, 2, -38, -119, 41, 96, 82]} +{"id": 16380211, "vector": [54, -22, 127, -93, 7, -19, 58, 109, -53, -55, 113, 51, -122, 5, -8, 104]} +{"id": 10597197, "vector": [-27, -110, -38, 37, 99, -7, -29, 29, -127, 127, 19, 11, 6, 116, 83, 83]} +{"id": 34432810, "vector": [30, -94, 79, 91, 96, 1, -118, -119, -19, 116, 119, 59, 94, 120, 89, -58]} +{"id": 94595668, "vector": [-110, 23, -60, -79, 31, -108, -46, -78, -57, -98, 37, 18, 60, 97, 57, 101]} diff --git a/src/test/resources/data/test_vectors_nested_1000x128.json b/src/test/resources/data/test_vectors_nested_1000x128.json new file mode 100644 index 000000000..0dffc35de --- /dev/null +++ b/src/test/resources/data/test_vectors_nested_1000x128.json @@ -0,0 +1,1000 @@ +{"id": 322, "vector": [99068.50376381158, 88573.2072597422, 15853.064675079664, 28632.989469555538, 35982.04456596793, 17738.390408696003, 81579.19998990816, 83550.36160149651, 16172.577774468078, 12407.173011917082, 62466.586202863895, 14944.28048334243, 51895.63040821629, 67411.49186977767, 82874.69624450797, 70707.40727542483, 54196.68741962424, 78245.5888156245, 52369.250870967946, 65187.2164525083, 13661.213228264323, 7040.9589866832985, 52418.47489527644, 34149.32870217484, 23047.848728567198, 9304.158200408918, 50714.429512049086, 27010.477967660252, 86000.97337722874, 19262.619217617048, 9025.864553964348, 52005.39507542871, 97222.62476129965, 12068.742178114533, 13160.922768044004, 79288.55247190826, 17289.211990437892, 17174.8624999444, 34737.322505056254, 27604.347388084192, 73737.57504407635, 10400.290963994252, 24774.79771851735, 81682.86000017286, 72730.83555053356, 4914.223178227384, 87645.81997374613, 87089.3814877381, 66771.76429098121, 76129.03264603007, 25307.513408494255, 21826.91796770343, 59295.44771322758, 72510.33111259321, 60536.414950457984, 56120.82790694709, 94411.84525648673, 91670.3096586618, 32349.549287113543, 32952.103729973416, 5108.999867738706, 98370.51585049079, 59725.65333734072, 33108.0741272476, 69122.69592593945, 96699.04035309899, 82247.36640939685, 97635.28044033359, 54850.6938342168, 13681.643886540583, 41755.70190007814, 54582.94245407739, 43095.66746738402, 67773.99769093192, 71645.7331194456, 86773.25308830473, 70885.46423317467, 91737.86055941659, 87067.17726086797, 52777.694477546145, 98655.09331800397, 80226.62727292119, 59952.330623489914, 12260.897754625055, 54474.004086384484, 4954.9713299904315, 78447.24339101047, 87801.74515242745, 37016.76744235859, 59024.69837713164, 73863.08146016131, 73000.37209498162, 77376.45825121911, 81444.46246162163, 70344.649517354, 11516.665047529506, 82911.03643269501, 60083.34823774171, 54022.7244240484, 30881.786721398606, 90916.90296596587, 26634.436329025557, 98446.81454349941, 45057.85079473461, 46184.386953657355, 77884.38584874007, 96437.1547694247, 76501.62101631636, 98932.33322067512, 25925.705275967426, 15356.58805391542, 61202.14624958974, 2421.288542598132, 12535.995455880899, 69540.69649848856, 72143.37123737848, 20262.992060173845, 92348.37537285395, 84848.42968418587, 48787.159705339625, 90914.29958986014, 92254.20209409502, 8527.774382362362, 38954.90398963415, 37422.72317763923, 76494.31213884517, 74822.10049643254, 34750.53819160653]} +{"id": 571, "vector": [45518.91613130162, 26576.640067248238, 47907.61270098416, 96055.72494689011, 19753.506751210105, 20627.621096022718, 63395.69929423381, 41942.417186873725, 2332.710176606001, 90362.35047122953, 25423.19777990937, 5246.908823541652, 13665.306814018551, 44644.84883987933, 99641.7915924587, 90468.3353375929, 634.6964749400996, 72304.46320476061, 84695.9753598577, 46664.61882420013, 46799.64933195806, 99283.10915171122, 16449.764839052194, 10191.28717700445, 69848.2230363387, 10545.99114076611, 49534.939715658074, 82036.10817804359, 25066.580867159828, 68817.27177278258, 2383.998401567966, 53393.14394887404, 43861.25859947717, 85310.18719133949, 49405.28617159584, 31364.623019516846, 93555.48495954402, 58313.14882053672, 59262.978826089784, 98255.53373225071, 38932.613351920045, 84442.1656738434, 37211.419240085954, 38420.81799561128, 3289.1199241183335, 73995.85835837593, 31621.59651080354, 63871.97029817826, 87835.89379683956, 82640.18003694105, 41398.270575836126, 28983.78575888342, 56577.73879850081, 66025.36972538164, 82817.17525312766, 16730.99553217454, 52261.52630085136, 82848.60156710738, 49472.710789434626, 78060.97547248221, 19211.627225903307, 84241.24760216472, 67231.6074387088, 83898.12002644969, 77473.7730938889, 42651.285042557, 32298.99649285932, 13881.51813950803, 75980.11387721862, 37989.33384437198, 38332.73412479814, 50976.859503958614, 37720.06185935408, 99193.46351252029, 36853.47757489766, 31327.204142920607, 33027.36645960469, 43754.23530096614, 98264.42380751023, 78593.72751651118, 23454.63174696979, 3232.2324125558466, 76336.30393831164, 23455.463690765555, 49636.089897134974, 30594.44656199004, 78957.91474476666, 55291.17173231656, 59482.264865226774, 36631.62701453811, 23535.935800019946, 40494.94230087256, 93897.71235524236, 9336.484756585727, 24868.543611674755, 40189.96580210098, 98752.86859990322, 54237.06113096801, 9369.889193582349, 67183.90744420802, 69460.07977474207, 62486.31567420663, 22411.40841870628, 76126.22085983853, 91280.2458172559, 98832.81162818873, 10927.784309266886, 64360.83482222591, 6681.061624074003, 5435.547915613748, 61398.69591733693, 4950.231175964692, 44038.85060570579, 40223.161365633954, 71375.50944880412, 7519.084776290497, 5959.316456896546, 83380.00473421182, 53609.47207066573, 27407.13813583957, 25659.123363286595, 62258.57734375094, 5167.049909781363, 39159.57039810029, 37716.746192275554, 95411.41474439763, 10820.610899237348, 36814.42095430023]} +{"id": 164, "vector": [28763.422191592592, 8422.724805662729, 77514.60659938685, 18331.89877663529, 75352.78095722066, 59348.18048384885, 65196.78586390411, 2006.1008426069282, 22326.371318470607, 26911.103813461577, 95636.86487516103, 18859.068261288892, 71603.83066918081, 48757.274929393, 41051.778247434835, 90955.43490253083, 62105.06364462943, 71624.72662112884, 36634.38314465399, 52951.95088502563, 17098.09444096181, 93947.39703834956, 16769.564692711592, 64725.58720343016, 70741.5041448602, 9679.182618752091, 39075.00085166311, 62810.5687310301, 56359.134327920416, 39694.82124689279, 94891.6489444261, 40828.82741134367, 45595.2339446529, 88940.72291768978, 29957.529513134385, 70173.69492312126, 64991.14200963969, 2408.9748817743616, 38820.67203099201, 13127.56051124916, 49167.483821063026, 51505.3943168003, 92024.01194874776, 66122.45798123065, 90781.13237971468, 3423.711407635699, 38008.61896772719, 18760.560409843096, 36767.38392456448, 19543.2485093095, 83790.19016946203, 41049.87227017078, 96926.50998222546, 99464.95440043347, 50992.79327018047, 57214.81564072638, 92763.83125530514, 28494.985730477707, 28054.66174380642, 8221.14798817407, 93567.72153072682, 35393.71490238884, 34028.81957850555, 12609.596130925793, 94921.5807994374, 95943.85423803715, 35522.420979976974, 24114.89810042752, 18998.390524453167, 721.9858179446592, 39999.68418398419, 33056.24692299456, 26540.673053265007, 47743.246244629336, 159.2033710386187, 89270.61855901364, 6966.848709057849, 8919.062306402626, 51797.6705111746, 82413.54867412568, 91282.37203830693, 88600.80338837321, 70192.51958752691, 21382.171332703936, 49472.70315719634, 15125.943966549326, 40226.43511050353, 96226.34484326141, 67735.72852627149, 22653.416501803626, 53418.71296212689, 30136.90320051756, 93294.96662502506, 92897.27845535311, 41375.4024166923, 4342.656546180312, 35938.33715402125, 32214.59467874532, 81874.35736004938, 69858.29288576824, 77467.83144899538, 79497.24399046, 70803.80505339631, 56445.728830622786, 55163.55972504984, 73231.52753716854, 77455.61351541939, 87493.89991354039, 15924.722739478948, 1558.1429347547694, 28402.443990105097, 6817.364911101731, 51494.44020903198, 23403.959090894245, 25047.45618665025, 63740.52528576395, 78125.38748791492, 11010.156953187767, 32846.500840966255, 39298.02659485334, 61858.31325436125, 61374.755351834334, 11004.596760594453, 41817.975216121726, 23563.62024809502, 6899.687993667647, 637.8567207981911, 44317.36574640524]} +{"id": 2010, "vector": [22791.10162898287, 26950.652130723618, 26125.17357875298, 39652.34625271533, 98609.88871463083, 13369.479002815766, 73592.68766542341, 2461.0035281964592, 89234.00756226206, 48453.86894513495, 81819.75650966275, 95216.7866293796, 19029.227213019796, 2457.1759194561005, 96112.83210556045, 97917.14926089617, 75789.56826875213, 13344.519980174518, 13199.693859665618, 16696.24616477631, 48350.45035023057, 73317.484872905, 56221.55278717057, 80687.81642827138, 65860.54791751069, 20525.390091220685, 32081.483627898077, 11289.048459195672, 61672.62083241597, 8739.19627484968, 96773.06738654534, 85271.02356600389, 9301.040573118713, 2484.5412262682885, 73390.82820110639, 32034.75785306298, 41936.3869677533, 32342.388301290404, 19335.66606713053, 32620.7082774708, 2295.602383698303, 23077.216371628783, 20182.589512854964, 48782.5719131646, 38076.04483215576, 16489.671819075826, 92717.92266633772, 40236.120209509885, 64467.54993530334, 3998.8061040336697, 25397.877620666277, 4086.9894887612945, 29556.581774691458, 8456.328830180693, 27555.213759603113, 60691.777394275174, 52765.771783905744, 12956.489005652084, 38361.704919385054, 41848.35103127804, 39660.866281002985, 46015.98244441966, 23965.3049325391, 44734.211695030426, 98054.71728096697, 42286.074125975705, 6915.987826552916, 86761.84756550887, 1582.6172938999573, 57676.879743331076, 74344.91331466564, 90326.19241711246, 74006.59643162787, 75208.24175141187, 1424.095530306424, 74641.32619630331, 67353.11109145578, 39540.88595464011, 72406.76161752158, 60557.09598110144, 19689.453644105924, 8959.229264066504, 19446.979236136398, 19291.000856698814, 82505.24191993658, 88711.06820748902, 97374.91880773533, 56437.65384391281, 99686.26320547718, 30817.568369333436, 27638.18979861138, 7125.539887683041, 16439.450906883714, 22887.075438371518, 23620.89604057467, 68886.23008494181, 90143.24106914537, 73137.8999698065, 36692.46183375412, 20042.075067407415, 65566.02086878338, 95461.9791130794, 4712.557533137973, 91449.2491620533, 97795.65458181467, 93946.97625631852, 7863.3792999446505, 71511.25895388954, 89246.66252248708, 45337.676041007246, 58825.85609532649, 67655.05739611814, 32788.393364690404, 37557.13469032088, 3206.4422679570102, 35481.374848641666, 48533.346559158585, 27274.75194989253, 70565.71349541213, 34880.51067237524, 54562.15533698289, 59413.82557954193, 4028.4431514461817, 34583.0820454349, 43830.66592533811, 37717.38106092212, 78072.99798748721, 5147.460454547381]} +{"id": 2030, "vector": [83915.72523892614, 48454.77150000133, 8376.754153665277, 18210.949810092792, 8601.103480754868, 82791.61248197446, 88907.35971212534, 89535.5217140671, 94383.2728219979, 37713.549428875136, 17835.43835996777, 79515.41828733121, 96543.36164578603, 3333.784223986291, 13561.90993358597, 93048.60647116485, 71784.49857982223, 90657.19403482041, 2431.1785766047087, 93038.63510275398, 8370.165243736627, 88260.61564929425, 93888.84458725678, 23916.89529369935, 58166.086241031124, 48058.15552867052, 99263.98680516194, 17123.38765044168, 82348.31194780453, 22914.14247122713, 40272.360237091954, 35814.42030372167, 95351.21456020803, 3296.4725535953976, 26677.47145007784, 59929.72907007167, 93311.75372463198, 64834.074927899965, 73170.21180432811, 2156.640732011905, 63504.26891815478, 74761.88857784611, 3280.741989746516, 70005.16541708942, 10094.722715174197, 43686.61453798928, 34530.84881675197, 12515.589145424667, 39476.296342334004, 28207.787053635093, 66053.34434024476, 81644.51822468395, 94825.09880052124, 20603.01071730839, 26292.76076507704, 26003.48491132354, 26707.292507426595, 30735.610017114468, 91640.43318661652, 47735.30339128753, 74238.07234832508, 23582.248424847596, 16805.189856384106, 53064.89489180377, 34450.23088632075, 92715.48789150712, 43062.662017214585, 89868.49763186867, 38296.87102388142, 24225.300678219675, 91713.31268637898, 9387.844449503258, 98626.84742641031, 17970.98690157245, 48814.05282468571, 65930.07648699221, 76071.2692392162, 7312.810419896576, 47813.80260553564, 72563.4022849717, 47302.11006880662, 79012.14130793714, 79446.77254132084, 71819.78614917603, 88233.68397106712, 74663.44180114231, 79691.15538275978, 48307.57463927773, 33268.75242263766, 56000.83914674521, 63419.37262323268, 24694.652966860252, 62911.1109788442, 87356.99308505142, 1582.023049756387, 42585.92303014358, 10545.945933864032, 81973.93699999976, 50308.007501495835, 32204.602548775398, 36934.342281522295, 29538.600688200124, 34778.19091945995, 38407.70641012099, 85219.86726216078, 80765.74973671878, 42440.007225365574, 80115.95824064396, 2448.0415970348445, 90474.63499219829, 44898.9055534965, 69048.98469963104, 50326.82217873836, 51306.15841902632, 43126.23611999043, 70220.93945376929, 43831.82848291514, 16252.100000634206, 56607.849415391, 90672.11845259224, 34794.09897763569, 78189.12242423426, 40190.286471880856, 99203.64840584507, 43184.495356161875, 41649.78937681798, 8059.722582673634, 56785.22232584508]} +{"id": 1860, "vector": [90179.0011338516, 88817.23970501067, 17473.31786086096, 36386.631869836514, 38762.7685536473, 34594.19300835991, 35819.70455068715, 46475.72282644284, 78494.50253408325, 79.42386124558665, 85085.46161795943, 61109.849726139786, 49770.86417942413, 48321.27509467302, 59926.635756615186, 55658.91023986328, 73435.33319934146, 92683.03686637533, 93533.23163894872, 61049.07500828395, 75708.60166834918, 32094.761528776027, 44195.645791463234, 97050.09181831389, 40909.66815301136, 53289.24896136776, 85640.41512900068, 36222.84417924124, 55016.55256527764, 90965.96035183914, 53531.49975237403, 39380.28539846508, 25652.563969764175, 62438.18418005773, 69367.0461560958, 88224.49041822318, 62712.44476163822, 80763.75921848703, 47446.11199245575, 95689.24155404174, 34355.824770049345, 56403.915508892445, 29266.843842276914, 31129.57063872431, 20491.52986497701, 33452.20716670143, 22201.0376808646, 87604.26477435422, 28264.931014282578, 52311.787976717016, 9941.10168497685, 21157.534902828746, 51951.11817264824, 87986.34708211705, 53562.808740451306, 45569.29758931706, 44342.4962729814, 75694.37708452051, 95795.56514304635, 61476.82844511751, 82380.29223392467, 43759.7432455696, 69087.08132233549, 88264.4732716139, 47046.39957241018, 56949.80394873742, 57564.57493328676, 34278.47113179621, 11515.851734988903, 25076.862455734285, 59584.84127989343, 89775.64212362144, 75407.63339341823, 92348.2902513662, 74613.22612624534, 21401.924920604975, 35874.83521248989, 4905.357585200032, 26454.55793439675, 99713.63374882283, 77572.64304519931, 76597.6691387502, 42565.90682102723, 4467.968422901658, 92273.50807513026, 31590.411657868834, 94858.09962681565, 35321.450387313555, 2581.8947374601107, 20742.462465108878, 64780.34670221049, 72254.62946996564, 97303.44902251092, 42639.267895863355, 25125.140831570236, 69722.00540778329, 29162.99577717473, 95717.93247803707, 17363.385608519256, 2220.2838677203963, 75693.1241593888, 25080.50150621505, 82306.31913979635, 32851.01616028987, 48176.243336941916, 45010.477878612124, 48723.73878520647, 3824.9959099359353, 62149.09733301298, 36894.12769643871, 20846.99128852391, 9495.265713513978, 21236.57167848495, 46697.02329558737, 25282.637978110124, 19429.589355906952, 53958.011040859696, 35929.222393485594, 43897.061308863595, 21295.17772666466, 7247.017473227946, 92007.35290936462, 89900.18631489317, 48062.84482334672, 43348.51284643799, 32225.924592760603, 9688.119970250553, 8563.788066559875]} +{"id": 1245, "vector": [58161.42290697215, 13747.684853688603, 53659.399814000186, 26324.263477942444, 96575.32032442733, 77912.26435833056, 10262.097708041118, 54811.68877169053, 17536.610887524206, 9475.658422233024, 15895.790839647294, 13606.244872824047, 96744.43023347348, 35852.60817962173, 39287.59862343951, 84818.22093271647, 58758.45841838366, 82131.61916975546, 80595.4211909791, 2141.6627747209427, 27400.604452097254, 30853.162020744694, 53273.33049305398, 80532.66238256493, 79506.36791133191, 81468.92497762965, 88049.77027055509, 20310.74617934141, 62280.574149765496, 4810.696608529608, 68478.22576836364, 85829.49927545027, 29879.81162102049, 56351.83064896013, 29266.006391486364, 1075.3191669713335, 8580.238396301498, 81144.21299076268, 49881.72084626602, 23367.67552830421, 39344.97901607006, 37803.79773583573, 55829.014105875576, 50324.92767804092, 82794.25273569446, 28956.538437674073, 71070.82793396433, 24479.630661914965, 17903.003446681098, 90781.65336836386, 79859.89572201167, 95275.29986080113, 72340.18348982008, 82282.36153630615, 15885.530149825388, 20990.96321202881, 30849.954037637726, 80576.16661187296, 43942.98155213767, 48951.166757547646, 41868.80457176123, 1179.9383212666603, 12303.043695374605, 14392.600050683946, 22084.738793032764, 79830.724531305, 51815.04369517389, 93328.84112203731, 6812.088090378876, 71829.16658529853, 56173.41658665993, 30521.918989529608, 95910.08819527946, 78652.1255261042, 74490.14388910546, 10671.07941519988, 10510.602386900347, 41941.96504209365, 41618.44349504125, 50517.44337070063, 55672.168631151464, 19704.609299340136, 21279.76084450587, 4371.760695499594, 77631.16198487082, 700.5231038409887, 10578.944674482627, 25104.07284609948, 51171.12130578521, 16043.857746699152, 23752.43653493593, 56935.40143996144, 23358.485455288002, 44786.92032320817, 41334.12640697885, 81255.00195743087, 7692.564316814365, 16027.43008983014, 74722.51488269748, 13034.456440065545, 28244.785413759866, 93293.16680608047, 46561.69942371767, 2193.8195955360175, 816.84072943633, 36993.96532442873, 58207.014664290225, 23747.683773035587, 73334.58152920083, 29378.05380342733, 46543.76734640314, 46470.43659284548, 76125.86538100574, 99565.85731658095, 93823.86039391728, 85435.57274313775, 5280.549533995349, 49614.451817076835, 48013.72021497321, 15160.60972049147, 33412.62283023866, 72572.70824945754, 29608.30676118903, 37376.110332814605, 9599.644333286828, 20732.602798182943, 19285.467520050115, 87760.15656227963]} +{"id": 382, "vector": [15698.054036410636, 64344.89401337323, 42690.97826406322, 89581.43027970842, 20838.186051120723, 43243.526444857525, 63489.805460216165, 33881.562410165614, 35854.59790703878, 87422.02369020753, 21910.69958381503, 57219.6304028058, 59065.017197846755, 81889.69408915809, 36534.772426475916, 64898.67374276712, 97523.50560339201, 29727.285571685625, 30628.172865824166, 11977.547488919483, 60688.70862189056, 33829.84793815261, 40514.78066594839, 951.8140350107008, 70101.18353052826, 21222.19089980485, 81113.74453032434, 79448.89121002037, 85869.5866745864, 73230.51653046419, 52463.704837000136, 24885.834634672123, 95731.28618303771, 28621.943346596734, 87856.29352939622, 81571.65821795589, 14938.611944327396, 43464.06379427596, 50297.2153109935, 32453.492322883336, 22519.4501950583, 33429.897814626594, 74039.2805026494, 80587.83425582734, 53197.88176862611, 11427.121153040987, 83882.33312910616, 44852.15668917239, 12104.264249194452, 38110.637894724074, 29233.705666350717, 85249.61072330007, 68081.69732871251, 5829.596426035333, 67993.82152791129, 50413.70772742959, 76372.35794744903, 38611.39388931834, 58564.31622081919, 49127.79799048789, 3870.88772609423, 33734.28073025323, 87052.38017523395, 31868.874211781873, 11457.965625951816, 99592.07680116006, 73326.63177558982, 36132.580864273434, 51102.207721200895, 26645.11558757634, 99039.95589586877, 35072.42124077933, 22508.371422935015, 88164.16487999281, 67209.1495019686, 82461.57665061111, 79730.93968046815, 71973.22833379815, 17678.013188590412, 9104.93839512172, 3697.148856115795, 56008.7216429953, 54491.66877439926, 85597.13142810807, 22553.346570933718, 31890.219266235898, 52684.17741754421, 28130.706523582703, 33179.93215096105, 57381.871076576805, 56004.92003563492, 43077.39898228838, 3632.881167144941, 27364.56532410927, 85550.15883793983, 59286.27155458492, 14801.122519115084, 17446.078355219874, 48783.73689229957, 9198.126541961372, 9523.579133329597, 55655.57372710196, 20645.9725839339, 91864.83914837024, 67501.31091859109, 56117.67972349311, 18408.936879295135, 89914.73310328071, 124.12467614085764, 79176.3527215346, 99544.84454026142, 86984.61551192697, 80776.46181434806, 65982.74489634986, 7443.96108937162, 38707.335826095536, 26988.688248761428, 57448.04239945116, 22270.864717597116, 27186.7080530521, 68418.7894812716, 86484.35364582165, 14652.349382033002, 69648.97745257574, 76232.37280904778, 87175.7260514941, 98520.43048817369, 64173.334670062795]} +{"id": 1267, "vector": [13032.45280740556, 60522.58660548381, 74479.88934911693, 85123.06800821766, 44655.68112097423, 47576.99390314184, 13529.340117153532, 84138.13168798518, 73455.9638600225, 31140.65671295555, 70565.22079874406, 34312.55927647191, 67186.34498162477, 81358.0281548459, 93218.15316846392, 35278.0433114383, 91667.5701111531, 65904.82249100484, 5762.067110747294, 45596.134075384965, 2672.023914337074, 87924.68137620995, 92475.89843977834, 66492.56144387687, 40327.62091268488, 64749.0137123183, 54724.63703883816, 57060.75493589505, 23775.845023931808, 14770.294502305815, 6627.5549339410045, 65655.57700766463, 62155.17815136441, 80005.85037218347, 69513.22371890115, 91760.73223395673, 43208.35105997434, 93163.62103716655, 42692.41123446824, 25821.675068533423, 85472.7250697604, 91285.93619506287, 69730.91761045849, 49041.86924828252, 94466.62364259112, 42790.027595540756, 41956.31072221009, 43290.97598834305, 14235.286135446946, 23748.784705313086, 24972.385591002978, 56726.9738900011, 36595.34355675885, 92325.44205231126, 78385.54306092391, 18353.344163841044, 31710.09327850316, 86895.97103729464, 5230.052357346571, 79907.56563623824, 54076.473564372995, 94448.09526041646, 64892.74765156361, 75984.16601715404, 90809.05066121282, 69433.44017827357, 17596.21081150954, 20695.93322557822, 70072.78826152642, 77999.17066737018, 8007.324599663701, 81606.16407811825, 57735.373161747484, 25241.597833650732, 5783.55753084483, 57037.91294163928, 81167.51814170646, 82127.42073307256, 2503.994355129413, 72037.92492043902, 91805.46504031948, 74745.53895540201, 31859.44748313855, 24386.52528695021, 95963.08900923796, 88851.05323715195, 93279.4467053454, 5899.790834687102, 88983.00325214509, 32950.73399408256, 766.1216510681523, 32364.894051065497, 68851.94591234547, 71676.1074116219, 32491.105220620564, 96958.95507572542, 57913.02744310796, 28371.043937955354, 28252.49307945069, 80857.30460573423, 81787.05244629818, 53994.65485756313, 78188.87255624331, 96772.12332153202, 90673.32249707692, 43903.453683488966, 91852.07618260631, 63060.9793873777, 78500.63743813832, 65063.03710920636, 7402.606850666371, 11787.764147451107, 1452.6732495996896, 67696.69704094852, 65841.85879798864, 3322.319415461217, 28158.715213609998, 46008.23796571804, 23758.88880725332, 78683.71760526906, 71347.00009473616, 11508.104268671581, 90301.81661309244, 61412.63626309908, 56215.86088953224, 12666.6325814198, 20275.54239786006, 76554.52234971736]} +{"id": 739, "vector": [17342.583785051236, 63879.12803597602, 64437.795768661, 50319.23967500005, 5892.88265754917, 22237.12701063145, 84469.01542140519, 13899.741318135882, 56051.49386682995, 37687.42611741911, 2983.199212984888, 59112.93908011017, 57543.117935640854, 65436.94277931978, 92889.12952790862, 5221.451016972323, 4446.700143380455, 16413.96310236626, 83982.9979091796, 31723.457832007796, 14614.236883669319, 80030.12184312692, 37295.625163589364, 34698.68310328006, 72495.0797974275, 67176.03568004597, 16562.504273782208, 37816.89932063246, 72551.94753376946, 397.9257479011955, 37140.17275144411, 88684.76920060361, 15725.135757042275, 38795.740600905905, 48361.94926871151, 15223.676273854015, 58814.61009895013, 73086.19509459643, 81347.82102800877, 59391.34866975594, 91016.61797839239, 82394.21270654666, 31005.4062035238, 3026.025022335732, 82379.44715748928, 14134.22101697801, 37525.59612751344, 62649.368147863235, 14258.505878225835, 73091.84965799606, 96517.08523677988, 79648.37138227717, 90851.50388157491, 95144.62332891335, 4655.205650962646, 53127.96304657081, 5243.810397269055, 7522.68802378383, 64650.30871761597, 92109.59466158767, 37609.179384329414, 11596.401195547856, 91689.46673552261, 68773.70942176423, 29972.71223919541, 1884.558813757653, 62886.29992828574, 53583.14901734264, 71302.57001409454, 68361.38524163186, 49953.72183068542, 26266.24241973483, 65765.84267517191, 12999.599380152738, 35329.8533260219, 92766.06320695583, 2319.980356461049, 52236.86911079274, 84163.45818951611, 81298.76317903641, 26860.720542895655, 76278.48960070261, 25426.31114134617, 58327.777520161006, 40877.1829447209, 24266.33118944259, 19051.382046825493, 35420.533912878425, 80527.63952355491, 1957.59701180932, 26983.509951146378, 81524.93043633143, 13544.222893586677, 75432.39280753695, 22136.162753180775, 89816.94370552893, 46382.273835706146, 58484.679745006964, 23251.580643427293, 11353.871032933594, 20409.912630278883, 76211.63235419504, 63154.517481765935, 88509.935851043, 78760.11448594478, 91964.93818578898, 34936.09833989123, 95723.87062191757, 32891.834069795375, 24363.5798196702, 27779.611761165957, 23447.615510871066, 41566.82808562675, 1222.6563653725652, 437.3035264654601, 97000.48673746748, 75111.53710179073, 88857.53248194294, 56103.26334762935, 83423.41770670778, 54955.47577992488, 59180.2772186245, 92968.1850253585, 66245.83741286845, 15181.999126595858, 15380.951725503766, 43079.40633533691, 58239.57807904497]} +{"id": 955, "vector": [74135.70239453326, 15913.873723905781, 51231.84468770682, 80312.37831393168, 53942.140172138534, 734.7538203433701, 59184.15453263768, 84863.81529442527, 7001.790134023733, 99287.90734428063, 99419.10507196441, 87673.2243565731, 56106.001033495835, 77068.78637690491, 57621.97150753171, 49472.66313821412, 40281.08810570401, 49496.44348750531, 19717.468354417877, 40057.58800733326, 94362.10560368646, 34954.823940999035, 64271.74659346413, 9482.599585138296, 9949.486676730956, 42287.51425179477, 34845.766552200585, 65161.8342278345, 89068.12646190244, 53804.55056570555, 96034.14353825289, 29448.776745749637, 86957.29016419513, 95869.10312045389, 9324.873549399183, 11705.094311080944, 13778.515891152432, 97220.64937631495, 40381.649651915155, 10901.272213962033, 75154.19914281479, 76432.66574597031, 81547.83889592519, 64565.75090611455, 20759.66244136339, 75213.36398012182, 50665.02390111269, 14162.152764474622, 86150.21233368648, 87510.76238125026, 66032.27329501302, 98536.92806792057, 72558.49710996935, 44344.7417689763, 63725.46074316304, 36122.81416742268, 12719.464216178334, 55419.08835386027, 99126.2959813233, 85831.59267586698, 40613.85815886417, 95749.96135149641, 37444.52734539345, 95032.95202766232, 54064.10245559283, 21223.061361758035, 29143.027965245903, 60652.82665247213, 39104.66286481667, 24093.275998753317, 61714.028537433274, 97768.74411757107, 48452.96671903374, 63495.318101718214, 98675.72856919773, 3116.1926346556565, 74415.03722467652, 26903.922203710874, 97711.91302787384, 58172.047166215576, 52189.30664203467, 8075.1136935595, 77762.7003808875, 73645.37790700186, 19626.454155019503, 93937.68726402006, 55429.36035485021, 89439.57767481286, 3475.5591151315434, 47474.73523568183, 36791.04909145745, 93900.18911455771, 84699.85266444023, 52729.745935756204, 82138.86332584958, 40590.58312919414, 81006.35608546792, 52840.30766216886, 48964.17735136792, 67338.92899609626, 39860.92405156464, 11664.763853227245, 3092.5765161253894, 31700.418546288623, 6309.116546810922, 67202.2966734673, 51975.62891939836, 37736.0886445675, 32947.04302842718, 55901.645131512145, 41720.490278755795, 79929.05260576594, 34593.66410673349, 80714.4745231601, 39358.68847342154, 52464.61915371153, 45745.32751850654, 43567.60948395735, 84621.25981391838, 97027.7606143268, 58436.10644805412, 54787.74585314119, 73897.42465700101, 28813.62373923072, 18238.593908578947, 93484.479017114, 28252.614375829922, 53655.051484401]} +{"id": 377, "vector": [6181.167172246815, 13387.517196436682, 90478.55528904524, 78339.29671102407, 97907.9074803903, 34204.710264648565, 93230.70087335363, 57361.49451436833, 38729.67789559805, 62062.60765380681, 19148.246899247813, 14001.648671810008, 92760.01280612333, 51024.62225398859, 55484.91980937329, 91337.47577364037, 2653.71179801569, 10957.528649523696, 82333.40499726315, 97781.80639684734, 45615.584771486996, 22452.986518515438, 88530.5342363891, 83833.95204151033, 24258.105003914978, 3734.2190343727766, 62031.566109887506, 84570.65709820992, 45336.453690679846, 34313.12196368089, 68802.28093741403, 83630.89530709333, 48280.27030343458, 22069.609213031337, 25474.977320756876, 52967.71160524776, 36777.32317839293, 67965.42707080788, 14914.852807988344, 47541.936477035764, 17093.584623847135, 49343.08573914516, 89515.02707100016, 36252.46559347157, 98039.39075255318, 48330.20418500925, 30040.89118890164, 47796.96217028872, 9374.456237835571, 24.87551145294864, 69306.6621846738, 27695.83978869885, 37636.64037747841, 97081.1969167608, 6223.532194259152, 82286.47511359362, 70661.5956559405, 27320.308075407316, 97967.9247526486, 40317.081413818836, 84883.89608304172, 1192.8295925706323, 3862.9261198158792, 70967.34326853245, 5025.14616972618, 2700.0909592709068, 54328.518009823776, 66957.41430190182, 58774.308163340895, 71802.3103546073, 40994.75076854586, 93180.52472422959, 21589.765437528684, 74511.98430666824, 83003.26610221251, 31615.8644526903, 47897.33544018024, 16453.573599299933, 43194.901326209576, 58237.60249415296, 8788.453034734079, 12514.490930028, 5412.916490563902, 96376.28995436647, 43078.12372762133, 64707.59164583802, 44694.39557424031, 23282.91499098486, 54262.90761452054, 22550.225688001225, 41107.12643724559, 77366.12015903133, 20082.63086816784, 1396.4755965200416, 47658.933025681894, 12373.747486905251, 81537.97527115738, 28298.72620384608, 17159.3654084517, 89735.74673191686, 3582.800301670963, 29563.507547551195, 4447.23741351084, 77934.64994955495, 78631.41334707018, 79909.48201998182, 74722.2273866798, 91874.0073332505, 63720.889647644995, 99940.30144338317, 23810.778833063574, 91577.9927019933, 33876.412348527316, 98879.63349828294, 73579.59746315885, 53987.93512285616, 3772.6301217424816, 41691.572827626136, 87500.60369376438, 44659.722690227165, 71968.4490557214, 27472.44121364214, 3197.486997723309, 93597.64808786022, 37474.88444568676, 83658.1761446599, 18817.772289271594, 74240.3943321634]} +{"id": 621, "vector": [88010.7927327726, 37181.26621337821, 50130.47379545709, 21826.698299031912, 91253.1243359372, 81515.70276388504, 66833.66996883831, 79450.8434418869, 54332.463798354256, 16247.446215256612, 34677.067683700894, 12717.42427582292, 70143.75692829108, 74308.83790550422, 64731.62830776091, 7162.99326574702, 37248.037689952405, 32924.018896583664, 16450.424417767128, 87522.45186600514, 65616.4505557275, 52044.058551855946, 50077.16406876601, 19410.072528370838, 34332.214080500686, 79943.22597758332, 56036.4327225772, 55944.21966837605, 19451.030101361754, 38615.90956767929, 14643.078954056766, 60856.62260681746, 73334.36810158672, 5348.014457586514, 24878.16329697705, 10772.807983785471, 46817.38144215091, 39401.963995970145, 45045.164095753156, 86638.64574858946, 37103.794956526195, 75940.85079767628, 37766.43160033697, 65511.67171205562, 93808.02411793658, 10478.89965832799, 7653.112879428115, 99584.8991061937, 6030.858833989283, 56.59379394985508, 77696.67271369816, 94312.32152000157, 60771.88142275991, 2807.3153526294136, 91858.93891268894, 32865.04421437314, 12284.697751594953, 46296.45168080233, 53133.80723648581, 93516.9442578574, 72445.98797020201, 41356.53783981451, 56459.36749727447, 42821.8321113253, 47911.357373998995, 58268.943812564525, 56133.94487182065, 86892.2547733164, 36947.9680986988, 51731.181465711416, 87419.22565227313, 14133.049638265738, 48711.24703378545, 67407.93000470074, 49806.52373357294, 18211.936529219798, 4498.497321086603, 67472.44858297842, 46183.08684087611, 30900.527847734094, 1754.8471008077881, 49498.79478054183, 47401.7945089914, 72277.7655410185, 96988.06143294556, 38650.88229952859, 26607.012550053645, 43651.65958540976, 98159.4546069052, 51567.31130222038, 74642.12839926506, 44614.811122123196, 47475.19829516962, 44826.06914967817, 61936.556110316196, 73522.71355429126, 49562.074734098846, 8071.771219727552, 68895.43339458691, 511.86412391219795, 4716.440742533035, 90623.15610721527, 71805.5149401253, 840.2486370204798, 16455.41991964389, 20224.981813363764, 20807.74689495618, 96096.01195666159, 8983.075753957426, 58785.268385972355, 4096.171073581522, 32506.386597900295, 89885.20377643917, 79681.05705588353, 93728.93362266223, 33933.820038688435, 43481.79427244219, 50426.44738387867, 17183.689935085524, 3228.9792596183474, 69146.69323668738, 36101.21315653184, 48400.24296901164, 90506.56685456201, 98202.99048320945, 51915.110039795, 2671.424980595061, 53243.295793185374]} +{"id": 1379, "vector": [80800.27654227188, 28544.61594111376, 36758.2114220381, 42675.61376730804, 50721.60513010742, 92865.57078084596, 75745.92085872933, 60091.08789576815, 51648.961032235165, 51731.70155439975, 66206.64854975694, 65809.12336765752, 6675.524270938859, 5981.738474956522, 65662.42746904341, 15434.496469671776, 84331.34446942763, 9499.361739958667, 54014.38215669384, 47066.43711385782, 22932.374669179746, 47328.29742434285, 88980.11827325099, 75630.18746084072, 52902.15180278101, 52845.33979467917, 46281.42832567772, 67481.59256827705, 47792.22553866709, 71255.90644898178, 55301.77838865591, 15294.526089531868, 3815.0425800106636, 76144.5206129364, 92994.36111267399, 63291.05607636396, 69031.89034082627, 75551.34867887526, 49155.88970775572, 99472.34903232353, 35567.81224731215, 91291.1928129539, 3343.8012953043717, 57575.64764330202, 61611.585515953484, 48043.3240841198, 80950.20360739641, 21655.845242237036, 49290.71483423103, 37548.45321529886, 9993.726019421656, 22935.447496185923, 23953.385803076155, 38646.31941120358, 25042.1343429437, 51985.29002383926, 34131.48247827899, 55396.31633870492, 41931.36375327097, 41900.589386989115, 6903.541159826532, 5699.362768272876, 55778.243799452466, 11878.017492007453, 5024.551073987327, 25141.953037821062, 44163.87620226151, 49248.93232325454, 26459.5984332478, 87999.85676076724, 57987.03641727293, 63434.67370369483, 78279.85767351766, 4844.73760182802, 33064.31919387448, 27003.82406214863, 96314.69670619743, 56024.42503376363, 3012.0396067032207, 82440.85605540531, 34188.53796188015, 19538.171498681444, 53030.27033607318, 76545.55290517915, 88469.90079082995, 39560.87539483782, 30431.48453039236, 45958.45384714674, 39543.64716742588, 1064.8259282317385, 16744.421823457655, 66245.1223071462, 80070.14847136002, 44369.69603311408, 47406.86002249559, 76986.5839029628, 13268.246194496958, 73688.56025851174, 25418.12675932983, 31615.76303303454, 83106.28613655767, 33569.00038501447, 34453.00870078153, 97036.86824207104, 14378.951443384858, 90419.48214872317, 4836.769995636037, 77709.51397176844, 3278.215708782284, 30165.881709444166, 66498.92167590666, 27693.861988237335, 93076.14454027182, 21308.651664143596, 5911.649359401916, 20576.742336252486, 8336.667859846992, 261.4971694196444, 93022.36581171791, 9725.081304856842, 31844.059585739615, 8038.586580527774, 9265.556481792637, 23910.803673898474, 21444.878285753188, 44507.69099379894, 40011.379559775196, 21538.123553907128]} +{"id": 139, "vector": [48418.82329987354, 25173.7740706086, 97135.46463586677, 60309.26654089296, 65387.10633955882, 24115.699203343556, 9461.705456762993, 95621.11982841269, 20663.405262925848, 40703.314387632774, 37003.340550955974, 63273.79277172493, 52217.74580341509, 13527.787825081361, 21909.868082970173, 24430.899369142422, 33376.639663095186, 6739.488249737003, 66445.61250343852, 57877.94022336806, 972.9407853063865, 13986.551905528211, 57905.47723526864, 48800.95100148101, 51481.76358824351, 88951.28537267142, 17523.096727832253, 49217.48490119711, 61241.10886451062, 15399.182946308809, 40593.44728499892, 6023.927334063661, 52711.55361692396, 41946.580657156795, 62757.2110430115, 77460.92654242642, 43460.78203890075, 16648.310693309366, 61603.27463636289, 14127.884397368573, 85970.86948575694, 68700.47037291677, 65035.14711973232, 59169.67333045233, 66595.98930855386, 34281.62662283979, 17143.131401361723, 13410.32707602361, 66536.66938011604, 50454.25508185077, 89524.95753778542, 16447.774512031177, 9384.552762986243, 18893.95307433238, 33968.7288880863, 67423.70481458787, 20822.740241478597, 34378.200824268366, 2371.2868910403363, 79453.46473480023, 45221.27687324118, 57443.86031214274, 60361.13353319566, 93418.76237927702, 25547.9293292867, 89984.38409078313, 65106.399213832454, 25443.282995852802, 46465.9564947848, 95034.71364255242, 51612.92469384338, 40946.264136401696, 1121.5691722115673, 77369.4551570949, 9552.63754552076, 27591.642884950517, 44015.04997724582, 70191.15963533807, 41662.332327904674, 64713.38862264249, 16906.07332571822, 37173.45620949021, 88715.95478858305, 88496.13540424069, 68343.77431326831, 85453.13628510605, 32869.191390976215, 49885.42675807232, 37754.878405969284, 67915.4089955484, 40048.98900480821, 47053.5226456151, 43952.911125677696, 81384.38883324884, 88574.25061661458, 5317.281777476434, 18386.283893709122, 60500.57533303077, 95230.23462819918, 72753.63665192318, 48458.63072417866, 13090.006771449469, 92709.48471931102, 39104.770382974275, 55400.76774732085, 32997.66883399519, 2031.3808683653733, 94841.18863304575, 82651.10554029989, 59914.47414741641, 19876.71390288177, 93114.98689155429, 633.5346872592296, 81095.23983483206, 91252.41361201351, 35721.07068157655, 35290.506580774185, 22228.36823687493, 89809.08376921322, 38693.76068585054, 79862.12877076036, 68719.2596653289, 61669.22409779908, 33113.29129951482, 3590.6526060808797, 85016.26907001907, 59461.938167717635, 96892.34763442788]} +{"id": 1958, "vector": [40311.483035652716, 61688.01775799592, 58926.11413968708, 61029.939220636144, 68399.33912160524, 64442.19849190033, 50932.36011542741, 45502.58771565205, 95750.68726485428, 87522.6302072072, 17515.292004052608, 52016.04935792881, 62188.47837951952, 21603.818480043647, 85097.43142120488, 37504.54561070574, 47084.071450462616, 28787.92355434695, 29752.435949760904, 42279.06843689211, 28211.124804530875, 3625.521945549115, 30700.647657661517, 77254.4751291172, 31050.10640094029, 22071.620455584416, 45866.512130501294, 39398.48650117299, 18444.601264200734, 59143.7159118772, 78506.93445660164, 41054.98031311293, 54110.80225987733, 60739.07939050202, 32040.979179450835, 53103.48869217958, 5072.996489491199, 29112.53037986806, 75755.29717386354, 80018.3093538323, 78202.4958975787, 95519.97698711931, 73968.0125972993, 3981.2415951864355, 31610.771217109766, 35382.33007878039, 44236.48013713879, 85853.22380077348, 33797.288271672856, 88797.13536698175, 21153.771701637626, 69516.14267050785, 89099.2496170238, 85133.14198917372, 36703.32340903951, 99656.66661959434, 59766.2140391812, 55791.338089265955, 3207.8795267327932, 68388.75820942997, 73982.25655851224, 67053.0322583231, 70329.81586716503, 82299.3884158151, 87818.75124110183, 84511.58882525083, 18871.311299307516, 69043.79525947038, 47551.3049221972, 3374.1126586831483, 71562.22807485005, 36938.5047017483, 69671.83096106583, 55889.075279164645, 70510.21241675488, 17856.68622569696, 87225.16978122445, 14433.227794972969, 27920.624565284903, 73008.15494512027, 91502.26654410004, 95155.9153892865, 54706.99730015454, 1830.0713453346384, 84402.25239671758, 73058.35187728264, 38389.559346386806, 85910.16084719109, 31567.967416723142, 20309.75163544918, 7641.009506934993, 70219.41930878864, 23247.196196719044, 74802.03266878103, 6233.898579021557, 34142.86123826685, 95191.01553242648, 96282.55924229116, 26189.015273995497, 97678.0124418688, 58315.80974437304, 36400.22306028115, 99410.64128746108, 28298.71564090437, 46654.91106543338, 70894.60227507082, 11702.701980574582, 89497.57380312751, 36157.16677381084, 42573.4488058424, 82958.24542636487, 69686.24790125, 9066.292836290391, 35469.58564539861, 44964.004340020416, 69748.49756164981, 79272.80342800425, 34008.96742593855, 75744.5480595755, 13044.36674554077, 90147.02738761027, 62039.353278559916, 56272.28131460096, 89527.66854840622, 78517.53243279713, 66941.41084597661, 68775.12664109605, 6579.257251338855]} +{"id": 476, "vector": [45726.80399926197, 17660.670723237516, 99462.0163454675, 94696.25817024923, 72739.19053348621, 67588.15002807637, 78900.9536724074, 97206.05400212416, 50110.6359683116, 22250.985294914106, 22889.358984954066, 4793.188379994184, 31037.88989001861, 60200.09464433697, 24910.848063009515, 68458.13735906448, 73602.30716597028, 48194.45935240161, 46318.81417866926, 53852.014906283985, 40276.925465497116, 30883.524017992968, 83814.35648743795, 64222.332732929484, 98761.74043978332, 99588.7587712333, 71744.99528741711, 73536.75958177136, 50073.02631794468, 86693.99790022131, 16435.90021637852, 82236.8376113506, 10237.97978035269, 8271.930664605554, 28109.9824350456, 48633.44642587932, 70168.2486732479, 24684.357515469736, 70316.89297489804, 41829.366319942004, 29720.93566263396, 18872.33607527695, 58931.714985074694, 74530.07485970577, 40131.04214148475, 9650.804177494376, 16865.107357237965, 74841.85373525765, 48510.207740587655, 89722.44426247758, 66862.94974136601, 80980.65592398001, 77441.84565893089, 86907.03581709365, 27374.70852938483, 70682.96466082748, 72407.2054210653, 16676.607616174842, 99790.0323080679, 75615.46863303008, 20648.467093999312, 83184.72534157606, 37450.17446460436, 66431.71706140721, 69893.80924296826, 72488.05053654984, 12936.751293736814, 69812.36974920548, 38728.21422826614, 32027.94813916211, 58816.85200743728, 57958.27201194929, 28475.286775478715, 46024.76808699024, 30297.658286423677, 24576.355820848028, 54897.618561768446, 15592.476633498487, 49101.9046645068, 26610.029029816353, 20785.97817620459, 60607.360142959566, 18090.687505797665, 65076.99325410934, 41196.87098155819, 71846.03090580618, 80697.72791527984, 6401.21833021885, 44967.2734480615, 75468.39624667961, 70747.91513131882, 40563.96878188106, 48529.51675352495, 45540.25488037621, 10167.048304545046, 62092.001766566544, 642.4257265049249, 32888.58639609498, 25583.800927858545, 95539.02324590107, 2352.209800570393, 39308.0640235852, 23494.162826225627, 35123.23009576532, 80921.96045869804, 74646.23525193673, 86613.32056591999, 32357.006793432975, 52060.06958479455, 88016.86584118326, 50872.01731093497, 53870.35400976602, 59326.52678539766, 35434.91661583096, 53819.02296373704, 48474.08725423584, 18288.27288309092, 22356.05792275349, 32244.82780325204, 17474.256197164606, 42039.34882550001, 2658.888719093988, 52095.33164137479, 50778.22430110681, 21849.99868617071, 37735.99145672897, 31768.791299856402, 15303.668735669573]} +{"id": 45, "vector": [96955.07626130774, 30442.858429628104, 3291.95594943541, 28098.64970464988, 9558.440605727614, 9451.475926522857, 6005.381345379135, 6096.029277201431, 79457.79501820519, 35482.57990209546, 84334.80653573328, 32805.596059558215, 21464.550530697536, 1206.6126617651162, 97650.79727881824, 53967.07306335049, 71585.83073140396, 51879.73123437533, 4843.112103106295, 38698.463685164694, 51330.33253320364, 77915.25583577441, 80083.82906680959, 57417.38246359288, 81177.47315824749, 1365.6728429856214, 48293.95748275779, 3365.010746740005, 11471.362583113232, 3354.116871799107, 84934.54734024269, 84222.16414913406, 9770.372285716978, 95229.12304673536, 5239.828444398709, 10356.159904716633, 38097.80648135267, 14831.064928172033, 47884.65994144111, 38587.30091193726, 58401.62509660717, 2475.5454220355186, 80282.06015559853, 20328.967745234273, 17196.7378688036, 89127.89084742617, 46082.60342791471, 14073.630711246376, 11670.651191629178, 68075.33142650586, 89.82598927919084, 8602.159218560857, 49473.81862726354, 48395.84232904525, 23792.28772742431, 56784.15518893598, 30160.718420499077, 31481.401759853044, 98918.34055748452, 23942.941228796775, 28990.98852653351, 37939.06885432522, 91838.88846599581, 23302.637704400055, 77479.75701374504, 8447.17775160535, 51798.420148546684, 8793.432317189798, 9602.826224873563, 73967.84576842366, 18103.126590616102, 50954.00826769038, 72125.81776378842, 23405.817169169786, 35211.867835737474, 81532.24149026403, 72099.20123873779, 85186.27190240177, 3365.176538917625, 90402.39311409113, 84418.07459687044, 83549.98827631406, 44544.56196871409, 51257.26187206774, 34900.88573882899, 2346.05602102963, 81841.30871154595, 90557.34696117042, 71341.78589243941, 15641.678940493408, 82891.02083259508, 73238.05285437345, 96541.18926761388, 72985.46122373366, 41284.045218792395, 52362.19172038875, 56792.638530065284, 16726.470859420195, 32216.940042091723, 83479.92283723503, 67762.72649952603, 50373.63063319983, 55747.03282966127, 47412.97835421453, 80674.16717966925, 63719.17133958035, 54454.650817604714, 48444.261157897716, 8368.644527049262, 73236.88937717104, 25237.208643414964, 71848.33203687044, 52462.18593156481, 5539.259837978106, 20823.56620065272, 75856.30323882766, 53448.14967090265, 35068.90394098159, 21214.76465313251, 51061.183631619126, 42399.969875717325, 34145.67871154151, 23588.140726924456, 60329.53730593024, 98271.79964694293, 89499.1294822449, 83075.51856072072, 83616.38479508267]} +{"id": 273, "vector": [74378.11196419857, 51612.062633215115, 63258.281051345286, 73315.062378286, 73634.9686244373, 9957.103909230525, 44821.95372399506, 70083.62979508069, 42616.310677398775, 75484.13561211227, 82998.04211180139, 38518.42288803665, 26030.192752410196, 45547.48263538836, 45031.67603206412, 22906.55430930786, 70966.68120984768, 8860.188801769375, 44379.74223404384, 84471.88915697265, 19179.04179908214, 41350.73924180445, 56251.41970004647, 2256.4483803360845, 95825.2969322208, 59524.14417441881, 95212.50457174676, 75349.70607773971, 7507.412063614715, 70327.5105470024, 52949.3132047712, 16639.69265749423, 81693.75247693389, 62105.87079892777, 53183.03956335855, 20049.130404809766, 33278.943129200634, 54168.98964382997, 98412.65298546615, 18346.749652240902, 93218.02523232155, 37297.108463783756, 89078.33308683787, 72515.32939233855, 21934.616133136675, 39448.0028484999, 99283.56450676243, 96719.54798621946, 23090.941518385378, 90882.64722928965, 59008.26741552633, 53772.07363650042, 12292.00890951534, 91473.19242020829, 10380.343317256069, 86798.74906376384, 29719.38948772619, 14522.89107419429, 96437.3066872022, 1525.3985941967362, 88069.83837911581, 78916.38231988167, 61723.600949125524, 51225.71523440132, 30990.8078559341, 90222.3370157027, 39764.914837429766, 6275.47250930931, 59710.44237097564, 82309.89143367221, 13916.195702349232, 23947.995567007285, 71052.14573044996, 83754.26002528277, 78939.27784318662, 35360.895379053436, 40649.21756598212, 17489.610733976868, 6578.8301067809525, 7463.7376787190515, 78254.77409082814, 6785.623210469072, 90360.6810157413, 95576.86897874047, 33116.67569186811, 14791.850910176452, 97631.25871033789, 7602.561400869267, 71087.5819897638, 21690.515047795354, 88536.31180830611, 31810.743768266915, 90340.061175233, 7084.873618507881, 75778.29349404058, 86724.83017097182, 15539.328494181658, 46084.126931553896, 23460.312769817392, 87288.39880447173, 24484.328618089745, 99557.99009935849, 73419.30527090885, 66545.53854285514, 8162.07293061485, 50055.71845622137, 59580.12624141521, 23241.180672884642, 39917.339492026425, 53880.31619461047, 76865.03075219043, 53085.10280205212, 55291.00814884121, 70745.89958697796, 66998.44721314301, 16670.742424035223, 12170.902148400242, 43477.3994681047, 54284.33794888975, 21173.198363533873, 65961.19582429658, 48096.43433908412, 66405.9618124556, 18105.386313240655, 47307.52294930503, 50638.45735875658, 98537.39976118252, 54632.67001850894]} +{"id": 1761, "vector": [9661.538753457022, 7832.942269700405, 46945.575596270806, 18889.144112394963, 80880.74868826637, 38356.075670789425, 75359.56977549492, 86698.89664385299, 32193.187896276522, 28666.362296951098, 69528.04704580878, 12957.544226583006, 38098.96204496108, 54289.53726402286, 39845.95357756775, 24366.14679586525, 85170.43696954027, 93637.0183339006, 98674.53749566116, 40445.170006659784, 2150.819937237669, 64546.45617557575, 47279.69915216297, 68322.21155411113, 48921.33033005709, 5462.759245308246, 39836.369375422684, 96228.19479733847, 26443.245200455, 57302.32112082497, 60185.11186518169, 58474.94606966259, 26601.124312408952, 99512.67081512143, 69650.95121586649, 55176.376795032826, 90090.71147544366, 96464.82516053453, 97184.4270254603, 54897.561146581975, 75052.80749548985, 16295.684866631376, 80818.25946755112, 75010.18643802487, 49340.6595589452, 61395.79456348281, 62104.20590364966, 79380.78167890514, 17106.01946542565, 59425.75334531625, 20477.45475167394, 70591.08166038895, 42937.55781362902, 89125.44260929378, 1180.8884620204462, 33227.06624246444, 44240.17380581927, 83116.70707520195, 75918.00694244399, 93788.87720931188, 98435.28995966142, 41398.85309134429, 70992.95114753132, 28781.004185026602, 69160.27904262852, 87193.56031085148, 80471.03855276672, 1755.6752283308886, 55171.632394036395, 21943.521503193097, 78156.82915261082, 2535.0326261520563, 40721.060784815236, 48114.60081615213, 9096.374652486516, 87389.94957639278, 8525.622423549894, 69794.6868497061, 69175.5945909366, 36331.72001180731, 97598.15888357072, 53162.8876230036, 91720.09637467859, 41280.110018463376, 18003.589091622107, 25959.27995177325, 37807.743797793024, 60390.239232690765, 41032.08429724276, 92028.66078456689, 37805.27230195114, 58195.24814405634, 47985.08221163425, 20672.04535139735, 46972.832566556666, 88286.09403986961, 56023.42821678054, 5713.61134135695, 7581.95519165229, 69612.99080111347, 84258.00688029756, 92770.75429438557, 91051.96828786517, 40831.45882427165, 6769.31082661909, 53107.906545141625, 69440.6234797504, 67088.61104505676, 54989.295417689355, 49046.36323081178, 24049.729725280187, 98686.15109214526, 84309.24636230436, 31406.930875683734, 7462.201916113431, 94870.72886416368, 94666.41840210205, 93570.06698391821, 77047.44252923364, 65544.26979853668, 15772.528980599487, 13112.956386488606, 72397.43751602684, 17696.3912645388, 18648.273039117146, 15515.960022274034, 37743.61457379133, 51338.531500729776]} +{"id": 225, "vector": [16597.34288931408, 18863.004150389086, 6298.03790654333, 81596.48861340039, 8602.920351259425, 52967.82089275325, 41558.268041563686, 68088.11126101977, 46058.61239640143, 74213.6648237508, 20114.470296725372, 11355.735544267898, 63627.222849020836, 93092.97356686852, 10086.020445730559, 59676.21006978475, 51363.605014986715, 23538.750721422464, 4598.656849999527, 87747.44677674779, 36133.116650254924, 79929.89173366671, 15733.144386099451, 65440.94786572252, 45318.79149631471, 43364.29605869877, 96057.84984628655, 56844.40447758111, 4290.64401220316, 74483.94159771875, 89894.70732516676, 86540.04628593764, 39751.95956701079, 78447.54381691401, 51597.02016372834, 51016.32966592348, 6204.02876166215, 17074.135287730864, 12712.610785681367, 40667.996466531455, 89983.13070870381, 45380.83122290623, 81360.57749436202, 43988.492702750125, 58135.668987844205, 1344.7432206856736, 98687.52597998464, 10980.421868848256, 59836.048600158385, 28657.431530967424, 12948.406103443056, 41457.7089474057, 23448.251125594612, 32781.93416240901, 97735.31524691258, 76498.6299268169, 80626.86299059029, 69736.80480066076, 73577.00573689616, 33869.362087635025, 86572.83202902121, 79078.60073525649, 99072.54124436936, 80822.98643497369, 35505.55901770118, 28779.618297000685, 16466.977764911393, 15002.271449537286, 1862.3930589732552, 14605.801964360953, 5021.601879087667, 30816.653918598637, 67879.97820161114, 26721.364043486017, 72810.17639850074, 13897.884142406869, 80970.91829639715, 56156.511702987, 34329.068112051864, 265.3932683438942, 86997.92456062685, 23035.417713162242, 75585.63545815344, 83296.45334576037, 42490.76015428256, 5464.186994723441, 44334.77229144075, 92948.28324481043, 91692.54696140432, 10200.365290346925, 33660.89224084411, 66193.58210074353, 82000.94535435205, 46639.77274537346, 78671.33189222512, 79031.6341094046, 68002.56312991474, 75607.06447872559, 89339.1532155563, 32536.069162510583, 75845.00772902995, 1950.3477458535313, 47639.201544893076, 39616.78838666196, 27920.24143611367, 7960.118478410294, 13620.723590208161, 13140.07047651522, 62849.88395645848, 40106.382066253944, 19496.1146551062, 31667.140107785097, 12082.737063503357, 97522.82661102376, 61938.80411439412, 58402.33002224299, 77962.68434990542, 91986.0738688766, 79512.76496909716, 50058.78039612448, 96602.1122267131, 8696.408296214531, 57303.93935576176, 6896.543291274793, 32422.199363208903, 58798.90402022143, 77218.89981964625, 6412.310510455643]} +{"id": 1764, "vector": [17121.024465473798, 58194.224658575185, 26590.769177927865, 11894.813955568407, 98682.66666028547, 25608.433653485263, 65390.797204540984, 64146.511556120255, 76822.48772865882, 61992.11170849007, 4721.5813099723555, 57640.834132571385, 53888.37028842605, 4330.298669806875, 24943.01395030446, 74729.46769966658, 69458.51887431036, 7053.364417410179, 18411.34523782195, 69646.32914179887, 41878.471132944585, 55080.97688574878, 45705.3056925248, 50633.67718906021, 56346.902004317366, 18060.647950753817, 40323.956282936604, 77195.79110738823, 19207.51674339626, 24354.53466764851, 12450.897215112167, 77234.54734210676, 27421.591311966407, 4473.154031811655, 5042.105230995197, 9236.67725472158, 43953.71051942435, 22332.83969524873, 95716.52585805985, 20155.363980142003, 17808.530821097578, 82955.63772266365, 46770.92092737345, 94967.42892170935, 60403.052362300834, 33225.51077184558, 97031.55530985644, 80367.28433615694, 58676.671142700245, 21778.529136170855, 31652.550207937, 25904.70506305307, 28694.69585489891, 36818.41523616342, 49135.14201393204, 7882.135401854728, 15862.810462790521, 87948.90107957103, 79299.23633313947, 87068.17818292617, 24073.673934684135, 58047.736972801824, 5146.268429259871, 75321.04206201374, 3558.349872853106, 19100.388065510022, 96189.13165641925, 93094.18600126886, 406.2479735761015, 95903.90976750357, 37468.671179017045, 61267.40654672957, 54824.84126639252, 39339.65740122395, 12050.383299400492, 87378.67042210733, 80592.51876584966, 86170.30474972846, 45515.04377691543, 67650.62141104677, 24481.801365967014, 6849.859753325804, 1077.6671845115548, 44362.914174113655, 44414.401281699946, 55265.20364916712, 3065.6532272190116, 35119.88850055105, 48387.39724651549, 77961.23165681779, 67911.23299453229, 42041.3900715942, 64318.292972147974, 78149.65021803202, 5521.416999003714, 3466.4994626499856, 69719.08264350159, 53627.726016180735, 26946.864852281506, 19752.47224378279, 23829.67953631454, 30089.307405597043, 80770.69921096406, 11574.045702052383, 70348.15217206368, 81203.60785185645, 18467.900600509012, 11401.014939028575, 68419.42509040979, 42093.28754522019, 81878.52330751189, 69946.59390758217, 19200.278819653715, 98173.15987932397, 91220.14442701018, 95689.60583126756, 68422.64225933165, 90627.05619491146, 92135.05976933804, 94802.19897390639, 19829.722085665104, 98312.90506751995, 77540.85453894583, 87217.6071708152, 79105.32796935152, 62162.61335388085, 18398.316372607358, 26050.851955037633]} +{"id": 1233, "vector": [81537.14085079118, 9763.707794190635, 14945.759260830604, 51081.188422822524, 3170.6850300177503, 99223.5239338023, 29065.674520948804, 35172.65991666855, 16780.686131469556, 16951.62116352038, 40864.79590748935, 50133.46816985923, 94207.20386814693, 88190.15229994287, 77241.59166416936, 45045.75330688756, 44679.004226757366, 40063.485400956015, 96680.07780897246, 8207.248948545897, 34718.02735111489, 38665.69424607286, 50115.89660900554, 58385.401855910466, 61504.85200420873, 92131.64164417003, 26189.704299607085, 82471.54003460634, 47331.86455147509, 15123.579347131732, 73284.60083764505, 70047.00432055687, 68572.62945148315, 94475.3112574595, 44468.149825523295, 36557.345197699964, 833.088229411727, 72794.89772838562, 3902.284228726316, 96112.39014544785, 14817.375555947454, 52966.80754494507, 5807.992210399127, 32413.272432705875, 78763.5869276458, 92723.9329895176, 97050.942354516, 21118.4872864838, 55088.07881455126, 92278.13171792273, 40656.30205155149, 51271.308821798215, 68661.44577451501, 45314.64443818415, 17544.734882005043, 5223.821492484127, 38111.646695383795, 85849.09138689745, 74987.65143571667, 29007.301336478497, 60478.532329933434, 8275.789416441427, 42503.50073855962, 96593.70020588608, 56512.80856193751, 51047.51225119636, 67857.9122005943, 81597.65880572058, 27295.324686792166, 89194.32026998723, 75433.09466923388, 79497.7345812759, 64273.37248830029, 22013.89406134184, 69558.67771360252, 49603.80236692983, 72089.33318725268, 55354.5907038175, 61217.11892477012, 47783.42347565715, 44523.963161481515, 33503.75739585499, 5736.746146620552, 16337.055502524589, 85713.38767498881, 34148.487487977254, 58105.42578181509, 65112.649219402665, 82454.01867062427, 92249.52770681537, 68719.54717853437, 79778.60735461474, 51591.1427909142, 50333.26423113996, 96076.57313798471, 5020.297025727516, 8506.19952324343, 5097.796125884346, 7571.953815967836, 38039.504127600456, 46706.367740925794, 69247.68967010725, 41598.358441258875, 54081.92943447159, 29098.663622125176, 6471.418584048283, 51361.680950354814, 72656.85272769081, 20883.67726706458, 30077.912748577928, 49443.42004004194, 49682.1995912207, 70475.50307004042, 27921.835850773856, 61161.07662426517, 48679.17978223044, 28358.808891283028, 63101.68942816731, 86133.89961258514, 11042.301130511645, 35419.40850394297, 47458.32684777037, 83909.00926623639, 14678.402854618977, 14006.511094522079, 7131.629457030408, 30007.012329976034, 85079.63837636574]} +{"id": 582, "vector": [55353.7512140156, 66523.37120654796, 97868.32879177514, 86116.67904241265, 55420.80099669669, 86284.07939159212, 64225.07281508076, 61231.0806220847, 84620.45290732408, 70836.06687809178, 7301.812752769232, 92080.809446595, 13440.337691353943, 47422.986832353185, 26269.97503358841, 5468.964051364167, 77312.3350725433, 20593.14763634581, 51787.98678691941, 72717.49896204918, 30310.083141227762, 89433.34678655629, 38536.20932631491, 68184.8689658525, 10462.605703766138, 8227.363035398594, 5480.996449035935, 87770.28078594293, 53958.15733282737, 4873.929322781012, 74010.52660409997, 71963.76974488141, 95902.28780006107, 86839.60253772058, 62083.428593861325, 38097.70246746479, 84886.44302033956, 58874.4475486804, 50450.45205504177, 92082.10490612619, 16802.75600428419, 42602.83878426837, 67519.82054771944, 20399.018143620375, 91114.72485161858, 11550.214158892435, 12892.613498726558, 26419.758332842037, 59682.63769261688, 63989.39153609753, 52629.08689820658, 45688.29135485298, 8308.7523420308, 40923.02973381594, 10486.68955686276, 88928.81055965394, 25853.949668294885, 36986.84034690992, 58473.296386092734, 86738.80907857776, 71342.4702329004, 81178.99624830973, 5963.042814347741, 39037.81893193154, 69053.5874720856, 2784.6636193328054, 54948.68715437438, 92989.91763658094, 54909.56277978033, 77395.18698734205, 81218.43719857799, 99340.83242454028, 61892.228111129385, 20758.96180158412, 93614.34058711617, 8597.028011398945, 30827.201858803164, 1059.8523808837078, 59625.644434998714, 86794.53589218202, 31320.934844687774, 68636.82172413362, 24046.362462650926, 10705.286466606922, 92832.2550983389, 76615.28179333457, 18949.195038916423, 58376.21834197897, 9775.255306747888, 89948.26154646187, 5507.094135535517, 33444.7062644532, 29161.759069249016, 87304.58855215403, 54608.71742736086, 50190.876471773685, 1865.847528314013, 63414.21952277509, 6605.798065115975, 97127.5325259587, 72509.82651617906, 81501.48958156048, 48012.011847663096, 84580.63453448018, 37478.0542358933, 19936.178990605193, 31451.679609154326, 76102.60760676042, 8583.653062978336, 20690.0505657167, 39193.4206049817, 75365.10988518054, 69798.40283827797, 16405.035558063817, 51039.191440384035, 1709.0265716449226, 44644.667353386336, 9293.781607792294, 30009.173052205464, 37835.44073790534, 31283.238388585367, 58296.6591629787, 61221.32152513299, 95878.8847278707, 57427.96681083043, 62596.27901996491, 34013.78607032733, 1308.6801400724269]} +{"id": 1263, "vector": [33056.65498671857, 40075.66299771288, 99587.42558748773, 28015.51998844366, 20567.19698534597, 74897.42113393145, 90744.60506522474, 49875.617228079886, 37400.39455428075, 21975.961229111028, 99039.29264120925, 24830.02774395503, 21812.88264124508, 66188.84910533654, 69179.51573465728, 20778.234101269856, 2178.6869705373692, 38945.6337299763, 18842.67544112238, 90060.7303247126, 46282.73476669259, 43473.039245438784, 33128.579939229705, 54705.21166465561, 34621.93108309761, 10131.511332862165, 39845.71631704409, 43814.80161152998, 61184.84059232344, 86549.09445693092, 3815.2045635267195, 65820.49983139765, 76675.24840069248, 82876.11579710137, 34157.07253753122, 50382.35850425997, 45695.55994912485, 93804.41938500304, 20040.19205328198, 21429.971994827312, 96281.35456024016, 97468.86872093577, 59416.18117278925, 67538.86162920513, 90643.90684573432, 49297.59337647286, 9548.414418500739, 16690.758439568788, 52409.97946032322, 68045.44959701583, 62960.15520671192, 40082.847787462946, 18626.376266666644, 30860.25289388008, 34994.334502032296, 7385.509915657318, 7058.796012419332, 10223.137442816333, 73457.89061035425, 86287.34285585841, 88685.6656474065, 88627.9958585168, 73132.67995450988, 79832.04345075306, 43222.64147965531, 48967.6753414589, 5292.547557595284, 39678.14318499311, 6681.973041268807, 8145.104518306601, 70870.55698635471, 21115.243994181466, 43932.999876478185, 80521.13452689258, 81852.05958158372, 60655.311611791694, 77361.78741032623, 67761.93700901204, 26323.874955791605, 68517.73160009865, 5607.28256760259, 60101.18096839203, 20610.616782737045, 21535.749078393572, 76967.5499146854, 27093.186527275182, 67882.27657795357, 77252.97308328054, 83653.15124245652, 80452.75284701205, 86667.27496379495, 30095.341822349474, 63700.06157511211, 18985.327336609724, 1241.7008460662514, 64685.602967094004, 82377.35660133815, 99834.11549450191, 73660.02353183042, 79805.83979833542, 46907.03016414456, 44182.547574609685, 58675.998975929775, 32978.83854998774, 9559.920462201731, 10792.735358052663, 9751.054552000627, 32138.673907367356, 22018.868637933396, 45853.782655937845, 6320.400000190918, 54516.07506228603, 79565.58242832392, 43389.95704335147, 7160.882654560119, 33456.1728251622, 96512.09732647569, 20620.39640322627, 82861.70931516061, 77125.96382528084, 36461.81847245952, 71477.85661383365, 87724.2643620942, 2009.1142296863484, 28329.041177519954, 69561.0976979405, 16266.761554473664, 54053.74590825355]} +{"id": 1866, "vector": [70595.18066653682, 44991.745173040996, 22317.99423434838, 40801.61348749529, 90734.20812569081, 4383.66200525091, 20501.58568249346, 96241.63596058953, 33744.82436433749, 86401.27884339487, 60044.18952704776, 60997.414034681584, 73981.00979261636, 76155.57799501272, 44822.81619427595, 84085.85959850991, 57572.83219210172, 24442.129514984244, 23122.70665232208, 12936.498201629543, 6809.002004646291, 23218.180566587354, 94103.15936045477, 51786.90615446221, 81700.96392684914, 98026.88602487283, 21467.66459176943, 5554.6030202769825, 47532.96123039481, 66578.63113047993, 74969.3656244531, 99995.21844875948, 55349.040120945116, 58862.116485291874, 54054.40206336871, 96515.6420007393, 58163.682319993146, 23218.07240740984, 58814.67946380264, 91389.42621012223, 49123.6432576995, 81790.97916019506, 43540.2367363292, 64853.58923297346, 83945.94662107038, 68966.02147283878, 33171.00087590195, 9685.517322147929, 96825.23886851568, 95769.72900227655, 84821.11143581195, 45460.20136748376, 16233.28897040579, 96328.52875933738, 77126.51143636959, 58951.87103412087, 74540.12598998369, 97287.15872777808, 38541.147352967164, 75965.04088292114, 89459.61132975487, 82535.43055631417, 17250.374267222767, 12546.62959933982, 60270.64395736242, 33521.40625746935, 1660.1472735803923, 20344.15769085617, 83489.49178462113, 43622.40531006055, 59556.67216724986, 50453.7743039912, 47100.06733596399, 37213.389832557055, 15663.697777746755, 58069.225457053944, 85909.017550923, 76669.00076135917, 54298.90898863473, 32732.606398792563, 69771.91169327275, 67761.55832159612, 21632.935286340817, 28347.022604586447, 84891.58881382752, 68466.8154105783, 39593.221398648224, 70721.50022033234, 23332.79892064194, 69996.92022867512, 15594.239954790966, 7437.657501503503, 506.1297612048654, 96571.64539687097, 1019.743064712686, 82053.13839166403, 4472.520527839652, 96628.8156362738, 38467.565030359096, 84706.90441696829, 5701.157244337774, 94455.09765326836, 94184.60088127953, 7219.171986483663, 49499.50121005771, 49511.93048658751, 13615.450619866831, 76220.84204984135, 47804.213369435565, 68934.9251501306, 97585.97417971176, 52299.804030388965, 4330.7705846116205, 68072.17041188202, 46413.40522663905, 80549.41822993802, 66172.77023781363, 54864.08991201763, 16574.13987439437, 6577.071991465499, 98639.03824629354, 88691.76101448489, 14723.426542240759, 51657.30270303388, 22225.162935132004, 70599.37154800481, 74430.2781988855, 16243.578669596925]} +{"id": 1732, "vector": [69157.15384834615, 32874.387197411656, 31003.38712047943, 80199.0364485089, 69798.40526819088, 13353.284370215202, 60126.91668109863, 96856.69734921472, 47177.69284594682, 45830.08279730092, 7497.470862304589, 73857.503654691, 47098.10533613815, 56501.873541196226, 67391.37672488893, 24632.72576510076, 885.8950597145143, 49257.646185471036, 65849.76692849674, 67744.61732217272, 55132.76082046364, 11339.196223685898, 26074.502513016483, 44196.88797942597, 9394.409068379151, 63082.11494539956, 59837.97499871354, 79362.22269394864, 79799.27327625231, 23914.3555863083, 69083.69195185014, 97695.5783810198, 70007.85418450204, 3747.864574799464, 79047.42266812306, 13802.290176207365, 20472.15734306711, 3365.708257947797, 29904.677558589345, 63156.514692641416, 59712.22753388461, 98431.58519638871, 13250.530193611565, 8226.027157028348, 9263.16078402779, 49543.04832994285, 7561.80001328699, 77454.48732545038, 1448.7920934632136, 45385.9785050573, 70676.13258209523, 72941.08064955761, 20436.392700265293, 35573.36582750411, 68153.85886077952, 13974.448211692414, 38054.47981012574, 63023.90473049965, 65251.33987932541, 22781.800171628576, 9000.905849677165, 85808.54059213727, 56512.60064094785, 67712.22692447492, 97218.4035104472, 49244.39287374471, 23764.984405549672, 2782.3745321296833, 50002.21770041522, 91596.3986777334, 85466.4206202868, 18512.605128967476, 89693.3405533107, 54717.966654471296, 64733.457826664075, 17260.38857326948, 9171.666121658183, 69696.25856063649, 38207.32844959571, 64892.02267758527, 56298.86584483753, 86124.0108676556, 76817.00526061194, 97330.98102330431, 80399.23135804952, 72225.42810848379, 121.09969332482207, 71971.34956396009, 74411.02304550866, 20311.516298620867, 29515.52979652873, 9668.775806027863, 297.70118161601687, 37919.57693346345, 46865.391517593926, 86897.7400112282, 82915.34084449436, 93905.8409762789, 12754.8668672117, 88791.43421763397, 37866.669892537444, 34451.2364125977, 15607.628804420327, 93968.82086228594, 21761.67523692809, 13117.08282560925, 64083.164613753564, 37493.1782272533, 66996.80261804858, 67595.50431111315, 13111.191682503353, 76943.82385805108, 5575.644713088012, 77316.6720091948, 65348.35840428938, 83227.2955371228, 66478.7860137351, 49586.006067576, 88132.62208034407, 42985.59537702394, 79171.27346936232, 21521.16014530834, 80957.88485656411, 88328.49278749661, 47966.16649874415, 20153.177215487172, 88208.29054767817, 77202.54728289672]} +{"id": 442, "vector": [80044.15396723058, 14705.439060364379, 34757.69654883235, 16241.880033353884, 97412.26786910131, 17189.211542638448, 25729.795896056152, 39233.385726635795, 2075.425773702666, 91408.49721737574, 49998.32870798112, 41151.27381808798, 81679.70830034612, 82215.94898484852, 98444.69832499661, 22962.611534332857, 13817.921513893327, 38030.81400674862, 25143.99850033213, 77232.915390829, 51212.039289624736, 82121.37639726046, 75318.8636271355, 28310.50780139658, 74917.78796496581, 41466.62956612641, 34401.05382896359, 60047.74103922558, 64516.815048670214, 33927.17045714193, 29626.14446258125, 98364.90792607916, 19427.60427360989, 34453.33384751215, 75633.51434903388, 80758.79659968328, 85581.81035623084, 97094.79038217221, 68566.57990268721, 62659.20488211884, 74096.45899550454, 23016.508244110777, 95660.7490813683, 15716.229953694594, 79564.53770856238, 85267.23244483628, 91412.45192132835, 53666.42119301247, 46391.515895758515, 95758.0636877091, 606.5724830168317, 21336.87818537222, 67489.13213506436, 89709.95365610573, 22879.747752944448, 34724.351407758346, 14867.890500769721, 68291.60407586778, 97618.92708035882, 83586.97819706168, 11584.957426880716, 52637.712803488255, 96072.64724938167, 4981.940915039074, 78420.9658653102, 86283.21125737405, 98741.04193464346, 97291.78147167472, 99337.89671225465, 96939.94692820542, 51059.471092165506, 97398.95025753412, 3072.112372316005, 59184.27853895171, 9078.699769824128, 71094.10526326526, 39243.2582842104, 47886.11658845573, 19055.37496523474, 55243.96300271763, 67388.30191639838, 5270.548816069998, 23500.80116641695, 34768.490253961696, 29674.369118865863, 50638.06011327073, 41929.957762154, 61743.00219823742, 25981.61449581351, 17307.372966256728, 53076.44438005821, 80362.33667821938, 18065.31436417882, 86906.85745513871, 30943.1247233245, 5302.146731749546, 56201.778797219056, 3828.289245775196, 73059.94845631896, 61025.71325998628, 39095.093336744205, 46357.74855042201, 16506.146359853425, 43582.04103631481, 15871.378937046144, 27124.797116109843, 72634.47332243045, 70959.01436793723, 16072.982461013618, 1852.8455629747255, 43444.17281350175, 78702.36733509281, 78711.95403657443, 5254.473561942141, 15180.621826943609, 28150.855862536428, 49985.39625901386, 61398.60576005298, 31673.40893031858, 85157.22293565249, 21243.32830521789, 18584.385552541415, 33148.00036436302, 42135.432335923964, 87003.25518269796, 65930.81582985337, 58964.30395606014, 63124.537484673434]} +{"id": 755, "vector": [33592.12851539425, 35077.65668552792, 24531.137486677413, 36688.14797427165, 60114.031615451146, 2134.9170169445374, 9506.681559321329, 81183.95674489297, 8857.643589787102, 85461.29682969437, 54991.31431354479, 12430.060856002745, 14717.423176157574, 76557.54520876559, 92211.00251877586, 52890.996022047795, 99833.52280553637, 30324.230928377216, 84424.46022579804, 23244.474347427204, 87780.57580995621, 73415.18222591383, 90686.98597829761, 93540.86214031396, 17787.201084479333, 76702.71785729256, 90230.70448513224, 1504.2316162071634, 43527.79092361533, 32349.306808218636, 82912.36076868426, 45133.10210475745, 10340.19211079844, 69976.04095715702, 42004.96343867992, 55478.71824817141, 92242.00540742584, 58072.42293810875, 55598.99062794965, 109.68336236636401, 37650.75622167406, 17843.885920755787, 56820.83655751488, 72736.5834939312, 12909.388717618642, 70729.91902522273, 25356.987840835453, 55942.048910374186, 33315.155002874475, 39267.88056707634, 72688.78376109703, 61217.400320552064, 36452.45501920842, 75497.86063173086, 66735.62002509985, 75552.44967986032, 32250.18822151148, 46461.1664831028, 77954.09393234468, 59891.01729534248, 35562.75428746616, 17595.373652965405, 90925.40186251818, 9518.84381483128, 53689.429926235855, 38724.101965358605, 13429.36885155297, 46931.3894815118, 16981.38021487352, 2584.280390748661, 11112.462181252702, 49423.36751933536, 80765.65431937795, 92000.41928776805, 16930.09242898782, 46182.61419620117, 39687.85132434267, 53693.067800716395, 44695.03548860622, 52879.17105816079, 33546.893297336275, 66028.9542267747, 78658.12714861952, 16989.302456735742, 75791.06524898033, 60745.08619887941, 74992.6093796163, 94510.55912869894, 72579.60347652182, 51792.65132597801, 80777.38937784215, 64250.12955336914, 94357.53871168046, 70898.67557293917, 37827.72933054024, 61265.52666182357, 11311.89913085341, 84358.19918759486, 17528.65418730278, 70660.31458538071, 40894.51381922623, 29718.762436471334, 9591.62637211508, 14099.57822995509, 50232.656133225595, 68662.99460256095, 75232.35730020673, 2475.156093217767, 54874.685969017235, 56098.94844372205, 77199.47958984428, 35892.801965697174, 65291.42737452929, 52071.00123905087, 36771.035684048635, 6953.508552008625, 68442.30071095043, 91002.93127166595, 91104.65490392326, 1099.1690981830836, 16920.711592132386, 14231.260039650673, 82556.94851491143, 55604.64290921905, 89550.85626621213, 87723.00301792633, 67199.26665106408, 27412.707951143268]} +{"id": 1716, "vector": [12988.42905485591, 69375.75769530323, 35499.99521922164, 70765.05258625734, 5518.49055112803, 30184.567392644047, 54419.3250928186, 29928.513168826143, 58254.17793981691, 55368.4289802856, 85561.01541991769, 84156.87492221118, 59118.38526911991, 89521.38596168546, 88751.15071483416, 94625.9516206619, 77667.037702314, 92092.6295537231, 32599.69129860192, 96579.48036122584, 86282.27727296442, 78005.22208764718, 69401.89819670154, 87504.20844254429, 98893.74331424186, 74301.80555242221, 55157.14053403943, 47123.40124426579, 73782.98732759924, 74584.17513772516, 59282.431297491756, 6075.781791823276, 7558.832216167255, 84665.37386789233, 9245.461827621904, 95361.82672712576, 19962.802867873175, 61038.30533062422, 98080.2763543739, 2281.4914989805925, 420.1195081236064, 31133.544858097506, 49643.894508405705, 56041.916106807366, 36840.36705666657, 95659.46599368409, 19293.85823212383, 81544.22889173971, 15641.761057785186, 84746.49028522981, 43198.52261081988, 97861.15925305919, 30950.49148383857, 51331.533612409454, 42458.98212933121, 32792.00912220414, 98618.14695191989, 96842.5317839132, 56666.85369001471, 76629.02747503875, 33505.43184628496, 34290.51778720968, 88084.39665753659, 78754.89077917048, 49741.40629893545, 59222.18921818944, 84676.4837322538, 31008.575848468856, 46368.17341069118, 63884.77211028313, 57006.27077637077, 85793.35232275478, 12494.552108818734, 83034.68133042383, 74454.66373538409, 51123.094765197544, 93340.90632594831, 96551.63783198447, 68178.0419443452, 66283.50726245886, 64427.47952844676, 39058.56707420471, 55106.09122313582, 90391.23451780967, 63660.26302120941, 85009.83702179439, 7248.246251763524, 62621.31264537058, 43223.23714970768, 84977.73656279201, 60947.390264017966, 11182.122476902923, 89526.71933868893, 22755.82207856258, 86311.62629738046, 8549.281730470604, 99842.77742238181, 42437.905454209365, 10548.573542587203, 88933.97432061748, 83749.35314790405, 27724.829455618637, 8085.937406263121, 23152.25963861144, 12604.679339954782, 9228.496367181404, 27113.294306155665, 60379.73045473203, 79490.71795632441, 10328.299481900705, 53993.96276997018, 80247.90126817305, 34499.77393147725, 76076.0935112553, 43319.59013400378, 72029.28771997218, 20881.933012259502, 66983.93040467733, 92701.9711689702, 24955.032046934877, 39995.32371078516, 2669.1602951010586, 63194.237790250765, 74053.10235887414, 1689.1498798095372, 28446.825142151945, 28618.818063464045, 39190.87221679649]} +{"id": 1222, "vector": [48228.78990198572, 89743.14088295396, 22522.947147875282, 24683.857476545967, 62424.46829028987, 83218.32471645692, 27807.973033514187, 77710.01154516549, 71463.6193908186, 64679.77510515003, 50707.731456835034, 67199.90120437906, 25979.296791932327, 55701.335540714645, 66717.30991316473, 91724.92995498345, 65643.12947982001, 11991.020447489764, 4272.146775009622, 66491.8493557621, 57409.90172629549, 50993.20902648058, 84526.15584563145, 86194.9611335693, 51320.15184006457, 29075.81264377527, 77975.5397201409, 64112.38290093424, 157.43414694441293, 55843.048044967945, 78500.11626287099, 29138.46799520381, 33510.148943404885, 83346.20046814728, 79688.82524033265, 764.4363354483374, 32958.741082670414, 19776.896443660407, 1701.85943388107, 16764.25815864394, 18997.56499470655, 14452.67809662244, 49662.4365862471, 61547.96771336583, 93318.84685337912, 90311.0342610535, 36172.619995379726, 83921.89032348203, 66738.46959052903, 91826.26381279695, 46999.6925498076, 92323.8276188042, 16838.6025013735, 72148.03168228848, 23562.770612146433, 72679.27555820836, 28970.01035850869, 78290.83793378447, 76700.17768799327, 82846.50346332144, 98211.48421813978, 17149.633598605727, 54464.38585349055, 55235.47937016764, 5815.909689765619, 73820.86744369725, 27992.23627068914, 97686.19519295478, 17071.566732883213, 97710.85435348943, 64535.36107054684, 97581.132133003, 2661.460796512105, 18439.866175949894, 57894.453245763856, 27612.59292629221, 57967.32409527889, 48305.47508436346, 72726.36087773688, 40672.034398114156, 47905.09471427543, 89483.58549667298, 48888.42518496448, 37239.92099690815, 38375.83564354141, 54721.90333143474, 96258.91315445775, 31293.550005072357, 20444.108068624457, 93981.24073479736, 5881.170089708942, 27084.80013855693, 83927.50049969669, 14798.91960706239, 42772.28650351258, 89231.65215397924, 62479.78503160596, 40538.19660228072, 76427.33996449097, 62455.14202223519, 58145.092194329074, 1904.7438676716877, 32193.33429986907, 77463.80840558687, 98776.58899365464, 55520.583855875404, 31353.206311941405, 77275.33190474393, 96802.65971759129, 24975.687765956365, 54473.49607939652, 23036.6059427909, 84411.37049563864, 67211.48296600948, 12977.59329564051, 40396.086630942984, 49359.10359492238, 18941.043117270052, 47304.890126678554, 33454.23041838056, 59763.36609092211, 88646.45930773226, 18704.45316828814, 76536.61903115499, 14065.276916545656, 37832.32834307168, 57152.765709731444, 72710.2036259138]} +{"id": 285, "vector": [25887.069333018873, 46234.79201996246, 20969.079069277774, 56433.51066216155, 37053.29124407601, 71065.38439070724, 53776.01900123538, 23298.952126552053, 17417.36683553372, 89946.69995052826, 58727.96581539264, 5525.318576448912, 65685.47239286717, 2767.061860231701, 6525.749098311062, 33207.220982981555, 93531.02953974508, 13675.367324411047, 10422.878511784005, 79972.35002998149, 76145.31512819088, 50833.422606697466, 56144.42520982996, 9466.776118220332, 29739.849218551608, 57805.580219807365, 76555.19234499028, 9658.62713828155, 62455.39400471557, 37985.498083298866, 1522.1469694372436, 26184.11617026536, 37274.7429482817, 58567.25103736612, 98397.39201727192, 58284.28650398037, 16956.9439454654, 50735.12057152863, 84557.94941391546, 13934.376838794871, 3375.928726539379, 89598.71550556106, 4531.761251615185, 26331.7869193588, 12968.03671749015, 540.1006347228399, 18526.936516267368, 18369.762095849772, 59526.7758178167, 67917.40493349147, 74526.68792814596, 48428.13121430718, 55167.3172221631, 17617.29429571629, 72757.81204836536, 45077.657471595136, 44822.08307937451, 42549.20565125946, 15131.047105291085, 55341.0116676201, 54821.48543729012, 79714.64948657418, 30415.981477194877, 64249.62144415629, 84686.1692216939, 70858.82694550003, 90689.1457806976, 20512.009144391173, 20577.960504699622, 97189.54575853162, 48850.18307959726, 44424.193150764615, 68432.65938856518, 85270.77993873025, 98398.66340885132, 72841.5420244927, 44732.39371635578, 20205.33007755342, 65191.12154722069, 51960.90074115075, 46575.96820557684, 31579.755265279262, 73518.77861959115, 80102.124709338, 90441.60882985636, 79980.05311664747, 56003.63243852077, 58656.569351837505, 48257.769907484006, 17894.28064071851, 30718.605603216052, 69516.99876278771, 39756.500331525436, 51051.056750125405, 95665.13659274134, 16477.31393361056, 36749.63887093614, 29529.288817349065, 10607.25643736371, 43973.980173565484, 34519.69500390953, 63918.23379856303, 27729.597123746018, 48638.50247322607, 12014.015908684118, 64400.419751198664, 74698.18568155817, 17474.14462624335, 97509.6551483208, 67697.9670713674, 85945.07349255797, 85424.95446178401, 32556.62674245523, 68123.93718067904, 36038.124615772824, 30054.265488126275, 81170.71622338367, 83085.49613583261, 98745.93615112135, 87402.97407216698, 27230.185271943654, 17460.77131216971, 51036.87063120145, 66650.022268679, 7657.299966285158, 61767.469072752814, 98486.47398194189, 15771.50870819377]} +{"id": 219, "vector": [30824.43154158315, 99655.6439978695, 83951.73593770037, 11938.829952289942, 22027.784860835854, 29898.458572313833, 77725.96380698074, 21056.50399179667, 43574.70646181783, 97851.93839369735, 73016.8020459745, 82106.29056502877, 17160.905669393876, 31202.676563128407, 48314.2052263836, 87244.35282149249, 34107.59745110463, 364.3734102150464, 92396.95869778031, 46603.59229523303, 18041.210209582525, 22916.820979874232, 96321.27009585475, 65114.330111091935, 82107.06921526298, 14722.869908169201, 17.720611941263176, 14403.709647227059, 57157.2910294058, 48043.86281577846, 60172.64687048182, 16696.5673524061, 59132.07576502886, 5047.2826918737, 95607.7779469759, 40955.926696504095, 15684.52252178012, 89477.70964930086, 68651.18433786498, 41947.37345861765, 75300.45359092466, 15711.955822946866, 68421.7730189301, 18283.313819381165, 69701.0492476347, 62848.20716838951, 93776.6691990262, 25902.428335257886, 97127.4427650725, 62849.96549263655, 70793.34815146016, 82488.83193541336, 58156.676544897775, 89445.36327944575, 5883.1675790559475, 57452.0122523379, 77012.73787555823, 94796.41011950938, 50798.29798369515, 83694.79573471463, 91884.11397204822, 95812.08857646331, 53066.649077341965, 35680.348460337606, 33887.54127097112, 53858.639498031705, 45903.14455762468, 30895.48184100941, 48023.38853515521, 76020.77855679316, 76454.03514166392, 64204.345084239714, 82368.88356217435, 70955.74328894491, 39358.22135018191, 13081.406722448684, 73000.25565076625, 63093.15421052515, 72283.94565672615, 40977.67012594282, 68292.7698249465, 57660.66042854938, 75986.8801771345, 71137.41142125771, 22938.693937556487, 55299.420422036485, 28764.503328396862, 53062.56368570316, 92393.0061660942, 1101.8218135232516, 9983.05696069275, 79451.63293835503, 52443.742988774415, 65811.45093616586, 89560.08514011811, 75685.5023246093, 67836.79451151473, 68430.39973830845, 1360.455967817853, 48988.912289860775, 21324.09525152369, 34754.21282678618, 80726.4849815297, 67289.48841836871, 21936.111890817367, 77085.17301828397, 36176.07508095606, 18309.988305407154, 35161.323099430774, 46563.709790801346, 18855.464279215128, 97676.52452556304, 32601.45598938462, 55448.4454405671, 35051.88938345277, 13886.666434397066, 2310.2517635697927, 55079.68209928335, 94342.43779063495, 1370.7858139190844, 81321.83303983697, 34514.50044007376, 26230.512261592732, 36011.93147965105, 90840.66675560093, 36014.7350605293, 31447.32033978326, 841.5954571999839]} +{"id": 201, "vector": [43377.13654031563, 54530.270897951785, 15646.135494519609, 69630.25844883962, 56978.21543888922, 53247.835036332355, 7240.628061968379, 41586.117066570405, 96410.69013546503, 57546.41567691501, 23179.573294269463, 74639.27185281432, 57163.12970619981, 35581.87854207191, 51111.04390826599, 79558.13380176385, 13856.105801753993, 23868.735068043756, 50220.62355295515, 28034.16384755839, 3984.794518943524, 38719.786846615745, 10339.350349540266, 14338.968063486513, 66322.78070642661, 31830.035588396287, 39823.89700326795, 14421.519035534448, 7422.222890495389, 15842.815344467053, 58522.12565447502, 1276.7723107383833, 68128.41979582245, 57801.57477840797, 66525.64972045891, 50191.32940017953, 85141.87580436599, 43590.93998670565, 67171.47115117387, 97894.70340209488, 32438.24851317627, 27914.229507274802, 42496.975066631334, 93602.40145880525, 9757.49968818902, 14857.177546191613, 48227.38265060821, 78453.60026059451, 74013.11829656528, 33034.56309504477, 72187.00691247564, 85102.22931441774, 32992.38967408743, 28595.462863105247, 70329.5303999188, 63048.479691541856, 62365.93500566881, 86287.84401142258, 2578.076385461714, 61696.616907762495, 40460.156504574785, 97948.60811881599, 49430.71984245611, 93763.32474917365, 36516.591282076784, 31411.21147235334, 98080.6450956893, 5642.159174073747, 87894.8820277063, 19589.6216450709, 13249.73489943545, 8825.6591198126, 34616.74722714681, 25583.814943581372, 8352.44618094122, 7667.1907409337155, 31559.097195908125, 79119.25227199166, 53750.25347795306, 21211.03585146211, 89097.82726138626, 44827.20138558164, 92188.53120947475, 25989.911558341282, 52975.68165911949, 11344.632890579209, 50712.08053323991, 8673.697384741907, 22392.53308737483, 33053.97867069608, 19660.717222420066, 25369.503152606456, 76430.0426778585, 36665.6116804969, 42081.91340783352, 62344.1858643783, 39951.38268927166, 92502.01470203059, 64681.08972175101, 71312.28858320715, 42976.86733755889, 53016.01203614697, 66923.65529669578, 20508.06601784756, 34901.24141826838, 72272.18904595105, 5939.6568538284455, 49065.166156086445, 57583.88388053167, 29516.769262287224, 45345.47017036589, 36031.13073681076, 3098.9581588825145, 31584.112029462685, 42109.23679204476, 21715.793468547028, 50196.93576407923, 93824.03571881802, 34505.50336897525, 70932.17059512684, 44724.73546147394, 7795.578660410707, 95262.93466179134, 26603.957455805128, 801.0524600233659, 98542.96282635684, 44698.4899093421, 23788.120213405462]} +{"id": 850, "vector": [56527.89290700572, 20492.426746510308, 30390.939546269223, 84464.20994542602, 66802.28789614476, 30428.239576628857, 39255.5538045312, 94390.00470947154, 81320.5755300911, 52755.49536341618, 52551.22365359134, 91082.24923312187, 51361.23976714124, 41327.3256137611, 50127.760900611254, 27690.312311118247, 54763.40137723299, 65924.3212417456, 57692.89356749072, 89990.62034849277, 50316.678516434564, 28602.08510089045, 58222.1919757141, 40895.3227365573, 82315.47863850575, 68631.38859287748, 65072.94317104679, 6646.502382247521, 40783.1192356081, 9486.048300729344, 85584.25969613834, 68827.85016873118, 25705.409261034605, 46356.371880310384, 38421.08776029622, 63988.28554507697, 74132.11558897955, 71521.84424171264, 22508.313784261914, 99127.77519998974, 86462.83702346674, 36866.79871802097, 62876.68755330311, 63941.972667396585, 19212.344742263354, 58923.49458615188, 4260.996412740814, 40845.767175641915, 80253.65068462967, 68463.01908993341, 90417.3806023171, 23003.545589241192, 67509.16620531394, 88106.75832231384, 46577.145572093905, 51323.26504959607, 67820.66125629656, 87045.71284880993, 57365.807808030135, 43631.68424991289, 4197.862114532036, 55600.89582328362, 91931.68341475136, 3727.297231147142, 14058.7603509709, 30213.433102973286, 56723.28116422737, 80642.78174493709, 5586.453420797499, 47274.07043425623, 22925.06005299997, 4773.795982874462, 39378.04018605139, 28727.38793337578, 88521.3120981401, 63388.354082029306, 603.8623200470882, 74112.68842226134, 77797.97696219618, 12843.228949055063, 23774.992100577587, 42526.6383446803, 26238.64497028122, 11789.370616884498, 36522.10839340022, 33117.05837100895, 10227.749536056985, 41720.55150835418, 24952.828242189327, 56355.49019676698, 38300.067413673314, 54623.05649560735, 64905.11595979148, 7787.99005890497, 17031.903696418914, 59433.21557639768, 19163.316452521794, 45587.032608784015, 16609.228003154207, 57374.963087597396, 25175.709925347244, 45615.48671805149, 69311.91121738015, 74449.78976762871, 38878.17626353435, 54605.721772773395, 18832.673332102157, 78543.82536462841, 37306.70543931698, 65449.810734956984, 34017.11902877384, 29296.602027787045, 96855.3500227124, 88840.2923759981, 43956.7299082643, 15509.631723347073, 17212.544326524516, 16279.185425606112, 31365.129790227296, 24091.33757086066, 26875.492380029307, 91835.00467572719, 57543.192514899245, 88506.31158511362, 65641.10055745547, 26566.88336283606, 87539.06158443575, 79021.3148916296]} +{"id": 820, "vector": [56725.75865775729, 76538.3346976301, 53730.1850587382, 46460.00058365742, 91070.4973201876, 16363.97584134185, 43409.31800555555, 18062.430833042064, 49496.910803923034, 38480.46947434185, 11980.203970460201, 91989.18075612198, 45350.43747691159, 58347.44600084606, 62185.761624120154, 10627.987193555022, 21904.690438299156, 78216.18469316611, 67952.64424265813, 30410.239059664178, 88787.59392082687, 57014.32628807723, 66652.65367618948, 87088.43478119932, 82892.69536901567, 1351.0918253896941, 30741.55989726828, 7128.944169603657, 3991.5997242591093, 26605.360651953837, 52843.40572509056, 88233.80411871073, 61144.324779044946, 2847.848017571297, 71461.334379747, 81059.49802281747, 20248.800431488868, 38807.0673712255, 98634.42302768923, 75049.5554544247, 80754.9253780335, 92079.08616504526, 77682.2991519211, 64349.673370603254, 54340.55974836599, 85488.95516320253, 91545.30219200051, 26769.46857865682, 5794.1046397419595, 11309.595754276757, 95581.86633531487, 32682.844347692953, 86733.57235080955, 5561.048592635653, 39334.208047552136, 54104.46450859825, 84760.39239223574, 42582.29071309211, 20197.591485683795, 91787.520813456, 31236.82379409115, 28495.320242818667, 11661.239897379804, 33043.417198252966, 52920.456344954015, 59878.23630753276, 53258.20612390174, 57496.42127212101, 33042.29348891562, 70998.57992040888, 32861.7063760314, 72048.88475353488, 303.8218315218444, 74015.44021495704, 6845.2901936628, 87099.81057571275, 77099.8744591876, 2812.698357020127, 27519.42975836038, 61461.40357158821, 25779.393974331866, 11971.684727512144, 68635.26713950052, 75265.76257140703, 95601.02623583493, 31786.334261761218, 86495.41523776474, 42924.372124427755, 12680.915574838291, 25494.28570038518, 87623.38943440195, 46844.357793356176, 46003.15266601172, 48696.949848046155, 67813.63536284138, 51917.932205870646, 30598.913148417796, 25536.25520997086, 79772.65468371376, 27402.673642610363, 5663.943069697863, 1185.12041677451, 36261.250284436464, 92454.88468962695, 48864.582892871746, 11552.987706627217, 17226.539775408124, 22764.74506971937, 49567.85884560012, 85865.90534842787, 40664.82917921746, 42005.79832773578, 94243.93111764212, 80837.53477901687, 18196.53601123634, 66727.70708950669, 11419.756968102645, 59686.258748376116, 78619.04752252971, 78669.26803225995, 12882.06353090462, 18802.9692105924, 37949.722195927796, 57829.94124705496, 58800.16081515258, 18560.29575509098, 79337.81440729827, 71742.31438533774]} +{"id": 1398, "vector": [52301.71632212208, 74113.02934625135, 17292.449741896577, 96848.70808212878, 17036.894854297345, 66866.94961809802, 78221.28249991279, 58782.03895151346, 19517.48799117097, 38535.838099302164, 7010.24361051813, 93628.16445416758, 33157.07059041755, 38797.90457416539, 90173.46950669895, 3658.3198455500287, 30733.23866621228, 22606.710194734213, 67606.83055323828, 1115.053236650898, 18825.13740507774, 84014.97639340954, 38639.95229085279, 21681.878969949907, 59962.42512831023, 8144.3402057568055, 41936.70028902348, 67992.04456275892, 74687.91366342477, 60034.452082960976, 723.4345910375195, 9959.12483767103, 29935.271392171337, 75339.0063781194, 33400.52420987697, 31124.506046915034, 9882.033856635564, 39461.77524093547, 94361.63177088743, 99737.03098787567, 33314.189684921934, 13947.925041994424, 10331.02799450033, 84511.59108945083, 42975.456825251946, 59274.00974839816, 86482.36403895759, 48611.079058678464, 67291.93541383058, 14156.73136390363, 51203.771674402546, 66079.2644801153, 66421.40161533585, 35649.15961017703, 58789.18280379792, 40361.65604274858, 42033.40645337589, 50298.79616369854, 6543.309505238737, 33505.155383443205, 44185.87335318283, 56393.607262683574, 67279.14343963268, 7173.990161753285, 75106.44266161852, 9315.208772711681, 93079.011635411, 43874.62188353528, 83298.8270201535, 45406.23255706758, 26817.66627105181, 88728.51803028013, 17588.830681771393, 53334.71491085281, 21522.893629681337, 73102.88379515779, 628.0972473872604, 55202.53710607198, 42932.85409465423, 92405.65691971972, 64958.543515599595, 82165.52348511587, 65969.27581993288, 63505.13391561316, 96238.08288605232, 70034.7462398404, 35382.880240006074, 79233.85931697783, 3206.159136476283, 65028.80835788836, 59242.147037695555, 57745.46855663064, 67207.51218169632, 21837.32734826581, 85559.41929969974, 90129.1390975837, 81635.39239845079, 10707.515589313387, 35125.413851321915, 15363.610038844678, 58492.76075748964, 58613.37513366409, 52370.80625744775, 12534.578839403142, 41433.81606162124, 86445.15606654658, 4118.441218887791, 86173.49040369468, 82392.99546606642, 58209.530110667605, 4992.909186731808, 59115.83621519896, 90531.33350281448, 356.0448040118791, 38436.32874526813, 5765.793242350947, 17229.27155296683, 4862.777008699648, 44574.13704654263, 74024.74453834623, 52694.595699309386, 65547.88713686625, 90300.13446569999, 314.3903506125922, 27148.216041631855, 64496.44550440312, 81261.7374524335, 5403.913804114624]} +{"id": 1427, "vector": [18527.89684719838, 8866.900777975761, 49193.268295798574, 84555.8647252511, 32704.8203871508, 26277.727057089738, 45420.594711541686, 88671.51121945135, 2170.808909064781, 86133.01071462575, 76247.21489607028, 61518.03252420464, 81063.44472703566, 6726.211508865298, 51565.83929525994, 83350.15546947898, 35088.11558795469, 1949.6661545768568, 69122.5053246302, 74317.33563536871, 12933.398537794383, 85644.25727146, 78383.76608254494, 62585.933251951275, 23050.440557164286, 82915.64354649081, 70706.15151278762, 44869.1299071998, 55021.711075012034, 53264.31966219346, 49404.88816662334, 69949.70668222081, 40172.0753823232, 8334.883459148568, 98349.93293110939, 8386.487455606439, 90401.67723384772, 74752.78568629455, 71566.23755943388, 37174.2685340637, 10226.415137501677, 63333.97252139847, 91733.48324821223, 45348.153608107714, 82352.98263236196, 89903.09249380058, 14405.508376388421, 77983.50011308592, 57596.632425785734, 88287.84710301827, 76088.59967477949, 553.8572262737906, 49724.95981481837, 24023.08490087386, 24423.93761971481, 45590.05705047452, 82102.40397187861, 24691.73281787207, 71596.18792641352, 7231.634208848137, 23015.608030814717, 19629.263240573502, 21035.652294598327, 14648.311202843744, 73309.21172701633, 74939.6624969541, 79886.81303300401, 12730.450183772246, 31065.257281513383, 9561.430664581361, 45041.32726618288, 50033.89699729175, 62905.134641013305, 62551.5077691571, 77138.76861724084, 97154.17848881053, 30442.139505607356, 75479.35829212434, 30438.358722651883, 61550.14061079248, 86728.91462863547, 95267.268708371, 16970.04316138647, 39530.94204186697, 13548.566392987894, 44602.432200806754, 87322.25920515224, 243.78408392710105, 26688.68868680979, 52431.54964904177, 83555.26727577804, 81582.27360903748, 51357.86427217157, 65370.95735010468, 37788.02910412509, 2648.1886169636427, 27632.630283928895, 80845.57942984914, 21669.39825257824, 31259.567210160887, 496.3333009793458, 46410.764369110635, 59338.44449599497, 34763.08698548521, 67347.69513355898, 53268.956522096756, 37470.07156108248, 44895.47600981666, 8163.366992013832, 48443.4790675649, 83855.15679228037, 10647.848735389132, 39187.5928067552, 10733.896432857271, 26160.304304097994, 18987.78121657004, 60590.9215685591, 96648.79415082089, 35094.28578278231, 36862.62899557231, 1640.0775185664852, 45452.093353958866, 81106.16564075009, 98945.61426530914, 4652.460551002757, 84716.16126925712, 63308.78069475477, 7545.388650811436]} +{"id": 483, "vector": [86926.77848135513, 22331.87441265858, 46781.16783112176, 38554.19821929271, 3168.9395113112087, 55592.73300998476, 50902.89060251448, 83301.47747620674, 64859.08749817384, 84423.69145936829, 82939.76616887421, 13690.655819438924, 96913.00942724399, 37799.09670552847, 391.9536573271709, 85930.25071183576, 10210.570508381612, 54629.93249491591, 55554.472491136876, 56574.71471940435, 25498.86329846628, 8848.647978367451, 6049.864685320927, 42547.798616636486, 1787.0702509631299, 64145.14190906174, 2411.5865875510067, 4673.683508297033, 40829.85044236665, 95015.87678677971, 87798.85824208707, 21508.24274482821, 78489.27563175069, 39695.73464026603, 59816.10321795138, 85294.69305754753, 5521.559825083977, 38400.62037426506, 30251.20684557402, 72086.65673504405, 39878.99650141078, 81401.7216735966, 38869.72954623037, 71502.0783312723, 77171.80255114847, 21997.84419689441, 69097.28658445185, 65643.63678647495, 65654.3187148678, 52002.13038619204, 98599.38118715408, 33045.85429538337, 39954.91236185833, 16433.953967813042, 73314.35693495646, 32501.426075958094, 49687.55966322897, 20756.143809386707, 53162.044985685134, 30396.812745093215, 10313.114177085125, 28348.8226575366, 70476.73293651841, 81264.61410340854, 54171.52515725796, 78986.86449077478, 13616.62435238583, 17795.375578607698, 85222.75997603957, 2379.8311327156707, 95936.6152535907, 92230.77902355471, 29231.168091517702, 45527.188775408686, 4445.16889172234, 78250.05409855465, 19567.930928741927, 90354.94010585637, 90812.43803161662, 71468.23965448362, 58381.96533171468, 15103.509031856722, 14505.154229380025, 36678.51972055046, 19692.92209660065, 18979.53816130188, 81349.19830891002, 40942.4635193767, 67595.62646977956, 6340.28279865625, 57336.50192774515, 20429.689146435616, 44359.661035679885, 32453.551071177644, 71597.12113265316, 78054.13128756738, 96474.5325978897, 4898.032586803592, 2618.320601760449, 12675.337688762467, 17428.802731563388, 38993.806315447575, 98786.55756701733, 46497.76965579021, 46050.9313923432, 95747.46068149213, 6765.280522350381, 59461.47060014217, 52891.52919775032, 19401.260635645045, 54251.25349013697, 41520.43155452204, 99260.40743222604, 23974.31475808308, 15917.920889196768, 82906.74882940741, 8136.987968013565, 84326.7367644462, 14573.669425201308, 1463.0384191771095, 1129.9144710682385, 68425.31937216708, 90859.09890434502, 72274.73980452394, 49612.41813403035, 24114.097253909407, 1737.5909715175885, 65664.16063454848]} +{"id": 1197, "vector": [3008.5811555938703, 13108.1063453802, 44580.187908263746, 5179.72694536083, 21626.964231671896, 63763.23335880483, 95477.47224702538, 45420.11573843881, 64781.13716036162, 19060.2840418996, 4553.968425009502, 38283.342167458424, 44205.544437781806, 25981.43932628085, 44260.030163817784, 24627.586767978948, 10514.983300211288, 89983.87421769797, 53774.06881209988, 37957.74164870367, 37630.08338469087, 64823.117796158534, 73153.84904709298, 39484.2695495508, 23218.549198835415, 62567.87062946042, 79438.16679624522, 28381.01778554668, 14113.857398120223, 83702.93575374325, 84775.94625493635, 94579.53241504417, 93614.10687107305, 37896.75159508943, 27700.5498150541, 24052.784600661715, 83788.74725079408, 89135.30726562263, 33136.55124558261, 35636.930734098794, 52801.672070251705, 45760.44772370851, 7637.430494432373, 15533.352172693949, 37247.4988819111, 28801.79799039223, 91289.63366653369, 91597.01913233551, 69322.29393193456, 24432.42100413454, 85485.1427291047, 35012.43904456051, 6172.691107822792, 41022.396207834136, 469.38771859978965, 43880.26555772194, 27356.837107548625, 62206.04553029594, 38820.72475422268, 95503.52725148798, 41248.61035975209, 39326.17379182081, 94406.44992938789, 25256.05475180257, 5805.8178712643185, 58770.79947936148, 51520.36928587722, 50524.87263597079, 86597.63020898677, 68359.41063369015, 87819.46193799286, 67245.33817488689, 85932.7963630443, 57056.96986897856, 76292.46105910258, 55013.895629258834, 81129.3280852389, 87347.94543222239, 47551.263128121325, 39741.297910630055, 24363.884888463726, 85664.93404847886, 60316.98401536303, 31819.463754963028, 79940.52809849753, 5743.307471953074, 83368.28496385842, 61288.83705272866, 26531.463301193613, 96714.06288891521, 37393.35530815473, 3479.067528130486, 33264.47516767258, 96887.45406580987, 28580.442779596917, 12285.343512696167, 99032.3971494507, 29960.23816269364, 95473.1484922871, 35250.03260698351, 82777.62362394156, 28158.192379589196, 56268.3262711017, 39654.26775586372, 35762.59360761503, 55983.536578036445, 97322.43573889865, 17922.621990165677, 27657.951662427015, 42169.92142052532, 73757.3879190339, 25162.03229184548, 35785.00468384622, 32348.101733611278, 8885.759531016269, 31707.974450181508, 35315.223510404445, 67260.44597597858, 44291.57844875199, 7174.88545720657, 96667.21518679618, 28543.58941447497, 92765.55180501271, 5406.68452704417, 88015.51997987584, 26282.44567175034, 3081.331319617331, 56400.63403183701]} +{"id": 1766, "vector": [53569.352235622515, 77479.29339415369, 43995.022787370704, 35535.6743710554, 13701.280070697963, 32738.794293156214, 11700.981624210515, 56768.92516083181, 11705.993401849835, 15487.753311887842, 89967.28236488854, 79561.34469858317, 62059.401237415426, 29909.71544900224, 75325.24635891247, 77604.87896943926, 44287.659805013966, 18473.183669578062, 64928.69218212951, 61868.9139532469, 34208.35389363595, 28096.278457130673, 24966.297326639175, 69208.6610462102, 20358.319882568197, 71520.96769496362, 64653.554242843136, 84132.66668441189, 76227.19469359909, 98341.43792408278, 68530.27502376765, 43874.12021428232, 26630.793584575706, 75123.77763636816, 8659.290088220172, 1163.5592611164202, 73959.12483874394, 8838.76688037315, 43522.02978880908, 1294.397738812081, 81842.61383020021, 81894.58709207053, 3012.951048923418, 54628.485128360546, 86080.08075165766, 29852.496269278006, 54481.01509270553, 52185.88453878562, 19938.426965830815, 25397.331469074634, 25076.573410451652, 72755.46219312244, 84818.20773377412, 41497.16014860051, 53045.93887608836, 77843.60351985559, 19447.78485926999, 56739.19730825644, 34648.086835672475, 62792.57176577355, 67149.45615959902, 95519.41563672076, 59233.419425755994, 63339.31607466687, 36547.39205043912, 8927.029576756562, 87426.082883357, 64462.778834297715, 18080.01085255333, 41851.19901923492, 18915.599143051677, 71520.04307975525, 97815.27801243323, 83018.33313567702, 18603.843666018383, 70492.82831697026, 29381.560210937685, 27456.170284757354, 23767.393114345716, 51677.29907139702, 38828.439858166195, 84072.78604977633, 56220.7697843683, 86653.44429649066, 52110.40968295631, 18352.788188112525, 92812.00587156165, 36864.96673790358, 43107.216444258855, 18262.232494418517, 5320.493138024452, 72674.30421541008, 56929.23649837084, 94432.77627036876, 21438.201376506204, 76328.91877572317, 25648.6013383181, 85287.94940594901, 71939.98527364948, 23459.940790514498, 84614.71691413138, 3702.0908730871715, 28384.704268907924, 2344.431709439654, 94877.85547456492, 58051.238263635074, 62895.40124195561, 80883.62017368399, 95494.22076103304, 27820.728146562746, 67544.42885637333, 27169.90194194421, 6980.594203379198, 95352.59470139192, 87140.52555627511, 23581.390464102915, 48858.03131044928, 73347.97531773859, 43897.46602913673, 9190.960911665614, 52498.704645955775, 61622.406299530565, 62650.0984130597, 47064.54032611599, 55670.980101802605, 16363.009717333021, 56909.588635695916, 22212.13280848229]} +{"id": 1355, "vector": [12177.315125119103, 21355.902959719086, 13610.729128796584, 51385.74926639371, 20861.145214108103, 6424.0925384555285, 47999.18164017188, 54740.39578782746, 71594.84041943657, 18047.67933506466, 59159.061028534874, 88174.8239397826, 40250.55684971799, 96212.3204830173, 23963.59717700385, 90051.24069569568, 98139.38536740352, 63626.28161808719, 15098.31168097705, 61963.016447419606, 96394.80890592706, 42547.77548496654, 16536.602431121028, 94513.05079031891, 92756.4968954502, 65457.68393714645, 23801.233867187155, 93847.24665069701, 28270.071784862495, 73665.9199271057, 25940.922237025265, 41430.51528428984, 64205.31454375118, 98428.5212084871, 55045.079539232676, 88864.8670119222, 3331.612727740252, 8064.83892697375, 93488.30861214033, 59032.63057213949, 25573.863723274913, 16267.359611426024, 74577.45619520925, 89428.77048960776, 66222.77155422534, 68021.4730171457, 77459.487029463, 52947.64148147365, 64799.73995925229, 26839.731513091225, 61646.444198892415, 45033.456331894115, 91341.14642366121, 57270.457968303, 43001.918285954554, 34089.55840697225, 13438.259993217927, 89888.60816764507, 22349.316134826193, 6303.111230534464, 30134.283035882814, 81422.76047871374, 30639.89194029767, 61108.1778413834, 79006.32713687225, 28443.91389034011, 33637.106977460826, 82388.91786953935, 75974.12364617345, 55396.176560188884, 57780.30177003456, 8799.52549153552, 79257.54769685621, 60435.852073445596, 6403.050591071246, 6254.721840852529, 1947.6000633809276, 6107.36187705484, 85391.55437509366, 13383.617167969831, 22096.852850527604, 81792.87109718066, 31392.366248503746, 39521.39873533513, 53355.22017091797, 7220.787245775484, 2322.498954630914, 63743.30908317761, 63268.0937879298, 28514.762776024392, 49236.85163878385, 61787.43553805418, 95466.95484133308, 16292.777387337575, 38382.77148500776, 830.6346569768031, 35281.98772084005, 4898.473756242239, 74542.01938346525, 43690.63485405805, 51360.62193081598, 82800.91149346309, 6437.860207251111, 46324.50483158251, 28372.897964935208, 6732.676160845119, 81718.24589593339, 94017.39898110187, 99238.81061617837, 21482.42553854177, 53569.68004029839, 82850.94428969009, 7149.062886412627, 75705.3280064201, 18809.350279063798, 87093.610017936, 31727.7262871421, 35102.719255924996, 65770.3554261391, 71239.87624330157, 10578.01688297576, 98505.78747125194, 11452.787754867333, 79054.65200054689, 64488.4271573861, 56800.55620040379, 90102.3494947781, 40534.039656138724]} +{"id": 1386, "vector": [97073.20462633818, 11577.935516253357, 60002.642319108636, 83375.85778957917, 7555.181699731084, 27980.428222597508, 97262.51360491081, 89676.37110741923, 90141.48445981882, 48087.56405342866, 60737.59712146588, 17511.882814260647, 99899.17202016612, 58810.16962281118, 73842.25630594441, 12526.070328676831, 86299.54831947733, 90280.60104569374, 19169.58987035393, 955.0762583895978, 59070.07918124364, 52758.24689669551, 79368.50708670978, 25279.20453699628, 69223.43513525573, 23935.884341508507, 69026.71750881991, 38093.893930002756, 26883.712365497635, 75814.01523910025, 90079.73026459507, 55033.36595504446, 71762.64734216439, 86848.30703712221, 59813.565504664555, 89604.77490295612, 38005.16987970495, 56788.41717479914, 91797.09572160685, 60306.759113904176, 62754.588692304314, 76350.28441470233, 47624.65774625558, 94009.17176801075, 75580.49891014704, 18545.161329828363, 75736.03870515415, 60325.50526032402, 98119.85573640528, 81178.64633000264, 18476.95652672434, 27551.995131797925, 89927.18257811396, 99830.28190768132, 42960.49841443473, 47419.16387531655, 31171.554908176837, 46015.22185343474, 10026.02308554138, 58113.84721108182, 5864.7902522118375, 89037.60054375714, 91817.38332430477, 61984.26253830807, 90804.16743619292, 5042.85630924981, 10617.365203275853, 6485.471497468875, 23119.755577950884, 3273.540233568939, 11733.326293349466, 41578.47536638627, 1046.5371362370356, 72130.82510722909, 90008.89293803636, 76029.67643485514, 94798.0656811244, 17005.086010592728, 97804.34697019981, 31728.188287738, 92590.87951567856, 80955.46081324895, 13204.608235831405, 4109.702648410096, 1271.440294011783, 81659.58302314716, 96066.55582218265, 62986.837698434705, 58588.92780945112, 8215.594903040635, 38310.327905240105, 86212.07331742195, 53345.73971687749, 16727.757075718288, 58994.07925909274, 7294.091405387071, 6576.682892549857, 76889.18150843852, 98684.51538687256, 14186.293515380832, 65609.50205371516, 19040.255679746864, 33353.22465136651, 13289.111583107317, 33110.85803945348, 25810.65832627313, 41626.31764901443, 79135.07962582658, 40147.411133925816, 15088.330954212692, 94714.71281054583, 57103.21819540513, 8382.171425722607, 42588.62545536314, 99384.77845521412, 3841.7531876846265, 74392.7210000533, 31041.584945347335, 89587.10912687659, 96723.58645974944, 86140.23976047517, 38630.65462363187, 63592.68085360194, 71852.61062432798, 11561.567135624795, 25612.373955079736, 28584.37067744838, 49161.814486555464]} +{"id": 480, "vector": [33167.70631916268, 90304.65718766065, 13035.706087205079, 40124.90746474855, 62847.171664869275, 89586.97903064496, 18402.534120483328, 46935.509280615595, 26928.884651178185, 99110.65919986929, 31026.922622141352, 95961.57875854513, 60983.137426236186, 68851.67950734482, 89266.16480512638, 40459.54153197776, 43092.8387256594, 82383.37944320285, 44491.32769137683, 81412.69255616915, 32098.671308253335, 93595.93743988372, 26017.790587163337, 50682.59377251313, 13864.884336922423, 12220.093524692189, 54849.20323631175, 3646.6785789652345, 41640.45928749194, 9168.544197505356, 15695.309423057091, 42414.484040945725, 48459.288785978, 26062.71880567911, 676.8593896266827, 29622.6882167685, 66559.83522054939, 89756.9445799128, 53834.00465175483, 53897.76957349091, 20402.777481681733, 52853.36621783211, 95232.87092612358, 92297.37825309724, 38141.98126163105, 20212.047182417813, 19749.331553871973, 92489.15118212698, 33054.18829180398, 81926.13849396867, 73001.69496286739, 85296.52210957001, 19489.050097296644, 24830.781511356403, 60998.30658186521, 36507.31600965223, 55321.07098004965, 37632.84152315257, 52884.58979956159, 9456.023513289658, 66427.32107977137, 84340.04388183609, 84751.66316108695, 45674.575328188184, 42302.55799171715, 20742.880778625793, 17856.80651827535, 38615.886532842516, 90672.19919906059, 68953.84936803402, 8837.137263861883, 15005.276892097063, 27811.085379961776, 92289.98224037941, 14915.877571172043, 44208.71171708408, 64625.34842969164, 60333.94023020968, 16357.343339161678, 15199.909202486117, 96896.64339199972, 23641.872458319525, 80611.51101475871, 65379.39918082659, 19187.82532387071, 7705.425943951949, 63281.00697630169, 57928.65264953659, 53281.588167450434, 68759.141846628, 78387.32247381216, 63242.57555074602, 47797.1815505344, 67860.20752406504, 12557.278491528312, 46594.79671046263, 47267.92411167219, 42540.22669125962, 55502.60573955085, 34974.97072977919, 25566.058023230664, 39301.431703815026, 31438.359678999816, 80003.00770288566, 92891.5874569952, 22853.88354154433, 91977.12457073147, 65613.14518396469, 71426.15673993595, 1668.8123394758447, 54151.60506322186, 76966.91862548923, 5825.097061664442, 66050.75259449217, 30375.66212659214, 55608.875301568296, 50393.57028381086, 46692.214689583125, 76771.42050146348, 20619.51553733561, 77961.686387402, 25236.648490211777, 85838.47854914407, 99882.88756091094, 55862.32161672118, 66062.27646235205, 57058.214439952615, 72071.39825900346]} +{"id": 43, "vector": [18698.772181921253, 5237.963730484474, 98171.38321296092, 76914.30653675263, 86350.99062721845, 34776.84809526639, 337.28643339402396, 46997.01803252416, 96070.24079826531, 61203.1816825451, 89628.2689695853, 7371.256836454121, 7254.310637195538, 71974.28809699748, 91179.97891237277, 81193.19086218034, 20348.117993081327, 79625.9163098983, 93897.90053879967, 59721.62832864649, 64890.84833366906, 51299.4853259346, 82725.92235947029, 16066.90890058402, 16215.31978969739, 6752.404088832387, 87813.23256370073, 94171.58572583516, 88973.66085573776, 92125.54187311772, 19470.74043597361, 36793.17346183293, 49513.65314827742, 58250.1227072822, 10274.658409370119, 44923.406670475764, 82730.22327664433, 57807.89419904797, 55505.74229121654, 39493.14827209396, 52927.30970199573, 64418.14124670805, 98548.47263968733, 60355.135794046386, 50460.505233603544, 43796.97489077297, 97704.97274059744, 66482.45698161119, 75182.53056542049, 7762.145220351924, 50659.49561227564, 36436.499082636634, 35263.22194821876, 83037.8981186251, 86165.29024618848, 85623.5520200693, 64084.62853670477, 40908.99062075397, 65176.65200088435, 69473.95384986911, 13744.243515764609, 31942.552047605466, 97338.51334649647, 17392.913077929374, 33984.56928225671, 48826.55597032482, 83747.4163041184, 76152.12845770954, 17871.239694214903, 60354.17468516689, 27633.252907414386, 87623.06894605498, 65235.82706712665, 20509.37290433046, 80555.41332908308, 63299.86613537474, 96166.9532645669, 54681.13919917284, 71507.62824433541, 1447.9469179962678, 74341.62716489255, 3561.5314358815153, 6710.601246864756, 64978.70373875818, 26598.386202058744, 17581.15414135135, 1704.8982908334676, 34239.03575044077, 78560.79085539687, 80836.67596448213, 81243.08061030971, 26263.543903325248, 21895.066276817975, 30133.181830529942, 34630.143704765345, 30210.776697407815, 36248.753796548204, 95260.51473500782, 61262.90276396902, 90849.29009049565, 53867.99883371981, 71904.11967608504, 74911.80330222582, 81623.34338725128, 82792.56998835005, 44758.58518760353, 98209.04414406455, 23171.80919576094, 87150.89475734628, 40834.53171909612, 70236.74398805582, 11276.7698029413, 13538.343725201174, 10078.42973345423, 14831.512735673925, 1004.8787478010856, 47165.41698193993, 30887.224362340727, 54293.997853088185, 65673.01349685929, 80046.96440980444, 94977.70137539342, 73554.23474031634, 26979.330664115285, 38563.753879412325, 91322.62784122712, 4037.7359484248145, 24422.793960160205]} +{"id": 1338, "vector": [50494.63890354861, 2845.5746115010115, 5692.729729362766, 23794.501267291424, 9728.958797619358, 84998.8401066901, 61919.972347707655, 62102.50586798737, 70093.90764526861, 11781.378272890108, 90149.6483976027, 34620.217758547675, 84376.49657788301, 45743.110841462905, 34899.77903199594, 94589.99114038188, 78325.06638775843, 97407.47914737715, 88306.1042290798, 54093.365446366806, 1256.9637336688365, 65864.96531362331, 98122.21542765237, 4083.3321312352286, 97096.4949540575, 96374.87420707071, 48284.47910337481, 82869.25173238471, 90886.20594697741, 16778.007962132146, 65338.16762826688, 8910.49531087148, 54732.96471733427, 61214.944681162444, 41461.98848629381, 63799.85369229047, 54419.024418948844, 79419.03702492775, 54109.2353043285, 93832.59609284949, 79191.19379068632, 47267.43252497666, 11675.517902037025, 3612.5199819368127, 4910.102581108111, 52415.006845718824, 53732.15923548758, 18368.288925695164, 42909.74759192975, 38635.345101275445, 9621.709479273966, 42854.15674834099, 62442.98282017444, 84219.60308526385, 19159.310369855877, 80641.23595713607, 86434.1920478949, 80074.40472803678, 14044.494430535315, 90679.343373068, 34145.97321884304, 22983.68359109193, 63446.74471761924, 73832.3438912174, 12612.92790263222, 6196.209704223122, 94548.40302802896, 13425.19688310362, 9811.196518386878, 10261.641324938508, 64972.59851882501, 31464.13440739123, 94502.11855290763, 40637.69056185027, 46600.049048517736, 69850.13247638199, 86171.23576465188, 52519.742654267655, 64545.44652217163, 61343.554833351045, 72887.51750382384, 89911.53197216583, 11485.317585519517, 46530.72068834327, 22102.306444080798, 62452.47973428881, 3253.405379283336, 87258.83810235308, 92310.52105459811, 76412.95764858315, 67603.5600253058, 40488.12559080371, 59446.807051129515, 85806.11411608706, 10540.958114142817, 68170.94777755649, 56519.07358157273, 11869.21914593968, 10485.46954781865, 96984.03796107491, 92905.68423977797, 66068.9235276524, 85101.7550652178, 15727.535788843328, 61672.3747169168, 404.1045944809585, 8318.69806345108, 40277.49498490706, 4420.971756423497, 47812.53535013278, 52936.23912632879, 822.8578355754412, 20871.509817342514, 67660.25388561905, 44884.37164262484, 63049.4257872635, 15241.722087742615, 16869.221285104297, 64343.31219837897, 33365.62834396807, 40450.22769386666, 6033.596499241445, 80754.6678458222, 80797.25945083195, 74956.5545186849, 60618.57926740912, 73045.95548707971, 93038.89455431774]} +{"id": 2009, "vector": [60507.91592265229, 74879.85107819893, 72937.36940533969, 9034.127469336694, 15239.927192705238, 6054.495803265536, 35126.11869566542, 49574.00142068321, 10300.54962630762, 42734.554696542014, 23177.41244304129, 33087.65194248033, 81924.35125762998, 43929.940348409094, 7177.7805682190165, 75855.58472087739, 99694.32731768127, 46498.28470470337, 11133.323334806588, 57852.981198496964, 13762.662529491932, 2787.898544299028, 32908.24592175833, 89579.33543736822, 25742.295717592457, 50234.41018090802, 6920.075885267341, 98311.1294743961, 22672.243868277554, 65457.971314890514, 17790.554555246752, 61136.386821170905, 81246.26282104164, 83092.07870750847, 66436.37250053929, 77433.5685561383, 21550.42876538893, 42150.415586873, 48103.3712030489, 53505.38153788765, 87793.57209248249, 56658.446291334854, 90025.4793941304, 9134.148058579673, 92182.46371446425, 72603.68363213724, 393.89595943616354, 58213.11232070364, 54631.8635330307, 97105.65190331852, 81221.49869097068, 38244.62208301284, 89412.13234145325, 69152.86591737957, 20553.862696969794, 63176.25413460568, 61832.95589539286, 43786.26900171878, 17412.062442146736, 32781.05286773523, 87191.34191704418, 585.9903205190586, 69441.96273740391, 91357.10795599339, 71321.48780772484, 40778.309921828724, 4055.041232386913, 81276.61207498248, 37680.84257409023, 48129.48883052009, 70749.41932157463, 53345.41411340941, 85641.12574010305, 37284.782076013566, 42641.49252123457, 28045.025156918968, 77252.8480515243, 96683.84620178535, 2351.9667072504635, 50461.83742379119, 5149.541243377443, 19717.590585566315, 26707.20293227329, 91498.41170055191, 52818.044877011074, 69864.67075400142, 4390.0567256852655, 61895.24885534961, 55657.88482360611, 49831.516402266716, 39624.97283092236, 30317.16145583673, 76888.1754176905, 65974.06283075489, 27276.820850354234, 35177.99272835111, 48053.89178662095, 86054.86043948791, 5626.344868614042, 11602.409477803, 94017.67316469226, 89938.21529472676, 29033.773821604693, 88663.53104546687, 30832.369100855205, 35513.06870526736, 91838.36888344276, 89669.34585791238, 59967.571536529686, 19315.44136105845, 94211.348899365, 96680.62249154095, 3226.9692727624697, 89767.99186089633, 74443.78661388166, 48116.6657041857, 53501.12324325163, 68763.55456657524, 68018.16405782994, 1143.3261160169739, 55280.90096082853, 20222.692972602774, 87933.9064556954, 49708.033055239684, 24576.865495913404, 80082.71801612231, 61493.60081068629, 96069.53086996016]} +{"id": 1085, "vector": [72037.27124217538, 4893.925612179273, 31841.260848838072, 47737.829695764725, 1709.8401476951296, 38600.742516009675, 85062.62049323553, 66416.65048407491, 88999.11729006625, 60425.62903271973, 80662.44828811938, 37960.354977115094, 59272.95303516163, 7637.145810866752, 88053.59791580589, 94530.18626333539, 92619.38732014438, 63906.70391964275, 90256.4123389455, 80092.61140515217, 17119.79409062465, 87961.72669592404, 70376.9622238387, 64727.43018562239, 43137.73095518287, 6006.074367963543, 95827.7437853501, 98394.94507198717, 28022.87949585648, 13453.209950110844, 64434.29409093606, 18839.824202548338, 60050.060743934286, 55776.12318622628, 9425.269050255347, 30830.61773478204, 56826.64658747934, 16456.09615198891, 63380.597050676944, 81310.81820455875, 41888.67068230736, 97324.11741630848, 39131.66657274004, 92527.3937942546, 48723.84252695321, 41122.83643447683, 89626.9320154953, 73740.59079086519, 78914.17929530126, 79462.95406932561, 59310.21178788174, 3100.601331403219, 89458.46340016232, 76053.6677082992, 5075.453236630734, 92412.83254481715, 19303.228534268026, 60654.91684602963, 94919.99872949939, 26212.32785360568, 28947.142785906643, 98101.73505663054, 86706.7726551653, 56312.58124167325, 19237.72605155194, 15220.611290384268, 46873.044580214315, 8053.440682735602, 59980.99938893705, 3685.881752989717, 31150.888901744787, 12518.606011233102, 52428.64020234973, 70453.78792583197, 45791.32616517171, 14196.096659246148, 95692.6195630752, 30264.60758824113, 95831.8877833261, 43249.671842119475, 26939.646317231734, 17925.767992175202, 35393.65586431742, 66278.96252459002, 49580.67996175174, 67902.15209918625, 41167.37326332632, 95901.95323062892, 29939.884685488527, 29622.208710262265, 38739.4894213516, 49076.79578709916, 83687.70523380858, 71514.75467811161, 9652.958892514374, 45533.18859108582, 85983.7251643924, 34165.85463272166, 57466.57409608045, 17868.067372352503, 63638.70662853841, 12728.862018700947, 60407.70877131196, 86907.82866573233, 20240.65170740006, 79393.74663770777, 58909.191736860244, 12944.348333708644, 39296.34930417222, 68817.05763008367, 44785.26696062535, 60548.423146104935, 37190.62755054432, 11645.12824892765, 19060.17522178677, 2572.21776153812, 78516.86638196225, 99845.03493507078, 72444.04048795048, 80139.33180163168, 91620.95737530374, 39450.14287340325, 16354.475031137505, 21750.26355972145, 11616.13826146185, 47393.73314055416, 18543.56744507455, 75103.76424712194]} +{"id": 142, "vector": [83576.4247603512, 13401.519920039273, 23015.217411318954, 52745.16239758704, 71194.05094406028, 33994.0113935006, 32907.34096903151, 25081.75108446198, 51199.56708583693, 52036.02721061013, 60534.86925801633, 75809.06021637973, 57769.410180624116, 58220.68155664659, 97570.40665104333, 99137.47002663917, 62705.4071275682, 76440.29405670128, 24746.807950269424, 88162.90756490253, 15742.966118459733, 71823.05440258431, 17809.492236052283, 39624.967922322794, 25599.03158542304, 32340.778250268253, 82967.27615581253, 53988.20098846613, 4390.053335251975, 30373.870408232284, 88847.18644551671, 25507.115332745954, 91285.97317203795, 43936.97173691362, 6136.754241114039, 26038.599908376757, 29085.3702759593, 4399.351485023617, 34029.021921504354, 72414.34469617026, 51110.94317046359, 99027.74647512483, 88741.87947716474, 65368.547772473095, 35719.67343765948, 16088.37617108798, 7690.9335486674445, 38223.008228932646, 91517.89898130554, 44415.28382261859, 90507.36078877345, 19644.12306197445, 87698.43547023828, 84863.31768327628, 85800.21405546088, 79263.73102310968, 82085.85649176459, 46721.30833576653, 61493.70679877123, 17938.604128074032, 31178.405961163837, 7533.1768352732915, 75827.07548601084, 45762.730518317505, 83903.32102654921, 86918.86887946576, 67186.9236408468, 57445.6213200747, 31733.94257347173, 2649.120260324067, 45459.48912502217, 49449.353312538246, 61174.09932926523, 22357.390832091263, 10728.971207840954, 5417.6557741623, 51682.31335179224, 46080.36811028879, 18138.29293306497, 56453.57942479645, 39649.23772663306, 55160.66917778342, 43350.95928743979, 79542.18226491041, 18893.20718468427, 26431.00817974715, 37164.353134469544, 72665.63100297464, 36094.562409887556, 23074.122332733627, 86151.47106869394, 24492.95866250518, 98184.9585582468, 46127.71126618307, 35217.22822348602, 40134.53750759584, 53993.36302539829, 96762.18507361757, 10565.859060228933, 92783.339633596, 25506.644829667246, 39768.39477158172, 45192.540513435844, 98777.4627703667, 45995.95065096266, 86481.00064126955, 35971.03483714456, 32464.594748339183, 31064.289518292433, 28506.3947982526, 55585.36838141831, 66740.46960653522, 46900.78872748781, 72229.31308777837, 29788.663503487624, 48376.88939034853, 65172.53065962708, 96737.11610486038, 73315.43785638938, 43573.50364681735, 24456.218911668813, 60986.37048545908, 2082.184435311485, 74425.96588635149, 79082.94674035489, 67516.80605525817, 5767.616942089205, 73175.70352659826]} +{"id": 893, "vector": [93421.93597321556, 35002.71787601721, 2957.976962586184, 38898.380049442036, 53477.153795599654, 43318.78398913239, 30900.32333997491, 83072.43782846676, 90971.5554758863, 62066.71213585668, 93936.69541891747, 63995.582891684164, 35735.551674496324, 92840.84999018256, 41598.52932162732, 56376.003942287265, 88512.05901328719, 66658.12641786349, 99251.51020131659, 42988.27487591242, 40272.56499955095, 29465.819149645555, 10389.478891368432, 84613.32699465968, 31997.582911026977, 86870.48445239071, 13695.914945557997, 64859.01415981653, 39064.21207193914, 44972.67370219433, 38872.88612759177, 65354.218812596366, 49619.48775666268, 10197.883683078424, 98169.35069139792, 81729.20093188333, 75820.81509279886, 18234.48876147611, 84075.19909633452, 38891.59397737089, 2546.458947967145, 86588.2223180868, 38989.57781182641, 21292.200854904942, 26643.081857804194, 41097.984054502966, 66244.30794705963, 30674.61232023021, 64292.418176087616, 3774.6406184327006, 26186.419666090864, 97052.51578536553, 76007.18623330693, 73755.07165787056, 84037.32837212272, 51371.96169083129, 21976.071419554366, 45203.196612715634, 49241.10373224797, 95128.89694454905, 22110.770714441896, 22434.73436593947, 98847.4777219111, 68550.71971428978, 55764.35163246826, 78882.12459130527, 63140.95680935417, 50831.80868926967, 63398.183742575435, 4795.411006663564, 15858.277710356317, 25024.209101082994, 7418.762042296978, 74425.18012399247, 33017.78058188508, 70332.48408192965, 22542.96777877146, 5606.467571146645, 40954.595641243905, 17532.586463531108, 31444.04591365527, 98762.59136592304, 52061.663216468725, 23476.542448029548, 24545.766254457958, 18425.02464106278, 43620.78036037969, 26889.118971822336, 89159.09409834997, 69568.93591118616, 54432.50854988574, 98105.15083979143, 45120.04015386558, 68668.18209010058, 89710.55538830296, 88668.75920317203, 95803.82066431384, 18656.71741617504, 90053.862934552, 64799.36956301309, 29632.9888562495, 1702.5789462480677, 85413.04535654391, 65445.86846527399, 78723.20353992959, 64160.76560406065, 43122.61018714529, 89564.05128533336, 45917.35638322768, 88388.76284034905, 11229.143546615995, 8762.182462151202, 31855.887596338085, 26171.045445598527, 11033.48749150207, 56448.71167021602, 74214.33136886421, 77596.07281150627, 89874.14258734032, 20033.157465855, 14061.388462807778, 35216.48287202819, 27127.76309890379, 59149.665235834305, 8038.1038113957475, 78658.75327172248, 7887.356364111675, 42031.229581829066]} +{"id": 58, "vector": [38693.837476686655, 53701.367663230325, 26333.591121043475, 95527.77974879244, 14902.560547508592, 62631.431289747976, 29859.386441148094, 33591.784595819365, 33271.25126449388, 66161.8941162348, 70014.1615079488, 72002.04361355724, 22260.018459944396, 59691.57663470063, 90035.26439880088, 2788.97406901486, 78671.51850578554, 97984.87982921881, 31608.45997767089, 74573.83539597862, 6831.828866592182, 44769.836580072384, 48836.08545239141, 10282.91577161442, 3699.039575227403, 21668.397340998414, 63271.25193904435, 68096.04259576355, 28422.744630677844, 77082.71294043149, 84101.42551093249, 43364.0253587985, 41408.40726529846, 67259.40908859555, 97035.28885475737, 90187.20840842837, 95260.23287331048, 3979.44884853999, 96786.65767371733, 39696.30819814261, 57324.843043951354, 99377.40545089611, 50581.24206555292, 45948.46496835419, 93077.70302016902, 40166.26194291356, 23144.854714355857, 12771.92954276598, 54266.96956491558, 27490.280787721567, 435.72982431761, 41392.38874088003, 90817.37669280116, 12158.788290815724, 41314.26191044899, 78417.68345122704, 46239.6355551402, 95320.25702095854, 32089.082869933503, 17358.297193578554, 25394.602030786053, 22692.414595119437, 23326.055075565677, 77194.27810037218, 31802.845043527006, 96415.82489876522, 36603.24397869042, 35852.860700410536, 95497.8399920529, 13223.85210713971, 35116.51847869054, 5291.664407281038, 32526.227430360144, 94454.84411835097, 71282.60281259469, 9814.667489890982, 42938.835187517674, 40594.08318243576, 45531.49448491347, 7709.579114260235, 2792.928110437298, 78370.81727389505, 13162.061292194916, 15685.013561879457, 14962.75464968564, 95976.87187558638, 95456.21941018967, 37015.14825681925, 49781.68763376314, 70558.74469727234, 33744.573680566995, 65159.343925804395, 13825.93740682928, 26502.323812334504, 86482.27981431877, 75526.14046191511, 70192.17218592856, 25549.732192283027, 13272.532750361699, 61216.12158902604, 52763.07440601281, 59102.922809655145, 42790.97585695481, 1516.319327241078, 88327.1612023932, 85979.37164067815, 84343.00625786417, 84449.66226327202, 40520.551306914764, 93964.81921187932, 54279.4418390441, 45462.000665172556, 5304.799113418301, 75617.18961523598, 20060.406906395066, 76928.6584853512, 66167.7107176726, 71971.10681183144, 69096.83648805211, 59477.73232348757, 16741.293091280597, 17050.497113106954, 84894.49206064852, 15852.873688563574, 16984.16602210021, 62844.4159845553, 40398.0623802038, 11670.281452227782]} +{"id": 1357, "vector": [30621.892662127582, 72355.44432135588, 74021.90938995514, 85332.2666151682, 60282.079739155015, 95907.10423391622, 83020.37365273469, 73900.97665545215, 81582.59384943383, 39773.52176712522, 80041.19235659433, 81490.9512219116, 26974.758076857386, 5806.28841435259, 32276.331109531988, 7986.4023475295535, 24155.622973463243, 87633.38973896188, 63269.34017204695, 19632.094867153904, 71442.75514339146, 31338.55953372292, 16543.079207290568, 3849.9952918873205, 29098.969175150323, 96281.39065484311, 93147.91552012757, 6004.358153577793, 87801.92185791323, 79952.33231762446, 79665.8688962601, 74400.50228926593, 13738.278115745272, 44329.85175092098, 57449.777095993406, 17364.88682606592, 27465.908164405282, 96569.88249170227, 93987.46514582132, 58613.38609842052, 31854.007942145214, 58300.19806210298, 37141.139465035325, 5093.80154525263, 47566.159664035426, 49274.60149096008, 55875.241569797785, 98201.28961306947, 42699.21092434406, 6320.495515199087, 17383.401008566278, 67557.75791822837, 15878.952411233071, 3233.8237089734043, 22398.765294779976, 10681.14096415963, 62921.202184794354, 97931.47691695651, 85456.88083518908, 3663.970948672912, 70860.33331079953, 47828.10069909909, 64839.987824154145, 41357.288556653184, 33898.23754589699, 95706.64261507004, 54817.8927519125, 16984.830719454512, 17558.22632802324, 60543.72839159404, 2407.3381675677074, 29961.241144232543, 32863.81463405135, 59841.89128511323, 22800.875220411242, 52618.91147416001, 78519.1621776405, 37409.21559187963, 49000.61750518398, 61295.95524108121, 74258.44369116591, 43493.59494292871, 78462.85661206755, 47629.67091875446, 13938.984463878567, 5546.952491806245, 68620.84106824579, 60201.67335731582, 41874.039294292066, 20573.14387857425, 59575.19479044709, 2128.3320655061557, 24071.91247545938, 64814.059633526434, 24284.840069528847, 44569.29568939776, 2093.863682760133, 89848.2339165208, 37310.78636724049, 29127.85173219443, 16859.371076649564, 95938.15019972518, 85482.94153403414, 43066.40250838434, 31082.947052473155, 10127.111952912172, 4331.672843702406, 7268.689664011318, 22263.713352111037, 30086.63633493761, 73444.4762359541, 20242.45148349333, 47724.63968890146, 90987.91852446378, 61978.05251831506, 86530.82242566539, 45001.533784935724, 91162.00214149726, 62807.96856888858, 2072.6053234280453, 64144.27146013992, 11685.972527345057, 52038.346416207816, 84246.87841265606, 70699.95041339133, 11762.89470111933, 70792.32077914609, 21849.81886815691]} +{"id": 2033, "vector": [77220.59598024392, 40522.09854977955, 46825.49354093603, 69098.99892126267, 80415.40935090599, 94591.17553815682, 31294.268917358237, 65966.17934861172, 65565.9579560898, 61200.17692484099, 55943.187175584375, 36166.91045728097, 37342.742743803334, 27051.038238619396, 91811.38785768888, 14109.491931192264, 96627.62649610855, 68571.67579553966, 49124.3409453382, 35554.02218300221, 88845.54961593439, 93809.34914322109, 95508.08111260219, 83233.70507784598, 32831.812298634424, 87150.37968750027, 12918.927208027942, 39622.7287512399, 15973.86186503359, 98440.88999670389, 24644.573745197828, 22778.887350992016, 55069.96957079408, 71485.75855727539, 20555.705815515346, 97863.75900093747, 17491.2650248211, 8614.766034111044, 25102.85542104056, 39763.92356302465, 95308.99765856555, 69935.86464633454, 3914.013528045068, 45077.539148938864, 69368.1423930479, 51204.805802217954, 82356.77682338207, 73094.52380671531, 81579.52401723017, 1502.864053267483, 71071.43185084563, 57534.301695659604, 21466.22376398998, 60840.25714541463, 65252.20521412821, 79956.41277705623, 40994.457437538644, 32286.731669658286, 52006.397923893564, 90218.1047782874, 81860.2629535855, 22609.024936330636, 72871.53903392519, 6188.046006518511, 59700.445869426236, 10562.681810511776, 26337.05458182054, 66485.58376759995, 90783.23829736386, 9946.110020106225, 26101.906501243076, 89147.6806209144, 66503.45762742116, 50158.80318050341, 57105.18835946458, 82124.16323738889, 7265.759086319723, 60926.91825944601, 98934.72247149696, 78684.90063157091, 78708.58795532181, 18682.760524340138, 11058.760812081291, 74967.90453653777, 65581.55052642494, 97677.3721609364, 80893.79686566534, 73121.01897990561, 12999.79066385284, 14170.960774185693, 73039.39322610882, 26735.72087634798, 94780.23373278991, 93888.07877127013, 62667.371097848045, 37615.892518050176, 50687.94807099071, 63160.993380544605, 16553.15491815922, 57365.97890999781, 36349.800386049494, 36585.90142070011, 8517.28752052363, 32954.64413082712, 99875.89231584943, 61585.54666472156, 95872.47400696421, 21673.50856915963, 16112.62001245579, 73421.7986096251, 88208.26143407905, 62494.25997849053, 54112.20803354297, 10418.754965957189, 71041.98692146184, 57608.755321217555, 10801.416187221757, 62684.49106758779, 96260.89730542076, 85433.09890568367, 79508.51574604257, 27590.079144312596, 33368.89184132325, 86509.75118648696, 31982.3101473095, 60521.481781391696, 47436.10940091475, 46079.71778323239]} +{"id": 1574, "vector": [25480.661697205764, 27956.656982841134, 14742.156786730964, 68525.5214307843, 27558.096242517793, 35088.538834101404, 25047.18431377463, 75322.35706853402, 2614.390784857612, 82409.14512785799, 87434.64830441258, 56456.71879995803, 86385.6006209432, 8485.682960700036, 55002.482360611924, 74100.63753314634, 8456.269617532818, 87627.79398697772, 63601.733264912094, 41117.89848163456, 24402.11152630126, 81882.27047443316, 74759.24118254124, 78918.47569750123, 17070.73967290569, 55779.012676133854, 43880.20166373915, 27879.268636550227, 11396.48151392071, 69628.70956707787, 91156.38061895985, 78429.9113482419, 6915.049835199216, 4969.940578348808, 22479.39313205627, 35990.61030732734, 41502.368720296945, 3316.244949712266, 23999.71694637515, 55792.89739577131, 47917.273333547215, 79767.50074736583, 92151.2758816275, 8812.941248477635, 13446.655628185412, 22941.27695808138, 83315.75080935557, 11671.182281302217, 39102.28917690906, 13273.101715295987, 25824.320871107364, 92419.05312014904, 40053.15507145698, 12642.051755180162, 10230.340485620582, 66641.46019922802, 12278.871829357451, 22038.10780414531, 48280.35990230433, 19795.10981290734, 6918.13229737539, 17082.23182884714, 38906.21856564157, 31426.81469727616, 47583.68278787829, 51427.308070056286, 93329.58407419668, 65946.46160408857, 57003.85112830506, 65637.2475577148, 66417.77650215746, 9769.474705094804, 55807.080808361774, 92320.37333300586, 78858.88714710226, 72714.92306774041, 38628.67069287932, 64605.88346198861, 44243.44160253334, 89069.157121465, 46256.3428667627, 27000.27758237614, 34503.96306196742, 30859.76497874834, 97363.15026529603, 23950.977412674256, 51175.18303243452, 12961.749704491733, 36564.709313856314, 78934.68166227883, 25601.11878525273, 41365.28791207563, 70553.71384886079, 63864.521996749456, 98796.64139943363, 7930.979962149109, 91912.47277727356, 63272.339047587266, 58146.75884890125, 45347.06110733898, 19015.399612287132, 17962.072939508478, 31631.117406883313, 42064.47455185873, 29372.73886537687, 29954.851194418, 5859.428827788948, 20728.068509727495, 33811.56896268565, 12401.442147975638, 78227.92941550106, 81292.92956199734, 52512.07525169921, 93041.88475113973, 17226.83171580982, 14399.604007520129, 42231.82952739571, 85946.9961147034, 6965.166600151484, 74491.64863741564, 40845.15463563556, 65420.118589296006, 62136.61864264499, 86647.01910744845, 26396.103415715268, 19584.80824654394, 11360.176992391713, 87600.86161481228]} +{"id": 574, "vector": [53249.21514853769, 75726.56542191138, 81521.28608626948, 2182.5255855415617, 8207.95953144351, 80130.68077001988, 3129.6703249644174, 33727.26219845109, 36033.41782284526, 10714.736523732516, 71939.19573461227, 56503.10959172623, 95358.85945211766, 74977.54788387011, 75177.45937016315, 45664.69787043533, 97076.92897957156, 41821.5077457466, 99344.939344258, 52724.92618460776, 27414.921972433327, 70249.46894974634, 8235.3792289229, 61438.820722391174, 61000.569028322236, 81213.68591938342, 12825.94809944062, 18335.36971235693, 86280.26052100651, 13264.06351728413, 81103.79095143125, 30439.194361023438, 52624.651792395845, 96858.79877598218, 10799.998788750665, 91544.12851965029, 1242.5652839735512, 26580.74080016982, 110.41092869078506, 15589.235072666474, 64039.17492777287, 29044.765029073584, 36560.45999513394, 60263.455082426495, 93981.43884151234, 12717.814085205859, 93013.20811431465, 65178.947698813725, 42213.02717740949, 7184.935330696562, 60368.236909223386, 68520.90133470105, 38142.49204606847, 3699.280778785463, 47435.19325601927, 90045.1485142838, 78416.21232367543, 54086.881215006884, 81445.05334586625, 28715.34170376295, 12907.054118674532, 42355.200402895876, 14424.658089970222, 43355.27427546888, 8160.510809005795, 89266.40863934394, 33722.389830488166, 67882.95079209864, 17669.715002974328, 18109.14409156925, 58434.30303438935, 32918.39543558044, 2594.9344361538283, 10134.99408897447, 82622.3254801644, 72497.22264839799, 63722.531575556226, 35502.197440997006, 89021.63908326211, 64835.50576186411, 77960.63606523241, 5651.463932549916, 6058.429355184824, 98546.5947118228, 12471.716850118231, 68969.88695475174, 29129.375162164673, 99289.99034926038, 6147.556667391751, 2587.1908698833936, 56173.849140602695, 53209.9835775239, 9432.448921581004, 82476.60011111034, 53254.76157932406, 77195.0741880424, 94521.11346091634, 59383.35153008439, 75571.82078666046, 92818.67103151034, 96811.30065435132, 40007.91827158212, 94982.21884618253, 47856.38816960437, 92814.61389003198, 24228.14974926112, 10896.90617466752, 94507.68273369767, 34012.92383081545, 13108.56787047101, 24658.960078901117, 49247.09620688872, 78479.24114339935, 59428.4735628446, 52439.937357383926, 60112.75963403154, 30510.195234316663, 27441.063233903096, 25821.14113540781, 34330.54399461486, 34458.6949128963, 3752.6079963057346, 18849.821042492542, 80825.17861866721, 9734.336545147393, 40541.83403283064, 80518.7101777954, 83050.48167113395]} +{"id": 1199, "vector": [58696.50191571084, 84235.01395310988, 15305.035410920531, 7455.515185775796, 16858.45582345181, 19441.032127100345, 29316.889170075876, 2024.3059691045562, 64345.6327587666, 40174.76420795768, 28429.947995874016, 26847.90407331765, 73474.17740768389, 35946.29052019973, 56669.62897727674, 5435.868073529693, 85469.04884536423, 65266.83899755285, 16201.104349821117, 32879.89042643592, 19577.125772993397, 17720.0162752845, 1311.751243321746, 49347.9769057196, 47051.48137196337, 14659.36001507675, 28618.841145379025, 14615.389872016238, 6128.512293345034, 41705.091731211374, 70293.49918481355, 95992.23924089801, 53416.03067767731, 87128.53081967044, 76772.6455533123, 72226.0068393488, 77895.93584299705, 67115.85661904495, 61669.70473439904, 45049.00073926164, 68965.24457588697, 43610.39480930808, 8398.731640669666, 62618.825732698344, 78190.10528796291, 60331.443027172725, 49237.083601440376, 25120.13089910199, 66425.77649767893, 58121.626508826215, 79278.82036393369, 38669.28915755349, 21827.51817142442, 59944.93707288251, 25325.387903721374, 72356.02844194313, 98891.87408547271, 95047.16343774644, 52012.547394306195, 24534.718843317827, 55080.507465632436, 85957.25735947912, 31776.618312621995, 16855.485163962003, 68323.9450519606, 25834.75410134214, 90924.46371099, 10874.70514404625, 31841.741706763405, 34334.5455267299, 30090.99193486604, 52574.06875497649, 17151.041294604118, 24871.64133028871, 53981.02381970162, 64040.63656786191, 12172.848469221053, 5241.573959031898, 90379.77665223657, 619.7631543910842, 87995.2860351859, 70912.05675403829, 34161.95964856465, 48172.38879045709, 49275.530016371515, 66315.37937589065, 49862.83761689781, 69724.5217939727, 56625.18205183866, 24779.723367395523, 85539.57870011302, 19420.442263594305, 82384.87355965102, 95960.05667257377, 82467.36875864201, 5981.213148381604, 63045.39242455055, 37222.625521153706, 26330.26359888403, 36001.582084272, 22215.471745749182, 35000.529642376045, 82266.98473926811, 6580.820191208503, 5272.69129247917, 70874.28374802457, 22242.35330422484, 88266.9213704697, 44013.129197516224, 93343.39149245486, 64083.75421188807, 15416.897991354239, 5281.908539330416, 48875.89216376853, 41238.49679646069, 44530.83690304894, 48558.855989014446, 39707.58352138915, 33792.597419095335, 47881.019920832914, 17411.702022595644, 3053.062519862615, 97473.05498897602, 58877.65832372245, 40725.10189438211, 25329.05568368318, 73825.11728077536, 47253.75698382447]} +{"id": 1748, "vector": [28100.502330955213, 64131.07864199353, 21254.630426121625, 19358.504664674314, 36379.366642105604, 15243.18162272571, 41405.07243113829, 51711.19436959654, 5127.3472978825785, 99711.44818234522, 96448.10592563909, 92431.3742030827, 40351.04987028603, 64378.39359388928, 54631.10532749001, 96313.11148444515, 62578.702535213546, 29187.422132016207, 98043.69866562026, 13297.582661448403, 90933.32559361639, 10927.80488466234, 84927.69239131598, 58854.7877818674, 19409.059443895927, 30916.664248529003, 32968.92590181366, 69028.72175490705, 63905.90428689876, 67203.26282261978, 78807.95385267309, 29562.880138047065, 61192.78876817906, 51169.45482936872, 17250.375323705113, 45450.63350307519, 66238.71241140083, 2365.1717271643056, 1542.315279771167, 90364.29837950018, 39151.4086494011, 64920.82623495471, 12336.92739615353, 15035.28168659991, 71465.42625156195, 3282.1749914393795, 56020.186504999714, 67132.31005485676, 28173.013787998934, 74364.29814712849, 4143.454923491352, 43951.17491535931, 58266.79740288484, 13319.514965454004, 46719.43264035051, 85469.30882223134, 45943.63470673964, 71418.70030996071, 69312.2848110976, 78800.08405083361, 45202.247305778044, 24558.882365309477, 25451.878700053498, 14950.063310613148, 45453.281846482365, 95178.2947494993, 73082.29003698193, 49018.6750032574, 87368.3305814694, 81417.72208447452, 88443.81472273715, 28512.969516795773, 32600.28921259307, 43252.75854381811, 35121.28415721769, 2535.063456312092, 27837.179394744704, 82145.00802245688, 88819.40620063787, 46561.650580695794, 31632.20624898263, 22714.715077575533, 14071.811988590576, 95773.08153311921, 83117.05704126862, 23391.62333288738, 40235.039114003055, 32238.10280116656, 86632.55082861365, 35325.679085496246, 73138.1225397974, 32503.326945062185, 77637.13135429689, 11736.798429738827, 66208.33844862222, 35141.87098500493, 70165.23384795742, 28144.38699746724, 61981.628214031145, 3866.3918511344764, 92347.66640533258, 93922.5333745351, 49725.73495967617, 58813.23580337515, 27461.45668683092, 81341.20230882007, 18813.16371881042, 15198.319922657543, 6590.697482521768, 99452.17341684425, 24105.126743362125, 57148.47786235799, 10479.658991280305, 85544.5146877148, 92525.36393534814, 43887.07041373311, 18912.050889174738, 8549.988134797393, 93083.41454389122, 3080.4661815136415, 41300.556193459845, 64634.70427684138, 77604.81656539426, 65092.75734752455, 51333.24758451776, 34751.65827671427, 3188.276544574653, 95155.70978180128]} +{"id": 1773, "vector": [83938.42847879947, 57494.14306510597, 4593.5252921277315, 89544.37798081838, 25785.10720551256, 91083.85387854172, 26598.636176249867, 5353.845343955232, 25885.846795775124, 34390.811497554416, 50208.98744627479, 99144.7440925696, 67617.32267794403, 4614.257708254166, 76164.28872066294, 66162.55325936184, 26944.887207582047, 10399.759542293285, 95192.32288784177, 63287.53446159936, 23413.23089143389, 67273.53201922163, 41355.018841293044, 41183.9752793279, 82501.63523720422, 40272.379901599386, 86983.43400357968, 58855.12062386802, 87377.41416596143, 38297.33645955824, 87798.1950323256, 74529.17325746764, 21810.776603378567, 79129.24701030758, 60369.33560410427, 29824.646291525514, 89279.13258381111, 86660.323327477, 68857.41062764563, 2917.1209822004607, 92896.27320191663, 41413.88536903349, 38773.52607044143, 18233.40019404971, 75048.99913675201, 27044.913255664706, 1599.6350698589845, 45446.537578388445, 23061.023761790013, 2289.501028162488, 58958.7546118837, 12725.176043939835, 63792.70304300546, 2092.5577337365776, 40829.46004413832, 71918.80584369472, 55432.784737317495, 90033.33929129565, 73981.65108803238, 37939.452520054874, 77465.88303015623, 42336.855447743736, 15511.385208031448, 92569.85066890901, 10024.457149115806, 66327.39849740974, 60640.50968359747, 51307.83114734628, 99589.38283219145, 56230.659123766134, 84659.44321312681, 50044.58354770831, 2227.365437438855, 72258.60368864327, 53430.19280534636, 762.2120192980697, 74459.42309916424, 42920.82326843616, 97309.06628101386, 6993.179318265885, 56864.19207469806, 23460.468914478493, 82887.6437830733, 11048.967169740497, 48824.53084356368, 53402.087800408015, 86345.73491288658, 74700.19750623, 21639.540568760916, 13709.478511949857, 49479.91366671821, 39110.344650051244, 33650.078212587076, 53750.45449372436, 99722.35328474095, 9778.968670842603, 94237.55694226621, 76197.94655717247, 83466.78295590635, 77315.49826149625, 66163.377066659, 97566.20434485751, 35823.556949697566, 7615.507614838657, 93386.71671306349, 24445.8169227043, 66789.60247425137, 34482.95289321744, 80571.70253897081, 9390.500861706485, 21679.023599871594, 55253.325541976614, 88444.10737488461, 67986.76362391646, 55929.533619295216, 4694.59414990342, 16725.913384071777, 88497.73225026033, 1579.09998468867, 50432.26175655624, 40964.91576070939, 6328.214373499807, 75685.15637856578, 30493.23264507755, 96875.70182762248, 43540.617498957, 4907.517280189289, 14682.062329567503]} +{"id": 933, "vector": [30500.86408059941, 33881.57857817543, 29229.005253932362, 80411.63953384782, 4299.3509326163085, 75784.03915531564, 10612.979126560784, 31569.01946956988, 30559.974100351938, 80557.12279909059, 10510.52979492394, 23787.964791136852, 26927.228653578404, 77370.95756304482, 35800.05303599074, 51007.29023063041, 91416.07392405761, 79031.5701589843, 65349.09870362123, 53581.83918056444, 80700.17245754291, 85555.30354341277, 39995.663981057616, 3729.7454505927362, 47765.44868939654, 55993.08979208435, 63909.559401496306, 11173.797346516767, 93753.82169262583, 11640.412080752305, 72196.30165022238, 85585.16902180677, 6277.202891396972, 85723.17078046082, 30823.96772105157, 25163.293469340842, 80972.78068417814, 36458.178220640555, 68169.54869498005, 35902.447540093366, 31972.500500031063, 22093.512305628006, 54856.98346483153, 32563.10741629905, 67259.25464030949, 41833.71016242682, 89535.51785631802, 55003.55840966068, 27847.639994881312, 48399.86982080159, 30369.610634105236, 51307.73916793373, 90352.07744536827, 29401.655827038674, 61895.32947822065, 72783.53195763107, 14228.000238012095, 24885.965540023626, 95117.14873815441, 84561.04216557549, 11317.987526314955, 55291.096430173595, 82532.65680611313, 30099.5750326694, 75046.19114289836, 51916.7374254219, 40541.56701355619, 22944.126235412255, 15291.98775577989, 76788.51490523784, 34704.65224255593, 90032.93829495915, 1457.7679495073492, 38569.86032427806, 9064.76949403372, 74856.75487990733, 90850.61849249255, 88275.31641743638, 57041.69824966926, 559.1137743719999, 1791.563662660567, 59702.53634619651, 79795.36754507842, 714.272489813017, 19659.339072076265, 35333.904508969, 56946.862948584676, 24851.74248701583, 63783.968227911326, 64205.495280420255, 57997.85287375478, 22206.52366747027, 22971.485311021468, 53952.79161694648, 55789.4857153171, 46489.546649970434, 46371.81779172148, 67901.19929481317, 17569.98052318318, 81159.78643176376, 99545.34092848348, 43879.08699611388, 90256.49095526514, 96820.82061297951, 6904.187392985439, 97910.60661490695, 59556.18095824804, 96307.83716605362, 74939.67019630414, 99296.56477588759, 44302.49681710734, 10089.18916504985, 83083.47295317514, 92112.33439715808, 62542.02101816444, 87361.86658398189, 55121.43614493813, 91266.20480732628, 52446.003846097934, 30565.923962731955, 31193.946847328778, 59445.67341544815, 99334.04442814205, 13157.92260750418, 62845.31314704592, 21755.25168248785, 37686.27115914315, 43857.355966993564]} +{"id": 862, "vector": [89578.55673242379, 92537.43892483372, 35177.97631286198, 40215.40288073021, 18623.663284308943, 27488.839077235873, 49898.710467612975, 9845.05615967658, 92810.38694206896, 99131.1788668763, 61070.59800953283, 10585.419678281327, 27311.92629769781, 15056.398104086365, 66967.30692285292, 21261.539580916087, 38826.71624389879, 89099.66933848719, 34191.613550109, 80783.41263574417, 81347.50363518267, 52898.012918328684, 12621.54608190249, 89103.71709953602, 19617.21489033934, 20241.45649004786, 99638.35822778447, 99586.99236720573, 64377.678910866, 93647.53936858768, 42722.434346556925, 80157.78283239184, 90021.09439215434, 30475.124806549124, 14532.947890552217, 4241.047092218486, 12970.73075237093, 10269.00320566776, 53845.19254658174, 25687.405342953283, 98910.97247398774, 88155.84402853518, 50674.991553475455, 11065.044622040099, 10763.465408337614, 17816.007896466912, 14864.41212100198, 22734.858030371586, 37571.96892249054, 87393.41101262177, 57757.25882537254, 92760.62899401734, 60342.69980407339, 94621.07178606608, 45595.651873308874, 48614.27421981431, 69792.99002455943, 79320.90824994042, 55824.146546298616, 63227.124144595495, 81105.61510301757, 26925.254028939926, 35670.80888535471, 69601.21618441655, 57725.30610059346, 82458.22852161786, 44209.878480809064, 2276.906164434223, 29390.989923577115, 48764.66431702328, 94709.84897723781, 36137.05617021894, 53961.57980236487, 38531.91321133609, 2180.1701739301184, 53033.19446747599, 15011.084548114706, 99050.05910848413, 85517.0087699494, 36178.76204037399, 68426.95130860966, 73202.3109287877, 7799.715264370566, 92480.68497132251, 75244.48555238567, 93631.5019865274, 3495.7801319436376, 35938.2971593811, 48302.44122303726, 47003.80802975339, 19824.71935385257, 56087.61721641757, 57878.95792588601, 85763.968297446, 84784.84280105031, 43824.572865320224, 90343.30591944979, 18688.457695841753, 86100.71032327041, 71698.20524429284, 7302.964352408103, 9908.858891944616, 42077.8196569792, 42291.229093895454, 21912.69156521951, 70873.19097480718, 37470.20496394624, 38435.03961965728, 13905.819610647608, 15315.763739551614, 16371.668327356736, 22586.227360029086, 63075.424076356125, 97390.765047265, 29601.45738675245, 30562.353889188966, 29905.135590756283, 48142.3795867003, 66084.09213781366, 71521.04439414437, 97009.57643038774, 60482.92129464583, 80082.1914852313, 7275.921596923052, 5891.243340772689, 73396.86766961821, 14020.633054798082, 31789.470500854834]} +{"id": 1723, "vector": [59654.42142416623, 46329.20186042223, 25457.23655346016, 98084.52781149297, 56474.93760143752, 42489.916972793886, 4355.153961538316, 86463.49282612599, 4455.857416126263, 53666.682264078525, 96455.35921939476, 2280.665818513505, 9330.060714033229, 78079.09462853587, 88578.64668089992, 10840.385515995065, 42807.85848913455, 67488.56410113451, 33020.08150670399, 88442.46143106256, 63937.9981353485, 70278.65616991899, 47101.569363708084, 73198.14449217795, 80396.1667906942, 20378.2142534509, 42793.97143406036, 95529.79918112348, 88731.85326503709, 82684.13671108668, 89998.04199234702, 94371.55417726589, 74047.37654156839, 39123.002402732935, 59606.37329832063, 52260.14989362097, 60330.752656662546, 59413.12743489451, 40309.86869186693, 67298.29993485092, 11423.056375868478, 3497.2291791074085, 77008.69955129763, 38621.49591597358, 65367.42275023032, 29363.527651957942, 46793.94325630045, 79201.44656891769, 77955.62943761593, 7747.942882854298, 78351.47564489758, 54752.80166810036, 62100.10003486208, 86431.73928740941, 20341.777376102, 48669.278737809786, 64093.26811615742, 44260.95093174408, 58934.51405640558, 75835.58991770678, 87964.13628289942, 12502.326885532311, 73225.33572817262, 81315.72921518939, 289.7025436526923, 41839.23350639952, 45242.57509884459, 41931.51300085951, 51851.563871521146, 88647.85333932916, 96744.69593397842, 75362.33758388653, 77495.36439099646, 25214.913053785458, 8312.638101743853, 47850.87080620255, 27248.175591688152, 77156.09790873004, 39766.3551822121, 72566.52643085894, 23908.02496267994, 75637.9066730375, 12192.85459587024, 40299.201426726264, 50179.38713879891, 57154.97804990415, 66914.39148710622, 78506.53960677529, 2749.39875124256, 35304.653363334204, 86152.86107601786, 61916.63145767664, 19501.578315263734, 4468.9379693501505, 95875.04591470711, 34950.670483117705, 27246.11024899254, 55945.94389171989, 69464.24116279552, 46108.54890708684, 4392.7536757650705, 58739.887451243776, 24194.74456082956, 64178.67615313666, 52522.45478253028, 82798.87767794743, 43193.203281006696, 45339.964471418185, 1251.7380695915458, 96756.741413062, 54838.89916985392, 36718.523192403176, 4949.851605746114, 72209.89291981165, 52288.79659763953, 59647.209347973454, 34792.51449130002, 53182.420710752456, 64300.82044155078, 61092.24966307527, 19998.080313060218, 46189.758029355646, 34656.21934835538, 91395.12867650461, 55019.530517305626, 98711.75222514666, 16116.014735478357, 49532.785035540575]} +{"id": 1684, "vector": [8313.98130980271, 12462.130432607355, 63861.59458551896, 10643.405521421568, 1637.2838469272954, 99523.13935222164, 79179.14035545457, 42025.76674407593, 73744.49078047555, 42228.89897215445, 96286.63717815561, 69997.96597914245, 65641.83594393787, 59643.4658065635, 4768.888504623059, 63563.64289004538, 34401.6985893299, 54119.06908534585, 52175.21224718752, 24772.152339090626, 13110.104135700783, 9161.101595396092, 26805.318748945683, 70403.99453475181, 77200.28616669269, 83196.89089577865, 56393.94821615645, 10371.94520274365, 40363.69149267655, 51794.00458110307, 74294.19032893056, 41998.34841680789, 45390.90030707995, 96899.95459707634, 3284.904906510411, 57719.794933028235, 94747.99544310915, 36482.20752050545, 20776.69998074786, 96858.48171069236, 25193.061930431195, 96105.25537090599, 27911.38862505168, 5760.378473620564, 46866.96457936617, 40038.017851901895, 4447.57946972284, 32995.43290885041, 9923.216399039813, 38779.62842602916, 14529.02339820048, 77865.58526215231, 78021.55093956097, 2456.5935346127167, 50177.63559042345, 31788.575483103643, 84351.4722831332, 99991.37179131557, 87062.44916887784, 72474.72221612235, 15659.385713694774, 15022.201306724759, 70650.74905625478, 61023.33247504872, 70856.97610912319, 14122.985109342057, 62294.44476190827, 55892.6169417461, 91672.06652152045, 96209.61932283953, 70666.9658647047, 9912.713284149466, 256.064927143107, 17879.694598994432, 59779.820286674134, 34995.931711216544, 48330.60015645174, 13431.768878814664, 38389.88055625113, 98678.1224555174, 94865.82703325673, 74622.59271692377, 73370.00431834067, 76378.90140914517, 93932.89396866906, 77803.65544958478, 6323.0781489959645, 56394.966897544284, 4980.02602275881, 56452.656785069164, 40897.98683722816, 60473.83945911496, 72268.88314715805, 99706.70154402065, 39316.88033135153, 85204.60402187397, 23103.27499154503, 96628.83372604937, 50131.39326559773, 50481.106874636505, 98881.84685862742, 32509.984572097328, 79621.64469620676, 44287.68535516984, 28465.436851895654, 8472.692730802379, 58237.030238355524, 14869.840036273852, 88858.57848032076, 32880.14665141159, 80945.99164556486, 90533.25075571035, 11504.455556485493, 80105.53041167479, 48777.627378537145, 18162.045565506414, 82919.92208583209, 91841.57495653167, 28251.550089107168, 76095.28062535089, 28646.309591630394, 25845.06556291556, 66577.35633611924, 50436.927393788224, 70527.80483745628, 13036.193211634662, 44903.61643753928, 5755.015347668424]} +{"id": 165, "vector": [25363.109912099157, 10944.188665203203, 31145.822665754753, 26874.646531929935, 64839.204052699715, 44286.90059873391, 82077.33239954473, 834.5269772571995, 93625.05097130155, 81737.32004525396, 58976.92543400817, 30610.83217535139, 14540.268584815853, 17492.475159076537, 9181.200711735815, 41257.43593609822, 45890.67070826564, 61.60606363977416, 89975.12190386499, 22144.481855193364, 49144.91400849968, 60556.01768369308, 92600.57443471932, 12922.8971513647, 99935.78113031169, 90499.050327803, 71708.02947108613, 72473.36719534751, 91787.96364141138, 51903.29628647661, 65646.30688030912, 12334.595300332663, 40483.59221217417, 11263.018211814557, 98108.66082732857, 56948.68204328176, 14035.45317010505, 89757.44294463538, 13458.694511632662, 30668.323472709213, 29089.33170919763, 75679.35998771086, 82549.3916906038, 84188.85690338597, 8003.071117958582, 4054.4696416762836, 14055.915878537284, 33459.45634059191, 25725.887412220338, 97021.0837006988, 22024.651101969317, 21625.516994488436, 6551.350766120989, 26286.58231515193, 73777.52063648359, 68888.84051337226, 65009.026567986395, 61494.4835607903, 23171.741941852986, 32803.98179708711, 67215.52260260114, 47977.54148717505, 6820.687778537626, 45091.90076686568, 32797.40388279234, 86204.43233668375, 17612.151401492847, 54612.40750993047, 17712.273389440335, 53121.453001682916, 99137.0623679544, 13996.764893521107, 71603.30979011329, 82161.19132594999, 85892.3757625895, 35893.326584094335, 56590.49661649638, 82066.6602982666, 95453.1052682397, 94731.25523424834, 94669.53985638646, 54756.57815010731, 60774.88314806507, 46845.31716945378, 84635.57578565615, 44459.58593647258, 91690.82691572208, 52666.194695183054, 72783.72234338631, 47687.636877064484, 39057.00326805892, 47417.368865567856, 76308.45313139653, 89394.81992555184, 77600.60474752286, 3781.1416966598845, 89384.13997965769, 94469.62233276607, 13754.112748120795, 48246.69545050075, 30210.636420013005, 95050.63539283452, 90993.34860141361, 66926.62679184749, 59443.60015780084, 84847.69757303198, 75218.15337525633, 89223.67735927047, 63077.146414302486, 12576.329904940787, 69838.38554425686, 86328.19148025059, 19235.968156800252, 21361.830355611688, 16807.195520682948, 83657.28551840693, 25097.649989701986, 32957.66658761462, 52781.38936093647, 69147.53114732355, 53914.02344548425, 28229.37089177898, 40193.463699115004, 32731.37452439148, 52907.608348907044, 81976.97435972509, 92100.81375844698, 93812.66898601332]} +{"id": 622, "vector": [62639.117367334664, 44865.41911969815, 64244.704148347555, 90527.23870154914, 95271.62511363666, 13398.995667273706, 94244.30913727228, 52856.79498535924, 26790.786064407555, 87250.41564721643, 73731.2620856745, 72497.6804605674, 48755.799046393935, 46435.7457898246, 49265.722413158495, 39038.65483787718, 74906.27047927055, 32938.98599516843, 28566.710074334478, 51658.18257738306, 10723.930883089482, 8903.679307777424, 6387.460369234199, 47521.3565671633, 65555.22433962337, 40985.93798835843, 26625.450958848938, 58901.9895683181, 69914.0525474744, 42182.054894461864, 42977.12422793407, 87392.68518994408, 36760.63822796738, 7897.72954156136, 99931.8137612781, 40836.918676725174, 71161.18470832308, 30613.871073998467, 13703.417599448409, 39467.79897563323, 69850.87219538605, 95930.30324026817, 36151.89634339781, 13790.53539473717, 95250.14692737878, 36285.85773398911, 65867.38656643125, 81988.71153053097, 26672.617128938247, 1641.7336039899144, 87831.29680589907, 29174.968970171267, 812.1543319337787, 42623.48534746362, 29105.25296418922, 65504.24279700466, 3223.428055740696, 75987.4448046439, 72503.3436341299, 34485.255867396714, 51688.10307343354, 36714.69030333428, 43699.06780126133, 82545.45811392082, 86327.69240346621, 56453.17930400677, 68966.94334720662, 14994.580044674489, 54711.358032374505, 57602.56684006957, 90241.12929205348, 79572.12164540961, 88283.11178938806, 2220.8319034809133, 76735.48110636891, 49969.73603799331, 78621.1939128371, 65276.574558430126, 12239.170106868636, 41790.61574964336, 55091.13687930224, 40033.48887515476, 17703.79130057913, 46326.95781589827, 75476.0979631043, 61685.27244008235, 29453.95358934648, 43851.81089209943, 35451.23759220986, 12189.929688780054, 94514.51353043839, 26841.603665669245, 88421.73055530137, 36436.91063729188, 5385.164631640549, 54438.38122744159, 56498.553096794654, 20902.508480760396, 37698.42188569277, 19495.215419786506, 80301.42934528021, 17126.723096710273, 82387.23636390165, 59014.238667165984, 90274.79567362233, 40004.18928078723, 16610.95637971901, 63969.127667414, 35588.86547875152, 18965.08574889618, 29655.545208257518, 47836.35794748225, 47695.98529009893, 65062.462868984396, 16127.570348059084, 3904.2931703965887, 48428.117063528356, 77494.35787873567, 99063.4151953, 58508.67364465151, 65441.59300926756, 91855.23536781261, 99624.70743591516, 94395.74110235219, 15029.583889111098, 44048.73194740233, 43042.96901705611, 78818.03363946754]} +{"id": 869, "vector": [66187.33007588911, 3375.517272438033, 4229.0769166952405, 99748.35129613499, 18848.81892152611, 95367.20254978888, 81825.89244126156, 92432.97660481592, 71465.49870170694, 1577.5957462728418, 25161.397078729366, 63022.43889754578, 53001.583776931184, 7530.223458512331, 78407.76512607948, 3991.891035077977, 11484.386173164241, 97636.53503192092, 59500.64059216186, 32394.675357951484, 27546.99001284646, 21041.855096351537, 8003.444587186393, 28828.364640628846, 52138.00987981288, 38975.75655565183, 51406.48104879193, 17994.08408712565, 77008.72949137905, 46621.54862884218, 38493.00814147531, 42145.104786662436, 58903.75727514678, 80134.35522466828, 21607.34383744094, 78068.8920754233, 48477.816434000124, 95113.39531956328, 63685.85083165984, 74907.2297210381, 19555.92222849666, 53615.70836829763, 80996.22052631807, 19685.084756149463, 87538.15218070056, 52627.82248441444, 20423.86992292278, 49682.130842952276, 42405.14433439352, 74608.21842917378, 46799.776914122645, 24192.73034830499, 8797.700148960852, 74172.88934859233, 17557.853795578205, 76843.35148140762, 6241.736561433641, 51666.129351090574, 66995.84871962474, 65645.89418706151, 9477.744497586715, 18891.121371888454, 65475.94171792238, 12294.978484406416, 63691.5512246189, 80693.27188889548, 49683.160545018836, 96924.35106133734, 66500.88450791468, 61528.59213280576, 18798.084457199406, 27430.48169454465, 2852.2626452510135, 96475.05102683548, 99740.09185427547, 23310.06189900272, 21839.851502856945, 73573.83888776474, 98156.47621538794, 17072.71744171385, 45952.8138444071, 91156.7124730696, 79682.55825131622, 4612.259712096489, 22246.22061643651, 78077.65816346118, 92877.27581007368, 74895.29231309873, 87426.77579603426, 64235.865374494824, 77178.89624442179, 8992.240521265505, 33749.27135513507, 44462.544930931035, 76496.00536219568, 25560.586487017223, 45663.130915831316, 15316.661392075714, 26194.195413348643, 71276.89746688849, 48922.29747200954, 91143.85301102915, 55935.20759885695, 36498.71182971556, 44988.54287372875, 9958.99084290689, 56576.52900276588, 87310.49370688523, 56732.37052908152, 67820.57103999333, 43863.075111390695, 40579.64838964181, 89696.56004384004, 11508.477894758951, 57292.73104451728, 66114.46027969065, 8704.59712322149, 35407.884670072155, 43082.383142915714, 81310.20337797524, 52407.23879464282, 24402.972714606607, 2956.3347877323176, 28230.336129951407, 42544.78416128309, 94465.02678794313, 34005.230485306914, 80167.29620400869]} +{"id": 48, "vector": [71176.60570545869, 60645.71345903989, 24565.402242394528, 28973.2439474197, 87658.31984532395, 29799.907639336376, 43807.90852493186, 72555.82980236794, 13952.291117376359, 57625.64639543579, 18233.24426704872, 32962.082772313595, 32356.510445376753, 53297.803875854945, 24121.132428506065, 28899.631440334506, 7090.995637828057, 43951.17577050032, 51289.517823960516, 20266.18985669687, 1024.0000769642932, 26829.100832349384, 59444.05499285801, 87768.76479686196, 96951.11234819794, 65826.53076562425, 35299.04057141988, 29669.711708163417, 8652.018833401899, 52578.2546212514, 9770.291652356533, 89092.31707930173, 56711.60915913744, 81248.29111446731, 59020.2375385965, 91934.61005823905, 67916.20974305422, 94871.41870898742, 91762.6292196927, 82038.39164000175, 27696.164259322777, 84194.48995391907, 68692.88443374276, 59127.3298045207, 65070.610591140445, 8403.42196583046, 34117.42298209372, 55574.48511654185, 81094.46845983583, 11137.432587760642, 62934.977134841465, 36105.27136754149, 6227.937613872514, 52185.59923225102, 10417.977853762706, 82350.64656002958, 17744.885048040393, 7658.126888721639, 60657.41728658138, 21331.9340692111, 63011.40667269213, 55936.95337805005, 75508.12440453154, 5466.334889199032, 56830.92834518089, 10121.98872102792, 18367.900329856544, 70761.20130597847, 80265.1177643913, 58677.52801937453, 50071.354381310084, 41054.94688861798, 87815.94887075643, 77446.75987246892, 12461.49716293683, 25355.49689938924, 48481.40780498679, 87145.07554349462, 4614.546080153892, 68151.31107559183, 50545.734022676224, 12610.629728109569, 90320.99637383202, 27005.238806652877, 72204.49812936036, 13007.82879963719, 71335.74171742756, 58063.06487907257, 19989.11185327379, 76306.78599581795, 28434.353102395093, 35881.72584225055, 85785.23271659788, 65639.30113792593, 39369.57415082602, 73166.41897476844, 84778.04447251494, 18917.613765587183, 15730.311975192657, 38477.09465853868, 77565.61801144877, 2573.563584397798, 78497.4228381806, 28478.907154251552, 4987.464260505037, 46942.98235576726, 13643.62965526843, 27319.95012375531, 19022.478803192877, 133.8101086770016, 94288.38207917842, 77609.24907300113, 63482.15635875113, 99757.53719376927, 9085.731976764933, 84740.52397260434, 27466.4607480652, 83460.46871290468, 93270.28764843522, 82079.49681361885, 47686.652317791166, 31189.305415244984, 10962.357620814666, 72633.18087976996, 66937.14696932191, 12547.92653542387, 51685.18053903683, 604.6503153622984]} +{"id": 1193, "vector": [74044.94564501438, 54483.43365427825, 39954.096461980094, 77078.81103285862, 82752.85578441156, 75268.78483555304, 80313.94996992011, 56558.56702081782, 61377.64675885917, 38001.66206593527, 84971.46122724349, 68740.70204256917, 35794.489902108595, 67880.96282199615, 69568.21344732954, 14112.810203090643, 46405.93369812819, 21827.39620987946, 35958.37778233, 83414.47228649956, 80449.698152902, 82892.68727338954, 56709.39916050697, 96960.35327875477, 69783.84596235822, 32427.91315140874, 40478.017191113715, 15326.271614574027, 74122.82464144919, 73438.35850185937, 90078.00658596463, 40028.0210260357, 51031.47417856872, 78945.03362905534, 84649.23002629043, 61032.79178131626, 69058.71178692926, 70426.37278940572, 84221.57458504513, 74254.45875869112, 25348.03531399251, 58869.47726400793, 33129.13649559845, 32596.099571194416, 28495.32213466156, 50030.97546900902, 81441.24013180862, 55526.208804315545, 58865.63415410715, 3228.377497350965, 19549.908872219323, 38227.374640261776, 66357.51736270243, 71236.8290787339, 33100.878767350165, 19252.233255224437, 86685.26604657895, 15826.32563941353, 22884.58378406577, 88269.24644075791, 6608.365886209022, 91043.03926826976, 28021.686454026018, 91799.28297131938, 43851.54563621887, 20318.586675057017, 9003.480948279019, 62369.270315141366, 14438.56748829665, 82427.82054032503, 5193.301654637139, 93728.50606740543, 94652.92284763037, 81422.11674021464, 288.80960782581286, 66516.5657785268, 10226.542655792104, 10248.76027723014, 8680.152765790905, 61166.87397332972, 54905.33779061674, 35381.49844054016, 62816.90381343645, 89064.83571011385, 84292.47229320607, 80109.001379099, 95324.41861380273, 22500.432609012554, 32889.33646253588, 15127.530636558895, 21770.193349340105, 89794.31212166489, 79516.68964999587, 60955.00055445041, 30706.251430651355, 54216.263344421786, 48791.95302776982, 5580.684200820707, 84774.81634630429, 73100.29580981778, 61996.08277463915, 7890.46219059093, 36192.42229385444, 38828.81875854861, 12832.406959854403, 69470.75625894756, 62953.93155363444, 95320.38827244744, 56883.68059153395, 70134.94924110036, 59894.24216700531, 80230.52785386969, 26712.95694053547, 5233.735828140285, 58155.6750745686, 49229.76634201469, 87458.5047842799, 45040.371940395686, 21003.82966121107, 49265.000737278875, 74490.77261265961, 10360.99170305832, 8442.858389021902, 74185.75841420236, 98422.16316396657, 85780.07101636882, 96167.58373823561, 70165.14986277232]} +{"id": 2000, "vector": [23216.371681738114, 85634.54787893004, 17118.14162922062, 29699.36592967466, 80449.04589932125, 96157.7407195183, 87874.68830709116, 72287.96265380333, 28162.03305883592, 95465.68885992712, 59487.90483908543, 23493.402119151364, 72606.16708033935, 90113.65592371026, 69773.48928448335, 83402.96649484208, 31404.881768946703, 88291.56598754521, 78485.86068898664, 40677.249841588826, 96751.76050649726, 56461.498478411544, 74478.9097017016, 80990.22654148545, 99808.69006127135, 74869.64360369447, 57843.94723859482, 21044.35376026487, 89581.88114572121, 76865.50699452007, 74189.0776471397, 72210.73367471457, 84936.42658490199, 80386.02987985796, 95439.56221391723, 83980.46309399525, 35756.30878065222, 72202.69247758205, 98148.58727792194, 13326.626496482175, 5239.1602001022, 70214.74351964773, 40103.31275638022, 17446.483280727487, 15369.970103031383, 58928.973475616775, 73159.16649784488, 20611.591224147385, 87439.51957056993, 47813.39727147228, 6237.222777107687, 49529.92175590339, 26567.770900404685, 47783.504342987304, 14576.407143026048, 45000.27422187983, 74362.1205710572, 36876.23762784151, 24297.48721425956, 20966.01404357631, 81285.02585876551, 28563.975862533054, 43175.78598554855, 99854.36689582212, 30767.665527752986, 41257.41189584525, 96198.54760847539, 88131.35647047524, 39190.32366570119, 97244.44726429862, 16434.484756331967, 48435.415196173846, 13157.851357611682, 89136.99441094011, 22841.553045940822, 6345.239244511924, 99902.01158933139, 16642.22007602685, 45093.83701080241, 42518.55699993447, 98369.43299765835, 29747.52735339864, 18924.913545115473, 34614.4889831268, 24883.524235950972, 49824.33783007644, 86024.26609169797, 36781.39389836621, 48271.172810557626, 87266.35486526025, 44538.18580895316, 71866.30918554905, 49926.5840408776, 57213.570369918845, 93055.69171371045, 52516.06392381387, 79093.03908064545, 61176.48873454535, 71303.98569539825, 67296.08257893624, 96458.0807983443, 78939.19337103129, 70808.26880000826, 9482.663014746584, 4803.8726377392595, 17156.841452909997, 28996.203801407682, 52573.556206467285, 7969.053916997415, 69783.25269746984, 40921.045590209185, 34846.685649608975, 23906.09642829088, 21393.009400408737, 49078.12242370036, 52232.59616348562, 87933.18402911825, 67513.84913157123, 32960.80105474998, 45628.51639664752, 47611.86702297613, 20210.3289462924, 12062.194392975922, 54025.4952651571, 75268.61809966757, 353.36747893187635, 40451.37161542103, 94006.07928955372]} +{"id": 1682, "vector": [19128.179807721357, 39011.295463871065, 15746.637051182044, 71458.30771666227, 67902.19219904278, 57632.384254908575, 54208.848777798725, 10129.101571573141, 15830.556597124656, 52669.7808956587, 48338.36377869832, 91172.54290653726, 76919.87984054234, 97819.39622639844, 50855.233761713236, 98874.3174566661, 9266.850691357231, 71508.42862176432, 68688.62463425548, 2066.4265411930783, 72567.41751088246, 49092.88462277636, 36858.482803266845, 18804.06824910561, 85105.00033336972, 58297.94777255158, 81640.18182664797, 13194.62232011861, 49719.923601340975, 18932.86042967367, 15070.859513637091, 31748.22608779063, 49843.901942485594, 24267.31304594497, 98064.54488666402, 96106.69680004219, 28525.22278247427, 16056.570441711283, 28547.287421753277, 93607.68967330802, 99155.65849509876, 57727.72143442777, 94838.60170835088, 48085.87390264586, 2092.60800502139, 10528.508379548828, 5860.7010463400775, 98604.00449382934, 53855.25087672587, 76634.5944037846, 1799.033360867952, 34439.6173714218, 58596.72465778179, 42092.306190835596, 87429.9206164012, 59492.81612222225, 30955.977362885147, 5694.490297161136, 68426.99736633355, 32068.65834525211, 71909.3109263388, 23698.819901993684, 77162.87808973952, 25838.143421978744, 27113.791771803288, 79303.08824291547, 45331.94180435486, 91282.16098245785, 68237.19807159757, 58089.040706092506, 6904.302704222032, 83275.61247854582, 33131.73441368492, 49751.37592644282, 48969.46814847036, 33856.72544213354, 8278.96440856487, 53050.95574099663, 21742.556060711016, 30824.42523449439, 84818.82833125924, 34107.83948119427, 39850.28227266775, 18000.92187517328, 50059.24113640101, 53085.9628345295, 29158.1593319159, 57668.08417458727, 20994.380582126658, 38749.390372336886, 12451.817270321497, 10915.105405064252, 88972.37664337676, 62022.74711341501, 82982.09092797409, 93705.29637612935, 35079.927334092106, 87293.49823918611, 1456.773356393337, 37438.94170628153, 78165.99534083952, 86494.26856723442, 29703.15265028176, 8815.893151431064, 94421.70088199576, 40013.4617129082, 25165.72776648848, 14723.169915873024, 70157.32007444401, 91530.01146740658, 31960.203815650966, 26198.88841945506, 67823.37140503104, 88806.40451364109, 36286.58966229676, 58482.889383985705, 10959.313101951851, 26803.009188838478, 26185.774288745477, 40560.06877723093, 38547.24028300045, 20942.161386551415, 87691.1385361561, 41362.20660146936, 86688.78963289277, 93032.93523365122, 61544.81903562533, 86786.58393050786]} +{"id": 1195, "vector": [49274.42377981043, 84727.16005981348, 81890.21090041166, 25225.838507784927, 83066.16620157138, 99728.53193952166, 48737.55621257715, 42398.91878513697, 38360.91235051939, 68989.1846272638, 74012.2448228811, 77081.92746954276, 70367.48596400987, 72329.40919658725, 54908.501360738584, 20795.51825751338, 36350.96837471109, 52784.97895466809, 8266.62682783188, 66708.54861391202, 41060.71669528487, 11200.583230024908, 4716.42212346759, 20210.4713845765, 18547.546902786584, 53016.72940284213, 64802.82726776183, 33983.35463171005, 97725.41015305548, 9257.45492899538, 13523.818422020817, 67762.42798696345, 27576.04716653135, 74247.99641264073, 63193.88061427127, 63661.094452902835, 15259.660678147657, 94919.98651567414, 38863.22992244744, 15632.111744523181, 31136.07011404409, 88906.38276826442, 70834.25301372212, 84098.53389253287, 88838.27878433383, 50964.496021560655, 26944.433025998183, 60259.82068337835, 49883.96582625598, 36206.798651602316, 81915.02846826443, 29160.31427085174, 47429.704493975165, 60632.57879017231, 82478.29203729064, 95335.03662409818, 92723.38126698518, 53337.42778035675, 85050.88906253576, 57657.05504950047, 28150.63543752501, 9819.274985756876, 50352.84527069022, 49303.509593986535, 57914.94433692154, 57037.4987291285, 32560.561616619256, 23360.234721470508, 67760.66920555444, 94237.29744571836, 75745.59288387813, 7087.202434195871, 56479.976714842895, 30213.787676163818, 36596.05468107235, 59399.201250744336, 5455.545840041931, 96750.66682615574, 32268.401657052404, 68235.47290704557, 619.052365107553, 83884.87829322154, 53667.392653311574, 47174.6633489191, 9365.235269124783, 23851.353758727535, 50915.62608482704, 33023.26339512317, 87700.68449420518, 35671.03364808283, 25365.06017145701, 99240.99405864808, 34131.21310378479, 60107.01417015082, 90256.41698157086, 70041.4809995131, 13485.756932206694, 69385.97747959706, 75669.73521879145, 14152.855358580297, 67303.84389170945, 9470.682716094236, 76378.09463186181, 25983.003132912752, 82893.78147966172, 58631.67528085694, 23509.90937433858, 12011.240008376977, 32475.61713234094, 19385.03696769457, 80040.49661275423, 50324.490655714435, 84367.07529805762, 39064.494212873105, 6561.790276167978, 79229.05300173498, 22.125538172212966, 57692.598385764606, 98649.81067719244, 35486.22175368621, 50521.228148182396, 45875.26248217203, 99591.5378020553, 72293.77796154986, 12366.606090885645, 3796.899078564042, 89420.55610275039, 94171.21985625527]} +{"id": 152, "vector": [70610.83448294688, 16135.104532117084, 47134.395033901965, 9289.514264365727, 64168.603531639434, 49908.02510416331, 46336.07585855435, 75947.66942785658, 72043.89666904026, 21459.823955304902, 55084.18102589304, 73720.00936624451, 39959.3002616781, 24165.00558090856, 269.5324343181427, 51642.056006398496, 8174.986658980732, 66230.94426162235, 49406.74627916859, 42302.353466471286, 70255.78155589459, 67363.03085006567, 95941.63235917171, 77746.24048892599, 67936.34740781586, 39777.16114342652, 38557.674819967404, 29217.735390854283, 91482.70204647302, 91959.80758060793, 54511.10580505338, 85529.53668058917, 79075.22047888784, 25713.810954960823, 82423.01995609017, 63677.64416765209, 25438.952717531727, 82673.54954588915, 17255.084439533275, 1711.3615074484057, 60255.887431756215, 31608.623077883458, 37532.26955040525, 92215.50445905887, 90157.44129507482, 33354.94286097657, 65718.30104728589, 28329.22636403915, 33023.77002367849, 20651.322084462554, 81743.59551081523, 89205.92416682077, 89530.08170369537, 76434.83462420963, 80287.25271107403, 57019.04493532265, 34149.45855619456, 23702.962183835796, 95851.83054008374, 35874.71940372453, 57418.80997005928, 91711.22752088722, 51649.49096740405, 42375.152114401826, 9653.402928671174, 93720.322525967, 10519.809400222568, 79196.02368507402, 82554.77081180818, 12909.806260726331, 63346.90119090344, 28139.089238840897, 69383.44529231956, 74466.44409825353, 87111.64140354638, 14492.562720694068, 34877.31031528534, 68175.84948026722, 18810.966964093634, 39060.45095011412, 42489.26473442411, 67313.77889274785, 17215.59073151262, 74092.86064652604, 63510.56757876052, 92097.78442787136, 3762.516078666267, 86889.43733379593, 57772.90877428817, 44316.66931276063, 80425.68839640894, 53195.309642335706, 78669.6939874394, 88685.12797078645, 66650.7042703992, 11568.577452020967, 45802.677034810804, 37817.75765812907, 96386.36868374344, 30195.177260488435, 82411.02195829042, 77621.06049634976, 6071.679331883628, 51206.21366573955, 1609.706847703296, 19823.76395227531, 56033.77582386867, 34714.93643483692, 94538.21733154614, 22115.802555563114, 88037.0184022597, 39349.60157887352, 83683.39575293304, 33179.35011130623, 92690.22115782666, 23059.22359081385, 84141.86200261772, 88533.94097308924, 25517.16677474456, 73045.23003607019, 18802.7812044547, 84463.07213929691, 81703.7918745988, 30121.037748308998, 25944.339242011836, 32112.962904936227, 78902.83345589013, 66486.3554713923]} +{"id": 945, "vector": [11998.543639623516, 22286.330019859422, 33750.627456346185, 79747.75367301979, 15091.968864224436, 87865.26961073995, 5464.401913002414, 69257.42305876306, 8494.698925252585, 51724.078147968314, 52700.55125660338, 58921.74775892674, 78803.68633027106, 8067.163606856875, 81683.949919794, 98473.2837934674, 57362.78017028658, 83246.7119992194, 87131.3335102701, 75563.91358902995, 68167.05053507623, 12826.848070396501, 96534.33508702244, 61054.97528325039, 79186.76593034806, 78892.26020737186, 47106.91529635738, 69548.18114493146, 97282.26352030627, 31000.23154421301, 50268.962424554884, 31948.638925156934, 11919.1916954406, 99756.96612008542, 76507.6156093163, 63327.36914548453, 6334.068322472808, 4995.100567932531, 42235.63910461132, 20219.915562080172, 71253.91709597722, 60215.06574787095, 43138.88116542759, 26948.81353895239, 32909.81404730587, 91475.17787321087, 38968.723180347806, 23167.023966147215, 4499.085894727561, 7840.421916494877, 5969.891432924756, 74511.35571758832, 80527.07522904876, 35012.54795373555, 16153.740698112162, 16941.73375555399, 70263.51429122183, 18607.168951844033, 59724.60561874916, 75168.7817872172, 68289.23605895463, 17667.14715207215, 69606.81958485527, 94098.20119634135, 24589.914041903725, 86784.5519927774, 21478.923071605717, 88792.94971484765, 19047.02776086199, 72849.67963139254, 96490.80876099916, 37987.24532768991, 8296.251210123906, 37023.31336719198, 34436.87411460294, 81055.2318836326, 63129.43584123843, 6293.207611020324, 55690.55501150655, 54560.21950779933, 84240.87021624258, 62723.62240757756, 31940.994131993615, 96926.4135617417, 55546.87099592226, 79637.88140662773, 18028.05073037227, 6668.318661382289, 71484.62591900022, 35629.37903519954, 24034.22489749718, 16609.172605725074, 69897.18947731036, 92469.05283315622, 81184.53623828657, 22619.539231841958, 55269.6485381083, 7291.635361130367, 78168.66776620074, 82433.53078617813, 87205.17548693578, 75058.49253261415, 30976.449855421462, 88613.97351618846, 90031.58921844202, 1208.2581439137764, 33831.26821213409, 9250.250863623965, 71305.32634653238, 27218.735418807617, 2575.325436454634, 50328.61828218847, 41790.084362511916, 17186.1287041863, 1653.8638535802174, 71444.51922624274, 36864.23570917244, 82566.43992342944, 54101.37056380643, 32512.622213853858, 89825.95118857603, 58053.10713023893, 24912.437514703455, 42603.60257211315, 45419.28126691859, 31845.122776778946, 53148.91424976399, 91973.71113004076]} +{"id": 1201, "vector": [96917.26860625354, 25173.229136029495, 14494.795682498041, 63491.42128171193, 34866.17357177195, 71412.28254224769, 59595.824251892904, 48043.50692477313, 10022.592737679992, 32495.466957352102, 37778.0088005534, 10438.470822569312, 24113.016337066838, 45380.19960088096, 33283.2099442837, 69242.02485547429, 38336.644201964176, 7314.00957759859, 52731.94758945181, 58126.6190050794, 28251.892688381588, 49225.562585679225, 50644.85452805491, 39629.94961637519, 4258.115649035732, 67751.83347118282, 82884.51396112119, 67887.30722867878, 75130.72911716161, 12461.663913426291, 20987.82882454957, 41123.282787563585, 42851.81182984023, 44413.46497005263, 85751.64235657819, 23512.883097723647, 29565.776747853823, 67505.68030639162, 16222.15827484872, 84996.62597522877, 2699.341260868882, 23019.207228427385, 5760.4563176396505, 82112.21889145434, 88902.23390391695, 55747.325750923985, 6309.0626826065145, 97287.67467243124, 16005.029611839072, 96473.58873962687, 13537.69914182329, 33169.82368972562, 87674.86309701669, 69653.51778555893, 85868.62396722392, 44301.80073265382, 67997.85601855189, 9346.92612084278, 59739.44377066706, 67544.86071213862, 89364.68402446673, 86552.64634253566, 3501.5844411314333, 75746.803357604, 70280.29278328137, 88256.59050285566, 70917.54917382648, 3712.962532048414, 83349.81190409104, 91524.47953461553, 26785.274242246127, 93097.42490574726, 59632.89911435018, 95084.71621803757, 67120.91292141931, 88627.4994932646, 6834.73691364741, 61888.35699092198, 30510.2555119089, 61180.16842513383, 79478.12312207097, 71457.40042180712, 61314.52663492233, 9277.486469402675, 6667.812718016963, 32426.30897918357, 28374.02575981537, 34101.21408259284, 69725.24095290912, 4214.543147535421, 8568.86902688132, 17618.839096866355, 34536.75653396407, 82665.08351778412, 50591.56280714895, 70844.35103034243, 95960.12821008376, 30862.15319064769, 89962.21101599943, 16417.61527564035, 78657.55979122565, 5300.6927660890215, 31846.78223675631, 92204.64041471633, 75325.24530378, 45461.56882753808, 92577.09228387236, 25530.023264688796, 3050.5570560329766, 8007.748654179903, 26515.018176370377, 41722.57523689311, 18574.7812227847, 7089.429908543166, 78142.24002303682, 9849.362066065914, 98201.33551780452, 54008.84962650795, 56624.787146372226, 91334.63164791146, 10696.554954314652, 79409.34606227594, 13109.577849806054, 3647.8719004159334, 44565.28052770119, 83946.75610711396, 75820.68528389146, 56959.80615544262]} +{"id": 595, "vector": [54987.00754318382, 82871.92461800067, 49605.678274782236, 9472.634492426312, 35187.49661641139, 72035.10095936072, 24747.491405660872, 12735.911724504367, 33681.88859218346, 83165.88048060235, 84968.47516143351, 31206.698296107337, 22846.36894550015, 54687.342083137846, 46238.760552323496, 89810.95406283965, 8708.042497000468, 57223.19110474611, 60582.15608975188, 48946.855969661665, 29479.56863128386, 54249.01684889119, 22140.505033038382, 25360.026853655327, 75487.47105184536, 68310.73854612181, 9887.38115329495, 40364.86265702049, 26254.677290404583, 8786.875236023916, 43034.72674142401, 22.281642670918256, 25782.557232501134, 57515.11422670273, 6800.549746880513, 883.5218035045389, 70525.96164710882, 17092.487999063567, 67421.85290198457, 71204.86449880172, 58160.76330091292, 56679.8408379111, 18987.928793526764, 26130.782540729568, 45953.75612950858, 62465.0979250269, 15370.124717124334, 915.832109945991, 19377.560289418703, 4661.251203050687, 62386.52140126512, 37372.61906518014, 26690.127397317032, 83844.40963230775, 40327.397666010125, 13826.54096963245, 1936.4941399795277, 18298.199729593944, 58488.973976144276, 46097.53100449227, 21055.281221281908, 1727.7352125562695, 40279.203446562664, 69343.49121783336, 86704.07959378486, 96617.52330949728, 31894.63386347209, 16000.051429474204, 74452.2481717666, 44590.66945605615, 64371.00233780134, 7399.499292082901, 30480.355803769522, 42394.89904661671, 37355.30980423716, 3089.271440452013, 39391.13307697488, 32454.948985461153, 62274.08155687998, 23623.56423805555, 88399.94020357713, 82448.70028938357, 88215.10493799533, 42560.16810586161, 66978.84931330004, 22571.839104286904, 88735.2252947245, 90024.37616511736, 58794.24155651253, 91481.23755625525, 30008.47810671722, 86180.95673151784, 58164.21235176786, 20581.99207133796, 59857.10133322434, 5036.300701402263, 80678.781459318, 75464.4278789615, 13060.486412644135, 7282.258397866459, 20682.092174734345, 35520.73028403829, 1265.8716247779146, 64416.23933273544, 4227.893614726763, 28581.40513877878, 30488.999666334916, 67382.88115853637, 70626.5070587425, 67538.11651203102, 88531.61651218733, 82267.5154065979, 84700.1999435565, 59473.25295558352, 36255.81509573325, 28702.270738757918, 78803.9381504054, 95963.30622184227, 76637.09830556126, 81532.38358416189, 72360.98068992565, 75570.62608364939, 81.21231889921532, 99011.95113553392, 84789.27106916675, 28091.645304195146, 94629.42397050952, 41275.64679192223]} +{"id": 1728, "vector": [189.06667470079964, 54899.77068793492, 22006.625660501268, 81292.42325746673, 9260.261428020322, 87308.24753067165, 91562.41483904788, 55245.65896096838, 95036.84347914757, 3660.64024580679, 22115.749933803807, 632.9191916137233, 20897.631129643447, 30275.368630008736, 24316.074989297253, 29590.999939117235, 16228.039725208177, 55098.61052764299, 90150.11384344086, 54358.063000914466, 52950.306458708044, 59783.37446432027, 97772.87585438469, 87734.7190467182, 34702.53698796036, 52127.41253303255, 66011.75100932892, 92008.2877707952, 33742.2129448361, 77130.29016496203, 47869.432036855775, 7073.610268937791, 63138.16480426322, 29442.070791696806, 65713.67781186024, 32695.284969277338, 1857.7002620396631, 16387.29042724133, 62943.06181364625, 46718.767463505574, 53188.55252416522, 62585.734730438446, 73921.22069352513, 31829.086113799087, 51094.01164450836, 66594.46186356162, 3291.9116745708134, 97365.27897421543, 44653.77125217407, 95432.23847722585, 40825.53616438761, 57611.31747652849, 97663.57906845167, 72247.29060372953, 25756.4355539355, 93656.98803581663, 6789.207100358197, 61979.50860171356, 62137.71983572003, 55429.410290246014, 80515.04046943893, 16436.98042208852, 7450.27020256539, 2726.182355239204, 79809.20089245484, 83122.44537822947, 73563.04300103644, 1905.6558553118164, 37767.5383131819, 53413.6545208864, 88081.74586516115, 48307.90877350638, 90144.69976768077, 57932.48766110573, 72549.10729877306, 83529.14328057204, 14523.084710798339, 15678.66037405493, 22890.472678327824, 50680.55369136921, 3509.9668964714724, 35001.861473518016, 74890.32888400136, 30073.064939094085, 29048.941930856752, 11436.693047602654, 76182.61162159589, 56493.975664397534, 15918.265391160647, 17996.91472686168, 57620.54692811895, 77568.2831114235, 68595.34978547494, 5889.032349503964, 98999.59625141554, 69427.26724220367, 30198.521431614034, 90133.08129358267, 19375.531273363176, 73741.3711132297, 92616.24319079921, 88844.91198852196, 9503.79966784296, 88477.21140432057, 33743.866097254984, 17694.200279414352, 47440.871012536976, 9115.95341736, 51045.24733899283, 10722.261621687157, 76472.45968792989, 42053.59406181231, 26354.281650086585, 52551.27143543826, 66041.50611160761, 28059.02662342753, 34196.65701838083, 64095.286570128905, 49424.413150028035, 21984.6393939382, 52742.01138019015, 10472.332877387413, 26395.78291897371, 40235.471595472874, 66875.14516074631, 89107.62425618416, 39597.73890187286, 6176.618071723828]} +{"id": 193, "vector": [85920.2904755355, 81349.3181250909, 5199.452524462767, 30345.753885231796, 96639.732116179, 90513.11317216304, 67278.85317265819, 12178.737468366086, 39027.35266333963, 95779.80060378926, 72209.97634587898, 9224.097188441172, 76090.2592766176, 91213.39932630098, 63779.20530157005, 11854.376030160096, 15270.878599704107, 56584.08204628922, 8262.274110506229, 71424.97344415465, 29316.63487945412, 82124.60730378762, 98405.92051637918, 11397.932565996916, 55310.96624909252, 12352.24776979772, 66821.12749822992, 49589.363209592266, 81467.21945221299, 53508.87269704304, 20658.652065550177, 17023.49100700857, 44360.87828344644, 72121.10927246456, 20687.380055433525, 65377.73226064764, 8097.724138313544, 38136.87828177526, 69593.40866487312, 75541.33346060055, 14885.472595401417, 66267.62626451901, 59020.01881809977, 29494.30656327675, 60724.57330702604, 49386.9364230299, 86888.90600742082, 79113.99589795266, 82973.81465134941, 99134.64315057479, 50986.854864482215, 19305.730850180324, 41534.22377947846, 35389.0255439704, 26325.188830199142, 8035.973582449185, 22839.223850774117, 67428.75950763903, 76759.75668959874, 4330.99984031482, 75610.46742646201, 64928.12031589258, 2488.029129037572, 45545.18492395264, 31357.17586773341, 8675.037263833885, 3589.0050535599903, 82037.62941190641, 43647.32645598343, 80355.58629246172, 34242.62339387366, 22222.513656477495, 24151.70227088932, 25911.301308884493, 97344.76898434706, 14798.214233768926, 25886.03715140313, 8506.10620212443, 34165.36680671659, 93122.25245049523, 23908.83527154256, 56588.25011879021, 45165.410073516556, 62157.78265277209, 24279.677524279065, 52005.70879579124, 72500.33532995956, 19533.821328030932, 54510.416538030426, 32750.045722787723, 18068.07079217787, 71021.74681267136, 33550.99275808725, 18207.941035450258, 85703.11666449656, 22624.469472346755, 67612.47030448512, 63857.05657922793, 96257.1273525872, 86233.69369198094, 33470.68497406599, 91288.41446418311, 72311.00384605957, 10836.327895231301, 14351.835775262334, 89043.88684786491, 83587.82280735523, 81236.52855721049, 8698.148714196552, 55208.71518104296, 93004.48299364686, 61725.28485566819, 31997.576776936377, 21455.757939339946, 12278.805270019078, 76183.61114893593, 80206.17007029767, 23905.072195736644, 79142.20798002675, 22054.96095901346, 94267.32456592562, 63257.65145963188, 14243.850505281975, 62699.50245962914, 86769.26942975621, 32472.80111963706, 12520.030000272342, 59558.82589138747]} +{"id": 478, "vector": [35856.38167284935, 93660.03898026451, 44661.5633069831, 74124.55336235889, 10067.967645399478, 86975.4211213013, 5379.988937721958, 8210.045500670305, 34402.023850358666, 58192.32180216344, 85526.23873603258, 59292.34498696053, 74073.78904872182, 8505.467886497687, 34215.53220168866, 36752.07493930571, 19659.909926313623, 40691.969106189405, 35148.5676440837, 45449.95290768328, 25988.261430557868, 67185.14496637158, 64856.19037891943, 42164.65152618808, 63038.7870986305, 35765.65048197138, 35948.08799097623, 20857.169593028924, 82423.61974277394, 31770.3775952327, 43946.760917016625, 67129.47325064518, 41681.9970321141, 63316.305731322755, 53039.37341938073, 21561.28121029577, 82913.61516178121, 7026.366566638831, 17696.1122827628, 79842.76720381652, 61294.39798822193, 37636.7633681282, 36725.10756821645, 23592.443503085226, 22570.990627707066, 7945.060433378281, 93870.1208692562, 62131.00531858149, 74561.49385117255, 52333.77777264024, 94492.13192171056, 13223.527653441657, 26749.37160627794, 81914.41514136698, 46169.54222573282, 89269.93784966499, 31027.28999580413, 67322.8634511672, 86789.2251261271, 98337.48720379824, 65342.794522006196, 17860.15419820217, 68276.24440172306, 37648.11146490721, 23795.41879603946, 88685.3775572644, 2739.787448651765, 40619.50660624841, 79787.16547917335, 26128.02493799453, 87964.94304303428, 51253.83230917973, 63943.761278429614, 96092.75298784567, 29744.30896316884, 39187.04294128369, 28351.817678245396, 34511.2054181202, 34451.369261577114, 80551.11569366818, 47301.93992347133, 52791.40136619916, 24633.547204534854, 21512.860922693864, 51418.07954786637, 98679.52797354358, 67034.34363814717, 52376.88874706674, 60424.07285574969, 22526.49797890116, 76149.97405245645, 49828.12569888796, 66807.60558640976, 24669.442675906605, 68242.00218525331, 86922.31486024344, 99794.05388585232, 52682.15858308073, 2951.589529472476, 25588.24688370883, 30696.170864338135, 24272.14615357739, 23402.755627780625, 60077.018898287904, 12873.076816170347, 40165.391881662836, 97538.997771301, 99806.89517837054, 50663.52152861285, 68438.92910788865, 12555.638411768023, 36832.46606251249, 32659.28312971128, 55047.30041799751, 30329.681628118255, 26147.8861413048, 99207.09841425372, 33030.26739145133, 40353.87822124636, 47033.156469197966, 35862.40165530788, 6605.7592587061345, 90233.91099262261, 65617.67838259813, 82355.98211328262, 34311.384758409644, 37933.7189272263, 75829.73864023823]} +{"id": 568, "vector": [77595.58332513584, 23323.501490704148, 7276.843761890151, 51007.05687106305, 19927.137170967955, 26543.54035285451, 43045.34396299947, 40608.34445053596, 6373.596306823348, 57826.82112263339, 32700.7402864528, 14352.7110764194, 25025.03888457792, 15256.271555584988, 54538.32077237025, 65625.97806047784, 46828.082593731735, 44231.721157689506, 39954.84188328638, 9898.772526384148, 33258.6499686616, 79313.18282330439, 3889.033418650145, 4338.62305155841, 93569.96680673941, 45812.85099744521, 81295.01982324292, 41716.788180913645, 74781.99362165123, 98386.25296998942, 76837.15019169186, 45655.660923725074, 19532.141242772715, 13299.944889871218, 24771.77470344324, 73496.21115014974, 21247.878566756728, 20332.157379488668, 14832.567965770704, 29120.924127751612, 87618.85117241475, 2250.3843757078366, 81843.32269611562, 16368.65972346494, 32223.082378921885, 4476.336443473961, 35636.458740167545, 65372.45399503302, 84216.4072204354, 79483.14323895781, 89336.53394762364, 96255.53645251584, 40902.03114872062, 60179.60864077999, 79897.28331725571, 91261.02695411263, 28139.869376705363, 65831.10675603802, 73069.35058397253, 32316.01094064821, 20595.98621818092, 33737.72803715813, 37692.1281908718, 83306.10985381008, 40356.51776554771, 74763.74920216123, 36870.17421457937, 20368.37716432356, 84212.4804155681, 17378.93306848134, 46266.556330971056, 48054.60659566466, 80610.78788077118, 99015.58561872705, 35697.278601221784, 2128.2462195858943, 18993.659217173932, 8970.649362757033, 79063.55777998418, 44866.92400537368, 65757.71064742746, 96515.33683024913, 18804.150771951732, 74395.1220968284, 95470.37502793287, 31112.7104758145, 4018.8740591610594, 33747.549595091856, 16766.83394332177, 30288.82280923303, 41205.79164498225, 39038.79903123215, 35562.57052073515, 8930.425888382442, 13551.065786366767, 57435.67986188766, 53258.411489310296, 96214.02977274507, 87194.10198596986, 12304.036899410741, 21991.701202408574, 31628.561513438835, 76485.09285142159, 10650.465465255855, 64355.1360363538, 18148.516071839636, 47536.04802416471, 89685.44879346083, 21286.534737416652, 8399.886920079647, 41531.2256981217, 47580.732485387925, 88281.99518308837, 27840.951749226937, 29617.643107016935, 56583.72854279111, 99879.01347440259, 30916.404299723286, 43543.13035128048, 19938.747224223807, 40662.19423093861, 98619.46197793675, 73462.35242648164, 93554.89587757322, 63362.0139127471, 88158.17740358783, 3148.777051716167, 9558.69147851518]} +{"id": 761, "vector": [28008.97908855493, 40993.712761092946, 51844.06835646817, 75987.95721027355, 71424.21001245544, 72035.10644636946, 43169.133009077144, 1421.1688079367013, 60281.30852196612, 90512.15554370792, 67574.55287971403, 62310.7108192845, 7419.6061724230985, 28412.465415644063, 9123.472935821863, 85751.49310033933, 58632.8984663633, 23276.041496921684, 88130.18435074469, 8259.265983471998, 28252.02775572805, 8092.569818071404, 54228.08712515199, 51755.16349774698, 58822.25968340565, 25166.367023677616, 40480.45722135972, 74310.49522818536, 41972.77444799242, 49510.849701948224, 57296.25591898872, 70208.06826639964, 16836.777590392794, 90278.19054619082, 12517.85272472099, 49142.64925001193, 97354.2482962416, 15321.211247759058, 34741.89122108383, 98055.8089545362, 63259.21730909799, 35909.30617273194, 88821.68031611727, 91206.1387567206, 17098.919531767242, 9808.714946827491, 84844.38449542402, 14896.61921929607, 75619.40275966359, 99848.0980485903, 26978.29228605295, 63809.197183998614, 31290.62117728386, 10482.972465320705, 11476.449868321548, 17382.492376646784, 4071.086234947374, 47699.82227122309, 94333.42463241304, 65202.03234024737, 62674.24193586907, 30716.69104707648, 75108.86886464508, 67176.36765061223, 61478.07806113823, 81975.44976053057, 1822.6650160813485, 5134.093318100663, 26927.371080489727, 1108.4446921341696, 16714.996936544456, 29135.521648159625, 65496.08964774809, 16164.987822325316, 34366.998708871855, 94264.76970856624, 96692.7009113378, 52392.70730160793, 20897.906840150194, 25190.50727520433, 2945.1369780379655, 72764.31799741094, 28284.40515741096, 37237.29440663374, 72038.50630246536, 54743.22716015955, 33500.20421196529, 20727.33098431354, 99208.12221487235, 65243.877581355424, 6355.869937856928, 33994.20683235348, 89154.7621787709, 93007.63146788478, 44122.91175088993, 18723.523845663116, 64977.96013145238, 9308.372896599692, 61485.42423553402, 76856.88935543352, 27910.04423179345, 36158.461090977034, 88228.51076824873, 99280.63341413344, 51299.84819829008, 48766.90081910986, 56084.42014396272, 40879.063537436785, 10682.447876282296, 38283.99766062187, 95855.08809617437, 27231.627027099235, 80705.2202724843, 1792.555588176359, 26589.138650588462, 32276.811779661162, 12780.678994936412, 822.227203944903, 69258.67721865133, 24284.642650189548, 99658.93019752235, 6875.63374611353, 5111.639821422132, 17171.672000678074, 23089.10771496544, 95954.32513991802, 49970.60603748928, 45397.987226296944]} +{"id": 1950, "vector": [97877.01287240381, 64892.70964940772, 65236.83563027874, 64566.50271315879, 96487.44290401257, 42709.67446773451, 57084.76694213677, 3786.4128245913007, 74213.12663733594, 58355.43763769251, 71294.73589496363, 84810.46495824987, 2071.488918537112, 27676.558453840316, 23310.379642777832, 43547.29773906677, 13445.046844734354, 56281.07154281381, 24628.83107920919, 85809.40053984629, 60305.37689149066, 40257.249156629005, 98937.44474229858, 64271.59531525367, 91146.1361000056, 73517.93208133533, 58082.43146969791, 51839.80198415794, 21148.286699452, 62885.27597617283, 16682.105407114555, 16042.028915180606, 13137.83093841755, 83305.49354655486, 72395.95959612788, 96912.84831111274, 88489.97788318868, 43321.00715076548, 72762.9414735261, 11449.50752685876, 94732.53230591207, 58150.256556381384, 53147.799059034616, 59846.97234396495, 72858.95015898588, 51196.79663737518, 81864.25670063215, 20535.683889795386, 56640.114109493945, 42603.2072358318, 78326.25778079177, 54449.77742234183, 72284.78772652059, 80492.95820978314, 26545.31173480751, 64676.87794444546, 43791.18650815327, 72362.26786485213, 96058.82293451492, 32723.842306445094, 69055.8308851362, 41267.7951503174, 30290.875723195255, 54598.94917304353, 85852.64892552554, 1176.4635517908407, 7882.443131679351, 18898.74948326906, 11843.822278911286, 32187.896862777965, 36312.396550387086, 84244.66104297971, 83124.52130309484, 59469.37557987372, 38115.138621360835, 8888.341874781081, 33066.21132780267, 2612.6675471280782, 84797.8985692528, 35121.06025240499, 33099.40683615193, 7218.237707825626, 91675.55433702018, 31440.627943322517, 6008.579102503442, 93398.31096635264, 86500.0856559294, 54377.69770921446, 52758.01391925918, 99378.79716538414, 31899.27551167837, 14797.55361011027, 62044.004870801575, 80565.13519182635, 54861.7423759468, 80039.65057248484, 83844.79929820994, 56566.65048788782, 90283.88259371463, 18715.937711787632, 14435.122307518577, 68101.71554757371, 29599.28070621157, 39152.878693860235, 67397.62385706068, 69577.18521089568, 32858.77288441341, 48975.5287901605, 81038.26209908408, 24484.06326002248, 14740.597180784087, 91477.24600467939, 48805.194306687095, 78719.17255091485, 39255.39308999735, 77986.71058748916, 94035.15164653149, 52104.579331335044, 99255.64336156678, 75570.66068692907, 64610.984205771514, 94610.77536701298, 97171.48003973294, 71285.22751752629, 19376.976433577252, 37182.316328898036, 35231.751194456054, 17484.989618078052]} +{"id": 901, "vector": [45122.66841926328, 7930.29186207479, 72306.06193715661, 80693.67524198574, 18219.504254150954, 78725.58966281856, 84523.6920927372, 82097.46409712141, 60551.9672225386, 63459.24719134721, 75441.20306926958, 30619.006282617156, 59527.72978175941, 75040.23031965394, 4402.99858477109, 87109.0450593376, 65378.640772163075, 6569.781467586011, 28016.32023304895, 76892.86430602432, 65695.51677174396, 19311.329920252574, 59868.77190973097, 99344.59855453044, 3726.738936815044, 34382.19686415001, 64616.363070294836, 15123.518754016695, 50923.104854172176, 94708.5342727183, 69146.86070251638, 23752.106905124238, 59646.060246606656, 60193.089022160515, 56167.85624828623, 18348.453496231, 86296.5653921785, 53687.87368349672, 86245.02019889042, 12683.868152942823, 11278.83282618093, 238.1324285997244, 79718.36713743416, 18446.32517038941, 42269.50526825314, 19124.814072967, 5604.905436279295, 64753.07970474037, 37863.015115487906, 50336.15681955331, 36634.46803716076, 24074.48645908714, 49770.13350095044, 97275.2578490065, 92542.7843594047, 91243.2821636799, 7061.251667847768, 15368.424038674144, 92060.73485963487, 1367.6939003784748, 11359.368536385939, 3656.1196078572334, 48984.58359961557, 37245.12749527886, 53319.701067779126, 72095.84202754227, 54856.32931898889, 52892.02439846099, 26440.948798278107, 3651.121831454063, 526.3679047194158, 21676.809825044696, 76330.4447557816, 81742.35522236254, 6987.892772884763, 61100.32804474832, 7838.553603227949, 28756.344017675663, 64622.76155325651, 57205.685337283554, 17839.433061270236, 96880.00344304976, 21035.02859139086, 7970.155469733986, 88488.48267306694, 57489.2742001906, 87144.82706136964, 64614.45232948594, 65857.43908992455, 5451.104736697421, 7659.496699471213, 1898.6763264413619, 44912.54547553659, 61524.37434431035, 70969.04654488486, 19456.887549531064, 69029.07562513453, 33002.35374100054, 80573.83124104417, 13875.471285854146, 61288.21048834233, 81227.84750295771, 56484.82838232785, 30430.79494920995, 77912.83686479788, 59368.05456258514, 78814.14435602016, 33217.19023428642, 17026.335654605384, 88028.94683786898, 26524.92086923841, 38244.52998485022, 7182.39174318267, 28853.533984484202, 95108.05456478351, 19017.10228133222, 78917.36754110536, 85513.45999608959, 70590.40581370762, 52775.663483756776, 38087.2091420225, 49826.836553203815, 98919.11931685108, 80732.27874150984, 15543.105023206139, 78271.85226920625, 8395.66965260119, 89499.83798827123]} +{"id": 910, "vector": [82731.67549590416, 40186.5223605896, 63665.65519552027, 95811.08159957948, 23263.116140997576, 36291.94226850326, 1464.8173551017662, 9020.189296625269, 99675.68694791231, 96222.39240662569, 65451.16278398707, 65242.21388749506, 76171.54538470217, 89258.67534397056, 21981.87097615314, 26224.900135421238, 36989.976946355084, 25327.320661370002, 30511.257347225473, 36087.988363772114, 16886.255437016294, 34193.9266095127, 53330.66916793806, 14544.813581130411, 22645.688305392363, 85708.7768059555, 9282.624131466833, 75022.51749858794, 66970.28862931233, 27575.864335741473, 39065.095778387295, 72376.39235078356, 43925.2622566861, 28098.647685079803, 12668.320410362654, 34130.153135584485, 51876.00558221619, 64102.17447179847, 88328.8158740428, 3302.282050360561, 46190.05618370623, 68995.44796328014, 88034.62698650012, 30780.204118038735, 21561.53870930547, 16040.928851956449, 6737.166059722655, 31920.9540842471, 3463.473454732291, 80891.5567421857, 86367.80532400122, 54221.986366361285, 47280.6106497696, 631.7132813999926, 94374.40457642652, 58216.75217368779, 56072.685418922774, 40802.00957776361, 19175.168807184262, 29247.85815260027, 4803.508647458277, 55257.33729016744, 6448.926806348009, 35850.46973349428, 60054.66637156615, 64579.917094995864, 64179.54700501604, 56698.02460617819, 98298.09175430867, 33202.993694173354, 22462.930136385028, 60361.80224803792, 56431.26822194977, 13125.279463823303, 69994.77667955511, 45425.84304851087, 84761.56357912699, 43100.52792447542, 91506.44236592918, 37123.616278968344, 8325.640318473448, 64249.15411098722, 83492.8667105859, 33904.09618942964, 69052.57171774056, 8749.358052030831, 34294.09304461043, 62486.86706998103, 59430.546894860636, 53140.84272392322, 17908.495432285064, 63542.66040563177, 83216.86254158533, 48579.7084114801, 89781.26065555852, 6216.161846044343, 1600.0783117219419, 63870.97807773955, 64775.60134714716, 28865.194276136142, 24790.59782887809, 12831.045880560177, 84255.35060432045, 8296.150816555304, 4272.352884659625, 7247.734456162025, 49859.793985224474, 36003.137926240204, 75094.52222705718, 90608.96415378469, 56207.10348666084, 79947.80394926717, 68405.15161797374, 13212.869985376496, 18633.774048512787, 43691.25085718695, 15827.083938566244, 97352.8672981267, 11269.9730638386, 1546.906315947827, 71593.28669028461, 63082.40244521775, 39090.79201162993, 4688.376462582167, 81130.28472675414, 49976.52941224171, 98565.0344947036, 78598.63993578203]} +{"id": 1576, "vector": [94973.10341603919, 30097.997701169566, 43187.94301815779, 63012.215007609164, 90720.54849432436, 76978.6837718007, 88336.05627979936, 77259.89846205314, 33439.17188407454, 81298.74744618409, 65293.709542075805, 41319.928396639494, 80126.87250684945, 22314.414931186344, 59484.32567586478, 85412.93426691159, 5699.076787699919, 32987.540309136355, 63070.817158626116, 84395.32453631371, 31005.19395481657, 78218.6230081567, 26159.05475209478, 85052.97233943192, 11642.458680322565, 78865.6549038815, 56204.97991553735, 43375.13262158461, 47657.32138451252, 99283.37447193859, 89203.21571425661, 93195.41715862197, 80850.44210519476, 47002.30415710151, 73650.86897142172, 28925.3323332875, 98024.65772912974, 85077.56620520928, 62785.92145633718, 28484.566338497196, 86934.21043265339, 24259.353554160843, 48117.63822921238, 80390.57233547972, 8411.59528517208, 68674.65951145456, 57106.25704037473, 24392.89028315772, 69321.30288685385, 91988.31334032207, 58760.735318789935, 92060.6646421386, 61319.749340662835, 26709.381316165436, 67288.51412957585, 85143.69097889781, 55733.95142340037, 60774.65131177735, 66594.41784420174, 17352.344880738692, 39178.42688154359, 38506.270395090534, 69778.2304853128, 70132.66378035118, 78884.90358333288, 8287.303739514906, 97127.91953383942, 11796.681576328372, 65557.17203633655, 96870.70274018575, 47556.3190335668, 14357.594328866264, 40818.47570028454, 65205.44999739657, 37235.871576478006, 19666.725845199573, 8968.638247757144, 21789.427789218928, 97538.13057532173, 45515.31253408203, 24606.558312495617, 44795.98021759518, 78889.67452940012, 10318.944138982155, 36668.34952934598, 71897.28812492419, 14125.277323665952, 71059.70116466055, 45320.63686199245, 32592.192129998675, 55349.44851769543, 13948.87875612112, 99730.70992010146, 10647.058882623462, 72553.0013548111, 63510.12327644048, 41863.55101428656, 28486.178639701964, 31006.35414285401, 91965.73127422557, 6234.206629756111, 48095.50299696612, 88565.55901508147, 97497.63820129196, 73467.56889136751, 22348.761140596063, 71258.31851818413, 20471.072367608467, 39991.67373704813, 52162.931329343286, 29325.87326796503, 72855.33044199583, 5331.0802882798325, 39729.89471420536, 41107.32069560694, 61044.48971285224, 72404.88091831621, 83445.30370085403, 96433.835503403, 67163.51067287092, 1208.5126776110067, 82871.99326066766, 67404.30444422754, 4143.738139870768, 7561.4273867694255, 42759.67929926453, 12745.282359820365, 98416.16717828975]} +{"id": 1997, "vector": [28942.234235511765, 56105.62928122142, 69084.92439456165, 81343.45661374407, 21780.842242468534, 20319.563099398674, 4125.327397708012, 60054.44092126414, 582.4520601029892, 20461.655238742936, 99039.55710062134, 73380.10410922905, 39680.40537882965, 10841.126789637823, 84713.41647709894, 1963.2026988001371, 96078.95796971789, 47106.04372123992, 15597.004612771792, 21886.956338917542, 76643.25645955707, 56311.08978682419, 75357.44517723312, 47104.96202537192, 69024.46817916894, 11713.092551841697, 98746.96197449979, 59051.50265153621, 47096.69703815373, 45151.975374985195, 78716.65338721467, 79794.43625709938, 82759.66691882207, 79979.16259458593, 58842.70118875752, 45657.70170970886, 34821.511552006035, 55163.94391149444, 33283.40937513277, 18376.318597720863, 30564.68419299465, 93677.34456267882, 99944.00344659365, 299.2956871868402, 73701.67835859208, 85622.82623509673, 63555.537016168295, 22785.83258385236, 64125.64164774558, 75126.77877345745, 49857.284223341856, 15605.992550868797, 20306.723846109675, 12715.185297718434, 83110.82350509605, 24421.390391233934, 5421.618325931221, 42377.101367482974, 3242.268954547278, 92518.75080310278, 94998.16090934836, 40561.250881730935, 58169.50440141512, 85197.87900960453, 58237.26962743934, 76112.79187295397, 33054.17187688187, 14805.436962923368, 58267.6232595613, 31293.261291460105, 69206.6610412754, 47483.91648152348, 95714.73704775891, 12720.32281529617, 10418.281177717581, 95623.55369807578, 76307.8553548588, 64952.29271476446, 59463.93739549199, 48572.76046678008, 1558.1014629360234, 54661.512428819704, 47972.88182661027, 80269.32622305985, 54156.72713230344, 56029.35477075387, 90032.42235473997, 62215.38603547291, 73881.68473195296, 6603.170951459181, 45850.5872396049, 53748.4508972778, 57725.54295903639, 22798.87253323187, 51256.25284519309, 21440.310524913308, 35775.241765460174, 31997.98783951523, 83350.41389710041, 59629.76175463207, 12083.051229226783, 92757.1561565752, 1790.7741119530417, 65966.85366408854, 83431.72510320564, 70528.86791243433, 38168.8555498369, 33323.44848583982, 22451.344805711284, 49613.874156538615, 88683.43314844511, 27078.948199841034, 32641.811810792842, 6750.749594125127, 91253.8530377946, 13913.620156139383, 44199.64037988606, 24290.776150287642, 24238.784336241726, 11295.461268529405, 49063.93306374119, 5739.114986354021, 14076.194716608825, 92055.64616577607, 72281.537949964, 64720.55364876528, 76323.59637481102, 89232.76243075689]} +{"id": 569, "vector": [68657.10559200695, 15242.217705284156, 87483.36948559088, 9139.455717263778, 58385.78050142356, 90388.47488992017, 59854.374819016084, 12406.393184198505, 95851.52460703443, 97669.01169925176, 40077.32729506654, 11122.505219618795, 37988.068767727666, 96601.7872972492, 47051.08119565003, 19995.890966029052, 4202.641653538908, 39126.64178773723, 20696.90859441099, 67560.24675211337, 69766.79310078168, 8033.224282818607, 82070.54516285987, 33190.710909530164, 56551.73755169944, 19218.769718734973, 2208.9571446520795, 22195.853759201058, 98180.85668987039, 45075.033340897775, 14846.440381567805, 58312.13498883696, 33315.490010975846, 66885.5745660767, 65239.624335024215, 1836.8702718098207, 289.6880025991755, 16709.699158835134, 98638.6041465571, 81613.03901209729, 74214.88263522857, 91167.76464308522, 7577.534106858652, 97672.5734917272, 90569.08656603686, 41687.35042502223, 21426.301085848077, 19416.286970775065, 85034.72809595207, 29096.386707097932, 45403.61948150822, 2705.4759704873477, 74651.3003709098, 56195.95440879276, 12279.079018310023, 99897.08349027864, 30917.495342226277, 1851.1659985871786, 31263.84969518896, 15056.771715464824, 95413.01793719822, 69092.94179559953, 51103.29467624101, 72669.63341641803, 86588.25434514027, 38066.01886318701, 82544.77508653277, 66217.30248439287, 77794.52767545106, 30191.639930441474, 68569.26083713121, 19486.906548664752, 93941.23230644772, 17785.709614229094, 58233.22095912563, 93193.3181610245, 90252.33138020989, 75493.32926123186, 54285.66922413946, 49559.93795965846, 40333.771553049206, 33869.251985561525, 80741.89442233079, 65895.34653712991, 50453.202200884065, 49800.65945445687, 40147.6920857753, 18879.38616884679, 39312.896592316705, 14391.755605729317, 90025.6739340418, 63405.08619635002, 61431.513474933505, 62000.65646555206, 34832.53969401734, 37566.17107328091, 57446.01252104793, 77529.90790723998, 68549.45351406661, 1024.6077466420256, 95456.98831722277, 78639.43627527558, 79695.69675262949, 36630.73479650686, 23689.889022056064, 75935.2765362345, 80573.45755158344, 98184.83605171347, 57053.543090228486, 42248.42306032944, 78868.6425777348, 58702.29454079223, 27530.795356778515, 51838.19331593657, 35002.90396879958, 31514.7175096532, 16985.027426848752, 1331.256099281608, 58873.355703178466, 70352.13672708055, 60032.263817635234, 8842.311961680705, 78652.65632528582, 13714.287138015934, 96782.17874823727, 73739.14767105677, 89425.3761460074, 83387.40353512413]} +{"id": 1765, "vector": [97620.12089774825, 18263.172733146195, 97957.51364014148, 94287.82058278073, 49808.04572148107, 72159.59610507412, 76855.12072689901, 89098.49229901229, 50502.02042503403, 84399.86001298283, 36116.854259066844, 8747.770784207976, 64580.42858210491, 28490.267477523124, 48739.077421605136, 2669.348342290112, 87656.59092082802, 7562.459098139873, 87072.56584241026, 16742.716293217585, 30829.30442427334, 75529.30522444473, 98766.91227242546, 8492.073539082023, 93432.88417893501, 1475.8607558686742, 26048.974142205538, 79132.13724315925, 2048.110036654749, 46167.925909381, 38306.88612502502, 216.9302708416976, 47826.63602183196, 36026.75758090678, 59579.25731529257, 80409.1543455884, 96918.22170726053, 88106.74876221555, 769.7052064108201, 49947.816523937196, 1436.9655084633725, 76393.0211216517, 53149.01772709586, 85006.78323612222, 61153.92873690205, 59813.14211632016, 32882.034522120826, 29514.409989020118, 49385.696426070346, 73321.8722689169, 49165.54336631951, 34717.71520812186, 2294.764650885306, 59573.1628541033, 40226.560699951355, 21374.129145374256, 19813.869488665503, 15937.277270051254, 41847.554532452734, 49875.5825115034, 63489.868325291754, 49469.67823570423, 89505.05072806812, 77634.53072094264, 67147.52056165025, 56318.20601408746, 6553.344608986933, 7711.4538284218, 38201.507860808335, 75196.07935559098, 48571.31835854777, 18900.150457905864, 2752.1353044628218, 42159.58561424025, 15936.159392360538, 59999.331183451606, 82331.65964579117, 46166.020710354875, 87544.10522903786, 61688.56932016511, 31569.66949878578, 83615.40262062082, 96634.03769349545, 47284.45293379726, 7929.572480530611, 84050.5538690351, 47034.00771484616, 56500.11135450366, 7899.4177099315775, 44506.96550513686, 51653.88315368677, 72481.35199025886, 626.4700200643847, 17027.12868619972, 6352.2504145663515, 91028.56511157233, 43154.37735304332, 51979.74320319606, 38421.972424191, 12166.033605102411, 25805.509340015964, 1058.1595839300517, 52886.21817819455, 9906.370472645365, 59668.38259420385, 12597.469025061304, 53104.29479292015, 22286.223583457242, 63611.24798292713, 50574.4483899481, 15680.77137668643, 59830.03624495699, 84687.96238516032, 84258.63538432072, 15443.758453489121, 3032.1001189167587, 86697.74715774813, 84684.35780987423, 35399.155217055886, 86805.73917168594, 70085.25371878031, 73421.56140499399, 56316.69231330979, 20463.78606749373, 56143.022197445534, 74769.77114361613, 33203.38234306379, 41259.101301487055]} +{"id": 492, "vector": [22535.18640096106, 59557.781540978904, 46637.639573183296, 28024.418315331368, 25115.385699079274, 9992.056690579277, 91205.21332282765, 79224.20516563625, 91952.38739224372, 2222.3532798969936, 70039.89295679792, 22040.889072593174, 45289.347292151164, 49615.38885438313, 81098.7415208594, 37803.70141431918, 34848.08583580926, 85138.01491493914, 47816.111839965146, 84134.08887015251, 54657.63319914129, 73614.19662604418, 50931.00500979461, 33669.41776555372, 57703.34995861909, 68306.0961018966, 75901.54267546801, 56249.378338167255, 16091.68222188192, 22993.86260767088, 73319.64252103507, 74058.9459272883, 94736.18789580786, 21868.46212924578, 47959.25307557807, 28299.919968823473, 76990.03527027805, 87805.74499762125, 40793.46830047909, 22894.497063150655, 91299.11854249449, 47848.9207348174, 97973.92243549047, 72872.69927789783, 1248.3336952780478, 75715.84050736995, 93307.26226689921, 34956.632139144305, 9109.05044570658, 39789.14436665133, 58368.842416706, 62177.59914501964, 64207.58341376518, 43611.789463998604, 62188.76659576866, 72556.66130348868, 95357.25836684536, 23057.59822405804, 46744.541075313384, 64818.88242612182, 14748.46944267385, 13829.011950014357, 86675.87728152996, 45924.6085294195, 34673.24242904598, 34307.760121769745, 85824.55364258889, 71218.89093542517, 80762.32808561767, 43968.29797132566, 30475.835079418433, 3068.2055065314185, 42849.782944452316, 4622.804198664976, 2507.8755952737565, 20825.864103046588, 58528.34827791097, 28086.31246132901, 61745.52269751289, 2546.881818201485, 62011.439089550935, 73622.63612738521, 61941.41551071972, 4082.0004359493623, 38634.03140890694, 42856.8977093027, 61265.16014264269, 94505.84673633818, 79531.29580339178, 31272.203882842576, 66030.0149637018, 25722.767881039643, 3930.642112765781, 18251.980045299253, 9689.645342153552, 90623.32381734987, 78509.43016047499, 60024.83893331302, 55946.8997155568, 49150.04611841955, 75539.94522556989, 27027.070073173832, 45432.18988179514, 86158.92910329264, 86041.91873136908, 55062.46596285003, 59157.13061559728, 72405.4467893202, 18022.309074788343, 64683.95448136356, 75656.30871464111, 32294.7382252294, 93785.88384525533, 14217.06273716512, 97535.2519046189, 34166.032700841584, 74465.75456268294, 90670.09151845222, 54715.59643878546, 28616.297288371094, 13412.38134874556, 80712.95627169259, 91894.0012404513, 98144.31389997818, 47425.983410060915, 12291.076381190069, 1678.2778731403214, 35937.57816765656]} +{"id": 710, "vector": [12574.732494013608, 6994.421647059013, 29044.918219210223, 19268.15956334662, 15235.458821390503, 37354.7323380028, 99898.43841945066, 32427.551285983012, 25705.074690960406, 92090.7500826892, 98157.74117932115, 13942.081917665528, 61955.52755938823, 91547.00438662499, 46981.13207003117, 61388.89484002648, 42634.613410876955, 25532.004313308287, 70978.9873533964, 24745.475788760894, 12514.606256428362, 76902.12016576438, 78530.53122789001, 27097.29033474799, 38867.62071726121, 31996.44991454217, 37069.836179489044, 60558.66734703429, 30656.540393767064, 12018.576625901245, 89602.67833460755, 17683.086133688965, 65105.52802077495, 51767.04749015767, 61011.369331617, 74612.77319374654, 31993.57553619887, 96668.88683013563, 64888.073771994306, 20911.434461109868, 5841.68279596956, 47955.6670706256, 90610.18734463077, 63520.68330042172, 33748.5646925747, 94985.858249553, 32611.92865668565, 69543.34195160672, 84290.61186757694, 95171.39272014075, 78372.0586321842, 18541.77235918132, 77768.16532531871, 31921.14327300679, 32878.15329674646, 82600.65604491017, 43281.37624407443, 51595.243403628236, 24270.121129586652, 75617.05214922801, 50898.91836385482, 69014.14597913195, 95956.93309672618, 13542.221061428972, 91917.50654034837, 20177.60884852512, 34473.41023577013, 47208.92839424412, 79536.96219748088, 75253.41534461964, 79827.45346072818, 48346.15345666239, 72335.01858554925, 35737.72321370757, 84925.03300886223, 66440.0399515194, 58672.32250950457, 52997.11706789742, 71778.35633206021, 14651.616440296788, 63369.14810369256, 74854.23772941966, 51515.66242118889, 17334.82026282376, 66255.99627268642, 66810.35285495348, 35565.60899524297, 40071.451086014444, 70035.86614208017, 53627.66952608396, 75122.7507108614, 50278.00098815183, 34709.066737673296, 99003.46710431356, 27878.09280044434, 81414.57082174554, 35495.26201212423, 78973.54269813151, 18587.574907235517, 21516.397878188476, 30071.706268725608, 69219.90070171744, 21006.19331355428, 48582.102480389134, 52791.97668446886, 89188.99928742727, 14124.152957066304, 90169.87216489925, 46410.51540332736, 72276.51619740661, 98724.09051396039, 41906.62934433783, 2569.504273605794, 30974.953569848418, 97502.48402665062, 86676.36214764147, 50825.717847362386, 12081.078980921433, 3610.226979981512, 75239.62173046664, 36484.57433807305, 66023.8043840368, 75936.46159085528, 83644.58665769109, 47260.35844939066, 91187.18543162057, 36703.17092618758, 47668.89587316059]} +{"id": 758, "vector": [87574.99960249732, 96770.05580394436, 99630.94020278854, 28612.269838351956, 64974.37471698213, 84587.02418877714, 39735.79527743309, 83157.78534112035, 64802.197957318145, 64770.42612045, 33645.37482561948, 19568.104588708113, 91166.0124004855, 12998.809147498525, 48797.79997900059, 74729.8066526219, 64980.9999568029, 17508.695574797097, 38615.41569103263, 66566.0402449265, 31834.41099830986, 82716.67727091355, 10589.395833893212, 28822.87905063199, 21534.24270503318, 3526.592327159672, 58274.415361214495, 47163.3626442864, 64302.17855022547, 90821.25513671704, 90462.63270866466, 65820.92123533315, 85727.07207520172, 95711.4892604871, 87494.21948188459, 80989.86393509807, 4490.108852398212, 92562.6224321772, 83519.28359888965, 16267.920337218045, 69717.74685576082, 91757.14878515303, 12846.861660954279, 40117.836846410224, 25814.959942278514, 54037.11654376321, 28283.999998813415, 79255.13954608233, 4334.6393763196265, 16926.1360591765, 32268.83123417167, 77172.5792169128, 83318.44062799003, 36133.68953532821, 6121.287622952897, 85748.94198477523, 16178.827319338729, 30507.65227746155, 83089.75353155627, 89176.77365831593, 42295.86062407642, 34722.201377645564, 77758.29822530909, 75822.23734678177, 96351.34958590327, 13282.56750258352, 31223.02029618863, 51175.64661741946, 15783.790751504168, 29790.56204310122, 8997.628353554943, 1114.5095039356233, 27619.543638216805, 60352.968170391585, 85491.40540479106, 90084.29237022575, 76838.94434523431, 26106.35474475407, 50282.962455975656, 33249.28174284387, 67060.85893361652, 18680.759631647194, 21401.67737215689, 99368.54145253455, 99285.41389637318, 31661.083706461333, 1370.4082461744483, 92093.42103019527, 40618.53857604947, 33877.303328446505, 88602.73197922876, 75692.60802201822, 56613.52372693653, 39185.14625240223, 34283.68019437645, 10069.368898540253, 91446.50056087055, 94821.63196152048, 16480.684734693306, 59960.76981280956, 12875.860076206724, 21820.795468696462, 32401.904401905023, 4014.3718747435055, 31522.09273428781, 8444.176977713847, 40826.60386215379, 41369.80401862871, 67563.8137305367, 50904.05516058911, 98811.74804432559, 22625.786645795688, 48495.14245283097, 79500.56323119273, 63329.65970080784, 58390.480446364134, 74495.153260117, 17190.817376891733, 36515.05883668892, 83538.29484401288, 90151.37155535871, 10084.367310206444, 66709.67392071715, 71286.93794929149, 25758.150572931958, 71358.79202407684, 19194.713579494284, 77364.2165231798]} +{"id": 1377, "vector": [50838.51501262237, 52636.54734534567, 75065.92268372135, 7220.3287989809905, 63787.775859359, 75002.80426840096, 85890.76836321407, 67221.96273873562, 83014.95735807497, 55726.185818420236, 77227.65524483417, 36688.24319199776, 25111.63960326519, 78393.93143026334, 21819.842489584484, 83838.09846154778, 59835.400526132995, 69436.53219329186, 63487.69930130741, 70269.82987880727, 77721.6962896664, 50704.26032305449, 43758.77028358197, 21833.931741141554, 2259.0036516325295, 50248.216524789146, 86694.78230560363, 358.9155350600448, 44687.016092454454, 30398.613231357896, 31594.46252586313, 784.6716317389202, 42281.06113979696, 10162.38711587255, 60818.7402842246, 63478.187843575164, 54579.31720053377, 21755.085162310726, 34115.583134195425, 29728.21355675198, 2898.733890458316, 38061.19569684574, 98372.48952832089, 17424.68317504331, 14828.0812199736, 41465.547006055116, 54847.094406344186, 340.76194479700786, 39794.03434704078, 49555.55777387966, 1406.6491971976736, 92482.05135225345, 74036.63588783513, 64731.49597921294, 66145.42296175103, 58378.41006849721, 57128.99796804045, 42739.00812387215, 90803.96745683992, 43666.83287929777, 40605.33371968392, 49079.75145370958, 83354.97419762112, 55199.1293014088, 67028.95194729712, 85452.82570297254, 62021.28437131901, 42863.46312826711, 72594.59690572036, 76302.32949805287, 56226.671286064135, 42727.06324045972, 37009.79487325107, 38845.12151159745, 8878.12854159321, 30702.02757170064, 22562.65712557901, 880.9871804242864, 84661.73111238786, 16307.427757010306, 12886.604346656039, 82754.85145454928, 46887.52448829967, 63137.628745202026, 28437.73420698451, 31996.71040321325, 54943.371398188465, 79633.29208771714, 68466.8222757852, 31248.517008184852, 26047.044957438382, 12327.127816181983, 34667.64069084549, 89135.23967449159, 94407.3480656959, 26615.835690692315, 86371.37073549702, 89812.09872384372, 27078.307323496465, 86961.62701272694, 45738.705103051725, 8290.656793236196, 62742.30713881683, 15891.836300665253, 98599.63002551782, 92241.7140787175, 98406.89469705289, 46315.34418856732, 78765.6354894934, 50718.02059105556, 39397.16937306648, 84846.61744724814, 2718.84008135459, 95520.42134122142, 12716.809662822849, 62187.782190814556, 70456.24204080799, 23446.787089745038, 9993.598089214394, 28859.619402133485, 81759.60348384971, 70575.12959276747, 82366.63058011534, 88606.83076241208, 49575.341341515144, 71183.4390648217, 75121.85413969276, 88783.87177791947]} +{"id": 1225, "vector": [43185.75275143667, 39194.27150552277, 40096.64053655128, 1671.4176257271229, 15752.690911760037, 76649.92247496298, 41881.49466020461, 52922.626495834746, 27447.117390008225, 53949.22594032781, 50905.53564409974, 28072.01592706756, 81032.29817542034, 57203.77902710696, 98250.67389630908, 54918.38963830304, 52986.692288982675, 25967.502067457117, 60652.16174239335, 84522.28430513605, 6295.8827016555415, 35022.64801236647, 72090.4867004562, 81893.75749777541, 77526.4868872433, 22776.04147402036, 34681.44446912664, 67838.90249954992, 93200.00902860548, 38313.53490208603, 49850.07215290058, 8256.988387571308, 62484.26713065192, 84438.94609934652, 1252.8990145351027, 18771.745510133474, 48429.8457357104, 17575.395495344237, 28626.857845504215, 13624.985273065171, 50654.38968297988, 21322.079320097466, 66837.7158802414, 22145.13666290324, 85867.24832711706, 4563.567613283137, 85417.63785201334, 15529.537211275247, 27529.0155807142, 75273.57864570344, 91707.58223494394, 56320.81125881975, 33764.16028405047, 83431.68723974809, 81270.1217409526, 16664.82808526567, 98988.17782046886, 46947.06061927921, 18661.421782578036, 54994.36412580589, 38872.95738535606, 45726.62976242623, 15596.09095474196, 34848.56487149314, 89545.82654228453, 55777.89727101745, 69247.64218613894, 10931.27281971339, 92822.30364405332, 30274.344405181087, 13103.101098513658, 9527.103044681984, 8937.432625911157, 80086.51179057459, 60106.179734013385, 91131.28582700998, 58260.17115744332, 83642.56091542113, 87234.06800910579, 61979.22160857743, 34212.62947640174, 15840.794824423221, 39725.644067271416, 20357.75402557045, 96173.06227359302, 96124.8316591958, 46419.76585697163, 20318.641470796385, 62481.64961348654, 2067.2382726388937, 89865.08596232324, 36377.60697093737, 7093.299403149988, 97502.78762286401, 94075.53074305334, 4101.744775042815, 58991.750415140785, 86193.25413086617, 89359.57141963045, 38252.878472170196, 86742.18242656333, 13006.359313593119, 98296.14823033668, 45932.026419561, 31067.770406026117, 14142.3422244625, 97922.30732074911, 92958.92803681582, 16480.66997213471, 56832.42732357334, 28307.563587072982, 60858.511694485554, 57330.51227735435, 74063.42517343602, 902.5616998428853, 61674.88125326062, 32779.84087750975, 31912.8489300329, 33910.2258836081, 60783.41567170629, 71094.90926708769, 98848.50873203714, 72124.50344049097, 73976.74073524226, 76686.56425068127, 51975.095063345034, 32309.322822613663, 58304.04957380007]} +{"id": 828, "vector": [78640.73773662836, 88131.21013438495, 75200.38425832734, 33513.65836541972, 17567.81621957377, 16044.303485445776, 68899.05338565148, 5014.360805751683, 44055.530039194244, 20639.151411833267, 89075.3425777803, 39399.03907019524, 25618.09375952977, 5091.3693315191795, 91762.66249875199, 84000.48802317807, 24750.01302264055, 77892.91097990728, 14772.350903467424, 31590.53083525768, 79264.21052275314, 29325.910338012516, 92751.0812986718, 64131.587102837264, 31754.854566752445, 54792.78831810166, 57092.85844687605, 14561.072629477467, 98438.64937745607, 44803.000240667105, 44121.12394258019, 80729.42788290753, 51373.34119292856, 8514.552935467413, 51059.5949326432, 29557.23264062057, 20130.86178843445, 22814.930632903917, 73557.9740574886, 73641.29202138122, 18742.432928508068, 1460.1793192854261, 48239.03380938991, 48779.46290800099, 31762.199562657755, 16358.02499816784, 38374.25700701085, 99534.24226573014, 91735.93453595012, 70217.97965157463, 59631.05525603254, 96411.36709008829, 75066.28881771625, 91944.48773808611, 51272.36434790198, 86073.43963760426, 65764.66271326684, 5865.712309842142, 26400.444753838827, 70501.63817770156, 51766.285373732004, 53449.237458077834, 92000.66005369584, 91891.94364739051, 84537.46100953541, 9995.453174018565, 97680.6996355392, 24035.8697965808, 91073.8677210271, 61496.35954156399, 75420.21884661654, 2823.6171033482747, 97958.51557544162, 29341.49090659255, 79937.15947684801, 13953.515156771968, 27350.038385849697, 67336.57610392362, 57876.097631802004, 59494.249645939344, 6586.529904665251, 10953.28295650324, 17692.678987362542, 34208.1364021268, 43914.7344696987, 21863.344151936537, 8990.58506542042, 46219.34227853104, 69914.27678686245, 7473.203877122803, 52461.9527056855, 73050.2935625345, 5334.698111981395, 3722.34235561828, 2867.5702994000794, 58270.62201340376, 58996.38960636766, 12075.066498289367, 6242.838890294511, 65237.01568586929, 63295.481067868954, 54148.5400737323, 91778.38537674744, 66222.75756210616, 82991.1210210869, 25174.797385054724, 39173.986213754295, 59270.13493663292, 22746.03885100578, 58388.922203378635, 82848.96581431081, 66611.4377009708, 42047.52112670249, 42598.357943654395, 17631.971293925242, 37034.7645502503, 83637.72937101698, 51131.94960053341, 95692.82520074768, 35603.78815748582, 26373.091077568857, 69588.8904001174, 57271.48152540694, 48839.92252892681, 80797.94493064457, 37764.99418437706, 47825.1877900709, 6767.167814337838]} +{"id": 1403, "vector": [55877.98202547935, 10655.271852036296, 93940.9422818658, 41958.54207106522, 62585.50200511844, 15649.299598091759, 65910.98301727531, 96274.32500647506, 26377.40968982256, 50519.185912270834, 10154.665115940665, 797.1303759972459, 40380.46086766724, 22326.66006444447, 23639.245592284242, 37224.954784657646, 26696.074280058834, 60421.82546758334, 18516.73818425654, 49525.37780745458, 88292.95733588166, 27851.565704046432, 98014.11414305495, 79430.49337733061, 22099.96941609398, 88517.74655736044, 9103.581611316147, 63231.90617687573, 81702.79141618723, 54159.80437086396, 57871.89889577402, 52348.807883303714, 26689.186945578636, 5395.804230469692, 49234.24227131072, 37029.94942791463, 54855.603830071566, 36420.86150826538, 90718.91292690158, 50549.58035788275, 69165.78148401639, 18523.87612355724, 62785.48894205289, 65443.07288811443, 31081.813173436334, 36346.616346411676, 198.64401926683595, 75817.44875088922, 72313.76778306016, 87715.81027601917, 40954.12335690952, 48878.1995414527, 4886.565260866049, 75907.31796026937, 55403.17691586068, 47062.42797367085, 69823.59390370455, 83739.89180106587, 74817.9009562433, 1814.701479080183, 50313.06885113952, 51209.03985183927, 25608.72215222877, 40833.42580207726, 43655.82238235539, 13518.84383620623, 19731.133494866983, 84633.98594165694, 12564.952198507306, 43769.91233954493, 58085.6806934832, 41938.895571692, 33643.419801651195, 48315.393193143405, 56487.9513131709, 36377.552867341, 54910.02840942358, 11968.212385641707, 88098.38728273354, 77755.66175375831, 33624.33443221836, 74420.49227870032, 32866.21743421575, 4483.55256188675, 88309.57293846384, 8837.333050300966, 84282.98300905307, 51133.844646680205, 91102.0842452968, 19607.16006067319, 56677.99357167345, 72712.53508391471, 51189.465278751166, 95268.24121744587, 44104.36933246835, 56286.9092817742, 87480.18638976211, 87281.97533348211, 53946.496989620675, 9614.208657544465, 5733.5692563490475, 46783.19728196639, 17036.884929812313, 20414.846836367695, 61291.77064677984, 95998.94528732992, 57634.532392613306, 1588.2148091917015, 20326.796233494082, 82535.72965155124, 44044.98152817939, 7592.397845766163, 32921.48178420714, 66097.41614513565, 24112.997736419173, 85595.95257153052, 7243.247660921759, 89358.70277590121, 15020.361189378862, 80316.35751222695, 89485.2969223223, 39121.36468441653, 45000.285251396264, 75122.85521647069, 40291.682676574426, 11819.860162987883, 39477.97678723618, 23142.427509050343]} +{"id": 1727, "vector": [43585.60079432209, 96593.4486123582, 45562.15628926818, 82963.77846138428, 33856.002191338186, 24192.648827554818, 55428.33077627698, 96871.45331482757, 90615.53089791513, 70496.10168513977, 56546.76491452315, 44381.00215856556, 96905.3596994747, 92449.7641924875, 47493.1709648619, 37950.060475246784, 8016.180587161714, 92933.68004184452, 91421.36585262782, 44485.98674222724, 72279.93601501103, 78437.19189968886, 7093.086759110134, 61487.127905788904, 86961.10935210675, 18924.281357969187, 76792.05619748768, 54100.89365353351, 13315.338376242125, 87355.33101927437, 87307.83974632353, 62605.20274714673, 32941.664151444784, 19867.875932606483, 5759.553319114774, 9221.92927439931, 56992.408862918055, 75992.51275321197, 34384.89493110115, 71502.07940709894, 84778.7003368723, 16234.83849366164, 6807.476485594854, 74982.95739240904, 51173.37736682852, 8632.816133861643, 14344.660185715207, 15037.382769595819, 44826.343210227824, 24783.03515179273, 73542.36180932522, 76906.50378390259, 55531.400328102085, 63856.63360600302, 72024.71033438387, 352.9803577499213, 11984.93243779759, 38205.17773960908, 83496.62202845949, 98938.42138537642, 15659.580198185053, 60191.975830279, 76949.21500695897, 66774.53616428684, 52164.683333523586, 89953.63117551823, 70190.68634891199, 62732.99261458446, 81342.14853576338, 96401.91186163465, 16162.87912912443, 17112.464786148983, 36863.347622305024, 57654.69563873238, 81012.5127106101, 77972.43314661898, 33514.78769549021, 36988.09200999557, 81176.03418526439, 88661.5309728396, 84188.93618549978, 52260.22632651777, 3956.5641921947004, 68644.57962099892, 14310.226428313566, 5486.135112017753, 68560.975382615, 20115.596084844034, 94645.64423990689, 74057.59974918971, 28848.755287509488, 89679.41277166069, 60637.023695542026, 15599.845134632751, 36989.45384174903, 40472.35155321056, 76580.07256360275, 56517.6599676834, 68262.45628674625, 98913.52997750865, 34201.60761324197, 17290.79881906671, 23256.0578838369, 28784.582683270866, 89242.02559945514, 40370.024609764674, 89818.56207580614, 29028.39792863615, 88597.0259782596, 40268.53657949103, 3545.666533667102, 94280.07741511184, 82810.16231872521, 4221.238611195022, 21723.289372426112, 6271.271841906933, 35745.28651831595, 55529.58054010175, 16696.661488614605, 4390.963034579376, 8404.914717984035, 68027.37097585507, 42618.79047846424, 97737.91405371921, 57796.43933901457, 92632.05501204127, 56485.187617709256, 86468.58744613742]} +{"id": 1964, "vector": [48525.16354691162, 85660.7981739264, 8579.936785576125, 18892.987922186345, 67088.16696807672, 85101.61260509831, 20556.834756584096, 77975.37856142731, 51662.1225632645, 59842.35399682933, 26327.172629931993, 11030.713126647008, 77875.04333655458, 19970.324215867786, 83466.1129440489, 18724.583141261362, 57902.1186064139, 40758.465229304886, 23283.072733775058, 34560.732049517486, 64429.71495371961, 66772.7955791439, 87258.68135667189, 11937.02017312409, 54968.5835060185, 55768.38370948899, 50920.69572432676, 5853.670085886398, 93738.50189760757, 62061.69268061015, 37427.626980136716, 98982.45018822853, 84497.74836373827, 86419.60135036807, 11455.705762502477, 30639.845392614727, 74636.21116133143, 58964.30896956532, 94334.95470418844, 5655.842596961446, 20741.351220290293, 23562.086226728763, 15356.587882115857, 50518.37267732653, 78493.80256030854, 92940.68508204269, 4844.133597057687, 34773.56536885303, 28146.61539415043, 56134.70677550563, 96381.03987059768, 7343.236925100416, 62934.45245321117, 62849.700837311975, 10560.447881592749, 39793.06865633951, 87394.30202666881, 79038.1896551404, 52265.52195160933, 8965.661900114297, 23931.345597688436, 31927.846302705886, 88975.34724085777, 74517.17639898916, 80755.97455887176, 37110.515407614905, 78037.2671604786, 52116.70436208071, 23478.134235453774, 40043.17054340588, 54960.89224947559, 64944.65639852192, 21262.136583605905, 91646.43539504972, 90189.4649774933, 2238.0779761147382, 19465.351903376348, 97286.17546171829, 68667.25131135229, 35291.35745859037, 62917.00293622878, 14655.779057425732, 45365.639616638495, 41463.39077503315, 81415.36526197041, 16264.726036696708, 44976.25089386761, 27959.300774387808, 76552.15538952267, 7675.643615326288, 70441.26961720035, 85518.22934930807, 82326.96928632758, 60497.090110112964, 70588.05864483798, 53791.44687807116, 21835.468003004877, 8159.743014934306, 54141.59256596075, 74814.84235804468, 46510.80604916645, 72012.27655163914, 19895.57940460781, 31597.090263301543, 42805.8564379532, 11487.321206795397, 70171.97922708573, 7138.497648354658, 76094.33867546347, 87669.7708319219, 31100.112342077213, 27.64598349923597, 34008.06329543036, 36777.800683416295, 58900.08833032054, 80342.71302116671, 87175.82789458742, 82443.47054190372, 8951.619578116588, 42357.866307783945, 26635.943556591523, 5011.063095173818, 84976.04608479116, 59461.27731735862, 93267.94485337216, 45867.7730577138, 48717.690567286976, 78628.00495097178]} +{"id": 1863, "vector": [18457.21708825704, 88707.51744441505, 31893.42495022497, 93425.08166274364, 87673.05490733331, 99032.53394688382, 5931.497215121584, 97066.07656655571, 99865.0538955925, 95602.33837659347, 59829.908235601084, 6257.381715061305, 58806.71083058128, 55976.877304837944, 36610.65165885969, 67358.4429995764, 31572.180382721494, 7077.363407983017, 70525.91528157324, 84354.67784463409, 77098.37594778415, 82840.4581463825, 21711.793854729498, 68380.71104461819, 49556.756723270024, 93349.07177740484, 78226.3202339312, 44577.942811452864, 91054.738139429, 48571.22708819801, 46007.81809032859, 69303.68901438695, 79586.30048530754, 98278.69330050609, 19017.397446720828, 56362.999820028635, 2073.0850810723346, 39398.71632987071, 3495.0739273089894, 52740.10254141725, 93274.59001701597, 38889.63622287197, 16668.42029618675, 70443.49114149157, 80983.7790473333, 60432.484146917355, 88414.0211968374, 22386.42908695696, 90851.7665565003, 2935.356580701931, 15941.071635397608, 70198.57380496524, 61361.60225261045, 44445.02136622395, 22264.846580438756, 37285.260409744995, 59571.12858138252, 82943.03937683784, 77448.48524737688, 4145.555220234421, 92413.00552565207, 40089.35346410604, 65071.688618776214, 27345.410752482647, 52628.5420927407, 60270.589553731224, 80369.45929637816, 84315.78303041804, 473.93329496353556, 10240.44535751809, 13263.079542931933, 71651.13647950155, 32992.96723963264, 42870.93484286745, 44690.05363800254, 88572.46139505318, 86840.86427642888, 82945.09836078442, 77353.51147552159, 12381.99428380684, 75329.16425157605, 17691.191487349035, 69159.17407453796, 66234.88310935628, 53397.64786136327, 75559.93947207628, 14580.48627624372, 34528.342152704085, 89376.89958947724, 61101.767579860054, 56210.281719007486, 63617.217618136514, 62375.379390102426, 66743.36098368639, 54419.04906591568, 37472.56813901444, 59083.17579115313, 46139.08508303268, 16247.414870775623, 37602.36866879107, 35811.00307264766, 22138.049731897558, 35309.863072712644, 93926.65715924045, 17110.91465798805, 38951.527487404994, 82588.4897070662, 23710.957370358887, 31886.15299411052, 52248.74636734608, 98082.70595990193, 4357.052667385119, 72010.21745387293, 36452.82085292715, 91565.86868649535, 45380.527726016284, 60658.95996981874, 60661.33186412238, 18194.049558467574, 68111.32214322822, 40281.82961976916, 87233.82756461468, 14670.524433720122, 34759.26022884329, 70205.20174656578, 47402.12763819644, 86510.16252256563, 75912.36469774442]} +{"id": 319, "vector": [5276.516757490579, 20305.694649498007, 19464.938369853713, 8657.38352924449, 45623.06295747178, 33874.914561778125, 89344.82953900626, 13049.782905138485, 91064.54508924205, 10938.903782658104, 97024.1730944543, 47306.717671479804, 62306.71532118378, 85301.15938800419, 92095.7175365549, 20854.35586605088, 84371.21474686515, 62542.022668525555, 91997.80102616474, 54868.154877765395, 5471.390763192707, 56417.76626016842, 39384.98940621401, 83893.06666243915, 99599.8668315432, 64121.44639220458, 66826.44688267137, 61763.4275059598, 82567.19135090931, 52227.52193273717, 91536.638190432, 90656.0817457044, 82569.3026184606, 44876.78926570082, 70970.29088479528, 4387.082925522401, 74301.06236957198, 88923.54827834106, 25151.52172038403, 83239.28338583084, 5408.692326099141, 94295.58282232504, 62366.23497477969, 87238.03892209983, 94937.34730764508, 48260.95412727113, 32248.88078736249, 33089.57506420799, 69464.65435118793, 48708.584173126255, 33930.27957614333, 28032.567128663122, 20809.96924754147, 8845.70866146257, 56107.0992013916, 47178.186605122515, 85450.70640179086, 45238.541750636694, 83168.7011056088, 49815.070823600974, 86331.56119396641, 70298.51323450981, 13466.747527626932, 26418.861066008347, 56893.25586226186, 71059.06786537507, 54474.91307780889, 18524.424734875243, 14789.993156609715, 57916.15905009126, 48478.47479265467, 51386.88423062111, 47391.019154650035, 78791.25857319981, 58393.87289121591, 57689.731681340425, 98406.47704724033, 89417.54425626896, 61556.81693932622, 53297.93605316237, 69100.77061466557, 48772.18036495196, 93148.55590319095, 78927.21053497426, 32985.86724629603, 92838.64694430465, 70069.91287946588, 8924.325215466522, 77446.10141559032, 59168.885437026365, 44270.20789367584, 56362.495037244254, 39359.09762679448, 57251.89350870215, 3063.1758253968133, 9605.779575397233, 57691.26161442513, 25240.95755055221, 731.0436563959244, 60024.31733885004, 14586.423046141106, 52085.6369502854, 68506.65532699955, 9530.48148512966, 59139.64762866526, 29451.116109451537, 79852.78841360599, 28010.185545050346, 60689.611441390625, 38390.680856997904, 43768.12426945598, 94048.77996952971, 71032.0287971679, 48541.549971963235, 96370.57492291264, 80452.75290924904, 24685.073058960406, 69332.97581696442, 33700.7162451907, 20587.396261130143, 96575.6993252608, 52084.103444859684, 47227.26204981747, 4405.199281194738, 5430.567132142317, 29961.198168532486, 79010.12489985464, 53498.17400846685]} +{"id": 872, "vector": [9764.698002454941, 25659.559766153907, 1510.2877252279768, 58440.07881493225, 99260.1082394177, 3586.647994890757, 25442.554745052505, 98209.27639595231, 18455.027380156575, 23176.287955099408, 31585.023693980074, 7360.719698398244, 28595.96827408092, 22824.70456322917, 14099.33553031887, 71163.2825886203, 32876.93187931125, 92179.50434752423, 67839.25043847872, 9506.156422476286, 85197.71662000872, 52896.00579809128, 10855.332256504458, 88130.08627581286, 7758.473326119997, 28620.494070524728, 55135.760191671005, 52759.88502990514, 67392.14703493519, 99257.27709453911, 92168.42923388172, 24679.3457442393, 90121.08436191862, 20659.230693848873, 8981.321389375973, 73979.47050484057, 5772.709948449239, 7087.515940222644, 22355.921126846024, 90995.04805891353, 85301.49333047243, 62142.95760701858, 83645.02246951446, 40693.33927851746, 58701.368287964404, 33013.83023816412, 48952.27743183719, 73587.86998897148, 4321.886176109224, 19410.244200066463, 63149.86564946411, 17555.081471428424, 34417.5093913251, 21839.534351542057, 38104.249046203506, 36852.948096246415, 92000.61097748239, 27329.34706238104, 78768.22146259995, 42121.65168327393, 22913.04538018658, 56552.01631387346, 5618.236879801597, 76644.92980883378, 39492.94304016879, 81499.1192334878, 81960.14208705163, 30436.488449373057, 92445.32912414071, 59339.1344786871, 41042.727178429464, 21915.35317606995, 46699.53373902894, 67338.71566889485, 44737.374611348925, 65516.454026218016, 92761.01285793322, 38602.15866676181, 10387.252374866019, 76318.49675804091, 84646.04531564303, 84101.30712274532, 22172.70738183197, 57377.525986227396, 56431.91901552499, 71682.01901738066, 94564.03819981668, 48886.99140152618, 76826.43476398519, 56454.25039349627, 40756.911150695974, 82435.81208307821, 13686.383811522306, 73591.32614802953, 22927.004183953613, 76999.1108231932, 62035.26342320014, 1267.7870805366308, 88063.62945434074, 94424.97286916316, 6905.540384820841, 51712.46430751852, 31560.373339479596, 10116.218925803989, 19742.035602959797, 39944.65824308403, 78905.60688879689, 18156.13594284864, 98204.20542505606, 86649.43542266742, 38315.97662868438, 58561.41070423231, 29053.124604479963, 9055.11709547795, 74051.8619365221, 45096.617555886434, 13011.580063995609, 10068.719874349263, 83098.75133388973, 54425.73822348309, 82643.34909749744, 79270.84859584487, 76897.48117055031, 81767.60261736842, 60120.51425524255, 99859.55503917529, 69477.79791179516, 45747.54515613081]} +{"id": 750, "vector": [5320.642442751644, 96367.38546126778, 71756.43776196045, 33479.24277758444, 11404.048723851745, 44888.78814245153, 20615.127492207863, 24115.588502716444, 88745.77090289128, 23951.471617596333, 2387.2055031600835, 63131.44871064111, 90105.917787923, 9313.873827606389, 81277.75903664176, 67610.18899053066, 19336.14595699221, 85052.7519981514, 84630.69226768307, 39088.86162925749, 33257.53697769446, 10664.861376056146, 88815.18051284728, 7877.651954524889, 27283.29722965048, 80245.65921387378, 96259.50255524757, 65522.069698003426, 1282.3718592038902, 48634.9936992898, 28979.75993332653, 94251.75464557498, 68599.89929143217, 70135.14974859892, 64130.35957988806, 36451.19497946613, 29598.50595283574, 2584.2014762023414, 21808.44002507747, 81380.3758959356, 25520.843307646002, 46783.581451921804, 40641.92325558915, 49130.800888939964, 13681.455728222103, 33049.722796145965, 18416.931686469117, 46803.540968955196, 86578.65133052233, 64347.65537163504, 23006.236751056564, 24746.064742892748, 26297.987340015294, 69771.52115120066, 33409.399954243854, 81623.50742189198, 68187.35368564907, 44338.11786141646, 81392.30990086647, 25013.64387986733, 98106.90574365843, 49610.28290327301, 42725.04135993348, 2303.6433312191075, 33045.42731824826, 41231.57359048529, 61192.20780591382, 22153.43037311409, 40986.56599434215, 31194.717363726453, 57764.70479222929, 5370.672907519747, 15533.273028385707, 74471.39049706233, 44150.44722949956, 98124.39200107133, 92079.72234721403, 98544.70575448047, 63692.55049984429, 54329.49356909249, 16931.04281754042, 86441.91820895496, 99805.31136722505, 70839.34061228251, 15857.104020290979, 55837.6276948412, 55837.38682359357, 97520.16591295108, 19257.018711555185, 11279.761286673007, 93522.7952618522, 20805.066083294078, 32335.268400802288, 55232.3397242374, 15500.197980748288, 96175.89706585456, 37494.82590219104, 30159.498114967053, 91840.95036307407, 46045.18495333521, 4788.63312651524, 89604.87831941695, 26413.3481519787, 96790.07107287202, 96752.54528529936, 19089.474031273756, 5064.312801415183, 53646.5647048162, 33773.588120998844, 35556.119754923224, 89045.72023651132, 960.7504872732875, 32071.765890115323, 45063.852283839704, 57910.105131819226, 51345.73885994629, 78705.77924073744, 82236.8183079183, 56241.18004555183, 64603.40846301117, 69054.73403039803, 17335.5612210769, 85130.6884942049, 43373.14971131747, 39469.99714546566, 1072.1730848807565, 6891.052214467164, 79758.7337359285]} +{"id": 158, "vector": [42541.96104318328, 62451.44105464331, 25424.54868258558, 18343.763397717405, 16215.104755797916, 95680.84729647705, 8473.306089054278, 29746.189494365215, 18010.336375576775, 69396.27920133705, 76963.812617273, 45885.251769357885, 92714.90763075573, 67021.3983982774, 11752.808050199914, 7385.1978020759825, 54525.344286974345, 90193.66455930591, 55280.03563122564, 95884.29526539703, 94913.32493103572, 72189.64924013692, 74545.58595573025, 9022.629162739615, 42579.57781254644, 2667.805386874389, 46306.33654126658, 97718.40358020447, 42768.829050739776, 56223.16330800554, 89945.58527052525, 57561.31326441071, 80158.50853515674, 41384.75168794805, 27578.241399813385, 20905.140840498614, 47716.88649407434, 60314.64445938454, 62492.37124936425, 9111.26445204803, 69.0803018261743, 42865.59988614578, 42171.529580913484, 34115.49053274747, 96630.66393522592, 92093.46749718938, 49936.738768572795, 30990.97369137702, 42936.69837862568, 55459.06401203469, 12601.323241967644, 24272.755171961922, 88465.54510828962, 75484.84510763836, 97907.1884746006, 12282.575968423482, 71318.91784836448, 43455.795377645576, 269.939962566268, 34923.398745881015, 29706.83293161991, 90120.22580645207, 40248.78044317165, 52907.55572567591, 34666.238884020284, 17590.021058216975, 61960.14958284185, 1943.6249829217434, 22393.599187410106, 17413.70918864482, 5486.632423964166, 79861.82157500855, 92850.19210334003, 583.8595385741053, 10360.878098941317, 96144.75112672042, 98767.97752413191, 72479.15850006393, 85337.89776693693, 56046.45580301902, 72156.09160686652, 65272.080986999616, 52097.690147923524, 46694.66291699486, 96516.86568588165, 18836.901864377654, 24522.151759191525, 18875.98185026117, 19132.852981591674, 63499.40967900557, 447.2204290346782, 41608.18149652819, 46396.64274266314, 47363.772480911146, 83686.32125989413, 75666.65035948118, 83295.56324347576, 67140.6762742234, 19118.577261517956, 11892.487945507468, 98196.45909780334, 45351.52094926902, 54239.59273083366, 77543.09062090497, 3396.7726547731836, 69010.23960307142, 87296.11957324443, 91536.57397521754, 2954.3431120541363, 57828.89416765197, 69263.06525189232, 9066.756862565706, 11269.245933693273, 96376.19397162169, 32514.8754019293, 87362.79496790268, 3295.716363258694, 90456.1330333955, 37253.379026157876, 54064.476879244176, 48519.47377578189, 34219.47562146702, 38196.191622204, 42055.22635686575, 65316.029731489514, 3030.885485594892, 73812.2143457977, 51963.09027540904]} +{"id": 1419, "vector": [36963.573476969956, 86435.65561951141, 47993.566620938735, 94295.8224857704, 47597.43838689983, 30769.73906902858, 1409.3387974830284, 15429.74884874193, 81235.4962742053, 87132.42562063264, 31038.21813855806, 53447.87560702847, 98070.52876055553, 61656.615478224165, 56251.91508162978, 65291.598674428365, 83806.17000108497, 56954.28108090172, 87263.98523241883, 53288.81837197449, 43307.889297111324, 66361.75993953196, 87998.27205990459, 67908.9748508863, 59610.14903458614, 86976.82382633742, 24357.34349237365, 61345.57630962312, 92757.9434451446, 37250.02848126019, 1704.7717526015304, 8892.244674754724, 84225.64221027457, 12715.709315397871, 3873.8240873684495, 21936.14573958037, 26329.432077427016, 72055.25594320438, 6890.722466718291, 13569.762111683414, 48572.77111340309, 74311.84358944812, 57787.56104307947, 42291.795830302326, 29437.75814441093, 94422.25979619836, 87765.68356144814, 68395.43603762584, 78023.74986871838, 39161.72521523339, 21404.80517545228, 93249.82327436299, 4741.616402791504, 82927.17089022546, 45806.44888141877, 69700.55622165538, 96512.64199506893, 516.4686639914539, 60622.69212253098, 60865.35038936195, 27878.468496855934, 23152.372283014, 94338.32011297105, 60589.9509557666, 83366.51360343206, 20601.118718271704, 94730.61194468194, 80215.50893178332, 14130.770156778217, 35180.4543289168, 22067.177423517736, 33751.74497413195, 34239.04025679347, 6209.8677613429245, 33718.401791518656, 59949.84476751069, 49549.56444325854, 72106.27982940912, 27903.963289605694, 69823.45795029607, 33122.24760917606, 67377.32190497917, 99999.26717563462, 55323.00143287782, 95228.56387928521, 43971.57009267412, 50556.697147884544, 15969.229275667496, 20198.826541706407, 8548.420451794203, 86920.88526758907, 28657.80821839232, 73203.07531796595, 74018.806364063, 14638.185826728057, 94716.36881194338, 24467.93360160794, 38552.00297485282, 87901.11334158666, 22224.435660922194, 18227.42697169577, 831.3509655685558, 83446.87457068004, 40867.4716587863, 74130.02938372048, 23241.86560621364, 68885.23135382406, 41213.526780395194, 19507.8932276622, 62329.66300573418, 64055.02043787542, 65013.399760116976, 39295.516214762385, 31719.788937786085, 52985.23046788402, 16119.216140009152, 38254.53201108786, 61275.17999026627, 16837.030007585006, 99096.97076105158, 24037.523675213, 90729.29626824675, 22926.30894642974, 63814.58165516814, 87738.57426703362, 48830.722326803145, 79392.16843352914, 77738.56664165221]} +{"id": 861, "vector": [81190.14109806532, 55038.466690863694, 28120.840300133088, 44977.88804335955, 59696.78676657114, 48920.74264342258, 58120.460561912034, 24836.079657689294, 83320.91782219478, 6633.605899142658, 66075.56527474495, 54865.886657308205, 89506.86301022132, 66466.00152592674, 50984.20947259369, 91174.30867777774, 50637.925585207675, 37366.95337012591, 76135.020299713, 43394.279225375154, 73345.29278636846, 19631.38983962338, 17249.702609263273, 23742.38583634446, 59399.976790746536, 80331.02725528632, 10350.052985694103, 87519.54540136323, 62224.658809406865, 73949.2900179622, 28910.225866758454, 33545.676194104126, 30505.68145470568, 64650.56075118466, 7251.386990538633, 65119.49306167398, 61675.230220150304, 77119.80629566315, 79718.84234330653, 57248.3404622932, 96807.67656277295, 12538.798453048528, 18030.462360989874, 6986.808974934255, 87197.75487963848, 20471.08868077662, 62863.96843925254, 38828.376309232226, 15995.546478723489, 84132.29717439007, 71931.12104884526, 61918.35309404484, 74954.2333555647, 99153.96966687714, 3848.942203755745, 12385.158590458046, 51111.55385366834, 22967.200820090227, 6883.155902836224, 23236.58663427205, 43893.04578979066, 66685.87084539916, 73242.93709206283, 43170.812608258515, 34339.31366275871, 70366.0874054052, 2578.664738760017, 54451.583899060075, 98169.20261573498, 18349.5285923063, 34519.80820837098, 27474.111136929612, 16460.30218549962, 78238.82206722713, 51472.614903920956, 43786.62854844796, 36569.34710800947, 99991.05017575693, 37348.22834442792, 53929.843314675796, 87331.43886396027, 11628.761814889676, 88210.92919683443, 5277.567297853103, 74777.03537084657, 99996.01886403048, 8006.082902327083, 82810.93975377336, 35169.10112828575, 48670.71827636799, 66998.82875059717, 48609.654392284974, 96013.29172484706, 12986.27564823015, 67867.71612453989, 52463.47825983412, 78872.92174337423, 40871.652362939625, 15762.093839772351, 24347.052861577402, 79470.38322432137, 40597.61047165347, 61439.8133408149, 96279.43025812572, 69912.79655795415, 55153.78600676567, 94798.41921729945, 55107.61449316709, 86596.48764252196, 29784.62292056232, 50595.740532024545, 63455.709040145426, 74016.24185964237, 44541.61424401284, 24362.429711078672, 76638.34264794565, 75110.94971088514, 45390.18846075814, 46789.05130598885, 46433.91451992231, 40867.35831235434, 73873.4270726959, 69531.5633301245, 28659.58560727885, 80307.36763938406, 88366.54450485266, 313.93968848180134, 18897.46760066635]} +{"id": 954, "vector": [58092.82722222846, 58651.73825934758, 58517.53293067979, 99185.58943699267, 33177.4893692122, 53712.47973329536, 68050.9284549627, 98521.4806304775, 21539.55816494156, 43591.32917197358, 80832.84855942152, 57321.6113313913, 46982.409736997244, 79168.48808452695, 51824.572824319235, 72956.25125355947, 84084.46879385108, 66584.68514510957, 73343.14352828631, 50577.37002379743, 99649.20011992301, 18612.379680423506, 34440.05834333106, 25181.02800614068, 55215.270914893146, 53840.06508355089, 84163.03904223099, 13327.947682062802, 60069.87794884996, 43418.922657648116, 49336.542844065414, 93314.4022594313, 9802.421244879, 53336.294429042675, 53427.17796245719, 96497.16846759601, 56351.07791592605, 86735.83680889306, 31448.12079269095, 98943.25240828919, 92789.16374035482, 99162.1244959899, 32906.291257955934, 6778.696210114344, 75628.51536050001, 57253.5666721526, 53951.13793736457, 56008.362991499875, 78895.6750705591, 53855.29928165622, 96417.55225768096, 67792.73640733173, 24731.971667427122, 86982.15878444686, 64462.98754954391, 76978.11744026914, 12493.33755123635, 47614.59743858957, 79126.9219865273, 9145.99347657975, 90114.78933160423, 10372.973776629058, 33804.22525904512, 71670.72898874847, 93447.9827081762, 52936.77338408308, 23503.52876143761, 97622.50817746905, 98468.31776962923, 85472.26544533367, 40003.4788583875, 27466.132082530992, 13149.142957591486, 95859.03329154053, 16989.355500689697, 33950.54389567591, 63016.585142381635, 83120.72219732939, 50626.53255639706, 8922.838591086767, 31987.5375093162, 38239.66950835628, 16896.218317627467, 33293.32271652807, 68462.42061424095, 54435.55364928446, 60254.131699298356, 20200.296098724302, 21767.264818911648, 20182.273633131565, 83440.06967189057, 51873.4641434894, 4758.177512268513, 27568.587607031415, 97951.38611180843, 96940.40169745804, 67285.97370590067, 39826.468198463815, 99874.27301899498, 11237.603838810972, 56814.78173601133, 45442.539858247044, 41587.81515182456, 36469.796083460235, 89133.37760892979, 9044.05417748334, 95821.96452019828, 91333.1822776926, 61371.95938076174, 8319.89169147076, 24179.94219637569, 67565.95505757711, 85963.70375342721, 17777.540720780005, 11595.419235141435, 48525.261975574394, 99748.92916435852, 15123.686838008054, 9533.818789575422, 82869.90260048755, 96661.99512413552, 49467.19616560635, 32003.764157626334, 59725.416869943714, 71366.92171868013, 1046.8083704338494, 65969.20574034874, 17125.625718524963]} +{"id": 51, "vector": [50226.420704096185, 72235.24119758108, 78324.91183376573, 46193.0717383065, 7861.394887908368, 32976.35221994048, 29308.520146684135, 37373.43528984966, 98958.36240465217, 76204.12264918676, 31831.537815909527, 32114.846958562404, 23631.62804186296, 25770.875875845122, 93027.03528201125, 37934.35123347003, 59081.19056952047, 31862.544253403656, 14021.96627656308, 95823.44031972282, 98957.46961566654, 59563.219966959405, 18824.660023246608, 20534.380207936654, 78712.1265277396, 38371.193964410886, 1651.6436000892586, 32735.888537219005, 65330.47884716206, 46492.043668350394, 41699.21932062467, 90967.60905694656, 35310.7453831628, 37510.85400858052, 16826.79555793105, 58455.66385980704, 71028.56115501226, 26304.119603700914, 29153.646172979108, 99964.5212223508, 89195.2017652433, 22694.60978997091, 54836.56164539042, 33953.46091979042, 62565.92710593007, 13176.594382006278, 65321.85463479008, 17360.17743687297, 97776.85828119921, 26922.828546202592, 47782.600059596305, 75683.52321747396, 40642.44434696048, 56472.535145049755, 30033.230692986668, 18102.25963274411, 7407.7407224267035, 55894.76581940815, 72062.03673452145, 47301.02737477821, 88030.8342186165, 28452.333357772473, 55610.537448158124, 79113.60368210245, 94030.6161643656, 28763.8832636827, 63218.28498223166, 74006.89183018153, 78341.2018599674, 53934.97912672213, 71128.09186419156, 58723.74985806601, 81984.18671795895, 61256.73880802103, 62188.50978870073, 4656.137007948957, 87558.87894453712, 4266.717719851076, 50904.38549762313, 99854.6292045094, 17805.81434251084, 34156.62493755682, 16289.797853827647, 55005.79530428624, 58191.62565281572, 81505.2440337237, 15965.031971646526, 36377.96209389678, 74207.35790543468, 47051.04309537663, 82950.91784573128, 48526.311272255, 82683.07279620078, 18320.942746978486, 44347.25109913646, 5157.0809602213385, 37717.21427016015, 3597.819500352373, 10635.145668797852, 4013.3903490540247, 88384.62823478963, 6605.074953067613, 47645.489825320954, 45241.992624880586, 8938.194898860418, 6953.369993831393, 30816.99526015582, 20008.355084050734, 91089.30201792557, 82300.60331149724, 68970.48346297677, 22105.734439648826, 84584.5770013023, 36130.43345334578, 33362.24391432349, 7565.03339247594, 75920.36053632003, 81666.83092772038, 65210.59852402891, 95.56810787993975, 60767.05444645673, 55140.70030992714, 87000.36788627438, 40018.759859656464, 41528.01680997262, 79934.79966933944, 43934.2394276316, 15358.310925541884]} +{"id": 1984, "vector": [58268.29457755686, 91458.92006721914, 42309.74352104718, 5862.90148970815, 66589.25947583203, 15403.659044862372, 20421.311141512455, 28770.05854006076, 59115.958069500164, 1933.782026205677, 23564.03273365608, 6228.022981530601, 98916.85930718739, 11518.514117399181, 45491.3746526418, 54365.193517675616, 21453.32986888444, 33128.264503076236, 93098.83105142864, 76833.23070050422, 63053.12864058755, 3138.4281790869163, 40745.995815376744, 99048.11978000234, 10004.828062745475, 16667.54771709713, 93824.81038905917, 92015.36652872335, 5594.518914758617, 93078.52263036673, 49840.8818812381, 9094.866045354032, 82985.76899885376, 77670.72903889186, 32726.748865589572, 61147.21754716993, 26283.855806529642, 22739.11360870564, 99027.78699931056, 21934.482210627804, 46737.88545017913, 25155.28447443689, 1269.4326954757807, 91830.69690749336, 68574.11750201452, 28070.058119687623, 8950.451161211548, 46128.260370760734, 84680.95973216223, 21707.753325371104, 17504.59239036212, 57259.42215047956, 93082.07255788849, 35450.35040591016, 15569.043656232561, 47048.498152202366, 98315.45352298483, 32210.574512722178, 99550.67964061322, 74654.33622006098, 6520.240665170607, 16799.426310656505, 78687.2268380343, 48382.59616855381, 74694.94782159678, 67106.75680448557, 48969.36230836443, 1830.153418400371, 88527.54526094864, 27297.98163410072, 43307.24831806449, 46394.92854938341, 37498.14140181964, 24836.30114527243, 142.39912171178926, 22949.863660645962, 37069.384455287945, 63671.930387337525, 31185.54112424169, 11079.786308342644, 82892.07468603576, 91647.37793955355, 65994.56603899438, 15087.696145453756, 85921.0359263985, 44176.4563432649, 3202.672421279029, 42322.93211581777, 84667.489335182, 32989.179953553125, 39682.98655419721, 79165.21399532742, 49922.495542643395, 96334.51703283687, 36545.15809847924, 72562.14228418579, 33289.03118286128, 81066.40988891109, 73560.51223080959, 20980.412720586195, 61022.637121471715, 68225.13759937738, 85201.00580349921, 97673.93438881323, 87432.11464272319, 5013.187330228108, 72064.42751041037, 20595.685917906536, 11042.52438092611, 45237.239950544936, 44370.489235561596, 58366.19764908075, 48838.75973065431, 98961.61218955465, 1518.812243781953, 88428.01251077291, 74821.8771593123, 55981.77356032666, 95945.14000912242, 89670.67620338195, 61120.7885151263, 97613.27693677992, 99230.53806404876, 17021.230183349413, 57466.6004733312, 83584.24875571081, 36770.596999594665, 48216.38199783325]} +{"id": 279, "vector": [33939.35381254847, 78067.39193725307, 36632.04215536887, 77802.94434971243, 89775.96989757637, 6863.290604634031, 8099.4098200021235, 31438.326646174653, 1849.5994830827644, 52695.82952688772, 18036.016809593846, 24106.800162593223, 97535.80298689172, 98733.83084378488, 78250.81244580724, 10088.376235582886, 86354.23102944584, 16907.628701658774, 91665.44671404913, 94609.47526866931, 36591.69249564397, 81496.10302842222, 20214.982734513454, 94356.1451178036, 32577.330666426296, 83136.10604415776, 21022.27440092903, 76153.70807607054, 18509.9028462974, 30932.47633185848, 52467.31922384202, 73197.99939638161, 36392.63237150121, 19919.891514877974, 74447.21329710656, 42775.020033138375, 94904.78460001807, 13884.163779837078, 42491.44583295622, 98427.69846521226, 79663.53038517236, 8054.739983683856, 77583.32505429453, 75348.65749841994, 16573.76441583235, 39101.93956644835, 7839.950193191503, 3798.5158933380503, 49782.797000090795, 38647.39946469863, 2875.999385448247, 24125.462360297235, 56331.22344889847, 88992.25783889455, 4912.2111143783395, 26693.70536694453, 18058.10238857214, 95819.11656374557, 28431.427447696078, 39240.382008171095, 31107.20328653557, 42015.45983309527, 38023.89468412081, 19436.990596897085, 67511.30609175959, 9363.672773552933, 47785.50554038901, 52242.716693771006, 15843.634213808577, 88443.90321168618, 46211.00927032508, 42649.304076499306, 83497.67283646183, 43772.78505871264, 21035.53861632499, 88914.98871107212, 60647.52393727877, 40431.108501115195, 19150.828776943596, 67041.13408837735, 23577.104312652773, 36804.82965733808, 7406.613536642115, 39957.51277240112, 72205.33311960488, 28789.78786240969, 96117.14944603255, 74846.8278965883, 20530.16542716646, 33565.468664063905, 62107.73698493307, 90687.38586079906, 24767.922392437336, 5821.581264072406, 42310.310396758585, 72591.46007601453, 53289.656989940995, 69517.70538509608, 7686.63292880668, 25277.86923749089, 57602.312181576934, 88271.63621346625, 37947.608653416464, 17007.530040882324, 4202.919547724094, 54318.73013686101, 17480.102195271673, 90577.18588793455, 23922.31867900834, 10093.965018993546, 76430.57971862682, 96698.46524180584, 30201.85292315688, 47426.41457013991, 39170.8557992978, 32492.57782902434, 62932.96022408954, 86268.38768117223, 38839.291620442586, 91238.72875463452, 5431.911776133235, 15197.382093681, 44292.81978314594, 92350.3658964941, 35730.24873116464, 85652.61220731681, 99445.02850590611, 23338.975778985226]} +{"id": 486, "vector": [85167.1032858942, 9499.44138537534, 26733.780085682036, 35925.69424659728, 19643.05660413359, 60168.22069055931, 75538.79621578334, 65583.97170363585, 77250.7351032326, 5534.661800449891, 14114.14422719507, 87517.8135467041, 69575.42200201088, 65038.93621805575, 98186.43653136856, 89927.48522490701, 95513.99040521086, 37097.60928125448, 13138.903577681016, 72678.8923074118, 49436.888992008564, 88552.50450262203, 65692.63679127349, 16783.225255450452, 49360.995830388434, 4925.164499340884, 43961.800278514194, 63055.31204791853, 73280.90601743624, 90274.65672998411, 51746.49626665234, 10183.546892001172, 17048.685647212904, 45404.319050594066, 11501.152217018895, 33836.65737986966, 91188.27920606293, 81712.15952898064, 68739.2809759374, 95188.80590578303, 15614.947381340604, 18102.54241691396, 39838.395748715826, 92301.31455603565, 38858.62646724403, 4274.92368788982, 57423.495190919195, 3475.82245047785, 62069.429384031835, 93386.62322617987, 15239.537106206102, 65141.176611975505, 66377.46619683325, 78541.25782548691, 17336.04599319668, 9474.746811084444, 39545.97464188626, 85918.15088147896, 22274.184905266313, 34138.640172178755, 7183.125157406367, 27.62579289974365, 75628.10284300231, 9168.644180722385, 22328.904399172257, 20369.507447906264, 71974.82431919032, 53609.27070719044, 46095.370305442186, 56354.077350900814, 9526.147473169, 97150.1850070963, 77024.28627480073, 19674.672472171907, 90235.87208994312, 14590.008698435175, 32016.525340210355, 20972.051461650277, 6818.070812103893, 96478.51638447402, 25861.511570597086, 28352.00959562135, 32940.58444061649, 25619.64354645092, 33872.37423566738, 75840.74871402633, 51795.68356879337, 7137.198379845233, 69989.15415663352, 71798.0240027705, 66299.99926508023, 73868.75958106705, 89839.16061195814, 90602.01625014805, 55476.291145167175, 83306.60813637289, 59418.44442739521, 96154.5637926592, 46319.91487415661, 51779.78318365078, 73180.45836325211, 18965.54905701009, 55388.29292623105, 69448.72800335537, 34513.62586159411, 9998.088021906693, 14137.866859050719, 97616.74382235574, 37874.86455455155, 34473.29343296991, 64306.583998718546, 75702.22200919334, 57607.57317033283, 80459.02337712958, 73602.29227996284, 91544.03167150404, 69127.47203517136, 22749.031051915823, 72636.93755486439, 90721.54852223399, 69418.04607202925, 55467.3211903566, 90202.15994272372, 64835.757200491506, 68271.54302997632, 96865.79041239757, 41552.08378560215, 44765.493520351054]} +{"id": 229, "vector": [67529.16411211806, 87996.1031650467, 34496.65225192027, 89174.23238725912, 43849.619045570995, 31893.472878606153, 70252.29419801211, 43791.26994086832, 66594.51691491617, 11038.512826801105, 433.44733653393195, 51670.51882350666, 90953.33816912698, 78886.14128515248, 36043.88833026586, 77134.67414277952, 35076.65438371257, 87885.32034327791, 83870.37246577672, 29967.84627064668, 10495.593974390405, 38321.512493398666, 41580.361593008085, 56845.34695832938, 62476.14195769472, 40670.46271704099, 72123.8203649433, 3546.433445235808, 17324.452036860606, 96570.93535758807, 52725.654134798286, 48010.56891257867, 12180.589359280502, 32981.18675295254, 96578.0788969448, 80521.6713110934, 96036.64890079206, 5485.369798994299, 4785.845059390248, 39974.9630096783, 8867.428376560527, 9843.441804597718, 24083.052010499894, 1596.2092287367336, 20658.733796060525, 12727.565771081472, 69687.70824727663, 72408.88747557979, 55120.68150631125, 71375.83291195893, 91195.94668784317, 20782.06872474082, 41551.418959087714, 93868.23494457385, 60434.998354931755, 22764.684725639516, 27864.682068584214, 3189.356990807013, 39070.64741388392, 6967.258513130081, 65858.28781455905, 33204.56997942864, 64056.9441681365, 57465.78288928667, 86619.09191870702, 41124.23866556072, 92708.6475956378, 76376.02716154422, 12020.939549933151, 59232.22585183696, 50846.796856263885, 35530.45004861704, 65099.14070312256, 51670.325690902064, 84827.8442608166, 89439.5361016688, 679.3613131627518, 14648.21130203332, 1843.8837599727865, 20498.30977758298, 53685.62695941452, 3715.6321112245873, 72346.80809256103, 42032.0452719895, 29440.54421927802, 31444.114371435295, 33305.274780321146, 38396.504745128776, 80018.76344629619, 79451.66251288401, 23642.77954824402, 91313.21856164213, 68773.38624786498, 92580.27549496101, 57094.94867870988, 14070.854910059948, 48375.47738268002, 20773.233435062833, 84577.92882435437, 59699.733973307346, 98493.79935275149, 415.0625844813849, 38974.35921898919, 729.1583046760475, 11034.87298370318, 16732.91616594096, 44726.60061021788, 18572.807343095978, 9472.99796767318, 2672.4314880036573, 54952.10862368736, 44083.14035501283, 80702.0882259989, 10686.965683146587, 75264.5777006215, 31048.856516620337, 18053.923783527305, 40994.453368639304, 98632.8345751868, 74891.69417200064, 77763.17194025868, 8193.848310676289, 8563.515928060151, 12671.974547795695, 47887.121746331795, 14289.997893818463, 32938.689998178306, 15672.199905448648]} +{"id": 1698, "vector": [40601.43041985652, 99489.01931968605, 28774.660373760584, 64066.020765577545, 61280.162886890954, 47813.602721912976, 3785.9327286101197, 33217.96271035661, 21422.535612860116, 49347.4720089471, 31321.56563058531, 72207.46815330866, 21936.245412140965, 4039.209316664505, 23274.740122954805, 93079.3887346752, 61143.424598625796, 49525.564355370356, 42099.857244442166, 44973.984454437144, 35930.37932208939, 82726.08941178833, 68522.44064963353, 52735.13863545881, 5524.016996321401, 98439.06526768967, 69664.8207205391, 60191.674400116404, 89924.29056105865, 11654.830410786642, 9425.869640833218, 2237.1020555775444, 17792.155272903066, 96688.93088606493, 25088.345694838175, 83351.2487269926, 89946.86629905336, 54215.03766373531, 25426.4059679727, 84937.34022570646, 66834.94800581022, 70568.57876071992, 40460.55835696893, 67783.37074793616, 171.43926134344235, 35685.95134074328, 50097.12616065412, 58808.68845290846, 94409.43487812982, 34056.70575951162, 11411.710754410176, 72564.87074855878, 28702.520215860328, 18638.78382933123, 3718.318626599804, 25713.086995374946, 10836.109159031581, 74333.9046009058, 49935.13927057767, 370.83371489009534, 14080.030535025357, 92671.20244550226, 44957.64112252654, 91826.224863888, 1263.6461915337293, 56080.79034513862, 38791.067083455775, 49393.5490007551, 87392.5158186855, 54532.54078942539, 77599.35218533486, 61489.32256846198, 94035.68822324328, 63071.781505979285, 52958.13050325028, 91199.0205710908, 26547.064285726185, 84003.13055965611, 56914.022939366834, 10586.088421660012, 57843.67340949713, 70065.41595889938, 8999.373696357705, 2547.3455012937184, 40347.93340750323, 50356.026979515555, 38817.28216984709, 64339.54726245889, 44343.07850648799, 80353.25401588771, 96386.20327376053, 76780.8288724555, 94050.10801711878, 4499.213228249654, 33468.920746103395, 61478.25866266975, 84109.33121861164, 60441.0208239628, 60109.847070825206, 7031.400813251065, 1918.4604949288419, 12516.596213892817, 45752.547983271616, 66594.17280239378, 46915.783920551046, 61137.21285572485, 90952.02419555721, 17149.175160610986, 4906.374495112375, 88585.68200315534, 96132.81172715344, 44245.41829172157, 2779.147462319198, 96260.62106860452, 73803.16189206362, 97252.78544741575, 36405.70286581046, 17847.695354582625, 65653.97449542594, 93297.25329552428, 72775.74294437825, 88990.78693319393, 16206.552435499078, 63566.70941794834, 61555.90810202095, 27164.709876008543, 56908.38943868216, 75191.95067953458]} +{"id": 757, "vector": [62732.47293478673, 94876.56421587837, 35954.19280554771, 73778.16159365742, 20948.698913590426, 61515.27178245251, 62513.34237539979, 54737.67486629483, 75942.36597310993, 48657.918484613874, 66259.09037808109, 93375.38613130964, 55632.64749510234, 15354.397959702892, 12426.559691960749, 70122.38345242856, 44416.405112596876, 58993.08496536323, 69978.655198004, 69884.48713654914, 80156.35657931476, 25086.51419795833, 14705.333077739857, 76448.09818794523, 84748.85548229225, 54189.70931853698, 41730.501888340455, 7034.064782888916, 30487.847686168847, 73710.74364845335, 98240.76586982726, 66503.27526378594, 76945.50686395557, 50356.11451798931, 79400.15142717205, 16124.752867832214, 48445.64536637327, 59619.6441803864, 11196.900151267964, 40150.22857653423, 7036.81045301886, 84717.56080772752, 85717.50975427117, 76074.29236001823, 69755.31474955991, 24956.104155834026, 61988.524724864226, 9888.381660348821, 38396.11417639557, 17606.91880371419, 23460.57806811813, 47494.13726413692, 39575.329337686184, 34522.86445521555, 52368.899861640326, 68151.39016692467, 22455.75340137954, 17860.306446122133, 38606.819821664765, 36015.87978991431, 77215.52523185046, 85894.83408584424, 7317.181983424659, 9190.979356445383, 62850.8955667315, 19229.695639142297, 14919.033426540873, 65209.477728722464, 71238.42766472728, 65219.970291594735, 69512.86973502087, 19139.724770688328, 88701.38939797452, 17484.28864034319, 92732.30920185763, 6736.120160230063, 17454.398867345753, 27167.900561537273, 96154.44980579928, 29844.930023331028, 23349.01770325316, 71688.26524381213, 87490.1572420361, 32785.36649628366, 91564.1124355718, 6560.571569671924, 97717.88310168602, 35094.005009296125, 54469.197560070315, 68016.82000168937, 5042.402000015489, 33966.9854236896, 75852.0494731344, 84632.85819754581, 89931.37957598516, 22676.340989790446, 45472.823437697676, 37628.41634450099, 61439.926556802784, 11457.943540851546, 10880.249753044713, 8751.8948175896, 60999.53712946727, 17304.51709093015, 67260.84326430885, 84775.76967215506, 96421.85826458388, 85213.97861029612, 59085.14115068042, 93762.56055511381, 66957.31947490822, 86922.54831913531, 57934.22170155984, 78544.22254296845, 28817.499886185673, 60672.795936635695, 21266.841647394573, 86645.08607126234, 6953.389763723605, 4966.073030557439, 70182.62587393171, 57085.42055277679, 40083.37890365902, 27992.072976472828, 68918.37605277421, 28782.562477872918, 25687.731099187262, 1650.9311547610816]} +{"id": 1242, "vector": [57564.93639582287, 7132.988127635865, 82048.33686158143, 37870.266448613875, 27556.075113386993, 42918.321851631445, 52965.6153582371, 53606.392350576185, 6972.428240614837, 26465.41616264284, 50308.62836960595, 85456.90308287523, 20960.385161815255, 68471.39391295255, 23559.76263321705, 16711.087698023573, 27000.11957831089, 90900.58000398609, 43607.5919239513, 86076.42558459374, 34524.11683028246, 34076.86555928669, 38574.302765697976, 75510.44713003846, 95282.10519693454, 96221.14146405314, 97043.53421236019, 25570.981037452133, 45237.55196175242, 92505.62845423922, 13065.415053947792, 69754.74960223291, 45015.61847022004, 51410.24159004947, 68373.2017171515, 68231.00691958994, 37689.31257643222, 63540.25383678198, 7102.920985805949, 69628.41006739941, 68884.26609951869, 10774.677128507337, 3151.977888920732, 1552.350318325879, 40501.20171958782, 34902.37641845868, 35208.93651236032, 88200.92120507617, 48485.73823374545, 84045.1865760987, 49054.06528928548, 65242.292454508, 42671.21026239282, 9377.16064510481, 49581.64408495581, 33608.15746553598, 37978.48524312644, 95227.12862549731, 23365.682127725708, 86463.14495250066, 32812.14199814173, 30202.441914906998, 70877.88591592455, 63910.91474698953, 665.7188984136365, 91596.57853833352, 60815.52489836798, 95369.99491448619, 89898.78352024732, 49391.511961601784, 13030.507201430031, 97569.30009346339, 53258.995981581, 81192.64670504238, 95016.65880923571, 18723.425327259058, 79416.23740201745, 78391.6615960378, 44106.361325000864, 10274.690144739063, 94612.30469451164, 4501.509892266886, 12704.403598658886, 86975.80847955657, 6214.990598451886, 76671.10455790794, 70781.37429970619, 96970.243192991, 7818.171553752695, 11626.403351607716, 65616.8446830268, 9616.879884487273, 98302.20508700366, 74306.71396613447, 57584.02659514574, 44450.75875858018, 29997.58913823479, 60596.76315236202, 74389.31887812186, 44797.96558597145, 61651.576004935305, 69603.99360841425, 96876.97484160111, 32718.683768925493, 36182.239208859966, 77140.50779982883, 35378.97282316277, 48614.09069300846, 57728.693404553, 15687.107007030954, 37500.493117939885, 84669.81947219935, 25849.967356173085, 70490.84483398686, 9855.78276020549, 35098.87279961284, 29810.184181338962, 51667.396286298776, 17830.809408751724, 71955.20028112053, 62217.819254908616, 9065.09611671723, 97503.62218369068, 76462.84645164103, 28228.94523400008, 97777.07808559865, 74729.59521630687, 67364.49780054623]} +{"id": 1232, "vector": [80157.62309006295, 57323.44647969355, 91760.13735758049, 37519.86150417391, 2146.7808745384677, 85037.93008795595, 96844.7123292831, 84908.62370105603, 63960.256888214986, 77010.10825316358, 16663.55829170487, 12837.943824791553, 35383.154724787004, 99721.48218521506, 52660.20953600713, 82477.96938110125, 38839.23646542151, 46163.11157822782, 53699.97416478791, 99384.92217245049, 20563.405473815586, 2512.6619647633406, 91625.63475654469, 26916.573933847754, 1270.0361942720174, 67850.74282954836, 20355.5954057958, 98309.21841782714, 26028.99463911322, 3244.5522588864574, 91094.7617568243, 53785.845897178006, 73090.24320414239, 88379.46911753406, 66118.18645833789, 60805.847386808324, 6326.165401396877, 69649.41954633154, 14503.87694230093, 81849.62318033108, 10696.99552920964, 69098.8488052131, 62646.18529447025, 46765.623222646114, 21878.94610446779, 62258.45716053093, 98701.16670789367, 70479.37529666843, 9869.773665489123, 92548.09280257853, 48937.825040252705, 98729.15228201634, 68347.7350033656, 41936.438045342526, 99484.05480130814, 53862.02513481724, 55908.68060046544, 12947.412554831695, 56519.67584276266, 40536.177600155235, 87.71579632977921, 38602.11068787943, 88438.77929950226, 80844.1817627537, 20728.928299091454, 29705.026341843986, 94181.07395024145, 68106.50491831415, 7650.862628217492, 17592.01727979378, 20168.013053636947, 50860.70071270015, 78184.77554824906, 64717.68463495419, 33257.794941476146, 44522.87672241942, 70675.84627661922, 20247.793013357328, 26595.865112696694, 12199.245362420663, 53423.49217876034, 51133.64143887441, 51893.0360199261, 33581.968271518715, 625.2574444856052, 87878.27710918701, 31917.286290700154, 40178.35269348944, 12368.836119221205, 16625.0977167541, 42237.596665279816, 90424.48958266267, 35930.981770065584, 50800.77201950573, 76912.05441711372, 93493.1382010829, 48167.51318365655, 60502.17618816992, 60826.11282745437, 56050.120252816305, 24123.009337458945, 49231.89989476077, 11563.742840893387, 6979.852795447372, 89424.68839637208, 89800.56595433707, 92110.23502428696, 10925.661758627713, 39153.0705122054, 63034.35899719982, 31052.95874287234, 90169.62631521089, 10480.642536502704, 93243.26469674174, 78169.16297760008, 12810.155478756835, 41922.55060466394, 5709.199726220826, 37676.88628367368, 33854.655152455845, 11686.717215321718, 64084.21238889771, 76676.77761093184, 34255.9381757753, 5870.634830969401, 33243.169243640725, 57215.38926691298, 21827.014738717044]} +{"id": 845, "vector": [77243.12847436883, 52766.994107220846, 28782.23646937792, 88319.87612753903, 39995.37839578415, 90578.63938306269, 33965.65324750847, 32956.70739609475, 37673.83151825954, 52908.11815602719, 89527.72525480244, 47348.63791591737, 21852.060248425954, 50929.71414798456, 12994.546387875416, 68909.45943741014, 9245.286315190471, 83172.53432469025, 57232.23967819033, 12371.012413280469, 2789.8385831719997, 62203.098288542744, 98161.41717105886, 12205.941665069642, 51110.44750251383, 84280.32374155484, 68336.5110088976, 20520.975330256373, 88762.03605714109, 42941.709353617596, 52578.4395923718, 58245.975037465425, 52633.22641662135, 57728.24596944905, 85831.96354775463, 46406.674894896874, 58641.36772578733, 32476.84373241242, 39627.85809830658, 794.0439012953493, 44875.69916800814, 47886.25307823621, 75918.32404820548, 69464.01518084558, 64407.532979463664, 19063.054453494144, 40417.35782091836, 66613.77615629834, 51763.58253494394, 51898.58178550205, 7208.842315348651, 61678.3505517042, 22482.425758392477, 8551.166490490425, 14836.623043546104, 27306.49794363207, 83949.076654817, 487.21609105610855, 80864.6118055092, 51583.19701312275, 1813.5427942663541, 25661.124143756297, 99184.96417087785, 92388.30533134856, 58177.11255143405, 89590.9070216457, 14137.55511438376, 39234.53730946883, 32549.85329145833, 37076.250711702276, 67445.87572759893, 67671.58507158853, 56353.64629055022, 71906.54788888185, 33679.75560551991, 58908.48946682323, 86013.8614872434, 95949.0571764533, 11448.665213406894, 89630.6188262901, 26918.912637420177, 5649.769373537628, 663.6027214611539, 42192.35290698831, 56969.83134730299, 58148.94227051106, 69843.92645961292, 78710.05361260305, 90983.5551682564, 71638.04170961415, 79456.62572426084, 87370.84426523779, 83821.12135456738, 15108.525063782774, 15533.20702554516, 89006.89085553325, 14432.38186132959, 9302.852621175827, 6905.554116338863, 93926.95850347234, 6712.011462550716, 84184.2196838842, 6334.735744800301, 58144.71403823269, 150.18929695607142, 35985.68254527973, 39966.9045668238, 75214.22616718455, 28476.5820694309, 35459.67939868977, 42130.685270663715, 19608.480096057912, 67621.15800653258, 48618.64731719097, 72317.98091977433, 3350.9783308535825, 86053.12593864792, 60099.82411829414, 75527.33248570407, 91672.17773629075, 94767.79336581957, 48977.00137949434, 70152.34765067992, 89983.99436274948, 62751.72306149828, 19452.586034867458, 36798.71136776018, 56899.05078000744]} +{"id": 211, "vector": [62298.95363967931, 94473.56592898154, 29303.575205263456, 80741.71806477927, 55955.91957185362, 26712.778673536886, 78488.2089544756, 16946.71246690812, 93842.62972533121, 44028.71480836189, 92817.60654608688, 5409.777011389639, 73716.12043297692, 95603.33319450443, 49775.47834038428, 16150.033154111443, 13441.951672744035, 24075.798487103995, 17653.15046006055, 98372.67730050419, 59822.49724820401, 89243.82693336229, 60283.66124585336, 76249.9451341845, 76979.71570228746, 48468.71494065615, 37749.482012650646, 57609.68781989552, 25055.890865099373, 96368.05249753084, 28203.88128593011, 76086.476065251, 73411.44524445475, 53220.37807266023, 7260.111460592844, 69280.69325159214, 38922.8796648841, 57663.29389998477, 24890.116891955848, 8384.553372057813, 33463.69739136772, 4446.807937699627, 73253.68681484889, 85329.40585371504, 64576.78238050138, 93270.70613755463, 12152.179144329612, 92414.17044208244, 55524.72723176738, 92751.63181116214, 23586.68762220513, 80730.34030339454, 61911.36608770691, 50373.747418298175, 62375.397256932105, 99021.85769244043, 90536.97542269186, 70000.95827529351, 596.2617331978337, 79799.42464586714, 6617.609766703847, 33816.913242591276, 94661.311522776, 67620.9820064206, 92484.85206823792, 80409.2148933741, 89702.98609480435, 31947.421646301656, 55029.48898943286, 81203.58549669149, 59874.58387628015, 36903.585618095305, 9643.306787296702, 84877.63702676995, 61493.11110791401, 17486.683253251333, 26477.79201109436, 93838.16551698116, 89608.05127861303, 67885.68893868134, 35308.80542460846, 21456.485860801942, 86980.20306466141, 20038.753646035202, 59217.59808376762, 9970.638397201437, 30244.604792711394, 59642.4560895909, 11650.28366192905, 27507.2790039545, 49086.13319301735, 58441.4532215567, 53388.018913293614, 49127.474418782156, 72192.72417830341, 16224.521239258871, 76466.88086381748, 2893.826213990647, 37685.3755854824, 39495.542560600894, 1628.3366265055, 37911.26673316227, 47208.00020710001, 82985.61079201484, 62631.76079900512, 17473.04519354251, 84516.09965250091, 60115.500743414086, 3065.9277782578156, 87841.35760372903, 83010.84271633031, 66146.16772454868, 18369.63153299921, 24653.235130511675, 4936.548519555084, 19030.56697368437, 26236.31134958553, 70493.46483326306, 39781.96797199891, 71057.23470046527, 98590.43547005665, 60571.49119227445, 59630.77734826677, 135.18255755521346, 84370.95052341552, 54530.22889662803, 38754.18353753892, 65584.77391085423]} +{"id": 677, "vector": [43988.2580515059, 1281.2758288312077, 22493.284961680125, 71727.22731225814, 23614.22262348709, 97181.70577442028, 70767.89585807716, 57760.21306663381, 96159.49355150883, 36307.8140361915, 25219.013782549137, 9140.478553301313, 73212.06400417704, 18258.64355538267, 50053.49251075104, 76195.64473304496, 41247.44648673461, 69566.6220809815, 79378.40010763398, 78020.97446221753, 71656.90814826917, 41714.396954955126, 11111.203042696881, 79745.49700823994, 30563.29616943345, 37967.922173463834, 47848.661914055614, 92349.13410035505, 63056.559240018585, 14773.896804589325, 9650.297432550802, 30180.904733882817, 12045.19830029267, 39474.38469885256, 55026.14797300081, 33731.48282594605, 42304.767915292294, 41478.670005601794, 78951.86750680969, 75928.14869284669, 61019.25423143716, 89209.52416580537, 45573.44854987323, 52107.250356187695, 64851.60321211534, 13702.098071418322, 26859.130148428634, 7999.88993842905, 48505.143290770226, 55846.53318183095, 45677.65458452532, 88356.54940218285, 35550.75948857745, 88379.9682679865, 17755.75162841022, 60884.33158408926, 45752.21251546737, 7796.635306008426, 55601.287942088195, 71484.45247722455, 40388.770382686824, 34901.79039577302, 31015.25237665935, 47441.39864332639, 34878.891853148816, 93154.97322079926, 49771.45385412591, 28651.388629807272, 44039.3177247529, 13473.557850824813, 7803.000325613841, 96388.70875234669, 61928.56978615589, 2667.5361696924747, 73398.64130341721, 27709.567515070645, 35821.11873372329, 59702.32808919047, 81529.2207523442, 43682.80878545391, 87718.54452803655, 12614.957069553113, 82362.1928796643, 17912.750079190566, 11220.199919581542, 61782.05151784797, 30575.54601623007, 79970.25906672192, 96972.55275875914, 66839.60337537329, 68565.17974227894, 89687.14998068684, 31544.261867664714, 60096.906009184495, 4613.352280016914, 45916.684906976414, 59146.77079740839, 74315.22715890207, 37498.20205331431, 72141.59109477738, 79449.11640411138, 17532.03993163005, 15665.385614637229, 28064.376003705936, 88737.79085125655, 93241.53882908629, 93529.86972830485, 78034.46626335038, 5684.372983009167, 95559.8811447251, 94355.85668141463, 44646.52449300667, 34256.05376452594, 4171.924059798848, 61816.50078906439, 12249.86808162749, 87560.30242105437, 41634.807798324044, 27314.620594576256, 41078.435894666385, 64298.7824942985, 44643.60999746616, 19270.108105288287, 42466.18324828199, 99951.58137109007, 83197.76736160138, 82228.28326040195, 71647.18819351673]} +{"id": 1255, "vector": [86123.14688294812, 83786.9843013692, 91196.97094788187, 94623.96066800776, 1385.6457533254595, 2053.7576162896867, 41470.90551279609, 73554.35028795565, 66931.52961382669, 15231.169304961279, 47030.467161578985, 70555.26404465658, 45462.80760970244, 62150.50228203383, 63998.0200832845, 93819.50676550486, 85697.77581396216, 43132.861535764234, 79562.19315520512, 46244.61322183493, 41819.596452709964, 35463.22903886051, 56403.74398667805, 55132.7715157174, 6971.918688909462, 56006.81849885997, 12235.02845416914, 1059.1487952563705, 55773.75941400017, 25258.489413388117, 52755.14411657011, 2505.4998646309223, 5736.873923146623, 82827.56813684964, 15561.89496164887, 71060.27336971226, 18208.334399348347, 94967.00022121008, 79714.82465975019, 68402.53405183183, 53784.730059275906, 97643.32939239853, 35248.05508871452, 53568.69946617051, 13570.790726113159, 39618.25938698999, 93726.73782310609, 37695.91396510124, 37291.73267394107, 1677.1359603074854, 55544.94917910019, 41682.806808269735, 30147.908168637794, 46748.47359233738, 71080.67875968169, 98705.15097766664, 66912.73575821397, 50393.18650228435, 87439.6383430821, 79875.1933261452, 95328.8740526554, 41453.372188849506, 20702.22955197023, 53219.38080947323, 97415.33226496591, 50672.21194887973, 90014.67839573228, 85236.76841582436, 77747.84543657691, 87246.43935977663, 80077.26823891432, 88889.53746802338, 52491.197377195764, 24432.879255371976, 40513.90756168282, 53742.25864184203, 18977.38069524081, 95864.3983430058, 23136.375755166995, 23882.75940477389, 13769.348965129979, 28481.936719791443, 78671.2194162191, 53242.754061457745, 83277.04231037483, 86659.34773415573, 1930.1338526926393, 20767.938698568432, 8723.953526377525, 93719.98704602031, 4104.632500418148, 4102.887896099472, 47074.01783938203, 32340.323808979843, 58528.3624374963, 11351.348630024693, 72186.38379340961, 39776.950492484284, 54970.37963436222, 8007.635590889262, 40395.823977563916, 5150.342747417769, 87242.96973179089, 71496.1965689255, 85596.37416716698, 75858.02755056528, 76606.11605412944, 77988.55486226744, 48395.91245591837, 17888.640201389193, 9390.922724758055, 47131.23454678245, 93047.03971086862, 49226.54852866687, 39792.930819113535, 38336.301013857854, 85988.47775394435, 75430.91471000662, 74325.51175180169, 68908.67211603814, 58541.12861851464, 20745.805837314514, 72408.03925159498, 71602.46349451733, 7827.618482766341, 79489.35738048854, 56217.61748208164, 63079.89567501021]} +{"id": 1336, "vector": [82344.8804238614, 17381.451335021804, 90235.16241035049, 74340.07546894516, 80414.50875013537, 80432.59542769546, 58669.99165705736, 77044.29572966025, 4520.511330105659, 69530.42969402943, 78819.18975686232, 72247.83804228308, 66003.15522625447, 3976.6866673396794, 19733.750891480828, 37072.81940727152, 22848.580575654443, 18978.79761457395, 75728.72354625809, 33051.588706496426, 35549.04754878845, 87879.53354440593, 78516.33175191816, 63360.03726017811, 67551.05234499832, 53019.29724706683, 30582.118418542104, 91857.01722318021, 17254.121856538695, 77379.96951407225, 53294.82160121259, 11416.969294788481, 87975.10922431345, 94261.05886706628, 95651.70694383007, 25411.57577189366, 25619.397883540507, 82913.85090793663, 95083.66388684667, 92007.63959110757, 85784.03368607449, 54308.81648308751, 89494.23014316977, 27421.937141760067, 9617.44261108074, 96787.65615578779, 35920.65551139107, 39102.29415159659, 64230.911726834696, 87661.81265912224, 50857.63061950337, 72450.0956569864, 4805.085143695243, 42672.79145527269, 1956.2197736710752, 40384.71363508286, 93195.5867737132, 64150.19167538559, 33235.169681857624, 92740.62274402066, 52714.51061583031, 76686.19920712325, 91998.97423582198, 82686.31062960514, 67674.22696394922, 61682.85326359535, 12493.475354198492, 73916.76305027356, 9074.31862583924, 73872.20360941337, 42814.80001430463, 278.78435156772906, 16402.076030615044, 24524.849988840902, 99928.41593091786, 21790.24946775736, 23662.360999808807, 15621.592463822553, 86342.28395628586, 82277.20550527769, 51624.55242531792, 57434.514771791255, 22895.494467718967, 76781.67029370595, 96954.89154742418, 39247.35585884, 78313.73340878593, 87309.83346358643, 51071.664081577524, 324.95915977037714, 50275.60764312409, 23435.10847892143, 59886.721899430115, 26103.02339807201, 56979.197924307446, 23048.126700382465, 46797.07470927186, 78089.27972675745, 33684.1749465446, 8726.154489715022, 75815.20383776087, 98732.32759503975, 57162.82654170278, 93258.95114885192, 66456.27083253088, 75083.25049868449, 99707.64728784388, 75640.10304901731, 37452.310327160085, 63026.62100300236, 27840.473171854406, 45668.94762196899, 69741.41293550082, 7175.730596501883, 40283.1178914247, 38977.965022230856, 22785.065581503906, 76217.69200032584, 55641.67357795931, 16256.435253576306, 8780.413844880852, 21810.951175927683, 26822.13114065468, 61902.62241271145, 81672.62790157124, 95691.20649691513, 73332.36320851496, 31677.438501088352]} +{"id": 1347, "vector": [54322.29414207401, 4194.283256825426, 65442.99720711654, 52716.83058845951, 69747.90742366425, 45412.25455965867, 52447.918232128555, 33699.693671695575, 18578.090047682992, 35161.260352614176, 7598.634171771934, 21278.975953498448, 50180.89844189314, 85343.16558319094, 43333.41493984502, 23744.40155520483, 41341.78648498836, 66634.66752932407, 2920.788651660744, 21583.66644343742, 89035.14365610316, 96122.02073554107, 76087.69408233691, 86353.5838781327, 34925.04878006542, 72433.09653539142, 63688.352167974925, 84938.72196646151, 57659.03533445741, 70793.27672538992, 81062.03362881896, 39795.51797526385, 6455.5974343876605, 76355.49359548544, 21500.192982443066, 80367.67331065398, 18275.745868028138, 99685.05859400125, 40942.94687211426, 36796.276149610676, 42802.31752823369, 57959.5999413257, 11266.913534315825, 79470.5034828703, 77969.98867013426, 15092.927113787702, 89442.0180124382, 41424.04630728146, 29172.536710160624, 37074.12439505542, 24927.126930710376, 43419.77834359465, 59064.17804674418, 59903.179282155914, 29730.24880420254, 23207.410259912274, 34219.78792500738, 23162.33309691027, 52350.627055763485, 2877.998162968576, 73223.29913746138, 43315.002778365386, 75539.72841768808, 91661.06053622942, 46194.02547978847, 44869.60353072928, 82534.43559091144, 18379.261974117144, 42828.71922205719, 75193.16682746219, 83524.12375975003, 92490.81007200171, 57535.56928884417, 27018.41645573151, 1044.8730652820038, 30241.025020869318, 68392.09511339321, 99590.12984966919, 39869.10586096034, 10687.888763983377, 90623.3005480993, 39288.03455005595, 79961.37524657765, 19991.736010230434, 9366.111102531528, 80274.89948017092, 90442.70179080301, 82447.6624843916, 53135.764373378035, 60093.43418815985, 14514.248350991533, 27485.148539083315, 3722.0984954764335, 50038.46977067645, 91864.04908771621, 91963.74427515893, 7993.582891303952, 26174.123424807007, 52626.10617916099, 10378.374840764249, 75247.48238582745, 79483.29089110662, 7531.931970800088, 85216.86968223148, 19060.175844139314, 33860.17582514359, 79425.49757363542, 22272.085370513018, 61905.55462989705, 66654.55084628772, 37153.23559076603, 59553.92287458927, 96147.68202592847, 67515.26055442113, 32074.97456745072, 78153.57381310638, 68563.8479767861, 54614.009533140925, 21458.042872773352, 86033.94198013649, 71647.03077033642, 37849.750120569704, 7209.17457054846, 86608.41242966296, 6142.416866188661, 57344.605242694604, 58959.05682707897, 57824.00221912059]} +{"id": 2032, "vector": [99699.2622400434, 39360.019510418984, 21270.43341201735, 35715.34316252455, 49572.64246786719, 63019.43353991866, 63728.5043178751, 46525.84790484596, 69084.85215421185, 95538.99113496297, 79408.17134989197, 55262.9084108361, 42466.47897496105, 37777.6753833892, 34583.85662403159, 12972.710196925975, 11893.961532190988, 38987.78752373049, 43355.13017418755, 90505.61012976465, 69018.26308820554, 344.6425489021632, 62051.51741851769, 11821.610008530648, 15046.426490580567, 54853.22472704235, 62752.79392484826, 84527.53701868301, 83959.67840839131, 65212.940714884135, 28655.061557758043, 13718.775039061515, 53356.14168252773, 70823.36716538458, 43956.254784143435, 39128.967640401104, 54998.20515517538, 12149.353089558423, 47871.780685083744, 8594.029812432535, 21662.75141513728, 66194.69125856945, 82465.52450113522, 43695.31667396653, 28337.197505023094, 5043.6601767801185, 6008.478565811571, 94139.10512106915, 82455.11290211423, 33715.53685201896, 60090.55494181268, 16544.11596797071, 52514.180273309976, 56894.59927262374, 87009.28598869589, 26961.99026604945, 89645.94528897191, 7483.856615244799, 70610.83198724285, 40522.64668916782, 78353.78481395959, 70872.49337952964, 87068.49139560398, 21526.84313309201, 58894.17026252012, 99172.14345207483, 18731.187625523595, 71388.17415205936, 34299.71842130706, 81483.05385450924, 11443.35575799711, 62592.11416012195, 37686.708068425665, 50694.11188076738, 25358.601796938907, 34307.327874735085, 55060.23782539156, 60185.2054524503, 23700.26412195191, 63458.6501281888, 25041.61917658232, 11680.262371850924, 70826.43676745011, 74883.56331606356, 80884.9178986432, 36212.352963957404, 98571.7294122413, 24780.08777407321, 23622.364933335815, 10477.43339187569, 889.3617842940627, 60472.74064869518, 11858.706317047574, 60920.99440653745, 62264.12687490582, 23385.17041447611, 59404.02184032546, 92507.32608418874, 93898.64506112377, 47837.47598889436, 1703.5374422869843, 57464.0125642281, 48275.28471757399, 81879.79092247118, 544.7359652468454, 357.6069856162434, 43114.519628224516, 83429.96438067047, 15888.138348018576, 62336.75198516959, 87238.90660435846, 35820.71142956606, 83207.36359836989, 84754.68141682279, 63885.91477326957, 1313.9928432602476, 60367.428394523784, 32525.515302873133, 21640.21029536789, 63180.961597416965, 39532.51679804168, 28500.926158259932, 20812.632203741632, 59007.32585158857, 46744.54270957098, 8072.646987246224, 58760.24113542195, 99509.86164703983]} +{"id": 287, "vector": [34041.14340536662, 81221.95787209395, 24640.38120488101, 68603.54753992317, 88297.1454349754, 19980.546100058815, 80995.64224065184, 7205.37255219802, 79596.63959713418, 49712.8794565051, 82008.7443168842, 42940.573355858556, 38028.50198612426, 1194.6300988491143, 40339.50122133405, 99432.38508635732, 33978.93405073873, 1849.6609691995402, 18319.527327527176, 36117.818833285375, 4874.728030429554, 36175.582863566415, 90560.0233464311, 21141.158989992946, 94527.2686204894, 8814.25356046549, 47917.495795981835, 39456.98577237337, 50013.251503282976, 25553.706181178226, 74805.14983095294, 9961.165615484824, 2862.2006774885353, 77712.14823227383, 80316.09841883833, 37702.25323800891, 61845.2544694811, 5818.808433991152, 34770.352288630645, 15094.671957877603, 28003.862329377647, 94263.0028143577, 69209.06176839802, 83593.04013554327, 20910.821453981287, 38380.61128389792, 62566.66506746188, 98416.10583340922, 86203.29352591645, 34326.64796627341, 98570.86386518557, 71781.94168689862, 96166.85562868541, 56179.846066856066, 65825.27156890219, 70698.81545237167, 91802.51386453978, 74679.06896800482, 57985.78373147494, 58478.89409763273, 88891.1187508201, 22164.419763636277, 49392.732415489685, 26092.61048209195, 92212.12556279672, 57079.89203332109, 10980.06034082205, 49384.21815647592, 11271.239372557784, 14587.1695787117, 50503.53329993846, 85515.30659791591, 13944.029413620063, 62024.15006087972, 96205.89473295558, 39274.55601106003, 16349.959754266807, 23759.35429245295, 61994.00756752865, 99262.21551121888, 85386.62419978341, 60236.20626015961, 625.7371418070479, 91153.67377934163, 10400.506179896396, 13290.008185178414, 82180.69580026317, 79913.45019481058, 94444.85299246367, 41273.1712305008, 29539.840727476963, 72895.07909255417, 25192.90547324694, 26074.241070258653, 67601.87428045561, 83564.83872677953, 10296.56112156785, 52697.283208686684, 54974.79749340088, 77481.57063958536, 3630.9187666685893, 7519.51668987122, 64959.838027172176, 88440.28101374255, 49984.205096243495, 32603.219981044927, 19304.299894697073, 36602.186190672735, 85219.50430984287, 80587.33459372458, 11692.572657821798, 78936.89023981294, 11091.368984932815, 32965.86940347952, 51388.20026047136, 46015.697738698116, 73129.2769901356, 82302.88696813381, 99704.21697403034, 15934.642625431017, 62548.59461567026, 90979.27875124804, 75119.50497470486, 59965.99154251354, 12473.716075940489, 28955.774373801403, 35721.47898154479, 28086.609415486284]} +{"id": 699, "vector": [59280.86834594566, 36855.43390113331, 82440.0583599553, 55765.17592176615, 39709.442457939884, 56762.2186324603, 75881.20625669314, 94314.29993513433, 66872.27190164257, 77128.54107166453, 93492.12708911282, 27032.5731598553, 34499.322013255594, 38126.841997530144, 80797.20052842631, 31812.227665050763, 61193.81522805931, 35136.48260163409, 80222.74850590788, 93300.2782931871, 73941.5875833422, 7541.186724362769, 69709.63342305232, 78309.15899387364, 61788.99267779047, 63930.44439447491, 91143.8626450597, 56790.0154909334, 24318.87362734133, 32544.763358158347, 72198.2319447401, 55694.27076625156, 98272.19305983873, 19897.7949111672, 89070.88316348306, 74200.83762913052, 83948.42487612167, 51052.8830967383, 82308.47550632452, 21656.158603354936, 77436.17077937511, 634.481661544195, 20976.920127457233, 84337.64768026449, 13703.533783979816, 235.40178338400386, 71882.13934907225, 46482.37947653075, 59594.79313232189, 97595.63412308587, 65268.93256495535, 37531.97821435182, 5878.33552624304, 34260.07847037315, 28664.015051003244, 90813.97093225468, 28193.123388781405, 9261.50614577822, 15625.156775117299, 86053.18415348344, 62834.928328503345, 44470.04333131065, 91634.46597994363, 15360.42512202387, 251.70804475440622, 20599.613769369575, 59052.380271546135, 56198.22114329204, 92803.2876769894, 38285.33953597237, 516.9569297574506, 52186.67025467838, 215.35538693060462, 18247.09991111736, 1794.7286698369758, 78583.9385032695, 42666.45466448225, 35000.25773942737, 61536.99723458318, 35141.3161021022, 64695.402845532975, 7816.441943699104, 56098.493020399874, 47728.85873138374, 83308.08796121503, 14036.610627345746, 67808.36488645064, 66083.73807540319, 47290.24137096223, 11570.130056808548, 55381.00567724548, 54721.084596673485, 17935.644646335426, 97634.07864013755, 9486.092891496146, 39624.87737417121, 7272.5709008559015, 82358.46142182623, 30522.009510952863, 88334.43410513141, 46373.243703471955, 33817.57757184968, 57246.7577244854, 37811.85625111725, 96037.51486102285, 47950.34818476518, 70086.2881310034, 68839.05657568086, 14829.744366218289, 3149.1945829875576, 70372.20673018243, 91752.39246881932, 31971.309478186882, 70093.1320407189, 76778.39606250511, 28438.465616747333, 60308.90034985651, 69277.86988236605, 71655.15886787383, 59555.36814476856, 18474.28564999538, 36982.700512335774, 25245.08548959702, 32034.909281634504, 57695.76239365352, 37696.12686786745, 76876.4304588674, 18617.367231235494]} +{"id": 2022, "vector": [67737.0637919375, 75818.18403639467, 39536.46832355405, 40226.489473366935, 99347.762672433, 91304.97490735022, 54953.00608424638, 62377.7884416831, 58095.416391764855, 62878.6381980691, 31103.809993195664, 10095.414251697131, 26495.73291296592, 17180.086002070173, 61433.50407101267, 4394.646526285484, 20008.064494712442, 50825.346511340584, 80117.8508283384, 34014.40560774213, 9608.49531272845, 15213.457317949842, 18575.569015197703, 57291.260691185096, 99830.55034914924, 54913.3960737433, 88313.20034460361, 23147.96027651552, 57083.661832759295, 52767.005817899524, 64665.432298743355, 14248.035621579269, 81219.20954979875, 15594.189638460564, 65173.146176449794, 16442.931452272693, 85436.18725255413, 72147.1191355343, 18531.58271214831, 70458.91754050486, 85874.46264944905, 9765.347767573185, 93337.03701966527, 88076.4368116559, 35357.77933521258, 90260.1612758099, 98081.34158452108, 55765.28051529682, 19346.948881501856, 64559.22404040257, 94221.72113015884, 83409.67077615003, 52475.032704420446, 7383.132064335951, 83127.71486681172, 34277.82348424907, 71133.73192527419, 45686.12999513545, 32945.96568120518, 78777.51371279357, 7080.444490845195, 77414.42230342799, 53693.42997769258, 84065.85801648111, 47550.34951670154, 22885.197744114415, 61225.713277010844, 35799.01739973267, 9539.05212607542, 10676.733033305052, 92787.56819219657, 72468.06426069383, 33965.33916702874, 41452.58066962758, 95004.64350024764, 35155.34904193043, 91081.35013874443, 8407.077425539144, 53964.77649457901, 42423.12063189872, 61928.66196279895, 87931.80756144745, 69350.39925092748, 44378.45977406319, 26647.683071388496, 15751.899590932084, 76011.64144171296, 34108.06552435503, 52287.27603661969, 39536.787934515596, 86389.92831370125, 17239.037486886245, 43953.88529501861, 20953.86673294041, 49399.3502104807, 56596.844564513645, 74664.19822709082, 94326.63813236458, 20700.054273894995, 85957.40401924054, 54963.60753626542, 10200.28618708968, 53500.98089488757, 25398.933213312215, 135.0631584171369, 35234.3677517227, 11288.229336443022, 13480.28091562844, 46344.15679194006, 90983.60501684865, 4748.679390505128, 21680.96811606961, 18370.396543101742, 69812.21000571945, 43137.07673353284, 5467.512396984475, 34674.1517158675, 8357.117473592234, 45655.75084289734, 87249.46327633852, 35340.330605197836, 80623.77809343311, 81072.10808483047, 39009.54500906876, 88684.40874923026, 30978.547459095353, 56972.3286246032, 61366.7840022719]} +{"id": 848, "vector": [83810.4988104661, 13250.285672594608, 53290.21152840261, 66327.24126505549, 58420.79921153183, 60094.856547546005, 97634.4579525315, 89508.65513181606, 97928.17780326496, 84404.90112868318, 9817.523793221428, 82895.31476077139, 42895.62337886181, 98300.02452645762, 50969.40028619302, 77036.12468853542, 4998.4586710764115, 58352.15010808542, 94563.19434270385, 38913.312525074296, 45448.04988372948, 20912.34188923179, 70646.93889715233, 78589.5785021261, 98264.40305243061, 41908.02608404486, 79720.58060752955, 27426.78120187989, 60280.49467358423, 35137.05009616693, 88835.0091604385, 16950.052133710535, 81773.71161374137, 87132.69428738324, 92572.08641072015, 7781.529370167861, 88129.98573761567, 67271.94740772186, 31220.070457908587, 54565.990252577554, 31113.588871970354, 57987.400624579925, 20837.328009581524, 28712.269846638304, 86337.53964422515, 36336.92873228379, 60524.33937041352, 76659.7465827282, 38955.2725823559, 68826.81633056648, 31344.41647624763, 82280.41613256471, 73290.77558000326, 38985.07809864096, 91936.51220237448, 34050.44357235078, 15670.640251089262, 95178.31850926552, 79985.20813305391, 69028.88503752508, 30861.35559024785, 98384.62173491616, 90763.86397116631, 83125.36339115584, 65883.37598276617, 99761.10592402049, 72596.7969367867, 86639.96704882872, 36818.58074427989, 89700.220508073, 30209.15540368103, 82459.68395941265, 16526.143689542394, 90230.38726215488, 13198.20196123659, 19041.151449091787, 15023.00855994011, 60127.85118811636, 5591.452585952506, 65229.424768653276, 10173.896434471064, 22104.68765899528, 28109.099551573945, 52909.989876603715, 32412.492225216338, 51970.89950275595, 84435.25785068469, 15136.99919741558, 6024.856428208803, 79921.30633809179, 56298.73146382032, 3434.5173520638705, 14796.462812892187, 54002.057493269094, 12842.113935676358, 32540.58427226575, 12937.889708631545, 8612.807917249687, 71768.65828987349, 26090.248483336876, 12674.581926096884, 11271.989171811947, 26295.11643344219, 96993.96652719546, 9119.942089980914, 57105.06816190939, 26741.337424080146, 68878.53849599081, 42052.26549261397, 56578.068349608526, 23365.10013948363, 14748.171071342731, 38029.25047626678, 60345.693261544955, 61628.58843756739, 8891.704435990589, 50509.192191665374, 50231.63182085372, 82090.12927842986, 39989.56208006775, 93288.6348036018, 20012.79150694766, 42866.67265595412, 23052.900065016012, 71659.32487330366, 5518.166158099325, 88383.76714468078, 67121.47977325474]} +{"id": 236, "vector": [88239.54511999147, 37357.85676661851, 24597.437484913797, 71150.48327231136, 74927.70959143151, 90724.65366932807, 41199.495754719406, 51838.07628913384, 42071.9912561274, 60800.63929331167, 83526.68046180351, 58751.83362858266, 62325.089475710614, 93743.53268317295, 48364.37955990837, 87988.26233650577, 20083.527436385295, 94033.902230985, 11062.015485776155, 33703.235468840576, 46647.258916582345, 58895.3983861331, 60190.631043110174, 23238.046653267964, 20293.162694341827, 70614.11845328347, 32505.98874607302, 98551.37979516586, 65172.82363363583, 72566.86744644301, 72978.87894985675, 52548.90411986916, 8649.198633843158, 699.3334368749049, 52269.62336001597, 70089.35069024161, 57232.21459416038, 47392.74561324363, 50676.6367135736, 23483.829645980513, 6604.297634891909, 10.796015277769744, 97401.8609127974, 60315.86397240115, 13015.263411323753, 80093.68307352773, 96770.4191697451, 65561.288669312, 79169.15742967486, 5656.916930979505, 68577.15425282778, 1201.2529464259903, 58579.7250359953, 67949.78523370413, 74394.48048298123, 53357.30195832971, 71246.96265916705, 27141.660749721297, 77636.06926121318, 50919.20781803094, 11968.152286842203, 37184.02731830054, 31351.945336631405, 79325.4849334386, 98818.61842553552, 89047.33106375397, 68882.33772738358, 4808.319609486478, 16015.671515587737, 9510.462816948095, 60291.111936734975, 54703.86798347921, 44699.47880601738, 36468.88161636167, 3593.423886973979, 41077.31904976294, 48767.04141268716, 89295.01095088787, 65127.51883345528, 24213.165737201758, 44003.0057997119, 6236.6720997779175, 8965.222620735913, 58588.5011155052, 79347.27126871952, 3797.462628224857, 4191.696415172852, 38611.135392813376, 67576.2654969583, 83978.8965534554, 17854.06141813618, 79546.44419581606, 39352.68294228802, 76167.32179796854, 16952.278060927107, 39028.07637759978, 70748.96395089375, 1483.58408334619, 31446.02893222831, 82962.83352003108, 76522.67883971272, 90046.33036595582, 42326.90815615675, 39879.023767417144, 82041.67836014404, 2924.616755585585, 33496.62088908798, 5135.317100407199, 17612.661024607467, 39728.26204226919, 45374.01696188666, 73144.34700984975, 96614.07945215446, 80806.83434648029, 92473.46687830778, 18809.939935081944, 14306.043745163732, 31914.97623179079, 3539.126179460361, 16077.070329367381, 517.1596395347477, 38161.91230540274, 23746.84555558174, 91782.08029877307, 43935.703844917836, 64640.54301227747, 18160.107030984218, 63582.92083674052]} +{"id": 249, "vector": [22064.126689359055, 81216.57159299523, 9864.37110027154, 22965.59639928546, 21711.997159924966, 14238.570528263861, 97368.6960007601, 46911.19900559849, 53802.36541973882, 68206.8305233328, 43718.35372572255, 55966.23880512604, 51054.69062174162, 18245.9707810552, 48149.89546903323, 90025.45703060401, 52537.75789230538, 53939.940199049175, 88394.94936943307, 73292.53558248632, 71571.85672545964, 20048.33544122542, 20485.133779060983, 33314.21263537382, 69619.57344235359, 5875.961473424729, 8260.968193252338, 64967.73498576952, 5724.263992817214, 46960.16999128142, 642.9389286266795, 41459.33006019845, 94894.10922487626, 74272.61941495826, 67547.26483103714, 33955.48074837202, 14090.74688792329, 2193.347692736669, 42851.47339780606, 38577.343898468185, 8195.724944251271, 50936.65204784457, 60899.711017162204, 17765.379204125642, 47147.331484175695, 84928.80888449268, 21281.22851624594, 50006.26256446662, 93894.37598918345, 56643.87505006706, 98226.93038459968, 23797.561978491387, 21776.063945944778, 57079.44424877478, 36007.1486732213, 38931.40571648048, 2119.170926322711, 2896.594266179453, 98298.07432700018, 37762.41663717271, 77768.83830168586, 56437.90878275826, 3397.0578385362883, 72008.25695326486, 14900.13510668403, 47060.317426777845, 19274.619126964077, 66720.74836568125, 92788.6520021845, 73105.46066926859, 53268.70365597997, 40717.201809203805, 92891.56933744623, 1456.4972550833888, 21056.631396921555, 59255.86141290099, 95394.75768108758, 31241.792249553513, 3075.0964431770276, 1599.0161348649724, 54008.414944142, 53173.72087129681, 73522.12836323207, 54026.812076927235, 27549.192381808807, 695.2040060907439, 90135.00584235268, 59310.24653321865, 55636.43082336698, 40691.99171513834, 14535.816730326213, 30901.841596483515, 82897.31395158137, 8949.58593977292, 94367.01322354426, 31873.18680035669, 64974.53946769441, 76977.26192399314, 99519.38428587938, 94752.7586482538, 49271.07171558572, 15030.9513971444, 99470.23895457404, 40824.97165903467, 54536.10449936795, 57727.36070275872, 80720.58678604742, 9505.423966484594, 19872.211026906494, 52909.98061420751, 51474.6595830267, 68842.09961071823, 9986.16855485548, 74392.03653297666, 77627.44936209121, 29801.199293244274, 90635.56727127044, 38090.772248154215, 8723.065588719748, 19415.916897183895, 93298.01367445433, 57072.49617569306, 78645.21754797052, 70048.81269710422, 74086.44337933922, 53972.134091281885, 21878.22001255151, 86239.4055591838]} +{"id": 256, "vector": [91266.62797384374, 49743.6371212859, 94476.91522723189, 63354.78323366866, 79272.79653550823, 36096.151950721876, 21556.475716200675, 64358.764321126735, 12372.662815980451, 35141.43783211794, 9888.31221913714, 89933.32131179234, 45233.84101837739, 67682.61528228965, 59982.237325857444, 83301.49562825021, 57639.22618643787, 13147.760321301204, 4279.463141438977, 65823.05398834567, 95431.75538014766, 72321.72224879859, 22946.909846887065, 46464.34050273346, 56550.4485185911, 61924.67571015179, 84411.28586007161, 57996.49508185938, 96069.56961487407, 26907.793388385326, 96904.7914811838, 87215.91834852185, 14734.844209454977, 5677.372714431317, 82024.0210580493, 70230.23193852337, 77478.76963101847, 34129.39343185002, 71098.64668627127, 14480.427060735523, 6402.52416532765, 43845.78041992463, 99331.84027876891, 63502.81566981738, 86983.31838179132, 1542.2834229312455, 11848.540527783536, 10231.96976593107, 92616.12376742304, 22669.085334164607, 66630.47504212573, 38947.94560621513, 83385.54252779395, 87787.61507255917, 33677.55809941159, 17497.97405536534, 27723.239642163855, 19436.378608391424, 70638.84798925051, 14394.129628602026, 41623.17834158112, 30005.568199491907, 58591.2518969541, 18095.436764492755, 96874.12541757232, 35939.725400520576, 22571.349750587455, 20496.064198782093, 2083.3840114194313, 44267.81331357391, 39087.48574411469, 63650.771637036996, 30564.96290641334, 56313.676229598386, 35156.25848282768, 42242.68036294284, 73563.94334098516, 18158.23482870803, 43862.22584133509, 26876.82693487493, 57845.94632364502, 87763.77844352993, 7172.256867871763, 5498.351270896018, 49588.55481806225, 6414.510895265491, 10939.581780297325, 65236.641077813074, 60873.50140165726, 65911.39765591534, 79764.83263641197, 46340.36429676981, 22573.32524015552, 54678.733310630036, 1648.7987952892413, 78127.24412120639, 18105.218631486343, 81092.23827362667, 87395.83611865276, 97687.25306871659, 27109.54752410508, 61568.674615364595, 75125.62853038107, 99456.94704953792, 15837.561792866418, 87841.34058840192, 11568.688218601565, 24399.67617277128, 330.00321793278385, 56833.748838953325, 41646.789057489965, 16156.012783725604, 34611.37830851021, 16005.658222998964, 2936.233593989135, 32185.591724037844, 49906.9958954906, 94973.38837549176, 87622.98850085516, 12773.341851235044, 80257.96417868586, 89739.71774707886, 77402.01849181495, 18115.247239369448, 82280.31121722313, 55223.57343961334, 7021.585880187142, 53551.97223844791]} +{"id": 592, "vector": [1326.7365535787335, 67702.46974615306, 43166.977793596176, 34852.12351818753, 38349.5176564377, 58362.899584285056, 23959.043196393435, 44337.82416113902, 81591.46350533985, 16477.999096832264, 1115.5407573532173, 96727.50566424867, 83073.23044903223, 48658.43590498854, 40776.081899262615, 49405.07242662867, 70283.25489849696, 44359.06523736443, 64393.35055589195, 38173.156903245734, 79744.59034014212, 81676.77168583582, 17480.25667100529, 51558.668717991764, 18360.26199542864, 743.403205363724, 61789.46782551421, 24446.52088070749, 30615.847047207622, 33615.844005197556, 51720.530928918815, 93150.06356753156, 16546.897354488523, 69430.86303562636, 14375.22942817423, 3397.657830339962, 34419.036370347996, 86635.59069831316, 87081.20970744816, 52935.639823023586, 46009.80354720472, 87510.87387846877, 23612.31245891462, 41658.51976457832, 86447.131744824, 35923.06628617742, 70973.51876267858, 74751.36234719449, 79658.50647222811, 53877.01287551113, 32893.352245436625, 50415.16916012414, 26974.490631598535, 43839.00566334129, 97099.67385738483, 87568.54996485943, 1115.9775178041343, 93195.62040199636, 19659.64924078716, 34893.95238954265, 77028.50803596665, 37311.46762668212, 24227.266003782723, 74316.74436441234, 42695.0453074021, 94972.25338110335, 81264.01166380552, 97821.08716827397, 12132.85112000484, 42001.76879079441, 58640.25846599965, 83385.17035959971, 86699.64339389424, 14155.682424690753, 58030.07828272311, 57399.68463062267, 83003.8124192007, 99544.98315040719, 33907.925968864816, 44779.61070486202, 61115.882700486465, 38560.85939749041, 22127.868584413958, 90058.99033824504, 72067.56376728875, 21033.94862787603, 50729.630335600465, 5536.970809067421, 63002.889193928844, 76885.45417079012, 75840.14637637236, 8395.972626432236, 45171.967819175894, 99303.27657016767, 43946.282882639185, 85829.95782545935, 58808.352764626296, 50196.35772406035, 68428.60005478177, 55247.81937023094, 54382.13460023204, 95081.76064513726, 24625.689227367555, 17577.554257196214, 37628.24315629603, 12242.956817919205, 54951.305794766224, 27606.360423923983, 77854.84813952685, 45622.98651601309, 29494.126994362414, 56236.795325868625, 13293.41880544943, 43707.763714030545, 69216.9081505827, 22884.955631599823, 56948.19243347676, 90377.88489974044, 75212.65308155047, 78816.46231665494, 92977.02135713708, 44634.97732156112, 20739.864963967604, 93875.73012290505, 52414.80210941203, 75354.85127159876, 76407.70353159685, 4406.987264695527]} +{"id": 242, "vector": [58855.50708195508, 41518.10794139307, 61570.08961379098, 88981.968142928, 31498.697816742606, 18261.31863802134, 28312.95348212505, 52067.20236339438, 84746.94832208032, 65091.014506713866, 8390.624215444364, 15598.14062435495, 55768.450794822464, 86613.03349466222, 99829.37927923012, 50237.6064253557, 79949.45689074145, 77666.03471050176, 73478.40417795113, 28273.59267896461, 89818.3163480463, 41738.94860341015, 89773.60132866938, 74777.5521512717, 75092.38860390178, 16396.832953615893, 86627.77910518892, 9886.907114788024, 90899.69232307971, 56585.63741589032, 51015.51271908783, 38481.26683441995, 99722.44577848286, 4798.60775242954, 96702.50918712949, 58502.76299769276, 63435.082451036214, 24491.865011066882, 75918.82704973707, 65756.68015690595, 78991.17490223178, 70788.45621914441, 89956.35940924319, 97749.52210792103, 78067.35489890307, 83838.20237936421, 9321.909327270128, 2419.217211407798, 77042.04483091107, 54061.40964933918, 74556.12175990104, 51797.72364261041, 8342.829991794453, 39542.88681720115, 57913.80909961986, 30373.25677104049, 59875.78347078022, 5800.879881400034, 65623.47581412668, 92444.74881883754, 26342.01517138709, 64853.63126280983, 34583.55367848218, 41952.268475222896, 57501.05620578622, 72076.16865729763, 56078.702906547005, 24490.43148427552, 7869.722469396212, 20922.169762825328, 13768.289084895758, 85036.3613473907, 7149.556702676052, 20985.924636792784, 7978.582355361308, 49979.48819992058, 10452.625592378017, 34916.88634866321, 88248.61174987754, 2562.5304483912646, 61388.104687801395, 25758.22479883736, 29811.853631835584, 76110.92697868163, 88926.87492457818, 39509.57079376664, 37548.4540688034, 2421.4859150841207, 8561.957956143995, 19662.463175054934, 96784.69006815023, 62538.15513069218, 17484.51367212549, 22859.059933030367, 34996.98582893555, 98019.11367719369, 74061.24847482692, 77076.69297324319, 46850.46412106685, 86764.79467051738, 73179.78581679091, 97431.44464893229, 44572.248540367924, 96416.31557361968, 80333.43353088858, 48059.190383799265, 92988.20225137161, 994.664639875531, 40230.18751892145, 34598.27036804531, 77463.51089121598, 18178.991251945143, 45043.91550384149, 68815.46477513759, 8135.308979370293, 65680.61216126716, 7059.375223715269, 48326.190485154955, 20158.441523603142, 60732.48935474978, 99047.3561209194, 54331.17788670387, 39989.389499390585, 3333.0591502746197, 50562.62014886938, 58849.341008958756, 13913.323081885343, 49507.59497468572]} +{"id": 1196, "vector": [45340.198397720975, 55321.38857965928, 32384.93812937233, 51004.291343829835, 37920.22822791332, 849.3235253046016, 30523.136102542492, 98262.3631439315, 74198.736265321, 26462.12730814156, 15830.868137028387, 526.5673177162089, 80243.99364320107, 51796.63168959976, 7793.818729058377, 82751.69699027881, 43741.288792871936, 60362.4364038887, 12674.13820846729, 77918.77143284408, 54775.62443511388, 70343.95518685646, 93114.22918637222, 19777.8618726567, 77323.90202417006, 38013.26181698226, 24262.50347088743, 28087.54003288656, 59051.358088559056, 1821.0923010788638, 13607.188153929128, 48550.424297364436, 37122.33766126599, 72113.09851408337, 26198.72914313417, 59768.81967999756, 63412.84530493032, 88280.83295344206, 67887.66102209172, 33057.72182660156, 89675.28747105594, 67501.63362065316, 38320.84324847177, 561.330177198005, 68133.18505474711, 74412.14726200084, 9394.649328969806, 39357.236097625006, 577.3843137562129, 69958.39140057431, 15913.175564838044, 95507.35521059505, 82169.6902417081, 28068.49617876942, 12085.350862475309, 99281.88948967119, 94769.4861016518, 95687.21421625558, 22440.5727291602, 27928.345594239923, 82684.59310879737, 99127.4035529778, 24547.456231235697, 60746.62430543881, 63574.56429905452, 37047.4725709419, 54386.45702228069, 52984.06961859676, 55214.85457870864, 94026.96272538876, 490.66689683743283, 97666.34295155828, 57130.8841730606, 15532.703986761031, 10578.68704414775, 44782.784873918805, 64859.00559555654, 95040.57187568732, 63699.16254920755, 52474.4036218553, 10395.812700989782, 68422.54637139525, 30949.06569200979, 51145.77006921117, 74550.02125896508, 59459.91043439595, 61826.35899386792, 27057.682737109266, 16763.836620360496, 25998.644631056068, 93184.12581769237, 59616.58296268102, 57204.62475321333, 80695.64665836503, 43899.04245307341, 64639.03254567115, 16545.222004259318, 45809.76426584384, 63298.8531170413, 91469.04615934212, 13558.806109542753, 82680.27601004235, 12827.453546942957, 9893.962535239898, 19235.686946969054, 13372.428691578352, 68873.31434621922, 88615.02426581454, 36674.57285407963, 59043.58680695859, 7114.922187393869, 73121.02389954623, 54862.066491743375, 57528.381690489, 53593.96449014052, 23711.537450090826, 78894.70550946, 12740.764281338468, 5640.874184165845, 5614.157008243448, 12180.899506058251, 21209.31375185896, 90.47209066844752, 2967.804910714722, 24380.756219640552, 31239.286455570815, 72493.12030904433, 5943.89874187391]} +{"id": 1998, "vector": [84522.53878084951, 94974.84248636017, 7903.513009933749, 88256.59885744714, 11953.255463477508, 16458.305438505082, 51440.2568049404, 58151.142342635874, 40258.0758949422, 98780.94077001698, 88622.08799167494, 39274.38533729165, 90813.12758367739, 11132.72046907876, 71119.4211895782, 27443.307847644104, 90066.09654461141, 3697.2716255445625, 44865.29296679806, 24364.876690190707, 52226.15998773479, 40079.091078389596, 65806.0669561809, 64444.34736842885, 81864.1705347398, 27591.41414422506, 58855.6562151612, 42570.97182665002, 47200.200607997875, 6992.119268253816, 64489.08547939784, 98344.48605065164, 71159.34235378237, 62700.4449426267, 84954.61999150823, 508.99482762524207, 35844.42753262099, 82693.96317962528, 10162.008252437527, 596.8678423856621, 47646.85516146952, 29425.85613043599, 50466.92914030787, 30602.8471950627, 86138.7271195277, 21913.285743889486, 32709.139564147925, 27611.976216601597, 26125.595096653942, 42822.29958609944, 23930.321730283176, 97995.60782924375, 59100.427084760886, 66540.1985215495, 57396.11837006952, 47775.09699379952, 3657.9629018544056, 48680.81016847352, 10893.353280542295, 73691.37703444422, 85322.94259150914, 78308.22545281441, 51499.48165067988, 25564.622454521068, 68519.9532094062, 88219.29407803624, 70995.0406243905, 32377.276703222215, 6216.016006414448, 22581.22408839395, 28792.79565817128, 27456.291197135106, 24070.60411960249, 90347.86997863828, 39869.9908367184, 21075.488363621575, 17091.04563243412, 75992.92774572506, 80610.87200780216, 12799.346158884562, 83546.88769317218, 48997.67069458547, 50072.856820965935, 33326.703172953166, 80574.48766719454, 25754.430257270244, 24357.333674875503, 51899.31505011792, 28590.871878090784, 26447.808185522015, 79387.56453624334, 99267.05406936974, 86751.0792153683, 37889.79131277662, 50656.052323140575, 91653.59578312296, 66372.2294269873, 51947.998306420996, 86228.95829154763, 30362.811421433555, 31219.129944701952, 32736.452132670413, 1270.8387366310526, 97069.76792787945, 43597.799053318195, 1366.4376727920312, 944.2792592228577, 36879.12922067902, 8490.652594632053, 26380.864949707415, 39786.73307757311, 57440.061192133595, 32200.50100821037, 66729.63976216436, 9450.590394443936, 68053.6487299407, 53907.49809587126, 4287.815678420426, 81899.32139393943, 7133.289466300507, 58234.17825692802, 17708.113605802235, 72485.00677159904, 39025.26556013023, 85191.70572417184, 62809.60523050348, 69878.95151655239, 38135.67938147048]} +{"id": 2034, "vector": [78439.95531364476, 79361.37966430541, 58568.698124122246, 34652.645523436884, 50533.591836551524, 24636.472485002538, 36861.94319207798, 38469.688449577145, 53634.807209756706, 95936.88117361163, 19206.74702414915, 34499.34630477165, 90282.91447502456, 69054.631717043, 22543.567601835024, 85978.21418046181, 58400.12374191373, 15280.398421420694, 64431.464980935714, 43712.176080114004, 7944.497157409458, 13072.623682289375, 3644.9126959082646, 74926.17608590417, 90098.09650183051, 10746.789518143807, 64768.47563069017, 11240.464404662642, 53535.72138468624, 34518.40927980949, 3122.5714946641724, 74952.71504907346, 68003.77658833958, 90776.03857740753, 51733.1166634866, 95369.48694233636, 4705.301544700868, 13744.257264585147, 9356.049864827342, 57136.0398851904, 62615.39669561406, 95058.46647551736, 34802.219451550794, 61676.06260246179, 44583.26422339283, 38678.831948400984, 2579.6654313677723, 90682.04224331745, 89258.28760059604, 56244.52108603566, 52607.196872034554, 84662.40403824652, 16582.638215051225, 13763.972009593994, 57214.67035377965, 66836.17270372884, 75842.67064344232, 30616.958709977727, 46513.41423303529, 67816.22109324817, 25323.865742580598, 70531.99502716216, 87696.01269089904, 35891.83187743705, 51850.97262201399, 63720.848973698354, 3018.1029354944267, 51952.975326704, 83857.59313717917, 50156.1486281046, 99302.22129575773, 97861.2609953586, 11524.732718611485, 31541.639566252023, 15879.33525352333, 31041.53096125131, 77959.41505898967, 23362.406888936315, 39936.2132077427, 11664.238245098157, 36505.652551686864, 61838.6699607836, 73246.40603443321, 99591.49452062244, 72790.75391505373, 73038.8758998247, 85799.32462963849, 4514.780014977071, 9602.598949250163, 36468.48758803483, 73634.81089514463, 43507.04545053321, 48326.56770126943, 15993.828833233658, 32141.05595900859, 45052.11889729862, 86531.95553670714, 83557.15481383074, 12615.135920194598, 91982.07912776632, 85706.87175581112, 80368.30968956766, 81211.47477553405, 60962.88744831657, 51728.96289098994, 17364.28073370163, 82828.82668951269, 30212.09958141544, 64149.63345841275, 35373.776636084374, 44457.61971570937, 63288.187191525416, 68564.01323187652, 62893.550030508515, 27560.94755778157, 96388.2571251397, 88498.8393248744, 67955.98054728642, 87977.90821359205, 60858.62477239772, 25931.149539034202, 30204.934451705478, 75071.6393489814, 2224.0464943553475, 23682.705888760636, 46023.57018382044, 49045.6720618187, 51988.39152307093]} +{"id": 587, "vector": [27420.933580109886, 6850.123548794573, 13740.518461322505, 7005.703656283668, 90046.62202368205, 60218.742533031014, 11454.104809059585, 56088.05106019701, 6585.139934606043, 12223.173034784151, 36631.67619725401, 94797.42472777468, 56244.94394173536, 61391.36155935279, 15163.44528626694, 15286.40137295264, 52336.030291645475, 95264.52807812368, 72652.55537296156, 38493.78160261403, 48995.417189288535, 46716.49917225046, 91688.63327693263, 17633.958120329964, 92344.7323463427, 12405.552164353805, 593.8796165923188, 96209.36978173809, 71827.63947008025, 36464.724893042374, 97602.04303722786, 45500.03844776217, 70479.55719462807, 86289.39920805521, 47973.29473668909, 15077.75567251053, 53852.80825695702, 63337.122940291236, 66265.63170119635, 89741.60785236297, 83115.83778422496, 64482.50261348317, 97297.87599547874, 71017.93996098984, 64228.68381284238, 33888.333677434464, 51273.169825032535, 87139.91045648574, 92436.08294077286, 16291.880997772756, 4655.709783074669, 50842.699253408966, 45565.21346455602, 44981.07854215111, 48154.328602485795, 32514.18964741586, 3963.6639392010898, 83617.20542079666, 88706.07885151965, 43593.34896274244, 22451.354105024002, 70277.11389519644, 96030.54098186504, 31034.29293317038, 58273.44982767295, 92986.487846685, 32876.47845700583, 48423.88393610998, 51877.764389942095, 35630.62356549696, 37956.4435642297, 82494.48327363378, 45957.226335950785, 24949.501912352844, 40199.280862065854, 29800.229916473643, 44890.69725220843, 92547.69868529729, 32363.82519411677, 78879.90133693507, 90856.0384761407, 96457.31091497003, 79531.80167310902, 30800.415018523363, 61492.25555704549, 30590.683819448637, 24708.826526471916, 64816.84085236838, 73024.7843589543, 70273.16880599993, 14903.320628007577, 2272.5538608206252, 82019.55407674695, 36104.18341019096, 80753.63814126006, 78427.22443423107, 22093.374367903496, 17252.476960747408, 25280.625934217715, 95830.63417226463, 65802.45178733964, 60483.32463478191, 78659.5901816421, 75593.15209775261, 63065.39359832537, 90234.28411837183, 56396.98951243886, 78822.68940854068, 32125.287561874316, 85674.04959125022, 27785.03767085908, 97424.20444152928, 99121.73946060192, 12846.827819048922, 52767.46214682853, 42960.54134374897, 87001.76985664417, 33732.902825430065, 3871.162336534806, 98017.95181992147, 49885.64795281828, 41390.35629291544, 81101.24872507027, 58359.38437583803, 86377.35214743871, 23544.747473875228, 97029.14570168883, 10744.354373673925]} +{"id": 902, "vector": [6645.030529003171, 97451.22192330142, 74075.11775466867, 17255.93749785882, 73580.48661617897, 51070.429770931136, 76990.30657108147, 6679.277046263576, 13652.582983308348, 70500.85739046542, 86943.75713806493, 33081.49735963257, 43458.580124967724, 27168.2823720763, 67898.60942842154, 99094.9979439942, 39424.48578620975, 31302.954429276688, 17693.068134180245, 6640.365452286723, 45401.23465048101, 66148.61861664332, 3108.801488836055, 28033.296822101827, 53609.48810579755, 36409.659665376734, 61965.61912995997, 13478.78762111696, 55237.98824070705, 48930.47703617165, 15209.526990634458, 65232.622242238846, 17477.517897988826, 80771.47027783965, 39295.747331011786, 64010.99799252076, 63033.208768549295, 67891.86636565713, 46560.45777207322, 14162.551421070446, 44299.86285605237, 91709.02388625665, 87805.06439049315, 6909.557447243786, 64905.73329742505, 25952.309489556235, 42836.396781421514, 2929.204923388373, 1515.8937366598236, 4701.990715875048, 19317.760097694547, 77996.87711990772, 85734.93749321236, 39661.3867337308, 75588.40666755466, 86212.59281934515, 30431.23524563912, 95788.34926292233, 20480.58776485433, 4813.331011502775, 96738.83616858737, 9251.564233730924, 78674.50830918347, 64800.091587501396, 61409.683618271774, 43337.44811972821, 59729.26740642728, 25168.95035638288, 21564.93508371421, 74864.19595711469, 25987.982127334653, 3486.494195235623, 42988.79920401899, 18753.490788152973, 2538.052088449139, 25030.52965899377, 30293.207366766885, 25885.351643410228, 70292.42167146168, 64221.92752353858, 55827.754471481516, 60319.06682791955, 10301.783150664634, 41942.5565720652, 52075.643904554, 26688.62077579085, 41911.167820599425, 80007.6037291348, 37889.901399092196, 66939.70534910167, 74085.07633032449, 94707.59468517828, 50448.18525784742, 28366.854120383512, 10295.833064649041, 47254.19782676191, 40498.566619667865, 9728.989530526289, 8212.249081542044, 39094.350655382485, 64194.47894860039, 12729.261881447874, 32430.496023273292, 98405.8590339849, 920.9032274123397, 69898.45703044912, 69310.03301544562, 96528.08846865453, 21616.674846290207, 95661.3510484603, 48648.933351816515, 41571.50498622145, 68777.33451341347, 71977.79467706378, 64915.40368756105, 32389.839780618324, 34946.19404257281, 88636.6733825497, 3671.314488675148, 28546.949564689083, 40071.29119387843, 19767.059535146214, 92797.74665850724, 33833.73888467709, 18665.776240241816, 77009.77575740167, 22406.246561051878, 59886.01721539099]} +{"id": 580, "vector": [71953.89232036553, 84926.80214286063, 91846.67443508633, 49161.01689931422, 14034.350030466292, 63827.38475526825, 70663.590538511, 38775.94173336659, 76698.0499129719, 24344.64250864392, 37631.50826527869, 1963.174506512122, 46331.254654925106, 97289.33995809765, 81281.05736704275, 98815.4098394816, 33388.998143441095, 73576.0496075585, 10918.681433571875, 38593.05176685568, 62867.12105145018, 99780.49929842909, 53808.4215471112, 55375.83837170881, 55371.554980037086, 33113.105579681825, 57115.13944554321, 61370.46051348081, 78588.735121122, 24035.867042307502, 32122.923616675926, 25996.379057051767, 87975.9954959112, 75249.01381185678, 53537.28707657879, 7050.026128934639, 67533.39051238482, 76878.89510573422, 8821.166548107818, 96804.07192945304, 55539.618050936755, 24707.86049803313, 97397.65013651122, 18814.156361317735, 75194.21395972473, 45146.96934728407, 86637.45304832202, 92647.77267554561, 40018.82196396292, 75623.29355296922, 79852.45351102902, 58454.65977955468, 50729.62509049881, 92870.42518054436, 52426.63935660864, 12926.081215402097, 59807.46177204286, 45099.6926440045, 35017.083548074246, 5731.24617141777, 50290.71951852883, 45924.27051807443, 40317.847368159695, 62026.92110207807, 68684.0686934663, 68190.40539687494, 70200.20749023688, 4327.113754038881, 62212.50764242752, 49335.22923483376, 56296.80175553693, 59475.51138664261, 77214.05458975026, 77046.26558237741, 88342.62395761057, 72172.82287267811, 26396.757762933532, 75480.54796871998, 81789.08652330672, 2785.783982073542, 62888.219336265836, 97524.83642191192, 14423.056950806302, 1728.665991354228, 69929.10131566519, 46922.246457222594, 91684.29243430564, 22126.503962749135, 81005.77950783339, 30018.04666533654, 50015.190220086704, 26570.938719238315, 89755.72290200494, 16333.524660097632, 24808.772055812056, 95746.24352672088, 35558.53636148321, 58769.367735426116, 26671.308371415525, 58811.58564001722, 30527.351779926725, 23012.6948728149, 57223.003454914215, 18499.986912508328, 79091.59709821356, 68825.95853609615, 73104.4978448939, 90014.53573211393, 40116.36676860722, 96861.57558294835, 14774.078570012283, 3339.7228151571203, 22917.858123881397, 52111.7997932196, 19599.875022113134, 42721.62140736854, 26017.315440241506, 33923.60451232597, 23741.50690241046, 27317.899812847125, 11543.80892959228, 9503.545302243, 54072.54828575767, 67982.86570976951, 61663.39050221101, 18917.884427978603, 53247.23649750701, 19970.772676605942]} +{"id": 1853, "vector": [72913.64042735289, 46759.854630035734, 92761.93151370478, 51040.000116427786, 24938.752338247938, 42412.80161149511, 6743.316229117846, 75763.04642135561, 10409.082207607235, 38325.456246437796, 41309.49133923619, 3279.6762748069973, 22664.01283024598, 87359.31316609122, 57722.91430764404, 66412.23000023625, 18858.04252381741, 89435.20637841527, 70976.93681725473, 93741.2661939623, 69026.35871249918, 43168.566780472036, 81553.37245806979, 55243.050361164016, 18194.614281571652, 49999.96156649474, 83512.82516019643, 20490.343466771, 67273.76235136064, 67955.13663538407, 57441.97244762186, 14716.087257858479, 58330.52988714492, 66189.69344530794, 27288.891636500623, 46751.86118146075, 36934.02361670517, 63019.12671920526, 88014.79749281776, 10168.554736649283, 57884.426625609485, 50547.86746857964, 44840.85805325464, 52617.73973392324, 41089.32916968051, 4067.0681282258925, 76119.22951425651, 97693.04263942807, 84423.8789048596, 8759.616651471446, 5311.19502061903, 35998.96316320821, 43809.329965697376, 54780.176217738175, 32825.173265242745, 3652.6323678422013, 65542.39503231637, 44799.26934125774, 8244.575444990533, 71342.10404512702, 8024.222542198545, 59875.40695165322, 87752.5878903349, 64260.05159795869, 1999.8502874749802, 27250.465691454883, 94189.58698514246, 59514.63039981485, 93663.84193465047, 97032.87975640716, 77189.67050689882, 57901.590687416974, 22223.687302521343, 63092.957615675994, 31901.714229916335, 45244.63690331456, 18098.590821566373, 62290.139400575026, 80026.8284273666, 81698.53629027394, 57523.96077153903, 85845.90999733676, 56639.12601764912, 13019.113398903715, 92445.93163896938, 89856.4496371856, 14354.276539883393, 25229.449371157043, 55746.60953818315, 85161.00757702078, 50648.68927345858, 16520.452743043457, 47233.891774444426, 17084.377914786954, 89686.67457690925, 3771.868618126617, 18983.736048630893, 68771.24189087412, 60301.637751179645, 21491.908296438534, 67347.53964559008, 49220.585024655586, 39909.45562161252, 42609.95018904979, 4947.056140879425, 49767.85728428328, 4662.762867879922, 1250.7536668747155, 82909.80756719298, 87854.25531720498, 67301.35696510965, 45402.05929355571, 13471.577840512016, 97805.27434198705, 26432.718532962095, 32796.41001618062, 11038.71310157728, 53547.52579135903, 66760.11903896517, 10658.61615363769, 34382.76552933283, 4102.4818844161755, 44665.42266681196, 11264.563472320677, 13757.993470814634, 62656.26525712323, 49462.01275826449, 60160.676623027335]} +{"id": 783, "vector": [75247.37479673854, 49259.85872999864, 64000.33979863029, 74562.48423158652, 97416.51409129902, 61644.12772728657, 21721.116424257114, 92818.09418884187, 13045.383973208101, 8896.549747802717, 2091.3680722640993, 91569.51056275846, 92836.85916313955, 70166.90422986679, 58828.23051038639, 98366.80228392831, 93543.807357339, 73997.15286096872, 96164.22181325727, 17690.83297975077, 11016.167584651015, 96872.96006404376, 84184.87669022704, 79330.96263839435, 46936.86484283105, 72918.1753177679, 83199.34032255823, 47661.627481017444, 11355.804371330902, 63607.852833868914, 95421.87502311627, 99537.39028941515, 60119.42103484551, 68475.56127966313, 24369.45761172863, 47382.06569235564, 59578.716800487666, 86048.77237875419, 81054.84772367903, 68920.99149509757, 82565.2989463067, 56053.044810240135, 8658.422558502421, 78153.64934693831, 83647.01210045234, 51653.900392465104, 75487.7532655627, 77963.05063317012, 49412.69188716558, 78705.06606973158, 8468.052298819972, 41511.15256362011, 27228.9979360102, 8096.309129831658, 8777.968927189617, 80753.71877596733, 91106.13150323613, 12262.232453382072, 31771.540702408653, 89517.11890710743, 42748.7766593859, 677.681406237185, 28500.070339553775, 42948.89194060511, 85192.52816346702, 10425.674416782038, 80157.91900003233, 92861.97590048896, 61502.03685495925, 29532.050569985124, 26146.421989426926, 17306.858356145094, 67060.51298019083, 19882.283062688257, 98739.22283649596, 46974.63838754837, 107.22205831623643, 76840.46791782096, 32645.21427718613, 54650.008994020915, 93184.47220598905, 3499.2294729320083, 43807.126568910026, 33965.86393441606, 60985.78749029271, 63331.55025517863, 51224.32524267842, 26112.576338255534, 62842.81088416277, 51122.87833377578, 55268.57652371198, 8349.762450267317, 81595.98170180302, 45233.44156479857, 88836.62614396805, 66019.33592616806, 85699.8653130195, 17471.13091404605, 55776.852492535254, 50555.21756284913, 35818.68603661494, 43167.19352186347, 73466.91300143127, 59945.98562915314, 56604.91197134296, 52151.116970586234, 52568.404478615485, 42808.301979142874, 94600.75949118024, 71910.21954991808, 21122.77380813593, 81634.27166618344, 8376.273293112357, 90428.08972734613, 67882.42029507428, 6426.520097191579, 92203.49189446849, 49322.02327446586, 77645.752176018, 18984.22817062044, 44672.20278628237, 55898.859185350746, 87095.43736353921, 40103.10001033361, 1334.7488874061119, 71716.39213953291, 57583.11855449754, 25346.47250906099]} +{"id": 1250, "vector": [65970.96152856105, 66325.17960658856, 42689.19415070825, 44204.182215162466, 27868.15090750292, 73331.3914561371, 79788.38469649634, 57197.93285750886, 35018.55108162897, 82843.22571289976, 47794.60814674386, 44618.96039120123, 23419.62118886338, 30927.871343244828, 78736.84951895328, 468.59944285232433, 92883.96615424022, 28355.333220259094, 72761.62518173996, 15674.605146864662, 85353.74183663426, 10943.323648847736, 67906.15755177927, 98136.05041862217, 74269.72730062691, 24329.5553769882, 77578.9882565956, 57121.9965280924, 19211.182278596538, 16474.127017781637, 80117.4463117902, 93973.1785655709, 649.3403081744065, 57723.64834492227, 22162.30009743121, 80169.00985566186, 15979.247911932003, 82211.45282815388, 74598.79682700658, 53907.7245394101, 48164.13463687449, 44581.0761978621, 2318.2641510708368, 45080.50565299451, 51546.318209597775, 13373.876711472043, 27831.871614860094, 3653.5216368009383, 43262.803971643116, 10479.199555990026, 85424.41027419028, 84697.43617448786, 63210.388782477035, 50358.342946735436, 4355.779670157877, 6896.767710916884, 25811.167927105973, 88291.30083666848, 57466.63924358813, 26311.94075410156, 63934.088659701585, 25692.967824842694, 49276.94056661292, 9105.947611616915, 63896.68256268731, 99191.47988808701, 23602.925987790644, 54891.17358686351, 21116.53142578236, 45395.61366707675, 34380.94023364741, 63282.57455752524, 5221.15948429277, 42453.93071160686, 96586.47986058908, 35268.111871518726, 48743.52236526735, 22180.797831780095, 37983.65795817299, 64326.5843027606, 37368.5977679344, 29496.78452768978, 48561.23486303482, 92869.3696709386, 58374.59834617771, 58263.5028742639, 84985.16664759349, 77221.47628751979, 71082.70644860796, 91667.39880978627, 78026.59943857994, 64550.13639669173, 62326.802114651655, 1200.3092989197994, 81968.74740308657, 4530.907081872116, 68574.8378808237, 74490.97856639956, 80409.66680737362, 20818.61815206427, 72891.66147680684, 78875.12041189092, 11851.430485073888, 87598.2401454028, 47502.681556521224, 90559.60384001023, 7681.492587144378, 69149.03650951164, 58055.892446349346, 24020.505408882997, 3432.258045405534, 92298.3811529177, 31590.30445208094, 62570.34318314803, 94661.00521736777, 10559.124466778469, 82105.20705363747, 8496.554098110943, 77764.76195092502, 17433.71151306634, 81545.5854286658, 83957.94996880373, 74159.04832234714, 29632.19802380328, 14498.189866519373, 81908.49256599104, 70085.66012528526, 88457.25731459311]} +{"id": 680, "vector": [969.0416298341776, 77960.13503365862, 5809.107765358112, 77692.82088741333, 88676.73111290458, 25910.688852484887, 79263.36979654105, 41960.66957364193, 21432.151658962695, 54857.8765306636, 43445.0725974719, 20688.626708437307, 4356.777010809865, 49432.827186732175, 18795.837271819615, 92245.82823136378, 57355.391771569644, 4285.274791481986, 67799.3953756604, 64251.29667049959, 32894.85147312934, 54061.56078425411, 95274.34607049628, 84057.53385155452, 94153.38207214205, 79723.62867465681, 28414.05051327922, 89881.43617878806, 15219.009514663307, 90529.13825634164, 79718.80993937074, 70350.49932372039, 13991.866321905689, 45583.07896662411, 28048.266287509006, 96457.80810873971, 56159.90273284171, 34553.9562157491, 70861.12805971502, 15192.139076368438, 91517.0378511223, 94661.28318914742, 96943.01713302759, 64709.0295805224, 41059.16536409814, 88406.74575494963, 29816.920252162083, 99742.72086994468, 51513.67987817936, 8722.325639128825, 19808.493986412646, 56185.70231123994, 90179.14458401658, 37147.46399956639, 81834.22082454638, 89872.26031797445, 65920.54558036776, 16631.58913882704, 70875.554840099, 81840.90899493896, 86576.70852676971, 70066.0347824932, 16867.571319159968, 2159.925946770469, 85626.90746891781, 9604.702182420287, 48173.2830378224, 66654.68356507634, 79483.34290334788, 45371.502450648186, 15846.321917960193, 25677.891226543758, 87864.06329576117, 74092.10922711753, 7012.833672037522, 30495.101823406367, 19113.2209809333, 47321.77484791765, 30628.788276336738, 62306.22434269307, 55796.81040611412, 64593.184105707354, 17523.33051736802, 11075.940843097898, 77152.14691631605, 2174.6757914780314, 53085.41214308814, 99082.66350559605, 63156.70368103571, 62595.3016653028, 22925.177322375544, 22208.743295210985, 64108.37002067291, 72081.73322008015, 17682.568691016477, 48405.501307051425, 93806.46265829509, 73881.19606754024, 83563.86779545904, 19266.2605943011, 29531.664136136136, 26121.52241869171, 98178.79572736293, 11816.314324336452, 1503.6537978374565, 65337.633804816585, 36416.40534606916, 51719.72934552315, 91868.81116744502, 1800.822576540795, 8873.028029608398, 89486.81439717465, 95985.66001261129, 61448.83640350951, 30407.74110380311, 17072.928521469545, 29396.511983771656, 44142.90707141117, 53412.58879788201, 25913.08307512743, 37184.80371636042, 4250.88759077521, 398.99430464617194, 29569.569437639708, 11028.644469921444, 49036.19938512925, 99790.75991504104, 19146.749027449594]} +{"id": 905, "vector": [97591.22468029225, 63006.52030235976, 35400.290488123865, 95696.75808046648, 72573.27079487768, 57397.26830891995, 85142.96761608808, 17194.587096469306, 37969.044790440676, 72799.17181174441, 26166.879656252717, 51996.7171874811, 8960.690968298357, 64314.95672855456, 64847.13975688522, 52106.56501183177, 75141.29108491902, 35805.81698710911, 52064.797311103175, 81251.88202776738, 27589.66830124766, 56326.24213062729, 28384.129723422124, 97078.85383925047, 98247.81910016641, 31856.815353166166, 35811.25342264827, 62072.35551584815, 48981.93716290357, 56715.28996307884, 18628.653399315077, 47476.987662292726, 19826.74933318187, 75806.91457681963, 6749.753682843718, 46043.74555576946, 34279.4940062088, 69140.39599760574, 33312.69735663127, 14036.004920803523, 54874.744218536805, 17580.943812609752, 32865.72481897292, 39954.88663866389, 40405.10987025785, 145.17455346101383, 89645.51666010203, 9240.91486963604, 37141.798890183505, 40705.71400288686, 30362.870775374475, 4066.557400927073, 2510.1611565633953, 54129.849531524946, 38585.04868191923, 92784.79275743249, 57779.87222348631, 35637.365886582214, 30244.31850442998, 6520.799799055088, 75714.51578335022, 90604.52550516225, 328.9175345853712, 46603.92793932322, 72495.23683470074, 85677.84583947685, 38686.138750814745, 11989.820837344778, 87526.7581198136, 45685.48506701204, 93798.26116916238, 46949.81681361901, 41019.10167081773, 79096.53236405805, 78448.60868086874, 6958.050186179232, 98192.86269746207, 30970.463006042904, 1835.4711528576483, 74966.28257948026, 16290.391213601608, 83186.72062481883, 2769.068185323986, 65465.761916379386, 96612.42098627384, 80197.9055800064, 93641.5873528892, 2568.5332168360887, 28288.1629786215, 96582.46165919893, 85396.6028313319, 33330.488042530036, 18369.095960934235, 66898.10326996047, 13192.63062326773, 35201.33109617004, 4881.024678116741, 33923.82049239464, 91679.79748319188, 91313.32805991582, 18796.96594404855, 32861.0783981784, 95760.12111490245, 49004.01426009897, 54161.744198836706, 88862.04611218133, 86319.632763445, 68271.04376888018, 92379.30140888793, 61996.84912961421, 95074.84537772088, 98339.55943722978, 85493.17519014592, 53452.233191350984, 18180.76353723287, 68434.27676750468, 80278.34078718306, 9180.367647899202, 71865.01811380246, 25724.59986583966, 37157.506080552135, 84159.23386327944, 73564.89760785125, 28430.91249206231, 83505.77598779261, 27413.003860803343, 98377.34624653732, 87104.61094166344]} +{"id": 894, "vector": [7501.3726214686385, 76502.27674378677, 12804.59932152448, 8663.772203258568, 54087.14912090144, 79178.57064326908, 5739.1354337428365, 21837.774505232275, 58632.680539907204, 59330.714012731245, 18890.659132790122, 39994.54301102957, 58757.99169434363, 67791.92811796865, 71084.57835182574, 12350.468002759497, 87344.58242145792, 58740.96807555544, 2809.589394350065, 81607.5177410179, 47687.63922869831, 92330.65531740981, 37901.89330408874, 11630.625216117996, 2057.494920940195, 6194.08514268035, 64294.18441952619, 84006.4341792108, 293.1972982149089, 41771.69265896997, 54953.4267084299, 11675.277587121212, 20730.796633530535, 55236.0798597739, 49683.899125516975, 32996.17774864577, 22360.081537332342, 98337.37884496059, 15983.672116075431, 35080.15914083206, 10612.540674535654, 26662.517370975012, 61418.61391031398, 99930.12669708999, 11348.017732989645, 22104.324922306805, 35273.108870682554, 90048.62093942316, 98213.29364223909, 82582.5576050284, 22615.177831406952, 83294.4688999762, 67166.91934124954, 90245.3061119446, 14025.340796598084, 13470.400535481042, 5888.4241003777315, 74938.2235979575, 48513.03574113908, 25771.363422491722, 12226.125872083227, 73271.2415553839, 60282.450806317924, 26928.75517686034, 69959.39558683235, 62275.78994409031, 70432.48214870394, 40988.39838656704, 33413.154280130904, 74128.25035741858, 10079.352475399828, 71435.83293154117, 22478.568811152043, 78998.39421062778, 25274.39310341819, 11944.894117685533, 49666.624649677164, 28672.81516106569, 31552.554159988867, 69502.23353661493, 261.3477762520189, 95206.0642610112, 84861.71642844846, 17957.129147701067, 40497.84851254601, 43667.26867248143, 72576.05260818153, 53774.45394882413, 46894.98655873148, 57768.77213682039, 23313.66755726867, 83070.61763580826, 29539.933406680797, 13617.714561657667, 8836.79089913092, 62929.05465252645, 29864.704796157483, 2788.5500544694187, 37431.72270428217, 57827.959182844745, 71054.23996919408, 12677.391748778622, 73584.90813711584, 77439.89572691162, 86312.40608258676, 44653.510230159656, 14794.073479127901, 52358.779643969574, 20290.391478553192, 23091.479202827715, 96520.97270635412, 89400.63158539351, 2824.2924034624693, 45163.1105553474, 36783.43090509846, 18729.106079206493, 82837.09534890996, 55882.627679709076, 80226.34670957917, 11692.680761389684, 27047.395191660118, 59357.47045088412, 73382.91465491356, 79578.4023563239, 89348.16881071331, 53922.80272580402, 91778.58883056721, 57573.70604849031]} +{"id": 1420, "vector": [17425.052997957137, 87629.72067373864, 7924.119162872345, 90819.08932019336, 66543.77209433645, 51818.78441150967, 87419.67913406913, 96718.07363331763, 93384.06392357129, 18251.259274032538, 52286.794171781, 48090.07090032559, 22932.193570335956, 72823.57556795957, 11332.544258293752, 75939.92086214684, 46146.353694581296, 97059.13636796676, 84594.37793312366, 36010.82417977893, 15660.602319075768, 99635.51396410297, 44195.19267835962, 95077.98180501004, 30795.012569008086, 60392.2243852204, 99322.69888289073, 28253.281741328574, 88351.0951776032, 75709.34791939532, 20455.200255594118, 46194.04687426162, 57650.50117935644, 75419.00242936739, 64242.97746697656, 72193.15224036103, 61064.11348378134, 28551.331757715903, 71009.28533276831, 65235.21915176195, 53534.75166288185, 23350.136466362237, 12744.085066718259, 21388.834494120023, 47059.68681083984, 69207.61588369211, 56407.96629522393, 9287.94310614992, 99868.94930617821, 93788.70233654344, 51423.10465254566, 50848.79353672042, 12933.504626403557, 75036.73840827758, 62430.86254431637, 99357.25277340232, 17761.79907886838, 13034.2671480558, 14950.563315854126, 21288.23285654372, 31454.465557402476, 94386.863078085, 36184.555900619205, 97353.35474681614, 38517.66017935192, 48739.59762621079, 90303.06089608074, 63829.15275092332, 6789.740606873129, 61808.578347482144, 61967.13744133207, 45529.07211010492, 18280.229048651876, 15348.461242770805, 22597.942857412523, 45199.91755820683, 31691.040950007133, 59778.28754366483, 96793.06395359345, 77936.78933486006, 40928.70906245054, 66840.23552475023, 61561.024337705625, 7768.934663940363, 91604.53887544041, 39758.18815963307, 92525.605446744, 47962.16467289466, 51958.047665710714, 68267.63550855564, 34806.36931423174, 85065.95276025434, 54725.39879759031, 64731.48935667077, 57113.154765844076, 74744.45704028753, 87547.12962545526, 38551.61788804594, 93103.09177174665, 41795.51523085447, 97256.38835673433, 23482.399739443328, 56022.1724458264, 55183.21978873221, 34713.63241319721, 16209.875606952563, 43461.49430106635, 38132.657314503136, 81929.97188745445, 97660.74807493255, 79131.13262481506, 72531.64153351229, 86662.21236262022, 84464.06611456414, 822.3184542163864, 56853.88181602345, 87309.48146738879, 32901.51143551657, 28042.56141251963, 81454.59598996758, 98917.4169312303, 62359.59562742409, 50412.116646948525, 48441.24012880466, 32141.710281118874, 62938.155148536745, 33958.22345044953, 6732.000684243433]} +{"id": 443, "vector": [64889.02952124424, 80405.15006137513, 90306.65758989389, 90086.90901135695, 35794.63096255496, 40213.99025180562, 41643.763156709734, 73650.13403800996, 95604.11596676178, 46861.452358743474, 74660.9481059498, 55561.79336068188, 89298.95048578622, 930.5611797997826, 35359.32165437071, 34218.1452429369, 34336.845573180544, 83511.9799170778, 20478.149658666032, 87708.45644294425, 95722.66259933851, 85771.82736503147, 86977.67583881995, 53830.545274464734, 16085.253899461972, 6253.715429446261, 16061.784018429282, 21584.85845160232, 54747.365351692635, 70187.52041329813, 2821.8218818281125, 82920.23611541995, 77751.37684358371, 49109.16083552789, 6123.759620164248, 87989.72563108968, 58281.16483334443, 51480.15305700284, 16409.08517164372, 30709.47080432046, 13785.415819777125, 82411.90841674674, 60873.58542414227, 1229.7699392232353, 31189.039243351544, 94151.90696455116, 10251.64196860755, 7442.929170623746, 8955.127561166842, 88062.58093846437, 87754.75259125845, 50476.11957082335, 73868.506814074, 42716.5790850769, 22042.99408232104, 62751.72447358621, 27898.37481736345, 75395.20915991797, 33181.51252801872, 85923.08440645524, 14548.84645784622, 80906.02088505554, 63216.9859536275, 12557.875306203192, 89040.578184146, 65193.05387219015, 63155.82261543672, 58796.732808544126, 87855.68129654705, 87744.53062601514, 7584.708380610383, 9072.987415044508, 92768.45036600134, 27808.819563803034, 93392.09942565409, 57182.62814266577, 7452.346813384603, 8679.872915643582, 23240.211812908816, 23573.725296816483, 50171.332547406644, 35696.13660510415, 96095.88199943534, 98763.69666068502, 34591.5035473563, 39087.71583211339, 86347.11008477108, 82738.49664347983, 68289.18229156765, 34525.80558525179, 31892.124809530644, 40981.9235974015, 59985.82867876625, 33555.73586267802, 8039.2109410351795, 7610.4408350346375, 25586.182872599704, 666.3362908021276, 3853.4636935810386, 33011.51156418271, 34931.94491626267, 45663.498709107385, 16232.45606604612, 2508.8042306777634, 32525.133020719164, 7231.2046803373105, 64334.67541196031, 8877.925549234433, 40751.01760538617, 25651.186664820092, 16848.46816930261, 52903.675583315824, 80923.5096828972, 42044.3407474296, 35056.99214682439, 29124.3780797204, 37597.985667514076, 4807.968021371134, 77534.69430558091, 66362.49960980401, 70824.54699248193, 21755.27537130555, 30274.57233579027, 47442.76547876238, 69726.11302583035, 94043.13927145512, 14593.30039081216, 21786.01450916663]} +{"id": 609, "vector": [79856.69137560147, 5209.3582850640805, 26456.630936595917, 50683.505708035445, 75527.85251002444, 91734.98348602594, 62848.5441488882, 99529.93276096835, 19643.130314180802, 26044.513629639587, 76197.0090930849, 94990.04196386486, 71643.46648888737, 73507.304869625, 58863.01885712017, 79590.66003813448, 78183.3566651967, 95463.82715717831, 53341.54379547458, 42348.17244730441, 55578.628842057355, 68214.83622636183, 58785.69001944476, 98324.61329830691, 13335.330532025324, 27243.69386387, 37629.42585621725, 46805.39287860219, 96783.5854506754, 95523.56660786901, 47246.169499969656, 29145.64616532099, 42225.103032881714, 61071.217673330655, 85641.52326243269, 68343.63757428517, 75730.06329208858, 43160.370244846155, 61719.4445703108, 83929.37698828729, 47417.83240724379, 14225.283667162148, 33520.085354249815, 98450.42423083386, 19381.41228254112, 9369.357359801334, 34267.600378731215, 49216.13819849154, 513.5152016415234, 76410.54917090006, 50378.86564820906, 13607.880733426136, 25775.78165438503, 65112.655140412055, 60238.379571901525, 90063.25934000332, 25580.794325617408, 64995.40970521085, 23606.35755303011, 92146.62701073564, 48417.561644044705, 78640.72434466492, 43278.83658791447, 3497.7103033273506, 65101.372964259375, 789.7159549618227, 23130.866111994754, 93622.94441878704, 1985.0016056910501, 45664.0734904143, 50565.88517130107, 30049.25650070114, 12679.203156390595, 40752.139626458214, 25559.623785399042, 14731.921656253555, 29942.461838307023, 79188.62484807802, 15991.76528533094, 60497.926923900915, 40811.10826250992, 1814.7162334824052, 63155.096538773, 18995.247376115633, 737.7312675338254, 41345.52539377499, 16640.11942662106, 84727.84364310463, 87560.28863127352, 89796.33732710379, 49488.80259039508, 46926.38383388242, 87677.29124887688, 89617.93588187873, 17556.735621488595, 94088.42656991557, 10852.464089742698, 66627.48673531019, 5798.410879160165, 83247.74048843258, 1253.794739747449, 65192.9533109189, 67781.53292080984, 11824.466763347973, 21126.998831292178, 81240.63239902954, 46664.188226578415, 66032.62623186104, 52541.58683995517, 53797.43184286636, 42494.70238825026, 42846.8428229271, 30084.472095474335, 16375.924306854917, 80307.64549257468, 56683.325201321124, 59199.97437193866, 50110.60960948873, 12544.357126808025, 73537.15395724507, 26504.059250646038, 81716.31768788013, 28537.998390959372, 33426.56341787785, 78681.6982387035, 80732.59488071802, 15845.077966458754, 41897.12824986115]} +{"id": 216, "vector": [60981.3169888594, 88997.59414490924, 34745.61639352865, 99598.53263208902, 87104.72530048818, 57888.89886975963, 83834.96486465156, 78984.94958621004, 1919.9879552622922, 80526.17703158883, 66191.1282484658, 76938.99535971144, 79740.92923642074, 6972.005463175723, 6215.430560201052, 54004.4198315558, 22339.986442413672, 4523.297198790155, 4893.652258726811, 20562.817213941787, 39280.28421170614, 70215.2395259301, 75502.37249483833, 44207.96959729742, 91895.174614122, 93119.08709310358, 37120.77861933334, 34322.89444984511, 68956.91639216758, 31496.10924622406, 80219.73430595647, 43213.92873136092, 24819.24827779276, 62736.14137919259, 32195.20509444279, 98898.18145391738, 17214.48131710961, 89287.61148505069, 36190.77881198719, 84848.75767932007, 293.67968661651037, 37496.69265876197, 8875.433963662537, 75595.66544444227, 99456.4116177131, 95517.43135534566, 52569.9308955567, 94099.82533255576, 86525.27692725093, 99854.79066543425, 12303.084190403546, 75301.69577790875, 60189.38685447111, 79320.92113618598, 22666.538235474552, 69791.11847056894, 47964.410654629275, 84444.02131452669, 47203.30488322549, 58329.54796773303, 48299.66782062237, 29473.008666184465, 23963.566054964504, 47851.76210185711, 57290.488732900965, 94604.5346532118, 64799.50613276624, 19675.06510017315, 58877.60159611478, 48508.12370324901, 10445.262717974501, 82512.48774493586, 64987.5184224584, 86572.76305645987, 33317.041217549435, 82518.06399981953, 28825.53112833519, 37700.739820293325, 30674.869409732975, 39507.10615451191, 35284.88125190841, 17604.475219008196, 98400.79324055008, 84097.53281873258, 23584.80646322968, 4559.18465026115, 51575.996963637095, 30163.801076977736, 57149.37560690632, 82025.12418419182, 9839.465639044187, 67291.20701720599, 33132.348109243714, 91843.58447011212, 30178.793305617237, 41619.09070586967, 23329.434851641585, 44146.30373407891, 71878.18533329389, 55422.56978763876, 89753.17856148482, 75980.59664912894, 15509.292340057878, 66265.59458507317, 15453.558216850737, 17729.586412019904, 78828.39634851561, 58173.26348886987, 53027.7075294551, 99101.36262377413, 23142.864929952288, 30359.09558958796, 32952.10624577888, 61365.935638016665, 75372.9277156211, 14832.627208788463, 87287.33442142706, 43665.37651846132, 27686.194058216308, 29261.47257214575, 25424.240541291787, 98726.42281960013, 68554.87834590483, 40720.442621749906, 45921.53728192183, 23957.283502186387, 71532.16697424342, 99406.15793505097]} +{"id": 498, "vector": [68001.96415707387, 54628.80777731989, 62349.77870878497, 59080.83825353223, 10069.09110086377, 91416.30935073344, 67174.80098336922, 13761.533920533575, 52102.16695479196, 74983.34867633619, 59013.25432458932, 25241.499946276646, 99922.96130808476, 76278.11076703721, 95782.94285341284, 22056.68491684676, 65217.76159959099, 9295.49719880972, 95185.73834988564, 55249.45951467932, 63640.061114872595, 10154.555164933476, 80142.94125526343, 1019.638695246483, 87531.67797444722, 44795.47326932043, 68713.0588327077, 27387.448420912286, 14680.498798753339, 48596.70352991568, 78033.63825210588, 64136.5609541059, 27855.764114700898, 9182.172381586373, 69743.42641881909, 38178.403121029914, 50535.79832904265, 83430.57595338815, 93296.54627024874, 39674.59991079125, 83089.30514746044, 30747.762931071844, 48850.50584233727, 11726.410302712953, 13619.02699823686, 73476.031333642, 19083.994143823078, 4973.581644661785, 26632.67131049455, 42099.97234602145, 26105.289743311965, 14866.6850624392, 64335.747264049096, 77366.20204838268, 13265.070383044542, 66911.1770279522, 38047.99598948737, 62754.95618346571, 86205.5991754466, 76884.66636236287, 98224.20868130797, 66161.4357346618, 12607.710875571222, 2879.09076531927, 94754.57864952927, 76467.73469636307, 82818.38230520935, 77840.45435453275, 97128.31991404576, 90942.36182248154, 30436.271661691506, 32671.257525897003, 30994.40368921673, 6603.262781215702, 26067.89474247153, 2093.550851726156, 55867.97959985961, 78858.43860845079, 94594.81583353071, 95335.73261929685, 83169.17891382196, 69135.98761576002, 2166.9123415528848, 11370.341096481263, 60673.71226360426, 72289.78852886402, 67843.95904057857, 95077.42400576196, 76105.43171823911, 10186.406942941461, 59068.57593416222, 76618.83192748588, 49833.204462458634, 62658.9726587148, 11602.18174394021, 97821.62014217975, 39458.08500094524, 62481.42097924233, 70324.70880806725, 99403.6148055906, 34111.52066827727, 90530.82996757136, 51913.852972442844, 66489.158622569, 82163.11147082003, 53951.447878213774, 5106.887870524956, 41571.161239075685, 39253.5091658058, 10375.128380162845, 52093.69478882835, 72199.94373766243, 76621.01580345094, 99235.46556369244, 8801.149309659484, 6917.878547550471, 56896.96856177574, 87313.72544979994, 59734.991859937756, 26707.34250352975, 63311.086573037624, 23434.41986408219, 4459.600726913004, 17271.63076249043, 63537.55829886674, 59271.60421251334, 87215.53536019319, 64238.53171969184]} +{"id": 1744, "vector": [15408.541443495817, 63430.27994787388, 67596.94187322806, 47160.74082888845, 95520.45771257707, 66265.42085313491, 62157.637789441025, 96048.26995581792, 30620.95972028339, 54386.525989442736, 87476.2512232778, 24572.09958560699, 34190.69934889741, 64170.97473201302, 54899.61007004309, 12608.070400902072, 85721.53362590431, 49651.78035718212, 660.4637764376808, 55474.63709621091, 72524.38605174559, 37121.21858116232, 61485.478995005215, 1669.2779451821127, 53563.15476689261, 90003.55745684507, 65830.25859270513, 12853.111188897126, 6099.237966847703, 76768.4347834514, 85746.96950964312, 1374.3063749353523, 35271.822238737, 76660.3419139314, 31583.021025517068, 26392.9024447467, 98486.45645664496, 77743.07618385601, 77838.29864309497, 45082.34990001751, 83273.55203855509, 11492.004318724059, 49783.70838482157, 31785.112040017117, 84287.43749803572, 88692.42525850031, 52979.78035084879, 79372.58028462241, 12179.09173132633, 34039.10297427532, 36934.22492560492, 96394.02536293415, 69649.23071203732, 88271.80769954961, 46284.84373979962, 81319.75479419588, 84800.30293029561, 84587.70796583264, 78572.2298602827, 77842.54022304402, 44576.76919969524, 44225.64877221096, 19132.18219738284, 43726.85943566152, 24141.582259443774, 89977.87591868332, 71678.62483151266, 9639.059157013797, 62835.22225415796, 91634.60460871593, 98429.45254353972, 46699.58089281106, 43514.87894257992, 75709.05614892725, 25623.741874069005, 27741.786233296873, 35820.51693652078, 90166.57434399152, 78575.63126163064, 9980.330846415087, 49908.27931420141, 26742.12067839651, 22473.522437266445, 26633.55377736919, 53279.518008306215, 93510.15234430676, 92641.3508607486, 79825.28788488854, 78300.07104930235, 54610.39208692747, 31043.446637528716, 84320.48791393882, 46219.7392374056, 91068.56123836251, 96576.58728566267, 6659.7513104313275, 244.77674213819745, 7110.305557700647, 21797.185751564506, 30837.14704340772, 95706.21070898006, 20383.551176806912, 42042.932872750374, 21791.91022386413, 83341.54066728037, 55319.58267354326, 73761.21435283055, 45515.57658381813, 54058.96959457415, 67352.08768130078, 94897.56816096685, 68897.48860731472, 59451.70712607606, 10771.532766473701, 62966.12136049275, 2131.6391997383444, 75868.14238232111, 1200.7090171313184, 22472.274580128327, 21516.844303834958, 44764.525300839756, 36274.59701040805, 6736.541547249985, 54956.80522220683, 13973.668295534859, 40556.608585013186, 76210.82610974685, 42644.79748626091]} +{"id": 1709, "vector": [6786.880696107844, 91317.16137492083, 4831.050505426259, 25955.445729768624, 92755.54095372724, 80856.54262327861, 59353.145176269565, 97295.78938006562, 99820.81710811594, 26163.40771656234, 84182.99813720054, 74535.69528708005, 72427.74839831289, 15772.207650217673, 71710.23935755502, 79212.38990846554, 48836.74041996487, 62593.08640816793, 42454.2789729127, 14559.038002313086, 87630.4874639622, 58892.493196524476, 4624.755370194811, 76732.36897487704, 59947.88956924042, 51410.81080509814, 90366.02368359172, 31254.870719131446, 95583.41744703932, 63065.04262084518, 9723.893668253659, 25609.179603949793, 23102.61850018095, 38903.40125130055, 23747.316923432503, 73677.57675544992, 24956.046868127403, 38118.204362465025, 32989.90540108792, 29120.235661764436, 96176.88124119278, 61040.3464328624, 78449.88087589195, 70790.43305245496, 94008.73318904602, 50061.17896624925, 37705.44972164852, 45526.84994582836, 51668.985219924856, 46216.82918871239, 45959.57639775668, 14403.847684340588, 7671.863449427874, 13868.974991017412, 16732.760198964035, 75716.00747315609, 62685.257885156454, 8744.23586557439, 47555.174650345354, 1366.3152854086147, 30945.251590149357, 36216.954831216666, 15460.281608202087, 98614.60023819841, 60738.86820832225, 12062.84426811458, 46095.275077955775, 1190.9317433699607, 79938.07864556655, 28076.49310164263, 6045.468777246021, 28155.828835674634, 22876.508899529646, 65402.91763483384, 99910.61113872127, 50956.554363200405, 92867.77186178407, 2507.7748105807427, 52876.446820271805, 6585.154454511077, 45933.32848364215, 25379.49462440595, 72839.57459647454, 92701.33400343603, 55157.818701828655, 46527.05864021664, 73848.7781086273, 7141.494433524953, 18375.514942122318, 34546.373411220855, 86247.74566177189, 2503.0042100144033, 24205.802215698324, 50586.98030353972, 95530.29301373997, 40802.94924752902, 46389.043632604844, 99486.88249929092, 59930.40447643381, 61160.79180379094, 34953.17654514085, 85565.32297298302, 15777.240075535947, 14695.045537052043, 64962.10085460471, 78050.6798194691, 56119.73385829927, 77953.8318296058, 90456.88490023835, 1936.2790439111377, 80788.07459926797, 29291.206853426687, 9200.942606604845, 42796.84597066722, 69942.61978352694, 36514.24073295141, 7650.016357402389, 93402.3417942043, 73771.8653737431, 94002.98551779253, 86331.61307731974, 95700.08269880693, 82359.31199313187, 52238.30537877375, 27613.410535737905, 25433.968403778705, 89612.54576591475, 56615.90945248531]} +{"id": 172, "vector": [61784.996356810116, 14009.822342157318, 22661.42708295784, 75076.77198017058, 15172.047385512255, 96728.40059326212, 80932.94988105599, 57716.93928002459, 77197.54735310678, 35550.46692720652, 56580.42895886508, 86682.44400104653, 57974.280681143486, 60073.30866445328, 95017.91438151807, 35941.48719072844, 6469.339402445362, 56195.99509568918, 93662.15995479796, 5763.545641455159, 77633.44319451092, 21190.918050831853, 36026.10257786435, 83384.6905434313, 67799.70632879515, 66659.7548549224, 86782.45220031531, 84777.09410129362, 18558.987837815544, 30372.35831025513, 79120.90450619679, 58525.977437834656, 79894.77548599799, 12721.853176602028, 66380.14797770121, 25383.3470824116, 54779.25606986159, 77243.3759023008, 2020.944226994148, 12824.92328475835, 55290.93915341745, 61874.93985226596, 30774.80554378279, 50600.40316897928, 30388.10867504861, 47460.247324049786, 86810.20196174613, 8978.091662621368, 52244.545757317705, 90389.32899701835, 52994.44924471897, 84429.85066148255, 94144.24101602605, 12258.965067909621, 57442.69424278277, 64211.671587492405, 10732.501554510633, 32993.2121595471, 59079.24182377525, 4587.250237016072, 43612.06032029704, 54266.00931525567, 68121.96563332401, 33079.04021939178, 64684.60500905334, 85107.36921586932, 271.57796289337676, 44444.96430913377, 78493.1118373946, 18137.067474855874, 32002.92660708376, 57889.4691045587, 12546.362689674117, 23239.083868626876, 29444.01053919431, 69441.93959645562, 76753.80922796208, 88558.97416117345, 58342.00392597703, 53437.611825938926, 65587.59369752278, 95784.56597620159, 15044.861386878916, 62563.26409288855, 58483.759346977015, 55640.324316737446, 28391.893383725786, 14424.469653389693, 47578.90373208208, 39310.256284229436, 86362.6929917226, 21513.09246997399, 41005.496964194535, 47641.916797395876, 2098.646839932239, 96801.73957132979, 8018.686305323675, 25970.367062931753, 3103.4323982891965, 62278.265008876064, 68782.08268084288, 10556.903435924369, 49637.47335376078, 8208.506426782415, 38973.11635550008, 92797.26248651896, 24147.392278807867, 56723.262792831694, 42162.340915118824, 47483.071555897994, 32527.076672844058, 32810.91600203269, 58360.78386485435, 35161.213827950654, 80790.45461838589, 188.12462566977217, 33472.65968239145, 52483.21704520654, 82913.96066950203, 62742.64632399406, 12428.829653275587, 9734.679097466382, 37753.81999197044, 84711.81314876869, 47139.702888982225, 39686.84271215091, 69934.224199981, 43736.317331819075]} +{"id": 465, "vector": [53490.62893552301, 55138.83323982056, 3532.6606321086083, 66149.59193951405, 12253.802058847063, 77293.27762119813, 5376.003835309851, 883.8455459787897, 49458.43918385682, 58151.61753591228, 45155.7984830159, 78442.89323175803, 73441.65714276569, 6661.558215766661, 67906.68451962616, 81023.64111855696, 53824.130731850375, 74652.27213227686, 84022.50002507008, 14743.14153971037, 40798.346427832366, 11706.77874598156, 14594.253234627353, 99396.42302294637, 34000.80447976818, 24209.22301803804, 50521.677269073916, 4083.1294898856286, 65879.26444526983, 25010.725110350373, 6762.666852281962, 31756.990409291753, 52430.649851962655, 3190.943677525959, 10724.818399734791, 21796.832952971246, 66428.01799169111, 75192.49056987558, 38032.828195363676, 87602.6400678361, 42802.27324522643, 71071.26141478374, 11452.598674995617, 95884.74001713768, 45272.48854007825, 58260.799470619975, 5700.316513133197, 83363.65836561701, 26644.47127602808, 82181.72281538926, 39259.3316156999, 49680.40016094852, 88939.00206439942, 73909.24626263513, 29879.507962862328, 82313.8245275701, 18232.554859141204, 10009.898745300572, 56451.51272585075, 90510.50242715584, 92846.32385877355, 67439.6610252948, 74121.29908608289, 24497.620300859213, 16771.09197291431, 46521.68444035511, 62245.090044578224, 69614.55374054445, 95746.39045917899, 40117.93395599302, 51326.81013981859, 52930.98463474966, 27270.529860572246, 30283.919344768183, 7525.897017489203, 82960.0020866962, 70405.4939350997, 51978.086580077645, 17629.86358162464, 4030.9584555181186, 76864.8901900246, 64337.97315401514, 8076.683265870255, 92.3168830973431, 99087.66316530555, 74963.41190413573, 19140.351128823517, 45718.37557242665, 92487.15976542771, 25981.83983446448, 77983.47885781553, 78196.01539633032, 82867.81921683607, 61612.483728022926, 37974.993211750094, 5037.235223197822, 88601.1269946166, 13586.599037437352, 89644.93020867913, 36477.20739607936, 74555.08860722907, 2830.769861349636, 42746.36786615958, 32697.927511949852, 12042.328262540259, 44998.63059861213, 69725.27922932798, 26932.148098830043, 82815.59748232606, 34937.55318571508, 72068.91879525608, 31168.406273954984, 8821.316493499653, 47821.00476906578, 49815.80531895069, 7876.272655438354, 20582.75827857766, 58949.00714954613, 63785.15395459198, 98109.71483586526, 87600.72364793821, 3035.236098863181, 27095.561692769275, 84211.00205919269, 76602.73868092755, 74305.61564826513, 35684.15175622627, 34241.23123085311]} +{"id": 1981, "vector": [89336.57745293638, 96387.95151416039, 47380.39415366414, 11659.835671212304, 43668.890921595106, 30537.173164691, 71191.12090893404, 45741.16157399645, 81808.33844774924, 64338.96820082998, 33491.20808526997, 56240.03548318087, 50305.67457182664, 73292.46063687062, 35371.996290355026, 35519.51163519446, 80058.77605758247, 77177.76249610435, 11551.812328703969, 79211.31064581052, 9148.645473321281, 62414.253522867155, 90905.22602376262, 63527.08468979845, 38076.327334764785, 72999.70741768809, 78932.34956690013, 73888.03236011318, 8859.93534998618, 61221.78832086311, 9703.085314429316, 16166.520834836807, 74444.83201672518, 47811.53081645134, 61099.65051173093, 98232.17498241576, 28660.06557485504, 79081.40459484844, 59683.88369433687, 2714.6243474219123, 75406.5813793594, 18589.528030568137, 72178.89945366999, 62569.43402368596, 72040.85101901132, 71213.0435896306, 1378.4532985360308, 1466.4393058317794, 26545.748271441127, 96515.5673285052, 4905.375195642525, 78223.28820905853, 63300.133878637076, 46961.12563586777, 89950.46506595581, 57898.57105443929, 36898.024554154304, 78064.70058528046, 36451.97864040205, 93981.18055761055, 92710.19487006073, 28959.942364986167, 65349.47877207389, 67721.66412962464, 61573.03079415458, 36298.843072014766, 12707.842012446768, 3408.3475083956814, 47685.32500244617, 92088.59343437178, 99700.33685524615, 37839.40107320537, 21179.120074333412, 94631.76076289946, 71689.35594737981, 38745.28352022303, 23614.762105673304, 48512.76670295388, 65306.77855647697, 84994.8314991613, 48206.20348258074, 71024.02294136338, 68195.0475711828, 93451.35218899166, 91889.43009331338, 80318.45638269624, 85962.89087231256, 68378.95275635434, 7214.627036883015, 78270.80479531424, 23813.184299468794, 36230.89013032531, 61771.18722319129, 11375.877343558006, 17157.219803350086, 67289.04319096736, 21345.594478847874, 94652.92197135885, 49109.60720239497, 35030.408024129836, 2771.1771141867957, 69121.12470281647, 24540.38624550825, 18432.32536114111, 97653.76600298985, 42444.66269568919, 33639.34677442922, 8389.554940090027, 79260.13922119245, 96385.63026006344, 42041.97090545686, 15089.55121922969, 59379.15845064252, 27224.543997331475, 71841.37582901155, 7189.559945220003, 39048.39086611712, 70646.71954677711, 53889.676701637414, 95041.75351292035, 67916.36827786618, 71471.18909363491, 89532.14765826933, 23366.676277256905, 27527.023896993363, 52908.18935090814, 31925.236130100533, 31960.432899481293]} +{"id": 281, "vector": [20974.29411011613, 88820.14592669763, 59716.43452637936, 75561.22590958251, 63899.09045069456, 51230.206830432355, 74829.26538490235, 95691.7282453273, 7297.353525866457, 88723.72250707747, 28924.764697511928, 72747.0888481377, 56023.66448711882, 33806.65087845127, 76464.74502054846, 57096.122214691, 7289.80934013187, 52991.32693493623, 33067.18080867045, 7287.433337374938, 28552.71215367694, 68094.3529705401, 42766.28580622092, 62996.737686653934, 27752.046908939366, 60320.62325041, 16604.867711243067, 74923.2070462169, 55693.71562273494, 19749.56233637174, 14638.098352674, 7011.424665364152, 79985.67071531326, 82010.87998980068, 18878.045102842367, 60492.24057779363, 34743.156715436766, 31784.87914422584, 12173.12851811192, 30333.843510995408, 10518.92777734551, 77502.23717553169, 24016.261660277305, 29477.609051989995, 56566.32972663062, 48779.9884187757, 85709.51485077232, 9804.628499059108, 28352.040996128602, 91924.68484855136, 88042.45415165159, 45502.722275399996, 7187.747437180358, 83584.88172314738, 9088.590551855557, 67760.7555616385, 34621.200671648614, 8344.612857429156, 65090.8789820018, 79353.22450316916, 36932.12694015613, 39116.972710091635, 45058.558593428344, 90018.37730787433, 70125.55175958527, 79273.12308439401, 81616.58856286012, 20593.77581666425, 51150.65918738842, 17721.192930263584, 40262.91898474795, 5268.750404121947, 33.163557358451, 77667.71733861402, 87818.5336411328, 30959.583962523597, 16297.73309978726, 2995.089070478818, 73894.30368564314, 79821.41272571945, 90717.9191116647, 90408.40967008777, 7260.644624844126, 63886.262184220825, 90651.97098288413, 73185.55718387966, 41413.47393579201, 81683.45152612869, 83500.39390665226, 22333.2777798803, 16490.56451555082, 1328.168204726321, 91455.83861304866, 28738.81355671065, 65289.11817083277, 20690.507006440985, 91402.39136322918, 58097.57284544614, 14767.724604160836, 31395.809025783485, 95422.64299115368, 2789.9341959879866, 10810.017023145934, 21690.202343206944, 57650.55962613515, 64549.83243318658, 32162.466796278277, 6835.03471185899, 34419.740031227106, 46370.624341305134, 41164.69604063644, 90595.85906557081, 13534.08258223091, 37409.79019406424, 60126.05072087478, 2827.321981469444, 85064.63829639093, 24251.094115602944, 81906.5249235889, 54612.230039770126, 82645.89664502848, 23417.817429516253, 56629.44399078259, 23141.053757700312, 32388.293163160753, 79413.77288878142, 55094.37734269736, 9571.24989985131]} +{"id": 1351, "vector": [15029.778414176832, 82468.43246101675, 30208.439792697638, 11686.22711841758, 11233.850669839529, 53189.80615832113, 90147.67944264201, 69591.26436253476, 82723.56797129275, 10918.52211378318, 89055.2309293291, 33189.639340812195, 7204.133806120028, 74224.23666602848, 58370.38414933477, 16598.828034041024, 22530.216223867126, 7402.726334881948, 56830.82457571945, 43771.548175052456, 9285.801499336532, 22853.966505820157, 30810.546993902364, 4010.4684536712675, 93909.1998871844, 27640.820405502596, 21111.324145076116, 40178.30259904079, 26106.775403119587, 9157.57864979766, 19046.327029677977, 93394.04526369223, 73190.96254419633, 82841.47567374718, 57175.30550619192, 24428.627138726577, 20755.80501192611, 98998.63624471029, 2543.8789050643563, 44754.856783868534, 36946.85201995729, 65377.260289323654, 25374.13703368635, 88844.03638461132, 81935.25277039202, 73779.37593870799, 23720.99953420268, 26233.853543379082, 89763.03302041098, 6075.252520867302, 75403.92268235605, 3768.8712330045228, 34931.550745693195, 1580.947157602286, 62193.40936505521, 45787.13899698067, 13995.949498928518, 59371.5544353198, 70101.8700844649, 21690.93829642449, 33161.4421605518, 17949.599104400626, 66523.81243579273, 41239.65197594526, 33409.09483487683, 56303.96533537306, 91259.36804978382, 74009.15878699905, 20583.674324974323, 60076.51225420545, 61789.653038632794, 25895.287937906174, 94458.36833167051, 58957.1756474242, 37771.646582376685, 68667.8312595406, 35811.8615901156, 52593.94283437289, 52649.91874102036, 77639.44897324523, 49741.02429556587, 37918.549627452536, 86081.28379579243, 28588.009655962876, 81481.71225164164, 59444.129352411815, 6724.1281922979515, 59235.08739658922, 66411.63687093418, 24983.103198344914, 78263.34687330184, 11487.837489665342, 46075.53412600824, 40875.19093193696, 7836.715430691299, 25778.834868796897, 17801.728428247752, 51859.846779487416, 84447.56391001442, 63656.47610210954, 20926.74097054207, 82754.9346575487, 99613.81032327536, 31871.0884342717, 28678.680383559295, 30887.996183314757, 90731.66019819555, 74851.51886743672, 91304.38104813523, 68299.96914935864, 26074.67974412225, 54602.37802183401, 82208.57835146139, 33959.75854239675, 87059.81032108978, 40678.62163396104, 20295.635694694825, 49547.4652686916, 85774.30242344858, 78649.0578297864, 11663.70796976508, 94011.04353692527, 54965.67382111563, 74930.14786943095, 72414.13126335543, 75488.86091682181, 26739.03258380097, 24637.5480277054]} +{"id": 1871, "vector": [95224.50057771776, 70222.20156594315, 35513.35150017534, 74137.51258497385, 5269.144890106248, 11873.530769559815, 28040.127960460493, 98061.59286560213, 15912.785182576716, 29070.97285818856, 56619.65735911918, 65542.05148820743, 95270.85109104995, 33812.9688414458, 41098.52663783211, 35105.23064322394, 30897.099406840556, 29382.398328418414, 49586.904764331666, 61215.18852385956, 26370.9898999429, 68346.42358391077, 86372.28912775432, 80743.98477982898, 99136.27909887901, 30108.452260097096, 93769.70779018122, 66765.1399657923, 58566.64281257864, 31397.541077700265, 47903.71051898238, 79991.95635938495, 95732.70676986905, 6824.9248913413885, 35258.07749718173, 49172.769812201186, 78495.30649053122, 36742.64330803858, 52644.44124850114, 62531.54437764715, 84696.08951842536, 88602.2237270944, 72017.00418381064, 51780.200176145896, 11632.18838871012, 55983.79569796022, 29159.70709827007, 5528.73388287729, 93101.24829108042, 43884.68873382979, 81119.24324224574, 84084.93071414145, 44190.78892566753, 24229.079981666502, 12663.919267508394, 47224.2292283473, 78289.50309098343, 70105.01107454926, 24490.650702317318, 75259.10024821086, 1773.8452214635104, 5550.13727797996, 43454.538264969044, 65142.03463165814, 89548.30305777719, 12826.37440437222, 64423.80185706642, 9134.949048584673, 32940.93667213646, 24100.258515779173, 18064.5644987931, 85010.75918790775, 83983.69525824109, 66276.81967279939, 67647.06592719276, 39468.62011967494, 74643.38940073104, 10268.67064605267, 92085.13409792945, 24058.69285472123, 47134.498770333536, 86296.08076101526, 31040.528967258575, 37622.698301671066, 85170.67117758967, 10054.129354819674, 68018.17098634856, 53165.65832431568, 99326.6924629814, 67047.30628810007, 45141.617783967595, 65347.900767524334, 4449.696883152543, 53536.326582712136, 30411.705425300428, 78091.84371346682, 28052.372370509172, 52732.14382404138, 42254.68460278896, 24242.11338408888, 8832.119977279395, 50449.4010145744, 42763.06830731855, 50474.731845135335, 40085.53248509698, 57212.02480020699, 91239.83106737754, 99627.55313213822, 8034.935907818342, 87284.68715338375, 71591.57496315904, 27763.705366729508, 28190.78941672388, 5262.73748133218, 52970.10530687464, 56775.136478468616, 40124.222441031176, 51500.25743197797, 88391.8297790318, 71141.62574352714, 16756.999739347, 79343.62572638119, 27039.010951395227, 32935.84786233241, 40755.6066419628, 90414.38034356905, 74727.04835204498, 31327.919002511917]} +{"id": 1421, "vector": [43751.54090365844, 7876.386543814729, 66163.18597836324, 19663.168505022677, 62875.242983285694, 69047.15371169506, 2111.452647885614, 19895.16083606263, 43734.35611846386, 8154.464916836235, 83638.91581979822, 56160.574143812766, 15569.69177492007, 66114.70393271469, 82222.41430609136, 20667.785478638034, 27522.657624892756, 95824.61111800015, 76446.67462404164, 24192.38563178242, 34779.9966826752, 56581.49476991217, 38870.46808748327, 17965.73376072478, 34062.960184984695, 36061.97248207687, 45812.74629369263, 56566.17978177868, 28376.98511927178, 58424.148453287606, 55887.86214232646, 85132.14394771718, 34752.45738903425, 43650.60214341778, 73389.82900499838, 55781.69603684433, 93220.59939099237, 42566.57169783558, 33890.558841176964, 7491.859908333787, 81862.10184867853, 45633.44384386341, 39392.11485862465, 60622.180514416665, 10511.417754184282, 26243.28143182695, 46840.249916480636, 97892.83075458824, 94530.77339542955, 90757.55386123616, 29559.008979888113, 99072.63157845469, 48517.92227573071, 19440.709182767612, 7567.690982428788, 53389.91977923084, 80046.0634677196, 53664.47341252781, 75898.72562405982, 61376.55474979955, 92999.3360330744, 60011.35636228595, 73347.81130431379, 50382.172693590685, 75503.12111260135, 9515.855985608347, 91372.23113611322, 1245.1702481173666, 92539.36620650452, 86794.99714656241, 30151.737016937765, 83373.90663581893, 19527.737586058447, 41567.70731004946, 7310.7507448715305, 13523.240982177309, 47928.34838260579, 49740.67227126096, 96739.77663974951, 41071.986340684205, 61625.383465700324, 9078.502012605171, 4809.421169937534, 36844.06251463438, 3452.06352068429, 88853.1210046223, 8584.322884442498, 86592.89894884817, 27922.25568728538, 48841.06362705388, 49488.18121503652, 89280.88962848972, 45527.30087685934, 71323.54409307193, 80654.98036984907, 76199.14389107582, 10413.679837753032, 52807.36122416605, 81552.42459162266, 25272.591200869134, 86078.45446307333, 73721.26497465464, 77363.22671435679, 69284.57962597096, 22662.631488486808, 60592.38266075849, 80085.93605577492, 34811.834019678245, 57345.476804691534, 36965.730976263956, 43914.14051244169, 83540.09469398868, 71285.18722057677, 8137.221291499808, 54917.88493450852, 97726.29840176126, 67866.55662492925, 86098.21707584673, 3184.42643445358, 59005.72866313653, 47030.18807230701, 60429.851906074095, 43162.56956935953, 23926.725741665778, 67011.20927618511, 27267.156555513593, 22533.926697876726, 69443.07276079996]} +{"id": 464, "vector": [26907.173892180548, 14918.32238126436, 82075.8307389608, 18028.568973276226, 51214.347625245195, 97633.71118775045, 57430.88066339306, 21674.306148259948, 46307.71197627014, 21358.732381473477, 19897.13528694298, 31949.349572892217, 50227.87621260513, 77792.50588693148, 4688.459585367577, 72003.82648129658, 39987.27867885412, 93480.29974813758, 67619.91793721652, 44928.78766945717, 20777.419230440497, 30975.591375812273, 83319.0912962475, 95724.96589794861, 10831.045120264293, 95930.56929224094, 44004.00294071746, 81255.6894381826, 67077.01821504431, 22063.804572713085, 45265.84896811098, 37841.84986414359, 23893.02480132026, 69596.61108059675, 77409.70482741216, 87606.67754321436, 7333.746236342209, 60422.8786379722, 74848.17643116116, 63063.11385332522, 76822.61602390249, 30298.67242693993, 25065.810389458908, 75253.98461733981, 92738.68273835609, 37849.55299060163, 4837.671684849154, 96103.12862020185, 35895.336561478405, 17928.853256301212, 62450.14074864741, 43000.580692086354, 74200.63913910174, 71678.4317808378, 59748.26555675855, 69442.50103312032, 15619.52395159487, 83413.9760033486, 86094.89109758027, 5163.227287751648, 12146.582507532166, 7438.421038002519, 65137.84415589878, 53088.456528667026, 67817.01264688619, 92386.51741758476, 34177.75788109857, 89023.89030831274, 70696.86887962595, 98050.75511436099, 70756.26617117535, 40836.303575286336, 54029.31260033844, 23834.114303406728, 58626.31000187471, 63171.41045139024, 49713.99640553381, 41461.75352532899, 84660.01069639194, 95519.85294320776, 1451.4159918394332, 60947.83823026473, 76594.39771624796, 7070.254091323269, 49920.258400974184, 11327.354905464048, 27992.45047334854, 19689.19772233465, 98123.1750712153, 62712.94533151204, 18993.81015368963, 91848.08534213541, 92885.52878542762, 46355.72883903863, 74110.67337819301, 36517.93514288997, 94808.97397105687, 81098.0766876231, 99415.38817560086, 76306.16742316831, 87790.51745951336, 35256.75759555827, 72688.65310824366, 71787.66264829926, 1505.747215394626, 21885.907223622515, 88553.16639564905, 69886.82707386428, 1275.1326838399302, 90064.83970581106, 51209.36642842392, 18598.00625584842, 67901.87056930817, 79413.5399607039, 76483.93560196485, 62439.248664618244, 47261.26167122451, 5062.329073169048, 38395.313465543215, 42739.8635804553, 34693.371298051745, 15545.929387245373, 11435.510665109472, 27089.917015286657, 88836.74925176818, 14558.363084846736, 93952.36487467441, 77132.42438074692]} +{"id": 1756, "vector": [94672.18479406367, 4671.382023809467, 18554.51032383676, 82291.78880656266, 50179.59712315537, 72742.56649649756, 82537.38108440771, 11914.889880813062, 18498.618905563093, 5050.443141626914, 46711.64779493209, 55675.777934295744, 54874.35587987034, 86507.33919000275, 77930.2189820408, 33928.39942302074, 73852.67693119068, 37968.2376557803, 44882.13422656201, 13600.727868939144, 12500.035935598664, 87733.90725954834, 10309.58304763222, 79426.86102498705, 52835.37506666449, 13841.68222373845, 11285.221763081965, 758.0681074730977, 14278.7081130476, 24642.163786816094, 81311.55839371702, 21013.049670035387, 59803.70543522926, 66348.8365738146, 3699.8208563526314, 7519.742810926333, 74063.03216306689, 47026.22551386889, 34990.251621375966, 21807.47020859518, 67807.50159350374, 33670.96620776376, 48729.314970698775, 70142.18240928178, 86126.85636884319, 74556.9715300409, 82833.83238060492, 35755.22948215726, 93814.79093392368, 80208.90921482713, 90497.74153182369, 96880.86095316477, 89508.00584323495, 71202.4316261386, 95677.1865536467, 76089.88462478398, 97087.5701910613, 69701.80781772699, 18972.57402339434, 82477.56243234724, 96677.60035786603, 40875.677912083374, 70372.66021573594, 82210.62728721311, 73664.93180658892, 33455.88399902073, 39991.56076134938, 24584.966112289218, 43265.713454388424, 93068.38695854304, 89935.47631970207, 62486.43772955084, 7744.704950499115, 24763.80988059962, 46630.01193235561, 69206.41802741142, 55175.53500722674, 59189.11027240983, 87082.83967197957, 50232.21344285181, 13518.001238544419, 37164.86259401303, 57657.19986120742, 85637.97312343991, 26352.771884849768, 93204.07456951737, 31083.911006297505, 16632.86373320274, 56229.522027289095, 90516.14239931064, 54329.96162189037, 46483.48110580904, 6685.151799138278, 8101.182973646848, 61031.49163517021, 43100.992349931075, 92805.60586173034, 21618.33109050284, 22655.301725868037, 64314.64457110128, 70039.64108458164, 76587.50386722975, 18845.50571908641, 19527.43568433738, 26511.02985620831, 34800.24158239573, 31699.762795628827, 99684.7324896671, 8056.704292204975, 14963.880096723715, 88330.86309281108, 43114.602692169305, 99008.76993708979, 79557.57072161914, 24352.628957530476, 14874.289589554468, 74428.04742252927, 40552.3030030732, 48116.46340362114, 34433.09060294066, 33530.226820787415, 51746.58348882181, 37988.92501043798, 70264.1893799825, 85689.79910086148, 57276.05543138621, 92846.25158682092, 98096.62671529515]} +{"id": 149, "vector": [113.26444493534594, 17327.123022407188, 57796.83251494251, 37741.83291457401, 87408.06321741261, 81367.31881817603, 95510.36023945716, 5097.086917044835, 99574.3671184644, 12212.040792357071, 8611.967347810345, 11694.768152935798, 88841.01055317305, 50135.08290538532, 84719.9411830078, 38877.66658712077, 65091.83000012654, 24072.47764008864, 77912.93095931265, 79712.75117223668, 68945.36824487722, 44796.1999205896, 54655.21160965417, 69162.96368779265, 97542.37964037454, 94112.01726521042, 72573.67512870979, 55725.537018921525, 21880.827577873606, 7967.21439194632, 99887.9349551529, 16172.649143222461, 61513.76406559501, 4436.047024380252, 23627.879602845227, 36948.03367105433, 57938.65165383465, 10021.143438473511, 9815.327471867118, 27786.039283986076, 57007.87028930575, 56432.16876826838, 5547.287359923536, 81693.38658616175, 42953.08726135603, 25688.03510029132, 3262.7205197268695, 664.4952480252098, 15733.507690789216, 19035.404096023023, 21072.331420800085, 76761.56505333017, 70052.96793215764, 51558.27821244222, 96278.79308651896, 88678.10983432883, 55501.3160779787, 85501.5667329138, 73797.80920499974, 43919.89109753922, 17569.36365396, 17880.911499422436, 53970.25300662674, 30998.4039287321, 10863.40604866075, 52660.286806037715, 33111.81950344036, 92542.63926426598, 29272.62382421313, 83588.75417170249, 63802.358247614044, 93446.81620154146, 7733.4250939744, 60580.921611381724, 78396.11879617951, 3308.5416117017653, 17849.425255808772, 93361.0518045963, 43771.60588992045, 73098.36352132932, 71522.22400898815, 73065.5654388556, 38128.668295396616, 22716.08168854905, 80372.5355177683, 6863.168414076781, 41401.41849820291, 79869.31822464964, 17269.297059152577, 5180.309522219884, 4284.414578341689, 89187.74079333834, 85804.0181878097, 15841.248282094812, 85257.13549681805, 36647.38867291692, 62197.417083484004, 54436.68778207636, 64572.15885530593, 4442.3320039528535, 23489.635383668163, 27300.98963091323, 45971.35336782079, 33824.84226864915, 6540.658186614667, 19239.986175180933, 63052.637954066246, 25864.474803091318, 52194.11807746329, 2439.993797821782, 70140.67414856184, 63021.704460633875, 55827.854862080065, 77853.44802048756, 66238.52406808731, 2405.3971873510436, 80816.96659034671, 30944.678210929476, 46004.7680334948, 59372.0954377965, 23312.564744178442, 35602.16072616149, 4594.234621582538, 72725.0975507399, 27890.615664605335, 90850.19456017968, 78069.75112372954, 92556.3087137176]} +{"id": 317, "vector": [97644.84508668057, 42830.60550148346, 49781.70195829857, 70184.18630100536, 74535.59170999259, 2216.764251662162, 38691.191190616606, 13808.66335703329, 69800.58107275942, 4496.02221167692, 79683.41361151786, 85382.2520572529, 16847.177256367628, 69969.19574048037, 21522.10617742658, 27141.554984948, 10204.167853337209, 54181.7072922444, 64073.576144816114, 88738.65485404109, 39424.883764069375, 49393.39448732416, 39956.770979095214, 66744.59499415137, 14874.708184051455, 18165.85719318029, 19988.27187566652, 76247.46432780488, 33899.01174328176, 54173.145209177645, 42069.69958465086, 96298.24134149168, 82884.19922611627, 50941.83009754194, 36195.84768678332, 33097.1737864468, 63605.03602235995, 19816.70715135214, 22758.320160072297, 56677.80410666838, 94444.04915859102, 57434.17110041316, 57146.28542927653, 22764.03467109046, 18788.43228698278, 93904.19134034781, 39730.0583167013, 97565.14262732906, 86536.05820374428, 81366.21268689416, 26941.240628244934, 34080.39898350502, 53395.701467930005, 9853.96999564918, 37297.410758646954, 47184.68365089948, 49534.32456114275, 83536.16705250634, 62260.397729145065, 29803.333759471006, 52285.77927801441, 23891.799252075496, 55117.064008551344, 32289.493677198356, 61231.17608006539, 14465.884808542161, 80142.76156824073, 26367.85051841063, 58000.04199147113, 73006.17130584725, 10481.481760357936, 70451.43641489687, 38394.58918399935, 63155.71954025197, 53556.441608886154, 33481.27185434008, 98327.94139227766, 89516.19242118223, 393.81457561152854, 94541.43469978789, 21151.37125708101, 29346.433642525306, 36979.081539743005, 95814.48392809517, 94817.32933606974, 10615.38714277912, 71327.42836389318, 78270.99915373985, 32357.54068831582, 39126.57620627258, 46641.870708476665, 29254.779511875306, 31625.735941106625, 20067.148341554297, 81244.40259939873, 31942.432530266342, 94632.02642283625, 79542.76719819402, 64289.94462225482, 70296.06184810949, 4623.553173771522, 93765.2362908201, 17367.857893013716, 33323.52705429566, 42706.60302356859, 59911.27141484539, 15831.90175751511, 87057.72222082173, 97151.57802811111, 5393.569769768447, 48964.720680300445, 19713.879222526575, 73098.67456266805, 59937.18579599184, 90875.19650780024, 6372.503690856491, 50796.50640193198, 63511.97888014689, 95598.78654326862, 84062.50512248969, 49888.07576535545, 83934.78273839841, 52021.26775691273, 16413.266123158144, 91471.0455504279, 67097.29079469734, 81615.30014234326, 11331.748247313311]} +{"id": 1260, "vector": [25806.51769909731, 78504.61944153733, 8935.614611788356, 25070.329945559068, 53159.38521366224, 25910.574867353454, 51538.79907240156, 56715.41795499706, 93281.91446742734, 36604.95145393697, 24850.234703508857, 15889.883190124188, 14630.491923763511, 45042.97830800139, 25203.205177923493, 67753.02325941129, 51493.6875064248, 97427.85530821414, 62306.66468746997, 54381.758643855035, 36066.528361839744, 33543.11128918162, 51915.425979315856, 67777.93518552008, 50057.897559556506, 64669.5047196705, 83421.3796834648, 98665.58574201987, 32119.915402926636, 98615.61523066461, 57450.03349712489, 49291.16179216164, 23237.03291354463, 9678.237034852922, 98071.2841232139, 7006.5449882732555, 49337.22195859492, 99996.92222513066, 41700.22677665971, 17561.644748204864, 39987.931619079, 30375.70776931463, 87800.58697378032, 74122.41491555324, 63060.4532861505, 76696.99916483273, 18399.89597524504, 47602.66661253341, 12684.908778134251, 94523.08260412555, 88753.88617498965, 64807.32614266544, 22022.056973547766, 50780.01261517362, 86333.39409443729, 61462.206328962355, 99299.35928730032, 42803.30423348709, 60857.020737814935, 97999.09813843307, 26581.918176409214, 30980.366422126302, 30414.260930906014, 64987.500199322065, 22765.81343349279, 88698.1885078461, 24784.747882620617, 75783.82958416954, 51792.701865942305, 98372.99600890349, 69004.87589359406, 41672.75744240268, 23597.25052657028, 35608.567498902565, 84290.85678981198, 63240.05092446056, 32300.82972638629, 48024.90426564643, 39585.469391087834, 66309.53191376521, 47901.409733909626, 85625.45135451778, 76154.75697887332, 1120.2051074927333, 52873.88019620543, 40669.77935631406, 23744.915864122697, 60680.627268118944, 35779.37916727326, 70271.76886428166, 56716.70399497607, 40844.29481641505, 35061.330624668386, 41781.00513789691, 56975.10484819303, 49820.20099904171, 34937.851405797825, 21327.218154564966, 17006.615327176823, 47042.02034168213, 85306.71087647315, 53757.040903311514, 53219.25205479393, 36355.86246863175, 68916.61943166886, 9220.372766426088, 85640.58229649907, 94195.98463836176, 23205.009906010855, 38800.21549092793, 43704.53986542794, 93721.58203242297, 25735.199246631313, 84685.15302312838, 79086.35430854348, 63542.51943138656, 59789.35559036076, 6748.994393900986, 7660.945065735325, 84963.62659197536, 1343.3138898104935, 26642.21177293594, 94929.07705269576, 1893.259619970078, 70796.71490821663, 86273.29908220393, 57099.28709953122, 40221.5450252847]} +{"id": 226, "vector": [63629.79513227711, 79592.82687426213, 5830.920230833647, 46421.77811561946, 48630.612078320926, 98691.64717152064, 86320.03597825876, 22271.04640464097, 39804.57216837723, 27063.646649088114, 90022.59754892818, 69167.55803148665, 85072.68668726753, 7989.567883462046, 32634.209214453003, 27212.561442619644, 22332.54841281518, 51215.46071185579, 49683.11737332349, 90778.37769897496, 82679.44508773855, 91550.02822045884, 46998.496475063155, 89020.09038306093, 501.2953382536378, 38199.927037395035, 47972.39461152246, 79766.84349542341, 87498.22768885827, 55089.412298691845, 72305.79712418113, 99075.09922862268, 80545.68597919379, 24164.914709020548, 78060.41010063812, 75555.24904437795, 16556.347943975037, 34534.58072982334, 22134.405030742633, 41977.44961552666, 77526.15137468153, 78448.57676367127, 79786.64623695727, 73429.34643834656, 49298.04656070928, 38654.98296981265, 10800.357303231789, 31538.61042567311, 36330.75433956017, 66001.14964468664, 93563.95325887024, 74966.23436617739, 22937.188546975627, 56347.840680262154, 96176.72079456804, 47274.68729209491, 15111.568723634538, 86822.81987638611, 69362.14782698805, 2101.7094744359865, 45599.978086936855, 78941.18774382648, 52786.1848541202, 89286.10397370855, 79346.30394159218, 25255.982385681153, 46789.36361352328, 89592.97595106538, 18224.114487144026, 49730.87490318805, 72738.03687703668, 32010.05247520241, 52526.507533194344, 31536.91535574609, 79357.87440238817, 97026.49318030944, 95506.68197709827, 27348.075409586436, 50201.24249023782, 50714.52045890297, 1991.2006716855, 51793.80507714077, 32301.621311384377, 65598.95283508951, 61847.11423581179, 65663.0319103456, 52443.075959420195, 85582.85699337664, 4002.1518279525603, 32590.388594946886, 54860.53568427651, 78851.97362547438, 88342.34515282643, 23183.748921953338, 28645.464181962565, 97798.82927376429, 21612.323496773522, 31994.48926246594, 85365.49575041243, 85636.73616904631, 92565.79383664922, 66075.5450588104, 51309.76755843807, 96493.80174167216, 95525.2833355917, 2911.3887636881495, 67748.88954870708, 2362.554233887071, 51019.97991696062, 33781.73068304162, 51265.37110411022, 8743.5414736579, 74787.83500370472, 82078.77052998483, 23024.45363231862, 45144.531149205504, 81593.99008373666, 65743.49683562818, 40401.73382762712, 6484.34861431968, 59256.061336600855, 97120.62202011001, 7852.8106232420905, 19778.63182168187, 51335.53580700758, 65600.35327043431, 13384.672625060668, 35492.40386743478]} +{"id": 844, "vector": [48443.98372463813, 40242.303864161266, 43384.85153432726, 17601.899036015267, 11316.604876849002, 46659.22968890315, 77005.9789951877, 26343.050140123236, 39542.01167709741, 79445.53694106187, 78932.90606488875, 35983.34344331468, 5013.383134774729, 9341.89197105354, 79052.02334421402, 13638.39696338739, 32238.05804463692, 49603.16309203634, 44731.37598900057, 91499.1671645382, 58604.66921010847, 2486.9306926092904, 23004.072638517002, 60874.9604942583, 45311.67639121756, 50203.25139359327, 64438.709699101295, 39708.2469985265, 42006.27910033177, 98641.20318175308, 85505.023167727, 50973.26861156175, 4503.0634889957955, 67151.33507658968, 27545.370620408903, 42230.43958731517, 1046.7765337948908, 79405.01544617, 48726.022975394786, 48332.19643770383, 94313.18277822166, 10066.612622185256, 87580.56668183501, 64162.81552661124, 88689.80761318073, 39284.76740231992, 55942.17245351487, 21743.60532496844, 59751.67266711736, 68244.82039316285, 83996.3392330694, 18590.27304478651, 71081.12966202117, 45480.24319396325, 41759.806839906145, 50861.53974676105, 68281.91499916972, 93499.3859174263, 3126.9085518303987, 57341.48444968795, 44787.96298730692, 92454.87462918459, 57803.39783762394, 29335.06697398591, 83030.31054936223, 15132.698430238977, 81973.20210213788, 81839.46604437764, 4424.75564132222, 47807.9660178452, 71404.89264519933, 19378.67200434531, 1294.2442142866662, 49559.161382770886, 1199.7058302862795, 71633.35493310126, 34637.399602291895, 56610.25443418325, 87638.75291733342, 70526.6951825544, 46743.58745660343, 90810.15710196042, 6740.65004564115, 29395.85882208181, 66064.63737294875, 76746.30140828717, 36977.302777798, 79802.34458008854, 80168.70137414134, 30520.704123374242, 55693.94145776943, 30726.308896582654, 42771.16726397843, 45779.62448870106, 1043.176941463564, 89174.36225041529, 53510.205589749705, 69414.51707313341, 84147.91775269683, 49630.82272383831, 75245.13602730638, 76580.86831269818, 51197.122470611976, 81693.09516650719, 67536.85622648233, 3873.2084391725816, 46790.01090003156, 21101.323723238485, 1721.8950037927393, 55468.50761954042, 11946.716464376705, 34429.21801758387, 53046.80867493635, 10286.891369960349, 48944.49903861562, 1449.9026366380785, 97088.88798183658, 40376.16058052418, 16425.63627462226, 73360.51577635533, 9409.0798832972, 71058.2294360316, 15006.395515314185, 70293.60822988093, 93767.87760095166, 3711.8958411687, 52973.61941007935, 50807.52008109265]} +{"id": 604, "vector": [62958.45378218763, 44650.53962410588, 71510.99137776314, 13367.23547333627, 77436.35562507506, 59032.148231112835, 49463.47388617118, 51729.39336718344, 88788.48209794446, 74599.18487172808, 84794.43986532597, 72743.16231678579, 11499.37478936226, 50588.26721973153, 71139.70089145697, 22348.83312242971, 17495.72230083547, 73171.27392480719, 73548.17977595635, 36880.65783680432, 67959.26657414781, 33281.71941495216, 37259.37759163172, 96745.46104600203, 92410.4507860226, 54017.22848538854, 632.2818020375598, 88307.75830501992, 94809.11321996614, 22724.581417470923, 6699.694590254923, 98218.69320162498, 47566.675983584195, 40266.2560840239, 90939.7322823485, 95173.73900331261, 45527.43553262964, 82075.27662136428, 56409.33045557687, 58134.8406008047, 33573.84743674848, 39401.523242361305, 3642.833318863159, 60048.70706741585, 27702.421628028686, 33981.323235489515, 67595.09813667338, 90939.40769113453, 90481.05006533056, 29980.259933664565, 34843.61995061021, 4444.464293005379, 71708.87344856712, 86264.61832652338, 76303.39691846183, 35203.77722413737, 62002.09295127621, 57232.25607610021, 23674.687004146468, 24428.02408035951, 24324.66391108524, 15375.606986421719, 37674.27097025681, 62421.762641165515, 51995.47222939716, 90891.59895835849, 15366.787510451852, 51647.223857152, 27209.942850297564, 39117.725996562345, 92559.39942345142, 4660.388725903997, 62726.54062207713, 67833.25995823501, 45203.45159715374, 50542.528367205756, 28736.341170103675, 38454.49837663637, 61313.92674366074, 76796.425729677, 91895.65829161613, 74600.3834246604, 93491.5199290836, 9106.83636666556, 1851.6340822958277, 90229.1198916359, 62674.37178515861, 85088.95545166328, 3694.573586163108, 9360.383126217697, 78480.20199661299, 1059.0540474218035, 71173.026529287, 13199.557722028676, 6061.439356385367, 72115.77673590872, 17763.72214379567, 52244.8925511112, 29586.51653746016, 85500.46539166495, 21955.953285758966, 97870.60393846019, 61053.67402383942, 14186.792440724861, 11294.733821303338, 94107.20556195162, 71256.74280201056, 66271.52683713625, 48776.22165435473, 14514.137181672171, 37127.290977984994, 18883.01314089972, 38617.36371915818, 66553.05800051683, 83150.19223873422, 11289.045092096827, 2314.4669555138385, 12632.438424713111, 73209.07553607057, 42054.20869887715, 61199.63239446686, 35163.75554857167, 62154.87630973452, 54374.653702612675, 69664.69702554797, 48775.86972441643, 49494.49523806184, 46271.85331421373]} +{"id": 600, "vector": [93527.62765398045, 27834.64919593801, 35201.688833819004, 36064.870188132336, 98955.66547592521, 32168.0509576722, 15874.155438924108, 53536.086204492814, 72647.95325006204, 29262.812039965804, 4965.66490315139, 14677.418880428793, 6568.376688667321, 18550.51544172156, 69566.31307268875, 88402.47978833351, 57316.622710175856, 14805.582194282608, 66698.37177475532, 59730.63202537352, 49232.93226873644, 85508.16261551798, 31948.91464931683, 74314.97215600107, 91940.00525371807, 28527.27445849702, 7247.956491692109, 26531.221549593243, 18556.95675193263, 41968.29889425705, 11044.257607771402, 79273.60718898983, 76743.60072031728, 28285.926530582885, 54194.81615796892, 23123.31990474409, 30479.286282340756, 60473.06336542446, 90569.88462153923, 21280.663238111043, 20599.707242562337, 77172.4895483321, 59027.173096431805, 78392.51135829564, 90524.01201224557, 8150.8894025782765, 1667.8467748082903, 51740.66577915158, 11888.699694135907, 74140.44712529829, 34100.238332713896, 84405.09496179187, 23742.52116752732, 82104.4660438914, 75104.79411793912, 27171.623881787167, 72555.6713044053, 92924.05745444803, 8036.304652324833, 48507.21375198106, 66894.04425768624, 49495.58390622977, 84410.13735394624, 89001.87817576998, 63318.773042871115, 94728.42474034341, 40428.22259988537, 56914.44790803417, 44171.49335884369, 25908.849200567674, 1939.5886998838873, 82785.51365513793, 38761.25897007937, 35779.45653561788, 33011.741929783835, 22338.23124530029, 68491.73997453434, 54088.89348242426, 40345.522097693174, 3321.4141525228724, 77114.18402817614, 48703.7012595487, 61410.02113645401, 91629.9618121284, 42593.00073133262, 51242.79470281102, 62124.146481028896, 34142.309698820216, 1659.259548357961, 37341.46826547301, 56533.13411742527, 18669.22481706583, 27549.290292515172, 74957.79294281577, 42951.76415045369, 7013.215677399109, 5569.827256679294, 48809.999548687454, 39195.04846409848, 88198.55336609708, 22132.829619828288, 29145.302454012966, 16079.106417997491, 9750.742754621266, 13355.614252379055, 14682.395032968143, 9444.328151313219, 46975.88434340089, 74467.40825111684, 99835.51247214254, 4387.8276544366045, 49119.61928499261, 50542.265017957136, 2488.566026841843, 13873.828371831243, 82987.5189277853, 57727.368958167346, 57219.39976186108, 85944.29035602341, 33882.803444320496, 77661.84020001696, 21155.36034609543, 38805.150007922784, 31431.105592146414, 43768.696452503995, 50176.49446159984, 54765.93037400127, 12731.194528942291]} +{"id": 1485, "vector": [33373.37874218188, 39963.77058367099, 4975.993741251028, 20728.67057677119, 33820.41797358444, 41847.9302320405, 24958.211172207066, 11653.805597422574, 69113.42329049534, 30903.424569606086, 59229.243247251754, 82171.37391836688, 26141.071972882124, 75848.97322682242, 78683.26823007806, 45487.08363969097, 93826.49430145086, 87892.39870559626, 79590.18560284133, 75957.64857147094, 40120.98354854412, 21420.202896193154, 88197.72892350341, 60199.96739488329, 52838.00392340331, 7427.8700814528365, 56534.81871176222, 50533.25011375386, 55028.50745345187, 53055.87281272742, 18820.283731818865, 79325.97414774331, 78882.2886431144, 57591.12740242169, 68000.69387718993, 9704.083368691108, 54785.385603604554, 37364.24049848008, 98642.57234300085, 53528.006211216874, 22814.24462577729, 75945.19734065319, 4132.5564935414395, 58691.20442392023, 47365.71607478655, 71042.0661908634, 20337.938792504752, 71088.63253061239, 74062.71813226111, 48753.65794702129, 62920.263712088396, 17578.747161352694, 52917.040737420684, 2966.6039960348776, 28808.18062035969, 87942.49895883721, 97435.50776394401, 93921.97113144364, 62368.84684136639, 18044.875238327695, 95141.13834812846, 84568.70801605275, 81948.93083931041, 4970.825179138527, 6144.301333637114, 15617.832546912912, 60675.106243996765, 70314.42000278384, 4245.1113116300785, 69325.93837753651, 65625.21797924505, 4863.094345756813, 78001.35453477917, 80189.70040156403, 52801.069350779995, 82369.38232662318, 47420.12261144908, 8907.611214125833, 91300.95957792144, 13984.391153138266, 29941.470503709188, 37664.743721070525, 3646.7143051830853, 8533.35153096021, 94664.98644983856, 46175.06969037395, 37474.68776779025, 76944.94139771361, 23539.601768675187, 69913.64486614167, 56047.97885619484, 16693.62108110055, 52835.190342901595, 16960.335390530203, 6980.582876971275, 7321.4407240059345, 22409.460207365493, 43999.37463333802, 27246.702756183684, 79703.50924834028, 64069.13847300275, 74989.86578326164, 52119.66309266671, 90964.98504178021, 10846.587366337046, 71647.0359318761, 55002.29347768116, 99478.94890867347, 42970.55438321927, 65098.37558039704, 99989.61425871709, 42948.41145500088, 26385.32582495903, 33042.6140757908, 5639.502938703534, 16598.25462117397, 13948.555350527691, 83567.97100281999, 85377.84018312524, 87285.52860475448, 16024.91946275041, 35128.1890684461, 54746.58324068575, 37134.114530853425, 38414.25878590787, 22198.197763425775, 53851.27426911582, 53898.96476818443]} +{"id": 154, "vector": [44064.19407056876, 38254.376865525766, 23216.658138921986, 3723.1057253103163, 69784.57661788694, 77048.85203665955, 367.13561376974235, 22366.452771500324, 32918.860574391576, 92524.21551125482, 89343.59469021943, 98488.90991936905, 70366.83489916974, 40110.27981852579, 79715.57593692587, 58912.68061161048, 24068.795346762363, 60300.87659299199, 88376.58444802638, 94456.99330406431, 77732.57995189933, 618.5557956375098, 6680.696147526144, 71129.52162893779, 42172.3943868202, 24194.984929714148, 32607.664812290615, 7958.632077890515, 69146.45489868104, 37771.2002726157, 84212.56679514302, 71546.6346235945, 56621.03050365025, 97323.6661982038, 67227.42636023357, 5938.160757350164, 32081.640101036755, 27163.07574823508, 25412.613884860526, 92761.21685268937, 62636.93051578413, 70998.16011855328, 91595.12165947954, 79334.15981000732, 4097.837838239137, 90193.58387510543, 85628.52764732628, 84841.44157730785, 78590.01417304328, 51207.80422747281, 70565.64151686677, 32118.975765998402, 40347.70630126236, 55716.44676402872, 76434.1028602827, 70294.87625853364, 64314.54637730402, 29207.08024086447, 75465.97062245854, 61567.09620919927, 64757.16877360844, 2918.6977652509217, 1254.0305598705447, 35682.70959997835, 63212.958489605684, 78936.98832774446, 46111.367681065276, 63768.77068814111, 91082.05435647568, 55773.15894242015, 1615.3225326669628, 59624.854125939484, 9419.141444388279, 26557.582192911334, 91388.83726139154, 94533.70590282399, 39268.476801435194, 81874.88654198105, 40857.81547643751, 7880.262743054178, 95812.77052591121, 72639.72707947926, 18730.151523955697, 70928.59231000164, 88194.37373267057, 90958.33571389952, 28793.050208423974, 24968.088313681914, 90264.69990572598, 72651.38426790164, 86141.67004949502, 34438.348223088855, 7185.638865044586, 69625.36143464831, 65834.47222182657, 68615.85273924022, 20202.791257937435, 56335.737212627, 19261.46853721099, 73093.66967949596, 43317.22170230088, 81275.27321370789, 9436.062102015285, 91023.92554306157, 73623.14601272879, 89881.88327019112, 4455.547717428599, 32284.31903258936, 85310.02069032086, 22823.910139312433, 53019.78906818373, 33038.74908222819, 63854.13258613446, 23773.257759571243, 40799.12283494761, 92483.47600800352, 22429.964320094965, 81667.7823375447, 70700.12636438521, 60295.18890918109, 55468.91718586699, 34903.181678290806, 93494.01558203381, 35879.34747290367, 2698.866958635293, 15068.429270180783, 80464.47993467125, 22211.365321209953]} +{"id": 2027, "vector": [71604.5432420498, 7702.292443710157, 13807.098473449974, 3706.935239640796, 83713.31093986373, 59011.89625153124, 18799.831090359443, 52010.97253162359, 83622.95005942526, 84089.07880072006, 81095.9397060405, 77703.34410450842, 49517.06180043927, 38626.72766510219, 45745.09764530049, 30862.214896296126, 63336.93659172938, 71610.75292849353, 48962.185532365496, 11208.798960300759, 67399.07749098416, 77891.73868206718, 68678.27060049713, 69441.89120963105, 92340.24007547513, 18270.907697174287, 10327.055502137027, 16841.950834944786, 63194.835392037705, 57600.90992494532, 15567.801582403961, 58058.71106753325, 98015.39176177423, 84416.47342515487, 28228.254877590687, 29310.376604101428, 10595.53966668456, 28815.212964526127, 29686.794401225103, 72805.6722625821, 44142.531630546066, 54494.32248088799, 41726.08164898893, 54807.48726310141, 34337.20337230735, 40100.91749534378, 80196.8444939866, 70581.91839272964, 46767.71706005686, 23713.621656940762, 74986.93246266793, 70930.09648062148, 73590.52808328372, 62739.86368812999, 58743.02156362883, 32889.704340189826, 7723.674922799928, 98251.11896506371, 50108.39775341695, 38082.98419103835, 2973.1475168843444, 23070.99907849285, 62974.01266262508, 58280.55546506522, 63135.28132207657, 72560.52040974601, 11533.403142517396, 89839.4892727444, 21342.785150020816, 75988.19414095832, 10854.79851002702, 47737.850075140996, 20459.21686086787, 69260.07054593542, 55396.1988178435, 38189.73146933965, 52624.648594004255, 50563.4250428076, 4636.232745737412, 64830.203034204846, 46869.18266464407, 1522.1736632870964, 81931.00750482481, 35047.994844621, 7561.899081114554, 29892.90823495706, 95473.83048915007, 56248.628157136714, 77348.3900536276, 57419.19026087463, 57753.17190881019, 33202.48588009408, 41555.36083742703, 25486.059685261276, 65298.05333406878, 5627.12571002556, 8164.5644128992, 33384.02612708801, 48574.86552435109, 10262.965761433241, 51201.6095759636, 83642.62192309531, 23517.72290645232, 54438.5703377392, 60042.268367727826, 47313.279155612174, 69748.45104565113, 32417.84312475112, 38929.57054291567, 96516.13287762151, 97567.71351595006, 75376.18207271512, 41505.6414318128, 45591.78963511907, 53397.85487091401, 29392.279101813536, 81752.16243253944, 53296.88493262827, 53206.65314559001, 80950.73408116534, 32377.997898887203, 31621.536550658213, 82198.90356633333, 22800.84816602137, 6038.45480945947, 93065.95511653372, 41340.97718829434, 34359.81437500102]} +{"id": 223, "vector": [70432.88293259946, 10052.229805177736, 78302.4830997041, 82289.97813696883, 71773.75454121974, 58015.80782179734, 82202.93743258016, 23749.321562659377, 47452.80062711785, 98719.97271106241, 68276.49652791022, 37760.22098333802, 24086.41358919271, 37561.20882106587, 53064.47326943332, 95652.99010702387, 9924.108624667393, 71282.67460968684, 75189.20707449179, 75720.83963779069, 80100.83892176326, 4811.638009582531, 13167.039006827397, 39311.823491562456, 43849.15838939542, 56701.02651169744, 90066.98513812036, 38096.62071917509, 68520.59725699913, 39949.44196836228, 21659.056210661263, 8438.16420040664, 95798.95041166406, 73587.68047674092, 98663.4829881471, 42088.392285855494, 98476.71215351063, 72055.0133879011, 1211.1452509054986, 64437.09140145667, 82702.3320831189, 34983.147579966644, 26614.834838069244, 30097.090423792273, 85188.80095663795, 73635.66714210236, 86266.58906062417, 31752.188554433815, 82229.46666554056, 46757.885373196164, 8764.290279302655, 28644.38052013911, 50028.55903395097, 69228.67693973736, 55335.712368347464, 66613.14773669698, 56589.329717373774, 5138.645005380759, 30132.98906484897, 7699.672006718628, 19654.535803842344, 24082.684487462182, 44524.93822774297, 96509.75365747313, 80558.2387654507, 32831.63951738051, 11740.714494390548, 2973.3524859267945, 37481.20264033607, 64198.08049325554, 85679.05362648125, 68676.73774069815, 56913.08744864714, 40870.20830311248, 68166.16387260477, 27329.19720081205, 44646.63810614587, 17195.071015404017, 60988.0418279094, 89122.71249501088, 85108.97966685165, 51070.64142236644, 61881.42346760727, 58756.24553220948, 42890.17386289126, 99739.41044457704, 46107.87911121268, 79019.98679638121, 49746.290401178594, 25308.324388891855, 88067.18069616471, 66595.36326987871, 29054.75024364986, 83791.86220892757, 35453.01193132887, 63118.02815927694, 33940.75302332604, 97305.95773124495, 1079.5401127930647, 39818.671403935725, 68099.68336369027, 76629.47777897248, 54077.03301961121, 15760.83808930746, 67589.38124778992, 39809.9508175215, 90749.78565088604, 16792.485223627373, 48881.22604118885, 16862.87151688235, 18624.82032871836, 68203.95731848764, 97200.69142292235, 66329.9722048202, 46564.55452027275, 93479.96358641714, 49875.652468532695, 59072.029351795594, 59083.55236843863, 95019.71140613484, 23079.137379339376, 53984.829809205716, 66910.6034808251, 45702.907994195186, 7717.658407612882, 47995.474115414785, 74516.9065738066, 36665.18823395934]} +{"id": 737, "vector": [83355.99707364419, 33025.402333711485, 1954.1120723499007, 32288.244058172568, 73288.06555989914, 88228.0137574613, 91480.97310800449, 22280.530566027246, 20889.95549028434, 11217.947923745385, 4168.539591905118, 76703.66758348109, 44546.19224337467, 82542.59558457999, 23503.41765155077, 8419.168618407424, 70769.87343366466, 47769.745700697094, 12199.308673192167, 31287.561927587125, 48225.83053219791, 50722.96224605507, 86703.0070272327, 86406.04635296628, 55627.13026785481, 82083.54981505139, 11776.462944327159, 64350.549328340065, 35308.213285089274, 98622.58582411046, 14100.962516855265, 88750.57794814248, 38244.076491522406, 51503.561731837755, 72673.56562814026, 7303.010233142692, 92202.6141775774, 32694.09825400923, 7887.41885747799, 79933.26804438517, 63944.5361676382, 83981.46791471903, 37781.26533658145, 11971.289990793344, 83589.32645886674, 28259.740999416328, 87626.91523343617, 97783.57970805837, 14550.875154756615, 38260.85574846657, 18067.4255914129, 37602.08274781539, 86715.35883945915, 55436.89097320137, 9793.882143347088, 19346.140267710754, 84317.65536751936, 52296.256911801385, 13115.009477667438, 62364.011537224316, 47282.726010660714, 12235.336507000471, 74370.71815347917, 40275.31678477858, 94201.96089347967, 97928.38046268864, 82228.217668814, 32597.891473428896, 34523.53958083334, 40659.65544685075, 72949.94072022373, 5101.197054372419, 48659.331308779554, 35231.971819967766, 62980.00704986498, 9200.39040053492, 25380.937183940154, 53872.55188088596, 9264.468026091488, 41342.865771006545, 92966.85355726603, 67753.69956630196, 7285.002242412164, 65864.5091087595, 91263.35781534563, 94969.80077854633, 76418.94380717154, 77515.28578262287, 53192.85502488694, 49885.41340218107, 76692.38833894771, 76064.12523130314, 61114.255649418716, 27053.18174915282, 83152.69631190565, 6613.193577517984, 44728.46679312308, 51121.67497948588, 38411.97551078318, 32818.37430998472, 89117.8020383622, 22841.639440838924, 47295.96970198889, 44966.569904972006, 21712.507580600315, 81039.09256357717, 78272.26208347753, 65929.90913148156, 45773.03764430602, 60372.07276121888, 46040.154878785856, 13418.523856941789, 35116.427523622, 66212.91670020849, 72109.68504321884, 75177.14691317227, 15146.840125077542, 53206.43212890717, 45147.456099575065, 52979.11798846664, 81852.76498971645, 94669.7718076845, 31398.183708434026, 23267.796853909684, 3800.8154697754317, 16805.001168108513, 1168.4744580101492, 69847.48729395242]} +{"id": 584, "vector": [40121.9878224526, 62322.42462029363, 72857.77639925957, 7379.189870511149, 595.7716607936093, 82421.64100545872, 63109.698695104664, 60978.86226964676, 36032.07626685788, 78587.23274248473, 2571.1094582483106, 26452.618674525496, 5622.714120254491, 91808.61137586071, 58326.78722223141, 18058.67941560374, 95495.5999863636, 3881.7846395905462, 94834.23513079227, 57391.16674898859, 59637.08007795957, 75863.16982006037, 77023.0840792632, 93155.03423422443, 63758.022224613065, 3232.4206567637457, 26647.241245987883, 57531.50897433919, 51330.252288159674, 36751.93876365145, 54141.41784440277, 43733.94167065592, 84456.52283434669, 94938.55034957924, 5410.179191148046, 42359.80428661766, 1437.8721011339192, 47278.21785114579, 23327.98719970053, 68516.3375138982, 22838.777149673573, 7868.945865511523, 96253.30548797603, 25504.313794564594, 90732.5446483794, 42591.91421502467, 6678.457435595497, 84702.75684903828, 50650.7987658413, 14575.916559787005, 97189.25914755375, 26310.598858005775, 49845.27425179809, 13538.967167157323, 78297.24559129684, 86336.94325029314, 60540.005239259466, 29195.656057863882, 82358.41503980002, 30051.248702765708, 87008.78625555987, 44975.59467597788, 13162.867592249571, 7560.019094184978, 48989.54641030351, 41363.56045275929, 23691.1230951379, 90918.30496427979, 19217.863493524223, 87621.84358832148, 51966.257494070014, 16857.311391988027, 74910.43699647141, 18023.54445617057, 70353.58942356188, 9472.80526531823, 17100.915294254228, 82533.60139816014, 57737.66728446055, 89329.73809881638, 2849.6636636561325, 69675.94696130148, 70724.78212293945, 65327.77848656235, 40044.74344649358, 94373.5464616399, 37990.68871633455, 11146.69243273898, 14993.581336283734, 49934.8646670076, 4106.679498491594, 58034.14618918554, 43798.3263882912, 58273.380492595, 55370.808707033815, 38141.520302212, 93293.12982922, 25095.574743140016, 82987.47353227994, 55180.70476226814, 15493.189172938348, 27163.255752755955, 70524.71090607242, 22811.32508665592, 73259.213298638, 87496.37261964864, 86626.70896176697, 50304.59687417209, 52287.47984245016, 6382.89656613451, 49866.08331948789, 80001.57274749273, 76505.69930777828, 50750.35867690816, 73533.3791811026, 43673.03751722576, 92350.61179559246, 24893.64141971634, 40913.1641857199, 77559.80546845823, 45861.57142224869, 12888.143310694111, 15928.808187405974, 48223.512068699456, 71668.93177744394, 60855.71991026038, 52183.984470366384, 64068.66727586292]} +{"id": 855, "vector": [29830.953260875147, 40703.94026121269, 60818.53247335747, 55760.773892227386, 31479.17391326176, 41527.80078597432, 18403.373856698345, 26029.213827166142, 26583.044981881652, 41268.134313694805, 16558.622355707485, 755.9436801764185, 30740.89905881068, 80869.78129286102, 6907.174761350454, 97039.45480290178, 6976.467102579331, 94295.36326394153, 82529.55362216299, 52997.15774020682, 2615.823675307949, 6218.326481696723, 95006.41537442036, 87272.09419146927, 60933.35758947922, 37217.327005829415, 90189.1369105504, 71170.87712735176, 74031.52980675253, 76009.0809877112, 14917.635798519068, 1345.6023918621108, 3031.8520440917096, 73719.62532950987, 25380.114835439716, 72708.71966979082, 852.4075657245222, 11216.831580659436, 9993.39846997418, 29278.297137066933, 34704.419642387606, 89282.81918347267, 79612.10482521661, 90074.65163802008, 2903.798290012927, 45640.73622912782, 41045.56046438405, 741.4167583519982, 64142.22131584565, 27151.737064099092, 2272.0828720120044, 58287.429568939755, 73287.35568406593, 19986.318189052887, 40335.274619909454, 47922.58209086612, 7654.431667506867, 87220.42100863799, 52540.31921394008, 69289.53315270258, 87756.62663960822, 12619.047110026027, 85356.89097129642, 53026.04085680116, 40837.87496527942, 69238.74908580538, 12535.076148008806, 12874.86781511611, 73485.23979848358, 57608.384415712324, 26801.42172747667, 93576.84421652161, 9443.066221464302, 39562.726372024845, 68991.11377073695, 19661.059917205836, 96766.26737399028, 5694.828917808514, 79859.27795883433, 64191.33491425573, 23438.052796821186, 44098.549766137854, 74648.59413136444, 85730.8187728541, 95728.53697741414, 58653.0594324731, 44531.47919567323, 90604.65654350433, 73935.21530713813, 21520.930757002698, 97200.1966139151, 15762.443343734589, 39661.10721489441, 83853.7935272671, 79816.2503300349, 60654.52198760889, 99166.01644606914, 10718.928146651808, 34778.86349267908, 17473.8249957235, 36351.45169484099, 42122.6258832639, 46402.10019901058, 14517.029755804622, 56424.81719214486, 64340.328184557395, 70428.78155134307, 27113.83551743225, 90058.3980136146, 72331.45137780305, 52205.08254861246, 18586.098177200605, 73207.62295617822, 76193.96539339345, 93967.94992166416, 46942.6937354229, 61097.03459870198, 20842.551145248057, 50791.293880826895, 49730.1359386578, 96524.10339682492, 57912.45997616782, 44741.151642723984, 15757.6621623164, 61125.60928396391, 11260.124967512264, 17372.688098031696, 70355.01901463939]} +{"id": 781, "vector": [90615.8795172585, 90327.39269844323, 86994.92594505938, 94382.9264576557, 53048.22586525487, 21710.830037984175, 23654.080904697195, 60995.84501373806, 68050.33658811734, 30168.170119216942, 25206.472152797633, 18169.585664318933, 59467.435684622316, 5570.383191936057, 68141.41389616195, 64565.657415101865, 26200.158958891163, 33134.28933403818, 63432.88554909765, 31460.655884465126, 86251.97879538393, 79362.96194059095, 6365.327166653822, 87028.0973045657, 59761.97330053049, 9961.208772210606, 39542.194124302885, 72162.93002732152, 21581.703224414672, 18089.527044240138, 35442.412037892514, 57891.78664068616, 3273.161034232974, 55793.55838330681, 88346.88753152665, 66334.77234838833, 45076.01799861006, 11768.415103318597, 83924.95673966539, 44848.89214723897, 32273.175007085276, 8904.621835360449, 56150.66246971219, 26849.63195758694, 92373.52112163253, 44490.73070260342, 32696.795596510132, 85624.90889615435, 43669.13128357755, 48575.15130349004, 22170.785400487614, 3324.2684446461167, 73048.63563131685, 39419.12705380635, 4784.84533598218, 65622.48525172942, 94685.98836163743, 45433.20725630944, 23283.38866611832, 2007.6128622949607, 48183.08192144358, 34261.841265245894, 65288.70763144771, 4570.243541940755, 91969.13551436712, 95964.98638508668, 48620.929597616916, 52783.83429918465, 64541.94623339876, 53220.17247232209, 18285.03729775942, 5842.735838438362, 33352.24356525465, 1376.2675092101495, 76119.09074747968, 11409.243651322531, 94685.34261388234, 21827.168881218317, 67623.77050463013, 93178.16642267423, 28962.958348431745, 87749.96128696238, 35032.30328978758, 75094.3404684692, 73924.64271952354, 49425.20173968147, 30436.25528861099, 30327.68878648625, 55186.28664892936, 52624.82110611507, 78034.62166565836, 12366.101067289459, 53399.27825567263, 39419.873021196494, 44546.08474906775, 22261.98222160841, 31369.503871001525, 93937.59168957113, 91389.69852266062, 36034.08605355115, 3269.970453774862, 62662.46673629606, 57861.44368697956, 86240.53896516349, 8344.975252648745, 94994.35924635328, 97228.33755216582, 2801.8795973724255, 22645.240682025134, 52217.80689285949, 46196.986488583905, 38550.52899294065, 66619.10416477309, 74255.86266227326, 3975.821247590161, 87700.771206479, 10335.924883446212, 80557.1829235696, 71428.50784031072, 44456.0111234133, 65147.4330952106, 48778.89473823558, 64721.37264284013, 71381.40603049034, 82621.80046418765, 57828.293989767546, 45035.79441429737, 9261.371027427624]} +{"id": 772, "vector": [44581.33747576979, 81207.59194584661, 22874.579832425356, 49196.41354202385, 82492.80538626092, 40950.492263605585, 63251.391044239914, 48898.81590243189, 81082.57660495568, 72561.86027554511, 88555.90271134558, 22770.841007848765, 28848.066381411296, 65440.55149007968, 19026.03164983426, 5924.948059093327, 25891.658357239212, 8667.160962837128, 49877.03748424579, 45717.1556943717, 27085.67000357436, 10747.100241161466, 58996.82408903776, 53304.96370621011, 97843.74346937267, 2752.617204342234, 10852.44051477715, 26604.771139762863, 15954.393333450778, 69582.57035123055, 46918.85341774113, 32584.34169857468, 73740.37037592323, 38607.362489782296, 78100.87427409197, 82954.04919591337, 76449.90652750217, 71072.72007305843, 88187.24192985751, 11121.204707449573, 53632.48117905871, 27228.739728068907, 48705.10244252467, 74503.48185884334, 60954.385590383456, 42625.8905611197, 89958.3285330168, 3328.200557894723, 56270.974189585795, 47106.800952022575, 50924.45950037256, 39923.782495561114, 47871.11607669143, 47061.53390748471, 16128.580572890549, 83575.49415959074, 3117.0320040097454, 1170.7245448666747, 1304.2730858799368, 14904.021648794553, 77539.79282182157, 71786.65869000072, 9673.064703208567, 51582.66822875031, 85480.28174281694, 16783.844449645967, 44330.54427286937, 31117.58680487562, 96264.99433522308, 11278.411749643814, 21588.60837992532, 20236.092988958764, 98662.29064488334, 36783.89455668417, 74279.98553590407, 11204.051293149409, 54640.89718945198, 45029.09981070955, 86098.00905377799, 989.6217191508683, 15661.28889997167, 61512.41219075742, 88406.15885651813, 39306.663681139784, 77660.88563864154, 56100.29328993487, 74306.839828576, 99203.22138217304, 85767.76454820597, 64740.640815710656, 10876.666423376446, 25488.73343472243, 16177.226351997331, 64143.41567481707, 90014.7818497069, 50769.10201545245, 98496.67609077289, 47235.46238695633, 19445.028531747466, 47486.91912206445, 24718.10295929051, 94455.04799969174, 17368.9807370201, 68426.03676414557, 23636.8784519807, 98819.56621072472, 24213.315464910946, 79379.05012592376, 37979.52756115407, 43878.18370820897, 89689.04826488487, 78312.32951653676, 99923.34133669881, 15713.447139048563, 91352.7191960977, 63728.56150772003, 7720.146968399266, 76261.59152258023, 59928.66569549844, 77543.79968920675, 43925.89467244741, 84233.5167414484, 22142.727651246787, 93564.82203260782, 67159.31787639893, 56449.462484157, 16246.713299377425, 66154.50498562596]} +{"id": 241, "vector": [62696.09140492381, 95080.21115137247, 68986.55929391779, 67621.21043100476, 67325.493492822, 23672.881846035965, 36601.82093672658, 7348.554148364306, 97285.69768745021, 20647.05818935554, 74496.72782772036, 289.7291546005487, 46760.79028591037, 22336.603147833546, 40104.05414350396, 7177.234983977465, 87191.94098636479, 99986.74148262323, 90043.47550062307, 57152.967144122835, 13689.02929219411, 60207.29591385367, 7029.467656156918, 5710.051827480267, 67426.39431158378, 90415.15570932781, 71126.0938481886, 4623.896700819452, 54244.99316025626, 49631.6488174168, 11488.677643950185, 44818.6441107992, 64750.31993519589, 86390.19828554105, 72365.52264009605, 21154.307594376132, 78709.53031633241, 85430.77740766219, 32613.30215839623, 36251.49800242341, 28894.47473385124, 49586.92279097073, 13682.075068860511, 50034.00899964327, 6887.449757416175, 19797.46335360367, 86542.48964526273, 45584.554777205754, 7991.239504046254, 22055.4595587515, 18067.646457784613, 38468.15835764761, 17678.941019588092, 32378.10780708228, 48353.02192524835, 30572.7846183983, 20150.99997945181, 79331.55346152348, 76599.6185185287, 98111.41172982506, 5034.467377776908, 11761.8893703468, 36074.41673710946, 69171.43867743194, 88767.33383391681, 35143.98553018855, 52630.29331932028, 21656.528959678333, 68106.38410331351, 23024.628710855377, 71394.16979820166, 45803.246890561124, 88662.86551054208, 65206.23293288612, 75625.02096962636, 19038.935126804667, 50795.207786946376, 84777.43073259611, 24148.936452403013, 34171.044930944074, 83267.0310404786, 61449.65354292123, 29407.51186437931, 89861.2449918711, 36685.90421531819, 5887.439274548856, 50718.78747150309, 15790.497008176519, 11307.06393660178, 76457.39304245052, 86670.59004524327, 41523.548164811975, 79973.69821604229, 74126.40288599652, 26573.98901845399, 87815.0404170677, 68566.21346231438, 52670.240352513705, 17343.61550872636, 31426.848741205315, 31814.199957206125, 30171.02008211, 39085.84095671462, 63688.5462719081, 65622.35748620458, 8753.719933479542, 32824.582879784626, 98379.82249475451, 5803.85151990932, 20590.038500765186, 13153.537504295087, 45241.56868761533, 24990.22543962228, 44061.16014636138, 32408.459568179638, 7917.8267067860015, 15977.340908479498, 21640.15676289238, 70678.42289775841, 10860.057833543346, 22807.031624666528, 56739.73315337516, 35433.95651375516, 81320.62558828214, 54512.98371236406, 45736.76311764432, 97545.98358104657, 18323.78022468738]} +{"id": 1409, "vector": [30118.385328433196, 12129.600256362772, 46435.94752477473, 59834.44973084368, 80726.58126181609, 88961.93910690538, 92863.9901039253, 57912.000501727925, 75627.50834623439, 86612.483293579, 83315.96446512506, 58638.37877079867, 98792.2364577043, 66129.83017363127, 98291.50974632736, 11292.642096691718, 69381.08803705816, 93202.50568321803, 695.5449972506966, 51631.823048722705, 55713.04719585766, 37399.11843317808, 31566.005420156995, 68486.55963133501, 47252.05399045447, 46909.52915270215, 87376.22673183282, 11715.039643592461, 36555.75329367948, 85791.51712675366, 1580.2244226870932, 37378.974898917, 56475.92062554794, 33846.98726997714, 34797.913843392715, 66618.70954773697, 42636.5438215661, 59376.35021035277, 40827.25674139532, 29752.053350879916, 76867.97804325717, 742.0322634521415, 2250.2839188169087, 62231.957471946494, 65047.10565656294, 16386.83228732153, 27477.28551650368, 71997.95315744933, 57356.53463323764, 75635.41348014426, 48002.2674982715, 45101.36713141887, 23204.064360094588, 90926.59633184524, 91605.54860270431, 94508.9052346842, 60170.427442885586, 82404.75291221167, 63731.53970043471, 54558.66166566102, 86624.438439128, 18252.576378279984, 71623.0790290819, 95295.2087955159, 75052.96015769502, 8259.164366260218, 12499.87248190375, 57343.60305318066, 24550.758255046636, 65830.18892081188, 82343.59098358889, 49591.482542197016, 53770.45656148286, 40799.31430200217, 7807.681045271453, 33317.24906153163, 35331.778446386044, 44841.07183137761, 96228.90587111826, 18686.182672970386, 77132.51063528266, 48782.068995947644, 78551.19187978414, 21783.965006421102, 20188.26799674971, 18527.38592312093, 92239.5552565876, 64479.193273670244, 66085.3160933704, 84363.49936729315, 2362.4682300601776, 29213.89236390285, 46909.194537249765, 72402.28711206588, 32268.138991386695, 38955.717785523215, 82736.39379516133, 49079.908794878305, 12461.611492822578, 17055.97737982345, 56541.442932032995, 70673.94299703339, 72880.35513042597, 58147.91034993191, 92936.9726759487, 9429.339293980287, 41267.46815887561, 14904.780159432317, 23109.875668285495, 12802.184092454905, 4046.233209908723, 6677.185450883027, 42952.68263191507, 17472.47278567433, 31020.66039726611, 20121.590347605932, 13714.601304999185, 75471.25600223504, 50113.58991350101, 31318.22657749206, 42180.673143241074, 92758.24994023485, 4768.687626328916, 77062.02432025701, 14296.199751053186, 19375.8856132073, 12130.967138029559, 10094.116279013122]} +{"id": 1425, "vector": [65208.39118963998, 82467.22153071925, 61673.98928683271, 16872.302213849154, 99873.65078908301, 83956.42492947396, 65493.74437090982, 3445.4007836089427, 36569.55365307118, 20570.95444502033, 48816.53240294472, 51658.193897716366, 32192.70955344634, 92692.60918024358, 49562.45949229888, 25306.62859817825, 26266.626334103814, 75512.96420033861, 98094.75058630899, 91079.66029057684, 2840.9615160546855, 49242.520538825505, 96987.51993299463, 87320.5140783439, 36149.25230763051, 51702.02204589645, 54141.29656656919, 93466.37508117569, 44131.97979781867, 9236.632877819973, 90025.98824160328, 36406.181901049116, 92718.0945136898, 84252.71200238347, 10408.95558928402, 14378.768960777965, 88181.38439803345, 66744.32548980643, 54086.780228509655, 25849.57090976825, 82782.17978562016, 64016.36190131045, 95257.36614396566, 51183.35834876014, 70631.81711732053, 16234.54942569218, 94183.58623409162, 61091.028383742596, 13344.70108796586, 54056.1026790399, 43634.24940891467, 40504.61056655787, 31444.589924468513, 3729.9344494379993, 54300.01796526948, 8730.02939215387, 72924.62893489834, 12611.366887537411, 86160.82167606492, 11053.496566660437, 7254.612937421656, 73585.89946092216, 66872.66025403311, 50132.3737471428, 83412.01143640127, 77819.56869627166, 28600.169484775695, 51967.064564310116, 16605.33406360194, 86015.29862582734, 78543.71299332839, 18139.692967879317, 24672.468458932173, 65677.50643279904, 53369.85035532652, 22205.174735800105, 46046.90296450722, 43014.90047340376, 92755.92375365384, 32362.387573362383, 3025.5353733096845, 53248.397852258924, 74893.57480853179, 38200.53469741801, 76603.3205858985, 77447.56909026112, 57056.92069280737, 15454.508609474171, 26693.759803209105, 59273.604670347326, 77030.31637526533, 50928.88313929686, 911.0820013583432, 94405.83156679381, 11430.531237025054, 44391.028151557424, 67832.510551087, 65065.56516105066, 10493.693722976572, 53552.14686580323, 55510.690054421444, 48226.111208618204, 3639.212933853153, 11032.505504432633, 97277.41166941205, 76742.44198065836, 29168.947045936333, 98312.36926208416, 24537.96948557677, 55532.67262974223, 24639.347941599088, 87304.1442013918, 77358.9226614849, 52130.755721602916, 21500.892192025654, 6389.790290576325, 75228.69405051423, 85595.88941649838, 35865.202390757324, 36705.33423050874, 70531.89561716949, 54378.41771673504, 71080.75562538992, 46747.41111222777, 93355.52122948423, 61463.53257378011, 50373.90414578452, 48778.99322785416]} +{"id": 684, "vector": [72825.98899921135, 91276.1137058355, 75460.83228757838, 38845.25757044237, 67068.10807069144, 3805.685998507824, 5909.174622773794, 6326.883214236878, 9029.064364786089, 16236.261119397444, 33937.283016539164, 93843.84905996415, 44460.36835214853, 77510.30162075, 61408.58173212349, 75795.84026881318, 72925.29331017433, 3597.3085589844354, 96757.56572403395, 65445.30471534254, 41728.05306888735, 11495.706190767429, 98184.60195507375, 64575.596636387476, 88265.33819123101, 92151.9045833731, 33381.14113638378, 35923.09519671563, 91888.09764070803, 34292.58655578424, 79418.73153756555, 26332.964010333348, 83328.84184190258, 34657.96807295612, 55199.48682746598, 7812.128845347676, 68788.10514475702, 76143.96900377153, 65466.981159411254, 20393.12365709043, 50934.0562780315, 11480.032317637868, 84460.13963435809, 7031.629143999585, 40765.780349328714, 93678.70738469066, 68004.53542213445, 62869.77307304381, 43086.095317255094, 34133.44258754787, 83551.4963936618, 40063.05314748937, 65834.78106165306, 86476.21578039376, 45216.535493366, 57529.95892960379, 9116.985515737064, 5617.827037784551, 50278.987347734335, 23178.029377921437, 39271.387672618854, 18329.458533553967, 14958.62083875611, 11652.280248144563, 32162.896614419788, 70542.82531670481, 77587.28853272622, 40032.870325749005, 27897.28584111073, 94519.08478303133, 20750.743030623176, 91217.77055740208, 98727.36937768334, 31601.03788499625, 18882.32237888051, 95066.63731324257, 59746.44056340558, 89224.03983700021, 41964.785891875246, 3831.4895075703625, 56335.60205784754, 38230.410129932876, 8488.421251189926, 99413.78843047378, 10989.01602962048, 29169.07565014074, 38041.86940468734, 26692.561301188223, 42410.3855080918, 67827.41151858187, 98762.18613349425, 23942.517785077587, 58945.15686221956, 70592.04788586142, 99277.98553747745, 11601.09060951705, 85919.3597864148, 74430.32139819054, 38405.97346671034, 21750.59049662029, 77733.00767131684, 23864.163503569802, 79354.84507879055, 33318.11570006335, 39186.464284362155, 85554.69226452583, 15970.365291038292, 3502.100799964758, 2789.163229115166, 90075.11239933711, 62832.70585448292, 61392.01396643643, 5763.100369594831, 81349.45462025698, 27973.073959909223, 39590.165611636665, 82581.6543637772, 81828.20854832753, 60735.672249106254, 6271.446160377958, 20126.029306846183, 97831.4274786261, 62001.826876543186, 25418.76613809193, 30298.290147080377, 97773.93517620007, 68030.91629676046, 92148.87054990031]} +{"id": 666, "vector": [82374.82602833115, 5834.999034193389, 7800.208658722662, 26465.042128401663, 80022.86419609735, 55819.51339690271, 54575.03818461073, 28263.92290713027, 93689.55297632542, 40863.79684358995, 17570.77952298649, 26164.88879347679, 42240.31312861055, 6181.049439828667, 40337.73007467363, 48807.83825337934, 49744.82615969241, 38850.93702233544, 47223.03170266286, 8275.001156952989, 43483.153435058455, 16771.2793282222, 42385.222985152606, 24328.90320665161, 83046.70141723107, 18355.44452864538, 72290.20686765476, 52272.37537301645, 18665.925542212826, 10910.344925330817, 64545.38412347039, 68932.30281737653, 36599.14807343939, 44356.7426034579, 54811.56319054187, 92007.0745244108, 11652.747839088739, 27636.598582842296, 32875.14226317929, 29788.274009343408, 9748.211671716666, 34046.39993012905, 76209.4540456494, 16752.330971290143, 67822.24345032401, 99370.95383399555, 28940.138685619666, 67457.02191505414, 45491.71186827955, 93188.2652337874, 630.6009189090412, 9338.164184346664, 86124.48869520024, 57809.59421654954, 84387.34969664674, 47986.8165975403, 96642.49856115312, 9945.93781909947, 80439.51360209119, 47235.17413219285, 75060.27585194822, 84621.07927440539, 88339.44695201305, 38099.961181287435, 59987.152539889554, 91258.36726474315, 6834.214254259752, 87681.99448256702, 6665.181249584984, 91570.27344342753, 39820.362132106304, 77611.98807022808, 37032.29398223965, 587.0181011066511, 49070.44726239647, 20512.36756346393, 91785.76193318954, 39113.157651705755, 1538.2680498978395, 62726.91274889197, 6754.023032923406, 68978.57620864674, 37573.40581492489, 2124.9043328812054, 18450.32734896479, 23783.709726578938, 6653.125238223867, 76667.20944643804, 17071.859119818233, 50570.259162462804, 60096.35369324158, 64392.60911025089, 11739.378126029198, 91587.24405653507, 3706.868765913185, 7122.365285096921, 32128.758208337305, 52265.38395691094, 51446.76068792696, 92254.22824322953, 49371.27832180498, 23962.153548495313, 53279.6646600388, 99139.79921173926, 2211.7892629527237, 25637.385052466678, 7718.063101573025, 76878.82090212336, 94537.78189719278, 20934.547258900893, 66937.31715414808, 18644.556118198972, 41720.507113872365, 34621.17474526869, 42840.508791114895, 23242.910643636304, 78581.10264249609, 79266.81781728931, 93770.61228527213, 82207.87827457441, 13644.114761952986, 65975.73206455517, 88714.47307385864, 12333.107123800513, 69161.61099082587, 40649.841810688005, 46343.807070059316, 77992.74100960008]} +{"id": 709, "vector": [67504.56625448784, 70621.30567163009, 2.562770661795888, 81894.20354567765, 53207.20150537668, 19183.918939719748, 97382.19532042857, 56407.33236033881, 29358.00694243017, 17074.362226422636, 57993.95777075045, 35826.100748193734, 63674.16721544493, 77940.9228190986, 11204.798011779194, 57273.15024089955, 84610.30377690251, 80042.26606625775, 77650.37941096681, 16754.763053209677, 45969.5500813477, 85153.72652132738, 46204.79114543983, 62866.496885317145, 43006.088955631974, 53568.798163958374, 28708.991861036404, 69959.93138042802, 41929.52841633083, 53133.822026308866, 16870.04486048582, 72293.61447744635, 20372.615548645357, 29954.375971553716, 96884.57638448995, 52167.402618457505, 75725.3935376217, 70377.21451650836, 38080.832136521436, 26513.09675705068, 36093.97633183199, 10014.210747258323, 94048.59477245959, 22655.89978740231, 16779.46548381333, 99567.30833231425, 24738.5441522501, 79500.33821202163, 38976.35172474727, 79834.12823454647, 81251.22483583436, 2456.9790704996763, 14179.59713461211, 71650.25464932235, 17449.99948038747, 9130.971458917958, 85538.73270914507, 11075.574085979246, 56895.51426285947, 76524.67435488418, 90178.18682192582, 15694.732973504222, 27221.962383920007, 25148.406124896883, 57844.99390463336, 22755.92477907352, 42871.718058409446, 7865.0245222212425, 97324.72734271201, 44130.21504107604, 41119.37841972087, 34581.37491642582, 91690.65229331469, 31472.88605666212, 41612.20485723306, 35498.89251894094, 16779.92237252297, 14158.122319319533, 11390.630489058185, 69673.44087442367, 94857.07194858171, 43820.38875824078, 80052.14106596325, 95929.99452245409, 32825.38773813294, 65908.55161287126, 89892.67859359464, 21069.46946334448, 14047.482758930984, 35397.650249598344, 29900.517400458048, 5849.283745381984, 42091.933928537925, 7515.652726740207, 2735.7088974855446, 43424.14439809088, 94092.66903535342, 22036.796524390556, 73161.13170370473, 9717.521458746025, 81375.84226966565, 24250.953062809454, 64271.61702787262, 73102.53059548655, 66410.24106605907, 40286.759262496285, 5026.423555766757, 18810.222069554882, 12687.529654827435, 4166.525223173667, 9520.432268578816, 24903.878556006097, 64765.083008898924, 19410.564941510856, 84193.15589489549, 22265.613840909802, 78256.17967700296, 2317.477809899271, 28001.051282514934, 95020.18982058846, 93934.79133948544, 50079.90595216183, 58033.033646243435, 5560.311455106282, 9445.742398447854, 71325.10690120842, 9372.569709634381, 54482.96158004362]} +{"id": 1700, "vector": [90896.7913191616, 36010.58989385544, 99340.76479767842, 66187.63888299365, 56502.000202072275, 44787.96956326867, 3897.737590321837, 28146.651366527953, 12049.973789936319, 38057.90472837826, 32258.59592364979, 4384.288870621389, 88677.63734413033, 39841.80321957934, 3717.407028456632, 41677.64577536234, 46236.64826391459, 33409.37379660743, 34155.43571120109, 33062.321862336554, 70586.60253244045, 19201.088941262322, 56836.599829019266, 65800.21705828636, 41515.672712320804, 53673.49163964426, 58597.98355433082, 52728.127757265254, 30131.80122899568, 13845.551949668721, 3608.2910579953164, 80503.46582395196, 6535.301360617263, 1261.3507876537144, 69807.34767936548, 68840.64994270694, 20743.99924637701, 93203.79807859994, 67281.74039657309, 90990.2860131706, 92844.78192039249, 22784.23958944922, 24351.631640349202, 54594.43674377039, 64438.28731179125, 85277.6294357453, 26947.07728501988, 61858.81457684588, 52791.12634885053, 422.5917816156333, 13029.731701259561, 83105.27354371581, 99106.00865881609, 33237.38883509376, 1280.2751228156328, 11950.35374282074, 38926.25536332769, 81794.69895159069, 29393.572091938313, 7325.341761891768, 27357.261731447623, 44625.04194094651, 36603.36658747963, 13423.417891575307, 49134.56100920796, 15995.03451170905, 41401.92833162958, 73872.30437734524, 96879.836137524, 73721.50702128622, 41741.697023463894, 24545.019310527394, 14999.537120626826, 6764.8558281133255, 46426.543675548004, 4018.904807124102, 79411.97814022042, 3839.163133501766, 6154.840474048673, 61255.66302658789, 54044.499316213434, 84146.82237832528, 32350.192652840204, 35957.65943933652, 82584.10512619848, 47851.19765548358, 84063.1977129169, 70119.3870078738, 44042.3778497857, 86976.74737116671, 10973.501311693744, 89980.53046015266, 78030.52739930929, 39938.34619211027, 45567.4962956958, 73253.75093700856, 30589.5474561091, 18964.160464839853, 8088.799990758044, 81654.36950000172, 91781.3225314049, 75948.88973790583, 13434.483278399312, 48937.41360916615, 5254.027229953395, 74079.48317160405, 34662.53916618247, 99684.11294613821, 58046.34034264946, 11147.068272284178, 61989.23997531759, 29759.61999600042, 56896.885494728485, 25856.941047185945, 62949.82184412283, 81283.92547182395, 50222.069050363585, 77190.08565440538, 52521.0554510812, 57836.011007419365, 89696.6415148173, 8668.527017851602, 41429.11197313324, 66536.90649892125, 10585.316288088963, 12316.095343126231, 59221.64958548136, 9511.485411977616]} +{"id": 1955, "vector": [40037.47030750566, 48366.83454358938, 51507.824879739426, 93202.46173055177, 60293.83416395507, 22496.80398163687, 94452.88436241311, 61337.38137329193, 96794.00415735703, 52769.098100726944, 19347.81749371517, 34892.754764882215, 28277.067270992982, 21226.177654698353, 85294.94175371982, 19561.646413722854, 60024.79391939079, 86062.2144338985, 49947.61598438257, 92599.13725942056, 77595.89232947853, 15655.42786452323, 56841.19810584614, 66885.50473828186, 66462.43734503053, 59343.51314457583, 84706.68126227733, 79200.9468975346, 96171.72911049817, 46122.30854354358, 93008.59332937529, 26049.778059489592, 12985.577500188372, 6312.119796145998, 57172.71067632849, 57319.92473105572, 13991.354828189329, 42335.83183349484, 50649.77578008587, 7627.749182864074, 74252.98321353682, 17934.751173640794, 6156.8439992438, 67116.52437388396, 17283.260785359733, 42431.38130532333, 59080.230681218105, 49536.13751257154, 41464.39201008976, 66114.07428049218, 6725.372245178818, 4962.764868810299, 52296.00732256219, 72131.27148055787, 52493.26278412567, 42632.27373293937, 66224.78243455976, 62894.15112039922, 2059.938101877179, 21553.24821259923, 71240.0289353958, 87189.65953073857, 96047.99181190418, 38945.999595983914, 15320.249023223354, 45783.365821173495, 45608.10491741536, 55504.14735089151, 614.2694655957515, 21382.048287719557, 72876.12346094228, 61047.31981601038, 44651.682634237965, 22984.847896271254, 59883.149500999796, 54855.82751280852, 73339.89079888024, 60832.44812943512, 81381.3096864049, 16052.334850592088, 17227.96282872481, 43690.23977980938, 24658.8403473309, 86177.02387766965, 35821.369071837114, 9606.606135679664, 77600.90198286649, 57465.44211396243, 66687.09138659317, 25799.589023738168, 79752.03673429054, 65589.21610622753, 21085.101568247377, 34760.31003742584, 54718.086683772046, 48691.45401581169, 9963.07681778792, 73622.71157721063, 76484.80736093089, 75268.10919248332, 45209.480201477425, 20751.05246073825, 60240.42986733342, 32800.880761804096, 28262.23444644559, 8667.32915299383, 52840.477905650485, 20165.6870237078, 91932.17428925082, 85514.23145850851, 64136.902321182446, 89060.68558793976, 9331.316212919472, 19166.55807065296, 83764.74423990448, 9566.607475490662, 64672.146073880045, 20535.53812899175, 25362.694132243003, 84124.61948652436, 43507.949440384364, 95909.83803077831, 45013.89938017484, 86216.20700242644, 35797.61186037702, 53638.335395232694, 92745.99967218313, 24391.895570513534]} +{"id": 1368, "vector": [5190.940852115411, 97135.3527608582, 69270.79897448853, 18907.250807597386, 22137.835844784193, 48960.98902871554, 29770.060446448264, 36702.66690978729, 17990.222789338706, 84034.9804733935, 44661.57931897744, 62286.640485522126, 91778.88016693166, 76536.98615459325, 69020.05494900742, 19615.87511533719, 1744.7412473081126, 37452.67888159172, 73279.55659220932, 42980.48962937373, 58328.57225063816, 33418.2215599208, 14453.512177706862, 68098.87544295243, 85958.0622884685, 97144.11206406841, 42040.64523187284, 86677.18022294351, 76295.35375047683, 25866.816633722567, 11487.925475973216, 82112.58923350617, 67677.28344573533, 20963.660722589928, 41584.88873140702, 15960.87160712696, 39720.25589620496, 34669.32970727912, 15138.441805555269, 50242.62500803386, 40811.82119787289, 3264.8760323209513, 27913.188973244585, 59266.01389601366, 16255.437348978186, 45659.68503301363, 49037.55843465008, 70400.99336859003, 96323.77940952237, 30384.077745473005, 17208.64393940539, 15346.17300849851, 24379.092054359066, 74493.02286292065, 67215.48822838628, 37768.554975678446, 91236.82902032306, 14409.223248568003, 65728.88349873424, 73927.67927299556, 28414.582945189904, 33287.81722812691, 7458.983729112456, 10085.242991921683, 94261.10342722316, 93813.06355595784, 72104.61397713466, 74912.5606832133, 83741.23998467633, 3353.2488636701996, 96304.7165423088, 91564.31490200959, 25905.893908641585, 9072.145098426165, 11076.245361984184, 55412.90553354689, 28640.120652656497, 4008.274336181983, 69260.20464779763, 40125.06403518159, 66043.98134367516, 89734.17341061929, 79182.73940503436, 18259.886896765485, 43337.575760237276, 30020.598016423985, 84632.4030755068, 97752.17474716448, 9496.88036720886, 13743.610208382639, 3035.0351244054164, 17060.530799982775, 69106.42149338547, 54194.82186275937, 12254.190032877144, 54434.79301438029, 35456.73434938789, 40425.43092670071, 80144.73180925667, 56971.58632169219, 37796.18514701568, 88166.43814052785, 33316.14332786707, 31933.59164539319, 44707.82998730345, 55219.69974818094, 75433.45765792046, 24559.152268701455, 1040.942834660774, 44462.084225344464, 58813.5020479715, 42742.37979204802, 80329.48761461227, 97741.72733872184, 24193.451967622048, 88562.32619434148, 8448.298691621758, 26226.424495774216, 49271.2518961736, 34155.34660718303, 8238.059136564347, 46219.39130674298, 91686.75321060327, 53051.64671328353, 90547.08101538446, 25133.63164443918, 80941.12001190505, 47066.88809114338]} +{"id": 671, "vector": [83517.15668914959, 66172.48853879594, 14663.554368702093, 12150.919532037153, 21367.892159601488, 13774.436850538441, 87202.445616221, 44083.02706553888, 58706.318191265906, 8686.307066755295, 41089.1151244468, 16120.057641562713, 25221.963292824235, 26388.88649122174, 1210.5365884052533, 44748.89932520653, 64920.842381734714, 80668.28323412337, 83585.88189476298, 39726.46253643529, 10360.85681181984, 46226.129175119066, 4974.303268137836, 26910.36248222767, 99969.49841242765, 87571.3383091439, 7965.644374754633, 93774.28127013707, 65322.00125537734, 75197.81110832968, 7905.375387321811, 97460.2479500734, 81478.73681996093, 7289.411595021056, 6637.7185384766, 43465.62382689483, 98779.99282538304, 48339.007940036136, 80962.84094381088, 3943.8288163436973, 79464.52214988632, 40486.68790228708, 43143.6099063306, 60886.582744427906, 82037.66308666203, 83125.75254124953, 20176.518815404444, 16963.155075464965, 13309.48905248034, 98829.49312289043, 2817.1772476445644, 20281.62891864018, 63407.08856821677, 27848.56564435243, 52737.59906103825, 19437.02243241182, 44881.36384322013, 84724.63779782927, 56699.35971926318, 8181.886518218795, 9386.914924503686, 90821.9433189295, 19169.750428790965, 17875.3284144585, 49273.99732048396, 63992.08583429543, 8081.094089712604, 55114.72271768951, 4165.203648809002, 53686.713632361716, 3156.6031965674424, 54292.437941685734, 8726.527666509077, 72535.81869721125, 35783.632245922694, 32964.09443329224, 48484.60405891307, 25600.386005238397, 88260.37516178883, 45767.83763880621, 76022.19554422762, 10795.921193702507, 44836.613919358184, 34258.09155588373, 29274.529215804192, 35945.01652048052, 42154.411981921, 63869.70843410855, 33188.02273762594, 79241.56308960884, 20018.511006823748, 1487.895429431596, 18368.131262951614, 74262.89626750704, 11240.67270356497, 36604.98652571851, 47827.56197490214, 29574.65771181754, 40233.0797353262, 1605.727610124108, 43873.11642224647, 40189.37682281156, 27442.112379603568, 10044.06478528288, 6868.321679871625, 84825.1581787181, 74997.63795185911, 37156.57076236137, 82418.29270903612, 35688.80732327016, 70814.75952934954, 19524.213860154483, 31346.354595137404, 96891.55900328427, 65222.689048225104, 24678.092602086843, 95188.99045420707, 13955.843165666749, 52081.029341318565, 55725.3550419449, 90225.4189494041, 57289.161272849175, 94780.22135947717, 62489.06474097564, 78171.59189177492, 64034.98040627125, 84116.60305705002, 3668.710795163033]} +{"id": 1965, "vector": [26944.898591860077, 51757.3609425491, 39514.365081042, 82331.60671767968, 91978.63346142793, 44176.38045677026, 51347.6146769343, 27277.392088966844, 47234.53164685351, 42421.84620797782, 2300.4130032253433, 73942.4012293125, 61859.02156848082, 9000.533163029322, 26525.3159743876, 4807.486359796598, 32583.53689956709, 69354.20286763691, 19770.595187675965, 95437.3601681114, 64620.516897490554, 505.35396538491375, 42762.68969978038, 62110.98566627104, 76582.35348272733, 73306.09523554826, 20530.174919488487, 6581.389651074387, 37016.93243325872, 65706.23857676543, 23785.88320496573, 9445.90224281504, 87779.87811243474, 34874.2527035754, 63148.256552667845, 57751.59684782915, 1833.4234295609208, 78865.69358248956, 1021.5786564813034, 85888.43246560983, 26484.42006342805, 80457.78979695817, 57612.72314893946, 69741.0620156726, 88836.30230300428, 35338.50732095165, 80760.8899593557, 48516.20416137614, 42411.87367942824, 68430.12931431782, 71271.75423347994, 26838.446294936635, 30126.208743985826, 64139.61340137777, 80836.02956368972, 63456.02553052004, 58564.26413424652, 24803.8420238321, 18746.330752975526, 92661.27346020288, 22954.134666753147, 80213.34591126246, 52794.2035401365, 28185.706896021322, 71940.3327458685, 84759.11623384058, 23158.623655646337, 72854.75089287257, 45518.87533709454, 89183.66417123738, 3641.615237042317, 78482.70488481282, 92348.20539727175, 17653.24721865804, 72477.14293167533, 20968.603466984092, 94941.01123479413, 83037.76317311502, 69411.36533722267, 92776.05545481697, 35431.261944451755, 2400.2546490833442, 6817.924637454442, 35209.35927605628, 61475.07110302696, 54232.570010340954, 81458.54829806647, 62540.40989962787, 79603.27097070334, 81966.1225880808, 36112.249754629214, 88451.34639399005, 55519.24877100159, 7299.762804111942, 27774.09934634768, 87179.49642947785, 19298.74850389811, 97742.45345243743, 27575.29291228058, 40138.79314463079, 79405.07006737891, 38253.537993756494, 23698.674244818474, 92918.89687518663, 51192.175682731024, 47445.8959477737, 85068.00787898563, 61569.32418691397, 56087.39263889386, 18954.713147658542, 35988.39701348641, 36371.19756841253, 33317.19974570957, 68897.72428100382, 78285.96191991934, 37821.91437539427, 55855.355005275575, 7856.6019842763235, 30226.45460435529, 89206.13533860643, 12144.363230686806, 80451.88119756703, 33022.63923380474, 69892.81520351075, 30193.088744312357, 17358.451912806715, 32467.887893497584, 52987.08385054445]} +{"id": 1339, "vector": [15341.721163330047, 96946.25250214672, 55410.57905592259, 14955.348775142475, 93087.99076114401, 66722.35502414471, 20140.506325703824, 7481.383972651323, 57629.88125145799, 89969.85493851127, 13138.59347661951, 86190.48596623074, 88515.25520891101, 3445.265020816468, 76475.24798919422, 9242.173637580952, 34103.16094960203, 41216.439381818214, 57477.639244478894, 67028.4722490839, 37948.346319119875, 59652.39518554833, 48802.965646774566, 96017.14436591667, 8043.953065140575, 36524.79233082482, 43298.17186645203, 15465.070685303306, 24336.935649106374, 74587.88834442041, 59395.7387497983, 8235.272480167976, 66960.8992202325, 21248.418811078616, 99426.699111559, 57770.34650820949, 96365.75722036287, 27968.260869195405, 97633.84327209856, 13489.226208739457, 45432.769755967805, 25647.240610690304, 43805.37661122319, 11053.434071403733, 65827.1959920311, 65909.36342298976, 65035.12970898601, 6725.899223134291, 19514.2687100321, 82144.73451625083, 59637.05607871045, 57443.43765296347, 34348.90434358955, 40455.50118897906, 40592.21488069208, 50615.26914765322, 9259.26792391173, 67622.19857434859, 81407.628717034, 82801.7222130891, 52017.6293158076, 40732.070733038054, 71736.93684679254, 85504.85222645801, 13253.86657496812, 15477.750165169668, 10394.96655934018, 94600.52718183998, 57992.95518541122, 10953.168496258158, 98126.48034060451, 76177.03249616042, 18636.82628696357, 1421.500563338096, 23058.07039975184, 74721.9871048612, 97014.57666958461, 92731.76362882901, 90558.59414669208, 8244.327415486852, 48733.27398520824, 36060.730582692566, 36334.879232606254, 37812.50312888682, 3194.8431283358227, 56095.68857708296, 19309.14240490208, 25162.886245942784, 68352.95983164279, 95546.46244197855, 19832.172337145927, 63697.01765655323, 71101.12895546609, 70286.14429074347, 12099.340923798352, 86062.43896249031, 74292.92603837381, 21490.3905384772, 15360.045042202297, 53713.73125205761, 2859.9953509272245, 2260.4518152573137, 38304.395577551964, 6167.615014637029, 28683.796946602157, 24006.862793123062, 80609.20899068858, 48291.42796668635, 695.8330670291391, 99788.03051585409, 81109.14566279108, 66163.0284779674, 92098.25011728809, 85252.49599877329, 63695.53387172667, 72974.87783744275, 7705.159975682674, 72116.96154808291, 68512.05258026085, 50863.667456063944, 91057.80731558584, 54455.09900378136, 91412.12173192111, 15501.926171988056, 84331.68272227356, 14954.397390092534, 23043.86157692375, 40780.031578695496]} +{"id": 763, "vector": [28213.866565927125, 39553.37703650037, 65916.69567688019, 71987.50977394755, 44971.18473466956, 15386.140412525361, 95424.36391733859, 23331.829996821307, 90565.78144140771, 59570.5551129084, 97980.24881066721, 53374.04907545879, 70585.54018075754, 60436.25699865419, 91242.04007755654, 67730.88775069252, 71242.13681582708, 8806.918831228682, 77263.9191314017, 81518.76998898077, 68538.0611470265, 21662.092036706083, 8476.571057599402, 8690.316994010705, 92032.51162874165, 57387.67942732522, 99454.98237428155, 98346.0172038538, 9366.328383771417, 25978.77160095633, 98606.86939113382, 13204.175950186136, 80965.30900840428, 20511.971698089583, 66818.9876517593, 39646.170356395196, 61538.00711711437, 85161.89022004356, 40239.60274314779, 48448.33180345982, 44378.67151183003, 5593.285469442922, 34085.589033270335, 47686.07218416987, 96484.62433153491, 1531.085935951604, 77921.53916199191, 81109.00891102215, 42178.23669117725, 20475.762538473442, 63696.99589312247, 53158.49434049361, 80621.51568280568, 21492.580448347577, 97231.01939475881, 60386.88818456836, 30468.978891599352, 79684.40946272714, 70252.96324214431, 83712.52924631322, 97611.39270339631, 51137.912990110366, 34971.380931103944, 67999.56363355555, 22366.84420791716, 88286.52011469645, 20351.274572886847, 46161.0478729686, 82813.75556301961, 98601.8330707154, 33663.452026732586, 7823.951895582837, 79422.69180077432, 58335.97809012416, 33542.731313444616, 8528.736360243494, 97894.9990076894, 54642.83396259118, 13662.677227918019, 91726.6370330407, 55469.67687960943, 58915.642306624686, 34708.407973177, 20156.258078776613, 38656.99122386401, 66508.36199621153, 4612.958255220135, 48818.70840371376, 73889.29996178046, 84775.50514467139, 4629.390714068948, 59971.083878147234, 4234.228781951743, 65795.73815697696, 54006.25916078383, 51560.194039643204, 10515.932879105172, 92063.65735895655, 76779.54532269725, 10023.40675159309, 41649.79530435294, 67813.26050761963, 60432.322381438906, 82324.87063803978, 12614.947113585551, 68670.22146826332, 22060.946481546907, 83405.74467198517, 37169.807988002656, 55774.916350537984, 44437.002613028664, 51121.086191850416, 33001.806786085785, 96876.17162233368, 5185.891558081579, 85267.87504899221, 47684.538256820364, 63294.38740635815, 56134.49779323828, 61787.351337961685, 41595.686764545135, 74695.09483578055, 74838.38497671415, 78951.0179590195, 21478.95796660727, 11085.238416138554, 59499.76323533428, 31286.141996227103]} +{"id": 589, "vector": [31890.363766394094, 90099.43962127522, 38197.57363165367, 26072.38701717496, 47106.75124004456, 6418.934671346244, 85521.40199249514, 32313.478390625172, 8692.599744632656, 93508.04094892908, 69444.72922978293, 42215.2854727203, 61602.54268968853, 60198.38680630034, 11490.173105265456, 29374.78339438718, 64928.272083960284, 69938.5193584434, 40099.22706639743, 59660.28805021617, 82523.61361524767, 10537.340558538222, 84798.25617905713, 82746.276618839, 81169.38241807825, 99986.2351441868, 54263.82574461671, 82286.86551139898, 20694.318364822695, 24953.65664433884, 45415.74860155943, 93714.72521441402, 77679.2985048523, 21578.016844722693, 46051.17090120112, 5245.723823453863, 22113.854069406512, 25038.046418152982, 15462.510804641726, 79375.75398621733, 5797.209934412328, 8132.584057697889, 7848.840596754192, 40346.44907659984, 47203.030983579316, 45248.202350569445, 69707.2034856079, 55817.18827828523, 80789.50540987613, 83610.11354795331, 54043.967070333885, 22903.389633916173, 75014.26746583293, 34203.56651203711, 63671.63528348715, 59272.477731490195, 16983.423689577405, 60871.780991871674, 48268.825161668115, 33498.39473917743, 68673.26643022912, 57066.36856022123, 2897.721329868963, 63081.6073350546, 9355.96887885677, 84937.39752931573, 49957.978330095, 72721.362976308, 9549.90830863105, 37980.18981245749, 30705.75675011803, 13940.753403191908, 55931.88487432191, 22148.37739091814, 41305.795331268055, 57927.59423475205, 75013.40874872723, 46503.224855886794, 33834.1031238557, 27119.055936050096, 15985.025496148053, 91419.69760149415, 34577.95213418833, 21801.314736103563, 76001.59435345187, 98180.89251922506, 56516.45986964308, 29969.18539577712, 33459.59177346584, 52667.256791701366, 95823.75050061791, 62447.93344018426, 12.048651312090985, 99956.66520315764, 13266.577972814686, 69747.08852497395, 24320.662970311525, 85863.36697296426, 61131.544996026685, 95280.01002210073, 20363.75354961303, 82839.95863458028, 75229.14384907656, 71959.70788350509, 26219.481884816487, 34211.79490091669, 26678.651495474205, 85479.46802941248, 31439.536274152146, 45957.29664262945, 48578.868402390086, 48343.1862358549, 73602.16325970854, 43136.49423390451, 45638.642342763706, 43172.356605812834, 45696.1156612383, 46063.92349364108, 87852.77970257815, 3149.975785801895, 88421.22857716108, 70513.26154857458, 2945.5015173790234, 84939.15311771035, 75069.08140322455, 46010.95098810984, 77202.45894783227, 71728.54694386314]} +{"id": 908, "vector": [33851.27119032494, 6920.529721128388, 99715.60592497497, 27192.367599668043, 96570.82528323082, 23657.135850022936, 31059.250737885457, 56698.3197362312, 25167.318966799347, 5743.7520905045285, 21098.20164616775, 69587.13780166174, 30697.927682994497, 32379.050351234993, 62207.82658295656, 38929.63783530914, 72837.88294058446, 97570.61798041525, 66245.15982267083, 33528.01409571959, 58825.8353077488, 26541.128123716484, 27457.792786617953, 37935.34109767429, 79883.35252795693, 41188.14122623827, 846.4374180241485, 70737.98116744222, 88838.09920402602, 91012.1996239341, 48607.258398369115, 5204.875327032232, 74859.73629184229, 75133.10154123322, 81668.157858702, 4965.609516976788, 46110.499319835064, 527.1175643319359, 58689.77507275342, 82410.98270912103, 85941.9565371559, 91009.91655764027, 91195.2489879394, 18651.454676127865, 52993.57328977969, 50495.43062636514, 97147.56634329555, 70946.44651544471, 73376.12184265246, 28487.59029562703, 4674.04375791809, 95975.74870696252, 76122.2304758665, 80416.17183956217, 85815.31383919992, 5683.011026442364, 49688.3996336123, 30340.091939299462, 87333.65364004031, 59096.503081411975, 15521.720392288551, 68621.94676289923, 27464.67836692903, 46780.906186139626, 86577.58059221478, 51927.59051740926, 2465.9511595138906, 90884.03647130945, 82115.85969142748, 65316.71459619797, 31315.632326707844, 58552.89588765809, 52851.077523139844, 18512.49683036582, 71358.03469145813, 90882.28387023481, 78087.79624749864, 60932.58003148743, 97151.582695696, 32294.58663711374, 26025.070752218617, 36085.00363989814, 31224.473057911127, 93811.17891355026, 36702.45989041018, 78133.10845949709, 68941.44091845793, 72496.68034979822, 4684.720777688378, 37413.29842536871, 96995.96022664687, 16925.98059959678, 95865.96352194459, 24430.738492088945, 12066.539752779881, 23584.72795289388, 7437.440261151285, 22477.6843221151, 44010.79366334154, 9445.523056902815, 95013.08784496888, 77408.05566840702, 48945.62611743355, 83722.0611166284, 54728.70964346675, 99978.64668651379, 71425.91918959354, 26909.62946572569, 93057.792105522, 36273.7458658565, 73422.37959172782, 43301.45052461011, 67040.06928711453, 94043.77752961229, 80848.05811156028, 14270.554248811284, 89238.94856973276, 22991.446405839823, 26918.713815714236, 58055.16066559071, 35560.91181384715, 56191.29188253314, 32816.805811041195, 95259.17635205375, 365.71738698800704, 48555.486017964846, 76281.71672477241, 29549.51844480237]} +{"id": 878, "vector": [54811.045903131126, 27434.228254819103, 99021.31685675826, 99683.8467289416, 62311.89644723135, 58686.5894410857, 84126.56942555467, 91498.18386414321, 98287.06963700576, 11567.9348860805, 65953.25522313222, 43054.69609827962, 25733.270509578888, 38398.74948268796, 92024.61842642915, 68273.56150656236, 85610.07060267926, 6141.683789460651, 86091.01714090172, 84138.27457734384, 1620.6477068433035, 63249.87514023226, 80231.4594361204, 18357.13054729431, 4134.083308741931, 8338.046148477773, 76711.5003639576, 14007.550825734672, 46781.240586180764, 46068.096267685185, 57303.79013361926, 81605.72346365692, 57297.18682194359, 78327.74150930227, 26198.916019747863, 90626.96473442344, 88696.95657349986, 81932.35856685771, 12811.636249175917, 55699.827298013515, 88644.94319654028, 57756.187433333915, 80462.32524092574, 84597.05509376117, 37006.11788899397, 3352.30138993603, 22753.814855658282, 67983.55711676452, 54276.45017327499, 39995.290797677815, 75286.28329962428, 66883.14229573015, 41095.65724860458, 77377.78692271697, 91588.22339499812, 51194.58351115945, 3188.2343124015056, 42551.410897322225, 58631.702220932624, 38956.30073318805, 19677.004913806704, 28122.958155007203, 97721.99688583935, 47606.19682646606, 35599.00976379532, 92794.08669095149, 90199.8286829288, 57013.15124660692, 17096.615536698755, 22831.21010439747, 12472.673388458954, 80654.234966816, 54608.02663518527, 89880.04115586316, 66801.60587536848, 1059.9139955482783, 79744.89329318973, 89905.47067037589, 74345.28628014188, 30219.797250222047, 40844.53278477844, 9284.290555270924, 65205.90769894947, 10254.56329596931, 92813.79711891765, 78096.7774318661, 14757.794733119455, 14334.335456826031, 76791.44715289006, 36798.64052159867, 87902.69613138687, 49565.621138648385, 27729.287258994416, 18759.6985075153, 60976.61858636728, 91687.32242029489, 50385.88762714661, 15722.282017056023, 62968.635628075164, 33498.520435206156, 813.7973580183866, 32060.539276247237, 89913.77968521642, 60850.17136962573, 28486.058004768598, 55387.80587236671, 83543.63815453532, 84132.44681623564, 45193.8614725043, 66489.91272430761, 57617.75326753746, 93710.45252100128, 52620.966836889835, 18930.87693663146, 93032.24857111415, 62825.5013182506, 14212.421076000302, 98886.01539122837, 44508.538904371766, 58783.51809791491, 55642.93748573734, 31374.880899800915, 48540.08103292997, 3459.547598316504, 52796.94241575137, 18694.40348694894, 98848.16786618413, 12910.667626482087]} +{"id": 1228, "vector": [38567.50882731842, 98886.59566809874, 6157.537597433105, 41004.72212568724, 83472.48830431885, 89112.79070891392, 82886.5741564465, 54191.78670613749, 84019.66647133172, 53036.96325780284, 79096.38479278015, 36034.178672653215, 13031.535417203955, 42169.15412637311, 3463.972229461032, 35695.56368690237, 66016.26606495588, 91465.72618467492, 90472.65989293772, 36626.78334114981, 81204.73530513716, 65558.66692505701, 33806.253796422425, 69060.98633022617, 14080.651344238737, 21015.638138715076, 96413.39836794107, 90216.88089769153, 68584.24850172621, 27192.01612793274, 7384.040157381688, 81237.47233795575, 13848.42401710743, 72149.42053827706, 11357.902409266208, 85223.84154265704, 16468.914694912117, 66150.4040424482, 91718.70936109642, 8782.221323901873, 18145.368856772824, 55889.35202203586, 35298.00848333299, 67309.79624383987, 15994.73228633711, 43883.88411914527, 96714.12059317032, 44526.52102405652, 85806.8243000228, 27445.203506170103, 30715.739518496175, 9210.317841068238, 75956.38946453799, 90160.61603341758, 97068.877889541, 60804.52236358007, 87132.86495390986, 66011.30943343716, 65522.102260681844, 29057.97450630049, 33846.43309601599, 41493.865113535314, 8210.090321359497, 12216.168773243418, 81577.56756584349, 94558.75381959103, 13338.246430849766, 13928.998217723343, 20010.06588087516, 85108.36785261652, 46814.06461071746, 71831.02320104727, 28013.337763508982, 97745.3272968838, 94449.6657798815, 41858.73696753579, 66364.27158665135, 50616.33079056855, 21395.12161765046, 63368.986975546824, 16260.527389715118, 23994.256030285156, 10283.255593656282, 74696.72694687404, 18812.45861227432, 8542.453265902328, 73960.75703700588, 94575.06092483083, 49235.03602711042, 70996.62908875405, 47357.40211270948, 21460.64117926221, 71366.14108718466, 69640.83866298373, 38056.35885431115, 73195.17022413213, 41459.76613519847, 65165.79084726329, 81046.04436375177, 67804.48368573227, 42121.77364407048, 5136.8609187605, 48320.6404751531, 23571.975114565612, 22283.948569015254, 26615.84952564582, 16087.805157553603, 3758.5359686798993, 8625.205596722519, 6202.105283844195, 81583.8149639291, 85787.53086271077, 95786.82313744705, 29808.805450275388, 46951.40667048656, 34456.34105770269, 17236.715338679754, 39413.378432009195, 29248.55467972344, 29757.722053934798, 52296.01308681209, 61016.75421792672, 72683.25292450447, 76069.03872831096, 44660.297476821994, 8979.463870509451, 37520.797661709534, 27245.441915317937]} +{"id": 1758, "vector": [30602.829922491914, 80425.0785711012, 30219.77148418893, 48893.99474316556, 33077.868692155476, 70151.9385338555, 52879.10425007518, 59099.42482981473, 90019.27299053082, 45491.167217158116, 98444.56629752094, 54573.97946594713, 83639.26200302025, 56883.065396596, 65125.75219205562, 79339.858611938, 17695.679906645524, 49582.63492278141, 51911.561307549295, 25947.25516359103, 46291.167731001326, 43286.89318514488, 31864.86624481507, 48063.522844105864, 50616.39310640016, 27740.813260521347, 78587.88089330077, 81741.49637322614, 74420.96241507518, 78439.26810045203, 81532.10760077043, 46197.80563723306, 8023.959859654639, 6458.870546782969, 15395.91001431131, 22871.331521691198, 18438.22006344974, 87542.1427420896, 25187.813718363785, 39733.39719691674, 37852.09957509196, 85865.96978036578, 89962.37834597651, 47397.08771052626, 22091.751127167958, 65429.67145928366, 44257.68759779346, 15662.618761450487, 3728.565789738991, 56732.93825213331, 14401.02313474928, 46071.445855966864, 45196.489690655195, 62501.8550288145, 43410.602065952284, 44390.66265252342, 3785.541280369875, 70658.32395952527, 26361.46091889885, 70374.09214131055, 14028.439272122805, 35131.78013878674, 75844.65043257408, 94523.09945655447, 55688.49396771625, 46550.62097457677, 98107.93138918902, 795.0557928746659, 95355.71975850439, 66673.18855851241, 95341.61845885798, 42752.22246113356, 90373.99758616358, 93898.48585546942, 22375.61245894405, 92014.72917294677, 20872.7041910169, 6529.835681778584, 44291.24358122675, 52431.842214921, 43880.611351406194, 36158.33856726414, 35861.69406609568, 96788.4681258423, 58615.835298867794, 82305.72113605704, 93939.26689239051, 33835.40184550683, 9189.546697466178, 67864.27414986245, 70593.09937274155, 27607.540907765506, 26156.101331179303, 76810.10504749355, 85439.62493458195, 45284.19862378473, 42204.64925986114, 30142.49843252609, 91821.79813148343, 38012.17804244012, 17625.523288392797, 98359.07849142728, 53257.906428831506, 92309.74322082041, 62541.569605455385, 48267.713666601965, 25606.712858823277, 65898.70806653817, 30008.050967070143, 54337.1780982283, 27751.779647282092, 62574.89556050969, 4942.111190857868, 47461.21094813704, 96727.3330874788, 87998.388246676, 75662.29929823775, 26783.92154716034, 34447.987122821934, 55586.95151737394, 71493.53123946587, 23445.62945771633, 24318.22779680335, 5202.979036748246, 36224.56476609085, 73794.129395116, 92431.6514323749, 14922.922899597257]} +{"id": 1999, "vector": [94060.99321111766, 24965.114063559824, 22748.18469068205, 93779.61339312892, 86316.21316048752, 60542.49037896229, 58520.74935506078, 31882.91687013769, 19656.50818868452, 78385.97756691024, 85717.4037667635, 82607.03899183287, 50820.548977626044, 93927.28674160122, 27075.419591816728, 53699.91883288148, 53046.1740992895, 65191.43628674995, 23199.858174547862, 74005.42172347239, 56701.59584720139, 43301.58392070197, 51200.24123313706, 38804.618908577104, 15907.962248480779, 44728.86586281379, 35286.959498742355, 30899.62021138183, 99974.90100963494, 42711.6224858181, 94820.07672619088, 36751.703789496714, 13870.644375012498, 60834.50053780254, 5218.310212301669, 38361.495216104355, 38032.168038432035, 44362.18298781176, 85829.97457152882, 90008.10827542758, 34930.54679036367, 3049.555918199065, 24941.873683874204, 53002.61945664726, 28162.17688834687, 7537.3038938737145, 70674.1224487831, 91803.49643586925, 23393.828291191687, 68219.2446225387, 31656.225266859015, 51447.53872399873, 71451.06454302769, 7378.327320276323, 83967.43233059002, 18346.948841056244, 15399.086028742093, 88800.03689659003, 15890.918000834208, 72169.39385506904, 79463.20735192692, 37616.461164469205, 84677.43478686741, 33404.34227628244, 16851.446449499454, 24657.15329505793, 63155.84888913212, 38322.125448079336, 55944.05892177905, 2510.627576526292, 23233.138897296456, 38577.59772817782, 99676.99485304102, 23780.78861803956, 37146.7861516206, 22369.35784403492, 42345.06157883508, 86794.95674668487, 14694.914942907179, 47635.22025045103, 14203.040040389447, 13599.329366295788, 45155.7042526146, 41826.37253330707, 46709.458516728664, 23859.47025865689, 65322.1859913765, 86091.43169791375, 96260.51871160627, 18277.060796606103, 20695.318544335372, 95020.64505622092, 40619.23584092084, 15648.162656143239, 48222.64062749805, 94987.24431620586, 39620.456816121085, 11922.966055262985, 91692.95663463285, 51117.695139122734, 51673.757090140185, 73396.53899479177, 88664.3204232933, 29932.038688482953, 18545.994378115673, 23390.791412211885, 58107.48500018591, 81376.42459356786, 61698.70908322482, 23895.821289127005, 11925.435278501629, 57599.535176936544, 10993.303615362604, 16645.561913580863, 58713.71357402225, 50834.845572462815, 16188.49701838523, 41007.30953935102, 29128.521845303425, 29147.743227672618, 51682.305880613334, 24117.78351219448, 4536.1883848106245, 72755.04112701958, 39905.24857589516, 23112.187536602258, 83487.6941650048, 85192.1844989409]} +{"id": 826, "vector": [44497.26762798955, 16431.44030626663, 42221.18545470253, 81710.04669372803, 35986.12768360302, 40392.52469931117, 85769.02727169366, 19199.680922577812, 87997.20582487535, 32991.78997471996, 53640.97856273878, 93847.32564233002, 2026.3851542657974, 8769.76751534152, 45629.84000443969, 38869.55955886516, 79411.98674616955, 17674.991173157607, 97005.22814953022, 15014.147272333055, 36383.46299747752, 43986.05617743537, 67140.51191155557, 48885.43604166452, 89202.32004084947, 5111.147417814277, 15317.721072471324, 83460.17766631665, 1272.5519167510636, 81845.76597112931, 80385.97492830225, 81817.24205052153, 68022.37620372139, 20287.666494693414, 33651.195174058266, 90158.1145818068, 79778.14153611481, 41033.61067187211, 26384.806594151178, 24592.372319194423, 98350.7140914485, 25323.585217928623, 86893.36441353474, 44885.244100636715, 32157.435397491452, 1304.396530015317, 94406.92679292706, 37010.4456328863, 40600.85768364021, 99453.79855620276, 46536.96296002407, 85597.95167865843, 47902.09652696675, 62115.312830563205, 86668.5003899853, 33805.64417286053, 69399.15397444357, 32278.733701748406, 28030.620331177168, 92913.32874534653, 35669.458049990644, 81454.43902824563, 94540.29748444547, 51014.15923752616, 56197.936735583484, 65577.94720070332, 68992.6482602087, 2864.0454653999445, 23017.40472404472, 7705.166981901768, 81421.3646071129, 75391.97059680065, 69133.56508991057, 22042.6959737966, 5978.913211983927, 78311.71436361353, 95895.97977807828, 73901.88962608704, 42600.77696910908, 70860.11307601069, 22624.397909626925, 84720.5886648809, 25131.925524742437, 10053.883669202834, 25855.553854819602, 48034.34070135086, 70168.06871553404, 21675.40073881371, 74692.11496207882, 41972.57989726989, 49719.67189568483, 50659.963816234755, 19767.509696865083, 5352.76378206857, 28678.54615396389, 2817.028207686556, 80550.95252273756, 34445.37288366951, 69691.90688494619, 3391.4495838022085, 60577.364817790665, 6129.534561110239, 4450.272687409362, 30757.169573258147, 81710.56782647537, 24620.804124788887, 84692.62972849421, 31782.024238645547, 50784.42056433937, 76901.07401337067, 12630.649050724962, 92647.37482698276, 2914.598222205855, 95968.83316764818, 97564.91864191776, 19925.797882301267, 4192.524162040711, 90569.27913761722, 29220.364189052572, 80132.19366508558, 22740.769437321673, 9340.527301598666, 96536.75146476981, 69489.16422295533, 89829.5372652524, 45746.1521215957, 90028.98554532441, 14152.091942453482]} +{"id": 1097, "vector": [51439.72083276991, 56821.798652648446, 68451.49835969957, 56180.83665113469, 54591.68113606983, 5316.709153256338, 8266.286060412842, 72142.76686962399, 18332.283892090894, 52950.86572652882, 97337.09578828888, 78836.96157613993, 40869.58324641406, 35155.670102691685, 45758.67181381239, 27834.005922948567, 64229.87703029612, 57291.825226916626, 65665.20324210684, 16836.385101453612, 86887.94642893213, 3106.9552726451952, 6173.875376063976, 23488.271035639296, 97849.01495338255, 64348.269725830076, 21192.800086414165, 26911.053147856856, 66719.86759858805, 16453.45040324746, 2204.3895554841874, 49715.20852669606, 71807.29571733408, 91613.11984329922, 44526.28164925052, 88578.65680544838, 70414.60380177162, 64940.27002636297, 40464.99277256001, 49700.65655299849, 58486.75960420868, 75261.7940260857, 34020.493921966256, 22066.499249234796, 94463.6480171243, 16280.478591852554, 24013.3803152504, 35311.51870116357, 4310.587736789262, 47247.2829292374, 13152.6260343787, 60394.58689857846, 69242.80105469444, 37792.58699737112, 93007.29201254969, 9541.22848971186, 59396.80644245713, 39960.46917534031, 9113.649559671378, 9090.93087408036, 12901.740556683893, 58610.092172512006, 38534.435604256745, 65796.47405058092, 21853.445116815816, 18569.385645814786, 29182.75014862107, 80288.42554636055, 93460.87931466663, 44031.888543049645, 90817.5809990093, 87087.50780684334, 86133.394098585, 50499.62237300141, 51087.90758533801, 3888.183231585085, 38832.43047745477, 85441.91421995989, 18358.533460541115, 74156.81677577089, 54477.06382444947, 30694.864296157233, 18881.484961066388, 39400.94258737513, 45226.84625054641, 21667.104614574007, 40855.203774227564, 90530.41463310955, 93483.11552359321, 24055.721342992707, 56900.34793485609, 62682.77931719073, 71421.09443529209, 25587.2169466536, 42084.25135378051, 4861.396734349599, 46403.038196528825, 16353.60146202004, 78439.85552267374, 79944.41068080386, 33440.298593213425, 95259.36976545287, 72982.61072671245, 6043.667620562787, 41545.25517622877, 47315.87169859671, 68871.11021723005, 17325.179566788283, 78459.14501919792, 29367.13056412993, 46018.79272492034, 80211.0498756352, 13000.234428145941, 76195.97945990729, 27343.177856362276, 83342.7450076815, 13083.704304319854, 90751.48872203518, 19002.741913756672, 2273.3258136223067, 69914.33784199005, 97761.58684064054, 39186.63416177415, 81100.05993135808, 92000.87735547424, 76979.41531548649, 51968.705821008145, 18468.738139803452]} +{"id": 1961, "vector": [45209.41188989427, 15011.968164052647, 90646.82843644927, 77272.2702065905, 40368.3738392817, 7861.613710005666, 6829.537698358712, 70346.45134359985, 5170.266831126746, 60020.33979270951, 15128.93624805859, 22302.08116365533, 78336.87483485698, 45656.762296314, 71596.05916752409, 84181.52083094225, 6551.3090810950025, 90654.56684505103, 13729.321377015734, 33166.72255855112, 74322.9159918006, 58444.83607644722, 61103.11484482597, 56319.46412248876, 88474.44025614008, 58988.11106131588, 25731.753686573633, 92649.00202812132, 70598.17383710106, 91323.3180616532, 7514.846233343964, 45998.9479735977, 87858.90910878703, 61757.00436339401, 87873.29293637074, 66787.7364992594, 37629.23222232137, 83266.12921870382, 96416.80626297437, 23701.441244027897, 27148.682770494815, 56322.71177092343, 1179.2128680706005, 83690.18590063434, 13724.776983638487, 63374.86001535782, 51626.57744254999, 52475.139013390995, 79167.5978497002, 59298.3242078359, 54653.565405841444, 33742.953775711656, 29315.863785987385, 78253.57588727535, 67685.88432761698, 66028.02735683406, 90428.13656246728, 26252.08936838517, 77702.70674550082, 51213.34822515167, 85045.05699468154, 64817.1575849219, 52067.3450153584, 66881.75466181574, 18984.39209063806, 32553.63054165653, 90337.12110926726, 76831.7235817217, 67014.66067153068, 45492.739797732116, 55758.45771342856, 28471.5853660523, 30090.47261299005, 79292.89094409338, 64776.569777617966, 98052.90849873687, 12185.781730361155, 49771.44394920078, 13541.683902372426, 23539.201110740116, 10640.36091797027, 39972.68752170884, 28698.860993848495, 29959.08649934792, 35151.44354559484, 90209.11103361739, 71288.11576481532, 35315.50485734162, 13127.495432770631, 48770.56966117767, 2090.56071336019, 35188.03508860839, 53654.02611111367, 87395.72684903201, 85046.8259685089, 261.1305556572852, 65065.419249050494, 59291.653641435325, 15412.416619723968, 79603.38431453376, 69944.42015902993, 48958.7907725447, 9457.522042648858, 13957.941479137182, 62695.34423209583, 48201.49277194804, 95451.4863099125, 33907.79417790164, 18678.182502631378, 39518.1640868138, 97339.13500880681, 31154.22932428614, 65769.5468466647, 57573.8955508928, 86400.43022634526, 56004.77055062672, 78910.81284978356, 96702.48674557719, 24796.93661735014, 91384.85104073375, 8212.147503093414, 69834.14212229093, 66947.70619965703, 51369.32878297197, 45839.84925622001, 14677.544248544993, 96303.66642376625, 34514.18629659968]} +{"id": 438, "vector": [28426.2907339083, 93602.60278274748, 28959.485749498348, 3738.063406236325, 29294.402563017164, 71179.13233886675, 4263.269980771233, 29580.5191085717, 70397.95609455895, 65034.28359673288, 55155.42201565821, 37907.9828243467, 6796.042174594708, 72432.30725610658, 92538.04723918367, 5928.031456370375, 39530.38121283572, 12171.33714833738, 41937.85456377449, 4338.588367242413, 96226.03396898633, 97019.66010809905, 53725.97911822171, 15736.376431021992, 31388.81658822157, 61880.334827457016, 18815.49957811286, 70557.52514291169, 36525.13103976004, 18387.637594133088, 10645.512617732755, 2008.380340819771, 41089.05145299806, 26210.050173632582, 64974.44980453963, 80026.64248610097, 4962.356744898367, 51011.91986714303, 88041.54972038417, 19128.207279175745, 97209.92199519629, 45920.653928209365, 15787.301756485662, 8910.697112992282, 33794.21530260814, 27588.508539743918, 21580.664719625755, 38731.312124407224, 62220.978665524286, 75679.18710600039, 4573.170370440216, 93321.9978456233, 38068.87233878424, 31192.627559416553, 90766.07364434861, 64624.58131489469, 10379.491446950384, 97318.94531226082, 15929.86614803371, 94655.73233765761, 25841.459622142316, 69915.55314916417, 1656.3914964291437, 65487.601611684564, 5319.099845978847, 80648.39257609825, 6125.114101503348, 22693.20460687938, 89325.28409076206, 2460.0146802075897, 92503.41658828227, 36487.06734217411, 67769.40911560597, 7656.428257654257, 56999.26949014904, 50359.04557704739, 65169.63442461082, 29958.486797445727, 34779.96918462528, 53295.05702863321, 67785.60032088275, 55537.90983999458, 36642.19560684412, 47026.22931082043, 5381.529624704662, 41901.26634465104, 12303.858978532211, 93537.36997320519, 56380.14426858592, 2783.9843271804666, 20480.137286855992, 58868.20327528888, 91650.57128623957, 78711.60112375242, 6770.359991366515, 91848.80701343197, 8645.397663550302, 18921.796977923, 29718.129079953713, 75634.45006209702, 18099.45928535147, 23116.02785432678, 25024.8426924339, 40799.78862166963, 82796.966804094, 46830.00851911172, 28435.24078463948, 54913.414667615834, 82912.90922452456, 37604.48871906104, 19280.734312355595, 15305.07092586082, 57984.88116435558, 53872.49572889521, 92298.42851405767, 91744.27937569257, 15333.970612019964, 24802.350730794886, 76723.30584739226, 59549.06891988907, 62931.81621738091, 90560.06292606523, 67214.38699291482, 20251.990918713935, 26147.58028711802, 54989.08626093929, 35267.173214741546, 2227.504162411709]} +{"id": 2111, "vector": [5953.531106157928, 52649.42693507867, 8089.976706779278, 87157.46325681388, 74188.76727830482, 67006.82754148247, 3812.4647805905965, 95185.26096122773, 2175.903509678967, 15315.57821226015, 29153.002359624603, 61791.86504932177, 26602.23073365605, 6932.967507928589, 3010.778580399354, 56830.217104512914, 73800.06745566089, 98529.5099145769, 58946.14138167911, 60435.773053107456, 20301.111684304575, 17931.853831866785, 2828.208393519116, 37270.92999442309, 20723.527101914773, 93774.27990061988, 19751.601384882943, 2969.8437645257814, 45397.397058163755, 96327.00559731692, 61321.12209776439, 40772.77697983589, 40269.81427270032, 47743.941037439174, 82090.48840622429, 96839.4831991019, 73304.35927341024, 55615.18094231257, 82727.13032552264, 36988.02019940781, 36373.39174808547, 90893.30736274981, 25391.196768651203, 10023.902954763076, 54850.70901503988, 81298.3177137528, 16116.700895524727, 4506.541410187903, 92577.074665615, 62775.796603806986, 52890.009307578315, 22830.22374925433, 97135.77509577107, 90475.35543253076, 40632.61781482833, 20047.53855020487, 31533.26238439942, 65021.677304100165, 40399.903036509335, 52851.49052024317, 91544.8932974817, 96143.25804393, 52471.410546401945, 91267.34924030685, 92679.56307687376, 97775.37591890963, 36044.92510873486, 55168.0145495394, 96319.58064945637, 25166.510925995157, 27128.335309100494, 42143.5352846672, 80625.23863102216, 5768.873406430708, 27364.510129448317, 42586.005047476894, 69063.45175589739, 25170.430860730696, 9427.182715323945, 77769.30196665537, 61367.96312783202, 72628.5156540765, 73836.98644384087, 75789.66750372728, 5717.911646477302, 38110.04934442117, 16832.555863392605, 18801.284755406923, 20390.84851893924, 30303.940390596017, 30952.70969812128, 45721.94150499991, 58447.060101025774, 37921.83422208111, 88048.48696757006, 57452.56908715218, 11157.857775448809, 99655.00442556344, 94778.62938182893, 15105.737935170082, 66219.89899277338, 88419.18771577097, 13684.537084744985, 46917.84199789237, 11222.303636818653, 63906.70732959906, 30864.399684516196, 96844.5813036727, 7103.440006788153, 20082.993079761723, 87301.94751033545, 46329.33412234237, 34339.81764620506, 43351.798568066646, 93719.22883506275, 43267.616200345015, 34152.19945653396, 60684.117490955556, 70735.3331644716, 22454.390750298615, 29226.11899373665, 80373.45240847532, 18249.514487893757, 80225.87172944174, 85368.22816834056, 39940.347554197586, 25952.536417305848, 96929.09495377947]} +{"id": 192, "vector": [59526.63564468636, 88876.50919244268, 44088.003394294305, 41433.01770451899, 14259.658636051665, 56788.964720253534, 74075.21410722386, 72412.72120763981, 65358.03269373235, 59212.560168236916, 97917.79908438555, 10203.487891687324, 51037.53699386521, 79300.46038744769, 58939.705336481195, 4807.992082260482, 3877.0172460330987, 67208.13056543007, 13720.226100915977, 8142.9989756971245, 62897.09069478018, 68255.28056239268, 86178.11107266734, 69835.17682320315, 41292.85323529898, 38533.49117118108, 76141.22932702967, 75943.18196879578, 89529.1118633165, 58310.45556435721, 54167.60428302765, 25080.693409431842, 18247.139816012015, 82718.58231341177, 9003.485346722195, 45781.323115935615, 85835.42661183084, 66410.6343497107, 78980.22444153177, 97823.9813433288, 23771.642578792318, 73198.35731860429, 51879.69821395259, 52699.76057766741, 67384.5540326244, 17352.794064966194, 66389.37049898383, 84969.1895516185, 88904.94936976531, 50548.2591782578, 48250.44529708514, 61814.16384342622, 71476.56872516908, 52379.60345555888, 24854.607698816788, 2459.443311322862, 58349.87400344278, 22054.916849976526, 49113.21051278353, 31655.46125766362, 49073.152401823885, 30775.74776452444, 83744.2675001623, 90056.00973218806, 38462.18722726462, 12915.212290667987, 44665.61574419789, 35100.13638127452, 63888.27296619772, 25819.9297144176, 28455.277041209203, 16667.116303781015, 22488.844604243153, 65295.28394360405, 57032.0318922957, 17611.70562514791, 95814.20253647825, 51794.21141692424, 14472.02691554963, 14953.642927443077, 27109.901063260422, 39563.93639081805, 55299.52252383259, 72321.74801450975, 20695.573095408603, 49043.60915926016, 99901.23793701516, 98608.9547928552, 38522.197810007776, 63533.26769959284, 36764.64667103726, 8251.235846879323, 86046.27894804954, 29556.03697216601, 13744.381085617053, 67149.57707721004, 5603.920268172591, 45042.29479417244, 8225.127445374137, 95627.79675568477, 77291.61673642251, 64110.29689327296, 8661.955273156875, 26328.571079394012, 68884.70703085832, 73958.57517924732, 71515.3030736513, 4813.643402636858, 27155.54221659414, 4226.5323273097065, 23090.182317999763, 51413.522473674544, 5237.16515444489, 19494.456456239805, 87890.24351648554, 54011.61589309552, 89369.77219682549, 54473.01450225731, 72175.49018821992, 8546.153876255814, 1645.920502239051, 14210.595478678079, 28760.301454937653, 26753.62215893937, 61815.81898828065, 31888.408736494643, 82797.91804151054, 72750.05157738109]} +{"id": 1944, "vector": [44460.410147958006, 13992.05776096294, 66433.75596991686, 93326.5386650315, 94402.41853797183, 33226.65862203473, 54559.299313192314, 91971.49342734367, 25665.46035562256, 84728.58752792484, 97704.0121565765, 78763.07140919469, 32877.353677878615, 10343.88449350081, 6902.112908819436, 84750.1129973942, 96746.13374280038, 10634.383903539312, 22697.899684095835, 46179.330394744444, 13827.6933828975, 26806.40522931067, 52383.83587310299, 40682.438968856186, 78962.40880828023, 45581.8224079466, 59808.53035518113, 27901.88162466678, 86124.38490524274, 62970.762418975515, 15284.072160418838, 77989.4302923292, 55973.52216899691, 57978.53360089172, 31979.479325546024, 2789.2827943598377, 76179.85040735384, 48875.34237462711, 48089.51067183471, 9786.056121964715, 19861.680143982874, 18708.022128101486, 25322.36780812428, 33023.72452721642, 60700.47001320834, 77483.17350777535, 36744.12994892423, 15927.75357340962, 56688.14125194146, 24420.016427733826, 7075.329243708639, 51848.60085237501, 7575.709120681673, 32927.590404437455, 57424.86967064181, 87356.0319863701, 76565.82290411936, 51416.769148087835, 76625.74314532816, 60328.24246244976, 71747.30009388895, 6390.704204480236, 13172.213206135775, 27524.065012447874, 50284.21502663122, 99477.95942265747, 62508.22832732012, 97277.80297586076, 65021.22180893407, 7547.832381250696, 83543.90430465911, 89116.46116501016, 81846.52148298456, 46291.777273683154, 85662.46665248608, 94559.79585325142, 48065.526354748, 71697.84760645035, 8306.1353328463, 24185.765048122186, 7208.7874114524, 69981.9706469125, 97718.54060155887, 41599.59493244448, 37791.53106141727, 34557.13841030158, 38227.81317110434, 73200.26540744248, 71218.54663421483, 60962.04854750582, 10758.818311874596, 96353.89789818744, 51696.453886521755, 6685.2353470514545, 8496.026648713783, 47990.341758931434, 66625.22817134058, 66020.72506509059, 76541.62186667239, 74735.71023307602, 28390.220611608187, 28496.780492617625, 39036.81948535439, 66945.81946102796, 65083.24752928073, 39379.153114981025, 43564.83631948405, 44778.1298396627, 15183.173980218979, 32009.567174123444, 94272.84658453698, 19857.92948080286, 83805.91170445034, 17599.967180059793, 79107.28585579684, 34550.073398557404, 87592.35911447692, 26106.712354174764, 43210.26034781698, 1195.1661734354357, 92962.64006545344, 67091.8046474012, 82886.64966732709, 17715.176742594474, 62078.58435004887, 53965.59521247015, 60056.85657965927, 67921.98705400563]} +{"id": 1221, "vector": [15833.59260477153, 15530.950194341574, 6633.337561565389, 6640.2287671946115, 44110.97319707905, 62155.30614679429, 85706.24530157894, 58974.36057395501, 25172.659810217447, 21195.92636655131, 12802.237238719415, 16324.557881714185, 3152.9817726166143, 48623.568001814165, 16867.184254868418, 86849.10729216841, 81112.970497397, 23217.269776135265, 3785.8449258424744, 40482.142295117206, 86953.41450270596, 84246.34031849317, 84783.81347460403, 55443.854102464466, 77817.2760315736, 78272.19201697122, 49013.593017799685, 92992.86096112258, 4104.806141771244, 48708.932127393426, 46746.67086330997, 47678.87536770899, 40026.132959772935, 27659.160666956217, 20078.66312114547, 12029.693245631379, 92290.74712799232, 25693.786798143203, 99958.4872555837, 99913.22449274914, 53975.47767875449, 31398.324229461916, 39430.23054978726, 30696.13047707931, 25472.901865136046, 5463.119236960579, 24133.674556373917, 25713.109097102493, 387.7888356565107, 70389.73203487885, 82823.05131323633, 52862.80589870165, 96577.65021083783, 59419.9843396959, 40033.57605006097, 68890.97495269452, 38029.40064527878, 34969.71164173271, 74483.33870611092, 55096.2178690553, 75237.60984679777, 60989.006642928056, 11962.460026620725, 16508.743472131904, 43771.26999593699, 9520.136741650564, 84881.33898780037, 93055.00891212453, 64825.8395659823, 89531.82192177954, 68354.78753993326, 60339.86386441827, 79721.48145009183, 43046.5614837806, 98572.35517103934, 68130.87248635733, 67509.2586784275, 19431.24591063986, 86502.91170426189, 75090.50463923397, 73918.6209312705, 81972.36876451943, 46568.43099572617, 78037.25652289634, 23108.44459265191, 92291.07258037034, 27073.24123802175, 28821.549455043736, 50925.30753292744, 549.3513682388374, 69641.6633752401, 46435.55660165983, 12827.611815362872, 86125.53524524975, 54754.58937328165, 2633.467313715787, 71730.36975284993, 97567.68675813545, 97778.61105669088, 11435.260605778285, 7591.872561166846, 36827.477822899345, 90239.40209542855, 3407.7471604360367, 21035.043466099847, 35673.760986675705, 40005.2567193375, 87944.04953914203, 96965.24080560861, 51844.30605698098, 79858.21086856812, 75450.76618918905, 829.9210101527121, 8224.435525333529, 56993.79934510662, 38682.435724161114, 81175.35908399004, 15249.47690538413, 46185.22284201235, 55080.64378764326, 81268.40484604568, 52661.498560351014, 33160.2897684537, 62377.10566242213, 27215.240410160703, 85896.18208705042, 74839.8899186043, 29844.89457520818]} +{"id": 947, "vector": [59129.26569859606, 47900.55287718199, 43735.12826313787, 20024.62101471212, 53129.88874033841, 16409.540282871083, 96753.25564026997, 56939.394308406365, 81506.71880648525, 3831.252331819224, 39514.216408753666, 49717.43763486777, 78739.79791487457, 49918.93854013386, 22748.068891940064, 30494.352999735074, 42104.33137653581, 69957.29393926014, 9979.210552451712, 12171.356154682644, 72383.29782726198, 89409.10253923647, 66632.23350105176, 14639.430563550715, 78534.26285840855, 92439.26639287113, 3105.7721931697756, 97264.08183980775, 98609.07551985829, 50477.907863535554, 13414.588412187233, 36065.98550800031, 37574.651997001994, 68604.99633907498, 48909.82258276455, 94397.70770640414, 75966.93219660607, 41908.3797794736, 71556.74858168022, 952.840952741596, 64086.5911034072, 21499.426856459224, 24182.59847478438, 52444.39399291262, 28858.12705208085, 76359.81393235055, 48670.47337758128, 11377.031182416553, 94597.7548317248, 15582.310627284058, 87996.45490888825, 59188.80758552195, 64135.35348536002, 80368.87617287107, 35827.983963421604, 30611.55087762848, 33356.075236738514, 93067.20481773112, 36978.520479274965, 57786.909448211154, 64904.67040611022, 75087.45047312546, 18962.622692894805, 32689.723395364847, 89201.45964043197, 39785.850184280236, 42927.09944258759, 52586.23534224297, 8509.313567817966, 76950.22438921456, 95446.31558297035, 41867.97778817972, 91823.40802972556, 31152.43451116372, 53679.90159151601, 12891.424681299757, 79720.1965373377, 1070.7027072538012, 3667.998156563712, 62367.11450145337, 58018.221903910075, 38863.119511311415, 70738.32974366182, 19314.66910242089, 42905.444602462594, 49717.652976084726, 16801.134024662868, 43577.76530402463, 3608.1950510807314, 36172.88510617444, 36110.03552547274, 71342.25208089077, 36942.95300208055, 74204.2261079686, 91458.71746012894, 52613.335328256915, 17236.69379054512, 24139.032385502534, 4730.02962077097, 51955.16196533642, 84440.14459334413, 66916.75104100292, 25612.108384080846, 78057.97330607008, 29270.858018262825, 11756.445264043281, 96696.0307629922, 61245.38045353254, 94301.98596811257, 37105.40269890852, 19428.705397144287, 9285.980034563701, 71918.1520525454, 4398.314864684283, 72946.69375666168, 56596.57708729691, 1098.2874008441445, 16355.420284155598, 62689.149650387146, 38352.84561925372, 1407.6441893442259, 38416.19092390014, 70394.17311589593, 64989.134914847666, 21942.105686056522, 24697.204067903145, 85548.10943331242, 68112.82279800474]} +{"id": 1697, "vector": [72514.09645926002, 66150.9588095762, 88416.2034775404, 49147.382990855935, 57109.78548305279, 72996.0478932932, 44606.67827676691, 46903.3848188659, 34587.66394851075, 12611.244129831544, 51687.54073376999, 64768.95300230556, 43891.64397418695, 11094.190486232792, 35472.714678081844, 11797.218652604824, 65369.65469052356, 26493.536918627047, 61083.74817030389, 43531.03685876081, 49338.612118139645, 362.44721334485195, 49677.777571869534, 68377.31801122754, 95694.10982896049, 68526.6371389814, 64158.4618702646, 45102.836458485304, 62727.48972495388, 62813.45426993394, 53273.47533957464, 58712.241731220696, 10914.801498462479, 54390.55702044842, 22738.15782772791, 26121.633091512685, 59577.36078064314, 82283.71523624091, 96187.59889146096, 6491.601219443844, 50693.757319814315, 94329.81497868463, 54909.89001003499, 48070.56259392982, 56622.10781902266, 64018.75662172225, 66539.54068072654, 92005.57623257903, 7929.205379401971, 87502.73669567284, 71804.29941734091, 70583.4862822475, 6213.096496031622, 11345.909066915272, 68578.94043799724, 69657.90925944419, 62080.06787096271, 80592.9978055102, 23615.074952232262, 17902.781414721692, 7912.368615696363, 90139.49131954185, 5856.6838455336165, 71223.12783583209, 32565.284179340713, 70613.18907949417, 18318.377079432026, 22398.023905143684, 95618.20198498345, 23257.835284675766, 75652.53048629474, 85305.1878496525, 61183.95373533297, 13540.158666495572, 7658.851136270373, 13078.231994434864, 97807.85491645291, 24517.991466034273, 19584.10359120245, 36911.19150765818, 86859.28512839424, 1472.9750536129682, 54554.13944036792, 90099.64769928505, 19135.512151389823, 20894.699734648668, 88470.52405518945, 76753.16033362804, 14444.811200966245, 19910.395412836668, 47004.041508220915, 42801.57253085127, 7924.147937038306, 5751.738565821795, 21543.644853827915, 18868.851324891588, 32834.812039803175, 25234.345521680345, 7141.271877134769, 63466.04466789249, 98109.13023914098, 32405.184011651058, 12017.746441046007, 4697.23416859803, 73788.79467673412, 57036.14286671279, 78610.88314989003, 69352.38807122456, 46051.1996384706, 88987.7118316213, 22159.252641427207, 27561.57088210356, 14029.545424008205, 31714.31062288327, 48654.3791084304, 64070.266708263654, 68670.96789571804, 68734.2070758134, 70326.60046659749, 72406.01534344358, 54141.692533715526, 38121.78371044878, 46364.58909197381, 52418.179692742386, 33472.47422543472, 30566.52201894633, 22649.217511934083, 93910.71618178528]} +{"id": 1987, "vector": [25218.183983389332, 44533.985744842474, 6322.190556237139, 73331.88160441715, 97251.53360899823, 25205.58305870614, 42204.7100768402, 94242.52230536296, 39070.58191102725, 83324.12744884881, 56338.64359027033, 47706.752061599065, 22929.92060397646, 24877.64281278323, 60720.831373666915, 57778.608607970105, 66274.96421431156, 83405.28111061781, 97600.81704887688, 59350.2398164319, 33972.039448864474, 79667.36748423369, 45534.781370795165, 15927.337514777595, 33200.414354767636, 42522.10977989211, 40247.1119525019, 61721.5031386745, 48090.57053694146, 74885.43546139363, 6745.275043641519, 39283.967676455846, 51647.35652569839, 61592.829147982164, 72062.39183075106, 604.3402143218412, 83580.9884923049, 29081.78709279329, 73643.91956464351, 9337.98211329553, 63049.66669668882, 30452.62916189856, 24909.589439319847, 7369.866086536292, 11823.699675607502, 6492.871742286544, 62879.12771518164, 21894.686605828607, 76034.22852004095, 74390.70642023972, 70492.78101855464, 67249.12022656042, 66036.49653843236, 16214.252734345702, 96700.29456505382, 93711.70261128379, 83665.3965120715, 51392.845648763185, 4028.350206798159, 87737.21275319895, 99215.71003310612, 96034.20900422228, 78054.84341829886, 20824.16242023213, 88941.73616406468, 69514.4064186487, 14620.809244888955, 23335.609184657325, 39955.55913958865, 36140.91173335944, 91666.57022040259, 81152.02536962903, 59706.574149600856, 17729.182687571098, 63615.64765352953, 62466.509971798325, 52304.051170741615, 29124.66128777127, 50148.90134039164, 46313.03680226754, 84537.05525582013, 80583.78146917644, 92882.39453308407, 34515.099246502825, 98511.50613841288, 72934.62416478484, 48287.41603715383, 48102.664117501445, 11915.80815505453, 1682.7715588909055, 60631.940230159285, 50237.29418918958, 11578.786453206192, 96746.08088976379, 85260.12132009538, 31627.467964945165, 95548.7322975171, 45999.32204280226, 72205.19236538338, 58457.60251028107, 38785.58673968273, 2397.853364345159, 4762.345306375959, 3277.1478389043373, 5676.831326671272, 78825.80826902147, 11997.301152859463, 87266.56445404606, 19393.377184287398, 26655.76757819379, 18368.29219819599, 92607.64678810052, 95872.04332467803, 20225.856168424973, 92307.4421516095, 86325.90104538272, 73439.36674887994, 14904.413699358543, 28881.16500736205, 49828.111314286725, 25156.301088160115, 73124.27079843258, 12422.399790473748, 15679.736315648062, 97612.72498393313, 94173.02377505547, 42208.392202771305, 26034.628716956897]} +{"id": 1745, "vector": [20957.974144810854, 92116.97665519625, 94025.03225858475, 31575.579867022007, 14411.762139876926, 93500.59691663091, 24256.03147209704, 54643.479776662105, 8201.02873602513, 82644.82421995784, 17835.948120263543, 44650.71002497571, 77545.93410925285, 19020.349679041716, 28682.336735716606, 35569.12521893467, 20232.107041453695, 37958.40196749559, 88160.21204771702, 41094.04729085313, 25572.97358355236, 51143.300872098676, 95594.47139203, 53252.90752840278, 14015.148530956323, 61974.25165529581, 22960.133693572083, 50028.937690359264, 9653.862769632937, 88060.20585582935, 38551.83531021257, 8116.38591555408, 73638.00428649849, 97989.92797838557, 30289.278273509924, 20213.039645346344, 69352.43758297079, 55426.71320151068, 83705.16031805397, 63043.706505934235, 26011.00474734266, 10537.088690920471, 17785.002399205674, 62234.54616916821, 92272.68483043686, 69490.08670911806, 23118.926554068643, 26062.730830587243, 74107.04067963209, 25580.806930753297, 2037.4047868864652, 40781.298513399364, 8015.73343831099, 37985.94636253212, 35172.28202335262, 75875.05091503306, 89220.51831817986, 83179.8234381259, 90747.54271193694, 72412.04851864485, 31513.398259012403, 69160.09324243873, 56588.36405175037, 55515.30391571023, 58426.04462363058, 40166.801250977755, 87838.20724056607, 95260.90708308644, 38083.39137710568, 69676.25429263571, 18091.386019302303, 60529.90255597361, 5824.75176914421, 66897.44979408382, 14562.437031347576, 70732.2375497594, 41887.39526471844, 66057.39374496492, 63310.89795544467, 96377.30617716977, 94512.16768279087, 41598.543427294375, 4937.586151023276, 1537.394290082883, 28427.09971635731, 33598.57542062605, 8619.01633246982, 19592.574566227526, 32754.359861569337, 10463.687636276076, 83430.04419209597, 47514.626228528454, 62885.98598232802, 49488.96174511622, 12237.03146102526, 98005.1870881559, 90059.85987727843, 27604.320427006747, 47049.49366305496, 72874.388268292, 34089.064020978774, 16276.68459556676, 81307.34125201571, 31023.52483906846, 82268.17961895144, 10614.692590375242, 97679.89091551062, 82336.05005838856, 43537.96854960688, 48750.42111607286, 63596.56963245445, 79112.03933147232, 27948.427004520592, 44161.920974212524, 50738.113638474206, 4912.7824928108075, 95998.33843355438, 94697.92230076538, 46591.76334994246, 68666.59076455928, 89937.41348554814, 21850.089269149186, 25004.449186672362, 52960.34462243521, 71181.2036443921, 15133.814410361534, 50476.17488418439, 11841.098466074829]} +{"id": 1869, "vector": [83484.0397356178, 81366.70343314171, 81999.30160603175, 15936.343819095844, 20356.687626598035, 72460.83221016987, 31657.68982277326, 14151.444065852615, 88778.29821887387, 17809.342362353233, 95496.56998437815, 12513.76455938269, 17257.026573409683, 76232.75090035136, 42419.61524082234, 92468.30354454856, 377.68608691430836, 18652.251830399113, 67782.82973718006, 3471.417410456179, 27806.06498630814, 41830.71739888271, 65295.25770412084, 922.027272357473, 34545.48984047517, 88714.85449981882, 82072.71373750435, 46350.04905917197, 52241.2566283633, 35856.67810860982, 57124.29216288143, 1179.0662822160568, 44343.00586715365, 79937.96379088785, 80058.88327955596, 92522.85575310903, 37369.62514646041, 92731.27609168828, 27143.74748949644, 34119.26609059254, 92093.39974159478, 10823.778826910979, 13680.796660433381, 5889.178256497873, 12223.484209009926, 924.8777389050167, 20272.799990352287, 62077.3902434822, 87643.97134696465, 71796.38525190536, 75433.92622863984, 69147.68699687315, 49938.06675428316, 45471.46054120388, 3792.347604236168, 28349.17459831204, 40373.04708998466, 58739.18801837995, 90005.45141463586, 45519.384609011715, 50710.50765666358, 11562.584143291953, 53475.26735678661, 71778.84196100594, 94679.79864616357, 65143.35402332417, 43310.84519108549, 29429.535671234364, 33153.86327677039, 64709.52079414889, 48568.97011197259, 84051.07364104778, 9299.391609015329, 26931.39850787989, 57439.43854850216, 8954.06619237994, 16897.56659996593, 99743.01485560044, 37583.18949027465, 49807.73559996465, 33220.28754056776, 60573.90895028984, 32438.092231449667, 76524.21552057669, 78659.10091149177, 75792.33335074381, 97547.98167770989, 36654.18079449646, 80890.54413393553, 55795.68325209093, 53221.7280879322, 24466.535228302288, 35153.919684257606, 96184.00831151033, 84583.21484205103, 35569.67679339007, 87158.36282622998, 97028.88600971992, 75659.74886306578, 28610.63701100309, 96716.20628300232, 6552.4319294035795, 10264.490800712289, 96360.5141368877, 11177.344544183388, 11034.916564040075, 38715.78896935228, 80429.28899598686, 88164.75675069857, 18343.24381685021, 11413.224811221511, 45039.87057380287, 51130.306133378836, 90193.80209115824, 44356.70542412085, 54081.61271912929, 71896.14350118005, 87098.50926571536, 56234.612759386306, 74468.53407836036, 88769.75851954703, 57382.209432900134, 46038.50790314678, 41528.760647333926, 5656.049844833877, 57586.84876478658, 78667.12459967023, 98361.17180821404]} +{"id": 1694, "vector": [59978.76007866765, 36588.66492433199, 96681.7972590182, 98428.68375386627, 83277.8251613714, 35583.47538686636, 28345.179504762385, 19441.443750281407, 55447.08915794192, 6355.197161588521, 36720.3859066092, 85530.83034262064, 38043.68413050775, 86206.2798293676, 80119.48952404509, 94415.65591691989, 40509.460777861284, 33024.657007275295, 11994.92445888859, 39610.015581196036, 20522.234040147847, 81190.24143262235, 54451.42025269044, 64842.06144121488, 83514.61579313231, 42074.30561942859, 92940.20190094737, 68238.61760221672, 16799.555915930854, 60420.520325489066, 62337.862039155625, 28527.695957924327, 24224.367530742296, 81301.21248780203, 7586.025061103008, 83123.22032757734, 27334.247522684473, 57557.663711644745, 38859.83614924021, 25415.543118422178, 19339.804642267256, 94441.43471089237, 22255.862606533872, 34704.43186925523, 87162.24893421764, 90552.25501584912, 56972.33579856124, 94119.1129608738, 61776.43709947318, 25299.444068737143, 55196.196220435326, 81158.90179905185, 47732.57370751469, 46352.54232820591, 20499.02094114865, 51627.77574291696, 93548.33100737032, 98503.58604949621, 66957.80452081404, 81056.68324290783, 16455.21102141948, 67440.51704762666, 75037.23437314513, 5737.242947944954, 9920.302898765709, 13032.789727964057, 39077.06528428402, 32495.375107593016, 52624.13457222476, 72035.30339103035, 56810.996810750155, 56797.49122987737, 15975.726283282942, 55989.929543742765, 27645.374605520446, 80813.5750455781, 45772.461684805436, 24836.44077640742, 49695.619433561325, 89286.78415106332, 17275.966916791942, 94549.72769137968, 22608.043043075442, 8410.903298735406, 29983.695420845346, 67997.58845741338, 78611.0892327005, 7483.867237957087, 10593.684796823965, 91800.68465322003, 83621.82293394725, 88003.23374292352, 64224.62907456558, 80235.62084352068, 25716.80089370828, 47304.81941436793, 45141.777218343326, 81352.00866847111, 50735.279328101846, 18467.874487629353, 8002.6867083913485, 1257.0619924249238, 56313.84988993204, 26493.515204719984, 5224.7177122427765, 2174.0410459839877, 65141.2213365674, 90079.95207489445, 52967.0944534171, 42098.202203801535, 53148.62957267165, 6648.20475266632, 53121.62902032545, 35942.92126563044, 31332.370984024616, 93036.17702035097, 70501.31785746028, 65962.91376541644, 74941.66164055072, 55709.91529886507, 63913.327038302516, 3455.1719693969085, 2495.891859211974, 98561.41768559552, 55334.660599594885, 78578.88498770089, 1034.0880144403152, 20462.548653077618]} +{"id": 613, "vector": [79306.88537302626, 74756.32116356048, 85614.23857484151, 46514.170628979045, 91125.23489408084, 60471.77118302023, 66907.00536819932, 18766.2051444587, 47947.78750185079, 83895.32416958024, 36130.71320626945, 79520.0791205481, 9586.56497214838, 94746.74012737101, 42604.438172360016, 67725.70521602732, 14184.842788702568, 713.0845785077166, 80671.79900664657, 43021.53508546672, 71354.19984092872, 2609.6251316430007, 89423.65297210646, 90483.78951242974, 64482.110878754276, 31792.746796370808, 81275.41402110535, 10939.562516316637, 24413.51531289656, 32007.25760053076, 61038.38620568304, 64545.339563882175, 64236.73519588586, 82630.5101911573, 33289.28711281542, 40672.368715740406, 34659.270805959284, 17025.029542028202, 46983.267865297516, 5093.143707667691, 43680.157350547175, 84116.8737665146, 59738.17886043699, 14163.10568061655, 15538.290955421386, 7150.913764295985, 26850.591035155823, 21859.099101657288, 18363.06871604736, 32724.161868973035, 97212.56736287616, 74367.24463979801, 34884.0112933366, 21964.744034677507, 65345.90183303305, 25310.524677634818, 89146.36928162056, 10327.880576302872, 37260.91657814878, 50659.55142834113, 569.8952131398927, 5196.654068202378, 89100.2762447255, 98369.72067255966, 27528.947518465964, 16576.380925876143, 71751.28325711944, 13500.359015349806, 33860.11761840155, 54814.61961989394, 34857.026918528565, 31397.098846186578, 93783.7342262328, 22152.05404400363, 93169.14521452227, 31697.40929999738, 78686.89316108706, 89767.49678754712, 27304.4246933285, 12029.977774470968, 43054.2576718418, 17078.17913015437, 53432.29976567616, 29746.33444620315, 98516.75795661575, 94939.44524802695, 92613.91416429693, 79189.69007947619, 50398.09169586894, 99202.99403817872, 55648.07073173098, 64986.69712638028, 3067.912742465473, 81942.67239105998, 72499.2576379937, 34495.52880982196, 26859.894402336715, 59299.1553276876, 66159.72398745081, 37505.74501691577, 63198.66213559823, 84322.68048431711, 86249.42746300703, 87531.86106023469, 57598.89802129194, 91664.32119783637, 6657.808847378455, 48036.6586500495, 97311.19899359813, 6557.548150786607, 29704.373159003826, 14463.40463030813, 55527.55262362706, 54097.12443177121, 41532.848188574426, 66629.50752867937, 54037.48757186897, 25477.066508236156, 63707.03496991617, 62163.69105200883, 95953.80665270476, 54639.86426744137, 60328.203439817094, 85316.05035205388, 35227.92514573882, 26547.459160682498, 91354.95190092079, 85054.08468938434]} +{"id": 1707, "vector": [20484.731578015824, 95143.28469802084, 10604.16156650168, 48944.626054916575, 52213.10692812123, 91611.9853862139, 12217.483272204843, 28976.56914344816, 61224.3614901684, 31699.734376747936, 42482.23785669794, 72222.70644821503, 68269.78445091764, 17185.90915631557, 72613.94312515359, 30631.599740209214, 76011.04851048737, 81893.60908273427, 69127.58999015218, 61141.062265492474, 20812.218257526238, 30298.40181713619, 31778.452873228314, 30099.499805593954, 74744.69877766113, 42515.66692921053, 49198.656420993, 19357.739883973503, 50632.93369417353, 53886.658350110694, 2511.644526273904, 35206.925507160384, 27605.220147842436, 44696.87146816562, 94352.48392851683, 61857.25614873798, 11766.76050487514, 58445.88818628801, 55608.986447367846, 51950.06671599197, 89627.71713551585, 36856.09614870839, 27532.486314056914, 75556.13018514629, 35904.72468873088, 44603.31459955302, 30452.915115584456, 71286.36752848777, 13804.927172463955, 57695.043393648804, 84138.74994309893, 711.8178014107857, 28454.578238216898, 6462.270863295883, 30837.765634904936, 90615.28809696487, 1149.361052431741, 39637.64898456238, 23181.86337831404, 14312.509389985627, 95592.44221238818, 13212.788660354823, 88074.71639711224, 12205.009257867583, 98108.98304870164, 56558.15677262398, 52840.68633761951, 41443.13478896977, 42059.20155909135, 95800.66779617079, 43250.99761172959, 55183.98208457276, 94276.1072980718, 56898.85752166261, 91799.57051125885, 45600.56543219827, 33793.546097050064, 75883.81254489784, 10408.61326584941, 88939.2355308462, 35094.41277817089, 92332.4088368672, 63200.18982740361, 4715.499884263907, 42052.132923572404, 17237.695619237635, 69266.94287167021, 85502.15153969554, 88286.17128873424, 7774.059908005726, 82724.77534829403, 9911.615643755733, 71117.9219917815, 98294.70431206415, 64873.77864818282, 302.54496597984513, 42419.15319846986, 40568.29436719387, 7929.095074935711, 1718.8620743604831, 986.5769928488977, 31768.992330350764, 40046.354302824795, 22023.996678085667, 77693.83185735154, 37171.640602264175, 75338.00001254259, 64527.816571310956, 5716.096935064996, 91310.58017378752, 89566.23045181092, 72159.28353278333, 54202.42869211594, 79558.31730057523, 94810.72568851012, 41830.548522585945, 55348.63339117434, 16236.477584932263, 42063.151827917674, 53856.369073146605, 89716.8536753936, 78075.17369275259, 89440.10136487019, 64452.046019683796, 85511.02657016451, 82368.00745694534, 99917.98688306504, 11059.517546523623]} +{"id": 1431, "vector": [11463.596134972198, 32431.086571774336, 21492.540831259343, 12887.953697084331, 86618.31319431204, 92277.43344467635, 14375.14694642561, 32254.776315538013, 75204.38817279066, 90961.21609740441, 15744.001857182433, 1731.2996128774682, 58311.16489284277, 21360.366892414062, 31143.0938499771, 2572.8380735127066, 5030.459144775145, 79603.49877305856, 2056.0638484727, 74023.27216997951, 54915.43661031155, 63154.41492352928, 17917.47594502626, 34480.64121545824, 73473.81963677134, 10111.404996097417, 93307.3977184641, 23173.423331894028, 58771.57668774608, 52886.05340630656, 13460.679725101265, 72351.32309673465, 71867.6490700007, 69423.76712369734, 63257.640570803094, 78913.79978177321, 19309.830243787375, 47785.82532988459, 42423.776353313915, 40692.88692501903, 8264.584735016655, 48649.51183606374, 10026.554706117984, 45308.39110764411, 39597.558679788846, 26840.774966125715, 9655.88798801128, 65759.65047218614, 14451.32259306363, 6568.3247194267215, 67022.84526489003, 30579.944582616714, 1003.3909299864252, 51517.717225820925, 29843.758005811884, 33213.83350960302, 33085.63879798751, 6479.986410720784, 43374.126057808615, 15474.812648932068, 4897.823066072992, 67537.72491853224, 58705.65416713166, 91333.37435688922, 49033.01682277412, 86897.12081884258, 89198.54453759582, 9862.691124165445, 61071.46253910339, 41535.02700434355, 5323.101968138455, 68150.88714515978, 31523.20187698182, 3222.930990167394, 37646.6359253374, 15811.301014135204, 12044.700099750306, 25657.649634415626, 98790.03539663972, 53486.999989853975, 63441.99443668475, 92373.22621074814, 87130.29519881427, 1157.8312885626162, 36001.81264336428, 31598.30771259411, 60285.877727732615, 13038.912770593524, 17485.371726646692, 56144.36710676814, 98744.1239482195, 7562.081390386544, 92610.21843923886, 29156.723466277756, 34625.824059496634, 49422.30975380798, 64909.726513711445, 82492.8590777332, 46349.7404534361, 44651.08537566362, 53048.12228936456, 52154.384506639675, 65753.93217258368, 97076.25241416803, 35689.60073776979, 75672.3167538244, 45450.33347407139, 58055.381427469096, 60324.16466456595, 36490.4770210816, 12310.657983514617, 87775.15619097442, 8160.62992214317, 5177.145638300373, 83370.61752475725, 22250.38213888284, 75668.94466750718, 42971.043708513724, 34809.52528989064, 93988.35791914466, 55274.28036310035, 27262.346959894778, 30744.64230483791, 39039.90262937962, 29437.61129746638, 85211.04568368256, 86478.7987982236, 51744.05516259247]} +{"id": 1246, "vector": [66763.42274253746, 93826.56458666257, 16557.914386741122, 50793.14188177001, 2275.924460189105, 71087.4571199518, 43897.95420729346, 11951.499486206652, 69404.75654899668, 44406.29484799439, 72113.14198337468, 17790.093125747575, 61607.63730552713, 61826.16098514308, 40441.504285685114, 86894.6562976246, 59182.419508766194, 71731.01717229579, 11224.706149814012, 35564.59696121166, 30740.962406142135, 65495.10746304845, 55825.43029609972, 91522.7232812876, 62996.178487768375, 7078.109092015395, 6221.350393121649, 37268.590563881764, 29075.128358861213, 86335.75233983334, 61811.44506582126, 77853.34594832998, 77303.81594000038, 27338.487524738142, 83392.96094531688, 79925.02340460973, 62356.12840768212, 96457.65137075372, 36763.04120355866, 14662.339085474974, 65422.28483170975, 50079.94655720473, 18634.198612536322, 69322.10075860503, 72209.21307346324, 55621.15268549729, 47646.9485060743, 81118.4692765361, 75468.81754447617, 87483.67899291212, 54225.63325931446, 23756.198692081452, 62355.70677113752, 54443.989815768, 43819.78824955652, 38277.963532380054, 80824.9705938591, 63121.01456349544, 80913.38662356095, 5127.938337759963, 39487.3600398192, 91514.94605409823, 20841.654766872263, 15009.664683996849, 64696.249927915786, 72472.8664811589, 65596.09217116695, 10.862703547764863, 81771.01268842029, 38130.78125683709, 59762.49588846949, 12342.84498397048, 67101.38854969764, 51227.8356302869, 33373.12690554203, 99261.6700751911, 54683.946984349896, 8034.771144224884, 91316.30416429423, 66070.0007290439, 92226.38165858708, 16345.801831766827, 78153.79903859994, 87251.60774317017, 83142.20368176271, 65464.11783663368, 55144.906459033635, 86500.3273121916, 29536.628287386557, 29665.295556081273, 69007.59588489747, 73896.66489692069, 5456.526101319048, 91463.5380182077, 61510.546778403885, 22370.998233556715, 15405.056464974665, 35042.598504159294, 12931.877373017263, 46383.98388358213, 59465.952470925375, 9666.752852131367, 62941.677936411375, 37064.53742444481, 7277.409980115124, 88212.14329794825, 78359.24375013911, 49065.70900824229, 80419.65610873196, 47314.787806844535, 62452.75872270757, 91404.34007076889, 24799.14544457371, 47372.657282452565, 63833.66788646096, 57661.183947827056, 30569.761637539817, 76328.82688568109, 36639.9236543656, 32281.70270863675, 45275.335896698576, 27890.619934786322, 99187.2525644242, 13906.776407640098, 54121.25740625375, 78742.0383945388, 48989.09518551435, 52791.340160217136]} +{"id": 603, "vector": [42516.79897713413, 47674.78675792043, 93119.20566921151, 48854.52490757539, 18655.581107570797, 63403.883953235054, 68690.954506418, 14900.482284748894, 99518.40133129491, 75186.87568249965, 1226.5192976050444, 26839.398907170063, 91039.07652059944, 84719.98777616571, 10563.12870727295, 36264.5482367445, 39346.921485430765, 18501.20213878771, 76830.31185212223, 20067.176750163395, 13064.112969202968, 74187.271293471, 3970.830434676864, 61275.27987445568, 54895.71642021756, 46506.14155335618, 38434.433708183176, 76556.54119584282, 2265.0368454745662, 16078.822141927229, 97198.97462110044, 22424.202278039596, 47869.77581594645, 9546.895969914725, 73135.64935322892, 24310.105471132225, 89734.69204929899, 63644.69035462923, 32282.082788167256, 86137.36252168044, 3909.5799196895873, 7626.750217889933, 48675.849190266206, 7781.787761634873, 23766.773003105012, 97787.78371028812, 79489.68615364895, 85106.47566987667, 71188.40970387819, 38330.484428586475, 23983.863439401608, 7921.306758875579, 55519.85987861652, 99480.10160515837, 88614.3299717024, 75154.70184417914, 76956.23748564078, 75539.20251842756, 28082.84259922439, 96388.70461512088, 4025.0111674237087, 15484.893435894819, 43740.858406670035, 99921.27291805865, 89798.08622967808, 45572.65290090713, 56377.560717917106, 84091.51148302676, 7802.377552925477, 16100.956005864231, 25833.063230194963, 41830.194709743926, 1210.0972139886924, 50592.357569883716, 5506.757425483899, 15507.597159564668, 35503.97299391814, 35705.05160799772, 60638.50889081928, 95133.39264175009, 19125.782250504795, 27526.492567072193, 6464.367552898709, 88247.28873129073, 78147.01899736458, 35184.563035054605, 4323.165342209334, 98207.4162173444, 88180.65965050616, 29551.894995734187, 46551.804107432756, 4010.950353967535, 30965.020489935636, 28845.760110743602, 45884.73522382688, 11458.671979732017, 56296.72416895133, 10942.047699086621, 88575.18822327812, 17701.807325003272, 28346.705552193453, 7236.217800125622, 11026.63153204545, 24840.73405845597, 58233.882443638875, 48095.39448383475, 73600.32552989169, 56445.37679885339, 55006.00969709902, 35825.84146678036, 99834.24255041825, 38683.69280241682, 33370.340674669395, 22681.633709707196, 89254.58958471577, 87721.91007321679, 94976.98949822442, 89086.9810537438, 94495.96394597214, 17451.41742098566, 35292.606025787674, 43282.70438342904, 92190.34566114322, 93747.46289676089, 61353.073484281296, 83025.8095918606, 49264.46173660301, 60487.54787020278]} +{"id": 155, "vector": [97871.73568473246, 61702.67914809489, 79787.59358363946, 79259.3348882096, 96706.9678697143, 99413.36983463429, 4643.811195915992, 2100.601393535939, 72538.09249853718, 60679.79810812355, 42813.96558595436, 71014.07042464126, 9393.323481365911, 80401.17073259485, 97986.15723241238, 32254.019938015954, 47626.737935255725, 26031.024582914695, 61264.91880914289, 53568.100669383464, 76508.57118358176, 94626.09124153794, 38490.13783232914, 59911.59598005652, 63115.31366833149, 85230.8239120391, 55321.13161379627, 95816.61346014083, 88213.30022472276, 81383.64128227027, 3839.232975882001, 16633.440731869687, 11872.256561625638, 69059.24158395115, 44908.51713729094, 92309.09540196368, 8598.484211574552, 94121.3179941739, 78446.29874195904, 2501.614744231673, 32656.31979967709, 74694.31805787314, 5008.086273941303, 42737.19343220373, 31143.15637998244, 21809.51730327655, 79347.02512974036, 98281.73340800662, 43046.480000207885, 99640.97849571674, 29969.39337629346, 94384.9399467154, 39718.10590771352, 64751.63708398244, 54971.94494837762, 32428.943691395907, 55073.04146754721, 65699.92440086286, 2064.7304758587, 79762.9570020751, 33627.52671815368, 94682.48112185312, 89774.40911145907, 56328.385151632654, 45916.69702668492, 38981.60228064801, 14050.938990989247, 55490.50831554365, 74513.79798522005, 28751.386403529756, 64145.95157799959, 75461.56650738952, 32028.743130059887, 7875.308315114537, 28092.09463437591, 64325.867000058366, 20192.557225275763, 62228.51401825168, 21970.075551967373, 78312.24899321476, 17689.834525659055, 893.77883211883, 84931.58968759653, 12684.998727393571, 50280.665386100285, 79090.21677533297, 92223.58610413934, 45746.13003177324, 8329.667355460957, 43991.602741710856, 15427.689240583632, 75432.04484026296, 69016.15686863042, 21723.876480480907, 63332.35395637572, 45877.675892623825, 86424.92448528169, 55454.06881206522, 55183.96088715643, 8888.702601618992, 75854.75430033723, 10537.161776842097, 164.32477535343048, 76027.25851691418, 48259.913324178706, 4084.7974149225806, 89310.05973397288, 85611.3787830132, 91520.67977443084, 24235.997160013812, 15465.104587870248, 8745.647845004345, 87244.44486229618, 42986.771842124406, 77485.47544332322, 86476.52045180497, 83140.06475327192, 75742.15444144924, 44023.00530557628, 25568.570475375098, 5487.9599033913155, 68052.62972397644, 21093.168418217367, 28884.992081244232, 55977.267309688104, 29300.91605467475, 45002.79836742227, 59698.393357631045]} +{"id": 1238, "vector": [88120.49896351594, 37467.0772054307, 27186.53262560571, 48876.428179962306, 28255.43579017301, 75950.1336608456, 90093.8156911021, 21053.410916846136, 98598.74994996001, 729.0444696569365, 75897.93760940878, 75731.26388429746, 47752.18916286651, 8909.80971013381, 58266.00920909906, 70623.3585661822, 47706.501526176056, 59012.14066565851, 71799.59523897318, 10947.710684480493, 91002.19732822737, 75584.81954132406, 21916.5679937653, 95524.49019395887, 8675.212354415973, 9885.210305934412, 71749.30656716815, 73777.62153693837, 93889.00925469291, 72885.67250115007, 18331.814512221277, 23236.855004284163, 23775.068546002796, 17461.547665125876, 18575.431485106397, 61276.8917510866, 81265.73676778494, 1297.3785792773972, 41955.80536798622, 5231.222577036965, 48449.002881207096, 52082.89690917197, 51991.78658122967, 68408.17307301647, 58716.544138132835, 63757.24558234019, 61047.168726385, 27543.705873638126, 66315.35089330294, 64273.33736381119, 88997.01996965091, 21751.609581283472, 78931.21153670335, 12890.311958465207, 26078.855964396953, 80691.26153495284, 6361.477193250076, 32349.5658328226, 89409.42562472522, 58877.00605962279, 18234.98533344007, 21056.941404311525, 9326.334633737753, 58776.21056830003, 25379.10920580516, 91783.32304071022, 56200.286301168504, 52559.42145541564, 65146.52297878083, 34771.67131520017, 8800.858106319609, 77145.31969189896, 52297.32972443498, 69940.90616544939, 15653.84835886937, 315.7266166294859, 46032.35335527086, 85153.20091357436, 72945.81949146457, 55371.9905899352, 79417.86486916557, 59334.97142425512, 30953.505148962933, 84401.05016326428, 14712.826721743466, 54244.00043466111, 22192.99589276347, 39417.21951797364, 48160.18511899986, 28041.881681749135, 82342.38053299025, 54829.72474784811, 26029.983038265847, 81547.31388142607, 1610.0166057158915, 39914.19331652105, 28264.051746562614, 61010.07685435742, 87630.17257337745, 85017.61099536752, 51258.19103062471, 13334.448948668343, 61102.53151789158, 26634.18946978944, 62861.811406906796, 54477.90249914934, 54022.00980208541, 53207.63424643339, 76959.14630940842, 54745.32458069923, 55999.2775156506, 31773.724441626728, 50348.098294399766, 94659.3866547992, 51237.92643906635, 81076.56132732613, 52049.628748262214, 86440.85201824464, 95294.04172967121, 89499.68983504751, 56272.37556660446, 49809.32117080066, 82297.99405939432, 40677.69730725922, 23898.49536917582, 62845.53522731376, 42424.552445344656, 91117.20874562123]} +{"id": 1402, "vector": [83381.19674382302, 18256.841992919894, 6679.6132447217715, 31773.501271371497, 56716.271663308245, 71018.98731982116, 5676.285767059652, 43655.23094260524, 39489.05708369803, 36194.38435732114, 19511.886472202255, 4415.268652992455, 74951.12734252798, 1255.265076264145, 89999.50791634523, 30092.57795799275, 34337.147044476915, 24839.19980896241, 63383.260684956724, 13992.091180994914, 3108.9156077414805, 3185.458266521679, 17343.503266786032, 10196.945288363502, 41164.38486418745, 96211.898770093, 94410.73079767656, 39060.20705276121, 3542.5903996467277, 73429.51082585327, 50481.271583234746, 38643.35232447417, 76230.19836635231, 84328.85387285464, 67929.78606240689, 94495.6209128059, 5949.026763850984, 70489.63140263561, 11351.720313053293, 22191.585730790364, 1628.784741202205, 37767.00446268336, 10567.745019305574, 22516.60607936873, 64435.15731649082, 42314.08169972137, 93555.48089587977, 23612.082001256418, 42341.3326603721, 35325.34829506293, 70859.27193754283, 94319.5082318345, 91750.87680898, 41654.2666658933, 414.5223891250715, 10504.289936031808, 99345.5591485863, 3527.478878848378, 83061.74913150615, 78921.37978992249, 31341.536027455597, 92852.85857205774, 86037.31458635833, 22428.17959437815, 60543.04808968772, 14749.889064501664, 93477.10782192301, 65528.82604364132, 12004.27783922271, 87972.56442969064, 62583.5271646026, 67945.33485295762, 76927.0114679673, 55786.10377910763, 35092.76553861762, 92711.4715884762, 99694.0464321763, 28855.923981702013, 69305.05222306299, 56273.908218937395, 84527.28458851205, 76090.44260499925, 8288.044328548549, 33118.84597206084, 26001.302526760515, 17660.286592677545, 66483.93753974357, 10516.291455463734, 19980.14204860189, 98229.9975694373, 7011.452495475357, 36091.66256196779, 74396.03406787268, 55136.28562390948, 76687.47075132959, 54561.710804753515, 92925.96955858685, 53062.55305638265, 89157.756123209, 40065.06180842542, 47752.902657963234, 56735.94314363577, 5720.351465332929, 30693.50556109025, 27611.74578933242, 64601.94764597441, 46715.65544224097, 80637.97394884605, 73262.50765518264, 54126.77597074929, 3586.5281702013194, 26155.92365849685, 88367.16539833634, 55778.41257048135, 40589.10473648443, 21220.450748148312, 37763.96482527886, 99415.96930518132, 79704.12931236673, 84178.53738115114, 46116.63760671247, 48488.204310792, 41761.64903570856, 40675.648702377235, 77390.86738271159, 12628.509613173166, 59613.72333082712, 54892.581640972945]} +{"id": 1974, "vector": [19613.271566187952, 222.22051393302468, 36844.94722695293, 59698.991950623524, 98049.19776442563, 42838.923572687294, 69470.0949648301, 77779.16993559986, 76748.83955170427, 52876.77649393162, 5195.684512119547, 66722.16904241519, 41564.34676329871, 58344.59949088831, 64218.1811759455, 37818.06912557478, 2392.4397717254633, 61643.77584129943, 48432.89377145124, 96868.69078653854, 29650.948001676446, 27717.802028089478, 62271.25671705933, 56926.86533453219, 91998.9787321492, 10666.670144160384, 69237.36726040533, 13607.052958864275, 51400.455246773636, 85855.45838714729, 95453.6869728615, 31971.958442320745, 48577.05669838466, 37490.909210656995, 69494.98407957907, 32174.29301515967, 70322.19877266613, 6007.589933504664, 10025.951821748535, 17909.42618710376, 60380.80063248381, 35931.6432029523, 83929.45125114155, 3535.674926925747, 92827.1233987732, 27242.667900141736, 30923.22878406393, 51075.94537674143, 86488.64153372507, 65680.73912123046, 41327.95954725367, 62265.03735096612, 64393.73758673895, 5151.89232149067, 19827.264999168216, 27524.225916671952, 35477.53803421681, 69588.5432683953, 78877.71422569004, 24082.791709038942, 26235.54799218185, 28857.50631220547, 62867.79071606823, 49535.556938349946, 38273.886927688975, 3114.790624656394, 18687.639783540766, 59404.10582136772, 93537.73448839164, 28018.61585702603, 16227.540323246503, 70261.94425007285, 43597.494339353194, 99308.89675454561, 98196.25411130815, 54712.22133065995, 90508.65430725229, 5426.8085131042535, 15603.772567864293, 67111.31746182301, 26452.478151222236, 66124.34352913812, 71788.13320243622, 62397.955373038574, 26562.141720241238, 99583.45002700039, 29511.790036640872, 3971.6703728825632, 67947.122598814, 74658.20674165062, 19049.99615168017, 3542.277772173974, 74446.71946555194, 43856.345820024944, 10819.839750942372, 60933.95010363684, 26348.684765510898, 23303.66177891844, 15984.829376835729, 48201.813150061644, 43495.16328707017, 35897.139597910034, 29248.868150849794, 23169.108928497462, 44693.19374724439, 60776.01048746809, 80789.16557543528, 8387.70626324341, 73022.28814223493, 65653.59727825542, 86059.11429774607, 80531.74643520333, 69987.92230306214, 27972.838380947294, 17066.017748673257, 71814.50818124143, 73599.96228975452, 4947.514059580504, 66256.56705333693, 69698.3463044179, 22872.312709283317, 92744.05080118103, 80802.25221945955, 89712.21096050752, 96454.87528232647, 52488.95884100391, 13452.382952200003, 40692.36604718597]} +{"id": 956, "vector": [24625.96210108969, 92049.8285111191, 82984.43830526972, 4657.99086641886, 28005.159643704224, 83234.317651289, 89858.86873173271, 50494.65056678547, 21641.871972757453, 25949.3407322796, 35373.21161141123, 30260.03944345119, 38904.38760661844, 75945.0625934267, 14847.993467239263, 45967.859678860914, 48087.44711450554, 92425.22444528773, 41607.711456235964, 53452.4026991947, 58710.8363685262, 68537.15527591732, 98991.03699894274, 42731.77403611745, 78324.74430368307, 46995.91375022121, 68428.1768499234, 9436.068740692783, 20220.593707984215, 10434.55798322931, 67631.05537107373, 42367.05528138413, 40458.23581593705, 65488.35145834698, 46706.436658347404, 28396.39834625407, 8333.926648684153, 9615.396156232404, 53611.677710000775, 158.07355562499347, 82705.21821973438, 74818.40657857513, 25777.83910016168, 47685.5811111176, 54180.77245018009, 24285.45447634096, 33189.86867997154, 22274.835904349366, 83250.3813738854, 50488.92796095589, 52689.31488691755, 96723.28670213002, 62068.26815967729, 57934.93555335275, 80908.63134299447, 99620.1841779192, 34252.68438934704, 58421.758041899244, 9088.108065526201, 26615.97996953353, 87504.87005546421, 99876.37682350553, 35915.27193819102, 93128.56995917067, 81346.05192409882, 27925.194222998463, 752.6842934291178, 565.0520180482732, 56437.68004740329, 12471.878822712046, 89856.18515949775, 30235.7829627828, 44509.212981687066, 86553.62945642759, 26958.40197270021, 60434.78058147771, 10969.445541411793, 37865.553284036236, 97471.64368148726, 1445.803661431566, 47988.1935337662, 86162.03367705962, 16353.78576160298, 84435.29208917757, 70355.22387068019, 6988.011087273849, 12498.089611678632, 17272.396079451857, 62512.310311307294, 17610.451514117496, 80779.18212225626, 60238.08381318453, 57457.58564558957, 92959.01661626161, 37055.21266519757, 38269.64791191231, 78039.53187434984, 10554.871702926117, 1333.2979673486457, 43345.06613944842, 39179.80513916438, 79746.08848585135, 11882.077520402856, 86594.07410736126, 57963.082900773, 64808.661591515556, 24796.133292458555, 42250.68817267188, 28287.61901901663, 28492.93284413228, 16120.450460125357, 16349.54516967575, 61828.55280267313, 21741.925204938616, 52089.78746358304, 45127.578429832196, 13477.438830444733, 17475.86108983016, 73399.71264182034, 97548.70646102107, 68271.76856760863, 51085.97492643484, 65498.29485501487, 69556.73272688717, 17695.43414143644, 57191.824739246214, 4661.910338788899, 51280.47725512259]} +{"id": 1580, "vector": [4900.221243898916, 31867.001541750462, 43479.75053695215, 55804.38855483013, 81959.81226609992, 72120.31856850472, 57261.54073208592, 1299.1836204994888, 58923.699565620416, 98402.67803724749, 95097.97541252337, 48069.837307079644, 52302.87628586066, 95534.90849043409, 28395.52226121903, 43272.2595919018, 87758.74416099372, 44631.6813063933, 5717.754349048543, 50678.44941475772, 89027.87983781548, 77467.70883672893, 37462.55092129974, 80832.8844821108, 59879.10684258395, 44349.12688515903, 93743.0433726093, 74361.1796159389, 24771.47679734628, 37306.32730615114, 60884.41557913198, 51748.64067273522, 75928.13496600483, 26554.631532137908, 59454.54907471509, 80110.79298170914, 58610.58545294775, 20170.533836872906, 30414.300072307764, 63773.82729414999, 31461.86018794811, 18313.814757450087, 43284.42006802325, 90801.82275368104, 36393.32351208041, 33263.98019368741, 1320.7254188447814, 12608.382893096159, 78502.33213270681, 59414.87848189875, 44076.758297874694, 14668.10155208902, 15213.67653194462, 68070.97581450704, 16450.093898538653, 2724.976179015559, 35807.759846015244, 95845.64863845934, 5938.21483149648, 16381.442435362791, 82154.4790981485, 80139.48875229289, 18553.227972075827, 66764.41546827325, 94354.98943044011, 83556.63279066882, 76981.24912063318, 14986.247664472841, 32265.290796046487, 68909.94927325136, 47696.37048258252, 25347.97987134505, 66532.07050162343, 57440.21968999956, 11469.79166780714, 96411.18062058817, 8486.868182286622, 34127.12765011114, 62329.91460131913, 76748.24107661204, 65761.8475482105, 9524.263373050668, 13418.408409941207, 31350.647531310326, 22463.824990962035, 26422.624482946943, 31617.688119000253, 83995.06600605647, 26057.253043441586, 84058.27667646622, 93042.02398395984, 5615.145144581013, 65141.295069525884, 40835.075439618515, 14275.370165096512, 93448.79154813773, 92624.8376498695, 3429.0436374353917, 21081.50667993426, 4494.3030775307925, 44467.73755235155, 27463.86178753184, 50666.66743672231, 35457.23729922713, 63882.2060510474, 81763.98568692272, 30564.21367262332, 54535.81104465241, 17081.55158354624, 74263.01088897519, 587.1203796415703, 40054.37046880829, 20963.240054299527, 31502.34678723961, 30573.33529504972, 59291.40258863234, 21054.610768325787, 31846.542280083966, 27661.95755268458, 31714.527229262003, 95610.26834160282, 99757.4076572232, 88216.4432803398, 68287.2941498873, 34686.51754458388, 87538.60951698883, 88743.26962022371, 98983.11402854009]} +{"id": 159, "vector": [14599.941244308622, 62054.48270389806, 89618.6348993627, 30104.362469735766, 46921.70557897394, 23576.060914936446, 14824.215308076527, 37882.09178640146, 27804.621672025944, 24125.28220464313, 51201.673195476826, 15249.692024847294, 35052.94566905728, 1777.60995294024, 5281.122098608038, 77392.12785083013, 38092.47501555357, 13930.02359316564, 93082.0110400783, 49855.0856514408, 86606.82444521907, 97382.64521485975, 56394.76740567411, 11563.934233297701, 6216.098359576205, 23553.842621190644, 82494.64588154956, 8450.04543559087, 42150.88291209571, 5454.636925248146, 80350.35510908281, 30927.75939248966, 26616.29626631151, 45338.75808388953, 11080.726433094267, 43024.13307220102, 45804.51459129068, 76172.48171351926, 97183.70660265832, 18831.32537935801, 49529.26077726491, 96871.91425181284, 67350.43285684199, 75219.26339577517, 37065.16328648488, 20857.51197875333, 85481.09114234686, 69192.62256981681, 82311.4475912218, 84204.1667812248, 57802.57584556878, 75768.243344832, 38094.32184158058, 59951.56904110619, 19412.02334613836, 20941.461125578455, 72184.82576665975, 24609.453608839005, 36796.62146385867, 32359.847151291033, 49834.41182209846, 71096.4967082131, 207.8083528356478, 42585.55215971012, 97646.28841747143, 90200.97915694895, 31392.115872162918, 77519.86139909991, 86823.325256492, 39396.85220878501, 60757.65894832971, 62113.251478225895, 85028.87090680719, 24662.683228741233, 69023.21563721541, 96999.60241553873, 94052.77608751669, 54624.689751678736, 4726.403510331634, 519.7023036711435, 54581.82037088227, 83466.890518889, 70374.27367175726, 31069.006880398298, 94968.49625322204, 89984.94617981547, 67706.04291609905, 16898.00530221398, 53163.83662267475, 63358.04287442412, 74441.55142414263, 27093.18087532686, 69504.60113675802, 87006.11528680543, 96222.52185010743, 22901.405160013277, 73304.88384755823, 37072.699651808834, 64655.15854327172, 66264.79333819961, 81007.49931263509, 60488.74613593246, 33238.16998252412, 60947.82585932301, 98185.40238683626, 69221.94919765848, 49010.21446588597, 9334.254702195432, 26767.511666514598, 27749.01345546923, 41640.408542092046, 54002.89220857962, 27940.465445211405, 81041.01761624636, 40172.814140035305, 51421.19586658289, 97708.0989260156, 92927.30361839946, 57918.25112391409, 88941.55454897229, 95925.72661546669, 26182.50559855624, 34259.3661036622, 8800.533862395876, 27696.003022364424, 89144.02059674702, 91537.94114998983, 180.33736019764657]} +{"id": 1963, "vector": [66434.53982028898, 54102.7908005248, 10333.958842376678, 95373.634125378, 12209.196153170298, 1985.9637051226175, 32763.960077047006, 59477.34709568777, 64349.47505423157, 84678.92373107237, 13623.041514670953, 10542.615139221745, 24899.818201273403, 6541.390595004104, 15884.425730043627, 93664.55618710277, 58646.73523658972, 66030.3768360916, 29361.996806297862, 57817.397788074595, 33827.38136399897, 83525.11907504496, 70096.22670341177, 63073.01298690167, 46008.53817225404, 97929.24231053867, 76713.91836518847, 99341.97172796966, 71210.87303278747, 65119.38258505215, 87506.19251905414, 94635.33903072368, 78532.13898448065, 44028.61362574256, 23218.589834466795, 34482.14775015412, 61336.986619348165, 89103.02147101013, 99374.18601335073, 58269.57881792841, 6697.114552496497, 76862.19721240042, 79617.85001627695, 26031.83945289097, 22319.30166697921, 30265.971373873956, 89805.16629637296, 2662.571695466054, 63194.241673706616, 68525.78635568327, 41987.137690706, 1174.2983028454778, 14952.698951711829, 99593.18916648903, 97484.80590313474, 86340.20503934637, 24247.64773001261, 28181.33855032796, 44215.63396729635, 48121.872976362465, 32516.158333752323, 73340.46787585913, 29577.78738982303, 11663.32823074907, 74866.41384669687, 71242.99181685061, 27687.49333933712, 95627.76972788462, 89464.26322084606, 26289.37235000586, 86927.77314045418, 3855.7914543601137, 90723.45937990834, 33831.82657154558, 93784.61319178992, 19460.894660401074, 242.94212343977105, 27350.094011366287, 85725.61698445122, 26700.934138827946, 30405.3419500653, 76996.54748070067, 40781.90452734762, 9621.251635692506, 30802.080666805985, 69956.59640616833, 46313.2486529404, 78043.92907930097, 70602.77358503708, 49784.77660263283, 67646.71669689773, 78860.68120315716, 30281.175142523633, 36985.72914372661, 87386.98393167501, 31502.085610453047, 63264.68326274072, 61400.62317386786, 93454.48111003648, 61929.980577243936, 56972.14798758985, 93106.23058760166, 10197.439676642984, 54063.13797869179, 661.8741185380195, 35786.15323369695, 43802.57054328232, 95570.42152349341, 25265.3260562547, 52565.86351471163, 53501.77134393617, 76561.74329685613, 39029.430977249955, 84640.63331665244, 28684.790591596044, 88230.24900658675, 27030.23432548972, 6371.961363218969, 99614.21335232387, 39225.4782614051, 74581.13382006822, 72669.67728280342, 61643.548430098985, 74607.91633510609, 73994.77015479654, 6673.22829624647, 84929.93671181121, 96665.64281934928]} +{"id": 930, "vector": [11175.815027791725, 37412.98837132974, 66584.80670847907, 77247.47766267275, 72777.9941160845, 85193.8475724288, 23893.215682736336, 26367.124847104627, 5896.202715411536, 23285.54386095123, 44629.903987099315, 39771.416393412015, 79988.36651419397, 5661.795219894783, 88392.59019877207, 51480.742340653815, 71654.30348425532, 46335.6832679343, 75551.47248670648, 557.8841162421688, 49289.16394422711, 70094.32958532023, 30981.93319287673, 67759.40938758355, 5464.880600777655, 94656.56776916378, 54016.629315695034, 6263.243024776355, 21221.44206103227, 90851.66537585697, 74921.37279025203, 81609.0196458961, 52529.90715178726, 2879.8602239691995, 88360.85915770177, 38128.708129989485, 32200.598855204575, 94633.07798066588, 46861.06800180372, 13128.32334528965, 81508.79689105292, 66460.41174629827, 26537.04044381333, 11387.676596289997, 79017.66106372191, 15959.881514105135, 23704.470741350404, 38036.08071703338, 23804.38260494414, 63804.310195507984, 68222.04366762655, 56077.672400602416, 71296.79217020498, 96043.20513690436, 37487.12304424905, 26781.704231973512, 97106.67001756528, 47823.02831159274, 84981.40064608038, 56528.26803291735, 71577.80724435522, 59768.37796202751, 57731.88505541073, 99400.69006864811, 63065.0186930368, 50054.7489473458, 55517.64455625228, 66720.11210684889, 32688.465686247735, 67525.63691931166, 6330.281629487755, 47291.274189010815, 60371.60696275324, 75002.60593418907, 61919.47975633573, 42680.64790629695, 19988.753840251207, 92624.37823922302, 19945.13535489668, 24655.082181918464, 21655.78981392965, 23890.813955234826, 62076.90436792954, 69230.81709466326, 93968.9170956915, 53430.14944057693, 74760.76240924552, 37475.628573865346, 64871.70397732983, 72556.63124714138, 43235.72573880656, 43102.146582883215, 61380.300209433, 22176.821506808275, 17900.15786337893, 93236.83741597028, 65313.25265256157, 82748.04118135503, 73318.51196446177, 19078.27635744963, 10064.948118272243, 26501.18214462145, 58978.92844990671, 41239.61074837252, 83401.15352167108, 40656.06466947078, 60931.981235066865, 50.90658734288045, 68988.62398577337, 72117.67743654396, 39675.5125963853, 93797.1114248953, 31944.411105691594, 9060.988327349594, 73498.75591492101, 44982.65622643006, 98154.72005785121, 91287.46446455317, 79273.49550267885, 28403.106791598588, 20193.85472286992, 86047.85229585772, 69864.68488233449, 65460.277586115924, 39307.02526358313, 88949.71472837229, 92588.86126669416, 60178.62472285852]} +{"id": 1407, "vector": [79965.70910379339, 35357.89930495753, 93335.36738970799, 67056.47823854549, 31250.71475305683, 42826.80351318671, 3985.6239375791633, 28369.14074207909, 7913.106041540885, 48999.27909453633, 43202.11485442176, 93425.16412986795, 98576.59159846012, 48302.496125047146, 92637.50703739353, 11707.692692917726, 66276.87625147244, 77280.94227910793, 76153.49023619322, 59655.89877657712, 8672.868192865002, 32482.592823311974, 22744.3508458114, 71604.04348673568, 56107.33119329178, 64244.56049139007, 85285.83282394204, 59904.3223262804, 98094.08129420379, 78685.41917813933, 73197.33356527326, 85226.19422889357, 11838.637787871176, 18016.14585041389, 92733.32073095044, 83737.8387657631, 15044.606532001559, 7869.417492248265, 63555.534574927355, 27010.866604071583, 19246.344210816336, 60949.49814089394, 69887.2229579662, 51730.00115953866, 12089.078663729846, 34876.77828964081, 35198.0156038223, 91426.44864022553, 24690.993264950645, 20238.93092382901, 19819.20034387853, 2450.651552648442, 82599.01364116848, 32538.27264600614, 2237.5181347103144, 6577.305021276314, 52579.417971293355, 84752.2563365694, 82240.60049580087, 67795.36305652747, 67391.88687330391, 27552.96988517668, 74424.91827032618, 25795.952435229217, 79360.2844820146, 36691.903965765916, 25992.231878387684, 67525.5243804806, 16282.829825956434, 96067.35115393701, 77409.84875603639, 66242.77824272799, 73694.72746283845, 3990.3896678848196, 23593.190029587364, 12065.983061619856, 6425.710767642323, 39502.40555097289, 48836.49147446214, 42456.44109567816, 79005.96855180465, 47460.16584577001, 86952.43854798058, 74242.56699926459, 63407.15939181154, 34575.7419087379, 62640.05387802672, 58230.242845184985, 48056.65714765013, 56150.718124361265, 75731.17917910746, 13315.52542820752, 37967.64188591939, 88977.47263331951, 19904.748191709266, 48799.12868862356, 86226.84204466922, 70000.99423301923, 3853.686766369435, 30193.50857065506, 95768.17563092482, 95504.13441025272, 79900.54691571793, 25098.583547366437, 98087.39229627111, 41007.67923998267, 47695.22877221919, 23832.074970215122, 26037.85180283661, 21437.011736460565, 47806.613777328144, 23540.90986632623, 29625.79439402887, 76656.88099696158, 87283.77252704723, 56517.51249583126, 649.555594382667, 41438.2115091376, 43738.20132549093, 14042.854670434568, 74689.56970103977, 38550.72117650228, 1740.9215321352533, 57521.46150532711, 99644.90485023812, 67989.7885656046, 24287.035352519026, 96068.16706234997]} +{"id": 1430, "vector": [3065.22802344974, 57414.17635743934, 27107.253046550482, 30108.687586057982, 28670.229177881167, 13182.857486708965, 64728.580681269734, 33761.49441156127, 88901.96925745378, 97405.58712273878, 27158.89797464174, 42100.10651428158, 74969.38096251234, 6512.1545454087545, 52577.73357723979, 20740.020275415194, 40451.56827241665, 39874.919537747475, 92681.33990937019, 80350.53379330052, 11124.8777422151, 14641.338039955765, 35252.00257171205, 89910.07098485006, 24991.96793090943, 99035.66286946948, 47183.33194770983, 70870.19993924703, 96671.74372438285, 89296.91116556883, 9138.732335702049, 84735.39874242643, 97286.3483893504, 27927.430791996554, 20292.55301783639, 24797.335653641796, 97037.91516514504, 59416.14925426204, 46217.951500569565, 22103.9629179835, 1898.0678408057283, 67353.45700314686, 14045.270957186029, 88795.99256773767, 17765.825324702346, 17952.665611841356, 13969.600974300234, 50634.75140983179, 46739.62443106348, 43963.0605781856, 69181.20161914056, 14192.2722810086, 86703.17138016454, 62604.0873847846, 8721.129381570636, 53287.803338334605, 1266.5241556789385, 95931.4260452759, 37416.86844853979, 29456.727071529986, 78853.0110338653, 58632.23683052402, 68045.338437789, 59516.587781534945, 3076.052876374924, 22231.657116747494, 24380.85542711734, 65661.73887830932, 42498.313517013084, 22553.04714205428, 98402.45959448027, 37133.4551797957, 50471.04818799711, 97310.59044724265, 21923.80169851297, 81122.03975972609, 89105.99360546345, 22775.55782592867, 7323.305401432467, 36178.12932322835, 22299.08471353821, 54282.958928869266, 84583.43498840644, 71570.500472122, 92631.4711139404, 75759.16516733733, 99841.64229738776, 47049.934066416135, 23812.860577453375, 7636.2253033233965, 70271.10914140908, 99782.48443437407, 95626.88039394132, 78468.31295157802, 46835.17657104672, 70108.01658954579, 8050.504735054498, 3374.8763421375006, 28827.025359812098, 79253.43884213887, 39.846232411122614, 9056.143892239521, 5527.131067824109, 81117.02554880356, 10674.536024377745, 96770.25415763419, 86206.22312832075, 9606.867286163257, 36.856001415941364, 80488.80419528007, 69307.53743066243, 54652.746495781044, 7205.701441451595, 19097.22883886874, 76568.59759751435, 76235.5910216842, 3347.852406507379, 45227.84532290987, 2189.230018799393, 81074.81153864757, 90925.31526989283, 66840.74308052342, 1641.1619114242383, 18170.528450190504, 62166.105781893064, 92568.16927883412, 46906.91107561991, 60806.48189618002]} +{"id": 852, "vector": [3050.524039228508, 79148.59369328858, 90545.76406483985, 10552.912056512143, 23393.105312301443, 69313.1555601266, 26065.746004892077, 29089.912821920494, 78371.62894264981, 47034.236705979136, 56269.97933934276, 45301.61979588001, 98212.38686218849, 33212.148275937594, 80417.92476465991, 11261.654507406005, 60044.414816882265, 61454.9027678329, 20766.113848307486, 25765.941926513737, 17573.243367797164, 4131.26845548778, 32066.390421676682, 77348.75123007112, 51252.1443513112, 99256.11311843705, 27088.870772876904, 73800.25034021103, 93982.67960736623, 8711.191763560433, 88580.02325044727, 63915.33758019492, 96019.74141811221, 99180.74786975638, 58515.73854233707, 70565.22030937581, 10550.865986366342, 23636.95427877035, 76061.30074589275, 26975.64662816091, 29998.119340420904, 36652.35686921724, 11796.386959883854, 10200.05473762251, 40323.29494493427, 80440.34735520468, 705.5675318847653, 26435.789050197567, 8690.753867157586, 82169.52993894862, 39912.55007145173, 70453.56917623983, 27419.528221281776, 77558.06678257116, 83293.41083378125, 49478.64649803916, 9450.776904493074, 88722.34613728635, 59644.89136442309, 6252.967167978418, 19443.37309704163, 98797.83625455873, 74423.93406773615, 18328.25554244859, 69446.55426592329, 96113.92571349506, 73898.49742509847, 2635.7508076776503, 85769.99933282286, 97274.13770354103, 42123.44027848394, 94845.1669565059, 8309.024809788412, 93199.64871581914, 79569.33065635522, 56534.866349319665, 7682.04776326572, 5625.293167302303, 1810.3094368614682, 48452.680922469925, 48866.45872093109, 6142.597554340879, 52114.707405506466, 52696.04718122537, 48935.36398803997, 83254.16518657292, 34186.31635365757, 76057.35649405066, 97232.96155973818, 83779.33885461104, 88356.184532757, 25049.10551907057, 59443.33953237113, 44396.13479343847, 88389.59524705584, 34401.21359432417, 72424.78571253798, 53167.490364739766, 53042.814043054066, 8994.933208394274, 31002.359527250377, 97501.64320350761, 30375.166004680977, 90286.38594885236, 31063.38656197919, 61230.60150858439, 47389.31687485928, 60061.552973177735, 61833.126150578624, 23504.2713673944, 48992.48267555751, 55740.795988985745, 24453.73238915407, 4653.848794031923, 44155.02351192203, 72989.9775547504, 41653.585511673366, 42794.53961865636, 66694.31712238141, 42337.46825336353, 78769.54990613683, 24517.34546910247, 65537.50239086998, 27228.35800805088, 93927.6824375785, 97603.97655156537, 27570.089262995447, 18721.916382739466]} +{"id": 686, "vector": [82277.21548581602, 52305.18746931109, 19513.460657586624, 29593.47849040692, 27042.459166331435, 55398.703490227184, 79660.68530048386, 81048.26465976296, 98251.85159095097, 38943.31143843972, 6152.978813604648, 68289.44822911436, 43855.66771515359, 29780.509169551482, 28949.95948913531, 36619.11247321924, 33202.54988802979, 7740.321926435212, 62016.60509615017, 72261.13146550987, 68325.00662739716, 57238.75890985456, 70946.27993898166, 65819.2898669125, 57877.674054324816, 25336.41233825634, 23783.24603797756, 23675.31432674541, 53619.1549141999, 83650.0028062722, 5285.913528522612, 82691.9413358233, 90144.1787815382, 20360.099861431358, 71605.93659211339, 92911.30327440132, 97831.85018129568, 49901.75560944956, 3807.4508901412173, 21730.88637352123, 14323.352620644448, 47743.098546056164, 11025.323102253249, 6538.251273829076, 43475.884298967874, 74639.22457864136, 6585.61611258438, 85670.09690991174, 76908.4468807307, 93382.71833848502, 83865.63067880207, 26300.382963948377, 92413.95981023701, 79883.1532418484, 85617.88051889332, 24054.541114497984, 4700.83307299366, 50144.326864773, 727.1765359777582, 36458.04214046382, 87913.17494354259, 73032.16429184955, 68679.06076923836, 83501.44318304259, 47903.07166245275, 78971.75011993058, 87535.35413602008, 50859.554490200244, 31245.34014676613, 19243.200928349448, 84680.1361546541, 32242.339797946395, 71463.55923914844, 61324.042936851605, 28190.836847833078, 30171.193133240417, 89677.64985128435, 649.42978723046, 58109.0921426234, 25061.380251031296, 14050.124319410352, 93721.20265363637, 40771.9836847738, 41163.35484343898, 17881.538910362982, 3040.549336282017, 76956.16287997739, 79145.13337193005, 91724.06362417525, 39363.31111034924, 66247.43365212242, 76016.15646844369, 56940.12041892385, 18558.475247069382, 82645.23441623976, 58358.62676647992, 32485.375058805042, 50994.08850990036, 76142.5500829117, 23712.692977534978, 45625.03742985263, 46893.93791332226, 35735.00308346678, 19994.96876279958, 54228.57081267422, 17210.090612055217, 58786.586758519, 62661.99566674736, 40477.467993226644, 92815.20887678441, 31258.690779777942, 28665.977556804424, 56944.92412637818, 57705.26971607998, 19106.80837163733, 32400.876819856905, 71736.42193141642, 60176.88404418262, 69565.83226496427, 72358.27518914893, 13797.225858396678, 29572.18396446617, 62836.443059557176, 3722.983615129216, 26537.578748292377, 70964.77529065941, 34573.032847436545, 9992.620813987629]} +{"id": 1194, "vector": [92338.07864539574, 90391.5415391432, 70589.65671171431, 61154.93092354063, 75880.08734546961, 70229.4453669624, 95837.35134083874, 23251.832235190515, 91535.97547420164, 45243.78365893271, 30311.119833350975, 57328.156725989735, 64251.98267231976, 52966.113415086234, 79088.39222651589, 98837.78254252349, 35892.594504150286, 17780.29078944715, 19228.262948274423, 82124.09372191355, 99001.1392141668, 85902.19867830201, 70902.33800919318, 2324.7092415878524, 5183.0787809499125, 84395.80692244082, 48696.56710694609, 96391.72109136863, 58152.80844757257, 75923.26580781642, 82057.68808005002, 1153.1247731305316, 31495.47924284408, 20766.38977356149, 35264.37666606145, 98935.66335223943, 40626.12095244461, 175.6121782752862, 74192.90928545214, 25094.51397263077, 8155.33186139622, 59607.62762161562, 48920.23681673988, 7263.533170831415, 508.5969429518444, 30787.563143102183, 75977.19640720554, 45735.313927141084, 58531.282758957655, 70667.02232275401, 63285.41860543085, 2201.711519816463, 23747.16539890416, 49195.01418384988, 26982.4162288138, 66459.14102954943, 85864.81442534711, 1671.9813909528702, 510.88391483895543, 82765.03998322389, 27120.70385210703, 85105.22704334409, 49278.31108151233, 17085.520084160977, 60304.40317217951, 79587.37287284459, 83244.83121031952, 31992.1765432428, 25549.000283840374, 91520.94157103563, 17860.362605810944, 97958.49182319372, 96937.60943194781, 50597.65349472866, 5877.928659132936, 61877.52505918271, 41634.38617556672, 51307.874624939745, 38883.11011756976, 5309.570678819698, 66233.13556940973, 63525.95080004075, 76744.89077695162, 62897.16757589258, 30920.452539046084, 66289.54922134134, 18459.305528889225, 15839.359464254954, 91382.05668654064, 24349.96187920778, 37261.367179508845, 57248.204347075036, 79289.04161817447, 34262.727615664226, 42231.02060867088, 4108.433360174568, 14313.186066587425, 30056.08666733168, 62054.614091033574, 34168.57858851836, 40025.058407410455, 22426.143767804042, 11106.821200548644, 27068.05478792321, 15343.387100316762, 16684.20065789421, 96823.89805031229, 66981.20715832774, 51483.32433963602, 19389.603224876162, 55173.90502520201, 15243.913443925994, 59143.14876524758, 67356.19286791653, 19516.60008254993, 36054.63766498751, 17429.900242121043, 99238.07606812063, 77303.85279263149, 20876.60196190212, 74661.59579420635, 8501.883041417368, 19724.474195417974, 57500.053570098484, 34233.10357535012, 91874.02683846808, 96191.87463764813, 60200.265231402336]} +{"id": 1693, "vector": [95914.39017213255, 63816.73599513496, 96037.17527661579, 53985.36784204613, 95453.1580442509, 3967.919539917131, 64731.23393121274, 98181.82472301702, 98062.40044231464, 5362.265018141598, 20023.712227191103, 47289.00741526942, 71702.31195422953, 66624.96986240587, 5421.2167880184015, 18984.468873933693, 59253.73313360782, 62295.832649303695, 63626.27610725126, 68707.68656076537, 56637.69729174583, 39556.85237031895, 59480.96969234434, 1700.3684842968437, 59964.7931352703, 1323.5456075645357, 3980.7963304145487, 20348.910135057817, 7029.053422559717, 83629.97884838484, 38914.034290382115, 12023.029681636632, 15463.726441219782, 32800.33249465427, 57823.67363391993, 85844.70598111588, 39428.74605524472, 78391.15902447278, 25427.262192996204, 3644.8275791358897, 79978.22671697554, 62885.901497605315, 27715.30850557292, 45225.19603251611, 7926.046227778749, 75393.34198886361, 67457.11006859422, 31109.056954657855, 72014.20765581635, 53937.1540074878, 87359.04893545625, 217.70397931595298, 36854.448700831344, 97277.34694407487, 24004.31755352689, 69202.84709713631, 17164.44563750077, 17648.41118940099, 86169.00000465487, 57970.09373290419, 93221.82077385862, 87930.19500104572, 30054.90444184573, 87382.26551180573, 17950.007623618047, 29456.71718100502, 4363.206529510378, 71617.90730840381, 38492.90613703383, 60901.167483485675, 39904.29532969719, 46332.68452738909, 19361.220709376437, 32433.86241681093, 16526.32000181332, 1602.3594736108703, 30252.902663868063, 19292.266264575275, 3631.4755490830853, 61820.6350500456, 87402.78336429468, 41391.92525651219, 33932.8083679463, 68555.93952872811, 19940.505996953685, 51373.72438221941, 86331.81708235321, 79654.34478524364, 65004.342381690214, 29003.396257950142, 36738.82316760747, 63755.09378026636, 83006.62840458647, 72145.57187768922, 62848.91078194055, 81309.74072026326, 80948.44218764956, 9125.237584570534, 63484.9525332784, 22040.452890922603, 22142.19350477189, 88041.5056259879, 36114.177012203974, 13612.828104576412, 84625.54147560809, 10570.030741775094, 2387.2765240886883, 59680.672924248065, 32427.26571596509, 45825.96028779279, 26937.38400398439, 31925.83136211318, 89095.92816061861, 11181.460073266659, 97861.13477798724, 24283.34858619854, 75887.14363885474, 28443.235938749876, 70932.68746382678, 14486.531449232387, 61704.595738448894, 63920.53671301265, 77993.17880294497, 12039.786654250684, 36377.97172991985, 57851.21959003815, 46531.125804432326, 34142.16533158085]} +{"id": 40, "vector": [30642.145100671427, 91141.09579900831, 56972.80265319516, 83268.23684750882, 68701.60018094484, 23145.976449845562, 66747.92558807807, 8358.5228248232, 67024.52950817501, 88495.82313358912, 11020.190922865802, 11986.646061133588, 16555.664602612007, 99413.88981953161, 93838.89834619695, 76112.0850194553, 40997.46653010928, 75956.15110659433, 13240.617259825427, 98170.73048775908, 27656.235566190957, 94460.15539224871, 48357.744099608724, 44372.30361493877, 30334.520534849464, 12046.87335302772, 99701.97958856632, 50916.33963942237, 31970.906453663796, 78024.53995372046, 57395.250703409736, 46659.26946398826, 61287.998648531975, 86108.91706157837, 15428.92195776001, 17493.166220121315, 38907.36843545378, 33033.74535234057, 55220.99780499783, 158.05892727647208, 17609.279367758747, 84652.15405630259, 5533.738745695749, 5292.869510973131, 69439.17365585903, 91639.84685669756, 86574.0936697916, 90198.21858438628, 29538.647362821168, 70739.82862054741, 77760.49656846155, 78217.23430821969, 95388.85232971038, 89707.9589453983, 71948.90210947765, 45485.20315516269, 71619.21009622587, 16699.00050459954, 8090.8286994544105, 917.6791329412848, 39890.965129806966, 94822.6549107008, 52121.49051296383, 93775.02927389165, 34541.788982394486, 13332.582739197574, 21196.454555079436, 59390.40262193317, 50544.018001698874, 34207.845312649086, 76243.84240931747, 63969.0821972412, 50430.692664981034, 32480.329315523824, 62631.18405919431, 79702.8711728206, 70662.9107039869, 7044.774001653753, 10847.235421359148, 77706.37515931588, 95229.64254986298, 11458.521332963834, 27605.779285797693, 26823.26166159711, 22468.484291917266, 65319.10946931475, 76937.2441378189, 62550.43280754565, 27246.88392857706, 93729.09345908371, 858.8288293133961, 5044.214593929974, 96293.96987445412, 22395.32017073779, 93475.25020451134, 81047.6777930968, 50682.789837978446, 80418.01072023672, 41991.819515554555, 24731.504536997563, 19457.966557728367, 74422.51400829449, 79584.53267090663, 83224.6050335603, 51488.39507996301, 91087.32030346441, 41470.84864742157, 27929.438706494537, 75432.29775434766, 10995.275780811331, 69269.83229695621, 60132.37825974287, 87035.624920994, 16689.008493175515, 15509.1467736163, 87379.43097157165, 85746.5780058049, 51730.8540895256, 13702.321369367088, 42490.93117675545, 61294.57274666316, 24470.68182821448, 16251.648689154696, 61504.14818507913, 56316.66413024515, 43354.60665011328, 76012.29861568984, 99723.60488950259]} +{"id": 1986, "vector": [83721.40694785625, 72436.90956421921, 38410.9734715114, 74846.04836969938, 54884.6926072389, 71400.25486507535, 52770.98896380147, 28713.88518294692, 55462.21310964592, 190.34924246656982, 3907.831992995725, 30439.195615379354, 45492.05307583085, 77603.17644339231, 54108.096841871244, 93377.42209561486, 84211.83403611061, 5406.045062661802, 48909.51376636028, 52010.76184014964, 15213.460000317824, 54741.814916017625, 47502.326280615314, 61744.169097435784, 70698.39500210508, 37606.895980402085, 35962.7944694383, 90791.86215797778, 87627.62205330851, 84045.00055859002, 52707.56751255232, 29985.12826882699, 89202.18766308652, 36822.896818381836, 77526.2107607309, 22249.320015511286, 82760.12978473888, 65179.06831467748, 19305.34070721297, 86116.02049948453, 28591.695351808467, 77843.03458894676, 87372.74601364881, 82241.43774511144, 85856.48592230253, 84234.37631300479, 83366.79734421024, 76067.27482743046, 36142.93856457539, 11905.514815513752, 94667.23263183064, 19655.861397256867, 60987.88939555164, 67309.95266636915, 86961.25113264225, 88092.56128847989, 55097.764462722786, 48001.07467561172, 37228.10808997207, 33812.29826642796, 19294.837708652067, 76443.62612939115, 45336.334348346005, 3513.841011751062, 21699.923453318315, 47017.178520588466, 890.2856023958128, 94323.76305135369, 1451.53683485989, 64175.00414474144, 16952.901963192002, 16103.570252781728, 39607.922387738094, 96715.45356801424, 75.18598236861962, 25660.537776204295, 77504.36269069057, 19393.9846767337, 95631.8295150105, 25908.293216449063, 20160.88822314047, 45345.50652547448, 96721.96549230361, 98500.61783335828, 68188.46268010134, 35556.501254073155, 26418.695582028307, 63526.30690708008, 47405.01910311856, 99935.80978474871, 70736.51124022715, 20955.948395435153, 57221.77515609783, 65279.966204962446, 43686.265587264454, 33067.06266312444, 59076.72772829607, 82476.89886462358, 72713.75240009007, 46664.634547526395, 11535.231887439213, 71594.3760891432, 69774.86580323578, 44409.79189932679, 27115.638363747963, 78420.28142767548, 40050.59644986991, 34429.977242388646, 70535.13285354008, 19845.447365020496, 12761.117126639321, 23790.335649300476, 90245.9762947766, 3503.4126033623215, 9967.004217244712, 51147.12493905669, 56675.76791329083, 90798.07452806026, 79669.96574627142, 29017.304685579536, 90965.4526722287, 31766.37805921506, 76244.30646048147, 24105.994504765116, 56550.38485023483, 2351.1940832967703, 85313.73303359142, 14053.832090783691]} +{"id": 614, "vector": [45204.38710101863, 33646.67537316112, 59956.7026017879, 80703.98028724447, 80737.59841503261, 14077.96511089744, 33887.66547723392, 97032.56086913896, 90835.99066798059, 82307.88383593339, 33229.76758514745, 61163.61120165397, 36061.30128145559, 63998.6203033187, 55489.234410163735, 37018.45337642798, 15932.771554841453, 50845.81970185038, 3473.235943211261, 88172.57865244921, 2024.6239820630562, 8683.594805138595, 14680.00964940327, 29175.700872274258, 17499.682646662306, 26380.53106828927, 51591.85927318631, 79825.70198029981, 32460.29086346289, 79513.83574336344, 96118.83898994546, 33250.87802464993, 19443.967385131124, 62611.12819162933, 61391.025081932785, 61065.91854341057, 51724.7490370161, 95823.01740222417, 5899.350442068929, 18338.941733766846, 83916.376072358, 31100.692244326434, 3941.157840576759, 72497.43423241995, 7760.0218247417915, 11019.511650905755, 30812.941646475832, 87528.34596907794, 91195.8143187862, 31695.691589310492, 53036.71912652369, 8743.536724800006, 7603.786297547133, 92359.11715052334, 27257.104890728846, 55839.5583099351, 11987.271095521468, 70695.60378718733, 68487.36875282635, 20499.91645507677, 41535.28773826617, 33690.690207445296, 60658.6339177528, 52325.929867603874, 3930.175292255189, 47881.37131043374, 62468.07631142413, 42909.200827193, 10328.622585821857, 48963.37194571921, 98273.60638885482, 90461.52071653698, 7284.4963744553515, 82363.42034798348, 21923.537724525522, 9330.480671382968, 26671.400456096984, 33003.31884473179, 53022.13512389566, 33139.10131364073, 48291.04972104005, 89343.66768869781, 42837.009832310636, 93245.07277931913, 35307.707924788345, 64546.83094775182, 37081.35144675055, 69080.60625125347, 81116.76189438831, 98988.83043377826, 15755.914858027343, 7819.967178956411, 86421.54720624717, 58953.46517590208, 74649.31056724094, 11848.523758404672, 74113.32985855172, 57915.29400254649, 45147.14323822684, 17284.01362580476, 36908.452436410465, 67223.79850370996, 73497.29993439236, 46960.83271969362, 58304.78504251405, 82425.18283711844, 86819.31768684763, 4341.828146035864, 42027.64139916918, 36236.443596519355, 68939.85672730739, 81713.37636024371, 44085.179557339834, 545.9897114425183, 75626.45121359546, 4883.6554144568, 65085.10599214915, 85313.91803991118, 66183.59975983972, 74852.95428564274, 84508.10832198098, 13834.664869268332, 91093.52969867954, 49682.740222741675, 33073.348612156115, 16394.56607147676, 22592.741827021233, 96186.48744758949]} +{"id": 696, "vector": [99274.6417264243, 36483.021173026056, 45876.64274311104, 67422.18621936755, 36002.17387230971, 50592.270367992576, 17764.86288604574, 59682.20479360725, 82058.65453191413, 99660.39141308337, 95826.64262066767, 72259.34524969658, 43001.33225697397, 61487.312250996154, 31807.572977652577, 26299.547571766645, 29541.282248944957, 12949.141136492914, 13987.4899053964, 84096.01824566082, 22509.638886074503, 26438.3851389311, 38858.129317614184, 60116.137716282734, 98620.60930592108, 58383.381580475965, 67663.38071145197, 10810.436153754532, 23502.509939723626, 79842.3124641431, 2480.652622278756, 52656.45478575093, 31496.751877261388, 6164.426271155421, 74510.26738875161, 92060.26682193097, 54265.39123536819, 48268.07251197343, 1908.2505137974936, 29844.427347593737, 81703.24416442147, 53579.37855301212, 14415.883249054317, 15849.78392097084, 7556.384789506498, 83306.08642461759, 64900.99721983074, 98756.94118527866, 30399.094115331125, 88807.31881190294, 23653.491281902363, 57746.34246507545, 42137.488985408745, 65861.22777944281, 79672.01330133442, 81243.88556820346, 61633.022218531405, 8011.434707486653, 68108.40731187949, 38228.1928789354, 80950.11064771423, 25590.525237567952, 99399.02837273915, 22244.686751112862, 66438.28108357222, 91782.72281301324, 37868.64300230985, 28873.76857073608, 7210.293884160601, 11060.478608969415, 18810.92242138358, 5466.656514944679, 39887.28032194081, 89683.63968033927, 77232.46667930466, 20132.320317656504, 18216.90308791243, 52521.19502860158, 99526.30122736948, 52939.509440625465, 96458.04019934131, 55448.17893011531, 20592.667018709475, 45407.94642407333, 3106.9279195853337, 2867.5158703789116, 6256.469550379429, 9421.804720069316, 23701.15163023472, 21118.349465627918, 16842.63087413784, 80547.78906529996, 46053.93501165556, 8801.101228143592, 53387.434447801454, 86358.29257838691, 63501.30561617918, 80015.44166723189, 21931.046773780494, 54676.70220143445, 39642.51416999465, 68643.95375023944, 22754.932467246337, 16083.986909916093, 5104.990701969825, 95503.02743373785, 53782.41280151972, 41225.63641218924, 16723.78311270294, 80899.817486354, 83904.0237543612, 26703.954067825096, 39076.95463326857, 30398.804928130685, 64641.80034122717, 2659.081374848549, 84343.39748345016, 56361.83092259671, 81494.68408660863, 62818.109606666105, 36887.71869739482, 66304.3892509202, 88941.76874992899, 13188.47933202073, 74911.22651815852, 30482.30650661469, 67631.46638375755, 98209.96136673611]} +{"id": 472, "vector": [99371.95741911468, 91236.76989522966, 71649.85085154686, 2599.0461540259635, 59721.70948073382, 61872.80919487908, 72346.70406077437, 28986.111781136104, 4488.95691279555, 73379.38463078845, 92498.4263371847, 95144.00039776305, 33163.229859954234, 87321.3370461832, 27942.730156320828, 13920.645421453404, 21340.039676678312, 3233.8331562931535, 10858.063874268653, 11640.904808000963, 82683.14558521046, 59902.42286339248, 76325.55276751431, 68133.80847880473, 45005.6946829426, 58857.34219181936, 39136.89406786745, 77271.80787548934, 57059.73417097517, 76491.96751854867, 73937.70818733516, 39842.37285820885, 17649.977092599245, 54443.7916253233, 42348.252607594586, 25760.776453541344, 49131.721737992564, 66932.47608878605, 50047.12384724743, 28186.15884384028, 40263.32545688929, 4580.407943347997, 13700.770789934902, 33004.79359693293, 20568.448272045014, 16492.00053613187, 6613.014594868105, 82059.49486229684, 9963.606362404787, 47919.80591204461, 79286.99013298165, 16841.47242236592, 92239.84480512938, 25594.405391715358, 87535.36841603382, 70171.40474818034, 10944.642269735194, 62442.31538381964, 32274.387085329272, 75622.35446823479, 29767.44726167335, 32093.326763143326, 63835.77287072051, 28366.60751858866, 95842.83961956657, 89373.31804432857, 78815.88917788026, 50670.53350882944, 72694.02038823852, 62833.799451505714, 63011.07698930441, 70353.90850730195, 23126.347400370272, 81008.86803764284, 21174.069239465774, 70913.47027531496, 19316.106105448405, 40548.54800689306, 83895.28313962868, 55509.61428029505, 59734.87681445182, 63776.65366064116, 3293.7382297272497, 8598.601174427711, 1054.6320791873454, 82554.153642842, 28943.75562919741, 77768.91113755037, 34968.92455138181, 55869.66422546625, 56987.27960513982, 58109.41475859514, 18361.45890094406, 40961.15437737902, 6795.408376877898, 95933.73964945471, 10722.289719582977, 46887.93272713266, 38491.02767538054, 43021.824715671966, 56779.1870203959, 86863.82533465198, 30770.18398861784, 69021.44017585352, 46473.66623076451, 39077.90941321737, 82109.50024051276, 46180.567818668584, 28329.91675337162, 17524.715329573683, 54187.24384449546, 3447.3057668230613, 39767.01472233645, 25160.78105390518, 3907.192478386301, 40344.112254174535, 75482.62562882659, 95202.74467034041, 20519.509763338017, 20246.09661906901, 2489.158513262257, 11092.891244332093, 26594.025809281207, 24730.03151673211, 40356.43810345046, 64677.897731438046, 87925.31872380803, 36913.817905247415]} +{"id": 473, "vector": [82415.77458526344, 10300.319388125656, 23948.019481332107, 70967.05740621054, 53205.520307359046, 58591.40110763852, 87553.73705960323, 34478.31565983063, 73772.53632933328, 16464.904387011313, 85894.52453388544, 49497.418234872035, 78964.27032272228, 20474.205451316764, 32213.474539983832, 8139.438506677543, 40654.83471484111, 23204.720029770386, 49324.68373941143, 72722.51237825224, 58778.6293349245, 94462.38568453326, 3426.2739532114783, 12733.069577205792, 80859.19252756242, 5995.192731374111, 34852.16766901905, 47446.16512302242, 2139.0125050767538, 95963.76516863474, 87963.51797190259, 67036.38948688918, 86328.46994165293, 29392.756552841194, 58664.5713039338, 77820.05195503082, 7458.163140249907, 14520.80242656345, 52456.38652208777, 54540.832536819595, 7072.276823876533, 79043.63301117429, 693.4465145900015, 62199.87789541108, 18193.53978003463, 78827.40878421628, 40610.870387461604, 97620.1604292276, 57150.49193565449, 80894.5777100147, 86908.29609188219, 89555.022311497, 44790.47142362733, 16075.552459057973, 78511.96366588214, 37870.665609130796, 66471.09925201103, 33641.58040022907, 55351.38182236948, 11713.237638398832, 2918.6228604552666, 19992.973230877964, 30907.87058136323, 37577.51468065098, 9402.345686221102, 43331.87235480398, 81270.95305407426, 29833.09918161746, 44366.79568449132, 91606.12091536917, 20156.973440602455, 99277.93660571736, 91083.68409817938, 48589.38139071566, 99281.02652820271, 53573.049186377444, 43003.96216758534, 35252.866994695556, 54163.519327451635, 70167.53760466453, 9199.994418890166, 99804.98436116187, 64465.02793992058, 81164.36021189798, 22423.661290241882, 18314.59156948403, 62011.4535534669, 7475.96515123623, 24729.265342510032, 85373.37370260182, 16024.769643913594, 45881.90971802907, 64324.22964383596, 99602.17488853498, 30956.854063206916, 33395.08559897709, 68492.23219221829, 35671.31162770402, 54564.794935593374, 71323.5140959951, 39663.03940677431, 27178.10671519143, 6784.02952281334, 64157.73167860408, 5308.62144880353, 41515.59690056217, 20870.755759691838, 50393.59425262853, 30145.104211037666, 191.48183939182718, 13085.966372353409, 92419.08144566296, 92964.48113185685, 39355.23556630558, 92736.6266263817, 10913.382332429555, 73720.70073332507, 84929.15805625239, 63764.88158884759, 74944.09142920014, 92869.1946866423, 10039.153962341707, 46975.27305049687, 97740.8704081005, 98901.72831722381, 12836.63833464036, 19062.149228635906, 22173.75910957132]} +{"id": 953, "vector": [77428.8166464561, 40057.95033640705, 84434.64207702561, 58600.1022086038, 35911.208618644974, 23715.337670402314, 11976.133191230765, 31740.34182537915, 14544.234616795437, 53281.18115002781, 46153.41833536216, 6319.963018610886, 47389.94626443117, 14967.706564273141, 30230.045955117625, 35509.204973022046, 14668.873065202948, 10250.98852751576, 90299.62823859353, 82177.2614187722, 70427.1980490375, 34363.213625051256, 99986.2567232488, 45015.45717350094, 12280.782389328659, 19220.72425702347, 32096.537837019412, 41846.12647413771, 61420.85496093932, 24132.70623415925, 37772.28660809874, 38402.71169915755, 77250.46888355994, 38951.28030644911, 83922.96991342911, 50514.28197088756, 33235.722348176314, 46870.89972630553, 78001.68343222378, 30503.160955956733, 98615.21919400648, 3902.650784518025, 96471.29922822076, 59373.675225170875, 89192.93367011767, 2458.6931371221585, 68007.5154717109, 38148.53034606499, 6435.364164719582, 38828.98740389025, 34029.67704794009, 44657.052458799226, 28196.148976161283, 44114.00219218431, 27718.08475415859, 30952.271063061908, 41403.67167695739, 54928.47315961239, 34225.36576178995, 1411.2540712451937, 12193.844710980895, 31924.913728987693, 36027.36511525526, 83455.7593712, 43026.54735840073, 27516.977237851283, 79038.50702009475, 93515.6492469835, 84126.84604050509, 11120.256507144422, 96387.26578763417, 76672.8488800914, 74229.65277957775, 19441.94456678664, 87250.17162348748, 8880.282099571934, 56832.66520064656, 24252.271717487005, 49917.69439643643, 65216.2258544842, 48323.74565436912, 5138.237775467891, 41770.82563475444, 26762.577004023435, 28535.90553667682, 29574.935318219188, 25328.29803853357, 32756.836193481842, 65830.15126837525, 24240.273352365493, 90508.06969973232, 64318.53340103837, 28661.688428571942, 22595.974526692466, 62961.79089122661, 59011.32274919634, 11287.86558249686, 35893.13587002437, 84950.25787117262, 29541.983624888657, 77082.2446959094, 42455.53423839732, 30658.584081669072, 42532.65888598342, 34371.208815897146, 25702.034711795586, 11465.653495882732, 45767.776262901025, 66772.38420420153, 10950.083874480542, 36293.02424469074, 95519.30417275736, 39913.68290398507, 11673.63739349, 25960.579191521403, 14269.28685114468, 71066.03251420916, 19800.606376451447, 28836.89415213527, 9900.423460678609, 42159.87956310058, 57455.964926584216, 31528.67987229153, 86450.41266577688, 24040.73197598825, 68395.63721887203, 16053.85149536792, 21145.89206116576]} +{"id": 890, "vector": [4096.445899333124, 10589.155495883851, 4789.178596668342, 14697.18422949563, 55743.85169286934, 85402.32082312566, 35856.43770066144, 63740.66726414814, 29321.66497387204, 19944.326024706395, 8855.715219807715, 76206.29255221871, 9763.926544817514, 17745.796135541725, 73420.14229668316, 91605.43422381, 23727.114397007666, 24077.880410475318, 43826.25738113569, 89037.1645816303, 84360.10469506863, 20914.616572965315, 25610.454776362923, 24021.001227696546, 93567.59381820266, 88888.39122062194, 9470.539679005286, 82010.06121177108, 3108.0476002380287, 57531.21461667381, 66719.66373824794, 87610.64371181995, 18489.873496958586, 28040.37030367489, 55736.465400293455, 61228.63036249354, 88548.3859967614, 65045.11890370373, 11272.367409795781, 31160.85006424405, 83714.21508053697, 15091.921022939403, 46264.326887805255, 96845.88367093031, 86726.41807124754, 72526.71988411581, 73545.06314136177, 71340.58337048469, 628.5954600789067, 3475.451838532884, 36204.44324648499, 91042.71320938948, 59700.83930031639, 12028.683518498228, 76730.74259194772, 57555.109810033886, 95447.78375703166, 40728.10989289589, 6898.986367917248, 92585.58310890199, 8330.914238114761, 53579.6487212099, 22280.027072717156, 52175.18107863276, 38063.725886273336, 98503.31602837327, 42994.33740032392, 72606.42634671305, 94039.86333706, 54849.820223981114, 88587.45824430732, 18390.501192920816, 85767.87963718091, 9709.992250665233, 21366.854207113895, 30856.000073367995, 77271.13911440318, 39342.63368341332, 55505.60054134742, 89325.5595870578, 99723.10547756808, 81028.84338314475, 8583.363983547542, 37192.16315599561, 83551.15616821637, 49300.021533793384, 90320.7315395606, 92622.72731433914, 29750.142407086656, 18223.875957136992, 29296.721605946143, 21288.158451578387, 17563.229859344498, 71554.65612529755, 36259.41106691635, 66262.1037866398, 35470.39160331207, 15766.79943183613, 37231.31383509183, 78509.89781970899, 79087.87200521203, 29569.48077993059, 64288.81243416326, 39244.26424071196, 35033.792420124686, 86452.14491862805, 71928.59701878195, 70582.48862785751, 49267.56390349582, 7615.817873635122, 68524.2664128648, 26734.33942275947, 84022.3822099119, 8350.962557749897, 60936.50950321696, 27697.569415227652, 4681.45194042433, 2532.7770242461247, 83639.10494281218, 36321.90690129245, 23427.2426590309, 39397.30677268035, 28502.647831760896, 1697.6326287377885, 11723.240338005848, 23074.4821969208, 31536.705397018395, 35384.33165822345]} +{"id": 1259, "vector": [10351.31604517937, 36181.366534278946, 41775.55892333138, 26639.733480763727, 32651.896983251627, 95284.67740490819, 7893.018654517936, 9457.39986511639, 67650.20826980376, 30323.022179255975, 37892.45542032407, 60811.7831132928, 92553.71911394867, 93017.67392920748, 53335.95685677991, 81910.34862374583, 63154.78379525272, 23901.16811850119, 5724.646764894781, 12461.072509698955, 85745.06652045966, 88015.69839602901, 91380.92430044716, 24324.796649126678, 2496.6396636741383, 79328.24347512583, 79747.39912812518, 40342.12665496898, 87959.65300687701, 79861.35201605882, 63299.28618474553, 86747.32078350996, 76229.16725992633, 20061.14940709227, 49894.7347771685, 89649.37062645626, 54521.61062345443, 77946.18301245794, 32890.292292069804, 9621.09663079348, 99593.19777436624, 92628.8365621545, 75877.38975383648, 86786.97756002273, 96688.46887868393, 91748.18939173232, 5006.130712356138, 98958.9049938207, 533.6789188592927, 59201.43787789393, 6019.7281620683825, 18094.47245954393, 667.3537789526063, 13290.97190133488, 95709.00721496326, 43482.826238863956, 28724.168191730216, 20436.317622846047, 74439.26869388367, 31268.1778284303, 33872.30439910873, 23322.365435139756, 79272.92552676176, 51347.71171818664, 16161.451647138758, 35220.15206349639, 90252.72926153976, 89103.9279237912, 78062.13205235796, 42223.93888171011, 67580.01985473554, 20472.76044635997, 70962.61513994286, 71763.90229536002, 53074.632517269856, 34041.81229141562, 23276.36290140347, 55786.375441039934, 30982.940784046354, 83903.73791485476, 77097.9326702363, 9187.332080467004, 83175.64393338934, 14138.975051282687, 46111.791101126175, 36371.54019353471, 33282.47068242005, 75199.40538050722, 70413.24283280064, 42802.69167110762, 36627.56174808237, 27545.472954989014, 87679.93167126841, 94168.92340840476, 98663.77107948967, 23275.015486653116, 11550.423248180452, 66952.64475278239, 35524.76597846386, 27802.520117566222, 32778.154336641244, 86535.72986281393, 31311.737129715133, 75843.78741470598, 31350.188807404535, 52023.13395925327, 68717.95859493062, 93029.00025985467, 78594.53837829082, 7109.926017390789, 78029.18658803219, 8344.244412054048, 44791.17356119081, 15320.902617100406, 25080.102703965225, 99106.48243140354, 66070.8472140506, 95479.97796055957, 66298.60244166815, 74414.47109967675, 51839.0943819382, 31080.954080994485, 66953.51884034273, 16555.350900296296, 35036.08285605587, 5558.26047233241, 80453.27081863282, 18156.29730614412]} +{"id": 1757, "vector": [96779.53864192034, 23107.43499476544, 26049.174672512974, 88185.0917875635, 53889.17034213373, 99089.19978071048, 28061.662066660563, 44921.66121072416, 6497.790944727688, 79707.91003374275, 47919.3270934154, 87817.17206814961, 52590.3988979576, 64595.42107204684, 16274.304477732792, 17472.31169548754, 28424.001920900842, 75931.47240122872, 38981.773734705195, 3637.710131337435, 69498.42232246598, 94281.98830898402, 80414.75576710545, 1656.1173812325912, 22778.150607771564, 43711.53416893668, 51706.03345597217, 51763.93018586203, 39759.24091858101, 64617.70986033343, 16812.12380523933, 54613.390146757076, 93404.94796639531, 33335.67646456973, 49983.41240841705, 18017.572406169667, 49958.340027085134, 67539.08331369511, 26550.31647759203, 96415.8952314451, 80131.84623399474, 68389.32680160938, 87429.7009021667, 28705.932381732502, 48963.92583443267, 54137.21172719902, 11806.188097695924, 45288.02012237882, 16106.794880140895, 3851.663776753023, 50544.33474540814, 2555.9367297277367, 59095.62607842963, 87899.98538805851, 42008.73594903425, 38127.893348483565, 60929.279778436285, 91776.87609218067, 33194.2183091693, 22351.625100947138, 55347.35690137724, 36170.38020660218, 63661.518584402176, 71455.7499418169, 88603.40841479985, 37906.76388476266, 92258.89915346213, 34829.9542761789, 97305.96430045289, 71131.04569509781, 45135.809467896535, 7388.584791838626, 3.1603347251607516, 55687.02598835077, 84783.1994945541, 92866.18883619104, 67194.82391163286, 93836.9208035577, 27705.09839738582, 7347.891148519392, 93505.3390032274, 63596.05830617731, 5157.421876554246, 131.06962629844122, 71455.85586341038, 2754.684840910615, 96384.68914384086, 78970.41464837102, 85532.5064667309, 40791.75560808822, 96272.24376519685, 5158.793162069553, 14171.007384597046, 51180.04923445028, 99724.48925706596, 1467.300794356774, 8172.753655079235, 19078.961376599946, 17161.95179260319, 98810.06006212898, 52239.5652374157, 44899.29570057085, 93953.90080408978, 59479.182870934346, 10096.460883103908, 83831.02760627429, 70889.24145169837, 47502.0087449531, 81362.6593035139, 27381.28780194439, 59477.050378942346, 1132.8646077705007, 27809.728999486983, 89910.88446980166, 13235.183705577925, 35825.85279994591, 29297.38243147284, 94157.83189744713, 75381.66140843343, 71354.37784915803, 56615.41584113289, 88756.20690387688, 69929.04019657552, 65105.07925479906, 90625.70716446642, 86788.959124768, 88986.41870608537, 2631.7093935842026]} +{"id": 1375, "vector": [36386.646878831474, 51886.0260521203, 6395.3556809367765, 9142.616896394296, 88254.61433183764, 70372.72281096218, 84440.85806337217, 74732.89945340056, 20020.581041020792, 11720.024818495422, 16446.316894543445, 26337.30891921201, 89224.64120539234, 64134.52168597241, 34862.84827137435, 20136.48588744441, 88328.91601165356, 16356.719936255626, 70174.98816350271, 13271.994181161428, 70499.85204191465, 52187.144971695896, 4774.151220971013, 32845.50629350843, 90000.354379164, 38443.26673782913, 19014.347703112388, 13387.988005231222, 70149.27710637728, 25290.020342702668, 89882.45646058096, 38559.86620280812, 96289.30378315006, 65453.84158224461, 16793.682757396968, 84431.50827402656, 16887.85609029153, 63822.91640932652, 88335.05432325546, 73680.29261453975, 68212.51356162883, 47906.9745295027, 82423.3266780868, 33115.89059056016, 36124.76171921938, 60457.307286306816, 68777.00967664963, 71211.23521351564, 24031.054042434807, 50219.71252746539, 21069.084490659585, 99506.73478004661, 71529.52377637554, 54016.38380564119, 56093.31621256247, 9968.770467365674, 24821.97344753233, 40501.138052653914, 17325.55070137285, 19609.362857222535, 30450.326381411287, 1705.2345218395938, 3516.0190939667714, 74333.15296611185, 52212.163360607534, 15603.327565664782, 52981.27662103673, 93467.5938886448, 61324.23527445239, 62940.34158950451, 78031.66769050669, 13765.771555015182, 59778.71622140655, 56832.45112422878, 94510.87467725042, 10324.143104476092, 51544.107708553114, 9371.668266782208, 42629.54755369129, 28189.47675591238, 13374.927023527449, 56760.91026219513, 51226.29896655756, 27424.59722531776, 70093.34373310686, 70881.15093524718, 3531.736712129474, 22967.15994962596, 94467.35481350098, 6020.934176155435, 58146.61426214345, 61155.945246256306, 44159.55026798502, 24800.250238919376, 45524.274817752375, 68794.0594445727, 42877.584040480346, 87377.42525460594, 77686.11267990568, 61781.11476583813, 62770.5274987615, 92838.01113851985, 57615.168455352425, 6422.892606326381, 39300.09344283122, 70762.17897496864, 93293.24095818869, 33300.28672891417, 59812.3511943094, 85144.71519909082, 32113.6497636375, 62651.56918179973, 13582.865141073375, 88230.38032956349, 92216.20356355111, 33282.90116476089, 13084.034898557384, 11893.808380429582, 59048.43335767869, 97469.46718892586, 59018.15460747175, 25521.836722309854, 95106.54546495518, 65660.15915534155, 57932.85862022713, 49366.50589812831, 90284.8300933526, 73153.30450996601]} +{"id": 1358, "vector": [82239.97743123995, 9989.703376814417, 89297.18938165739, 67154.39791559498, 93074.63945024536, 17839.924619818637, 48939.342348624225, 74613.27226577517, 47115.40865857384, 99766.55507096308, 92134.48585858564, 43125.4442324157, 88324.89959163625, 18332.08458051684, 97186.18098972454, 18456.07383178698, 58876.17136027714, 64669.845277706794, 38836.2339316791, 3092.7961213168433, 80046.55042147033, 83402.90175767345, 30612.13840356558, 28772.889179123395, 7392.464332016413, 93916.95416086374, 87915.47042497192, 46864.657167068646, 1663.624803340691, 52933.28179573348, 48303.64697661449, 91825.04797208084, 12022.739666302006, 40127.143090991325, 80337.49955848143, 9262.8443253303, 35502.193731635045, 71855.67084676733, 79306.40122831328, 7460.143999993318, 43334.531695042744, 213.38078692911822, 29830.318290040457, 52068.30830932483, 80763.04241759935, 37578.71404819965, 38879.08504058278, 59142.13875406138, 33137.06941774088, 8996.782322387253, 77623.30610254114, 49292.15089762318, 49831.47493781709, 82113.77410390822, 13950.691236404322, 81537.71536405072, 85986.62920616733, 69283.17370355049, 33089.078021149064, 90913.38860766731, 78848.29407471765, 13593.46517715525, 3379.997988688166, 81163.32650650191, 26821.81626726531, 72344.61498324873, 40595.419890907746, 54224.6411280866, 20400.79579680295, 45406.350468250064, 89600.01889681509, 41110.76138727184, 4471.951404745245, 64937.10651486082, 58566.89094533921, 89758.71115079499, 44970.98501488659, 5956.225804435533, 88679.81936675821, 66142.61586899431, 23877.68359347967, 27224.49857347824, 11177.478721650492, 18656.569510781228, 38115.99858921133, 41192.39133050708, 78616.91190336103, 93463.98581701115, 74409.26999375134, 44804.11626792474, 29983.842870391432, 18035.857284921265, 39449.562580290876, 57278.95018280792, 15986.780753539399, 68911.03424065009, 60656.143489821225, 50639.14996084548, 96046.04384380803, 5748.212173722367, 93338.77321177004, 7456.841681202742, 43647.61139138052, 62573.542623991954, 41542.61308592713, 76205.44672905313, 41151.12474078857, 89776.70807193115, 84862.9670954105, 57220.76959313634, 17048.230930790498, 57299.012542179065, 69983.37175700569, 37009.46572303195, 9117.680658161831, 4318.405621574706, 14068.00502723472, 52163.79421789096, 91629.19586028667, 90476.999859685, 40484.646860131616, 70319.45188098897, 25773.649272150113, 89755.76668276305, 29893.533922021943, 24419.196667160002, 59168.22343734896, 89604.8307578909]} +{"id": 180, "vector": [12057.760533631934, 54378.79717926605, 24978.273196535138, 41008.135038901084, 50122.33906888449, 2903.6763054752337, 84710.74341316002, 33729.897435223174, 97039.5306254621, 87670.04451418137, 77739.7774371345, 36840.88992684888, 54604.03415444653, 21368.54223382597, 97570.15507540348, 43173.749045826415, 31827.244925191844, 67005.35724303147, 52121.526486482406, 24793.83553502488, 43379.9715548915, 19986.91299483466, 7194.844063095152, 56916.00228340415, 61892.85438811236, 15838.394752875905, 60242.29055294472, 58098.473360155855, 36625.34191818878, 62673.713216886186, 61846.514241044584, 36115.66537588148, 81171.51563281853, 28418.20362313625, 60585.9131133877, 73433.99340752163, 39401.03310021195, 80935.06780444813, 23538.247995730777, 10119.598307556455, 6733.8382314620085, 55935.01950555059, 91109.17783689096, 74036.77337366807, 83875.37085981599, 23220.92244849946, 62310.565759326564, 42766.30128057902, 27840.11854416074, 68774.33640543878, 31100.962488306173, 28933.341935060707, 76181.47807640306, 92143.7596679156, 84047.48497278047, 66605.2095459352, 10114.496940275141, 19212.98405858276, 14039.09984868068, 3632.0126430411824, 83854.62908327984, 44668.963185267865, 47878.36751680851, 52661.41240550347, 11126.081103856555, 23719.557126041214, 99283.73979198106, 16264.890224774297, 16535.955686465542, 41849.996439636176, 11458.465576289424, 77185.82316928665, 87519.55915013822, 65215.48686614902, 81891.55924739808, 4633.989514914183, 44312.34009289492, 12842.115085061878, 49453.864543246615, 56302.15516346909, 58247.1554800797, 9386.720662210524, 14014.637299095955, 13678.583843222159, 35303.72007715764, 83181.10403315796, 37811.70979494137, 9974.967188671535, 36073.13047313582, 97030.97673025935, 31952.83502412314, 62136.858950746355, 52927.08054848292, 57481.186452485344, 20474.631991468883, 2687.364822590399, 28023.74119846146, 31052.00480881316, 70681.84457592998, 55016.009441227776, 93548.76037937331, 45501.131138676195, 94374.37336242871, 36082.977253041674, 69189.82545853882, 76558.46538626996, 14141.18594754059, 76383.39830015684, 88876.74320808241, 29734.508833950156, 89250.21343823106, 85909.10072701379, 93066.59163337533, 67719.64822617239, 36862.83615714311, 88861.03434404306, 15418.747793050414, 55300.129499071714, 84467.72183279235, 20427.145435577888, 4579.709837139833, 65508.38222163754, 51251.63651467386, 46509.071234947805, 90632.92728457041, 75371.26499996865, 76977.78594856082, 84545.74755572726]} +{"id": 742, "vector": [22760.900592483657, 73249.51717604988, 34956.78703810567, 70652.75931766647, 34643.96714123171, 11743.327923981806, 31709.91639030972, 72626.20348505674, 69997.37672768769, 86397.3925707431, 58799.101329319936, 24345.611328454863, 73094.19042966237, 25582.88036446108, 90863.82581810773, 90405.99688667865, 69439.92753378008, 48781.2068161821, 40982.67350226931, 71710.20818675571, 56985.341178472714, 27046.489097236547, 57769.99701391312, 49563.59939556669, 25746.399615818304, 24811.162485574812, 1410.8368687296036, 67853.65252247933, 27349.270656762936, 27781.528963641988, 71517.75163654686, 21339.85419669793, 89664.41906510202, 70116.36512643837, 99650.1845224778, 84794.35960438513, 53324.69881032938, 46532.7881574877, 95698.20840926674, 10322.525985096208, 28318.094595504506, 59533.97777265297, 5756.621886993973, 34548.32312743434, 39365.69026211617, 71344.21789224967, 68997.96606638725, 39477.54826573866, 30256.349528410898, 19714.498105941024, 59882.5703960567, 17386.318037560333, 80264.46529056544, 49241.27072173621, 95674.35348790576, 47256.074596113074, 51058.556016367016, 84632.5286705935, 92824.44517057536, 28336.246104991413, 39771.800041834234, 30961.997459767408, 4168.255209577365, 33638.85937241462, 40804.89782557972, 68328.26993608008, 80777.83836943451, 88593.96972731379, 38881.978764474065, 1710.071720468076, 92957.24092470713, 28105.3532946573, 81347.81672409855, 97704.48595025051, 96431.69107967576, 54515.49418140383, 8567.959317829554, 17539.22100366584, 52741.851852281055, 40305.47531352425, 1622.430021501786, 34373.48387346312, 75899.14374278554, 54228.37293209347, 80219.96230732351, 52081.189319284946, 21943.171783467886, 85826.27880774127, 22115.843945361758, 76941.48036751947, 89120.53855462388, 28609.055509108526, 82150.57982744412, 37790.37417546538, 26136.358705098493, 6557.858935217587, 20753.007112452793, 6424.506266732455, 55736.87187291433, 60945.58014273782, 73261.79395487581, 38132.234925913275, 73751.77196680295, 68723.28911196522, 83602.19074181795, 48730.591372805684, 49835.32037199287, 96416.5640797904, 936.4477218316903, 24737.241220122563, 21932.05397117969, 16205.032991642398, 88234.08391438978, 62416.45171034401, 19289.688070469114, 8332.297962289325, 99788.59258304474, 29488.6547382894, 23354.14775996867, 47777.8977227099, 17016.624836466355, 2189.5732646592614, 17661.188358176638, 69406.50271939547, 6318.0616539274315, 25472.52977556561, 14943.281162294319, 61427.81038515697]} +{"id": 2031, "vector": [39827.933779937404, 15978.78509298747, 54325.06405185241, 88156.29152696759, 2498.6869000913516, 38575.128742269684, 8791.754371861649, 51078.60365695341, 69912.93689775949, 80971.04916995172, 47233.07066481085, 28555.12939693091, 99139.76349532114, 87465.51868353103, 44478.27427732369, 50057.9095081941, 707.907510459993, 7279.099153514779, 84489.84576785992, 26651.38020038407, 81437.56425986908, 31269.56641659088, 60663.848426680466, 92261.65377804059, 39160.15943974679, 42184.875868285795, 2922.7790545097055, 83266.70489919647, 60684.695368965855, 27976.05205303978, 26526.091493472748, 71870.13116435413, 78678.0356231503, 65194.46899758069, 36773.304599334835, 16632.83180678069, 86973.49567458645, 58790.0730906144, 79727.43591801779, 73964.33838298738, 75590.66515437132, 87619.91454023992, 83993.17991749017, 84746.6043470563, 17142.666802271946, 80626.9749678799, 74758.70028956753, 41978.174874144825, 91913.14088588578, 53272.3863070428, 95627.90495748671, 4625.156090135296, 74303.87757397766, 2533.30097878971, 35933.97819019441, 10156.489059876616, 40788.997588674225, 19052.87013446022, 25557.47326087253, 76611.25229849386, 51619.34735352109, 8127.951935762745, 23814.942404092133, 17418.72543333741, 18874.736674865188, 7493.546490079061, 10732.4140701615, 22748.781952291873, 27609.955576027554, 64852.801089668246, 52783.999814426395, 17994.117419232392, 5887.361712724515, 18951.39982286852, 23271.060372215292, 55046.0184043176, 12276.761610745334, 65493.98004537422, 90429.97798026663, 48661.374572460605, 22619.603476990414, 99062.39501950864, 72795.48470301826, 94508.35911578994, 24956.612682144598, 56862.15257644315, 35844.881115202035, 661.4538399510761, 46060.90875916161, 20986.598000033096, 60271.03263440157, 46149.06209629579, 49535.81187759972, 20099.00657179472, 17596.383829253402, 36433.155773179315, 61856.07631103961, 4432.1453593059505, 33875.227537434715, 3968.9891847429103, 81508.8249475295, 82376.83593242649, 14216.191523829792, 83996.19980542093, 99331.19280847788, 74270.8326646353, 64804.182528469486, 11850.05282848175, 96974.83751670938, 23554.110791420557, 2751.2348634885943, 27600.8336824758, 60072.412035200774, 82822.16940958875, 79444.29633111057, 7855.416481612465, 97406.33199979596, 4410.376935231142, 13386.64841585765, 17885.50092942802, 41023.05767920914, 96866.50663123313, 11719.172278108335, 53436.31259862219, 71304.34194190918, 3829.0359948670916, 4107.457346385168, 85314.87551006826]} +{"id": 575, "vector": [36504.05545428259, 72246.51206461781, 98301.77012360012, 65414.16394475831, 54413.51934288689, 60043.878604958845, 44982.250114923336, 32572.571015429552, 17261.067493411698, 91055.81078654146, 72876.68757633163, 38372.53652706672, 58429.322529735386, 16464.9444311591, 29657.994989853953, 70696.7913405106, 61380.56234626945, 98598.35115026712, 49279.85018368972, 48584.27753504934, 2459.6637243906926, 25750.426578472252, 84660.95023122693, 80486.21317113821, 18240.842788205457, 72837.89539726573, 6083.092207218632, 32903.2260514258, 35424.260075395476, 27847.24342054511, 78689.41843994246, 11660.706170207402, 71127.05613460607, 27352.969694583295, 4227.0536049671455, 74025.65341220432, 27620.936389919036, 43703.30546883713, 5705.785468715341, 63265.73479789569, 54603.119962022894, 5423.472223090786, 41888.52229497044, 44643.185483910056, 1127.3313184691046, 94119.17512263758, 84450.14154621739, 8513.989088934182, 93109.29284618926, 44393.443643197985, 26264.827364595887, 11460.186108974434, 62002.988599767625, 54864.875342085514, 45047.49301479332, 38207.47569711159, 68312.60565205973, 65848.75424088881, 73093.51101663156, 87449.02973213926, 13799.353019179394, 54163.94528494108, 8454.44492763996, 69332.05618234797, 31387.927915372493, 55304.33284708279, 67375.32730319243, 34680.3726290399, 54230.50276646434, 32807.04471648047, 19975.337426330854, 36025.07435147532, 58677.018494410324, 41022.84519070581, 25813.74033007463, 61723.883168888126, 97548.59679025602, 45439.38737775417, 79185.34675378285, 48652.11510753994, 88249.63049302598, 87060.64325900824, 29813.44761156851, 7156.994203737232, 13695.56126155087, 30016.874223183677, 15730.607331429213, 82802.61102179775, 12402.385591278531, 88142.79579720122, 38377.806467777766, 81900.44928974379, 37775.41843624602, 41138.40586955988, 14723.233614190112, 33202.191405698824, 48708.82638148573, 76248.02048280957, 47962.77411849977, 73075.47993048505, 45687.68035988092, 42651.940819154355, 81568.40272342144, 46289.20645416884, 20658.01412519297, 36771.84211360649, 97609.13332113666, 54792.105861522046, 38241.846780226275, 32824.234952067134, 6785.99189312954, 17454.080199297427, 37209.992978054, 99411.52937931378, 49867.335234384554, 86867.99593426636, 49338.556563437785, 25597.47569643115, 87765.74431238044, 31310.1835114039, 38229.40512625276, 6098.620898558515, 26945.25439729514, 84228.32159464095, 31770.793656869668, 34348.798607116325, 54209.114867454446, 16737.361367674108]} +{"id": 2005, "vector": [57639.74098185951, 90263.945205901, 7105.676254547843, 39248.40981217972, 29085.294357675863, 59682.490011109294, 74264.79188321566, 51006.43428260966, 89917.95183953896, 55434.69693435252, 31530.586528160966, 85442.75335754656, 3084.786653354499, 14245.160034098802, 87072.46276431167, 15317.945831989387, 22302.989013228125, 74474.30921469911, 7815.274209270718, 49275.50284202249, 59131.33394179503, 66831.79697429696, 24506.72620159702, 9368.700780379879, 16228.199738058169, 53949.35184475996, 6980.758572977741, 94188.1873743209, 76747.07748821171, 8278.617259922283, 42980.759994130756, 69694.51347878907, 56941.731689731, 49650.31388983265, 12984.532855893072, 66532.43958346128, 27589.671654995094, 25048.39943334014, 37386.11406161535, 17626.09076337148, 52618.001844226834, 79421.36026937678, 31921.767350083184, 94162.23041179225, 30094.55961548372, 82004.26949257082, 78332.8256623091, 37735.75211593932, 87310.06899984225, 24427.40800094465, 40813.1232656404, 54137.25020324884, 67236.5307164714, 65971.67895651583, 2608.2817802886684, 76555.25972142494, 93587.03761945672, 91903.18532526014, 2698.8463108294723, 45973.862437335236, 64344.008269357946, 47593.76693265899, 29555.84142910351, 3749.9631678752476, 1392.7265554911305, 41711.2553140882, 33221.74337149001, 18286.07229292363, 8572.034999480471, 97123.51963734103, 73816.34073923896, 35559.1641982364, 80820.62010953041, 64650.897507237314, 61578.05296766768, 11121.599342629652, 67635.61853503894, 88081.08253860979, 60556.833935712704, 43282.69414175363, 64442.331014631534, 11182.251815832444, 82236.17792826123, 13642.340458465696, 30592.789824833068, 4566.290515737536, 23242.130963737774, 77969.72172690148, 94371.13290293154, 8779.395056638861, 74341.76617559603, 63355.42717985793, 91715.8301708376, 16532.77475402879, 14440.109986373129, 59823.64783561047, 49483.207943979614, 93235.38054470769, 7899.104754408104, 29793.053422389083, 22649.892481216284, 62094.91697038837, 28647.595426871976, 5005.284664890919, 54516.41783127278, 20664.254154940278, 16616.24854830752, 40700.80086017346, 73097.6868939116, 12744.931056570218, 63534.86219775094, 72380.05551613685, 16310.914713362012, 74545.51995087146, 12707.89607772561, 11585.917865132778, 27861.289439056258, 17879.145399714693, 76406.22776127295, 13706.703726390202, 48892.64907107246, 39221.416294601244, 58668.864352203454, 20766.53827669005, 39007.50892512822, 24339.605324553115, 53222.44334420111, 14934.73824992635]} +{"id": 784, "vector": [63320.09327533462, 38192.81022666801, 64675.73515471835, 78593.94614066175, 57955.301278770865, 14854.262548689801, 63667.87662272214, 45389.5411061473, 96970.64221739852, 8715.589521128275, 84489.8948078934, 9060.937528561697, 60292.109622259195, 61911.852776811116, 97893.61764208764, 21116.85577174802, 83389.48541266457, 65535.748534688595, 48352.87371612294, 46657.7074035375, 90187.71962546669, 26293.55654135127, 75070.26148481453, 94147.65319860009, 18544.333779745015, 82580.18327703621, 6335.515451421669, 12016.026325587693, 39155.66689867553, 60527.72684281065, 96121.98902210883, 31776.18901909648, 60870.861972161896, 6510.149727895709, 78144.48237878505, 86203.5377531841, 10316.868864869877, 62863.94296530735, 78649.14737909602, 7674.1794685839195, 36647.76729252933, 60018.64871408496, 87059.10353468162, 49659.50302773228, 96340.27772614574, 26087.861758766485, 29106.12554821519, 65599.58085618114, 35973.813638092666, 44948.01439796993, 13183.849865500984, 46992.82858558963, 50848.20959755092, 7040.8434028547235, 81541.71919668397, 99330.25296075712, 11634.012623083756, 96035.10112606238, 80134.90293391976, 55399.50807523506, 2341.7794018666837, 57912.344209546914, 54380.07152378488, 84072.76164774694, 25501.790866140873, 91628.00119102575, 44727.54540325968, 45956.7138895833, 52634.10818540819, 49637.49429385023, 81450.19381527699, 76003.64632243197, 55102.27711456793, 19557.554754411987, 87555.2836435722, 29441.32416725396, 56389.30078151078, 64897.62372815885, 60997.5761371788, 91206.68087247985, 17907.17387526577, 44006.35674495782, 71676.28122235627, 45824.64997440541, 22518.340050652132, 30897.599753488004, 23981.345862038404, 44535.46850124248, 42563.30151780685, 27078.54751325305, 91678.42871847794, 93197.53439194922, 96648.02898802055, 66305.0552101817, 12967.63210058849, 68240.17612909732, 91749.75316304603, 67433.39170312932, 23949.118961140026, 16518.13674108725, 63864.35854550749, 28274.11276519264, 79150.34601327137, 20382.04558489607, 78029.47565437939, 79825.50616166952, 30841.65299637692, 39651.26190046253, 12888.084594224158, 34544.66938785044, 51035.19525482424, 41266.25578975044, 76874.87734343362, 10710.770753350873, 70478.94560757645, 3899.7442706609654, 82399.96802572886, 20427.74020343825, 13985.729464964757, 23292.00017805586, 36375.90542248919, 14016.821544891645, 9346.86062957305, 58666.053987920066, 45519.9848623467, 79784.31707094643, 42165.03455124602, 25784.693063341736]} +{"id": 1739, "vector": [73775.20571367328, 81899.95654451792, 76348.86197968188, 37039.72323765832, 10448.979926245727, 477.99614285662926, 41964.76981799883, 27025.09736353751, 71960.09647893823, 99355.48328901194, 3282.21728334821, 52041.70077718939, 14.659688111606783, 33223.08181511697, 89485.93052901317, 13192.490515262367, 99464.93492977024, 18042.736672024883, 95103.56337098133, 74614.26747072066, 71393.03456528735, 75382.3514694282, 15539.747142168426, 63864.20661635218, 51055.503725023045, 82580.20039762445, 13809.34882010818, 15329.654575812112, 30449.427471327006, 92403.66934728278, 15092.067758568095, 92583.46803861635, 18188.1234149861, 43068.409289774434, 86156.29502225327, 84733.70921094193, 77658.37675182866, 70308.46546080762, 23969.026266506822, 60351.723297113815, 93265.003999931, 60468.68062500949, 44920.837655770294, 10182.296062884332, 33595.926781092225, 26553.395367609144, 9506.657697152865, 56707.173870291226, 70330.65024395539, 36037.248030223964, 43545.287431644894, 44883.7189100372, 18529.195234020524, 54807.32630787352, 69796.87334518667, 24831.953918918138, 97381.30587492265, 19409.794299057616, 98837.15523281487, 80197.31043850994, 71448.46760940713, 67621.4648878945, 52166.56416311058, 32547.92875501432, 99787.28074865474, 26946.24386527159, 27481.3191773399, 23442.308223025488, 98748.06810278683, 11754.868029099385, 52288.1879919186, 73799.89670038834, 1766.0191102251256, 43659.86587733136, 3802.9886356488964, 22760.482963650862, 16086.736254381272, 69122.1219406238, 74355.91780045658, 2220.168772897779, 1617.9525672929574, 55254.91200785959, 96656.3518473751, 23324.04248081028, 75631.95710994038, 30232.66358802328, 64420.10429266539, 49322.966348561895, 21693.042043382316, 75985.80365870641, 48725.80618965374, 93072.9776248013, 34369.3609864643, 46901.10219033774, 87217.96826792437, 6257.952214271722, 93235.11191562154, 31205.822027230603, 79022.96441907865, 20183.966318578816, 79486.09668684319, 34811.049784102455, 28533.276597145006, 78034.34626880492, 47948.63929981951, 27692.81411602077, 1859.1221664429502, 22735.866420448936, 99884.0497511144, 21536.586209439112, 84024.515239247, 38698.69985141062, 83881.99529101576, 73771.69319515573, 80775.56781258724, 43530.23206046, 71228.01255721091, 134.9517869777439, 95978.66228606297, 14985.340705967708, 22402.176613382962, 17246.72521620767, 82280.92840219477, 53114.768265590326, 46684.38821890203, 84002.61483476535, 71229.25120154025, 51401.30301928899]} +{"id": 577, "vector": [10088.528704413158, 95534.69092059985, 29365.928429882715, 16498.226261068783, 48575.684895366125, 17829.620131404445, 16140.443810586325, 71691.41400976952, 82014.53370377782, 91352.09022738355, 93065.64466164123, 22507.08451908846, 32014.72625641191, 28046.634292544746, 58467.12507727547, 45622.108301517335, 21319.64784566527, 47618.27180800384, 21119.189805132966, 60984.82560728136, 4999.257435861548, 73234.34907432116, 22837.051982794408, 80155.5148944503, 66365.06578965273, 27974.527089227773, 5715.7066490732595, 44795.289793490956, 89794.12580736047, 33554.03735658446, 56062.518962755144, 61805.078719884135, 51819.34268205941, 81917.0335639941, 84917.3764005722, 52091.70492157365, 8696.680288774427, 53346.24662066143, 64807.67620758798, 58558.35196891371, 89737.86785489222, 34245.82127410425, 62624.055516332046, 32557.606949978523, 44525.23613677089, 12411.912171342965, 88663.8208375136, 5116.7913997675505, 38001.34001441581, 65982.32052221733, 86589.92270401167, 91061.91907384372, 63653.68121261259, 72532.98987913642, 83913.40460905999, 78973.2167776973, 32601.87068829683, 36068.02212387488, 11165.949609251202, 85095.63021476925, 60639.24745441321, 92773.71841712312, 14787.261270962681, 42394.01856811624, 76198.69909194847, 18865.855675825816, 38124.425761928374, 59599.9126489074, 27126.755031418215, 68976.88707892224, 39234.73095547641, 21401.22200811687, 77199.01747088657, 90238.01385978296, 8880.649403439178, 66019.92038058751, 85167.18497706357, 80812.37135444362, 44304.101264310855, 50822.622668656826, 37159.43339000568, 27797.810352537188, 38160.04034355406, 17661.16416327468, 36197.6463528457, 98117.4424501853, 65994.92224189788, 60287.636007515466, 34786.3360758631, 87452.71575792234, 96881.5044401619, 7223.2058622994755, 30957.823526509987, 52950.97427188393, 35720.179766044916, 30790.321321978743, 57047.52315853494, 22096.60360707285, 54682.06268881287, 51685.504265420124, 53703.96843694258, 73612.08642175778, 90628.68594731092, 60945.69910010352, 69192.81660205073, 72177.39030176468, 61026.78215201623, 55389.41683340583, 15446.83577943562, 31023.298623206254, 41557.10480419449, 78263.84135110893, 54070.09311122429, 47156.30809567965, 82061.09467746681, 72809.78369140912, 16440.935526212306, 22555.302516537147, 32711.241244630484, 2369.0826678429366, 84552.09676045965, 72617.51635829262, 42039.52928310869, 40520.94725342207, 37265.315528954154, 80381.09029385654, 48326.94545758595, 36161.430466968115]} +{"id": 1867, "vector": [91483.52877794103, 30079.099080630534, 37811.26629805186, 25370.345389217542, 26984.760201141034, 72567.25382559195, 31962.12637533288, 76896.15021866551, 6803.408457437244, 38740.06351513285, 91324.66250728146, 65506.12099662329, 67145.06108134238, 72040.19974793847, 73373.43704349552, 55383.28719478356, 1415.8101783078746, 30896.408437120237, 63506.11482210869, 62298.96957235659, 72848.33317444142, 60925.27388591614, 11292.046168582216, 72373.68939360052, 37459.02951561966, 39242.03664385988, 87885.607578046, 45935.591806618904, 57720.92823879352, 140.46260796480857, 21085.411396822252, 18555.83845497417, 9363.842184590887, 51524.011922399106, 25323.432205095607, 23661.679616362595, 68372.09101814244, 78407.43141614282, 39764.9543625221, 1034.839804409049, 16912.881692223058, 46014.812894705094, 4606.310193430152, 43932.44117259524, 24619.339251696536, 898.6572198463216, 58225.11576419701, 17894.815695217203, 89545.56695703459, 70397.78846311905, 85774.42891336056, 78645.03590433918, 98763.75111520199, 70569.53325695162, 32535.12837257012, 54396.894118433745, 16350.38493490747, 79915.2728796753, 28955.128202791868, 3486.7520511809857, 23352.295884324427, 90959.37841866363, 85581.16624789411, 50938.01978278212, 92079.44837802049, 95838.60420953353, 67947.46987434349, 86311.97627272646, 25082.63916812041, 56551.200273972434, 90844.74802758932, 51438.3300284149, 25087.16362010168, 66580.79934524167, 33802.29670436795, 78685.2415284718, 60131.26460997086, 27922.000993306996, 42169.86863225972, 88573.01563613863, 21249.13494854388, 67913.75877545134, 22049.24662769275, 1177.1732884244045, 42151.6131958544, 89332.43750010936, 1673.019601830472, 41586.01851715056, 80670.1167598968, 90201.59311584715, 30785.251111731093, 46674.245539926465, 18040.751212811214, 57005.27539651158, 81890.41036516397, 69834.5736698933, 33097.859263136364, 59333.16433091363, 98146.74919091041, 52862.19712129524, 34584.476697541046, 90345.65767557846, 79031.36969783028, 90127.1262347741, 86140.86514578956, 12802.805537561335, 32576.581209537326, 47569.487344304864, 29766.092888251504, 61517.4396735049, 89201.29625837512, 74225.85170972146, 56916.0751687109, 34907.614591231904, 61544.76654135457, 31683.04022008105, 53534.34294009431, 81948.6183191603, 69142.02528761461, 18486.77990786688, 80311.0187731026, 35424.88464711324, 205.5879575066699, 34164.82619784453, 83559.67618202769, 84826.395561613, 98648.98558505568, 14815.611661945393]} +{"id": 1395, "vector": [17055.03914609895, 64193.83616655183, 26695.9054944528, 95218.8060099019, 3788.188295710648, 32180.27013837873, 11681.836544492275, 21767.64345023804, 29431.957112422202, 49001.08830466749, 20011.331489790384, 80095.23979710236, 18013.02083890571, 2154.93136555196, 1770.9406891679214, 73692.67889690195, 91582.55357459915, 92518.62899743907, 97867.60229157344, 16919.112509581657, 94526.09726383639, 18120.43408900217, 37927.18664399534, 27126.007028159627, 33674.98065403142, 94870.24878345018, 98317.58359924857, 68022.39914623869, 2069.1203719234386, 31188.02289003706, 3971.602875665403, 77894.95151578869, 18291.73072535618, 46742.1957735278, 99674.57623279915, 74655.34019536957, 45001.56559685253, 51311.11462611956, 58545.961287605984, 36961.38275956019, 75982.71789510465, 24576.06236439367, 45149.23797377899, 39136.62501836484, 82749.10140406186, 9915.476132759815, 23695.86534283622, 52385.510350907985, 49887.743906215306, 34097.55989888036, 14275.032888298445, 28632.69748097691, 58139.21272391597, 91059.79704423399, 16774.698801633258, 37740.46055803899, 93765.66745346152, 58698.42411225376, 35587.47218064791, 58553.07162406449, 78171.15625436627, 6218.417805642806, 71242.30690168357, 32638.528767102325, 17603.699739877622, 89856.67415658709, 70696.24907260523, 8888.431083699266, 70367.03418121285, 14185.217008648011, 21125.699582667636, 93880.01058876372, 85871.89521514028, 9235.654703785867, 24168.85291685096, 98687.54533432845, 43645.41927325056, 35265.23680726946, 36299.430308278104, 65019.38616014675, 34807.651186881114, 5518.933923777314, 78930.95570542727, 74295.43913584173, 21299.259440478723, 90411.007681664, 962.8017526737365, 55552.624585310004, 89439.40572435973, 96400.40867550079, 14769.985905626814, 78373.13915590111, 90348.03122309578, 93841.41358365792, 18358.60604838461, 71408.44207618467, 7683.887392024225, 3331.485527001965, 56143.67135115882, 85572.68818307885, 48775.391798188226, 40255.44344049015, 77705.72917019822, 85691.13929621276, 39473.48472562034, 5925.329935950607, 99739.94688583472, 6978.7540727690775, 26207.68785108274, 59887.293971381216, 70467.56996544074, 64474.0784240928, 65540.07474851763, 11048.688660398564, 59585.53926927296, 78676.37283399835, 96802.63805931102, 78317.0159079723, 480.6348564861951, 8771.783933183619, 19001.278317468496, 98689.94945244963, 15828.563157062757, 54222.50054392507, 55670.11218370332, 69977.95740732511, 17522.455841100505, 14037.725292157154]} +{"id": 1203, "vector": [13444.856693392427, 57978.50983197348, 45608.08985151286, 11659.530982003796, 42086.502972495684, 32705.560704597603, 77036.28358964002, 12153.560918591833, 20518.286299364874, 9103.940897539076, 15305.477652826361, 96778.69735282264, 95374.60571755783, 85632.1220995465, 9622.900513177436, 44921.60360937464, 93299.23377776387, 24785.850838374714, 40261.984847665764, 53240.30860634214, 26764.936201814216, 9551.928849369351, 82728.31318568476, 70611.98238722043, 7296.607387857712, 87846.13096194838, 72046.57172340972, 70109.51591387703, 80835.5815429202, 57308.685243396976, 81466.8693354481, 29593.28861362166, 94169.21261594696, 98988.2260446177, 44418.44273516705, 56755.521970367154, 39679.188937397594, 41884.969408910576, 27668.786794402946, 38223.13253742898, 55229.35668697938, 57250.25691301876, 30554.7420526173, 79168.3849102166, 76480.75490345874, 56322.82180787054, 50420.773570627774, 26493.32135753243, 64563.38042459235, 30328.529085996637, 18103.487144684517, 84857.49639436338, 18370.225362180594, 95949.55600111069, 26770.122111006454, 3376.9500990270094, 3398.5490727934107, 21476.693174899785, 39899.8034230576, 9363.628782171862, 62743.39454631852, 96480.62218200378, 85494.78384599247, 56595.973622014026, 98202.81697600917, 41170.03195130606, 83041.62492357586, 28704.40115645544, 68632.16380213303, 8655.56230711908, 49361.560323504316, 75802.54561137794, 42952.00937200528, 68318.08106588286, 38050.783135971215, 63982.503628067, 71480.53255490908, 25513.792718356177, 93011.66956177664, 46926.97977500817, 44117.71440720954, 64852.406673085185, 51960.736215751735, 43149.779000402654, 26928.65895611034, 9068.536217307077, 74604.446239478, 77276.61331674666, 24746.924204407962, 57148.61756387735, 27095.045121149575, 2522.8406113041, 75831.17466743858, 8116.847766950397, 94242.8442343867, 47487.2218621211, 68656.61267317811, 39268.41753516075, 10591.718640306524, 19332.231626931938, 12844.918218290803, 90655.76573587257, 49915.72137442959, 33803.81504930793, 45455.85932075154, 76983.40445143006, 52487.56935724917, 60011.08380493587, 66386.6376732624, 7575.978898774005, 65828.14173833911, 19856.028935131555, 36307.50756875989, 82540.31080665771, 5440.5568222232305, 87514.7423047218, 16498.00606824251, 74978.78323301167, 34664.550691094024, 50053.99943887241, 46349.45693023018, 2810.174279887956, 60909.52608219255, 78271.7294097596, 92394.6089072859, 3127.3199190629543, 72297.79661234033, 86971.52346877212]} +{"id": 712, "vector": [34205.061103764136, 4442.268272411654, 60126.776141738934, 74284.44945202368, 94339.98769225893, 87339.99309022067, 71127.86544196762, 76400.37096171133, 83476.23829636424, 25669.92109638532, 38470.253699419955, 51674.69380213727, 87997.62823155783, 6467.640122753027, 12489.158636346076, 46684.87954325944, 28162.377269467943, 17886.437525948095, 34789.24551918362, 32540.747971272067, 26155.302430706794, 22954.647908507, 24711.536977989366, 85688.87980017427, 74447.04087885568, 21880.53443021, 44070.949041791995, 69680.9617975773, 19296.584843048724, 95265.54604916627, 4491.113858420481, 6479.462640021472, 80235.5564509678, 47483.19727173593, 44377.52279228525, 27438.409905205197, 94406.58284787579, 1664.9033666016112, 17041.619347527725, 55003.53357372604, 33505.61688314456, 86074.66291396075, 49088.77462685426, 4053.3287492546256, 85323.41059853243, 90380.88674509389, 48348.66323172318, 43317.20523316096, 97269.62578645075, 99228.79473217666, 8110.659446728341, 58678.94241398778, 38113.45044660006, 8259.07482380398, 78174.113368394, 1710.3999149981064, 80456.8759916223, 33290.36577026203, 56914.87544239039, 2058.5099700186847, 32652.1094334127, 89237.89881470021, 24768.73044345075, 23724.86378568711, 37536.911211280436, 89061.18511716412, 49708.40421738382, 2953.9171364666217, 71760.4186172653, 91755.42092474565, 97431.12189828797, 51590.726229109416, 45623.0784117904, 66369.39029127914, 94368.0256913689, 19404.99983166939, 64953.28947681322, 5496.955373468771, 56077.564702724725, 88038.44943724829, 94471.25692578143, 28447.10856053071, 11539.455793875131, 46308.01977483503, 7691.049098844626, 91615.71816992387, 13116.938558311176, 43855.691039742196, 77792.24528666126, 1800.5248644636263, 51171.89689357267, 44950.86383553596, 18580.669433963638, 59686.01883413265, 91012.52940543492, 23587.68470039475, 3148.5635069310124, 6749.101640313559, 32649.901525105084, 68711.29503683466, 79579.70248550645, 56735.516664878305, 62074.86155136408, 82563.24263707451, 9506.318612404795, 84314.4063283142, 71143.39751306729, 46808.26140555926, 43434.42543954979, 11546.256237549656, 43346.531439070466, 24198.37094919325, 74420.38008749457, 44576.844180169515, 14956.403682062202, 3059.0746255489407, 38571.613720515874, 72634.23771788503, 67469.23905989228, 99746.42287366219, 2969.4802277640542, 974.7536127076462, 3432.83432660495, 87746.12586131308, 24989.08511082687, 266.0052051161266, 81846.29417853797, 11049.074956577288]} +{"id": 602, "vector": [67039.20699849055, 70306.11721305258, 38603.37435921269, 51988.431820719816, 82140.47867800615, 12157.827669448252, 61271.57202478931, 71777.87851569695, 97146.76245516371, 64053.14666366543, 94738.57635300871, 50298.087145943726, 69309.18783560376, 38939.31338107918, 97901.7792866868, 16189.361145419745, 93048.83125615222, 15377.777162102146, 68323.7274781532, 74158.24902584693, 63755.43642425262, 3534.828294132808, 44489.28577330272, 64014.81944396596, 76329.10706880168, 59020.231172042906, 70344.95254175043, 87689.10433424197, 99681.61808454651, 29467.164457294824, 28781.516677952, 92317.13634407688, 61805.519231061844, 84791.07605054369, 25438.74457601876, 2979.0347562678844, 71207.95664994855, 32112.12605033654, 55406.568179132955, 82776.53133951587, 44651.02487283888, 74261.5314402755, 7687.417165561938, 7485.9538914192835, 12196.862666177132, 89732.72193294247, 86043.57940130787, 98494.48941515324, 38195.0935741882, 92990.33538862353, 9322.104766642204, 18560.057519759976, 23943.23330609198, 58037.35526780555, 88462.6431850562, 33858.302376904096, 32151.992701235988, 82685.2015929778, 40668.88577651742, 66200.2997420419, 64270.383722336344, 15402.611561147784, 88568.8571063243, 24116.753038725637, 25644.06550617386, 93116.9176540171, 23898.18529749842, 41366.416620562486, 72510.64452091695, 59977.14169674896, 60079.47155057243, 17996.835899544483, 81793.05958654605, 17126.86733753195, 42714.38202601635, 6286.632206233556, 80236.36528794591, 97763.05923901619, 4451.778564048392, 31093.521118900324, 13995.216140408062, 14537.873647436727, 20444.756149227884, 15387.341766735608, 17103.074173155754, 16259.13078926663, 31697.1745838997, 89873.97490650514, 60712.60809625785, 90699.03337315322, 80543.10359671069, 2483.931164070452, 26205.37771620215, 51776.69512818419, 75969.82612794038, 44283.31473981842, 23673.392997350897, 51225.54984183062, 41196.31985057691, 59202.029885860466, 88120.3214826812, 629.3392254228536, 58048.06264123336, 18640.578270862818, 73214.11433935368, 250.60510762336952, 29965.450394675554, 80371.69533098882, 75667.25019647952, 19985.474732593833, 56953.32756640613, 56927.275451461435, 79911.30768972736, 78182.45336241259, 57861.97358763716, 38673.20260336354, 36372.22991444039, 30526.09397814885, 32765.716179400693, 53418.6728092087, 76662.53452179386, 54535.51305842567, 92828.46730846529, 78005.67992923026, 61074.63412145681, 80419.78692462675, 41546.74681288346, 10001.925827435765]} +{"id": 1772, "vector": [33499.198551340225, 86393.56390843893, 3679.2688728933376, 4131.27050763954, 41995.155025062944, 40773.18586978036, 44005.936330888784, 98906.40138102103, 63424.69525521551, 77416.5346270024, 51387.54063824389, 2743.2160999037005, 56607.25358293802, 50263.60897247909, 53543.38144195477, 32513.94393244943, 54287.323589028005, 58637.345770729546, 59938.9397578009, 52217.96151139008, 10716.611345592786, 64537.06643638968, 86724.58218013599, 9666.9449223311, 30440.76935275338, 90127.27535319494, 44030.188352336874, 55346.02803723512, 88317.7426361115, 81972.71463291733, 6624.564405158007, 40585.77586833596, 76579.09090130313, 59378.93485935071, 53312.8327449064, 2807.383374630501, 85389.29579555316, 13767.830184891995, 81708.140287387, 32277.397624973135, 40212.879538038884, 38455.33528916324, 34389.5046491531, 24339.396790195933, 65479.4671601244, 27874.30495013439, 9450.46169519389, 75288.77891342019, 60408.27339949428, 78754.67249026264, 68191.05820782846, 70991.11336662815, 73226.3750951429, 33806.92941819298, 90306.07234101555, 85989.88644279593, 19114.96785733244, 32609.822499819507, 40389.664556296375, 92218.194282207, 71574.64617471008, 57454.850868752284, 30799.32399802293, 83678.14717921932, 19533.200293710583, 68320.34219275581, 89869.02944030544, 66237.39646963062, 72222.32697221392, 32010.25477752373, 50784.835475361215, 15303.843886906687, 9516.751601434604, 71765.85567950808, 82980.69044242502, 46521.79230940482, 50535.80253227108, 70229.97612666045, 31035.503817397457, 71702.30427391338, 15183.032897922056, 92015.75308668822, 42214.99247183441, 59433.5867737188, 21059.139024306405, 60630.91140078476, 5221.08694074116, 87930.95863372102, 30324.03367874672, 78849.77797009573, 76650.73442045748, 41860.060344165395, 88851.00779056909, 14525.723260192724, 80293.13663906889, 68014.5500380836, 26771.32106975626, 9554.06299329088, 50668.10388109453, 28694.018669895217, 3101.0099881598862, 95238.57082315904, 18589.79512902166, 76229.79472444524, 38777.69696005004, 23176.95448229351, 35105.78314886975, 22797.414986034335, 76367.06162215, 80052.53989972046, 22852.34783082294, 55527.46264893436, 77956.70720141496, 15642.931238160018, 86196.634203814, 65190.27407435286, 29895.932058148766, 65555.61492661807, 48939.968872070225, 54887.36359424646, 92805.85378991925, 61004.600585615335, 91546.28905802817, 88652.18535516584, 73603.35069581633, 45192.904281832954, 78353.4040704565, 307.2619039516833]} +{"id": 1082, "vector": [51957.62124374412, 31233.113246841014, 88033.20376446117, 42986.703268906225, 26098.75196153347, 93767.651015297, 73141.81455971734, 77801.14440093256, 91972.95797046208, 90273.65101916637, 33621.605197917335, 28034.843113665775, 94944.04443588396, 34240.67101542922, 92760.21265446869, 44667.04337358357, 8022.6625277589055, 40141.77963051058, 66539.84890136222, 68876.62374803815, 38272.26539114144, 8382.376100468637, 51200.34716134645, 36628.9150673789, 70560.89701105095, 92170.58523770119, 84220.45645058845, 49639.77721597602, 79094.40604161986, 8346.22043131662, 17760.74698573912, 22529.40419918087, 57336.01924850975, 13192.851165470765, 70334.57929950711, 85234.31104296364, 6495.301038436219, 95920.60666486205, 37664.911428138825, 46100.52234637433, 41322.79476088978, 64696.65833313122, 17364.47616836654, 38838.22061956109, 850.8203592241581, 92882.0280582805, 41279.59163963691, 69540.86799876323, 86959.0588456187, 79725.84744207334, 50543.269988516025, 39263.34335705269, 83857.67466709699, 46492.16871803664, 16907.756171330213, 73116.31737009007, 19649.053713530993, 1494.7682590314648, 25541.874719317282, 5690.976783073309, 96211.413622978, 67332.74808334118, 95481.80555555336, 30619.54176265036, 62565.429746084475, 45257.01340582945, 87330.22272889687, 37729.86235244371, 11498.209622356591, 93156.70927538787, 85034.63564097874, 90955.41086344063, 24493.048454521915, 85186.89577716033, 80748.88568985413, 78844.84249932475, 63990.6124209858, 7207.376078100026, 40800.95368897244, 57106.23372391371, 78317.68151915516, 92749.30634680059, 66816.23449007123, 56883.203375320736, 79488.03463697773, 60534.874822745944, 87811.4611248265, 92694.8783177226, 14381.042962305812, 20348.607132405017, 31910.185543437587, 37906.18764056901, 84747.43688218617, 67610.5128905346, 51632.24687256309, 20144.883919086053, 73967.44275956413, 60883.42349103497, 3205.183554290036, 23798.253397140536, 97470.9734935357, 43972.06752213683, 59858.9841249461, 77120.58259599203, 66781.9825181455, 54560.27208333922, 68056.12673800343, 18037.433040053995, 47521.831004259315, 54802.66823485669, 50095.45694255704, 92092.73233952603, 45963.31706414102, 92154.38628394132, 91169.70589750596, 73536.40331887774, 39287.287990874676, 13347.864340776328, 47388.50760006711, 67368.94408529406, 40635.29378949079, 67173.68850605472, 62256.71690072111, 82536.90723122719, 43123.712676202165, 45693.29771454242, 80113.96242107586, 96729.30577475554]} +{"id": 944, "vector": [39421.68470210543, 7481.956996067463, 62239.96293316396, 18448.443907480883, 36525.88861763862, 14218.967597049548, 33521.41823495334, 66779.13221301253, 72633.23510113108, 77776.91293980676, 60274.66200860967, 46871.53196951217, 94284.51168824613, 18710.033978865336, 516.9458176017549, 82122.74085755652, 45424.399955419736, 5181.342325595695, 97805.55488102381, 19015.243302289044, 81215.3626971646, 39963.5907171905, 37705.82099537952, 87697.80930610844, 68112.91391904539, 26584.307538960293, 29364.466754051467, 89699.25925540045, 28181.622346500335, 6795.081569931738, 77409.18237080185, 23042.395857351315, 46278.63640967799, 48787.63400142691, 66264.90868837415, 76701.71500269181, 47167.5754731947, 36205.15376483805, 75691.40433216018, 28913.73579060922, 62239.37718334713, 17391.711100498575, 66597.477839, 49828.0866608938, 84807.28178667613, 5317.202844714486, 66176.98860642119, 90847.27468557595, 54253.58907002423, 95917.69334547833, 56149.940671624965, 5205.154095236398, 47368.59372285723, 15783.241036559015, 11097.228401053295, 4058.912651795976, 24722.31193924608, 67691.2461891955, 25249.7910222649, 80270.45392987128, 9857.840297617027, 21392.43666548367, 86598.63251772907, 43207.415722957485, 25617.50623976944, 37926.37818744255, 8375.642771543335, 73865.67914483006, 14570.685077924883, 28461.175879324906, 53396.607428985786, 59044.18279056356, 44778.61012793851, 26965.137440462207, 45852.73297679762, 42282.3706631957, 59716.59531135829, 37823.539646850324, 95404.70600215341, 48674.996123297395, 16483.247944291867, 93284.50319598286, 9906.733094360654, 67068.42839526803, 90817.95521182411, 42618.97051016528, 85736.33345808538, 63674.555029339244, 56278.52397158385, 13569.282239835256, 24920.151736571217, 74272.91272253796, 54983.299733037136, 3387.8201594177535, 11810.870123840245, 71472.91970958673, 20190.796031932157, 4138.928432191014, 36238.376570496024, 35256.72534556915, 34485.88472443177, 52383.48321387412, 28674.045583267172, 66976.99963715656, 56352.88305389927, 91970.21976894264, 17361.675326619476, 55767.53759764876, 77238.15928657528, 9500.935246702058, 17359.680855201277, 7967.825136295326, 12845.174100073997, 71683.0791688315, 33793.5056734714, 95836.87119819093, 19257.102902731982, 74276.49644360803, 36788.76481084291, 94268.82560285869, 16372.904281643152, 54374.83296373469, 51454.841262786686, 89091.49718000411, 42270.98149911891, 80043.20173514659, 25033.047865161083, 19635.317652828344]} +{"id": 167, "vector": [66158.67226561754, 92911.62873367673, 72810.48652215672, 42620.963459792394, 65411.81472324269, 26494.18500312627, 19006.865711146693, 59306.11453599527, 13706.21652396623, 59936.05159898201, 94283.02700554352, 45475.41562069708, 57533.65951822823, 66550.4748968152, 99649.38118446368, 47898.10509550806, 38149.637633636135, 68257.30035668625, 82327.30601051876, 67450.20119025497, 31739.435552515162, 23693.34304301588, 5299.559079723815, 92560.17293711004, 58254.38760383411, 97314.39476788293, 49584.79953292059, 61033.53597903325, 65102.74049458485, 77426.2560282146, 12868.084362172549, 28283.091649130256, 56729.66203107542, 6942.1069477851315, 21706.75425526608, 29850.82795635612, 47857.01376459173, 95768.26602997712, 86341.39227163931, 60948.69403364548, 10572.534593677796, 77659.88326141544, 51525.27414684706, 70119.91858996793, 60120.67283108492, 173.5651940258265, 74043.7536967055, 94503.23725709942, 46113.440497170974, 3436.8520524309497, 38024.40394350421, 48167.060981899325, 23792.59188977315, 50061.35645113238, 37632.41447962366, 28957.529344213584, 61975.964814768195, 21314.21704024823, 9749.173506903975, 4633.318569794586, 96169.00622438152, 93572.10966458291, 82222.98466080822, 3108.0021708308705, 46643.068339680285, 21262.532060071295, 91973.25182680928, 49790.556640147275, 83296.74101468298, 86425.97283646975, 42901.87451685343, 95934.27857115488, 75367.47491719424, 65699.68038140229, 27035.778553750013, 99672.5752775247, 99284.6019683964, 67201.22606419258, 56801.87896820427, 69366.52342506363, 56018.744293433374, 78063.37001187193, 56410.22883191005, 95969.00824559774, 15646.954011822201, 82499.9234766693, 11672.660261179457, 5918.686465571798, 20953.6682991978, 69564.09715298968, 11095.9787847968, 14029.24479983082, 47594.268632297506, 88975.00472347188, 79542.66707079059, 91934.92699989016, 46969.45764024401, 74582.4497683912, 74105.06210467668, 55478.73415775629, 75031.2106316469, 25337.292331541063, 99280.65398768763, 15779.58201024131, 25158.99315183614, 99876.81449770856, 57258.296129292765, 92694.19716714746, 32414.74547348151, 90977.03350245435, 38648.15778625569, 26286.426695378017, 94387.23806064112, 26858.657991085045, 52481.784661623795, 44603.25726208897, 56919.7670544868, 39839.17432734063, 1643.941942290983, 2784.247051985367, 49371.33048406198, 53861.06078021014, 53981.65207878792, 55630.10198365851, 34130.17994722444, 58548.879149937115, 68408.42909779729, 47361.796562235824]} +{"id": 189, "vector": [91215.06714965215, 54785.26494732813, 34679.054478650076, 58415.4771393069, 90921.22439075392, 79174.85635376361, 62606.09429833486, 28049.382973515712, 60807.17726823751, 66031.32143144062, 40862.66853869147, 77713.30204850255, 96335.23059113453, 38400.6139916487, 76957.5105889031, 14082.408890806508, 5709.118956673309, 49857.82271743569, 68692.60874587337, 93819.50845809486, 2798.3136722646964, 24800.520443690344, 15207.557621014168, 82340.42699711044, 35201.5488028153, 5246.280917682689, 72923.70665809714, 50479.03040668154, 85782.460399119, 31198.672017175355, 92551.55377700219, 60992.347072225595, 37839.78488880995, 77750.48586911878, 8435.896449110436, 18105.95612618714, 26199.55101904682, 71797.23277018486, 71154.917795469, 75345.44772013319, 27694.986655823504, 46314.26384797618, 48990.77181763411, 54452.130754982616, 78177.20638867436, 20290.12117248924, 75861.14019090655, 14952.082220764973, 54625.673904221505, 97747.54801244094, 26636.51453019118, 56435.34590443452, 50425.34836022249, 36908.829679605245, 50321.8672970203, 99844.08938966699, 80192.56553958474, 24425.3011064411, 1272.0431938068932, 83006.75702124847, 30796.648369979506, 48249.9575242713, 83977.12015599046, 25698.64360808627, 88921.59412542316, 95891.69244507368, 42266.57945473363, 12447.468121571337, 78717.63856221097, 10943.945671472766, 25458.11660043492, 11930.167143799787, 86358.10082656263, 33081.71216582107, 95180.48927471334, 8847.357831279523, 8795.521638937453, 3646.155782774385, 27983.535410379558, 63578.39890921145, 85479.25925152138, 98393.15513611326, 66294.12196021376, 57166.618981463136, 90188.2088789463, 60134.996320901824, 81357.56808459805, 30539.156716390993, 21935.24302836587, 20339.164028026047, 10124.428277941499, 83194.07786820238, 24813.572048853493, 64981.13409794614, 1783.8218973069586, 64746.43083870935, 85810.46073160635, 45556.527695692195, 47765.452673928135, 1912.2180316355486, 87705.4659874286, 70293.83384495955, 17709.896820333193, 82937.13610243896, 20299.853040882175, 91915.04795812372, 45295.44319415252, 86791.61546933147, 49678.28694199566, 75928.56187818642, 52994.15885256709, 15449.781406059248, 7868.743892292351, 85138.22705597228, 73565.64871720098, 29497.232939557514, 78195.2140618182, 35113.15135879703, 17754.10380293829, 22968.791950292823, 31012.88484633413, 24062.817820791082, 83209.23430963328, 9857.142667536657, 38976.88740976541, 42197.2772019476, 18862.95756336265, 98336.51310095514]} +{"id": 839, "vector": [17922.764138779323, 78678.79777241783, 72127.63916129807, 52438.08025059663, 87594.93738025105, 8638.5278163568, 73556.85646924218, 57562.751871106775, 54008.54555192169, 47876.94240417896, 2965.9536099581605, 51408.48037316761, 19635.737413000243, 67848.72746763934, 99372.56568356347, 11245.60856968282, 9025.656592936693, 85461.52845958162, 44001.31151780437, 73865.46667819584, 17618.59368914961, 62576.084427360234, 99767.94170250656, 8259.323548902032, 20143.516797230564, 80194.2803136956, 89660.59261450083, 92700.50719185648, 39590.845289297846, 90593.88119961972, 1375.4731978441348, 51841.82195415533, 73683.6866626386, 92859.80880964044, 97409.61247976721, 37594.137186928165, 21917.688268853984, 41217.14023815993, 56939.10909826977, 93445.6930656125, 20489.520961463815, 22866.454448520522, 47947.0905823293, 64357.77405529106, 7325.278853239092, 93465.3914634453, 88232.32736008184, 70385.72495565076, 82299.34744444929, 71433.78214288496, 90495.8116306566, 31144.913819860565, 68469.00593091772, 73088.46726611635, 13656.59648053822, 96182.20600167064, 86269.24253745312, 90637.47982402181, 39564.0888576083, 30173.31211433889, 96339.79563744177, 74331.26008096206, 96754.820695938, 57305.667561247465, 73503.01531160361, 13575.605510179912, 47165.76619760163, 53632.866040991976, 52188.95759920921, 27122.820097251864, 63282.02402522073, 38122.739640551175, 35404.51876902784, 59915.40708374635, 71354.94033625828, 79101.74508376958, 79344.06060819604, 73648.75924852333, 1668.362411768265, 46514.41939481082, 51120.3149258017, 30671.44042846386, 39668.139220773, 77988.19650518331, 29642.42986301596, 36304.21617178609, 71006.77446695064, 6553.427469497541, 53944.35130658225, 92393.13339997329, 35127.01000464647, 95799.1621975216, 56062.28590749245, 45682.85083271997, 33033.922534305304, 57658.436255928726, 6920.6820161752285, 41428.948195210854, 86928.83683653252, 12838.230431387254, 26273.45732422277, 37137.36490079713, 360.76251193437605, 86082.39048287344, 31548.47395758189, 99676.08537553414, 86925.17680768849, 94542.53939827325, 58515.805478322436, 162.49643395260804, 39975.44606765775, 57794.8345353605, 69198.22092534522, 52236.2276806798, 17632.991737975466, 56079.39276072787, 52690.15821590869, 17047.30428301322, 16402.859743360565, 47420.467359511276, 48948.61247254951, 78044.96895112522, 17316.868164787535, 81766.01745223912, 5927.447741629632, 44494.07529316578, 68490.62514703521, 87894.55263246903]} +{"id": 1705, "vector": [1353.4931145712803, 66436.35533383336, 92662.57224132483, 92020.54888549598, 53856.805516394576, 9390.232176157155, 66931.97719213968, 37510.335858952334, 7014.466200856451, 62854.627973382594, 13419.243410236848, 38783.84997954762, 9306.53488511951, 31495.21653930729, 53302.10092892681, 57995.407632644215, 15911.506029806644, 85689.97543450475, 10540.340335152332, 11506.618313202676, 77616.01268848474, 42234.47622174662, 70713.26257879865, 55711.86720390921, 26886.340330486837, 13498.946723709681, 59870.920444026386, 58882.9234444087, 29772.223644065078, 98599.8254921131, 57395.2351429863, 72306.6281245347, 97282.0110967631, 24077.617052978352, 18469.915160234195, 26953.366867605855, 78797.53822675208, 60359.75860908176, 33979.777250168154, 64469.00778732982, 38023.129102772735, 57435.6195454566, 4998.720569020698, 76548.22672533247, 16296.719530617087, 18972.65057221288, 28546.642266199317, 74431.19292919897, 25779.314867045454, 30506.440584807704, 56346.32020373037, 93822.82776155973, 78444.60609523517, 5348.840118931242, 72729.92164452728, 6675.706975104734, 36080.036237078086, 68242.44462453229, 82905.88696430791, 40403.92066313086, 40229.69185828097, 3306.824676786524, 39139.101173762625, 80717.32610715633, 34092.61882044598, 68801.9963758421, 87154.10411217892, 39198.70425094111, 44821.506161940524, 21294.137728988717, 20486.483046141646, 2180.8187558810087, 99936.15218044215, 70273.69853762841, 49984.36396954611, 5338.648422560399, 68242.4949942538, 56721.123489019155, 23235.306902999375, 82033.69667432529, 58805.730304708224, 62651.42785828977, 49330.81217338198, 66222.89797618706, 66573.0162186051, 7230.039087351014, 52146.62896426733, 49350.58996767151, 49476.224689376315, 79989.94635298, 22643.376341984924, 32827.34284331337, 85003.9952106345, 91090.29937138387, 78926.68008131217, 9831.35020341588, 22101.100435058408, 53336.789827473876, 8834.984354130338, 9460.203484423746, 84465.55947472507, 63288.48807263873, 67624.50962867627, 49266.77411840359, 96297.54729235261, 40212.629960554936, 19898.542720594804, 7870.211848964536, 67741.65396705084, 4560.943317916666, 31705.251513781484, 96529.50920899773, 87334.10439337067, 43221.465616894784, 98564.54864231752, 14724.687645781309, 5397.853274242215, 59181.50126726167, 95401.26951967529, 1648.6937041807037, 46234.94026257813, 70292.8226659879, 64830.582533479916, 37046.53078764914, 88040.84430263836, 95620.81550642785, 62947.628209317896, 84902.04487557948]} +{"id": 741, "vector": [74455.88710948799, 63704.940724237444, 82578.24713564008, 88367.87316582013, 94911.07514178763, 80773.50440806206, 30807.20906635742, 95243.56695339094, 52234.263839546365, 93583.41923213236, 54834.13528985047, 89333.15398689195, 91410.01703862411, 99553.42504230925, 43716.77090403216, 68037.20310895579, 20520.871842920984, 57247.85308676385, 62816.94162036318, 95879.39926037712, 42435.43511578082, 79040.62245216391, 40547.200778577695, 46578.88828863083, 46136.1600034527, 11455.506098544754, 59811.650242184325, 49636.1848614007, 19072.26759174808, 58633.26333272953, 66221.98678120985, 65854.32285394584, 50510.87898201948, 42927.35687881978, 84068.78120360995, 48372.828717829456, 94123.3854681187, 80800.35831937076, 51351.6194822517, 90189.4399890761, 77175.15913170531, 17119.322620346666, 15324.493720537546, 33294.50859834594, 20524.54531497584, 1679.7205922803316, 70823.7666743207, 59508.58240000602, 24119.716550838722, 34937.103833118344, 53303.86019734312, 26141.01773231664, 4168.060161624909, 81304.64321834133, 25152.44645873986, 92022.5112343672, 51177.63194356434, 31828.699563906946, 79613.37686069254, 98680.20243031332, 90090.59152522916, 55499.932045132984, 5269.44963635817, 71015.53771263946, 57195.99907564278, 26202.49487917533, 65257.143755288926, 92270.88838017937, 37135.71614709258, 33638.49271769344, 17818.949040829, 92750.33737182505, 36724.77061062554, 26310.431708460557, 90331.2137604376, 98628.38799721461, 45848.539672582745, 9017.067579223192, 76167.97272991223, 44623.59811406357, 81350.831894508, 33189.482147837094, 2871.22033736813, 69204.88009492405, 87365.42469297053, 95777.5036931258, 60661.532379269025, 22209.05113938495, 72351.25339297464, 49285.579038848125, 91859.01036458084, 67780.1755829337, 85351.48592661614, 75966.33071564636, 53474.48379341631, 57667.42743686052, 10794.747140114514, 45090.19372244398, 85608.49064823585, 37896.51936101892, 96383.85603128707, 38005.49008382873, 11293.019834342254, 40410.138584146735, 87690.06736657812, 65651.72776614541, 15604.026959150364, 20626.038391711776, 46737.381802080854, 16068.658783441759, 96706.49421756747, 71289.71978919624, 22624.994021893697, 69117.15114005476, 90909.96273793637, 51257.61863963365, 95573.15660140688, 44516.68478086024, 52347.92177186389, 67849.04258624074, 74794.92513401387, 32366.216153724192, 19560.766169880084, 14391.529625856103, 16375.426903215684, 27299.400493170568, 56645.889727560796, 477.832964160696]} +{"id": 1989, "vector": [68298.23914258125, 94344.42943514287, 68329.40151830376, 44817.72148446899, 67803.97515963844, 93714.5582557539, 4360.899201522939, 17574.448077183748, 57044.934132167036, 83554.04372017941, 88187.92450085629, 62710.58647051885, 26377.407406254293, 51618.31527066495, 48491.54915983206, 90910.47876008495, 65225.450197830825, 45729.30088270993, 28535.614437250046, 12347.550026943189, 75339.49161705376, 60746.60072342258, 36472.70753322395, 88820.30229211098, 38477.91652777873, 15915.050767696592, 95431.5068831192, 13628.844428358356, 64802.08250551529, 72789.32257332077, 82106.52031087688, 54623.338192114665, 79730.73098048569, 50787.20549346556, 89036.71939940774, 41069.474045555675, 92408.7580162689, 44673.176600942075, 37926.04895244488, 40220.82682775976, 6445.105489805481, 30915.979470544607, 92886.90385577822, 50566.76038849601, 69813.60135884273, 25559.877683295806, 49864.699646877656, 48557.302081900445, 33564.85360760193, 63424.553957681164, 37314.32938136883, 18156.95299490886, 6401.2235784259965, 68392.99989695477, 11122.7471353587, 45348.38205747195, 35709.95792934125, 81433.90800240083, 57603.621619690304, 38690.86626836689, 41645.34256040732, 15091.15635324355, 722.0372861731162, 46362.02068162172, 30642.188297970773, 37742.35865864214, 2503.3477836070792, 9717.437078609848, 92794.603860914, 38376.19344452673, 49732.55064064728, 95603.74370359635, 35068.04588520188, 76525.22124099758, 56688.3283911934, 61396.58645802085, 57244.729986234066, 82074.45652125389, 77978.32589790793, 68958.71328161033, 41148.098225461385, 74031.01254821436, 66325.63961315215, 21796.369804574733, 36928.32126259663, 78781.7504982363, 57004.98283097526, 15296.215403280066, 4992.7751292914045, 75338.83065345068, 48090.651057814146, 78674.28637718261, 66969.16842675705, 40193.46159372928, 27596.425023073436, 19324.768311367425, 61567.00211449164, 16299.270785860565, 39106.52290246922, 77493.0443791808, 5513.829432832063, 51230.18515176165, 10709.476578089383, 64373.1784783386, 50827.73443129998, 86714.85018809803, 68991.68742500796, 57565.354785725285, 5955.528555358225, 88478.24672198077, 8307.417766434311, 53034.676912192735, 604.2436037114074, 47627.92722513583, 65121.50487544714, 7598.889302797185, 21152.618000671762, 92790.69582981698, 97074.69164851717, 28192.22052652117, 93360.2446424188, 64537.48631095173, 91131.67232808933, 36226.82494567452, 9480.911089140776, 92179.10923785696, 34525.4236529096, 47081.77776611947]} +{"id": 1393, "vector": [23918.36807817549, 29391.86789141486, 40084.472108194146, 77505.41165905847, 38333.28510151893, 7583.5853775481855, 27006.465675640567, 14446.889412930963, 20282.62804247629, 28417.20189234397, 91384.11935408598, 35461.80067482184, 55453.71435686786, 74929.88450929591, 17989.40181704224, 40265.779242824996, 80896.40113940276, 68340.56392124548, 1694.5755209269553, 4968.779740176233, 9670.974463732884, 28074.06558352916, 24198.928355728156, 10062.470357131493, 67745.80425356628, 31385.43284297408, 12759.495925079356, 84559.68949372534, 37654.621857653445, 12172.07694775826, 75436.76155783409, 39998.880252148105, 37472.11797677464, 97248.35629773393, 72402.17117857288, 39942.41524932122, 83879.21574572395, 58984.555332733544, 72757.1462617077, 98565.82003195475, 3852.569384388427, 66155.40857710165, 30995.094315506012, 36839.37144120302, 27539.0709660854, 61391.3557951426, 90996.82869743246, 38429.60529821788, 85537.67969789883, 4572.921718218825, 31028.315654091755, 59198.06051498894, 46461.92900969677, 89935.10277108705, 15817.763162567711, 37637.80713033903, 11089.79157102542, 23185.14196706252, 69443.53234528101, 32766.941356921685, 59268.70794364943, 71865.28759029563, 6103.436937144613, 32620.55555270351, 23955.560525212295, 99537.9194788963, 20707.13955216783, 50250.20624081945, 20904.200849613175, 93030.52823318611, 19400.139823850892, 19590.607995437127, 43610.9219243358, 55343.803530975325, 86642.91932416473, 60380.528938494, 26067.62340818999, 25904.596516096746, 52571.902483979335, 45267.30067288438, 66054.2612721446, 4975.263100439509, 58692.460253324316, 81052.08309241317, 99442.94061915751, 92.20759165421421, 47270.887489429435, 23357.90057113326, 18251.677174329463, 59687.17397761558, 85624.58785205589, 48049.08005582567, 759.0365646726882, 1888.7779736836085, 19393.86733979922, 96579.76574133574, 70029.48613660596, 91081.77704313511, 8290.293317674901, 99945.40544091085, 35426.411035506746, 47742.04915098146, 24352.765210017023, 16061.876459525103, 37944.17793561006, 78106.03287961606, 38922.43192241416, 85991.81030684926, 13126.837922461555, 58434.01254398329, 373.93834323744545, 88648.19642065225, 59100.1852694653, 19407.42674016106, 38040.18816973205, 25111.567496231546, 58483.548618257206, 41404.74281945058, 82340.95049541561, 73494.69879195372, 81808.17178900556, 11651.122797297054, 12953.796139839713, 6386.890466239736, 11090.513502360922, 45009.20555874268, 73718.70418371576, 34902.34656013492]} +{"id": 1741, "vector": [55118.86467440291, 62184.422873549105, 89395.0517143046, 63863.95136097987, 31014.628160385484, 21231.166464965732, 74664.3304929392, 79591.37330145607, 88264.01522736896, 65340.634386365804, 61712.92331202889, 79683.66544066013, 53155.596839937905, 30824.479782142676, 86332.91812484097, 69552.38719758883, 50289.83819547742, 61754.94111759885, 43011.746106379964, 43070.90241613827, 50263.413448652886, 73846.50615319048, 38651.92312212505, 4981.715630546746, 5047.2812916335315, 99373.92641218871, 41089.877601207714, 40118.41303008541, 82293.4413240782, 23282.67532291123, 39111.00903056036, 13932.427783387902, 26691.865161682148, 82477.675637346, 87620.87178133051, 91672.42091906039, 89458.3973360016, 39840.697367419176, 93397.12908709148, 24484.931801050014, 89797.6289756641, 3020.549615615542, 91163.27989590708, 32928.766244294486, 7597.870656319761, 96733.86337023138, 44409.31990252326, 38177.412719897286, 3603.4193044854333, 64116.35624267009, 3362.78793717677, 50641.334416188554, 41604.033428686016, 25349.76567147883, 2964.3535961498715, 7306.99893622282, 82659.4186196966, 94327.60855767349, 12499.710976757617, 48063.544354519516, 38149.102871394156, 44301.826372939046, 44779.96454239706, 85588.07321004996, 91434.35507261178, 24577.22484298467, 21329.450806055338, 25653.025653978544, 49196.45925046635, 57479.50599605307, 40054.380079485185, 94976.18815283761, 54404.23865945222, 48248.9425318534, 24529.010642977733, 45884.928930294576, 44622.33487537302, 81167.03042856604, 78675.09453483092, 92031.9371577997, 39291.41009502641, 67854.35328720033, 22415.975104999274, 21696.133479094282, 69770.23120191637, 27770.22388203714, 47175.581798633924, 42416.69384845701, 1717.5278155248552, 5485.387131277886, 63738.91449979044, 22094.12481188496, 54992.33158814701, 95525.08943468193, 18354.481160186486, 70038.08701840224, 29087.155006566445, 99100.20079014837, 15557.896020422013, 26965.9617602261, 9162.177035387409, 16393.21244669213, 68079.5982217834, 3976.761188284905, 76180.62326008196, 35889.892388454195, 16421.01491051976, 31084.84716744362, 33673.642133184, 21079.78524315901, 39457.39795471443, 29184.054490712762, 11491.134569142114, 61806.47573643191, 71456.5843936266, 18293.8936508653, 87394.48454348632, 84889.71546463807, 87217.87456529103, 40379.93870111146, 52213.53995845275, 76450.71019004013, 52152.891426978174, 47909.35031504591, 83668.30405908034, 25602.90748265831, 75358.04742191262, 58325.72028844305]} +{"id": 475, "vector": [62465.39214993567, 88572.3986055589, 20861.509736325403, 87585.84345759438, 31376.521226991583, 38910.9070979524, 30899.65614908946, 63609.82019958541, 38852.80673067384, 82613.01348018207, 32471.47811089469, 72198.25320012783, 44869.58826423544, 36073.781174216216, 17945.069782920174, 9620.608182062606, 25215.7689501609, 8344.286419805047, 43850.93011887584, 64428.03306266925, 25298.162391992006, 49770.10826534871, 1402.1217137806352, 8117.633604835639, 54891.586697996885, 78312.48482320615, 97533.58789546872, 88438.28999592697, 26003.539242345043, 85975.37619382402, 83186.86933190169, 63469.93310187633, 10711.1199305712, 20707.247059735022, 74920.59660744682, 26760.96833615157, 74690.82360804497, 30814.608664349442, 33301.38921183082, 37762.14197700788, 93820.053014801, 80595.27988756963, 41815.477264689696, 51574.35143550904, 17060.341919925126, 79467.91914646568, 68554.00714997711, 80294.62431040272, 3351.6394478895318, 43875.83491425153, 77.73872032758034, 15429.648813064556, 38851.634416849665, 36428.4516387213, 89364.32926638743, 38999.648896551975, 71489.67257102406, 38684.16032645294, 75487.70282224733, 48599.452229831106, 64388.92938121217, 93410.48561350284, 91279.09340732344, 84733.8916988375, 45921.41750625801, 32399.30307431518, 31997.039421039954, 68505.93283164065, 62964.4018393094, 46622.12434543712, 13455.24904798392, 4934.944752608916, 39362.94700748118, 60419.86000566893, 38974.58118919399, 61162.93252404426, 54133.104990027605, 99391.65739625927, 44920.16849989085, 15868.410339679984, 55180.558654352666, 6724.972939940355, 85363.01511818047, 95285.03197142376, 10164.21953346257, 84823.81202610773, 68714.40288143892, 69685.30704969921, 87018.39205400016, 65875.81057328913, 90275.30464621198, 98055.67907157032, 53652.225598905825, 89859.07866525573, 61660.642461012314, 57026.24332668792, 15479.661867070026, 83764.65500082629, 31429.960248677537, 42420.059487790226, 33907.090438493695, 76612.772299551, 13503.714543739197, 80118.39375369193, 35622.92324397883, 6949.528091135693, 30812.507638789, 51476.978550943095, 39301.36016101948, 3173.9522203447336, 48662.630328122905, 36677.45842073196, 12745.832147296698, 86977.34344795569, 40622.90143518516, 14476.357404829643, 21309.14425256313, 31093.2925456489, 61111.76221013689, 71696.72897241275, 11608.146339527304, 16311.509770163857, 69980.48812864641, 11858.395544332823, 78736.35683097944, 97523.17979614668, 55298.27393893208, 21942.671112928736]} +{"id": 488, "vector": [93159.92605455898, 40094.33259525046, 80053.02854722238, 33642.96767703444, 86799.28251155138, 70290.23789197819, 58316.50051122175, 34828.14637342656, 30287.03190886972, 28453.222479846518, 78793.54395473773, 44219.7820400039, 11627.54090501511, 70901.89341099914, 80879.36921990084, 39598.68642881733, 49766.711357166394, 41573.057688775625, 23427.248154034787, 48749.88391951793, 14997.08859267087, 62508.83662956849, 98945.60592809695, 1545.940484081376, 1799.7633378572298, 77020.33048975441, 2389.2941387395726, 63233.33579065211, 65028.98945794806, 41994.51245981634, 28567.92747418636, 14731.851210103054, 96908.33480418782, 51340.135643833906, 48274.20658119461, 89801.92296843337, 39923.523900640575, 39478.8526051982, 76948.10232155037, 73721.97051093861, 29100.331930589175, 37481.49019470956, 96887.60606950498, 35284.55986049097, 80233.93363638611, 54307.7528679444, 50735.08915053773, 11692.541679882961, 75571.2746413711, 16926.217262704824, 82309.56665549728, 36002.526309524364, 83016.23142889146, 84939.60311451391, 24030.994258835803, 82400.4275082401, 34786.357988686344, 90659.12286459317, 4168.619178442556, 71081.09053364738, 90119.60623146032, 94249.59858287327, 93735.19267044259, 51052.477905300744, 113.28737440841064, 10191.938440247073, 85457.9707509273, 37489.27322549298, 43413.63909413139, 25768.458761128088, 73060.43558335483, 12201.73977392618, 287.0529497982388, 51574.41022386543, 35192.09672544433, 96111.32445686475, 59475.28593942731, 28733.66993669344, 5846.992207142787, 57613.71485239355, 27806.644692496073, 18151.145875834696, 81632.36290330866, 79925.51812567186, 36740.89835038573, 53751.193261222405, 64431.21445289343, 13860.852165767945, 39240.00315722223, 10640.999598960998, 90671.25143183905, 83149.44735837511, 76765.9063123111, 70160.38014367675, 37609.52222645417, 91969.91646198141, 11789.693226163534, 56834.16379077314, 6632.110441807648, 89319.42363455324, 74975.66022373135, 23126.214339382223, 40397.30862423654, 63775.14807411993, 33667.52646302613, 91846.59417594108, 41781.39268872001, 34344.44980463739, 82850.13234646953, 50947.38901609971, 68406.43755659097, 39669.192104370166, 69622.11746801047, 55143.741387181566, 96485.7551948587, 14877.635409216273, 74003.221005763, 75357.77781946251, 87174.81443333863, 23174.30323371482, 67386.65644110227, 12949.768101627356, 89251.4198614369, 59919.64297680952, 41299.83998315336, 8409.768072351786, 49779.683454305115, 63249.29088799498]} +{"id": 54, "vector": [33188.11260079596, 39348.807334234436, 22755.854065938496, 76939.03470227572, 74263.35237100777, 55574.554405075505, 64825.7056957952, 48343.98496957768, 41064.363645220605, 13160.436919052976, 13069.435706503251, 8588.691576293295, 74967.84490883074, 68365.2248066024, 33256.956398168666, 15526.839848295393, 49097.1583236701, 93573.83619243986, 70085.00192389448, 2580.2922263924775, 35198.56803118942, 6040.527386749228, 21003.334347281645, 30433.572848132473, 25183.923432870346, 53484.36779126169, 35743.89981256174, 8815.8574890441, 32493.591009876276, 80212.5469796876, 14370.517118636539, 89336.58950212471, 70421.76446683839, 31873.33618551407, 94287.78077617456, 8995.850700503637, 67912.76256842673, 90245.00101025232, 81188.56264111112, 54602.38107726706, 57847.314692955144, 39731.288485524004, 51292.208118881055, 75522.84715782717, 64026.4414038118, 60425.98350972558, 34729.74411474836, 74700.26671535833, 69134.81253897828, 71237.11604490055, 61511.217451583114, 53756.03987010278, 54033.96566592919, 28854.521241416398, 81623.55231480647, 71708.75591964879, 68872.18408105407, 47275.73030182238, 94836.17828986149, 45731.033319783906, 12422.091374173295, 65923.57541069348, 59054.4167082577, 90125.24904264591, 81978.13713207263, 66381.75270775193, 88581.7839905876, 27400.626476912883, 5625.034140246099, 32303.42235169362, 88898.06356303053, 43802.72820996251, 66968.52337488337, 2951.6855818302656, 8415.480327563508, 15216.03767819516, 29006.566308658053, 87277.6807466943, 81387.78605258345, 25872.276986700148, 50992.35351553051, 26601.24230604094, 59975.026795726524, 97054.7918892623, 85227.56349760287, 56618.145783363136, 35897.85308773033, 67997.44843929823, 31368.35017386178, 73828.84266557865, 29780.38231281753, 47437.675131825476, 35476.93588355124, 85343.00860075651, 94051.85290760131, 76172.7892737787, 88196.3403531661, 19620.33860450283, 51897.77781219364, 55778.826791635074, 42648.16190774254, 97886.992099562, 27793.20157631159, 6280.547293658256, 72390.27430994024, 85862.03957723771, 4503.013901378916, 93895.8330833097, 31969.69375614391, 56664.425770055095, 69988.52720618536, 23430.188612000624, 23770.42485543914, 7484.130866076266, 4598.38008406116, 66971.06458778352, 75681.75403341872, 24298.390315523568, 29714.35825496719, 47656.940202291546, 94001.4170447211, 21262.430537466815, 16319.491254378949, 11624.336743609698, 23211.545146511613, 8992.247705205558, 75144.5177507603, 34012.02306953668]} +{"id": 1718, "vector": [17226.383590479312, 81216.22992706491, 84312.12981729134, 78552.61090318394, 59298.968800665345, 50776.770498808975, 95663.06039208712, 23527.101222329704, 71724.73352527346, 34460.905470609956, 34464.03622142342, 93183.63169499065, 52942.63217379508, 54558.61086716478, 41462.21220351718, 33091.526676400776, 57326.66025935658, 46822.369267984606, 32911.23748890533, 8007.120424929149, 17949.84997833726, 64327.126617790134, 70617.94552524174, 23025.93363275175, 53965.62987884625, 30815.28675543206, 2822.24580042334, 8092.505401037831, 14120.80239958149, 53162.35560742337, 18456.736563687926, 26020.545810088013, 18339.835016696958, 97929.30203205877, 54254.97616269318, 39737.16928260174, 69871.74872895601, 10396.360036542406, 4905.923115577782, 37469.06052260806, 92463.35392979033, 69093.73203338074, 95034.77852235611, 1833.0363969167984, 82124.6828115879, 83512.76045479639, 4674.771182717585, 89964.02102196796, 45238.835859323844, 75610.68518873207, 8234.756014890354, 95105.51047605589, 10560.233711839173, 88770.77261821693, 43431.6700739026, 56954.19509952408, 49642.616621688816, 14921.19377877047, 32749.817450342223, 16665.560226177746, 63681.771252300336, 16430.79037451963, 17066.03705345705, 80344.88902663144, 70683.52464549421, 80207.15858468624, 64754.32897321557, 21397.181668056364, 32018.194656347045, 76088.74328239624, 12772.249690250781, 69079.37012658647, 44293.74651947464, 45853.616422755214, 79705.318314861, 61044.64110475014, 91472.51741062485, 7171.986665583241, 1052.9830166042918, 21539.243537826445, 24083.785065175856, 72166.46476814176, 19186.477452448693, 10958.03463570697, 4474.242303945252, 47855.6761220504, 45282.66319957518, 82707.7330685531, 89130.48779076526, 49899.8178043567, 43747.57181123507, 85687.13285083321, 96054.67601545888, 86817.71797163364, 27840.902652041055, 44000.136730786566, 74817.12960142571, 15196.319686778392, 99558.76574190227, 96767.384433019, 5949.308593266145, 75562.70618261381, 74350.86185303207, 58367.53890692915, 71518.19936547063, 55204.74993073189, 24373.413659906128, 97001.36678489526, 80847.00085895714, 69825.16267057163, 815.003841472195, 70297.3470570638, 51853.65909971033, 25512.92416129082, 42265.378060079114, 39671.57626680968, 77709.31353107258, 74568.05844626341, 95200.31401181038, 5003.104460906238, 78527.04597007173, 34908.13157688369, 25189.403850882565, 91858.90895665644, 52520.07773222668, 15325.988307605265, 26191.689081775105, 88543.98948794838]} +{"id": 1952, "vector": [22161.49089386491, 1303.5568973601253, 26670.6748958213, 39272.015848237475, 33651.84229235487, 9684.57257663554, 1278.277628787572, 22094.805651724902, 74073.55655730251, 13219.680061605677, 70332.33029412318, 55144.81355016886, 71635.17851441978, 59681.22332704398, 88874.87472283155, 86903.20142724735, 83176.34813165941, 52255.31455441177, 96943.33451473342, 18348.803808657365, 84036.7833848627, 67785.37473260531, 15835.890729709667, 95829.5746859146, 28604.008891816058, 21646.56428312389, 129.91625952795127, 90567.875278877, 42463.95193578805, 60179.68648883739, 22144.206969799307, 11968.281634114552, 19750.38783873594, 17407.90877447207, 78437.80408605382, 33266.047270582174, 71618.957320316, 28001.776456021442, 10444.166693599554, 99613.38810602426, 40303.66809601804, 91855.7633651442, 37132.476408476025, 91082.38066142936, 96167.95977138616, 41805.79448185029, 19614.91714849727, 56046.82169701584, 24850.862356771842, 53393.98607575285, 54651.96777292445, 59225.24380388414, 43723.721860101396, 80950.52363889568, 50959.295774769766, 42014.69468754039, 48902.830003438954, 51813.72228712707, 81815.90512022657, 20885.011001928666, 49230.8890535326, 58546.555543250586, 94282.81564202692, 91363.6699440785, 36150.365604972634, 91756.32230267735, 76405.62199930295, 47374.9024994565, 86198.04886128104, 86549.011775612, 5363.946747381654, 70494.36382439181, 87348.34828452735, 51102.578901407534, 55698.197099490375, 14994.323718008518, 21013.475912047506, 26776.020042875214, 3742.9237195173882, 51450.679126297415, 761.0063527605848, 37416.4582192417, 73522.75758525147, 88011.48845212733, 8386.650816352203, 29586.250755854493, 75950.15281693192, 17003.82070097817, 97738.99560474673, 29497.815614285537, 56727.89527414896, 11112.55945631594, 32900.60501008327, 26290.924159437658, 84063.8658500314, 81483.292884663, 80120.12468664958, 72948.63483084032, 75493.1620096863, 33402.67885912637, 87687.313643006, 38034.73330783256, 37935.29684527352, 25793.805789460865, 3723.931519488588, 18348.633803319535, 49235.30424793365, 90835.4760374279, 96692.2035413176, 25391.341248388533, 41477.9306053089, 38976.336932439146, 30994.600793272242, 22410.61783072592, 43753.09889454889, 83512.4361428638, 78609.86016155858, 90143.65138692438, 40139.49370569053, 45151.7662701857, 66495.81614649211, 13743.416778948691, 42340.06280448386, 78843.79200342808, 2527.0216297422653, 67611.25706473106, 3288.2138431538865, 12555.567873502127]} +{"id": 834, "vector": [32005.27361107548, 94235.58010005155, 90796.70394303354, 57180.95087585439, 49188.99585968484, 7601.323611727584, 68969.9928035745, 75181.58708450606, 74250.84658943138, 12484.853308728827, 49932.65455600916, 4033.4603112525792, 92573.84075905253, 12553.891737411604, 72058.22968671143, 23941.726055264167, 51899.860951230905, 54598.48326786303, 13428.694943578246, 41908.92533815469, 4981.0807798638, 50037.02430665408, 79031.708363573, 11794.611792692178, 32381.891477728997, 81099.40355766594, 81593.23109785606, 11231.43556525763, 53810.39437926629, 13483.850468652903, 99969.84918808773, 85283.66860436917, 73310.83275275795, 24109.440920849178, 14355.472831365212, 58806.01230762177, 16356.728424394796, 96415.33504972239, 26394.87104717454, 71457.34080297234, 9917.647417608188, 80686.9257477829, 34986.46141353701, 26788.957753945775, 27221.938944718837, 35583.826357466045, 92353.6687096575, 15496.606539212344, 75659.97329201469, 24330.347541147403, 7596.1769320795565, 94190.8795914438, 75729.16976891366, 48220.48372068077, 75383.41869298747, 97028.60185652919, 93714.4830148726, 13699.922505240458, 67293.37494988405, 91552.56194751407, 35068.30558843531, 71521.64210000538, 63765.4700530098, 91142.58721538364, 57598.59145635291, 3898.133172479701, 31012.519736442566, 98115.08754559171, 29423.711413088793, 21888.157432906908, 91629.51712252887, 23184.10933856775, 41823.43762637152, 62774.15679705225, 13794.396794238639, 3236.3572360181615, 80814.25346049927, 45047.98526469931, 7628.774353862855, 25387.413880213127, 12393.12071562194, 45242.62696367037, 75352.26367412646, 42440.38094374479, 18984.711630847072, 62020.52611071516, 11178.018428514424, 40539.59926937084, 65731.76649537445, 5276.9664074184375, 28972.463067097266, 73663.0498206582, 15909.504039941157, 13240.464544829367, 37867.72242019101, 75890.5122432929, 18780.69069361753, 33288.73789787904, 97527.25976157007, 16232.04136231028, 53190.117643900114, 831.4585350044812, 94276.67906054045, 97844.09010524391, 94164.1666713738, 97658.62665423607, 18258.516367856748, 13133.77149472228, 46104.1714156783, 61361.968187728235, 53798.5568793634, 1649.0062574705867, 14.136954626187137, 33025.049893980016, 1225.296249022456, 14863.948350879653, 68315.99626929892, 33081.78535290933, 97158.8481536415, 61128.548214905546, 34177.60437855704, 72227.55007543036, 64405.64777543269, 74740.00667657236, 65476.16906623757, 55526.87319754694, 55510.10357719345, 93018.42897224714]} +{"id": 435, "vector": [77738.24409768607, 35658.14320503384, 5302.26871851005, 28516.070233803413, 88615.73308281567, 62301.7985605201, 14753.327087241374, 4058.217001973963, 96746.24296549495, 49655.219798420505, 8516.519407703272, 76298.6965505596, 72103.11798732037, 88181.69655652388, 92321.94269393518, 58702.491912472935, 24284.88287979195, 73415.82873796669, 2871.7656475372055, 22953.80478661293, 71769.61342334138, 81208.85527805636, 89659.50931253354, 59590.13083464474, 85144.0686103363, 44197.55302466245, 5378.418429990106, 82345.32069575677, 89329.39474925511, 40649.15488767309, 1257.762456244449, 23698.72207754661, 91229.47625506812, 9718.02484021279, 13842.84055591587, 26361.072070484304, 12680.477715437799, 62109.06546409639, 32646.93997713892, 36967.62066278475, 96223.10772060051, 2504.992652755833, 76293.68402656712, 68170.78754008112, 74253.20353764747, 58044.880518532984, 55153.27368104618, 13781.328975871866, 6237.290549681052, 24244.469787516497, 65115.38035220581, 53593.71418441089, 40275.39422566891, 51641.35778503949, 59300.257886713945, 78229.86690352003, 32819.114207516344, 12040.11573642737, 42803.13869828204, 29272.680321151223, 12370.046765599353, 16328.274185933467, 52065.59634193628, 91661.97212765248, 97628.43230046159, 31960.733359116854, 88754.73918784854, 93147.96624714685, 29828.261594998872, 44862.59062134439, 37977.14281486949, 84561.6390035446, 5094.91641574028, 10639.674673513055, 44779.597943360124, 12682.018290752605, 81008.68765489441, 21209.937896494823, 53785.93081336073, 92655.04488946323, 99839.27123754757, 50572.35407696931, 21221.22159218983, 90513.07514269774, 73109.12641264562, 44090.570615434255, 58725.8046169551, 24782.08362770967, 58036.26004407884, 60303.92267632954, 85708.586795629, 47894.568382482474, 17019.372546425428, 58219.73332456748, 37266.5608615934, 49312.168313540074, 70089.1622504343, 1775.8901012851936, 47317.352990924286, 91421.85190035093, 15783.100271391859, 37206.31269155285, 64874.58185320718, 41935.66175292295, 48718.860718938995, 29814.342446374998, 11233.624787290208, 73738.92961189084, 5314.412251233059, 57892.65056559198, 77126.19122077848, 27880.930959947724, 43725.134656763854, 74574.46265838646, 7157.9315757341, 87952.49254828326, 67922.96658798492, 17683.758053387, 51471.63492461253, 3551.147665803156, 27507.046137860823, 88426.49333604574, 58122.231381056685, 77818.79726034729, 59634.24094305838, 22888.61749612674, 97798.4493379665, 43092.83059931999]} +{"id": 596, "vector": [67687.27629733995, 86453.30441400848, 12897.48902153226, 20452.474434969547, 34993.98319996327, 34587.772496986814, 88733.59084930125, 5157.003177721475, 74340.00841546002, 13606.167945513225, 22010.914356048263, 92391.4234890674, 90505.19077021994, 47679.58641613366, 98708.6740697412, 34956.140186277175, 53834.90692012399, 80793.29307784988, 39615.46427741669, 20221.653710723698, 46915.519764004945, 37639.339483903976, 91003.76433275074, 16475.443650961275, 75634.619709969, 74189.60153086681, 84592.16702450428, 92898.47237757426, 8727.783548089928, 97232.0092899635, 51985.335163129275, 13971.121244235119, 20461.47673988603, 92450.71038434267, 49500.971375415706, 4740.553456667607, 95957.30284260254, 32507.616200999244, 30901.37319681535, 52521.832309531994, 47066.08918352925, 92488.45751783559, 45691.24724552914, 21593.548439676768, 29425.682898837567, 64333.430427314684, 84465.84635753154, 5866.691779796218, 95610.72098981615, 49876.25767627188, 21803.866919459393, 45231.4570518111, 45842.742352840636, 63607.81497389192, 74895.45593627892, 77065.85690932092, 76834.53183278005, 19940.122204933108, 64639.47282465199, 56528.526690724226, 14634.390128170704, 96887.68332140386, 31986.671584730055, 86900.35543681776, 26778.15421361397, 44272.86251289672, 76502.96155740865, 37978.58630683606, 22082.273425741627, 14108.76876460455, 25245.229778144752, 48556.79826445001, 40472.0216206375, 58145.14928613892, 20698.658673125246, 95852.4863443644, 23733.138785423656, 32650.146651107658, 13963.929166685795, 87367.85435871362, 33977.69572568374, 78885.39615268153, 38944.93602947521, 48404.66146203707, 91613.67802338766, 4634.454066483618, 57671.54314458984, 91004.81146657113, 85424.75278866943, 52641.32113134975, 45055.23906054169, 24571.994771285033, 51212.520040142874, 21025.690302725543, 82113.33042609088, 10526.759552137111, 35366.1162983658, 28696.182355168166, 2991.4284471957762, 13154.737477713885, 50411.25917198046, 11021.207973463665, 84861.18667644738, 53555.86240470425, 94426.2924486273, 34269.257013390095, 89740.9725351523, 72190.7515024008, 95812.54605352048, 60576.83092723525, 37882.94313073986, 22191.763655367726, 1308.4426852634556, 5848.351129933871, 79828.4242799868, 47866.248946864274, 38087.36368714719, 13974.721830206316, 52211.136446681594, 81600.36766155752, 32170.307742876914, 21750.942946626074, 43545.88538065275, 71157.48236096535, 22456.31954614601, 26046.994246514376, 13379.366735072173, 1044.0103645460886]} +{"id": 375, "vector": [97109.37101517936, 94124.26981781832, 6536.619425377766, 40997.66255243041, 72600.88339539188, 78645.17986072588, 7540.470439724189, 49376.12773598643, 65160.07543448074, 23740.00934673465, 14882.899687979212, 94844.887345646, 65711.76324757977, 17099.173369054555, 89238.85796526278, 8396.509963781096, 8693.438654396723, 47437.12148750507, 78918.99770453466, 6765.313854879751, 22928.698931411018, 94518.42513550966, 14128.832418834481, 73706.28903979696, 11174.838487799621, 39150.79981858352, 56864.4531327114, 35636.99280491197, 8759.227805905779, 96498.39059759241, 67402.939767545, 46498.33567895702, 8969.402518868852, 6762.223801890166, 49097.30486015197, 67343.04326329663, 90052.85403395616, 96057.59711854381, 47055.31346451472, 29841.288854520444, 9842.760226220704, 93173.53166664997, 29509.78779280111, 8942.969449780847, 93922.24897536791, 88133.14711034708, 73000.41851928411, 42125.31853811609, 29685.327708493835, 44223.85644112987, 64547.85093934619, 80216.83192695735, 22481.941089158976, 16978.611046548376, 66633.79056164503, 3545.985400628393, 60298.402016134314, 54351.804734037956, 6603.059167492153, 23421.099507190036, 87723.29188357318, 43563.647637258204, 68477.61652856866, 38663.42443326594, 33183.44648814021, 86709.03900770492, 86367.87362991575, 22049.722951656902, 20050.275077656555, 57917.054507802, 41961.03981079955, 68753.17748016378, 19234.62442729448, 84140.66341768223, 25365.48770669996, 40730.93988864631, 11794.3639669433, 3243.483391158708, 50794.911913886834, 99518.94461069994, 53495.8098924067, 13632.043009245497, 49339.483720808304, 52822.74184036358, 41525.4277639521, 90713.51719058145, 53595.60761021721, 40960.47352035771, 60699.78644183043, 11058.427967445306, 62022.86789939873, 62336.15284460219, 18481.701204661527, 21207.924815218426, 23040.71078651756, 55668.30296488439, 37384.532257240244, 56291.39277220162, 67579.08355003147, 72430.03816492515, 72072.53189094402, 72277.85918920801, 69758.92215413216, 83403.90348669079, 54786.682831753074, 95320.32067273124, 82155.81319057665, 79562.83651611008, 95560.49356996636, 91444.0653146155, 29775.325572846345, 60741.51616109748, 71022.28279047577, 4778.557727173172, 71280.62640422581, 88040.39649682466, 36652.65068494953, 73419.8161930116, 14257.414090724107, 68790.9579259045, 98716.07662631264, 70056.99565979268, 71710.72356882431, 50636.737250496786, 4662.9572670730095, 60295.891732164106, 37154.7580299998, 33765.91639814661]} +{"id": 151, "vector": [9434.18614477226, 22215.83320277205, 54073.24459423166, 80596.64104080346, 99645.26163410422, 61574.94637608703, 78493.14000759907, 56907.919707422705, 10136.009600784822, 47935.61182934602, 44838.98697647125, 82058.77838142012, 49570.27102456386, 47156.79281016394, 78415.01439653011, 75664.47755907298, 3158.3847041333656, 22850.43445356185, 26302.438918755277, 76740.19949949649, 58131.80810180182, 64185.56660938105, 56202.25113953446, 12655.590034534493, 61174.349193260736, 17964.263172751536, 44919.53349139143, 7340.75396292857, 43322.78346030537, 34813.33180287097, 18126.058071422325, 32731.309302468613, 42192.70672921558, 47700.340591260574, 43258.294479568394, 60268.11446012039, 65935.1129415956, 60415.03995670025, 92671.18137410397, 78982.34244543656, 70348.10657775078, 26830.678704379872, 2069.3942474857117, 11131.690417458396, 38898.8309969739, 24546.128834103354, 78522.4038910748, 3677.3806653339957, 88923.91095007118, 7405.075964653318, 75753.80062982938, 59489.13016835249, 75638.43783252033, 16429.827612069468, 56562.81093360155, 21424.817027888777, 29729.627740880427, 20227.735505738743, 39237.55172900621, 39132.000064482774, 31897.51074774657, 57710.85554156776, 78522.89294342391, 19234.040629049643, 76548.0877416502, 946.5758425507343, 55233.57864133949, 96673.8192791991, 37134.79872099952, 3497.858607908044, 85415.02884892187, 28917.12969126491, 13584.740451591015, 99017.67134801163, 48376.86939346962, 24675.299338948185, 44997.256063316956, 19127.025482703863, 55636.35228036632, 99789.17541492057, 98013.39167435654, 8036.294742572137, 27146.07053569721, 97981.7861338949, 19806.08867299517, 75183.65262218307, 57936.0641651342, 9662.668659616136, 26538.263801293095, 26170.101741410133, 58188.49868158983, 88195.80539522941, 86323.95318484568, 28816.33850089348, 29015.394375863725, 61063.26358379249, 76690.33720030327, 85114.49105174126, 96305.35222242014, 63432.54499199404, 78608.24517176012, 26621.555776308036, 69325.96573272889, 60912.06809084204, 6867.857069317884, 73980.19258476129, 48545.072583960544, 66059.5650335604, 65303.88404442665, 18268.980986636598, 92548.13808532165, 45189.70581071435, 40538.18911046678, 42167.769562137495, 79742.62118501122, 29590.136421702828, 79227.71652272926, 76331.03562171634, 73301.75221697906, 72331.90667332955, 11248.008893278271, 90372.77884348699, 2364.7946118605655, 90624.9706990859, 45906.96402430586, 86559.94980418707, 26107.565778886565, 705.158885754209]} +{"id": 567, "vector": [86847.74290977042, 50587.91094708118, 63182.91818700028, 56616.27583728466, 83017.87227908277, 71257.48855262475, 68509.00402741815, 18640.22793704806, 54911.89168561427, 46447.119442021736, 17797.40383073025, 72011.43460939957, 52691.05501803933, 20023.118056785017, 63296.534806892836, 77201.96781998161, 99755.02712499145, 34297.06811875715, 12108.168682058373, 28764.718281182155, 18397.33369546146, 97122.20175347272, 35750.661324327, 94807.45316478475, 63834.78945214263, 50315.39675835651, 81231.7438869105, 72016.84025435943, 25805.70420666193, 2765.767330902014, 72725.03710907613, 97328.49326696465, 9658.5340724616, 11182.192421743997, 48186.91981706046, 70396.11694936179, 61347.609526754495, 37719.20920163777, 24386.054924609045, 90771.36112447169, 58190.60405500711, 32865.97646709365, 53129.84186944937, 66804.8378604483, 39445.17203673418, 45408.783620111084, 95426.27680074365, 45083.83883104733, 99723.4312406222, 5741.567764804567, 46961.44173021538, 36996.26938527901, 90984.59649643226, 77166.96582085727, 14211.181799032269, 74364.73299207572, 98053.57847929222, 63033.89957250989, 75217.98171614073, 19136.541603289203, 45851.34396123602, 16651.953868484416, 58501.39385006939, 22836.032287239374, 40875.002692726615, 11665.82895875251, 12907.195346679568, 6773.2773898910855, 24325.47011355951, 98542.81319572996, 14415.532295246858, 56661.63008131505, 25027.813269347742, 99905.7130879819, 25136.323304838435, 90868.41533618896, 66918.82050081603, 52595.15578823722, 10798.759474734243, 16952.79534857269, 72263.75092792686, 32596.974811035605, 59977.53496050851, 43429.01750843474, 78644.03403655547, 14367.060279758814, 40075.8384074886, 64920.10553263258, 55917.77332045125, 2393.342755994077, 73044.97011886851, 5091.663795758827, 26685.561521409494, 90474.80389612976, 9245.689422103398, 57759.88981406968, 4818.961478215267, 13237.3182327436, 67027.66009868446, 2412.0018883683756, 88723.25165557067, 24314.23003388542, 73141.96208276687, 37236.11675632299, 66729.92337180022, 5780.8841659843565, 73684.37167924624, 25729.294472743848, 8317.339603553186, 33753.82562522623, 54830.42244160945, 66127.45071634265, 24167.3808448998, 17236.06552165713, 75382.57169145264, 56213.90433438237, 98172.98194361717, 91743.56159249994, 4590.179479071899, 6079.874172676092, 17030.463072551793, 43066.91619325027, 15243.005343955207, 22473.326163661044, 94524.10944728438, 45422.269364840526, 49392.07227456378, 57488.06092579904]} +{"id": 1337, "vector": [6872.041752733038, 90422.70117785885, 34776.418595822535, 56837.72537553375, 65258.46540155235, 5822.406588495932, 93216.23729005935, 85706.30137775744, 69198.28163325384, 25268.08790379047, 89553.66221586491, 50741.46812848129, 50855.792451251145, 34092.21471120046, 18124.40497907628, 69985.35826106528, 20362.071211318413, 66287.60008543321, 17978.592399546833, 31686.514372057583, 36490.01808300428, 19886.769256135107, 14251.95921861001, 42288.458009383576, 66909.75207736353, 58464.40528231172, 18725.8586892461, 95263.42968614861, 86450.30082391185, 54995.767989830005, 57064.162682400696, 36109.08522735405, 25979.968773573026, 7911.17076510931, 26900.0817539224, 20172.282420737687, 60545.0368170679, 21141.166799304356, 49960.20225005835, 72454.54416613652, 9420.600304599337, 44975.89897641974, 73984.18438362353, 45137.461889712285, 1362.209270599768, 41493.437065706086, 26487.85270688533, 19732.705629976277, 50130.226310396174, 795.5843143512053, 72033.85108850707, 18432.528167351782, 26186.627095870295, 85185.61039845274, 80884.55290436772, 96081.87004907375, 80330.60263298557, 85002.68865038807, 13355.4044733331, 86351.1794680337, 93390.55338371587, 86449.7820281597, 22414.73613467496, 48585.19731333867, 42782.21286378079, 23424.479350517315, 63269.13037708062, 21449.437351302393, 75118.07347019426, 12484.656211815249, 38807.09651420932, 29715.726059343593, 47648.307299409535, 73359.33875646458, 56942.5075493057, 2937.014941782856, 48842.879603120295, 69262.7269461602, 10614.586854319985, 39775.53105171816, 34015.57643440061, 91938.62597554238, 52431.03878097511, 75825.65459928843, 22112.967106151293, 15706.91583142223, 31546.181812948802, 28810.163209131566, 99153.92077989301, 190.3224392447056, 2269.700970481603, 78769.51876711808, 45640.52303682848, 70463.48253695563, 59384.48852089976, 25127.397414278097, 85458.71255642208, 36283.144466947, 31684.888195134463, 10611.111489039371, 17548.570921200157, 22757.644267574517, 21222.67822628415, 63548.28852509344, 17062.723689324055, 17828.835362503458, 40186.74499733849, 76634.17486000611, 30534.86323262975, 39511.08958477659, 38178.33091259679, 95258.54716822154, 88630.64629723913, 20194.619695352278, 62574.65260790866, 31561.89538005033, 1822.4843116817158, 54217.308219656814, 91815.83858333682, 83690.05586089789, 83600.16425984706, 21635.535934402917, 43339.99342400977, 26656.989876395743, 77437.11401788346, 41359.835091176334, 25412.95362578635, 44709.89985528668]} +{"id": 1391, "vector": [28125.65073149752, 60787.21707703178, 17564.41374269464, 66574.93669212097, 37555.72245623216, 75381.83948411657, 98078.58935420218, 3724.43544764377, 28921.9607754016, 17013.443407114548, 77921.48313560468, 27488.00119595779, 11542.741104804998, 9628.400662561799, 47303.88717671723, 37498.81367081279, 1675.4328048814382, 11383.491064517448, 9848.91367714561, 4198.69796287422, 25939.973165348383, 25706.22222496798, 44841.770215865006, 34232.870679355, 46330.740526008194, 59593.21425032183, 16023.987546187978, 20993.069336968063, 83598.76053681446, 45625.83953918743, 9383.996889347412, 79075.30965140912, 46972.60334035583, 88324.35506296068, 71278.47466929007, 53750.06061690263, 58048.192224226594, 99476.11930983467, 97412.70162993224, 38944.90666983984, 47405.77169222815, 91962.64556887251, 84951.69216561201, 71202.4058108053, 24104.07980249756, 91559.0201021889, 5164.587582443059, 41265.2833757703, 72136.26957314034, 23454.211919673253, 31835.97357786191, 12591.176180038732, 27014.59205238812, 37539.56398309755, 98233.81245384044, 79221.86588400742, 35740.37234308825, 45188.6128479263, 11745.502760820991, 35430.583982104945, 24255.326223496966, 531.2854407019785, 55725.78176408075, 71617.08541273892, 50040.47197089364, 14688.062299706373, 69192.2483670153, 29756.222425335876, 56565.16810773259, 94544.76872017536, 30766.7068875764, 67332.90561119144, 24736.56409054692, 53433.2307724745, 57061.884732900435, 74986.53941310852, 22388.22930607932, 61737.043814134086, 31537.390712103163, 7732.61540974397, 67035.84284236844, 12044.938445196285, 61270.90984035599, 82871.03357757855, 95773.00157778634, 5034.532786189349, 34721.59244527524, 52141.14408293096, 4097.035862861498, 94879.79951360934, 8723.028636942186, 76297.51824196982, 31492.517796170883, 35460.78270839359, 35432.77235709793, 27865.407306565903, 78789.47184142533, 72550.44051411566, 84765.74366557378, 38634.15626620645, 67995.44692245324, 96273.38063955403, 56652.98295371689, 4487.194633109948, 19637.291064812533, 3152.558170430164, 30154.72836458777, 83329.28307498294, 53222.015419594296, 22376.429356337492, 10400.064220383343, 42606.01950310581, 44019.14216107666, 87586.2453974757, 96416.09721835601, 708.5120371095766, 65283.62996255203, 5620.2701287574655, 48564.68514726647, 15551.04400589864, 13520.27936130099, 61618.77827455383, 65814.51897466049, 16042.619602067265, 27032.64983593574, 37792.66660016427, 92064.5311535846, 39320.292996272845]} +{"id": 368, "vector": [34663.70468440678, 50330.062745115945, 66044.50375850452, 95096.85053595882, 84631.70936107167, 95332.04880972295, 43667.294306377094, 26077.061574852913, 84348.0469563763, 22027.618582822517, 56949.394653289106, 50054.32665480933, 65317.993798142736, 35684.9379168389, 17298.151233138982, 10860.39558796552, 60836.26769827836, 46431.76083877248, 69950.64843932477, 7083.882689506293, 74827.32821020756, 6934.34935307099, 88172.47869626488, 77491.25550746772, 22824.52969888974, 89957.02013840116, 31618.412253164595, 76243.6123725512, 9769.166436727028, 80904.18539255619, 28461.33811549805, 73861.78615678073, 5512.9078413641455, 49149.99987537778, 68721.96137424468, 26612.365775077018, 46715.36296030153, 86370.49943393088, 50591.670850460316, 91452.77422399262, 61617.89472402166, 19376.940684916877, 16414.456420572144, 53293.431589538784, 30154.143320091986, 29688.216150918102, 96190.24325144732, 36679.02837537957, 70352.51056129127, 82709.92122979069, 3167.5854670765057, 60338.764864755554, 16291.355055699441, 95031.53365981461, 74449.25529248851, 47613.66738295283, 24344.13254785299, 85875.76299022559, 59557.535464977875, 55201.567476308366, 74725.33079308955, 86613.29442835123, 25899.88467740969, 92138.67654863189, 78387.54110228947, 94150.74053770708, 39256.392905978886, 16042.364085363048, 3168.1440361087443, 73945.87440244989, 12241.98597152818, 31780.94315590586, 35450.63558561255, 46992.76415725439, 20959.894880178665, 85918.16808093069, 31598.90118673453, 4939.090104357624, 81035.00667375994, 92989.5996239057, 81322.28323658745, 48872.3730736557, 1028.7495170154036, 27481.51995939605, 56935.91256420098, 49164.65263810347, 12861.302991294366, 49693.99701059876, 26522.768283650676, 15629.781636511065, 4170.944835418122, 49432.682941405015, 83728.11386329016, 67607.63719225509, 39779.0265759777, 27560.42722808376, 74417.92125843656, 20207.081748412835, 32486.34433341182, 97770.27169408002, 41122.792390711475, 51794.54570744816, 34293.139965967566, 47284.9449248333, 8029.115983911895, 33096.52840432162, 41900.72905530217, 92729.20357252684, 23943.845244511165, 50435.51301194873, 2716.8232777685985, 96336.29575052646, 32232.061798395218, 93308.83194687426, 3082.782736304646, 13466.138273434148, 42630.73879006915, 53687.216172850916, 44694.07612738794, 95860.72912912283, 55491.51235917136, 96759.12036306666, 19536.232422625344, 76431.22870402368, 90996.85403553446, 90677.27184887578, 85860.37721896307, 21551.632732260696]} +{"id": 477, "vector": [33493.816464294265, 58635.31844936587, 44117.683318812116, 9548.7652549205, 66533.4229962926, 57889.12944230099, 38131.1781036795, 23732.761855082364, 31893.669129041315, 76086.69222740486, 53105.89365576396, 31440.88771320348, 15419.423376721497, 62075.42287415772, 24010.034739848674, 18332.052362867067, 1717.8980739647964, 58424.19417693828, 7547.382033431671, 4189.019748959288, 81862.50211965136, 65886.74540611196, 41082.688977744816, 72337.19103569587, 30188.438127403162, 19209.97538755399, 56059.73135229154, 77239.37087134903, 22901.374593358414, 1369.580054217323, 81558.15001378683, 71476.40320773816, 24678.85014316006, 55688.085843979075, 32897.14224593532, 49567.00128849923, 62753.71517257142, 62287.152326536125, 20663.68219937588, 73662.17745664732, 34918.964027217145, 6662.962460092592, 81639.74086629551, 63137.79121769365, 39783.07883893488, 623.4279291155165, 27719.22042554418, 43813.62803513724, 8046.366423071693, 17531.7859487776, 79786.80697144404, 97153.73420317976, 96352.29567189462, 89669.37300999698, 87712.95644430166, 46874.266305004276, 72877.2423112341, 91203.1035517269, 90700.86890424744, 44320.02392172579, 89401.94982504472, 90233.56142515973, 36122.7001992159, 98275.49183670928, 35910.33348515532, 52345.277831773186, 46637.77830568668, 50237.98030711069, 38145.883933386736, 25520.405948455726, 6756.09396243465, 93907.46992282421, 98313.5477400325, 87090.31160533598, 24024.08522439422, 56400.46933517787, 54959.842284105376, 24347.78550910034, 56669.89011639674, 6562.5708899068495, 24094.275976047564, 33978.490071099965, 82855.47153519091, 64194.434517203816, 53776.112136345175, 25405.077767146235, 35062.140084769664, 56463.11813469317, 62717.22528778303, 71980.12644631002, 98648.1261256521, 36796.67880274311, 76202.42680997841, 18652.798509473512, 33439.69223959189, 64318.315987040194, 19347.147812446143, 30924.39244273344, 54871.111920003736, 47118.01768323855, 64047.7573742618, 45502.118791822264, 13295.136202749747, 76697.07878037987, 93676.6848445563, 7842.946543075457, 36481.013452112034, 78186.03282024454, 726.158277297051, 70912.86298507368, 15402.53397217184, 47276.46356820342, 51710.89461189358, 70612.90060855636, 59062.23037212898, 36618.41523687038, 51536.14089711107, 29242.661473451935, 38320.35238844282, 53567.55250606782, 74717.1470023926, 92563.37881514676, 76985.10852404086, 94466.40807643438, 46460.788588362135, 31856.733134481165, 12600.554705670109, 56852.53663559957]} +{"id": 1862, "vector": [75218.47255183151, 41788.71259187329, 54734.982791642076, 16520.46054679045, 8092.758431990544, 66082.58118216855, 55236.60878810811, 76331.22583551222, 77350.47757018669, 10634.991979016584, 79885.62018637302, 91872.78387582403, 46806.244090354565, 26562.769623431424, 55695.29629558163, 11600.738182320625, 86104.23374709662, 86618.17353793229, 74651.26614191422, 13172.055841863017, 23317.775928760475, 46134.23026871846, 81167.45486666523, 805.0583601430627, 54084.0409016532, 86695.15349248676, 73511.83596440867, 14442.981042645186, 9014.836409267813, 98118.00886904291, 22815.817381730085, 68546.79932614892, 81607.12019889557, 2484.897646005746, 61489.38668937747, 23032.901462237187, 58944.99513312106, 993.4943929910345, 98979.67098130083, 34075.34353967158, 39610.504341426524, 14651.163967699298, 43287.5514363788, 20579.814219313907, 43862.49714538603, 20226.858934499138, 2383.1770655077357, 60562.738516898266, 32996.9482427935, 21418.450683505675, 37460.94719490609, 90826.23367838789, 57495.98185716737, 91717.33219046328, 83880.73926873817, 36032.02264695524, 54895.3636122898, 18849.669457481323, 98779.95079326522, 80043.26074262222, 10068.64633526655, 89681.66040831244, 24540.55579592619, 20327.905150903258, 59892.23810812068, 66562.39251520202, 43872.26220628598, 42398.04167870913, 32827.76533090981, 22574.761947718234, 53710.440646309675, 17049.27307957236, 78725.3920507083, 89843.13297435694, 29070.731590023322, 31139.451479273685, 21902.623987047587, 21279.247390940913, 48208.37956788368, 14936.571162706758, 63110.28765286797, 63793.28595717686, 57208.64382158831, 58339.46325115306, 39883.79726091448, 68227.2192484493, 35603.9733726254, 85242.53869716097, 70714.27975660404, 67347.14944836879, 35574.883125059285, 9532.445552116098, 61479.788653581345, 62766.3347398913, 67113.44203791714, 81117.43981296504, 1285.3230182279462, 15827.47641998543, 28952.211113832636, 92413.77380236697, 33075.85455692483, 65167.69553254942, 35094.166383776705, 27576.047591558796, 10133.91606008387, 24109.421262361473, 86402.00846752468, 54854.47892955413, 82566.94382600664, 3497.2115559380954, 48634.359823618164, 70993.15457875415, 65761.02263551396, 59464.36326196354, 75919.92506202718, 88472.89357857482, 727.2877270195432, 54620.239651522694, 90769.16633308561, 21969.134366776543, 63272.43006694945, 41154.14601794837, 66320.13751013209, 57059.86650747518, 40714.38175609756, 1141.705569566065, 90597.8295764532, 79681.56837539173]} +{"id": 1417, "vector": [38905.79172129425, 44612.31037446554, 8931.937854598982, 75473.7494090633, 2543.211057410455, 62853.01912685824, 76779.95671683284, 22037.51122089217, 98476.12904289967, 40.24571355552009, 45107.20816042199, 57411.289595348346, 84100.49350144509, 59109.95458363835, 76076.84080537906, 13750.337588425544, 96185.41415373534, 79458.55738758319, 54448.123857621365, 20239.457706427165, 18207.011907229797, 92457.4755274557, 808.5346697664808, 35095.52698156081, 90646.60002074165, 21871.946007821098, 27693.600457485656, 50246.316579447215, 12635.775474454025, 20136.647743733505, 28552.434335618727, 18779.478181867747, 65413.50568897919, 91266.4915733382, 71560.14659793896, 77032.0335587959, 84762.71989536117, 7872.678917063392, 74743.23696310323, 97801.58923460021, 17403.122163593478, 78735.27766603525, 81005.81097242719, 17763.395132822414, 55316.06617032579, 40110.14579331678, 16077.54048252823, 86148.5470027799, 84120.45421379492, 18769.85859515544, 84931.84556567001, 6060.372321904939, 88069.3476485669, 14073.762844563276, 72217.46526716152, 95215.32073974638, 50825.36222940214, 72815.332831833, 1323.5619321754432, 81918.67636683956, 29666.23890669259, 40681.791637747665, 74290.75944474975, 14397.026622209252, 62652.672393144545, 88611.79897650267, 3097.5787538488507, 13769.044563730347, 938.630113565464, 93967.91916568359, 23824.714014041583, 5056.798511881011, 39000.638969468346, 52652.668983961645, 9380.51712702237, 39943.147120199006, 73608.65974031406, 13560.408776788525, 41516.623097334435, 81073.46293326927, 9667.489583786215, 56247.46876544237, 2576.599882397956, 93972.97246858776, 20694.665711448655, 87245.72854917453, 5863.881800377979, 20924.146097061857, 24422.659891713618, 37293.01810673937, 33683.146761460106, 58766.831162682356, 36429.76276100083, 54030.999437347, 70667.4654905896, 2600.8528915882234, 86788.06792945511, 33963.884371278975, 71801.19706812457, 39453.298866693665, 62382.045643479265, 24125.530876557943, 79311.60729128131, 84869.91367172371, 9419.743077335374, 91547.03814983623, 64865.52407005651, 55595.70540387186, 74229.39987438188, 55200.05796656322, 32856.47478608642, 66471.19187231961, 14269.423903181443, 4745.221151934087, 44465.39146422467, 52554.65695270111, 95281.26619573863, 94900.90041040405, 16959.169761731762, 59988.345817081754, 31564.28228920062, 14736.443352311968, 48415.65853035175, 57927.229224863906, 21447.302328239926, 54266.62023169417, 59935.99470801588, 14056.73210271977]} +{"id": 1762, "vector": [97863.53477520974, 80436.35658847688, 68210.06242201512, 31084.056073417145, 35168.071360706024, 53852.587133280875, 61806.96129677815, 37772.39939578724, 30508.183533656953, 13967.33562099861, 18492.591638330137, 2004.8603309059665, 55050.920264361346, 34349.57891462426, 20999.56677045173, 94867.64896081669, 46884.29330507785, 89802.78136364634, 24043.846132865987, 85513.05033384016, 62613.77845427268, 36506.50510073407, 29178.505745663086, 89475.87895867127, 65291.19955472546, 78440.80172680822, 45640.924153507614, 20084.70660863584, 63895.78624728965, 84978.19079086884, 68737.43806413325, 2104.1939740075954, 96545.24767969381, 37397.34179754982, 63467.116316580315, 65352.894922778614, 11066.230136296874, 93962.75941145944, 22560.885282419396, 64622.92719832357, 88286.28629528676, 76579.3131723087, 68617.86518493529, 92759.07425975795, 98945.00571439273, 80139.54094243581, 69216.95222799416, 67993.29886284226, 44503.009528724324, 20706.832573304102, 97330.8350115539, 80306.80175113412, 14059.165372514237, 92689.57687884542, 41830.74180137288, 33842.06494089331, 23929.671474419443, 80439.15108444853, 57011.47770419909, 92889.90735149806, 95213.84315584885, 27351.00404957972, 23260.260709754923, 36218.85089604353, 89377.12266964348, 10708.857976732877, 45740.53061055793, 6737.841203088701, 25858.611532838717, 3687.818582911051, 96261.6939763001, 36402.58729778538, 90333.66740628057, 56321.7218677958, 99679.73740617033, 3708.9549712264193, 58175.68343310213, 1193.0179895841552, 26251.548087111474, 4206.3676053784475, 72767.58421419156, 82711.61955893932, 37518.99200655529, 82311.20833503349, 32069.01588730987, 46850.59047967305, 30293.381431020603, 315.69367527782833, 62660.91647078571, 33406.242205906245, 87001.30893010573, 7938.359180885235, 96986.06005851693, 91560.59006249535, 15257.999220695006, 83179.75855187204, 81778.16209680818, 31470.883389359584, 52600.98725289725, 5739.716186147825, 71375.53151335074, 55164.871146801765, 11669.044121002382, 85467.00588701638, 47099.649727484095, 89528.78717620377, 15800.784287672399, 24830.21693530809, 43913.823451295495, 77074.89727388059, 74930.14591237032, 22801.45079878725, 84771.37423807496, 18430.699731425415, 3596.141344779591, 86689.37818855207, 6257.546667845982, 25098.95208045927, 4063.4584829784326, 37648.97277765525, 37926.248374282535, 3674.569967372643, 1618.28152257113, 15438.774763806163, 71580.29681118668, 2028.9233008486951, 33908.270789260874, 66373.26038196974]} +{"id": 1696, "vector": [82999.2253342901, 81423.68356604617, 63505.1716930981, 58451.9740569215, 84678.98764604534, 61269.543143178205, 99466.00557235767, 36827.762673387, 25630.54736572602, 23088.930361967552, 49884.09008397419, 31163.02889713457, 75869.74238930918, 850.4461723140566, 35815.29777329902, 89260.58795960153, 25040.726034409, 18294.75609087151, 94815.05094712319, 18143.103946703155, 33502.57013899456, 22865.63380694885, 43687.68191990797, 75565.49742825907, 99915.12691042708, 28250.90256473467, 26077.235661326216, 43082.765432311644, 86352.17312547006, 28470.578335382335, 89875.53283035631, 63802.67614289755, 65830.04592677345, 61047.88400868657, 77825.14363987878, 43463.68349760957, 21736.09205270647, 39939.553439768686, 39913.43418929781, 13991.144712263536, 73072.78739231148, 63187.451814332984, 14267.345899924989, 32134.01523110736, 9342.797455840746, 27330.368273610027, 23389.31389713581, 43345.743980583095, 25690.239197948227, 58322.43931853292, 80062.8202166646, 13873.67099551251, 66031.05558951505, 24172.17487243897, 43941.80926408596, 72664.93074711863, 31721.37122574896, 94152.15948632188, 38470.625572782614, 14282.39550282111, 5836.071478291693, 3535.1235442098837, 86407.09363745917, 13415.57829470067, 95973.83742239566, 29579.95578228637, 64026.00197532828, 914.3054399799233, 70322.18517207631, 24561.604998673647, 18932.079232681575, 94146.52000127175, 57329.66864486271, 95777.7874245548, 15863.24216663454, 16100.724808759149, 22523.086402298366, 56006.823849005246, 9093.192300680697, 77512.51248912534, 91291.83929807643, 55384.35492710909, 61222.8092956024, 70348.72041164925, 65616.53130944415, 76835.15859551996, 73605.88427506956, 1186.0140230491202, 62735.76057952457, 34246.51238095257, 19566.464410809847, 99682.15620799817, 56222.23055354071, 57727.22545610833, 30001.27262930542, 88869.42867032213, 46903.097095576806, 57663.57500841472, 98518.02198124885, 12137.870331050648, 76783.42730435324, 10472.071564448383, 21188.339082120332, 1503.8894682772864, 21823.81509662027, 36538.9107526052, 34490.39961510639, 42796.57862698514, 62451.59936346086, 37333.05192975172, 7153.566799164101, 29691.563134539556, 20128.288414797025, 3137.4235837462793, 9982.648774159663, 5995.325979786825, 82394.73036444811, 47541.92312578511, 38141.261978032024, 28363.814360636887, 31242.99698694233, 17320.451862369246, 90599.35991901158, 48352.90838587227, 50926.971956635505, 95373.63869202284, 25876.971664549386, 38071.70565622887]} +{"id": 740, "vector": [11780.639796923931, 85302.39891278809, 30748.08749901673, 35088.73518132962, 15921.115073121828, 34664.60034458446, 58397.35993803246, 14894.283491870208, 33870.05914156069, 80243.9713863899, 25931.944796704363, 70387.90756125122, 77451.97137223496, 77039.48692819932, 1791.1620083947532, 98765.12646233654, 15649.345558950856, 60220.45687296514, 84824.15070834172, 9218.456733936342, 91984.03179303689, 79530.64773677988, 3819.960843785841, 23944.604109977874, 64597.56264576043, 52887.40057172914, 11139.764000930652, 76568.92706497968, 94710.46096963847, 28716.157119216412, 19450.81093269696, 30715.433919568546, 91227.34903773865, 22569.38991600129, 10998.670590146698, 2520.069425552207, 21787.26357008578, 79860.09267865018, 40998.86127279998, 10149.50777850977, 6762.926053135332, 96412.95652192782, 76854.75215320417, 69476.46096277304, 3583.7198507638336, 25810.823532122275, 52126.23967883041, 54778.71073480501, 64650.61025027617, 71313.37484017777, 65780.13076220173, 11501.86707054096, 56677.50930096143, 74154.98455087823, 53611.72732347571, 74391.27539699362, 7843.351473608118, 43765.7410282388, 28946.765881137304, 30791.947847436528, 42063.3464632106, 86954.32186286163, 69318.24620997258, 62394.05372749931, 20881.839720929453, 5808.087830516806, 94346.07640581044, 44752.823139119355, 28577.216531314607, 87762.8581770437, 38245.60185059297, 20423.531441180578, 54125.462382527956, 84267.53100169735, 28179.564121428102, 48287.47929489644, 19242.074593704085, 46065.08427442809, 83180.17905111522, 25710.861131266694, 27021.940368807762, 67129.87048060389, 20440.208060622634, 54422.89930129601, 25155.89021989838, 68320.84401815571, 51789.10980447875, 60946.58546369975, 11456.526461376503, 27223.619546216814, 35493.11416059654, 38169.029611840575, 62999.85023541678, 22381.303145624865, 36249.83194212651, 66801.2242430972, 38333.306536190394, 16874.486096526864, 33290.237895094855, 70001.17057787499, 20346.748072041242, 87255.10256055524, 31233.524545398785, 86176.76152380476, 82126.79597715862, 46022.41837135146, 90307.46225570171, 52499.303953377064, 34994.40732212405, 33187.051393949885, 34982.26212075363, 98062.07218076043, 90869.9102281793, 32031.16942088542, 33047.90181687509, 56827.753001159595, 51602.17268284535, 92491.96033948971, 5103.4122214268955, 83507.26511123357, 99532.16755959776, 11717.358951674196, 55901.17519049434, 21950.45825487608, 96968.4249450821, 56655.195072684575, 44909.75476266299, 3060.0685719860744]} +{"id": 240, "vector": [76197.22104037955, 19974.03633089825, 90197.1866162834, 6182.709217714711, 1632.8422427618227, 18220.755077748054, 66121.71972434733, 15527.358392498769, 11772.366616925367, 58877.0987534597, 2006.3566293370027, 38345.32198923006, 13517.454484091595, 8918.511687776576, 80329.2593611001, 3544.57048064718, 54222.34731518291, 74813.8364419617, 97967.6873862005, 29538.46321048943, 25097.66812878038, 47810.48713629074, 73140.22505239118, 7910.375653350788, 57000.547189260506, 65484.09764441752, 31400.382023737227, 9042.77753702295, 2230.8765293099, 64770.92576166677, 35163.18170357425, 99839.72687288742, 78980.29770103391, 33352.91288341694, 71550.44953473899, 12639.835444468528, 88421.40297715725, 43727.06816736227, 96655.55453083546, 33321.25909917104, 88869.1559998977, 49697.19276108806, 47301.04152794923, 8017.815009033025, 15006.480538603484, 18477.330320861296, 98540.43413002828, 72675.5364108474, 85822.67302595617, 39536.826478283816, 27777.93643907589, 63285.627349398485, 61519.647345554906, 46302.36418890202, 19692.257547385383, 81913.98105712478, 2697.6807271072876, 4164.897123388034, 99359.30835275441, 98134.41903144743, 57566.13707408773, 87187.31674483752, 41578.55526029053, 16199.680400692107, 75047.21689615837, 3278.34092516015, 88041.90534404037, 80096.06597964521, 78643.6087041432, 73371.60178164356, 12811.062989642407, 17458.21236500854, 92031.7239747588, 99142.57030893063, 72769.52353304873, 99279.18066336193, 70284.25429439318, 21146.661545121624, 46611.713350220365, 43780.49544819542, 92302.0830551504, 51105.20339174213, 82981.92146438404, 68010.48137610195, 59196.87344537432, 8589.46974147381, 75657.4765627347, 34703.51932009868, 5094.293186821664, 27645.602948991822, 32624.57827606945, 20862.816818256437, 8661.422728396983, 73051.71988426801, 51284.27396786967, 30598.314220989665, 17756.322534251532, 9683.380857102953, 41486.147019608885, 61578.92869940764, 15046.042467057187, 70506.59246356785, 42094.753783314874, 35293.29056561465, 99427.03514771519, 37384.16491545575, 91104.21156131568, 27803.278880238104, 75917.30616892377, 11536.048657287478, 9731.019710596689, 45049.30717558159, 5900.500414222898, 36868.51465596459, 65909.03789808108, 66764.77118560711, 13839.178337888246, 80128.72154689161, 7491.721892634062, 6676.943718790829, 57441.234193771896, 91787.0243286296, 56522.34686210717, 33387.78836942702, 84805.34378098356, 31681.674903913416, 51902.264098039974, 26322.327553800285]} +{"id": 1870, "vector": [92256.526098922, 89994.68020795305, 66205.75198823371, 31403.187684446886, 93113.36699057605, 89498.69972107261, 6374.860547516559, 54357.89407049445, 92140.06311220698, 5400.608336618695, 72590.53979978529, 7483.833200676892, 37101.613183021465, 19864.543678810885, 89911.74486620653, 72598.32044645642, 19977.182766163347, 88441.14710277194, 3400.523420061363, 39815.65900253026, 77883.09528877125, 74653.66550397228, 27467.75881460549, 45776.05514642165, 21297.435447531254, 75261.31789627431, 24371.260720412392, 61630.48934390953, 17789.443948869244, 57073.69184580472, 89559.76702233947, 21756.964921051967, 8051.343851339321, 30831.840668663168, 40355.385292639075, 55510.64911312116, 91421.28645231861, 71887.86531911793, 99646.89299840653, 50126.76541533816, 65788.65157337065, 44686.80778618562, 48910.696995206825, 59929.6313425556, 46437.06629430496, 44254.48774250269, 48657.20183260827, 56156.630527339556, 78065.85520720607, 62452.183238257065, 53410.82941006953, 48137.78089612897, 23404.52897991707, 88732.49600384278, 15434.635313018929, 50227.10112031713, 31111.262243121873, 81827.92541682921, 18758.50613620538, 4211.376040172876, 71104.60203038443, 11676.820385117047, 38370.81255073109, 97507.00223610565, 95191.13461380762, 60647.574562479334, 80260.12795778722, 28147.99968980176, 91824.99010493013, 39345.42650144417, 74004.52361502803, 15088.058161600504, 50506.27760399905, 58164.19807233096, 66929.63995053452, 33582.86684762236, 70842.41108569897, 86233.3221107257, 84402.41481969155, 3982.431257389718, 7709.541667533248, 73485.01567842148, 84554.20689515032, 27908.491846338246, 46954.46871205269, 35030.25387188653, 77759.86807343626, 46333.47305983703, 94483.22041634862, 92787.09517304812, 73912.89570381113, 58549.548073844526, 64063.684382077205, 98226.97327842811, 14675.591376461694, 64532.958030730304, 59340.11222552615, 5408.678948922218, 56203.15666282449, 13539.176515174035, 92587.46601031763, 88020.60650192696, 91535.04437313776, 64128.59135924536, 35058.27279273538, 92855.8045821042, 10286.006283279958, 72261.68562463892, 74823.26369854767, 18385.803002886238, 3025.2911263547453, 65202.0961832935, 7642.200619095507, 8113.680070491436, 76811.753190793, 93717.32438219071, 49380.017141357435, 72531.7308725551, 62159.043759428445, 85302.50144526041, 71827.40762633547, 59727.28913654906, 7428.843206498581, 84664.8999868937, 61417.96562282521, 88851.40222665627, 38660.0861686928, 68764.86950087837]} +{"id": 53, "vector": [46907.320379753444, 43048.92033621819, 77934.72326100018, 58724.06896399409, 53079.54512087821, 3407.2082314049344, 28852.910535034294, 93495.22190415049, 14015.335012530839, 87521.18697784208, 98155.95006321867, 14838.214402210591, 30790.815987151178, 49514.40291483378, 13938.2810430137, 85655.5239115404, 56762.87787058543, 79975.96982967207, 30409.500001608492, 46348.20746692228, 1094.82195838152, 99457.6858937902, 70190.97029753175, 72951.16609167037, 10295.693595143484, 12021.750424070276, 50540.162388934696, 14534.878254538864, 54267.052722169996, 28879.88888179003, 98639.71281420336, 46557.54141467887, 52573.94168384167, 10178.167087152679, 80640.46789290207, 40257.91539702409, 75845.35262441258, 19562.99398131164, 87323.72198656114, 48648.64984814202, 6552.223027473514, 15666.623846833316, 24238.087905009077, 15827.964260105564, 54651.952891142, 947.828167055298, 89886.248736894, 17110.077863668826, 69189.8605753158, 58221.715525366824, 72518.47918436307, 65889.3253345688, 53976.79615842981, 14000.957272056392, 71703.63096359673, 3229.869896847892, 86530.75029093721, 7994.57110434375, 45526.807071305855, 14147.206877796492, 54584.58619635307, 75644.12467689642, 59741.88842962231, 49826.0170146241, 31383.47339454789, 49820.02936666717, 69406.17461536416, 15103.515323518946, 33514.55311843158, 47056.9814135077, 98846.80475672775, 91077.38253320835, 29257.736248650846, 55678.9945290055, 51176.36938844328, 55796.75179579937, 75922.35318688529, 57612.24541300908, 93536.63443005741, 5623.348436072528, 8191.12240144535, 91906.11573532088, 36005.7401645448, 29760.002348778748, 2957.2860624803575, 8807.794152217752, 71879.66827327917, 30198.87658533338, 55410.18191906754, 44168.64549085102, 6988.349425191797, 67406.31594609248, 91928.79875458521, 13533.413622691636, 58073.2784641808, 45087.15646741057, 82489.64413270168, 6778.317996696182, 3270.622320911609, 88809.28404814914, 39620.142830277924, 51763.72282249765, 27146.99301097433, 21929.759864388478, 26298.509510741387, 10942.359002193725, 67525.28897991932, 73912.82790709773, 48525.501106693235, 54371.594398661095, 81155.7033777292, 72833.42568675322, 42938.378933454536, 25921.344697847802, 47844.65656652621, 68406.42752192386, 49061.28757903324, 32807.991780546785, 7572.22672247001, 12658.357016355216, 21516.07214324388, 66595.41548856038, 32963.63070142937, 56739.790296847605, 59505.607507983106, 63856.34684832785, 46438.43876157695, 40457.20098204203]} +{"id": 466, "vector": [88534.0094170058, 33098.851923615424, 57324.644627164445, 10222.629706595599, 69240.74408507692, 42935.31268121147, 40705.86779174077, 89943.46353779358, 1394.5811678274467, 79193.08136824348, 9131.047532606563, 16213.712458755235, 40338.87002576517, 91410.84531882043, 83027.42658590635, 49382.60892622958, 14369.600601417877, 40874.69085909803, 10529.606370970445, 11713.23233408692, 27746.03386869856, 21331.062266116263, 15969.519301579405, 61959.04888589266, 28132.558111350703, 12230.91776088363, 72998.83657522555, 18177.335136281315, 73554.95716831739, 28713.240121742532, 36316.19347870232, 80368.46840348245, 2715.7949363406988, 5730.8267532176105, 91665.98281992722, 4037.1607505437337, 85674.89079819123, 57894.067838247865, 49307.59415168422, 85873.0920545104, 85038.63030506339, 87181.63072812381, 53440.3385090788, 45560.08354364054, 32044.201325610044, 3545.387269613165, 96647.58798681223, 45757.650050729746, 93213.09742931096, 90820.96558636588, 73371.28855414913, 98477.49186673573, 87261.32811021812, 34982.73415435905, 64766.45216429884, 47494.713940158676, 49169.581314343566, 747.0837129981201, 49935.015678297365, 32313.322350317987, 80439.88566109519, 23382.886531379943, 22213.850402881173, 72631.53320106084, 67224.40426648043, 11085.763536073679, 76133.23620719233, 94811.4486088658, 97004.94808603499, 89851.56230306026, 5895.331509189472, 15100.124171843809, 826.5818199629016, 8087.977885301356, 60900.09723996761, 10738.963496950239, 20026.103167270936, 5858.9427749668575, 51057.24850852159, 85815.66982096374, 98776.52112569087, 81836.52782626856, 22260.447657726312, 76743.59267626569, 82727.75259359795, 41128.69820334606, 62780.19309513682, 76091.8548390743, 9211.359324962099, 76248.89720126275, 62925.074767785794, 65950.1176105935, 63404.471988557496, 76972.90816978866, 94688.57190704641, 69713.49096241269, 26693.130979016623, 1154.1163067459781, 71551.44955137395, 48434.647248093024, 5434.223200113098, 3684.839802180817, 23557.883316405947, 40482.04682371786, 25617.470940659583, 58444.42857738265, 82695.97042009664, 18811.98896715903, 60999.5686841331, 22973.5301609306, 52503.73829200238, 6198.064140327886, 26257.959617031178, 6903.152837401105, 94516.0379712763, 49802.002788187616, 42086.062200212735, 98872.1028801112, 76539.87077267224, 32816.01990693146, 60924.11996963514, 94669.94821602458, 58319.11622044006, 27832.568033728978, 15425.085104019887, 63437.2609985712, 53542.33667079923, 50804.3073331654]} +{"id": 1080, "vector": [59443.044251421474, 88891.76452223567, 63832.209328114055, 42223.80922171159, 35761.34999462096, 26674.60167202873, 78679.34489187413, 21285.726648780877, 59366.84266165469, 59075.59219000287, 11245.173524966012, 90271.98044654471, 46752.24906268868, 90582.00026127395, 68620.76196781089, 12607.162782669413, 62160.51993713878, 27711.302790063197, 40409.30770808196, 3336.4334592410282, 89895.1792042751, 36952.37784715947, 21874.519107966717, 57796.682414766896, 85721.3524517574, 21827.239500499607, 26082.082834048113, 19027.463533147704, 84318.7630550262, 92763.64646296206, 59366.629135940464, 20406.72173345689, 23931.407112470915, 38958.29045193086, 73591.8597674345, 65871.86775676603, 90089.30472443471, 68440.59591846692, 55098.8544552253, 78046.27886398196, 54888.47303156154, 87622.87858231128, 81286.76799528606, 35085.96649386503, 7467.240320403012, 63767.6032854338, 60097.60679083054, 73316.51878667467, 49035.20999576443, 40037.0422025158, 41930.89679863145, 21509.51834166198, 75792.08128420154, 47442.420489346136, 95810.36535613747, 93724.60800542562, 98415.520654735, 11868.01612309717, 44299.084378442436, 8667.62235977232, 95069.31925263345, 39651.10803963475, 60404.592709438584, 23234.18296335874, 55880.30137788483, 81883.31920811147, 59348.46475782172, 45525.74235714473, 19828.85040287987, 92655.10650050959, 85367.02766894473, 75891.5009674914, 13678.564934593318, 5584.314899005538, 85868.09618439469, 52070.13317278173, 82034.1870484742, 29082.232956342745, 91225.81114773366, 45341.38703394837, 44627.75661901469, 98786.92576087025, 31861.942817197243, 7820.256469483433, 86495.30904630247, 42817.26615794122, 12350.262721756344, 8514.32563121879, 46105.198910730796, 89922.73483798771, 23854.76579848499, 79700.91567885384, 60930.21141884316, 11892.290368413038, 6773.344142241266, 15892.923471209408, 97023.44152785702, 36219.98069356138, 32897.47463747994, 72635.06220219041, 33371.852846541326, 17504.901582062193, 86635.19554591706, 11051.063040597664, 78721.96000810416, 76141.84874758692, 79989.39553162518, 61806.62229744808, 88241.7032191519, 5534.481701486548, 66664.38991574723, 4972.94251494298, 62939.546556139605, 18676.08222107744, 83201.97256168547, 47782.541209914394, 26955.3122977649, 81030.12822420102, 55861.73316282645, 33793.860530098755, 94916.5694770368, 562.1123727439614, 80488.71816299499, 40204.32648833046, 30583.25998923991, 84113.90677580047, 98068.96498893964, 43176.723332021495]} +{"id": 2013, "vector": [92376.77571520864, 2228.5450903493875, 51483.96995049908, 73990.57613047108, 13685.78984932154, 8839.089498766629, 77104.40624740077, 77206.98996818098, 71441.38620422967, 80857.7250592934, 47646.93493920371, 58898.36192038148, 10003.154691019756, 19236.65878007528, 63954.27761568375, 29630.733944437714, 89558.30524471441, 78274.04607624629, 63649.36799507167, 97535.87317310637, 29874.27486687373, 64303.36176496929, 92322.47992132805, 87443.64331902155, 22409.342042854307, 25473.76478320965, 59105.18576867892, 16213.201469861826, 1461.5073184583882, 92205.09415040626, 73627.25103027027, 88117.0569920565, 57465.55942213473, 36227.37243872735, 41343.09569186444, 85532.50001686283, 24601.41181508777, 95194.01716454385, 44568.73354062717, 55885.87294260172, 94045.46051069694, 2040.6020372464307, 90072.56564334301, 76888.98311348495, 877.1102890203331, 57285.691749653764, 89282.50555371234, 42914.49631202084, 60730.518841238205, 72988.75963772558, 41481.328783436256, 91551.32636784112, 88662.38062451922, 25632.022387082863, 56246.098432615545, 9061.338174448496, 16786.95941024335, 91688.10669742638, 84873.39857735537, 24829.14369544188, 17820.203349297924, 79858.43556065005, 48926.653629904446, 88777.68510054637, 34141.97571625165, 85925.47222583473, 28448.26319317738, 65230.86186229108, 80481.98290015932, 11275.076940111296, 54713.466980918354, 65130.67916676994, 75233.6921618888, 27829.00562110786, 77082.23370937708, 11826.699653785545, 24447.372606172434, 54843.154455874275, 70914.68655873007, 81753.24717427332, 90264.56600578636, 69823.10498227927, 69502.67334838033, 28037.733146501163, 33961.12479485893, 77542.12822359269, 2693.6987137767596, 65741.3679854645, 39324.07898182207, 60386.91972648452, 26329.8609893896, 80458.74205677489, 90968.61516625634, 7006.815324566196, 95693.27231400598, 57079.9965701835, 99949.17582080254, 32551.00411315439, 6469.816277176455, 41201.176728554135, 69952.57597864838, 35637.31847621846, 94304.37746624167, 39781.85122335782, 8716.606716106267, 1999.2518133623794, 17889.90204309696, 86833.53832911, 48407.44059491271, 33234.44664161184, 50125.60023355082, 79449.01236315916, 11333.971074694016, 12006.467612032735, 81047.93156033417, 44451.654352153346, 22585.099520494045, 22081.017788999645, 74074.58175729748, 94617.94400636773, 43962.22086527737, 68335.69670449384, 11577.14896857477, 11075.612804118662, 27968.954639239906, 69281.97439698405, 57825.3670460714, 76331.59695670063]} +{"id": 676, "vector": [60372.51683621153, 48499.744992747204, 74603.90109728131, 96807.49976280554, 39477.066879303246, 91961.96472574712, 54873.56141593675, 69415.52268298375, 65855.31460604355, 65992.30738523559, 16395.255064506597, 8809.026837531375, 17726.699124892144, 26319.686892293616, 59525.91848481924, 33001.77038630745, 78725.61081649929, 6345.743359343559, 11902.353243837815, 32433.49436505999, 71406.58934594145, 62323.91444200094, 73266.2248208056, 89489.85161582849, 94530.66087294597, 7403.862919689008, 15513.103952648966, 60364.34202246087, 30110.79602053014, 63554.51339854614, 92015.41878452989, 68245.13186201679, 57980.79758056697, 92048.27947768319, 20598.172278374073, 17023.903074350656, 11514.847947080709, 45056.40721892456, 51335.68262353253, 45319.14422230392, 10641.419002415863, 67950.95949092775, 65046.93496704388, 47957.94693029694, 77196.82511518447, 13417.443323972899, 57114.40219411899, 8096.789083677447, 58106.73645127409, 56777.80318029767, 68776.43446761694, 79486.13022818523, 83647.41058526668, 63330.60235318531, 68566.76766076646, 16895.449487517933, 18049.875400462646, 53666.91154751143, 62756.54339590188, 51791.9902294928, 99867.5572678826, 66050.91547641913, 35171.52046641389, 6096.89931634162, 87279.99648946273, 82007.61988413216, 95198.65073498405, 32245.521275259147, 19439.623336244083, 13018.802779157013, 50878.21310624804, 63707.05093602548, 17400.732766677596, 95759.9955267546, 20981.482266045605, 3163.3975250905723, 54601.749522549784, 93433.72045273328, 36098.82674146594, 32590.436928026746, 4260.448956957286, 1687.8057053129503, 54396.4334153372, 51893.254218231, 30075.122618817208, 30074.00675517773, 23691.591123196755, 93865.14768649808, 57923.25388098941, 37644.41547388959, 4708.765263254433, 71954.09285791281, 21582.690907482494, 95107.8745715629, 94856.721415066, 821.757737453721, 60955.40815311251, 71415.24752439382, 73816.39590086309, 96225.71874322231, 69247.12961044173, 99070.73362266866, 58391.37794941639, 6982.261139868629, 15200.383061467493, 46636.01870400496, 85776.89088503072, 35736.476623026305, 63896.71099322179, 67536.98263138079, 27682.353340535014, 39138.76255444942, 55.8794774072946, 96334.6027262452, 13236.648534665896, 22341.34325928748, 37015.349954723075, 60792.80524480688, 87734.3436398505, 61829.00374206741, 24879.645411172045, 58240.781371977915, 29938.325552540802, 92026.22107232158, 91796.45110364031, 94027.12118731422, 47972.80699181885, 64097.86643288101]} +{"id": 1865, "vector": [82508.91077035024, 41924.170701741845, 42227.84320539338, 48160.13718185217, 6673.576107615509, 33916.2166357903, 59166.022881871264, 54862.21273960299, 45535.44538395647, 10854.732848522819, 90260.59309829577, 29041.2948368599, 5729.287982501685, 91926.32221768273, 49254.10958415592, 49909.825435219966, 15817.843298691792, 37929.351412574644, 34634.977455063934, 42185.0856972102, 69371.42324429851, 13004.23314763921, 85703.78075954993, 67192.86923794169, 88921.39046543796, 57264.77945065329, 18448.9352347877, 4829.671277222758, 99441.34167940005, 70911.9133100242, 44712.50766374597, 25781.243539491174, 18191.022911609965, 18900.59535430927, 36384.04388297744, 62994.69410852461, 88807.99470609394, 28482.672179193858, 22189.156944512633, 63262.65554221525, 9705.72624242848, 75857.18270540748, 77464.91123132987, 81341.27265002654, 46931.84321659768, 10602.874475079194, 73712.0454005444, 19253.63070000402, 65350.039063973745, 4105.504223194534, 99387.15265608412, 21630.837973041016, 42961.811443890816, 27929.661403163973, 80144.86112917107, 53423.683662782874, 41290.736150791054, 99788.85262618387, 75673.94085097122, 15159.121098119533, 89602.79523685858, 25275.190016716344, 29334.306592245706, 88474.23403542934, 86434.42029878103, 95505.42614063667, 84727.52553327456, 16843.17395435123, 92543.52415837295, 29470.130335880083, 81561.2130196068, 34063.074299565444, 18410.141747027985, 51283.98982683898, 49395.95488179007, 75450.8175191177, 80998.32323610023, 90870.01282196681, 1809.3821470755668, 75122.6608045184, 54658.49667425109, 15228.166192973991, 75113.25344650653, 18056.957767687654, 92815.02937164356, 97604.91391817163, 49629.4967567907, 64850.387634866456, 1934.9540846243297, 88038.71591142955, 21798.05084826054, 58619.444971077624, 67171.98889887409, 67742.63856981347, 98399.54489045196, 98568.46877465695, 43975.75621466272, 52967.67910129906, 13308.589294268158, 78659.52528373465, 40275.62663305927, 50831.637281646435, 14112.537724442896, 83.68004149068575, 75164.4228229243, 64847.87905970259, 72524.29532380404, 83027.03612648536, 96978.02555305071, 31328.458341567766, 98653.71336953792, 62949.384095535366, 48657.83878254939, 917.1788010261084, 47233.47066501733, 67565.46004300688, 9565.973753870783, 64888.792034167454, 68832.64087050979, 62448.869362529826, 40552.261749447796, 55242.94653931995, 30061.11623717843, 65708.6610423335, 44634.91626874642, 31327.97258806036, 98524.83966468163, 37638.71861478727]} +{"id": 881, "vector": [90651.1794279439, 59886.56718201067, 72162.12459480266, 94635.30745420193, 13531.676216677624, 47941.713985850365, 95141.09386274897, 60740.70291786139, 97709.59548472398, 53811.945988562635, 56823.736849532346, 51504.43195743721, 10345.255133717179, 99801.11234874037, 4790.821107501286, 12581.156523282421, 17797.768194235676, 29915.354052535447, 5522.057323733776, 74345.0087570369, 54506.63138010373, 5204.326868934717, 36898.22260199148, 30494.284101969537, 71307.40976218217, 83383.6438374897, 69358.28355852878, 52170.69289140645, 92594.94323777092, 80922.34423692753, 60197.87011538145, 85327.3905141977, 24973.7288364553, 6650.024903731333, 39346.67182201736, 16651.97500507545, 60420.019802611336, 78947.32049715305, 16214.672942694053, 77685.10347299438, 66417.76065671246, 18247.218222596395, 64104.442135878984, 84447.88825508868, 51304.02185275047, 47006.05214279099, 97912.11677740935, 94011.87407472891, 93389.11636194008, 98932.9331667245, 10945.89391551457, 85103.78614693975, 82689.56829070736, 6882.733456595913, 50013.75095652993, 59985.535388060496, 86604.35657872561, 26018.26567804144, 69492.36569549047, 18280.02479862455, 98047.98538310823, 18561.62858010998, 16888.382638656396, 15080.164141617259, 13559.765574253468, 63429.478358705135, 90311.09105084924, 35793.796128153976, 53660.95676281992, 87360.62508215362, 4498.185282509548, 53883.206551305964, 77695.80055875583, 16097.760047809796, 38988.464317533675, 58686.65534844794, 28841.121222311038, 1120.540434534656, 36681.03866542714, 86349.71887558403, 3529.5563038770906, 3656.771624466759, 34799.771981965656, 39613.37274616552, 79611.78625074266, 57839.632014133145, 32116.333201729154, 80199.72875233914, 88990.7711288239, 26406.878650703282, 22343.522008932083, 17361.02402763675, 4964.044280377, 20535.44118760805, 91242.2092334385, 26525.660393746475, 40828.66333783827, 12607.83710043264, 91991.77286109138, 75704.93913085053, 91030.84131323462, 37234.6919143298, 79984.36475721428, 70496.68636207313, 62281.45681452117, 20359.799992626682, 66202.90684238286, 21910.7022071275, 5152.204407686445, 15035.140249477974, 75786.14461240945, 49916.99067585272, 75914.76734542972, 20894.997477433753, 85641.8192941873, 23220.595908057294, 68267.86942182582, 77291.62212758965, 89821.22673859562, 7300.620597701945, 80357.18003988077, 6015.575228583037, 19146.072704573093, 89440.09056738875, 35140.32334312487, 51362.73171335356, 87268.99386164635, 11618.664197212813]} +{"id": 1094, "vector": [36165.417553008374, 23338.65917427792, 41720.31114653897, 51793.227183470844, 30348.527368244737, 78165.65531275146, 52365.258546865225, 21374.893945366235, 24442.68587458803, 42845.49608070255, 65209.80136176633, 88148.54839355222, 73859.99792946766, 96497.81028103469, 61352.888210797144, 84960.63502759626, 14428.078699941781, 25176.41226332664, 77372.08537312498, 16022.617641595805, 42517.27982522786, 62636.78865196941, 45826.6137910475, 2608.9472964258343, 9660.490645028296, 42830.33709627695, 71034.68314634249, 63721.60154350387, 62923.747493877876, 61042.14794560298, 69178.97550035316, 12266.164174805594, 16490.439858987804, 92599.1591685722, 18865.699102604616, 41567.995338815876, 39810.134212869045, 49786.60217561762, 83879.45559187759, 4523.921292787525, 52220.24747650116, 36926.56933848264, 5820.5280399744815, 28209.240371172516, 83099.07577750405, 11176.700358856651, 7804.824914339026, 32585.16485988485, 98002.25157610532, 43444.31828093832, 80401.70564706447, 15348.555296651111, 61945.782017774756, 15100.887415703834, 95328.05317155756, 21430.93816598225, 19639.62355242277, 55330.202527399284, 74015.66995082279, 60268.51316196912, 98370.1186774398, 15313.924699932324, 95128.63057278428, 96668.04130803034, 78211.52298874504, 29641.913370212304, 88646.31656259745, 39928.08462517134, 12386.18739357452, 26990.949375347296, 40589.996968363164, 75201.14330316766, 77145.30374873382, 74582.65103836328, 85619.39152177706, 62442.82668652503, 5348.426693050113, 58492.50238986377, 37673.75784810292, 91785.60244326823, 37738.66809488048, 66476.18227632195, 89191.84370790355, 53048.21459538869, 65797.30395186233, 60603.08256738835, 99293.75397185724, 5602.547148743109, 48494.3367584783, 73508.87722466962, 88243.87120424374, 32441.83047275472, 80674.46070388278, 58634.117452418854, 75886.16835416104, 75911.02274350704, 61698.18771613661, 18407.12522715392, 41292.2273471861, 49802.587086893545, 46374.96753097019, 44065.31444734567, 90452.01008974982, 76593.49824130883, 73860.02212282721, 85137.10396345008, 68568.07437959684, 75025.93071262771, 25826.198996793824, 39955.086168242626, 58008.38921518211, 80520.8618076431, 77067.91808046444, 10562.271892017017, 10387.732052309317, 99879.20697731692, 50120.21244416167, 16458.327110273884, 62290.08062804327, 18304.256178876643, 22534.251325540754, 50624.47118274166, 17122.290667365745, 1718.981261910546, 7284.724262304698, 19621.712009269544, 93856.65016888893, 65093.182981668055]} +{"id": 1381, "vector": [34540.58446455013, 90314.55130161723, 5149.480828791053, 41798.55998876221, 96421.32511545747, 59408.63144719738, 34172.09732417544, 72006.65858318341, 70906.72097420448, 68987.41557073212, 89366.86871312333, 27271.692967600946, 55050.48855852443, 1278.7197392543414, 248.20039349436885, 32105.02148857779, 56467.401468890224, 83214.67852820741, 74252.98982036694, 68558.14588055016, 77949.47697115534, 32773.08499088461, 27940.611137461903, 46989.70654408994, 98545.93512039098, 33244.000168952705, 89506.38339735084, 45568.55253518416, 57843.90875102201, 10080.552151887456, 53078.174276654565, 36728.37636848345, 39456.38157488302, 87064.10811901624, 9458.015886124904, 30017.67624438144, 61159.486927076425, 46237.46270680792, 98004.41249184511, 61769.62453210391, 15715.59918019022, 56531.04452386335, 9689.346180548553, 87024.39616550585, 25590.17954544942, 71034.19209930523, 5126.294399256381, 77352.76403027613, 40278.74324481338, 40649.29895171856, 56036.136283219275, 30147.557732022902, 85182.14170492614, 48030.36068094731, 71079.53813122363, 26542.012829721873, 25769.636696380083, 12685.671943639243, 84634.1062265205, 49932.42494666272, 51394.587445517456, 60792.648463623336, 92906.88914727568, 17089.89581320456, 59251.659567319395, 488.09040518047465, 8660.3191038235, 50432.94021401586, 21968.436666555903, 54814.49710541425, 95146.86017167877, 58957.99838425919, 11861.284768113212, 54564.217671503975, 49922.955477722164, 34060.26270844696, 18792.728324838947, 48984.02553831848, 46951.03767061021, 95449.16554360298, 60323.30117068658, 99899.23362671462, 12429.003081612866, 42766.55907744056, 33293.074785906705, 51936.166995199565, 97485.24609665717, 46043.882106317425, 25421.85267315019, 75153.24810013216, 96029.91725154857, 66811.255777723, 29460.741904296163, 49348.61666828749, 61233.70980951699, 83653.03145554704, 91713.2152732406, 23488.416736620875, 3480.7069017206095, 25568.149565106934, 50513.920883665494, 90340.56456610562, 4078.226368000015, 17840.861341059765, 20797.05384361935, 41968.74713621087, 67011.06727944475, 92915.13759432813, 25489.60005694113, 51629.55521282071, 45088.003474833175, 18822.38443788091, 25225.15331217111, 15843.388508719747, 1507.756918313319, 29453.57065514591, 68194.41566443697, 70145.65063606927, 95070.85470360883, 75417.47573699424, 48625.068939578916, 10192.966663055302, 86950.51941851378, 35033.0830086155, 24812.899124641473, 32647.09768890477, 8843.139045233029, 68464.30714097763]} +{"id": 153, "vector": [89342.78815473629, 22189.077244344637, 23786.90308011786, 25577.409412543373, 45825.28944117742, 37110.42636176138, 50799.17874465229, 98509.93559830342, 67474.02728889564, 78689.68426396283, 4084.084907304597, 5754.842090146583, 58265.76872711958, 81661.98567842689, 40566.17018638501, 69384.12456490028, 84634.16863212328, 87280.76541656653, 10317.965676326536, 41895.21259172105, 75864.95066893351, 3852.7998102097617, 26013.208439570346, 50648.09430546071, 83979.77793129919, 59821.19729262295, 91340.49254982478, 58126.57258520704, 42912.41971359529, 553.9187679179047, 12976.734751377073, 55807.438221560566, 31278.689695517205, 68223.00813274067, 99381.70867428501, 95223.52660888921, 69873.26527475826, 47526.63546156679, 85009.50934264738, 89056.18893193755, 7806.047644726122, 80200.11571082914, 26029.543456070893, 81047.72913215072, 60332.77060224702, 36156.65702371549, 36988.10199644793, 27944.262161520495, 82415.55313010984, 95219.6996476425, 93432.7623234111, 61502.327872924914, 87303.33843155032, 11300.832411260842, 22284.76648389992, 77039.43462430866, 26184.822024029952, 63671.49178488064, 55905.716578180334, 67053.0930197408, 63287.24932601495, 89476.99116188615, 10629.034045455743, 4056.914359487851, 37778.17435345907, 43150.154960376276, 92920.67290339628, 85738.02778837482, 97347.87161984987, 77435.44363260559, 2713.833015758138, 21076.040246338245, 10450.467177896739, 25783.53781123075, 49688.04771148455, 7112.891192149351, 43226.88151232513, 33370.06072476673, 97666.2801997189, 48162.900481145385, 77633.23420999666, 18560.443026741734, 71543.71892176823, 51025.17757289118, 62911.03200968634, 26223.6208233655, 89219.20083915582, 66272.30545675458, 50147.40269095066, 15353.205270028458, 13531.061571544622, 74716.48396483478, 17285.05886613819, 8758.395012054532, 67845.98124108555, 7639.267022753993, 69403.51394238068, 98247.15857326276, 32997.097519036215, 85274.85317648368, 9827.372106029563, 57294.53714505211, 39785.02003263137, 29343.835084153558, 65530.62618632699, 27417.1105345296, 88176.11207974602, 97721.2039360138, 62520.54792603677, 74227.41188224094, 77497.70959293857, 41074.14554711384, 32656.990388803464, 32107.793381996096, 30363.20252602962, 48467.12033488981, 5761.270295191556, 90294.22345720309, 44033.29740405236, 1348.5425121545825, 32325.82608142056, 45194.14286169777, 19430.170190145946, 45107.06368823908, 88003.2043218438, 77030.3760456422, 79310.79628315268, 17752.66534751837]} +{"id": 1374, "vector": [82674.12862999088, 19598.683773776458, 22696.79869388256, 26264.862616395378, 14457.285859606107, 99347.29448510274, 81970.18212261348, 28955.77953359636, 55723.66345979053, 48661.29327821171, 52877.44442140021, 37460.372412590346, 14118.95259386079, 29239.24705420008, 17210.440543657, 22908.38938574563, 85146.35298318382, 23971.793935584872, 27138.281324443724, 19695.73077242517, 95258.07019768721, 99485.25262824987, 8310.159571232556, 12864.601889534033, 73383.61312663117, 27037.608228900477, 54544.91290077449, 67861.95480976977, 31389.680915456753, 19211.609509196092, 31486.811922387904, 49293.8205092637, 85661.94522749762, 71771.05871692994, 50441.39451395682, 35213.86747452314, 45353.20665971382, 1857.9356540799897, 13515.804652542207, 4752.954080752847, 2043.830829121507, 53585.16822979755, 79510.59822850752, 25137.678490146176, 88812.92640510872, 58334.067443534695, 34748.003180991385, 10554.051815061937, 11271.164563570092, 94002.48406249945, 4880.099371534752, 48027.93560061168, 93531.15772563925, 79128.64583058712, 31043.327814810295, 21690.71488521962, 40429.079627640305, 4433.549327317787, 53190.97916005676, 90373.03430355886, 85792.30784428406, 14061.940110094773, 50680.85181900829, 82074.94192829444, 12983.245669906184, 42834.438557346046, 52323.39000481443, 6507.513132964505, 46554.586462076506, 94848.13340065544, 78369.04567007223, 37264.61921922635, 43068.28142736425, 96860.0608736224, 43725.355587760176, 24837.72965609592, 52098.83320812053, 97782.67269479275, 33533.927877552705, 72335.12986427578, 24002.660784027386, 59063.06927824906, 19603.32262725376, 43916.3781527153, 34440.72728067743, 15061.950566586669, 71287.84731847115, 26575.04025920647, 74993.89738595534, 75234.26800307326, 67161.16724234643, 5323.509871829835, 48530.671899343084, 63616.70206872715, 98080.48379532108, 55498.23439418234, 47722.40291904367, 19079.376289360385, 95510.68012151957, 33670.20598999576, 78706.45470143197, 88137.22204161795, 22516.21638899255, 26479.83524046744, 1736.3503726161578, 6467.427805433234, 27449.393936442568, 73161.22028725935, 97107.43302926235, 19810.675118520583, 40520.61962706899, 65728.40262401421, 15656.419255806099, 35964.065799502474, 4679.3758408352205, 23182.215964807674, 69968.5330456628, 36256.044219023206, 27912.428454542827, 9991.244611068994, 67527.54698885615, 15023.429178793956, 48092.979108830936, 60213.239798810944, 87650.02378467041, 84847.30968573851, 38894.85991557973, 17203.854356554704]} +{"id": 289, "vector": [21748.821587921662, 38121.85695892581, 94413.14107165307, 62079.880791285424, 80183.55799256127, 26299.847470031345, 20278.836847989533, 7602.347134930609, 92781.28038259564, 13213.404877414747, 45977.939176102576, 24490.428505122076, 36093.94872922791, 49003.41500956078, 5399.383483586628, 39789.15737621134, 70262.71411010851, 64679.977631337635, 46765.64147269372, 80335.05343190863, 85872.38529647792, 46980.86753585647, 26330.946555229206, 23588.646828422134, 65758.41431157668, 80743.37850021828, 18071.1037783582, 35044.561718653174, 43996.77337015875, 84010.12474014396, 64993.44809900584, 99378.46081487516, 31624.042226580874, 38672.49655669153, 28832.299416238948, 77866.96389280431, 35103.40698700932, 40115.75206436133, 27691.588405333812, 58499.02230695865, 12723.391447562415, 2952.396236209387, 64071.9420799707, 76030.41515443797, 2859.27109010905, 43027.383469426604, 22654.98477909692, 27610.54035373538, 4642.856449839361, 44140.95365261753, 89527.93806812025, 22233.59604461035, 20103.709668874835, 49215.589480978946, 21188.4994047559, 206.12239053833247, 23792.024080297735, 74619.2150774025, 29508.194351679096, 39903.63447358955, 48190.623271746, 45235.40256904234, 31626.220127452452, 63044.84860354451, 81711.39935670097, 70449.51844258531, 51539.76253062172, 13592.740980714723, 36874.81094354512, 21871.643981621648, 90028.02164388895, 12842.739980332462, 21501.05514878896, 64666.246806489755, 84728.25332660621, 76245.04225336907, 63834.20592414363, 52301.46102206595, 12597.046174022842, 774.1015719353705, 43918.95584938183, 31072.84553435187, 60365.312713744235, 20809.008126896177, 88221.52965240573, 60690.38635790584, 68017.770077289, 52843.75441772191, 43178.81181648664, 79973.43393257489, 7331.488498315252, 3888.5880704474407, 83459.88133826718, 97035.50739991266, 16092.548921872973, 63778.13674205336, 79329.03912198191, 36051.50692959338, 55039.575930058294, 60216.93844022641, 11267.094346678241, 34621.15370763451, 87692.84236276474, 47635.809416147466, 55218.39522745834, 66635.25969972221, 4721.337486322541, 54661.867782529516, 49299.859250683134, 83924.7238860449, 99820.61205893777, 69387.53722642992, 67142.56540727918, 74262.04275948554, 88070.38724284818, 55661.83741844457, 30502.37111191162, 29211.055455689795, 32020.19232454547, 22021.11457085809, 17083.443105293038, 97561.92728592953, 7595.28715076665, 76247.93389456898, 16584.339299685624, 7123.112890096306, 56313.61036280891, 53082.40020878269]} +{"id": 1202, "vector": [6654.572462365049, 84223.14331402579, 91411.42379669403, 80313.32218904325, 8466.591390879408, 96028.00691609512, 70937.14695290802, 4418.53293942992, 89384.06813260555, 14653.338742383938, 95432.92649602589, 56858.05780145662, 59358.56957380278, 39612.16095255763, 8195.543688513884, 79534.40006381545, 24002.892770252405, 38560.66767104292, 14802.771689297877, 26160.92110560736, 75384.8449721585, 9707.166774785337, 67416.41212305895, 82299.52779554923, 45481.88981609949, 20711.375710567016, 79775.6417513251, 33184.67897203833, 8556.29339532935, 44421.69413653567, 26392.67401412898, 7820.982549508782, 50940.320883651235, 74519.20143813416, 87991.84153115014, 13175.317236744355, 88647.3639239627, 63440.273236633846, 71879.74309412952, 62362.87934789987, 82684.98111071344, 37976.32802016222, 64339.38587248588, 72538.58683248953, 50357.02191591759, 19650.822222790088, 18834.284079265228, 85695.68595879174, 89215.1249634958, 36023.40303609964, 45516.18646991997, 75053.83381806868, 76349.49608060483, 87032.11870375353, 39174.16214203481, 46994.99825317336, 84204.11397281896, 96947.21353151237, 8847.601025330909, 43452.82638096666, 47122.99246517219, 56741.10378411847, 77995.34228003061, 41809.54680301686, 51223.69536568766, 16079.803265287073, 76487.93963934401, 71304.85059285232, 95598.3975369031, 42924.883447212014, 23446.643093504994, 86937.73158013706, 12825.941954421372, 38241.5906712515, 59461.09065271198, 78068.15282273291, 63435.0104572463, 40258.09726465071, 32610.03300031462, 10831.159283480996, 83949.92408343805, 8471.34600687769, 7780.118640003541, 25486.88565297641, 99630.16719708683, 24568.744588330705, 80550.40318772315, 54176.660885551086, 71301.86985829663, 46395.611635544934, 75755.05139239905, 36522.20234132982, 45996.30737504956, 42658.50560340719, 47240.826136813004, 22251.508613192218, 13643.890703888595, 30583.997235615567, 95029.34930667683, 6844.620967978188, 4813.887421765406, 93826.73773741994, 45473.31514698334, 56604.84047069453, 84538.62159057164, 58726.939532428456, 95838.27943409035, 7794.324898151294, 41003.44773475649, 88938.54737995607, 51157.71088552464, 15685.905849497862, 132.5581582239388, 78798.32202539775, 10426.92794643203, 12943.826702212702, 76935.40537119198, 35063.55925948341, 27023.863603475773, 31432.234661618586, 91780.34662016679, 50081.36565751805, 12826.838757055359, 38546.32006928042, 90853.34961852842, 86865.39948258911, 43871.4685645434, 63917.61499768481]} +{"id": 197, "vector": [2612.5277105782607, 9081.603665405148, 1525.2819088772185, 13472.135513263294, 33993.026711238694, 72326.23959864251, 13966.546290059878, 89345.4997421195, 82615.35967648437, 91896.73423735377, 34641.15180554141, 35694.58087567721, 52647.916162213136, 65246.951815610555, 77951.91446148242, 45312.915175610855, 82758.03684092737, 14126.48120316854, 77314.75258267313, 73117.10152702082, 59272.768449396084, 7192.970539383093, 28539.027924737893, 62906.269112231814, 26568.507225976744, 14389.972950600271, 89066.91473273502, 43068.282174889515, 43683.99899046614, 86916.57534786925, 39075.89023916885, 50330.45674421153, 8445.006258734411, 32215.86564660398, 52311.94953809509, 76579.8085657723, 83998.97418549402, 80066.99070352242, 63694.26735130591, 5284.622038014241, 59182.507322964186, 76396.14582592018, 39107.747810520734, 67088.23340544858, 52641.2263427691, 23524.354354692732, 20490.770349133614, 23728.245726429686, 93299.48420382269, 71417.0143298715, 11246.482086756549, 59816.116671723576, 75194.6407651806, 2642.2681232782243, 4957.339098394209, 64822.13146093866, 20138.11326217497, 2170.834280038969, 89570.6606824598, 71112.5065586469, 15967.951137662272, 21028.41592943523, 79992.26613924894, 27060.15077215126, 72360.29113305194, 39769.597352222256, 12639.755936317688, 92217.85349595148, 81205.34347354175, 70123.08014829217, 8555.532734158443, 75540.88377644774, 5329.999156668608, 73272.95760946005, 92492.25354012591, 89650.19848842591, 11155.601122695301, 85200.57624040509, 56435.55020747394, 81440.40664126947, 42422.9678745933, 97231.55324364472, 9038.069492302047, 42727.846317132986, 12308.916398913561, 1164.6987181517688, 1452.9004987607896, 21627.631763608944, 96506.6622275635, 39122.049138401206, 82273.93859220312, 84087.77331324272, 97959.26823732922, 22018.104042752595, 69443.97880274396, 86126.59175586553, 43056.7013385649, 39378.78277576366, 80953.42943314355, 62732.15848710965, 23128.026248843624, 6362.828628201278, 27393.5816211054, 82303.11255569904, 1070.9581530908063, 88606.95271599223, 42732.889572755994, 12486.288434566017, 89210.37948927714, 69305.2359192592, 83834.15924119124, 44435.32534250216, 45253.06005939904, 26480.441781933383, 87904.96720371585, 23975.919821637646, 48842.910987784795, 87610.11211930026, 10481.419821983496, 21703.3219098725, 64505.84982907753, 50147.89346726942, 24590.20129945172, 78002.79085812328, 30026.672496712235, 31819.794522151413, 54.9734556280046, 19782.794497820032]} +{"id": 1362, "vector": [77426.5916734358, 60308.392610345814, 55931.3367241696, 37615.044516951544, 62414.61281613165, 83398.53879346717, 60663.31699908375, 9002.19107578364, 93503.00927798086, 18246.874987556737, 94414.96458360254, 30293.693758408957, 32215.362585062634, 52354.02793613253, 79784.88630475603, 7666.320914433533, 93783.30739124413, 83597.00982139657, 82825.32714349334, 68279.66443382246, 55234.77903659526, 8257.861894197804, 70776.63789885821, 55959.04539119495, 96314.54181104481, 31992.00999352768, 56764.31583113554, 44969.23332704704, 60316.07340583891, 65384.74577188572, 54965.90736535795, 85755.67104528355, 31933.5269851309, 48887.91432142465, 29799.717081095332, 87707.0441078436, 32320.11721227216, 45252.04926953419, 27119.90704211158, 46333.566490159115, 47884.96064952451, 58674.04535481344, 1521.3655477661403, 36468.021678062316, 97267.68601767704, 12298.508797875362, 99469.80751018667, 82988.81283415633, 80179.70159193501, 88138.49350622586, 33497.3574899579, 7351.560741973406, 98659.08192998405, 15439.981244961298, 56004.95193903649, 16381.036211646582, 24674.2652064074, 70701.92100588232, 63189.35302905257, 18654.03450046492, 56467.39754536099, 16473.813919058623, 12633.497844408814, 6147.038985063913, 50974.8748800029, 99052.31814802223, 21192.71144226761, 7401.232694242721, 576.236218839854, 84916.02625637146, 82705.87304693424, 80995.72809798401, 51515.15324472988, 2144.3196065145667, 29983.43868481046, 84143.90316902369, 47757.01485153847, 77504.68097619526, 91701.8221043175, 99323.20424369168, 37373.16898241082, 90174.88490779426, 56819.01874860096, 71616.30273296779, 49342.85407420362, 31279.86990099446, 77264.07153383028, 99506.71918727312, 4560.183616642388, 61242.55090253746, 51241.92281713542, 41323.19854684222, 58650.47650667874, 69528.78686365829, 2907.045851944334, 345.32822262666184, 1119.5637961601835, 55498.141722382854, 97804.06622884348, 69969.78397806184, 85935.26561827736, 71570.72518091153, 53325.05607629171, 41433.28885369917, 21687.736297781634, 81474.36304153048, 73756.01267925483, 55856.55512114893, 43322.956184758885, 60698.06441231273, 37394.07566389404, 14512.547739394311, 10852.858618318029, 12160.645648718626, 55028.67602112399, 48177.830356991144, 28630.378728733853, 28203.539485372963, 63805.08295222415, 67305.92499881721, 12354.010641353918, 10261.74380356607, 94445.22391615863, 61184.68476160459, 97270.41676357335, 82364.52840189393, 85636.97594733346, 16433.095969235645]} +{"id": 214, "vector": [83947.31504250855, 54636.65808107774, 94825.99082561942, 22683.80408499765, 53701.71917572002, 79824.70004659818, 10907.422166180137, 81816.45949948762, 45236.67713427868, 69153.2295514394, 57134.894648646, 41943.05623903449, 45608.86804252503, 72331.7506722726, 1056.7781437469614, 82199.06846270649, 91284.14218021407, 67682.17134464893, 10577.332589774236, 76899.50166827302, 15676.623206936036, 78076.77211440179, 4252.311407520293, 79457.1421903114, 12440.295608823915, 6994.760800038913, 96285.67956600933, 45151.71975986102, 68549.5413457788, 7006.04717967972, 92867.53831450429, 69862.90503288877, 749.4999493604437, 59584.39865282934, 15615.205793578834, 50428.27488814645, 86847.90927364418, 41718.94381964876, 15786.145330315727, 69339.9325142037, 26222.637996500885, 97605.92731119896, 80861.05509556748, 76984.10002984904, 81966.1812242974, 47236.37963898582, 31318.474047001167, 91055.21660166013, 79500.55615325311, 8329.133143977797, 33977.20958999288, 30865.931371500334, 14517.944977721465, 97297.61447782871, 34489.71280041596, 86421.0512627555, 11809.57983699481, 46950.5435160002, 85769.18504903327, 78992.60009422744, 33689.34547425863, 37923.90744043972, 37158.37958143389, 69094.52397355533, 63109.34524945381, 95148.50549705976, 43930.82965354666, 78352.09352216283, 96336.46581638536, 68875.33065634906, 47073.88975938771, 46662.075748405274, 62104.7911026497, 94969.12629586118, 16079.150443386314, 80076.43760304392, 18863.49618406785, 68106.63499633563, 91879.08723417146, 50469.474605039475, 54840.159799945264, 56798.790161899815, 33305.34903050959, 5433.791287212275, 87872.64067576359, 39093.05318881625, 77726.13970274338, 47474.69757712822, 42174.927676338535, 11845.887149305001, 41853.41730736033, 31220.586926425287, 80487.78911780458, 85366.41074682916, 83130.39527772725, 60659.57365632804, 52339.33852411764, 96339.96909456381, 80971.631757217, 67408.36966558515, 88263.38959430273, 86055.11491743906, 32777.89080894071, 5936.866787690132, 18842.40961371927, 86025.39809064496, 46456.748403191785, 47620.460108248575, 31811.4964963123, 87484.11824017957, 98281.90132638435, 12428.923667994562, 40325.4455008123, 51853.78227696579, 59507.672772519385, 57804.29174967833, 31508.92774946431, 26058.19185117736, 58726.16794522464, 69248.46493347207, 72462.08967716989, 43335.504965388835, 68581.55147827967, 43522.2415997747, 96619.09145334936, 92839.29049291577, 21293.67083252909, 94453.77514969227]} +{"id": 1949, "vector": [85748.51128224848, 90675.97403476217, 73998.40365428987, 59318.98056438102, 94698.81605485537, 13069.304810042182, 16037.508679482526, 48767.2363088875, 70680.1264824477, 48249.23767341572, 22663.58286160346, 49907.020471854004, 71372.87680335736, 33429.51203253945, 92385.40979819404, 41864.300903261865, 93224.81772609816, 93089.67513533881, 71034.17632291753, 51603.43640772063, 42305.04856166557, 70171.5119600044, 376.2463653932957, 68886.49045591342, 52364.29408099897, 16029.785147322873, 33882.42714393736, 97367.64010339843, 51515.43210594343, 42193.07246187012, 87122.42722067567, 96486.78530093278, 73525.08122756811, 46262.580512800945, 86541.60648930163, 64176.02244584578, 49953.45435254409, 75095.1836157075, 85406.24546609123, 45082.02811047068, 2833.8597005153733, 37452.62210704885, 27586.31672267029, 3537.542689602613, 23669.577863371393, 63957.90907537, 78776.60861677917, 33638.08436393439, 63667.068801209825, 92600.44498029216, 85329.33915306286, 58100.69589418114, 31649.824285250637, 58910.84155529637, 58903.90159435676, 72868.38954407965, 79241.95615732021, 27307.561774292342, 14900.587781133034, 17124.63817293537, 75531.76070969664, 49861.372355315005, 10841.9114346887, 3832.136987684631, 35166.00968192099, 20883.833024490195, 54170.2207372542, 88302.95925460546, 47996.79066400235, 55406.95426897995, 13017.211960353336, 56428.04122133231, 60324.52823425567, 31726.119081538618, 51813.96747009062, 66828.52593585213, 32864.36895680784, 46331.94382437675, 8237.727486798163, 92498.95627406637, 20742.494738826168, 78319.31068925519, 73661.99642715773, 89680.38373741803, 26641.494675828937, 71881.86779300816, 61286.33708173414, 56394.81820370027, 65643.45077061835, 36770.20148853559, 73454.38308264785, 34609.756884887676, 77975.78876623801, 10229.407647530086, 77400.98483034664, 59716.440641483074, 67327.49471075133, 66132.28783114889, 27654.187028570854, 69649.76504719806, 36864.281990036885, 97845.52501860689, 45210.58618900954, 32421.712737954487, 87212.23422202388, 54315.99634832735, 32598.866975011042, 31917.02395562166, 86765.04133373934, 34760.178766600715, 47002.89870737713, 53166.27997053917, 68796.22480395297, 16687.398372155683, 64134.48613482049, 11493.947890724454, 43446.628498885075, 55673.98569629518, 12681.341618533215, 66491.45810970468, 66511.44766696771, 39075.779519762575, 95975.90122330433, 69937.35872529229, 90648.6074638249, 21878.4014431196, 6171.570309519902, 14437.42819111593]} +{"id": 1493, "vector": [184.4889405484107, 74910.18132903347, 52186.18963990454, 69108.31519310686, 19374.4578606975, 14081.637918720136, 10808.415217220458, 32615.042027293563, 94790.32678023094, 39531.59584824144, 29634.65374920532, 9085.606798639523, 93280.84849446025, 17688.420993950116, 18418.13845337804, 65884.02854287802, 67409.92775263933, 20793.496099706845, 91134.64901056858, 18432.908296752827, 37607.69894516411, 77318.41991755065, 28874.687881208207, 20327.675980656913, 93082.79545677284, 70812.35494697408, 75133.95290997034, 15954.501021586499, 76305.71369228582, 46889.968246784454, 3790.7611439715215, 8655.752639698965, 75759.04165401045, 83030.23854824378, 88397.29105859762, 69303.62432624897, 41216.47088681779, 66154.43982658589, 7025.93745294976, 4708.299124668791, 37549.209806981366, 273.24559685933946, 75597.56248073629, 64544.75493262276, 94864.72196479881, 48399.63098129125, 72136.12041515854, 71826.763717098, 83581.17317327605, 34513.890990828724, 55019.25433252472, 32003.72586531891, 50873.83033300959, 61539.8317203393, 53579.78694474576, 60823.96090411464, 64124.985890784155, 74778.44689475947, 84261.45352204806, 85748.75230101938, 21559.511794635968, 64350.37313215449, 87273.16000408506, 64675.49237371566, 4414.205399076898, 99332.40341207312, 68506.77990912371, 40945.88623674352, 3872.62278311189, 34877.5299899691, 87750.10411827575, 7380.201846239498, 55403.44396639223, 804.5182402447448, 33060.471541762774, 40542.608407313506, 97370.38531646195, 8084.560392294693, 86796.3415003003, 79987.65960311679, 9905.254315513012, 5086.338617321906, 46197.31423066108, 31395.741858641635, 35635.06885262052, 97137.72772131172, 37589.6843411209, 14759.643937088174, 64500.986066229416, 7864.146292158413, 28983.819043335312, 16437.034079782876, 23358.071646716784, 42036.97864853576, 68800.59500429929, 4610.907101849204, 88213.24777665264, 16582.508670469786, 16722.428737787097, 68375.9923038123, 67210.72609700216, 61738.68169743708, 91950.63461202498, 67486.66152301978, 79443.9712859782, 23218.71180434445, 73802.19965353428, 75364.06111201663, 87031.04161032337, 58420.2947568256, 58276.26428123468, 96724.1388962548, 10654.280427369711, 70551.08444823982, 30646.38571049464, 32948.34093335014, 75248.7742311432, 43101.17451988149, 95479.70220072371, 72914.52028782143, 82954.09005048408, 54067.94205118066, 14153.553989791733, 6258.394315301141, 13770.380452529884, 82012.81278410707, 42757.72402478093, 21390.174491322156]} +{"id": 1721, "vector": [114.85942996855015, 13613.28326052793, 95693.23951888103, 83996.52927035581, 90145.86943435397, 16515.531964142505, 51690.62509267591, 16226.211346908192, 48288.64582940856, 91482.49508395653, 55919.77281586046, 97580.25560150863, 15876.835970765445, 91009.1267823584, 78337.99412602116, 73680.58082200249, 6284.142796826886, 20163.174285048724, 56379.94762278087, 32015.615059713764, 59388.37540590351, 78120.11228387001, 7307.724631513634, 13687.822525150239, 83747.82766051631, 48984.28053201453, 34610.15321866312, 39275.04738481955, 41847.88138627499, 69997.60440277826, 36583.9439970707, 81564.34400002622, 55211.862613637495, 76268.74462829434, 63134.57458703746, 48405.911441259734, 55916.19429112319, 8376.506501822023, 74478.3193225106, 96854.74639392261, 22693.313074945985, 58262.38004971485, 40846.322241777445, 14323.163281182284, 49884.97519136378, 86842.93797274529, 10437.048932394511, 47315.20637873878, 5214.777247655067, 92379.35166823867, 80591.90351601412, 54113.91866231607, 99925.47948505102, 25732.771880841377, 91074.3703871216, 79840.4087972133, 21603.658464070984, 3952.425082658406, 77806.80372265408, 44462.72426326476, 43224.92724000908, 14917.144844447117, 73224.28584059347, 71795.62110665462, 69500.95030891628, 87907.09055259834, 29255.92824146025, 3087.4566979566366, 62325.539868064385, 88891.40043445217, 63557.600673096924, 23251.82418610413, 88035.24762534648, 97334.43934832142, 91263.17029434159, 99556.762243612, 94982.38386893005, 42310.90217691588, 46517.69371316271, 66126.11432927867, 85992.21672304523, 59763.049492414975, 41572.091174708694, 51528.70121896539, 81740.27080343125, 63538.43841997707, 10304.170023486526, 46555.39265897418, 51270.86013223286, 67410.57510460881, 32765.059687318397, 27266.232052670002, 10567.529186356584, 15608.775102129635, 77138.8660356331, 25061.429241964637, 94087.38219903636, 29862.27080896354, 89078.12291095054, 20130.299261652784, 58692.11043042794, 93033.50290144839, 62221.58219359276, 12250.586070781821, 23667.519882113218, 985.9179124669826, 28381.568636151333, 91287.37232162013, 63454.06251116918, 11912.599063105945, 78173.84540482165, 32605.62829642003, 8284.658139121071, 12778.248135394577, 44984.81236464104, 70819.26051736713, 71618.07726417722, 33961.435358598246, 40951.34349508183, 18222.131421868216, 26449.74545937673, 35820.25644828756, 72557.9348889381, 85160.12981549073, 13537.236869394153, 23652.73181081592, 89513.90068596056, 78492.43686519434]} +{"id": 1692, "vector": [63569.89437316573, 20026.199298137402, 77175.40804545248, 70404.82590934186, 74586.53456591879, 55180.11834641851, 87300.58297128344, 36570.89422133425, 28605.021881477023, 88667.96874336562, 18240.67642810252, 71050.04868559568, 55217.510876468565, 95608.70732088918, 14384.095515947338, 19869.36929552301, 84416.6921220615, 6670.952461521684, 95350.8319572603, 48897.84244829385, 8668.965296395836, 55897.04708603148, 19372.491409629343, 1038.6161672629958, 65508.29749009474, 79162.64959709137, 6613.633398106777, 96994.68706272838, 79169.77447215961, 55794.72250097239, 60257.56639137697, 81523.02212409842, 40247.3750131307, 3470.2677359482072, 34358.962072088485, 77603.2291160519, 14515.244506503777, 77863.03965465752, 27977.41387218149, 51479.278479382265, 89181.0573491718, 24627.556708165666, 86967.5639945201, 5167.942992481667, 16668.622325903183, 63997.23144408276, 88192.88894530632, 89007.28421517185, 67863.1737285223, 50195.24864435417, 16321.365402310961, 34840.65869870679, 19806.713604358916, 16890.08250116465, 95403.93024788429, 55260.10678287906, 55430.493630779, 79597.98000309205, 81855.25441482302, 9997.52626167213, 46592.36024193372, 46882.069944143426, 8833.305679336501, 86771.21718637359, 75178.37941197543, 69782.2769327769, 55123.47457511823, 61363.18098906272, 77951.95238769238, 65130.869581994964, 97778.41535672033, 73066.50294205901, 18794.378592635774, 44625.53353894118, 24249.704796481397, 47137.86815787618, 64158.55250224991, 12977.424883488076, 77349.61249175195, 62033.22812112867, 76131.12635459154, 57898.4741723081, 67760.44783016425, 75321.08332655062, 27940.0393038612, 64761.911928534886, 91538.3211765926, 88178.31927441512, 55707.85375380476, 38404.87436247747, 46530.778923321515, 7280.68310003066, 39013.37616465696, 14542.089696146888, 58917.28717894901, 75837.85516384541, 61481.58160396537, 59140.72005415767, 82744.71364210073, 14513.877350294746, 88211.79875938721, 15757.365142701963, 55717.85110651027, 61210.58307582898, 24424.17119697723, 98326.63310607354, 94574.13427861767, 3004.6912950508763, 35529.339084277344, 32407.707567644717, 92412.62260362075, 7372.201472325801, 92754.00868694698, 42136.70064906887, 6217.076656139253, 72379.28876355445, 54506.64170936177, 39820.913624887355, 18086.42192525005, 99489.62231644553, 840.19986751519, 4265.764205279654, 86825.9082191683, 98605.61407821061, 5250.699271470682, 16973.491745371015, 19299.26240162119, 53408.42972920391]} +{"id": 938, "vector": [49342.3492329095, 86280.77954799, 55511.414129314464, 50476.87017712191, 433.4892857627892, 16273.517622275634, 46189.58321739653, 30632.604000519805, 8737.10330258306, 23222.85662542385, 39865.86639424039, 25090.22464643732, 16453.093443347643, 8521.18118183436, 49725.75052238265, 29926.35314654892, 39850.280587873975, 9017.124020847355, 3067.1723875497214, 20030.510708663984, 11491.666664980083, 89397.96924670612, 51486.850052778886, 79270.47527406669, 78973.15064829018, 375.0001994332952, 60938.88877114263, 32444.20999998526, 53439.05445166313, 22345.93230137184, 76432.21424088567, 52230.8889390141, 74219.213126968, 79638.29748704267, 74245.0382043792, 24239.819577724185, 10912.003573071705, 42396.47848638608, 40752.21799104898, 99826.91544898543, 70891.9139099605, 33102.86543607409, 84588.3355576733, 12980.343161333185, 44442.00022405209, 75232.31119046344, 48744.68742454041, 27138.282871351683, 60413.43484270003, 5756.202853825387, 89741.29373975378, 64999.89489495934, 5428.6920769773815, 48133.789536203476, 63913.37566899509, 9033.230509379508, 6668.574052644305, 48776.78910999267, 33654.3529757125, 15930.377601640645, 37671.68847507827, 24813.48776889055, 51729.14651928817, 60896.23920097282, 48704.514400679574, 53060.19236519908, 10008.2650805974, 48375.14040814921, 89452.29023952117, 47555.144025373505, 69020.3335284041, 61188.978124988666, 99952.3086423106, 57180.54854472303, 58746.09266947911, 32093.365207832016, 66395.06350092156, 66715.66806549497, 97205.93821325815, 44360.56386276072, 23551.260878279023, 23891.231950105386, 57050.66870538147, 6130.920188290756, 99035.50392961537, 29199.194360006884, 83111.09011670777, 81707.99509676578, 8431.634088019913, 57937.02842520152, 53706.370795773626, 49663.824290803015, 7923.696058688479, 55678.97451949281, 83428.6114174265, 49473.52095456783, 11917.398517791566, 56357.048775892945, 89943.1739246729, 50625.253507922076, 33644.55287461543, 22516.25028653158, 66398.46490139888, 87541.55943830029, 63144.00839469988, 51990.86279532188, 96987.27675443084, 49807.91230540681, 76043.24772328249, 26835.59871108753, 79953.53230855531, 41939.906966451716, 31873.20859193855, 11168.61926006617, 73292.74912195686, 11156.089582870265, 72000.64896503955, 31851.47967871137, 64513.81000158004, 70470.86048447735, 66759.20158628329, 35036.19567145151, 8397.990948752898, 71604.76993848108, 78760.55016355346, 9676.552860214482, 79052.6808300292, 11459.938084574984]} +{"id": 777, "vector": [73733.46357201692, 99149.80643829785, 68265.27929367336, 43170.25696722867, 32377.86314655824, 12360.191135119403, 58085.715858654854, 56602.938971146374, 93450.20462474137, 76840.50519065716, 29004.57522904808, 27205.807001373505, 7277.315291351394, 86238.31849071625, 63715.860349505834, 67184.98452747677, 81642.7062834912, 36026.923068927965, 29239.89951160736, 74904.73282165948, 72376.20962550018, 92338.32582756471, 88075.82899687396, 69030.35723337738, 40581.799186439406, 47076.651634977505, 27310.432169254782, 48289.26558770591, 58451.9231472325, 95868.26784487147, 28544.98658475253, 87832.44720843041, 56315.0914945363, 87636.57485896078, 72268.71218260999, 26638.192096253144, 58955.69176563702, 743.106939982674, 85815.25485164855, 63592.67682464285, 3176.8801474090114, 30035.36211954555, 43491.89352983767, 51359.855460717685, 98045.5987817252, 39563.0571975186, 87227.18241330633, 66507.22406529635, 56613.56181123894, 22890.198270799454, 4372.635612285291, 24384.126722176003, 80183.64087101136, 34928.05590300497, 63036.73638270205, 38237.39551299081, 83067.05389050044, 93504.22850317124, 31365.738111861974, 2020.4928496906582, 31199.03970081276, 64354.987886157665, 13804.861585952389, 9458.360724666614, 52141.93275964411, 120.3554567195786, 93611.39944771191, 49867.28927379902, 95053.92005754501, 72336.03230689502, 60231.86628538542, 71490.46676703637, 59033.41644244507, 40152.70666016604, 80502.35294678125, 46135.86833377996, 52993.58408064572, 58150.06478520817, 19513.362485107446, 57994.23446064345, 72195.83864464032, 55943.47127131067, 3552.6483631551087, 42505.45582347459, 48637.3152443537, 7821.863361826864, 52636.46546796711, 48164.014314197855, 99925.0192847375, 17782.972060861135, 11224.653358596914, 94306.24344178376, 24025.88063723251, 59468.20932031362, 26792.632718923403, 3377.240988810648, 94618.61539457647, 2630.177308792159, 1277.1054950155624, 32713.505459378823, 4302.192682988281, 15522.98736777512, 84339.4938709237, 5602.983474153511, 67419.53538139963, 61357.231888913, 16342.448887237726, 22900.233056831254, 921.0114986322848, 28745.330634517653, 48186.875830844445, 57864.37603308662, 34000.35410474647, 81058.88980666426, 15421.887073469064, 54647.82040975712, 33947.41982029075, 78195.49801436064, 43998.87434159206, 18153.111011450495, 10918.001253984377, 60783.58363895199, 48834.26493940486, 81543.63653594417, 47981.732500906626, 21264.094185837013, 36784.820744902456, 50441.64999640296]} +{"id": 1266, "vector": [37014.3217397787, 97510.32368060756, 47507.83420896637, 5322.009849664122, 69630.54215693565, 27978.671896408734, 82429.18429172829, 47165.978576531554, 45676.9872540569, 73428.43932046543, 43292.7159464365, 67831.91905940408, 81039.2054157682, 94919.23817836077, 3370.6088633400855, 14084.343706602176, 49474.707266090234, 89269.61023631315, 11312.40530266655, 1722.3623418517798, 54308.62953539267, 40280.31349011602, 899.5237989747662, 93628.7256570453, 36463.12123771274, 9175.65416338696, 584.9098378615913, 12908.397360888657, 89298.4642310567, 11986.086653190509, 54275.974947908886, 82070.63044513337, 84485.95544709798, 71900.84741494776, 63909.31010038129, 84922.60884069538, 6470.899096194538, 44694.99439917559, 63786.13966633199, 74054.78265412572, 21080.409549319047, 23215.446295606234, 50821.015455171844, 18737.07884860537, 73490.00247817242, 36075.44302214325, 2724.193390363594, 21302.315596568933, 76091.55053942712, 3131.8543135178234, 84135.54901345095, 92196.75384688796, 57854.03063058879, 22481.443403973688, 7870.972011478671, 85167.98553710725, 39700.94092111172, 73760.33568100576, 1936.852807902234, 57822.47676724689, 3272.0957382518236, 82239.3481390777, 30874.717084236614, 55803.76094438691, 23497.27112400013, 27689.405671265733, 74658.90423894662, 71563.63378835075, 89289.01228017584, 74271.0389828219, 25349.130526070672, 58540.962151028885, 41671.03775543115, 38694.684814362736, 3151.4052709713637, 4205.7134906229085, 66246.22612838351, 31322.097128095593, 14284.722974780228, 7993.384519930691, 44649.64948883231, 8274.358088164458, 56067.09431221246, 38346.087354630195, 88092.89048979494, 98483.03455696272, 29978.67922455314, 2883.86499083475, 97133.84034693813, 39979.92728529785, 9900.766970322062, 58133.151526619054, 7445.206168213081, 63513.794558392925, 72086.53808630924, 21430.815639745237, 93299.2691705202, 55890.46951077982, 55154.50219142321, 84123.876548098, 66511.75133648959, 97060.75293382259, 81943.84987806145, 35934.21043019177, 2624.484953271666, 98428.32446002105, 10824.972635516027, 54694.66063757415, 32417.890480363876, 36599.41278371017, 91615.98745190314, 75549.94324369493, 81691.48997199655, 45376.31021920876, 84659.91712777421, 42349.02295623909, 40327.33213775165, 77758.3317176452, 44981.6976490727, 27804.2375329134, 69436.50001200478, 39344.223754327126, 17574.057926288744, 91023.36708403984, 19504.67970778791, 55024.33113326185, 32117.899431001384, 17675.29903787529]} +{"id": 863, "vector": [57067.02875946464, 39329.4853556877, 46511.15346532047, 38791.60345525494, 63164.113819845224, 20841.209183321098, 73574.4175134493, 76914.61450801829, 95435.30268516898, 10749.531345635032, 11003.3834305715, 42163.12880459845, 58077.202595335606, 25894.045356350194, 32957.26692252835, 50902.684904650094, 39323.13031642204, 7129.226766158736, 62655.46263251724, 77798.94806780088, 39997.308186357026, 39294.293136875014, 70819.05848970117, 71831.73028568161, 69725.23864761979, 8260.203486983542, 52926.420731450205, 18846.180777115907, 23094.299648230222, 67457.81030340878, 51749.8517414548, 11503.04804916188, 77101.61074499824, 78962.54267449802, 30213.479001208398, 64949.661763508004, 26671.040257403754, 33942.95082876746, 19809.725197041495, 54664.78576239514, 22477.802995431062, 6552.381256622431, 76904.26661575897, 88082.52446570272, 15793.169437383847, 31882.372671899462, 81984.05509415558, 74283.54429676772, 34759.04259352176, 17882.558052016928, 52132.83002518999, 38345.4562505144, 98107.48405552506, 14007.358665504898, 21727.344589865384, 3049.5267754041324, 61839.352494304236, 48858.94029776402, 61660.80147706686, 27847.79383159518, 33902.0015048812, 92307.52024907501, 1309.432274954214, 96281.91736612968, 64992.26344542865, 96638.59789655427, 79023.49046064845, 83868.16427160482, 53412.491173212606, 99204.90628333803, 30600.344385817134, 95067.1954705426, 3313.28389778226, 80504.61042668641, 77011.24417095899, 48576.08408699253, 55580.54240107113, 59328.802796221535, 29228.86051720005, 55900.55683704668, 94734.70596100966, 2917.134562935342, 53615.97617099777, 76040.70470828678, 18997.115548655176, 4417.16056764726, 27099.048596527096, 22029.72941532799, 28223.819186615452, 14954.915907712695, 10207.491109281109, 35858.14531928945, 46011.43898402318, 97515.34303086704, 33318.09762249949, 13010.429912086807, 21425.45545435085, 695.5889975495455, 93458.08734809424, 78708.25520606927, 17110.935346797174, 36671.46917729091, 78974.89350127048, 45516.16089584565, 17655.962434270245, 19223.841866776103, 542.9571853631354, 99329.15429279371, 41313.74426788493, 28573.50404440143, 82208.4723011144, 14873.783574509236, 70123.83738942003, 63453.4910689353, 55287.9618844335, 65911.93951969322, 15911.453956329513, 83894.02283267894, 67752.72804679397, 29310.82875563682, 48028.57544166752, 2282.71980526763, 66752.34680964348, 31464.372344848536, 84198.95653196544, 64717.669180086756, 49965.410524822626, 1071.6496932218101]} +{"id": 1497, "vector": [82566.72939028162, 41862.78529933631, 35492.04120586995, 21367.437430006452, 31229.382949583352, 1572.1216212755796, 24043.95355054556, 31752.69507140047, 43961.726393999365, 65581.7590606792, 18277.73784627851, 30679.30707423452, 49710.19566094943, 26702.39925858189, 23371.06979656135, 97015.40804187657, 84403.01018588858, 24485.49599065648, 87006.91265808404, 65870.48733659102, 92972.60223743731, 26329.738032168916, 25091.075443118705, 92521.33525168043, 51579.96501735222, 42065.98997852442, 21465.67781350979, 54022.16075437388, 76900.5070632409, 41980.47272038512, 29303.40191097871, 85448.68059245565, 9701.007407874251, 94341.56219988583, 96586.65363210814, 95335.33824829741, 27177.517257411542, 94251.73801519578, 14539.350611221147, 74075.72203409897, 94768.29908333934, 36597.76351308325, 47469.16051251332, 62347.42440805312, 21336.554682054688, 22730.10400335347, 44669.37380133722, 65852.13159389261, 762.191021389813, 1740.7819984210482, 85854.74334762368, 93208.01105172145, 569.131966835612, 56409.05079902158, 73253.06557060627, 72871.18557665365, 14095.070275568178, 34169.870573484084, 78909.02986438587, 50119.95836361284, 31450.32403180439, 65140.2174888178, 230.56205029581278, 45819.10133461329, 18712.32359537164, 60539.91803590645, 29603.778992974305, 43006.608775701, 59021.951015956656, 99092.70937933552, 38885.45542769504, 75232.00860937282, 67007.71955591456, 38505.222954466255, 12104.507499520945, 35146.492154055006, 2762.0084915045973, 33421.34400146023, 61485.64893825289, 87965.60656866232, 3416.942997964334, 53779.2576194581, 62877.69180874629, 67920.82321546078, 33580.29310524522, 79979.57709282388, 48165.122314834094, 46598.225025652166, 74855.7513162168, 67204.38006109514, 45573.50621774161, 84940.72895685567, 9825.519525455627, 44178.334381163506, 24865.932257398836, 19281.501232711074, 64436.38773167544, 69279.0181321358, 59213.75909563812, 40171.55753333679, 87582.13169853117, 71209.60269314636, 58574.97488725919, 36635.903035511496, 17892.141772386993, 87235.603797546, 8343.737348100822, 76504.88107832924, 78705.24122273376, 35523.69368680405, 3540.6928714968753, 58329.62883667942, 37912.46991405597, 22777.171964930963, 44585.158830094006, 59048.732389113415, 11677.117843712793, 42387.31848358306, 39223.696033278524, 46255.94899331653, 75510.86171202036, 64935.22061671293, 70752.58121538693, 57090.564141990886, 5218.486980079562, 85915.88462958708, 13747.776061369954, 23793.12096538019]} +{"id": 1733, "vector": [34037.42736545257, 10128.76961210638, 69325.82529628075, 27356.181078796348, 92655.63876625929, 27788.069696136463, 77403.28578723964, 54139.63121832712, 30842.928863783636, 65329.51850401388, 34245.867600022706, 20982.206857898756, 26347.75274760761, 2049.2207858878064, 65826.88340808863, 63444.99070811804, 16721.357138313542, 87781.68005459645, 43452.310375064815, 33266.314866026536, 54276.32670487215, 80349.77809117398, 4357.013053221071, 77308.32603748322, 75144.45387855424, 38769.18262248707, 72429.3697454604, 54147.5898216756, 16884.221665769648, 61965.27083954633, 92807.28962569006, 49155.03686254852, 3131.5461747319473, 37439.530824722526, 27993.368203393908, 84199.0207321376, 33042.27621786703, 7636.159320436953, 14968.737484144789, 89513.26437945444, 13367.886302355004, 9559.009171690424, 23677.86533760039, 88700.69285010951, 11096.984373700914, 30892.55218674043, 15302.911174419376, 88428.8215119529, 55843.37582743477, 50196.524026706815, 39286.467661486844, 88745.95374164017, 20318.93544323017, 67317.35909643625, 47889.394634961754, 40979.11195603633, 23400.35278200768, 34364.83235903552, 63034.690995849196, 59567.72956647033, 30579.27212934336, 58412.691795597624, 94978.4705367766, 41908.5181373762, 35380.711852960136, 6899.544017494774, 51620.19705376674, 77992.24392889392, 25490.855673626455, 92780.68719292688, 29426.696649918715, 91076.33822647455, 17598.339519429417, 48303.388889853195, 3634.299983993494, 85983.38350850406, 19719.607662542738, 9655.971545511089, 32087.878754941936, 52085.295966000696, 26156.0618412473, 83851.07324049562, 38113.434505109755, 29178.543399152666, 99306.00096915006, 55608.181452238285, 98207.87465657026, 97233.38267562969, 39656.848052388406, 88817.33739397125, 40850.38129378315, 25295.607903992, 38561.59567942596, 4317.757158334523, 45894.42870180906, 37483.31772290696, 46009.71292962255, 87506.74631875471, 28968.45370868957, 96100.19011408446, 57465.51042624436, 3058.849572755806, 44536.11188897127, 38223.92773688281, 119.19851423589645, 24189.045962611544, 68075.86944151932, 87502.84257568418, 54812.34101218929, 48204.042156743424, 10502.171307535113, 7048.335077264789, 91593.41723453844, 37317.268618641145, 86974.77695687386, 52559.80322508805, 21758.320198820402, 15012.95499880595, 60357.87752449157, 42225.06853450339, 5455.758535481247, 32547.62476278591, 89858.24360635495, 54534.47133685493, 98716.96777309703, 9992.653161562392, 6992.35824217389, 62219.04696536653]} +{"id": 682, "vector": [17816.49053789499, 56530.47938020744, 71476.93591835607, 83006.03263955476, 47582.97679249659, 60221.27042200045, 11982.261925717663, 5203.899730301742, 36350.82600515049, 14056.935364650037, 17478.25403236496, 63751.010309543, 63509.9394053956, 91085.13330803798, 22089.064050686113, 54102.062554955824, 69704.04774508342, 98079.123753231, 16643.546391156717, 22078.04483540631, 66958.49548199475, 45096.690174750845, 62117.80803027623, 61590.281208725675, 38586.62052004258, 8203.826922130953, 37964.56708418186, 19157.80369540392, 68498.90702971737, 62691.610083697415, 86488.37685724064, 16536.490798272975, 45000.76408399395, 43883.06763253067, 94816.41138099022, 53295.72812236805, 91743.67142191433, 67965.29292961115, 82955.00212317044, 86686.11203396184, 39224.90589468577, 83614.95712563195, 68227.05621127176, 66519.6412163083, 32625.49573121252, 75383.67567204169, 80744.70402696393, 51947.422354198156, 78608.7326242362, 1081.2736945026936, 24260.82764661859, 67249.07274333198, 23816.29647396031, 50958.87881955435, 7240.680948767197, 33188.90674508645, 56116.81364730341, 56395.891840121025, 74333.34598324144, 65573.13354798136, 72670.4632305798, 42984.91861905249, 76975.8215548612, 6181.829225280932, 25808.2953601108, 21079.61449253808, 62479.64477450276, 86748.70388631788, 78726.57612604662, 18138.18594177766, 76918.13802699407, 20805.849248199047, 49237.228988475676, 79075.98906456325, 98519.39388400388, 93385.1092538674, 11207.26527476954, 70492.04628121085, 39153.31783358004, 5782.2292841657745, 59182.00640031891, 5658.908839982258, 68701.81272549396, 66583.62498779657, 3850.8927796900184, 38338.71624419817, 74934.01967996011, 49980.82499424742, 40890.87218254943, 33935.05501992794, 56074.207654886544, 96291.33007680229, 21479.659947318873, 65258.87976385973, 15497.757750050734, 67620.59566197028, 12515.019921823377, 9603.998951460691, 46467.08860797963, 43091.05011897738, 9274.527624299311, 98910.22399274906, 52860.613820140745, 79392.43302249495, 82870.65003787879, 65879.07930862806, 40546.056517039964, 19235.833500716835, 47943.68324840431, 29195.95100555484, 13375.814671719378, 28280.93487105985, 96296.7203814591, 57781.44855884445, 77986.35251269827, 88879.61605821265, 51014.035292913315, 14017.555103019242, 7252.1448605165515, 36377.86419805824, 54700.87562903323, 5007.122462701086, 15740.54250824094, 35994.98730600763, 66935.57082995275, 60570.87403717969, 74912.8876904559, 39546.77138976922]} +{"id": 2024, "vector": [99014.10635244694, 88449.60137818663, 83515.15599984037, 33928.499499359335, 55436.61255742051, 98717.32436549617, 16856.724627355692, 93133.85593975744, 81562.90889532382, 16772.378920277686, 23262.36931510399, 40963.984681086804, 94163.39552878299, 30745.0136838374, 25231.452982241597, 51162.49326333267, 9022.32857849028, 21720.596330270626, 31131.21030089768, 64278.37196343341, 64149.1847934762, 86265.33755083084, 57617.40142628573, 18221.62175242331, 62687.78446980108, 95086.6039634084, 70529.59720994261, 13139.600356431712, 94614.76737997185, 99972.83567147379, 46072.79601994956, 20214.708042454564, 41796.81094962927, 74396.20854904101, 4726.603208838931, 93918.93898328504, 31212.85062486322, 59153.04653485088, 48267.20349913973, 44516.59500456719, 6908.905744463878, 36211.94643667182, 66567.45856075156, 58529.83699528925, 77780.86873036453, 24957.79726118529, 81042.7793588572, 95868.01627247562, 90945.39621304422, 51112.28025808217, 9279.005895592041, 26488.48171828422, 42389.80306415946, 50960.3326749141, 73662.98219291466, 94855.1445684057, 97608.99837538318, 1943.0220439611512, 91311.67298080343, 64049.51636350915, 18791.562272747607, 10416.416973759513, 49353.92109271275, 44045.70363780622, 84641.4814016145, 39929.604010033305, 94870.56852816853, 99575.36441578742, 80188.77200246225, 67431.65965246734, 33988.47278502487, 96654.76934888714, 99980.33456943235, 89369.03139638869, 16564.015669839282, 79830.77919669416, 15007.823553564736, 10344.944969406755, 77131.48593600384, 22022.765694138114, 43327.02245558754, 78137.22137681341, 33079.04213268164, 10217.061255528004, 47927.98250659712, 18334.65647782525, 9001.554620503815, 43031.62628963776, 33829.6338683791, 30282.689276771314, 85279.97697855163, 90963.07498595606, 87178.41608215169, 38067.384055019545, 52416.03960343334, 80643.69721593044, 26070.360177993567, 70678.01739138312, 27985.469476734324, 52582.7423674691, 22419.3786747106, 68585.30382043298, 88075.91974357601, 49293.70099159828, 25408.19912330038, 51283.41803285189, 14191.261219198326, 27599.857122544745, 15976.975786798565, 57673.34714564535, 12254.099356479186, 24627.89310047133, 96357.96190601267, 84910.21120226191, 84898.11045185913, 30704.594297844935, 88014.81592091951, 3017.7369831816404, 98119.78500671366, 77218.98490958064, 48881.14801592178, 88911.99691673914, 74556.44311323432, 24497.958232491434, 69799.69431615583, 40455.798002434516, 70717.3311449575, 8539.948869568314]} +{"id": 773, "vector": [65324.12702779603, 78415.92418146956, 60279.73813454545, 830.9910713041435, 48281.21343187887, 80011.12848134509, 23855.480191527924, 34389.961233725655, 11168.796552626669, 44429.97163315959, 79771.18271011619, 58826.836606629004, 54850.234821118946, 87163.74737059575, 33879.49009514181, 86062.24708643658, 31640.57007169666, 26814.06492460795, 19794.371616747096, 49638.23677396279, 23953.580638600524, 99087.1591037838, 53619.9554536341, 36914.681086528944, 16897.698624325807, 79622.32664104362, 52788.78034910917, 42226.56612580347, 64644.7727122875, 15727.439468370707, 98622.52990181488, 12743.883688005919, 28598.276625836515, 79720.93330897509, 76593.33531483015, 82248.98159871275, 94990.11563331222, 24623.506454457078, 71103.30900065719, 3004.8647175115793, 62197.00161966671, 45531.01362824198, 22256.090085571966, 82951.96292552268, 97403.08109970947, 72690.07805434286, 44124.04024305415, 10406.89112119555, 898.3732087171403, 14549.848456201398, 6337.868591714513, 21694.94568510867, 76598.06742754692, 92683.40577853638, 13652.52207026405, 44677.97615964996, 51681.05936795942, 67528.55067410987, 73928.79908326788, 15618.793699203603, 67778.21774658597, 23255.735203040917, 46066.138207294985, 28895.91917519302, 88465.84614498245, 63583.86823655806, 70815.06311779865, 25974.874577148275, 49198.44726535605, 20399.17411909167, 36244.878061456795, 53428.59169008784, 35124.27771803076, 54448.37520882593, 73570.07803689154, 91119.82304581991, 83210.26062585901, 88137.29062691671, 65295.69380484874, 15279.433928022612, 88395.10804987702, 82479.3984732643, 3872.2571339223964, 82988.18886280537, 33110.4949601161, 94885.70325666442, 92891.21904498125, 3311.3521485716446, 82123.33189905908, 89841.09852346838, 75149.40774294198, 19101.292689302496, 45765.31541833952, 41172.99420151019, 46261.67172155146, 36343.86241228435, 69388.5965549148, 72676.54396612098, 25852.302213645205, 22467.658267260005, 92280.80933434202, 13504.828333045527, 90228.34823062722, 75994.7458081978, 12340.950194291112, 5944.607214740616, 67581.85063684975, 16840.92604953882, 71538.98380115909, 7667.86585514313, 64611.2031300484, 60329.59676543613, 47758.144391951355, 74535.54844079082, 4179.354601285723, 34322.45247482361, 9545.529019753973, 11790.668542632731, 76098.56680993496, 12517.93454722092, 31627.297381906705, 63026.2951317428, 22518.204138016572, 97507.57482840639, 72047.14086530948, 25125.48087695813, 15362.790319654174, 4882.874402574411]} +{"id": 451, "vector": [62753.031528732674, 14745.628646426367, 74395.27491580532, 52572.19304650974, 48430.8637329213, 7232.33913703889, 58245.91602912955, 75077.46941122852, 32654.824038671624, 20782.853638891473, 18796.291045048154, 1806.9723585849906, 82508.825246023, 73300.51043553925, 6779.486297442827, 76499.6543672372, 2107.915069720978, 99873.61042614035, 16854.877874445963, 91106.29165968674, 14182.566156200215, 73509.78733585893, 43462.20069242547, 16650.187073648725, 36920.176243065085, 6203.770780781959, 73196.06914429968, 90535.0785939966, 32775.459921294314, 59045.01320383602, 93001.66260496699, 10259.00110429253, 86593.03921089794, 33021.139459946244, 61741.277377323924, 47120.138812365396, 93675.21622254244, 93903.94349854672, 9504.609277230536, 7719.251901669666, 59207.08329589535, 36470.49201193114, 28836.433676430395, 78513.81374784464, 1688.7767730618841, 85415.75958471003, 8472.537693980054, 23002.428793880346, 88594.98231379647, 50730.767440850264, 42078.991771302164, 38177.05164395055, 8464.713318674521, 83834.80480398178, 30785.87775003968, 71083.28489563354, 90242.93872913758, 46361.39952375955, 13127.199423765935, 42893.26532381419, 82753.97088208853, 39328.231505187316, 83385.89970724269, 55000.54843454335, 13364.632114731123, 22720.247266382055, 78148.72153491495, 59427.79823985821, 48217.980326029894, 58038.5286868916, 71142.66168944749, 55758.23970326058, 66859.1608573545, 85188.53190550095, 31457.887540147545, 4487.2725383091065, 14234.551747929314, 69636.29061991, 94122.2441611828, 57039.000577277424, 91372.62711607988, 56793.58036069405, 38547.96548792256, 7166.148472825529, 22417.564140719558, 13727.629979173316, 41094.11287672583, 11556.98046671092, 68168.86231160325, 4841.450623618149, 57721.26909109855, 86791.78299188743, 97776.73777692854, 34741.555100142694, 19652.213356372617, 63856.07854320485, 26524.862503892145, 52099.284809765886, 40116.023187596606, 42779.61498769023, 62927.42416972792, 91512.46888472536, 16705.354924799452, 92949.32185482037, 45682.91735781661, 68183.60009933184, 29515.812047428113, 22442.21825890994, 59383.20664156887, 61867.847979670674, 72743.25484770807, 35117.332429106995, 17447.24994489936, 49793.26086514757, 44887.40106976745, 51863.45610079695, 29766.976456927972, 12135.598147107085, 87800.15908027052, 60526.66082585306, 67067.43935835698, 85814.79899090203, 35437.12901561621, 8981.367423373544, 7635.204947782137, 59966.61196239433, 93506.89371287779, 68695.02991016855]} +{"id": 430, "vector": [34053.94089643734, 70743.12819544427, 38378.216175991605, 49644.23736735728, 50266.19495149478, 99418.07396240714, 96779.67039615136, 66008.10357402093, 36756.102698110495, 9107.433573477052, 61723.53603395457, 62832.605691122466, 52269.69559771007, 91752.61428774413, 17574.366553601696, 36799.17724351168, 77593.59957867581, 74710.78241467934, 41719.40178555218, 26819.47792570327, 93417.7826082975, 55512.67431563746, 76366.93634270172, 36364.102828550924, 95047.48154478193, 46340.547425633515, 34384.610773356915, 25266.53998422862, 93207.06390027821, 2847.114274971374, 81747.80252002245, 87325.2994958018, 4446.500368069584, 76056.50521537376, 43612.857378182205, 97046.49677545148, 60760.21684458754, 8228.303717598306, 52074.939652214845, 31972.538624305012, 47086.858888576986, 2298.3075266407973, 42490.512974228026, 42483.2149286221, 63126.85733089229, 15266.571102567195, 10198.042608113878, 23527.232523742725, 45902.11121053189, 99898.29563815088, 87678.62666175811, 3357.49406954059, 65273.46783157395, 51060.61101376391, 14847.148305758628, 50778.43238205526, 78436.06746816714, 33687.47677001269, 54233.59307268503, 32247.79452380593, 51592.688329186654, 81456.6402834434, 20082.94373274585, 93545.59725210161, 15310.82829652628, 57128.37373252629, 98313.40450673796, 67107.48030647024, 51257.76958682072, 72272.27581840986, 22341.601944623013, 81279.87874477767, 56239.897958656926, 91335.93455105771, 34196.325832338945, 96188.53704163263, 12998.79344073368, 14325.467353405775, 91941.71551466905, 75324.08738779642, 15836.681321567692, 95907.14689000763, 60170.51066503942, 17205.791061807464, 68676.73403945033, 85082.708974595, 52755.99327372525, 95630.87474271264, 3447.1479370654756, 87643.00558456153, 1844.321561701312, 93528.01829283165, 14079.296156565002, 88817.24876642131, 68251.22506456527, 60261.78204854787, 9030.357279999756, 96707.79854681352, 79947.35969970442, 82407.69341147035, 32885.11697516585, 31179.984191587206, 66190.39492672967, 46488.91349473641, 6939.390264013578, 66339.95927592108, 55248.93038777202, 89026.27311467078, 11777.015461104245, 82746.26766001595, 50596.98617306218, 67123.28120944345, 82021.90055950175, 72606.95559061822, 69808.9224920335, 39836.53269769747, 71622.24223496245, 34227.98898550046, 74689.40685392667, 35583.23396272279, 88451.02919081927, 90665.38241225177, 41560.582479829, 10292.62994697625, 11070.29894362337, 63721.49611235258, 74934.09335055925, 93658.87261209813]} +{"id": 904, "vector": [2977.1713317407466, 59157.460816964405, 80956.18598530066, 70089.66732084128, 81076.64973541923, 74194.30031731508, 98702.92866440894, 31552.61317453215, 64853.509930010056, 71523.45031477764, 72972.11673484292, 55071.17064697823, 22138.10388269576, 97940.24631916967, 57448.604932298884, 71352.954151714, 15101.548349484929, 59601.28523695126, 93724.1331822196, 79899.37221642882, 44648.21785232943, 84697.11686387105, 58130.14751772647, 98206.1294388394, 54955.00332110609, 52117.91463000611, 9699.257449429888, 67232.66759261535, 2699.5710456792945, 22045.459341333408, 82206.63990344729, 4117.992504805601, 59845.715274536604, 36749.03007491127, 21506.51660066547, 51925.36219566112, 33864.522626856175, 65406.12885392711, 51701.33292970255, 66979.20052676047, 47511.11488916575, 28336.18552278886, 87929.06211452499, 63149.387020635906, 37413.622332204344, 76428.88618897721, 63883.943326215696, 51784.9846668653, 85840.92671187628, 19959.131251201612, 73182.50696198414, 15976.824727533613, 2768.72223814445, 60586.529298609385, 15306.731561066366, 63087.29425048332, 62055.45076916629, 53525.060743315, 76407.83046547667, 29027.876453935976, 86273.95619979087, 64342.53796278503, 5329.827769358675, 15780.475248922254, 16053.170235033209, 77045.31376443256, 9955.256385645473, 14953.186004269659, 4633.058454990591, 23384.25564654444, 85037.21960700338, 67278.4385830967, 15408.404828904377, 97330.20167109995, 15023.298633816696, 98609.9998031907, 44732.1178145685, 76620.77325899983, 89430.87716804886, 73778.71925475438, 70100.3698476771, 91315.8278660283, 73658.0471165638, 1751.2795819601745, 49621.17954486668, 52208.628956096756, 19157.611354561475, 68094.50013707358, 12078.923510470884, 16359.064242279765, 74583.23504029468, 25044.17927121393, 97852.98726759136, 63753.555897023914, 69072.18749348144, 54124.44000638275, 13369.332297957659, 38031.9446455929, 39821.6304734332, 8209.115365986974, 21209.52669140962, 58965.757728855715, 97779.98777254093, 48561.320494401014, 4642.252507739697, 46168.31447624757, 92365.99547286461, 41086.315595673, 4808.228551177973, 46585.05977856764, 39648.94228809441, 93216.84679078196, 48071.44982019352, 30757.268747351874, 43406.91894491425, 26388.394343726908, 5494.449731195738, 61703.21678270212, 82011.81709277055, 65633.83216599603, 10809.884689562765, 71159.81433991094, 26017.867750476198, 52307.822862926245, 99891.05068437515, 50080.50119535019, 22147.132439537865, 90106.32242987613]} +{"id": 588, "vector": [73250.50063686266, 82346.18079322555, 1374.6444656215328, 79296.22118804605, 29150.76887608533, 68281.86534560569, 40417.812565129716, 92954.03251998754, 79374.03089375704, 39734.53645923331, 65348.41197555829, 88980.51864470128, 92403.87424361212, 15805.988276967199, 24356.797941228924, 78759.62399196732, 78609.73864069169, 47924.86139704981, 59359.02689782706, 40286.028705862656, 47938.21872726452, 46435.77351137275, 10655.257866507705, 65329.66443338688, 34592.480214772426, 2420.8332548164567, 62536.17099663355, 9524.037841625155, 207.02503955319918, 81139.51170314323, 71668.77414791429, 67408.24503034944, 68397.93846855308, 12155.927494399943, 84142.34785619732, 50159.55326282152, 75677.38893489134, 64129.75889494688, 13251.802051966477, 12991.656557587716, 74074.6275765534, 67549.79305049745, 29778.59954744301, 42797.21807085525, 68397.92111822889, 46420.745518312135, 79001.06951384664, 41012.58588509611, 88117.58732183486, 27341.56020167029, 71163.55972746569, 26496.20044928114, 68932.69540377744, 3418.760446538394, 35643.87272291242, 73376.72195769634, 8898.955795103291, 89303.88894129875, 5133.758273910138, 33978.11442249061, 67690.68098190398, 15609.441431036154, 68483.24645056292, 96040.46471062183, 20791.470941264644, 58216.404222014804, 76999.78016080377, 52509.492187006465, 12950.385039724044, 18676.237550249563, 25805.40738982796, 92716.72434808924, 27602.49571806206, 75398.27608049232, 49720.858215300665, 81244.62836371863, 85315.6784370568, 68674.32069214461, 73960.49007686698, 13505.505250243732, 62901.27189191694, 73420.49045022775, 58273.87511842107, 10787.765258553984, 17154.81955330469, 20755.09273980315, 55023.813214798276, 78777.63355258043, 31375.493207428062, 62207.362670182876, 19493.011389101222, 72240.04249519693, 87793.48356266048, 22321.38660273362, 73251.0468143858, 1492.0407615120146, 69834.51554431123, 34651.7103792358, 38318.66312994573, 66291.39722776657, 28675.35904156875, 79792.86014602924, 63101.433140240384, 66157.66329021139, 77941.49392590241, 42479.78133273787, 56931.86733595501, 7068.0677614148535, 17768.856860069725, 69943.63988152245, 56614.826765400176, 38399.30770679586, 61358.587115628, 26699.14413924901, 24447.407345072093, 22216.957887673983, 98954.42052574037, 40768.4353934891, 91674.2676131594, 32478.827167946056, 51113.34036924566, 77875.64316642791, 91706.41457069982, 27912.16872535309, 75553.68007589548, 80113.15991102367, 52025.87968507868, 43452.8666688036]} +{"id": 55, "vector": [38159.25041728316, 85983.2312253954, 14711.997970645318, 18415.88762910895, 77862.6240204097, 75455.28640574173, 38384.8760529652, 7187.932175739853, 88270.26735102375, 33376.17233640856, 54931.65124115904, 56141.902089380965, 54133.63143402305, 42722.40988457253, 52862.854643743914, 23460.231492503714, 23916.48741558405, 54527.511650062384, 8725.924028249066, 30113.82399744077, 55261.687626221435, 44766.29850623108, 56714.44402804675, 44030.150800005955, 33102.513488763194, 38410.677946345204, 20082.18770546525, 73150.92317603089, 11358.759319482082, 98622.33056540648, 86037.98019767986, 92326.37528450483, 1528.4308230500087, 61689.189841297375, 89718.50582451398, 99556.59857238157, 77651.99982238656, 21337.27895565508, 69801.38755542481, 32978.711560143114, 27584.408388048487, 37974.083800162174, 23006.626871841418, 8011.029976162276, 17961.985977491844, 99797.65171508305, 23659.028669293526, 52440.47715422639, 33391.67034970994, 80506.43904356992, 44971.144871474986, 33469.240744446215, 76862.06500913476, 28590.59560770518, 33277.2798707125, 40790.92073290945, 99389.96007836166, 72369.26122646457, 49424.05864868467, 49472.37494659771, 45070.542971747076, 15999.87252908025, 26819.99726596078, 15898.887709639454, 27627.593091063663, 67384.12252137868, 63409.4191279962, 93469.64813744267, 47590.293292896866, 28647.793942984, 30897.75692689156, 14758.287844617978, 18155.971968855745, 33846.73238285978, 21976.43410147786, 96049.91897552708, 12059.19213473815, 96377.51472671542, 31290.198769992596, 92510.35287369088, 13602.438158986508, 72947.01299431699, 90912.32018457311, 74616.13777400415, 40724.17888797235, 23648.363362607717, 34088.650151074784, 41879.887813254165, 57386.258510363376, 25460.28156642346, 31176.874018841903, 47499.72516176289, 8178.304066172437, 91245.04737311721, 90103.60956918498, 37222.02477320441, 74157.61686234154, 75253.44711795388, 71970.98399403699, 63260.68051663375, 45576.140838505664, 26622.463927972607, 63114.43447664553, 77418.16770858828, 25049.202240866343, 18639.823551070058, 54108.80443248752, 87971.79328144454, 35617.30764580876, 54103.39075617009, 37352.74613311307, 36075.255993012586, 54643.79209641769, 33137.21174816412, 29881.243782521637, 59376.604619891026, 51382.492098882016, 20344.088336629575, 41832.6384792345, 39261.45873097228, 41087.97506669207, 68765.22189818314, 98148.98026015949, 6619.569984205043, 11213.866610066536, 73306.52007712875, 29554.149181230972, 71202.57658916747]} +{"id": 891, "vector": [71434.40829564533, 23023.93646116473, 72917.11876851004, 21219.694941936086, 72626.42709512392, 30193.310829170172, 76452.2207646882, 39936.605937077664, 83714.52254083056, 95288.68938060213, 14292.12793579161, 79260.5657527625, 55568.44738037893, 2233.6181082662997, 15067.729575729483, 47456.39574778262, 43654.58812040954, 81514.51156334978, 38404.02432137811, 49506.25022887712, 94486.35318978822, 90350.64015164752, 24428.805378829988, 80960.18844963211, 16945.74285024586, 37102.51593698648, 35088.57843948669, 73085.46912056356, 18509.743703290816, 61397.5319271533, 16238.527523081148, 79815.15933045105, 53628.66866082747, 57957.51666375402, 82915.31044561676, 64391.73213478054, 49688.301119144344, 41378.059717429904, 27189.524888860728, 61095.315531390625, 14572.482317020485, 29011.93027923823, 79936.88091142308, 81955.77493302175, 47420.84456477177, 94710.34029404011, 92517.76233038673, 84998.81143423374, 6268.194404368976, 88920.77297993172, 31778.377442131765, 70589.32726957451, 96155.5758186211, 2228.419889048927, 52458.61376306232, 87440.1305186956, 30365.46039335173, 43196.4950204409, 5247.379311195233, 9794.678232444265, 56306.48323180587, 97036.97898556532, 93843.05497047157, 27515.87458308371, 59164.687399123104, 99832.02546182676, 36822.56201802676, 93264.1715449165, 80281.87312125742, 89371.41549227659, 49352.24445771492, 35497.57230655302, 80721.60561256173, 40994.711960458895, 14459.756833548654, 66367.03937834318, 13531.498188265534, 89997.66550506908, 46248.7297438906, 60096.759976462636, 60786.80295934299, 14251.18978703771, 34639.45081514518, 56586.39693809146, 36028.62309091368, 20588.32533176157, 82047.51729172793, 71881.00269736636, 15520.72803529244, 65119.74383114383, 4848.85006917064, 15820.320253907028, 87118.350805366, 27982.077901812852, 39693.97192380624, 1168.2613305332711, 48492.81256483035, 54885.35612670993, 23570.266915430748, 36481.51393569186, 51286.5608387611, 19068.92920945873, 34446.16677918834, 75848.48510619448, 25504.904857865706, 78362.78816524606, 87798.40992870722, 1895.3102307523895, 9021.416017804251, 4128.83283427935, 43229.13776912103, 26472.940129230905, 86302.04604303472, 87379.57585391699, 22833.268084320836, 42709.98112215023, 9925.696135264661, 62389.49133031135, 86475.88828078532, 91141.21054931662, 42785.48008145863, 27913.803070790498, 54468.21338377622, 12874.380342818758, 22304.05410722973, 67242.4506016634, 46803.91460828402, 7387.535639455545]} +{"id": 1573, "vector": [57755.600407460275, 96740.72730115529, 85491.36158999316, 45878.86712307573, 64293.36408959444, 12106.223365764035, 32930.30689284064, 67303.24978992571, 8466.2748935473, 61741.02132749084, 27987.01156387192, 42606.23558845641, 67195.32843747825, 84106.66748583961, 75100.39936671649, 2981.339153345175, 16579.897796070265, 62933.38386723909, 16308.29673989851, 46571.18756716432, 71406.5720076727, 43535.1130207385, 53787.94776161494, 89285.50860304265, 35829.90368662401, 38833.88556413985, 77541.17050093395, 87532.1651797086, 67315.5225680342, 79216.72023106607, 51970.93937639585, 69325.38622171484, 68325.59082406186, 52228.09985266901, 25951.002038082148, 80355.51865268561, 63569.37911969104, 45287.74924373659, 89291.79181304896, 88884.5161464829, 3993.655416228814, 75448.42400058346, 97012.27602983013, 76669.17762863672, 21355.85122593945, 40657.172766870244, 8038.636464144566, 51595.14564928227, 52196.55382212768, 38733.97984963033, 58478.04757887876, 10268.012614863508, 4694.342652871952, 60123.41241477097, 30775.764088273638, 59486.48920291093, 47583.10246018287, 30937.86217478676, 23345.074151458844, 25432.502504997112, 85170.52409487551, 36349.428795592255, 64325.90039396891, 10860.95864504475, 20736.388490637604, 39322.154929503406, 27364.710832110395, 63618.501806332206, 53091.70259694982, 69833.66898543014, 90434.89405219568, 25940.715964065796, 11056.79398580618, 56971.3794213853, 73122.9820445214, 99373.41735471974, 53858.92776761195, 85769.50212779979, 55197.74857236578, 44657.88832099503, 47450.27900731632, 84597.92926220677, 11509.518008252606, 8188.7355757753185, 8336.303564636282, 174.49205546493653, 63647.39579501799, 68968.48220445977, 70523.27663484169, 89463.64780645246, 29693.119438872105, 13659.58759406104, 23530.41487392853, 40889.15986224601, 64768.87016825205, 71451.3247375141, 65594.57869349464, 79663.86457854442, 80821.35715320657, 94791.5751854567, 55075.77426018334, 24399.963118504842, 22666.366964717676, 88058.68895148575, 12798.203451097268, 56895.41038279047, 43041.383285437376, 41197.00170045797, 71264.29621293242, 18114.576960869745, 51725.21558057204, 85753.68795653783, 73406.86761787967, 54544.701226745696, 5383.191324790859, 2481.6719589821237, 47509.83184707468, 19238.388041602462, 70087.92497323894, 70650.5372403023, 25972.19702894692, 98825.1872754291, 16329.45537410303, 32628.665112052546, 71126.09788926049, 6802.040103312712, 35900.61914932029, 7874.6275477430845]} +{"id": 496, "vector": [57564.07909939245, 8923.424405547887, 68307.16408511487, 60144.80261177564, 40508.17931639494, 24359.669559716513, 12003.59787213715, 27058.744277592104, 31014.5781322273, 35024.75304841878, 9181.917476158429, 42572.63441480181, 47647.68932452543, 9210.464887572201, 74548.30398118298, 4556.50545514762, 58054.32506754085, 73203.006605647, 89074.31585734148, 70411.4161238209, 91614.11131663005, 99224.10405799556, 9138.11143548403, 91039.26580784968, 91375.88059108796, 29559.14134154697, 34587.11326516136, 97741.1640678308, 28364.62490046193, 26379.816829072523, 11178.185775755734, 63087.697602008455, 54730.12687965117, 50897.255955077926, 36895.94350883824, 5530.747047895046, 81150.92004239032, 99393.8165761763, 35876.50179943613, 94579.1642925411, 32328.058871583387, 46110.288102394814, 39567.30601314056, 33190.61518061529, 25087.57153786083, 23382.769955540418, 50256.65618997052, 70396.8871841216, 67525.25562143678, 7581.104041953823, 66670.88885787199, 16544.143302322278, 46310.69598667288, 46279.738610382585, 56458.81152622568, 19715.776215087742, 81806.12807159066, 17734.52362166039, 79282.67630888968, 92565.35582893758, 43986.91382954776, 20820.864617874267, 45038.120926484815, 49894.63086275767, 9070.140912438184, 77172.54885909667, 68786.83378412291, 43096.98021164702, 21850.61558698158, 46428.00099357113, 56950.367336762676, 6659.819712629478, 88039.24345176271, 86500.4279748947, 74391.37984409196, 22238.548425893325, 99142.69300334548, 58675.140297329395, 80485.15348953934, 47202.99131151382, 91476.17341094151, 75458.435671613, 7525.602699029388, 46009.262674208105, 37798.694771793365, 74674.47944813622, 95826.87941092569, 26297.22606820705, 71076.07242980189, 10488.90557364509, 28967.603615658878, 60808.59533130103, 39514.450316777096, 88220.30167007825, 10824.058855280184, 85854.73725243923, 73157.19083927808, 73370.10561446368, 92318.96687905845, 40025.16536030983, 59040.67971356273, 54780.335694378125, 46746.57449790548, 86298.42341737406, 17744.77821717103, 60965.409856346756, 35604.08733063033, 89094.3577365732, 92112.65574912073, 75667.05314600261, 22577.95629753553, 71700.58927969504, 86983.78236159935, 61313.21231617799, 32735.421403078835, 17035.73323321238, 43634.342380901806, 27190.877460392927, 93397.52820194626, 92217.87893667893, 75287.28115489041, 23409.6485082081, 68952.45254845789, 19619.07689670017, 52280.785273622954, 57709.360253848965, 29458.281572928667, 83323.84365284686]} +{"id": 825, "vector": [92379.34132782405, 78886.43095184071, 74786.67397222029, 19634.686938008595, 86323.04991730303, 36612.28556880933, 66330.06555926571, 24418.800229193483, 48524.83823815442, 89132.25288184582, 34603.91088536014, 14065.149895338214, 25848.374209864178, 65272.3131228515, 31531.46641265263, 39790.53180673834, 87249.5035971943, 79655.05889382832, 31074.02660412555, 15558.708927164733, 13121.771282504269, 23023.599893563285, 24455.6009776284, 65861.81768035408, 54770.48346381106, 47040.97832002051, 16844.74751965518, 88036.64262013904, 64008.040122507846, 59525.77284590123, 74832.94495225539, 77562.58176244398, 88162.18335493295, 73197.88644368039, 61880.149458080894, 69553.43840622951, 48861.08807141663, 3358.2365260984125, 81464.84943173784, 92913.63007547206, 32072.621273288893, 17707.85890351484, 25899.04853790923, 74167.16118580884, 41401.307939002094, 48978.08464924387, 67905.82741670428, 87995.2190469613, 52598.653296660894, 81822.33331349949, 24241.78405155185, 93943.70802202709, 5529.058569299284, 1179.1434170132397, 19345.06166354293, 72130.65654753898, 33972.68576155924, 18922.50977698794, 69701.68573897087, 60579.49719414363, 11723.425667292076, 76485.0396702764, 65550.820414792, 50278.0808704406, 37794.68901017391, 21740.866180163644, 28302.066605704924, 76251.18832836956, 1742.5359243523953, 52000.44645699917, 86025.10625226705, 46116.40805854113, 6836.823303164907, 8600.31005310522, 16966.690747133372, 89289.26858722832, 73243.82163949347, 3443.346534165581, 9560.980367377191, 38516.69376459593, 41832.45465584127, 19381.56432616862, 62089.14595348847, 26395.32235165787, 146.291491970052, 3651.7047453163864, 9990.913400202839, 1070.3695114349543, 79498.3152800745, 94296.86904434145, 34274.49545647459, 5415.736501843393, 12016.517440011576, 91075.26669149172, 47280.99769652292, 75681.05570971013, 35444.86654839111, 20768.672803331756, 92824.33415656972, 6890.826308785636, 88432.96331148625, 88279.33862759144, 92367.41275760013, 27435.06275450446, 14561.349791099221, 92012.38794702021, 86960.49241859975, 86106.86867184457, 94520.25859711118, 21406.73140464011, 76100.62166402306, 79301.94971108083, 84489.81505958617, 35707.783387811265, 8792.426428556455, 63214.04594265687, 94123.85909151225, 16723.095280803856, 21642.062616936397, 7141.013737048163, 61340.36630957815, 69880.9494788826, 37117.19333124872, 91817.26222996201, 46543.98212958292, 24952.739918689436, 90627.69319373202, 92255.4035666498]} +{"id": 889, "vector": [52068.53245940699, 31602.140401549204, 29624.651991224073, 88882.27450562658, 5384.034979756336, 70564.33334020503, 82737.1601512921, 51289.450109582416, 41461.14335092791, 32177.499526046027, 36665.396746089864, 22752.008595389707, 956.8104341803663, 89959.09037766104, 99645.97550615291, 85794.44676145322, 46652.39818825896, 19511.670089303945, 70815.66408344549, 99991.73158710242, 55570.727164636024, 72944.15749593009, 41631.62425862838, 49794.45401945041, 64897.402287402794, 91430.63905346078, 57942.33187845197, 54214.91960767292, 23619.012910991278, 53065.364432155606, 20020.85958181865, 97462.96967998294, 13733.306307624294, 60017.087854985526, 38294.140190633676, 86536.89587608395, 95277.79747573945, 28673.85904938917, 74777.00163326121, 50497.042275218206, 70709.30303620291, 10026.454185590894, 57507.31202639523, 40565.963361509435, 59366.39596576202, 30298.46103026408, 21257.84566475295, 31323.268836132036, 19197.24354171397, 76823.32107287449, 72671.17187525697, 88099.57839927834, 78508.1076249436, 20918.569913247444, 88309.36480197156, 87068.69025556327, 49023.64117517201, 41445.51434044656, 40894.55025366745, 50920.04132665485, 35508.24205176652, 72513.35176532973, 10148.383195496346, 12114.254027147208, 38978.31187785603, 44800.923022315284, 24597.161855027614, 16044.329379530764, 50711.91746902329, 92768.44164231616, 52936.56251678665, 25498.805065439967, 34280.55669825498, 16348.392936646484, 74594.88293301064, 17292.796168060344, 9532.472295588834, 35279.67315347941, 90327.93121948016, 97401.14751697105, 3928.9414674798295, 16488.371324756434, 75468.99661440683, 5752.0417327739715, 35132.00404848886, 57846.77495065519, 76709.12104338771, 33898.81842588447, 85778.52818055086, 7484.128974639548, 30633.327917443865, 69559.95025399607, 5283.248914958949, 75590.81918480848, 2585.680294584025, 2710.353496243101, 89442.21772011957, 372.9056234511474, 22410.538820071168, 96653.18659182823, 37683.978121533255, 68527.17210506978, 67494.95475276948, 84778.44393904883, 24075.838341413302, 89960.80463886328, 40556.47392169421, 79588.10277674392, 75913.84713018275, 20188.371267798542, 90572.78592254437, 14145.884596214431, 16148.117040772835, 73134.42910675873, 5786.875185345086, 11920.534574878406, 20165.008977191577, 4600.587066526873, 63498.80215952152, 13080.816935536443, 63020.44619486745, 60401.316542530905, 91970.25442929045, 74410.91980489779, 59545.55131649893, 62274.30103734537, 45763.88976827361, 31237.00322959706]} +{"id": 756, "vector": [89240.75843329295, 64247.5269951873, 62777.54118956064, 80107.59220490015, 44873.23163072191, 74212.49841744234, 53790.40841810758, 47711.755304806604, 84803.0763230268, 98419.78973488214, 48455.234032091976, 98184.98960853777, 45043.60425839278, 46610.46535521558, 35134.01360835632, 74751.63677798539, 35421.54563724101, 92763.69098024703, 42064.33954663842, 41899.6767283495, 13400.0153516444, 40690.14108563582, 1849.1023333740752, 69833.22113457703, 43048.64028956703, 17918.375901236115, 8097.889016802229, 42661.20696830542, 67135.91872019034, 91664.44316877307, 31273.110780378964, 27712.99717699195, 61497.78361139371, 78463.6877163771, 9006.229326418703, 25956.44049343897, 84653.81352261378, 56613.77326175066, 39091.46318253928, 65457.61831514039, 39176.82684941726, 30350.081835394714, 21246.017791403836, 94066.61442106233, 59589.30037854093, 16.661154508057496, 4299.268379613752, 72129.11665429396, 35353.55920300555, 94832.78507473168, 71017.17044074963, 23908.531880430208, 66438.85435233988, 89744.45681238848, 82648.06618347885, 17100.416162813082, 99133.71899311498, 94576.68765299914, 34195.28649564747, 92961.28093782997, 24855.617532811913, 88103.84260443793, 37238.02895642877, 15833.490843323783, 84705.34477183105, 45598.70565028524, 85891.13874758779, 53462.49966612584, 67338.4512927614, 67159.08920442741, 63019.80165278319, 66477.35250991078, 32530.17837334804, 25901.742550636576, 15847.529145971528, 26133.736626421778, 91705.63573417268, 35764.18780547679, 65175.68566663574, 38579.93429867402, 45086.2633562201, 64720.31291889627, 93122.35126592469, 45142.747011759035, 97487.79301272081, 41046.31045501779, 97570.66995784579, 66807.40397158581, 24778.386331336176, 81880.23974968493, 99166.2199067974, 11591.169057502348, 18860.901923354333, 13252.149366131694, 8538.246243539994, 31315.88957555387, 26056.339020433596, 66286.5368075025, 16878.852061823203, 68438.0580321407, 6143.431670740262, 65106.34827071998, 82986.6676218904, 22295.42391736542, 32765.640074551415, 30695.386767106214, 923.0582270340682, 55251.11157746514, 76226.39788720699, 51695.96593899371, 47456.12539654726, 38746.10267048119, 2148.3294210163704, 5838.687306972734, 94252.60797364218, 66798.6248906775, 46023.719518050806, 42177.11516928569, 61830.746177279296, 37790.13197118341, 38626.06112193084, 69795.986106401, 33912.072682874095, 92485.01283381389, 53014.61818112053, 36293.56647502806, 4547.528008128621, 92971.40374674313]} +{"id": 1858, "vector": [93449.6810560467, 35317.12488910913, 47243.687956765534, 96983.97061830237, 21637.561899714776, 5454.4960566680675, 56245.954886131934, 10485.814938410987, 47249.127074063224, 85509.62452003827, 12461.568948129508, 72623.6709401138, 14251.162324434075, 73203.79336519547, 40446.21761517463, 4484.278974766931, 87958.33075745871, 73034.49119094253, 52759.334552191525, 28774.59251862994, 50424.64251365276, 80214.34436284237, 31474.487593782498, 10783.600131433568, 64216.96736400668, 53355.12980029278, 14354.426628164285, 97307.12903102425, 32955.3799427842, 25070.84756030061, 23007.757358323444, 10409.710271141548, 7291.168681743632, 75198.94586516249, 80823.31816841307, 81497.76853314281, 23209.287816426437, 19642.844532605042, 16431.578670767376, 26419.392448104627, 13591.773856624079, 99480.91843217639, 67089.26953732966, 14655.711625494516, 76575.49006318506, 91020.092489053, 54156.52739681012, 89572.44098563347, 95683.73575577514, 19721.692963605365, 66376.279435733, 96868.33948734, 61392.85835829793, 21789.51947471951, 70033.84475254407, 40101.244593277304, 30870.02797980929, 96896.6202340675, 91425.77881943353, 9616.163135602317, 99049.08157434383, 7713.365595412803, 22095.99624960257, 76666.40387088865, 95864.37348361028, 48457.175119784704, 74231.13346766334, 35017.63637161873, 75867.48653006315, 37723.17468517688, 663.0904487144318, 42324.50323084752, 86501.37371924758, 84359.87356372914, 76971.11641465404, 36129.26722086669, 50576.33877097555, 48922.300091296725, 30039.454437002212, 92172.98005134951, 45577.751386353906, 9309.989813589658, 51629.097899826906, 15498.096497613067, 88113.132334337, 5375.751157992026, 18302.0998383031, 50928.83112020104, 19909.528575623237, 78863.76661010171, 89033.37208905588, 99948.02331768918, 6320.672913578973, 78445.46384172102, 66793.58474415146, 48290.76728589554, 14332.884186700889, 96353.58323726607, 5075.7582690827085, 85587.33202819471, 52899.18423983926, 66775.96522020768, 30917.917034594742, 38879.92421180842, 61485.283158069535, 21609.996915158612, 77340.53137653557, 75185.58460315289, 56460.351779815595, 43035.0522070552, 67024.56737902744, 33837.92242003625, 18977.649939266237, 86835.67080912812, 3365.8468107157446, 10924.312895143918, 57995.31488061054, 41612.833586632805, 38215.79672354103, 63387.36812871641, 62680.28955645153, 83814.88863504629, 20683.807625603946, 96281.63627504489, 37718.44409873441, 20999.035049156002, 48435.06633059352, 68539.35586407458]} +{"id": 1217, "vector": [82198.6244056402, 30185.722011690097, 92332.6897543378, 56905.31935976379, 39742.65389669068, 12156.648423646366, 34801.89894955827, 42924.99642757053, 90000.57075295752, 43475.71672866958, 77989.87142400962, 77113.11275928111, 21703.17541249127, 72301.97886634401, 15079.908943012144, 2128.0584459360207, 66195.78874372723, 41711.984620352116, 51542.943005513174, 54746.22398659038, 142.04354559601563, 56316.943591697025, 65379.35270457601, 50346.59371381768, 26226.00564447223, 38975.859004595615, 44323.10896226693, 18788.75979357171, 42431.25373115027, 35732.878202981134, 79172.62896065513, 40057.68020578254, 50753.287331588435, 52571.82217234192, 34245.911555010454, 85943.08075052108, 33985.36530980843, 92580.75998937317, 28539.189859458937, 63698.11063946244, 16346.019739581674, 92931.76037606905, 30160.630093066353, 62646.166865045896, 40022.9780865484, 15456.474441475466, 64690.91385460382, 38133.40277409384, 43713.39605647787, 95612.37827463685, 11836.096464837865, 18841.0298956073, 61874.064657686424, 63453.80755193584, 15314.351572853391, 8981.916869646522, 84084.81434378061, 50104.8514100627, 15802.470982673743, 70859.12329167941, 10694.7214888091, 47051.01894481912, 55504.908193124604, 65562.36598141266, 98724.03696300088, 94616.98661483274, 14436.761155483335, 110.06890634163157, 9786.703041059174, 99714.72051103057, 9892.131163978924, 42934.24924335605, 92664.74776318522, 88218.25118452632, 84996.95085235764, 53342.64492550437, 2139.0623358403027, 4789.400940129729, 78402.54497682408, 20847.502420153873, 64016.85859161999, 90134.75922603285, 47241.62320887618, 78621.56046969196, 60878.2812592222, 53187.698352576095, 53145.23309379434, 39810.607231537484, 14768.4996605706, 91225.3964203242, 84327.16408402451, 57804.3040181095, 26193.68762974512, 32815.04100437598, 4247.538290859609, 52927.58303536429, 93023.91208123593, 58279.69002762764, 21295.426985753853, 26549.267853627967, 39983.02991053306, 28828.981500035778, 57678.65538465874, 66010.08051541794, 21529.83121552502, 64549.869205616305, 1757.6035664562228, 51325.468027187984, 52337.696081126116, 88122.19185078019, 89341.56089973185, 84243.7059626731, 64284.25201082287, 97043.1846146482, 4156.697602711468, 12301.732393950748, 41600.597343881105, 79388.54013223364, 59804.25280295729, 49262.47883356842, 90568.13595964687, 23977.29285204714, 74280.16094020211, 78932.70970437495, 88474.96891876297, 27860.750835894054, 63112.18676363647, 77620.17858731557]} +{"id": 320, "vector": [47255.84716759731, 50958.31597607002, 98278.89642636488, 6914.620378903324, 27729.993334049785, 50163.023315850405, 65508.96486797886, 67145.34136794659, 15613.74410834011, 3831.9741631269276, 97562.23863016083, 67840.01383268832, 56688.98965916175, 41268.268330469946, 14118.265398994201, 52818.88507464526, 83353.05199198767, 78636.10280406, 15311.267291183773, 91239.0992946151, 98066.76751240373, 10314.35797658734, 69973.18060895018, 16467.435736382362, 50765.62754237862, 67515.1359327428, 95942.20494339657, 54909.82021571475, 16745.44166891876, 93749.55927070249, 12577.341383099738, 30400.338328384867, 81913.66762208032, 31915.319430357446, 54358.886100562086, 49743.580976902456, 80566.7171937656, 39510.11487611573, 42957.077237780984, 13556.257240606428, 84904.6837120294, 50895.31640368276, 30608.78106731132, 47603.47087835548, 30140.160168467235, 89197.97025457046, 58414.55291444707, 71198.23576797366, 27566.443633898452, 86443.80885088236, 40912.55267549342, 41437.807478287104, 31968.16355747385, 88392.24094561815, 61295.50775662206, 5133.132237151383, 2370.1693169215, 85797.95884679272, 36142.57379370973, 52121.40271778498, 10949.607055093591, 24555.428425748836, 91821.20813596218, 67149.32140906282, 20778.749597188096, 99292.60343223874, 32925.21487568634, 1552.6707368870962, 60448.46190643268, 13789.407024496692, 60234.09326550361, 15910.719922365179, 58287.98327572014, 82956.20944271002, 34234.420404700104, 30913.173520614546, 7426.240359074132, 33127.72142660473, 51215.48618554168, 12044.864540669032, 7338.383758231426, 50274.58364780485, 85230.78152031556, 15204.508001092909, 27748.78738095394, 60019.86305579052, 63781.466412954826, 89130.64058298405, 47633.963215879405, 99915.53650098984, 33987.14497332484, 17036.642694265804, 14907.546082041223, 56388.79022584189, 63800.63014598888, 28652.085097787894, 74509.88324668186, 92273.10579785856, 65274.16027758817, 89546.44867273283, 62049.14598689454, 19917.784465817145, 81565.14222050676, 83334.85622684742, 74719.30713474382, 43140.47839004751, 24894.753494215372, 86055.78727135365, 43342.88260602118, 55920.077336136186, 73105.16729499993, 99898.80123131759, 33316.21145324862, 68131.68689014982, 87293.86348460434, 83891.02554684904, 29077.71616684022, 46810.48274755044, 54264.658025973346, 74341.20571254188, 14071.775012102373, 44717.321574665555, 67883.48803097551, 97877.13383142401, 45162.325178962856, 28894.30140030457, 26504.86392207847, 40072.50615161386]} +{"id": 439, "vector": [2621.7827928711636, 76291.96051859592, 61394.37055351169, 7921.411997281047, 43048.68035706499, 98065.80458566609, 98175.765349504, 96333.07197788447, 78889.17275233181, 7612.515583649182, 57887.872156425816, 39644.87258666955, 53371.410925932636, 63962.22923186844, 98867.89967064165, 59853.835626365806, 73315.09532563391, 90107.83854399752, 36971.55186235845, 99835.9247585256, 29146.58085951354, 60721.86398461826, 56123.30276751151, 54480.53865793925, 15427.179566056926, 74820.2565461301, 34451.91108457916, 79439.71745728016, 68202.91619566301, 67938.31665989234, 75843.66797326642, 90591.6181645202, 30505.65099836512, 21581.13099214537, 83943.36541878547, 85036.91863539917, 38640.356983635706, 87333.24712217438, 96262.00528217033, 22440.103524339327, 83458.16975736634, 68265.13970715074, 43526.17094055409, 26585.912057049245, 51332.34941790849, 96018.97752249544, 27336.21758900341, 25635.867823662116, 71920.16617052826, 36429.19813885127, 32977.614435871124, 70564.61960577582, 19438.83753407786, 54520.44436638138, 84879.38489354124, 97684.39891548804, 8812.689619667657, 76488.3992916194, 44376.124483854015, 12553.295981337054, 75090.1537057303, 94230.60242142667, 62700.4160318383, 33551.98356941309, 76553.28989686932, 93774.17508484276, 87426.18659082729, 72322.4548942641, 22532.058478170493, 95086.01643877097, 49869.76455997737, 67535.7935293932, 16850.253569397235, 15185.709617115928, 95893.84757666604, 19526.35208677477, 31764.383002579933, 92092.1577509891, 48614.308704756215, 9763.35811731791, 68480.34952854739, 97357.34114662597, 78479.39173398624, 73661.6316439046, 69255.499274073, 69985.70990086961, 22933.733505901742, 58523.5045369765, 50124.30272968579, 5291.940715813926, 60191.596233995195, 35758.52038254488, 81251.2442583754, 46599.950144550174, 88810.93315631284, 53887.64483410721, 21298.025666876452, 21189.524868730536, 19373.103699127703, 85458.6180385312, 33099.729375707335, 79205.22102973766, 41584.24240915246, 88157.83229103619, 35190.343218253074, 56272.56463749833, 93642.77801372358, 39732.69458411897, 13896.320437295972, 44845.81351084117, 62824.82235920791, 17181.619064256414, 10183.408606247202, 34319.155841237305, 74680.95169936224, 20885.288403885148, 53612.59620107717, 35009.97635331734, 13062.029019419153, 33094.946774710574, 98130.28583663734, 63461.24647166213, 12334.784126242826, 23174.372346735217, 32125.34358142115, 9025.216111185619, 91016.33207594436, 14560.12807337641]} +{"id": 1855, "vector": [40695.82995097486, 28738.351773406546, 76505.8131667328, 6790.874099903632, 15714.869357205818, 23065.881025769108, 66038.35995714819, 36862.20146441659, 48850.974136335004, 8350.815799669708, 59461.98008677815, 77279.63213692578, 72367.23218164477, 18599.048161208688, 18639.549253966838, 43381.4011506168, 40413.03566656503, 24506.604495947427, 68732.4530858657, 1315.309620698446, 3928.9708540961365, 15229.191242207851, 66186.33075126518, 20073.063768075895, 89473.16897092237, 68630.93280166775, 75893.36546330673, 71268.29717885786, 99662.63762253064, 25612.36275779387, 58960.57174783992, 74918.10304616575, 67684.02024337587, 441.94440555281875, 88442.03611767641, 69201.15605931169, 1602.0716263278168, 17869.689769085362, 81841.82241456282, 42930.045936795555, 5730.417542856636, 92757.94958001038, 44478.04645106097, 35920.61610045189, 83070.84791365651, 60429.60189302397, 91040.43414985396, 19846.13424390277, 95592.98649729553, 32323.624236707783, 19598.68389956365, 74222.21706368521, 4630.663747232611, 30286.06951291508, 63066.45778243171, 1324.4849850379214, 91079.53344896957, 41797.3953630857, 67021.80953647177, 36788.495334917556, 35860.862294093255, 69517.67765831717, 77322.75069937114, 28272.104849616397, 8008.969208426509, 36653.33719205882, 90.95728951350468, 62930.234661329785, 4212.850204453466, 58063.80361858372, 46652.9829959898, 73062.37836288355, 13364.886608173898, 27729.514441772506, 51056.43978362437, 31363.765815275292, 94767.46165815517, 75685.22959326977, 95153.13237416295, 38145.05486704933, 3500.456039672195, 64606.93983467675, 46718.57117748204, 93681.6142275268, 48999.24751874429, 44248.234870370914, 76688.9634269328, 68691.88585607137, 68221.12558135157, 7074.995382955285, 97050.86916350656, 36418.071135696795, 53959.40282174576, 14428.468754485513, 71320.64601933055, 74877.22963895276, 38724.43935427926, 83410.4317414703, 86306.18673850516, 75801.51838696652, 46373.88394098025, 91310.79898327238, 60468.03130815432, 6113.205902667085, 440.36419087349896, 77060.02874079009, 30891.971839278976, 34842.10156495387, 67293.62389498141, 98639.26613184247, 49205.027943349145, 23180.91893611611, 18701.89233860201, 18876.421044463354, 31342.17101967741, 61603.70497079599, 82529.34091450665, 4114.129137371902, 86614.38345694657, 66949.71272179305, 57574.9465571748, 15461.19935319743, 3685.802030552576, 14471.31155343201, 59153.44801018777, 21656.06008950679, 91874.12916328305, 73491.2794898042]} +{"id": 263, "vector": [11943.623414653115, 10681.173924281606, 67474.74220886445, 60049.07460725485, 1162.9162058551135, 75893.90671892822, 9401.230431010754, 59976.89962885844, 31856.516515841628, 79834.54562879962, 4057.701033602601, 64417.03512633476, 49324.175159174345, 1923.0107160274733, 26227.257271676597, 56302.1447944739, 82249.00122709095, 11829.019350148285, 32534.766615858945, 4699.3165833749235, 19457.174576034187, 95891.02156429025, 79175.37797627934, 44958.59582546092, 64389.68386428114, 24941.759228975458, 50855.66160750377, 21586.40657703853, 44380.4709941995, 28224.22786968206, 72219.14924362257, 36119.80499229658, 25541.542489863823, 7995.450305893548, 96857.38882658724, 31628.949425677278, 22852.84538324752, 40538.194189487534, 40110.65737501537, 4840.463829845298, 77671.89264444407, 23473.457802262878, 2370.4330859834254, 73518.3159045255, 65344.95092019367, 94685.91580761435, 39870.83865442271, 31986.59516534139, 65384.284875166435, 78332.43394837837, 75122.01486129856, 75228.48172965623, 32069.618240162923, 70542.80236784677, 52773.033524353144, 59137.54680468506, 73043.89137562092, 43237.99578164902, 90262.66583707544, 15604.596131138149, 67705.11037402804, 91687.90853462052, 42173.29349894041, 76996.8488893618, 85544.5898962656, 78246.33279145412, 42435.59923277083, 62315.138444410404, 17500.088679520464, 93286.36305339269, 18785.286150388114, 42620.12161606765, 1636.6673963485234, 63618.812791193304, 4456.360331141529, 71448.71379157089, 94390.67517171768, 22764.89169748156, 81398.26486524724, 11948.558706450196, 54106.94319749477, 87316.46433959386, 12238.342939394519, 57961.17987405603, 32524.488570570644, 71620.89148861654, 36700.35663091405, 68293.68908900225, 62843.130582567916, 95794.07699375374, 49176.29626235816, 30847.37236215649, 86421.84360176565, 56871.8625360602, 71798.62500875173, 11207.093349885066, 99280.25600937431, 97467.60786542627, 92462.20019101053, 82501.83387033963, 36391.26896741701, 32086.872309133498, 66406.62666402035, 53531.02852604783, 47129.89301495089, 1631.2959078009003, 68710.26180710313, 73661.45855018722, 8477.31464206527, 78519.9171776282, 30937.91148695859, 18466.752359325368, 96295.66839021328, 13615.42154501536, 64776.418076722606, 87564.51351777071, 73611.71360803231, 67510.57856273829, 74594.79363571013, 30076.38342039496, 77204.81827945904, 38063.42283191697, 49941.4843121349, 91222.57409871943, 11644.611630860769, 35940.44536296369, 81388.60950618071, 11896.63243684056]} +{"id": 1405, "vector": [32478.892286259976, 78175.6535200114, 65688.32974026947, 54313.58607716902, 22.152606565550848, 80342.53016078995, 64358.84192860417, 718.6297835381828, 35550.38225028616, 70983.773893167, 6396.733944571453, 42429.74851716242, 32391.010325087176, 23840.471310119672, 59215.29490048609, 45419.862186598184, 83459.8161664843, 63870.6427819155, 61842.00824135073, 29842.811739762143, 70168.78201400455, 73770.24981262535, 76957.50069367295, 74316.48533553295, 16878.722052885918, 35451.22289004222, 88411.83833861079, 57096.856926606124, 2686.6539832810176, 54111.21782294614, 350.5404903951681, 34280.13515082813, 12379.553309889712, 57219.03589870912, 20180.923965089758, 26428.11109434262, 59109.910901925345, 70476.52209742571, 60752.449897066974, 55047.178812179096, 34664.529059370456, 85755.71448254997, 81750.44296365116, 52431.55377889838, 84956.65387495318, 66657.18810869672, 42884.795509078955, 70073.51540643073, 82114.49649961964, 26030.572360974493, 53606.877877909654, 24640.205073007226, 57855.227577464786, 16905.725883102674, 76696.36722048889, 90890.74018576581, 43786.869798735825, 79008.28050096284, 76628.61337532166, 69222.71398064661, 48488.61714975127, 66304.98837565254, 66263.80165936441, 37346.15672098684, 28867.168136977696, 1000.6490613907571, 18833.146644408836, 37036.34641339032, 86327.91522424704, 55447.939170961916, 33473.86869543678, 83656.39414457764, 22788.709916376083, 98062.37661649911, 64091.206113251734, 47750.654597811896, 81045.04101163654, 77454.51665623211, 55297.95890706268, 63924.075511749325, 72915.57088327977, 89633.49849325973, 976.6687203314417, 29234.143344521213, 60748.12133935711, 50474.27464811606, 41557.20415820556, 31637.41353854659, 84411.00136214646, 64176.93830075852, 58918.186113775875, 65290.070368905384, 88735.42400790492, 81655.55894552408, 25052.280461343646, 48101.79624018145, 30396.6769526415, 79481.39635207472, 71525.84060457014, 48981.09974208879, 94196.68678161933, 254.157062547411, 71868.76664701084, 4573.9091755576, 39339.951403076666, 1594.3818061181391, 93663.26100424686, 84127.70149733635, 47965.24875009398, 69596.69732767381, 45970.52454534291, 69796.03227645151, 95657.1606630525, 95781.17463452964, 36956.22741570362, 55211.58495108274, 30633.856838672957, 92493.4230558624, 35831.38389299946, 4796.335001390206, 88462.73521930576, 16563.18931826899, 6036.201808803199, 57652.85385486357, 94559.20922775369, 9092.856334801147, 83892.26768249011, 225.3414894795913]} +{"id": 1096, "vector": [43788.02929963664, 69306.36259672107, 65375.15244901011, 24034.838298156104, 14061.866856689054, 64166.27705418446, 77996.27124443889, 15356.824893328157, 80051.61188693081, 26790.024173217396, 69755.90090944138, 17473.523615766262, 68506.25534645127, 89461.0789391565, 85586.48517768933, 1046.7095357400535, 42997.618155844895, 11919.70915606082, 66365.40888449384, 35677.475271383744, 43453.38986357953, 34312.14941058094, 62767.39281045117, 54402.62391961483, 6475.68654314985, 15132.18906004027, 88959.08537852383, 4663.267142058958, 11545.296408114958, 45694.455168111715, 75832.06017219191, 85772.12786695352, 47060.82459391013, 53439.50818770109, 75065.33409987763, 38533.53389509362, 31757.889164073506, 41559.48938365241, 35858.369099235664, 69613.95459261232, 72296.17960262914, 99628.20135902638, 18945.790788120397, 69714.49464129176, 29584.74392160939, 42272.87791301106, 18231.96221549821, 97143.07903042149, 96505.06350274297, 84350.28067762617, 43241.74244169619, 42200.2512990364, 27261.53315976011, 94452.49761258511, 83133.57933161178, 40152.03682961056, 61579.470620430664, 84803.22237236545, 84606.74581738042, 50884.895248512395, 66331.76287929367, 20779.073726076058, 55152.040098930775, 92312.13807713229, 15559.838105481094, 597.6740146693804, 62227.653325124134, 95750.4354681018, 39294.96936258312, 94117.96536317823, 16743.07527199732, 95410.24455612169, 28196.007064142203, 10620.087280932821, 84499.00822295665, 78169.37153152119, 93046.5769975171, 48564.25338658232, 12338.247062507224, 10297.82184358352, 52121.02755685396, 58587.81168384947, 81891.09762708907, 82197.13782699639, 27149.706336687108, 15027.279478255417, 22104.25496347086, 11723.848121710445, 22674.062040511635, 82804.7770619609, 88281.43983322084, 83975.07804848065, 7983.853283176645, 74472.08681453686, 31505.12190351349, 77779.50644923883, 41237.39126651839, 23293.01682295606, 32786.138018664344, 7592.4305453639045, 60230.72750803301, 49077.28560147555, 10636.231174773902, 48644.29238765412, 64572.78229820421, 67296.53166218214, 91845.31036146522, 60871.56675305273, 30421.24133027623, 90702.16330296792, 59524.28085870432, 88675.93960095428, 78271.54389176014, 63026.85884389635, 40910.10626780553, 79558.8421585935, 82360.50019349849, 51443.16179254385, 20278.861857669217, 74196.44039659046, 69240.15400230684, 47348.73388755004, 91727.03531467507, 81759.75833217616, 27980.814382588458, 316.91377731438706, 66400.69950985827, 63619.610746521226]} +{"id": 871, "vector": [46595.76218506289, 76923.28711767537, 69472.76200360355, 58462.16180441692, 52566.81305351469, 44193.23971785694, 62194.99401815304, 61023.01938234176, 4854.213932941964, 94676.81222932426, 89722.93807104357, 70541.10503766901, 64905.5953327349, 85678.12243368059, 45419.939678434304, 1287.650578519961, 82921.08390706549, 50338.42336303079, 25297.867471926736, 62609.567127830625, 19645.854426403297, 32615.541679099246, 66320.52175046613, 83535.8921976929, 2592.9255197791877, 17554.869514698425, 54592.10185153369, 2287.602915501141, 12085.124429668893, 152.4749584962626, 53177.568575156445, 35972.57206586806, 3500.8759091540887, 52870.03628305175, 27131.99428337716, 74881.81454695151, 74155.05509692195, 27679.919284821197, 35997.37750011971, 58654.067631799175, 93352.30055992641, 1856.6128674760485, 21380.735953184827, 1042.9676315497293, 57183.58964944741, 97249.24504455585, 62447.46872132981, 56899.330241478376, 4691.76555008316, 68914.29883642965, 21412.65369779959, 41585.43923657419, 21837.085947475964, 88132.51547312348, 18604.593481624554, 3027.655516795935, 40058.106278904146, 62433.8879801115, 80359.24647066445, 5164.915730234465, 2285.660515738752, 35446.72991919184, 83989.93329701923, 73651.80871329475, 27768.950263097937, 99649.69014389004, 29733.782612900406, 76598.58272516463, 40181.17986116126, 90764.387017674, 6072.11369893862, 82672.07298623609, 52442.38281740339, 85757.75090918256, 41648.34632922007, 21435.097210905707, 65400.93623360326, 57911.51670742375, 55281.202977393215, 59195.42152124999, 43717.841066504814, 72724.31294693494, 63448.76971771438, 96798.57849756401, 79899.27484878435, 69707.01172185483, 27633.060228266004, 21093.687305273324, 75082.08814622718, 68987.10101042919, 5993.4144121586705, 30073.581444943342, 93696.4507546369, 46772.016459990366, 12303.82296247341, 81769.02616733174, 17651.804421066852, 38748.80101345995, 68242.43403197602, 6437.340238686417, 85968.06370875702, 78357.3950807296, 36591.65568168561, 23529.25181810851, 2326.651487503706, 19114.910388651253, 17559.881228387807, 59724.67643684331, 67051.06913976479, 97753.09371886202, 1304.472956429037, 63195.97096753845, 14573.901416638058, 31642.798649120683, 96798.4862379626, 20990.092249008852, 92163.64703182383, 55452.47549295115, 93296.81004999223, 50320.09918730014, 60321.51503815841, 42864.01327556289, 96757.07279942915, 94017.00740449579, 97211.73477554056, 47405.94240704531, 63651.25454346664, 19934.995015905733]} +{"id": 688, "vector": [75787.35458973619, 75046.39776355997, 997.9132231258126, 26713.87174724281, 16155.275256784018, 17155.20410231942, 10453.931442603958, 41846.26522465141, 944.1579372020615, 95067.38440798045, 53835.57809868821, 19808.645189768824, 45213.60814097164, 28365.764359238576, 60630.5040410319, 98833.15716961448, 23316.377298653733, 51033.4746893696, 32912.64780662102, 75016.60181900016, 39499.573928936006, 56084.30585295967, 65202.88881393511, 84715.14235104843, 39890.97095958787, 4535.385654736812, 52839.361545204374, 56572.32381171459, 32376.945920567836, 13224.423071669777, 42647.67445308375, 35827.17984541014, 30909.703450637062, 93510.69675155434, 36058.516317482834, 47462.273277701395, 63822.267808177225, 23171.964154541758, 57349.920072259076, 93091.33088438422, 65817.91937831898, 76361.2109827239, 25212.507822633277, 14287.826562912942, 85784.60194431516, 55742.317614394444, 90998.92669932032, 63060.7093077413, 85155.75958588025, 30414.319858548522, 68523.89849902196, 81643.97504400129, 61547.84399724922, 23291.130149444507, 64256.595362184285, 68322.92665345423, 84030.19634834831, 9005.981725182222, 35845.39696154191, 91971.84308723973, 63171.67395494395, 47612.08529623128, 93816.02170393338, 25971.6919294843, 46951.443380699646, 26840.55313513529, 96259.47454288368, 53957.252794210486, 72545.27181674956, 40448.85356954952, 43873.18832891699, 62270.79029901994, 79902.42702711842, 79941.09464333588, 54903.176473260784, 73463.6201575564, 28251.987278922596, 58202.1701200684, 10403.57581587661, 66724.68598458297, 80312.17924340395, 22960.049293542626, 86991.85091658971, 63632.45476804485, 39715.18584106688, 89174.95691718324, 26559.573354473563, 29163.544890474514, 49267.93413751821, 53741.881734040035, 38248.94521567039, 77189.78898414898, 81967.27419923194, 99024.46755570211, 87310.65282116817, 52136.13482857329, 43187.370847605365, 1328.196696843309, 88031.89552223508, 77114.20186908844, 39469.93054868786, 44270.57959685132, 47221.69677513963, 32206.770968954213, 94021.82419673038, 80305.34362855549, 60845.42255385573, 78907.04267076636, 72.29172534073402, 4051.3532633340456, 24163.65059016129, 77217.65871024855, 84947.34209964814, 17208.681654996082, 42121.11100500053, 70820.56807992191, 82607.18351398897, 65653.38815994613, 57730.17646155737, 1868.210008666682, 59681.339600669606, 10911.497594347697, 10640.226483578452, 23795.479374251714, 46997.86299330072, 68511.80258645627, 25473.02165269786, 95803.21228154427]} +{"id": 246, "vector": [79980.09485734979, 32704.83558038255, 51762.68861805764, 52661.356687646185, 73773.53627246915, 9015.53666659618, 89647.8554884493, 2334.125152530153, 71689.01930427978, 72173.21641656997, 12128.16317265012, 19028.0890204347, 27594.684565928095, 88293.10449088702, 70174.66360013625, 9569.57775687841, 80938.61889007085, 20839.22542414489, 31891.61192064498, 98374.95220980284, 17335.608405236337, 32930.496922757804, 14665.095815062068, 17133.53170220391, 98035.68613094422, 22077.504995929554, 10114.78893853046, 84388.86044191774, 71024.23972273066, 96725.64536774675, 43865.33708397998, 6091.927334162006, 44153.06626271186, 55077.8663054138, 97477.47865879856, 7309.713742578261, 48049.42768900956, 5992.519837667076, 17539.724086340048, 58415.6138040892, 49576.85804199203, 10144.436312458427, 13214.041521566443, 10856.636568950395, 45327.35508369399, 39199.910598324204, 966.7554383285037, 37652.17441060966, 89795.31918121921, 73860.05751181104, 72545.9606801863, 51633.42177858525, 18759.44342095599, 34126.52645021575, 53197.14249592613, 74323.18270256331, 44385.913844128336, 13030.815962887244, 38546.08082562183, 79768.64649060127, 56261.74136737602, 37940.69474666061, 53410.013062481965, 42799.66019591551, 89146.84573616527, 81595.0986687062, 92743.15585730546, 62496.9210077699, 25265.39358251989, 72115.26717735917, 58447.699785960605, 35793.81223708309, 59389.486161577974, 39615.58976286717, 82539.90568080817, 18333.215949655358, 76534.89818713932, 95604.42054866519, 96433.39869422268, 50019.07353747499, 37886.47855178432, 79672.0040335211, 96593.65243386134, 35779.56028241891, 81246.98760558227, 62682.78193217922, 80192.07183741507, 21979.406689120817, 88639.25845671383, 62214.284921359606, 38348.07863423407, 76361.66688630127, 6187.664456918574, 75563.37137435938, 50874.60573248592, 70193.35380101275, 94510.41780573643, 96644.60347505202, 8672.413868714479, 78680.55288965612, 5112.452800006395, 69722.03056320791, 8276.059917548639, 38785.79729792688, 79526.04512583307, 24599.76011492413, 42132.117096736976, 5807.241051472911, 51933.903414068016, 81315.32315454393, 70588.83217322925, 94821.9395942024, 60278.546114361096, 46469.985292054116, 50522.87298553887, 26518.45170218703, 46680.2687483336, 40643.989117908066, 79184.14671106247, 26854.142674887582, 89628.75225370955, 94894.56973820232, 77825.68617627322, 44185.33736326149, 14251.866656117574, 4620.198590177582, 55605.922491179204, 52054.867955200825]} +{"id": 883, "vector": [59185.98264935488, 40258.98183897513, 65768.63315738503, 18260.758472473783, 20373.954015995034, 58640.72958430609, 69629.44477071974, 7844.964202010629, 13921.549692184088, 46546.61634128592, 48864.800058142966, 88197.82117252199, 87776.97048469407, 85723.88896890229, 99905.97805466336, 21295.81982880433, 86716.57883014911, 38415.9739791293, 94541.23719609168, 72841.15240352895, 59971.56961230693, 84685.44269252007, 42032.055547682445, 60457.62902840644, 29504.736235394168, 30325.891961236197, 69273.67271097438, 52129.551086522144, 35352.911641547194, 70659.21880052789, 53386.63570399995, 79204.74131744796, 39253.70938510075, 63773.56171147468, 58993.07979797176, 81199.62638261128, 30790.978695524464, 98776.20197551681, 9616.61210617467, 10750.39631740049, 77847.01524189068, 7762.857624867603, 74695.14287622529, 46203.71216945278, 79393.19062325309, 96689.84262004799, 91973.55426370646, 16053.262363480037, 66211.90302209885, 78847.86724388052, 90209.46971141107, 59297.561970261115, 99925.18148772545, 90387.33930782547, 4926.065098192967, 33983.687621530255, 14296.453002018328, 17864.86419225346, 93001.98424357631, 40643.399360304924, 10924.173588521058, 87332.8709264064, 95522.39756337216, 12154.520327552587, 48810.207957446946, 62218.07424364107, 99321.00696013837, 15183.88804405648, 31035.28999146119, 34090.983546904194, 79192.289036802, 39663.986913260895, 9278.593045646321, 27034.15382480805, 65914.32579726861, 33856.95944383169, 29325.31495998969, 1991.0340685833082, 17754.400789722447, 44975.45907426138, 42414.83050311323, 47366.17188648599, 31394.926242905396, 67889.2817502297, 21130.45876895414, 58584.50319566564, 31664.541628200892, 93194.72806268252, 48629.00174670246, 32021.52933049186, 15905.6363039909, 57482.13320059075, 41942.99164732823, 73492.43914779586, 20178.20669523278, 52954.86968359517, 57477.53773965956, 91017.12466044178, 76769.3512002486, 33354.015698490315, 56093.97910626206, 60827.982546211126, 69759.29243028084, 59155.43284577859, 7777.908772852194, 69673.15303651946, 22629.811116009336, 17438.472329929435, 78187.85172487216, 82226.22609365052, 44623.21729433362, 44851.7759173241, 80091.41618964945, 98004.81177033413, 39302.16467660708, 36487.90069664549, 59050.40355451061, 31950.541407012322, 27776.663758611263, 35229.47537017183, 61600.02234311584, 95111.10331634474, 20650.039124218965, 35846.79079700149, 81894.05659043397, 61982.856161323885, 2732.952867421623, 6544.722634881173]} +{"id": 931, "vector": [63909.82366229777, 51842.08355160951, 39423.488959010625, 83492.93407536323, 2068.8307333266653, 81838.23653595346, 21668.59237486156, 49718.618990796946, 78621.54793288742, 54917.05959714138, 87346.03313468752, 5127.9170687429705, 39744.9641364768, 45936.81501545234, 17688.626147840092, 54025.24470918542, 23751.456241700107, 15685.546117254335, 85506.17626598479, 93535.23160119265, 21609.561532738277, 75413.5028196029, 49238.9845239791, 34824.72841699996, 65587.0025747581, 32929.46643575955, 26581.71879111103, 5360.66194122764, 36015.494189992925, 73784.25232020905, 30392.66621536294, 83772.28995628639, 59292.63806194206, 39191.18282818577, 45302.791207202776, 84639.79443703046, 92383.05623524645, 19767.564333473187, 51883.58344079625, 26869.230405914324, 12239.003396045267, 2072.9167415626025, 86281.64182170444, 48329.954455174586, 43714.203419283745, 67118.13500500766, 92662.97746368512, 93519.43125007617, 88033.25951609323, 57269.91416368675, 60470.31482714873, 74012.58793151587, 59207.941344280065, 59780.87487058933, 57558.596809146045, 50933.44168299919, 67504.70368214954, 27337.331281900788, 65836.87061737372, 9013.628994960054, 77459.41244228325, 37388.82036591315, 43844.91419004822, 86180.96916970091, 42550.840515794254, 89012.82253621695, 13606.913303970092, 77910.53847051992, 86436.47546831699, 98972.97031457366, 23494.646216138106, 64295.49677994758, 48096.90863935756, 34736.7110962236, 62860.008211134664, 44054.538025479706, 55977.40092383976, 33402.860657954894, 82138.85679554995, 69879.4232015811, 37920.59553821795, 81383.05774514981, 76071.77466710315, 91285.83169794145, 17417.24018954345, 84310.25607963222, 29985.072457735805, 32665.599606287455, 61998.37620014923, 42142.18154609678, 60717.21837618236, 74772.49847907676, 34424.34153190621, 53588.81957269534, 57473.78460010393, 27486.11069451534, 96668.38034770975, 64840.74951180521, 90716.7394382739, 68208.09896144793, 32157.52176314983, 99816.29904323604, 37787.29836823311, 45489.56246898329, 2121.550641658232, 67237.90587545752, 63502.77879190239, 21543.90389267684, 20335.583177225548, 85901.88229361478, 45311.466282505164, 50592.77865128412, 71953.38235137338, 91287.64729189649, 69583.03374836108, 6613.443848335465, 33350.042855469816, 56896.649884562314, 79862.86593733374, 37678.21946607408, 71305.46252180882, 46031.22681472202, 86279.19950667635, 91188.37037831891, 75039.36531037213, 24882.51719190656, 8832.191272274325, 21886.369577347985]} +{"id": 886, "vector": [47881.56153793077, 11595.15861240359, 60351.479473584324, 90059.51436422314, 14962.698471650925, 1849.6350054121092, 10443.625557472125, 18597.024514008055, 92678.5464792297, 59693.84017509894, 29644.28680324668, 67947.54327902249, 62965.65540111776, 12610.655563779615, 47311.99853090525, 43552.99432796699, 45422.24894537406, 66499.50772312506, 77145.35958851046, 6601.835654154819, 47053.642667072105, 9952.545379648082, 11272.47099709835, 7476.8948282408655, 22107.613367457, 17045.687942153232, 44769.64698164084, 66954.20645753964, 69281.50438916728, 40658.2215229594, 30910.078079795556, 99346.1846267633, 40559.28959565582, 36982.5114480562, 56876.21369346146, 76993.55584983766, 14574.648549157342, 74267.07372704403, 1022.23658222097, 8524.810429367135, 97308.75483642168, 97786.52431669056, 70872.7284541273, 19789.453222746335, 40834.434137612974, 36175.72399061414, 55331.46308301243, 13438.427533613029, 26174.23387990159, 48178.60257080374, 9895.136441654107, 15472.26238097974, 61412.22009386672, 51197.56117856115, 43362.788142831996, 20788.39694012432, 33438.190574586326, 42155.09572076446, 53066.0646374714, 11883.25321839413, 84427.1417188772, 3080.6882147279666, 78049.37457029801, 21229.539146000443, 86902.88265393085, 10850.667525850044, 72647.74983716873, 66355.19116385032, 76573.7974833086, 16878.645422210437, 79933.44341821621, 7101.911792157667, 82078.48841107498, 40691.356943958766, 83638.89817827154, 72074.29411849174, 31704.667684006705, 51902.780547114366, 59270.967783578744, 14183.047832437935, 62299.91346139949, 62745.114993518844, 94575.56346072962, 93367.61945574378, 57015.976854307104, 30048.59546375881, 18058.72207203919, 61261.30035887848, 90456.30128628458, 96682.35907421139, 63362.583040577236, 69675.19562562872, 60731.5680442221, 63954.84855236771, 34557.480035717614, 67823.02904972872, 36378.80047188675, 37516.397009636305, 79551.83788614169, 56261.703288431556, 5485.986255708086, 28052.13495952579, 34818.24186715495, 29629.311279562564, 21483.218280923043, 25346.541652122367, 11120.83547520839, 79133.61029937274, 63696.43111377096, 23052.93761017204, 30267.025076848386, 34639.37123447509, 77968.38300288575, 10459.226857263171, 51701.73257365402, 88692.10745152016, 61797.27702012867, 73350.15008284592, 90462.69354133192, 58784.589005063026, 25376.989593443534, 48926.82301038464, 33203.6758026628, 8863.694522052356, 60140.426270357115, 20714.17024664981, 63316.00049438841, 44284.35007309838]} +{"id": 581, "vector": [11929.322835168032, 56816.693242807756, 40161.67748792609, 44529.176415358874, 77626.9969204732, 41644.48125352118, 60220.92141545747, 64706.16504091294, 89784.5209091889, 77765.77122605329, 90943.89399521273, 19244.894715160597, 65470.52459744165, 47410.29425059225, 42271.27971629007, 91218.25178536578, 23787.561535189605, 65964.56668361547, 66893.0519938704, 76962.2929806287, 43682.01204741146, 36879.08113939964, 34127.880200565676, 76864.38323658874, 72870.47378571247, 98109.51225372887, 39728.05728558566, 7418.657891590042, 22317.96094426929, 33756.87567423361, 13680.594317492434, 52943.23325032915, 46904.32325883327, 73230.35262103358, 45029.368387154, 69716.67639707583, 72927.74999514768, 53704.83609270042, 48031.267770563536, 58585.09007610285, 55651.79810879457, 8773.879032472387, 60827.01201671091, 89316.47134187206, 449.8006805997101, 68565.20747688074, 26825.922893403644, 40201.38848193294, 88141.39063326296, 29752.38465790958, 6610.573296371458, 81362.63411145807, 15995.280749622698, 23903.51989245104, 77990.57032255955, 61120.27655304163, 50563.99683139237, 97625.56398676096, 91692.36385194544, 5854.35776474763, 97186.94269615899, 77026.99696873369, 37855.87238855485, 80717.75931584276, 22645.3562070556, 53265.749523941166, 35595.42349968219, 46646.52873109427, 61890.6526963148, 73757.35713170674, 90549.68346962091, 32324.407098085772, 15639.733536231959, 70628.44435686692, 72999.48155311424, 89962.1149286289, 43550.662522318904, 74575.1016968149, 64222.79995741004, 61384.25390522467, 89300.85733257922, 64619.18769441843, 84898.41560203551, 57808.86852700956, 57256.4401340764, 58522.742844055894, 75495.62936497123, 83817.94370598659, 34645.54198556104, 95166.93229892729, 52375.79701583316, 88198.83571071128, 46029.80629333277, 44073.85371962702, 28252.979963437607, 53328.08026239486, 53134.29820196365, 88929.35347403624, 62304.12565387628, 95479.5961602131, 17955.97578229865, 54000.69220973482, 55254.35793244983, 63493.30685900156, 48158.77413012035, 80852.3861811213, 43881.30566776242, 75157.12927429007, 15019.861738810636, 94512.14908572994, 89582.64385748483, 69515.3697078792, 61256.2569864873, 27705.730919410522, 68047.51207310501, 45256.257351172404, 57002.75491545972, 57716.542703768806, 50696.670260857325, 87578.03154602244, 6531.922150529846, 1431.0232890812458, 5780.481956116323, 2561.4473587918574, 46908.48886532206, 12851.750453892351, 50243.22738869324, 12071.243722887026]} +{"id": 1223, "vector": [26110.617975714624, 51547.68992634822, 71467.63250303805, 33323.5197043613, 35569.4894663225, 76629.35413144076, 81804.32411975798, 30966.326519100894, 88759.2574115476, 91561.01390991472, 17545.08470813939, 50388.758501607386, 66118.98791927587, 91603.49696721451, 47648.14388825151, 2304.2115642854633, 46637.7083452026, 49113.97182107964, 22649.01127187501, 62016.48548607681, 15768.985752952336, 50564.811925561895, 292.56166054537624, 38132.52263027025, 38804.22480492767, 16051.97276129977, 57553.930084928405, 5024.262687844927, 43598.609169952564, 35940.41983899234, 65812.72884478369, 22987.002523218747, 60796.54801306224, 59488.04064248801, 41720.04852081057, 94565.49680861966, 21666.36904067082, 846.2510060232021, 87640.77157056596, 3909.578144995829, 14978.753390497845, 71397.40239613173, 9298.049344848825, 97755.58763704891, 88191.54699053799, 98594.54282307248, 58497.394818287874, 28419.38480527996, 66521.59783493789, 57622.69368060705, 64173.248688925865, 54958.97563955764, 96889.85152866905, 76639.29429080087, 12977.06061044135, 7460.463694642483, 13984.17750161942, 24604.324594014128, 78237.68942653587, 99174.24084438109, 40971.73558630711, 81942.33844399067, 11671.229840489006, 96930.72249077781, 70203.47490472884, 25820.058582485228, 89091.93446172061, 49681.707305019096, 95990.2613929009, 10412.693972651998, 24725.497093654958, 80944.87596064826, 96139.37933289216, 35576.650235686015, 74685.86334479079, 55964.30300986687, 85202.37260532446, 92471.81757359153, 23162.62128174389, 66156.13498466687, 16785.730773300565, 60766.754392946306, 15073.855376346102, 63957.988630589934, 17799.82455741207, 49854.65563293849, 11524.997550415872, 46489.80776456923, 83118.0161468038, 14357.265425040177, 9371.975850668756, 1749.4651717858467, 4950.424089279126, 84219.99976303193, 23478.44378992654, 87003.69947461752, 59630.78227476862, 75612.49011500862, 80279.9910832192, 57433.54460934511, 7685.099779417504, 12207.105317038291, 64306.359547690474, 18288.06737743476, 67090.62866263819, 95345.70587549524, 70924.04819283857, 34332.68928614982, 31785.739087642283, 45722.11871364512, 89716.1090858281, 82954.4707543541, 64237.803503951574, 70493.00015894507, 75014.5784465273, 75063.50025597106, 12603.7986303829, 48653.98424898827, 24896.94661384976, 92015.81259577982, 81466.02637485505, 31234.486428505148, 69722.91590811533, 84616.1569904671, 16408.821703553556, 2901.4171138598945, 55742.272744660615, 42880.55655172635]} +{"id": 892, "vector": [1994.689215648604, 62978.260252578, 23244.943593079835, 11619.687772000298, 65847.64117386944, 23265.404413042645, 35207.525223721335, 10430.297725873128, 87900.5973408705, 85510.24528522717, 18721.82979639566, 5609.50124188283, 5740.050274302466, 25889.03527545293, 22787.405138291895, 12257.778134081122, 41148.96107670825, 94879.51726928772, 57916.45731761659, 60429.11657670863, 51814.06013403551, 9987.663539449099, 11939.450581148114, 97733.1745145188, 62045.474219628806, 1070.1214088797096, 52964.55113840948, 78377.56811865086, 1723.3117156938426, 97870.45580959892, 48164.51989231083, 6738.475609420136, 68567.06162795467, 33096.0413740279, 29165.378209337534, 57027.881493183275, 33488.57633298562, 79882.71541910425, 36179.59207386801, 2725.3520648367726, 96136.82869517434, 6284.598616629744, 12462.653402597145, 36332.94754195197, 82328.91304904677, 38102.71597959727, 55895.88480145218, 35698.347835596986, 678.8368798153144, 42247.76248296215, 37343.61118851871, 65131.21662823361, 28894.08049721949, 75162.23223703165, 73466.99063498981, 63117.80080544279, 82114.01226014721, 13260.827863461755, 22708.981639242054, 74503.06559994337, 66883.79715299004, 75885.30103154057, 46141.1402111065, 588.4579628588682, 11958.702787712817, 54525.89435756634, 81243.03277377758, 57543.61859755505, 31851.424847893817, 59195.78459155443, 19810.373880678046, 58476.895818101846, 74254.70912695138, 82310.90507038988, 28302.5289988869, 32046.813218886404, 22813.097809443483, 14120.704706523413, 19942.794607723623, 73799.22847065824, 97500.02293128285, 24145.09731351514, 84740.35459549059, 62749.972079887986, 37758.972862137096, 71416.6378212626, 42504.816426617734, 69876.24699945685, 36190.18331109686, 51197.6306890382, 81093.4838268885, 33287.60726308308, 47252.58887484198, 24498.395660687722, 48835.29877840217, 32721.85389103891, 27388.432575120303, 32883.63990482498, 22028.54592440301, 72218.1980701772, 12631.554243197885, 99415.50645515177, 58073.25837637424, 20260.32753542247, 68559.69068303557, 81768.09334441376, 87021.27688331029, 94331.01312928373, 76148.59310665129, 85910.60762198736, 11533.2774883674, 85873.49949100931, 69898.99990269651, 51899.61107887081, 12089.00459198976, 25153.07633844106, 16941.78939731922, 12787.858705180943, 39278.05290171491, 27221.339215450167, 2428.241863392444, 95641.79338881282, 60274.98750336936, 22012.933486455444, 60313.32074496725, 44009.32328198087, 24002.49716636048, 82503.28889632321]} +{"id": 282, "vector": [35639.7272494601, 90711.98854539802, 16656.14369496412, 45073.058977777844, 38603.289728711985, 75247.58348049072, 19810.865643504738, 45914.97519325389, 98530.7467231639, 4552.151670284943, 53578.62532952984, 53292.504896560196, 88131.43722774542, 97208.40397571055, 84535.632563449, 24066.27957278651, 29333.95205250172, 87932.42687110907, 68711.81555529287, 1517.4512222939752, 47089.858520171, 24145.823457858784, 97158.6803694196, 73592.88552316636, 57894.74164284004, 74527.29818129596, 94046.79568485619, 55434.02794665861, 9115.70154763609, 44688.09955220633, 33059.72540724574, 12000.134506194505, 87893.200583151, 6335.32402795679, 48143.78697485251, 17194.145281658348, 36441.45055383988, 21511.529474930623, 19627.858699335564, 62914.683297860065, 50122.23024033544, 78417.2177101329, 41698.074049738956, 28921.43752202705, 70139.25532218584, 46875.14697877749, 86968.04884072063, 91266.15134475395, 47715.727380739816, 5601.511220089162, 1858.73490453885, 23326.77328105769, 62779.89348367133, 48137.38063158626, 13728.430431880633, 30762.325484545738, 49921.690625274474, 80268.10573793325, 67382.93932970252, 44550.072453111934, 75606.01812114898, 36191.109514068776, 75049.54317691778, 59500.98053215093, 90624.112642264, 70576.76234911967, 2531.215603699577, 85540.01469225358, 2134.235804684881, 32810.85468826417, 34091.438083465844, 32872.18773718683, 38489.58755100326, 50725.496954392445, 27820.786218087957, 85951.69374637405, 15324.803983811675, 45355.90821609462, 6614.702345933321, 97114.31945561573, 51884.196987233445, 58874.150609923556, 74407.78854054442, 31338.110559803346, 79828.13945243426, 48588.00517406756, 1019.1668340071791, 69843.18083034373, 55954.81954418726, 90625.15031052301, 80596.02222735119, 10304.095736276397, 36305.50358705909, 19505.580176756033, 42173.76004485601, 77933.99575500166, 69755.65712300484, 1022.4020658487709, 70252.76791464732, 74273.7805603717, 40558.917982697494, 49812.191199564724, 12305.405362843825, 4699.317010812331, 34184.48821132058, 95593.74135751021, 66167.2707790486, 23629.517172247182, 77602.72726432825, 39987.36431983428, 4167.824825080357, 20408.835156972225, 82340.28951173642, 58163.827129929145, 86944.5796192626, 26684.707837959133, 35594.23451417062, 45462.1621737765, 61635.08098731999, 93520.94984074649, 88281.67411937444, 16210.712882856536, 61984.25030914656, 19320.24269842294, 35881.03117683492, 89325.81464038504, 19700.736182403, 89877.56266622544]} +{"id": 1714, "vector": [62699.453636293765, 9727.399999528496, 25951.422436842407, 18357.68700349396, 96365.0657813437, 24719.51479800183, 95639.10946035656, 32542.568495425596, 38418.605674784325, 37182.06115424211, 56445.92698412746, 96634.97600512355, 50446.92021980716, 52485.13127000218, 39858.27127951251, 10793.359264093971, 15704.417120434711, 16189.272950304312, 9215.992441992526, 77161.21923845522, 52398.55815359833, 23661.244727191923, 64534.81856077468, 96232.80532254143, 15112.60086543228, 64138.106534617764, 47789.897494977886, 67229.010717062, 14383.881456292069, 32803.82810420328, 57415.04060928174, 24888.92542939255, 89737.91708488234, 7888.99887911454, 62017.263439352086, 36026.488199815365, 22887.0903108366, 87221.46000572755, 88418.1670764023, 57384.209298951086, 27628.327641628715, 43978.01640392371, 61444.55340835461, 44448.08159686163, 87270.17563120287, 22004.02480113256, 87541.39344375489, 7938.293743199476, 56364.20906924655, 99620.57038971655, 49257.82179338453, 7134.619824449528, 62345.07613108643, 29396.16917412757, 84795.93273509725, 87392.78994063997, 37059.72152825232, 64530.39591392057, 64005.89041165572, 96082.42239451472, 81344.05740484849, 17124.406578924067, 41010.391609506754, 67413.55171852636, 51302.45457075724, 27354.30039427861, 62874.1439336257, 68776.86080924273, 22648.078437561893, 30548.934789590974, 38652.51041521347, 1369.3684330895128, 64522.64543235076, 3116.401704137861, 72069.79596820498, 81981.84618529171, 71154.05567065353, 84922.67448952392, 66454.88854820262, 63990.36285142714, 73373.46324793913, 44022.3196159819, 62849.40715051524, 29167.750533856863, 36203.42670090711, 91648.98591013224, 3494.1302177897705, 88712.79938293794, 41119.65327357363, 93452.04887796423, 89415.175946007, 36473.11550847393, 21832.427788798424, 52484.71512926855, 25694.235052279557, 37466.18970556761, 85154.22634061928, 96493.44588584935, 46951.4852380783, 81990.01946238859, 82802.13772168635, 63792.49665728829, 18157.59307228577, 17486.75680722821, 76782.95807905874, 41319.175272100394, 26776.775697093013, 11899.618867153782, 13435.47693775692, 168.27040156651708, 66497.59238042064, 6340.609180343248, 85092.44948496376, 65649.59476814991, 58394.63942928986, 19700.832652514633, 78884.70707040366, 72957.05946223748, 8426.757086417414, 51726.50389189818, 16306.828990233502, 81290.88068195271, 37338.522929702456, 17802.270158591728, 42993.434041553934, 29366.488905759426, 25949.014403968686, 2695.612334297104]} +{"id": 1995, "vector": [81725.86283404581, 55037.27125274327, 49929.02835903762, 13082.124637860814, 59934.645978642366, 46905.545499242166, 76362.73549392029, 85133.18949150172, 20140.91012830228, 21046.474568816076, 65004.202049028536, 25978.445601977517, 61794.27350152563, 95200.61529885899, 48157.936972249496, 5388.307556387306, 56601.711857113434, 77836.8432539367, 10135.533646139449, 49509.498694632835, 27398.698225141503, 61780.022321252196, 98077.90499512365, 99677.42296418351, 64758.15583342063, 303.47475631082864, 99195.57220642241, 16478.826258801193, 67878.56304451407, 89933.37292013271, 33821.636550638745, 96946.57122525334, 96726.0484415057, 47591.829450196754, 51140.182107855035, 93915.60824968504, 86135.7589377866, 83150.55796962336, 99458.93300464479, 55045.99956265911, 40498.90116877963, 74759.67311028659, 26911.870596486508, 91566.39572640578, 12077.129239159623, 7332.226212339554, 41566.29492226983, 63389.70970614445, 12016.88867398295, 96397.43657190271, 47525.59736432871, 39384.1911049811, 16384.248590147276, 29349.711096325882, 44717.22487991079, 58395.15832538146, 54029.8562221308, 24843.01074567845, 69380.67711496154, 91723.428563065, 45476.29959778153, 72276.98451155904, 83480.7675663898, 62877.84224713241, 48591.00419902063, 87955.26541341757, 92526.18284601433, 54124.29574912114, 24567.837797587777, 45150.48671219605, 79.46460073944283, 42640.211118353174, 45177.2239599897, 82370.12714120511, 41349.103093529426, 48165.46670943874, 5807.490696585471, 96040.51919635004, 49787.625585649934, 52748.41374274465, 31352.47165564764, 79955.2099589705, 50306.28712723043, 44068.181349682935, 12696.668207948847, 70318.4443824753, 81658.2031169183, 97516.88358524135, 83168.27461419567, 25595.21421597082, 26944.291357260365, 79220.602757491, 73361.86202379085, 94435.95171353851, 43908.3568309382, 4231.036766542428, 4632.118709275901, 82217.43921974453, 3462.394875556474, 28217.029913858947, 65604.96044410452, 41007.55259888101, 57262.806086801946, 38557.56647665044, 91663.24292557375, 94419.94379635726, 91110.29194241216, 20432.897345979683, 26135.286997743046, 56654.176914109965, 9802.695043053489, 12297.730802580243, 1708.010222901346, 61287.186482262056, 43844.43608331089, 15535.180300296402, 48921.406987334514, 5971.593350265336, 67456.76757426102, 33376.851251292115, 96478.44779838769, 58207.88477809572, 83437.40734869304, 35483.973087348284, 67075.46112896656, 79513.23664087532, 3408.0646810808357, 11376.241226412765]} +{"id": 1954, "vector": [27169.094251881543, 48141.03854366772, 98268.92184583542, 59138.06187893307, 15175.14090334282, 75094.76170798329, 51642.347047332616, 97430.8642274513, 63873.3233896728, 14479.029845609226, 52314.69136460899, 95854.26021098008, 87590.99264082553, 63044.83944814736, 53429.32174306735, 67107.11047184658, 77657.92170870955, 29958.96988256381, 696.8095109372463, 82663.73989160878, 3585.5823656413822, 8082.001002463313, 34709.01862137323, 34925.54637895775, 59469.93880776439, 30850.324616571455, 89111.28625156944, 39343.196358861074, 47248.77444696351, 62213.64440792132, 83873.21555522246, 93445.76329098723, 49307.0494982779, 49371.657351892936, 11423.457845433593, 42117.48674466278, 97167.25332486315, 56945.82181284987, 77001.72734601451, 82711.99588885425, 76595.01455030155, 92345.13250831029, 9225.872112847223, 93620.04767328627, 93409.89795884833, 30419.48334542315, 42467.60051037839, 60017.76285503758, 31203.552104504095, 74275.5321067811, 91974.84985084538, 81480.88133644352, 93180.97070362083, 51656.33615175922, 92055.69555802394, 543.4712684102893, 66162.06005655999, 91867.63378753072, 23695.80261095291, 42041.224355097904, 85359.11015884252, 91804.96002528575, 51834.86898487126, 60322.99475908598, 11190.07358933819, 61040.03594981885, 49590.65426736176, 99970.83956045394, 63259.59221520566, 34599.91303559913, 81629.98370973322, 77126.67770901181, 67875.0109283693, 77210.56988095469, 51738.905781887304, 54986.88224886296, 27819.736683111696, 47090.666523134714, 85838.48335586285, 8561.921443208275, 70668.66726466476, 10834.708440117036, 80719.57877802881, 4879.378936811673, 12290.097864435002, 27446.796967231814, 96614.07889491881, 276.4641961911729, 43291.70189052558, 27545.75440660768, 86751.83477699602, 67311.68191665334, 79618.99735493962, 5189.029002828316, 20717.215291284883, 23753.025419242225, 29533.874138019968, 74501.6303113772, 37940.96357285541, 61176.81647387542, 61082.20465018488, 42383.87953447931, 48379.93776102657, 2583.1013616616992, 91399.05422016249, 76096.62443059063, 28834.257669322793, 17112.64814422575, 94499.12110546375, 21113.21236942134, 92881.79838782575, 23871.763287945392, 93057.18343914513, 84938.10463914901, 15681.524930687918, 54660.98437516889, 87684.46440487956, 1714.370048681524, 95606.77786992113, 85507.30611647235, 24318.60737027086, 73787.29227227431, 6968.273220946264, 44239.39393886671, 45756.632100585426, 36877.95709895848, 75187.5027240789, 86219.62930515922]} +{"id": 244, "vector": [16199.640950101546, 145.499876843036, 35754.76175304446, 58482.041263429775, 649.5910564940699, 95628.12457273746, 88750.98183068173, 58976.20658363252, 77863.87376421117, 68739.0756539733, 84975.98727248788, 13828.751350568225, 89371.329753423, 36431.49513107201, 66958.86025451761, 26273.782432693715, 54540.09427307547, 56161.725423686206, 25121.829087089154, 52325.083971158034, 16012.29263057251, 84982.65796487957, 34594.436887175114, 77187.54316164849, 69514.78702426434, 47765.85304314748, 90375.1904888745, 45174.7189256787, 66767.90293749602, 67196.26813475581, 45536.74661080943, 41879.87172075719, 3898.6811243597153, 77419.8089002684, 92034.94838502393, 4170.308721357618, 76075.75676429263, 42884.178183612195, 22060.585110368014, 43133.717135258456, 78905.87182750787, 63198.97245401369, 59298.512417396254, 42591.07661736375, 56152.357263296035, 14885.112355007002, 3342.2129939046476, 32683.38047104714, 10941.17426656459, 72734.01164706159, 52462.83567509286, 68004.7598655908, 22221.602067081625, 46624.74252621952, 47579.57638951669, 9159.546538718034, 49993.997395119826, 17817.55481184244, 17978.288588206513, 76293.98170083162, 70160.99475872301, 83849.58728050604, 45574.94987273837, 65706.76258667816, 96430.06007350788, 10873.164677435065, 40126.266054455926, 67937.42480941078, 20654.793315772025, 88461.87041089374, 9866.013620263193, 58478.07439774278, 61613.78742656709, 51494.66904470354, 61347.93160948743, 36258.45226423456, 11523.437365560874, 56323.50000400338, 86760.63033921787, 71650.28377637536, 33307.171327434204, 9827.693180850561, 32706.116553099484, 7394.933319182329, 99353.5737610168, 61230.71924734467, 62404.11491331344, 33242.020803129366, 73656.53325490585, 59898.68346517903, 56313.10283139266, 47532.45142271982, 78208.99014676889, 14677.41182959803, 49408.352311264025, 6489.45257102056, 9069.967375826227, 40481.786403766964, 60096.93759467592, 6493.206566008292, 86134.28649878842, 73151.02082246686, 37710.792049063624, 28676.42895732212, 15045.715466568166, 12870.866184181785, 44880.6535738866, 69250.4195674735, 21347.155090425294, 86393.60907852331, 51030.45557200088, 94805.15608099705, 39007.158424572655, 37477.43117551455, 57390.78343204864, 86050.029400095, 19112.266653574294, 66700.11227378236, 96213.86925073662, 35688.08574151168, 58138.09571726234, 49120.302405586066, 67973.7605668538, 26636.08124100344, 3135.8530541387486, 51069.101033420404, 4580.142897410245, 37043.609155858816]} +{"id": 503, "vector": [95661.56145118675, 7829.096971565985, 50215.99976517749, 30973.301766663684, 46489.148478754105, 60020.7693862387, 46987.14647639046, 9840.793094428445, 49027.160286978986, 45965.34153530126, 93175.49734239503, 37179.45610755324, 78264.10659848005, 12276.84794435777, 89870.96427951143, 73474.86575130827, 42912.72315669454, 38414.33734912625, 79273.44394922201, 9057.635500698314, 10930.842590068223, 71569.72606722557, 70217.72322539311, 70052.1113871636, 65871.0491986797, 57093.537568494714, 7976.681672185193, 84575.88345138845, 61715.33151997096, 9179.452511249963, 91732.0819788488, 33109.15143058153, 60995.127888834446, 31104.708007073878, 12104.651416772273, 11212.176208638502, 83618.15825343732, 25385.654543530978, 6335.616924152787, 7422.711732399401, 92081.33119078155, 82918.73628587199, 53207.62826077089, 51989.53297084036, 12483.622532406258, 65875.77976437887, 81444.3010100132, 69691.9321344817, 83657.76209474758, 27119.117961908145, 70802.88977181725, 70851.25409748328, 78950.34766737522, 4658.534370875244, 81037.90594868959, 24511.76465277608, 52650.407880684914, 2775.239471267266, 54986.60219144123, 54852.380531111456, 1883.9962732652093, 69248.78720596158, 1932.0180454902247, 82431.69335149853, 13914.672723656251, 66263.09073165114, 14442.733214067726, 7591.0280526460765, 191.83938193443328, 53598.314590966664, 41558.402220819415, 99064.25019259026, 1320.0949212618718, 69549.3821325445, 6078.254167658326, 65343.4903687213, 36633.00967574388, 76508.75153765567, 94036.02738628317, 32755.799742716263, 105.8028092961294, 18096.669975497138, 65510.47578856359, 66458.89985939559, 94566.95327742852, 98934.8261903366, 39688.207616473635, 66330.55397046827, 50241.01299450685, 42140.51616028225, 6611.409732594054, 65379.644752505876, 42470.20530488532, 26496.815784896367, 18273.7808152734, 52105.4908761142, 39293.0059905701, 79316.12603029882, 98857.30873807774, 55360.35325602007, 29035.735515459528, 24044.77931126372, 29720.48735794647, 24268.202540966522, 76646.23333899201, 27245.392695219172, 17657.46614608552, 4798.650963066963, 54643.19151846383, 47780.832985015586, 44621.01441397499, 22822.000544053666, 528.4849451575924, 70534.30387917825, 7353.059135883711, 26550.515838912914, 96053.98181432727, 93872.79599319633, 52137.79886384185, 46852.89372529359, 92093.07901085448, 1215.1606029114803, 30691.416754958354, 5421.328392780345, 25515.330722388386, 19832.00883286759, 96782.5149642884, 63254.63268682806]} +{"id": 1342, "vector": [69555.0747520157, 26346.63744649529, 60620.96817448406, 31838.497815561983, 47793.62220091741, 72425.04197850406, 22204.239504516176, 47128.11490632131, 8554.720249300752, 68702.93872128813, 71160.4061873783, 41319.67377242226, 4931.219980471003, 74988.03523361472, 23033.068902751773, 13244.375216867076, 41408.623034851924, 12327.803621412304, 20422.588244708473, 74731.4839593636, 66829.95615952655, 87624.23223634544, 8663.860304609749, 71169.49410535584, 42086.28514527129, 79801.3865171393, 295.443115787708, 11992.750150198417, 56930.15622128472, 47113.10310305711, 68978.68664788587, 64788.07289507771, 19080.849772655973, 66946.24838357817, 91549.67216655742, 61775.502044220186, 12237.32685592177, 68945.15627027054, 91912.86588323177, 21917.07506949877, 81322.40283084442, 33657.700578100616, 19404.970787990118, 74939.88685580708, 47298.168551858435, 56408.69521727236, 27322.502421891237, 67718.04992645814, 4932.268280879825, 24533.61544342353, 8913.280877077634, 2090.160786504169, 9166.76344818671, 3835.7122438418824, 94779.08761716228, 56632.225380642565, 18885.956403447777, 85788.88088726983, 1598.052128972538, 63054.507860137564, 50160.675426039124, 85306.49101218455, 13542.459816600705, 43170.54425209037, 45129.31356581156, 17792.45967199541, 46810.02613557377, 95748.0818653379, 88361.0510333845, 45265.13219741014, 67019.15943687115, 39511.892402981575, 42178.10182881684, 37218.05638625194, 30915.3130226539, 71324.01345887515, 45143.939361946395, 50051.018779251935, 81656.33095730536, 19250.312328518514, 68353.32055157775, 85323.38959759503, 48732.02807145217, 44137.93389189583, 37471.988312905014, 69663.9901222619, 46604.257352986635, 80789.73884090415, 78611.24656793683, 67325.20208475016, 33894.26627974834, 81079.4781891296, 89116.03920642493, 33839.48240501603, 99828.08570489484, 87837.97958750674, 4614.364930698778, 17622.55956277532, 52380.91791520887, 19385.699187176164, 72868.0712682318, 11697.954633221874, 60952.03444991122, 38507.949367911744, 55180.740784010806, 78389.01330923187, 41898.64637179859, 29829.31978235087, 20382.296303112056, 20798.721353760997, 25967.87000448757, 22405.07490412288, 19757.843980177393, 69651.04878223763, 4513.035977064639, 27936.005089650705, 5362.198324385681, 90758.49632605677, 24616.46624077688, 76007.02971112831, 17618.73428115438, 10273.093847473003, 25926.532610729315, 38324.99671773858, 57502.70363814626, 94266.90898406752, 4297.270626583105, 43530.63859937423]} +{"id": 900, "vector": [86475.95151681657, 15943.801795375522, 30329.58249766986, 63808.59658432947, 19654.243741578248, 17194.20899723161, 59392.34331484962, 333.29963066999204, 87970.28127250474, 1624.6723396102002, 62030.82899794662, 84225.46998455739, 11534.892131985875, 63445.6397346749, 92674.8515455118, 84014.7855098368, 90296.03525417107, 41318.35328714327, 57593.408947348944, 7816.645609457751, 11353.534161256928, 87261.5448474202, 45704.79414863792, 12420.437585180254, 29863.748387494426, 45967.48679718583, 4829.982174089142, 43952.086401446744, 75817.31516295664, 30070.293618507858, 3651.637393666174, 7077.673119222527, 34308.01925188403, 56415.34781336539, 64928.89664040764, 82922.09845957134, 23785.324684742416, 87653.60516104598, 2069.6856612559823, 27769.368599060173, 16853.394044541525, 81494.94473335147, 87257.47135502692, 65832.60138047484, 87680.00975592561, 13262.987484502797, 45690.68318310904, 7439.4114697756695, 80122.81300413504, 42759.17139623306, 89886.1210782575, 8187.435438356194, 71083.61697907533, 63816.27729767543, 6891.42899805063, 42905.46598186366, 64692.296852441345, 4442.80395877179, 90482.13646910503, 87064.86996805236, 54725.816248689385, 77482.34187659506, 93793.73736209526, 78248.18544508987, 8132.411806727136, 40454.153114295244, 20731.526684170753, 72390.16850550624, 28707.27262786741, 94535.982400775, 46677.717398080975, 14906.47652800282, 40116.48316739163, 70917.35319254364, 26993.373802292397, 77012.62811580575, 92716.29243880873, 81632.17062855544, 45255.27012137365, 10956.1058092153, 74003.38192624057, 320.2311077083353, 16897.10220778151, 51293.363714682506, 22164.99769611171, 45652.431591819775, 24981.573888047238, 59268.41359230421, 86516.43383418972, 13936.947625504903, 23869.16615639759, 92451.98333385684, 20442.0867937829, 24801.45283889523, 79358.398290535, 79886.78997423706, 61897.25564856156, 23167.002293956328, 37919.17992401862, 37031.426610547926, 83346.63431418694, 94458.89023998361, 82966.92210477366, 47169.99800672794, 86788.90918448457, 52111.53596190893, 64233.75293623159, 90133.23516425383, 11818.577064989366, 19888.586942659946, 26642.529652528658, 53995.80232280655, 51603.091860446526, 49236.75319837489, 73380.68419969236, 37010.7449960656, 8719.215831409421, 72020.84313542867, 99911.67900642415, 42104.46645157566, 69198.36095451174, 97442.86770184238, 78657.90094810283, 14686.39257247123, 14472.278405532368, 41306.485246238066, 83961.7270358324, 86934.40652481999]} +{"id": 875, "vector": [29721.424508531414, 41328.056174007535, 52223.88594999103, 57942.96520886823, 31821.816254651036, 27736.53911297416, 95441.08025308345, 64468.23900632458, 60956.08131645083, 26078.483240913418, 77525.40344918784, 42303.315594450796, 34800.99759848063, 99214.19949034863, 33837.55935296838, 79945.89261487297, 65515.94599225472, 79221.31909822744, 48553.878940374794, 96445.01311599663, 99776.80993363613, 99807.718657279, 58180.19798467618, 87257.6090714403, 45339.6973669565, 23910.235778009515, 83563.37963718164, 65950.20338313072, 4967.600913727332, 75844.67002016352, 31895.16511765401, 41009.56756626796, 80459.91571024036, 27280.85179905355, 61089.49274246712, 91325.41292238283, 4606.548644982134, 55113.58122687108, 58912.14723210505, 93861.30720039451, 83494.35971667044, 77668.66750409862, 9313.634054977581, 41447.35351854726, 71680.56691774316, 83435.9814565037, 37579.08266820797, 83813.93391283168, 70228.569776289, 3477.177549464816, 15136.435645315238, 67499.78568938568, 12258.37541686805, 49531.6129367544, 75243.60305184538, 88725.53923646326, 32616.412452233788, 1927.0152692926533, 64708.86492348924, 86275.12201737873, 81914.12303482788, 11703.471335283766, 22263.901517407357, 78529.43400452391, 24647.489125446897, 26787.460442232612, 66895.38315333052, 49835.656704786554, 61218.72112904746, 10250.07661075913, 31319.190203365823, 71695.93264614009, 32296.78453231892, 45096.727053154165, 84291.35581510802, 13759.926562033741, 56638.65101134644, 30603.31028090595, 99620.32034119134, 22900.35956053027, 98188.06871399283, 1879.024829967113, 88156.05283596688, 30488.018160964613, 19819.06683942327, 68697.44098918916, 76466.57689544733, 85616.06324649353, 79977.10582141422, 74911.42992372355, 43104.876326330384, 62682.478541348915, 93452.4865722184, 45279.6493993027, 35572.819000201205, 8510.810602441154, 63599.86376893737, 21051.91544158903, 36120.02047255829, 9520.63175854001, 1274.1670849032994, 41970.75968764905, 93074.55989898347, 9743.58195489672, 51731.90028126023, 78566.64784687574, 92801.2763828225, 88885.69372884562, 95372.17757018143, 92241.19117113433, 72380.26074902207, 97793.64927564914, 26720.493205989093, 26720.928824081526, 63015.428063280335, 5250.46592801961, 13496.798349834316, 32681.209168804402, 14571.520397756587, 69307.14528592098, 40851.58972377487, 95432.78593920905, 53500.132875151765, 7159.562930851271, 11206.655284083234, 92060.8645541098, 14567.998115629332, 79649.27601786611]} +{"id": 948, "vector": [96302.71323128183, 2801.685843842083, 28631.752321655014, 62861.053345223336, 59251.43559897017, 63111.83972411348, 49442.71842941658, 28835.666683147876, 58297.03738373395, 77617.35428827748, 5110.949868581039, 47693.65241742538, 31880.33039695357, 78979.71643606435, 57919.48570076728, 99123.23404768834, 7258.453494812078, 92078.39318788839, 93394.17422649842, 92565.6704520452, 471.59208373140916, 80318.32750436485, 80352.0952862759, 37673.67144076986, 95118.14888003432, 88736.4001543349, 18935.95045484587, 26454.289295351606, 42882.75359430208, 6660.782840934498, 39936.44661641732, 50555.51863576827, 17364.620960185563, 44461.45931910534, 76403.19397085547, 88387.48323076798, 37903.85062210658, 88530.00205863077, 3661.4312719829845, 84740.05781985051, 82269.21291759795, 12846.25323827252, 67120.32922419845, 84501.5354120466, 64993.39849574624, 8165.863865050116, 79988.53750035018, 48452.29934532441, 56866.9033082846, 26661.90686952078, 3208.1020969557117, 54934.835655820214, 44066.36178503051, 95828.92667607132, 21303.3861349066, 38778.36692398572, 6959.498238652484, 56448.26965213615, 73795.19429468262, 55454.77935624643, 44404.18166004103, 83208.92915395802, 71198.95148179123, 86621.51565598819, 23665.66829910881, 24033.935630479453, 25485.176575333568, 70497.4690419822, 33244.900696885314, 28181.38542826618, 87315.54472271737, 16207.250192504618, 25583.10926357449, 38348.84200423574, 35967.45594127854, 23204.13541043712, 43892.69921498052, 81319.73093608207, 20505.478002334166, 47761.89775215082, 93659.68573759515, 8726.046147296518, 58948.71977195105, 8965.000885923491, 98828.02098680609, 75907.24170145085, 47800.99914703486, 4871.588276123984, 11738.808837168424, 23854.608561339406, 62495.984527870976, 6766.338683042739, 21054.283646407435, 91250.13509522966, 79191.25814817243, 18370.81777031815, 63096.9893341258, 93790.21767727357, 79105.74501156296, 40458.676393346126, 25211.6417645992, 29295.992977845286, 76800.41054559992, 34383.58448628125, 18624.80015218827, 54127.12853348007, 31142.928852736506, 67588.66965588302, 47460.550154543176, 31206.92674000163, 5288.712449783539, 56583.60826326573, 92040.14563797644, 87576.69326562552, 75915.72983181528, 44110.72837342496, 15175.345747103263, 62202.061782377314, 76379.7043221993, 82142.494977185, 37140.65097978213, 27929.866124340762, 63761.20274887077, 79697.62769734973, 93726.45106218848, 12535.54066101512, 74175.17608010437, 24172.308118712128]} +{"id": 1240, "vector": [27621.773010881858, 66150.350369987, 38985.992470846555, 24151.430286658015, 24636.760388652045, 23327.693802182548, 26048.57506372149, 33452.45377028133, 44192.73614622162, 80576.71652049795, 58065.054502487765, 7747.290541823982, 28338.479044490326, 65811.3347915122, 8072.518834708042, 4438.133349167783, 563.5650858423991, 94615.56659068268, 1555.9732183947572, 14603.421590745125, 66233.20607226009, 75318.09210423645, 68674.64465637336, 72597.63739842433, 34128.48452523205, 70301.38675481078, 25267.01540660089, 5190.1688175762465, 53017.22023825209, 62391.03056699723, 9216.680490057572, 85059.82056583214, 20036.066156104047, 79167.54958488693, 51918.13188324862, 21110.237767861105, 54736.42387566546, 63406.76173176738, 19919.298830938948, 89372.25106777092, 30255.6527423952, 93060.90968252523, 78697.88721973155, 65845.18507894813, 54386.298746864006, 94052.3974064829, 18519.554456390175, 71067.71093017652, 65646.50085692185, 19057.90099501018, 60395.037070979284, 5900.283215517632, 30807.75589418583, 51464.22491397179, 3547.0781158719487, 3109.1275420859656, 37210.99065421285, 69404.93656065826, 87712.85069596967, 29980.156376800514, 47967.47034783095, 55616.05420920227, 14878.65109636105, 82816.74799402029, 29651.42645110904, 71238.09040732763, 8827.34032247926, 66438.05273806791, 16225.342425380562, 76315.88951608987, 81822.63020886444, 68632.52811232889, 16663.177114310456, 61683.534731750675, 47232.85350491807, 24171.671138081907, 25078.04508404439, 42043.297401397795, 39999.62631995613, 56716.21924176779, 36781.217505785055, 6351.548939966223, 82965.81838658251, 50310.62192864081, 41320.71204154497, 8438.534744459836, 19606.380256689292, 44243.97599970088, 33028.13859207791, 40066.85063211864, 99986.45088155537, 32828.19349317874, 89252.70227465217, 17928.00998321661, 84148.8068631584, 86370.9787203866, 65766.23693609414, 66680.2515829342, 36196.664981892325, 67345.05044333536, 96528.46929013405, 52860.61187145125, 87584.6822229494, 69089.16673145932, 80785.69665198262, 9709.11271185705, 66633.44557445166, 97866.01983821677, 41305.10154863004, 54950.60195519414, 99373.69609147581, 28989.713076721946, 41347.03083945932, 544.0266410991823, 44283.595944158136, 85634.71021701857, 70394.361765503, 12693.172875647773, 17981.10119577061, 7839.6724848282865, 27916.401775045706, 14045.101764285928, 7869.043304729817, 69746.20634850455, 99140.16891610103, 51965.460335105316, 8900.769539121644, 31310.797119977862]} +{"id": 481, "vector": [62849.07809427189, 79420.51560271117, 86574.30512342282, 99175.81882433491, 18353.667300611698, 63813.013482737624, 23654.277593236904, 1346.3194596981864, 7876.626541753828, 56418.69106879037, 60123.123612917974, 37268.26477097707, 27587.478618047535, 6111.115248531695, 73943.823735871, 44028.578479740674, 84439.24006418818, 14463.193473115798, 58642.33513810789, 47189.75513652124, 66666.72549753061, 57316.393277533214, 64379.38621606467, 43499.50416865446, 65504.54958145945, 94676.6295813698, 92492.39399574786, 54039.912657216795, 41920.18904924614, 21368.520683679115, 6077.627954658171, 15196.894116923531, 10994.335968015323, 44067.00626038505, 74960.26957191172, 38497.00469658632, 82546.0885633737, 48376.04715534452, 36042.40694404618, 41647.58511725668, 45058.20458656847, 282.8652017873967, 10217.700029138121, 73149.9931482034, 82814.65433669055, 92048.76199593006, 48709.957780662095, 52208.864607479634, 31538.03026498422, 22782.878480229596, 26471.26329991598, 61646.743497779644, 12669.043762338139, 1155.4602448002993, 16208.562518026536, 44333.10910009454, 52418.68628713143, 53361.25731599403, 27911.164444270897, 95122.41737280758, 23239.164629150644, 85385.62641801644, 72896.93794424922, 2634.35750139267, 60483.171430699724, 36925.34262374909, 76722.42641658064, 47496.66635477069, 43011.911224618685, 75397.59650661326, 98049.55395922971, 66044.88359414919, 84209.9082329997, 52284.63656925916, 16869.60828186225, 15207.644340734594, 40395.190122976244, 47674.26342948744, 53840.239717555436, 83498.05520540445, 60837.88733406773, 69416.79483075014, 19055.509171476104, 5737.151131046958, 61200.81607525152, 50680.169826699625, 51943.40964546483, 27436.249338342288, 11316.01733626435, 55289.527322695765, 76716.74156531245, 44473.77937780603, 41214.98661119817, 66023.05456544524, 18862.196336603232, 69771.87405164033, 31351.82997957073, 5974.911371937541, 22076.20456534124, 37632.18088174763, 68373.62420586972, 72426.00865256613, 60203.7570880386, 13962.238398558735, 11418.894468958186, 29686.288833892206, 2826.3555540899542, 11551.328543970629, 13099.328705543478, 87792.98097914817, 43747.80531907248, 15310.77202269293, 21613.82079244568, 81790.04340015393, 31211.441138567643, 55216.38836831635, 68764.21597157983, 23497.26503433509, 74654.40348852392, 79820.75832525847, 90678.12041638527, 97175.03672303898, 58946.33320693028, 73839.0481967559, 54087.490310869034, 92644.12098929028, 30338.3146688742, 33265.98984574889]} +{"id": 1253, "vector": [7601.143972982394, 11010.67560343606, 62729.25794292298, 445.77487059435094, 22807.210084641614, 53146.41898249687, 11361.997744919894, 41270.57241497646, 42310.20325176332, 11430.074873683727, 90684.73892618669, 67177.37233395068, 46999.33854503229, 16488.387522480974, 5852.744048993408, 1189.741541435807, 43600.4777372206, 9937.428750506982, 45376.958081538476, 6322.537198783162, 65504.12098568307, 23127.11332135574, 26032.969768934945, 79741.3550413614, 39677.58052037498, 68584.01908729564, 50350.875129517335, 20717.497158825227, 88502.40072725802, 58391.232682469985, 74296.40315462994, 29007.71084184618, 40805.916779203675, 50799.74090472722, 57667.047936858275, 69492.69135453082, 27264.810394396867, 39824.77702012772, 46891.12966340684, 40903.143085016025, 76839.62711689541, 82882.73464801656, 88347.84757029614, 777.3419551656114, 30436.99372657993, 93503.60912567955, 74379.97635809575, 583.0011612751651, 29049.679370444715, 65066.51046418207, 42569.750723182995, 63810.289119514506, 9868.651937424322, 49629.79877265094, 96469.68810432058, 27052.477971622702, 1816.6621003661842, 76102.51886362571, 59004.58453887376, 41325.62467284562, 93522.70724620098, 80725.88847420206, 1720.2311640532985, 43012.18123509579, 39743.639198569355, 13413.773436781496, 44689.21802275657, 54987.37893450072, 22191.24957091143, 17214.193881299554, 85667.20648100106, 17480.955569589663, 25106.690104897832, 80493.17581152286, 47248.41735913634, 72242.40700323269, 25716.04118295051, 21834.265417396437, 19622.74828636156, 28845.285144548172, 41207.78585065928, 11372.012324392377, 78546.0803143782, 44393.4359997156, 5372.17194991978, 14293.280889230708, 50867.09714693075, 77424.30426707749, 89822.89468033359, 79813.61150773156, 70361.78078176189, 19079.720295564086, 29533.50078536112, 87607.56211701715, 43064.24781002223, 49658.66217784079, 53321.400260504204, 49126.07443049531, 73332.58092796749, 43819.67133315092, 85311.02977883637, 36709.45808511231, 17571.284156900234, 54898.834583664066, 53442.61531673842, 38424.39717743063, 12549.534076379698, 53763.97250601234, 7469.333014172641, 56207.76630873129, 16572.48651118166, 10906.209067668193, 8957.4685377955, 76751.40967296743, 37268.36518096388, 39141.89562611813, 98972.96029008273, 31452.382099061448, 94732.43695325799, 7564.668451819045, 7519.219321252324, 11167.304160617708, 7936.026485990832, 24710.31418033883, 19255.38320229968, 22480.18981087254, 95282.47817750584, 88268.06336729725]} +{"id": 1210, "vector": [44756.871723584445, 97656.20267539921, 99518.2116727149, 6592.628075489404, 87879.5821005403, 45495.10147554495, 34414.60382156973, 51566.325085685225, 69034.09656553382, 82170.09845705445, 49906.805345048524, 67728.45281445094, 78191.20315143315, 75966.42580187587, 11367.313818252589, 32933.15812738888, 4119.93322474189, 9152.57868566055, 75087.79672479437, 14727.666994037458, 40106.14552962888, 94207.58282502081, 63619.07318674475, 19981.288739524272, 99763.32979086731, 4766.50598838183, 45474.419546859834, 56032.044223002806, 26439.032677648076, 45823.64557908371, 26870.154095438604, 584.7119864279992, 69876.4244991511, 11876.998633179059, 77887.28544197764, 791.694697098444, 97145.19141936497, 83305.6956420966, 90324.81117520566, 77255.98773685131, 60246.91869824537, 99005.85674477645, 3549.742165404768, 37882.82597927646, 93839.07779356265, 2354.192393321752, 61118.71352957282, 13554.487930875714, 91116.60036649491, 37915.766783969004, 90405.69625771043, 93009.85990891258, 45241.73374495988, 89313.6134104654, 28946.442765044656, 39347.71005054699, 35927.65973257306, 71888.22023461883, 1043.2083175856421, 24462.48555190521, 71889.54993383288, 77874.43204368358, 88624.19915130401, 27219.159749103273, 39577.35777780861, 90112.81380258164, 60466.92300913158, 17984.047196458232, 42419.88542845473, 83195.09005846742, 84821.0863080472, 41130.15780633364, 98609.07170755639, 53627.73250718155, 36259.417183830636, 74618.98858545095, 5883.00718825574, 66174.15753325286, 43400.79160632837, 75861.39369860671, 14639.022531253466, 29228.044125012075, 48597.84649541504, 82948.25944144088, 17690.182856823467, 3029.0883207543916, 23484.86169069336, 10481.867657574518, 18717.02826093322, 6537.847123984219, 23054.316203536586, 87104.43145765367, 79052.65536749779, 23602.105816405016, 15253.924806235642, 25049.621033878433, 46522.14135941388, 11901.772444627179, 23576.47915449659, 41062.005658401125, 29201.20596534129, 41644.89284020166, 30715.009352020294, 74015.8089361464, 56268.58493656997, 88769.7679235145, 60811.17212972638, 35633.00809516261, 24594.195604929893, 44440.127974068324, 3401.2285897681127, 10073.989787649462, 39181.660369733254, 61175.30361287561, 66571.33393025663, 21099.63984608443, 79059.34718712019, 28779.7857078418, 21779.476738394267, 38475.522949331644, 55493.805594418234, 3808.26509053509, 72584.07756616801, 238.51077487583305, 43544.84640041062, 78792.47266504445, 90634.3350065423, 57807.33493178339]} +{"id": 1872, "vector": [36266.555630333096, 19087.866955622623, 67174.50847701496, 20723.09147032906, 77644.88615555379, 24557.36125815502, 49193.095691059905, 35552.50599645754, 9053.598156322683, 87331.84636334484, 70132.4234497357, 75928.75114674482, 42572.61802544632, 9121.307027605275, 26739.013934728962, 10476.198544413463, 84438.75111203529, 80130.93882634144, 77015.03740779082, 70492.37425045249, 22752.126094547577, 33273.24830017328, 85.49157755465275, 39743.04006399956, 72605.12456662799, 71583.3688384321, 35094.389980699256, 58569.54468359384, 20102.80445895093, 94447.29471396413, 14174.676143123843, 67493.9828452329, 11918.637626086958, 74255.94888926407, 32682.7734899637, 92363.87797338911, 1758.724839566861, 9441.476180232012, 75215.10244770686, 67747.57171194798, 67382.35132784222, 41301.55295984587, 94277.34043686741, 42337.513802778194, 75845.33015228447, 20053.204693929903, 20655.64126816569, 26232.087203485433, 9876.625562920859, 50627.85822516667, 42887.22220026393, 51335.20914226083, 53049.118527531245, 33456.180214994856, 18133.984877257793, 41913.56241974711, 46065.74194703241, 29614.044576592813, 41977.25496338287, 5613.773691658342, 60770.97021327763, 96896.09894312515, 50941.764902144736, 40366.61828998058, 49505.49720078262, 75647.04158745988, 37791.97309902137, 35829.75033615041, 38656.897511148614, 76933.32232378685, 93864.64983596985, 16623.385255304278, 81539.57478421526, 30057.291826894405, 92390.13442342996, 75340.25196421973, 76921.9850148493, 61423.095004896255, 59653.59282921156, 94347.30006984623, 22737.362580800323, 53678.85765955009, 10301.708934020104, 38095.32684287682, 49616.92141333889, 93662.64160655116, 49043.20750519486, 27482.814871655504, 96106.19065794972, 24589.302294884717, 67955.1193349482, 63579.687408497535, 13927.412738861944, 49765.34264732913, 43319.34617558231, 68797.32252666926, 33569.08705342424, 34909.808153341946, 76570.96956576983, 81893.36395596314, 31150.039982173726, 6324.3371661235615, 8713.021667089104, 72182.21247960717, 41897.17040612363, 77860.97652149407, 73866.65127302856, 71452.01492691247, 18350.625629246864, 77302.63662965284, 12288.02354661992, 14398.759145813323, 33807.98752097499, 25766.855495166074, 15166.30332021952, 88495.71035344158, 74385.94977570757, 68674.54143359272, 18273.012555656842, 94818.2884573996, 52053.156545461396, 55932.42310409645, 98664.9507999063, 18069.804335574612, 69380.03646338664, 3317.6530866076305, 94337.12116088271, 75353.2125390193]} +{"id": 2017, "vector": [22769.095565620435, 70519.02122748039, 82667.23290175611, 13800.293456500478, 72134.90842204135, 65920.64056908265, 90564.80011394847, 34568.344752691526, 14057.891102270214, 92050.54010168614, 86265.82466264137, 55063.17110023605, 61074.54133335844, 65063.47031886914, 59873.8529528673, 42374.32635389533, 1824.269643318832, 22830.87652003869, 75850.31164304458, 60260.23033063255, 76975.2846206984, 89460.45859818028, 67492.0953163062, 98312.26901057591, 61050.81759298548, 50407.757139730245, 43967.76463380998, 71062.9342049337, 85828.375799274, 32848.16486839763, 12888.129875432385, 65249.028613679315, 21590.978001342286, 38499.733707687345, 36467.69491999415, 67933.77921049776, 4785.300217070454, 88468.14088396278, 80132.67794574038, 37842.919543649055, 95179.94754973578, 43482.07266197205, 91964.0914889828, 965.0494400322618, 78276.91337290002, 31286.38426107637, 90220.48105344722, 19425.922030774545, 884.461119934854, 52947.312158279005, 16841.91106340274, 55025.75732200047, 60883.77642615551, 92591.68250120549, 60691.76198194488, 41439.77227604918, 1717.891271553862, 20152.567501336904, 45097.696607019934, 13020.62533086179, 75540.34958117288, 31466.873365907555, 88249.9528830137, 66647.12937454338, 43085.11491141348, 47062.10912481702, 6000.7864953398275, 29474.459359657845, 34038.52765779646, 7075.187979665776, 84875.68268233107, 37535.241553835695, 34475.14095070025, 97687.55536803519, 8410.253401865286, 93626.9237843313, 82498.05031836004, 37278.95565607013, 63538.77347369148, 3236.3703591900994, 53476.11858742116, 75788.9105607336, 61474.20196592766, 33588.219094500026, 81409.28109005283, 87434.30532657908, 96815.06327208449, 49618.98354958737, 5087.964849466375, 77958.55542140166, 77442.5274077616, 3663.151042424695, 2100.8898075703496, 48312.650943678105, 99776.21471030066, 59477.16320124318, 15158.040054463261, 73563.65430360011, 64822.327748261036, 96373.30708408804, 80009.04489596398, 34577.593058459264, 76972.65997570247, 83068.91478303273, 85165.59967805288, 70155.81754943477, 36501.32117878129, 6162.6127881291295, 8956.396985143943, 13880.29458661687, 72209.65805564176, 79467.21273540866, 95105.4681977042, 55233.05855403212, 92237.38698505168, 81037.5534907802, 3453.340765632673, 23405.754184207726, 74434.93729036993, 59501.52475875552, 95705.74140604504, 37977.67043891081, 96618.3511600809, 50267.12073784201, 34953.539328201434, 7845.815473082429, 34326.84495923214, 86231.43407680132]} +{"id": 446, "vector": [40693.4740258999, 64413.54094670546, 87286.06226476195, 30845.412609478328, 37718.830876967724, 91671.2608780072, 55420.52657892951, 87069.68343034138, 18337.02402150841, 43429.041575573465, 17987.569709565167, 94572.99206533632, 52720.45080303172, 53423.35746427058, 86599.57587215787, 63045.03627102619, 43903.69171921056, 58587.569461114595, 63119.16366268218, 41029.95024330026, 89757.2580190791, 80094.16289125581, 78480.16349610496, 40275.01165671495, 20327.91046753013, 90217.05797604415, 69063.92135374337, 87003.45452370399, 58354.770986500924, 54062.52542322766, 88820.6856271614, 84455.69898009462, 83904.93707357424, 96403.10045747863, 56210.95615582082, 26110.90409289709, 9089.138588676715, 22406.502091334736, 13517.98038507841, 96883.94750872688, 58583.344310656496, 1124.1388454652167, 267.450928409374, 4666.361846132761, 71163.09931357802, 22850.90008044065, 7564.625140004433, 81698.70708926884, 2559.324791263129, 45819.52187370075, 22120.325288413344, 56295.543762339854, 16522.11688395351, 96182.03366170032, 74715.98383532315, 95487.49422201171, 12679.038472782233, 48904.0895259456, 77206.48737137161, 88028.96125366018, 47072.200492064876, 46056.371443370655, 43595.845288803604, 83862.12657921154, 20221.50115264554, 2594.4744134438147, 31864.260853333803, 65952.01864897626, 27587.898400421505, 36761.1961103672, 27833.788166003804, 79178.01007526164, 82362.25929651309, 41685.10125730325, 37954.175483834464, 12565.021175688074, 15291.50436160599, 85989.94604900171, 82984.86412653772, 35965.718320622684, 64268.388812196485, 38762.54261133226, 22242.32213337305, 4983.794635718708, 50300.04786856015, 5482.396604520767, 77425.39417903751, 9867.345109208414, 73783.20560350642, 41268.147904932404, 63653.58134367391, 62831.77501400396, 18956.621451369138, 82317.29313863775, 46443.40019638404, 70800.8283476375, 35577.32908979876, 38106.725011074406, 12517.45104682601, 97149.41424420533, 60995.229666851556, 62417.49839158122, 98016.78557019965, 16334.557705372632, 63736.34178765793, 99958.59730084013, 33229.79695806888, 51259.15024795188, 74249.61876797862, 7422.235996057247, 38095.619647933265, 48084.942612885316, 95172.27998494901, 95120.54829421865, 37436.265764005424, 59069.55825941663, 64363.65363472767, 74786.81422404971, 13155.049405755504, 62889.491470262416, 93015.102696649, 12296.618645917979, 70911.68823508435, 249.84531863802007, 43224.90869982636, 48714.86514046844, 80946.60232599163, 54119.40274704972]} +{"id": 1975, "vector": [18847.101547436087, 92333.06380946915, 71049.41110229996, 1450.334708909229, 29314.948161633114, 15537.672034317262, 85213.56534475728, 22587.586174139375, 66145.90776286746, 43414.79303805005, 21568.659872365115, 21185.172039386667, 85300.16630440809, 19095.038441849145, 6521.486047709901, 66.17508425119655, 860.6907185604529, 99715.20390959896, 61064.083688418614, 62498.52877164102, 69761.55321390464, 12868.457210968487, 82748.62096811502, 63874.249263764184, 52680.788817353576, 93525.06788976968, 54679.982956993736, 11842.883516867165, 53994.26816414957, 33941.05818414264, 2342.4803305565624, 93765.17737634164, 4900.479478452291, 58142.12857406003, 54394.75424882706, 529.4560539077397, 20303.45291545723, 58786.43562468934, 91677.92547660133, 58904.8876324221, 22412.808732002777, 82800.93233014371, 17865.468824897558, 75750.39444519783, 26087.674939123805, 38088.933520921186, 97153.88433211048, 4968.676379033632, 52591.22524723636, 15311.965073181067, 26548.421582945135, 98869.71767069891, 64792.37576939975, 75215.53097082651, 78989.46787815786, 74498.47967209181, 79281.44505283673, 29825.068089272965, 45072.881779707874, 89546.63571849129, 17332.969259583497, 46875.70330739186, 1071.913413857928, 69383.03917972522, 18381.614468083142, 26490.123172796175, 20172.86322835008, 68113.14566295546, 25629.538759668103, 51112.35607313671, 62376.1999266037, 69125.22365005921, 75420.94380342824, 10402.200198372624, 24219.414019000764, 9634.424665929642, 27017.072090184156, 81523.52406607267, 35963.4082257692, 54140.83163891692, 91320.32734296804, 18041.84543736338, 42165.73286017232, 3024.572124014724, 94166.48602371455, 97250.8287765826, 65190.33048322474, 22849.55459122461, 56095.097709418485, 96702.3959854342, 76033.96158805306, 11859.99708320682, 63859.77397197322, 45952.33844652914, 84300.79498298804, 50355.71704395685, 5444.296379917324, 95373.58671622128, 53850.15838057817, 32046.247276310547, 8498.079803644421, 82507.95436264935, 80648.52313968922, 76187.042469951, 96580.19819188473, 25969.231946180273, 83803.39009226233, 35228.39712697911, 12292.65674892881, 57104.071437177765, 59245.383185478015, 97948.81425820492, 88344.47581190546, 5002.757568906013, 15539.545270270282, 28776.352814630147, 46105.047291973344, 10568.79057854555, 93213.8805263092, 44512.89976007165, 23459.405544347544, 56081.721120785944, 78259.23214584161, 53893.16596719516, 86982.1976428858, 74148.66909762916, 40578.28982703656, 6844.083219936503]} +{"id": 366, "vector": [38615.06499000679, 38890.866092873, 78906.42139674217, 75183.63297075413, 72038.00823284687, 87813.00855423551, 19261.750351224706, 22280.636031192247, 57193.928211727296, 23434.42083937277, 13895.635395148198, 47657.411545266084, 53959.177261985904, 20419.231934083225, 37700.56942047595, 88464.31760045276, 87944.52238438049, 9323.635473746184, 19698.00731002669, 23452.518859144424, 1323.0888756038617, 78900.43676728284, 70784.01567479195, 14786.016297386062, 59301.49320156025, 73700.50028040842, 76828.81683355123, 79918.60598115528, 78267.33975400889, 62044.43498978275, 18304.101907427415, 50982.3319872587, 78913.38631487255, 19819.09835486978, 40988.019027236514, 48545.76690343401, 55383.75465929094, 40357.39115548446, 26344.384507726427, 84899.65320869618, 1299.9074606382944, 62288.252684736835, 12172.088796599135, 39023.424009856375, 49361.14606106921, 30323.1446104511, 49587.00921071844, 34166.94474843904, 10888.220212393362, 65777.68732658621, 16473.602419413946, 22811.57145388337, 56926.24131661789, 9404.596685352817, 28950.835473818493, 46128.10178905059, 58914.64726082232, 61639.214498415226, 22302.577013901093, 66462.46658116649, 42997.1140451212, 43807.505618669085, 9115.921823026807, 55150.59194659933, 47186.97298166127, 90933.24316181272, 77649.59671495466, 65932.98012877481, 8842.268485634242, 87452.72708653385, 66938.47984250302, 53303.7002006022, 68671.07767142775, 34300.76634247828, 10482.602943407594, 94287.63124983694, 39947.58690853023, 5350.534144398466, 66230.44555254004, 30246.25945853838, 94842.49092118777, 61672.5588813854, 71333.72411177878, 25395.402375711074, 29604.35873591446, 92171.88781834395, 77611.2424504206, 85838.7934100446, 12115.714005562295, 40438.26683882741, 32007.835043908162, 34371.27100968097, 26846.66559527955, 29128.06331568758, 6647.798655696913, 79976.06408049433, 72734.11103768322, 87368.373516197, 68207.64095296011, 69716.28219133828, 53028.86586489257, 50529.848141378134, 12903.206904517618, 93779.28719137536, 58447.405034147036, 20332.13755311165, 85572.30385369326, 34882.70723165276, 774.4895885519965, 40757.713807177795, 74968.24520596552, 42471.74074524156, 43275.509605948806, 82671.5106279027, 76801.933955662, 40636.97450151953, 11800.778388797284, 84178.8413299872, 2795.0943885879487, 3580.492616852382, 74765.27872727637, 78644.20416369294, 81553.35098174482, 70031.27402977562, 34912.58556713134, 29040.15767728949, 75103.49084127916, 41986.06398198275]} +{"id": 1769, "vector": [96205.9506378956, 35272.366126621724, 30913.821281981236, 67769.29232140032, 66372.54174407027, 9540.190587241692, 19885.768277773797, 20909.99978910023, 62478.5401751665, 74998.6528565879, 69795.9353984442, 94401.1014997239, 33060.7690431776, 28373.970388608406, 15323.400684993061, 14747.516765039725, 39046.77940960623, 65404.12527391901, 44720.838621867, 10140.055794854996, 99034.94908838168, 69615.66569579227, 68087.95411807761, 7666.114146221814, 39438.145745583795, 36350.05227230194, 22033.664500501116, 94444.33114705433, 42354.500300163774, 12966.290953759863, 86966.38105332317, 89363.65151729174, 74917.65403945137, 84858.61784706301, 40128.64977214876, 39206.435234920544, 52325.24701554845, 68846.20502214773, 75910.89901393467, 92135.63110622647, 17003.541869179684, 93818.06495320452, 66165.0431437457, 14832.837783482944, 57093.25982524991, 15322.062747342403, 79623.58600531584, 35263.865007728135, 96276.59122572264, 77225.12681545038, 31264.89949188144, 91056.30567313622, 3518.261312096993, 43265.732206100714, 74636.24817872187, 70790.59341434677, 84251.79024817148, 9262.700382633993, 66205.8063531496, 22885.508867611705, 4811.17254812129, 63793.051867542716, 33676.964950653906, 48616.91523784662, 95117.97621508881, 93003.12658042376, 97323.69140597488, 37956.753636817986, 2771.8799626842315, 81062.81765595792, 60633.08477428846, 35611.48489729061, 66444.55701961294, 87011.722044628, 50483.85974986123, 38632.14905036441, 11620.234578415268, 26498.781888963596, 63058.468600923756, 42414.31211426, 7583.381200355743, 3952.2006178984493, 89947.16561920318, 43288.165966630586, 9620.516869243567, 84186.27983223788, 59550.06454447451, 20701.788927323327, 46822.70414757213, 33260.34828699682, 11906.522742935476, 19303.62857970488, 30212.44143984926, 52450.515598716695, 65473.42209643576, 94848.94067152943, 28168.631749575845, 85999.81743351842, 1842.4867622576558, 63694.51375819792, 40560.12119993937, 30307.767634112413, 57474.56017223114, 53003.15180431876, 99832.54772209834, 68033.26140760143, 98954.61553484648, 80813.15679846407, 43354.254999851175, 28368.467965669795, 52769.40231275174, 30499.967303325793, 7938.743542327986, 13874.921547598995, 48661.556226625195, 95455.97010289879, 41485.95932002591, 98803.07840085833, 32498.471909979253, 76702.25518228477, 40694.90039391327, 19167.321816244, 13364.371283335153, 59814.29034001822, 733.3659845924601, 86616.44199636189, 59269.22248820318, 89131.13697702005]} +{"id": 1382, "vector": [75958.96834334605, 85238.8802814016, 95997.61995184545, 88759.3496319677, 31805.88103299814, 19834.623314254895, 67427.7857466505, 43374.53055087536, 39879.6290202524, 44876.78004975336, 95830.00545322629, 15737.764526232811, 51977.041509421986, 98957.71459587608, 36002.46491351169, 76413.44423227271, 50894.13565489191, 56841.46899120087, 86063.06977103988, 35626.158325326476, 26435.28840202657, 3124.244382004604, 42121.82050263216, 98225.3921737415, 87686.7116891206, 2175.683442095355, 45878.62723115337, 70338.36147633508, 14742.907097112611, 77226.1646916585, 57520.214371535214, 40010.67049789343, 23031.76824363904, 74636.05737985627, 18875.038151657518, 33374.82230986335, 62649.83125610272, 80069.03011198642, 92470.16310285621, 37519.02955080661, 33016.096847709654, 91167.14293203912, 20791.165120058264, 31433.70406859197, 98617.08015618146, 81106.22630876866, 52586.70147180597, 12320.382996077527, 68837.85376703192, 12403.8710421991, 50768.69895225346, 81850.28974399343, 13269.675332501152, 89830.48374672935, 42491.74871575895, 29693.923799437573, 41951.81269275784, 41307.48209297358, 97132.2844875329, 6384.32918262215, 65806.53247597608, 89372.1311695988, 85697.98205205648, 72426.39940587149, 63293.70367277708, 85672.24465768624, 96587.1235872273, 31054.187020717472, 63170.65379308719, 75673.1820802312, 57881.19321682645, 50849.0989360314, 74567.82203791446, 81557.79903611752, 65272.21963235289, 54538.65083011622, 79408.16087686935, 25776.415438522883, 51153.39581347137, 8940.490651359145, 90181.24310480674, 6362.845679762541, 46946.09040670329, 98540.64026776653, 71002.84974047722, 25730.82545509143, 8636.518014602412, 92617.23491928756, 17039.144492733405, 44001.387590269704, 91136.70697187551, 79202.43136304019, 28108.678941433274, 24910.350300851856, 57620.30251740295, 48093.2067101573, 25896.68593983184, 75968.80805602897, 33413.703722214785, 4387.138553454506, 61417.22226438367, 23476.90525451407, 74698.56054234203, 9502.404628539396, 60383.98023827796, 61677.321495262615, 19012.233006739578, 43161.423252536166, 34099.60597275828, 29324.00775776064, 61695.13167964518, 10713.356593641165, 644.2185313374594, 60020.489664804656, 93630.94508593151, 28594.83353821797, 62539.43605528215, 8865.319182412346, 17554.95075614889, 18339.53042774353, 48238.52336785301, 54832.85813596479, 29416.006162381735, 19371.13652180743, 34309.338957610926, 36874.00904792166, 32650.30346301848, 13435.014452219619]} +{"id": 707, "vector": [3959.9050845367524, 68542.05787616559, 5734.836584508141, 24543.684416587075, 75784.84445160606, 69074.24916998872, 82928.4739713123, 84328.22522033082, 32329.98940926213, 45129.99184645958, 65719.87862174465, 70493.43252439774, 24648.4784187364, 39767.74526435744, 19070.310762425936, 37445.650143574705, 33471.471652302695, 45460.321914540946, 71087.60851600845, 78278.69586649018, 79482.80681769122, 8134.162143337619, 16318.82593135997, 21936.992042734506, 50028.820823888156, 22010.77050057395, 77857.65660052709, 81928.26462124268, 15187.969016443658, 6955.926537492652, 84990.45838060537, 31804.38746655987, 9488.083145257177, 68064.93126754006, 13790.882009938921, 15624.646574217826, 98356.56945983245, 5655.4098647375595, 84480.39281065916, 98618.16201942296, 37804.59386077115, 12231.935162817475, 6905.938628670016, 53729.40102150853, 79827.01025180396, 56188.91314277126, 18202.703097225458, 66377.34312283312, 72519.45443139765, 4277.327647152151, 4606.50980670545, 49402.80758761667, 38867.468181569595, 68594.1328573987, 62555.13340055448, 43516.67332469364, 69871.33643821185, 5713.251037405332, 98034.07841349544, 93741.372348964, 62496.10431737394, 39943.97337960216, 94006.5482970248, 5910.3507488981295, 82409.64517770431, 10084.380480241172, 24870.486023444148, 84808.15054346458, 93825.48266012255, 50821.22259455177, 76668.92941187976, 5361.626134322839, 75918.61690556364, 73410.8581636128, 52952.154808320054, 96373.5296308244, 77240.08154508017, 23890.693965639686, 48872.942485830004, 55010.47243088042, 21623.608991503384, 82771.49609307555, 49033.817269114574, 97571.13024002264, 51227.36469776128, 31824.569826568706, 99442.78078206578, 46178.66917190121, 79677.536041179, 11639.978073278457, 93169.79012676711, 91543.5663535052, 4027.8036795725834, 81263.74299282204, 8238.586796466685, 59130.7050140706, 99430.40722381735, 57446.511203182505, 50737.10790293931, 29833.27545750717, 34067.708208507785, 84533.45681583317, 42223.21687964944, 39930.88556738713, 20141.443193854204, 1843.177306466681, 184.32302954949353, 39751.269096291544, 48055.692563406505, 85954.91728046475, 71324.51596000696, 32470.198898473478, 25288.894683157938, 47051.56450578377, 27127.787659557955, 8715.630423486342, 74854.41176778162, 68911.28366994401, 31866.40878896072, 36772.870242924524, 38682.96838762634, 68616.14598741957, 48491.52076191117, 36976.855137507395, 53214.4920654146, 94235.72461168798, 33535.81283960978, 32070.477939392516]} +{"id": 1258, "vector": [87086.43507458369, 81597.67588041075, 55028.30855257757, 11676.753087606317, 25721.53832013273, 86876.00379534454, 40055.0754079298, 6100.334418154851, 35796.14426000641, 56504.70706065573, 71065.54090788332, 10196.786601062879, 22968.379333998135, 17826.371564614696, 11179.387178014744, 4630.909124179894, 49231.23596191571, 91721.99969359103, 27927.7138950081, 29092.328002634928, 77417.58476989144, 1429.365580525932, 12087.167628728168, 71450.1069806403, 42697.84821979207, 12890.821049791357, 13179.875693820231, 96984.95917090541, 64878.867140670736, 6079.206102159817, 44820.15844017499, 4228.242081342825, 41114.28430947923, 71578.44147326135, 18499.994316394408, 83824.89833589141, 48279.74272560498, 36561.772486290014, 23702.82446027212, 72905.69071229537, 16214.155723538282, 42093.74304539389, 67487.30001856165, 66042.35018832903, 66027.36736326411, 82167.9603342497, 61933.928989587184, 86070.70652725936, 26264.882947215705, 37992.57883401159, 15600.697432248024, 22917.12544018408, 54778.58502087672, 62421.52298061775, 25297.6617425549, 10771.77893326533, 52538.381383825574, 45618.85553469108, 73029.27937411865, 55412.71993388547, 92085.07291893529, 43180.92003780675, 27437.637016184523, 64957.19957572711, 80060.15856698701, 16416.641268715215, 40402.02673818368, 26657.94549637116, 37202.56510319526, 81980.84951472975, 65229.79410986651, 65708.39661190205, 10312.622186238606, 14358.765023774766, 58764.209047260716, 68891.04082618048, 20807.23147875948, 14261.291392372166, 61305.20610964443, 26428.90031255891, 58099.60343817475, 20759.109816420085, 44859.997089518445, 88631.53099303192, 12930.549391451195, 80025.51353950564, 17913.066058122273, 45120.294014350045, 98008.38568082613, 32441.091462436878, 41683.89147452896, 90952.62733144096, 85393.36679299247, 39384.504754617534, 64323.51123870064, 35620.83737851627, 31340.399137559725, 39224.709258101175, 11067.925647600285, 73323.0761057578, 41393.07797852757, 78563.20571178754, 64054.83817227997, 67926.86687254348, 98892.69080368105, 9703.208741375247, 99050.21719932217, 29878.101447983587, 28863.015409658354, 99234.63154863556, 16215.48109133516, 41076.302411853896, 47412.92812545488, 61689.7897483702, 42624.986991426114, 1266.4270442324987, 94422.93952784163, 61801.524907741856, 7491.509684269826, 53758.10299640128, 82100.42407366156, 61030.797393270885, 69829.0635586362, 73600.16351086732, 44177.38360589605, 34192.54852341801, 61360.44949528387, 98407.55748260194]} +{"id": 840, "vector": [8611.630420819583, 85859.84959745895, 36094.15381868419, 21462.036794092288, 18730.465121229056, 5002.442649527894, 63339.43293204817, 96811.36444560293, 25184.780930927263, 18409.822911849984, 61019.78745750205, 5706.541257239072, 19825.872669200096, 10162.388192053506, 38915.76797920179, 94729.02095576783, 86776.53837080403, 51066.261048005836, 24831.88789069941, 69765.6229516329, 53110.11280503109, 86342.66794109215, 44634.08313070437, 31054.368080243632, 20219.00755865621, 24005.971516569145, 77500.87850688827, 73268.80125262051, 72462.37816418998, 50389.139894458065, 11461.752804608994, 75265.30243447232, 7642.996950314873, 68781.9459339569, 59610.35805826287, 85326.63524935837, 94748.79528394472, 90269.68257638824, 14827.785267605708, 54403.60401466623, 39007.51623527091, 95738.06587246215, 2479.8636364020576, 24393.231434160843, 70952.89928230015, 91195.2330824539, 38963.915535191096, 47020.61404489101, 80121.84512820035, 75689.13957382194, 55679.454524512315, 43673.88056826286, 19830.32981751086, 30938.222733997354, 59544.5852521597, 76157.92377544027, 59623.22767549761, 46099.52963257128, 12152.3234152492, 57957.068182477444, 99533.35257252956, 7485.296375544648, 97985.45572558214, 95762.1618110004, 33511.03717361955, 10685.722327064585, 75760.95724496289, 36635.78225319881, 34563.675673836646, 85877.35364226863, 61115.08859017181, 68424.59282821229, 80269.22573639892, 54227.042766682396, 48012.85594292568, 70954.5872336754, 4940.23524995737, 78433.7202456647, 1866.8630322326574, 59189.8889138756, 64435.58138086175, 57080.25832727284, 8989.649677717105, 25383.718200921136, 28989.776016606917, 37438.10732445178, 96029.42854985001, 95539.6432322889, 18780.733592413257, 11531.14492709113, 50533.15918361964, 18747.9448573876, 70134.69268097568, 7094.7751351424395, 62427.53051676349, 44077.797401677606, 69421.30092919269, 3246.611811580924, 96004.43176121586, 53206.669599616405, 80302.43148486069, 19449.80089536845, 47483.149626550694, 75303.18623776038, 88748.73049164453, 36364.516261109915, 16170.216865387587, 86049.51355924534, 92817.48349346334, 68915.75071632907, 85277.56670320765, 97536.6576034036, 55784.75517410182, 16539.98642327418, 67793.36051403795, 70316.96427788676, 49919.20123480652, 26563.966164662455, 58579.58581325103, 58347.37521307689, 67386.74749598444, 90170.30206457125, 40522.04748419611, 12370.7554878576, 48961.14780490526, 96206.57907187134, 74931.9872552398, 69112.33273866418]} +{"id": 471, "vector": [38847.81046114183, 43947.027396488316, 47638.456107521044, 3652.2568598466255, 10963.176436984568, 48105.231777773086, 73550.31010338952, 22662.856444232217, 3198.7063378023818, 50739.89919386681, 30659.590327844755, 5555.823847548269, 77893.97169883698, 7937.264878411254, 93676.03035788024, 16929.009308899946, 33494.754404879764, 47388.9773092278, 13018.308697307635, 13082.225604735375, 72071.27756936819, 56876.467216192694, 43054.46328570948, 91262.62689219705, 15197.492610325835, 42301.01893618414, 14886.589896523217, 50904.729546152135, 26880.793791820444, 19099.87856259373, 88298.00136995468, 28184.254227212423, 38807.33735160831, 35416.72398958971, 10848.67036041326, 48317.15575361845, 79524.73308615698, 93891.30390381982, 96587.99105144516, 31032.282942479327, 8277.939588983285, 36320.00790965534, 34257.52110610623, 93846.6424001844, 21142.485920912568, 88500.23633897584, 40814.40963894707, 77125.41249189475, 11868.916372949845, 97706.17183269748, 46580.07598414871, 75338.36925945213, 81853.52155727791, 60033.472969093404, 24976.581170825397, 78544.64638153175, 71698.13794286373, 6190.670562647616, 89727.4148723151, 74084.62273884023, 71493.44025620096, 29270.60948916105, 90881.74412330474, 93687.85073367985, 88946.85537917691, 11284.808181159156, 32255.845571596776, 141.94332211580064, 39844.667223174634, 46207.90491528658, 1803.8775736880398, 22573.022299463053, 52362.515831142155, 36155.42674252726, 92212.92450925893, 99135.33957221016, 11173.510944692554, 39596.58926496993, 667.8547569331395, 92451.6886818402, 72090.54696260147, 57742.368750294285, 35005.817625441734, 92282.68818499043, 56883.808786284506, 37051.56811209446, 34968.772831967966, 88055.97885470971, 52487.450223685584, 74917.44594200568, 53607.6010275898, 83289.66882039538, 76435.2483113248, 16492.712165624747, 74631.59340701844, 85569.8110584413, 1784.4291600430217, 30915.6797758677, 72507.83577256021, 3581.515672589086, 39669.200524608306, 4499.707240430428, 60957.12393572773, 83051.88868752177, 68010.01078858413, 29165.33110103299, 31176.071982676913, 3866.51869816339, 7171.926284172759, 91236.15334287677, 26266.371782884125, 25018.09474352392, 40889.79610999321, 28248.782950253724, 23501.85465782487, 58960.74715818043, 90864.81180991724, 73651.42947429542, 67773.02127459095, 57286.17415245676, 30710.16706049391, 35260.62735695292, 8335.35184660189, 10576.729513239292, 4514.278151051354, 72687.96131194239, 73808.54993060845, 48655.63632535333]} +{"id": 1685, "vector": [60711.99448312531, 75314.29768165172, 83275.42482901207, 49872.241014622385, 33968.6271103949, 55191.205399449216, 71780.54733412844, 56731.89533712404, 50777.18777225509, 35978.8728406868, 92215.0006661362, 55256.9277959139, 91234.62572608687, 30860.04803988446, 12357.795460456866, 68990.98971995812, 18839.398794145745, 78563.55605162872, 1234.5780997610411, 43934.70305977017, 11609.27402479004, 542.2691081011233, 86989.89636979328, 50702.905387921295, 23931.77366188638, 60577.900737242366, 84536.36649279513, 11797.288565249486, 30564.38972058931, 45674.09200696475, 29262.307751310367, 43966.495871207764, 6304.9890766286535, 91794.67996706565, 34231.18814764903, 27727.25519580185, 17635.61232049221, 32632.50728118642, 11442.843986069673, 67832.08999549353, 47083.12535715829, 46134.43586778474, 53588.43349998216, 59927.539748881456, 10498.325037120992, 79368.9892079895, 27576.397213863645, 27902.202420985002, 52335.38863505572, 67630.5702735073, 50901.44384706299, 49642.36152959961, 73171.26559089507, 19453.634976037003, 47421.98799376448, 34780.285471608855, 47208.86155421357, 84807.18606620519, 77928.23015431814, 37225.41298024516, 68129.0855727413, 1981.283019962632, 51920.072286054594, 79453.16075067197, 67081.69711045406, 86041.16685189967, 13725.605659072182, 88251.77189676961, 57405.36169536139, 67.55916645745374, 5139.326087633977, 91282.24473044818, 83393.22914205493, 45462.43278413364, 70333.34081798201, 67538.61164806764, 52809.483276297484, 63904.84258256279, 99010.77337739052, 51815.27934645294, 53001.40756189936, 49282.73925421794, 3882.8609571042725, 87879.97870333274, 83673.37643469102, 16938.73308708269, 83309.08678173323, 66869.96755312644, 29182.88004593911, 28606.30468317179, 41010.99993637656, 94365.53766448458, 6011.656450453129, 38904.07565177324, 7903.3993467489845, 31708.067882701864, 16434.1172084738, 72235.20632317917, 51132.64898248887, 84656.55532274743, 80415.88432865798, 55376.09283400516, 61093.97388144035, 1787.1147125444686, 28808.959671628465, 26395.533593250497, 6052.446620250617, 78217.00564336163, 67320.76107822343, 40804.53615323947, 14004.389633863668, 43394.06249702128, 7147.119321472839, 77617.65954320387, 10699.379321602342, 10986.781294440018, 67448.37768776961, 22619.605338445715, 42973.4044010959, 17635.96733470758, 68917.24795549942, 88014.3644985796, 97896.6863360741, 1161.514248721751, 56564.06412025485, 79143.69724674041, 20641.827455813243, 4649.28528119476]} +{"id": 230, "vector": [88178.78511940745, 40686.73862466018, 24269.030554483885, 13525.339656086566, 86590.18441453524, 91876.30506918536, 19713.30048716139, 83937.77542817591, 3358.988894383108, 68443.30967825197, 87138.1156757196, 66385.77529134607, 74166.09430504634, 85804.33653220595, 76879.9334614865, 73541.3822316036, 74959.20971293004, 95946.08212745748, 69537.01151513522, 62284.02491687492, 94218.7122260168, 83442.39291537843, 85572.60518957501, 89438.54092673787, 42210.969210882766, 37712.52448376898, 73721.46789276919, 9558.858535110492, 58019.78426233089, 94632.7561510139, 5949.006596165862, 56163.34847716945, 2187.8754822851843, 19083.5616320573, 81055.72594785473, 44744.52321188237, 31746.164133947586, 13444.422060295146, 91920.4630033545, 73694.64545789221, 61512.74398399709, 58805.75485995364, 21742.065758907203, 38679.07872454562, 53265.43358918002, 88365.36978287123, 59691.24410645337, 45509.823034402885, 71270.94205762778, 94680.16397140833, 62675.966850476216, 97746.42149558778, 47515.38717159136, 76427.81073945427, 87757.6112339916, 86046.36136435869, 42993.09877914481, 30473.40378686395, 13362.059519284785, 47772.33485763058, 4338.489160648973, 68783.33852856183, 91524.64535290247, 26642.502590470973, 48721.201519038725, 85380.45152246827, 9230.744747764486, 30778.41386829122, 57307.24629383364, 13756.456621173007, 81566.4414087624, 49359.66300046065, 87255.95360657382, 16046.163111687083, 75691.35062627896, 17310.3401776161, 74089.6958654791, 85861.06519756284, 55866.98449959572, 68800.17366668217, 57532.892861696906, 12314.121328116868, 58804.359400065565, 14353.505112553732, 20825.124335934852, 68110.81892820125, 26285.14680960148, 26105.819766846704, 42195.010723745865, 28302.611214429606, 22122.832831033513, 72534.0272643302, 78064.74439727243, 92703.36157576652, 60690.36970334588, 29949.004756557384, 28864.72940023771, 95378.05936560345, 36007.11608751809, 17962.596730005764, 50911.341756990056, 40150.719466915056, 28481.72490827825, 32336.636319530222, 87194.96180474332, 33864.088542826175, 51052.48677859718, 70405.53693869256, 66309.6737971424, 18739.608147336683, 69593.11146776834, 97380.31573911053, 32349.867290452992, 41331.26448586376, 14088.073024171232, 91672.2018935315, 61353.52516345265, 85408.35629776772, 96118.93113288605, 92561.30076269877, 83844.61998461976, 577.5208456638259, 3947.012696178831, 64704.88972335628, 67541.7817315591, 54653.12141558527, 61597.37693435619, 25517.86385142256]} +{"id": 1192, "vector": [70533.23638534665, 43424.54102202519, 34959.89845010815, 95969.67721878127, 24784.989271191283, 40817.066023970285, 83699.5763470948, 30999.64261410124, 79989.53766601885, 92333.04570642213, 99351.05974361364, 53214.67802490205, 25856.229399805074, 99012.26672126008, 22619.896030246866, 88484.10605888999, 69496.75987015225, 39322.09364756537, 22351.717402521586, 38828.38195324932, 77331.57761681419, 15117.646038316434, 90621.90960640582, 47417.308468442665, 68517.4988929869, 35332.33381054253, 84405.63474614921, 11197.136913814344, 88093.30737907936, 78055.64612134364, 28340.56344486047, 98158.93843518286, 95564.22884995004, 36685.487344839596, 44254.13773496313, 44882.787080269016, 17351.857088730427, 39258.866291103834, 79185.74497504279, 57118.66153597742, 10065.79343572469, 57436.60090655078, 52574.087184705444, 23729.842982274462, 61585.14418816408, 78154.16221804076, 82437.25633479648, 36282.360060870924, 37151.23292452147, 91909.12375209614, 9910.044143169584, 9756.01305995576, 25194.995606209093, 88962.5432042116, 12290.714864705054, 59376.8795153513, 22659.507089128696, 56858.484217851765, 37943.29653760801, 88037.0650318804, 64930.39367522349, 75985.20678800998, 35570.14861882102, 58231.21442946946, 72358.90786461842, 96604.03694785049, 15227.201668142465, 12272.316702727638, 26796.877020024913, 80929.91156538311, 96812.85151060949, 85374.04254096505, 25967.429881194937, 52151.76310395142, 11407.962160945484, 61380.93819546206, 51969.7578222461, 98790.65582180089, 40241.176488430494, 12441.12211449041, 8781.70045275296, 85021.76941798431, 53291.74922573066, 15646.578533209831, 99956.99402539563, 90102.32530484877, 74491.26113065981, 88640.85466639971, 25190.20427729677, 43752.14657322345, 70148.08592545766, 56552.035608565246, 88500.33822969935, 32996.774850499445, 68678.90056858756, 49145.1385498281, 45381.360309347205, 17961.876463724846, 89444.26576888685, 1033.6748939481, 10161.79446713853, 6539.321618044458, 32126.244781483725, 80736.09778976813, 96156.36864214907, 59993.23309819561, 55668.7878618726, 77628.95673504453, 54336.29647531692, 49704.729383237056, 13797.198706699986, 78442.88830839348, 48634.35869523525, 89823.50349762329, 46832.95520153794, 11847.893619495742, 19670.9686809309, 15365.954669331593, 84442.34031178718, 48065.31591562211, 87038.93103326822, 34709.9424406745, 70390.00704644185, 87487.96915934546, 69922.49641426456, 33473.6303698025, 84170.45386027408, 87566.24376080485]} +{"id": 234, "vector": [6994.278668658038, 79312.71370307359, 48516.308941651565, 39919.08069838058, 12720.476641440358, 37609.60282489704, 75939.20757387331, 31916.85098620375, 62679.63547294744, 85089.62561855404, 96962.13494868956, 81620.11010713359, 87143.68353892144, 56938.527498024094, 32722.938444730065, 52451.94562482913, 10824.399268861629, 99272.21652677335, 7827.728409401313, 89852.72266003251, 17597.81784547245, 30496.705913755017, 21455.11383588473, 96789.49401375752, 35032.947899692845, 78857.185390151, 83256.29750237714, 90443.71420826764, 24312.84640622776, 85692.37278749324, 71784.2063971462, 55699.92545973272, 21120.501549820492, 50713.734858973694, 68903.89932724333, 45494.34338595153, 47353.96063464357, 62054.59399276262, 57711.33867334736, 67446.59579314271, 81497.06157886924, 56145.773904730944, 76083.17242581662, 52842.39145412669, 18681.205823019554, 47905.04331454142, 90319.85302975749, 85360.81039908422, 76458.40976879232, 49417.06343463181, 35820.665753686066, 71545.62789435347, 60415.636554394805, 4573.347410538709, 36795.519268190925, 16158.171330565174, 35722.55898355259, 66864.63208180007, 26682.445254849285, 90164.40608646587, 81030.6793037463, 46228.670390279905, 59964.08343231924, 59649.88817326936, 87703.87713903758, 29294.941303708954, 57355.576587230185, 70358.46358320372, 74880.8953929873, 59199.64996802104, 1255.8416110559967, 20237.25992722496, 29328.82561622123, 53623.79626553097, 273.96425025052463, 74137.25358175652, 28288.757594226256, 68185.9990076889, 32347.749066175336, 87520.01900898894, 60529.946965650226, 30736.1459354194, 70728.79776407339, 48648.118026827105, 34895.62492956839, 34145.693407186176, 16319.736690374364, 45700.12742476495, 13259.84340653944, 30649.45035365786, 4524.699557308787, 96989.14057819989, 16479.900303738847, 19202.513274221554, 35316.69523578342, 13415.920196697207, 97457.66524483445, 14096.108761060288, 77304.37138487848, 8213.093164324748, 88909.85224739123, 39663.20431738032, 4034.019640181985, 50761.5355121051, 33934.81915006188, 99416.68904350951, 27120.19958469485, 93010.30082138765, 57699.74122469966, 65516.15531815615, 46435.12884280061, 67509.96341137211, 42775.39370081608, 19253.527742327493, 66488.79761676372, 4874.413818216283, 48113.52151591565, 60703.95145139744, 64282.87642004949, 47328.00620844718, 56931.42311603862, 89206.20414716277, 44660.7756590599, 52469.42025475588, 33112.030625716114, 16569.676584058678, 99693.57105745754, 61161.21869561432]} +{"id": 1706, "vector": [66015.16936156868, 72062.47059592835, 38935.457937288134, 64043.123469491184, 61426.38428123264, 17983.167315627168, 28780.128574730836, 6653.30596208199, 59224.81742990982, 78126.85157121469, 61885.71684648627, 14333.864365308846, 565.3835843224653, 50894.19847314727, 93492.61022362404, 86810.05144898422, 40699.44184049899, 52588.640982377125, 88191.04623293025, 82730.6040882469, 24608.24544426652, 27472.73614148771, 78842.67874928174, 70452.69628320812, 17141.535123180653, 60776.36139716756, 75739.25178461087, 66842.53224341164, 51316.65096035932, 44672.92197072974, 18179.30027383128, 57354.07628963278, 83104.78114416818, 94244.78779799736, 21893.177524709896, 41106.76500925574, 89459.78475630809, 91047.43379166993, 90514.96026843511, 70523.74837299206, 6075.378582153879, 22060.830298472956, 51675.00654869205, 91579.61443315735, 23514.90775313767, 48017.59372934768, 81228.64938784417, 9771.43462980996, 18458.762850758903, 10432.320550708451, 30888.65822094029, 50067.50889258449, 26760.154908898916, 79055.93638883406, 92918.18695321717, 36576.352513910206, 35189.279102713524, 10713.357945552294, 56392.18249460759, 93426.05783361809, 88995.42548846114, 22864.495447989473, 79807.23034408693, 68374.70485968063, 25777.603987271814, 15999.311934065763, 19687.341512350355, 84105.42540271748, 13204.170970657015, 31581.28340679811, 19430.875355627795, 85270.11867120636, 71401.77374622563, 72172.11599664326, 97818.84831370862, 34952.963981351495, 68026.12048195729, 19353.66441948968, 9913.314869104628, 93726.9117740856, 89721.66770737093, 81649.32900273666, 77802.07992014935, 24266.267140936892, 15938.931773787246, 95860.9970989503, 19492.365873390794, 48859.58554217512, 68707.10194219851, 58969.20200577419, 56580.07330716188, 11808.755444781893, 3766.986667336458, 36058.05918073149, 98612.50405667436, 39883.36498202372, 36578.405138985836, 85774.12650149985, 55505.51998352181, 13066.9894091893, 79046.89111473065, 15072.40315375743, 79058.65275285163, 15789.72935276074, 8551.043615464681, 93737.33084222206, 47861.47541478216, 76707.33708355331, 92454.10151331007, 62980.733169592066, 21054.07878327784, 36014.95143743607, 40653.92178907963, 18544.812283587155, 15885.300425193216, 99699.98444255748, 65415.22861201336, 96177.05369479474, 36323.475314955365, 91088.14498375978, 99355.5506172402, 23656.561495984162, 79471.02088630442, 47351.09220049154, 93407.93416516097, 68731.2488113207, 60942.4933448368, 46708.77795493615]} +{"id": 1262, "vector": [87950.99011779366, 72403.60652863765, 15378.761313076016, 62869.946881539596, 66965.6669859367, 68338.33155065497, 64831.130584651, 93025.40362233086, 88285.84828678962, 29023.26219480249, 22631.017277360686, 18807.980842198336, 10271.33778707976, 92218.05994950031, 48896.214900199244, 55169.42027643936, 25756.02814041267, 16602.343266831733, 11531.343386820381, 49361.0543881557, 57652.767654239855, 9136.39226156634, 48635.24623737607, 58957.984724439615, 77191.47251300068, 15291.42222524994, 16489.02152120445, 88005.10993033033, 13557.566038374347, 52502.35442679413, 74806.64968092568, 25605.85459702056, 24684.051668310192, 86673.9668208857, 3582.2076228957058, 17501.093405401567, 63218.33886296243, 68818.0725457964, 70596.60404169241, 11042.752951474933, 54048.21749658721, 54512.77331904894, 91099.32624994725, 42594.79785138266, 62307.5844405515, 89393.9295402833, 56628.068622647945, 51527.91443361425, 93225.94521395199, 35464.08911264652, 44271.87810508161, 81788.5192638244, 36591.71232002311, 14244.823847830057, 57395.96966872226, 36074.96315202521, 50240.420107595084, 21209.70794656031, 13155.009887572034, 79604.00118054044, 14988.873638936173, 18324.880581056725, 47025.80270628096, 31806.745851774453, 89340.66217284692, 94508.65850410251, 65005.468875810555, 77494.16473819515, 39226.012585632365, 1527.586119106228, 71069.76041428339, 2782.601017721531, 53209.419155249205, 64627.41549528988, 81891.64710855711, 3121.4647434826047, 6041.524270480003, 61698.88265762179, 91837.93266340031, 97450.4435263995, 49010.15215367346, 91558.1016807754, 32584.439409044742, 77420.31735295446, 96254.09463952704, 65205.99312012755, 94147.25676609085, 522.8157144386403, 47029.36462424597, 98409.35076132957, 94081.2991155374, 4043.579025158572, 41543.523929022784, 31807.377390408576, 58497.82979804767, 38679.77564106922, 98197.64133646814, 45871.660977364714, 31985.331797653595, 33738.53078011757, 25143.166826135344, 67637.30998410431, 91284.78395696389, 6304.838274104895, 2415.6848642340688, 44397.332974428275, 51780.17675068376, 4596.046542525855, 62420.69724688288, 43496.19230063041, 93709.58103497067, 43163.14577496314, 2538.5990143208414, 66524.75662532578, 41274.810069778054, 6561.483669929702, 84360.35934169582, 99970.90093049817, 10349.176598586419, 832.5278742928322, 4490.760755242718, 54730.98402082566, 11120.823159584192, 34138.73222191816, 15703.825220525436, 11795.815756938644, 18135.975734534517, 76583.40179546257]} +{"id": 896, "vector": [59450.86927151437, 70274.9405519863, 5134.866115303216, 40447.08255054337, 91108.25656534638, 38550.997838687814, 43138.79516198515, 62879.99033843883, 84386.97364655283, 83610.359162238, 85005.89598515136, 69653.3664122932, 42530.70073569623, 68726.8476295255, 53500.53137159265, 63498.163395532436, 2746.408931928124, 50456.56680663015, 51075.08720542074, 89831.60784734701, 14145.417056618948, 68612.09964429574, 23543.441085679195, 31998.651981549643, 75619.71599557987, 28294.46009193485, 4142.564439720353, 60828.432321007254, 10691.501696112071, 51306.82724451542, 64900.12984136921, 23321.908231855472, 68348.59111046352, 56515.76248786413, 27316.931437610616, 7864.641459136435, 47254.900190645996, 29935.04855167759, 24295.632962522963, 86333.41353252195, 21658.491914983348, 35583.60436260396, 51138.8711515266, 65301.24964032263, 17270.87012471251, 26920.76927345416, 1975.39851643026, 21553.825619751653, 64272.44943757855, 33874.99796197322, 12044.276340132586, 7865.137074239626, 59219.96654259611, 78993.29618001427, 15408.201225208839, 5182.9226520332395, 40934.089095478055, 78759.68949748926, 55032.8731549123, 50071.531601971474, 48408.638976144335, 32809.20480953232, 76591.7645566505, 132.0027730787765, 27175.296686898444, 55750.57849482186, 68047.16690700714, 97313.7594803272, 46511.57053453279, 86472.3707948476, 80522.56487437333, 8294.115599512119, 99809.17468280489, 50453.814159724556, 97540.5091191908, 27006.039145160023, 50721.24283095976, 12078.033858611525, 36384.373866138165, 5525.536542368858, 44731.947746750055, 38089.5686078964, 68794.81034912403, 57680.947058632504, 87731.82587484235, 64779.36192394151, 91532.44346843226, 85794.94411079376, 61447.27256387781, 43405.56464375955, 82193.73870878329, 44715.3932031994, 74117.27851090975, 42253.82697720256, 82456.90871261412, 54908.36406012769, 25626.588742807533, 58645.962466693556, 39164.03196132033, 20562.9078670323, 41001.63666941796, 36026.482221760925, 41683.19402927192, 74367.60496737438, 39021.822719485885, 4221.263900355332, 60968.588075998974, 42288.0382994706, 27672.854156053138, 41607.255563829734, 26974.401715348406, 93440.84595616153, 56529.97502568993, 88423.76604826807, 10904.343764006197, 21473.974817000908, 49886.6861347409, 6071.320164816984, 73432.6144890707, 12720.186918857167, 86972.45954027855, 98573.83321052974, 54908.17702343309, 91852.44275198426, 95501.55869291909, 81176.1396370073, 96376.79741414577, 43250.74391372009]} +{"id": 371, "vector": [3862.438602357154, 32868.18126262271, 70888.25100935761, 61211.09571569916, 93308.3123036127, 10865.103721832358, 85787.57968960529, 1049.563876764703, 59644.76150042762, 23892.601106967348, 11232.353234937065, 32106.060274330273, 32563.70194574859, 9247.827783230734, 99114.36630862237, 77349.2433608053, 45407.95651305771, 72943.53057616825, 19526.5416281458, 60691.74161911427, 63008.875199216505, 53198.240264599925, 18757.254714417493, 46071.318711394895, 30909.395548395423, 71752.05101564943, 95401.77289033435, 55964.42794851432, 68580.68473902866, 68452.8084396962, 58833.45772819164, 78068.5289497407, 14603.945506994343, 76022.38333928346, 15701.619073663975, 98446.54931046635, 53837.84227036129, 12048.686296168187, 68945.7982170874, 60072.05900918483, 31983.18420455205, 95423.3807741101, 57296.13850201874, 29158.02057207605, 98882.72693923728, 2982.2964164924915, 46986.600573927375, 59620.23626405056, 91366.87196708027, 79802.56178191504, 30891.965036332545, 76046.41793251621, 39440.145526610446, 76010.50216589446, 2191.386224290559, 23905.525395753026, 89604.90835047363, 50499.02278954961, 68262.07727836509, 70938.35902192844, 18015.52299365875, 62291.4530044691, 71192.98243682862, 29626.660159849816, 87514.92851227945, 65236.99480693749, 83878.3611834868, 14865.634738587098, 64883.95252296101, 51203.37548954104, 79878.93245250784, 85015.62010416028, 95858.92760686691, 84428.76113643919, 58812.628724039205, 61817.46345351387, 44485.900059545005, 86194.5615464921, 57324.673210036926, 60831.94326222818, 90231.82511672955, 87436.07152954812, 31561.12869407872, 35950.16422663189, 34269.31954333215, 90515.05342661579, 8999.074631157888, 81314.69971419337, 67160.77234759011, 51387.13944906436, 54132.22755172226, 54084.4808479091, 32029.779498649303, 70456.41871291981, 66054.31223209281, 97120.74383061581, 53174.016561334734, 69887.0249834131, 78691.63470440726, 79366.79049224437, 60996.089951960086, 77759.3185650059, 66230.10493332047, 26373.538213036096, 48213.436540658695, 36255.068649000554, 84258.81866999247, 98809.20195608216, 79634.30978041442, 15049.32618807897, 42959.08576369929, 16944.149300651246, 85623.6924400995, 58640.13391756251, 47969.20193297668, 37629.91668058746, 22662.33364960475, 32866.84446159156, 57157.61690393187, 69113.17829571448, 15017.323122399717, 95037.22618155605, 6656.700475444443, 16322.810418998313, 33868.04027083371, 82862.39099384028, 24393.12677623624, 25362.638387563795]} +{"id": 1992, "vector": [39767.88442211443, 56740.64964092497, 99234.73430791093, 48885.91767920225, 77357.055162995, 56275.51065217675, 27720.272947942838, 94350.08464420882, 16819.89561769398, 2704.318494123492, 86119.73475317913, 14213.008649405534, 56446.00550959975, 52034.79533423714, 17914.67442396767, 79396.64948559602, 27885.724840557636, 81223.83923759268, 7027.084865020894, 16038.612885744962, 9636.706721770171, 28505.939943493362, 49415.629516761976, 73741.6181020623, 54134.96833438161, 96816.61928601297, 46519.49935179645, 81984.51053661069, 49036.12088714197, 72492.30984661337, 42813.274734456674, 84071.09225327257, 51709.895706853116, 58623.040982149556, 52399.389681493725, 40880.49416973845, 7180.173750672625, 99299.8427265002, 84115.67262915164, 45637.99488160291, 65164.36436736189, 22468.324234110638, 77059.59034643763, 47964.21941354268, 96318.72751210246, 54820.22989294616, 61167.293665820456, 93048.95462674186, 42487.42771487596, 34060.172177151806, 64462.7136751458, 11983.019746212098, 795.182730803956, 58235.1811797863, 57217.39312683723, 79835.17378435555, 75610.64702026089, 54129.824682709805, 59790.31696998433, 12462.218059386121, 34263.36054847118, 95381.78320669143, 14024.217118928527, 75298.75093902682, 44381.27632470248, 32585.3682669053, 95592.0262305846, 2923.8339330700082, 80954.81318683816, 88420.69842982173, 21216.76161293492, 26151.106452006446, 36710.875128361695, 86960.7544830802, 24945.488303724094, 97032.0964611822, 26386.045185640007, 81319.9390591387, 12338.96562623714, 28464.89090536464, 24114.496003931275, 17848.829492249373, 45363.50220063092, 76916.25946183378, 89707.22113571432, 92099.14099153044, 71861.3864330627, 64279.122513341455, 98222.23634368643, 41042.21898282479, 3995.619909974257, 39842.59867223239, 817.8990635051009, 62161.132420481976, 16647.36299818501, 8155.08801927457, 88437.59029489859, 46970.92518204479, 51620.70883126979, 39670.86625834338, 3346.701026604981, 6189.622315483212, 95981.4524023924, 70613.7792397722, 25670.27568559542, 61340.056220651364, 32631.40779118532, 19834.47747220134, 16142.78076237291, 35478.44732172298, 731.1348690947627, 4379.288883092447, 11200.33346122732, 58393.31265707471, 46180.305548201526, 36776.86941584096, 87066.7789780979, 79883.2440718141, 51126.71277247483, 52502.08131273818, 4808.878600945932, 75787.23720928474, 87294.49941860126, 28765.014089922737, 43849.19612462561, 32379.896232369454, 70679.4426796428, 94955.74617231426]} +{"id": 1230, "vector": [62793.320615588476, 75928.46692143315, 51394.535777587334, 28194.09165956668, 58037.46238814852, 91003.46131301024, 78937.59837842423, 72575.43112527352, 57487.97858401543, 27101.352835243597, 57478.6451537327, 41251.87867914819, 4798.0359243699695, 76944.22263088367, 39976.79976975765, 3732.37533727, 95793.66269786957, 48192.36035737199, 1039.0569243184157, 94060.85005103041, 78216.98438516469, 40678.24819680392, 9123.829578108056, 68826.28310552755, 15395.620241287023, 64869.16179718607, 42629.27335757679, 13266.752052131536, 66457.64264374437, 52909.82101570902, 45700.05904204115, 86675.31173734508, 35530.49331771393, 5249.231362250317, 54572.76534439758, 67606.95241336989, 87660.88250101315, 27172.597762741712, 97595.34718767928, 10654.594049633137, 16860.229845648155, 73968.10949515771, 59141.13810901963, 20294.18700494615, 30009.549525776223, 29872.9694900694, 95379.21561204443, 31255.545628746484, 39911.67883730294, 36335.95503388437, 94936.0553171587, 83944.35064138567, 17040.16828405689, 40643.61582881193, 61038.74697139899, 82774.56688254444, 36522.650218928124, 3629.023379381935, 9764.300747702926, 9666.271517599178, 56021.38077790788, 33467.91117717469, 1495.8200206755423, 74885.30597089666, 91705.95817487258, 53979.90499511105, 70233.299559295, 75396.67161805618, 87728.69670334684, 53135.930508990445, 66356.26795175747, 18700.38158634534, 49230.438829765655, 50869.3850684452, 43483.77869642841, 17736.551505564792, 21259.082294640953, 56356.78420076056, 68519.15221764133, 86557.24498758269, 555.8990274669662, 65199.81770657052, 44834.87153024993, 96451.95683600096, 10085.976146589981, 56477.06057173387, 35823.95360947183, 65030.81367468362, 9719.79955845762, 48084.482697852196, 3492.4385603466226, 15707.599805685935, 26789.46895700608, 6221.409891032626, 99034.9456263521, 97735.33226284258, 3866.1952472669236, 35662.40208107953, 47434.095954704826, 91461.65805347088, 10955.117987228969, 56159.616315641106, 64229.529521587414, 54407.24881575516, 55940.0068823389, 97754.57563971526, 61579.28141368038, 71649.65640122793, 760.1216488377082, 3498.1654074055955, 10750.86381301662, 11817.833629276653, 33121.69081026159, 90314.99294961084, 69160.38190043179, 5486.688976141351, 59038.26058010814, 33399.812007808025, 27555.92870397442, 36462.82725835177, 25063.75304217153, 89375.67751156751, 63540.98526026092, 99489.53598357938, 56644.800546045815, 45559.29167399858, 22538.92215621751, 44122.3945678898]} +{"id": 452, "vector": [4981.699822087482, 95855.14395413127, 5313.461080119075, 81641.05229847827, 21425.160699817392, 57071.71139127184, 36343.76584829366, 87333.91698135526, 13515.586102122368, 49.17545902030085, 69047.44009658752, 56251.26450841096, 82358.67711682539, 92596.44286613874, 2623.825227285692, 60875.395134674916, 4192.94472969135, 1290.5946638666733, 99035.20445532526, 74654.78913733366, 51379.28300901041, 61248.90376556803, 9867.565028850988, 2405.4259263987187, 5395.117329490539, 66148.02443264954, 34611.998389287844, 768.5101286894036, 29410.90012388481, 15340.617571460813, 15870.494263183931, 55511.86037738501, 70663.51014548742, 22620.258601035894, 47996.17992467586, 87686.46762714452, 42531.14536264168, 38470.94157894112, 43318.43396893509, 58065.67691409012, 73576.16237627277, 16974.367352779518, 94133.82194149047, 39770.95606543396, 1315.2643990125946, 72224.51565658662, 18467.69232018024, 39339.15268452053, 83592.37605003336, 95634.77042454886, 82283.78653375745, 95598.71699971987, 76482.91699207705, 75816.11649640117, 50999.42247964999, 17165.74316167062, 21554.43626864122, 20929.797681587115, 73417.43786170598, 8838.804812833778, 11565.809428794637, 20600.501533290648, 10436.625150770951, 32736.76123746042, 79618.10568192556, 46154.33443434628, 90100.45534729297, 97972.46243859794, 19467.33114620377, 91558.76040579806, 84897.57275192742, 36923.660100679626, 14537.478004054727, 51920.1584206011, 63982.96061273612, 52251.90289211782, 35407.44833356606, 68446.28820909535, 25466.007308972672, 86390.06918018221, 90021.57356815363, 77964.32258386078, 85143.37761626126, 13453.283530733317, 1426.2798447538528, 95824.05814261077, 43227.38194863428, 45551.25684226317, 87560.33401196574, 42315.198630962084, 2748.776575017098, 99533.29973892482, 516.3521368199641, 38764.36591160174, 73092.44215184129, 34154.12724314157, 53760.82097246744, 29381.51178512627, 81534.795384161, 30660.760742592476, 81621.65388592835, 64841.71666950706, 98555.8333808418, 40462.259176243584, 42808.01408808914, 304.7926011569557, 95544.94492318155, 52954.47648209392, 35151.673422585714, 14628.435650968497, 31201.051883081167, 46038.936746099826, 29192.44309629492, 3237.8758469816303, 40488.240512704055, 91678.08688895422, 9117.102361742323, 87220.50754198676, 37920.89688005667, 36462.40722261389, 1253.163987678696, 60611.428446878235, 11311.068293649862, 77864.82599020319, 36081.179801057726, 46233.38182874541, 33123.98146798102, 5818.162585050568]} +{"id": 2110, "vector": [84315.79935766103, 92586.40025479125, 88691.05468118486, 94330.59548474816, 62175.74071080914, 64200.00154923488, 15196.5884659231, 90856.77808104796, 78647.98198869906, 40051.318816832725, 11104.964450138654, 24532.938399207917, 93762.51831380147, 62781.589503283816, 3995.7412523157655, 27671.685006435164, 50007.85391494642, 25496.785639107122, 48563.19904410522, 88023.05869633984, 17649.28004382601, 31922.808725617437, 25249.739396819128, 79742.17122843227, 56822.721246544475, 12886.684128645731, 91510.88679332563, 61828.116096145306, 27520.180191766707, 56511.2972912977, 10602.650092435262, 56937.47168314487, 28366.498143945075, 15016.281394277054, 160.13104582445203, 73591.50082515227, 55508.91729008644, 21727.84457482587, 43755.703992904506, 47738.49138086248, 53671.430706120605, 9988.5640566576, 31986.62366630771, 53561.4778755994, 24385.558824483465, 95665.97721292818, 96366.92328620446, 83364.64042767441, 51571.805382997336, 23084.0328925477, 20048.357980137378, 37465.99035674333, 85629.57240502136, 81250.45497445871, 3406.5986902860336, 9058.532654830764, 37985.57896629504, 54398.703666077716, 66467.2266641706, 32551.60557608634, 37431.18231999758, 21557.47502765094, 2553.3994854372468, 87948.52832526104, 53785.5670784242, 20873.500968559245, 26290.165978853285, 44016.8110093481, 23581.068235309023, 16253.464036362553, 73100.50546285106, 38225.25324750389, 25200.20670591694, 23435.28776701841, 51822.85996189886, 320.70071421788083, 17469.202432719576, 56776.79883519594, 18816.014809541393, 78518.18333214843, 40796.75927555083, 41971.32812046826, 2982.2206762209703, 54641.97408111576, 83202.39050516328, 52027.36782965255, 49147.67821081683, 30734.15108006626, 96045.73190326586, 6899.331709838397, 96704.91898628736, 79419.39759846621, 38666.09777519183, 29077.343495974084, 66328.38795149245, 48957.09744324103, 58142.88902526929, 62049.05666541458, 95918.89451864133, 9692.573865703813, 43109.74975093952, 14773.658971343295, 74670.53368572923, 63702.80387847158, 48190.92687020651, 48931.86662241601, 37919.404240159005, 72601.33638839133, 97048.75990361263, 57452.71633025551, 96989.77743815194, 8283.931665963551, 53965.08731653663, 88448.31810234964, 10093.503757225031, 70044.14596044541, 48316.81793338655, 52664.59017689814, 70578.58861335856, 2885.760976699181, 46499.99574093679, 70292.53139777012, 96338.6961563517, 9928.223387881308, 43449.88203365563, 98408.3107177116, 1740.7658968082605, 34613.05418451584]} +{"id": 1365, "vector": [32027.306141308454, 7834.630054672464, 21340.57845442324, 49974.398453227455, 82452.37300800021, 45145.298030009326, 25301.165012538153, 65844.19457893152, 81790.35756177282, 67817.57479357545, 77110.3572037414, 49238.87839040252, 11302.698585998205, 62769.5042375813, 25524.13090539821, 41402.59649965317, 99092.13319826571, 29584.041202091936, 75408.35833230382, 76360.04699481325, 58041.19273014183, 99770.11417975134, 17144.17693336563, 90914.96310490984, 52073.47932229314, 53047.693813143174, 75064.4787224214, 23291.802394520688, 61333.43913892272, 86106.20251469582, 71651.47127238841, 43411.65057433852, 50581.07584765248, 6091.554694811485, 58793.327127037606, 2375.2421387549625, 59667.71623808407, 98663.24388135878, 6615.276593059294, 39116.05147807425, 63326.52332611144, 78434.63946252898, 67763.08295068692, 81400.8676785852, 5110.707609252651, 37061.98613801295, 66242.70628894898, 48035.13154709175, 55381.85139692006, 57563.76107820879, 94295.162782766, 9647.285811193573, 56832.69278448214, 26764.527784414127, 82030.44874310847, 80246.4920616308, 62909.33511452495, 50174.37823551638, 21975.85140319225, 88367.5116119308, 64944.13025597433, 9241.378514514608, 75322.11949612192, 98422.78757784014, 64792.49015247104, 56694.26473680482, 77604.68571522116, 2047.2979161526239, 7058.753093563874, 15030.518340313449, 58906.057145953026, 42578.11914669879, 57260.87460416671, 44702.46687952897, 82657.33436764702, 83647.55077954863, 19133.58690296968, 45518.017341322506, 89623.55875823063, 63980.399561928294, 40673.000219728005, 93553.17309935638, 27929.296978203532, 10816.000276488181, 18041.858244005914, 6669.543679636991, 53306.413553395905, 82092.38269972974, 29711.530962380228, 19410.6027797607, 71527.51507595916, 957.1130035508469, 65817.84505970067, 29597.089791713348, 18492.429016344202, 63338.63734875449, 42139.200808674956, 32361.035607158028, 26235.862712990365, 53257.77222826908, 30422.433219274903, 52797.65982477443, 80890.2148680504, 52604.473762312286, 61939.56728391413, 40257.71902945252, 68665.65421533113, 59559.68534791176, 59191.96625404219, 36083.45428282852, 93987.47602802598, 14988.09901240028, 71724.95685436748, 4415.86645696257, 77545.37986198759, 35090.11518819374, 79141.70149436926, 35821.1760236901, 40528.83357513591, 66435.64836654541, 64690.80196053445, 82874.3108944039, 50885.98048215415, 12421.729233224343, 1551.1265123369756, 22254.28775232988, 23835.487159784607, 97074.93993517297]} +{"id": 474, "vector": [89719.08216273799, 41569.98530285773, 3145.437107144988, 89519.65443723247, 33143.17133559962, 35501.06623256466, 55020.415873579186, 66775.4387799709, 28360.280447882036, 37774.56987964041, 53405.51554587812, 43806.052905504854, 37974.0970095078, 32951.7401038609, 14264.407372300237, 87930.84715969676, 29473.57626881938, 85075.84079929732, 96131.99530561671, 50067.375495716915, 94019.66349692341, 94467.17193132151, 37011.19832888258, 79209.07938712787, 16522.626063431722, 32387.401448910812, 52263.01226501301, 12251.217831199312, 76245.66471569093, 28286.690027045326, 52750.778816587015, 77131.50281509575, 62442.230749947536, 55340.645320073636, 77068.66566810814, 38672.016205360684, 2560.3636603182545, 21118.126601233256, 46299.33851257951, 10096.792680943034, 40537.94046691257, 40723.59058558661, 96285.6290510944, 20515.534674196824, 8148.610329925609, 48602.14857328644, 1002.9249744209711, 39092.88547792193, 60018.02531974717, 63617.943036110235, 9924.493143535452, 7681.589781780563, 65681.37047977236, 31960.719436860953, 42110.45752436981, 43267.188731215945, 69473.66862059257, 68020.29046412489, 15953.613445591653, 60777.676832066594, 53545.46000363413, 96803.03632910986, 95221.19605187568, 29378.254798274138, 55637.471322318845, 42573.23881736987, 51035.825570778696, 48867.14537250787, 40935.99067683987, 64110.1815111289, 81674.03208032233, 79153.6541672361, 86755.20920318045, 88163.2462364084, 27597.24171150527, 24155.37425721124, 97322.8072075947, 45824.06426076024, 34191.98178795608, 34688.731615524914, 72014.25927195359, 19365.752257095282, 66989.36359201132, 56927.99859244054, 65014.19678858913, 97667.82770037008, 67375.02742338486, 12302.256059433337, 99126.8073677442, 50549.922590816706, 36966.79696168053, 9496.539718585951, 56117.93076377794, 7679.189001772402, 34527.75584492912, 57150.957282421725, 79253.76557662297, 6467.769521780964, 49253.94592499048, 13182.738171309238, 40237.06306374713, 5301.399579373189, 97182.78007658488, 52041.299517748805, 11781.707223162419, 16013.376115487099, 36150.88101363332, 99531.52986405989, 77706.35511338185, 58536.93778649572, 219.61257398367405, 61130.07041195773, 15374.205477957548, 5928.4623377571015, 26474.935321920668, 763.173676072948, 34.53112130684666, 11662.48166245799, 59911.470957617974, 90310.23647194596, 38175.8338246341, 77527.61384994413, 97191.14331547613, 42770.866861923474, 11697.659912165693, 41506.54760009825, 38162.320867497445, 58502.084064860384]} +{"id": 885, "vector": [37105.864979380334, 53699.08880675594, 75791.99063844, 55637.14170578651, 38425.08358067573, 57426.10818680902, 73762.71150470267, 41902.24219248625, 95404.24110690181, 50088.39516485173, 41516.684107007386, 43848.26920762982, 48977.48869125983, 3325.1469230263233, 64709.67091546993, 56198.70570572524, 8484.496560977928, 9085.517724035086, 16316.977906107588, 2115.4628410223086, 15358.177120376926, 32997.12966232168, 18096.935786612645, 91398.56210695674, 9435.786238535215, 70876.48594629085, 22700.628628041653, 79472.78395239897, 20781.787252923412, 32884.025828727434, 49190.5965217452, 14184.954263830095, 7637.7199030230995, 72841.38390131133, 56651.989779378506, 28902.893963460607, 74420.39143070475, 36218.474475359464, 84376.56279559855, 60067.291459615015, 74086.08413193663, 28453.63347423866, 40354.97982334494, 85601.00435845283, 91758.6610502074, 3138.647919727289, 50777.653307388835, 57338.51517514653, 94777.08950040837, 63520.005835492724, 11273.38433769628, 27138.185461762267, 6743.08131724446, 17510.886077414078, 47033.0191302636, 66339.71925180468, 23833.159365119005, 74326.47871222219, 15632.407452671981, 18001.534753521253, 36046.36582064907, 88625.54879041617, 95502.4180515416, 25294.237004131915, 61408.23760423362, 27370.94408493016, 22916.561500544307, 22574.59518592605, 32845.70581761859, 75060.61070947873, 51206.905234810605, 50577.39322350452, 76788.43281042035, 26758.864140597183, 30079.9456963194, 78697.26265362372, 54621.79880163248, 32640.295961491396, 14107.547404330468, 72996.37499651143, 72733.63852004214, 56199.083675334405, 32050.832788573513, 46525.904364390655, 47520.10071249521, 27228.697690494642, 13934.195684534523, 85829.06569968049, 87324.87027531194, 89126.15576313205, 7852.7684187887135, 79296.97519659137, 57913.88283111968, 6452.499795302291, 42253.18098990674, 46851.54057922939, 50126.33472583432, 44596.8202578988, 43840.8655847151, 55508.45882831562, 27662.105098049284, 67490.43928460551, 61313.73956783029, 71375.97616770954, 16432.40630121381, 38843.58221432398, 62983.59972811277, 17996.76006681069, 24894.733624557462, 69578.21860038572, 23538.058269317597, 32458.211836464212, 46240.42968520725, 21730.55013644597, 59174.783091751604, 6666.6535739858255, 823.6970049910864, 81076.4748889813, 52316.49133849753, 98755.70909851293, 58295.906875287605, 47918.37215355284, 26982.8736204635, 3284.4340463108024, 45062.84031786997, 53402.91865010085, 40543.281513442686, 74380.33893464779]} +{"id": 679, "vector": [86884.62091971526, 21377.344948871058, 6313.994427723457, 36257.069289892475, 79158.53676916497, 3648.268370449359, 65351.48046042746, 88288.38646838635, 93711.36742425569, 79126.90067345637, 37436.96530559631, 63193.38879766024, 94631.62001595428, 3540.238948132546, 85881.23365363048, 65720.94542845395, 59141.95977562821, 46633.813242912736, 69981.03134477563, 67164.16852048573, 43100.46889647619, 32150.432894990765, 13448.823476316107, 56305.702332488574, 73713.21589220522, 83215.34092610262, 69973.9646054894, 79352.02017415322, 93996.58154392468, 41818.48165472556, 44704.394869223965, 26195.47239620068, 50594.05844209861, 99063.60178027338, 18424.9023488812, 99613.84620450261, 86949.70529944147, 50064.40065004169, 28430.043467376632, 46389.61852339305, 93291.23416649953, 48665.10195873417, 72289.72970196824, 88623.9536184666, 42322.78741728076, 2100.0973022541225, 66292.33826107443, 41855.464413670285, 37586.91436804864, 76359.08760103394, 54235.78236492637, 70783.72777026951, 69386.41540174873, 10155.23547740217, 16901.72758936579, 77876.19606347936, 88766.10445052973, 27967.004277540607, 5987.297059874885, 61531.87513662601, 63370.988454676044, 90128.5695078569, 72484.45721781673, 7099.422431131108, 45997.24826383136, 62375.75508192227, 58924.5514796615, 11480.591584105892, 47665.38490907589, 38503.41154362116, 80126.74178802702, 83583.97246964405, 90066.2845201854, 48407.77504127617, 46494.54193812454, 91282.24967759007, 50486.76744243633, 35252.36876281504, 61097.33388660409, 7959.285372931191, 45985.019163957055, 99350.25910360488, 41353.07195179604, 28685.863372518306, 8353.248806128133, 279.8165137330333, 87147.91963936393, 51671.87767298661, 32622.04182050359, 76899.84524754116, 61927.24389890307, 71767.89342859152, 97698.76686601335, 38015.60679118265, 21972.76461698061, 31792.06958619143, 89576.17407545052, 53558.19867583214, 2914.970013080231, 85880.85976146882, 78252.04072173483, 10414.655018478703, 5967.705126699818, 41021.79322745872, 65256.5260289132, 25454.256825193144, 33971.19360955426, 57656.12951337926, 61761.21611572704, 56519.28783319934, 99948.7126856724, 59277.1173846801, 51514.08791787177, 59927.92711477864, 12210.622420505668, 55538.31346794163, 5100.398049815213, 99643.00335219473, 98358.14744580197, 9210.716115301631, 97747.46415083976, 9458.372154880446, 54805.009959237505, 25409.85078082695, 4729.92482066169, 73533.35257313256, 32425.110374055566, 28147.176062149592]} +{"id": 1422, "vector": [66548.4157523261, 44336.42862277167, 31565.02666271228, 48203.72339239581, 87799.0562287829, 94198.63377993075, 85439.02580358405, 34718.709840525684, 88945.14328715764, 9455.545148810628, 40970.427835456394, 68139.49795859493, 98964.25764010176, 36850.21435389622, 88225.60956043116, 85987.28432336626, 58501.99049043744, 52322.007669889026, 48.50330560052241, 73582.86824020131, 72940.54396005176, 63519.9195783044, 11160.21121779771, 98761.17072994393, 56840.30373604346, 65633.85744171304, 167.64995967349927, 23092.654161639926, 23529.913309078685, 34040.13449526936, 80758.64587546294, 86594.66639685129, 1414.229326691785, 81560.18475424188, 38300.989723823484, 24846.86716774015, 46373.89586922614, 49286.95730312841, 41468.94525241117, 89899.43724604332, 29911.720157805787, 91976.62764151869, 64929.43893302018, 88131.17207380499, 13070.354324089716, 95699.88166561903, 78338.95961728836, 59845.46215817093, 30079.826437346168, 63768.86099946821, 92788.44490153131, 73840.549485645, 80638.3015765535, 78779.85675185599, 25254.708076765553, 58979.373483394695, 86763.81989287797, 15593.056864601496, 4831.42291758788, 25497.762690975356, 83077.91678447468, 78882.77601410232, 73552.18517042395, 56153.5006312471, 30039.844088071433, 13907.384183653714, 74370.76481001044, 9480.754032358984, 28305.94640028552, 35722.48852413677, 92468.85646201481, 89502.24029126827, 7937.799709145321, 95841.51313622846, 77089.12650417464, 53669.0002137154, 1783.1647909424041, 58070.14570047331, 99130.0367665931, 74441.18309626712, 34125.373040020066, 35879.36647346968, 8081.622662525067, 64983.08825113415, 48642.873806158146, 34153.70780634351, 38449.158595379165, 34655.08100509118, 69486.21206326273, 19282.832628094406, 35074.48859319758, 4390.452635280695, 43124.94518786222, 53512.73659419379, 38269.51786007826, 2830.512604945723, 14238.890065244113, 1230.3377712269858, 21400.18424062975, 90291.77156345177, 71030.00999772262, 36094.96690159566, 90857.01221169216, 34566.8181819054, 35549.218904051806, 3568.9197715044306, 46826.53632666164, 61071.282975863236, 2919.4238465300227, 70921.54455777259, 96702.89913315607, 51281.46865163238, 51573.52851267673, 15265.983747050139, 47711.22540766437, 4138.9780391866025, 29450.89411278412, 83261.50821309777, 7225.701984980226, 39572.16375544728, 36873.8837029824, 49345.83738844489, 73291.78049169421, 53353.10870097509, 68426.77580983759, 24356.923574042066, 20480.275143937477, 26648.982017859013]} +{"id": 173, "vector": [48794.479933002345, 39923.22740816267, 70956.15827616946, 82097.18952201688, 41620.95439771628, 22506.811854478325, 44141.14102911848, 33890.26214059732, 50646.50113870773, 75276.70612434013, 46674.407058122844, 47701.90317051467, 51666.91642287429, 93143.45424276679, 64292.69835089484, 45121.10769648064, 23972.83578297502, 76778.3710977247, 88410.01948475726, 26410.42905782125, 67223.48314020359, 2745.187998298915, 34649.52029643884, 87637.99634759864, 91888.08298642033, 57100.722866824835, 75617.25540252101, 78233.43354976317, 70484.84073715736, 72861.51201063795, 95264.46107342541, 73315.13610393333, 53117.85836939567, 61418.00179265538, 37814.14386763087, 78313.62797203752, 9575.478292611073, 1898.7971368074952, 61100.04512356793, 48967.21935616174, 68824.31994394258, 92060.13883075504, 71257.98130830872, 87445.4121214964, 45194.81087548843, 18855.577682956737, 51435.72419854614, 24120.200241250634, 44151.27982547619, 22559.756246587203, 41594.86127053501, 97869.51809573709, 40738.36263361008, 23673.726031185306, 26182.397594955943, 14865.085257716992, 80935.68715319071, 96639.79315587488, 3152.5808081770633, 58429.133148732995, 42918.897265409905, 4538.183943441753, 19531.551481898456, 78627.9460684578, 32835.31319176747, 79298.80752716851, 95966.335525733, 24978.49530850803, 77843.57809130102, 60361.997886057026, 99108.3935244512, 14029.414893525993, 84988.13778753062, 51568.26486224995, 15263.85350445939, 13615.728724753773, 67171.68265782934, 72454.05564755491, 87483.42085182214, 74172.2995804115, 10214.313006847697, 38877.07294595567, 56518.590250775094, 88347.84816461448, 19217.653466040785, 32710.70759190875, 42389.09511033773, 15564.921165109246, 86871.79211030043, 56564.476615935775, 20734.427033995307, 92666.42197515401, 31349.752439989243, 70570.74861153336, 19674.87604145427, 57420.02616022456, 74438.80012364818, 7136.266341579589, 18841.892743396893, 24007.4566789609, 57787.43560297774, 99659.2712881185, 64715.453119356505, 2667.9782165923816, 62660.05088161842, 76390.73003305704, 58234.5181640532, 48641.640679149314, 13072.792594250359, 32392.278926274455, 50392.504660987004, 48256.85590398784, 37264.88477008164, 70933.19913373947, 15596.354563603087, 16449.36959653691, 85894.28500151975, 80137.44564071287, 45098.62958249177, 23513.297055560422, 4718.226893143728, 56704.235543556235, 8053.28460506094, 27173.751172876415, 31276.24481770418, 96620.51423164266, 63171.238068267245, 10581.403703224047]} +{"id": 1392, "vector": [50112.74806333894, 96758.15125070291, 98145.9798116596, 73667.53675320475, 74086.87927108846, 10105.629339910238, 73730.22066795446, 56054.5148376753, 11820.10505993477, 24075.329704855154, 20080.889017504076, 56840.24563031568, 5553.597935136057, 31721.694444865036, 79745.18996162275, 51347.24806223234, 32984.467042448094, 46991.28319391531, 8101.899236368348, 90626.95612179079, 39273.59073989514, 46493.99196827887, 65930.37611433839, 349.57031217175415, 73720.55104951731, 73789.58340074058, 52737.646212362844, 59242.81154270145, 75529.60214637764, 38205.808136684995, 34814.61392855187, 23320.40344395895, 62272.52365838431, 58577.651524481174, 50190.13672953893, 72253.70395693759, 31819.386771877224, 60328.516549231004, 5559.839425457536, 54005.666313563495, 71713.64598916192, 13712.376393000857, 61608.833310611655, 66866.81771986885, 75559.28778994919, 56998.687111942, 85703.39687068002, 58339.8400699073, 54208.20842803752, 77439.84055373246, 54649.269517853405, 11178.367426751867, 48497.651473844584, 61789.80556466691, 33277.43573485791, 44812.594716171596, 20001.65790128008, 34288.1358559658, 57459.95285197561, 54698.06062709247, 18583.23371151307, 67351.58492663929, 60495.01749557527, 16001.144164268377, 88701.21726823249, 32241.1475718272, 60402.18669960554, 42670.329109674334, 229.60107358046545, 58149.90051999539, 71164.00463654277, 36805.92594929666, 67890.71930925142, 40797.981618888676, 56303.229518697015, 5969.661248806335, 10649.61645183493, 94689.23550114286, 37485.30360241713, 37887.8084988591, 17819.490559532303, 39637.9382545193, 39971.75665780499, 22132.44641787746, 57462.05966152721, 89455.18630050615, 21915.299281709576, 95697.87208162704, 35067.380904171056, 49226.44635244231, 53173.99707626242, 44179.96961399069, 75357.84679276409, 95158.29482549932, 80272.97734565852, 84418.6642013316, 57139.40061088979, 18239.22580034223, 79252.27892463186, 12972.658535938075, 90786.58687336065, 75355.49304956826, 31426.138896151344, 45464.4162867368, 18302.197033462497, 67352.94967393472, 62597.145380142494, 68865.53197914471, 87842.00346548081, 92806.92174888469, 83901.89072746283, 67088.70761988442, 84559.8269455274, 51896.832765275736, 42177.624757216894, 45055.46086731905, 90537.44152677267, 45187.167868394565, 22876.515101917226, 12129.12035793714, 32923.38052618129, 25284.499723233177, 34904.65423660185, 28475.62486943106, 41843.258718301826, 47213.67298704443, 66164.78435743546, 46668.01630569485]} +{"id": 210, "vector": [22514.173617883716, 55382.34140270701, 99416.15187642215, 68742.78961331864, 49394.82556753904, 11434.391095166364, 1307.3509647822946, 54252.16087862567, 40707.04623862985, 9048.020606628237, 52160.74768746025, 16488.89528705493, 74828.92285479468, 48702.8952806526, 19666.457878419387, 9832.08394960038, 65272.93415309521, 80563.68016111813, 82378.67417829404, 3969.8057725817916, 58087.38038044463, 39864.73612504183, 22819.926768768793, 2952.9599428381957, 15949.056706154019, 81633.04868177252, 54922.87954444071, 27819.240672425083, 65582.6487182139, 44394.12682236319, 16186.079649983509, 13993.09259489726, 68991.50008797052, 73273.2299287588, 60313.97087493951, 58791.917531814586, 5599.286053336372, 22631.051164577275, 67796.43775265073, 44025.7359336504, 57103.67434056661, 66624.20801535367, 42773.54453741053, 74350.5020653144, 37972.80624502407, 83802.33995186021, 84355.51117376582, 13929.285839353412, 48963.84619807771, 71476.42774251178, 1488.4821700860916, 63804.643273694484, 41499.86400851503, 61785.762614438885, 64369.18735117375, 19726.43245510587, 78484.06311274246, 2277.3824489499384, 63505.255589259046, 6275.223621819004, 16439.715089943773, 51088.46140757689, 1549.5329365405098, 36636.933094797416, 61274.85807036382, 54113.24426369364, 85526.84744986113, 15325.023627289114, 39503.36712258841, 33008.96576185347, 1376.6438139174109, 8789.286398716778, 14214.967578210313, 93155.10468723738, 4310.428148494161, 44647.27726577378, 37043.218918440965, 54425.11266211734, 82627.94373905937, 6477.757185070654, 86788.94450538482, 22426.06319818218, 26301.957909252927, 17304.392682892365, 83203.40872639726, 30365.5366626278, 75673.38120509108, 99790.90084748178, 162.09062427640086, 62395.15283940983, 10082.102188886154, 47485.3358035598, 84721.07198732217, 99355.02445095798, 68789.41684446081, 68447.63129128749, 1474.865352520749, 66118.34599980264, 67017.76370743949, 57832.00207134392, 11096.680961337324, 74221.62740026318, 5537.882714068265, 25512.703049883345, 23744.922080674813, 32103.664547411514, 80216.91923856553, 87710.64725138509, 63415.78487567843, 7741.7732398802345, 10849.386776022486, 29031.358560551078, 32384.993413645625, 9435.838694192189, 55332.93403493117, 83815.49856549819, 97373.517331213, 4831.799257013847, 47297.78701601475, 77716.44612777526, 14512.62894144023, 25530.922588780002, 62965.605614816166, 64687.933473681536, 39541.01617862947, 90394.79289781538, 38960.75398847339, 58483.901321402984]} +{"id": 1722, "vector": [64697.036325657784, 74604.12620093806, 43273.03296652479, 64410.75150541291, 27665.062575641387, 11027.640003054152, 52402.14771453594, 11228.62841668788, 82816.24626716164, 37224.8111930859, 3529.888489611943, 21304.819647403318, 63294.50933784532, 70932.68301516153, 75529.86279197398, 33024.47665244887, 19559.239048855914, 62096.68837124115, 49068.218677173034, 69138.33040885637, 69368.26206277568, 91186.23292253591, 86056.99496017337, 47534.858023478046, 67222.78441870674, 54237.36095461729, 41541.384586825516, 53450.43838906369, 15917.330701118226, 63928.96202632734, 77889.85904437453, 98638.7321506253, 24810.495638390483, 49733.05200211452, 70888.65367999145, 67713.47037289666, 71541.02597498575, 70808.48194419181, 28093.46773735777, 8958.980641723268, 75966.69988864809, 68350.42573258757, 31890.17374309402, 52553.49497539633, 86395.2705846571, 32367.718579836146, 61637.53113826068, 77694.06061041041, 78089.35874849887, 28098.566998513306, 92734.45337168386, 59883.82294521083, 74453.47124349087, 72813.73009195241, 58427.37094771734, 14129.802022610449, 9303.821041648685, 46025.22542215967, 19144.112578799344, 12023.929655855547, 17924.75252953184, 50527.129110345035, 16311.704689361139, 17631.693517447857, 27407.456488005468, 29835.698586942104, 13479.631744761222, 72493.73936319292, 3163.6400995533, 94180.44233711236, 60754.40087873939, 89195.54167208388, 33165.38128737635, 10520.978442232454, 49077.179495773395, 87318.42432319508, 41744.811837824935, 60968.302618612135, 17465.99705189611, 58639.689531225195, 45850.98995184712, 83859.5443347018, 56195.290730821565, 63445.60956762908, 93136.73366192797, 24985.718455745053, 12988.82636537434, 23883.185536933317, 66981.1436859332, 74689.84847426694, 12919.709912670085, 43289.27125303854, 76313.2028627092, 43172.897721551584, 41260.06440801174, 9942.511431718038, 55950.664470058466, 10510.460649128028, 93020.67607123668, 67238.88285581207, 33554.06720480621, 44840.38937200532, 34910.094045971615, 56584.053370079964, 54362.469658793125, 32905.65833869986, 16078.50929305772, 56405.75581961075, 31918.791824302607, 54424.69157503599, 48036.27884535518, 15299.782633120984, 79426.98561763532, 15773.146722535836, 37378.17664357309, 86307.51363116618, 46855.67568023641, 27794.02395950643, 91085.10915688565, 85834.87368791824, 9560.733086301598, 42458.42743300232, 39430.16042395099, 48732.87325864728, 54157.06183172652, 45652.56750238504, 80635.95345020165, 69238.7317531981]} +{"id": 705, "vector": [68647.7105691868, 51163.7575101999, 97691.62534598753, 55072.91130361983, 33675.990958600225, 61387.90682988054, 68216.72501050244, 63988.13585119725, 26274.269558606335, 84642.37499649753, 42025.75186391818, 24272.739218735285, 86768.24313569142, 50421.54188537488, 36214.23408775833, 44477.07451074849, 62912.596251911746, 79280.24569427216, 87972.49532665436, 65641.2760794771, 93652.16814098686, 72024.64344596087, 95618.88828557143, 8958.272611730277, 59517.444158182, 84254.64665275445, 51367.893275586604, 86503.90256166235, 52105.851757056655, 10222.555215430219, 68374.08165476224, 22447.073240328697, 78534.20213498925, 31274.365893106293, 13395.146344268538, 13058.083807114574, 49968.452288255736, 86694.61332015363, 88809.49336704827, 67508.2265940923, 54608.896321402535, 12710.402686662957, 23714.322801416776, 14860.283303229317, 5710.089550574949, 18193.794037849355, 77019.16266135509, 32373.518065663186, 52333.874955847336, 773.3771098851761, 42349.4048857456, 18777.171199480315, 15478.0349000042, 47446.529006963035, 85822.21012586656, 96264.60419701718, 26309.7870372771, 38259.39377337214, 87446.37944292689, 25853.861618156283, 92520.89380058093, 85732.47945290398, 17267.717508279766, 27679.710149208615, 86411.60421881443, 74089.56011183868, 13127.001060541965, 42630.3025265216, 17882.976231869874, 94716.33363824249, 76140.79655676252, 69190.11198383763, 22158.89449507222, 67549.71704235599, 66943.56818565809, 22535.924312373732, 85689.56226659271, 5385.6212808479295, 20997.175414220514, 79340.12864692904, 61825.67722115122, 33848.4095711963, 27634.558645896035, 41506.29712983741, 34.14115498452386, 30497.828040431043, 61231.21435642066, 98704.6975502416, 79085.32082270613, 58180.431445067894, 76782.53861323961, 28894.254750904678, 74733.73673523481, 7916.719108578618, 36239.591143717975, 20645.6422201833, 42004.219934881716, 37667.54041320637, 15978.336143377292, 10941.463724003876, 56739.55440307482, 31205.30975700443, 20581.212120673386, 87265.10093029417, 55038.40842765707, 50474.102751564234, 54923.83137266009, 47657.18043910121, 81993.35631785337, 17087.89724224107, 20866.230660308214, 67056.68809868423, 91105.58025379546, 12993.65500745857, 22998.024863767652, 81263.76703047671, 84879.5802378551, 78633.35316749364, 11875.266392624384, 99173.36660684065, 5340.022747782847, 9954.917757645188, 82125.76053282138, 5950.456627753698, 31294.008755039627, 84961.12480548516, 62684.19678262535, 57266.01117727963]} +{"id": 687, "vector": [1040.6874186558834, 85157.22256554924, 35765.097822594886, 23275.199704672654, 23967.454024800718, 34276.9913147176, 77583.54485547719, 53277.00379586221, 81543.30460483691, 6417.414106043851, 42898.65447909167, 11358.739842316058, 34724.742011960916, 55231.92686760394, 96579.16276457363, 38844.59471735917, 76658.63420457291, 14461.258631462404, 26145.598614581377, 97491.71219303207, 90256.56598072166, 41652.88704682608, 89865.01801422931, 46707.257649270075, 96396.4417200056, 57281.50383849949, 84345.87449550701, 18695.746304137272, 52562.20776100592, 20764.1342655284, 83750.77696880358, 78039.91703745374, 21112.687012766586, 85051.80698949586, 39813.82572921094, 98014.78378054331, 70868.05181873906, 24588.73769753579, 16181.406422115995, 18960.300402324283, 25537.62865238133, 80519.59983583704, 12926.497295970728, 61838.08812445629, 20728.79922923101, 19017.64618359637, 93034.64908193557, 66496.95751798566, 77655.77574413364, 89828.10381302837, 20818.369914251645, 18906.057856726933, 54194.39931974144, 90941.3118456299, 54160.943879528444, 66852.02860535045, 20566.685430993613, 69424.05219513203, 13869.123656808435, 13356.783246359306, 4465.8951047405135, 31422.94623998666, 33293.77812394677, 49128.59441607026, 99108.70350728203, 10110.772668929769, 41232.76500806834, 16606.04582369857, 5537.250340504851, 91730.17340144671, 30251.191668734533, 82226.94582380101, 30498.123447535487, 69033.00662635443, 18455.790785863966, 67138.90473429303, 4285.148024607077, 48917.754936026184, 31176.78239428693, 4127.2197541203595, 33598.7446994892, 73303.96652068739, 43519.114056481965, 88347.5887520105, 8011.945542770472, 34123.23628316663, 12961.254185214866, 99155.86676775738, 9236.991228195346, 13564.005161941906, 26600.57902963856, 78375.78625395714, 8057.661857652498, 58528.62804928602, 96968.26552919703, 84391.30202274794, 54180.53191274247, 64864.36119184504, 69515.99302468736, 62336.022537125034, 18975.378956264532, 20945.670446009124, 71869.51057201756, 7550.651871956038, 54695.98167784289, 18662.674428563387, 16304.560430076497, 10788.061216155176, 71118.13448007322, 31075.717776299116, 31107.461633220733, 50077.22757272152, 7227.966371993266, 51452.26746671383, 16481.440027417706, 75856.82940810852, 65894.53069491057, 63778.10933417113, 22561.497593798053, 69344.97822619093, 7377.200445478127, 47383.7172230319, 86857.85554768765, 62991.586755895034, 4606.3887895914095, 48826.11658694652, 75939.59484270359, 16431.88591823892]} +{"id": 939, "vector": [47405.839890236224, 80426.46659211071, 48502.610369322676, 309.1195652289991, 37726.4849227243, 66370.57227531385, 65098.275573354156, 23524.69344521807, 95256.06480389556, 41221.40933422712, 14476.524181313522, 93261.93103509405, 74027.21971971252, 59742.10125613653, 34316.78175115849, 54236.88445891923, 18640.35391226334, 22653.73572271685, 530.7498302362212, 19123.36860019751, 4786.149870002254, 37630.359473203556, 59826.917652699995, 43198.64905500814, 85361.50820697036, 114.44090245400496, 36869.628669874684, 48844.64804591692, 24955.106721944474, 47014.002350522765, 86953.07234990085, 92072.80052457293, 54392.7319623767, 92655.19207853237, 14202.580116033781, 58380.067103101974, 27521.556516300105, 86624.77506938438, 71797.95475433198, 48387.65590986074, 82842.76032208833, 63547.28191658326, 5856.242024281344, 89878.86767472525, 15499.757656040802, 1539.9951915404442, 93378.67693457015, 62328.141091796664, 51100.679740825115, 11994.160720458536, 91023.41904045783, 368.3798159052065, 76320.67150085156, 22947.463936106316, 38215.75587594601, 49443.046671517, 44138.126017025315, 34324.12184080075, 51492.42833343995, 53089.96912600353, 24587.929975002044, 95983.8069740834, 10713.541491811384, 19560.113360042673, 85359.93258434988, 99258.75386572772, 52314.403249326526, 86594.11905011054, 4562.395184808021, 67177.16083756398, 7530.535423880525, 43286.6827997636, 48866.63877635416, 34015.03533642749, 22056.349836840727, 60125.41685976898, 50007.34946536773, 59464.063436153905, 50958.30507781922, 71112.13932881514, 92148.21957717247, 64858.23638807753, 65094.346102856194, 21332.794926561528, 62399.03501828047, 91844.95008310874, 93935.65089611721, 70613.95146093256, 50982.128723307986, 36749.218961053455, 14945.407370091823, 50751.30131366187, 91381.47599450736, 23981.759211640507, 4259.7813780077095, 12275.453183914631, 70844.06371341186, 55467.61111770247, 2116.022472372159, 93576.88450477488, 2553.0103739994092, 94107.31302699403, 97339.1411797705, 46533.421108459304, 73019.31988144062, 58201.58212453179, 78058.95945731037, 47185.64445100866, 11385.087059711086, 82068.2547107413, 67111.48517106131, 19243.022470738626, 79739.4796582503, 32152.527097964656, 91724.82863642993, 12572.315278689528, 31994.532190273785, 22978.201581148714, 35242.30682244421, 48014.83914306257, 78.74917215855426, 5408.157678653869, 33508.85696465942, 73212.31005532308, 85845.01133066295, 17492.161212431434, 62552.51434154897, 87417.47608268469]} +{"id": 1749, "vector": [46015.02920012193, 36173.47931839825, 93393.06264190777, 25694.15683759574, 64283.3509269581, 42993.08019699406, 8630.17149923042, 51265.111700034024, 9606.783882866255, 96321.90878026193, 58402.33223538648, 62322.49014225628, 56995.80841900126, 57920.971841220235, 35448.717782879954, 76391.4836595677, 10851.905314453248, 72327.37559161613, 86996.52043578474, 36700.37234686087, 28652.21563569974, 8911.279926222116, 99184.90603693656, 55695.366349817275, 74312.96918273007, 7341.53060532805, 17955.535328972117, 88453.01471693364, 74854.01473667398, 43393.491091291595, 90060.61517553909, 75154.8812503779, 13497.788243606223, 48644.62702761269, 35775.67137035495, 33055.88425265137, 10158.684616582825, 98080.16462640441, 70851.7816056159, 71852.91036226723, 24880.268605869915, 5032.090447248361, 26326.106305206853, 17196.160814496052, 9866.032906497347, 19972.405371239878, 97777.14933354534, 90904.53673371467, 58354.06025113574, 25836.032094369766, 9375.0859302706, 98210.20601763317, 58432.88944011208, 1654.381050913467, 34138.133100304214, 34071.40041802008, 55889.812448280594, 88970.95862945549, 87627.46598626047, 64329.67522047846, 94480.88555671473, 98304.14556650964, 6724.475891423786, 65819.96824346628, 40199.277324567796, 44250.097573097366, 56402.34143722727, 71784.69228360547, 58508.4182304613, 51987.638135828776, 54216.998757104506, 48746.35818280634, 88033.44242662002, 53277.49494859505, 40039.909115383496, 2433.4635484500322, 33799.93193825274, 64971.32798869619, 70163.76371174981, 38578.28541477684, 5847.223483457653, 47020.670891151785, 36201.191411992215, 67620.14492216628, 4964.564707340891, 86165.5458837631, 71859.92910212842, 53122.212673885326, 95424.30580771132, 97363.5056286413, 56590.57088116707, 83968.00558524726, 77333.34631252027, 88480.95262981932, 52897.00220191166, 2092.2667412313854, 14746.09793849081, 92360.5098312116, 64813.89622931097, 17002.16839220433, 93528.27866478516, 22360.58437861238, 59281.426991599554, 51534.87336765869, 72426.88133016796, 92978.77589668562, 40468.98880183627, 8073.964315582116, 9419.436910005097, 47766.5590573344, 53720.78159529079, 62662.96749824725, 31688.398550517883, 59098.02500467417, 86093.81080773904, 2738.718178574817, 12058.801669117225, 74724.51225770808, 68658.73616565303, 62681.38884751275, 25237.445251880243, 67974.35155983982, 28532.752159876363, 86111.34647597118, 85160.4795979367, 712.8356604495623, 64447.19304255335, 99342.94791022992]} +{"id": 1237, "vector": [6418.57637217077, 82807.70649386603, 28617.001302936394, 48213.788783481745, 73306.89721432596, 16226.993309854066, 25391.786351686453, 25884.30032538358, 71529.10380567973, 61092.32982840832, 1758.365055928357, 91792.20375615329, 57356.647572713795, 72201.1430074056, 52738.52606687753, 56780.93551797556, 65997.52109696147, 49800.31891640266, 8309.252101586118, 67847.55767871944, 32172.249285789432, 48391.16057072841, 86345.78457972733, 44190.98041476654, 64196.705543286356, 1902.7147587995175, 21900.059711158316, 9485.277608063048, 74918.33646251628, 76795.02527187263, 97551.26632424328, 38101.650741195815, 24775.890551409295, 97995.18022524806, 89931.67346859808, 5155.5335626163105, 78107.31644108704, 57636.54078994644, 29616.52847935301, 80296.18048326737, 53667.738164810464, 93311.47091800993, 39124.327074444554, 23301.549609760496, 36878.677409769865, 66282.9082095818, 46555.60545650637, 86158.27823711668, 44307.58112614084, 88155.15718202801, 86827.24642016583, 63753.6950864616, 36152.32021564606, 78653.25785197003, 2736.583136358106, 25306.672188486235, 21409.56597551039, 29372.535812706858, 13445.122778933837, 19120.23311010572, 41060.140502490605, 64633.28533509226, 45672.183313461945, 49401.17392752093, 40695.53936016939, 74574.70161711385, 58510.245300472496, 33705.84745662886, 54698.44057175226, 41815.72537102114, 46151.16328483982, 16504.363266991062, 48001.82836269156, 66998.10241990256, 61682.58830716172, 19316.831041692505, 42854.28338694368, 5158.243422095121, 53233.78515947725, 67834.27811976535, 19910.27037269938, 30507.354933064544, 4895.213564649814, 64348.856056534256, 15281.083295464005, 50306.23309078195, 76368.98466888044, 24113.596553911808, 96338.8322901619, 58284.77592449074, 61237.91620189728, 18840.863179865886, 15347.986394650316, 85951.19185358759, 9611.10064368509, 88662.25555489388, 48707.478747772235, 28214.293810539893, 52083.39943441064, 79844.8159083084, 87953.23451226216, 77972.60574526749, 48871.76141790785, 68168.80824342242, 73971.44981458299, 12496.490032859798, 93996.2652702404, 55304.02411242509, 41091.63512207531, 52685.88379924255, 34954.97574447255, 59162.744359208955, 17311.99923595663, 26497.523248942543, 54393.867706064135, 65526.79414351371, 9112.663504468499, 61384.828172825786, 64921.75512862316, 55758.86453263222, 91604.90484889923, 2750.0291203328197, 49571.15106413946, 59284.8690397123, 98574.79508932777, 40351.116564943775, 3697.8297353271405, 15894.243815798025]} +{"id": 847, "vector": [57827.71185353297, 16897.369624839754, 34268.22549655846, 18570.357050990806, 39454.84176042254, 94036.02218353128, 69998.62847230132, 17736.607215615186, 49934.73846574292, 70019.80069959415, 69617.49175701977, 78788.65073780701, 23730.954369157374, 56432.90016099516, 36750.44772758622, 27782.61662778794, 18746.396814736832, 19305.30584013931, 70939.31740148894, 62062.425992405, 9197.122096847854, 4031.8125094512493, 92621.27089596314, 65212.70461769606, 31095.55444853138, 44180.07942535924, 68347.93804278504, 61257.41201835363, 51112.53705702828, 6283.11323477716, 70475.06827263349, 60807.173424220804, 38021.57254268484, 89896.47758430298, 23242.290581857662, 35551.57725984639, 47224.8248989286, 87040.48012068347, 74094.66215945108, 2312.4608006267476, 40000.59353406321, 42650.511075776165, 43985.86486685068, 41724.00295065136, 84582.28644230928, 45784.722500260475, 39882.389873426284, 53787.64462198225, 68442.2339429499, 15651.10440524039, 19024.560908937925, 48108.97001351848, 88277.83786327379, 93617.76207449715, 53231.53761664804, 20149.345359609684, 13951.154455021175, 30018.425184963104, 70238.79379072883, 65537.18839830387, 30231.324141099492, 30907.673696123406, 90032.69211375593, 23643.597565642314, 77491.84483864855, 41971.2112831585, 73131.78545485702, 60392.208943052436, 37049.52279117202, 22993.64856138556, 94257.24879845961, 80537.60684066894, 60294.450451942175, 98413.4242660636, 84140.38042696957, 68699.35775789818, 47093.88572258755, 28813.425630180976, 4362.599829285052, 96992.183924252, 71647.73161828068, 21789.388275542733, 7230.167002152921, 45591.31324789941, 18948.72102928765, 82580.89174175788, 85741.369133577, 83851.29982796233, 72237.89319944377, 50820.27476062661, 48482.92410932229, 67581.38753061289, 73183.44892368316, 48182.2978036224, 23019.485908901337, 95721.36232133835, 17966.6067510009, 46738.07605351673, 40657.195173087566, 80399.1652923877, 67981.04823589849, 64287.15016776195, 79534.19395833298, 35988.95402565065, 39721.75996494581, 81564.09388711968, 27563.327776580038, 11829.025535609771, 91474.05482611744, 15526.114114255817, 74794.0829173527, 68222.89959164982, 88448.41934428694, 80981.65793003881, 2503.897813428413, 12685.456604156465, 63642.01110116043, 84895.6428592604, 59085.77380245955, 64531.417717499884, 8918.463471459037, 94194.21504625306, 26915.075048143157, 9788.145126167925, 78831.3604004072, 40822.21508892299, 49538.93703409724, 20946.645285246545]} +{"id": 1971, "vector": [80602.8477534776, 19485.403733979645, 15071.576651683594, 25296.5827248252, 98058.17019460717, 96694.47457451685, 47094.637899614114, 18589.070708691368, 55454.90223648629, 94634.9879064447, 89911.88723040422, 79310.11280419955, 62446.94514505292, 84602.1572586215, 2028.8735338086617, 10334.422324242587, 19868.02094436334, 292.40571421494946, 87775.05008574613, 39451.18147557687, 20548.181609496794, 19450.094040956457, 27890.50066532921, 70393.9558819816, 34142.007815664496, 88848.86237958395, 80851.02379268673, 16031.696652951156, 66957.75371311305, 75085.23584588809, 91823.69918215481, 44975.17724839066, 21556.227089293545, 9880.600324239242, 38069.622106604475, 45776.38811873746, 48952.39692854702, 78308.04124109518, 25778.119737455352, 87815.54874235834, 54626.148883222006, 57010.05923156428, 27903.572437831757, 26693.520821561313, 19675.836447861795, 19466.301054305703, 20121.940848724207, 41751.63278467584, 57031.49596124859, 15551.275798438513, 91734.6190227014, 23976.558006775816, 46224.77997787977, 48744.54295010393, 34354.816992449145, 29496.489459112407, 17744.836615486947, 19521.80453140926, 58719.46961186728, 68739.73474009546, 99118.73007006246, 92927.74249221182, 43620.87674396402, 28206.528756131123, 85970.88499060633, 72675.30330226046, 5614.8567386618, 49101.039257639655, 1623.3304079259715, 44341.026425171425, 44306.29021196456, 64660.459233372436, 81951.8412732283, 65050.98076122799, 89478.00481317667, 979.4546748404964, 69297.97912181952, 61185.02126002038, 85485.67243446404, 8744.24276212047, 24819.55286691647, 68620.64384440889, 12627.734405087953, 11444.685925619413, 26188.3003987878, 43030.88165085347, 95358.92777065819, 24093.48571637262, 29733.631918408977, 66437.5797202953, 94594.7784302056, 50447.91697882782, 65852.01385887658, 43017.96071470988, 57374.66479669571, 92376.50266725548, 41247.527996950885, 38181.63971570143, 72066.42684704757, 86881.28791125842, 65091.761507972624, 85244.85556916131, 43297.044656296755, 91195.1841716568, 44094.476375760285, 44774.07931321823, 11814.921212426088, 85725.65988558922, 60117.51275036407, 93977.57471003842, 79115.40148509157, 928.050299096439, 41996.2283512722, 13763.77432566449, 83875.31216099364, 61578.86207824902, 54756.74392027341, 57.267886391743076, 29669.218992156708, 72868.77066570768, 2710.6166555021737, 32467.946515260148, 14600.67855639341, 55372.14003957006, 33773.71979100062, 25502.070916562647, 28906.086685956667, 40188.53032361289]} +{"id": 460, "vector": [52066.951085615045, 50553.34621997336, 91982.89780991983, 26259.151900002387, 59719.97324857168, 71665.78926965651, 59287.318596533056, 93381.55716083209, 62253.83380570357, 89788.48874036, 75127.26952520946, 9733.88296686627, 41436.61516309674, 66895.68285068627, 92440.327303335, 768.7115537813538, 91587.28645699489, 36375.74020216049, 97832.04388803223, 8215.362304383156, 8175.399671341921, 55889.74256003781, 93690.14631165718, 1897.1386437800497, 21164.557617827308, 59376.815941445275, 44782.40316899852, 16484.056386972225, 25301.590247404714, 12025.371394919937, 76912.99963615142, 68722.68916016736, 59399.94968359298, 51811.99811734969, 10367.36932569905, 35193.42600895863, 78530.98563748309, 48116.84356713698, 78429.94231686204, 80912.24077538612, 65394.7573670931, 99651.1474764302, 35081.26241243645, 71988.55546821891, 71886.2227432161, 33989.89462222276, 90466.22160557336, 8730.67316418581, 28988.873536718616, 69617.87938773309, 32989.41803396343, 99548.78453017258, 35214.19290250775, 67613.89032159897, 58901.39902967626, 63069.28503774879, 10397.016373986722, 74422.60516907777, 19681.21154076714, 34914.47919385219, 76297.18278399811, 57181.44013738308, 67467.4207331988, 78190.38339194028, 29657.757395759952, 81848.76319178585, 90937.13952125172, 76419.17315601994, 94137.35611359523, 99826.207422148, 66377.35329597368, 85597.51727638909, 52176.395791594085, 92690.64531262123, 68132.11166824907, 95384.75144439752, 81488.26578857223, 8651.840706333747, 84068.01077963819, 87200.03937233899, 48540.75286350976, 81885.81538749475, 3646.58130779506, 81311.82855904594, 24910.13635820718, 97579.33723927179, 30320.163897868533, 59440.3091592939, 53701.02645582986, 92069.63229753893, 53091.036636354926, 71671.66342474472, 45468.461214586896, 91116.9786936713, 58940.38005590332, 60113.72223173883, 33537.20385999718, 68478.04478116657, 23965.586047189103, 43862.897544654865, 79120.83851925931, 63408.916293505215, 37024.34805713005, 80587.92935284048, 23029.75662658623, 55667.97856588618, 5686.754909344538, 35962.30976194109, 50913.04004088696, 41568.49391437421, 49317.1359527195, 61382.54118813019, 67176.65015433081, 88734.05761309418, 11416.942140507213, 23031.78328915676, 18920.28213110438, 74141.5774080211, 15474.918594310095, 40020.8764696967, 2187.7118413431317, 13879.594814284179, 15085.839341553197, 39020.884835818695, 28238.047636789022, 93947.9074410073, 31019.8443475296, 95581.21406041412]} +{"id": 482, "vector": [43302.886878733094, 7784.263804735514, 82588.03844897028, 11854.905637568969, 78295.74436562456, 77269.09233968682, 5669.982709033172, 91179.20006242239, 63581.02018361673, 67867.31046346886, 72771.70060464498, 45983.75179200572, 71395.9309340813, 91768.79927679467, 85850.67385205982, 53090.80315981799, 1760.9114638921897, 71555.7430385252, 52626.52510414899, 32090.63825469368, 92346.05824859558, 50683.522814246906, 27059.152199695225, 99218.41831254607, 43312.13317843115, 60743.23023109678, 33019.483846366005, 93417.76231340438, 21698.03451993516, 43100.82494393781, 36540.5492696052, 38838.0619239322, 91127.5666601751, 55322.80253726081, 40778.970102711806, 66932.0142637464, 15476.627210827699, 64756.129303032336, 16048.852655558943, 24550.490724977604, 99531.07248966224, 25864.319270259795, 75578.63298878516, 97236.40588378228, 61102.15315153098, 31540.357168684142, 91273.80073523278, 48019.179572076, 34509.94299518502, 5601.422432683356, 13420.1584047549, 70641.13091829127, 28335.473416986635, 92045.96451679312, 33998.05608969263, 20116.256351696247, 58574.530454293905, 66448.64280466073, 2965.6337529704533, 92896.37433113321, 63229.9887491873, 91755.69589493325, 2774.5457884058246, 35967.84404030842, 9400.621056132597, 41936.947816827655, 11136.69130387578, 60483.874786481865, 41165.4000852286, 68989.06739431513, 47429.17294635263, 12110.853551179778, 43973.90419166527, 43246.737730962785, 72295.63993784664, 72390.56507623862, 18383.940559375256, 49353.69930639073, 52256.26342975929, 55592.106799033594, 79382.23238232119, 37203.431695161205, 82172.32413789413, 10381.40342695142, 33902.603800346464, 53518.53304766322, 33619.7346041009, 76911.35230057381, 54748.9173545488, 55529.16513154735, 11963.247997910386, 66370.90423969622, 37842.86686800271, 5747.883208542348, 40673.001586069615, 56816.60643494374, 1681.222125607751, 68385.40924774251, 22102.75058919028, 74900.7656432602, 96231.79308865507, 50143.705040017274, 3804.428454862585, 75806.77072964827, 7276.348070446492, 41544.981554357175, 75428.97496764653, 82585.66052085852, 48602.22212038371, 5946.059040680307, 61078.44548963024, 1286.517064389503, 37320.444014559114, 95245.30070653354, 94444.13032256361, 47610.53528111427, 58236.98720093845, 7131.779906180957, 88349.58993644825, 47479.41999944254, 79031.3006319575, 85057.94557066582, 40445.58871776016, 21537.647943005355, 98081.22583363029, 26807.77298541306, 56262.43976839993, 51448.83984468599]} +{"id": 187, "vector": [86718.13064667689, 40142.68403950105, 52851.85046664737, 53553.10382063416, 71888.29437213967, 99337.17722941173, 61197.27410877853, 7778.275812332747, 78226.19799665378, 62486.42217882239, 38373.28759263637, 90277.92138059916, 85976.25393161716, 15396.947543476792, 4183.623199755848, 79195.91918615677, 18117.25451597863, 85240.19591862452, 25952.691181440292, 95225.20177969518, 95240.33779358854, 88762.002663874, 99890.01465601793, 39340.275283767834, 21217.19329632108, 73555.36043231648, 28250.268536258605, 35006.60771367605, 67908.66850711334, 27378.02031264375, 73839.2746875981, 97943.21051015906, 8478.710536883482, 896.8938012424155, 66593.82472720383, 35613.86176636083, 24.13123184165089, 58589.23844370917, 83025.9391992117, 52502.75436622899, 8485.87567465684, 43063.6588939978, 32959.9607684418, 22443.301850035736, 1474.0668783157053, 72104.19209358814, 81208.02686974865, 9849.278234062442, 80766.91714403864, 11915.805121104051, 7037.5055954456475, 94763.2889891833, 6174.941490731933, 2824.3105039916672, 55149.96437081201, 79630.96988974065, 85784.73964085647, 17274.146371765808, 13201.007766933537, 89982.16797370683, 40953.14026843671, 49196.774730293626, 73582.62430409473, 62177.79209584158, 69623.00536609637, 2543.697034827985, 42033.00095241272, 15663.186158966868, 17529.770156178103, 9230.897886829925, 35072.9576917266, 4718.35042276817, 55185.37061232455, 82846.92231595163, 32880.95291178961, 68923.91701984171, 20010.765362786766, 64335.771563852795, 11241.749081952423, 22243.89767303069, 75338.0669610893, 14429.702774163634, 65610.45290571799, 83724.13978222964, 11109.888664864176, 67436.03002348673, 90519.96490919185, 21176.375347042675, 86436.24517577671, 43401.91827115133, 37474.87301945652, 32188.87596921848, 93056.18686488822, 77527.80053221961, 56692.0559748788, 43400.89252216233, 99401.8056267342, 72811.70646621782, 1759.401321004217, 78751.51471521001, 69454.33565851154, 29357.532340144564, 11132.695687568483, 71959.99629972523, 3082.499407137362, 43997.99540750329, 19420.56233706163, 8717.598131112147, 71417.48096162466, 64110.529230264656, 31245.314903482078, 41110.262346449, 95418.42734541217, 85461.48056541439, 91022.90355076114, 65732.39397924511, 94273.55817738286, 31097.67737320516, 65619.60276039327, 25890.517836611114, 98678.82810742638, 35798.312219433195, 32527.932792156822, 52453.14157817729, 34929.232399598666, 31699.431572182977, 78539.52879033868, 61740.91375069728]} +{"id": 1737, "vector": [40984.55142683134, 5897.286818912584, 70515.45818210719, 70732.60873292228, 32943.32470903159, 15908.350971292084, 81639.04549990233, 95978.64047037368, 37836.4885124222, 2240.9741664200624, 40585.883501548116, 25730.59083745527, 1797.6047776866012, 35341.95511654373, 97857.21483904606, 94406.64272411825, 10824.550922198272, 79328.75303274166, 40969.88286921041, 58867.7635996077, 97800.72507277217, 12202.379075417814, 41905.94956561767, 28153.381145242107, 12849.941709474322, 91995.51812582719, 2060.9986115849633, 53925.33529669227, 96881.28274331511, 7726.373907308704, 78219.38426707563, 99945.82253950724, 87201.74710975194, 7819.29664169666, 48545.91838115484, 95241.66908736607, 48688.619447310055, 9636.137825993617, 23684.079338999887, 79341.52146018641, 59723.2935314156, 7972.055742116746, 41595.380233695854, 92924.99143448936, 10056.035787606399, 42536.83100579625, 38814.653976589194, 68688.89649389562, 18830.831104284163, 21280.930298342104, 96406.03498159708, 66502.17575192539, 64870.480642006456, 97410.94344932925, 43057.394522736904, 40544.19654933808, 33843.27818259311, 97367.45305013294, 49328.4134836826, 78982.98889039113, 92943.58495004418, 13764.185449355293, 33362.21717119967, 4201.790508122583, 2087.41531055433, 2501.187874848232, 83123.74333136754, 25173.254400028498, 32296.63252378847, 10835.728355873975, 16939.063105027573, 25410.071758576923, 5583.405120108176, 47315.102294787415, 57246.357181871186, 87545.74245573679, 78021.12378635076, 5065.963316227351, 21238.49603284539, 65865.664914312, 76441.09978832988, 94313.23389828186, 40091.20720137642, 73714.03340948392, 15531.990624439806, 80751.14741200725, 27809.030476635966, 12498.60227822619, 4082.9114220181427, 97827.82217236087, 10359.702598063825, 62587.32597333473, 36298.2360492735, 44962.52579227826, 31584.699718070508, 53140.62203479236, 90353.56423988941, 80659.64364302436, 81614.95508812615, 84827.09097234745, 81053.68943345212, 80895.95873886591, 9821.916403401065, 20051.946701025638, 94486.62331931712, 23006.20474748356, 89180.39870274595, 41609.27430570087, 74861.28110380398, 43445.543187265204, 73072.70906184534, 82832.77528387426, 65464.10244511154, 89306.53813902149, 88347.7634883515, 53152.786429852014, 94625.7618373531, 5418.682700947508, 43529.25968322524, 46928.64788552626, 58503.96839700251, 41373.81867482518, 52377.01070735617, 62030.12989865798, 48208.42744834981, 83403.8013973021, 33851.741849462305, 4496.769299429737]} +{"id": 494, "vector": [59884.55912211165, 18946.29469091471, 14775.943966431081, 28401.22517753876, 23637.75966522359, 33613.34876489669, 62098.415284580435, 62961.14225891996, 34995.30547713657, 9893.031280620267, 15456.693989392445, 57685.31481910172, 91240.18425157956, 81007.19578982773, 5774.02004920522, 47659.802022013995, 19337.98678983677, 93000.34349202595, 81125.47212263738, 49881.553392198766, 48737.10533986791, 37384.777513676185, 60009.728893605585, 75964.91543150731, 66660.85284994211, 56107.18423025879, 3357.1828220932853, 3104.6991223545374, 18674.968213362354, 98064.94106594972, 56368.60648104368, 32968.1427346467, 41622.1247301092, 49858.47016855183, 87242.29141241469, 1140.5167341499766, 50726.13924688196, 20900.382042680998, 43999.37321259042, 59756.84756170078, 87927.65068090257, 19471.247066681706, 73693.24942818197, 11911.269999267493, 13631.769365262458, 16464.280046174517, 37870.464699772245, 28391.878023913698, 93211.42158867298, 58131.72713684658, 61916.04151537999, 36008.24597034753, 71016.420567309, 24016.040765620473, 73628.69630822566, 4195.042095041701, 89661.90510626457, 96554.9313732213, 45582.21268368582, 11846.458553037965, 88491.88355894397, 57704.29890569151, 32728.792132843744, 2242.4406560391576, 88824.11755347141, 78062.53201347144, 82240.42947504928, 56308.48688551549, 58315.42899271277, 73097.47734654746, 20201.604863407407, 4823.230195531958, 38045.83413188032, 61342.09959407484, 43635.155510739154, 15519.04339767911, 30045.42470136198, 91710.46559645706, 66143.71387879454, 61705.51943837816, 14317.540017719277, 70679.15312077042, 70508.69338884273, 56345.90186345413, 62465.01643977984, 48710.53159448917, 13480.685396807125, 38930.27775022246, 81424.75197392395, 71772.2961241758, 65541.26830685922, 103.31340563042302, 15153.305452878341, 23375.12355326099, 82437.43320446393, 46547.11334349504, 20241.59375547765, 1303.4297443024423, 56216.548854677196, 4501.979993574212, 26196.16010421155, 63299.65111857957, 84336.18754449354, 40927.49376198163, 66790.02904292993, 61857.251446286755, 78378.18898391367, 70875.72508502583, 12731.085856286372, 92715.92409167011, 12203.541349304525, 35596.42421014947, 84468.28037268642, 25949.045092509692, 90390.03062137846, 44639.56272388585, 49708.87849833648, 62162.58302041859, 48824.35055993757, 98367.43778495798, 83378.06640762062, 18975.648530188384, 24883.768628604575, 12269.456116071697, 14116.895145863762, 24726.27197942061, 172.09455186508737, 72411.98322900951]} +{"id": 257, "vector": [410.58817294240544, 98007.97187262666, 85834.00452713988, 84422.54379512086, 55854.67921694074, 83512.64442841653, 29944.521285113125, 20625.309166892792, 46823.1198586099, 60771.60077811776, 61250.53050870732, 50625.52603800249, 66832.13344515846, 61114.70285393517, 84600.25291905737, 49311.22244813524, 56288.74827097189, 35419.4928731686, 52611.441258781975, 94724.42089387834, 49120.44750875246, 64801.96253986754, 99788.2380061444, 76045.72322058321, 72273.97299388588, 77160.42666409862, 8525.271978934024, 81834.61318848298, 75580.29748569355, 21798.57768900102, 70961.2076651842, 66270.48294925729, 53201.61256865606, 67869.63125240363, 50696.21541875916, 38215.56994614193, 47725.52436072696, 91246.41821120928, 13477.500553161059, 26320.311557277808, 52533.45504668412, 3327.1466350839664, 42575.250245753115, 42637.9436045476, 24713.48315460714, 40989.341223058465, 99856.21183249353, 8473.802206654978, 81150.37595541723, 38100.0155945304, 52458.473687310514, 67221.55496277609, 50939.2404551137, 70001.40494413637, 27636.746297193393, 55416.86896912571, 24277.123401315515, 25605.62810126934, 81107.54072967962, 17310.403721284263, 90402.48006671928, 48785.64102399732, 99823.71214090161, 10963.59893658524, 15074.221148231081, 39387.09329075858, 9624.037523566874, 73486.46850011952, 64972.523086508845, 14705.150749748253, 85752.02889093297, 47117.34679827435, 94119.68715349908, 81883.49513118035, 85830.59271346945, 8975.83624993753, 8746.83556628535, 34041.203788910614, 89202.01423905995, 50674.412813982846, 79403.28884765205, 58348.348570309805, 52332.683933458815, 97854.85918596401, 20118.53120458539, 77983.65569611448, 52982.309117733814, 62475.59479210071, 48767.926037139056, 35371.7562338846, 49075.915877833766, 49751.35360840618, 21845.602984264788, 40711.86667982442, 85266.38111632301, 5950.631840610221, 9984.30122978199, 40704.39165514995, 84104.41277934263, 22736.993299802754, 61869.21900889692, 86960.7772390967, 80575.40666340243, 63276.62495771986, 50028.91124929229, 44380.1890211866, 3115.810741283176, 27655.035273498983, 31071.50778188841, 74283.73907565823, 39695.038484388024, 82797.35251718458, 9178.219652481379, 3773.3865826945043, 24485.87288737083, 64232.35449734279, 87154.25706456418, 85409.23124530722, 98802.94480731504, 44015.25207089192, 29947.32794310945, 78578.19553800175, 26389.766131681503, 52299.24396535939, 42853.06834872407, 63397.65144528214, 99659.69337477454, 42632.80410785924]} +{"id": 1426, "vector": [71915.68168883231, 74159.58642013512, 88093.28120061872, 62194.66551918958, 51311.83616017142, 20080.37686566353, 15377.47016452139, 12344.508347803096, 17261.434563741197, 81454.7248248676, 2758.976766882282, 12648.639741171708, 46602.38268494578, 21875.98712882909, 80736.51164347123, 98983.7883149346, 21808.733347738096, 98266.71019813188, 34459.37642036716, 46267.38610059342, 26479.170617829695, 93194.16844552192, 26283.778835005967, 95651.11817265885, 55953.327119850714, 5885.51785264958, 87305.45321107327, 5752.204110817715, 32689.800168770456, 96840.8747806663, 83379.66055259597, 36272.37183258757, 72810.32275068529, 94083.15443954988, 46405.42642456482, 96794.75498495274, 62698.485081888124, 51677.9722157614, 34822.33708450049, 43871.90797331528, 6990.373625111568, 63072.67225604203, 73982.93894555568, 79354.18244870265, 84958.47310821846, 92359.21030850027, 10652.397658323975, 77969.07243590616, 13578.042652699607, 46339.786009324715, 86668.59518469719, 34164.76159629958, 34294.61069509832, 68281.17763300016, 64346.152103515305, 21585.88089043816, 61984.715084034426, 22323.385551705476, 1304.8882163660846, 68898.15957932489, 78415.64221292286, 113.28641738812539, 35103.78791257999, 2937.2981038661705, 72146.02488027282, 36744.44001868411, 12384.25782904775, 40693.43964398595, 92257.91960700255, 79546.02712461652, 24817.416991286256, 58160.820249785116, 93968.85246829028, 48285.88441555082, 42852.49166938603, 27618.519363154348, 20957.712328557587, 99507.78588076725, 20104.55732411659, 96715.75748376266, 86836.66943796707, 52470.92743147802, 99360.8862549221, 83473.04110451335, 8506.220488436122, 86013.13968323822, 12286.505980799157, 10502.669746539106, 90052.40720084203, 55658.18696480146, 59887.838885035024, 52004.66397974862, 7790.13028005725, 43802.37261766444, 50872.45109482279, 36062.98976795974, 11066.033338702553, 94728.55527541286, 24463.731230140475, 11063.404607825889, 54352.182981961414, 2914.0411865386404, 39627.939505242684, 83258.84902561642, 70859.00798372962, 23077.98968046868, 71384.95255199057, 86554.28062336426, 99004.19362776807, 83729.52476794917, 59966.71930447951, 2591.864899357166, 1554.4399548508502, 95088.47763029778, 96337.96130014156, 79131.06326424141, 7334.808357446387, 84232.95797409958, 7467.458393627502, 27445.027586393346, 343.53664219012313, 14920.535988074656, 90629.4718683196, 12691.559132097718, 34428.8195045654, 38068.31369898783, 21531.95003958177, 17565.850779998516]} +{"id": 2109, "vector": [58937.96481424085, 57194.35214714087, 40650.65869079654, 27766.02640902741, 53287.54484172131, 96937.88972499291, 7046.780213934922, 6365.615735279262, 13913.233964292725, 32774.498872901226, 3854.9874445366504, 45325.59373746685, 51844.10835962937, 89484.66206373216, 5602.1325848314655, 41943.92322484906, 86742.5211868281, 98029.22123616502, 57310.31320468405, 53165.09879925847, 23327.250226419637, 28541.93442077443, 66594.33099582541, 73914.85904702937, 84862.66745266425, 15052.337546682325, 51304.52643059432, 1748.0400765903182, 48455.360817058514, 78059.74846506116, 18959.00295278956, 31693.71397363392, 19671.92415350826, 33583.54240183887, 78107.8153902128, 64773.56968372322, 26267.695539599146, 74173.7681560242, 69383.235385215, 91945.65766604504, 27340.471266449094, 15752.942028051242, 20602.729986843828, 84500.60160611634, 98123.64148344638, 71763.05083212131, 90034.72678562823, 90888.92022658093, 27597.380271033955, 51762.58029112459, 10434.065962166971, 98706.63032196397, 93932.43407862665, 21328.058474485144, 19817.05278601251, 262.7816750042533, 89511.84304043493, 65894.74860786136, 32457.081958669565, 19529.50477678803, 14559.428092684622, 87877.40659299506, 96580.15275051986, 83905.2420166496, 85326.24568112912, 35392.1226200325, 55999.61700372595, 54318.014783346654, 5289.654325949711, 49678.155096581686, 42196.59577916343, 88751.48620369054, 42205.05299690963, 79991.30868047316, 72909.54464248926, 81449.3231550636, 58543.56529432, 240.00330577562679, 7413.653661960995, 68407.17817478087, 18341.951932177002, 7323.081047010549, 2285.790088493367, 91438.44946558915, 41856.53215881816, 92694.17180444526, 38516.34605449825, 22113.160444530065, 44062.53878950574, 11841.730039103537, 2544.554598501969, 40894.70725745904, 31299.96215133686, 51772.77805556354, 39835.28063077094, 8489.225424626435, 8207.571105599354, 72939.83749837847, 66244.71571195431, 80968.71340965643, 41152.19809702883, 68460.97951949397, 35439.56826799574, 37807.1899057143, 20281.75719430303, 88064.37865029302, 3507.1729462469725, 39313.32281785212, 93290.46779130817, 12543.745756034641, 44400.55007583364, 40780.856542207875, 34820.61711880329, 56200.60723438183, 95766.49856269982, 87375.72143615018, 94903.40672923428, 5012.73488459254, 85931.01633917821, 37637.800421478554, 9102.064990275294, 45436.51423717779, 61675.3678194513, 28348.91620343607, 55515.19515703952, 78745.87977216899, 13367.14557317491, 47974.74005232595]} +{"id": 169, "vector": [88201.32116772977, 81514.9316403656, 62145.8730620769, 75688.24456656295, 66358.79027736891, 33376.7525959565, 8211.134472545911, 31787.089562828198, 20343.45027012607, 68568.68655344345, 9053.557314948192, 18935.97373151529, 12838.121097612044, 44754.710161840885, 52167.90901789376, 20575.274755016548, 22411.544550299568, 1909.4795265185649, 45866.11279932555, 14257.774484488216, 84752.69652721142, 46525.462375826966, 44433.49771819591, 83548.64333230323, 59022.77796569748, 98388.471569333, 82353.0817035329, 26255.352782515463, 25601.48808924131, 54831.98396353894, 51897.63277283027, 13998.00898047563, 17635.060736816442, 52618.029541262644, 93564.8194625189, 71168.4504712647, 4501.492005383767, 51839.53725097866, 48228.9279337658, 48989.34189626454, 44356.96084867414, 26815.997770539736, 87295.32358409825, 83774.24316745707, 57209.88993091751, 53989.73268659224, 44176.424078430355, 20866.53068868355, 67798.84527702525, 47418.68281106605, 96120.18140046626, 87770.6740754781, 24187.389654739243, 59913.97322581765, 23604.909886365178, 37102.848766916795, 95875.6608674044, 99994.29847779259, 20575.936699708407, 57330.47852446106, 89665.46477800711, 67553.26545224803, 337.75517160613555, 34933.17105865237, 43615.38372556768, 92526.03314310634, 78021.68912875846, 32304.196656364715, 80507.39146067148, 4057.9396931981205, 76812.2318093469, 15676.35712915626, 81258.86744712584, 77826.24722191761, 91178.53026088403, 2257.6102925162213, 34624.5362949101, 7504.8631826567735, 71247.39292961963, 78104.27677187404, 49290.476294329834, 87069.39126142472, 93901.02488852228, 58294.85984079541, 3844.3869259165876, 74889.32219040727, 86915.68528349324, 56529.97174418715, 89583.45359191591, 90860.62584718288, 59691.16817286328, 20455.11559099097, 80595.70364318173, 1520.4140297179204, 11581.818992048598, 83889.85289137969, 98519.01676189066, 53046.66831515837, 29086.90168971746, 93714.08411373686, 52158.35398162557, 62209.929904202385, 68144.03348744487, 83340.90692790406, 76041.84401584184, 63641.67523853839, 50102.970922859844, 54920.40968245467, 8068.37338540749, 33803.394970185676, 15051.275141350907, 96934.04209297556, 89658.8909056588, 73831.52629597056, 14973.944638899538, 82592.14396571743, 50900.658270043255, 57127.03068537449, 7566.358769304759, 95034.7008451205, 68951.21809301228, 6814.3496277035865, 80262.63040998997, 63366.064773831386, 83203.71457137339, 49141.74291949682, 18440.789080737464, 61266.72381816203]} +{"id": 261, "vector": [10935.088596943877, 40719.10728518459, 39967.74124237677, 83411.79882747724, 2271.433908357867, 60892.13949048911, 69265.09308651675, 80711.53605450994, 72582.6382893769, 72563.88489534086, 24193.351418138063, 13486.809995468007, 9976.773137324835, 74917.83802439687, 13393.617392230894, 75584.04106891043, 16837.708277639762, 49827.61073989235, 66852.4532278819, 33311.55488692841, 28327.404303129544, 56867.939689999446, 96056.97754874946, 90996.71246466546, 52309.90052241505, 32829.49774463394, 12503.335569551233, 2782.78188219806, 54998.15610578761, 96916.66420079951, 92162.76046103612, 17204.50777391864, 25418.797758195833, 26313.617383593835, 39468.17504185888, 74442.18076921137, 99289.63735952564, 53922.13195592222, 22082.76722966729, 70313.14749448898, 55998.55015461345, 84842.11696901527, 64147.737049797426, 67271.02694103235, 49288.00631916613, 10498.970252633799, 69936.17847430822, 27744.834395973296, 29063.8871555573, 2187.915904126536, 45431.760044003524, 19844.4303742766, 5447.794142216245, 12250.631565528913, 49438.47737818205, 5347.060535650783, 8816.82296598364, 24063.83671413527, 89772.8857260522, 21920.295436362492, 65118.230608717764, 81773.45450151002, 76959.9426551533, 68824.52360810067, 13616.160989964832, 75201.35569830402, 24855.539845793628, 60385.3148489081, 59442.33076030116, 93723.01656885834, 13676.154606236036, 18348.543017175412, 96701.29401313915, 79900.91103511731, 96389.08702038295, 84580.38330157213, 43016.75805887023, 43759.46270220685, 26543.643059926537, 1560.7450842568983, 8735.58473122491, 94393.12575347415, 74515.60423175935, 44235.076159864606, 62358.364676941324, 33546.132107064, 15256.022633806377, 16578.103300600076, 26810.912584107584, 78690.64049212728, 2995.4658548725033, 82945.75450520418, 59502.76567892989, 96092.44465410337, 39322.869538170504, 23955.246368518758, 63350.36068305946, 13548.086968270423, 91506.94510655933, 90634.3184105608, 54867.97650344785, 21660.453633777943, 92064.66989492952, 80429.98242303618, 22640.068199637142, 61533.98050320812, 84583.19328213451, 27013.396665863365, 99751.01891976941, 35775.72943032298, 90850.68226727324, 95927.26978730143, 63973.22799962755, 16339.061413487232, 7092.218369993219, 4724.303877218305, 47932.19037639327, 69922.61334428686, 82082.80083590219, 41465.09699562663, 1542.1971189687356, 28016.47639891297, 54335.1336110885, 40533.605553147725, 48282.6102194052, 21287.49037342741, 17368.716925874694, 82872.88410523493]} +{"id": 880, "vector": [35623.37268146835, 40331.310933100925, 25267.547782407273, 90159.30544780455, 22241.67177310038, 21960.22446178082, 6723.470510041141, 5452.042918298316, 45887.428131603214, 60624.549716865, 39776.470893178775, 94798.62893529578, 9689.403381008698, 47278.555282726986, 67267.39410347232, 89967.40636130258, 30485.995808827392, 54203.073966521755, 18662.895590114615, 22763.482887041042, 52660.185379024195, 26534.662168009283, 91376.44262727037, 49526.06072633221, 89056.973985217, 70810.82627208145, 46222.96311759771, 4338.283905083018, 3285.713747363639, 33816.91076809261, 79620.51450371384, 93503.59481467739, 73615.08602144259, 49926.049631745125, 58020.73800022261, 23165.086035381166, 74184.85970740451, 11878.539755565254, 71821.54228921093, 78173.98377113562, 78112.6315021966, 99555.60898789908, 95385.3451885824, 30129.173970190324, 81811.1418991883, 15013.860279634338, 2352.708372025891, 17658.001413291757, 3707.637691808452, 73573.29880815564, 491.41921941260324, 92230.54341730339, 548.8895474020871, 54540.90659591081, 63485.61356921096, 63211.95086660736, 7883.984017210677, 47963.09469532968, 65414.38051253432, 39138.05886597174, 87188.92271981834, 28398.28916808079, 31333.239976719975, 97407.33114903235, 73469.72393118015, 32205.858584645808, 26806.530540273045, 92535.89740699469, 22885.933004953564, 75796.96157319065, 85912.56258486545, 99854.34007551866, 92778.25575154624, 43979.907809420816, 84999.55034218701, 39048.51289560378, 53711.4382875084, 35928.821903766984, 76570.51404327947, 89757.20447048685, 17306.815573002543, 86847.32236467698, 58861.20445350377, 45898.99512914393, 39430.08468950378, 19231.768717322262, 57908.4889054859, 72654.92583012141, 51439.770004592654, 35749.93158704016, 3788.553208758272, 92556.77975289752, 63587.57694041066, 74131.1829968026, 53246.44844992913, 87381.94325255214, 16954.796202243815, 5826.229716511743, 27128.138624400977, 95849.66268749315, 26370.019366179255, 10774.757209450747, 43831.78784440413, 41899.96235500198, 58050.63882064195, 92452.90765637162, 25888.207428386257, 9514.615798614945, 22312.257891692956, 55608.412093384686, 49919.690805167, 29345.472009012687, 23773.328611211007, 99576.42575425653, 74123.26604158278, 12398.745337254091, 39952.6792188314, 5892.000528300135, 79003.35297994672, 18760.292656648635, 75681.6099614796, 23133.1859563498, 56622.1081632799, 70002.89090359714, 92779.40838262768, 78497.41924628077, 74152.92218857683, 29928.7312255625]} +{"id": 1229, "vector": [13701.232737391978, 12109.9707848774, 84558.45467255039, 84390.01411509953, 58169.77948682924, 70921.90187525052, 32904.63199849638, 1737.889020694916, 16223.314287397972, 5139.188371135261, 43757.90764580897, 46553.34943073227, 77467.0865863698, 22370.996275348363, 4281.24751501302, 66936.04409393153, 49163.22035869435, 9494.352261513894, 44131.943631867354, 28598.495363505437, 40561.93526866847, 4149.060083016787, 49765.113639301606, 45423.580126580455, 60020.32709378497, 51201.47042005373, 30645.918106454108, 15265.91310397074, 42204.81866152328, 48156.93632103757, 33257.71777234341, 29832.406799201373, 27553.491061233526, 28497.75477277552, 92006.4059053217, 99964.30581149358, 9108.703196453605, 3452.951469060095, 16814.52301958034, 83433.51866733727, 86327.66550289239, 38629.298536241666, 74903.60932535141, 5651.2757479634465, 14098.982739081577, 18257.90770177067, 32305.59169764178, 49036.84728104072, 45070.018245519896, 57371.67367687034, 7738.304205352098, 1370.2237567531638, 78216.91100754212, 49254.050742444655, 73653.91903119624, 40297.50370504901, 19424.417004354178, 38268.315675965845, 97308.8084525319, 67052.5932920801, 13815.758568120096, 34073.20284028903, 18454.49065484337, 881.3616793842959, 31572.910400615638, 99529.34105556237, 87381.13216545199, 9236.998981414612, 28849.612333253095, 2482.9950121124434, 26542.59118005957, 88030.08042117821, 47630.264245191356, 59377.464160940486, 39389.215015416026, 38925.77897417077, 66102.11497702381, 40777.83794239781, 30516.017814816198, 90209.3549814444, 77274.82996084454, 19431.14194451534, 55147.93726089627, 13677.718991409516, 34468.51713798279, 45687.17774292967, 95517.86256430688, 65320.16868960361, 53876.70012611294, 2380.4916026534784, 82072.67206701366, 53905.93817384946, 22953.41805536856, 17606.735903838056, 7718.100436389108, 39554.35521478207, 50209.59958578353, 51899.185856743105, 69839.13769617831, 20223.26718796501, 10597.93136123659, 63820.42787111608, 10287.307273351653, 52496.18653541861, 15219.597806314943, 48415.980360525544, 99681.91275994378, 67531.56781789307, 98459.4740262545, 18998.911903935346, 20692.798432922034, 54269.087966466, 82841.83228296576, 71185.28167684017, 52427.18857452958, 95700.94371852533, 63142.90612840392, 75172.88628904257, 3380.149102655694, 23105.672899169705, 40479.72315216386, 5441.041981518335, 8176.473301715537, 67990.6185770268, 47719.73981918972, 26592.581243712164, 77524.66014874072, 82122.50520073567]} +{"id": 1352, "vector": [89764.72819918761, 55553.38020896821, 12700.845080881185, 53860.25336875373, 92942.68940142475, 29460.544365364716, 70468.38374984926, 32495.58951685506, 72991.93067920719, 79364.10871755648, 63424.66484250714, 18862.790638328297, 25097.970110343405, 66420.38633073398, 49486.30641505832, 71044.14584535109, 57096.2316356299, 71591.21444933796, 55086.024278903176, 47065.22448068621, 10855.704259287702, 13017.504787667678, 36938.42359704919, 3741.8773908938997, 18971.835619422916, 37318.872662559574, 61390.851669837444, 58130.868556630674, 55205.97623385833, 49255.04527038661, 26564.66814181597, 51333.233834511804, 95754.21525886611, 83098.85090999717, 78339.03339674615, 19087.62950650058, 22974.412149158285, 79566.74747430667, 58787.28249502632, 59482.43784118066, 43881.71730335163, 3768.1934149281783, 20177.60867619477, 1868.0269859076448, 8905.304227560451, 76305.27754424444, 28977.98580183155, 96871.74006892652, 58406.58163621824, 46799.91571860187, 46796.479303147586, 15674.731405695975, 67773.84750195645, 83831.23586200328, 44233.82384271478, 69691.0271509296, 9536.688993406884, 58360.02105770521, 12038.774983785372, 59197.308626988975, 93657.12318265496, 98764.90692734178, 22177.046501298926, 40696.500436433525, 71128.96902269065, 75518.4696270306, 81731.26824373174, 93259.4337050989, 38779.243377561164, 31456.971010605906, 23117.353327466517, 53108.21562861576, 79831.36160804321, 77477.81585365561, 47049.632783179826, 54592.002560329136, 44422.46779914842, 87111.90363445101, 65403.122319582566, 74460.98465848989, 93024.41320332016, 21410.825752427652, 38246.08718999476, 57417.812309617955, 43501.612375191035, 7271.565837605387, 39979.81234792557, 31520.979549827298, 77729.63422138226, 68150.94857543228, 65074.22841193929, 36613.022917141316, 92053.60973014064, 36663.038496690984, 60886.10302658588, 52143.050697878876, 69698.29650909596, 63066.359331810105, 51968.80514659916, 60689.41134147963, 1010.7767776962651, 42324.75116452888, 10486.48019337488, 69053.11770377899, 21470.35232228709, 55293.98002707872, 97461.54625747862, 6879.213235396475, 97026.19989807231, 371.07225250867515, 90984.22344249529, 7519.975592778261, 3381.3356882419375, 21099.830934699083, 73520.9580073288, 51453.48455574095, 75078.77733553751, 60127.08382259207, 45519.14631047373, 6187.722864362843, 60899.06704436815, 29173.333322591956, 66363.61218194256, 16824.159313702257, 76825.60096084992, 28219.157169214348, 4277.744758652868, 65757.7713336952]} +{"id": 1708, "vector": [43075.9273461237, 15643.949129023338, 57693.84861897329, 21833.605032907664, 44033.6972887583, 83995.71248514028, 46889.35033755978, 35271.44922999077, 45406.46020169512, 34768.50408031583, 5766.254239114188, 66974.88741162847, 31751.856298511673, 99029.77310171281, 49472.402985816574, 47835.84158385302, 91991.83086096066, 55067.071927470126, 28360.739146398973, 36512.5310417803, 4296.8814738371775, 73209.80777118276, 96623.42540736188, 60854.77672398496, 7542.305600846966, 25144.689599534653, 71966.88045048362, 51885.67562555817, 59642.20609648075, 85113.21048890424, 9155.252663719082, 78303.71580835257, 69787.67423729193, 94966.07611663724, 68618.30445871946, 2212.912906684117, 73655.33267177996, 6912.651951287574, 22647.04715082606, 75861.45527514647, 18196.19870509288, 29240.200964127016, 83431.62414819919, 21107.833075952487, 60837.7626160617, 13807.683253598401, 54682.53265445204, 51595.86942971567, 78219.0546510982, 51920.43881939428, 93822.45058851986, 62407.37834569138, 27862.131675592438, 57403.451677739846, 85013.9816535552, 22982.998807217937, 2730.291656586803, 87118.73227587741, 49429.213380813686, 79065.79706098417, 23935.47277289042, 93847.8332040942, 68496.83848150543, 39662.21954592054, 34290.14496909514, 95735.39640332566, 64357.599823590805, 55223.6119722466, 4533.804500536487, 78163.58972895653, 69192.8111961838, 60739.984783525404, 51637.36825979857, 85287.87589791033, 61289.96610462108, 19765.608851999426, 99648.71230471272, 91716.48905934159, 38074.26484510827, 31183.312986223344, 22802.445394446713, 25028.432742880057, 68688.37204669576, 71374.71104929411, 33581.09576692233, 96151.42659370783, 25906.7232155612, 61032.862953326614, 33606.17054641107, 34720.922768840624, 46380.44247270483, 45069.0754884517, 89005.13553894886, 77571.01586502348, 24018.20317082498, 81314.89052550701, 17563.240989437934, 53264.67535594753, 71670.3726200451, 43013.020576278774, 99926.78689703715, 66202.35986864212, 43769.5377411082, 42772.998802206894, 30491.088156080015, 75201.41937389267, 59400.989199360156, 92320.17493818697, 44594.25088189084, 77752.54603273336, 74314.73468487867, 11951.709390572529, 91639.94118033082, 56187.8121361716, 67639.57674921329, 10969.644840573756, 40530.75409091659, 36750.30342536857, 80411.9876317377, 76084.13645735996, 80643.90077302263, 45384.47724659483, 72231.86324131097, 43275.774035023554, 66111.69777740088, 76570.73502592601, 61057.42254547684, 4446.165866014584]} +{"id": 458, "vector": [33440.04812541059, 15467.647913840143, 17613.24888436774, 49744.683699272915, 40074.41703345972, 49749.108787366684, 44250.71508317566, 44776.482268408094, 94779.62887160637, 34652.15976834318, 36058.38207180322, 26953.106717028353, 66443.48109366014, 26164.953437160166, 32954.28731867287, 9295.85508824281, 62424.20354411997, 76973.12991086833, 51437.82068391256, 51701.434550309335, 68444.27517215362, 4260.751236370875, 40006.803519285684, 77027.66211319309, 59371.49533934269, 51927.19320989298, 20533.73178634661, 95852.84002486696, 24879.876842465797, 22175.188404707525, 36206.967732907644, 20058.953175076545, 62235.25711274493, 20578.757100273215, 61556.540936930134, 2132.63327244958, 41372.714173644796, 87385.49608011599, 42262.08736397262, 47586.51855439275, 50076.73069082327, 58513.19497542422, 7934.2017753118, 37013.68815814213, 7303.497147740401, 25008.337897326095, 52994.311117045014, 86552.52337837357, 3320.5154173197584, 76359.26248687436, 29128.90773762513, 8635.763106510252, 23312.910754805394, 66183.7461877787, 38981.68610043887, 96267.3945739151, 31201.211608227353, 63256.807162635785, 38133.61488234715, 61078.49477517256, 33244.73283149394, 66900.56177794216, 9939.615669871426, 80822.95270270245, 26463.16680719375, 1347.0190357719569, 45429.11403220861, 62861.6200710151, 59679.53027857176, 70044.83912599625, 63720.39409810095, 44124.271109621215, 67369.43147726549, 41973.355343570314, 72831.5753474007, 71327.69802078402, 65960.62774231964, 843.7793731619458, 53950.72792638298, 54294.768702864116, 98811.55702577159, 10441.063355613978, 76305.74319996331, 76407.25387116424, 22319.251010171127, 13418.007173409342, 43378.89844371283, 52253.19899090605, 51302.03510411249, 38487.584754902186, 17325.117629861943, 24995.428411994726, 23749.891050258575, 97745.34429048993, 95737.05171927236, 54467.374486646724, 19122.012394802547, 22845.646964290234, 80591.69153883304, 68492.4863524, 54187.024377622336, 32558.742540938478, 99095.23814243921, 54076.756925574686, 29038.431222018036, 60109.054615341316, 7876.031351452739, 84137.65839801809, 8599.59999982276, 62495.371249605494, 21731.784683479284, 41285.70219820204, 37961.48412462916, 94529.76274975126, 26459.27003049238, 28923.269213393443, 45505.63378717018, 12803.614264462638, 13149.675839869013, 45377.992050840796, 48310.65054340162, 93495.68611626413, 28447.600088403113, 92479.05341405216, 33178.11105659208, 48098.50720324259, 6097.632377187112, 84721.49699768917]} +{"id": 235, "vector": [84352.07186519714, 57309.6383436629, 70939.81180615029, 74021.97872590201, 75383.52894709623, 61706.05960081481, 52038.10205242274, 62047.600830275616, 52213.7616901923, 24041.000973614548, 37022.248397880256, 56397.89425500209, 79189.8749899738, 15678.8926169386, 19075.937896133364, 55140.898830446036, 59554.78784664357, 34958.326976836564, 98528.72247126889, 10745.520920380513, 34507.90953946876, 39831.002191006635, 80029.35384667655, 90973.49971644691, 11407.068300439927, 93584.01625952547, 31032.43955401962, 71475.005992183, 13040.671211534138, 35307.74643626242, 27455.734160730804, 87723.16837663605, 13074.136086672728, 97114.32282364373, 97029.04328425026, 45811.51936333523, 75676.87629358958, 69949.76831398791, 42449.19443772054, 52024.246567039925, 52080.28478999206, 40358.2718547599, 57367.56290033762, 79495.30598797035, 89279.7224512615, 57013.07839992642, 41239.44194185095, 74132.60994659891, 51218.0553008651, 62635.58478445827, 83690.470355561, 24387.632123747084, 85891.03469564697, 24004.021133067075, 58116.7129224768, 56184.41484330988, 7307.560450659123, 90232.59415185223, 17690.555319279432, 26612.44157627398, 99044.131443095, 23158.5795073316, 84671.56819459675, 44939.197531047634, 54114.748782080955, 49305.41973928699, 66916.44412404316, 80569.66876233337, 98685.39444690957, 55714.15833028093, 22134.78357626003, 65294.25733797245, 80200.46938537348, 46105.86075960624, 55966.65880949729, 42762.563440776714, 77033.05201276668, 24498.48803267456, 17115.27608861424, 83375.78769805146, 68691.89755162544, 49587.16515284221, 45113.7245197066, 61718.55125707818, 71454.98811976763, 75557.20758817207, 40072.50581127294, 31712.338780163394, 69363.37963067522, 15047.665479716754, 41009.18023732203, 18429.248034290056, 51814.872641356094, 84606.93672565109, 66079.67940340628, 37020.11955649901, 39593.967101713955, 86995.82154354731, 42841.19734203633, 41305.6825957241, 84383.45395956513, 81035.3553538914, 43080.94853953884, 51995.75798248634, 88750.5240452356, 45947.33792739417, 91514.63732091826, 99815.35242243359, 67714.38657187328, 69189.82786889332, 70255.020093972, 35952.13639472173, 146.32123182811708, 14444.99530775759, 59841.50827258059, 17145.135077168437, 73300.21715866515, 53995.51622721014, 85746.23821245242, 66799.85013568723, 92111.57973922421, 34369.67076616773, 40333.82632077833, 50582.61777069607, 26551.696369058107, 93514.95377547195, 69232.2747210488, 14046.302610718663]} +{"id": 183, "vector": [45680.52542061028, 90601.1644374573, 58972.793420335714, 49813.67911269824, 49851.478706361726, 86825.53252960266, 19295.92361646577, 33669.24547495209, 1817.4290643716229, 20473.043041932604, 93019.63883176488, 34249.04871885463, 85411.70428687155, 56301.2463449302, 92773.50771364613, 51171.18923752373, 94206.75993990515, 54206.209885643875, 41417.80634093085, 54941.286215231965, 37278.47722898541, 5569.564281952844, 78726.70725780682, 60912.774967139514, 78792.85725656332, 50958.267597869446, 14143.039055935513, 5025.433994092754, 59526.910672878716, 90777.80689581702, 66812.55987040664, 7635.653223973593, 51799.658864709985, 33646.937669264174, 94821.22590931821, 67238.29352337576, 77846.79466932645, 99377.70061908611, 16818.39819616169, 32531.919191740744, 39022.18245709565, 78550.1383566729, 57666.56814157271, 1844.098227785551, 34347.34349606517, 30092.24094341153, 383.12801316779945, 34910.803236302876, 88084.73562237373, 63968.64757617784, 76388.494578705, 90335.56436757848, 15154.550914911802, 53128.924801637746, 52509.42226694086, 17131.19107357758, 87740.32146549273, 2116.887521493682, 51940.08930397629, 95237.261089756, 15685.11972179889, 43351.05965826348, 15745.116977524909, 59970.70966771549, 20204.57573351766, 22719.81972402103, 60438.28496569815, 74191.25972105264, 2732.8154211510914, 88915.17356266556, 60321.77034160072, 39418.413823658935, 67343.95668820784, 90876.53384894154, 39402.26364248779, 84321.94722797652, 6894.462322034778, 51728.58069472827, 30525.597840259812, 58157.196116502106, 28642.237027566443, 28770.18537986491, 81726.32907977432, 21602.068492785565, 78852.15451730402, 6967.887442986431, 61575.21509995738, 65800.69356845807, 99481.42781778028, 7847.1254621791495, 61444.91344505918, 59604.93444466297, 40870.96085505251, 39544.8748546538, 20315.284915006003, 26533.152914740156, 76291.67217661686, 30453.23198706009, 85542.66563849911, 53452.537349981365, 69272.97546311181, 16798.727503493406, 46986.89653669316, 54968.6295213215, 19824.849695255776, 79455.31791461485, 75616.58024000078, 97960.16132293524, 40314.71631628642, 79591.7100775431, 63191.652679171784, 49221.92289112188, 84684.58121254532, 40110.64404574793, 28728.44698124698, 27375.142528918073, 87052.75223279232, 87731.25275568657, 71035.83194626519, 70290.39134808606, 76066.35803344, 13841.65885002957, 32195.511915268093, 53155.20665626555, 17110.10117920214, 43235.011924109924, 28897.427494189942, 76173.47841625614]} +{"id": 616, "vector": [70559.25486302607, 63092.048892336, 51959.93299073637, 39110.72399346809, 46865.450908430765, 51448.17136987956, 83859.10496604862, 61031.117520128966, 55628.31942252149, 26262.456336870342, 66643.8518922833, 6202.856559257697, 63917.09347150814, 45782.982995866594, 76360.26807930174, 4504.7529852070165, 57938.75187483919, 51184.9503002272, 24056.877762488893, 40590.82196849898, 19857.12226652149, 75730.15070350654, 64096.59913426298, 82674.46577337617, 2601.9310684731954, 15226.909207051254, 18650.78040873861, 70006.51096194077, 75328.55999506506, 11097.956664016028, 35798.6753892956, 27858.27638512799, 62206.18990802276, 18738.26579593112, 7015.448697254711, 55068.43290017813, 42505.205991482064, 31000.393246111336, 19796.580829808187, 68129.76329890304, 70493.18249446832, 11280.664370265902, 99248.90802082527, 43565.760929578, 6918.388544874066, 50016.700546131535, 50320.83848992779, 50009.072836130974, 65430.22560165217, 54920.7083474649, 52703.53365860114, 76249.16055180036, 96196.36014748603, 79145.94026805626, 99459.16587272646, 15483.12167814424, 10285.178876549173, 74403.56705817372, 77349.9640229226, 38093.508311279635, 1488.260729611779, 68714.60482635471, 62343.63718420282, 45486.491876929605, 64836.017371174035, 7966.166895801319, 78567.71932208167, 64916.82491456426, 27149.90561427166, 15796.234898264895, 46105.06422374368, 92887.34218700555, 83138.40128356712, 79663.72773927021, 22482.954040370107, 80066.71383268606, 51328.63710757161, 43847.27327111428, 25268.5837243782, 98607.39173716812, 26025.15250150507, 55922.32702269667, 37171.61440875689, 75331.12859952767, 26222.007781959488, 9596.037439734617, 63887.00479876182, 66942.05470466005, 69639.66723487768, 5751.911780090768, 59592.361581492514, 44893.82041811114, 70645.05156967742, 38320.10956858418, 92889.41868630672, 59881.03841458637, 8419.374819800607, 24898.92876370279, 54749.25641705591, 37736.54251268067, 64284.15119283931, 98227.58745861548, 13756.391059146677, 2610.2001219470594, 39400.92735853714, 8227.650499523332, 14747.354483918529, 35248.756062580236, 51283.1015197794, 24474.243639867465, 10957.162555352528, 12108.265749769664, 4410.41694616977, 31055.66594044591, 19048.712366363674, 9486.951018350775, 77662.76608731574, 83944.2196058387, 4264.310398222815, 48477.517666989836, 26006.573347314523, 96580.88143954294, 89656.65069460604, 88982.09009111459, 81245.43910309112, 57861.541109710946, 12597.416680870267, 67183.98659933597]} +{"id": 2014, "vector": [92891.4808710926, 69479.88549009565, 94103.65675937694, 86791.96140346919, 81520.23156166657, 83118.23533301867, 2145.1393660975928, 85281.48360915338, 65121.60678695283, 70916.64160699822, 43536.21218718385, 96352.71959559091, 22295.860787214217, 37043.81863765428, 72916.01201862886, 28972.558603961363, 4822.805505762295, 51175.36615171705, 71293.28082323965, 75148.87562789331, 75850.8777314811, 49074.79923230116, 28704.902951373322, 12472.106504005054, 88071.2547306514, 13672.002071677714, 69910.3100823397, 51790.80714479807, 38365.67320675655, 16361.823138074682, 58411.1539125196, 30678.695996189243, 67902.16460242016, 39310.27917446953, 71717.33960569053, 62525.288637391044, 32112.20498792886, 27452.203232918848, 68807.13574380487, 8732.967184089392, 79361.26305918144, 61756.960669297834, 38001.58527284217, 65348.86176759247, 99392.08760791183, 33505.92366305861, 6511.848487326321, 61372.22271908836, 17056.248675210052, 7443.7909305674175, 60632.98057046059, 29344.542201728185, 90326.19701211798, 53313.25966109157, 13788.698039861214, 42772.52966129907, 5716.254941234322, 64646.297655325856, 59964.37744780526, 96205.66407410981, 58160.20671413375, 45186.82430508183, 94199.35331332809, 96303.80895112721, 38327.547949992666, 60322.96442454668, 57026.15447053596, 74294.10093194207, 18609.08918826637, 99538.7246215382, 28501.28565346609, 51659.23087027045, 81126.32093415922, 5804.7599688973105, 20825.6794124151, 35510.495415970945, 19619.447416646497, 23525.743216115457, 17298.74388940783, 31894.209994225464, 83198.32980680195, 72957.82570183049, 80888.59854599719, 21307.760718576763, 91858.07432447792, 89895.16170838583, 79971.06159312307, 13283.736264164192, 71230.84656911911, 94123.9481843735, 28808.355541522556, 95724.57114861165, 71439.49276649315, 70546.92837055161, 16690.099372254976, 98624.77749366338, 78075.1428050505, 20895.98210317891, 34503.768483222084, 82707.13973236698, 51922.59833230003, 62651.213577776165, 14972.762550258512, 42443.185539307284, 7051.815063766775, 55382.2489291777, 61448.694521872836, 71691.42228177216, 99711.44802671892, 40100.707308230485, 81676.24573125919, 62054.646439764125, 91580.63724727827, 9186.333740505315, 13588.498623603196, 62865.374830161156, 18331.299957604697, 33875.951602196605, 2759.474196009826, 92528.22678189645, 53380.203037643034, 9728.160815131081, 54337.23630262631, 84437.19941734598, 53427.419328147975, 96827.16368394333, 95495.43818340052, 28393.52697458971]} +{"id": 689, "vector": [77828.22729792651, 72436.01428035198, 96321.63540013788, 15611.487645038535, 59301.7199210072, 94016.15274340633, 4178.978578387926, 85250.07956634359, 65860.45297972187, 49231.35496383613, 33324.67036935383, 13488.441106061655, 83074.84572730778, 41892.27443182565, 33796.96953152654, 76157.75735950153, 14201.391816825737, 57402.308972644976, 6003.907528518815, 40510.75029672447, 93141.81642131018, 86482.93457405418, 46789.212684794526, 10432.882606084493, 42701.6131236021, 92966.79317581032, 16883.654157322693, 5705.95378795824, 13791.107759189481, 81984.73482046173, 8694.748569342892, 94435.44144672765, 3182.755524666381, 62942.714303140834, 70202.37693719477, 2903.8660168693054, 90887.6320263423, 25945.35254719056, 95440.78013949428, 89406.34659683672, 2676.883075762171, 18781.961353781797, 23981.483707296513, 531.9671404838577, 92887.57241739653, 75915.17150562575, 18196.455973482418, 44341.644623516826, 61091.827889442284, 55121.10503803483, 60718.810354271605, 80584.11382888704, 30172.39324792331, 94881.20068095974, 65245.90228845344, 63469.691679303505, 16810.5374902342, 92939.48315377768, 31174.596363024033, 74622.70104249912, 13109.453631371049, 58521.53087240386, 22845.04027679889, 37945.570555628685, 78535.2847360372, 12592.866054919783, 7740.812394223018, 40776.40875251065, 89143.01523099779, 33012.704328403495, 97627.78874096084, 91038.68379271933, 44029.305669931404, 53883.4185758072, 14145.681696310408, 87049.55158631332, 26082.036760541516, 78433.2098565959, 60037.2958875624, 16847.10092778724, 43242.21678533625, 15833.451050970305, 34440.27868511722, 21811.245502571797, 12325.793605851664, 80920.3217332324, 56121.2463507067, 75961.91380325041, 95771.69871110369, 64934.405443367636, 18760.11157481492, 99227.77475563013, 7503.753344520314, 51851.91068987526, 93172.3919448528, 46477.822402509926, 7456.727016828823, 90033.98812662569, 64571.986391219696, 37204.83816842277, 94290.89989120353, 23156.088706072675, 85295.66283770982, 52366.61993006715, 53876.59198184144, 52978.497917218556, 95487.62501636927, 24767.47236368979, 70290.54496308437, 30998.60711236925, 93006.07423684449, 19010.863240723207, 75938.35954580108, 74266.81197453305, 8163.28925952593, 69640.73439791902, 90815.19511501871, 75897.48857981944, 29632.660268451717, 54678.06190362927, 8111.979845741513, 63300.83608525134, 58282.07635768942, 34929.62834129321, 43032.64253081909, 99682.73435102034, 6484.538608374124, 93781.32607912194]} +{"id": 597, "vector": [5910.358244339231, 24436.701408371984, 48314.849665947215, 86513.15565995491, 3711.102813833933, 94272.47686044668, 3950.0621395957337, 7317.639635650575, 51697.04988800114, 72749.92323659846, 16056.583319813644, 55448.87157825846, 37606.77869319506, 99168.15952296501, 27928.044028936118, 873.3833705630634, 94235.19815088976, 91070.56480629073, 24233.282527036914, 18477.417021601028, 34129.11665184354, 72676.9297260346, 6934.8002164220325, 14821.400831060804, 2385.0633230207195, 55216.91913818709, 57340.32324983104, 31759.91976110699, 18020.56317510099, 2110.3503698922555, 49665.492167682336, 59808.45926014482, 83247.32352092388, 57474.05669788418, 74729.18336047993, 29075.61971000161, 91885.8295833354, 38508.41139757442, 32091.96313069056, 11830.100351994932, 63174.62360005095, 48461.05928646194, 40266.24418072119, 75803.86382722178, 7803.6046193730945, 86588.26815103806, 54576.251261330224, 63696.85836776108, 24372.756853306055, 51391.33893647493, 52952.74214941036, 32178.797431694915, 17893.964278036812, 83529.15766922403, 8588.412485857643, 64444.891835030336, 29217.74873682136, 18487.451222910433, 17158.190420885476, 53481.06392612353, 17179.863064062505, 32712.901944584526, 36356.253914593806, 5524.933626707151, 6605.594256887659, 33342.622510298526, 54810.11709312802, 86355.96208674845, 5882.08355786054, 44775.09541923085, 66653.8954199557, 91722.15552796087, 25340.749111560403, 76761.76053285683, 60097.22592560762, 97449.2812164178, 10753.5010043057, 31187.22595938793, 2115.0710665881033, 53406.84129928024, 77591.30173728666, 83359.5896623644, 70354.83494147849, 92274.08312977033, 74250.65413034685, 49920.12408099066, 45374.52194651618, 11276.825114522882, 79086.02895135366, 3084.0528868520955, 23164.08638446611, 45909.885156854805, 97490.43942703234, 45877.18899495815, 78288.13068688322, 38262.776458607805, 86742.46048681025, 45483.997735496494, 28396.57349308167, 41062.12246777961, 14579.760185379942, 33487.78822534887, 37955.1099604578, 91867.75651967758, 56402.39448256482, 69568.04575454192, 5563.92870344361, 6445.383842302499, 15051.513203069344, 12723.368217804498, 30474.64535117881, 59816.33019008654, 12533.954131474567, 65057.12629291103, 5315.704817735645, 39233.218013517966, 83472.96317296052, 57057.13365143964, 58719.31334796967, 15818.073821229651, 62192.51548651774, 42583.09978839498, 93802.489913436, 24022.498237432334, 73413.24218858301, 57466.0032153413, 89276.72289555676, 27966.887649327255]} +{"id": 198, "vector": [13240.583202077949, 98636.92458534375, 95121.25989882495, 42832.92460762398, 95905.6683840708, 9319.169164947372, 38291.77990474346, 74377.25115415346, 57379.3595557357, 26665.508942457705, 33273.17026470558, 21943.22831964862, 81739.20923011417, 80952.06264806731, 3009.337316151739, 38754.65040451111, 79498.61025886898, 4265.131850524073, 29936.97507895857, 37221.10387524766, 19997.18330830216, 42972.33906637915, 62676.33041709818, 33934.342537072196, 19940.700835824133, 80114.39929091101, 29364.69442227454, 8231.784744858294, 6222.912754787402, 93132.72289197326, 79646.17716112421, 4884.994377292628, 34150.093191565415, 83272.39006851001, 1587.6785043092423, 44643.69054750587, 92879.94893852697, 15178.514590042947, 15490.312144584106, 86994.19250672318, 50688.67849293534, 14823.848225739788, 14327.760823142165, 75168.51544606769, 32109.1584738184, 92268.80962565124, 88720.86333318948, 77239.1260804802, 94258.19469681397, 3552.3174949014137, 85882.83718049055, 15052.189057486099, 86274.95001764908, 9622.273343555265, 14963.133352654679, 65706.68592348103, 93959.84020062449, 37663.702517745536, 39679.085778536304, 9140.505935765665, 19953.014358871045, 56308.434753102985, 14922.993732146982, 63373.0273866848, 58724.39839804264, 94079.907539957, 33608.94684112154, 81962.61255916517, 36776.79119280692, 87611.07364712076, 89494.5698206504, 47522.41366378166, 40683.66904261176, 34104.925585268786, 58518.33244174569, 94847.90596688642, 78032.23963516421, 71832.93620883285, 20729.820347389938, 27285.39193856876, 80893.64420310105, 66207.03777022423, 99458.56881836994, 2480.973230121186, 54009.544052966, 32502.96474634199, 45431.86336074324, 57107.11365081993, 2306.982082188558, 11895.77728572585, 65556.40482902633, 7264.955908279525, 4231.932981559871, 39816.050882792275, 48123.04692267404, 60650.40103285858, 81918.21284723672, 4544.324554615476, 36435.70328888696, 30680.970999495217, 90053.182333226, 68543.17927712828, 35862.45214887269, 98546.28419146885, 15820.066928563881, 49096.78369348861, 52031.665863649934, 41106.86472394504, 14931.603254349613, 24009.33533967836, 86994.0998179111, 4655.570863713055, 54594.206374805195, 54981.41125307538, 81559.27514564131, 47009.636750221114, 39431.72028961332, 65425.182794011795, 89106.97595438923, 52289.969167596806, 20935.821117810505, 90604.18795595017, 69087.48292737607, 6452.155956709482, 98000.16197632064, 95922.51034947357, 93054.65923710109, 90461.99436205429]} +{"id": 1703, "vector": [73333.00391741426, 36316.274454424565, 44576.05931483662, 61754.6175760304, 99503.01581737363, 71157.54364008973, 80580.81219835195, 24737.44968463342, 13976.472023029595, 37031.48518414551, 50392.98157708082, 85604.86785845913, 36150.575778733175, 73791.2350647618, 79194.33811908077, 86685.33569867654, 89409.22885128592, 32229.652290174094, 61447.76291110824, 8451.813265760933, 31758.82421096109, 83721.44519504749, 38169.937848012094, 45419.97370744998, 99384.83527567278, 40436.86647484473, 19066.856011861833, 94135.34042829068, 74022.55009518386, 67493.67905083306, 47138.64841941892, 5688.404327903973, 52084.19156183118, 79377.73595346665, 423.32778742336654, 83823.27978869315, 29690.543421102244, 15572.792444686735, 56575.72497203994, 21996.911445890644, 2006.9832187569082, 81017.3299308991, 20174.07293707666, 52782.64011435566, 49041.06388496276, 78264.93137068846, 58863.60140634968, 25215.99011403961, 82228.22638590446, 20334.984395121046, 95353.47594269144, 25939.033681199675, 21032.179651515547, 66746.44184473998, 8091.640742567596, 97682.6188729401, 39009.80575616106, 7356.239186194058, 78548.62974814115, 77264.37163725852, 54169.41595439111, 44220.01404166992, 59642.72925333978, 84660.15703107361, 78518.60382952445, 96561.48387834462, 41670.470422442144, 77144.54592091807, 55239.00804136949, 60421.32055128428, 69186.09239470177, 10054.608069242287, 87686.71989235021, 63725.513338389086, 13690.379982880619, 8337.96167582428, 9752.975602353343, 36429.04179151777, 86794.29287329593, 74357.95900697139, 11893.57008605144, 96121.9066091852, 47655.160175836885, 63787.67134588187, 40838.077629347434, 12636.116757434156, 20558.26423964251, 73284.87717520981, 46180.129407212975, 87480.06818624488, 23958.851989936848, 35979.00732445958, 90053.40563861764, 52201.550667176896, 98285.37079749265, 57185.77566558174, 98648.62775649548, 75806.53614031736, 69659.83426477312, 82340.12131723748, 46428.88750876552, 88451.50208037237, 86019.6633188885, 48565.52856343855, 14770.12615331942, 23787.17401895897, 63488.735774045235, 58399.557985359206, 96750.85534920973, 67693.92367936525, 97346.70851799105, 38857.155008036716, 51161.55814864576, 95605.70615991334, 68319.95968232083, 75864.671640058, 80571.14060666384, 32943.365728549914, 9495.110866325951, 74109.2164479482, 57281.24867898132, 47341.58440729303, 79665.23667588901, 51054.088505634485, 57581.49209415058, 69357.72949623606, 24134.579099957355, 36048.256930000374]} +{"id": 1750, "vector": [39788.96597198107, 97601.38580762701, 5978.499801060988, 84646.29096100606, 92014.8618748366, 48021.39335060861, 26878.41558193548, 92260.86958174543, 44783.73091355262, 39679.9238942264, 66785.6656399105, 70941.35070135006, 84938.02952608133, 93633.58106273573, 25297.156274411493, 23233.46995459088, 33132.76457154121, 11379.66056937325, 29827.00656045093, 32681.250279603348, 51421.73597025027, 81909.49816457235, 49212.80162303463, 45776.005484906076, 8914.255541543125, 75965.43949135496, 28876.70215817888, 86449.04064281177, 53457.50278669853, 65788.05374653722, 58705.91629941142, 54826.933628992556, 41540.0478338689, 58436.010256009926, 58382.7303786373, 12384.353582077134, 91764.39786100149, 4693.308153449416, 17568.807704670486, 78914.92806693142, 18068.02290006848, 98977.25054680133, 43814.19998244137, 29981.41310514432, 42797.36194583358, 77193.97002799781, 94954.26677810558, 65794.75090903514, 31936.737341642252, 56823.6910391099, 5019.522079563155, 71865.55821732579, 59360.61640006076, 65142.48389358037, 11427.269865277256, 85889.58043297719, 61291.64285500853, 90630.91008642227, 1443.9805077809065, 64879.0606659135, 50518.184181498684, 73867.73269927554, 98055.65417608031, 77509.97175449094, 29067.919896710613, 2099.8360173564156, 33225.577902668294, 74850.80524666405, 33266.93267098234, 10548.013440683435, 43688.52352311369, 89101.02395578263, 32796.602301407394, 19326.720351165182, 43648.86026396709, 79505.2373660963, 4053.8292039926982, 95619.04480668876, 10358.012648979076, 50533.64608167354, 66559.05450882863, 74590.92577739134, 99385.12946595829, 44773.93451420833, 26223.65434947079, 3176.3956612445463, 10160.624495999882, 28585.004419915793, 67045.23168937408, 23341.916541944895, 11263.042504411369, 23189.21575547649, 28452.521367833884, 26307.017503817766, 16585.138041255766, 70263.9975419727, 28895.172078751395, 42146.13117599263, 66489.28287388725, 63524.11549934919, 33520.25662596806, 87014.23242847508, 5830.7369539697065, 68301.84949830957, 99947.44679515183, 36731.48421560797, 8750.99967155425, 97892.34435639114, 29870.377100742317, 20590.239427686683, 53102.686084712936, 10550.308698876875, 53036.87492082466, 19280.114947240058, 98759.97229042609, 19846.292868110137, 19036.526599776782, 91667.58057731962, 70804.36910885193, 92347.73387619098, 86418.88548479919, 76795.25053052245, 23828.97357474394, 22852.11494808269, 7748.164354701714, 14070.460628029092, 44654.58246951437, 31784.989658140174]} +{"id": 619, "vector": [5879.361439803199, 69515.30312444999, 65479.293518807004, 33021.716836142026, 19697.599617569915, 48714.6299247158, 50713.18467957142, 74539.80940933852, 76918.83156043303, 56848.28979527886, 79658.9866084789, 93273.43462713096, 71230.20761090837, 56005.713489911155, 74292.27602503517, 84064.92255189162, 68562.51403172452, 21059.873007253336, 78454.12106945082, 22071.060459311208, 83711.19078663763, 95321.70211465303, 5685.363854261061, 16270.418788694786, 83333.69475708676, 52788.48664659307, 58204.552749961505, 21637.06444447534, 6028.844042160886, 64238.30540851839, 41116.91226091577, 34370.87573031362, 5440.144555676207, 93230.09455031237, 85969.95831866593, 36932.27580616335, 72304.27692541122, 32629.099941800356, 37506.492289072965, 64225.846752710226, 37614.34732194484, 64054.74662985865, 1199.1215876598792, 58941.78784015166, 73326.18317992572, 89934.00925649237, 20068.92820821279, 64405.40912284415, 36265.189225304195, 89126.67259156189, 82315.4284900733, 47715.711701813576, 71034.47807417433, 94560.48434382028, 44411.655457743924, 26828.377824407602, 50525.747762149345, 48732.65834079651, 6456.586003626497, 70683.1151800267, 56094.148300735746, 22997.522908584844, 59436.74037816743, 79844.96531674688, 93507.62993546456, 50014.47516948783, 35315.26944832892, 21656.817190632504, 20199.268976937867, 75076.98681649979, 87142.06540056407, 46960.861939843846, 48360.35763745048, 35892.51770875025, 81067.64563947654, 42330.22453289492, 87635.3416819672, 62921.00618197792, 69657.90759627047, 14964.99189099485, 97798.9895414217, 75572.23641812369, 47777.61913409145, 82922.62589389776, 87537.87265021681, 25966.326995702817, 88366.02256399492, 10653.75937668911, 10169.816018006017, 13054.33865122836, 83981.75516711995, 67983.96288848449, 54722.884813934834, 46574.15352767356, 41384.29328923301, 61807.88708893473, 79505.6105253132, 78678.22733918346, 89184.33264691726, 94158.5715429978, 93596.297641459, 26695.364808319588, 43486.53410580883, 59977.761183475406, 67846.7322710171, 63053.91698231575, 84858.82893125537, 80980.28557668519, 63586.88617283407, 88732.2892557652, 90353.37672120113, 75114.37552689409, 38519.94034610886, 47762.01337346514, 25500.900167410757, 25131.16236701948, 549.4701181472439, 83264.14107001502, 86664.47184403175, 74737.53094758303, 57435.57952130787, 76551.9110812074, 60478.60963426964, 80250.28103334167, 73924.68257853467, 27045.1625683509, 89889.06183034187, 55914.09098571116]} +{"id": 932, "vector": [40587.21159110447, 29108.81934826741, 70052.68415879426, 62762.38792337171, 29867.258856953493, 11296.84413229859, 86010.03297682722, 62933.54842938148, 49032.82320998403, 29680.25264893077, 28288.342881922414, 13205.261421375159, 2340.7277204151032, 55919.66827539693, 19753.031105678896, 41960.943579323815, 12723.021761429698, 8122.924400096876, 69820.94836797468, 85859.90183200393, 83909.2408964209, 90388.64385222206, 86774.81590084528, 26081.691828068764, 25390.616485381855, 77561.81041257716, 8294.09389023178, 65727.78301159889, 91225.95944273406, 65428.436354461504, 63401.72569697634, 16594.7632342483, 54771.84369485873, 16572.39681098629, 78031.71535761378, 78247.33560070823, 62906.87991524051, 26271.915779874587, 98216.68897753152, 11939.313503254634, 7976.25355311159, 71856.15660227698, 98787.22760300133, 21208.49259894638, 81830.85103392001, 37567.13274210764, 47725.30263313398, 72900.92167897343, 13918.399380718105, 63212.768589557076, 87023.37715214783, 90369.05188432746, 15775.64253938255, 57486.376686986194, 35140.74729028982, 69897.19963687693, 40694.60091787873, 28580.87192216169, 50989.26503520732, 16762.062341503326, 76988.99459112583, 62518.53651721186, 93766.84152555255, 16833.37173786943, 13074.780738207248, 19800.24428101671, 8097.003354122911, 89399.57019508875, 58024.08746613315, 94070.60503935446, 63166.66086986393, 21634.626905786736, 38119.579600284116, 13922.982100430925, 44319.588090205165, 21076.889161736377, 79735.93111265247, 8368.267703057996, 85215.38778884323, 95668.17117697987, 85620.84211353872, 67449.99227284569, 42178.51397640529, 65164.746298856204, 47717.329734539446, 46120.76637172187, 1458.728891779082, 19225.66025025305, 59514.7311785274, 92709.43364128232, 99670.90885756811, 70583.20787407328, 96781.94899578768, 7214.684615337086, 83243.91071531882, 31647.240143743828, 50872.36582855771, 55386.09886782997, 2944.907076115766, 35991.98690359855, 59623.55772614781, 70508.74555134741, 25585.38803149225, 13640.677167823524, 97916.28917392566, 86029.05001841624, 29350.412888447674, 13313.903411807027, 88256.2814164521, 10636.427707628016, 63260.47991467271, 53247.80755822169, 65127.343564955685, 37787.11363593964, 40252.04112693679, 19637.290558583776, 73707.77420002512, 38174.81069516895, 17213.236280100995, 70167.85158186694, 83613.19234523344, 11100.928174891389, 49639.272698065215, 35313.12737695777, 99462.19593978886, 43540.37412263008, 81785.394983104, 77910.55817245643]} +{"id": 177, "vector": [96852.3830220102, 84175.63313035849, 59818.2107978987, 63486.46633908982, 91188.89703665588, 85619.297424183, 53924.590269910754, 90455.63915563283, 25441.938263525375, 35581.33237998478, 74165.99605144403, 89319.27799103755, 28793.346529053775, 25282.89773013317, 56551.07436352824, 86012.62334657308, 82895.52021254241, 31179.37594840232, 7261.522442541546, 22386.078272321374, 20750.356085486434, 3698.2721170593422, 83387.29294481027, 21945.350132540643, 5545.800616398388, 72009.29089274888, 48908.064731493614, 19819.86536203568, 2252.5092291864503, 3376.324152864596, 32108.40174815216, 69757.10859936367, 84554.33584689666, 20018.083372219975, 72215.36911335567, 87181.05989871376, 69051.21208602689, 53515.01005198587, 10137.446358085956, 87809.02692367489, 51515.57009565197, 38891.685179098866, 66205.19377386819, 11216.99637509259, 26502.375557129755, 97748.60851496813, 21167.55150012667, 53376.10468665881, 58988.802526081454, 32060.022941303858, 60534.826126053464, 17270.108267558517, 31858.232868154413, 87914.9515127133, 3025.9603892255573, 68681.35164269607, 16233.070430581341, 18836.74146078915, 88723.39990445762, 10326.766826654055, 12173.096645430103, 85738.00673640102, 43020.55824163783, 43649.79504171912, 43531.45674757647, 5539.783699135181, 72354.6606982392, 51833.926893202755, 37979.85756992381, 86248.18447406356, 17568.390211580154, 88774.4699059953, 33427.13300773831, 77712.21752418383, 69568.04318397855, 92136.05762566601, 30293.724578284055, 1893.6454375068524, 6479.160880028534, 53710.479626675224, 59186.6317741363, 19105.79297544044, 20138.656439268256, 14362.221275280119, 32288.112367962585, 90104.71622480532, 58162.42517480066, 1507.5400227974046, 11481.992381819116, 64948.48812975629, 61139.0527980661, 88016.29407470816, 52694.63439827554, 27754.052252251084, 7913.157481630129, 96907.65240349318, 64737.805435784234, 34381.76528349706, 24303.80088987818, 95300.19058943106, 28945.341458196304, 85635.28837093631, 83012.04455880192, 50468.711729915674, 65208.11944562063, 93485.058142402, 79013.69451249971, 12138.993030613787, 70986.26431232248, 24105.146794782617, 5389.714053612782, 745.6679114860809, 80827.3326767572, 46920.93342435745, 75178.96869056209, 19780.958029626116, 97577.58449255323, 35943.10207260714, 86664.30768427654, 96526.60691587812, 60478.11554304162, 60157.94558090826, 23174.630997940014, 74135.54970542851, 27576.403263533146, 65166.488559041754, 58913.96853447977, 33898.37371710819]} +{"id": 268, "vector": [55915.40515260758, 85246.59732674983, 32568.997088793407, 90247.07460264786, 87829.1421117671, 39491.18717122041, 22277.524500327207, 36735.11699791847, 63374.11376017329, 36372.548899244735, 61547.111633244036, 56278.0586493246, 88050.56076471854, 95520.5039131101, 70315.43890735239, 57853.880775428246, 46343.78999775534, 91423.67186280045, 17194.27746483715, 27459.15301044185, 18309.85713670784, 49091.135396889775, 63040.83824478762, 430.11588193369033, 1718.2155179272017, 45351.866336973755, 14218.582825775882, 17414.32054793096, 65786.74756529722, 53.87461902479629, 69509.70450365929, 13696.647723587384, 7694.530889974727, 77914.41388204726, 86314.0385346281, 42663.994083690515, 69931.20706425184, 15513.624059275166, 32242.585975531678, 4068.8874662629382, 5041.175165430523, 32953.860310590564, 82103.59982906243, 49182.623077543096, 55194.07098326179, 89434.37455880226, 55489.57932804175, 54720.74416150974, 55670.84523538282, 58125.730155328085, 66499.77215805081, 27803.433413814928, 53391.88057396419, 94383.63861892348, 48732.73597615327, 45074.52751848134, 76225.66749873596, 3984.3812589442364, 55191.11205661582, 55312.158275644106, 58698.452040300275, 73112.80942059912, 65382.608438934716, 93586.56452581493, 41392.86525289535, 10641.149193441646, 35115.796195900264, 86462.90941556313, 72198.63358184906, 5777.2011400735955, 3032.0704785585795, 95459.85391605993, 92361.44270433988, 99557.35404341057, 37703.71687090219, 81449.32661540444, 11892.907286093623, 88629.50273740086, 32562.073515224467, 55619.77202136096, 82979.06000252278, 72265.10270369101, 28465.678156896956, 62334.345535911896, 69011.63120704495, 98186.1893135471, 99338.00929679273, 81522.49342652004, 64312.47466302319, 53480.68319465648, 11852.227685809536, 19189.35214412687, 22403.274246422723, 14598.260735987089, 82192.96984140573, 839.4676813085699, 76345.30542703906, 93642.31708006043, 70549.86163123201, 5957.927842259914, 5785.377487496723, 41182.58664877362, 76833.27489693214, 57678.17690177955, 41447.0170617836, 97087.25525489576, 38124.483829891324, 37857.988680430244, 47625.827961835355, 56017.76485071804, 15048.622253998856, 29933.28907726145, 18693.538770517403, 86765.06829749547, 88788.39866441688, 50984.98066767569, 42775.21598701347, 82357.96272114507, 88002.86675195045, 75017.60365227652, 45498.41245246616, 54761.14910148643, 45483.08127682666, 70336.05276438578, 22462.606542142803, 69646.344853017, 29091.570454680972, 23328.57626106333]} +{"id": 1873, "vector": [82897.78901731301, 57958.9350980753, 34156.824878412626, 54766.35524972118, 49671.996228257296, 15737.856725133992, 46355.054939931026, 45330.966976964635, 46648.96436427687, 45809.26333840349, 56044.05077276989, 90010.20539508451, 3206.512344080792, 3903.5771594879143, 54028.76135116551, 53851.8095546056, 95904.80018234973, 29492.82153032874, 13648.181751972443, 26063.915781118307, 41869.23500081864, 13906.568936633912, 88144.88120852194, 47724.67197600145, 11346.250394599854, 53610.05459094494, 99700.13687684147, 72932.92196788684, 14989.847221025788, 31026.282555572616, 91246.20405402752, 72369.95929539436, 81947.2291617573, 41163.02378387412, 29834.581180840523, 54388.45875259448, 63497.198316904316, 31645.701648158287, 92240.70219076076, 87792.74513730475, 16446.457978216487, 4626.614066631796, 44118.1351287185, 1445.3277152617684, 14258.783721044554, 38480.155214583225, 6652.96372983939, 71765.64780710063, 22320.9582335823, 4903.5717303057445, 73458.45071756467, 71489.61408789089, 9790.849558685033, 13703.514890476343, 60422.52110486276, 32087.91064734573, 96872.96462744556, 7307.138061807806, 54952.894408173226, 83776.2104303978, 72953.50918470728, 15462.385209641105, 9273.159402750398, 5193.7940240549315, 59269.15768813295, 50859.240468145996, 36585.78857706462, 73900.46555861046, 61621.83178844598, 15586.484266517864, 1459.3116755588942, 46892.03234627665, 38718.601860528455, 29538.700132707585, 75696.07122589547, 44543.56770425694, 77943.38464103136, 22607.164930594558, 31325.024995428364, 10062.550857384256, 69916.64205138254, 22931.10304179088, 3812.219497868674, 48064.07506731542, 77415.91755610259, 82505.429695883, 78566.48735395019, 22780.195066558328, 53489.46659672499, 94699.99476558802, 2744.5352322726712, 89567.6725359147, 49164.813651686134, 27562.686460438435, 58776.81944881009, 28531.177435397738, 20266.58196344715, 1732.1316015676769, 60665.20695308345, 93203.76819962544, 14238.815948982798, 49969.48199045718, 8630.305205272936, 30913.3137473227, 92542.8915747747, 32346.618457856446, 10671.536531213611, 35472.66071682029, 76201.9794723195, 18294.17981225888, 60569.391518714525, 16739.109702898335, 12677.770051878235, 90881.41334623328, 90932.995134081, 44619.41209103374, 79484.19441031292, 37362.51640835938, 22521.79926357908, 93494.85082110281, 96810.36577895143, 60100.04632273056, 84045.28851101876, 24075.505126725337, 57418.932171671186, 96869.91501319168, 71049.80805389104, 60246.35736888986]} +{"id": 1256, "vector": [29467.210876342888, 51168.840990520395, 96153.03370608069, 58626.8907765068, 30265.855109316388, 24407.598403532615, 96523.31723511456, 69486.18383674991, 97813.99091155655, 5554.95685642552, 86323.08790030704, 73150.96866474864, 92962.79517290358, 68924.27097050111, 54444.32998001466, 68928.47671664246, 71244.65792921613, 30176.969381576957, 99782.48691515902, 29240.462235430154, 38012.40071598548, 25973.509652017456, 51151.8631232785, 42961.73083570719, 3985.3527849519232, 34060.83372698902, 86032.30215755092, 55196.52015336416, 62273.047273509044, 16455.076287721793, 68427.29191742158, 41825.94560330145, 58792.41417059003, 48810.31497121002, 71860.54644387448, 32220.405995346922, 75422.6333049464, 82375.93857702763, 37607.4891444653, 49547.74163023071, 17772.159093671715, 9453.2989757399, 6719.298241499072, 33127.10205524192, 57744.75161716053, 85971.71534677682, 97896.82369984755, 709.3541682593795, 56517.393449604635, 91761.88238759676, 36408.70610353503, 99380.22045003343, 68405.76226288798, 21787.024713087343, 16270.070684809756, 38907.780659763994, 98514.09792781677, 61875.042859030815, 6547.942597809298, 94166.82533388687, 86224.22642149006, 82604.68993679679, 65244.073178182756, 44801.36432354114, 3915.554854323555, 38517.578425618274, 81643.30475168013, 18209.448546791475, 56045.81231832075, 69787.42924858724, 79708.33286464168, 6863.767592001646, 23431.987186651328, 5374.65774662852, 69416.46939042484, 20233.369987500206, 4628.446434479205, 3946.5851560300357, 4282.164019846402, 26542.627107544125, 77032.4191692877, 33827.815908722114, 80402.04888048914, 58588.7957468411, 97843.9634116694, 45159.06040893578, 2396.9765723903633, 82178.88955993399, 95365.63539410634, 62991.85622721286, 58938.391570995576, 97210.24881832919, 54009.45391415377, 3058.9302590664947, 95136.26924226448, 25793.609869098455, 40778.027255205016, 25850.279059556124, 62997.97715955143, 85169.91468333911, 7654.103485783925, 42338.66638336975, 92064.74698443, 57263.97345634775, 60322.69232952729, 54571.69310603723, 74590.72874325016, 83824.0549783568, 52893.40228340193, 9963.393167939006, 29336.301502413375, 43170.816417573275, 22393.482525561536, 57886.3790179397, 5524.180617600027, 62892.59226337054, 11331.766342000261, 77077.00570354829, 49099.213364240226, 72356.90980956968, 22619.734610137744, 37040.77754494993, 74698.13106034283, 2291.017627152847, 47793.527780593955, 48303.003129561985, 58431.613773549696, 94212.3463786768]} +{"id": 1343, "vector": [54202.74420412783, 761.4977696808634, 55908.82343131992, 34951.27946174297, 85343.40868093888, 84066.72649332036, 97355.56591282706, 19505.7068951638, 38939.89851915718, 55763.204493590645, 76515.32157541941, 29023.24843783306, 29260.27921715447, 29779.022341823867, 95333.33660594193, 85971.87166158282, 33243.46094893845, 75688.18900027033, 71988.48320631211, 95424.37658964317, 95306.50634522701, 88793.81920991137, 68819.43561406464, 95422.65148662146, 95376.83923209667, 24893.805241282716, 52972.42084782687, 58485.15938344643, 66776.04659377965, 31420.66787584561, 68639.79244813371, 40438.10115805914, 52598.999146569644, 44899.611443962276, 46542.100723396776, 22492.444331897223, 46467.781160164544, 18266.905768034892, 43740.02414695851, 48471.767906463305, 40852.3447953057, 5490.659029585221, 91389.9585936807, 28179.13092453129, 2047.3366453240938, 35656.475930281216, 28728.022681892828, 91525.33903727656, 78618.13195551524, 43971.12115094276, 41258.111503765, 36377.90305673068, 5574.7087367146505, 71436.54617984965, 52848.035652460014, 42894.21658303645, 12748.094997210314, 46004.69782437299, 86332.92363166019, 19706.874014205878, 5752.161576129355, 54683.88907769442, 28341.585631519607, 26243.871074752722, 10321.293665536357, 24459.56707002217, 2698.7464317625663, 29603.797643158814, 49233.65895134259, 18622.32580984322, 9491.046893465715, 17339.536548833144, 52404.48210970505, 19549.47420413283, 68014.13095872116, 84504.94808979672, 85294.04012086849, 99745.03418090346, 41185.51636208525, 81290.44274051115, 74586.58332693446, 92582.25758911957, 17429.866506997427, 44854.71527942372, 26420.365659887622, 73064.62548573698, 76230.72139195725, 60997.39929805516, 66125.48106379123, 47513.95812888167, 66078.19906798142, 82867.84194259818, 12291.593345823849, 30960.57663915044, 32644.313206136045, 79035.5171266656, 74396.34574631017, 23495.73831268751, 99229.48592555126, 10142.852868004571, 34345.16026928135, 98892.93435928297, 2394.503193615338, 46309.51105902871, 42762.470437928205, 86414.53207718162, 20802.236117104512, 66633.23881059508, 1900.9266437257534, 32690.275241913336, 18791.640705235714, 81923.7029721944, 56330.37031060905, 21763.686098924074, 52790.4074660708, 55241.07381830038, 19675.173295833592, 39036.23634164558, 64403.82693524066, 16058.214481791443, 47574.795051776084, 80071.97374591368, 56530.03098753021, 28208.68000478567, 43669.53736512983, 65532.2216254425, 43775.09855933015, 22177.48243347598]} +{"id": 765, "vector": [87616.65395612584, 69467.78017590713, 69022.5519235942, 90085.02740080305, 6948.846945949783, 17680.378468369352, 93103.1260195151, 94315.20953108817, 65639.8541446835, 18940.552312485637, 69265.32927972589, 47827.37157452952, 92070.02090037822, 90422.13283164967, 58043.4548926132, 14872.906951450015, 43024.97168998998, 54016.46599020014, 5078.304593052208, 81871.40032471006, 20355.251541284815, 75395.59904761544, 19144.9652586828, 64573.22920911833, 18885.085406932565, 20068.87607014035, 11979.435407904437, 27444.836246627703, 18498.878159056385, 99430.36397683069, 80133.98890028238, 88577.27478672269, 8690.664023417216, 25258.992486803712, 27202.73802895623, 5437.74103393736, 88045.48604954749, 63931.29513003968, 4611.046616643988, 43506.052274378315, 98307.91172381333, 46536.07310079866, 49658.686717393626, 35051.022610006075, 5667.916319264077, 35816.74981948426, 49077.861070909814, 64781.368594827545, 83220.63947318272, 50666.68684400833, 19612.889064628103, 78306.92992984591, 88856.83623159549, 5870.546111981934, 85242.256741, 85144.00792715167, 46225.25720276046, 34324.20534347742, 29219.644471920725, 75252.71732470003, 60965.46928771589, 82375.02275375945, 20776.3237499931, 59830.24289030504, 91400.60777917055, 64291.02719728224, 43034.84017023744, 10520.220028710237, 12772.97716888539, 16976.798003253578, 92943.60711732614, 31068.97784032895, 75655.36779339837, 2225.8324658339325, 98718.6904367075, 40621.993892362865, 97002.73708782445, 20477.8146803963, 19456.187958386574, 67936.361555079, 39336.088433989215, 49973.231617858684, 51228.98414990711, 81038.29078454037, 7356.210006454411, 43267.712647531545, 83323.44142043755, 68771.6118598946, 32438.4556559687, 66891.16158176353, 29133.034451067542, 43253.263679498654, 70594.44975284005, 7773.609489296462, 40977.1098019841, 7178.442810597175, 47248.1531869431, 41362.87491808229, 80574.29303469116, 7773.473283795218, 8423.151184038647, 9310.993973429415, 22663.250101324793, 13011.862118056106, 47432.36119906985, 47042.46054381056, 76205.7382157147, 45332.150048729694, 15043.586730444948, 82778.88353937745, 64833.776840615596, 99713.68297470738, 44595.91467569971, 28612.219090118175, 44512.886410500054, 56272.38302964782, 53110.805736620816, 40205.15347292486, 90718.38834217699, 52797.30622402728, 82718.6497996116, 24879.90062327148, 61669.477664319, 2471.3592667330086, 78777.77995679848, 89474.34050012604, 6634.035490964563, 69608.57922638037]} +{"id": 1349, "vector": [79608.02145265919, 23305.391161897314, 65104.09706693078, 24421.75961361345, 18997.878228351063, 42045.76879474573, 75148.66215213442, 27920.865475279566, 84597.67090424852, 24470.396226041703, 39252.87443882698, 63312.95410991704, 22219.94933177137, 36926.35447220005, 45675.18089713108, 70896.07462402894, 70734.79725979107, 33769.64327425965, 40589.89807577906, 19044.137997690046, 28296.586312680927, 80936.35186732696, 56294.67808172986, 22514.050310094026, 96697.3290160947, 10867.812112428155, 79820.30587759957, 59709.55569796491, 96424.44432867596, 85717.57747076632, 27622.446522894206, 89945.62865018578, 21101.457674626166, 21404.993708884558, 93868.41314433474, 80002.15061812288, 89003.63915144304, 41960.39205793597, 35297.625369124056, 45543.80471408231, 61093.88599739652, 34447.86639264262, 94560.73375341619, 61422.97820256083, 78880.03806126579, 78012.30428495206, 36445.22447158443, 64840.81761503074, 92020.46712570162, 4248.943242796355, 95102.48184261472, 13329.671211558269, 16331.114104607192, 11716.489854105861, 12476.479297269494, 12870.241604035926, 71114.80521552848, 60500.16327590364, 63566.58230956913, 17991.417515463847, 27059.039351085656, 62632.29579273571, 40249.67155538415, 78548.18362808181, 53994.14267762145, 49918.129207300524, 74592.80301857697, 27661.63228707795, 4132.402160563586, 43286.74968498052, 48627.89939516865, 65175.169940665124, 50581.58274570425, 70962.11915891735, 26406.89256051837, 57997.86633880021, 17245.223262404797, 29370.95627451878, 4765.643482997372, 78971.28538958738, 26598.715055563414, 65189.76901589066, 29705.16002222686, 28242.038234745138, 76029.18005559187, 19241.371668630734, 10754.902910833842, 58504.673004962046, 1880.66148616588, 60729.09352113865, 54510.76187584464, 37145.309474831534, 87057.94781511897, 78451.93618203314, 95138.50849160724, 89946.7592600665, 38595.58501288242, 889.5614195378099, 43373.623693441776, 39730.52048988328, 47477.78502352376, 12522.98108290888, 30384.90571158664, 8000.565570377494, 77529.45471059921, 67687.4276263664, 11161.426032297239, 30354.0138679931, 73085.96015952974, 2721.0471458956054, 38599.95812054513, 53151.224043068156, 8383.366118725855, 21317.98365745282, 25486.312047051986, 67896.96250711937, 61270.29243046891, 75441.35744631114, 52698.50435840118, 81695.25262589159, 97856.35636211162, 68954.71021667811, 63073.57681216399, 94705.77773379284, 94783.27381009166, 65367.70708192231, 89561.86844071696, 11641.56830997679]} +{"id": 1383, "vector": [44089.958205941526, 51634.38663217891, 88492.24921203262, 86522.95412684359, 78427.8703471099, 7556.7799957694915, 61954.45176526206, 58448.49895452575, 97681.65371671907, 77268.27441844424, 14910.454819513807, 62509.78645011634, 4375.7780637787455, 49460.61420233774, 85554.32649204388, 5681.135499829748, 66232.6877572326, 90821.87495966275, 61681.514609225494, 94180.83349015885, 94713.50345230216, 16737.281615971002, 5281.434297193965, 60787.9497290989, 8391.755551281954, 18670.999426123726, 76616.6668794927, 40445.71340688536, 14702.888200488007, 94330.25237055738, 99419.09517934926, 9722.443860693453, 46293.7495108454, 19820.577024158658, 85926.52818079959, 52123.92062316892, 17803.743996268608, 17487.58079080166, 22044.37692977882, 97065.92827131414, 14376.219115031896, 48996.38582115683, 14585.43106530753, 71363.68072577642, 14704.888903657331, 20852.57061917877, 96515.83597567223, 45955.238287088985, 92362.80046210744, 49350.63562455764, 50658.09816634859, 21284.61554101364, 39902.12497033458, 75652.94518925888, 65739.91092934097, 1874.2303234181911, 35743.29429694987, 45092.36196585743, 28322.34088464718, 24214.37525415886, 27440.294570591028, 17265.215629760445, 72108.06175791695, 37434.87045094123, 93461.56030048046, 3517.2799664102026, 59691.7830225445, 96200.79966161831, 1561.6705369619233, 44809.82613540528, 83786.4632689323, 45941.92654421291, 69580.97010951802, 48402.288063619715, 73889.70369951968, 55759.44958824797, 41816.221183191425, 9386.302996811346, 8947.669637784305, 71025.83426943464, 88137.73147667958, 99730.74723363452, 78857.1742583129, 13815.452629732683, 1934.3543334437218, 57698.3503740743, 18014.984248807454, 92813.53439893365, 82237.40982714348, 94063.33505980781, 70120.12140110666, 54242.35430368905, 92692.91803171294, 89367.4634622375, 78558.10886072909, 94926.03622950795, 73392.32221391823, 5061.507340724725, 49929.66440823993, 27373.820470024533, 4456.449596986856, 12466.989736307465, 92760.93572370848, 50418.361576618045, 758.0801046488949, 74731.54323496313, 47348.2587392642, 20915.731930063008, 46965.58404909431, 84793.01061072019, 78617.09873491703, 47483.651947010076, 37583.97871204505, 10454.337737524454, 75416.99108881954, 35463.642408639214, 75944.5736278895, 25127.179145167855, 92064.54591627452, 37787.45698481292, 27642.572056816938, 18421.58187841335, 78497.88904696926, 91555.1692827732, 23111.053821621543, 19922.004998834207, 12071.36174374367, 85723.11982639796]} +{"id": 487, "vector": [83330.4554100338, 98193.13244724454, 37086.38659216833, 12062.7620128121, 70227.3837048836, 74799.69267100084, 13591.568649508801, 56682.309426549684, 23780.82179012445, 80599.2384958067, 91709.17895775869, 8826.093346583762, 70866.42915602055, 51286.173906830154, 34529.67510407663, 24668.86274386867, 69492.18371310477, 21583.681218242822, 57275.366395441255, 3344.732205034462, 16655.652722850056, 87237.629565421, 53314.20011784012, 36857.387423622065, 64904.31542642846, 1220.92662586345, 42542.475034582116, 34238.89295764955, 50270.44351614743, 56414.188182551065, 78123.98960486022, 14746.37962410813, 14753.864108656422, 9667.032979159861, 3857.913676956648, 10325.171694069679, 40897.39602239689, 36687.767674848736, 30758.11923052958, 8044.418486175841, 12516.237815697228, 85533.77928920992, 24793.041975741915, 19565.45129508508, 22492.488341314387, 77849.16613622579, 19056.32172956876, 34309.47225174717, 15693.530246296672, 20999.334231234756, 67311.07621597014, 53004.241640004155, 73403.80078286513, 42098.08641209879, 5913.875834161353, 56154.114475511094, 9470.831179698203, 85591.19029471815, 3436.5227258520536, 4643.037825666385, 3076.641117585466, 13502.998935784128, 6513.20618905763, 65394.88627641146, 3938.1765987873905, 87568.22476947829, 81396.75391516439, 92096.61026921819, 92975.37427669755, 49972.0655442121, 47223.54287147515, 7394.790240858595, 40123.10930885626, 42531.03605317567, 93811.81137902982, 11156.28838087227, 71079.80933895905, 24172.576928570532, 71800.0877736513, 8386.433327516763, 82955.46595781685, 95967.72585432304, 43291.70299493813, 24862.817088199794, 91295.63205199264, 2792.841833764026, 16643.375640201673, 73138.22534878914, 43731.06611366769, 73749.97070985266, 19553.53066022324, 71035.3689015423, 81358.11962096789, 62771.30003622846, 58065.40851372627, 68415.2012470383, 61187.72859199398, 22061.037106240055, 45518.7920885607, 55474.80402385639, 42602.855236545955, 75582.7794750777, 68793.97062194723, 73811.9746515511, 19999.674081373752, 15524.042814593275, 17265.227382592664, 34827.38743241226, 97522.55827277785, 54669.51216489383, 94430.07519175051, 8859.633175488323, 60678.38492498724, 37325.85005488982, 87827.59171489438, 58642.1921391939, 59072.80141264275, 19727.078242288942, 57717.55967769248, 4524.96411636466, 67604.44202321015, 61790.656460112594, 82681.78244173239, 53517.01382718771, 5078.29483698653, 60277.32388255823, 64410.06952599679, 57045.0115301665]} +{"id": 381, "vector": [68272.89986779739, 15671.523575454583, 50539.534390252295, 9529.509969803674, 20233.02851951484, 8730.78036091446, 25676.384175652976, 88222.4177953724, 49022.14648810658, 38753.16792443595, 57531.39702680745, 22819.612135877465, 36774.30590262135, 81420.05440678814, 19341.547623154776, 94104.49966820072, 54834.49033286939, 43901.4522022275, 10309.790507107264, 82300.1488071864, 32899.07251211857, 97234.10217534222, 79030.48713647165, 62690.76256245064, 88889.97117837661, 97741.0186585949, 69457.87831601448, 22611.743507413463, 54881.99809754447, 93654.58250538354, 14345.018128277065, 713.0237024330688, 45647.48684999872, 88016.59291777769, 47361.947695743176, 93609.07416795925, 3649.7945323673566, 35987.32197141677, 4409.813584057986, 61955.16424230056, 56710.16836650753, 10893.920646560595, 65818.24022812428, 22068.53783103676, 50770.04187600007, 87817.97795903997, 72828.05462459258, 81910.3760988586, 17463.779774095667, 70766.66665920966, 23542.962360184905, 23987.957887418775, 74799.51107850537, 65817.80629481445, 50783.297459144575, 72384.11069484644, 61863.08271577679, 17188.878646235728, 92317.9838045785, 65808.19956250528, 95985.74327709257, 29352.65219951434, 47166.18742951789, 1564.9218885473658, 18655.473495134, 70582.4333368651, 4702.370216872986, 99811.36189064456, 62592.46807592149, 60736.365947902224, 10365.991453859491, 45364.219546469154, 18641.55417979011, 15121.343872362104, 67881.20721901598, 39288.06724955483, 58213.90373749251, 43538.043653714485, 1577.9695017533625, 40130.04022947443, 70358.54468588864, 77203.13425921282, 51899.05176829165, 5177.368102808999, 85781.37939306215, 80830.53694547013, 13299.42343573185, 45149.746945080704, 76504.9972154858, 56884.79530906645, 22893.68298982607, 20958.280868736114, 71610.68221609823, 48995.31667177917, 4399.891883232798, 91659.2983380649, 88425.7154031057, 10650.54791343667, 42135.249099966844, 75501.57115213516, 25671.474286934303, 52831.900615229824, 45026.46204259437, 50524.542051836375, 20619.423980551233, 4097.9974631501045, 24955.933082961434, 16548.822112060767, 59383.16357339926, 21908.81394128088, 60684.42988719589, 42161.441155904686, 45158.50299003392, 14620.795152947941, 79677.6255421765, 46734.47055117569, 55878.778251367636, 65815.22078454273, 12735.051584859713, 82828.39020149794, 2962.186844369241, 40570.419277280955, 71558.26130139564, 36442.65726758619, 89295.24532267285, 28382.69505519144, 61087.24273398663, 90947.35049772785]} +{"id": 821, "vector": [27637.73103024424, 8201.575858144904, 10240.372902579065, 95193.30484705833, 62096.52160453515, 68865.31814566899, 48378.43758984044, 46983.28835370633, 22446.046313126633, 7201.437319703863, 88215.70104160749, 95993.52484922747, 31344.497235098588, 80120.91692779415, 58845.59829295617, 47304.268977714404, 18155.148385470664, 17035.167583655562, 93713.40434356318, 66198.12430159735, 93032.33698865263, 48030.38168259163, 3458.6910141224057, 4001.016902599519, 13837.873871434636, 77894.60035983902, 28470.286631668307, 93898.78370294628, 7152.232015131422, 95951.7396849808, 21497.02309768592, 40256.30771941702, 93285.7924322393, 33534.404730351795, 3593.953406400574, 98254.04656731774, 55.77347327010429, 70462.358374526, 45098.70286917936, 68306.52719969956, 53109.2692347774, 92548.79746743361, 43435.33656261902, 21217.15125178464, 26021.02060485455, 19218.91718510328, 68071.03767921642, 50407.15436212668, 83113.33668738774, 4499.1340624761515, 9072.266986048417, 77356.04207697391, 94912.14620594477, 45631.48933076146, 34683.66959626907, 22193.163312267607, 76034.70860103106, 18087.594469028965, 34427.60473858783, 9838.701972021834, 40188.839408654734, 42.603495015836046, 1026.4518690074387, 47370.016172459895, 55891.59698300816, 7104.262961297137, 87925.04368742241, 80615.74226309122, 5970.403041173767, 18254.184762131197, 27150.026134530268, 97587.94070019951, 23764.677644663047, 82514.10762523118, 36680.70625140095, 1376.899156591871, 80427.69464897031, 75624.71788242005, 6380.738074042147, 26562.75392445564, 56729.24245910338, 23703.434736470474, 98480.6725896335, 11518.62251779755, 85704.67872945585, 29828.51016387337, 42520.24953574173, 40582.351709020644, 49623.07683049376, 90046.81209298548, 85848.87813223201, 53569.095585503776, 56116.73961486697, 72127.92578670912, 10915.465756839903, 93995.90458976121, 21627.655222399, 47140.55186051953, 55304.99428878239, 54413.47507398373, 93221.53658072407, 39626.96612897824, 36788.19713089351, 74259.19084336238, 64806.58071702265, 67211.15941021178, 71726.74481783126, 95804.546806305, 95565.94148666305, 13779.189697101712, 31366.660066595552, 58280.50635158255, 64733.468395751326, 28760.934792970773, 14938.196926433433, 76583.4855181276, 23647.736721167978, 95015.24463925714, 33722.869398113384, 90380.38255343866, 60575.429498265934, 47999.18931028704, 47597.69096389445, 65421.94833600582, 72.80185895669477, 30436.147636368725, 41299.44335486071, 39635.29095673027]} +{"id": 601, "vector": [66611.08977645947, 64522.929401921516, 2249.3563145882244, 89009.44780385353, 75048.14383934914, 38939.28046152448, 59146.536685461506, 93027.0447174121, 80095.4180896388, 7114.476775048528, 46847.99925669742, 68430.9428071315, 90989.23840147488, 36792.64834973676, 60805.9670161554, 1732.8215673997338, 48678.03988006667, 98300.52283298658, 69850.26457493758, 79020.9132986612, 29833.993028442364, 8379.17945478327, 54931.475290950504, 17128.22143308632, 36609.44681879269, 26356.763898851244, 78917.5847650767, 49645.000031602714, 35842.513768363606, 67963.67737111951, 6381.422455279228, 67701.8037907209, 17190.08759352625, 68084.5343012711, 6531.441990563191, 96408.12147532053, 37976.3833887955, 2238.607568064166, 86315.90678147684, 35285.29318974467, 75890.72701187969, 93002.23571632063, 26658.748317492307, 32239.41636506933, 83711.07088721659, 70273.92328783775, 81423.41848125377, 57922.35075172994, 16955.56712075965, 93367.86176707527, 40776.58218672878, 82553.81154101754, 69675.91178505437, 30417.753777495625, 3400.0418343981487, 39666.75797269833, 32425.474473798087, 49154.23594419167, 48457.4923824175, 16294.515655045361, 4470.167667378632, 6545.324427596422, 91793.81395819635, 59677.12006640875, 35534.9836255927, 69511.17150739463, 25960.688233322147, 44851.08604494653, 72793.73202044677, 50002.56271388321, 94540.80861609217, 7616.770009287555, 19712.585554407204, 7592.417137775476, 81287.67094241006, 40239.16231572123, 95710.54191489956, 85328.37178019754, 65642.11056017282, 47643.88774827673, 43815.64178146021, 5636.783267462198, 16987.418787641895, 31690.257424397903, 18457.297644546656, 24706.14162817445, 44586.38626057124, 11337.194946013806, 28275.661462686276, 77467.4667595492, 28931.327491227665, 71910.62622411663, 39989.842760624735, 58666.39260964739, 39784.64063601297, 66605.1813975627, 63184.99250567095, 98848.7595093352, 10068.958332347622, 74686.20049801578, 5484.710790420521, 73776.302067218, 98359.48023947616, 72370.18970429919, 11871.611710570462, 69675.0968760762, 70762.42613622859, 16815.854388127773, 1248.2788817661628, 86756.60676521303, 58038.90105174181, 10184.577975512742, 56790.97762946436, 19230.540968114852, 96212.02176401955, 55991.53302328672, 23658.267136318078, 23559.0627677034, 83019.48965099156, 54488.08841823953, 61666.03242837131, 27782.450791275605, 64450.33750979544, 47488.77927474277, 22465.647010418, 38615.310979194684, 85783.4338938985, 40561.44797581704]} +{"id": 748, "vector": [64087.26754017642, 37352.67255823788, 50131.82722083163, 52265.62277044916, 12571.548786155585, 67382.28745198305, 44216.57071516716, 40300.28667932856, 22278.67071285976, 4312.7860142977315, 36428.9483261945, 95915.66523624952, 64811.11754733471, 23535.544908231877, 42836.23617943499, 16053.714370969463, 64860.616049966644, 28966.2752574103, 14123.669145513119, 24525.695192784347, 66159.58004870926, 3080.0992616541125, 3880.5807369639565, 90806.83028380595, 65440.65395556994, 3984.709768939132, 51443.89874820956, 96231.37358235243, 72253.36263018986, 30751.890698153184, 64902.63267481638, 61784.33175975957, 14755.484203743374, 73270.20856840914, 39168.764526850406, 90887.25842014476, 36315.02304660713, 70668.92027697193, 85760.13943136239, 34307.354397328956, 68069.99191546226, 86569.07874725612, 76010.05786566161, 26471.28875261917, 96346.06210695264, 11890.710871931298, 13889.360015065278, 43048.50066570367, 89744.8242299064, 43084.37786484811, 5671.55459173897, 68315.14451278796, 49698.221463747475, 36567.16668590774, 7705.687981688714, 40758.88917723486, 21412.00511969481, 15165.517137225048, 71058.45672482533, 49256.66953309556, 62518.3515331703, 28859.69043091272, 21787.140584022512, 80548.58657493812, 79182.73816520946, 36713.71748632007, 60796.70253876468, 98116.5875514442, 93464.9649969821, 56976.522900802986, 26225.310892876852, 72913.6269594217, 7187.647355080384, 61896.9102665589, 74215.41136446952, 41064.60564584211, 57040.7396769016, 58251.87136653327, 44361.0635506963, 70498.03310740415, 79192.59539051887, 72812.64241220643, 38460.37380046746, 70148.0933816381, 91276.09810295438, 37712.90666873842, 41062.78097618519, 25128.74784685343, 18948.910485150984, 3410.44787907, 41285.77186925031, 16323.65943228179, 16266.381198353618, 19738.215349035214, 55499.73639351243, 69374.18327094124, 44674.49836141384, 43353.45528204204, 52195.87967317806, 5531.697465640884, 29567.860556908567, 39819.68342351403, 27512.75502657077, 98247.52617330114, 71804.32434153651, 60586.27074952173, 66013.46091087049, 12952.013556654263, 18540.72082278997, 58372.74243808579, 63568.223037106385, 13360.341612094351, 54261.986963136864, 70835.7784021514, 39721.382630404056, 52877.88877355869, 84151.16419858298, 15298.431362163057, 63657.276081323245, 36306.33337059711, 12117.010249023419, 85117.8214111748, 3543.06861721021, 54647.719347569466, 30435.65047892909, 91527.52309765325, 10108.182078275995, 28460.68855362652]} +{"id": 2018, "vector": [15174.13218521293, 15364.086176571023, 57963.949937563455, 19795.146236208515, 16375.372342059713, 37313.083909472574, 87185.51683875675, 70726.22723158597, 96880.98545516675, 71224.30915133098, 11649.677595386765, 48462.05419089069, 78899.38841196997, 92438.02780431147, 31730.941529153657, 44282.30067451624, 46838.035939719455, 11584.56232765801, 23804.420162070415, 91053.79223583995, 12679.4187868011, 37585.272292325746, 5958.033884364866, 6267.701670555514, 62480.51717056313, 94124.13224926483, 88457.69167021313, 18477.131288528435, 7919.507960295679, 13818.60206453669, 24781.21917311503, 10366.787534767342, 3431.811164791088, 36202.86954133882, 73640.62311788768, 70832.6696166591, 45180.85711742053, 68313.22317984983, 13070.798813404283, 53595.26730216238, 54092.72363900775, 60378.45331193532, 29024.59734185113, 73260.49498412988, 18616.58376830443, 32126.010973420747, 93277.15946794822, 91288.71563627578, 96715.15956417128, 51596.654743974555, 47287.733283633694, 98829.17503452241, 4478.650032642273, 31204.31269165982, 90550.08959901724, 4584.030104945625, 53968.60624630734, 75033.4553772776, 40634.871621760794, 93800.93481611887, 60634.817873005864, 70750.70491809443, 13977.711031341922, 1691.604927441459, 76254.66973825505, 48367.150159603865, 89083.16687066892, 34109.434039878906, 8084.788011569377, 40298.23834777494, 55774.56538192418, 57690.28942669138, 67342.95486219131, 29378.211973827318, 61747.61633920057, 78011.67084567007, 3983.1104912117053, 68821.68607804764, 19975.74602543396, 50367.00509556701, 67501.44860783184, 7327.998906431188, 28648.625212840394, 66369.71464647164, 89430.8157135057, 13857.071826439538, 31476.917663175573, 77439.0578069568, 66940.49925856352, 36630.4545549892, 76097.24724604409, 59124.74496175465, 57171.50851391969, 99048.70494786323, 82084.36405906049, 98350.38399419942, 92210.22273656879, 14483.658077567063, 98456.57440780743, 72961.91874204733, 90563.31502949767, 25917.244284353514, 95220.58426321988, 44999.69276403546, 44444.721267263376, 83837.86527680563, 50694.99295979739, 82958.2067576519, 73292.22308388555, 17897.68791609726, 56503.43933083771, 21567.378021143348, 49546.437683052565, 34866.37044731707, 38351.00917035977, 85989.60662822251, 31441.83647425908, 90096.58265676869, 25644.536884907244, 47022.52139348063, 50925.74654718175, 56874.769024786656, 72754.51554319063, 38141.26682669964, 70020.81106407364, 10334.89068136132, 61179.19284863946, 42168.84542140662]} +{"id": 1481, "vector": [4248.484317612156, 7036.261908838792, 45066.145058383154, 83812.95243022595, 66276.95542907515, 17272.14279980094, 49065.254144280065, 40941.71686122195, 20196.242536243324, 85576.55408480753, 50118.129103460706, 86378.885062174, 87269.40826172475, 24346.219576202733, 60884.57430079365, 37456.14207887347, 419.39349352929645, 75285.18925217298, 64306.38264418085, 434.78726871922777, 68423.1727808318, 71080.81184220285, 16758.792052489378, 35641.272226684254, 52986.69033773944, 72351.28642936696, 98947.04649704245, 49952.211709324234, 40108.99576781148, 17734.65439775006, 62157.54281132748, 3325.5630782847325, 7288.91186815297, 52817.27538166026, 41467.673454380216, 10424.66883730303, 37626.47913113326, 94025.29223462948, 37039.49449279185, 11721.244172660605, 70420.48746401332, 8200.374023439328, 1525.9758990936346, 38375.42480621786, 78736.4141705099, 69125.34500035674, 54954.5534508913, 55335.988782995126, 10820.962769404163, 87309.560310223, 25217.271690982292, 5377.908265983156, 33381.70835116253, 29017.602778837005, 38040.33371126297, 37302.300766946704, 39351.13472252775, 44918.2246329202, 95021.42787209479, 10346.715473950275, 84774.2396240082, 42848.20903029772, 17854.223073071684, 9503.680996319885, 22928.38929160942, 6258.054505412591, 55723.218940208884, 74533.35267222712, 62701.01682628148, 59413.83742561126, 14651.127404987474, 86864.07099977603, 15167.551343599162, 18970.82005343276, 66287.90009693558, 8207.554825191753, 40740.28741270007, 21020.300941495072, 9016.544454688157, 30927.903765180774, 3317.8624724747087, 15480.99102055781, 16185.721129533204, 14598.927229613933, 12321.030012466194, 72854.3448667599, 86979.56504890799, 38749.57302591885, 68358.13761733165, 13041.424360595289, 45302.11728227157, 23146.071273490244, 60633.118182197024, 8504.69066966213, 66544.80937822825, 29826.516513402014, 44046.69443782196, 59922.28451480874, 96931.40045580594, 74085.80755330138, 93651.68923551375, 13162.970081038893, 45659.84360304197, 51023.454226356946, 82735.03846168751, 53586.539485619585, 69051.59987876436, 9086.964628155869, 35159.07935649699, 81042.17817520347, 34360.247602351345, 96016.90074992456, 9318.488086121779, 57216.44622704293, 51185.34002825926, 73366.5619443446, 59743.52107080995, 72120.68943863049, 7613.418873595757, 36559.33090892991, 99044.63420079077, 10246.552825915134, 39583.31456641573, 40437.60076837294, 60797.025807738835, 1706.499957389107, 84700.5241766373, 7601.90815586912]} +{"id": 909, "vector": [61974.073116521264, 25340.791435623367, 8124.30658411779, 25066.24588609123, 9663.307864431414, 68784.1964359861, 57661.29308435336, 33836.76893365764, 62726.36640833177, 74612.02579007344, 70679.58711148213, 75161.561929219, 23298.804493173753, 9466.579231355521, 86121.33615696723, 67906.6457898877, 46042.30501522755, 49420.829823745524, 59587.93735944465, 94903.36785784067, 45560.23944375845, 48223.89568890187, 69691.38869173014, 29278.715421620294, 68505.66021057608, 71852.60076519837, 81862.11797064984, 15726.289138175576, 60483.81036765881, 11519.196759404682, 56995.192762075254, 26026.223107163547, 29207.518914558572, 66969.66647009354, 34116.051274388614, 97929.26128755751, 83187.60050376502, 20912.7821729017, 25164.830356762126, 67882.99178808014, 87468.8646678478, 64437.068392300076, 72992.40203752421, 31655.94993454499, 27008.386863205902, 66293.89433873756, 28747.587673137554, 95378.31359260828, 95463.73826998263, 63745.616475688614, 78680.57883464429, 39586.88891852148, 67443.87213331876, 31505.061230633513, 93538.39283649263, 4670.038604672477, 24033.07698096221, 186.80922493499173, 68041.95368984058, 69063.88271330702, 35712.70511946059, 77992.49897800839, 2242.298403862908, 45255.376575027905, 96613.11692694256, 57023.38069639759, 42386.19697233139, 43376.25892591718, 80669.71151251093, 46489.26807333086, 93861.88494772786, 36158.54779944133, 57519.578832478124, 90251.52270619855, 77467.43319867091, 86126.79493354206, 48025.506824316886, 70531.16355414533, 11893.640355559364, 6087.612252470442, 27858.22243980156, 75060.91799728645, 58579.05825673214, 68610.41510927308, 30352.265384975664, 69588.32849842838, 45371.22952666188, 35833.87129940763, 90563.66039592732, 430.3337382088546, 99263.10479451087, 86855.97282222877, 36070.37468730329, 72387.17438146888, 887.2961408724178, 83233.50639023092, 29959.357102663376, 21937.37655239314, 96197.16207643267, 17749.33160055563, 84063.99465951901, 20064.450492713848, 67446.9830054944, 48651.93594761078, 81025.18959365375, 34882.10530148005, 25456.849253621505, 52773.37170117442, 41493.9819353632, 39151.51027259639, 25303.179405596566, 29822.563682346583, 76245.99357644444, 76424.09917306843, 23324.326633980065, 15772.534570325248, 29523.626417522897, 66654.7333998762, 61255.79012477202, 36677.45918390868, 87969.95929050379, 82476.99818448062, 90929.95651993294, 39839.93619542766, 94485.02132475724, 45254.84229659636, 25816.390163218217, 92587.40844919671]} +{"id": 2008, "vector": [52067.10437314364, 927.3035386238471, 82022.55831390354, 96683.00224436591, 83821.56128784132, 73014.247701348, 58350.13093743765, 92835.35113082576, 71772.32659940048, 38478.84112211447, 94781.99071619811, 51821.08551159009, 44980.8074284775, 77657.6889379714, 66927.40983851561, 15169.000278243295, 30613.251718252588, 27145.608083736162, 46958.62520973808, 51462.561927939634, 97520.42574426308, 29771.937479790467, 55471.00204035992, 10891.23188884109, 23576.881733485643, 12484.890248329273, 87107.90912520848, 49922.31823882852, 14946.2798417478, 29390.149536189747, 67073.5382273128, 78639.54629671636, 52529.994805217764, 31883.90883684039, 49784.27428769604, 85316.9305293757, 25800.159262485, 88124.62011166116, 37566.82434389211, 89708.69017688988, 21991.21692612551, 18329.006704607607, 67351.19339404066, 31345.367808014656, 44804.861750222146, 54308.34405102763, 81751.90619818094, 12524.869818357365, 23894.38824591107, 72416.10083440573, 28218.959959563294, 84632.5213456529, 79145.221217602, 74510.26986915035, 58297.74302236714, 81984.6247948678, 80726.99852662356, 65124.552373135804, 33701.571083887815, 92532.86812713718, 93811.05702883792, 91733.96305658828, 53355.21093085679, 8051.250749723727, 79911.46054896782, 43381.59906549578, 10734.783563192495, 23602.661770691924, 87395.41608004646, 80401.89426063653, 855.0007633616241, 78346.131547472, 23453.85816000354, 59799.60270946581, 76780.91019520439, 96862.52166019456, 80868.24428117972, 32603.63775977545, 43116.702121393944, 19539.128756732993, 69754.27775235435, 54608.18248860918, 30922.117222574452, 57889.28496095389, 20570.661431354765, 52587.35328528596, 73605.97927594358, 15806.88624298634, 8160.770617918955, 41728.95559857887, 75772.76715995852, 57171.59097284445, 84189.57486460739, 85758.55450198629, 57383.23731620972, 57858.02688510828, 93953.84096054413, 67941.66360337586, 83722.13274786004, 29382.754193938643, 94486.80838248119, 23424.88012525795, 39607.45941387857, 64618.73802920083, 40689.73327279832, 16446.4828431945, 69067.22960850499, 49293.426923949126, 62038.844176762934, 13279.921925815008, 74087.73462655059, 66892.51213663415, 79862.13517300926, 35379.90342626909, 66496.24463036843, 69388.59847825712, 50835.32591854672, 83266.68404707895, 67898.26589084441, 86387.67276061264, 98178.37225271523, 42761.63706327245, 42926.03028799953, 16466.634906930398, 33363.68941191454, 90905.98017151318, 96052.47968259577, 20010.810928918356]} +{"id": 143, "vector": [41988.258336358296, 95207.68186064655, 64496.9685827097, 94645.07799983915, 19983.2701065746, 52578.321976129315, 54606.92564060119, 3217.553275642271, 28300.431108012737, 74540.54243981585, 57766.4995399922, 15711.2416871394, 47412.362469718646, 65825.13497766465, 65853.5931668807, 93611.67443312882, 32074.263002014268, 76308.24017948816, 57349.77885694116, 79002.92763923356, 77545.90802941262, 41669.206707287085, 17063.663835747102, 18344.08787492028, 12661.747508518594, 19683.61835525241, 22796.747471690505, 49791.42001360407, 63260.18397334978, 28699.04627595018, 82513.37724463518, 20917.751297515362, 5434.735737176055, 28615.793178921245, 17213.902711998373, 68548.35807408259, 35123.11179233768, 27129.87103932175, 23524.44595988569, 79530.33704673279, 18527.222160675927, 5294.904992356175, 17972.414433382, 40772.992519249085, 49298.56450779052, 69742.30929965073, 29090.4829768027, 51429.91428358017, 74355.93998888807, 82865.19689829876, 83381.03843493732, 63291.72825712842, 24682.67642283043, 60067.37778210849, 14160.191842003844, 99067.084951417, 90942.86989040766, 14921.616127517234, 84526.34261632431, 44086.41632155036, 27435.27767422137, 719.4012337427447, 16126.469633101226, 21147.370988452574, 41148.55507554501, 37109.95481636661, 46443.94747953898, 5084.315059473754, 76330.51711355134, 57841.77681470554, 17604.649527795325, 90917.78152038784, 61575.43131198329, 45373.3727008181, 47363.157441273506, 40962.69461397847, 82521.93835150759, 45379.88004748259, 16883.32105792495, 30975.76171097478, 91531.71238824392, 29973.507560023714, 39813.356400913624, 22122.974274236964, 83540.88373143197, 69386.74649987808, 57585.2658632276, 90568.73280729043, 6075.041580338947, 54445.48747716872, 85582.40358242202, 48032.19798659741, 75603.01328301146, 38154.03325969233, 47106.92497094752, 30698.020218908896, 54832.41668787884, 4392.433849347332, 49024.086723669854, 46648.96930388598, 45426.188068154006, 53049.14045056736, 27561.047117408543, 50228.280674794834, 42649.510914574494, 47795.520645159064, 19718.62664947217, 49625.57342420958, 40221.370350409336, 19157.262023258692, 95073.12369681298, 99708.28291459299, 37630.16642296579, 34411.45674904084, 11265.33944220165, 68675.18386143885, 74093.15829174257, 64719.09733172744, 61654.77819080033, 46096.27772670321, 77816.9320634861, 20841.535987838077, 56891.79662231216, 34235.57594084303, 76365.12312864709, 12496.1659832166, 44976.71259679768, 93218.1795081843]} +{"id": 693, "vector": [96961.35519449822, 38476.1649823312, 2134.431236119738, 87001.67252216615, 60245.12696611761, 2980.6393004732977, 33931.07174638723, 66000.11748706822, 18679.67409710687, 8229.683594399496, 76593.30752071232, 3879.4397601260357, 57965.34931162369, 92279.30066249517, 74465.58774867511, 70308.22210131539, 6688.875539958439, 89350.47153607763, 87650.69222133415, 30954.482849579967, 83803.01630037498, 58381.97360964665, 24317.054671188776, 1811.9788073508448, 81312.49392433942, 61527.888396701856, 22514.26793177248, 88871.95476143493, 49037.79860160622, 38018.684091742274, 24982.10325625787, 82262.899655425, 1384.9388088400526, 97330.93390445411, 16743.851408275023, 62766.008853076935, 70392.57179005501, 9160.71954601524, 53722.181395942906, 13784.987701682605, 96907.2087752339, 38284.06551439858, 88219.38386295707, 2043.272518794148, 18828.413265132105, 54351.0391914627, 25806.886598426172, 47572.14281316523, 85631.33869051588, 39860.69395139491, 18586.89221355715, 31122.62967026351, 12302.461366687123, 74143.90353625572, 36485.76807870631, 63210.16737602414, 37869.44294883642, 11377.965060634499, 20822.164561907863, 95037.11950053362, 60649.82339956735, 88011.02921954244, 19752.391387163825, 59836.33102716281, 18416.758243948596, 66149.34824649116, 9953.559067752294, 67250.5940178547, 19490.441697428174, 60895.63183831698, 1527.5793449520459, 36490.191855651836, 77170.67942703857, 2886.920690110206, 34553.27470648667, 16655.05807313593, 29853.42842866222, 20080.840876647053, 73305.86873101824, 24529.019716123545, 19798.652861234823, 11548.918265189279, 72534.08039307597, 31347.189502050733, 55058.54126093519, 76454.19170573789, 83192.70634924862, 33437.14887378101, 61422.834016478875, 56605.983815112246, 5876.367901472457, 70396.44929829845, 5488.257186386248, 61536.71728552716, 1808.1753162148218, 42092.214747223945, 55162.70815216016, 82744.3903158812, 95632.9125675055, 46492.12727505802, 54917.37166388206, 31715.409379370718, 96562.02090402016, 86765.5575787423, 26052.592348840964, 14181.57897544159, 46615.93969381283, 13619.719814071663, 44984.95764257205, 39851.704531302545, 19464.006056472106, 62918.10402517734, 55127.24734767209, 63376.14549221134, 22035.264112828834, 81273.76644207041, 44796.31265067384, 16258.459821901217, 23442.632352299242, 94284.59760207594, 97142.72204962315, 93923.55262888235, 9036.616993618829, 1527.010795288386, 97221.42054098005, 26146.756309931152, 5561.558467059313, 75183.15425421664]} +{"id": 1359, "vector": [71652.83689249781, 72700.54181154883, 3883.8348375070277, 37393.96381044392, 66987.87306605988, 14098.517696775614, 82571.73889564823, 10751.01441979519, 45659.26113230685, 40879.563074919635, 62924.12663249978, 63735.643979919834, 62698.41932632715, 28672.45543076, 70057.37143169649, 33938.475847422786, 65664.01710986112, 45059.506817339054, 35136.11838816909, 63028.77958755998, 13067.139740765977, 1538.7296426933926, 92866.50747250447, 91621.57988416398, 53808.77311991098, 29000.562482656976, 90227.01498388374, 73915.71856974986, 23121.088139930922, 89382.54539681604, 84446.5885207089, 68215.03908752416, 99182.55438115638, 96169.80667901426, 65950.18831421835, 28845.383613562393, 87259.18983503383, 95928.0913737595, 95400.94205024221, 5909.391507147155, 67329.86364654663, 79118.47111419715, 68300.68071299439, 56118.698787436704, 35763.5318118274, 78067.66909016085, 30842.250498474466, 7744.921406632432, 14706.705868763125, 97500.52431620595, 73959.54483070355, 41381.71655228946, 43748.83084463398, 92257.72068904276, 56775.58356443997, 5021.231268123516, 83285.41988100241, 19236.23006894366, 67485.2486149709, 3496.8390333472653, 19290.792601095265, 31048.577475617913, 66886.89607242246, 48419.63339544932, 89105.59203974521, 20929.95334185207, 68577.70544335373, 54620.81610900329, 48397.5843750999, 58094.34245496022, 54907.02629819229, 61381.88945431052, 30567.6852018243, 94751.52304973536, 18294.783859020514, 89580.55579760931, 63450.004459228374, 19407.099560227714, 814.5880585676224, 15801.856227311706, 50440.57516487179, 86107.0915302576, 88985.65986613087, 85545.90209978847, 38123.902762210826, 81570.22790686683, 5963.405320130777, 73636.49415194913, 96299.0701141951, 14868.615994888578, 14471.05331449452, 86865.43247593827, 93609.87540939935, 6510.919103369761, 99823.66141594676, 73688.54672254532, 95956.03979015732, 73191.45197368941, 41449.95601793659, 16137.747689624139, 75547.05850130835, 43882.18579220442, 36227.75563010214, 54358.29273925733, 43300.95748582643, 43391.1846997683, 52227.50617836438, 25169.544710595248, 61778.32706896449, 16417.874661871912, 28234.992925450886, 35336.48419811874, 55450.717101306735, 40735.87048144731, 61495.85144656053, 67064.44008092696, 35197.105778438556, 4874.607907759842, 67857.71435018606, 96021.95920439188, 49852.66009522154, 52310.878785960005, 81039.70469552821, 56762.977821225424, 63315.502145523074, 73450.85424682127, 20750.791227985177, 90246.35331518394]} +{"id": 685, "vector": [73955.21013373097, 68959.95001963324, 85002.0451611981, 64875.42424552557, 66539.06014838807, 35715.39019450949, 94248.3518773205, 43486.01659986282, 88931.6370275303, 6672.91981613286, 80864.41434229324, 88204.55410136863, 34035.128893331756, 70784.36745417601, 14485.371147477477, 72768.79238041824, 37096.105018969196, 9493.726497407763, 29131.412248736156, 60721.42876429012, 74028.74318741394, 19494.710257162416, 63721.52926101414, 9561.87467462769, 97797.67404172018, 78603.68135347997, 77326.51661272584, 70654.25808246098, 96971.60337262157, 88500.9613868021, 22179.028439598835, 25805.380073095985, 61819.76555723614, 38067.11135896816, 3608.8062855995863, 25706.969736415318, 71557.46296568836, 91655.8801916371, 71281.34008812298, 4020.082068712416, 12964.454817591486, 23850.467266072894, 70665.60508801458, 13243.205095009136, 73173.6304648412, 97695.19724031448, 43399.68652850009, 59149.27285474133, 62755.13798061658, 36125.671229306914, 54480.67507145752, 22935.173077121584, 16808.53045101789, 15346.716180581521, 96515.26560562765, 16539.570314560402, 58558.31730160331, 97766.80274843314, 65624.20217309962, 68038.81941950663, 22953.70813152885, 65834.00819287226, 41897.3631310389, 44556.89393275689, 59680.35620325418, 1719.005827941822, 50060.82423326875, 84999.94215036991, 78162.03514682098, 7339.927909715227, 50795.99670879928, 28960.28073154553, 45242.80641885775, 2501.380418524035, 68636.50652579758, 89004.72400897424, 86969.49588885819, 45975.36386728939, 37218.791341269556, 73963.00962579086, 90775.57874751005, 4243.958672624682, 92859.68460338224, 37260.4426303512, 30205.29190390526, 16287.801637855959, 87132.19919518832, 20482.656580816005, 19934.00715887693, 37224.72047751286, 15647.235323295827, 64214.53759570713, 83465.49433737218, 12301.008201398055, 52348.520710189936, 52538.925757576086, 45335.90573531494, 73489.17621895474, 82488.44426735028, 51471.75925686557, 58034.37252648913, 4292.226133318489, 40005.68861049073, 75176.79123093764, 27534.555540703066, 8288.04277827926, 59910.32643535858, 37938.6752789538, 95324.59638476456, 39888.78550928399, 54366.85872317313, 47946.24490599742, 77863.79682374212, 12264.945111682824, 86480.10206827163, 57438.04913506456, 86180.01216700271, 26034.92948476551, 39765.73939011726, 20071.50200244422, 11709.026618616736, 24125.729146400565, 85165.43494293664, 77840.23857546663, 91695.59024697146, 75204.231376787, 29023.41681857812, 25048.836256117647]} +{"id": 52, "vector": [62818.841691652684, 57620.406148073285, 4595.422017882233, 76220.89697698002, 45241.45573885088, 68863.69325575283, 85815.54333539741, 43867.286846933086, 52132.360145941624, 27227.097702336232, 76926.75338164721, 84130.5790542329, 24222.9266849732, 12469.676584480438, 2133.3475442108174, 98151.49632698722, 72721.70523479317, 27472.30012755869, 38897.1432234552, 73826.35787680604, 19284.197040689876, 50474.18214668989, 75850.46769888914, 56567.85900288234, 40993.01720959149, 82385.9732859447, 56093.12678008961, 46632.50719543799, 41753.40781547014, 40510.48304078267, 84975.97519030006, 85664.04561414603, 24334.47897593193, 95076.4260424577, 98229.37464020024, 56638.402715618264, 27637.86304969863, 24173.322503378113, 87911.29025953128, 84227.99356712753, 26564.437493180303, 94966.85724797366, 49246.78743317097, 95461.10460710779, 25301.156457403307, 86060.16929297117, 98108.06734314047, 47448.77557919262, 46040.77168357083, 37574.42122594481, 965.3311295478306, 38046.024680078524, 30519.918745404175, 1192.8836027972457, 29977.962256675804, 55498.78775930762, 15454.37735540146, 58152.16503104345, 21455.69299673885, 80126.14756291667, 88701.57305820502, 97755.69166611918, 10140.322839601367, 11795.816113712142, 16874.900236278423, 45442.28776615349, 18180.45363526769, 2862.003499443877, 59710.061697465986, 65174.15294617955, 23796.612838722587, 41347.85568555214, 92511.78553247738, 89623.6902801848, 82320.73368150248, 49930.39117699396, 80370.01113996454, 2483.920342354695, 26533.710048088953, 6131.020815976185, 56613.10477277706, 74434.94684422445, 2393.1441009535147, 27096.15530185279, 5367.704792876082, 29784.829919116706, 99639.88161216424, 60602.4039414917, 94462.15494532065, 3771.0234578311242, 53640.44019100166, 89909.63993691637, 99082.83297572518, 17983.254053235953, 69153.39482169517, 40510.66979847009, 95104.44354439824, 33958.61635734365, 12683.558575860388, 56496.549831452634, 18161.414101149887, 51603.73722117241, 52096.21504278934, 81473.72075967364, 12200.052395719275, 33587.22316974313, 20793.243471242164, 8157.149169465017, 20850.59060991985, 28886.08004977069, 29214.502951790855, 22319.0888655564, 84200.88208851099, 17968.4292571052, 90533.28413360499, 36926.42138659297, 33003.50721149853, 86748.71700588163, 46748.25603746472, 56685.32010201867, 8345.732370052328, 24069.018174133827, 4705.090333114747, 94620.02307060215, 65458.57837669945, 69389.77963405794, 6262.6306059545, 22241.79635447572]} +{"id": 491, "vector": [68587.84650061374, 97664.92738149279, 13933.620108901912, 13762.804531086536, 54189.085393774825, 53439.007394840046, 8836.635255495883, 25612.002946740653, 52852.52117354904, 1478.3400156730077, 27886.999790816702, 55895.3677378871, 54896.56125061237, 76136.43098921167, 3109.9770168318773, 42036.22826433681, 37750.374028438026, 16455.721156046155, 13129.5149197451, 8551.790245544733, 16285.617534240771, 46283.66699905007, 19601.47106044596, 18015.935514174762, 78084.8758383325, 41295.24400336522, 85113.67692495347, 65316.10539623538, 69935.59354123622, 21377.082999744413, 68892.23818940649, 25734.439509869244, 46128.02323323404, 26001.683646828744, 37989.7676004764, 94156.29906674124, 9180.732752181142, 29900.6847569128, 59589.33207896262, 30723.110122006135, 98949.30180359072, 95177.07040197197, 49209.209176746925, 43078.059652901866, 16043.244992884376, 8296.363755038672, 34053.98344614651, 70277.47950589063, 81529.30469588509, 61278.097280346214, 77454.95788463487, 16944.840874289657, 54.65211788115987, 81828.0430731211, 79517.8154626952, 76571.21313387806, 3116.584216401663, 63403.06608706181, 90975.5296303521, 30855.74056315582, 17818.88454651981, 5446.925627716392, 1096.137688952614, 85958.78347029151, 43299.59806023501, 48349.17392526471, 36709.21083610563, 97497.7667553615, 64807.89596981447, 41005.02261826522, 50524.92145114642, 67504.48817901414, 76534.65845051865, 44634.60090752556, 79077.27153426738, 29061.56046671077, 62815.323609181614, 82064.67820806749, 96869.43318338624, 54520.087451094856, 62988.021310666765, 15101.240374048108, 65075.3982464816, 6513.685222265475, 21231.44029523255, 47772.80529639895, 37438.635838109345, 92057.94249403417, 89925.0012919399, 21825.756469952885, 36477.60840976398, 84594.51402274285, 81712.39766340042, 59284.00432837027, 12279.342906494861, 9932.211871501562, 37095.01682417395, 19712.80485880004, 27585.92364849458, 81195.11993645987, 57311.41359601599, 91137.10496473237, 71328.79167863318, 69418.77417384824, 30477.625408856024, 75895.88343511775, 58271.13172917956, 12645.231457609685, 44773.165146546875, 2086.6322915996993, 19262.65556225456, 93689.30118315543, 74496.78715358983, 5879.450770654571, 68617.34854509735, 97472.22271503847, 8097.294244813879, 48348.914651624495, 40610.17071600797, 11612.696530070822, 83525.23083701158, 78752.70542893255, 79987.09367852063, 42577.263955142764, 76152.38568002841, 17095.067644017505, 41228.184619778054, 82744.62395311698]} +{"id": 260, "vector": [33028.983769277875, 89704.3923762624, 46670.81960220367, 5346.4737916200875, 81603.41596209054, 84019.27294914775, 57059.638193134895, 19342.830745980555, 35987.119546523696, 77030.47381619646, 7972.759336048829, 19074.36193083293, 64032.78459213331, 73602.1681887618, 56986.365211210934, 8899.247105189244, 27626.795128771253, 1096.6150706037015, 16328.892717679144, 8757.130039662763, 50385.042101720326, 83534.35801524743, 76348.84092223739, 91747.18859846407, 40117.93030639895, 3019.098372492657, 97008.83474800426, 30836.679959572666, 6574.591959916265, 32393.74781309996, 41604.50664659482, 79902.89801258486, 57090.637110999334, 34057.70138665939, 25891.478677783707, 52651.08562509904, 41632.82596888469, 74223.13341009688, 44016.95160136896, 63280.198935402484, 3026.2353341865887, 81615.66096248952, 17191.500816919404, 92756.44732734477, 25371.3029584404, 551.5694325516018, 79683.47669009557, 74816.81009574932, 61012.779965920505, 83163.83697777959, 12420.704034298224, 60079.03734399582, 27037.242954965634, 72508.43087125817, 93223.37516173006, 68453.27519083123, 14224.136282039035, 33314.38227029925, 49099.76136209777, 31689.418119019665, 87304.36329078364, 32496.061164999857, 75628.95936595231, 75545.40238540326, 46187.031171779745, 79608.39439353447, 15416.327948842689, 37670.987146611034, 70960.0966934595, 14644.661567257266, 56229.93005323587, 60089.3931810587, 63997.285460698935, 70011.29106869976, 18908.517277999883, 63597.326883674, 63268.94413374037, 64091.79466776978, 42697.06798096197, 45140.319065936674, 72789.26747828767, 41236.71745868485, 95710.5762692665, 74004.88062598732, 67701.68991864174, 11584.37055560534, 94817.05470558669, 7756.129579415605, 6862.045239969372, 54024.205560220114, 77941.03997318819, 63886.44289445418, 68714.51650030665, 44446.13297809911, 59747.67625524425, 36779.555101618666, 11868.1367632974, 93078.58264339676, 53299.1816378356, 22998.321444061232, 4676.07248902977, 16319.450953737014, 2357.149256201152, 99380.50005553228, 89663.15295815912, 97225.28315053179, 25867.397347472222, 61626.038095348835, 31703.1795443134, 98806.10181766251, 41578.568933190305, 1822.9059966760274, 54557.847165717, 31336.87813738636, 84646.37909499493, 8976.015364026412, 82588.41724620888, 3114.3825337293206, 67758.99837777307, 31013.02003266475, 24310.463034845354, 50137.892881355154, 19907.878935701974, 16464.25986465192, 60247.153432744904, 93743.78772284712, 59272.203799452385, 46229.74496708926]} +{"id": 759, "vector": [27168.481816837975, 96474.10779120761, 42797.831824866174, 35672.923781002384, 32515.57066893993, 87781.81515478189, 95102.81609344174, 5361.025269076591, 80931.7493658181, 46551.62084637636, 70428.52248287963, 85674.08292863917, 3008.5435596693187, 59298.200177056715, 44935.56029341036, 58244.93937876822, 22587.81902759809, 49565.1431268457, 74982.96398924898, 75479.15064512864, 57462.12524337002, 98506.58314189322, 31076.617237043447, 36429.25304430586, 66376.2121341783, 73060.06370785528, 81899.31610784645, 45457.086541506294, 74800.97000141963, 2085.77436671914, 44837.481051466755, 48995.72191501221, 55190.09045371288, 51986.6214967584, 46554.79447617147, 20787.431952330702, 97694.63448912963, 78155.90997031647, 66284.18145739057, 25700.001847327912, 46025.41183815233, 60928.52205835779, 75030.58489151248, 62180.84385017984, 43013.788208165824, 79508.03971782073, 34760.4085904415, 32516.501356071814, 97396.57604913544, 42362.900299691355, 97842.48552555658, 44296.4471931387, 50051.74189351196, 33249.732815589894, 35981.48975234274, 19960.40493172505, 13151.11073775589, 53390.27448320743, 7195.7328785620775, 75399.52320716932, 8264.28837893608, 92549.86868983878, 54569.22216644449, 25688.66433423981, 93772.51517138025, 75784.93018755138, 81863.19933516359, 11350.84252422034, 49192.480052534025, 23302.73184488697, 1446.8938035035683, 87862.75832046839, 47194.26839175897, 25989.927225484444, 65522.829297748656, 81894.56117100558, 57960.31503893929, 54749.771747482744, 96405.32136356819, 74755.27815941504, 89086.10454958897, 84532.10887413981, 85080.53493656722, 89944.91523134329, 49022.84085899194, 20109.552287434228, 71533.0985807251, 55444.57894920226, 76373.23012599058, 32452.342823040803, 40718.43353449447, 12830.558480062526, 12868.258072952944, 54027.36651990248, 35132.04917262268, 86490.12914058624, 62499.429442609755, 75666.82589791794, 97200.44459208766, 84853.85963021424, 97197.45001309099, 95576.10492316342, 67229.58009008176, 35570.04945130704, 8232.049745837212, 55144.97410755786, 54534.065100606356, 32782.4805051857, 80747.32519050849, 49726.59214794555, 2609.921367502388, 89919.41522103746, 96300.91009799702, 42514.57023566876, 8234.91900374581, 80261.25034614946, 93901.16752217525, 70664.60685567548, 9972.74099106713, 80123.9439563033, 79888.25957110948, 85949.1379023961, 3818.128363739881, 48165.4953377302, 52738.87374859815, 88417.79098160942, 11789.10605144018, 61054.69940919002]} +{"id": 1089, "vector": [86130.91648549108, 81024.37257026446, 77815.51790367422, 52478.29867094814, 86750.36345366803, 74368.20277517942, 45765.73159260313, 9354.430642901645, 69230.1923255963, 79771.29282778407, 22356.104124600362, 44143.36367501636, 91894.97455637525, 72054.49395447891, 63201.896196129695, 45504.42695870214, 67396.45231963876, 35231.773484948826, 31964.633785401962, 60073.08789829602, 79455.84049711312, 58030.34937164966, 90631.20483272306, 97252.66804981261, 55339.155726904275, 150.88779836760492, 69413.40413711405, 46224.789581121964, 91862.08927020484, 9156.166835594271, 93884.0767663799, 83912.36979852605, 85996.06583205175, 5433.748577417674, 22422.987675738324, 33400.87328930858, 49541.81012781428, 7245.870264697618, 88598.19604202574, 97263.11316354174, 86074.0473382008, 75466.56344025553, 59920.556642872434, 2347.196353912473, 12952.862814911792, 8187.197265052937, 35665.46622890102, 39822.94730613556, 25868.039842458846, 16423.906267002552, 50054.056144585455, 23143.883018227818, 50217.912955204854, 92212.84527476302, 68385.77695191724, 7259.779691844869, 46085.339997794195, 93678.50470241896, 43596.813767303756, 32852.946773135795, 81083.03697884225, 35771.85633656358, 33038.17244695211, 64365.404736859375, 72879.44586431238, 70308.04344300584, 18137.12004664525, 2961.6399474682753, 8358.56686098696, 1485.7140800868528, 88581.04715849202, 5815.502540311934, 34429.869482469774, 63668.51045473918, 73482.35485536668, 35545.62650099844, 58198.855231739166, 376.0552912491444, 79599.38558146938, 94311.94091901135, 5881.217093032276, 47454.077551285736, 85581.41754537186, 46657.02040168338, 91504.91828134326, 76162.52617998268, 96087.4405043844, 79970.91735208714, 7788.760819328144, 27514.038473835157, 74644.62235362946, 42596.330554580694, 94200.80925918813, 2571.7594050859384, 51621.99969874966, 64943.18626144467, 96693.95474373983, 94327.60039398666, 53197.70280672187, 42313.57536520281, 1996.206330568495, 95619.12130642221, 65176.768040773146, 38801.95901986287, 39797.99774397694, 2337.109021888795, 47794.549423300916, 16588.811297550954, 63589.12245796119, 63683.89502031949, 75687.90615311384, 83764.459118387, 41052.71813593022, 96641.76230208938, 76640.21138126317, 75582.97881762187, 86707.81869316669, 46963.85361521455, 93241.41221095237, 85219.06718493204, 82468.66834077444, 50467.83934259803, 59424.98907528355, 9507.015493918592, 5909.054568444938, 53939.971864856554, 8714.983237202667, 57899.776863514686]} +{"id": 1204, "vector": [83924.20806428374, 51268.70488143121, 54478.61000094318, 50442.03538277575, 98262.15502175268, 52748.49090923356, 67400.66046119742, 56668.09018496862, 50390.78203511128, 44000.169787634746, 57642.18183621245, 51563.09262848758, 94999.61591297275, 39488.69396630261, 47816.64888679237, 75292.2155283918, 90249.57323749925, 70800.57432595704, 32468.64015393046, 14307.10021711823, 36580.50897142636, 65571.09556391115, 47996.4919666889, 21066.334623365834, 94296.87679716102, 60308.81019492297, 6139.15684067462, 7967.297127870898, 76748.68481584842, 62722.34784452424, 15972.780716805557, 87407.56169155335, 89568.4788129304, 1827.1310518882756, 34975.28726661807, 34666.837098335265, 8904.197894665378, 79349.31527042491, 99320.532295227, 74889.6772028077, 24756.7418264367, 41193.00281424732, 90668.8132598149, 72184.86921652147, 73111.2878155883, 59227.971997345674, 25495.845412525097, 6176.601264458248, 59652.86454625767, 806.3206008688705, 43650.89370362241, 9090.414941513025, 78077.47090719081, 80644.85522360654, 59885.33818680729, 38460.392701807556, 52986.033331108105, 1071.4865777066818, 66373.6617842139, 71506.38298576773, 23646.325606255257, 20948.763788812263, 24369.31071301286, 39347.379319208034, 64367.99916474828, 53170.0154888798, 56065.45805423321, 13184.650376156014, 89825.28843442936, 91131.19045461959, 44594.443139460935, 3760.9291106153632, 29468.85791281888, 38873.03546417804, 89218.27423677809, 53645.61852635419, 73220.02960740043, 75870.73900229312, 80238.22919708242, 26297.16164674586, 93567.16234964784, 75281.24208732694, 47036.416920478405, 79747.10782387987, 24403.947269258562, 30380.527538944414, 26720.4536838874, 36649.515775447464, 94738.73070545022, 29912.78198098303, 90191.92655668236, 25638.34661786638, 39785.59181575591, 20582.783391897163, 46353.82336686775, 42509.71016672055, 20078.65895173223, 97836.77632457111, 70257.3732935414, 81841.43194860518, 68117.16828862687, 22780.510025343527, 28713.030199056677, 73521.71768871762, 65710.96082888448, 38624.16482324995, 89959.97573931974, 96836.52563319451, 13210.744808477692, 82494.61774209287, 99526.57866659219, 6606.085256193517, 65601.75233943663, 77760.76105243182, 82727.22861707455, 29896.252127334876, 94831.05596325181, 31631.191814029604, 19024.2944228188, 51167.23396184456, 68945.90117371478, 19611.292324548547, 27362.151923054047, 98705.72176988199, 10782.75962281594, 19186.37411099481, 55841.72861329502, 63059.57207762717]} +{"id": 2104, "vector": [16210.716700549488, 31317.293896644038, 4655.840483353246, 26958.673317425997, 6839.401866782069, 65902.50029820114, 44224.0373527148, 39265.92486977841, 89770.66515274871, 26045.75683293412, 64794.05852238027, 45490.47651264857, 48627.98900752024, 88639.1764395025, 50831.47436683733, 3104.026794872583, 82257.31142068564, 56244.588216036725, 35721.210840143525, 16615.78668761848, 33773.33990446979, 73379.06949921719, 52996.838751202726, 48496.50492871123, 49438.3595841073, 1744.8601425805398, 13578.274110698185, 72944.29268988736, 54283.846687948164, 92611.02414441132, 89608.37970506516, 43628.16801319577, 37384.74205950778, 34686.38485260261, 92455.10603993713, 75102.959517057, 64246.67008927983, 66621.50180928137, 57852.51719523867, 8938.988858202456, 6876.396844225729, 88227.85862027016, 30686.278104495934, 7683.725466466695, 22815.102735033797, 72046.92325026632, 99985.71132873553, 9141.044356311124, 74444.60523760058, 10473.433308774016, 55653.485600859734, 7131.179788701936, 9108.419322472517, 12862.910989404596, 63434.80194838578, 90793.76425892263, 72891.52520171546, 29710.587785497522, 38644.74467526591, 39556.27885313, 94681.25748239306, 39492.35650385643, 34545.866225651545, 41611.742775210194, 95727.07009982826, 45239.62693176684, 4729.310192614311, 62402.73309210731, 22296.0041806018, 2097.988163205511, 8819.317310799868, 14524.680513676612, 18846.846323868504, 44613.107955384236, 33048.94118714924, 12753.43973718065, 70351.55135487253, 80649.5765142151, 96156.43466717338, 5212.415458222686, 12231.057874058683, 7150.028520700446, 6745.646626711144, 39359.16556016717, 97120.79561509057, 92474.56460620403, 78043.8401956065, 59050.65491513655, 58614.836665759554, 8861.351773529357, 76632.90551920711, 59137.79327382124, 9110.617414661703, 90188.91620748938, 39304.88399118298, 77986.10073907246, 79663.93535469164, 36591.24139855224, 31034.906465173928, 54741.60658135193, 14509.581187928388, 68273.98860224913, 91637.99674616703, 25384.810003337356, 91286.21742108837, 76560.11934324556, 63989.217256970696, 11472.192459859509, 7900.472628582144, 73183.74056784481, 7439.670253030539, 56868.495395762766, 70380.97221193548, 56934.64155222174, 76107.29495615199, 19385.143693690454, 97381.80608858893, 61734.66608764487, 17651.31678872529, 15117.292185816055, 40029.771479284005, 3443.005870167015, 63067.282968000996, 43562.30955077166, 54585.04583462205, 49459.364754451184, 82326.65945528072, 66800.85716557538]} +{"id": 205, "vector": [91457.0076271612, 13287.31565262662, 85069.22871395313, 6860.236512748596, 48662.12369836648, 3123.632053252012, 56783.634961737014, 53273.614786512044, 40126.081176108564, 39019.27052868236, 56303.74178698496, 10166.916573774975, 44543.933658797796, 3119.5793479279146, 3820.6926345811466, 714.4908521484749, 78105.36261430384, 87489.40079955383, 57989.53684489454, 53876.53962529388, 30938.040742453122, 1266.750587930443, 85076.21302996062, 80169.39980678333, 41549.90563951554, 900.5981702684518, 20858.950069401206, 96252.01089934728, 32559.42733914681, 39988.10616467284, 38313.650804559686, 93324.12048684974, 74683.1168371323, 50877.478094817074, 35999.87370864885, 58156.54734706142, 18782.317345557432, 90105.77743025943, 52376.160958666995, 64413.160586239304, 30231.007457702475, 12819.953584433586, 19280.07569548932, 25996.065191985308, 20032.45319971305, 35733.003037298084, 86816.00557936112, 97662.6440717912, 25143.06799984154, 62859.629258399276, 14963.633959650557, 19206.646332891443, 81801.81046222983, 12230.890067334465, 76607.56742156055, 3724.7798450448568, 63533.75447803501, 84434.89954776427, 8631.371436137437, 85255.51650749329, 50425.74874350938, 37063.91923667619, 69818.86785053159, 47830.88995170394, 26188.746730652612, 44316.93290170878, 47820.070598595375, 1743.7688990374788, 37909.43568985518, 18001.860042212513, 30837.477955098802, 25922.327380783307, 62053.73989434421, 74187.38179962864, 83584.76974937624, 97907.48394923036, 74868.41742379824, 42464.220653236414, 74206.46487738156, 58494.28663445693, 60850.15531878407, 48068.41804421596, 68380.31342337409, 81729.81570298474, 91250.0158985259, 49448.56276368453, 19946.09081612303, 16126.096463291638, 11910.95278194655, 37220.77774328466, 16762.63549005903, 76417.98930238797, 38448.388891704424, 6392.208816672129, 47676.16654118082, 90444.82718146271, 93376.03946897628, 27950.506220650605, 15344.20398574503, 46842.36911655891, 10384.80095817761, 53697.77230524877, 49070.46582449762, 34351.00739599537, 56132.17721580014, 47015.102383547244, 1246.7416932781528, 7811.773792372523, 3142.454499667413, 1205.8041152177013, 89224.4311379696, 62863.87431339149, 46517.40175282648, 2136.1084963901876, 65694.4655732397, 56925.48802605239, 48635.34526635292, 27673.9261588146, 72514.69938481688, 59627.6577371535, 14005.010051216294, 38942.24312223432, 45490.948258286124, 39917.84348991725, 60765.141507703054, 52878.534971046945, 13354.47859846125, 81073.6607106404]} +{"id": 1412, "vector": [88375.58991246394, 62023.61203286904, 26327.06493680852, 1000.2028111024797, 55008.96313488534, 52580.13244234587, 16444.853049682708, 96358.68311379495, 44438.589442326724, 93425.54883557241, 36685.39905830688, 23920.039832816674, 27822.98982101582, 25890.203011857826, 97954.36053622879, 78626.53132036325, 88974.4316155384, 35696.59488332665, 57695.447377103425, 17538.704086662772, 17538.99799844635, 17679.436527160407, 59469.60163983957, 46597.80497421643, 45238.51151740576, 52106.84096249768, 65320.8657685255, 6281.922509133109, 25094.954532810832, 34082.54706961621, 98574.77698156302, 8037.96048058979, 12330.663426915056, 31397.51348443168, 79867.64010382842, 42852.61809137514, 45390.721615498, 15544.60669013058, 63295.8567022633, 20696.682244534113, 50531.59291506984, 60418.486176139275, 71499.13782238222, 77827.96390707912, 91821.8524000807, 70291.21560782217, 89232.59198518173, 3500.407473874645, 60087.70742680873, 9936.611902845249, 11960.594879440167, 56893.839913446776, 71857.4545514942, 43353.28002935515, 88376.42816200724, 48778.75954419164, 81348.57894071228, 59068.328303076676, 63383.7617029257, 18854.621154324945, 87330.46515824797, 96475.81721683864, 75030.88976646855, 64368.16495713058, 21601.864831364048, 35512.70327928483, 1352.018157051005, 2526.879803616977, 44984.060759531974, 70110.04566102127, 53565.92000753482, 24363.89396183414, 51113.00987654323, 29399.51142342363, 44356.16081285176, 66524.61227648915, 72025.2228569783, 33655.66705566635, 44455.1584024069, 47864.98162463254, 29373.259496506922, 27235.681542510316, 75486.20672372976, 31913.750594870326, 64479.10735640244, 94597.31784247515, 41488.62215286826, 53272.00709610249, 59210.36943807495, 80093.9224188932, 25227.738609134598, 52135.40732596681, 66517.55363798694, 91309.85709266005, 43738.72117292993, 96325.51176729047, 51938.676867027556, 94446.54432966329, 32600.342637867365, 36127.38396012717, 81959.45535364232, 75047.73061901407, 99442.37579113038, 60969.081042588434, 39984.007434318955, 71581.49527852904, 78270.06008563869, 21996.989386187626, 76659.10890777374, 5158.312985068325, 89868.87545538893, 40867.06488438813, 10071.822760152705, 97823.08345815833, 80832.43154572198, 9633.087547922803, 8589.62212534682, 39700.93651295517, 69267.26786753332, 40472.298544563266, 5373.0960537671235, 65914.69525879185, 54299.50095315663, 93555.9183016272, 45253.10350869448, 9886.124604324597, 288.74946499373164, 54820.849567637444]} +{"id": 1861, "vector": [96462.85121862277, 82934.99112101615, 46231.844422424896, 43248.65271892804, 26513.058441589234, 20718.76470044217, 75322.11760173872, 16661.054504158266, 66005.75215345572, 87133.96974896654, 40782.67320590137, 656.9745916019442, 2176.424164987745, 3890.6105863167895, 86070.31681117485, 19930.196811410005, 21487.692154155713, 53483.41251811595, 84357.22415837522, 41157.49401236681, 38282.36000009423, 55226.65336993449, 85999.28169632555, 6936.42100410602, 2668.1375661033126, 81676.78015688469, 16808.352723565844, 77911.42941150487, 12539.401518084393, 93026.20325031798, 31053.817822508678, 40621.332619965746, 9241.194921956463, 88607.60277674772, 51744.831077443, 37660.89782998663, 58860.15387587799, 8710.652884495663, 74083.73282787998, 72326.91995227477, 38427.19451335313, 95113.23656374487, 17400.797552254953, 3787.2877002492646, 42696.9329380411, 66563.7950937134, 61716.469261281236, 45857.054673727995, 3636.9498639870267, 23242.37016740701, 61149.038627941496, 37073.323985294024, 6629.524977706525, 14073.735667042753, 90071.34750775358, 67408.96422481984, 54741.81449137058, 13030.284778564872, 73208.23687129427, 80465.35278108377, 13914.613940445008, 85559.80347126495, 6165.57150820426, 61557.97475040062, 87053.60619839521, 72435.85421525278, 60940.773630453405, 61125.66554042772, 9042.9271063306, 58100.45142767004, 81890.11266855322, 26026.246369172222, 87762.44293144095, 18168.305506221295, 40903.40234473316, 36098.052677604595, 6102.51148424148, 46977.50757635574, 13393.899664290288, 10876.428333653921, 70565.41298521447, 57884.91982488518, 17242.37776319688, 2913.6292168729037, 57590.362748006664, 9334.122403629852, 58032.13132162522, 16146.82541700908, 85370.98296626253, 73579.1093519884, 92714.11920708239, 68495.24350167507, 62245.54321301336, 58296.10843250999, 78712.6411230752, 4970.060742042703, 49696.73786270046, 42615.89384877086, 40976.400035695326, 71024.24258211207, 58910.90482477385, 30092.638222605907, 5712.45320163144, 8304.947840100507, 81523.56474807505, 42998.79569993924, 9696.906038327292, 82434.21425433301, 64296.84451232493, 81522.76639276302, 52709.716076906356, 35069.27243611231, 22402.47639884484, 50042.36474292897, 80243.41149968158, 48797.07540231046, 76366.63659686148, 90758.80261936563, 47630.499233235445, 46509.631397376004, 45286.34751489341, 28511.135146301247, 70219.7361577386, 23541.835322329207, 99318.2542220641, 97201.89201092307, 12833.447875434646, 95072.80477803138]} +{"id": 1092, "vector": [80344.09979273086, 93259.61843556154, 58600.609640113835, 48670.21322828555, 91416.8929043074, 89387.73239829256, 32551.78996590158, 84056.73151612199, 691.8905306646072, 91252.91125223052, 99624.08883574049, 24144.832740803635, 92799.2092139535, 7663.256581576272, 47762.87860172086, 91671.32281292933, 43183.85520495621, 95028.51463951208, 92812.64197381875, 30845.5026681808, 35003.32990719052, 25527.680917144236, 67497.0347829577, 75887.96420997345, 45874.40746999552, 73672.24784382823, 87587.88689474418, 97763.20115637488, 176.00278041329798, 87096.84493770463, 76879.51498757767, 42271.860569637895, 40327.535279318436, 97652.37438569879, 39993.595530324936, 4380.473834610299, 51008.94866954757, 87938.65977269782, 57346.65062781292, 47790.44051299162, 71069.50991209512, 51332.94342897128, 81622.9029556451, 46097.056169326956, 40140.80603370224, 90177.61367545319, 33655.998131411136, 77893.06332632274, 9899.514340852056, 28653.593287079948, 19115.412526646604, 60355.14986568653, 44014.063032671744, 18052.740204504556, 27963.750453254142, 48732.01409456964, 92837.85420074851, 2925.7926614126186, 81432.37408049444, 17233.561561764876, 27047.397424494935, 33250.66272754996, 6813.232022290438, 18348.472426377193, 10946.226154181582, 13514.629391092803, 97083.53563064293, 94201.6661693165, 65401.81960439937, 18829.13157829803, 31403.52850103264, 18063.224364404916, 98352.6732267831, 72279.44364523905, 55835.25680626216, 40865.402922656736, 77463.75759433088, 32801.39308134572, 81138.32277634492, 314.79306346798853, 48305.13663368563, 25983.328022361762, 88250.21109475786, 27791.194679621258, 15341.782415473548, 15602.785792360151, 66534.99420455376, 98094.52558108358, 24822.36136112267, 78549.80567729066, 491.3804191922555, 37704.3474327533, 49458.09932286032, 83304.29011380789, 39969.06612301207, 68106.1897210166, 35802.733225074946, 22427.714994376, 23775.722500017604, 45904.32247696778, 48411.745008117636, 33223.30137794218, 76097.47081766756, 84345.91131040732, 35373.25989313703, 98851.88661905423, 50413.00740795158, 7198.337853960057, 43284.37244422938, 47188.18679757467, 74095.18454646575, 13545.970833932364, 89896.67420707384, 19399.921193930804, 55795.61363146968, 40665.41972714036, 94683.96812981399, 34898.08589896449, 62927.792112706746, 66946.87060464329, 55436.73568971006, 93698.83971556863, 98262.48459538398, 60535.433761302396, 46486.27195830941, 42398.36171031336, 45790.37833468015, 84973.37729303706]} +{"id": 1399, "vector": [18624.893115398867, 45122.03528188705, 91696.13471838737, 35825.55404559799, 76340.23347572592, 96329.7247482079, 29394.985495425564, 58097.3725407332, 44062.65793160875, 70430.25280343425, 10789.420157556573, 66030.25088958036, 53801.8470401466, 82324.74809503958, 86972.80333840895, 59838.31395160718, 47237.086064617004, 8194.687648649468, 18592.318767787576, 10155.05406012659, 77702.66698878464, 69617.77278648155, 93372.93494563401, 29305.770110207617, 61333.34403819849, 26498.665689506084, 68909.09288650233, 19990.396934961187, 16254.023308898946, 99593.2948674237, 97790.08572219196, 9407.833256499209, 16817.702819097834, 33377.691524671005, 65761.10913827884, 52805.53240925577, 73478.90575596446, 52592.21534877439, 86626.81123721543, 29201.589105938197, 9574.984855161505, 40259.079824557564, 46190.078942201784, 66367.21443754842, 34137.58180002916, 80593.34527480292, 21396.0138321037, 59722.16816194623, 91439.81934890636, 23131.30875907382, 71439.54263231295, 64437.26787953036, 10542.98323278543, 29259.25763823789, 49835.622382170266, 65509.36196052978, 2332.1036109979377, 28210.74719366242, 30588.016192798485, 69878.53569757214, 30646.232574457055, 64538.39983633006, 96799.50749704958, 54089.89615258739, 21894.391756947905, 46872.83033933404, 59780.96180600528, 45095.8574552868, 49621.660474030214, 83232.66371084671, 32847.98581687597, 59422.165093792275, 12596.904517408902, 68853.10938937125, 37091.92204902768, 66653.02873748961, 84930.10597544514, 36628.8786558592, 5405.841466764805, 28053.36199724735, 26801.96759194502, 3838.580385753576, 67302.10717823176, 32652.471436331143, 36315.461109940254, 66746.06050272373, 21473.537660569455, 21200.986185326965, 38492.745465876345, 87955.21371917019, 50974.91296305725, 14817.395857481031, 93925.27538815406, 59340.75372801052, 8491.473735830845, 16535.171917398216, 69971.88209352008, 11147.52019149734, 27146.320105788524, 36418.34723228447, 56752.12241749921, 62643.57369410286, 92453.59293589168, 52313.79449291493, 66863.21960854049, 62087.224359925465, 38657.63582504381, 72593.2992032808, 70289.08832258914, 76788.7988004539, 55983.445480470975, 97276.95466519309, 1615.1854529941945, 39535.27836202925, 34479.032494189945, 17028.255947326743, 16532.70225534651, 3030.2803979454397, 45867.851857329486, 61929.316169481164, 82178.57634917648, 46664.797161192306, 25107.819056777724, 9795.848417354946, 44580.3446664901, 71522.83732006191, 88842.87743610931, 1454.7142444682358]} +{"id": 39, "vector": [44678.01103247433, 60083.06412401678, 55416.62484102456, 72177.945041212, 73816.4989464496, 3088.8693882769157, 5604.078295937765, 43726.819510108464, 20697.786685307852, 46464.66399161869, 75428.68699520463, 8667.197208113786, 29354.588244487633, 10925.839412465155, 44911.63683728635, 81586.50603745457, 92601.53856989286, 57086.23443616394, 55025.041668753714, 66759.49407689962, 25366.646083492815, 82246.05702906735, 24843.27038846863, 33370.36960430351, 2719.714737208856, 38874.93794240614, 72956.13548714861, 84986.33819003584, 40685.685343986144, 48315.197247666954, 28470.187498429812, 61430.16056799664, 9095.927744967235, 7536.260183263099, 43372.89055058888, 83376.58570262762, 69713.4990099035, 64435.98902965138, 47437.59234284808, 52668.20289958779, 21852.51249876059, 51231.38785719685, 13639.70366513374, 6911.253876186296, 15693.616006039101, 71990.06445572965, 46468.76700784991, 53365.30459086227, 79963.64017860751, 40378.77008273644, 21351.48835558156, 35299.82921033767, 20895.868885525735, 36854.924685783, 64898.36533087957, 29237.479448868973, 70415.65618975078, 77197.35714335777, 58731.51724112516, 28515.16668241123, 67139.012952686, 11589.363362115424, 12511.550782682112, 51625.458749242, 76054.87476301695, 82719.78789380836, 35930.11760134177, 51286.18784811408, 84506.95569541112, 26596.994255690963, 45046.62102615108, 87886.91126183097, 58937.200128954966, 6156.740579859244, 68256.69867010147, 98955.3686278198, 64234.347614247265, 23444.12775483352, 21419.136541941076, 10835.632519805105, 76361.22662119228, 20519.657560997217, 416.5670027516777, 18259.708173250856, 17362.914087290083, 53073.928997826246, 79879.08203382995, 14960.48518877009, 83784.58485212174, 73442.59244017143, 34110.058363918164, 78977.70006102943, 86902.54926459324, 47391.433150352714, 34366.83979567702, 83738.21453051586, 51696.76847315785, 70370.44984814108, 13209.447604293955, 58605.69733445527, 12245.92235661014, 67240.0875669344, 30855.73374531524, 79306.91265071185, 70822.12686645571, 66431.05836729793, 33520.42105977111, 25255.14006065549, 47361.53715530553, 42306.15340195958, 40360.90163278282, 90626.8657446729, 30545.111816950164, 80344.17876452263, 85178.20099207468, 67735.84082599606, 93180.74471707127, 23692.961966842384, 23933.953413340416, 77517.22311418391, 18353.820860891134, 27917.930842059337, 14569.166555776414, 13700.148972092307, 13420.76054532998, 4704.623811576003, 20556.72395859047, 41308.83448953419]} +{"id": 1344, "vector": [77054.79282180766, 86455.13990455358, 12841.544131210858, 79241.21956095523, 45166.72087840735, 35339.28528281457, 93076.585133069, 9070.023845318754, 48371.24393467313, 5255.106662859, 73112.28203341491, 84585.38697804327, 30483.929369357487, 78396.52528352347, 61098.502693672526, 12003.208482179196, 93109.90313814596, 85643.97261457643, 70327.68091761156, 20891.764172483006, 96433.16618645652, 57894.83783537694, 47489.2840236259, 29427.297982633903, 34537.09405206863, 4786.580542454677, 4449.14405690302, 51027.97501276878, 90473.6146190062, 97103.9002227496, 71998.68027967155, 71201.68060366646, 38182.16694933422, 44537.69612603301, 93490.49179606262, 39955.96568821642, 61949.43330886605, 54565.46699783796, 30758.128021473753, 35653.208030266556, 2037.7192110464182, 44060.61674661783, 37023.800999957864, 12207.737287530528, 5823.80758243588, 30445.543389491235, 5544.748337655336, 4717.793654714675, 48698.80537309893, 89557.94448344847, 43828.974015611675, 31743.658624646, 49613.40431165944, 12675.860096821934, 97991.42480368196, 69493.1764477761, 99461.97616405143, 92865.48162426766, 81725.7576730374, 42101.29687068329, 62297.423975327714, 50590.7683820203, 11153.882857173669, 9882.16684862485, 93195.07201628883, 91917.24361265116, 4575.491241378249, 86581.89216637185, 84296.82178624776, 13685.14058634399, 81247.72228740108, 26292.34656821633, 1539.0647201939166, 31442.335900541406, 78081.89506297006, 3366.3206742794105, 93168.35683173078, 46588.60145528557, 91714.26825278708, 67696.56552387292, 92666.00606597115, 68199.06252923961, 23393.698344883273, 28798.9345348726, 81680.54994038145, 34747.4723330284, 58275.59788928644, 39488.461812759546, 52077.37057635773, 76082.03355088366, 54980.115337885625, 28948.954320364985, 47040.2353423119, 53732.08861994058, 22180.81785113749, 46338.00716954859, 42310.73207658462, 17406.819459090173, 20008.214792886425, 53204.35446701083, 38524.85127425328, 41574.244205688046, 7141.146226526606, 54032.355784698615, 98932.81692972408, 13446.859701257019, 67406.6289410507, 33793.280391866574, 40932.779344369315, 41360.23616948503, 6429.125553199777, 82149.64840022597, 59520.18909301462, 70235.87284544758, 23470.189542605014, 55482.43791103319, 53632.44418705169, 77834.20951830802, 51439.8592956038, 61369.074272685044, 72891.68869729646, 26012.92070476211, 80949.51088829323, 31921.755808480622, 55789.361339403134, 1595.6866510427049, 63515.734069999155, 56502.014365618634]} +{"id": 479, "vector": [96468.52297333449, 74044.48885507967, 64423.245965910355, 92553.34507327405, 62197.919266651545, 63470.41105238066, 1555.982408879697, 80623.65304744865, 85303.08586611747, 95007.24818850875, 58983.98872919008, 91003.57036922964, 61832.36652919941, 36131.264263026795, 78543.25729729285, 95385.79417306803, 66737.52149609545, 3623.7095500595838, 48980.09633376359, 22894.661990749344, 21470.324871108736, 53964.197658363955, 28487.947357988818, 18198.98740521607, 20941.85205724418, 3433.2932745718804, 5706.79259968404, 44023.25729890325, 46571.58005211438, 64498.719144080744, 28571.399765797767, 64972.002461459786, 72393.50042577366, 66140.40516072807, 70615.06960300286, 4054.8004917735693, 12424.091704950957, 49673.72139275298, 17448.137779250585, 63245.43019336235, 40144.260504438025, 59240.85430691817, 8982.273955698873, 19855.451051490913, 87163.27078244461, 82036.72593476057, 3502.888076911381, 57616.17744572981, 3415.287306207293, 29173.939344976097, 6508.958956087241, 31764.860824937026, 51238.743288404454, 23199.414251137663, 68629.32500696315, 47419.09071438619, 16287.398373643326, 37630.83553375467, 58908.75058407811, 39042.52487269499, 53642.283191746945, 23230.23822815523, 59109.07268062452, 14829.607344585927, 69886.17019835638, 58517.37737803582, 11148.991132889796, 26294.81627522461, 42868.42415266382, 6290.152453659925, 77075.83018490962, 49602.61447106725, 17610.827225452053, 18477.031055741732, 39423.33530237924, 16487.176673027894, 32337.61675850546, 81451.91830363877, 848.3477154349828, 96824.98689606976, 90292.75657265208, 31035.186582953043, 47348.446024304045, 27785.05811438845, 40690.63671990558, 4500.021031054713, 17140.61019107488, 86928.04361062917, 74780.59045265759, 86186.00522732823, 47821.10120462888, 30586.220269506026, 959.6397751516106, 59552.56900215658, 25470.71349214507, 93394.78468095695, 63326.14266972214, 51974.75349931782, 53087.23292824753, 62570.68028151619, 77732.17391674517, 72798.79760504123, 7263.587502595404, 4202.432920252363, 35490.29689223361, 75258.13330087367, 78352.81118240117, 29499.51339065562, 15505.421822946708, 96165.82232407994, 78753.72713033672, 71466.47566081557, 93562.7456802755, 70044.01481759081, 12728.567715501771, 4265.380741392555, 27135.23718947536, 76514.63266857123, 46509.07568814082, 28321.51276874395, 71340.59760407526, 27720.46428067092, 18669.21503582668, 34289.707389094445, 72367.24901162727, 45623.310918328185, 28679.632346882987, 5160.236370631399]} +{"id": 1356, "vector": [1984.844518124973, 50418.299409619496, 89377.25992762642, 8242.624212696037, 63881.606847857794, 67696.92181346494, 35629.51629300816, 48785.42399897491, 21473.00871287324, 7421.249191589274, 41789.62617875034, 8483.780185627942, 3200.4036828722437, 59321.572754250265, 63341.98530627129, 81002.53299499619, 66755.1878376003, 61824.76417682611, 58894.75145405844, 34080.74724305936, 82370.70131093604, 50291.206847822745, 17892.28195420256, 54976.402854962995, 13862.834514329825, 51520.089067239474, 51511.92155823699, 94225.86022559067, 42494.3581314804, 56017.67022631399, 43268.463274012145, 41481.72092169946, 41394.50878087233, 41303.75472511645, 64493.21411709421, 60953.71235428124, 56321.579726886965, 40189.020744951165, 12343.426186671424, 59394.5254966591, 77409.12820215539, 81199.8381509952, 13999.378048544042, 93428.41029054245, 96081.76877590762, 76861.59841825598, 14965.912526210568, 96138.2805844541, 1215.1803467916777, 5884.635590885512, 23370.321420804863, 96475.41182618591, 42097.06091395775, 43937.62020424241, 92385.30770276839, 39220.463495528755, 92338.87113867736, 57384.432809783895, 68155.57642647845, 32404.623674217703, 11213.699055302417, 11415.137324622083, 19493.100999368562, 44247.88388863276, 4418.513729552154, 98034.96920833793, 33991.678383704326, 96851.65296264025, 53699.11996474539, 74053.19187391606, 65424.37790701169, 99787.00801561726, 70261.96410612411, 39028.71028022281, 94073.55766740412, 40041.911712074165, 36224.79348927251, 55648.990696955225, 38950.35813961087, 17992.510599768986, 30990.627137168423, 39230.414548606685, 4151.21642943842, 38958.896032539946, 1430.7804644544954, 92556.16141018404, 21190.47621544118, 9920.441528003475, 20882.996149653187, 49208.359152828794, 64221.9202432358, 25953.753704769744, 89070.01516189579, 83523.58690463722, 44689.168284564716, 31394.730559135765, 64227.23829772049, 53977.58120444151, 10073.575205220543, 67854.01373515202, 60460.256660103005, 57657.195558987594, 21311.837054271364, 34646.460883771964, 96099.3340258864, 19159.954167791948, 18236.233266898682, 37052.20676819013, 24996.74338954142, 43497.855140437416, 13157.61689294318, 91630.53480566971, 17495.991489449647, 68934.53289675164, 72776.24293150945, 86471.66038524309, 17377.37456873374, 6479.4300135797785, 54672.8369435214, 57438.70543871633, 10094.893542376627, 87743.8300004883, 21758.30206445789, 54603.418743334274, 98048.99348897442, 99689.83478025829, 32116.675674798713, 4979.275816063478]} +{"id": 1956, "vector": [56115.617139732254, 28797.13846738464, 29547.459967372015, 94539.74941503406, 62733.39193926845, 91423.32587530588, 76801.06462544348, 44282.26351667313, 40469.69061891532, 65056.43616015699, 55626.88618673091, 40840.451672581745, 6487.564922338551, 13785.814669236363, 54565.01930533115, 28637.871225141687, 17361.344875014252, 27716.294508659612, 51935.18536797409, 23450.343043908528, 45936.25861168279, 83063.81258194517, 60352.93181159066, 40896.13532824549, 94016.40047150197, 40221.57129553284, 36274.512425737514, 70315.53984457918, 70014.9187739247, 66722.3157813671, 75956.95659243871, 35942.03804684629, 76071.55848340203, 71001.11725758697, 31841.691625346346, 3897.69408928754, 39710.71762398428, 95169.04009347831, 56101.08984443842, 27644.758582289698, 77681.55051968903, 50284.15301843834, 64295.61934975646, 74839.14671459282, 92364.12356092437, 3128.17223640498, 99155.3147381643, 98835.89755149573, 86284.84929862485, 54080.75054503735, 13646.492114939247, 20455.040186564533, 6904.879319022505, 34744.3995746047, 51958.80843020755, 8058.048019565833, 19778.83553094584, 3381.5829851169156, 68770.3492608715, 352.3264811967852, 85989.10376760628, 129.98596986799615, 82563.07351948005, 9396.862375461646, 73511.4997051614, 79296.55630400463, 2697.1466617775163, 86976.66786902882, 23407.658097051222, 12534.97797029074, 59063.10223950643, 52752.911230890124, 47076.161848345524, 69361.89579743613, 82280.50496314553, 90319.36258681564, 18996.66489167463, 82390.10847628357, 99488.44147760684, 95910.22573157812, 26332.734256694468, 10767.787098069004, 39780.33332463127, 10061.611613663268, 32379.027143531057, 69990.35485027004, 73653.61430282745, 33400.90016334243, 72790.05955430065, 78099.80725725055, 35578.76960011365, 86086.49072205993, 11489.356608337153, 36153.82805577949, 2232.899417985046, 66129.6734727279, 50470.60840489941, 94068.5998367767, 36639.29853651851, 30395.836414181565, 59391.446057147514, 35995.53878006886, 64396.1715451241, 5255.714074774942, 26727.1290476887, 21949.493250962103, 52235.91543731577, 92132.45223393636, 59191.111857281656, 38280.75845605621, 49035.54671597801, 56729.145765437446, 50387.56098950969, 70131.62208212815, 96041.82039747405, 26954.383636600054, 97853.41019759313, 59199.09394174987, 34337.042743633625, 39365.355220994556, 66600.14897050225, 42538.193036078665, 28736.830293120187, 73117.06060859202, 7003.892569173542, 28657.51538276745, 56435.61391217744, 1527.0266707223868]} +{"id": 594, "vector": [68128.57359335039, 27866.341202039734, 54807.65187531971, 59100.71732064403, 47166.38438277, 18862.057195819947, 7753.435412416421, 42380.66810018704, 90459.43908176084, 29116.161022786924, 8544.250893577344, 59443.24018018373, 92943.71875560589, 49613.86219868029, 21121.80271755585, 81734.37447153348, 98711.28818409679, 55107.24031031239, 43024.400751827874, 28406.37272027726, 16232.147762175064, 81765.19983782021, 50129.73964180636, 93391.97990865895, 5634.584449445912, 21747.562953930967, 68907.03740476788, 16890.946938678775, 85945.06514414545, 46087.936800647236, 25960.750551968693, 68388.08118416979, 96852.59195156119, 19479.02285294455, 54717.56941539654, 57373.87239613798, 24536.24622149917, 39836.041377023335, 88708.82011722583, 74437.60533873472, 2951.534444816795, 34012.86985338232, 84180.5132926153, 90440.97557032248, 9190.920146069591, 31592.34678271924, 14130.168509948937, 24510.23377087794, 36807.92898732399, 42211.69812307508, 55038.48735662188, 44632.495392765646, 73856.07972446432, 46160.40726370213, 61510.55421710561, 69976.51598791404, 74057.59500083931, 52172.83321061305, 87567.17132347007, 72057.31800208082, 14791.482841552994, 858.723684899032, 44214.64160544833, 69142.1442856255, 59795.6127259305, 70578.52280705268, 90916.47520518069, 84814.58586953746, 12456.120475575404, 99434.77919520512, 20242.100469954617, 6823.78778856072, 91399.59246564831, 5901.704928679952, 72551.20875964426, 3884.6226942609974, 4448.196137240146, 50039.39107517484, 9999.454502777306, 34556.11828990568, 59393.10393783358, 14827.077725306448, 27704.531777897868, 44419.08050931566, 14867.207897749835, 92050.24997464733, 96241.2399049975, 13505.264665269135, 8456.577637978346, 32769.291590412264, 52934.76013003271, 1656.718177553418, 23039.34639997197, 68002.0456660399, 10679.746577719818, 9720.326484944719, 16107.69378570478, 62424.98128119805, 61936.696621574425, 87963.12049457766, 96629.06180091933, 69832.64694522678, 86507.39585516858, 16242.230296670967, 83300.69400435167, 9694.225382812172, 50153.88768211253, 33359.88159896735, 74153.01177515878, 85837.07442649856, 81190.56500036169, 73489.98194548672, 34296.31319158211, 93257.30037785288, 30663.6805990673, 32512.126639556904, 38311.14206070505, 61122.95681176838, 16020.892448863044, 70174.49784546882, 83015.96231115566, 13427.943508097705, 58569.27598279128, 65599.40633487831, 82446.83891568016, 66985.37225920263, 46956.71295254376, 71696.17571978859]} +{"id": 1413, "vector": [2371.912561001488, 86072.690882943, 25605.393316850234, 56698.644320575586, 56129.37413938713, 66754.28957140722, 46391.57725550932, 92861.06543368928, 43913.9844935456, 29621.735626447888, 80064.14983572465, 34617.810157595144, 98466.7806076729, 37902.67375757619, 16414.770491007523, 54548.408160102015, 21397.967981603528, 38654.19854264699, 7480.6807520670145, 45593.36440079955, 65812.78655495423, 29927.289165820202, 81930.08280735459, 93192.4153170201, 89630.27913417129, 20032.35877932761, 49066.158573935056, 94790.32933997402, 55343.53513413057, 34893.59749641748, 74886.96245388668, 2062.430826561379, 32198.00626816649, 89559.4825490398, 80714.5573257814, 30519.05454833387, 78225.47694958016, 56782.60495280195, 6405.506600518118, 2702.6213851651382, 88791.00513649736, 76551.04533026111, 50466.17724649592, 34427.416192760174, 514.8683466604575, 30902.88562358663, 32453.225117832164, 96621.33208890159, 43840.69935859614, 32086.437890650755, 5161.755632953058, 14477.305715573064, 65876.50254110576, 98544.14128550657, 37537.42618347333, 3999.8640002124607, 10328.07241402972, 11588.220465227394, 54732.85051086784, 86387.69660888537, 4017.4176637183477, 92432.64552513057, 20509.55400533805, 98523.90627244025, 84238.22779360985, 44345.49501680142, 21383.86310847735, 5393.617869068601, 86049.82376025086, 85417.29982171171, 45883.916585245584, 44370.62295469412, 92306.42058673062, 50970.530021342194, 8596.853826338547, 4306.603174224688, 53353.20822496058, 29133.827630847896, 61002.57936148094, 82954.54103458021, 77847.21214963775, 14811.770401592783, 333.6723624588478, 58360.817306692195, 663.7648320914291, 91380.22513278996, 85729.5990285356, 53158.35141734728, 52954.977704560144, 57951.90281945089, 5991.890240829256, 82853.31192804236, 10384.562273010999, 23874.19681034606, 97417.93938844699, 94438.42184759538, 11737.48345757577, 53857.247262957695, 3264.9311575516203, 99631.24228470567, 19626.182386698663, 87589.82352803212, 92752.52557478688, 96426.15595995798, 41042.0899465365, 1656.786888220152, 84045.019852068, 35359.23619202287, 6324.262359364808, 95757.31621518357, 2030.7463535754966, 88296.81808253142, 46916.62813091515, 84807.61386404115, 23134.13360588825, 31836.82867970503, 29153.497859300005, 10162.780449511987, 48184.00334728974, 32314.49966262948, 88969.58492322596, 9781.379558581439, 70064.93672931193, 26929.727139925864, 54043.097326578834, 78089.83102390135, 28812.31484065091, 14476.802147489987]} +{"id": 2002, "vector": [42443.15956659702, 86422.56861560098, 42885.787583792575, 10180.804480207395, 81540.8504885496, 30445.823854981878, 42791.526510197786, 94803.86967362235, 93839.28070789234, 52807.934745321385, 86609.15441316139, 58730.5993873689, 3650.777786915216, 10534.718607219951, 67845.37229741368, 98353.76373430666, 45096.40470138028, 80242.51024895138, 32920.13663167535, 93602.00033765566, 65947.63795356988, 68547.27715798221, 34219.944758021, 37867.16119537962, 47893.49450915546, 82437.85265838557, 43018.25690652779, 69581.42423465801, 23187.19847442359, 11646.750029180053, 71197.35331812229, 43115.553521114656, 10859.031397615892, 81687.71691986492, 1955.940700064318, 65701.41800317231, 30098.107503482675, 3104.94689981492, 79525.66527916989, 11550.804891100219, 68403.82174630798, 95112.43384408404, 72250.46336559547, 77744.17893745407, 67306.63130812685, 67707.47250993397, 68377.43287578653, 24889.821957907123, 79271.12972782692, 43745.22651857181, 60289.73143917092, 23192.162366024837, 34833.22672137881, 37332.09363976572, 14808.32421643089, 41064.98098756265, 47802.31290797236, 73549.5406043555, 82785.66115401503, 77393.41273757319, 95071.0664699182, 4340.066601106729, 83126.86491064997, 3424.202347661798, 33118.34464423659, 28533.71659806475, 61971.99060231633, 67480.70123967524, 12227.878400305026, 29666.356191133546, 77367.90194701946, 35430.7548446844, 69062.41458121935, 78858.48267642347, 77309.92773709445, 30830.667902691388, 2006.430087229305, 25064.30999784409, 77388.48223347096, 90116.06099321904, 90430.6474898497, 7490.772617009056, 91968.48955176416, 99065.64220561722, 14745.606598013872, 21794.89004808509, 55683.67906956992, 29357.485688882156, 23081.51186859273, 92681.294805131, 10979.132563984616, 73939.38466751085, 75578.06268130305, 47599.797364970895, 64625.75789182948, 59520.05777365932, 48644.96321593137, 46942.42733727846, 48273.46524562967, 26362.060971460876, 26124.32852596034, 77418.77234150881, 69539.09392328243, 10255.704833547297, 3182.608203989712, 21030.65502178524, 98708.9819785001, 27140.946587662496, 61839.25838753021, 43294.50344639322, 85089.42277733929, 98822.86136678222, 56319.559933071985, 1338.5789684442261, 35613.64990060557, 5094.580475569454, 82529.39515076429, 39950.19114721871, 75605.67384333954, 47173.80444824888, 27103.169041659414, 47974.43159682932, 86717.0505928694, 5187.187876253963, 97704.6568693786, 7859.601336091359, 700.5423136704825, 65238.73757817559]} +{"id": 1215, "vector": [95822.84200285768, 1088.3646149490444, 59516.328975835306, 44392.07669161562, 97247.49241146393, 16885.519510990853, 32657.94220962439, 14803.92540672829, 53375.32211833166, 7347.247206875851, 16128.16073110147, 81077.71562409887, 59728.38251139626, 23485.40831694411, 56233.797675113376, 89654.68563628377, 28522.851951115448, 83545.94698602101, 66163.4678791194, 18788.26413521144, 36447.82558749604, 81953.40090853036, 43501.90743287955, 77711.46588982077, 4789.741798863256, 94775.86869268533, 17792.562535029967, 18558.151833767246, 64830.90898948951, 90175.06575273564, 59050.17372390677, 30619.471460964152, 48101.63681262597, 19615.627091507315, 22188.719999083707, 76604.76978988468, 47131.235600562904, 29146.67031474627, 98222.44098395728, 52931.31420793601, 94318.18123988099, 67812.49750649204, 24863.967144008857, 99474.6992901802, 7606.028965492673, 66651.17572858749, 68289.22228028288, 51384.55820595373, 3906.9161258533213, 45634.36689850581, 93693.36445455257, 503.28990265742226, 79062.17624478997, 74441.02412327133, 37367.11256907318, 75736.3751883128, 14327.482769925204, 75174.76755456218, 54994.87144825598, 43176.11109262488, 6108.853759273147, 24863.05009727384, 32137.27400013189, 74455.39399243434, 46432.79686273171, 2285.9290368482775, 21367.408574636225, 46936.23388788609, 63894.02314365554, 86087.11647910056, 57358.5774614255, 35202.409666959764, 3030.7448054305073, 88914.55889751855, 17988.188698012476, 27712.177655957228, 41373.11524880672, 69179.02772382573, 69999.08114939691, 35144.1358462348, 38010.91270098449, 39237.192559789415, 85619.5282405898, 21262.537469659503, 47360.22323751985, 35958.4958099974, 90005.22775942707, 55412.77707962964, 91833.21247736625, 77613.3813937277, 72854.46156091466, 38604.442003035365, 6418.223189024586, 77441.99535567652, 40393.236347843165, 78477.36274546759, 52289.70012305374, 72438.02204598261, 59648.479880172075, 705.094234520165, 69691.72444916633, 89527.19414224642, 20511.249921593568, 86692.32577868408, 29057.110965471387, 95949.65213968018, 7659.428334749485, 20026.777087466395, 13244.164943494341, 96730.45047207542, 58474.62968680601, 91404.05549113406, 27259.938620904777, 30226.911422037338, 26528.947069262987, 33226.96400150824, 84097.28087342475, 69350.79204960453, 85651.64887695342, 95840.65027135384, 22089.049729101353, 74128.31760339475, 86276.95897781814, 95608.07708444979, 78335.7209681823, 20406.87722820388, 57186.81712525461, 64592.73671378757]} +{"id": 1715, "vector": [23916.40072587178, 63932.68059787436, 50886.16379677482, 345.4266063843137, 72904.1252830037, 69922.66282857684, 50972.179460561085, 13920.574390406593, 56885.32293561135, 9091.471170316634, 29220.355438956103, 97470.86088204372, 73565.58391293797, 60974.55370272292, 49651.61816496305, 64487.240834696815, 47853.30580694196, 91665.95782909456, 45671.557528931706, 58260.825019652526, 80778.53748086953, 65158.05851490551, 9699.864558614647, 5143.522320355298, 62505.85884240264, 3468.968923863225, 41336.75163355087, 25820.991484984836, 72658.78511798843, 96337.0613474765, 96229.59077106787, 72995.74594231078, 5499.765554397706, 9538.483028554134, 37564.61467778333, 8763.353113932393, 90985.77508049025, 9817.110837171927, 26313.709170155686, 42954.13417053635, 42762.97854357126, 95532.67108114227, 85679.98776412003, 35188.52951062955, 76064.13817798707, 21792.646232729374, 66408.2917464229, 69762.19692332117, 986.518552309379, 78426.326809919, 32223.518888438874, 69675.05173050986, 19917.68567376779, 97685.04857986026, 949.2617729539021, 8318.74527666413, 97128.71263393812, 52960.97967154617, 29673.840620906165, 43222.901877095675, 82407.1641613855, 16477.84431247721, 42808.51136844596, 28829.12091937272, 2452.663479367867, 32773.62639466396, 82194.35792996567, 36396.04569425359, 28092.213745486082, 65616.06352219995, 34220.30343377943, 38758.05561310288, 38315.64465810531, 31270.478103624155, 76605.20487747701, 90024.68464052751, 45874.096626174956, 74504.41503349738, 57035.9950645495, 91458.48642255233, 72234.28414480668, 28562.359423262064, 57873.18489965449, 30316.247716225196, 42173.06715878276, 10475.15560408322, 21978.02175880138, 55899.99270667423, 85060.25854917178, 59692.631307002666, 78545.4321252885, 64319.05622858479, 79017.32811524316, 94082.02290166433, 51638.05755150318, 41048.7241050179, 84857.89561044575, 50777.09218011169, 61469.19570944729, 43018.44849290286, 35055.36505028182, 56410.63073391926, 57059.323858317875, 63671.17068333661, 77042.07608450478, 88948.26887652885, 13367.564415360477, 56281.7544753889, 56362.917640121545, 1901.4690878622841, 50972.30779964591, 33566.40378655516, 10488.012588652862, 38203.807975461954, 39672.457236620416, 43818.34319409767, 8283.019611709797, 78764.70152182636, 39472.19747520909, 10240.810365454956, 397.69638477205046, 60729.17568686837, 11238.029666550665, 95380.41292630571, 32561.6353668173, 64657.97298081432, 93085.73506957207, 35048.13389667148]} +{"id": 1346, "vector": [72819.33843765409, 73949.51864914403, 86008.75628433982, 67117.50038178805, 46995.8936139666, 60548.60904136184, 39310.2229041276, 39146.89313028564, 65696.69678896012, 22038.031902323284, 97874.44082005376, 16174.007332745743, 32366.45298955799, 32795.690154328586, 87201.03791925403, 38234.63815656172, 79230.1039269701, 86218.55092564547, 51223.813838148315, 42328.2614954686, 72793.9261783762, 97613.88812470053, 49820.48953307816, 15986.747970188098, 94880.68384873311, 769.4608615955945, 19869.90118270423, 74451.53616251984, 65918.73177493221, 64959.52722443107, 14895.577697166418, 75678.26966660666, 11361.719039593943, 50297.71408423546, 34871.15107978812, 94302.90081406465, 14554.048668125663, 21431.218429706143, 84695.67568097459, 40286.91573356587, 76987.054010074, 18.138635681874682, 75976.4368560954, 64794.90780146623, 1609.9510138782857, 84994.87101238698, 30403.579609157383, 44913.43609808025, 19797.909041324503, 44202.30362151984, 56368.22806252687, 49197.076940328465, 49345.8508049035, 38934.0605311675, 589.8500479883495, 63758.30016813275, 84275.21931511587, 95446.02924021608, 73659.43573255415, 72396.56269109134, 88768.47822993585, 6586.209326164693, 97174.58159828516, 7533.423615788181, 40923.81826650213, 27747.82019536699, 44944.386587939836, 92625.85401388173, 29543.74560641241, 59205.30382509437, 78380.35969067976, 54775.93547751601, 48600.39622120833, 46834.56661407499, 31970.907147875492, 26906.2889294471, 91620.47662233336, 44857.26909816524, 78302.90098708858, 25134.513807449486, 50889.11716132494, 23356.979674182134, 82404.51953917884, 49633.00522553391, 26912.932801505718, 14999.058273232124, 86246.90238413334, 80788.45450922025, 9664.300821398352, 84397.51986336349, 816.1723024285639, 23332.570005195263, 72908.70407859559, 7527.199044897426, 73109.69853880483, 4355.208282252776, 92420.42136115712, 25559.047053219274, 98996.21137543848, 46073.530300982515, 82818.70989684212, 41654.06997841431, 3960.842240773088, 27178.71838833833, 32935.67672854207, 11147.675888782238, 31916.679195914745, 66539.11247570827, 88669.92012018956, 33700.51903708544, 45083.744438468275, 29527.33952587415, 94194.61263656702, 70371.62563432408, 62452.41832227332, 38663.17834818057, 35720.865457631015, 36685.22932490711, 4120.9136650966375, 10536.441305954302, 72896.75972816092, 51919.144680207384, 76895.5168246797, 84128.74112855273, 7861.836206189232, 72478.40604872027, 40697.85310654277, 26241.46609907362]} +{"id": 1968, "vector": [34596.79611736997, 57403.159663551975, 18473.572823923812, 60763.90267031644, 31814.934028699183, 62803.302084715564, 62630.73887881285, 79922.47744761904, 92037.71305322628, 98330.30191632142, 38957.59897629602, 62093.67345683008, 59773.21376124196, 22667.627563733095, 92762.656372106, 2517.3348605676215, 74623.66314152058, 58625.458694928566, 20941.56560130115, 46918.69759986196, 23487.61781180437, 30133.697630307623, 36964.08700192895, 88283.00429491111, 48665.168394557004, 92559.49128588176, 14266.328716604137, 64345.195348014786, 13907.337089143379, 98912.23775544309, 32667.084725480556, 48034.520599311916, 49243.032631352624, 74497.506625118, 57188.78403929983, 88707.64828275914, 57896.94920510831, 96526.50388387933, 17732.672583293806, 9663.375662403407, 66374.17005331154, 49824.05202640228, 86967.9449330703, 3007.7613138790093, 53636.440200945704, 40747.10608654332, 19994.119579804283, 47611.70485844587, 40014.78213318268, 51206.24573397592, 84726.35765179986, 29318.207700524345, 83500.45235960656, 95390.0508028991, 27675.099700250707, 76995.98731860668, 39277.95248853189, 21817.452954952398, 14308.4953698406, 5684.470845963796, 99263.66996791176, 10046.45750836094, 72019.02662767607, 82080.06740610718, 22391.4294072992, 2487.277569098445, 20855.537532334, 76817.61889405016, 93008.7420103534, 11356.29924369529, 31929.330555761615, 54282.7325188992, 42910.67692347562, 16325.304849077971, 63159.82130668427, 78272.45200723442, 35965.4425740069, 89811.331314202, 8557.042532257941, 60054.051928832305, 62116.83754315363, 59172.08801948033, 87886.71027640224, 32321.76981818513, 32662.69821687485, 40069.51708142904, 76708.27611804927, 17361.599481148238, 40363.69050687396, 66496.8638666243, 89211.33247396906, 13495.801310204102, 74366.04360798871, 71331.32343144242, 85118.22035106755, 96051.39818324371, 34053.77111086686, 46293.28696109978, 43100.7746321068, 62365.677293379296, 81919.20090386386, 57919.036662458, 77109.28320834092, 99250.59969101788, 7503.139798863123, 23841.069454977725, 4020.1204794931054, 29698.17046717683, 24764.858624709374, 43319.8432274361, 47996.08710645435, 66842.23733051386, 96186.44095104434, 69803.15914278412, 29565.724801911685, 69164.77503074727, 90750.52845701281, 60518.95197102836, 51448.02705558274, 51318.05858390297, 67594.3581887676, 77581.71263933196, 67928.48340714749, 2798.6234072045813, 44522.483688345084, 6651.139099583869, 12259.253009090055, 13938.895226561155]} +{"id": 1247, "vector": [21358.021342528642, 86961.2565140737, 71685.21567731863, 83376.50857052067, 95782.6334839609, 76196.38448047236, 57450.44884529121, 56688.74198934136, 26559.43285001634, 65279.65806961801, 6108.274256788293, 52562.50596584092, 74213.99029627304, 21350.848987237703, 44583.28552868551, 15780.222433617751, 57433.44135040307, 45070.63274892412, 69887.20601036624, 14858.297759143736, 98224.09843340742, 89070.65358580588, 36013.367873716226, 2718.8119091695717, 42339.54609460788, 55784.53372343044, 65315.908767801026, 78564.17552117779, 25801.632620290347, 90178.30679652031, 72611.07735275409, 66174.08046473979, 54879.013423511235, 46894.80996150623, 17058.202167659896, 47185.95815895226, 20073.991819129467, 20686.994928114742, 50569.925349172016, 9055.643178540595, 81601.00446011891, 99139.94610260548, 33221.323691351434, 55610.40783562314, 85879.71058736932, 91556.95269110583, 26195.62744129421, 4711.263227966711, 24275.238333103556, 14318.216637007386, 96738.86499179235, 13882.824082119183, 53230.78742162475, 68170.33729540804, 84476.46493443086, 48213.655452743646, 97897.45347046496, 71517.1070959526, 14747.531752835708, 19933.92271301717, 38801.46851929519, 97908.08576979632, 44869.175906298, 33360.11487893311, 54000.00957859746, 65250.98936643471, 81936.25544344143, 70149.62674429358, 57893.42163394292, 21805.039980012574, 66169.259947082, 83139.27247213393, 3316.131632261188, 3665.9270193477587, 59464.71159232196, 17839.151434574607, 96051.95364499024, 68531.53869910089, 44349.90498015606, 12759.644927425117, 32322.68577953631, 68408.55746514433, 31308.946229562483, 46053.989315512525, 57578.19169685221, 27651.586612562118, 2874.4747392415393, 13873.891262120142, 68114.51194572114, 20088.65594724566, 47797.24678210225, 76922.87017754072, 23437.970111257844, 94763.17356329488, 60345.04761084582, 39614.85700353071, 49141.37876731018, 31284.433339757077, 30157.76796557921, 1615.5570345963488, 46264.12953322566, 33529.157677352094, 65238.26046670507, 46618.90892174582, 11283.57468404313, 24864.866354455993, 41986.89968507975, 73100.24397679591, 15382.2927305176, 81771.84905684, 59976.22241901776, 19732.025291303147, 75534.57009094825, 74495.85867003915, 54874.94594477191, 15040.878706138139, 84195.04858100625, 5234.7681755745225, 85215.22365908435, 39105.27336418865, 85178.93546361114, 96843.52842762641, 71160.05917767815, 43591.732070454214, 3635.6634653653573, 73772.62989369349, 96914.30244795758, 39692.627441794095]} +{"id": 35, "vector": [7280.058747177786, 59984.51448666249, 46675.423202411424, 41429.102734493186, 84102.80287095674, 54708.33917010276, 51979.590138445645, 87061.41095467117, 67569.77794143787, 27318.033557254705, 43314.60959356145, 49164.84625949884, 22341.517458351813, 59076.7559668188, 65067.49938929044, 82570.81685854576, 20588.811581086342, 50122.94957779443, 21670.38562364302, 29881.631828871436, 11043.597980717645, 56606.456049009124, 79296.9556284368, 69032.79806847151, 1240.2997388187841, 35442.74927264135, 28220.893456921258, 6400.402317776532, 42717.682846574775, 57656.65267996743, 9178.655217072184, 82673.20423152315, 35903.37178787476, 91513.19360771954, 4117.425849223222, 69048.35069728662, 73672.99857820987, 32918.6451637832, 41011.352382575475, 87076.68181663567, 76093.61226851397, 39787.646659187296, 26303.83423874737, 48272.56995015476, 34005.71207235687, 30296.442789819233, 46097.68971822476, 66820.48415459029, 1670.7103603365138, 21372.15641180563, 46577.70747516992, 72204.21056271544, 69759.07338338728, 25965.061774090624, 68472.02824087976, 12376.907309743512, 11521.371377411027, 58936.57114225935, 76045.17936947617, 92818.93548840129, 82617.10516408994, 84928.35443097986, 78892.75953705696, 20041.348601562426, 84497.70352597779, 65512.70354943447, 36935.78786806151, 71558.59145207792, 78156.45559859781, 35960.37937088629, 40372.84952958942, 93152.49670596202, 57089.87656902342, 23932.78106381446, 34077.537696699765, 31377.08742638612, 30810.785010757634, 66436.50484312116, 37131.635386078466, 48456.726388127725, 19442.959137281312, 4241.4431134803035, 11417.957623867103, 56947.19248319301, 87654.21185268534, 70695.93303159947, 8742.939782529236, 81545.90889607492, 53606.13080893843, 63021.47792540411, 71959.64398241254, 21691.883243743738, 87039.09644154325, 7713.096583642554, 94381.75775624113, 64285.97550908438, 68432.92590839691, 84321.3071866305, 81946.50980654506, 76240.533420309, 92789.57416152178, 5886.090124971277, 10484.44074511894, 59248.45059991931, 5105.003133510943, 53274.50839525049, 45180.3005770541, 43262.40611972038, 92996.64426013891, 23990.17375521455, 61684.17827098743, 31383.082810193675, 79068.6723691938, 60448.08331544933, 34869.363364974284, 15779.390661382886, 97522.93644888414, 9546.595056838036, 95825.67204855088, 91051.78328298389, 7136.104807595989, 82873.90359866062, 91685.99712167696, 23281.16695999284, 42636.907019721184, 23621.996463144635, 30834.512602477505, 14534.128154342829]} +{"id": 42, "vector": [85057.9632870594, 58124.37184028431, 96185.91497156667, 38548.60408135742, 62613.11882902445, 28233.608056897807, 52582.55981741734, 11493.357218059864, 57909.07852262719, 62391.764989546864, 93800.07488167315, 54748.788114753566, 59451.70958629619, 76837.59952946805, 68417.83715201425, 62698.740174970466, 48644.207089310374, 16379.683219707675, 41659.99955921405, 48773.99223068829, 16629.234596816954, 96489.9350222741, 44124.816750386875, 9550.080787153081, 34659.04693376548, 14389.959595800206, 35903.9872602397, 63640.002354547905, 91875.67859398972, 95488.43116244288, 52137.83684983585, 66412.96841408392, 4609.935317323666, 19481.622969582702, 76350.18669628089, 37933.49682890811, 87472.94556501479, 20124.508716248547, 2518.26131083972, 96920.01415304332, 32860.27668910177, 83632.6210974691, 29527.26692937859, 71304.22144277609, 3042.5758754136223, 4166.800556499772, 62167.339938551406, 40801.45794947921, 64622.10974192729, 27942.72734637551, 52696.46460182741, 24122.744244611604, 15653.617185747027, 64524.3373009018, 18416.97385072778, 82946.95620634867, 30853.237671012157, 90269.17151466674, 89843.59163051902, 80141.60644425964, 26658.78543108062, 30124.152739450015, 81720.20151033414, 28258.184177491476, 14492.705431068687, 20713.409227401615, 91598.21757114149, 28896.924289270708, 39245.959819930176, 24426.726951422774, 60932.92480639419, 3739.9076711840175, 45935.25467166707, 43551.39360076803, 44942.73042940597, 61182.59070689227, 17651.538720071036, 55299.77656361407, 51432.96791274384, 33849.69671394432, 95340.30603199152, 27568.357020433166, 60553.08017364122, 31866.537615971225, 88678.82984544915, 38734.72450981404, 5827.376299604725, 9289.418563849262, 74654.4072637229, 72451.79618889141, 20683.977969565247, 83773.88732028069, 64407.895981500886, 29323.72983325495, 32375.060865386407, 56778.85131296415, 42190.19643092502, 28767.946325532757, 91243.9540941081, 23142.050140392057, 85604.43077127376, 77445.74854742343, 54399.51262874095, 86463.75334353965, 61772.051345748005, 78129.4100191476, 87304.29783981477, 8897.884119014276, 83296.76447559599, 14867.458392672794, 71206.45567667928, 37027.481790197555, 91551.4967301337, 2681.948238513554, 56832.52114620226, 44001.012045427626, 86055.80973918238, 2074.3235442009177, 31209.456045974326, 43063.69143442905, 77758.99113596545, 59470.23813369777, 27894.99253224631, 79716.16601104409, 41551.54812776295, 75492.77702878878, 40870.36479552055, 80798.00904364491]} +{"id": 708, "vector": [8491.819868576611, 90521.98682757634, 44512.18809555628, 84148.81371653563, 74345.48164659095, 29728.305345067583, 57612.29579311617, 51835.76565626986, 83447.59186816722, 63375.46479379499, 51812.57059843375, 323.0476659188519, 17431.416810898038, 32090.008028771932, 733.3477697030411, 84945.76726879388, 20396.948379690028, 53463.72148151577, 4081.7965728578874, 4369.29690990181, 49358.178915462406, 24809.636146465076, 33151.986185389236, 58437.520274573806, 34445.359709492426, 39480.64341510217, 31137.675391754037, 1027.523546707576, 27755.400934303954, 47986.616458873934, 36397.4577223094, 91648.98952381365, 63589.388213670405, 18005.800447018995, 95496.0181695874, 7355.894095963422, 74155.84853137401, 35213.618605725096, 849.3559524425987, 12240.385599977177, 16880.54422197881, 73770.41082576453, 27627.902203082976, 8195.63615983736, 31728.825755161495, 62941.60691731515, 73589.63590032129, 92752.30209051685, 33311.93217501446, 80365.80516071676, 7038.608786087552, 58121.36765669099, 83609.29231056412, 72681.75648423078, 23383.295141774717, 77478.72891007928, 39852.60029626436, 63775.484458655104, 5205.662055762761, 25171.10492772344, 19686.617323362265, 36518.35069394988, 94096.75684895233, 10540.230653957584, 64314.61101907688, 23749.34952484181, 9653.173215476329, 56696.877508110934, 73383.56790055463, 54190.996950470784, 61909.51520217309, 37529.87727619492, 8658.545080059055, 5258.869957431045, 87673.28084398416, 46176.9957298632, 96872.9707803126, 44376.54938292319, 7747.205903961852, 89347.77759656747, 96178.50756029022, 74364.63689370565, 4291.978648376238, 45260.70524524587, 82106.94274737155, 42449.282448278005, 68676.739333986, 60515.322643670166, 21693.094331103126, 26150.314491298832, 55034.489316812316, 32114.90436135459, 21193.221609631164, 15130.211684353046, 81039.33558751721, 16247.722431898315, 67404.86160330413, 78292.00679346138, 2604.771087546798, 63202.791553703166, 18303.728989132473, 41999.416010234025, 70904.34952000363, 96816.33453536242, 64821.48320046013, 93660.81032289921, 51102.858389279885, 6276.166351349799, 96817.88439050771, 40746.75082269371, 5777.964809918468, 19873.719852015238, 77474.91253842652, 95904.02969521758, 20478.539321850298, 74072.6614422481, 6652.04224243432, 13791.517034582634, 33169.54041179714, 36346.882973942884, 82606.1997399486, 72799.86508360332, 31739.542139940957, 87445.75401537938, 8361.995793877386, 35425.162413968406, 91042.5628750034, 44717.52199158575]} +{"id": 570, "vector": [65119.7576538293, 28643.00896782691, 57428.155897733115, 35531.03840116865, 29361.17448919123, 26707.204259487906, 54234.948249454865, 97155.83141438509, 93985.4883566385, 4832.348773556771, 82172.07281854606, 75600.32283808867, 52402.731318921506, 47248.97613359761, 60861.593097095145, 65628.36262722984, 61418.20711427987, 22266.19531333498, 90227.25480250588, 58750.42270959749, 17969.785677047745, 77664.80961148153, 71807.26986415322, 92549.56513148808, 58530.06355845868, 96266.95129190844, 30182.255778078325, 81547.59294343754, 4830.7800002311715, 57197.01104070517, 95022.40286147392, 55602.50431407817, 44841.088366929915, 85422.33802731993, 44839.45580847519, 86198.73837322825, 59881.413223641524, 53893.82598681036, 35332.38826567705, 21006.99485273637, 98274.80524359643, 48506.37594819258, 35282.133033291626, 93103.03705520555, 73907.78556051708, 81435.81866353242, 17343.977045054537, 46187.2300336435, 61034.825749374446, 81343.68929956926, 10235.098214307392, 92418.28097940571, 12307.554812131639, 81541.1641585128, 24478.142364359745, 68873.86641551311, 86635.16799411568, 4967.259527540813, 11463.632026412097, 15335.375338649594, 3480.2016667130342, 16763.873408060714, 11086.087493417817, 68499.6243355528, 5452.745794270552, 10597.37761103986, 70728.81781588041, 77440.06024755287, 98191.54882138118, 22539.419070494314, 72908.74989959157, 61157.76195701291, 34437.51618276571, 1229.5698591654468, 51651.0663989376, 7342.009666858151, 50058.372901553, 78904.97861366648, 41528.0217232902, 19333.648452985253, 54317.98519315512, 79059.83672565929, 60272.77560652893, 66147.68360647824, 20196.654285821623, 58038.11170991632, 31153.843079448496, 28912.334468389443, 61524.21137981824, 84874.30262448777, 25985.1429579641, 66745.83891307526, 14632.097098528562, 30782.227941677054, 18621.086817324183, 68422.67597219386, 14146.208146895533, 1118.5863595771784, 70702.96077091205, 14414.806081176579, 4651.544725170053, 95161.19218078576, 48611.58516286318, 47660.132961131, 14220.583850567004, 76907.98939328686, 57268.30903175838, 98896.01781595459, 80957.97370297933, 88819.81681693338, 57390.941641980346, 11185.858004900683, 76096.06619446962, 77.76996854009788, 56246.548904730545, 6265.7773976509625, 84154.46419859171, 58614.53374850756, 55540.773314039696, 10137.56366077181, 70171.23358627348, 86769.64994743426, 55186.880366156154, 38533.304672314975, 12481.720248886752, 64693.95515050588, 56432.66189035209, 82087.78976980901]} +{"id": 618, "vector": [90692.8245375052, 22552.74163277249, 22413.717734651374, 94022.31015121494, 98167.41457565727, 55280.92609866866, 21580.00657291019, 11767.96272795071, 66961.07505088567, 87921.58388737105, 8163.352806990853, 41578.79915480455, 9205.394447456172, 22702.75175674702, 21914.227727306155, 70437.65373855295, 7253.264866239362, 12351.302644758722, 25535.140288285875, 99660.67146266198, 8964.766594654904, 18338.354420095515, 93108.11793634998, 46539.752419095996, 87726.5662082059, 10442.063890988806, 59765.741894597144, 69402.95017729679, 45744.64437988492, 54914.561568456564, 32893.501052203836, 81624.91083986286, 85551.42099038161, 8082.6251247600785, 60458.471293440874, 41724.91432668056, 67622.5798290844, 96065.06012222097, 37886.25774708096, 31475.45625143312, 10618.417615245367, 10220.729214631509, 36744.238001690974, 63607.28597392026, 60354.38333303068, 72309.577203229, 63408.68886821707, 49951.96649737993, 265.76313771181634, 9477.247816962741, 21109.41062939804, 74567.83051140537, 72029.17535104544, 99618.54103447407, 97272.79278566584, 96268.24758552539, 80434.14051197072, 4553.881864857356, 26477.375742464505, 5764.719510410144, 56462.494761209, 5759.839932690158, 38198.61996421556, 11850.813797280458, 24557.785951155554, 3514.303941500163, 88913.37318708099, 19116.212960113, 84351.84148449637, 42814.10257292213, 61199.21025530391, 56822.211019677816, 32666.743516503717, 48979.49501890286, 7017.879309276665, 45171.87081683847, 63691.68533776295, 1689.6234206926874, 87989.99973581261, 82965.59213306995, 13847.175188870386, 95096.26982931832, 22583.858252231657, 82426.62154020686, 58216.15916591168, 90988.23950747312, 21909.160184592136, 83708.0118414001, 77040.18295609158, 6533.108973318136, 29765.974056512277, 12437.462278247458, 94602.51248313487, 1469.9422866222278, 90774.29329176848, 55217.678402443074, 64301.872859866984, 10783.645711582201, 25258.05250753229, 18319.437881088008, 26278.357996628376, 47541.96832061441, 8348.9044764539, 60075.27081960534, 39698.57913539449, 82750.57828894728, 98446.46276997682, 64679.55662693972, 7549.08081425163, 9598.268505435304, 8631.869587248242, 81939.543136273, 65658.39646201636, 26331.25257995661, 83516.34176063932, 26627.470560322865, 38025.64779704859, 56638.840001352575, 59801.290442220765, 22543.46877725757, 70550.05510672226, 10722.81041010481, 67958.00231128208, 25450.679693699964, 18515.89352687235, 21543.267873684057, 94153.20429618876, 78142.58933595779]} +{"id": 849, "vector": [31739.784165997655, 70046.26842968729, 37092.339331884075, 63782.958416754576, 88893.51916631618, 8510.116903005937, 64368.2519764, 90260.343506506, 74213.99655054628, 51920.69588045795, 65495.975417165864, 90410.23212636658, 97286.09982555707, 12434.670127077985, 28605.077551214854, 45369.70216772177, 94360.04035588352, 22044.789313785783, 74550.6473877809, 44417.837646804415, 79945.39268483521, 43480.26254192896, 27997.052656969034, 78331.20218851992, 21498.445488089877, 2973.9201740292096, 91795.84065860574, 9767.771382471203, 53033.71430191176, 25236.717394225507, 28731.012284149492, 70203.50353096276, 25449.918693846463, 9198.320672003434, 79058.55728367684, 75122.30045631199, 44814.38100243552, 99408.93269587643, 41167.03225705619, 34465.58565887389, 81233.97536855964, 9768.24151660607, 64735.34635144696, 39200.32738540399, 38639.054360569666, 64633.59742644729, 37546.062847650755, 50880.59507472115, 66376.39747905932, 63558.546514671674, 48778.16813518386, 14537.438803166946, 95939.75004629561, 25559.784678743148, 11524.110103382323, 34446.06520699014, 67262.10369245344, 74486.24182853856, 65838.35012956511, 46367.97409005372, 5559.5171613020075, 30997.378553324874, 46122.43473422806, 1957.8013882806667, 56868.337321339124, 14673.953264503214, 71316.2646861371, 5613.0363817817215, 70768.3545655938, 94570.27732108385, 33888.802855518865, 31919.337798363966, 20375.106963166854, 64980.5181585683, 33019.56424419464, 30643.310510933687, 52737.896581326284, 42316.315754568466, 12107.5633344783, 27366.229440848954, 65294.01248013581, 70028.8888385707, 61445.30498804518, 72323.82679624761, 90875.91051229142, 16550.937552794665, 70531.70588581494, 86602.51936756841, 63976.97083631081, 61558.7766813848, 29752.36210523323, 55717.81435800065, 60139.49166137955, 35751.60822025155, 74442.84935796126, 45722.67183455777, 85753.04957049307, 62474.57352187361, 45598.15866160365, 68050.79876841263, 61316.85825009847, 35144.60364997931, 49061.13993576505, 53177.78754328944, 19184.532056495762, 93398.6705232954, 13777.431960198428, 53591.448716712934, 93820.847450221, 76869.8094260901, 30783.585509407152, 65373.02617039566, 49197.654064120725, 21125.381980799473, 59901.84965515427, 78318.83022112826, 30946.897110235237, 86235.37595996907, 58133.79066099731, 6921.231544859185, 28368.591470139436, 46271.20123622887, 37799.908669129254, 15125.313934071515, 79859.77993148568, 8155.788544173936, 90474.64753300838, 15664.55888259035]} +{"id": 695, "vector": [63559.54411803779, 28319.23740278177, 55951.83007653149, 73627.84150780948, 83947.41330671556, 68871.19958458582, 91934.98769271474, 75839.19945494183, 70584.21549663943, 53325.760462697035, 14177.044312749022, 4087.0814404179278, 72105.69288781256, 23617.396589731597, 19101.889208069035, 88224.11641669188, 32460.712189964204, 22027.74269707479, 28649.99863411425, 38923.21054950064, 36698.7297281881, 12147.250108870656, 55269.48103827968, 35062.70521142697, 87527.54876848341, 70581.06529058206, 57231.006785215824, 99771.81592578995, 93873.5686444838, 2109.587735151619, 34598.928238749446, 98558.2753939446, 64219.70914018176, 51388.111769160314, 81958.20614376923, 9176.703193117486, 83251.07962509849, 45996.611741212866, 74681.00585365061, 95261.97300017452, 11104.265258204305, 58559.24275102869, 85033.69165912051, 81240.90226635907, 22175.107623545966, 41457.20996567521, 76228.26223860645, 8960.175662478054, 90159.67953993833, 13263.59115399719, 40596.45307651513, 82827.84704665373, 13384.452819026194, 92801.97033587273, 20086.83643463609, 34544.72659818693, 35399.6613751776, 91471.0009904182, 29569.99526013191, 68919.44787826955, 39875.57588210747, 44255.39642933998, 73316.59539810535, 30100.27858211081, 43404.072794764834, 57788.274866891465, 19939.212968782736, 21351.46605699907, 29022.491259088212, 12860.205459797548, 73509.37288630137, 54690.7218625774, 25963.640753786844, 79680.9869991867, 49335.498226841366, 60080.325143410126, 56068.99048918592, 35547.12259623048, 69816.34056801695, 71094.27092911539, 11754.615002631486, 38252.78068874732, 42406.89257908635, 10049.411330745894, 23221.287483319218, 66792.06125056422, 95906.52150528847, 7012.587813994164, 81520.06420490531, 39084.647582829166, 82092.17736994327, 8922.763670376788, 40422.30108363219, 96319.31181741797, 12549.629304589493, 69548.35477281349, 99836.8591590351, 1032.9733230468373, 34956.779431399256, 39072.53429021159, 96611.142705446, 43552.88893488695, 58340.880181577995, 20497.379149174354, 95422.89302637914, 57868.16001182766, 47133.73812034163, 82301.32716684192, 17278.508967618734, 28779.97761659482, 67000.67569647485, 19753.72380730731, 23498.86000462328, 59874.491142690524, 62140.21920333717, 7598.427468654645, 78865.49325795987, 89956.11291410391, 32208.927941284826, 68073.78400330793, 45811.857411224686, 99832.87533322266, 98508.92372180325, 79551.98747175795, 8981.889912099872, 20028.06669844579, 42729.77615130261, 86432.1484343161]} +{"id": 816, "vector": [58689.38089635234, 81480.41343308901, 36188.01498731701, 96420.12388263678, 45746.952298098986, 44516.97675285395, 49121.77637898445, 38077.212866812435, 77765.25627108008, 89110.22956136311, 48468.092274589915, 16694.900662993183, 47691.39105051129, 84921.48710303966, 16251.810363291286, 62267.20501056926, 93640.58826187876, 8659.984535453435, 60240.89894037814, 68689.51210481129, 52238.143154681726, 12287.505178686853, 2620.6253086564548, 70373.47920636559, 91625.80529236721, 56280.217125865885, 80669.64819570765, 55362.77999989787, 65535.35753338993, 44342.55919151596, 91106.57604721485, 95479.97823694939, 40012.43334026912, 40617.59902926866, 34891.60401177112, 87733.6513049642, 37384.542481813696, 83791.10830294597, 81129.28808579536, 3426.3391648792553, 15343.568890185777, 81987.08057460445, 50808.016497812445, 46364.86199183016, 70063.89022889874, 43416.10040629723, 354.8660672895276, 194.3978512962241, 78873.46575749117, 7189.564630134349, 16547.82175327241, 91228.35349318104, 30309.27405005701, 97382.36036505332, 37000.11192939111, 63606.970061486514, 6746.793791697792, 92097.95362296469, 16915.945793729792, 45889.47667669384, 50777.829670006126, 81028.29741289794, 27843.368417463433, 94968.70835097437, 56098.070057679084, 5112.1523338664265, 62583.79089198481, 87081.1130337213, 57286.668828470276, 61598.53125420164, 99162.81387296368, 60024.96044052115, 77561.66789389621, 48866.958530038064, 29543.466705552102, 62953.13777580179, 9482.246051268396, 55460.31802919174, 26896.458329979345, 33146.705593056744, 43095.57822579545, 26215.80539520668, 6453.42870536284, 10313.178273267964, 71688.44051382276, 35994.450328029285, 87017.75685319783, 21187.355505453466, 3708.240802590035, 99388.0041434372, 32886.38352164185, 50422.23446507456, 96051.05772945461, 45868.615967485224, 83696.58996408677, 29146.51223160426, 98895.24720511143, 45807.13737449518, 24084.08188011385, 3824.7626257957636, 80095.52963358331, 94604.40963285069, 33421.51493724628, 49062.40014353207, 43281.18658068914, 24556.524318498618, 78083.34742549139, 4885.438283830479, 72904.97651958464, 73650.08000212874, 10615.226230767772, 4556.520117663743, 81744.95372306241, 97991.4938190012, 50581.29359262257, 79542.62472080423, 20297.879733309786, 53050.07576473072, 32273.75619711057, 84304.1987913632, 30700.663259512974, 39453.51332864919, 91074.82198294235, 50903.5333758389, 90100.878681568, 46832.454901999045, 17182.271881793808, 91853.91878263155]} +{"id": 176, "vector": [19556.531812603505, 71869.61722471463, 8769.13479425816, 23344.52245687706, 72503.550911123, 9561.413726067203, 22841.74817511693, 46080.84944628846, 31682.43363697565, 81932.80420297639, 76160.62344091266, 18036.134417762318, 72843.73480770177, 20083.949945592096, 70706.5244046653, 81364.17415274096, 77194.36508144574, 50336.95372071302, 41079.544776209965, 36657.00011842931, 93268.5287916818, 2596.3754619597103, 71973.0637776959, 85020.47268566072, 59130.05045254422, 87038.37898760929, 96194.17765999466, 56617.97278045484, 79174.13103017151, 8633.602800357843, 51464.165610430166, 93381.51667981096, 51139.998438439805, 5838.195286510761, 38590.639463933854, 10937.310201768336, 4444.250956295104, 65625.56342678168, 13175.405462404233, 37357.547687813305, 35043.11543072494, 55874.923384667105, 75594.34155530971, 39284.051506859105, 23070.71460246898, 96622.3532212585, 17368.508454605635, 14208.374421308667, 77909.63886813387, 94350.38800527553, 51371.41793731379, 58971.794419781654, 3672.4869407035567, 73418.03299147071, 85477.44971922606, 85007.61003720805, 93646.20351379835, 33176.39072516273, 72170.44005688597, 75522.43767638358, 38106.20232080506, 10714.322500048678, 3836.2483447193063, 74676.45593736145, 95112.42295835975, 25557.79452370164, 86932.1713652847, 19557.853459040976, 89330.64967719465, 49107.330023996285, 90399.23592308654, 22525.48451973656, 1815.0834336435385, 6722.286946089484, 63254.017365110936, 69219.71293017767, 95953.31691826416, 57234.30801300482, 24941.047509852233, 52186.910679690176, 1552.8781447696426, 36327.16873191717, 4963.968014078701, 30235.140714325837, 34758.02072375284, 21470.069465740704, 60572.663380820035, 65673.3422793111, 9890.791994139947, 30675.113057696668, 21652.171168595523, 90153.24209221813, 85164.64994924198, 22031.17293849729, 47769.8858852726, 98802.04618360709, 38399.11277521909, 67937.80007329477, 59110.497900491784, 9081.066425336403, 68613.79928732305, 24334.64388580977, 32124.16255569557, 91936.13412947945, 23749.115642384742, 86044.8289041737, 84615.5734586117, 8771.18677421962, 46536.04104358091, 26680.294648483516, 83057.49396630713, 91993.32732410792, 23422.768119873494, 44868.26335032268, 4035.275345663969, 83042.85096799392, 23139.987703075672, 88750.09559750439, 34455.82356134764, 60872.84830944001, 37117.745011492465, 18374.799290224197, 97728.59855584348, 48754.80359110773, 21590.751881207005, 50992.70919858868, 58279.55179144425, 88255.77559978151]} +{"id": 1482, "vector": [14263.345537489102, 99557.42676522194, 65018.4506051766, 74658.05786029618, 75167.17078984548, 78910.28556353015, 57378.75840278866, 71673.83908676723, 53256.4127168056, 45606.62782117397, 84137.6585337798, 43137.38103081055, 66159.73722661796, 48947.791710719466, 6631.545489695856, 55474.43074985463, 72036.51465201579, 82439.30290154502, 67408.60433743811, 45999.84318755583, 57844.85836796589, 11094.31094087, 3955.6936749298943, 47739.52560324396, 20984.9730803391, 25934.69922296373, 55893.05679894031, 80608.78992334454, 41677.916163280424, 2891.5258172834356, 32965.104579113555, 55382.64578857309, 50037.22021501934, 77585.77977457935, 4092.2214788507104, 15984.801116581804, 8772.45297364262, 74168.79557048582, 9056.508348371473, 9839.81769893537, 54486.762910584905, 68016.84908919795, 64482.1690830165, 48422.608454391535, 78311.10600228047, 8760.0678018382, 5311.560850506947, 73673.51078905699, 20528.822622497857, 45668.95128873768, 65204.20266672368, 49247.29242852678, 17118.43061392192, 49888.96070326152, 62935.85518528241, 10580.955912652978, 11359.200811548486, 17934.203759459644, 63626.193506208605, 77232.5857462588, 74093.95944891703, 12393.867406327498, 50045.72184843598, 43462.37497415588, 72510.61238846791, 24435.65972957822, 58943.61368967116, 95896.61981272965, 89922.47922880939, 697.5773562217569, 26605.599495032828, 17873.487017834253, 46309.50205284292, 32243.038453542773, 38059.741878976885, 41208.21000998415, 354.11166019684305, 31443.681792132284, 61886.780317538636, 0.42571568805716, 59766.82163642717, 50674.790946809066, 5413.463274611297, 61421.55807767401, 14393.3671812562, 79795.55707744158, 13990.072294998945, 50393.63077843117, 8303.925165109038, 28979.901248121652, 70655.8937719269, 6496.271832880385, 83372.45132425742, 7553.198540770812, 10325.484155310694, 40792.26321901545, 55115.77137324044, 76479.23941556738, 37721.1850624486, 43651.633013633495, 7990.211100788569, 83390.4759786654, 11208.399009523728, 96825.19613886758, 8858.76501793832, 61291.26761687326, 2469.7018619899814, 82868.0847645082, 51473.79082449787, 75315.36677713796, 13697.104047539688, 59329.49092713158, 33040.045255164296, 96752.23838413015, 2089.097234514403, 64811.005901518234, 89480.39786116726, 25016.225626263287, 17627.45883928026, 7583.441628278031, 55556.970394885306, 30160.856036129037, 91990.77234093212, 25888.145630655825, 5796.467251157012, 42168.187621177596, 95418.14438239526, 68293.63185753684]} +{"id": 1946, "vector": [57438.254099545484, 68491.30288224574, 38680.80146958608, 72739.08539076727, 26511.097362249737, 47439.36860292017, 81709.43003964453, 16251.182094019712, 52916.59487544593, 78756.4932514739, 47532.937402311814, 6448.858608194319, 12546.287678526813, 39810.478255381, 66396.52564203998, 52382.051798344895, 37127.74049125096, 72589.37202395151, 7693.9294660218875, 14605.47694599773, 38989.90557108457, 21811.604689338194, 86825.33112350099, 22710.082152631992, 88195.08208627002, 42876.213607815494, 74086.1079450859, 60649.32497049582, 90614.05917714215, 796.8606174062587, 16701.507643818768, 8385.891126601364, 66545.45829765119, 45929.79082814842, 84563.66299712715, 97556.55921553509, 55320.11112399986, 42056.10268408688, 59722.49598233614, 96325.85727129054, 11241.093440384897, 28827.323357631252, 45908.70153383128, 15394.27773014196, 14634.050424360368, 46521.97892512599, 30442.6948335089, 87204.56781797542, 49477.074300393266, 16667.43209915067, 29560.7134241044, 46446.65979468279, 51109.47610586832, 49441.43134757875, 37635.164750671815, 53021.983997005904, 23316.491522532113, 27755.32031184277, 58997.251558110474, 28601.1424267972, 78555.04388584802, 65055.08325817184, 43196.29381153676, 30403.363822455907, 21311.651750791872, 32302.51879112257, 99948.79434829947, 17012.300788379696, 86618.21146755839, 22069.508843759533, 29263.980835139646, 30935.978190254078, 58917.2414687246, 63019.54223547072, 64570.69487206473, 27128.150595221057, 86778.79510591377, 17091.210594990047, 26817.31902060149, 73468.76981968264, 11273.905192767652, 5031.8078596987225, 76765.46437943145, 89618.77691320947, 64637.64824601669, 96381.90438332119, 40218.243221975405, 97817.33217062266, 25053.789259754412, 4693.9092453635585, 37853.93629969092, 15244.238274639587, 43722.17670564402, 47852.82767823674, 79000.81859442807, 6436.15540505802, 82601.88738096897, 69230.45343892563, 78606.46420229648, 45716.09351838177, 54574.03938580454, 99865.80363551129, 37196.276589932706, 42522.483920830025, 29093.350284432716, 22180.782449211245, 29958.67593060758, 67652.48084771834, 78791.87545592015, 16799.679858121, 87385.6345440914, 14094.623300287512, 90618.61542749568, 28390.817142561587, 58314.264979577376, 95695.38641931242, 30535.93107339503, 70547.6264864617, 50895.629835966836, 52202.91512934513, 63225.230461132785, 95314.83964664344, 33566.18157458705, 44499.58098666018, 38264.742488611904, 72728.15809155426, 44188.877383320425, 91195.97224080226]} +{"id": 605, "vector": [1538.4069317389115, 9094.411908059974, 66061.31077719259, 38911.70792051603, 8586.556204713359, 3605.8816420550643, 55769.856132230685, 41454.0970217934, 8001.27460511435, 51243.70194283836, 51251.84315757314, 84669.63728949937, 27982.11622951653, 48890.753420033914, 45830.361256693795, 19897.537523693067, 4909.239872079196, 42966.34733786908, 26660.584663936083, 14844.20727283302, 54886.898551356244, 39878.37988606371, 40776.87012422594, 25951.347766281364, 64570.30012753827, 78615.26143563741, 59340.63445092052, 1551.3075757909721, 11361.707914945062, 17038.383525391422, 77382.59384024839, 72732.10596715228, 86099.78799273702, 81651.96615799695, 48799.70415501762, 16007.00665633883, 61115.61469135382, 96972.67663571713, 25034.562266464432, 26447.38548565031, 35028.81546699368, 89494.21800855488, 14614.263229300617, 95404.71312659852, 7704.242786076321, 38318.444226299485, 5120.640424765055, 95977.9953008135, 50389.867008469555, 28304.218161534678, 95299.2792700406, 42005.892927323206, 55983.23265776724, 16263.673831197568, 97397.20724991428, 72121.82302562459, 57313.83138672294, 19584.50251078293, 13298.155662648482, 59664.90894778654, 96296.9791688413, 28236.187307193573, 66419.3749555413, 3359.1199943257543, 80693.71008464872, 95175.08008979307, 78060.70937939745, 61154.38557530725, 33347.676448270766, 24160.613988894842, 69344.03915554192, 16367.511075873754, 55743.296702491505, 46741.406615954584, 93425.67607220479, 8490.583549486608, 53405.61950650375, 36957.16668816334, 82462.91075063366, 93574.23579513261, 55154.8155962622, 64777.2889663005, 48410.36446802727, 77115.99140607653, 56064.05290822064, 18231.718391291517, 81576.59932168634, 96359.39289302035, 41538.52926079943, 88596.39090915739, 51226.24559295016, 79453.14835711876, 14767.002537824392, 95111.36668290284, 67586.55794236419, 34867.13486576556, 5167.0616554728485, 12843.032398338728, 4090.8541668661537, 74000.11804014162, 61103.122438721824, 26910.466694404888, 9269.391228635037, 23564.228390097098, 47378.63401621504, 10993.701527255294, 5517.319494359518, 70393.23990936206, 78971.34677825742, 79833.3177598376, 10444.382950612995, 46039.4817978288, 18593.929463543303, 79370.7624975767, 94687.49755536376, 31449.716486415757, 77001.28260862568, 84138.04836217758, 71136.39917940358, 46930.221601255296, 74441.93613824296, 14606.422778094307, 53504.079032320806, 4695.847543315268, 36291.90956671968, 35346.67629167049, 36687.88641603068, 68403.38893584635]} +{"id": 44, "vector": [23274.5057990146, 87132.50526661437, 27166.444237827658, 23905.1679823704, 25279.946478407, 33166.96607139564, 63316.763852436954, 95490.5078466962, 775.1691603636646, 76057.52404780687, 32596.009418305093, 68685.60625584229, 46151.42555002849, 41177.068229462835, 96176.15176825061, 61082.22075264962, 5459.544654076331, 97143.53326103499, 79415.01211948443, 40736.060221111846, 86010.50609193523, 49676.95653966846, 96297.04030668348, 16062.724079295022, 93773.73950314087, 92482.09530244146, 94014.04910065004, 83538.09814870772, 91124.91231120793, 91098.97439585862, 68949.48474762934, 33166.60505899656, 75305.3420389082, 5965.706702706364, 98212.61420740302, 38495.021675773234, 61124.57801907786, 41889.30479130797, 49048.12206980072, 64554.44950744682, 75089.31697442895, 7570.563331287028, 51653.81256275653, 77633.10627801844, 67891.03694857677, 27205.185610356064, 28356.946899943327, 21713.120764298143, 33826.73398061443, 90474.92207655634, 31552.65940042551, 1826.4396446229014, 75583.33069727181, 62317.057230760984, 58859.235898718056, 64726.78025301617, 29131.21281766864, 95302.41582482976, 504.77035708890173, 25893.84658831979, 83438.94168321883, 9608.211681948864, 88446.18319529429, 36461.29591963587, 4562.283663924438, 10057.20348341178, 77565.60062970997, 58170.706787535266, 75659.5883408127, 24353.58756913605, 15271.359750398138, 10823.583499959645, 99985.45588744756, 88251.11801681778, 54135.97511235432, 58285.77556180391, 83469.0379917236, 46057.447325407455, 68809.50080277496, 86482.14385192307, 20322.90202126067, 99767.53218789469, 97567.09367786112, 2282.93817740427, 80151.73503751191, 88382.89452111976, 54224.787644318465, 99688.38909233069, 80107.2924658016, 23269.479314331333, 89512.7033654562, 84851.12750162235, 94312.45280914386, 27256.47049510057, 25295.203426336888, 34352.42598970843, 54276.362498721195, 9437.558468759988, 62907.371283687644, 16302.726861504436, 75016.5304270122, 54534.185172673446, 65618.85941819011, 93526.48174560427, 19139.479710710984, 78001.29313151038, 5515.373021583625, 75447.77497010445, 73064.84387427884, 65488.997205864085, 56349.36385810802, 5340.869109741841, 74361.36082399856, 37274.30315084912, 87303.77189577307, 18217.83639159521, 68645.25473625502, 79888.16571220035, 75810.2789560297, 27044.416692436112, 65420.556593619884, 49274.05687309445, 14283.295002891038, 81392.91280966082, 47556.20219056271, 29026.09008885637, 23020.59141936731, 50756.407185741184]} +{"id": 625, "vector": [28953.490538220718, 93583.82024112085, 59090.96049698307, 20957.029007159323, 29180.75604174928, 23028.68866243687, 69359.9663054781, 94498.65130120322, 23200.924113452882, 68906.37280344211, 64792.58281508967, 68781.89346771674, 74816.35700052882, 45567.625995228715, 29432.955379252824, 36296.1232241662, 81345.39970672106, 90358.90386998173, 29553.800548511143, 19234.125458173356, 56774.658199166006, 61077.35482662444, 99423.0727827111, 4335.2153668288265, 62170.77391419401, 29230.057869046555, 90382.8135614563, 63499.960795660794, 4496.241182304328, 52766.8836065033, 36148.43982088872, 76563.81190951835, 62559.39539120681, 37181.501364870375, 19399.769250022793, 97699.14417419751, 5129.659392130248, 78127.64684412858, 41359.63934896096, 16109.55592814961, 7480.958194417098, 27036.396491502557, 3583.2726441970995, 60759.34138192531, 91430.97771216344, 8279.603341676955, 45176.02563795354, 31637.933386845052, 7782.038588176765, 97478.5785381264, 86414.34913285304, 75471.75338680482, 14711.685201616221, 76484.3881999693, 79155.99547888101, 1989.6329378782052, 67110.8353089569, 43741.4751808724, 75437.78264155849, 92629.1355548861, 33024.47201365951, 37355.4249859148, 74816.35778267449, 89700.6682094903, 90628.7562872356, 4492.491708438262, 70489.24550096822, 48187.81460926185, 51256.33997973367, 85632.3016958397, 75339.57223393561, 32354.618818195315, 53096.68429226014, 38789.22756505291, 4655.398522384946, 15215.195504975509, 43238.095071951364, 90366.93677051301, 62950.21217122227, 4868.875909286608, 43931.33963758082, 64425.424340771555, 46080.72742222833, 3536.9891309812165, 23653.308677876306, 72551.78101876364, 81927.90808384957, 38585.15393692644, 68017.41406967632, 36915.14923667184, 35991.88563186034, 4065.339739948426, 148.71747273424995, 97151.99208889503, 80893.96321464307, 39177.89165960539, 46090.63700140932, 99708.9613061467, 29914.578632580004, 34736.22026682047, 97635.11470305263, 84907.9766840878, 40705.60814988764, 20876.532052998897, 89094.03227993158, 68908.06390616857, 52212.16372720118, 36604.884860400765, 84477.90685469861, 64333.29053288824, 6334.211536073398, 43839.12212526613, 67230.31486611863, 31592.93030594519, 99484.47204660712, 42380.68302930929, 59672.29003211747, 66517.74516793394, 62755.47315914808, 25091.421386349433, 75196.26012900285, 36799.25320899573, 30735.75405923683, 58182.675614694155, 48722.829500210406, 61073.68734684875, 91413.06659515342, 59651.090367846075]} +{"id": 615, "vector": [21703.63937823978, 49041.571389591365, 79068.20674532634, 89557.66883542134, 79983.47064463471, 13076.647833444566, 84011.91940612241, 28052.298615893313, 36503.09947406852, 53155.16893524865, 38537.82647865006, 37291.942493337796, 20313.331696868743, 60807.95026470349, 51444.728924112496, 15181.16107708305, 58850.43104939315, 80899.93521596478, 38890.61105916955, 70605.16860875669, 3698.7970498630784, 54530.26044407759, 14671.454515994375, 11346.675598081913, 75898.73275327666, 51639.09651395708, 41793.41674619337, 9236.643385706966, 21937.919354913305, 20727.14869466058, 38133.19193076919, 35427.94292211555, 70923.64483709841, 58934.09336675484, 64292.59230659331, 23960.903086815146, 62822.2516945404, 26181.65315944797, 89507.40270344149, 92529.44840289498, 63990.997810264875, 90303.58882780051, 32970.04735984474, 67589.0352825028, 41502.29866006065, 92993.43412425049, 61715.08355697963, 75474.77165910644, 61386.17336184923, 64804.655086542654, 62248.65874915998, 29067.207864222022, 40174.96352159016, 84362.50965246033, 67174.8378514848, 42631.96322629999, 49350.86345445666, 60368.24658883062, 95942.38662956517, 4394.126553690925, 70804.20840557554, 68.56934446695107, 90907.38020806859, 19086.639227059855, 27953.783375618324, 86521.72942460295, 91222.4433070072, 51161.46252979666, 19090.04466567028, 65063.8793298046, 2407.962002102004, 70085.56464867956, 87151.757810889, 75498.05255202102, 13424.556296841738, 82109.03833698809, 87806.67383778046, 7004.032824039464, 27718.628896813247, 1800.192220612573, 57438.68626018883, 49043.75467269267, 72682.83573022956, 58760.01438231693, 1881.0207990246909, 51670.638374289476, 41920.28409328007, 78785.55818227286, 46137.29529476872, 6432.405242777161, 61401.30774187287, 17544.688117597263, 39100.37700765732, 48434.247682616915, 61630.59439277996, 55395.69214362569, 7861.931673763589, 87318.74088071969, 96025.21733419863, 90831.74663363329, 93549.56071621674, 25274.235586405746, 86949.83848426932, 19212.43683516848, 65052.99233947335, 36933.518154127196, 24605.189397362294, 29791.33166271415, 35309.01819473847, 37441.82086718189, 21247.44470978377, 60503.43777077247, 44041.29126039321, 17644.94853876103, 80406.52545124886, 53526.04605829276, 83702.67652627414, 9981.066862000953, 69446.44084608016, 44868.68778145402, 12488.865759735434, 32488.774194728554, 34299.81510215148, 57732.368242600816, 97430.83905304407, 33434.70937805286, 50489.10686436038, 93940.0229757782]} +{"id": 1489, "vector": [83157.09234472027, 42583.01305662951, 62823.404821678174, 15162.375468013877, 45677.28277982111, 69740.00607790149, 24768.744923598217, 59229.68374181444, 78897.81816614242, 52742.0268788324, 93396.3707860349, 63758.42404623177, 77912.80967311039, 11462.774567701328, 12930.25753700695, 78336.97855967784, 35426.189475710125, 99509.54498601495, 39453.64365053964, 58016.63655864526, 37487.56224791521, 53458.782600790044, 46967.326588859, 53312.97798396929, 32477.06494931777, 47604.58943060425, 26339.73753114658, 19601.561120609167, 69364.09007257599, 71978.86982592096, 66036.85283273461, 98106.07533932778, 47304.16377899034, 66330.75506464441, 46944.14741935524, 69410.66285938233, 41159.45032033022, 27221.470317439456, 60084.059589726145, 86123.54193950782, 63391.97336892744, 61693.25030946841, 57415.92779039011, 55461.68325039261, 35471.59654877847, 61028.331318451346, 75145.04000659643, 86237.80823670281, 96772.41207691738, 46524.407468523146, 14434.114845255075, 12376.522536585366, 29448.567401971948, 23601.7937276904, 76296.50553362093, 59024.50818340538, 30389.67102755833, 55321.52493667142, 39030.98101175307, 48814.829995749555, 73089.4249544688, 27277.140244092836, 96354.60065649891, 90588.1878682443, 21652.774703925483, 40158.540116975906, 3222.9133599221304, 18339.647628855404, 86667.53871839821, 93271.49248229996, 38627.04615749919, 41013.0358443867, 10282.077965706616, 29217.034163221644, 78447.36858835655, 93822.57289654993, 85961.78468601547, 19252.428016624588, 99850.74445675702, 29259.865030054778, 83664.8358815003, 20504.845353673794, 49256.1143343097, 97831.29534844753, 89840.05100545078, 47724.907982197816, 35805.88798973479, 34640.68898718868, 49990.754695011, 63982.063705730274, 12242.21106717821, 41500.75560629498, 62841.097325747905, 2197.18658352821, 14818.536944257288, 39722.53092373484, 7378.264317178585, 15575.223953672568, 36674.45573949905, 3698.9392560844326, 70606.34608181166, 77955.00669486055, 92477.02166450348, 54038.08790824995, 8210.86744786732, 7159.9691488067665, 95370.00907274862, 23207.85472711825, 29113.67026817182, 76073.42544480905, 25530.557156880073, 72793.03332575226, 13130.946314840574, 5305.195872503587, 3980.553418799071, 92719.09494635042, 84755.41634307083, 54383.765858345025, 44141.717838629425, 96252.45099867083, 18807.26656820143, 93247.50725455463, 20731.972037655276, 24656.03011951907, 73381.76445382829, 99574.0370005198, 89089.09175188573, 25389.037314068242]} +{"id": 888, "vector": [92553.41170474564, 80780.97012923531, 52635.872978525025, 77337.15134716523, 84666.34366992519, 38099.73752121288, 4426.234709367472, 70214.85235926982, 939.470637292128, 61909.62673645709, 81202.51857721905, 94769.56813920102, 4746.218542508041, 41095.03810136159, 44114.23945991005, 43684.224657925195, 78515.09497824231, 28192.000241345184, 45858.41659638254, 86328.50489081981, 25970.5335322859, 69299.65099809073, 52369.67713160433, 14191.834408079229, 57431.24368544251, 76312.3973162123, 56550.92282196359, 45858.90394753378, 5175.1090553363465, 48606.49184227734, 9129.5893696969, 90854.01879984887, 86864.96258305639, 39534.71916282508, 44738.33701256181, 93113.6796875539, 53974.29400469016, 72837.88064885167, 70085.9849557367, 89103.5550080176, 78610.1182021874, 64883.77328497972, 49245.59715131314, 81920.33897287458, 68553.29302730452, 33021.709267847975, 70843.4572561597, 65198.00946908461, 55275.185545642555, 51357.93704355249, 54809.44864712154, 19445.826463685466, 3500.6984955215926, 66378.25511375866, 14042.191756482458, 58963.62813062096, 77718.89164021183, 51547.043408915684, 53350.41789694741, 53521.838963840164, 43778.01266443709, 14697.084846501495, 4826.67275998565, 38667.84599850191, 36835.982926970355, 60962.901115434484, 69297.32334304125, 80691.25343927043, 58503.6465886287, 74225.72136261649, 78753.87892542213, 95400.3862293972, 88205.17243003647, 50112.25680188821, 13966.532942283882, 30575.88721750748, 20428.130519702838, 49074.13995440756, 91409.69320222437, 48118.61952651037, 33392.53001036926, 90896.5935440291, 73472.84148443164, 59217.763179030735, 14098.453429910263, 93679.57736338103, 30449.52625596584, 88714.05418121684, 83591.03934014129, 45999.41159965042, 11766.20575059113, 27206.58381494101, 95509.49535891533, 83608.18107882213, 42096.43126339113, 57929.485762557386, 96616.25741047927, 14833.008550025239, 80136.30845601835, 60533.44549920886, 88800.01286086792, 71395.65182963187, 31940.4847559993, 91428.20051537508, 21805.868916003667, 17008.441462544877, 50639.11979942963, 7197.727686785993, 2448.4039732669994, 46997.73589036173, 85102.9199737266, 15652.543764220094, 14419.90422524816, 93724.74640220463, 46729.390781597314, 83800.16552016781, 117.00216959092957, 2624.6448345979557, 80477.34128323966, 22350.317680507702, 96687.63586326377, 76381.2445272786, 15458.62687313765, 9788.149728915641, 86280.01193532263, 20211.97368505827, 98436.74466923997, 69364.61931429661]} +{"id": 1854, "vector": [47654.509662967735, 23022.22299388803, 18248.52605163051, 94397.872440151, 95425.09109042244, 82197.88537697347, 58077.04699799926, 65639.53713910774, 73352.3102839626, 31024.58304984298, 98706.62321555817, 96036.43271018354, 28307.43302495036, 63336.10284165071, 52543.737425868996, 18887.612097645135, 42079.6022813522, 91484.78401388653, 6565.311725771983, 12262.029875416858, 99164.87353078721, 78602.06121894626, 49451.457835579175, 50567.94578099163, 24670.607455839177, 13660.032491612306, 50384.074672282775, 32632.682758437004, 29579.044231856456, 23827.813119086848, 50280.52443678439, 92116.9850514324, 57101.939184840754, 90137.08763787612, 20471.72605335541, 89330.23014853048, 89035.73827183191, 13160.41196836929, 61477.089589919444, 18505.14367947683, 73222.99023965721, 10866.02774699993, 14559.045155469486, 82588.9575992581, 56439.942237363575, 37530.11445870469, 86666.53877296967, 71233.82846500255, 66400.14369425013, 62486.964528725366, 49047.651552371775, 61512.388112971435, 45663.885624956005, 38854.9472580675, 99895.8190513929, 89229.3500249158, 96586.23738179861, 51247.61423605776, 20799.70322071728, 65533.455918047046, 52680.7040314219, 8298.430692661785, 29374.47646486824, 47052.868761705126, 87298.16392565829, 59070.20194130972, 49871.98502428294, 91552.04326926985, 14467.267061986044, 85521.64341709808, 34726.77868694176, 36801.46571611256, 5937.618157123048, 1453.4437830669656, 53785.177475644006, 70882.45946068638, 5428.1672539993715, 55504.195986018814, 99861.96278818375, 59390.6247905151, 91265.9975577695, 87495.33951774234, 51995.03017376902, 17972.63056833257, 39176.75795194087, 46475.69264752422, 93717.66098060452, 16088.016897020507, 69874.29440566797, 63636.7112544297, 9989.525159550172, 68424.99079566117, 58724.73469266072, 72235.16284160572, 16146.097446322172, 76098.92007892896, 9474.447948337805, 51686.68135286438, 76724.71589261644, 37945.051036628494, 92579.85191826776, 35994.44027075489, 94674.73378646614, 86491.63769075103, 29500.31348697213, 62938.07665180739, 91925.02987238714, 25915.891239777666, 75491.5580383929, 12570.499058087204, 60404.27072174284, 38809.66938586332, 60848.54221148931, 81272.84707174885, 59976.324964686515, 58401.629938069964, 97603.22706058696, 82704.18925635103, 14562.225204248147, 28200.006724658135, 86791.19065932724, 65154.15255529755, 70078.74309256603, 52538.73263519423, 92122.39492023339, 48075.55701187353, 52500.09440042778, 63195.59281475934]} +{"id": 224, "vector": [27646.232960590412, 41716.233493501866, 70590.25193487103, 66840.20476220401, 55523.57611613875, 71279.52567890748, 65436.89983556467, 38271.97378935424, 11710.73174147672, 59721.12378277984, 67801.9591843986, 61361.35855814054, 2778.7934891563837, 29226.405310255843, 62230.58547905804, 39689.7865931519, 53359.12633405794, 95039.163297446, 21623.40732763065, 80881.54630124105, 64526.771926399604, 43012.67037436588, 76848.37645903134, 65547.54871657756, 29931.945475033517, 19036.448457549206, 44507.614953756965, 48332.915932577205, 15503.684449582523, 57071.76311954497, 98896.132891838, 93465.6648683926, 1026.9533633218698, 25980.40832113706, 24859.02258714986, 38868.359648168946, 90142.92181359041, 7834.923023806339, 76530.82464129041, 27827.437499007345, 14129.238864105775, 82386.9072383822, 64277.02402771702, 56466.09310314575, 74689.54635445449, 82254.89114460941, 72929.86844550399, 58325.83095528236, 30131.806652966843, 35573.62499921307, 12157.51384188829, 17780.61531698646, 37036.84603526419, 91973.59138232947, 24863.318338141125, 17060.218383478023, 11137.477739222668, 80596.88259629115, 84048.69921216555, 1330.167873056487, 20502.274199540538, 34639.43995351685, 29051.802686076644, 83144.26610814682, 16947.877972114722, 58999.4659384927, 42555.00355826891, 83554.44571649878, 52412.76228582483, 68041.98070987289, 62727.715186729874, 53048.04558376768, 71968.71399351608, 11076.635071399232, 89254.62824069112, 42932.78915550589, 20415.50868250551, 20961.065548833547, 89451.27046630176, 88714.60614712977, 45689.61740189387, 71457.98041154469, 75298.02706681359, 62534.11989165766, 24768.834381863137, 48839.51740533911, 61543.26369385787, 26747.216234667816, 62851.123872459, 91573.78168477089, 35575.21106769712, 10545.785577846435, 13613.973568710924, 87583.59422400098, 10057.179357394185, 56231.29901234643, 53912.8874330136, 1644.0888205096483, 29716.180762730517, 26977.50839015769, 42669.684590061406, 98588.87098857557, 41786.64868406211, 41005.73166445972, 20326.514797499673, 75933.33594153047, 13552.52477127955, 3901.581758491823, 7356.376073320603, 53181.24714483561, 16673.635551842824, 47473.02929548506, 33968.34159953981, 8838.876818697572, 1530.4662095336873, 65499.67309334426, 57567.7778618043, 43238.35343696222, 35554.16154976202, 63878.05811287335, 14766.691457708092, 37935.29389159229, 16086.0605059856, 44435.29653168378, 17349.104048986952, 62679.79825377217, 61490.80003969649, 50445.684807092926]} +{"id": 1231, "vector": [34241.415517731846, 39997.07850029305, 83639.40516207433, 68868.33442079238, 7102.092075675337, 10515.227482279975, 32232.39099129761, 98916.75877442934, 87112.37198305875, 10151.198976796317, 97838.21092041416, 82936.84963347967, 12424.511560179486, 51443.677960632805, 20088.72743102299, 37332.74859218295, 77684.0476685179, 17499.167657516202, 40286.076367949885, 96855.05657014641, 93404.28602863908, 25103.348001252067, 87075.44006444214, 79902.17916859007, 13874.678901113703, 19830.3624167963, 86926.36397040209, 19578.99242411195, 42218.495623114315, 13175.934879578066, 27315.744161963506, 40183.92204584669, 52700.55703917004, 11061.119874560622, 5702.729999198286, 26754.032452782405, 71025.16026799256, 43097.27304823533, 45163.22547273929, 20445.22158541625, 10663.958243637317, 50933.99789807421, 65527.95587281303, 28332.944392718997, 94769.07794261315, 14057.237678793177, 26715.56578076164, 71139.40155476054, 27444.836203347346, 47985.91081901489, 50634.71656235343, 45764.99211087884, 62929.46962235049, 18891.181293527203, 83068.82019413369, 31988.96473342748, 75137.91525186285, 59859.38727606641, 44177.0101063382, 59274.92919677196, 86600.43634817793, 35378.81353170397, 72024.42470874009, 14109.037055598672, 54479.304380243855, 58728.315964912705, 46024.92816739347, 68712.17763942042, 14102.06699117198, 50584.24226970816, 75706.3780670947, 40589.50998883383, 91813.83087310428, 45975.14801666569, 1974.619614284656, 13832.359756848722, 15589.553130556766, 61255.63650531776, 22335.249437588955, 87810.06741009903, 9858.943566315293, 32250.812633786718, 9099.711805843757, 2308.632870736771, 21587.285168015525, 13258.229352604822, 63464.03421817171, 78825.45637245345, 76167.87386223923, 44381.30482832442, 77849.65097981572, 17787.341071311224, 91140.89825591589, 84966.60001572224, 88276.24770471187, 15376.020639027944, 14828.351948194297, 25594.808216070265, 7582.598478025638, 80566.12094820375, 71769.39465801948, 6936.862752014905, 17490.81900734709, 73975.01000146366, 52487.730950322264, 73968.8940876153, 20692.705514541944, 91663.54565238002, 8052.524827370822, 65487.03110080646, 20099.40659676227, 49377.27181952336, 73294.32388533988, 22097.70580275312, 50649.40991775869, 47084.1135069052, 48704.201342191125, 81467.41234798623, 94225.92457691114, 86003.9559209354, 45965.30618683985, 17860.37604644385, 61335.89399620045, 72952.63893719498, 78535.90972857755, 35692.48062476724, 91254.27362032271, 75233.3616816122]} +{"id": 1951, "vector": [15349.306793379103, 37288.26426647349, 24814.59366630783, 96556.90646055253, 2208.686732511067, 81984.77046471903, 85444.58012504295, 82862.13717226862, 90075.3934736115, 49603.01651949717, 98613.75685805401, 17156.481492867028, 79401.50821175697, 8189.430752525195, 3625.9821147166417, 89819.899709377, 19495.779807285995, 54222.130631656815, 83646.09175873591, 85769.70339150258, 44644.01627656237, 78151.82044636861, 22475.468889920936, 99030.14975518921, 64863.713582438766, 8825.67206026873, 97221.68271139698, 41813.8957042491, 9956.985924279015, 68006.4756900945, 73679.24345058527, 87747.97802417718, 75351.9858979063, 93922.70952463566, 2580.830968866288, 63264.90920558327, 3291.5904427590403, 5018.490688851251, 87335.58478960849, 19107.598813100292, 40406.63462304325, 91633.9010109128, 72429.309052135, 49659.96123543959, 28981.703241242674, 20431.407658795462, 97095.36374379572, 89483.21938728745, 1167.4842650744365, 72674.68067228801, 94153.67414510784, 58814.25882984255, 80527.81288200025, 81395.22641070592, 24149.595537077683, 89532.46590115973, 10070.929699738728, 6882.482559094449, 44160.39260670257, 53348.435327908075, 36953.61938337343, 35175.02885482593, 36809.39290835434, 62882.72618796017, 41395.03696771962, 70833.79795157342, 64696.907333758514, 21949.412620533138, 83551.61236764045, 47309.69944271134, 30205.815934292103, 83735.2840559562, 20334.009213727288, 48023.27823788567, 25806.79836452243, 98403.82793032216, 88825.33765632605, 85720.70036186554, 54151.47419694457, 42943.575092483996, 95379.01509956979, 95154.54669246283, 1608.8569167660883, 46780.54951841747, 88139.975063112, 54886.33078926361, 95468.98752071579, 69328.097987403, 59946.23967826972, 52899.22815187864, 6831.6862597747495, 82979.83929613259, 22234.043330276167, 92143.87109868281, 44562.43698580271, 27542.011932178368, 94075.29239511992, 10855.271765813635, 28716.96318369189, 56499.601174622185, 1031.9260374958117, 6151.510136885163, 23361.900911139433, 32117.269755632104, 40153.85744648674, 72719.76362898477, 41373.19484523088, 30106.80190760978, 71996.25176696021, 87681.91181091171, 16265.05246957174, 12647.519688910214, 77926.76552901659, 22395.398617944316, 26187.21203925095, 22278.372477333287, 58023.37079102543, 60342.308716799394, 49839.20770121207, 66779.75721063829, 69973.56009038011, 61883.41224305799, 60935.181312789085, 2017.0545208481938, 37873.853105429036, 24789.089831128862, 21594.561771804234, 25491.17667045988]} +{"id": 1360, "vector": [43263.74824946014, 82314.27177107628, 9740.311462709062, 61704.75854100241, 38598.70542583245, 76043.49818853002, 80660.33278275642, 11868.584140333449, 39567.44349593747, 20631.651871488586, 25110.098098254908, 71023.54342123483, 68316.13012705518, 88727.8783363111, 28249.482057201858, 75819.27583979108, 90551.67691713227, 2038.0994040681944, 88107.83974548898, 97831.5858836695, 87688.7360062137, 33748.47089680234, 19949.28351819666, 68219.08664369797, 19292.20875520672, 11920.778312782799, 69896.95121700788, 13335.961829370324, 47536.14808497995, 30840.492698577295, 5361.7229070135, 31697.48587273733, 16106.32632300586, 68570.11158638024, 16931.06767626834, 70810.32927555572, 99359.15872126895, 7585.633925914092, 22629.317887300036, 90127.41061838616, 17111.870281682295, 42261.46404228254, 70075.33119752357, 66241.86705848844, 67896.68967000907, 82572.12792378156, 96854.57078824798, 81237.80125735009, 74693.83798086822, 68738.45748024437, 68585.26887823857, 31941.68101448801, 66127.71265859401, 65812.55357513417, 28424.14232913705, 59210.625466813995, 50435.19312252618, 19011.543912833495, 25813.289281004014, 14122.61179321792, 93650.95800015633, 8782.112009306686, 3313.476995385156, 93949.3111584396, 64905.25833958195, 70277.99042488873, 13507.397629742813, 92469.73394964302, 47335.03532368372, 52794.21394719073, 66173.86397070508, 73500.17686750636, 16263.186077504666, 65927.05308841866, 46884.82804590367, 72739.59615758694, 77104.25986744647, 55506.98227220619, 29063.88152927074, 85830.18351471264, 92287.78037842145, 59129.68395939352, 52523.57534510503, 94746.82656378257, 19768.15557646281, 40095.20171580866, 84989.37108179263, 47691.95148029605, 52221.790089686925, 65260.08608407567, 22227.728866080055, 56037.5596672237, 97676.12850968454, 72474.39534553193, 72254.80547887294, 21257.33825539049, 81202.8178692531, 75429.99554734363, 66561.50660725533, 22245.27379568202, 56356.921421357394, 81217.11113700816, 60800.4631326916, 82121.46542601375, 88885.56671883607, 98992.31388474666, 28848.307399952235, 86838.72144449364, 17794.456726239405, 5964.318970770121, 35679.403255888676, 60774.12858404617, 87437.6047202608, 51824.41400819045, 31130.33310923794, 63459.89892122531, 33864.914579574586, 43909.33527332086, 55038.002765250836, 45995.69000834536, 62434.12440813523, 4333.685563921142, 67506.28501983931, 37291.858022153545, 19109.4665320628, 6441.295470916686, 48611.31138802831, 60369.17845508621]} +{"id": 1688, "vector": [13422.060960569948, 48772.693642348975, 48559.811293283936, 54110.77824939059, 92687.31690397549, 14421.319680946399, 220.78201624420313, 30707.05473319627, 38361.19247422429, 85350.11346280089, 85475.36074274113, 39449.81468285997, 90331.0480050079, 97820.1464277441, 258.31751722446006, 60345.10075044599, 43412.68410079064, 66624.05139599081, 14616.405570677925, 54516.87342482156, 28030.13516862143, 75256.22996894656, 1042.5932853830088, 16291.116626575098, 94806.87570770754, 7120.050948835743, 79894.23470130592, 1211.3332934576947, 22980.0757032371, 834.2933589443157, 3383.834805602182, 99186.94146181662, 94174.41872009142, 55628.851539802235, 82220.03940653162, 18037.36127796661, 49688.580159807614, 32933.986713027094, 8493.938192237383, 70685.9841476838, 58157.63338131239, 15266.026069946214, 96067.57062992522, 86943.08666804728, 56605.801672538546, 88013.32947377989, 7884.170866866236, 68696.78768834754, 44485.885583786876, 81550.55537390726, 75159.62918067636, 36826.13973176165, 61234.5881956616, 3528.6590406454766, 42069.319437437014, 66254.67486807558, 7499.048968315614, 76585.35676954224, 97452.80539855966, 51468.82139848102, 82133.87858965322, 85098.88028359973, 88985.65099387424, 93485.50849593288, 68836.37629754981, 28422.78596791443, 56298.40752253249, 30799.34619857668, 93641.86842402209, 97314.0119664833, 69230.40144461798, 85904.70788791013, 19069.454852849678, 36771.01315490944, 7378.883053447549, 52344.42017336426, 73775.19222341427, 62200.33423833754, 93395.58667940907, 99707.35422331754, 49430.24007707593, 28921.49140525625, 26215.113645751553, 83023.20577945854, 79718.33145776484, 58034.46697156071, 52341.82361072667, 83205.01817504212, 37214.076168310916, 87731.21475623765, 15672.516160188488, 90867.01643731141, 41490.575990829304, 34596.07397333099, 99234.5106212991, 21462.881818262114, 54667.292199940566, 1528.138880145813, 22635.200179952553, 67082.41385122077, 98901.3597542, 93800.85684394605, 3854.364936849497, 91858.54003281229, 40170.51789431379, 92204.58842171644, 33530.04780383741, 22429.124744716933, 44440.92549373473, 45962.52375737406, 36987.57088161832, 73199.31099245267, 37373.77933870245, 54857.596212832905, 26977.29512246264, 4764.379500495696, 21836.87411321402, 18275.218095030086, 31059.543306851912, 79312.66837013255, 25248.13172450191, 36912.22388877462, 40923.40625439217, 41978.105588878934, 37970.92383134169, 11170.297368006244, 78372.39950831477, 4051.3213150697447]} +{"id": 490, "vector": [22924.60054033013, 55919.483945645334, 80411.56561472148, 69416.96961067342, 53261.05980777065, 63343.222119715545, 49528.70849288769, 53088.04267857024, 73461.24434227607, 25846.065121546246, 1630.8400825185142, 37250.1005627498, 52063.60776967993, 6296.604876444711, 30039.717109590212, 56259.219159282045, 35919.5661849997, 77464.24958261727, 28245.556123368988, 79592.64639879894, 46700.57107859479, 21457.1753475986, 64216.656039011745, 95080.19324351335, 97343.71099757506, 13245.381244544507, 34357.84533408497, 90845.678188954, 75942.49044552997, 68000.43261640616, 8658.799731403999, 42620.2924134865, 9972.535497634404, 12192.101967291392, 81283.46385109934, 94158.31487338853, 11918.92963491481, 94542.02635012333, 95213.82902434254, 79697.45663746723, 84401.74655371462, 63810.70984001734, 27672.666933356006, 28753.133932955865, 82597.34570501413, 69186.96100593882, 68913.32673597938, 25620.83949322834, 13044.36522723693, 11523.433049608479, 87949.61798783847, 79116.25688263995, 5660.470421485764, 76466.3891131218, 91104.7036533313, 15945.960375803925, 33985.25943868488, 78716.07528788442, 52241.32508914756, 65193.87279541015, 37478.23771915567, 34005.48286780608, 7269.627964984315, 17185.1271206399, 15492.287443681786, 63886.604508264485, 23832.616704827848, 3853.6393218002063, 73088.56340142676, 65128.12215272721, 67273.87979113472, 5245.173335570452, 18336.303704547885, 32411.77374420969, 3178.348952313459, 50581.047209220174, 99259.16956975502, 43009.74252892642, 66619.86336561009, 78.23511409124073, 77484.88672563912, 57233.23738746843, 74022.02508717669, 22143.418424087802, 27221.841436600058, 11234.836657102209, 84091.88560405586, 87650.86227733728, 41138.20977327414, 18545.427287625636, 33484.37095148766, 47292.270747073904, 57870.65313994046, 73852.04408888475, 82350.96304111528, 86110.27036302468, 65513.74471065554, 54382.870315662665, 55268.085423185374, 30726.491460721063, 62723.04651562871, 29868.10803030373, 2882.9963814028024, 83313.55389611727, 1788.2109061637675, 99646.16871853759, 8960.691198245064, 69993.19171868177, 64042.250040617, 16778.787283625883, 98939.87615514817, 24226.270751033007, 40660.50017834235, 26376.986861393925, 87411.30941474417, 96135.81243278379, 90615.76072934162, 98271.29414501628, 48597.04546654411, 82972.86717260155, 39506.83086119964, 76408.65344143461, 22637.243840852418, 4058.002522368931, 10856.094362456437, 19402.388518444935, 78063.84522664019, 34050.33750261131]} +{"id": 1090, "vector": [82902.52665061742, 98714.26662448441, 70221.87010760553, 38785.57226479541, 45891.13799881675, 26115.742679530696, 72023.2593526178, 42832.3347811703, 89335.9224968305, 41084.341730914544, 43745.2354604561, 59441.976285169396, 52415.306733578924, 28541.27318716746, 768.7450643993233, 49890.75167353565, 35753.31806185742, 92326.41283210766, 3564.394736270393, 37784.98607034717, 55883.28132740354, 97675.74527057173, 43157.235870577446, 1599.453914146276, 99728.28025106403, 82845.67129405588, 86751.76097894763, 90951.62478152681, 52194.01058580071, 95526.84865785328, 16418.154019884434, 13396.398638501107, 9212.143916640925, 63627.3454997173, 19321.75298044829, 68049.68975933675, 35396.66058513427, 37083.00529813018, 10483.423518966072, 68342.87729540419, 81265.93460185027, 39258.99739369574, 13702.584151498188, 89598.08083532356, 75092.45977378968, 91516.59905864109, 35861.68179234037, 1729.645822597936, 65908.51065449462, 83578.16283355211, 56691.22921475852, 79177.86499561135, 8764.735623767228, 55004.67324212366, 64394.33984021553, 33681.646860198874, 32051.913037129874, 8046.897430230116, 70440.99666891638, 82381.13885872981, 65891.79566847642, 76002.42917815801, 87986.37514782413, 33223.44826656408, 60732.468226481025, 87169.83975008783, 55617.09639271369, 22288.72947554872, 46942.70268953219, 13551.049017799089, 29285.964028730737, 32410.067504397466, 73247.79275267136, 24114.273477652892, 17364.528568944814, 13627.6209845263, 85861.37312570662, 94496.71475098, 3589.745694813873, 5831.013463046353, 23425.722872480314, 82193.00806075436, 25909.38135750104, 91106.52079402126, 93834.6359272805, 42193.94868309335, 31780.83885230768, 72392.87773794333, 91879.14688672339, 81348.81250374121, 16105.751398693936, 59332.6239147015, 76441.73068596412, 93596.8855083435, 9120.477289104223, 65500.158642974704, 28378.26339905445, 75280.08524167414, 68311.5707853297, 47737.82841970406, 46765.56793492179, 68889.81654775447, 29751.25129767996, 55081.179417188505, 97363.49086915297, 77767.63758659844, 14301.213096983889, 77138.13792619135, 48189.03165714689, 1755.5677315615692, 41696.24925674991, 52760.16526741192, 99134.41327268914, 96154.39101136418, 91277.50607106557, 12569.7268173106, 51681.25154241018, 41085.64679405889, 22512.493586717752, 13077.332194960478, 66708.58846643077, 59969.71075951101, 39300.24504124017, 8839.401193780228, 4147.2674717235595, 42149.05857504722, 66326.91821284365, 69623.59541502393]} +{"id": 57, "vector": [98431.06346628416, 20770.467728913554, 1579.5571828954523, 88194.79954171936, 40492.63125414995, 97103.17857029308, 8236.211481603328, 37670.077851302056, 96785.3424774316, 5255.043449018948, 84754.95373337214, 76961.28968015271, 91094.61314048406, 96550.6725832148, 58782.869001072904, 49812.57698617079, 25001.067340020054, 34450.4553393302, 78805.02187582431, 64215.45499086216, 20567.846040814187, 14115.922380720158, 81140.03073304825, 83245.35972150728, 39722.045813518, 83632.93534987983, 71935.64159867991, 25801.968368973972, 48583.31919403717, 99259.96817522604, 58631.22746741316, 76380.21377250842, 57947.3986968961, 69246.97793355885, 89163.46770692304, 67802.99759683857, 58069.89895351303, 94457.53614037062, 97385.444202566, 47641.74657269503, 84365.36483153496, 75762.54778819649, 88668.2289356374, 55787.59140740502, 13217.457511579878, 20829.988406888722, 18541.761433439817, 85078.17496129981, 38461.56412336083, 17578.116272315146, 99209.28114532575, 145.0358241770422, 74817.90627577965, 53755.829827680835, 27190.06445987222, 18857.58648667677, 16583.59239736008, 90043.66895126806, 7645.8776348785395, 55433.05958799691, 48179.75122487099, 11441.679485983013, 99618.02358952347, 78058.57300760075, 78081.8603004183, 91608.85028945634, 36103.450974793006, 78708.7741608053, 92382.58078843883, 88457.89827587032, 79175.76210893372, 90888.93618027549, 23229.017411079378, 35839.75740235501, 49691.940569393, 36420.42737949592, 94422.63931649, 85282.70146790006, 65815.40915124072, 18357.96028146511, 61206.57104135016, 11588.574109175719, 25239.630006949454, 55703.58036478914, 81857.84737430733, 90761.42668650109, 31286.16433833824, 68801.4562712901, 93083.83549795025, 74542.50998677975, 68736.86686160776, 79541.53513806808, 67430.8231936553, 80826.44734032435, 89913.89555322586, 39380.17814528521, 91572.88345338962, 7948.064316284597, 45591.57045662135, 44236.489753685724, 48757.4292961538, 58093.48993066026, 19463.173588667283, 58498.63554800285, 1158.9185297039783, 60712.00726508764, 18988.365669142215, 2503.678684889932, 69060.21042945984, 30322.506635728896, 71808.03286990826, 47073.05018469289, 78509.39537260422, 93270.37338495476, 18191.280177496526, 83315.96937812578, 42426.366589252095, 11599.39517195846, 6972.657349864053, 79393.17660953497, 41630.775366607544, 77400.9258057247, 96954.6521043, 58658.61791567463, 53254.03265600882, 14765.856279096035, 60383.58292297125, 82730.41392624742]} +{"id": 838, "vector": [57032.104126368686, 41741.3552594339, 8150.6430449792115, 61421.61471814723, 12654.317826657412, 52454.21095605343, 16993.66177505488, 34432.34472010324, 86686.02953131564, 28295.24311179844, 66993.51931598558, 74035.62092777881, 38101.12267887857, 4075.655703700165, 94821.7459454036, 14913.815928408536, 82884.32315599972, 37659.361514856595, 70113.99851916412, 82746.17488865199, 57022.21764098811, 24525.911858487936, 7797.6958359301625, 77399.77010247174, 69291.42440835721, 77304.92037619719, 39470.68492495983, 63607.113688595426, 64689.833064248014, 5105.050301515957, 82770.69170442902, 81234.79592421713, 39324.73811520049, 3687.9623122186954, 56005.700830482216, 53341.53494399515, 79275.64452987154, 2877.163199759225, 76951.30351691932, 94314.4797521233, 74018.68862985392, 33120.179747054666, 14276.623724363879, 66057.9067716301, 84335.67755813923, 2072.4539668739108, 94751.19107652044, 39179.60066985004, 33877.091958310324, 44169.70362037824, 43238.072259405235, 91568.68318349724, 98120.24518949163, 58457.43208453709, 14049.473660122057, 58999.58809426238, 31596.83843354716, 97007.77717933929, 15473.002742728282, 72209.91712177274, 80456.28171610356, 12564.91839658157, 50642.132878848344, 55504.44913416003, 33453.87058813671, 66122.77873055398, 77272.3927364218, 22942.66613277548, 70026.65543104826, 12547.636851497546, 21579.314087050207, 97223.13442970786, 43106.70956759697, 49634.703995971744, 59076.170667802966, 87154.61366750757, 5902.9885787382, 67373.49001567782, 63011.67037316046, 33665.45493375311, 4806.087137502002, 32264.57910691797, 26864.485011265006, 81284.32893932937, 75594.29162590965, 90606.15912242024, 98854.45288828608, 2486.6709627775595, 23127.91498517599, 90166.3601650825, 43074.78283321465, 79491.21999849044, 20093.763977362054, 54790.54343010752, 94737.57315848948, 11228.490145079606, 40463.96791967315, 58587.595965366854, 22450.947598314964, 28492.05730083686, 89385.32317293264, 28202.516326301287, 54657.988919375166, 4234.930116093249, 19799.296257709808, 55700.99769245122, 71208.88629816666, 45058.03508379315, 86470.07487546984, 23307.90082918246, 39668.02979453753, 34640.7772678639, 42638.725470133235, 17314.56405370796, 63063.94456025433, 1554.201748148054, 23115.469860336678, 50602.88590635035, 92087.49411917444, 65168.69402524595, 40196.332167241024, 16204.261380249174, 36942.509638847674, 82146.12039197635, 61614.53057210856, 16086.632565247317, 95856.91585223678, 16326.7867580838]} +{"id": 499, "vector": [58963.68724249844, 62745.04253770698, 51431.66986110059, 81912.27997303038, 24700.797602480252, 27640.5746424398, 13325.951780073541, 39930.264409866875, 27155.333816828253, 32908.503399008194, 52994.43474983856, 64862.4224769788, 42198.80049916731, 81877.86510808888, 5040.4188839637045, 40338.12540595062, 22907.3763471915, 18008.296812453118, 56787.82286306141, 29881.731894519293, 18448.697920373823, 31195.598551586056, 96454.34490648333, 42991.30172197732, 20635.44780607851, 51328.44259270175, 15094.784987197996, 20421.84192228208, 83528.70174884208, 21660.567116215556, 10297.676058072258, 79650.82101787496, 19660.735470599844, 27466.13168990719, 70585.52952995765, 46104.277984778164, 31421.774102441468, 72734.55492729177, 15082.172757143542, 43549.00703053256, 88082.09329721516, 17826.387734549076, 84825.28692752527, 68870.4868251032, 75409.83134763561, 33340.053252304824, 76065.69979276943, 58854.66286133886, 55704.60505919109, 70859.14148907253, 22046.944796059743, 37305.00561374812, 59199.12981228102, 47405.94402650705, 48712.85745964017, 87172.3926735423, 51482.00076188531, 62066.467700493115, 71586.18107336268, 23024.534657574357, 59050.08757320609, 29554.983952505885, 87905.29863199819, 5772.3765740095305, 66992.23261201836, 63783.587302345804, 61143.14368376834, 97610.4608933276, 68221.22959130838, 78071.53528649319, 17840.747052741935, 20218.058816503613, 60827.74203123739, 87807.26546026704, 33152.67828032431, 56601.55937793026, 31914.494824233665, 82195.80221457621, 51205.07755233794, 7324.657556692216, 95298.13321550965, 76175.44960901124, 2705.785538460925, 85325.79652961648, 94733.29179171553, 15768.648613958725, 50082.98693336539, 99910.55271784545, 50327.582338936336, 73836.04516930526, 74320.77918348006, 77341.72432016887, 21720.723665906215, 41061.40770486195, 47669.40628961841, 18867.476807473293, 81085.23052935621, 47877.779528864026, 77637.0103982306, 97377.03369121531, 53067.0622897861, 25964.749919741604, 97947.47937861326, 46849.84620648963, 68259.44592684026, 26337.834504270486, 76765.8482173023, 31748.7351325914, 56374.51428309135, 49817.10852743188, 78912.98707223922, 24959.28961921412, 97030.13820140119, 51831.09994320012, 69041.45614593635, 79927.14228693038, 99973.99630668602, 67219.7430528328, 60355.96471487309, 90196.94654897237, 1297.539241513268, 58441.26031220187, 86892.65306741172, 71016.08307700185, 84711.45339881215, 33267.50040570009, 31336.9116992775, 54541.66086484304]} +{"id": 1993, "vector": [61018.29000293258, 56453.45653136841, 26430.548957135925, 23450.453020246965, 7596.450196494464, 93133.48911287588, 95673.46641020685, 36742.34548099118, 3169.6440753399834, 23146.085404760997, 41633.12233818626, 99314.51090395887, 75530.59669169347, 4080.955772086725, 26316.0176150407, 57533.7964017816, 59249.6109700523, 56222.7028938513, 29212.631266177024, 69167.73789373582, 36473.89573215108, 57855.82081218508, 22168.21388129101, 68257.80430168418, 15557.341465670437, 31722.734255985008, 34278.476167884764, 48862.66084645139, 46077.61024474998, 48333.958681416625, 58895.14485053037, 58720.25344379641, 64027.55453264092, 90420.75572305037, 10872.146257774484, 12809.191645496487, 63996.09255468821, 68751.84143997595, 45352.78640221648, 28997.35121419912, 60793.38897416395, 72227.83958721293, 51223.96621993077, 65145.748301185704, 5252.279346215661, 9678.074916238565, 85264.16575891079, 86544.69181838808, 55528.41693527285, 57516.842540084246, 5513.059569996892, 42326.59621589693, 80103.73729658422, 46186.24756887034, 79307.41283500844, 67307.291788143, 30567.969877296564, 41696.56727054461, 24761.634696684854, 19462.9787311818, 51135.4404048002, 30400.48307289387, 55787.39095500517, 36059.47022675913, 67337.8580061166, 96758.99152402487, 98600.34856088737, 24667.671943859114, 86491.41869689427, 92723.91865147417, 92939.1806657459, 70996.60577806363, 35610.42531777333, 59226.087010180694, 69799.92027675892, 27602.589840704815, 33778.26702184444, 92231.08017792065, 62626.02867400192, 85159.40802936298, 43876.326615491824, 88674.44626202478, 82016.36316311845, 2120.409049149885, 91145.69776480467, 72021.9169823505, 88671.0441769583, 96467.11801056746, 3295.0510256189336, 61945.69950995954, 2262.39563312568, 62772.15802550342, 26641.91965765469, 32905.93767913323, 59369.9239629394, 14004.174126696045, 52335.57703816083, 57569.118864221644, 66147.14465085542, 25641.202680745977, 31285.667776447703, 333.97307041980184, 82062.66473288492, 3825.592574022185, 12445.835966386765, 83648.06053638732, 52963.40729389084, 34600.28722503645, 6233.28153176903, 47003.92768132656, 27593.735779790295, 52368.81896037007, 29664.640959654276, 69678.15386839068, 60755.52892233941, 53702.79943670086, 37398.05576830524, 30688.258994891516, 82169.43526156856, 65330.73196301089, 21945.5870041644, 4940.455171769187, 68878.32502220348, 82414.83967916126, 82769.96731350129, 71641.56499678755, 91666.1701646931, 55931.66019312691]} +{"id": 1734, "vector": [79569.34860827767, 5511.180120467385, 2852.4822702116317, 81060.91774259946, 83903.47062230793, 97701.01138471898, 34990.89019184557, 61714.94205251728, 40010.52301457367, 24961.444377968157, 50829.998220259506, 80876.66287795645, 80364.61642228092, 1965.068941276482, 55963.37641429385, 12004.711940085655, 9139.054872099729, 79491.91276541841, 58927.10080247762, 82396.65031849634, 4128.60485514992, 739.4858429318418, 82132.77539382529, 83793.11080351792, 77381.47890632639, 85841.44395332056, 20383.50101743377, 50061.88405329246, 8079.539649726519, 16048.866251829786, 67279.50474975692, 69377.07425081971, 70165.15972719184, 63585.0493223875, 66113.37351940678, 80574.14085682984, 69385.61364683665, 18632.342944218417, 93669.89969128426, 31586.42334067243, 64385.27656526937, 35492.75179060275, 4631.161414449025, 2584.625172205113, 15579.63684259207, 77059.53961337151, 82626.25021826256, 83142.29152835309, 17557.234560453526, 95339.73007716457, 48697.046928688105, 64468.43595542798, 95150.56738414335, 47644.00235309794, 21767.27034477899, 17092.76079634078, 58881.61192166896, 70659.96383913164, 48872.99668152578, 1197.6156848584108, 57.25596550198997, 725.7021677807951, 1736.9421459583978, 58213.52120462695, 99213.69746624675, 29265.959002902353, 27277.051220155834, 42539.82110342993, 21233.19227513103, 80956.66110121696, 78444.85636161098, 43810.41538939772, 78429.46475220688, 60598.899405773824, 3507.702023019732, 5892.599623484362, 63259.56984439988, 42570.38422465369, 12376.581108587192, 66804.77204960921, 25465.555422204212, 40712.55901029139, 27943.162721554625, 43055.10535345966, 26085.73748426505, 60798.860733412184, 36744.28428466586, 23511.64921924478, 99753.25931908752, 87439.92099369229, 71116.24668726281, 59806.28047674944, 20407.99345829183, 52585.51953078945, 17808.156968989795, 79942.06183390222, 64875.757114644286, 80510.22136450284, 5105.150411602255, 24352.933236967845, 27433.04522842912, 8431.065185575848, 99495.69954767945, 33318.160720942826, 95485.01718765093, 53236.66181295389, 69628.0510871114, 87559.6839939993, 3603.3398648123384, 61132.81583735697, 13129.449385369408, 40381.83619671909, 32172.972998117853, 33131.345181076664, 84128.66852426116, 11051.106386964893, 46352.46419490984, 87028.23864009015, 72844.76662798064, 51354.51243484222, 98314.035482894, 20246.37164169927, 3302.874269479572, 16891.224986883903, 23524.89264492975, 23569.598334298615, 32068.097382154716, 76997.24316077393]} +{"id": 703, "vector": [99413.48789863237, 4667.851442816584, 39507.41141931702, 82168.64493043104, 80412.12405910327, 96147.39082404255, 29380.202423848812, 79830.84032051408, 56595.359019683965, 83709.59003235806, 99976.32235644072, 54292.54616014412, 91247.40843342767, 18343.299513799826, 8165.958678176832, 32624.65978023552, 97356.95652527953, 15154.27743021055, 1864.282768225023, 37114.4456968418, 18545.823983930433, 71270.39707749656, 50095.0536398914, 85346.52297355617, 41272.31797526719, 20668.285713736823, 20894.687320815432, 69891.7132588811, 3075.9793074940035, 84411.66517709193, 61882.830545986064, 87835.75415047999, 31385.99677230497, 12836.57479561141, 24823.654187377007, 34960.32790624323, 75615.99438593896, 46767.897075029075, 91923.11679979005, 38045.1147665553, 56728.16911157762, 46463.06427015124, 6182.614836709011, 16732.746493549887, 2906.1865971160673, 84050.72122794796, 14514.84275438224, 32756.516321464234, 69870.36954396301, 47041.822517658184, 36240.512386485556, 8269.38122264751, 38069.83207394462, 86982.86848294354, 7950.368554167852, 85316.76029803579, 97434.59087464071, 9794.038181572185, 83284.32935146385, 46302.919437199205, 8078.326916010681, 87265.32488919793, 49456.370416466445, 8767.81341638032, 88957.1969853331, 10364.720386488125, 86691.70159752663, 27671.434849670884, 41613.1420557737, 36861.27222551785, 45548.04370708199, 1760.8081407343045, 4336.266169082825, 55417.4376409238, 69910.51867769846, 35701.78473252087, 97054.00809341657, 84152.06999285378, 95106.91806782247, 93876.96647843682, 4089.8179911693087, 40975.017893907796, 78090.81706115916, 98199.88602658574, 31787.481529502616, 63253.32285610248, 43148.78700926202, 22360.66811818236, 24954.308289518824, 84516.54805018926, 37584.979065823456, 99647.36059943531, 50520.56211881946, 91399.090484957, 52940.49949404966, 27941.387718927468, 72141.62221176783, 157.67281263922018, 16124.779594482497, 84322.26454164015, 72253.59470078826, 80897.13953387071, 1704.2370258778749, 77372.98275922518, 75216.47889952532, 18975.66204351674, 73083.39181089081, 24392.9952226142, 91346.58664118918, 58479.831115281, 83297.77448583227, 27166.036589242005, 84168.36230287237, 22618.30495383699, 26141.96345567601, 32469.972794035308, 86989.38039188046, 2253.236300653405, 86070.78877535151, 12670.176389169495, 66990.58673507522, 24915.464443403267, 40779.109735062644, 59644.32835055185, 22905.32145097647, 51448.056065629346, 78916.09172699694, 82430.91334686814]} +{"id": 1236, "vector": [76408.75007503247, 75427.57378862172, 2574.810575163, 87260.354639193, 9974.51825696617, 60456.52785433855, 85743.59902556232, 86672.89784485492, 66382.05917183912, 78662.07018990736, 57333.960344237676, 12333.131237108886, 7961.528035854837, 28000.56620322623, 22625.781121328404, 8386.17222966207, 7774.171160195798, 27602.22889877888, 13227.799140323848, 41307.91130605618, 8097.568969872593, 96624.16787437293, 777.5873467138039, 13580.17620715809, 8120.048515111212, 36314.988167063864, 56192.63355783592, 4059.611622511039, 3545.8914922061326, 1360.5630952048919, 71738.58452145747, 47634.50201710678, 53512.43035732499, 42280.46768817855, 53242.180666523884, 36388.464762748816, 15605.715604173687, 82434.39138634315, 24860.115738273293, 65081.454164362294, 24963.638875225057, 22923.254142254635, 28015.610880245113, 35320.679188257716, 59086.58436975285, 82671.04012884475, 22590.38410421779, 81202.4546041762, 50682.62480984332, 25175.582384729812, 48476.605723242115, 15955.177703105239, 86190.4879901231, 54405.43370131321, 51313.65383683674, 3469.921032994461, 9142.139958210615, 78904.13637889735, 34987.4842547421, 9618.782362754353, 90015.9659720471, 53813.363568325076, 48432.450027521525, 23989.437552653515, 15064.48435895158, 28648.74162637132, 56977.50791467017, 24517.56836599458, 33530.311861799586, 94479.06190257074, 26388.27473087345, 72309.3584196705, 87942.15672279445, 77094.0254392746, 61613.96437700724, 81417.04360619203, 24769.838386645348, 61217.81058817748, 66780.4030147738, 4303.957196663843, 25084.004079130307, 74120.17286598135, 76225.55008800667, 96953.03438558258, 11719.682985862422, 50810.868432325995, 84436.9564230452, 99856.22047964303, 25428.23551208161, 56483.22482806404, 22945.02690676151, 88417.60952973006, 21680.036007877712, 30918.107688897344, 15700.915089138545, 28187.940912906874, 91902.88587154054, 64308.11202519004, 61973.995994454875, 58778.27645791314, 93493.13534358627, 53005.48482098933, 12957.14553879741, 2180.580654093789, 75241.48965998288, 6947.195439075249, 22399.187589816138, 94555.27689911127, 52481.96087972039, 19811.189011572606, 69914.68821900916, 64214.639484532585, 78523.9127522731, 71405.80300632596, 54519.373287503695, 65414.63991603579, 67071.72194229551, 1902.7898781847296, 14455.390645889165, 13005.067278139582, 84214.72695718736, 374.57093171764376, 9631.019195418678, 37344.606674678296, 71200.1868296696, 13762.091698318569, 44430.01671562117, 49594.78429171896]} +{"id": 50, "vector": [81617.58647064697, 18277.275363815836, 85326.55449726057, 29826.003026936432, 98445.05851842657, 494.70183942381675, 33867.153434597145, 30856.529972488013, 37885.15649857559, 4805.6852013734215, 71859.67314628293, 52764.14248027706, 92673.03486651748, 18490.106158897423, 47455.2571911813, 36669.74567900687, 56099.999108272736, 7064.952023133086, 71959.49036675139, 43622.06304333859, 28891.024940345065, 92770.61566233948, 33237.741492130146, 34846.087143059645, 36616.13816079371, 85608.63408059122, 43553.40589635782, 47607.72828202443, 68016.20394826298, 90739.78315872619, 30012.72014850055, 33409.187421380506, 38139.01120193799, 5395.133769766636, 59884.21818657089, 24077.80972078206, 1143.9950248563036, 57815.264372283804, 75517.65115661627, 77233.43656820049, 63739.6169009596, 34712.84558361869, 36981.77443784835, 25610.119618222838, 5064.615431460584, 63826.18308339778, 15939.327460110442, 9303.258966476735, 77028.5190379742, 18522.293102749954, 62049.14431890007, 88719.82340430986, 83525.76168700903, 62781.936218673465, 8936.657895017941, 54079.1644802429, 82368.80561937293, 35775.409494535925, 11599.83743736096, 51999.63123911834, 16290.453782935032, 38160.362103750624, 94551.03814642508, 27931.953331230343, 40758.83309521775, 34104.844699483736, 50450.94671674412, 31233.00992857848, 35456.49484467237, 84572.90364074081, 46965.156584762815, 90391.7940935423, 46994.912208620386, 66300.54604821562, 82460.58374369112, 49416.02858169938, 90193.20547369268, 46481.20386501976, 50419.92983573132, 31985.65960631511, 69498.94163518414, 41315.764618534246, 36239.76665846101, 7555.747726170425, 54020.705370833035, 53781.32746242537, 96900.73398602837, 13817.697472712409, 56991.95079863404, 92424.87885548832, 43293.687081990676, 56613.97465185532, 78953.78192633193, 7648.350307165141, 4355.5416482196515, 64525.57333147498, 73649.17495297134, 31514.399592615606, 70179.18350809271, 88682.98661589701, 3318.4411099858926, 22873.87315957754, 70753.03968960007, 69919.59147622647, 76168.24274053416, 79593.03898540493, 88248.36855509304, 98910.34966039131, 44987.92251617613, 98072.90393320535, 60250.4760666426, 16783.32493996223, 66304.78043643734, 10756.931587858964, 75302.23930544846, 90054.37905861113, 79453.09817322789, 27268.649435524072, 68630.45285039746, 83395.6247660148, 80660.28849064831, 26088.63780859624, 20110.490671159907, 91684.7854970027, 56263.07667722752, 83657.4451335971, 64631.47456456464, 12400.260485453196]} +{"id": 822, "vector": [77281.26783235806, 60720.693469546575, 91412.17842509804, 43760.55737219463, 48337.74598441493, 81303.17816536342, 79779.04691030963, 16454.528419044633, 92897.10319531376, 64974.01686577949, 62051.311388525144, 15818.660221762248, 32286.40359841515, 69428.71473627325, 99565.2045047039, 35464.66360445984, 84027.50049108463, 59458.474456206524, 91399.19691422039, 42919.776386615194, 63204.07758319061, 88223.01548535633, 85485.42961934497, 3434.9437032802466, 8177.356130256608, 39673.414793471726, 77695.47589508204, 69146.46134514514, 23417.328034526352, 21066.715072281404, 36169.35069620495, 72134.4396775131, 18179.778995881512, 37994.97242594557, 78049.15379881485, 52386.8355122745, 90891.08494791819, 76557.01240475297, 50498.68243203687, 46609.70579762747, 22701.636312941886, 1979.7534838820297, 7697.020115613773, 12088.990127199884, 45555.44834251384, 15355.439246295355, 99888.3511733435, 1586.2875273446764, 20994.152795114584, 28816.919763057424, 68601.68208837757, 93223.0579349898, 16176.11826551264, 93833.26217492386, 74765.97822246316, 96708.08547397217, 85897.26487924776, 24505.50059877622, 92742.36095157331, 60844.74789803259, 8271.09841831234, 94771.68926759415, 88230.98942415087, 43410.619607808585, 60735.20438349403, 34763.06393766001, 11135.111694675448, 28611.39983054104, 79109.07977783795, 99158.9916561544, 44388.132277443685, 84755.91295841879, 27582.90052472334, 21782.50081759192, 21834.5713605563, 90174.61556308303, 3852.008762117487, 45463.500076371754, 56222.70333660958, 6439.667051554731, 34540.86847738983, 47049.252644986926, 65545.1980817734, 771.0702031991423, 28297.14681022779, 19986.11815992387, 75329.50585533377, 28888.80703955481, 73103.80977830767, 87053.42468760276, 10144.39027247569, 59733.84368879427, 68443.85933574889, 9584.014053261968, 22104.088392410158, 14659.441950277918, 58499.368842877, 64090.93649539882, 66434.47871424904, 87225.29855179515, 6475.539636759664, 37836.62435908711, 52336.0781661771, 25597.185220064133, 86845.65097135863, 24600.93239026525, 83413.0443372558, 14245.458009848733, 31468.840138056454, 37050.02788141325, 16375.283886330639, 60833.84774990577, 6889.142836513096, 9839.03667852405, 3890.476766908457, 91544.10772743724, 35677.89731667175, 15242.024609734872, 9522.668832687741, 34883.25588038488, 13515.426743780801, 65217.8153984805, 82632.99419163089, 53596.938435073906, 97617.41060201441, 93906.91015249155, 68280.84050539286, 9000.97426549238]} +{"id": 221, "vector": [18985.698734253587, 77361.81455313697, 4275.904680598653, 24992.204846753564, 44267.60317273382, 42727.54135268804, 64441.25303345963, 85387.99364313694, 15894.531756082486, 44004.30604156155, 45442.8282689446, 97750.011018998, 84123.54544325196, 99605.66868088952, 66378.93295149194, 60167.397371164465, 33044.477200515255, 64659.65984851717, 69715.96677041774, 57728.204025316976, 2110.118946441486, 98398.95748045185, 34317.53113936269, 68499.93142864098, 96808.97130165368, 87437.91153388974, 19238.344167369214, 93772.77073510154, 57618.80553933714, 14325.985581320045, 3057.511045116068, 52677.072125260784, 85628.6198168884, 85264.45922012125, 7644.240014803317, 49416.779893528554, 78670.84294919518, 72968.32381022922, 28974.029748665853, 33149.70419714006, 89516.9857674766, 8141.004672448693, 41730.033097474625, 45350.97919719082, 18242.62175639847, 38116.81675940116, 64549.3935792976, 36096.75637908249, 19416.875612431006, 72430.38874113833, 66705.50392377343, 89974.62084139771, 45261.42830553929, 90780.69369730084, 25359.145748728286, 98501.44794359696, 1492.8134925816794, 88870.46607975937, 43926.58403281239, 37230.9528437594, 97124.7386774812, 52553.323427843425, 76604.12905768532, 94896.92906128262, 91305.17058834253, 83531.16394110925, 79161.90863390223, 13013.43039894507, 4686.472626232374, 92281.01790524319, 41515.48393319259, 50858.205348328, 2278.3308372291367, 80723.58302782217, 79302.81658581633, 13972.934834425943, 39735.01639909127, 43529.4474843088, 47890.68654030342, 57204.162504252374, 58408.912776034784, 62249.69207594425, 65525.07402210563, 50977.654630549776, 13804.917544506723, 10205.246630446996, 7718.184825002228, 68113.13822491263, 46450.2151648897, 60400.698979981215, 99119.43933219853, 16731.842455422495, 96545.95344178606, 50786.240400801784, 68176.93590538061, 36029.498520883164, 58702.851170537164, 96861.2915385343, 23676.575726409676, 93868.09086592816, 64129.685494540456, 29147.170916503062, 67518.87937230928, 84528.35578854592, 52675.99145559786, 81414.96265391965, 70934.51923504594, 45785.36301760137, 49149.33509224163, 50995.95380771238, 34894.640508666045, 94958.8292810381, 11197.636646735577, 72885.87545096483, 27824.41299727455, 39978.426520296984, 16319.456414982258, 35634.97025309271, 76429.29186492637, 38140.25660384876, 65318.275381974985, 14823.153880828899, 77286.46200028903, 14109.03566604983, 87951.86318585218, 4958.84803092459, 95622.22990374839, 60619.43973475089]} +{"id": 1254, "vector": [18317.757926953782, 80114.3601557377, 51685.47128231258, 62861.19709997416, 31625.135184300056, 32885.301573860146, 80736.29340461786, 98095.26970915959, 81092.35565384386, 66699.57055347918, 42669.9457172665, 66577.74164884399, 77081.04910362454, 6930.8870383794965, 77718.09596356774, 43424.2198470179, 77815.65444976985, 26401.792104321452, 20960.065842478205, 19041.143569050124, 49654.10196640601, 62272.07269381922, 82826.05461541389, 87173.54021443045, 76465.23318457509, 48552.99798928048, 33303.485542020964, 53874.68456397897, 15253.407060171887, 75996.60442820592, 53684.45334838398, 35116.327065531594, 25362.28318246716, 86830.85562462005, 88699.62572746191, 58278.213066108605, 262.6125367931054, 83426.15191064929, 43189.38001240784, 58073.972401368024, 60925.12638529807, 43965.34138982329, 72963.23261705316, 39924.46012709967, 56193.55054908171, 44868.04085225411, 39875.39026402475, 71031.52773305548, 2454.517648954213, 17147.059960188813, 96413.19966070153, 22347.403342170168, 32735.729996343176, 1593.5138216442547, 47421.5338709935, 23480.188175403782, 79228.93954153315, 15344.21136226648, 33608.87360199892, 47392.93631223904, 77848.28848267252, 69809.42400421483, 22932.05995410942, 94923.56630804668, 79187.8723349655, 42820.14947685555, 5420.963366914777, 91040.71771772942, 45283.88466114488, 28066.8550377005, 20558.195255244926, 62249.260234964364, 80736.7241396838, 51961.6971626781, 99174.53527338919, 5207.617916998308, 19557.48414594689, 60821.16681657152, 83.69950822290085, 63719.4748753177, 38541.55903286523, 30501.69639392347, 46102.02507706844, 77440.5479688493, 88342.40943899806, 14222.807951861338, 62906.72422387457, 62338.73312308359, 54007.12658656037, 37293.493410862044, 43385.32980905824, 92769.88498877443, 90210.33710644713, 67909.68748495403, 47696.33317935935, 59262.05494902892, 64296.52308472379, 55329.628222321226, 30398.285472958574, 42795.00736816292, 39077.017768579106, 86310.91919403106, 72277.3748223615, 2996.1159209021757, 63818.592505183755, 3460.681893702333, 63440.03088686181, 29065.226174680625, 94484.19570463248, 72819.58900039726, 30065.37186953161, 19960.79691311423, 49688.93828411121, 48202.83306031305, 78429.31835814193, 82254.83305927178, 61425.37418336398, 37532.504841944916, 34154.986626251106, 25421.00033503838, 34592.29588521215, 23550.143886227015, 28218.847066143215, 42921.78736272917, 91244.24909002325, 36427.48437184653, 74355.32306364214, 98818.79892419047]} +{"id": 877, "vector": [77166.08075780704, 26583.40414287352, 99165.76552316692, 74540.6308819017, 93736.0494763593, 26888.3878955547, 34808.69696337903, 29347.16057297555, 51540.89915877076, 8720.676111018665, 62676.17311443608, 41050.84568603986, 81381.41256078669, 43072.1655333385, 82921.11200038389, 931.5771439200815, 41138.45893122518, 40312.601303950214, 35353.82373336978, 57536.63158391348, 84966.48787825542, 63839.9090497362, 55791.33936262249, 24440.10653309199, 92473.41881333999, 97503.57918787352, 65736.18541897663, 85609.52540030162, 13735.677466790741, 84001.78830476225, 9011.893409009253, 26930.37305172873, 75209.48321611014, 44248.26271432302, 79350.52197182951, 70561.82375909614, 11249.727992833592, 33438.515110157095, 35174.54743028725, 57280.61541985458, 24931.097359078747, 79981.89633908872, 10905.284145426163, 56741.20310390587, 18943.023501618594, 87606.87753737085, 51441.747794906, 38740.25321133845, 88374.51862621366, 46117.86276494556, 76133.5672665839, 27736.59574754971, 7418.6595005980125, 39937.357756035664, 56921.03192598135, 67606.68996009447, 23100.817008957285, 20424.182601211614, 59906.247936699234, 48882.76123178017, 68623.37581011633, 85697.8422647942, 55709.77066868279, 49102.01998130983, 78682.76034255314, 77770.52437241092, 88566.1562960976, 58078.22024346139, 83925.72807074685, 53887.79472021077, 55534.55140762451, 10342.96024681277, 89562.11701263656, 68883.08922946814, 58647.22275538926, 65307.04940089912, 1043.9942239969469, 53639.226208831526, 99571.70322169963, 74570.04127395981, 46719.35989655892, 13545.335055238016, 57984.339701549856, 33072.804294873495, 28219.420377903993, 27114.34811544381, 82612.9896027613, 60758.86612292728, 16645.848797100505, 80184.96136379555, 68325.59960817258, 78009.21745428318, 18500.687711777053, 73184.50609231673, 28602.75941551934, 19940.95876900861, 84324.43280271378, 80200.7047374485, 58218.51239049589, 35975.871266922055, 17374.481043925705, 11662.560745653982, 64382.11068197189, 42594.021666264096, 84413.1829462427, 74225.93419649641, 15655.139836503151, 17729.152883731535, 55994.236360136376, 32951.12876691794, 32270.60866781354, 28427.21513800789, 47108.41453067143, 35757.5318831184, 37692.28823186167, 75847.97357533478, 35217.891256239694, 87751.08845212824, 59222.6323520152, 81209.05249977966, 87414.76216051329, 35988.813976928446, 32839.77438824793, 1611.1841752796895, 74315.63833804011, 93136.47425145582, 35304.38658306233, 61.045904123580016]} +{"id": 1982, "vector": [79782.84704945656, 41503.27677746798, 53366.423564777935, 68002.1278367019, 96492.65372651708, 7314.2146676659195, 73306.81032058108, 96748.77680943246, 39734.28615857009, 44513.417733037386, 59964.15805952768, 63168.19517861003, 8870.130738941929, 81396.43353895564, 69033.0605303113, 28522.92289716979, 76055.02060514942, 69023.79676155737, 31196.15862114451, 20514.88033335799, 57349.27442927432, 73934.60808114427, 47748.09157075675, 60988.27701701528, 73692.91593274048, 51264.27112035188, 35133.087444959245, 19679.77890398931, 51100.22946301617, 38128.29883469814, 33715.079757943764, 94304.2646947044, 21307.479465280965, 97210.0696407223, 39550.651893546106, 1822.572191111349, 3435.758304275971, 15410.027085307054, 32197.69298429004, 68197.66697821049, 36051.32897429862, 71352.28075634205, 28600.392995569855, 13193.074158838292, 25192.347907481195, 12502.303168728335, 19144.940361281882, 60141.36638356393, 87962.26885911303, 81818.57946009385, 62687.06247726249, 61666.971363484656, 93684.0615895059, 96274.70657598686, 98629.64889032384, 77575.5549268671, 20929.654307151824, 12137.036948424473, 55763.69394736618, 35864.631568669334, 96674.20994251284, 99795.47969269572, 68048.76161414426, 35866.85868027919, 41572.258037883126, 14952.638386982819, 86791.28331654477, 61107.330074403646, 56219.217077207606, 68624.15089505899, 10330.362588918906, 92185.12523317631, 19821.15592760585, 37248.219587228785, 23532.698857936175, 46458.892320339975, 19552.15532675787, 35387.99662755185, 87708.84965256478, 74613.74210905333, 57323.95658853532, 11190.1409202121, 73810.45634807377, 86161.73119439842, 91059.76474807339, 14614.752785177554, 45613.65453630737, 84955.71878868854, 47270.29319509821, 11638.659945625852, 15943.7039363299, 38110.21877948629, 10927.588761408591, 98970.55162556314, 51485.50766583164, 72972.47032516685, 94464.59397250321, 49162.95057570422, 79593.60954989793, 31642.033772736522, 55316.48411739477, 97431.56126830554, 91776.60149864857, 58703.52760695584, 66618.3299427299, 86252.88381133579, 7090.4974151208, 7027.170814192729, 20593.711451522133, 1038.2907089961036, 30510.525817122612, 25122.273255677064, 97510.33517683505, 62627.46888885732, 87539.0391573921, 36624.49919723146, 25574.465110180565, 24857.05127363336, 97953.83756047285, 81190.85316558168, 15635.496966305373, 70021.49986448827, 89879.40819590513, 2853.425050907299, 1204.2400070135461, 47360.096724334246, 42444.439287188696, 88287.26448659578]} +{"id": 1967, "vector": [31315.443989994972, 62049.097787282124, 49015.65744854589, 84886.34311694039, 39491.995370570665, 58343.56525050196, 3691.120413336535, 2958.4058081286057, 32912.86835850195, 15216.521994754406, 37587.32436071422, 18468.370613743202, 20023.467362645853, 64306.46072680015, 22618.67202059762, 25441.594103184838, 26152.334368047468, 86596.07616558284, 94164.93776139182, 18441.653378899315, 11954.63475431402, 81603.72064893915, 40180.4401386775, 17181.18215455976, 57648.35139627127, 33885.40682868676, 12802.767184805774, 73580.42771715672, 97520.95851456844, 51065.97599674889, 31981.243379893785, 30755.62860575762, 77782.26832211879, 60464.16340956664, 2869.462056137462, 66687.79698006167, 9408.13834308869, 4477.661657068233, 92442.7356218674, 71982.14075966153, 83183.00812332652, 11259.88711716146, 74645.99102129121, 23170.80303918673, 83755.5638217841, 32468.234104297633, 62075.81157966353, 34890.193011145784, 36798.77758447676, 94321.32721986949, 11937.69405879882, 25970.142447970633, 24042.33253597464, 44149.66415214213, 106.04270113806491, 79468.40643735073, 98694.78717911961, 37656.24240822847, 43810.98046523089, 31998.510422441763, 85837.1078977946, 7712.03445645372, 87414.97689469134, 73267.77313682646, 84637.73593297019, 75778.74724370804, 79877.76138394637, 93460.04829077529, 67237.96417391363, 80450.66896956897, 9906.518799534659, 6107.751367461589, 65337.45118652307, 11591.092462605302, 7028.783135207795, 53291.830545564124, 7028.785831179874, 84865.07004579272, 72751.0331247355, 71438.4955472617, 65812.78878990127, 81893.15428226552, 41357.757254698234, 30509.757329361477, 96597.7236800945, 32694.239450341178, 28500.79289761812, 11833.05493320146, 31214.457477386903, 35455.94058054722, 38776.67076012462, 94034.39167766263, 52815.255531579016, 397.79779130868496, 52870.587177997186, 25264.326807422243, 81345.30824573322, 91555.08409129092, 31918.424466281558, 31361.684532839918, 70698.81488628572, 74644.51130547812, 24253.36835810026, 87524.73711027902, 24927.45707218299, 30626.66284233857, 41151.330318317836, 45429.44792718216, 23272.971088021553, 89996.25791539197, 64743.10211667861, 41636.16036578851, 768.8703733463198, 14974.936227112234, 93006.60011559632, 12046.213894752855, 50402.89419187505, 28145.518591587537, 59287.904495266775, 9851.346664242255, 20675.45110251725, 82651.62995740106, 32771.931405831434, 30233.073014514222, 18744.414175168655, 86528.25373137764, 47100.25553065654, 60793.0974195614]} +{"id": 1081, "vector": [10879.604791929987, 13849.266754043489, 72833.26863483844, 44159.20844896392, 99862.55604647551, 70210.34371688333, 3439.931153258291, 73662.10966040973, 65307.8110331816, 48204.97939983785, 73984.11974100694, 66812.32975509111, 96716.82786910342, 20810.20487930485, 65247.73725525148, 98308.98618788943, 76040.00578955431, 90914.18758660679, 28013.207517588722, 12522.404238355733, 21054.222571134316, 39057.747742414715, 11701.755206917009, 19670.15206350129, 14401.114224598221, 17337.911980107558, 93916.59733955587, 93866.73055581687, 15115.64767050172, 26808.478219690824, 1531.8674597376457, 63593.167048867595, 13214.42529677077, 95604.21671180344, 579.1094573673106, 91145.84505051201, 75241.00537824785, 63118.43350092044, 43738.20392187858, 69322.30095188726, 5853.194960316221, 2178.474930241747, 82232.30901899945, 78093.96786224729, 74341.96909517478, 85932.19048404327, 73734.40404015552, 5383.377485399988, 92565.89813788708, 97800.38727162742, 20041.172599731726, 74824.57248066635, 83811.05868163705, 28814.14096526893, 62491.479486248914, 73903.88426321124, 64020.559650474606, 33798.936297154905, 70395.68731146028, 16775.821787140212, 50980.61282385942, 50718.321255294766, 49361.33106888858, 64770.16688195122, 96601.00674531245, 13081.805474655528, 9484.325390607084, 69992.53221933663, 3414.1245948231312, 19857.206813480454, 65506.20467989996, 67350.185618288, 75472.45865654299, 61678.757575266085, 87792.79359253928, 48123.22337955742, 99287.30080165254, 46172.06161373125, 73590.41856802383, 80964.76447224106, 36099.56799009717, 32603.20496062322, 95853.20541057727, 97051.74742010956, 89682.76542051721, 76667.82496721443, 89585.38520084046, 60179.01810848025, 48595.21756260076, 90305.60977334899, 50233.51241841279, 92662.32707364987, 18401.390663849616, 85846.59742505521, 7311.371678131695, 77390.80222962232, 48168.80773864053, 24425.797807536863, 98306.9775170619, 73752.31474072748, 33121.03469184471, 18768.02405465937, 94145.86240405132, 48777.05516211346, 54439.81970023257, 49656.79683191352, 84181.48722938716, 7119.927764151524, 36933.35722948545, 22943.944168715945, 83387.3496754844, 56015.01911031259, 27041.980630506423, 34916.67289565251, 90888.11919680687, 67837.3509126892, 9370.175880453668, 32826.73115804552, 14343.039942489044, 93913.81862036168, 12034.22161736375, 84611.95819971, 99526.93041966906, 78630.06731159936, 21988.200616802933, 12439.570471975914, 42097.519206215686, 83737.72331118773]} +{"id": 1390, "vector": [99616.47124607499, 49348.679418081774, 15314.861054646655, 50392.771568760516, 73692.13239782568, 17788.25633838279, 66988.14112912536, 16301.324487713142, 8678.219763287198, 58720.35449956218, 47091.001057427, 42084.0646798203, 16676.34261305919, 53740.03766516473, 89813.09474733431, 43652.154000792085, 55818.22969213094, 31299.49139983844, 71653.92999940511, 52299.62605536643, 37826.69766553945, 88869.04746723236, 1684.6374729064028, 40407.61941472176, 76452.6576555464, 93224.74464530913, 83542.35276845205, 59325.04757148074, 72663.47144580261, 26160.102409081308, 49819.23004298418, 50940.7447377884, 38584.511158681744, 57535.947375291675, 35124.28871331985, 17483.730014681798, 98895.36463929094, 81577.57540778503, 2171.2518870425133, 16370.982493686859, 93507.55849387818, 33304.41110519091, 8366.449082390203, 91453.13911087395, 78785.60182657451, 76957.3043542105, 22739.63461260449, 5034.396547837672, 72246.63190144462, 7423.885996755519, 7609.653391800164, 54124.10289219991, 62851.82746408986, 91096.1202684832, 65000.71277207359, 31746.01186171885, 67256.89418947803, 36644.3741733786, 40233.006305924566, 49167.918074628, 719.2565170587973, 44497.34111555277, 4534.551410844556, 510.15175233509734, 40799.004929322015, 24198.49592646024, 66584.50551171912, 96158.80206997883, 18102.60146442222, 84666.25129924824, 25267.681905926987, 45008.56755541499, 57227.52810987043, 35273.66870940601, 84247.27141064158, 20844.15881319248, 26179.73296120094, 66959.0306155325, 78098.2944509492, 72383.94644626298, 60476.344203259294, 21658.04066396151, 25808.712852798686, 86894.98206320789, 72817.61131542482, 75187.27227985016, 93159.90815367199, 2368.395874499374, 23259.77553823728, 93972.71507924543, 90650.61750505681, 67298.12185738541, 12010.424128877417, 61955.208311883616, 36861.32201645528, 59478.95965801563, 41305.70043753842, 57382.84861924987, 70452.7005307237, 25801.70209434203, 57192.88735635417, 41297.6096830865, 57334.052072302824, 63705.738681429735, 77495.49441418395, 37023.68842509137, 35017.531333331695, 69684.00764527272, 21238.088770324313, 10427.024464295353, 97700.90420675598, 3372.698048390066, 81498.70620035434, 75531.06322691942, 21004.942856289254, 49953.62576956087, 68629.34414401148, 40827.86548788401, 85977.15504197383, 41796.353631389706, 25905.117911608566, 80088.6797458749, 50002.65136113057, 56924.22299692072, 41187.10562901537, 2736.0496492547572, 82536.68915921512, 14990.755713677683]} +{"id": 269, "vector": [27726.940772416543, 23668.64122698227, 6409.205054566569, 64054.78375510407, 65699.28493058209, 28064.41102366598, 90223.49482723126, 5310.940918603058, 63477.220001110414, 12425.144523469322, 88863.86311510963, 24222.61980888102, 86245.48211701044, 11814.320509631982, 43768.72008081109, 55562.32212699913, 44702.58933729163, 89983.16502640699, 11229.726199470768, 88976.82083030006, 18481.60848139997, 75213.11500324734, 56542.392575579695, 5573.151079523653, 51880.14203743959, 5303.092541393162, 72996.92290127282, 88561.63442726694, 78577.4514982049, 45702.16876549794, 8724.267069400405, 21174.710247390027, 36766.88818421503, 39614.17730409394, 62766.68366386354, 88330.62875187413, 70950.56724506697, 64646.8939811928, 24271.869782675592, 8718.018040856801, 53264.53006458411, 25811.417917262912, 34049.34715633738, 50110.57587449988, 1942.1266762952305, 83140.71487918768, 37362.4507041013, 27566.486920931242, 50535.725279938684, 89656.52835122579, 43360.490639322525, 59029.655622915976, 25825.3169783218, 91335.32654267586, 79538.7311104147, 76178.24832062225, 18838.756660936186, 96422.4771537091, 53851.46744850695, 79255.15870232222, 98533.5280403485, 2461.191381062733, 4261.510224669085, 57996.795163262424, 54337.689483576876, 85459.5604938049, 13025.682147234063, 69318.26585042942, 45457.62191715391, 54259.8494809204, 5244.596200095264, 2236.927921849097, 50600.64136591088, 88028.33735407676, 20370.970138417455, 48503.32290282718, 32.58910656049885, 81906.8557337783, 63107.32641778887, 14009.2211930547, 65399.10353597161, 63573.0707758091, 21418.97284581332, 75120.13132091553, 50181.96963093941, 21681.648626381968, 9233.584405953887, 34680.7435003207, 27521.773630378033, 45806.11610781195, 46322.649149633, 70096.38144721268, 27360.560521220978, 98835.80570298406, 67551.90255218797, 51963.63303667279, 68764.23115925811, 56463.22048691086, 10911.615945952703, 24142.951893359987, 75320.34624099071, 85814.80046188076, 30068.643808164332, 26687.70227792907, 18175.904285222732, 74397.10720984492, 53845.951988216846, 42275.80857598233, 72345.39013740214, 27243.136120455958, 4717.973067151304, 38792.053196392604, 28243.414454982907, 29531.58330127551, 55097.126106295626, 63727.32970424598, 42766.49808306775, 10986.5816700355, 75613.78653157387, 45619.24306712937, 77094.23580251713, 53815.31775704883, 62484.472652214165, 50124.515308256334, 43980.08851770402, 84842.19282672588, 46087.00700840358, 12543.601885303935]} +{"id": 884, "vector": [65810.36043714982, 40926.35030003417, 77712.85779786887, 83899.0531078194, 72090.05435241603, 79733.23358428199, 69616.35419011256, 18651.130753609148, 51931.03085396247, 75450.39425791777, 56712.50789415476, 10809.942973860841, 50420.65693306823, 98564.67169333818, 26894.170989808696, 91750.60922295536, 37372.42546380093, 23116.763335202784, 22951.56616414109, 64374.43172234304, 95487.09704745421, 25971.99269465662, 17460.92322261994, 5601.121136410059, 82171.31844627361, 14117.901387544129, 79747.35584989576, 88117.79093446746, 99985.6928097177, 97295.93447856742, 3882.1504552569054, 70808.173465216, 26228.294880717716, 47706.83950136379, 75746.07287246034, 29252.926366473865, 71677.70749736746, 33236.39319692645, 13938.044870851252, 56628.859208258444, 57474.57415607284, 71647.32466087847, 52090.75418026598, 66066.17005616319, 32150.41915123269, 27044.161035059467, 64301.12940717351, 41802.33923996591, 69890.34489470896, 7375.585659542449, 70927.08139306722, 72638.57619775258, 35872.97422588403, 70000.50263591016, 93371.93087149116, 52791.75871564965, 29300.253470421623, 19517.50854527725, 44842.07871389493, 99694.45347180353, 60660.57864903176, 99476.96393509943, 63826.75948689461, 96056.95679518458, 95229.74829967659, 53120.862140850455, 61839.09528720036, 58325.6810545632, 48252.049278415674, 18012.24502063302, 79422.2814084364, 34626.5424955761, 61597.86770252983, 20590.783529230426, 73770.93050719237, 3883.6477558684246, 21017.20247625517, 80587.13759137374, 35503.71316754529, 34600.45089869146, 70588.75617206514, 98227.85920416738, 55291.464902390784, 95382.99188219341, 78442.9853175282, 86971.29600887976, 89050.02665614906, 63997.78774192123, 79074.67289752194, 35643.23403066045, 19933.29677226461, 15285.402687719108, 41995.73930958966, 2157.2200811170173, 4643.92867797746, 38161.74601851413, 57079.717858227756, 47875.07567151743, 44973.01971919587, 65918.743147246, 18366.918570509337, 92937.76199005467, 22722.703737081796, 9532.103189408259, 45755.83682877616, 62713.694927954464, 83596.75180165897, 29427.809309762542, 66980.70447900068, 67608.82075187276, 42080.628650474086, 47853.94098423052, 66689.6992438284, 66214.87360315066, 62040.047242659835, 19541.69100658907, 60610.60557404189, 76814.96767716654, 97344.49847382576, 11410.838705553184, 90572.28232083592, 88687.94532062563, 69341.06949859844, 22298.24139486407, 69272.45290933701, 34782.72636329339, 76448.19699308119, 40774.40733117305]} +{"id": 2003, "vector": [89302.86602818876, 55603.19385834676, 96244.83662811591, 79147.04830381076, 2602.735736605899, 50525.985562471134, 4511.311150148933, 81850.00147341397, 63954.849062412744, 92262.50757345158, 64863.05176735735, 678.817045678326, 86594.91002486116, 72391.8757669802, 52547.28875910002, 48295.36243052126, 1552.3241488998308, 97901.19373277582, 10933.488154162585, 28138.040620135675, 46550.71684245945, 14657.528650581331, 62442.995594068474, 14687.351484674049, 36500.375528697805, 63035.78364708783, 58934.71497798905, 21661.674864273962, 69041.64858005717, 86060.82084843678, 15986.493529803214, 69202.78088742417, 874.6133883406504, 71728.07532289132, 73059.80793865524, 14674.832157991403, 62859.641613194624, 86147.12644726151, 50236.76511510013, 23482.0270265926, 53933.91997469013, 28900.39918680403, 44770.11958499664, 61703.47919323856, 16130.769031812664, 41189.32660462086, 94395.26411059385, 23146.922145821536, 76732.89165287634, 53995.866449566274, 15349.497573774463, 11389.673576213954, 32107.193055803495, 38032.85233341194, 67216.5372622054, 88672.86300736488, 65100.41066057375, 92623.45129757456, 28919.264616189856, 30667.720386613262, 55902.28347654691, 56516.85533118556, 40110.40395972212, 12328.147346346917, 5226.364385414994, 33405.20944219817, 87247.5629918902, 82944.18591148437, 68402.73997358924, 5483.649644752775, 17042.49331134774, 6873.284313181683, 23400.350034203377, 36425.98581156604, 12036.595806765594, 4736.710241122355, 12517.525531389118, 65765.7559804651, 87754.76902212691, 93105.84559503097, 18454.173745556425, 1819.901354042941, 71949.68846365913, 11180.886294390491, 82171.61632075871, 68851.98122228116, 48845.63826261255, 5623.585643333029, 29024.741188569602, 73707.0111516333, 17843.621347582906, 11321.678831084004, 51084.597908690565, 7991.372667839014, 10000.670353301311, 93079.84077913803, 47366.99167360553, 14162.811775235528, 12314.547670472964, 55173.08533729132, 176.86650141582882, 33328.08761325632, 32829.21021865841, 35058.43585336897, 19831.414172749108, 56620.95843534646, 94685.22970562492, 2025.9024541395277, 59188.43058267565, 5865.147210534827, 41469.06401722882, 51392.56551997398, 38410.91894180094, 35773.12672777237, 43961.06875978954, 29636.395441546338, 51141.577676612535, 42773.61858095133, 9885.050238164673, 55653.25422058231, 18763.959228524007, 9475.650603771324, 24427.955674892764, 91435.3474748393, 60406.86370993249, 41598.503667982346, 23334.41055502329, 34751.94093782221]} +{"id": 865, "vector": [46980.72372229045, 52802.264980149885, 10901.39487477264, 50963.6673889593, 7532.620452769267, 55605.86409185715, 23902.413931534105, 76671.74288398869, 39699.4610918746, 55143.43063041031, 70948.30477282823, 59524.565150532304, 1476.5566973353073, 71658.76209069266, 15340.080159916337, 78814.1869463021, 36115.64259857527, 47822.88273602897, 41687.5204734759, 2366.81746879005, 79153.50634135975, 72938.33007134109, 99617.70401374162, 74263.68653602924, 5978.850651172862, 17680.569442746662, 12049.50075484701, 80652.88826715125, 44528.60015411563, 87176.49785481543, 51125.00315614191, 32236.284457115817, 46953.76717407084, 39883.24665550862, 71413.01094072466, 96005.28264890485, 43236.60104931436, 49235.09880521183, 99100.42239066871, 81190.77833906532, 11764.271197852617, 55225.12413829227, 36425.14294047886, 44922.35563458077, 62174.8488429468, 76648.35381592634, 81024.42622381568, 48151.42273902647, 74354.30953829232, 25965.959638248136, 11430.046561321316, 6684.065430898689, 33332.51404748249, 12281.357855451803, 82652.8014505802, 87510.47994466443, 88167.47360609534, 3980.307969664676, 59512.065119181556, 7002.9935537171295, 58140.37043285428, 16966.55659490469, 55695.00625558631, 12126.971399745224, 72003.22687113013, 38046.870451442504, 33163.613567909444, 34851.269696546326, 99435.84311419328, 50874.00448806335, 70889.495293687, 60433.963209821806, 79964.62476190855, 89251.9990559314, 36604.12255798853, 25625.82576790312, 28657.09618839488, 7004.509220507971, 18450.883849036072, 78832.79613978944, 84360.78247945744, 83329.11430488422, 84086.55806534653, 6840.811694017812, 80011.36686960458, 81629.58587918166, 19318.611852575017, 70405.40275697965, 10561.070379478942, 98755.92191987336, 19150.812614866096, 88914.96955312323, 5004.148383177043, 32573.490607366006, 26277.426669286662, 93481.02911578608, 91949.6738131718, 45122.89663224842, 1971.9544794047583, 1881.5815754909981, 42577.96753578597, 74664.5881202788, 50390.3491939103, 52025.876291134286, 65795.8692344952, 10885.543252660145, 93814.68328092596, 73767.0815469284, 68006.08213081438, 82014.45900605689, 41571.46453430996, 59994.4540684484, 71975.62610610212, 4396.048199607483, 37.40107741507792, 13875.01161549628, 1817.7839658708006, 73705.65266452452, 66783.82381965086, 57827.87876568441, 81412.76459260583, 71528.43064971811, 89768.81128993673, 15778.724176613512, 4023.844255360398, 60733.80960555239, 29923.282349492696, 14353.202445331903]} +{"id": 767, "vector": [97147.03025192696, 72492.74835191252, 28466.556087230387, 32129.187839537044, 65906.90292348979, 54542.12410733174, 97599.13624310387, 32045.8913515552, 21359.4859595336, 78133.33622825517, 20661.892832603557, 10651.289035007261, 23617.06801510791, 58841.86102142449, 50360.96578479285, 1037.9847855432113, 47106.45873548155, 64859.68481223228, 16252.549205507483, 59822.820644586485, 94006.13185706844, 54343.02148300381, 70507.86382856873, 3257.5192200273827, 37076.48581780193, 76905.9986559662, 93518.62725603385, 37602.08700209645, 94550.73373205433, 11022.07504367696, 78004.26069447288, 27293.44419055231, 69986.96933155243, 2885.9015522237464, 16034.576405301515, 14655.708317362192, 18326.758481934612, 79592.76447487251, 26948.411151531647, 55677.61683170944, 8886.520548477061, 55802.220431115915, 78805.36310209095, 79023.52598430186, 15061.217473474997, 73869.10042072227, 81779.67173310007, 38070.17200817857, 87110.15583112647, 27962.388890657174, 1249.864449105531, 8392.84483476308, 21498.542214240024, 47895.86209868162, 32652.87014984537, 89581.48566286398, 43503.58207328548, 3484.1622113550243, 98078.64361782197, 96572.15061846923, 71983.99796521728, 37643.42134834412, 50146.128799719736, 78982.91670944955, 76974.42927117387, 4079.7162929774754, 1005.7837790105273, 37562.955861137256, 48373.14171114543, 98677.99920066594, 24349.401304733987, 19437.45925818978, 23126.678895931374, 50721.93699136318, 99571.37935931949, 78739.626257883, 84877.74199519008, 6313.715008462428, 46298.098929968364, 67681.07966771368, 37632.46021124279, 5843.060183995963, 93652.95410811689, 67246.90500607243, 5442.306220307091, 40440.10947911466, 63462.68809315544, 72455.87549035896, 56120.81393036505, 53818.066606466666, 50873.02067708822, 89629.1769722895, 64729.87201644041, 66348.97663510394, 30805.024569985595, 20096.809206783917, 71203.35020342666, 52878.070725760765, 66463.23005238631, 22606.952983259933, 7105.585692637151, 676.2339263728601, 69775.65291817897, 48923.25410927192, 20327.525323241603, 25602.475370347423, 98424.36893041735, 83638.12670802846, 84476.89772113597, 4725.651093662586, 4284.105670531235, 15192.397849352723, 45569.526816108766, 43335.49970964896, 67670.11003265905, 17793.422292057836, 35798.25548301527, 96180.52160196709, 92807.50988522088, 75212.93703089988, 76374.69505157268, 68310.28482221098, 42242.7939810243, 17552.54947517163, 37111.04463652979, 30772.024329103897, 38004.05428133776, 21543.684016388932]} +{"id": 1410, "vector": [79868.27182849153, 90307.14850784668, 35374.43678150294, 68272.59646899099, 87122.58674566205, 64790.40929062906, 87201.77888918051, 43879.793765549126, 18465.76761974009, 93888.34143522888, 95800.16788762345, 33318.569979745815, 47486.050994415906, 96011.03978148005, 17738.134973477638, 40507.0452803373, 5905.6865327282385, 34516.73495918415, 44204.44461465868, 67049.51794442203, 7828.082572297934, 27315.11624221742, 81057.83926303688, 4691.000607964657, 25112.61528270433, 9739.404009535612, 68042.44587979537, 17792.70874815673, 52072.48786615682, 38891.020221082515, 81951.52268111281, 90277.94349888302, 84422.10871737481, 67649.83329223072, 21986.213585945137, 38443.01184882747, 37179.23421790772, 85195.54431471809, 43597.783462597916, 85096.00415004778, 67425.23466466673, 59390.62801510427, 34707.34423778101, 65131.3575819726, 56018.844699317095, 78263.70969608729, 32651.29365345195, 78559.35848104974, 8845.180460637437, 70887.35516813141, 37191.254880989065, 50249.308803894695, 92880.51440368447, 63548.62371055001, 99051.63458297194, 39261.181902027565, 97210.46920090132, 56840.90184231137, 4432.693414198951, 73243.1773237811, 51278.57081328965, 42245.789175272985, 16248.68805987576, 16404.927157657166, 4353.735844703799, 27811.29303672316, 16949.369941716806, 19116.299813796955, 34435.53274395269, 94545.67158446314, 64682.300626783195, 83454.41881227428, 29095.57297439046, 50214.53401823174, 50025.79613122611, 23390.286534393024, 86573.15088123163, 7953.144878660268, 68871.06724242354, 12881.515083804596, 8234.317238747191, 16278.54475956595, 6284.8019170780535, 57210.59277947046, 6878.377262269652, 97568.19752138645, 99112.00996027289, 27089.666341612705, 97802.45800706344, 69352.61127966314, 93284.92303902935, 88963.95470303972, 97579.29756649432, 3168.3436593447077, 5266.204429826027, 8878.144076565353, 47840.97816103552, 40157.46855875331, 46899.4796521925, 89445.5061215865, 77350.74010687388, 70411.63534802009, 29167.061099308445, 47344.334843907745, 70594.02165854964, 85397.96373362886, 44700.11408516428, 55228.654734178206, 25664.62195366471, 14785.433886436716, 54684.41442047246, 77955.3596811007, 45599.33917716238, 38678.53338410852, 36177.87378113373, 3180.018788968286, 14112.738136432645, 39673.80782302493, 72491.44377060092, 50070.914983103554, 26369.54675217168, 3155.0007573136086, 50800.912621131334, 10414.96518554179, 59877.12121131504, 75079.00509304692, 89060.7852025705, 60639.660351260194]} +{"id": 583, "vector": [50515.6776348614, 5551.548685210939, 56128.29849291141, 88315.18545153318, 26583.120544628648, 30448.909507416265, 76671.32944830843, 95547.95241090172, 72943.73215006289, 46935.95858225994, 82875.08203001221, 1236.2135645307858, 28144.974658726496, 98760.4321297013, 20568.932226111934, 53853.0100851239, 41270.710794235645, 96608.09402405606, 31878.492116941925, 71408.71664450203, 85657.79865756788, 43204.528327029526, 54124.32403581198, 62036.622133867306, 7648.774092934874, 70158.49348764386, 80897.99121409768, 42127.384660807074, 43074.92963870867, 38164.752135699055, 64855.80777114074, 25072.61402863601, 35068.899171099125, 11076.11005409337, 17441.335409456406, 54633.10202785247, 26838.443237000465, 67866.2068996452, 88675.45785426043, 96350.42375750307, 57616.766058602334, 823.1856659253411, 59831.041671929364, 59229.058437422944, 40294.665423674414, 3697.732420728417, 36101.9552101254, 16355.849793837806, 56257.56438063805, 23564.30964714299, 63670.34290109155, 6144.028201971063, 12060.806119748702, 40073.16646867749, 39055.52400621034, 4883.689708500317, 17544.585267718605, 17217.53226625352, 2557.548126937226, 41496.04050511583, 23310.723253244192, 88614.42266647272, 94070.87981745691, 4228.197998982586, 85464.9795409909, 77704.1022338911, 66102.31383633634, 6895.2641637822335, 69995.66097862687, 37994.55580252159, 22572.052763352414, 67288.33057286461, 36638.20746948714, 4932.135445004071, 36589.94353311046, 6879.55322736501, 60236.912575220034, 3951.753578812489, 38235.94811346484, 73253.46908224703, 73833.6933679968, 40030.321458203456, 1503.8575724556024, 55621.64732975, 47026.05231549944, 20681.908285995378, 81011.75842137795, 87563.51793485958, 43098.752384926054, 59191.828614144404, 71046.04322333992, 25180.493478214772, 42971.29311124296, 14600.084470871732, 29391.26081985025, 91236.28061507142, 10931.279165374819, 63722.262752658746, 60352.71857504475, 19584.89224322998, 99116.81243447722, 27269.114615971102, 70394.35089087243, 5695.498938786814, 85531.94798163947, 17785.235995380433, 52792.35794236389, 38642.78686258945, 65772.59652697372, 70833.3344157427, 57300.035144170855, 14401.247375583514, 15593.717243656047, 24792.48475563053, 66753.22407170534, 61908.83167218541, 24443.8600649339, 85340.50920806582, 84212.19432345827, 3770.7422415325764, 32107.78504806516, 54539.8291628091, 52681.389707839546, 71743.06678817706, 86716.26259748469, 97151.99115689898, 8456.149269295243, 64477.072092804025]} +{"id": 1962, "vector": [27664.133941432556, 71458.04570445188, 52862.53944067572, 2467.957660399278, 47739.601785904626, 71172.23408521418, 70535.80280077759, 81519.47550885564, 39679.23426235584, 3710.8515783191965, 75040.20502120808, 14382.509209237603, 40321.29112528491, 65976.5182883474, 61951.91922922565, 36492.52019728017, 90.62312490216718, 32476.971966015033, 52857.49616044518, 18427.59809786988, 78374.68985563872, 87007.31223969992, 89002.21655495866, 89578.90717604973, 70817.40788460837, 96430.77048065457, 62049.740836288904, 9592.30631334761, 16515.466910351704, 26016.834507374788, 18961.144017520604, 95136.00723428729, 38022.74146592409, 66866.37723081066, 85192.94109995906, 91862.26553969052, 67909.63185957793, 94053.79842771137, 67715.15001386811, 44397.96629394174, 44117.726746835215, 177.25964213859413, 19877.699176044738, 73073.01288647458, 46204.90355103573, 79082.60899183979, 44948.60996856636, 89002.28564229203, 89444.85157528109, 73272.61671669928, 51991.631991408816, 4573.303650408489, 89539.34991994877, 3986.2630976516657, 86205.32423094602, 67170.46477840008, 90891.39870282887, 97882.92642308517, 21763.543709775357, 53630.637661482484, 11038.209251447728, 59940.466381001956, 19726.936033194375, 60769.733703552396, 6809.73554842198, 72563.17512716683, 44724.81518676017, 74493.45047011878, 18034.674297156573, 78636.04009612216, 11431.821988526735, 15049.873193888885, 17241.132542899184, 77893.43870221745, 96101.87235449461, 64237.59486171766, 60985.450142592264, 25883.708065541865, 58454.58151496072, 24793.741750310626, 38879.3650939409, 72683.95412861937, 95485.05168519857, 23394.87176075169, 33842.80869766072, 43242.18367576789, 60893.01243380924, 90035.48383615629, 62785.43879043009, 57560.04016737437, 24946.15463749359, 96388.016311374, 52754.55411237845, 55882.47206193475, 8642.410481204355, 37342.220491054104, 60839.95395437908, 89554.58705224354, 19238.789078227746, 26204.7373585509, 66415.62354447476, 85593.34828007074, 44024.64525343238, 94118.4761944213, 71680.43833968086, 42095.028739945665, 22018.382681060113, 88109.51960275599, 36219.56665881038, 24431.531839375566, 94347.93912059286, 58350.33723093561, 76976.9868972398, 20952.34886266386, 79926.19913697153, 23439.829077121678, 66993.56078635568, 63136.939537282364, 92771.53428858006, 75902.48618383193, 4871.59060467186, 46541.104717302216, 88639.49915971766, 22315.25735715205, 13793.532059544033, 57592.872762759005, 31656.956239299227, 1296.4527133178417]} +{"id": 1406, "vector": [87475.74802394991, 32110.31432111554, 26417.57641041084, 27322.939298818983, 28877.095008804077, 73169.59997178071, 5830.6631986658085, 1993.944671385417, 18608.84019770015, 86803.05421734789, 17323.541748251126, 26661.685341652443, 44849.554652696366, 82643.28318908291, 6463.472536438319, 52428.82099573976, 22871.323563509748, 65582.25346260694, 26589.90339821228, 29455.945285662765, 15164.447806734916, 10134.592873290449, 14805.650733307508, 34585.72155443238, 15174.646884626163, 87672.76393039564, 4236.345037274425, 20633.163137277443, 8751.531144129232, 93202.53135509243, 25035.361136176525, 35089.88913404902, 85907.92721099264, 52521.8889269338, 91382.22789247126, 40261.611505075845, 98040.06643308396, 32098.69355082815, 23638.202503751414, 84145.8944004266, 19399.952544431, 52973.298639917586, 12307.22176242741, 48244.64269040446, 87240.90128295505, 66183.72342192344, 24531.535070912658, 20142.97176498736, 26118.133008783927, 91239.42905528663, 47314.9601559802, 19248.65308076281, 74328.01881569452, 44584.66783335822, 49628.86078715627, 25230.795775212777, 89122.58490140612, 63552.43990510893, 96966.01699407207, 95468.81279607421, 41014.265982032084, 72284.84189352518, 52339.29404011265, 91.63152350917247, 93679.13546608729, 70362.48086438405, 60837.7359239183, 61946.19000011844, 50404.60233922046, 46971.237904580776, 52955.12919901651, 95525.26220277001, 12983.237531753644, 84949.56851364198, 90831.71257790166, 16044.114235538376, 67982.13745750657, 86906.41178236163, 47335.489101631676, 40488.93999742036, 77893.75833180374, 71380.18686177877, 49631.59922039181, 37228.804563385085, 2514.813338626998, 77376.30957895255, 59947.187219287145, 27474.547217806034, 7825.244962027722, 70963.37431759063, 6565.497605171944, 17271.373158624716, 75931.63997350291, 61113.06495310752, 48582.60535769338, 19371.074149095035, 86662.67196936622, 66743.51038660212, 26394.405422203672, 50334.18808554355, 85617.90892942126, 62496.51649364002, 82409.84022534144, 50980.716807690515, 24407.80788161996, 38195.88354571094, 93012.81092350793, 95065.01914350108, 75950.22459489202, 2772.14369845723, 15209.34446689749, 85489.06960519157, 78493.70664795396, 35778.16912450944, 24094.318359125522, 38792.55124389459, 413.4244859513081, 83127.73847261313, 3108.627437989453, 2996.9630562968086, 37330.220540161565, 58755.9121941085, 99663.0902040559, 48636.61783014858, 69516.6356850118, 78414.57657283432, 3771.2020249997204, 78748.84369803696]} +{"id": 617, "vector": [52985.16278763966, 89978.85436870312, 91306.47941176711, 48180.24457032963, 88376.01490522355, 76706.04704763532, 40602.634009560235, 60437.85798484062, 54242.86837669477, 74181.79510467217, 51900.32832064374, 10045.010923341335, 138.01803575640426, 83888.77748840873, 76877.40516448411, 58830.02765181215, 52549.111946827776, 60921.39686921596, 28082.371233111237, 40530.15128830453, 19896.196810603094, 14825.80358326796, 56700.50373637382, 29797.05197404261, 93685.89267007612, 95882.99398189811, 68300.20990782327, 81518.50794059769, 85702.47788665336, 58820.81755901889, 14849.873333867481, 65724.32190269239, 13980.715595129566, 65442.385204746555, 74014.72462304728, 28715.18551778497, 50981.311313390455, 83528.45606312435, 53422.6947493348, 6048.748386700309, 63665.26697423508, 1818.1498702337651, 18322.323674824358, 26473.896739847947, 10742.412169889836, 32683.692435107347, 69417.21622917635, 92812.34776242459, 73176.82506806757, 27363.478190019418, 26112.134787305986, 74620.81407647688, 85949.33002954468, 39129.35071389786, 40655.05708607323, 13170.45160563346, 65498.86904179757, 56371.75231182586, 38758.43990564879, 61836.70234397548, 60368.707399891406, 27029.052779564612, 78294.51650123505, 16916.525955608387, 52423.781396832026, 33845.01473526832, 89938.4649872244, 19120.136712655134, 41379.86367966919, 4068.6669097257154, 63003.954478732725, 22873.416151520676, 19207.49117436752, 16114.265613503898, 98282.15413878289, 60948.097824311095, 44213.44162761654, 94092.72116727667, 80481.80410315454, 46706.35993904175, 71808.81120690572, 44277.51635778977, 27282.529862562875, 5985.103826590843, 3649.17422424732, 9531.558780548621, 95417.77823271534, 54048.69499178383, 27885.294197645937, 54899.34401814006, 97205.21832323752, 74745.13030554532, 79092.03865509952, 98334.09986030369, 73437.43413036318, 72184.82485464014, 6089.378249174426, 35404.94796211588, 39076.04460154762, 96298.09929332795, 64904.80058469254, 24416.498314818215, 65018.63051918654, 16377.707794321206, 1404.8093694611907, 33937.065283071664, 3211.9932555864207, 25613.403189526318, 44453.00843906241, 49136.239180378914, 14334.383461244293, 10225.3992125243, 8750.97145534397, 34038.67713093982, 37906.99588359548, 94673.40172864153, 67692.88189010826, 17113.194019318613, 27122.83725974679, 33268.90739533887, 75974.7015210091, 28914.23684923399, 12468.970585281702, 77307.07516956935, 34601.50790457095, 61050.080330682846, 53161.56055663567, 19893.996592971664]} +{"id": 1483, "vector": [70172.29874633947, 36081.07826814273, 51934.18032198864, 54320.17680796864, 76993.09063664306, 246.65602782677487, 23250.268921349314, 8169.658102853583, 43401.9062700309, 94640.622469623, 10942.852798835445, 91489.54366173278, 7675.60561266517, 75678.87436607141, 68642.88713348913, 98573.01277491772, 71002.66856919847, 75741.02703548245, 37388.57028301997, 27974.155036692373, 39175.660401603716, 67213.35511745281, 57243.38786138806, 21550.54998396667, 17955.83850680078, 84380.03441639201, 56348.360938853904, 81551.47748247575, 32408.777630373064, 92558.65218370398, 86959.51668806703, 23025.426074087052, 96490.78973720188, 94561.68703526894, 39693.11444749792, 93179.8881138881, 76064.04472916538, 53917.42959719017, 96774.8604729497, 93186.73924708054, 99505.99658996698, 58195.87602709778, 84210.01232817073, 61136.14866039582, 59524.55179260443, 83116.30682991447, 84522.66600491392, 38452.39792838694, 61424.12466539062, 26040.857787451678, 14039.282601517933, 99147.11128631962, 49250.413168481464, 77266.90098323338, 25623.639197812685, 11287.790015529587, 4639.487124590436, 28805.473296061646, 18599.249264026097, 50199.893516826654, 17117.88985527761, 7358.227110280469, 64038.20904765161, 64525.67321880662, 95686.77762462225, 98481.93106569529, 8391.478811987685, 64663.32053390418, 38805.38180941372, 48914.35836409077, 57407.94829257633, 8023.513808598759, 59814.56718839177, 9667.485700562462, 30429.87240982443, 93463.03606362223, 72046.0999079847, 32434.2672890808, 30096.648860132147, 99954.09264483345, 504.62725048857624, 83201.86586268686, 45487.038558503154, 34860.553223059884, 22721.43740393404, 31073.486715051367, 99464.47164510314, 62671.81346311213, 66870.91732517933, 21970.278914024733, 87614.91461144567, 11369.440564073475, 87210.37047992602, 10700.213532752889, 77968.8887964508, 85040.43712574229, 78875.04808969137, 96428.60159946267, 47113.31572912954, 52468.31780130179, 8804.796803430625, 24612.143146817787, 63695.70564979314, 9817.959927433118, 75527.15844004392, 16120.488570965374, 76859.24537514313, 983.241281516578, 29994.68123895509, 37427.879752328285, 69825.02560747194, 23307.867462073107, 23033.76629783643, 21052.329203031084, 43952.89933623552, 95623.5975978725, 90207.76630536345, 98471.3232108087, 27675.007384468365, 37360.96494134351, 50072.05166208847, 17751.34038722902, 27276.617298678364, 71551.36741817341, 36796.52572813261, 958.3027203052375, 66747.2640183814, 94047.7635681214]} +{"id": 624, "vector": [81565.23281049496, 37146.278079866635, 44828.64411374184, 27832.52959269734, 7401.429058402054, 75164.28126075759, 42940.175588264065, 36123.84221371946, 25737.061246706173, 83734.55052002687, 29980.83240646827, 62394.39899215481, 71707.64372295565, 11667.27818188561, 53993.97714522261, 29934.357588610794, 80157.90568789243, 7045.556074195935, 35526.261158523375, 76860.21765308156, 37642.35863087092, 47455.75033266803, 11519.4074604133, 35473.28928964628, 8602.649407574814, 95965.55764627627, 98593.62906140376, 13717.384358279849, 56694.97545690311, 29885.13662153558, 48233.860288016025, 12181.343446381188, 24490.867585514687, 55607.96860503152, 25340.695763972297, 31910.826179386742, 86143.27145092143, 44092.48538672132, 3830.6041479577834, 34764.7598783117, 68152.54276387287, 86454.81757442113, 60181.11019860387, 17826.282347709064, 8939.608310922575, 29551.4383942754, 12405.36828495491, 28549.93010708392, 8360.276464280558, 95704.59168166952, 48783.20027790661, 14663.30772613571, 17606.548951161836, 22405.268704929236, 81919.28044015277, 65597.8952157946, 45701.76328083405, 7831.478693323368, 46066.06459567899, 42369.52753987662, 68929.18764708388, 85417.13728646412, 50479.0425327641, 64252.053868768635, 49511.757871459326, 53606.83156510986, 80183.07031525088, 28327.702329907035, 77345.37339240128, 91584.36914091201, 65884.84014975607, 23942.7720913452, 44042.445367596185, 34236.93785216685, 4250.038484411866, 25993.435039474243, 14390.540195702317, 81690.2412942469, 35627.159586335256, 16543.21579723006, 2221.4970200714724, 41068.378603276964, 76127.51428150141, 44870.81286082748, 43095.17040156812, 16810.276456743923, 54319.478480258935, 6616.22194960827, 99975.74715963549, 29618.99449629877, 55865.26097694025, 13303.5087625906, 10446.778488311915, 9011.821593017145, 28063.67872370259, 28132.49940847077, 95037.57579846565, 51702.40439005882, 79813.37505611907, 83961.32207356473, 23374.86702294125, 9868.820270523915, 50240.430552022044, 48166.75600603257, 94200.31779457486, 79495.05273680834, 46032.67105715296, 64473.921640621644, 38208.63740592313, 4893.738811857474, 49628.19300606267, 44977.946354052336, 44449.57981818236, 8694.60900313972, 63685.610866448915, 5822.378288468144, 47477.403251186835, 69608.0233414944, 83278.52784393987, 67835.7725345831, 42312.223598747456, 70381.6653065863, 32551.94087806592, 22718.322488312893, 89856.38536741532, 61414.422719930786, 36668.099617152526, 67159.66760980745]} +{"id": 928, "vector": [71499.24237792172, 47543.36645213494, 99716.0091500911, 93387.77042767675, 38524.67236944068, 16539.562289952504, 85585.08789183668, 42561.118639477514, 3347.837290915512, 34164.23502839385, 25173.11609920857, 73938.86193274181, 30190.294976701327, 31747.30383488392, 94633.44913620134, 83253.46230292789, 56424.275840917646, 65365.61132192127, 34229.343625491194, 576.529105960466, 41470.53304479883, 45870.899059163385, 9117.254774076178, 54806.69623018877, 26615.710306271634, 81360.35824665822, 83600.11706188622, 83600.32316874136, 21845.714988810338, 76009.3585916663, 22125.71203150715, 42104.841793924534, 47535.63270086979, 54564.75966624431, 15806.537502857487, 56137.661593499455, 80416.21809724014, 61300.53484818155, 6623.510344678219, 59892.95759324018, 95405.28097265106, 7431.107066068698, 65090.75290688612, 14010.616574550328, 1282.2373336433434, 56364.42023511089, 47372.18202044215, 99588.49551515937, 4838.708318653806, 91873.76021204438, 93738.9313316393, 28616.695265924318, 25725.079003690633, 81769.00805283785, 74436.21764309025, 85240.73130790087, 55520.45260956797, 82337.20900046024, 38384.731651129354, 8847.01434957217, 4094.5383187459215, 10876.88632405569, 90830.76655895644, 75210.1928281099, 52693.57682052181, 18689.819920283357, 26397.28669646514, 52146.28717576571, 70411.53644277235, 48031.715522265884, 62037.73723548682, 89109.01745669142, 16087.575153137057, 49757.179752209704, 19552.105225002626, 82062.84596816219, 34540.84527846829, 5126.189490691935, 76092.8177041351, 12781.638216982405, 17303.453333162655, 99184.54026959516, 49970.4921776043, 54067.34577114791, 12436.74882600021, 41075.23935079161, 19807.58845576267, 89597.4127543251, 44133.771443015125, 16977.16810321749, 90710.117149843, 55151.41730281789, 52908.20346152002, 35527.2815300311, 5708.884075147546, 50738.760486484236, 63391.04712226606, 94193.1260442116, 5340.890624122796, 15967.650598882166, 95650.50590034865, 16848.467401956368, 40904.802431373944, 31115.956874597083, 84971.45010985367, 3950.8197326647787, 68382.8008574779, 4300.880464429113, 76150.53634762246, 56730.84644272912, 63204.4594934743, 70191.35508755394, 26670.72167606368, 35230.396181537006, 10844.186621758688, 50392.44939853797, 85947.13120231702, 95474.66475531037, 11271.795495921822, 7046.567179047103, 30251.767276776744, 82700.29932100476, 75522.52742456486, 12453.65462260717, 38100.61693079849, 66710.97114207782, 35354.81274814887, 48443.72746259266]} +{"id": 778, "vector": [31448.634384060537, 56358.01095545216, 81528.52749108186, 54359.27818393513, 29035.55674292324, 74888.11439266086, 22267.922723634158, 96146.62357650707, 45406.32965968037, 19133.307203256478, 60228.87066816006, 43897.3598202357, 28343.15683465254, 89164.3745042483, 57858.90594773393, 78590.2937769949, 62683.20292098985, 13542.086765268636, 74125.25276897452, 33050.9313773122, 73624.31591278392, 27609.98267612861, 5857.868828927293, 65213.65008122384, 4281.975298037988, 51886.253115298096, 54841.30955001333, 29159.330601909838, 3717.2320242847, 36481.450128527926, 34210.422478155546, 24679.539143342554, 51047.25221000634, 29247.38600554575, 79226.86799466221, 91877.76212155055, 73830.49892132264, 8456.480305096802, 14484.157949129307, 97238.5826941392, 67875.42029792698, 14120.603395683273, 56349.20167642982, 84133.6203514679, 30178.397602908168, 709.1613029821864, 96066.10543183661, 72896.14946494723, 42920.41807341682, 21830.28067256022, 78087.91459329493, 4394.089010558133, 44238.80385811988, 34457.26790052619, 91106.74716381007, 65744.59002952182, 31688.953329040814, 44880.582597719666, 91676.10716543956, 10016.650938948058, 37441.20439565471, 43155.44106585137, 58278.68174363497, 87709.60696191032, 85922.22295405637, 24064.11331869629, 40022.22012915354, 51244.77951776298, 11010.368651566816, 26906.84583038703, 98402.88201357731, 69190.32300214189, 71599.8905526467, 91620.53284120694, 29195.37565895536, 74785.50700469427, 62312.30432273273, 36615.23173345355, 57419.89960986037, 76645.05244036615, 27398.663861633355, 98118.64591367003, 36160.27333094779, 13577.049374819495, 11894.069491659542, 96542.03794233425, 31627.26062235971, 34992.95220949866, 81262.20724774014, 39262.68196268965, 60434.19943750609, 61219.992349067965, 57622.80061909373, 63542.01703124081, 73993.79225159234, 67568.19388468297, 28804.182430940175, 30871.29044155462, 41389.453157821445, 7532.804208895638, 92029.98297162635, 15633.438932043486, 93697.90621610828, 5440.347277753277, 99393.37304640633, 99973.8178218898, 59204.325653139225, 61834.93554866284, 45553.42767967795, 39483.53521789363, 29644.554672061862, 83201.94586428351, 61586.95309965803, 25143.308670121354, 55538.76730182597, 45951.94504258953, 86445.74837918703, 93262.07419212931, 5868.4375957730235, 50282.098762967405, 49620.918080871365, 48684.49880694337, 83670.95589025237, 82674.77132836434, 53930.72261513665, 54532.517479443755, 14609.656753647949, 54236.725427873076]} +{"id": 1387, "vector": [7602.338699972766, 57546.862913871264, 59188.62743701078, 97132.496328588, 1183.6519578971693, 58216.15515257793, 58998.917485884005, 45060.36830372136, 25359.944481137132, 85200.03051374803, 71593.65535892456, 31070.380814716304, 58173.51181305625, 14001.62629816597, 57983.05906179203, 71527.46847381101, 4016.993376769984, 94979.05349139018, 60190.84342486637, 76410.55479859002, 97737.42564088535, 15160.070088672639, 25284.39430831836, 50260.114219951836, 3729.135393210814, 22097.066711895764, 97299.41100700678, 9118.666385164199, 3592.3217487877414, 59729.272163551264, 38129.98450177515, 22380.442868160346, 62104.79753980353, 54445.508220121344, 47534.081397551905, 89018.17724812195, 91570.72639320533, 77931.08930468111, 54371.020436188825, 86136.97053909517, 5995.877584158127, 40937.42809318181, 27966.451171862638, 28724.599993556443, 64784.65294442901, 94590.78459637372, 14911.826467343104, 67533.9687729156, 31704.73762608711, 94804.5839758696, 41883.830179006894, 44056.44013587768, 16992.932261325932, 77638.42598343488, 73621.46988974372, 52866.014397739644, 16741.046745779007, 21951.37090526239, 34526.06914485337, 29529.498132378907, 31477.461218289372, 55215.32987026089, 95060.80826338504, 42360.14609356068, 92005.87430737144, 92947.93258191725, 83958.80648677234, 34516.52444167077, 67070.15355630762, 56705.056734306614, 80081.42876883919, 14296.8379723589, 22756.781177237717, 65426.911684006125, 47850.204528352224, 76119.75590467725, 56915.698550050394, 18209.5453374267, 85691.55288469262, 49664.32690653215, 40466.5450219442, 72467.13073125792, 20279.442015527347, 11630.117023456376, 72667.48657321176, 20478.428101693426, 11205.019088713376, 31632.284255601317, 84806.3465820144, 83812.64173152515, 48867.334216439274, 69810.56691491415, 1044.0049221329796, 18452.21550243554, 10893.82804082557, 49450.81823464261, 42924.01196405555, 77265.88410718062, 92564.72656546012, 42627.62635847001, 28481.64240312504, 7906.253819538611, 54120.63186388222, 70964.8017511986, 86365.74585552409, 79359.9465818674, 45175.83189708799, 16222.253169032385, 15456.44831311117, 54801.431142657006, 16653.047236528295, 48924.99040627224, 69553.53264240254, 21235.168274905624, 82102.12746976562, 21856.240377369275, 86326.52425889349, 82527.798297413, 71714.31983979861, 70602.45569688253, 45159.968844048846, 84341.04934957183, 72194.73605382553, 97732.6091457486, 17393.096635923168, 42608.0102796527, 18015.505055663038, 51489.00572428523]} +{"id": 851, "vector": [97169.0485204218, 79503.83485344241, 39430.20036985102, 30229.528302356925, 669.4370574929164, 27153.604167439193, 12322.90703024227, 70672.15613592116, 56744.97586835265, 32559.27021214954, 97576.0259795931, 13560.791982874443, 39643.131843306335, 36848.21205654789, 93481.44516827402, 66334.66739588018, 23825.706489164222, 84077.44187087197, 51504.729200460475, 78475.46138772146, 62191.125448821775, 75713.20555849206, 14298.809654913446, 63662.28511668184, 33258.79442794185, 14768.13420206523, 82791.15096603174, 80236.14586340086, 71002.75785819977, 52152.48305921989, 25218.66713307368, 1563.4461130094924, 6464.189088491678, 20122.324580940996, 25770.77811420856, 4612.327345520129, 48529.61179712687, 75271.5565554975, 83556.25747158377, 96707.69673623909, 43299.61051371949, 64171.38732008283, 42752.73665399875, 24569.840008301835, 50221.48188975124, 73490.62152539539, 8392.9272842439, 88930.03652654315, 41965.4499338582, 50033.518706975374, 91942.97758352757, 91957.10099719361, 39179.25523880025, 3984.913633230869, 98873.77998128849, 44549.796908853634, 49894.35152165973, 69510.5414740374, 79747.00417578085, 8975.834706799646, 28493.45222539318, 76766.44561005068, 28539.216845808645, 5841.044235650583, 95802.6398324511, 46544.10388664727, 10972.98410747608, 7792.484286325152, 3679.7351742126507, 92969.18135339947, 63310.22652692108, 11720.378777196827, 30556.237463050362, 6862.891816536687, 62788.51792257947, 97486.02459601415, 49670.625999104726, 8528.515909388223, 52420.33316475828, 22857.674259233696, 67800.92424484498, 54126.19551223842, 63648.02037630193, 79934.68059421523, 27367.963395029237, 24832.73157995467, 17570.13583953022, 68282.32612899668, 69699.60440498994, 6815.6459025594195, 78920.35691371506, 50233.31215792229, 64887.69312175433, 30746.198426857496, 43661.92787614262, 64200.298186382155, 83625.49322989708, 20495.529138809732, 32160.32203270509, 99098.3801903902, 71828.68740378242, 25792.285025019602, 79058.70856671035, 1389.9386233068967, 89882.85873786878, 64993.83447501932, 1191.6586943060859, 8174.994610546515, 29577.784802811446, 1068.8617316209736, 79531.00163247343, 22539.270303052883, 39353.23027004994, 19767.406150391464, 53964.20253958151, 68675.3025667295, 47103.80325370804, 7601.991590606872, 62459.78487598682, 40996.57793210598, 76627.99356635672, 29641.258019198423, 53491.71180821828, 68002.48096452173, 44261.998462869145, 78938.2049390134, 58737.96530159071, 446.3476388374632]} +{"id": 1341, "vector": [29155.039824468986, 84820.0852496271, 82609.32393060558, 56670.46206360256, 95168.0154795555, 22628.708350943838, 54478.512112346245, 91033.42930335604, 99039.61686370111, 69995.57826722451, 50494.85155614612, 35202.53585706264, 44868.288870655226, 88433.84052041112, 65686.8615301902, 88548.79799848175, 71107.96963009323, 72244.97650495873, 3977.0570251639547, 22795.08232897246, 68044.16305414538, 26185.568894044518, 79097.60619848609, 97002.09070882326, 35491.45768367056, 48874.096482710214, 25115.632124172727, 58209.80594206174, 55700.40258456425, 24484.85812418103, 56420.56746505759, 631.3083913843443, 45392.8442534966, 73519.72615403557, 77713.66091512064, 4419.996543893012, 14061.402597846905, 67755.99030955053, 34382.59145916943, 99567.17611826657, 2301.952232229354, 61261.073497947604, 68062.51239242795, 79286.3578883213, 37525.502743111414, 82639.82265897062, 5269.961707083681, 97492.73889019412, 73859.4333066196, 78306.82584356004, 6343.3406097033185, 19121.11416985249, 607.8344561255444, 33278.78502850739, 15049.626452220722, 85530.3081986768, 7634.535337234372, 58102.358017829945, 42983.32437409624, 6214.129225175436, 71731.3554789517, 53356.1134711256, 60383.874441766064, 22538.85528342413, 29741.483822883496, 19665.95637174715, 31488.76064395042, 92750.14819757121, 20144.202142474154, 21303.43207632257, 54465.532769521116, 11802.922117614467, 89878.86513584583, 36152.542502733144, 12870.406726746198, 59147.59915738008, 32604.757072724045, 59089.182527889294, 54656.822126201885, 85297.86516267047, 14979.020267714115, 25908.230547950207, 11764.616817225015, 64060.56007165361, 72169.71684773051, 84041.70173305979, 70479.52270869754, 3780.8739257211732, 14714.396535129094, 9851.66786660996, 9330.374043004542, 40652.1800690508, 64758.00236265562, 16763.914496551024, 56862.53664285714, 3797.0514374390473, 71492.30940125504, 33538.05190188868, 75843.24758552846, 51389.343366002846, 94540.62584677071, 46875.618244763005, 42720.428852731115, 49995.04856967209, 22081.20802006619, 39926.02236191117, 9768.633397380177, 79743.49911067492, 58826.1172781928, 4174.301807077319, 57635.75764605335, 1565.3703863527492, 73502.22988560889, 31053.055394341678, 26889.826140452933, 75184.24619656998, 47827.68905731075, 70845.73017479596, 35919.84094551327, 32686.827514172634, 95345.84513529015, 64581.16695449179, 9293.563686828376, 84996.7127158742, 45473.4812670859, 85399.88457925274, 96238.70235464006, 15755.716625045336]} +{"id": 698, "vector": [51349.966903760134, 74852.00888477324, 59612.80854819333, 31153.133614998875, 99562.57999917271, 66409.47942497584, 91170.99023103357, 48469.86345844963, 45213.39589655211, 22147.799729998995, 94912.37153900933, 87732.41699518482, 18091.466309233052, 21411.770976860844, 25453.527517188457, 60674.74320946051, 41672.27319487575, 7737.45798585378, 36168.31174771292, 80685.77180715186, 57020.261351469904, 78805.9552168608, 81588.82804168784, 37527.025419717975, 82884.10565939253, 65688.4044584611, 67446.79192753076, 61411.87531847801, 25534.81640708721, 26957.131407799785, 77218.65732909276, 76973.44570975495, 62159.7212414922, 82145.14842001843, 84508.29031833484, 98079.2785517784, 27744.25376111509, 12871.473652090415, 54704.019266477466, 51547.038130283, 20838.953486305745, 12293.211421424245, 95683.30253439896, 7526.406792342411, 69567.66516922292, 63430.668128261656, 60186.40606664515, 31176.253620859505, 97546.61722190029, 85614.41446319486, 62453.508904759045, 83288.89552424088, 41461.36683174645, 70294.67866232888, 90289.29130882956, 90264.76851121573, 10121.553875681433, 56672.71168611568, 19850.653517578132, 9502.12386516166, 35034.01550934717, 69116.52362412374, 2251.7600608704115, 88431.87403987907, 35177.49710764866, 43908.58245625409, 73885.21459124764, 35988.637748624686, 30257.760000691724, 86075.83422211489, 76988.88993269236, 1985.871585393728, 66511.58906460187, 11162.89022931617, 3738.301362353358, 97285.53761224277, 6048.948708284518, 55073.25196011736, 52609.45361184203, 1851.9550988876254, 14421.021763444352, 75411.8180782942, 33105.054101710506, 61860.67817921052, 51482.09427802995, 71250.13689875754, 85325.71776338964, 96246.31987529148, 84679.75997890515, 92819.20860051927, 58889.83988428734, 12000.761419553419, 75567.69012918587, 64403.77166244645, 27770.017423922298, 86845.88742892553, 607.9437622793615, 90083.5361586104, 35836.56574160018, 8246.022995636782, 31240.493739593712, 20786.593956010023, 39383.55507560539, 81086.61511265214, 96084.24812961239, 90994.04788579905, 44709.236732299265, 94742.86110938236, 98377.11734279693, 37294.273267599376, 38467.50180309922, 98502.7369098073, 56262.89829097886, 44131.12808111656, 87378.54403894888, 96536.54187564252, 52049.78285823804, 64223.37931274949, 63963.67979328612, 47721.24079223185, 54457.90854536215, 32198.662752937613, 18027.221324667797, 86893.27421649877, 68896.37396215876, 68207.43275686077, 75735.87734222252, 46532.01065204414]} +{"id": 701, "vector": [93646.5066557068, 64334.76444036984, 82500.45930584663, 44922.080511526205, 74799.99213697127, 78585.02459025032, 67825.42937814807, 78818.74839555398, 67079.64678301344, 76639.48042931598, 61147.99848180379, 54813.99755276861, 68062.23391793101, 69146.54916017877, 18221.765880815932, 37871.095836207445, 44994.529087585164, 87762.94886249954, 24474.71519830652, 78816.45312669902, 70424.98236287774, 54675.30598250906, 28087.56358779174, 79203.87496759238, 8697.26122677883, 688.1370893288108, 52172.61655319807, 52121.40363001454, 84533.69179606249, 33399.91926327737, 37701.6855407531, 66818.03487749667, 22758.068612135685, 39225.22132454598, 1048.8759436474204, 72751.24882836966, 82311.64198894723, 97988.34772700761, 80135.56174161103, 44026.61731747654, 31283.223413227814, 82589.71459315995, 94931.77022861104, 67104.00621470512, 83861.27126187347, 51069.62445879415, 74473.81380889009, 58184.52635606961, 80812.12471855758, 17026.720143566632, 33652.955864262825, 68029.61436028715, 67433.8125579508, 57993.21478270788, 77602.90744861601, 93749.01209441836, 26493.392684069062, 17282.971553680403, 31147.407435660512, 38932.46577672532, 26872.84830334903, 99201.6282100637, 58112.59367422672, 41107.2251148109, 57630.22392444336, 38267.0604975346, 57060.15974851277, 69062.98509326782, 60427.84383407513, 20580.75830592322, 22250.551594564826, 61011.87530491206, 7676.138786708786, 70671.78057160287, 24355.419860439964, 8734.93973243653, 44688.9667370555, 108.88692917547793, 42135.261090777574, 93223.31326568956, 87977.97982589215, 47972.7399430296, 84953.12740175915, 77457.46498993957, 83774.5507598681, 92966.21418601293, 40455.341296567494, 75046.44768730528, 8222.447183393855, 28318.694024615575, 33652.39899944177, 20185.21370140014, 40304.41271742727, 32036.33056349121, 35145.613997546076, 48565.23123930174, 51869.5972099569, 18940.579558957972, 522.4465822177838, 70448.992930085, 9921.125812537002, 65666.19337906038, 69716.4335419115, 52022.0281255292, 64900.02468441986, 98014.68640222638, 55449.201165094564, 61677.84259030228, 14270.574552605276, 83502.04730599206, 7517.755532822079, 10185.075207185268, 70888.73073585932, 76612.70316672187, 67636.86264343502, 79157.75525537066, 21837.538422081838, 44539.16213477391, 56048.272857527045, 24778.840940475966, 94098.95017449597, 9705.448851264031, 95240.04469085985, 45902.494981624906, 51331.71518256093, 4203.287199231565, 16371.331147222112, 41914.42389154196]} +{"id": 611, "vector": [84882.44409911725, 76907.33132412336, 78804.45355924289, 10441.009598189976, 72318.8610209454, 42072.58239038071, 45184.82265571088, 79633.60070255757, 89309.13443828264, 29131.991640303157, 58285.15518744041, 94758.4808829579, 18443.489904476428, 58746.21097371261, 50474.457358751315, 91315.55526580353, 29769.313127079477, 77869.27125257275, 30543.14852420802, 37825.27208547339, 63519.16749828524, 417.0813993206734, 81331.79294786774, 55723.00128065081, 83694.33066927602, 50707.479228798315, 44314.141313042135, 29565.521342611435, 12761.158297179176, 97538.8069446551, 53113.13723972615, 90637.34324039178, 80514.411391644, 82789.17184771982, 59059.15550651112, 39176.78886252431, 62390.995417881066, 96391.84433543542, 60352.23092441447, 92252.08579451808, 39921.2548512496, 83007.03992223997, 18026.983408052743, 58186.7639274043, 98419.4378981834, 63805.81972483852, 4816.674217799555, 47467.51792761344, 40927.775826629186, 53214.927694240476, 4662.826709421431, 85066.39554555863, 35048.31704362133, 8878.274607390767, 71089.88178039482, 27734.107581529253, 90683.28110515857, 65292.041150419325, 49060.28446915087, 67201.86517731009, 29265.89727822867, 89816.85806649123, 71371.33937353524, 79434.99493383216, 94403.3944144226, 76526.57903807516, 90052.3238147783, 83675.18726217425, 38784.69559059938, 34569.307494413646, 88651.30516612291, 8996.131251225226, 6275.485583272622, 84762.1418927919, 76650.8354950964, 72921.49080366177, 3877.3519354748933, 56970.25139869833, 3963.911092303718, 40331.51690965064, 4869.258657051622, 5011.771700191847, 64216.634727168086, 34802.233463950346, 54841.86445601452, 43648.583823546505, 13112.294746059593, 94997.3564726209, 39856.13933978489, 31858.88525731193, 75004.91668761619, 1905.8887871568952, 47216.58687235798, 30517.10943833579, 36382.79476960772, 77250.47820373617, 12679.89349355202, 80214.4128822345, 14313.967875113898, 7385.224427549786, 26260.468948340764, 26718.92318667234, 60995.92756552366, 85897.66074894642, 90922.63148274264, 12351.768567373178, 93064.38483296972, 89639.22116200863, 1762.221739860026, 47262.7768603605, 43590.43163311047, 4988.964238015503, 95802.31683953306, 79620.00514778981, 62742.03811477501, 34853.173590450526, 22638.512885979977, 65638.95781108989, 11416.571797996967, 46751.411236736305, 42957.52455338856, 79847.35055118048, 58789.20746742871, 52954.10436246979, 66828.80390275009, 49077.99327236378, 79599.52513446644, 40236.2538597718]} +{"id": 1966, "vector": [81747.65561526743, 91974.3833960357, 59115.19091164066, 28008.700752046643, 79663.62815597162, 76158.85518538547, 10375.36578746543, 31769.121503973198, 97038.15691241423, 16707.71901808954, 4691.218524385255, 94097.78053576499, 81448.86191490512, 57569.3743072734, 22614.843648367343, 80649.72629938714, 68159.07711917417, 1784.9621544096194, 79741.64246935373, 12436.233127436624, 35526.432101026396, 83797.27588166998, 5013.754493897315, 29519.57865886262, 61938.85653219825, 53964.25666455057, 81060.80894847837, 8192.409333664575, 27864.767737490693, 40720.09668923976, 86185.82042172436, 14733.392988235772, 65953.68662672036, 1589.4776028096746, 65515.104361671394, 48897.17402150634, 62415.14443025553, 70184.79244743638, 26001.17210431109, 69033.52035592475, 85596.31587660544, 99976.1456224039, 6420.5837472819385, 60527.56718506773, 83265.0396775291, 78896.27540704605, 83186.52250981846, 6207.044737482382, 80324.13071623637, 45411.83227474641, 58239.98985201674, 58529.36400122196, 23485.92604617964, 63117.400156235395, 42222.90454252915, 89980.14835708698, 81615.28221261124, 57528.59831422632, 24838.7483706621, 28468.540041818578, 75594.04385229082, 2308.123302081111, 31157.71144622317, 46127.939968792176, 27654.32062731944, 25074.832357301722, 12610.42539683267, 27742.129667532023, 77012.0370211825, 66003.76711636405, 35633.81542375442, 13201.47993259091, 42073.256482867815, 49722.26360541857, 96263.9273882431, 26603.529229148724, 3194.6496860975726, 68170.82722340798, 50928.43900300333, 52562.383683952175, 90572.822471093, 15459.0012301422, 59261.07864573833, 58887.65478095953, 45184.85039995812, 80970.10262851283, 28571.505910673222, 32021.090941598784, 34070.41015740488, 90043.15409808533, 6591.346833386702, 94207.2451553462, 82191.30074633031, 73839.26734026583, 80136.15849876494, 78569.72510199939, 7279.658037573733, 75284.00066069003, 9650.065902968952, 71600.68784529965, 80792.83720996279, 54240.61882263639, 44331.291525760505, 33426.9806889344, 48093.46516147038, 4190.513301434317, 32991.476414373734, 95744.85553097309, 80065.47183346507, 47512.85740597918, 6006.906893722419, 86845.52487565462, 90060.82025133073, 80819.0957626552, 86288.30487079451, 18169.485676842934, 6465.862278716095, 67329.76895139934, 75071.21210241153, 41052.92159097853, 21736.927812252492, 65134.87099349039, 59583.09391201171, 43721.70010390327, 65619.16092627626, 73664.68177762086, 50086.843958872574, 83828.08497840933]} +{"id": 1753, "vector": [18508.14213639943, 23842.705241595886, 91433.71422196174, 77186.84567432782, 66811.48067261223, 9931.1566420752, 93118.3668834668, 86646.85506281498, 78222.78248235528, 95536.48305049773, 79642.91348854275, 67584.70813221243, 4089.412585680152, 70099.91639174573, 91560.25968200614, 26542.277991984964, 60622.35293044124, 32139.420799077732, 80234.19558737906, 28949.64197602611, 60353.96130120909, 66946.48182463524, 36180.788477255024, 32835.240558679834, 82641.72999071049, 46108.031562278986, 35651.56511226546, 43901.68229805367, 48719.13286050764, 74022.4948024651, 26583.35202934561, 95669.12747078972, 40667.147795122415, 21367.004291253754, 95596.880362562, 84563.31776116166, 96302.48828818102, 17259.911064511834, 39789.15929846001, 52062.28689652549, 70331.28075076503, 87607.78822693355, 41054.43064513471, 52012.89545429405, 17757.587521326044, 94801.94426214762, 15528.76942885455, 72468.85887829965, 1029.5106702054356, 15017.40106581394, 22847.78825555833, 30820.659466100275, 75144.88071832921, 40753.68978703332, 65105.235212526255, 85630.66532902495, 28893.261238561296, 75600.47950870363, 17238.59101737749, 84229.03510135399, 3694.3827830636765, 79921.0055967039, 5808.845643019423, 35547.102327290195, 23031.092289862954, 4268.78656570977, 88457.01681795018, 9991.448703948736, 17705.694041136954, 96574.61158538066, 12911.613222210482, 70918.90352575669, 26141.113383634063, 15281.678298921275, 8709.566688989045, 63101.417752228095, 62267.62272371205, 56457.85994821747, 34313.288878532156, 98305.97993332798, 17477.010937626692, 65719.12254138108, 6672.653241780325, 20840.734913491655, 5242.794391524874, 70221.09989820115, 24280.18438275651, 87738.48574524416, 43336.35888393949, 34958.39738795373, 58309.19325798649, 79381.30150191365, 10548.584569866836, 4770.186718091507, 2276.7508362332655, 79801.01164035777, 84072.65312413404, 13116.107167553615, 90048.15820251452, 47913.07792201622, 42584.4281349038, 2975.092579410965, 50904.599235447764, 79066.02690640162, 64036.66897437601, 63014.7209491565, 5583.421323416859, 79815.46303827423, 84668.00106555934, 213.35841104760388, 83736.2241420029, 8176.48951232699, 59838.03644978595, 74619.42524625661, 52110.558263273844, 74578.01967566481, 99697.85095730191, 21786.72211346525, 8389.15700570788, 71808.92232392632, 75280.00867761765, 53964.70775587885, 95397.35440779474, 11290.464498201092, 20870.859986340784, 53925.484666713484, 96224.47676075509, 31260.50164974995]} +{"id": 868, "vector": [58651.85375393109, 56575.2062527634, 87535.59383505784, 72298.95898545183, 66707.30828670326, 55484.29592073708, 29822.451911127613, 2182.2302926419848, 69335.40283051097, 64064.287986690375, 51248.16873009149, 78909.9634187406, 80612.16333804476, 9052.01431362661, 16549.58447147079, 19728.636983267123, 1632.522484063992, 32105.116480476758, 87344.24624506508, 93219.75292281572, 19026.339365353928, 34670.17925555056, 6479.056647001302, 43975.798527597966, 76250.54803395635, 62976.99921599132, 54461.067302029755, 82753.00515914605, 54465.52706963814, 53495.83872526662, 24321.818913526215, 26750.876386226762, 20335.97080494327, 23947.383378565413, 7643.724433298893, 51590.16384907601, 63088.19918149845, 24902.565185172785, 60890.9660572714, 34033.48021645618, 49882.5407881005, 54312.209434598815, 6500.608081945014, 15004.882984915646, 79730.6688766232, 36274.47546817073, 49852.13702813826, 66471.74144712939, 46844.47709033761, 28103.657453344546, 62506.85952052107, 34396.790978764206, 33880.696266865685, 6295.766252199708, 39850.537161177024, 8348.887440978404, 98096.38451376201, 39583.913756460264, 36079.87897638981, 7110.581370615021, 51970.43780642674, 89200.70253107995, 54534.29477996885, 13024.065060896917, 51882.105934816325, 31775.395624120785, 64543.894616646656, 84551.18278667127, 65866.15757733422, 41189.95723690894, 77731.79907041021, 68145.4861621404, 58961.177279238276, 71864.70774401797, 29551.801466735793, 46652.65452267445, 24724.441947692212, 52906.35763676091, 79464.72930895525, 77448.99916232527, 48071.449501310104, 70154.82440938223, 9693.728391914268, 78204.65452175555, 9603.66505342226, 44450.556638693495, 83103.24090683933, 6632.26928288172, 14344.26017977749, 47861.74032564512, 34678.2829995669, 8288.259945195075, 32910.0475973883, 24999.329429659258, 73522.80515557237, 97677.1230492847, 48453.051683025325, 41376.67626237364, 28508.193989272324, 89883.22783327111, 73716.3482363365, 18277.166956705594, 50110.00234413697, 5635.493421345394, 15775.085513179754, 38093.001231051436, 9618.177896057166, 95628.35222231912, 44110.29862803618, 60210.87238071354, 19989.003459305477, 69559.05270125275, 25840.137813535803, 89238.50518271413, 67459.27583027996, 46139.512328343655, 69057.17751781245, 18904.382889341498, 44686.18082026643, 64218.66095536796, 34640.35225464802, 65747.34742487958, 55643.788433868765, 39318.41330993817, 3722.23714882145, 40061.483430386725, 93472.33236291651, 96756.79181447302]} +{"id": 762, "vector": [369.40168366420556, 4554.440785034597, 28507.65177113109, 97172.58997603196, 97316.55197387225, 5765.859321912226, 78988.22994192642, 80213.36874003969, 27281.68622170338, 10948.57395823874, 84901.98153908533, 4238.784311658228, 67617.1061107939, 5570.103642999457, 46430.06466182246, 82756.94584858553, 48707.04247717068, 23187.166892129586, 38823.03783899901, 85914.55636324083, 7522.182554437051, 65656.83099051853, 3359.2091503063857, 1989.1842561667095, 49445.98781414359, 46413.23802992465, 72952.88442283476, 13675.176631005437, 37913.97018742964, 63937.761539851344, 57923.349763914935, 33811.949052965654, 45536.4361603707, 72064.53443667927, 71166.95654667294, 18424.893597052327, 98635.07037844627, 87422.75220615037, 64840.37952177819, 88772.591417089, 5593.312105848958, 93680.24494455704, 90329.02365792736, 9712.33025740409, 78301.72577213151, 37427.99718284739, 89837.95201669475, 48799.04296275731, 15878.965439983427, 55190.35893308173, 31231.970465504754, 13938.905927318978, 20592.701555089297, 19440.366308151068, 43508.587321625455, 74866.30899455318, 2487.396710403855, 47246.14185119801, 99629.47424505716, 57709.307406890395, 31147.837294407887, 58084.05509418827, 14207.15189727173, 34465.90906926631, 34825.037253833754, 7937.218039446326, 53068.29539287914, 13065.279145444041, 11715.377551710182, 22187.70286570747, 52232.726292263476, 4062.8315563743354, 5589.630566394443, 46657.834332573315, 47245.69922268542, 59262.29785843306, 73584.41055639891, 50048.52274619577, 75089.21141639851, 58236.06349073751, 6024.4151273255, 60696.80965784636, 51600.16853792695, 33130.90217801479, 17860.52292686341, 98880.20957807961, 56378.866619439286, 67065.23211580078, 4009.3066636512685, 47286.552408132666, 43233.23907922194, 36075.49966111997, 18066.504375676184, 43216.40799350716, 39099.01949922803, 89936.27946640938, 84224.90055306997, 88237.36891241555, 16255.292439368508, 69954.70347050368, 93292.08457000286, 84968.55556418137, 62080.10539291642, 51906.88350479981, 90071.24600523853, 79698.10784189812, 23273.19324420205, 44614.37405431927, 17072.10597232599, 17662.479577199618, 26624.77906625792, 19739.107352648065, 68628.01192458138, 23434.7703868968, 92558.43713483462, 98790.18109486802, 22211.915082545973, 83476.16897774125, 31015.31804836356, 75889.72515615907, 41459.768614949535, 46612.90919518691, 33794.450119050765, 3901.870253764683, 84319.6558371999, 45792.874127059324, 23622.978590710176, 84643.53561613393]} +{"id": 280, "vector": [96350.4867757109, 71894.59402796815, 87647.79678547711, 30634.77833462578, 90887.25798041606, 61678.577031865534, 50583.33987241435, 88869.54471757356, 97466.99832145406, 42414.54691716745, 66780.21178863288, 58620.39958709721, 84415.07645538429, 81454.63634606493, 20536.892959910358, 36169.97818643709, 19257.946911529798, 25260.200618638395, 50554.77141602899, 45335.57686938183, 40190.62301618573, 62993.46411466964, 4587.2806465931835, 43258.47775776956, 14543.416240594532, 71767.83003254628, 92969.32574589261, 6800.6067577330405, 75453.87038971689, 5255.386441361409, 35964.18927697391, 94912.3516828697, 25038.631136648437, 1830.0340972073116, 61482.4029200806, 40520.94898216103, 43186.71485445152, 29821.573578046777, 82875.17123117263, 81976.8469141705, 62533.67843890518, 89911.38647547677, 58376.39531307012, 59077.37085985022, 10094.376826538997, 12477.315975688063, 73432.29064674914, 15008.49110602297, 96285.22273971153, 77935.38474106451, 64470.26055031945, 59058.540737260366, 72824.3033238336, 44085.838605698635, 74350.81343232999, 34052.50667640917, 6149.997372118909, 13329.458729688615, 21106.20091958404, 97029.68269117617, 468.8193049510514, 11817.74816729395, 36892.20710897451, 42436.07312652663, 25163.627711921865, 82839.90231317857, 90260.42380943592, 1260.694716712063, 59741.63745291253, 28185.88961727447, 9336.080122961675, 18183.97580242741, 91312.2587660937, 38677.03756527625, 3128.113774226049, 6849.24202642182, 66104.08845038785, 29046.97063454943, 64650.78082102731, 7560.8817514476905, 72277.49949099976, 85697.07035656463, 64977.80440112772, 24556.512598055426, 69718.54988559027, 42708.40971627774, 12919.806032687498, 82750.46425406366, 62156.65967269947, 66471.86322855267, 43278.251929736434, 31430.47551218737, 42291.317445614106, 90984.71103190645, 83272.42527034535, 8224.041531302251, 28840.736711047142, 42173.04832629062, 71986.95429387988, 91772.81734610705, 77356.63832980586, 49937.40518288213, 27549.70021228208, 5496.9771702438, 5732.134642337627, 59954.39901035119, 73485.39460336538, 15596.717037264241, 22842.774224736895, 87809.79914549527, 49772.97793028143, 9707.460904626398, 99244.75417794539, 23341.467331455668, 11153.858307723263, 91775.64755094833, 49351.87802561989, 29576.400897940337, 37281.09894724808, 37818.611020960125, 97039.62087168462, 933.4476464775698, 45268.03934949853, 6454.814969238876, 23453.929217650217, 96834.2708329074, 360.6739713420626, 4654.298156523895]} +{"id": 493, "vector": [48662.21795704157, 45333.48176145437, 93157.76334576814, 87593.86821528795, 1436.3835587969943, 28941.273641669763, 47517.11043972155, 96655.90715214376, 82563.16847455878, 66819.42483589733, 99048.96027292454, 91139.24337732306, 99527.15266904583, 8099.758433111603, 67202.38806295396, 27769.359080038226, 15000.098682819118, 60643.029133997894, 99358.1612684158, 71658.51161036002, 61557.82210653615, 16047.636252390252, 35854.21658267012, 59477.33365380977, 21032.257423541323, 59777.51261449656, 40361.89423849694, 5908.309713189264, 77902.71498890761, 71478.4740044917, 93321.0589282798, 69104.66564659952, 62697.94258797859, 88021.39303572729, 97845.10469558921, 2739.116369664374, 79721.70214528085, 5311.117828885803, 53905.8995851195, 20717.316307665, 14002.288370697914, 24690.740196557683, 74713.4296263692, 94508.46361154607, 40167.76633005242, 92707.28688738278, 94406.49577643513, 58983.978084070885, 25525.985557260577, 89607.10648001515, 94906.77459460699, 34882.12585851782, 2790.785595581369, 45099.11450205305, 78031.47861565836, 36251.849749692046, 35595.238080265444, 72262.68406554729, 60666.85257221376, 15674.129186884533, 20230.811587289954, 43025.20984660533, 65543.89906317375, 72949.02184928535, 17572.472938393523, 23202.11813659372, 52875.25971638278, 93062.43744752153, 75893.80979345366, 12202.443338057601, 35861.431962358256, 89326.06449035896, 98574.94012142297, 83243.49744986436, 53072.90494149045, 61010.267946415406, 22244.652292317434, 84898.74300985427, 27227.39745343783, 62397.00243240123, 87604.13792498235, 4295.152699730187, 41557.94040772399, 21344.75371491564, 28201.517609750957, 63509.22980197077, 70204.69628691487, 43266.16359290719, 48510.04598330444, 44481.145180613756, 51556.463118067106, 84356.10022979447, 18259.78456357713, 57235.018800583224, 47501.69869444395, 9841.680960604415, 69903.67842843017, 47355.645957486726, 40845.10675780099, 262.34147799620854, 75551.35039346559, 4570.85790083126, 10136.123934650077, 57379.471597739685, 3253.395390436453, 80084.3889817628, 71981.61835778801, 17518.7688110507, 65454.25507132555, 166.82372246472622, 55736.62944836857, 19459.44377611566, 62121.09885979365, 30055.19788925176, 65769.52703020822, 63606.5622419729, 28654.933124383853, 90971.8557473228, 35147.81863821045, 41147.00949419513, 70932.44883200487, 60429.57067718825, 6874.665042150652, 74078.47333844306, 71534.2939896173, 44647.836499749086, 15751.191722709202, 48805.30811500384]} +{"id": 1370, "vector": [69971.35518357808, 41389.055583246234, 34363.83444683323, 69124.30247346831, 41081.66896539669, 63745.66439722203, 26347.694534999067, 63468.06593160713, 32811.0211724021, 54832.330272642015, 38236.10258420662, 42569.88071268386, 79250.06144988305, 12682.427813792385, 96552.75755235575, 37728.205010396356, 74080.79145923155, 33149.274096935245, 37762.05055800397, 82471.56033235735, 56633.61630253673, 2762.6137308979446, 86878.79074858801, 93803.64060703125, 5444.54910707749, 93372.52394150871, 14472.85952175471, 21878.16421094937, 92511.73212782922, 524.6166202803826, 16696.819560441232, 46884.67135225079, 58942.24938285541, 68070.90199538517, 38375.538164650934, 89903.09861406783, 19535.89673875633, 62994.44193040307, 57319.24314736602, 38689.68721537164, 10649.161436165155, 19325.93065020308, 1459.6827634254294, 3764.484904625276, 44443.11157358728, 20588.916214715315, 41834.13133210074, 22679.86729277045, 69196.52006662873, 28777.155135602305, 50467.76696182954, 69292.76950413847, 83849.57202954635, 45480.07218267626, 44319.66152169341, 3242.811676139312, 39355.37168006634, 29951.7011299856, 99800.92447389546, 85633.27743203989, 6830.214959892566, 48096.28801696795, 6795.107323208471, 15673.390796754395, 62284.98527360051, 96838.40669926473, 95289.41454788703, 7970.339193321241, 30774.078219081613, 38645.130815953176, 43839.26807661827, 72035.40298478105, 37483.421911607984, 62137.601852530934, 83740.33689961612, 30372.29426800433, 98366.47603705994, 25882.35464051869, 29725.2236126054, 32344.55474246667, 96470.55484325378, 11811.134065529715, 5243.498980298, 2331.747276803897, 72266.02339865391, 47175.18714469543, 7083.308422036905, 3013.9214859209783, 81541.12512649132, 37600.44628955315, 43672.76650747194, 34920.216998171025, 49604.116132596144, 47790.62303351671, 47537.25510153927, 80426.76420081589, 4547.98423895606, 18144.61136376233, 70469.69731809416, 5974.799620074899, 3429.8822292941677, 45020.3786840684, 75603.71336709127, 92361.18947244486, 27345.508851137645, 48572.86147220825, 24527.746642285474, 76423.49049683941, 39447.16854112952, 74075.70412313662, 58510.16713407147, 96879.76314156693, 98850.56128899551, 20674.047465220625, 8604.999486279563, 95164.94401124014, 14216.138327492134, 9791.805780223483, 77046.30559435237, 79511.10619920563, 54997.70448893002, 1358.0742453775051, 59075.20711676129, 41330.841318822655, 23792.93042536216, 50841.193471965686, 35920.052902640695, 37200.686569836274]} +{"id": 1701, "vector": [23291.079760361823, 56780.80876667302, 7603.544857342948, 37472.68197893172, 2874.491098000509, 13841.837070781927, 57029.530896004675, 15649.661654903968, 27880.23422120799, 7144.259258207519, 41350.56853639285, 64332.208742530886, 40289.3917139744, 43726.981655261254, 49774.41338638433, 1532.5223356974948, 60613.55877793493, 56259.45547710691, 16813.079181497993, 41259.69172508545, 24913.549366957923, 63846.117847300244, 16574.28114890751, 83589.72369221746, 91504.3158092723, 47504.425095434344, 46604.731105799605, 21602.149347812792, 50246.760203421705, 13630.767593404946, 3031.225252903935, 66458.809048225, 71321.32496194591, 72484.77819571282, 43807.79754283557, 44017.14831641123, 67210.98454915317, 66593.95791619316, 5397.647454099463, 69037.7365142649, 23344.91151430651, 25814.707929936965, 51386.68838214966, 49357.95102335175, 29532.068776950717, 80522.13742631482, 40186.79276532313, 89556.82519376969, 16883.90724234876, 66356.07811371623, 6045.26546727866, 89428.98984050639, 76835.90800330241, 14496.828181034349, 4171.876560893628, 49935.221428993995, 72015.70927439253, 25616.29298813799, 70910.68352370207, 42900.10110223684, 81683.26979780714, 48052.86941814464, 60905.07037031047, 32664.870648867873, 56534.06801721283, 32082.051520000943, 44502.7985107833, 4317.52859215635, 27291.78255924555, 56798.37955258873, 58074.03127415609, 63612.600678750554, 23305.098641513243, 88394.97344143364, 48144.44031883097, 2784.4956918379958, 37345.935001141894, 57052.09211993895, 92543.80445500575, 89962.90748691323, 30331.28165048501, 9374.235882727255, 80315.73867378234, 71065.60785573033, 14496.913221120978, 79041.19119489581, 72037.11443422415, 55774.56559893993, 12718.326485533993, 7583.097965119278, 64939.961513098155, 45870.51935973182, 62515.90021677583, 35643.167025822164, 41199.42784404327, 13821.667648852532, 14001.075616759017, 85517.95789759896, 84303.80896558928, 51733.730955812614, 36577.213729353316, 94452.56449348679, 94053.80366772668, 28338.162205953842, 86163.01727155445, 81382.25959814449, 50009.55002003895, 11253.067620083846, 81875.69913056747, 27195.173698970786, 16026.993453457972, 60362.32459907468, 70504.11966242721, 29489.250769148177, 71163.49048588713, 11425.622567232962, 80672.05933626839, 91647.62621750582, 18359.06536872013, 86920.83836107331, 42401.99337715962, 80680.83405666833, 91204.07251972449, 52671.03492944575, 2974.2268995704535, 86828.68651687325, 71184.68702944079, 22421.501818979985]} +{"id": 1372, "vector": [28891.20670716305, 64092.01988263539, 86825.20225760613, 43096.854258808395, 42950.73594241912, 4161.977450411414, 72534.5738331371, 70142.1213157883, 56118.434211661486, 99734.31867573975, 97856.67406602697, 54524.81000597415, 14459.509072518318, 30030.40304285326, 26114.147797232177, 22769.31314261823, 15011.888912620552, 54318.45368520537, 34757.030449558355, 34572.02796119709, 38611.49235772495, 49841.74985820799, 63609.17965003943, 71745.05533991144, 38311.95587286754, 34952.2093438391, 63375.99561887231, 81209.82534886434, 3964.6967872719597, 65377.488066074984, 6617.87911159919, 68100.05072873658, 41227.043234464974, 69442.51634363651, 86691.01113711462, 60803.52546480489, 97183.31186098272, 95607.24843867672, 46519.675376853156, 22664.036319797808, 34075.78365184737, 42509.53660301702, 83636.48996409282, 38002.23313502724, 52844.78309285239, 60074.536188683, 96336.03522841063, 74649.95368635796, 69984.9908774689, 67381.59365668317, 16802.24762204011, 19074.474793650053, 83348.48645160801, 39206.22980320856, 42202.85798710347, 4651.996068460007, 23432.964026494985, 78615.3762781739, 18887.66550792155, 49550.443772641986, 75251.49738184288, 53238.014842362136, 73351.85127224623, 40385.18092426885, 19486.618265580146, 34461.57850396946, 19868.729422497967, 55933.26468273616, 10599.55973657557, 11765.493130035255, 62554.7949485887, 74825.07138568755, 70254.1423513827, 92002.46611600317, 7376.840758690584, 73783.28671942952, 30966.0757092168, 16399.57878794166, 96232.22503275861, 87070.56185011863, 89719.85790824286, 44168.651371826476, 91402.81148375821, 98920.44355061262, 13541.602872147529, 45821.588555642644, 4746.409848755006, 40565.411204731594, 7625.352291453225, 95217.5546196152, 73231.83503335546, 27543.15770234532, 14210.953307673568, 33004.439113903405, 33212.3721574195, 24787.78190321488, 87013.3516193403, 86239.89665572903, 32124.889435487003, 80212.89464222222, 11000.590986371672, 97513.04505219823, 10758.739684031416, 36402.930103180806, 57304.06901873648, 85706.32905293877, 51871.21983684139, 40648.8882837206, 51295.92984587202, 53878.0308138587, 52090.39334762168, 98438.85287319233, 49379.531295127555, 10433.647595910323, 26384.60707077287, 34657.101912573504, 6262.964632115298, 62586.191891497845, 5292.311862851185, 17768.443184824868, 4079.3336758161768, 3741.9678445729023, 62574.91130506463, 21942.479321701016, 1514.8216946163307, 54301.16787545629, 17479.2279235539, 60512.20714932642]} +{"id": 579, "vector": [32641.533450110226, 4003.515287036785, 49930.98401786869, 90908.71291317669, 73025.53561434883, 81147.46645816945, 68473.8714260829, 96602.60416624857, 74715.36612987079, 39872.283956119114, 83680.21111116522, 36350.77220133079, 10988.993974611316, 37604.76974169837, 64283.676138109106, 53704.611991028374, 53113.38350600067, 39132.335315861244, 79688.05139011504, 82742.74453401961, 57327.165345132205, 94288.94070335203, 27815.675188459798, 94018.51684386718, 52637.87654163491, 26063.747217396904, 16581.72025313408, 69209.54135242711, 89731.26255581893, 36135.14946677486, 19565.57503877885, 71910.79813475802, 70488.26368766122, 41787.76560076966, 30665.75396363269, 7587.499929776464, 53959.570276946724, 71031.57247250061, 72805.45572390525, 16365.32434988438, 26008.130094190452, 67401.70678913905, 25054.737335446887, 8384.933805223072, 43914.034212684586, 60723.096040446246, 6024.041143121328, 90399.05098782855, 43093.34862532891, 6748.926518603226, 26387.005566574408, 7465.242834036412, 46460.798494470044, 8851.411291571865, 60792.72100308154, 79479.78358711104, 62075.64467040063, 85419.68943695624, 69477.86936069498, 122.31459734919348, 69980.35152225594, 44142.328550307895, 13841.866495478638, 23016.677147506325, 74850.80764246175, 68231.1540903824, 88022.99598622747, 74742.41784885002, 60823.45546472282, 215.40028840331127, 86058.74962996243, 51944.34975443282, 82713.20991217437, 76542.89924504477, 23313.719700050682, 26317.463898968508, 79980.27130992194, 45091.084142931744, 78326.1158120373, 31975.068469867118, 32032.525020730496, 63531.58121038559, 31352.49090509419, 48450.026849675254, 90170.05306332391, 80315.19423168163, 84763.02134502746, 97568.6467145464, 47350.30030130218, 9672.575571109799, 50006.43216607243, 9861.00181986993, 78401.80300077057, 27920.730986380615, 28476.926050815688, 79129.50217084501, 12811.210181886845, 28682.409831734858, 9990.912279088481, 20691.559041170825, 11932.628788906308, 81276.57160699542, 65470.86889163212, 13573.543016121881, 69653.45329789481, 82566.81555662285, 25960.77869194706, 35287.21456522035, 68323.00726270118, 70263.4699642319, 10832.252821503575, 82409.72067893264, 93192.5410652122, 33178.33596439224, 18107.601869955124, 43801.87027421893, 1594.4558217759752, 35058.94922637026, 44534.08293457073, 81880.47506605342, 40526.021444230886, 16551.453166461437, 50758.75156184237, 55126.119587513414, 47534.27525894649, 41693.983659982216, 37579.34004107209, 51617.42793511215]} +{"id": 1345, "vector": [7751.739417901837, 42771.96997149334, 85764.74846804442, 79208.90681120422, 87781.78326514956, 18527.46039437674, 27776.51006008274, 36843.17506321356, 59646.409713676396, 67269.7942095246, 64000.97011381426, 47811.39331612325, 13162.289626603962, 24938.98504141846, 27083.571604638146, 1931.4320955289065, 66696.21161938348, 96493.42049867348, 78701.9466266394, 82150.00146213626, 73316.70766362105, 54982.54269129546, 72980.88854902158, 62956.11542879875, 10979.43607996309, 1432.5446523064666, 2888.1047955264917, 17237.58816565166, 51828.69004999203, 52200.41045028311, 63544.0790013091, 83942.64807041959, 25103.50107164667, 86457.20086877455, 62334.332858501904, 90145.67679610323, 50439.18319642019, 33493.84132517891, 27169.943747107616, 28912.92743804991, 53008.70105197011, 61901.511156641805, 64946.39369938596, 52493.38622612193, 69679.07801350836, 4755.452891149259, 6309.952111969475, 32231.730002450087, 88409.64475829487, 40687.52368996348, 64660.00690473847, 85432.73348392491, 22529.126924470165, 16141.976668701285, 38608.621101906596, 91768.63128414348, 2349.747152947279, 34593.19654199463, 39157.195593050434, 91279.56067368374, 87804.54718852327, 32277.98716441059, 32148.59853265568, 24378.20858439612, 24483.798198779805, 62681.772318636366, 81587.74746067486, 92642.25700322981, 27500.932421317837, 11462.518634561126, 48213.5813986525, 16305.518343533999, 2456.806094088637, 38458.27493010451, 70559.70489437642, 7724.417571539821, 62532.06770570412, 95696.8589616339, 2630.0341809004426, 83704.76502729813, 4148.956295886052, 92066.45309462442, 60308.57728538381, 32063.8001483202, 37559.332063676855, 37485.15862913365, 79279.83661918694, 91284.24834762514, 41946.58986631788, 13625.63967063023, 8151.102521548248, 6831.339071680809, 89117.78536863794, 71530.48335565187, 94985.48007466688, 61755.802917072935, 29836.36375695281, 2044.8215860962905, 23220.437846369434, 30688.85545083151, 85747.32588580408, 11066.922336306829, 6684.945010730503, 20453.855234985673, 31080.453283476174, 67404.23516495831, 28295.683349906907, 43391.65651818176, 40482.99546297459, 42527.74996422944, 81447.89262892528, 42313.68371628548, 26992.09660813803, 68284.0134094919, 13997.895121590687, 22021.26668169422, 49676.54179884471, 89061.01545347042, 4379.480683732362, 4731.493384579589, 71901.81694304221, 8.045305025927707, 47072.79554387776, 63436.935409180216, 70575.18267070511, 90777.17346990062, 79416.70978190606, 60286.278960608586]} +{"id": 780, "vector": [51265.852825585134, 2413.9587501826763, 8859.47344515341, 69079.10670279364, 27853.59246183775, 78064.34889293778, 24764.67270792322, 76636.72885824136, 88775.48800151031, 13195.05558892553, 66639.43516121931, 27454.342857853164, 67876.97077674836, 10373.482519321475, 73860.28624077926, 30850.659798739256, 32997.71333839131, 64559.873797041146, 26463.98611767533, 69561.95783905618, 16379.158065848698, 59858.26185424399, 28658.848655754577, 36055.026563448155, 39096.113031715984, 6389.387016697768, 51565.232529441426, 32036.77478299205, 88537.87559178271, 92763.65556591761, 59759.1850475817, 95093.10759966182, 86256.25308380791, 58015.795530338495, 17225.043729509638, 66435.76832134112, 26321.653284143853, 38385.68131186069, 69842.39297402307, 20112.624569201842, 81187.17937575099, 78652.38173917148, 18571.33002126291, 35322.665187409075, 80661.2821165446, 55534.75138566304, 53855.85337317348, 8011.617396226278, 40417.747481110666, 48432.31951079913, 23949.352586789453, 65040.81351904281, 47707.18136627922, 42873.50179971112, 68150.07103469945, 1743.6688557316838, 8759.598801237378, 17964.925520193796, 51500.54272921054, 57845.766390886965, 23014.18996211384, 39961.50361201183, 89424.44792292426, 96325.82839013357, 24969.662918924852, 35122.560219219544, 99740.40813140413, 23798.595982966242, 18140.408257048803, 17231.298874724587, 39095.525063274625, 37227.318861981454, 30839.777294758365, 65450.94149774385, 11625.020747628356, 90063.45310171113, 30852.912790601604, 41012.569822866055, 77705.3256914948, 93086.36148202182, 98611.5617551992, 26791.09506170172, 62525.175563263554, 59345.693294088494, 2388.3612714764467, 10923.846505788504, 39355.10839349816, 38621.01365557635, 93581.26486305533, 27388.735747133076, 19574.277310257592, 22307.48681340449, 67698.40203088224, 36046.84921580004, 40623.272086124605, 84822.58473606098, 13805.376022313643, 32154.38380481046, 2720.283236491761, 46689.62133816542, 69550.9191346947, 19902.700747600054, 12873.410560299204, 32547.0289718242, 26256.933085495348, 1497.525925344645, 12422.123325952738, 72727.5174526233, 53217.53787851319, 14726.600362458776, 76984.2516752687, 98169.31751809054, 21146.63634900128, 55146.03327051184, 81.03922132312036, 32916.72720934902, 80094.8442722334, 14315.470471533254, 73443.30501053706, 26238.670512063924, 66762.57814456092, 81079.05400338784, 15113.308423698369, 27388.611105886008, 7956.511179122439, 74658.69716437299, 4120.779288693366, 40347.60676053998]} +{"id": 856, "vector": [48191.029831227075, 22237.38407888407, 71532.47850716577, 46177.040239660404, 11629.752471634347, 59864.67123114462, 63351.838720255124, 95127.21050677085, 16254.035554060974, 51361.85030053285, 98661.86879267276, 84174.16486700712, 40795.08077961493, 26388.59144728183, 15409.284404063983, 65616.10677078283, 51904.35886517802, 43380.71903865221, 24387.95053274131, 36417.776274065174, 14757.367035969848, 87503.46781215508, 44432.33374733943, 15156.521350160978, 3351.873264010796, 28622.33342922219, 38980.18949701526, 26030.65445238496, 94430.84998274724, 86020.33569079131, 86017.26731603102, 22041.66452230284, 91866.0398721474, 38483.834304456446, 26811.366893044087, 53433.3933941889, 54043.21973804699, 77578.35582482557, 74002.77655032836, 3781.6641221693812, 48524.054000640026, 86891.00196651406, 31491.303813793937, 95074.92271198798, 96956.39937741906, 51332.73438684577, 27020.415641179996, 35553.56666486171, 90505.79576305494, 54037.40915164625, 2843.861756118016, 16641.862105656757, 29130.651183385435, 22532.617575360837, 52546.58755332359, 89501.5751385882, 70830.57885473473, 72616.5566483112, 17514.120001813073, 28616.749112420493, 96489.18336900463, 68591.54922099273, 82336.8692744431, 71065.69598543229, 3733.090054587951, 50055.86463427979, 28003.83542156636, 13580.664802097486, 37256.368666326045, 13320.463848250552, 47214.85836737041, 69884.4978734048, 64130.52936809015, 51826.403228223695, 87245.03362018625, 85461.88205308997, 86826.71573780885, 5006.885535020011, 41314.14385673918, 25133.991700096347, 98821.03498722958, 90062.69447061012, 7820.12873804594, 83723.43303853068, 48913.83303813285, 62669.89567220195, 13532.143897010863, 15174.420116394405, 86096.5107705998, 45473.026535049125, 48729.59634117147, 70029.85784149688, 33840.93867461496, 84400.19806550806, 1970.9082480864338, 46812.257422427254, 58878.18444508022, 97544.37039118327, 93902.9902032184, 14240.251379224845, 48739.69137052432, 41426.516408254676, 26355.898651598574, 67407.18378716196, 94413.36009812, 37383.89535811237, 82751.79523607579, 21654.566520986216, 59903.607014213434, 74763.03773017378, 49028.61120860239, 51938.05628670094, 69643.38217918288, 65995.02487241258, 46363.82483820029, 11034.21993902204, 32531.294192576064, 98173.92888302937, 95432.0048662217, 85372.12689276866, 415.79266611663223, 66213.89734866997, 5571.445182642098, 32146.57910270072, 13715.594204809844, 43450.91089500327, 19374.857299065952, 69217.87313699079]} +{"id": 286, "vector": [76467.92648089962, 83404.95139684198, 67783.62264376569, 38607.538202456184, 37727.0704762997, 69905.59550219798, 71170.01698759517, 46509.46387196304, 71918.67733909878, 30325.47240256228, 82611.5321184824, 86081.84268515177, 47427.019720591226, 53576.30521838082, 41630.220897160245, 69419.9427113611, 15199.418171639756, 97455.66171684599, 36327.71988534743, 36890.99065942434, 77674.39140103415, 24892.27115225122, 61822.72104839988, 2927.4916033870336, 55755.600249347845, 46159.18268347199, 65001.780387321516, 19741.420078871808, 50055.781709478695, 94986.41325845559, 19536.76793310717, 33255.03868355971, 44170.22195490036, 1251.868911169618, 79922.84656215421, 96655.65088981157, 40006.53752609987, 65474.73613482251, 83423.22859000522, 26850.817175617736, 15048.484936937111, 76332.6376906777, 44154.46181048627, 24585.087967254938, 13109.975599932277, 24833.15151938291, 83583.15993252923, 8284.239370516843, 23767.85641493414, 2191.236290294074, 86914.2054917216, 94790.89092306989, 32160.83417881943, 95019.99086678741, 66308.61438404178, 21953.638038854162, 54798.57407516843, 73784.18033254184, 35541.33220193339, 17523.559775158683, 55849.42789727008, 71456.11638192639, 35477.627605737514, 77219.66566594878, 90158.5993791838, 22296.891258387965, 1664.811224542473, 50107.81017047743, 80658.15805335334, 29269.98530464784, 41908.50933024761, 94638.03978861465, 39908.24653740895, 96911.03883603179, 54271.10534332424, 19839.72867086178, 13960.429991426981, 18710.941541483207, 18648.391374280327, 63735.43271579036, 38798.413853875834, 24226.478213187464, 77024.84046464346, 29717.98443823368, 52994.33040431634, 65361.030541503664, 70371.11494872441, 71861.70532400931, 58468.21056939735, 9629.267361655524, 18272.00039119129, 24668.049921888767, 77767.3621060287, 45396.91951066462, 69251.94886643978, 20030.55617289464, 81064.21048975711, 68133.10606106294, 14720.614801031328, 30501.8503118619, 63056.33695915174, 27761.431044925233, 7100.269016712624, 43838.779793158166, 21267.32857225493, 57408.60992892385, 69857.04672942306, 50691.96380740343, 72614.20927261298, 79987.79889481424, 71986.59048858323, 74029.73450403441, 10638.8089859082, 10627.387050065972, 45765.91588378596, 89686.2253378677, 62983.17612639045, 45905.64631583644, 3530.927392783989, 75188.07627334833, 13757.368436895833, 42797.10147048388, 33657.985495854846, 49889.9274969007, 75837.64967749287, 57452.45557254439, 46838.54293782994, 22287.413012976696]} +{"id": 942, "vector": [76.36494267625471, 32042.293397157286, 55441.310192397075, 69234.67296256305, 27875.981506525306, 90910.96512616247, 96360.97764252826, 24996.661803964926, 89977.31948095895, 68944.27142850134, 73274.80719703209, 39343.156080539586, 22340.994811652658, 63189.81390930661, 51115.974730007765, 31590.625433765672, 91233.0380168933, 51172.16173021676, 75477.90189471992, 89498.6429150711, 40773.135698883125, 81379.07074014278, 2324.5398576732136, 45519.15532353098, 6309.781545184834, 80259.33453413485, 74416.15822419188, 45614.30557851276, 54023.26983578213, 54338.25267969314, 81198.54532350144, 91415.47748215536, 43205.240671168554, 47655.18199777361, 63583.376716954845, 31502.11680256445, 15434.351960269365, 98802.11369310759, 68647.96973418733, 62978.74929441341, 77083.09256206661, 93834.48627574362, 93911.1383635071, 28831.35348109711, 64731.5597903005, 91996.29644767223, 2500.492094025386, 65102.91136892044, 10164.992164654252, 20862.618481206664, 66488.86056987518, 16258.889083488071, 83895.4596913132, 87664.20549636369, 98110.80525011376, 64750.52081302984, 50216.3785490907, 70392.91466640228, 84027.48875014117, 83047.19805895108, 55827.945775899316, 28597.364844278494, 77987.8358955826, 84468.65720854636, 72865.86292583209, 8840.00198049021, 37522.028931752604, 20858.201115734697, 93503.55384401852, 57667.65786379928, 546.7497683864675, 645.5304030621667, 76120.32888944093, 4992.839383405212, 10665.097518161116, 94565.1987805937, 30298.201606762555, 72236.78888014903, 81446.26872920807, 89927.6903641047, 48987.31185886728, 56439.85972200341, 50453.2557926984, 78982.77818324197, 51382.027333393045, 31704.867725190943, 78136.79561028954, 14973.74728048796, 56705.40538330051, 42164.61391280274, 12035.961486922886, 27345.936651818236, 2559.5007569425675, 66262.6927846836, 15001.4058343376, 12045.413079436828, 71106.95541192932, 66382.21925739113, 27804.807084425753, 20958.8969061822, 13830.822484872751, 61120.66934645634, 60212.931994721876, 3702.2635624171407, 45570.71323787778, 26398.52275880664, 22569.084406802485, 79599.46400089144, 25544.332191076744, 28749.922419012575, 66010.95887562391, 42005.22731380119, 31850.373568015166, 85539.76683069838, 60113.57418838694, 58738.4420271605, 38664.60970332484, 9094.702263290234, 75168.12761594272, 56378.26162677024, 50318.08064808761, 51275.74373299106, 74553.46026736371, 8928.052726837477, 81772.03091994305, 9602.20489876139, 89126.54132723897, 35618.80872796523]} +{"id": 818, "vector": [85605.90384904224, 59147.62538102247, 2060.9891878608287, 45754.40669712365, 18032.728339906433, 41722.84349006461, 39260.84130829865, 16583.209199729154, 64782.93778802174, 4837.413990453399, 49302.33969369704, 57335.749989928, 52072.667646312155, 30568.386576974415, 91844.96751040255, 36961.94654583465, 46294.19496877067, 73009.22525592225, 69145.55644806409, 63346.64072586682, 61583.33569528601, 68370.872387562, 21832.764163123764, 57077.648859656474, 58743.60157230203, 48913.13769755343, 50488.53440069599, 51965.90988083116, 67679.59931528992, 76872.24412808445, 45238.68797645912, 48017.58464846333, 14106.604498608354, 21805.44299801742, 57395.82528566749, 82406.80742568022, 91213.95074806164, 97217.71860509642, 85365.63578655232, 47007.71335499906, 82349.68946731437, 6037.486944868753, 2826.2990312032944, 35223.58064921117, 22449.14082417122, 7168.998067818311, 58379.17335979853, 55418.044983297696, 15651.940573872304, 32438.08943757245, 7990.680713837395, 79263.0843853312, 85598.3093174308, 71863.82547813283, 76783.97184142417, 17174.821554978847, 86940.13638114814, 51310.93438343367, 25953.67176826534, 60221.36210849613, 7129.413141913777, 23602.090686007294, 51659.69507955017, 68075.73882559422, 99211.63978573905, 52406.73672272789, 90490.82577792802, 52985.05294272682, 51939.38489343275, 10787.532510165032, 25563.34321953749, 40519.620740270344, 14846.125365838125, 82373.419971543, 88413.12853198081, 94505.91554992848, 52946.096846293614, 72205.6420741786, 13394.944592419733, 32320.729771135415, 64245.26474767668, 18294.503114751526, 27570.49221643595, 83107.45945673839, 77692.0344561851, 33462.8585203897, 16060.330214575602, 77043.5439379094, 64726.70219343706, 2321.593187973614, 48529.786438513, 60837.644524558586, 59198.49307025966, 66547.96140057078, 56123.074598329, 31408.632828123184, 17611.765812515536, 41033.10316299627, 47340.74991832493, 66636.11906667399, 83147.16152090691, 54803.2282562021, 71888.47759215695, 91677.97265114552, 4672.379457902242, 26604.428945832304, 30975.103946081595, 11657.908462237021, 58857.53677602533, 79039.78881216436, 6065.741047299455, 31761.969004568437, 16200.893633830427, 955.8609559627284, 87052.10858348549, 76268.68939052083, 93916.94348699284, 88739.84460882067, 92980.8820399183, 56750.14466094417, 40381.75729592006, 65480.29274154237, 31178.9962916578, 2710.429644433654, 91776.82923604222, 86750.13953141533, 72244.14380151039, 77905.52718878594]} +{"id": 194, "vector": [72771.05178786012, 39623.90539741151, 5739.8659627253655, 56474.44719650231, 31144.190489872948, 85970.5021289384, 38400.180456723734, 23437.58855185576, 61293.31036282165, 19195.414733093232, 23352.912305105656, 53884.090513601026, 94992.2659515343, 14423.841800945847, 41193.66995358349, 19175.976832073993, 64835.994772026905, 23802.35566244049, 9383.38875459791, 73927.36781532213, 10507.591060509469, 78130.90933299152, 22073.589133907124, 91061.92678032347, 31984.097885803785, 8988.433457316369, 28949.50547407318, 86415.9075582138, 89500.68250760599, 79838.03356354935, 83248.61696288131, 21196.473176672116, 34767.85130144733, 64048.455129808724, 64331.430704450955, 26577.990737700187, 20684.925670768927, 50672.00952665212, 80651.38593781076, 23474.359587494033, 46385.08790255319, 26467.231986136263, 55452.11225935731, 78413.12245579688, 53000.07065095472, 90149.48920017922, 37750.682933144184, 9619.754830306614, 35508.370619907306, 18071.606026875175, 46348.20816140809, 30885.387555846533, 76528.3271283637, 28730.537854015936, 79326.47078726231, 46246.39823048714, 36144.93313988637, 85412.99038964779, 26144.57377548255, 58895.94596749252, 41996.34321422806, 48209.870126148504, 44023.390948958404, 19125.678841241435, 70002.51461897054, 69189.26397918815, 48323.76924303923, 47829.565266683036, 8849.548269894114, 33427.431294607726, 73588.36051806499, 50026.72223247445, 40192.350269711395, 68481.6944485657, 80688.4283671126, 78008.52188391391, 1660.0986241534056, 58939.18827970541, 2630.1596002401893, 44862.66115712686, 58222.998796028776, 84665.7937910145, 9512.930466381596, 23801.984313940706, 6277.448874331771, 77952.4426312539, 4541.624260830579, 15645.241782467567, 43914.114679255166, 67474.16377895889, 94456.32815060254, 36878.90901493146, 81520.5721333001, 63589.89664163205, 31082.176113128113, 72305.24888744761, 54176.6254159491, 76486.90689483476, 5229.429393249907, 24145.492913356735, 62926.95272326893, 55003.97030045499, 51527.96516675834, 34556.98068047321, 87076.11067785516, 79806.3756065291, 1665.808278827019, 44481.84412304746, 94734.36851183114, 79476.62758169505, 16516.36141364612, 42943.88393777723, 75056.00011009366, 78484.72177427936, 44169.66647077073, 75068.05314305666, 93132.74040980946, 51555.44795846534, 33000.7141393325, 96639.7886475965, 92002.33061385201, 23602.015942222653, 39724.5016340311, 90644.74353152222, 97555.61191904402, 61583.8129665518, 17699.842408724307, 64722.1724991922]} +{"id": 578, "vector": [85177.24288818958, 55390.787090099104, 89877.10522816847, 93161.01145660508, 19638.720711176637, 80669.37364213831, 70913.75432006316, 97187.9283233313, 10807.077232199947, 73329.01907144593, 74491.1471643137, 47799.00193070623, 21453.350580061913, 83538.82999449297, 35201.45586524024, 19670.907806925363, 38735.474106497226, 42252.766898087226, 29585.47476587061, 39357.07031322202, 61516.073473132106, 77358.10454903406, 50122.489928778814, 20282.206071886932, 82114.03777083711, 1259.3876816060722, 88815.87227149881, 18274.940165700937, 24379.760265951, 32071.89082087075, 30040.760373453857, 2527.148893868092, 52147.84444389533, 7467.5291834835125, 70317.21257480147, 70917.68550188551, 19230.016737142807, 76615.5127856033, 8245.520724915023, 92519.52185700648, 3957.807888196907, 11072.013723548413, 1659.2476328448956, 45436.16594000719, 92882.13912365388, 77630.58671911054, 45649.249295549846, 7637.834852637515, 88245.8819219524, 20866.62982180194, 52300.40720679421, 71858.70748904077, 8056.287086672875, 98036.37984151475, 96464.1298185665, 89159.30233824735, 60051.18109845502, 27212.479899234455, 58385.13257929427, 83396.13344254553, 69762.18141447312, 12768.643676834845, 29235.25069257521, 38759.54338966002, 46656.7528256972, 44558.38442087223, 96835.21895857768, 18830.25430028743, 31998.810110838484, 40116.80606168112, 14489.653923687185, 38406.73544133202, 54131.431882815916, 48679.706601622565, 64431.283725838875, 55119.72694590042, 40067.79146004212, 32470.591696574425, 93718.9158944949, 3028.6532039177528, 55979.08216619088, 70955.82142534279, 98311.11085861486, 68986.68388103036, 56796.290318298416, 61649.75806158388, 93745.826084528, 63114.278462763185, 22548.756765149792, 48695.46290482368, 96467.46394360115, 28802.509711726343, 4699.170774165895, 18090.043155688752, 44237.92376275154, 94024.29355240865, 20195.999774553642, 44029.09334613497, 10891.771312499599, 56211.10368551663, 61300.796961307555, 59215.60075831127, 97718.85720609555, 79712.3762252115, 24816.02167515965, 38254.75145844353, 9111.327754047126, 72295.77492293564, 19220.039244618194, 29876.977495939605, 82254.53148183397, 90401.32030620507, 98629.98957288303, 2176.0619500247767, 41784.03423106687, 49260.217973116414, 31544.238771450197, 45416.447331299845, 30990.49664436482, 36856.825804391534, 74369.5931638103, 89791.51059831613, 65251.063576949695, 6171.618397972245, 26720.855522408772, 61720.27799330608, 23157.82714000254, 75880.72928191359]} +{"id": 247, "vector": [88189.86269803306, 50865.90717768049, 40769.860016438666, 14733.536691058236, 76138.40868912912, 22329.408420711196, 35640.87403344087, 52523.231024612745, 78492.37951277272, 8916.729953273218, 81325.00539031922, 70932.3561241936, 42057.07545483806, 87726.95884755264, 29521.153856958248, 99219.4106592519, 64671.84093327272, 38349.3277097776, 38579.39237593644, 92512.95719358236, 1731.2231259858702, 95383.03762661683, 69451.62870164981, 68181.32667232107, 37027.07384239978, 27662.69647217712, 6717.07254623527, 11558.639534618054, 25100.4887016503, 46836.92515437914, 46435.8382024521, 78845.0489719111, 59491.63686363238, 71181.16037625342, 97439.72290778268, 76164.13341793933, 27705.523859928537, 71989.57963245455, 65933.68411395073, 5266.369707270058, 98767.43173687535, 34641.1515300287, 31899.999866172337, 41305.86374897366, 31713.99224562964, 76752.49001572057, 84779.00578212492, 29423.06268541226, 61593.17142606442, 94955.19038781105, 96757.43088866226, 86854.9667304318, 33654.7243851618, 17580.712056843175, 58211.17220264623, 63291.980379069915, 66407.01328594483, 16913.486829430603, 69405.23310533592, 7212.884756715199, 53436.824569016426, 76157.99581583758, 89514.42395320292, 61034.30815816443, 84727.513454346, 85053.02907151415, 78208.2092360016, 33809.81893328282, 42210.47005194437, 90662.91557028648, 93808.5217876048, 79704.96847062014, 9577.032469266178, 67015.4060971488, 16962.967744839163, 86832.18587957064, 96356.90460742584, 12259.78478098575, 47887.08664089573, 18998.608274842954, 39724.75244452479, 69784.95328904912, 52322.36670648336, 28569.26142937859, 98686.3522198871, 34373.87659753876, 71137.724805902, 2498.999754947595, 83191.18375383518, 72019.36586672773, 30892.913742113316, 28994.068755862536, 54261.55204857025, 50358.520133463666, 73469.07165395665, 94696.85929675837, 14000.480523015724, 57572.42560988471, 28755.195912769082, 63350.93799879252, 45865.17707994894, 28957.56980181762, 19595.71317498193, 25728.85904637924, 64266.75431380019, 58515.69232491064, 20611.27221583009, 17872.179551523805, 27853.57795479496, 1997.3732563269286, 74321.95674202348, 46505.25552459968, 14152.465116951329, 70518.97686319241, 86861.91802686099, 55059.25869098294, 69734.44444700408, 41721.657937784716, 85215.54882488657, 96801.90113209908, 6288.544044522304, 36261.957463453466, 63315.447794969645, 15654.665861847372, 44505.10179625333, 49714.53222603162, 31620.779587533787, 30073.60039503928]} +{"id": 2011, "vector": [38021.07570510669, 21864.35935860368, 41577.57237314883, 97437.02423782184, 98635.45766975454, 55545.96134342468, 51171.12134177853, 69771.10754023939, 37861.11199582934, 68643.00780074662, 27028.31342556964, 93370.21438796647, 79425.54810211645, 74426.2831457258, 52846.1096419931, 50247.99862160766, 56193.38778771568, 55259.366657505816, 92857.65708931061, 3483.876203795444, 78775.38912631308, 12673.059749610627, 99267.46839491237, 42775.78580644722, 39498.34907497062, 62663.34012122058, 75766.80934444933, 81488.7423358783, 36235.53900896477, 63656.10344436836, 64816.70605440504, 24718.213675874646, 20137.76707686794, 69579.27127650457, 69826.14644658382, 42393.16739398088, 15158.398090692093, 92876.08527767233, 30415.225077272113, 66739.22248367537, 2806.625519216288, 72728.45828347509, 99037.00766344181, 13722.452755448756, 87561.25407021146, 37326.51780345373, 67968.4074894938, 22.601331330396324, 99583.62505405703, 4144.706782383311, 67382.95502536671, 4712.818385607631, 55641.31601195913, 15176.834757579927, 11770.966559929551, 69112.94212257257, 76331.87556240277, 19743.327774189023, 57881.60093168707, 60780.14894090974, 20673.02187827007, 13891.938704996575, 39004.31331837856, 35584.073324288736, 7013.530760357734, 20050.549255744732, 76004.59069493666, 23441.822885008114, 38160.97932572209, 6576.759196262027, 46788.24570274348, 13375.43663703118, 27151.002934215874, 63999.01771010229, 46243.58651069906, 72705.20769905043, 12130.431278937282, 54134.384393657434, 44352.666171759534, 17799.694626533346, 77910.0112430924, 57553.59908500125, 54641.4636558473, 74035.86744080893, 41425.363457973515, 36515.20370519743, 25444.756463190486, 52087.31846902694, 87605.34098832817, 45095.529034698935, 47352.84168973203, 6280.984684078395, 77428.26020733775, 78767.47914502531, 37897.67197236321, 65008.41936713164, 68378.1547874137, 61866.39170359377, 69391.6875559036, 85209.45492604455, 30523.06821155545, 24780.530918501874, 26406.571804812775, 83807.83037612555, 96981.68714832391, 86490.92639622264, 92301.57479977973, 92364.233384578, 38072.73918602469, 66155.72012964988, 4154.3653889672405, 8040.218334308991, 94813.30473110442, 43534.26673422801, 25305.071007019364, 26840.990590361303, 44314.74187373757, 41425.33725294127, 11094.726464977111, 67389.56846967818, 25411.73657525634, 99431.96392483423, 71989.69779133631, 36875.291978313704, 35752.889637155306, 48858.14487457566, 27708.567032686882, 89192.01620966202]} +{"id": 1494, "vector": [90162.47009243719, 43090.70006637536, 67105.22996744761, 43567.02025859821, 35001.397099192545, 94507.68937302237, 57362.595632387056, 64611.664572177695, 8895.885026703709, 2325.049348057928, 45670.75246018603, 92739.88385936663, 33577.897113071245, 90789.75046190916, 94319.84077505226, 97446.17380627977, 43408.065477631084, 74724.92707631207, 21491.458606852044, 25358.93553631463, 57131.454950780826, 45153.83615067971, 90568.78226797986, 30982.37799051887, 32311.39129844075, 54286.27767475871, 39867.60093107167, 24004.321035739173, 29751.58429828565, 82828.02903031289, 39636.80679483517, 7683.332930588327, 31166.386355058516, 34293.61641821367, 84568.88139900706, 48448.95353255278, 45162.31878771473, 32426.331846501842, 21621.601237401555, 41956.05207300275, 68532.62964673866, 95958.99283370539, 66752.29320866763, 55133.55963958473, 31656.120516377283, 74227.65311415773, 72558.37312701331, 21325.348815760713, 59259.336616111905, 47908.871489303485, 56039.50217856557, 95046.30067365606, 38335.45411002007, 71038.75639138221, 43495.27119455717, 56195.687625874016, 60166.83838468652, 80843.44112946227, 62069.83432546855, 77729.97956900064, 68970.31022092045, 1750.970543936281, 47105.314854757606, 38009.262943856724, 5048.475365752747, 61366.40452470168, 16242.485448186639, 83680.5467833949, 74217.21062249994, 26789.142739775096, 48854.44891169639, 3825.970986239791, 71755.69082009036, 95339.34727214849, 15063.56391531679, 40254.19247391893, 64935.30182795227, 23142.545210589215, 13121.534900040166, 75912.0921883663, 12851.680305093781, 2200.7585050011435, 56883.81885418583, 46231.81116254336, 63536.713801792874, 18930.953539871687, 36285.19599133302, 83701.26560104189, 49513.60878732182, 17737.69051000185, 85465.3998898174, 46430.18365437064, 61664.80649804051, 95098.34659691398, 37664.841103773746, 71945.08029160328, 70826.35404987288, 23007.395344445325, 62484.68429503345, 5884.54490120881, 38380.03537663259, 17305.610824833773, 25398.270652168132, 42913.27347845135, 95348.45890599204, 81294.65334967278, 13761.587832574884, 79494.74248206484, 90670.85585508069, 12009.761038119816, 77291.94836636879, 40642.77079225271, 18597.193877498554, 40598.21173710648, 8545.883589591553, 22547.03023200525, 20768.82492764499, 58093.77806740308, 75359.41991179831, 29946.19689737792, 36166.84512443167, 72288.51950329081, 45694.7473259887, 55216.25832716082, 51600.178044700915, 42470.6289710718, 56308.57427914696, 22115.594849832254]} +{"id": 626, "vector": [83648.29262864408, 82524.66830246976, 8660.284638648764, 81003.22564345185, 49374.755318931886, 31725.952897428157, 6689.9564235541775, 35534.15180493705, 78635.76940696374, 49350.342684795156, 20860.917127122015, 39152.17907785206, 91918.07759418638, 93131.64851781045, 14744.029872539842, 58369.39501357361, 69554.68263139419, 91165.33199317753, 78787.14452062927, 93386.94122846918, 34403.754186455735, 17250.570150061383, 56069.10042481151, 41163.753051575644, 60842.5508015552, 62629.409947664586, 46108.671661434586, 29325.694663486545, 22241.075683867828, 25086.071939502297, 22495.746982244447, 84852.6347909276, 9513.369501746827, 4288.72451908483, 65898.26561286514, 28369.694645724387, 39148.27420915085, 58523.231762997486, 93596.13515572515, 23201.76247961121, 84626.80730768641, 8447.95628513849, 2860.082034203948, 48882.70545132456, 29876.269899129293, 34304.241084281086, 41403.13629739991, 65346.26127362634, 80685.31243546869, 85269.22206688394, 56686.74015205134, 72039.37637258045, 98296.5203666917, 91110.48249233804, 39347.38911056699, 51241.53461676243, 65211.39487726365, 30251.100890705107, 5538.492603878886, 15493.569884338476, 37977.29284888559, 52121.61755833506, 65009.93717206276, 4749.002575240735, 4885.94642637622, 87038.7929187635, 31015.685441271013, 5291.899934629085, 54269.665220406685, 58116.61646878056, 68389.93399226971, 32443.057843236867, 98600.2637898895, 90908.46504156535, 62056.20980558404, 85323.54799324363, 64757.849095794816, 80102.2888667647, 40832.3132104479, 80276.04993436554, 6245.262955555652, 50818.9955181699, 48186.26954291786, 49547.327878646596, 55162.33361957961, 8125.5309611362845, 78144.71985431865, 57210.128717513784, 92045.73903203031, 45839.52938061758, 70322.07921844203, 2781.680986529145, 68671.53511842819, 18106.262249805626, 72455.38662561859, 18932.24073646995, 83804.31576543901, 10937.527031218886, 6687.056033607785, 14027.910824410617, 30684.134607006607, 41944.70072410169, 78735.45997644789, 67888.33295440681, 36065.71295411573, 91193.87111030317, 32855.081322648846, 78066.60393719359, 72381.1308928691, 76837.54766761519, 5707.147688437819, 72818.7649122146, 55676.00185574907, 61566.725379760435, 92014.02164760642, 81071.23626875089, 91769.69947972878, 27759.42490742118, 68121.40520893481, 18638.91279010603, 66991.19275802893, 60933.34569283731, 23096.068758574274, 13445.245474762212, 91352.62898272036, 80753.5543390725, 20929.02799869627, 46537.12703005525]} +{"id": 1404, "vector": [17548.241426181343, 1509.2913741673542, 18981.556328566796, 99727.80982450963, 25608.722751203517, 54965.723392052256, 52990.69100721142, 34631.00970686168, 90496.47135037142, 87350.6158437515, 4630.939277010959, 68620.148124143, 76770.02068713539, 79162.41925075698, 79651.7128774516, 71343.48023064648, 84740.723080335, 8924.784105866434, 99496.62545398861, 47158.74521736886, 92011.3171091434, 60635.259240592335, 53888.57207003094, 64658.91092310039, 43221.32464097151, 60586.76572314662, 33529.753221933315, 63587.04974370109, 44001.86884357556, 7232.716252700177, 81211.45328725572, 24544.84400790683, 3317.775200956763, 60497.14778040366, 71988.89934990779, 80003.78375073709, 24432.332558959835, 3878.1294316534654, 41732.13156771859, 57297.329561982035, 28626.49903658222, 24311.495214818602, 67594.61784576252, 65500.02866757477, 3091.658990823043, 98987.2804989639, 68266.45207397305, 47062.18215573314, 77907.87381200737, 94096.83336471266, 88931.06151578056, 33665.231017656624, 96559.28471375475, 71204.13590422492, 87009.16964320566, 299.8004709071078, 47753.35164465486, 17271.372480605372, 60531.22430909139, 85581.08376200826, 5089.046736696479, 88880.14944017444, 60751.9694456374, 41253.82693452987, 85390.99811588175, 53325.9184609852, 93859.47001443506, 36446.287403748036, 55179.04376082167, 51708.812202788955, 21519.588822851878, 42827.27873933828, 87245.60110935506, 77824.47352653777, 73958.33664165018, 82056.72648775976, 78854.881752444, 29958.321868815463, 72207.7238125507, 38994.259030064284, 48049.95471461293, 58283.73965411221, 12679.014152183188, 18861.43924286544, 65898.75443297236, 39723.053051503455, 87897.25714408518, 83090.93840109339, 11745.165748694664, 25851.025492793633, 81700.20991211834, 50818.84317951926, 63033.357722448294, 62324.73643406064, 8630.568149112205, 64424.49210218154, 38673.50220637784, 43226.163313962075, 40791.6530002444, 99991.2723626727, 39966.107185798995, 68193.53120487202, 26713.484145867616, 75448.1237438348, 31039.147001264842, 21178.397524385397, 54379.98903228125, 5390.373687310579, 37914.95926949135, 15260.478655210607, 19818.521805726865, 69540.70799343486, 20344.885865708486, 93454.59319207257, 35227.916290486784, 61614.36641185979, 47064.39045185252, 75204.96311086035, 56614.49808946449, 35685.603694737656, 82994.22092207163, 39744.69609064887, 58171.44042554053, 66108.21313269537, 91632.44675396461, 21781.440986615562, 12470.937224291301, 6593.557938725936]} +{"id": 237, "vector": [73919.94439106098, 69662.30707017897, 60372.32887079239, 75250.47513728598, 88416.38849801633, 17109.48689228523, 56307.576077055535, 5335.958440419597, 86148.94235906057, 68541.97895583695, 3011.753585380217, 35364.68479753565, 82309.02493366, 66301.04373633453, 89773.26318501704, 61659.77155817668, 93334.97640089499, 91418.9476591198, 74997.99476962333, 72275.00208456142, 79723.75313755791, 95462.31317246384, 51706.28040260622, 24547.07297076093, 15443.458937722953, 3291.01368102962, 44862.053904858454, 49079.53962532411, 89030.60307129213, 36921.029560588504, 54682.34768863855, 40362.472193005204, 9071.658003127459, 94268.88801459128, 98949.71556528332, 17207.94584587767, 56961.08299038612, 73309.84174221748, 32324.648737178275, 88835.98115844137, 25183.768786970617, 83543.04736776854, 2057.2078373636837, 28622.577546805805, 54468.02694561679, 57224.92457192787, 96807.8377638566, 16106.883769400238, 71181.90352346371, 29710.364234083074, 77098.15676927681, 51070.869535409445, 35378.51901038169, 51249.533001142234, 81515.44422794727, 56084.34656734964, 21332.471380637053, 91777.89814711994, 49562.92870480516, 75117.57595005329, 731.3907904065942, 46517.8434427615, 20234.232215452896, 84095.41760546922, 13159.663624233986, 27560.550623301606, 79153.4758251094, 46768.24435727974, 19672.16563588574, 43901.09882796477, 79094.32214335886, 51357.06745786064, 92608.08052265135, 47502.94278993341, 11232.405232872945, 51043.451704907595, 13485.489572642384, 33545.31195328081, 23675.146112830535, 70864.35293080096, 81976.68348706339, 10769.11831561429, 67655.48723690587, 32134.89435145962, 3602.242143464973, 47684.07520886756, 30532.21704872523, 9194.48547799212, 64154.24748116926, 51521.59344430185, 1502.379303706891, 35981.28707412975, 25642.467477511767, 35427.163304185684, 72077.40306258357, 98262.60998856832, 80322.91832329147, 35028.72603019699, 74147.93852594156, 59707.82094050954, 20472.74986004802, 95309.9254455275, 64760.23118774632, 8355.70846504623, 96253.12513730342, 20974.53273976406, 27007.471723089493, 75954.80936602692, 87972.88936079401, 93143.04644202645, 39647.84446209958, 58193.59315041781, 33743.7176782734, 51031.61476581882, 23537.239411792198, 28940.494377993396, 9735.595902763484, 49403.94203150899, 38270.540214883644, 92867.18670591239, 96863.98492056377, 65141.18845582417, 70345.71965822935, 85941.54389300001, 15617.843571085876, 86182.27460286215, 71583.98068073145, 35541.892841338]} +{"id": 2107, "vector": [80896.80205453323, 19206.372688471372, 79322.11385269948, 87670.11134176787, 6296.991812886144, 29092.75572933946, 24363.116219460968, 56410.21214438823, 4183.651866848625, 11845.35014518443, 79386.38181364203, 25680.050523028363, 78728.62577262055, 38093.78733210741, 72630.75711876016, 64098.91371009759, 33994.59879298233, 75949.2573007724, 40312.203676480196, 74605.92354546054, 34065.60182538939, 57820.05161827354, 41175.12259027795, 42537.94326658273, 9977.67867749162, 34688.335209877994, 40407.4143465517, 4643.980545491578, 37515.238457569445, 39981.8018704533, 46798.58262116979, 79175.2356941822, 49985.5617399104, 4718.179721080384, 83106.71980520523, 96538.72506090693, 86554.69644063742, 42179.24481021257, 13009.711531069024, 70687.23432881806, 91384.16304906605, 91276.02660483819, 34016.62401439024, 88404.86042336797, 63924.15012020022, 97723.71234829052, 85126.32103256816, 86499.43793083589, 39673.17709056047, 29765.696071340186, 73470.35001653327, 54728.60278250487, 64540.5099825675, 59085.45739099415, 39694.82780353016, 44680.78316939223, 66593.19911887369, 90837.14935788614, 31604.53615613341, 47543.31763407604, 38577.673217313815, 38261.81282044536, 75557.24361592671, 44022.332015480584, 37167.51450940129, 93705.25653807225, 14207.899437364913, 3420.920214464873, 25334.24065486638, 37635.36220262562, 69747.47492607715, 34911.57196968324, 10701.939714446307, 43875.42281293544, 13741.711184333293, 39417.35397548066, 42002.0083569794, 97528.17335993049, 78702.88296544597, 47861.41449344288, 87210.57528978695, 93549.1150363141, 77953.36167335678, 73002.20965175783, 85010.45831190851, 97631.3785524777, 57537.49062668637, 56826.061629416334, 90594.68892513758, 95734.51736045236, 10961.6583663804, 62952.88086459543, 29621.439141048257, 67328.64635766123, 59408.92606885062, 83529.00452550208, 5133.151880602493, 65312.79520820636, 39233.075684331765, 95153.80798784267, 29580.237851933554, 35798.851304461765, 73010.17074374252, 87018.1024885223, 61472.80445404889, 52961.575650899176, 56682.12028682774, 70642.10156482327, 78768.97996898963, 83780.12278588806, 15051.88227274129, 23924.299175015796, 42043.08528293104, 94205.14778290094, 24237.701838256708, 52945.17434826652, 78502.55224828361, 77109.8231648886, 16775.504121626196, 58272.673113555684, 36512.98581532954, 94592.4588388364, 21022.02278325881, 28589.990825454657, 11559.575775671594, 68154.35768836469, 97534.41732334814, 26591.110996528787]} +{"id": 713, "vector": [44291.54212329881, 43415.588818855285, 85664.07690352997, 60179.76961798422, 99685.73010816798, 23812.19720827763, 76314.84707328666, 70152.20773818932, 54409.078111773655, 13071.720112330187, 2228.161239169246, 58441.47386594441, 89526.49401288238, 81109.77292950718, 11865.248326762801, 50745.594986202734, 42200.39824346849, 29785.227578689843, 6971.266136951371, 88727.93668689238, 51364.32960830729, 59861.26174538745, 7783.830566362104, 77532.81319039325, 97974.81847728534, 73071.0415443199, 52552.02041227626, 75772.98615029262, 6152.740416121449, 27004.11755247988, 81338.82154912654, 15464.951604247857, 4772.657167129779, 2744.9246973780373, 53330.980305463425, 9422.516666579295, 16232.256821176294, 75747.38824490325, 4642.2816735788965, 28277.84084095306, 76432.84830330385, 41333.74764812321, 48444.62400278397, 91850.1527676025, 92313.51639875215, 24357.57758936562, 65513.9131606584, 50263.35445332246, 51721.14584660117, 51302.28129671809, 56805.96734954775, 52660.09379225909, 97003.89885679923, 36818.06044301164, 90248.62079162011, 81485.75196011098, 13013.480699863356, 63088.58334362604, 68878.45765712607, 741.8741872853963, 64392.2415755397, 73813.21353392609, 34705.382101237905, 56014.154633490645, 23282.008227460905, 87825.29440392341, 36652.80928763127, 37609.60202053507, 88645.40664871463, 18194.589384600335, 50798.46286121221, 61883.761288474314, 72594.68317306238, 26953.68203752979, 56861.17167213129, 58604.47953590109, 54441.53577202028, 53379.366880072695, 85200.15007973285, 61508.196045578, 95888.40911116953, 29366.194503745814, 60214.73177988199, 58133.732887797276, 57355.29303411624, 24432.12161281799, 23506.291598744756, 93341.47625617613, 44347.92393688086, 63779.03998414995, 11805.211442194885, 87139.89925166013, 86558.268872307, 74623.21087574646, 2807.0722295093774, 72459.85652820136, 51036.69946715462, 23371.647777298454, 37593.83262876081, 60208.579607395186, 25305.333934020502, 23316.5833723567, 27405.833354524566, 10115.231133547697, 44186.61932330866, 84376.91312236885, 81500.03346520357, 90434.82907984307, 98829.6448146, 12481.814549942526, 90156.14261939982, 40467.892280690845, 42622.36132022109, 65037.53282559982, 50563.86019186808, 55194.559470487606, 52590.37845566278, 26365.83077101681, 36947.88319545301, 81735.191358315, 64294.364843795396, 94594.12805465588, 29181.040226107136, 40002.3484481886, 50492.12147388786, 85189.7657242497, 17242.161630232076, 4397.08694819928]} +{"id": 858, "vector": [98627.94680550213, 28893.66361793624, 25829.521363757813, 35724.93924408322, 1299.420331979362, 52118.71583043614, 34966.17827220802, 87949.03099700954, 53233.08118380582, 52097.02319405884, 53182.59756122597, 22872.98022212565, 86771.73243442414, 76570.88406636122, 93117.64506653776, 17944.39203096704, 3565.4936220653854, 85771.3752792823, 21699.414062104348, 73917.55627110928, 97539.49779393166, 91753.16056670444, 18179.53922663559, 968.7608975873618, 18008.546766900803, 15628.248950169977, 38856.13028533717, 54822.03791092421, 88611.20881889523, 34487.35315476867, 29349.712632711922, 72546.16096645944, 16462.350052927675, 6713.238885641759, 99648.12645026112, 94004.07879924128, 32568.403572838688, 28941.31589332416, 51202.987239918264, 32774.96169770462, 12869.543731123113, 6151.166790220653, 79115.4888304405, 66441.40035216814, 53675.26668848196, 95239.8017449443, 46589.04138718798, 44026.820851590346, 86348.68380810565, 63543.53058327596, 28473.262778290074, 39256.65235228274, 97557.50276114795, 35711.17830997767, 91560.47760589475, 60821.5543951955, 2995.292198526034, 29768.20691481178, 45360.69478810263, 21427.83219714348, 85122.95600406725, 79859.1886662266, 22642.547741017337, 79672.51768874525, 38200.77467161485, 42832.69350807156, 96148.10581663427, 22720.793042890673, 42307.01791641497, 14913.076044211515, 51262.1880014066, 32501.11674403393, 5645.351951608913, 82083.42542402218, 28489.018063710526, 64202.48695493377, 53296.99051737577, 98575.52172774376, 37141.37906113688, 95584.42249233474, 5227.731946477187, 88442.75966302144, 69560.60564538612, 65689.31851404217, 17152.932662903862, 37921.49935812999, 89189.52670214666, 40866.85582045516, 45201.420318212186, 37434.83494609675, 22924.833443002946, 38963.83049767185, 41773.08993440021, 48991.34722055955, 63425.34159778332, 58958.67922187683, 40010.533765276254, 45843.24488454419, 56287.91197220227, 29902.62525312175, 92451.76831070018, 41483.54401401535, 69596.37740916187, 90317.64108549824, 89945.91983343464, 91446.65392493847, 65105.72768798432, 51321.66851853741, 57797.53338954794, 6566.953378312967, 89368.17176807552, 59508.323344861135, 3186.008951694408, 59497.5067072895, 98352.51699135631, 94333.51202949269, 3382.6665900279827, 36970.52021117789, 48242.01068553335, 36717.84303270279, 56756.06472075539, 13197.832162581102, 6005.72911572319, 58227.18215579486, 74710.024454586, 41239.42553003015, 3511.8566817351702, 93992.00294851656]} +{"id": 212, "vector": [83603.541944037, 2596.705963783774, 17847.69274664687, 30972.157755088305, 12388.785959712312, 16705.76787582968, 33443.959786844265, 97411.94312852804, 74595.30222815974, 98107.77718154134, 65586.17429232805, 99041.42164231358, 54429.30532316588, 30429.130892235557, 98614.24292980648, 24294.627621168995, 733.0957971145247, 37600.53220931734, 47160.92909988855, 75922.86225124075, 15646.710556499676, 19279.95746762787, 53269.28208998038, 56255.34611597827, 64759.583606683336, 61046.13452414246, 85825.8906796634, 61972.28596171045, 50513.0302292118, 24933.39681826133, 34786.29336924851, 32507.49241345724, 4510.904180709596, 92785.38032778681, 61771.39451368881, 87530.12022138534, 48378.76738077248, 51819.60303449436, 26180.732237535463, 91904.49737378945, 42795.90970390469, 38693.3954329176, 58921.34364214115, 93340.11175326578, 58877.83925134549, 49222.395388585246, 47610.07532296372, 55167.726561557574, 26494.55719299124, 10558.784649065223, 53500.432600067885, 12273.914535682106, 45690.21738785441, 54184.52928038438, 27302.03087661004, 12792.414760256754, 93124.96830510246, 4393.45569262104, 62487.69528072998, 1957.8884911447215, 67315.19050288788, 21525.482683201524, 62883.443931975846, 85943.24088657256, 55582.69516650043, 27923.52736777498, 63759.234588026724, 87167.63068041258, 31213.34759687191, 4435.657366048129, 82514.37286198273, 86898.38269492328, 66154.26429081788, 2218.6255887569837, 8881.99732634467, 83106.49313082223, 94172.852497507, 72789.5072879043, 72138.74153746809, 37758.40781240929, 30784.072148903186, 19821.984309720097, 38939.79568883969, 1122.2235871438113, 40581.94199994576, 80526.6298582258, 35599.56591149498, 26960.641074668667, 12699.661215718339, 46459.09253158849, 40369.65158650742, 22843.147263330942, 29595.634884911513, 76555.49497394043, 29201.729494393945, 6979.9656627635095, 94721.46866600578, 60261.95565443142, 30474.195297571794, 4849.169251401997, 86071.2476289096, 88661.54313258082, 84149.45129101373, 5687.820111695163, 38958.33498120186, 45747.71475751173, 77225.82140240773, 32923.333915386924, 16388.795272296586, 75466.56534725578, 15833.333989134191, 44489.093856313186, 74034.91413748461, 85354.58078731374, 47840.64020329112, 47102.49535071962, 96567.2683527981, 88254.84755947656, 25260.265463031028, 89366.71989683609, 50495.096342409284, 16409.4467436494, 35469.217751208045, 3855.1889282934626, 36798.673921534486, 640.4839514970839, 4245.012013873872, 7201.125016061349]} +{"id": 157, "vector": [93308.39045352835, 68008.76112749487, 74193.31292470336, 87212.16204229453, 33133.32866749545, 13144.440145927605, 44815.81257599374, 96280.98657051529, 42027.80552470431, 64654.72882341033, 13958.518081782056, 63192.16217794295, 32578.05230322248, 41263.44588613076, 74525.41279946353, 42992.652860622, 78152.62107300754, 36498.193243210095, 79431.9601137671, 38132.15900801023, 5471.433604270593, 71131.89799032455, 58849.53330014432, 78445.5128632425, 71188.97096393639, 46754.654057985914, 13450.44377363035, 43852.89460577005, 5944.61004663327, 61851.15329531393, 90382.71761219316, 98663.75966699555, 15154.450108219264, 4927.889918423267, 87746.97787906833, 30945.464643879706, 52224.03899542094, 48827.50547818323, 28925.856718658273, 9518.089191137435, 672.6966473024398, 91431.82596300814, 44670.40477468552, 12146.342009908161, 70499.12430541869, 41111.646051696414, 34304.293387133366, 72076.61796524003, 33322.57071794008, 70073.71875944856, 59331.03615349824, 10865.946091190892, 21757.804910729737, 17631.2008922599, 18269.49684137861, 33218.32880910672, 94542.6398783896, 59640.73576248019, 14600.310239060755, 94015.3714586955, 90657.03929422951, 76538.93856657909, 49937.737937392376, 54136.45661363842, 33487.857110866484, 60382.1133152094, 48033.84045108935, 8321.329486462713, 98139.4987508832, 4349.544608162281, 97266.22983446065, 37647.05077668413, 81938.33274348949, 75267.53221814688, 3130.098947629567, 97068.7415635573, 31596.60813767442, 54112.74994428885, 85752.72338868829, 63361.96868392156, 59802.41435117751, 79752.24126633837, 60878.94152306693, 96589.00518397646, 4996.458401996729, 71964.69790487537, 71511.55632986831, 20274.04812452467, 46306.08866968595, 30401.39421665361, 74690.08701868643, 39887.88187630039, 57607.926897645455, 95440.99278914362, 98816.98044161458, 89250.77300565082, 44240.15053717093, 6019.272088108208, 34871.19768581039, 55708.290840082234, 55761.99495799841, 68170.79808469923, 39473.71383489012, 21807.471995805507, 19506.695228836423, 95709.28709180813, 31319.798741261005, 50318.920932280205, 29752.84481800684, 92005.18540359646, 7700.046662567639, 51480.336115262995, 7177.870145989307, 30593.26836528974, 42879.77629004126, 93629.26916098013, 70375.09239502622, 11248.183842386294, 70594.31506785787, 74327.42716556256, 94752.5536253243, 7050.1087351168335, 39267.910713576384, 68952.11137061733, 63299.01926001968, 1443.0640595821221, 51918.84053664643, 65871.06059769908]} +{"id": 321, "vector": [48824.7695432157, 70608.87405339789, 65342.371539960455, 86638.07364594504, 37212.778636362666, 69595.155557298, 55609.61631084793, 43305.916582865044, 90097.74716336049, 27477.071895778314, 41713.90681907711, 17266.56147539719, 97418.20657258076, 81225.5991830366, 27551.01676019246, 22405.936239137747, 10568.982200504306, 17741.083770846322, 90128.89121413397, 5394.257414771164, 21289.659794077364, 11684.735613669784, 29620.55508705067, 12618.116095102205, 67149.33381819606, 58564.032259057974, 22788.11444758725, 92520.82779770391, 90033.56530150917, 99103.9030779216, 69001.71163893292, 86741.61210118362, 89891.94657874812, 15252.814910228075, 38082.924961670185, 26087.688318326553, 89251.06583811209, 53785.41539103232, 58496.53731554798, 22893.277562542826, 80139.29740839744, 94323.06949100214, 45289.89882408765, 40368.91321472436, 91767.22080758098, 21665.459842131062, 97523.0595422195, 61741.4389559909, 676.5336061146598, 32199.717403339357, 27149.95100081532, 11722.756284213487, 32922.958988413986, 94480.2975896781, 91361.11891641066, 29863.58808640399, 90101.97140093191, 13928.966864742753, 59356.61339095359, 24718.482097193682, 63903.468215958615, 84669.09869919234, 81541.09662373702, 63020.632343293335, 12897.53476766532, 87119.94148236414, 1876.1457033944716, 70883.86977744657, 98768.45085521364, 45892.58231104454, 15461.035332366491, 82808.96904081527, 54280.11948559711, 51710.64544104325, 36854.15953514614, 99578.33371433063, 95612.9705275362, 52619.0697421307, 73565.52860103645, 72101.05310564392, 62696.088565805905, 24987.298811526394, 221.08887938744326, 25435.375216112876, 34885.53508417444, 83130.7587376095, 2116.5508046644964, 54260.92694826472, 73524.62729199264, 354.4828307305292, 62818.58788462075, 39408.96070428463, 16358.356825056386, 30013.38477853035, 135.90947492000404, 69725.83999336581, 44605.542915120954, 50188.27158732251, 88234.69407313655, 58220.72354606864, 93908.31574340796, 49209.74075114045, 75334.3709796997, 59379.94895039756, 62083.8050017946, 62960.747965946095, 37463.711778954625, 92218.1171490147, 93834.54860312631, 59142.97689511454, 28756.119504668022, 54661.23867777995, 83070.94753981054, 42497.89360528833, 46891.76844713847, 17290.04726725477, 17030.76161915037, 89557.19724384745, 42759.63289724178, 81527.89496554673, 41292.60021222384, 75015.59416774362, 70428.47321083238, 80017.32913121559, 83285.28737397594, 93598.58198223893, 40355.3261765051, 9581.91777287123]} +{"id": 1212, "vector": [3163.3621199715135, 16303.166930218782, 26747.557484513396, 17606.91406477095, 7193.724692384684, 31997.68048767463, 62106.93805491776, 68593.46631797605, 18840.364149499666, 73408.80746497422, 73428.51873123372, 4020.0270432569796, 62341.84486497776, 38510.48157582439, 72324.88259465547, 26790.63250677609, 34450.30947436681, 70292.91427190888, 10805.60636569221, 53466.813708410846, 10146.351442921243, 22588.471067947692, 50637.30739413374, 10902.725335946061, 70035.37871953708, 12443.43486913757, 35223.55421239921, 14972.993016087854, 35481.47788309777, 5541.046853594245, 7088.3384034765795, 89412.40899569672, 4902.139006037664, 67021.18621952592, 91047.75685869459, 60972.562812549135, 76916.22787465987, 85521.7327021943, 56267.4799444482, 43183.72775266288, 89608.90018043523, 76484.22644462956, 86754.9029760275, 22170.45419902094, 22661.788132577574, 2438.188100948602, 58617.710843955196, 66038.69679407444, 9985.626421896965, 38160.61849138749, 36208.19120483277, 81062.28180298039, 16694.028763923863, 32873.119122464144, 37860.05444320128, 51668.90433229745, 42896.59423300012, 53777.195531735466, 91631.18095845246, 47008.541165975534, 96215.16564797996, 10977.783394682172, 9207.354863040917, 21110.960316093162, 59259.66073624417, 41647.40772871809, 39979.79353090558, 45275.108185467536, 64459.30409486009, 70266.81389147764, 84540.75000943242, 62522.13219481514, 65700.501329336, 70470.7683468324, 4662.173818561233, 42411.07776979665, 65726.64772568241, 95661.52189200552, 44882.51147759151, 61889.22817291058, 552.4027542260445, 99389.84549328056, 90586.3763180918, 57773.10668556934, 67788.74328240335, 28439.088676073254, 41926.64045164628, 863.1162949806037, 22133.807954636664, 83977.54631231242, 39908.34836469216, 63255.572881279324, 98036.95018889071, 64386.068652667294, 64683.727489078825, 858.2080350769905, 90492.83335486494, 98373.79349315856, 46509.83617743737, 33164.43023786149, 22451.862669653954, 10837.157216035597, 13794.976846717833, 39405.80367836977, 94813.08343103743, 13546.343859282439, 28490.128856367348, 10622.98369512812, 64823.29689342545, 21635.518417428324, 21304.687160679194, 95951.8607473476, 58756.4703450201, 12853.0597658851, 65338.03753900507, 37597.30194433963, 48652.16290024806, 79122.7333579485, 27735.844201861237, 98532.73864744682, 72120.72773938748, 91209.74264933617, 25905.51895864014, 52864.08881400134, 48875.744524729445, 87119.17964703981, 59443.936269625345, 77470.46361110079]} +{"id": 179, "vector": [50994.39113426144, 28061.143761254083, 32437.788126613355, 16635.44603437319, 68779.29132303348, 68206.25853665233, 63001.29935043633, 61424.38293523272, 34155.910117601685, 41030.257973861786, 38044.82686896435, 10818.694992322919, 11430.42072684649, 45790.273449057706, 56837.452453876635, 65584.03653885468, 56716.039494082426, 92714.92819483497, 91289.34203199406, 49629.31047988288, 4082.2778757506285, 8847.778479570434, 98397.24427419691, 78441.76537059441, 43371.95035309144, 76297.35310208278, 11052.529446222536, 96095.30210469566, 88363.66537128437, 81415.64277636322, 49065.09586768909, 69035.44423217273, 67902.42775428071, 17352.281537594437, 16104.215190051475, 47852.87057475498, 74813.1660612959, 42044.48628301151, 16959.049733711974, 80457.01474701295, 89160.02500057117, 10397.599077079522, 94506.08992722834, 25395.888252626064, 17115.05595112496, 73148.14171921053, 35467.57230436789, 56878.13319409827, 17084.095251015708, 2521.460854275426, 33786.91389514264, 99633.44803190422, 46882.49015972431, 32998.85925254391, 77582.68936227661, 4059.4226527512656, 70884.38130804675, 52112.718925132525, 93695.97901447346, 31506.914395290707, 14642.448532501618, 82809.26810985802, 6158.963485595114, 93928.5155982253, 41370.964783244, 34003.84950225359, 6791.587113551523, 51865.77201769144, 9564.60169807547, 38277.54903461619, 1650.2884212753477, 44026.41503067154, 26224.65212850763, 48959.4709288488, 87280.50544699968, 81706.85353561526, 92538.94485773447, 86273.74448925296, 37762.75408559277, 62615.84047397629, 10175.20707450269, 82436.39989502514, 17739.26785728288, 73750.96643360966, 66501.05712898026, 89307.61345044858, 13732.944128761172, 84869.5651963237, 48546.2261290876, 30772.454698736685, 63175.3643324091, 54038.44677855341, 56947.10062313298, 93843.89295454464, 36666.966970798254, 74270.61920990271, 41402.687501882116, 28475.295888268214, 75705.66947710459, 87658.90823301054, 9044.076323173356, 57281.11470208442, 54873.202563319515, 16008.00553189644, 78775.87672615697, 2260.1559107527146, 80516.88194308546, 49657.856094022565, 46850.093555735184, 55512.53647682659, 74651.73471237508, 1721.9345545893882, 46224.341326453854, 15335.53911272828, 95386.5451111278, 35638.48688100141, 685.2605334955797, 46317.536405180836, 46456.946934921914, 45016.41944937579, 22573.78139286298, 22078.258192693524, 87005.12248403272, 59513.88643069511, 6101.041952026998, 84468.98356648264, 35970.83028776768, 79716.1618419001]} +{"id": 238, "vector": [9649.412746931273, 17694.41782658778, 19154.867565461598, 26528.062759049066, 9170.414507722668, 34754.67843750867, 52159.972397876285, 35803.24918244757, 88368.99274516937, 88936.40461993139, 24418.853970359112, 91942.66188488202, 20200.879891489574, 12947.762151532472, 23495.760272925527, 76699.20902380794, 67166.32880868464, 59558.64603263451, 42423.782965524224, 27182.954550716986, 46238.61246601004, 83709.32476702168, 50007.67270013756, 35324.44516863484, 34320.389406300645, 26181.571737995146, 25920.596083750792, 995.5315764095141, 29974.12349326569, 58980.623344916916, 79576.65419125037, 64065.19546169501, 2349.5317954447924, 85905.08298689389, 77111.90025744558, 4135.1298687439585, 8567.843899283867, 73492.04467958263, 20111.90039727815, 13229.640521917874, 36565.65118450183, 93941.2780260362, 37300.378643449905, 85326.1034864955, 73013.86645343505, 28268.706079920736, 55841.45611127779, 34788.21952233382, 54434.60911309146, 36539.27450735769, 57274.17134666829, 93484.15798513824, 42363.29451247348, 10390.349729057469, 21791.311687443016, 76855.81486568073, 77227.13016342313, 92417.59595634868, 59064.852954644906, 6079.647595675841, 46406.470598026724, 57113.03264174223, 80616.62439469877, 944.4947167519091, 78403.06215475086, 95043.3485961178, 41076.95872683063, 6765.351438329037, 41402.00000554588, 13746.366390578158, 91051.79966796636, 5137.182768070603, 97044.49657164673, 86909.0159013223, 40825.62740516189, 24180.487064427813, 58649.56483346461, 4593.589024240919, 23480.862759065436, 85246.62162931233, 6898.152658509161, 30790.790851335427, 86072.36118334523, 39377.47598487444, 61544.805679381534, 96543.74097429808, 76405.0555710795, 94496.40954059715, 57627.22905062433, 94790.21030725836, 65895.51245862221, 72980.72665771972, 40088.198440394604, 89523.28877675322, 78644.30790539458, 17637.658617633566, 89794.64439824449, 17853.41147066989, 22735.157238347903, 4018.026151401433, 42414.10180604169, 28165.990132808372, 40557.46979048773, 19721.33797171368, 84302.77573515868, 5447.698497449937, 87793.64895614324, 68624.56478312006, 72434.69350355915, 86228.90902948921, 48269.93320193355, 43100.98115844333, 70138.53911066322, 94572.64465602244, 91258.5553146277, 94361.77693759411, 87320.57678631801, 32121.412219500555, 81041.05053956159, 55430.80304842398, 72425.17494136418, 91875.04502960604, 43631.04467514448, 62984.18800254949, 36032.649442827584, 65948.04371773625, 60707.854554689846, 46322.94689699844]} +{"id": 1978, "vector": [19827.965985514616, 73315.83883726106, 45185.36363917438, 15142.604285723794, 1720.308724460673, 64265.950209377595, 65602.82831835034, 40109.4796966351, 92217.54863960927, 92274.97817318814, 29504.622779351896, 67578.24788987523, 48867.59951994876, 61964.54806014453, 99142.09221030597, 47659.955775993236, 97142.37742851382, 34401.58222823783, 58780.22338084024, 62953.09399461869, 36074.58945715226, 57754.53074769312, 13815.4514856622, 52526.808030298664, 40107.76976469598, 98679.41487018263, 15693.54207746877, 77356.0398792298, 45545.3266151012, 51244.18421508372, 55038.831779325956, 68791.93216838881, 73030.58726279675, 94262.90888731, 16609.71806164131, 42312.07231630659, 70930.96959204695, 96874.29453945433, 72257.11690909171, 36963.36418727825, 65454.8348169325, 42377.5642244176, 4003.5354956767133, 50576.5609081703, 99341.7271484092, 36343.15973202108, 65381.297151121944, 18171.847552726173, 98625.59593820116, 27720.910559087584, 7080.153489683538, 84751.39117212342, 80124.49614503703, 22299.366885555693, 47457.96606001077, 30220.82849141441, 27100.094147624597, 54134.67896402693, 39992.6166870425, 25220.645583121626, 48899.481233743114, 76345.77533087638, 64931.624161763946, 18740.788853676047, 81877.30218708553, 76628.64934940654, 97248.91097649222, 29135.73395415573, 48465.332817792936, 98552.05977838024, 48154.29330032266, 43664.46352729135, 98810.19312830576, 38622.600765133764, 64821.739038466076, 62311.41643096227, 63068.5005148186, 55678.91162001158, 56927.40963319478, 89532.60141590149, 66307.50600832734, 13142.66802093983, 25951.847531672967, 70738.83473163968, 43.607498266495796, 24213.672403219367, 67290.26925817046, 61839.474071593715, 15241.153561551624, 42957.53079547495, 21470.61480116106, 62122.24194765675, 14990.78914589258, 56842.74547881214, 988.711806339948, 34712.45056882894, 99601.61685312664, 91372.71064755622, 52241.83752524, 91906.29326871902, 7557.70333115936, 98986.90240546384, 32314.128417268505, 38988.02960005357, 70280.74744116973, 17248.32262085557, 17019.738571611088, 76038.62014854528, 43763.47173369729, 23945.652562403673, 56591.494228067415, 52947.397239247206, 6123.2720177548, 52736.17617232681, 54704.75879312482, 23659.002758695467, 43178.331791118995, 94533.47274470441, 84848.79275996884, 62187.62160705183, 84140.35231970363, 10268.868704096101, 41155.706405998506, 2404.4446606957927, 57137.131614815706, 55436.498347107336, 67639.4244467887, 80848.55252442056]} +{"id": 1423, "vector": [78214.91975157787, 12211.754064772307, 45469.93449427629, 63660.25535455204, 18874.072157160204, 34343.21639931769, 97276.43720089221, 15922.785430857899, 76799.50641711654, 80640.72161544045, 87767.8818159286, 44071.40794036709, 89066.29772149431, 42198.56949012023, 60959.86852962487, 67958.36218029277, 50727.609794873904, 82997.99896211119, 43989.123339444515, 84880.81834615344, 28280.674441358857, 75078.12130682114, 21621.550573727844, 74448.1212066208, 16116.808607455214, 90575.10588341844, 95143.85702065595, 71290.44063695034, 95679.39401223535, 71557.39236909125, 30210.39081385214, 34900.23062371666, 9769.317674189404, 42342.69316407698, 7724.011186883972, 51141.9442351341, 2819.839542499758, 16483.572997069372, 18707.477102347468, 23798.344532358216, 62098.26585238801, 20759.160623557404, 8656.750423269621, 3365.891390326925, 23952.77328498826, 13857.363416143575, 47489.11148939997, 85512.90147699499, 1546.23957758796, 31259.257060697255, 54064.822770593615, 31917.770032105607, 3921.1640710721363, 49476.884768386866, 45468.679223406405, 85852.13198830053, 59750.20350610457, 69120.66877881832, 98307.55601465073, 35227.27446054291, 6137.1787607138685, 16508.94609864204, 92599.33496853447, 31542.736965821237, 92339.42271745783, 13840.239638111618, 1258.6549295386162, 19725.080935835158, 18388.908752343315, 38491.03617106767, 78976.73257618054, 42641.52948043829, 91446.56804427062, 74738.28948514113, 40348.457006050805, 85083.51571215862, 61909.4620703002, 58899.18729998923, 1127.2678044557226, 64657.10100735538, 60835.080826914425, 66863.31424485499, 38402.192900059265, 52128.37325550399, 27639.860216333855, 77645.45386131394, 40923.31449143535, 26215.786710873424, 53097.618717574005, 49841.13135100904, 44743.73409843049, 43046.82643142619, 38674.49454726991, 73386.03716349766, 73942.52480661699, 34140.404638125045, 79023.2789977693, 64313.99612408065, 66193.89141545292, 87639.42099486303, 37076.040565790456, 60520.82991079221, 31594.215652955514, 31675.736835541524, 70710.85042892372, 59799.49302505833, 35328.179390376725, 66200.77419975537, 96140.1195505578, 97391.96261295116, 47757.89967760031, 38528.487371713214, 48139.492095383976, 9866.910602753054, 44638.450248792535, 79562.36877644765, 40059.00719297705, 26449.2604895055, 61742.74854319003, 11726.877093323106, 44928.98725680606, 55660.470417518605, 9778.414220899002, 40865.01756168159, 29854.931979017598, 93746.55859055581, 60195.4558448896, 21324.705800613552]} +{"id": 1492, "vector": [1396.652658337494, 417.5715852063111, 90610.72989501982, 47303.5190109537, 97451.5825517569, 17111.925432164167, 73812.19433880283, 29388.71560944718, 25771.809803163236, 62891.386744897114, 1633.0078440643492, 50973.350538661834, 27949.697043238873, 48224.29358951456, 11698.897930628893, 21588.637515341947, 34763.45482663235, 4989.968533574795, 22327.791870359604, 3395.8634952678144, 44571.45090316992, 87895.12014649625, 6427.243107334069, 86968.19383169348, 83313.40591432278, 43150.17245019701, 63659.376643045936, 34384.843270722384, 89812.17304084181, 44637.943359488156, 74117.64983706112, 95019.00457210695, 57492.146513756416, 21314.893509415233, 51629.53247280615, 86841.45636097249, 91724.3287120605, 99137.04021976043, 14649.711761659346, 89054.47506839776, 41766.340898496426, 70999.99879150705, 95949.77501998337, 10845.762853330387, 5237.030018795796, 79848.56270043344, 37205.55557235783, 73201.16749859476, 7722.636202760836, 25013.779877156696, 25255.81229547731, 9620.60276253679, 84317.89988487301, 46206.90739230476, 90485.83841086026, 61081.16666831346, 53472.886350103, 21618.110864971062, 32383.82782139634, 54998.66328619395, 17943.714819795852, 75932.01172095671, 42786.30214665358, 45168.04125337265, 66164.61471526917, 8494.36100574169, 85719.41253642322, 78393.50164013155, 96982.80880854776, 9334.56590632552, 64004.536907508766, 79040.15006230543, 24230.30953787948, 7822.692982690493, 67393.2393227523, 28673.613806043653, 21797.29068204681, 78573.55774854602, 53043.69028523348, 26796.460994700243, 94682.65150251906, 62699.097277441375, 79197.29834085499, 99746.41386553954, 3986.5097248449333, 64028.06705938207, 93373.0193950644, 99596.13399661762, 99076.16113594836, 44993.195802811955, 29277.137733824442, 53490.46082505816, 62601.74774360755, 86580.55643257365, 67924.78142057208, 66489.92646071984, 64273.16111480996, 24012.965719510903, 83781.94423467698, 11154.90170183977, 13619.826870282614, 5366.6309717313125, 6843.658053832991, 44394.44664073009, 12454.192875440196, 40268.24573457247, 1658.0842036354793, 88493.91753025907, 80020.71557113717, 85542.69951795242, 77720.28019034436, 88382.45775156948, 15693.77939988037, 61685.11425303693, 18622.31819668315, 56314.92274014756, 8459.870744385411, 52167.32785512155, 72570.3259014497, 34621.91657536785, 63099.71219724238, 67186.00773586037, 70.9143103103993, 28517.930679789006, 22447.166390956896, 65777.2865757276, 33041.06877558971, 55149.366405328496]} +{"id": 1875, "vector": [11887.829611476298, 24328.149400055743, 17287.04512727809, 68724.09521475853, 40631.39646132806, 28874.04219204305, 61706.62143239142, 88338.32211785161, 51820.540832230356, 61237.03545097248, 40088.20069864646, 98994.38571036751, 9552.186313571054, 75223.38655158035, 81068.01336707553, 89691.05079766396, 63335.60774012361, 72283.595132801, 22642.080128828336, 68733.75308778055, 55240.85876393502, 12247.60679406779, 88953.9549675416, 57047.055553891856, 39.59268084205991, 57662.21510296643, 97953.21832324546, 46705.92643001074, 88825.17917556337, 7079.163344385331, 41021.95428574097, 76410.91427360168, 93416.14530870538, 22236.240778555795, 41985.95043242141, 92079.44280441375, 18854.182192168577, 13671.798473819385, 30882.731176436806, 80665.67754868926, 72840.10097325976, 7865.10219567903, 39972.79499518306, 33618.28254269604, 43916.284904146196, 30367.325517255173, 40027.294765280654, 53324.16370498624, 89126.28471037636, 18490.925926018586, 81352.29039353, 18581.55766637043, 84277.92829075451, 36943.39267052309, 61502.10577041793, 34509.643856059316, 35030.0357490006, 24903.66689288401, 86638.45694658683, 76013.23504872156, 90090.60278209619, 94866.487783181, 92642.14579189236, 17664.566637789325, 64224.579012765425, 7874.126604546328, 11685.7140588017, 84007.53320719038, 40357.309855203326, 83086.5144523681, 24666.17182397791, 15864.082786603407, 22431.184251293846, 71032.4276692392, 96217.89729244211, 26319.15974409963, 49226.74336946717, 11713.65787302846, 45549.53730711278, 26074.431048352144, 62486.19392931972, 79151.87429311538, 20060.051527235122, 9855.199161983119, 56970.95277574673, 48474.74722392887, 35004.11746144385, 49119.1491526108, 64349.232138869236, 95062.39882957812, 71116.00617149707, 29662.805561825455, 72359.8499047274, 20900.844987481338, 22113.20874893643, 33022.737621763146, 12273.074676989982, 58913.53416874833, 33724.009865184235, 9492.129022693163, 33812.7721512688, 39472.65252225267, 65226.38680460888, 35169.33348287535, 37521.35305380343, 14659.640043499166, 82049.59137671882, 85710.09710137047, 71919.36843337251, 21399.489931738302, 60356.32414137373, 19456.677477659756, 50046.47071350232, 28425.822021963188, 3789.9607694671713, 47692.45844510165, 97910.26535609948, 65166.455625531526, 78000.5381728814, 76321.49241812421, 32117.899735915456, 79186.77264816269, 37346.9759414932, 84782.24913047726, 26261.398782120537, 36265.15142540606, 64249.34578261092, 77627.24494816316]} +{"id": 669, "vector": [69989.54250980962, 8618.837731627771, 23055.459721020143, 43569.19284137186, 77822.74933516179, 8349.619526039709, 28351.998745307115, 75060.20275355101, 28672.53497230956, 67821.00162786621, 10503.402390018702, 85313.24313667616, 93396.24418165814, 7033.639297999972, 3791.531261246506, 60374.792503276796, 69217.63537061644, 5450.2341638963835, 70653.86255368563, 26179.577892257745, 49227.09305988453, 21336.37842725815, 38037.807383498264, 34031.30596631289, 94228.43074636447, 41250.55553077098, 13930.62488255049, 41097.7749462811, 21528.930606841266, 43158.13143339559, 76167.39866379177, 83082.0753243466, 50100.72325533569, 15121.807609723104, 35566.55019663536, 71902.72583337735, 84774.33374702839, 47289.64858681642, 17982.500070016937, 2131.4892149728926, 98779.74442678025, 18621.115522407483, 85576.68951139036, 43687.66516480015, 1074.7748235728993, 28908.684722380873, 35613.99479732541, 98532.3700009777, 10034.813080964523, 86003.01970312206, 15273.866621829113, 60207.235767133, 70935.1024401894, 71221.31477878115, 49162.822912055904, 93577.94458275127, 22293.707774277493, 49195.93791699062, 39740.44724046656, 58000.38828286116, 14670.501332952068, 61674.35761826836, 19565.790813140982, 14902.032429557876, 63749.507292820075, 10950.936462641535, 24421.62093667034, 45952.600757094384, 83297.99528678507, 47139.39563610085, 69240.30868767537, 98702.12722663257, 97222.61206175426, 28761.81639403739, 77384.19537018338, 15996.08165510691, 22651.308106417502, 40645.06675724634, 2524.8281980009724, 49903.4321219325, 25969.928995670198, 79817.5549574589, 17381.96607193534, 31098.516323670676, 16220.344238908525, 23173.38037014637, 62876.38131290676, 39377.91441188134, 59237.90735675882, 11172.963922427803, 47877.85974689215, 76735.92740318237, 19199.032414531724, 53436.41820326611, 2653.4102820887083, 9485.432573940954, 40804.235994702634, 68913.13329236257, 2347.5503131587416, 47234.124507563036, 46867.69553564347, 83681.89383402908, 35025.756455349, 49639.28100823496, 20380.597479392425, 24629.10636543505, 39977.39501164842, 55843.138456536486, 25358.75689859154, 98909.79919398869, 64916.08840787343, 46659.59911032783, 72360.0438638642, 60692.76198105834, 9278.405543377321, 34856.3574922379, 35782.56450347596, 71806.34920786436, 49321.3201973974, 26634.878150833043, 32690.462199000936, 59255.228551846696, 60570.30857977399, 69191.30860443879, 2858.9519032676926, 66795.17073015845, 48521.6980895702, 34351.356546269795]} +{"id": 497, "vector": [37364.866201803685, 56174.626416952124, 65859.3330950773, 4646.15611041036, 67065.10537202861, 726.5973108551926, 53446.34849106741, 78956.8990294708, 59698.864407710236, 25596.89097719645, 37475.188670862444, 57711.94592431945, 42671.13618388958, 43660.520157125335, 28155.909471681294, 14977.11215821812, 51456.322002269604, 56593.898278272536, 60476.13025864634, 21525.951538541776, 15066.227659233777, 42550.99287455415, 39502.874729018666, 50282.49246107822, 26983.346082209082, 13348.850539742274, 43443.37090818237, 36910.136172117745, 3575.098021285128, 17133.068906962777, 62615.6783220435, 79695.53749108453, 5686.312046256226, 44772.90307629071, 44651.184162478145, 51289.60615101389, 56779.65376956496, 52835.74143511136, 20077.824135316656, 99631.72190435445, 1254.7837095609138, 87105.923643786, 77020.46577421193, 23239.455341888137, 44237.15175908194, 36210.26829504933, 71474.25196579238, 11734.21965951611, 72443.92316080324, 58954.20282589242, 89620.35284071273, 93416.04778083193, 16744.790784118955, 93456.18922039882, 74176.32311651744, 71916.06336724985, 42498.850419476395, 27343.621517391115, 88243.26025453332, 39212.622380125395, 60024.933160994464, 8412.239047222869, 86602.76102657428, 72093.74837106747, 20538.718687331326, 36999.12190014603, 48409.37546388617, 82175.93369817446, 34413.33136885973, 50992.55403606243, 95694.68505841325, 70009.179871138, 40067.95749242512, 46508.58830607859, 96855.91661919595, 28323.194181891355, 19100.97975935511, 262.07929740088565, 99660.47553187251, 56831.28924016455, 12534.080843166495, 24284.72300680715, 44070.84008666698, 53064.62316974272, 31456.95000129225, 69985.40928339843, 93116.11956927061, 16585.22811779424, 33789.26868132131, 69902.6604284431, 97398.24137860886, 77155.01906362284, 12371.335268417193, 75488.59089332542, 81277.90054543651, 81489.81896076935, 60818.50818205665, 33585.883742798585, 44835.602308209935, 65573.13531954304, 70613.5159865812, 31866.238765886577, 44028.53863270454, 45934.41180373948, 88485.02850466996, 4505.48775588403, 27056.479617570174, 93079.20712906448, 38726.64440778042, 37345.92377135637, 60417.60385152807, 70478.86824017738, 256.77976357275645, 23787.366027806645, 58691.6293209539, 16793.139229521814, 35632.812424256386, 7184.295351047876, 79009.15730743698, 14054.040695022984, 59526.62408785539, 95203.31001324313, 68677.93203914963, 4960.466653640649, 42934.74642157599, 78743.79393011588, 59442.40511943666, 58279.26176867826]} +{"id": 620, "vector": [44534.90088992258, 61266.2181511782, 83474.67764920027, 52352.328040364606, 2619.9297976572943, 68352.40580406661, 11492.570643523759, 86364.22724440598, 53661.012856238085, 21311.957144297055, 12134.599637206711, 2835.8395565488136, 55846.95527485995, 81886.20045017608, 59603.30884850914, 64945.051416885944, 55075.46232301847, 94231.57840277375, 57625.87519424651, 56939.50557027274, 38096.12575665875, 67186.5367736568, 72681.68078205618, 1705.246965674434, 69257.55330110028, 73581.51946480974, 14988.301109601398, 19322.30955822952, 87132.81344688388, 87048.23318548665, 22621.141742140706, 54578.46440849983, 36644.896391404945, 72858.42201485539, 47796.03582289309, 25970.31204814454, 67671.41589010195, 8946.501563405962, 6136.9793518360475, 20745.07446880015, 84943.06165744289, 59351.719769979936, 29104.846329859534, 45148.43892434083, 12154.039828329365, 65435.765824059075, 95564.98995224683, 53970.753129113094, 69986.39995051609, 30440.26626873888, 73130.08871727448, 14133.439026085725, 71416.71434287686, 13889.603967596375, 46287.965323996716, 15144.605206155315, 36487.87870410943, 36748.00964380635, 76287.5766966182, 46148.97248547052, 74942.72910413276, 85831.26979683111, 79764.99367981179, 68534.26473896489, 53250.80683213984, 83688.84903631448, 30575.502379712292, 94843.30207871749, 34299.083229273245, 88902.21816235999, 23460.267117085863, 57210.94687731263, 7526.846071658133, 81944.03189706689, 6621.71134990569, 47705.923866496, 39649.9064844729, 45666.8747256013, 48434.367987619844, 35137.82190971252, 24478.327904399564, 29843.09807712412, 69750.46485039276, 60575.33861967122, 34756.25042911451, 65994.52403582945, 99151.32243316129, 2916.650296190071, 19997.016845844286, 59023.56946303419, 28862.629158254793, 69781.4928569319, 58513.11712069019, 42910.57018335858, 19607.205626075218, 45610.839737772854, 69930.19206527619, 44220.16610756441, 84254.49808644157, 41736.97823460186, 28187.369609559977, 2948.9469956243465, 8930.019549940049, 35641.867772433725, 80624.25158671464, 3612.087365816108, 26369.26377126998, 51035.74059625983, 48397.18973345139, 73116.12152883083, 44937.10975169836, 86114.53502384532, 79505.28520808344, 52092.894856153005, 82179.12646709543, 8057.356647962055, 61156.716480122275, 14353.437269238322, 82937.00458777715, 25922.309356466267, 23629.658549808595, 47734.093871504425, 51196.33701846983, 40892.21839374276, 97608.39200116618, 98766.72631949502, 51162.48128950196, 78718.47385299759]} +{"id": 711, "vector": [34698.632253604235, 3070.1660640511273, 9822.19846493264, 5810.264027034873, 12283.59786539911, 47391.61949358679, 1862.1103058814924, 53073.185276948054, 23851.63448080002, 65678.34619213338, 11639.953033671669, 84197.98616048701, 16175.000163985265, 9965.047291963036, 27326.27882719809, 51700.1550614167, 54801.70583874459, 34254.205588668316, 44666.61188806592, 52814.924258714156, 82895.09158866254, 99425.48658095731, 44798.97239348796, 77028.45161244774, 54202.66664593388, 27159.416860304253, 58272.62834360892, 43674.86835897091, 9396.02774043219, 53393.97678014316, 55037.312544402084, 51408.22373621955, 9552.907580558933, 44982.04670580577, 58574.50940542551, 71855.64794420458, 14205.903246704598, 16046.470814584634, 61978.29363678557, 81043.3325041377, 15323.530265678299, 27763.523371168318, 51931.486891638546, 67865.58528027525, 68592.54202550021, 82244.72879387203, 93097.42889349714, 61441.201646965616, 2494.697374089283, 10073.625516377771, 30229.94963169312, 78350.0982487399, 5929.240991588503, 97799.1843628036, 43459.231921547726, 98506.89303971032, 94643.17520998849, 24980.281778947578, 14221.142827843036, 65646.87177919887, 53148.36188156607, 41285.24814744803, 84228.56281489563, 70855.40104998658, 58144.24012717258, 6120.034941329422, 65198.684044725196, 96097.54055727953, 48511.64887287725, 95890.42227968681, 53791.83959126016, 73881.13530735113, 59143.579115744025, 38360.758607566946, 85229.1726048077, 19853.746180428876, 32890.653436275876, 8778.668826825875, 30113.429304042715, 39688.46641750456, 2043.6089744720887, 52138.912328443264, 16636.63960341961, 59030.65988791201, 17595.395171179916, 78653.71563315963, 53007.0105053882, 26582.195626412053, 27728.443755557873, 32468.33310898829, 1906.8382482762436, 73168.65865761232, 9486.396096319739, 35001.17613692976, 83819.75415976405, 71730.0880913019, 24264.67924563389, 33905.52195846429, 64497.15098250259, 89212.98545124376, 33757.54443304373, 84045.89188205836, 34247.28999336223, 78936.80678403626, 29248.838434097957, 42302.73572089292, 53491.29918213281, 86726.28230509126, 93224.73640771497, 21438.54283120139, 73428.93098583528, 50696.831028119974, 10851.23831158349, 89969.34030936516, 92983.89763638737, 13417.250368158839, 43662.26430254945, 67127.5066175707, 33140.53082986288, 51678.86954014281, 34464.459698206716, 30978.95186639362, 83401.675985556, 79619.48360016152, 37004.9379526575, 69269.01680234182, 7970.008853392307, 17603.828106086337]} +{"id": 1378, "vector": [46580.74984488747, 17029.208499547087, 73748.86365731263, 34015.848625866216, 8148.039212343428, 74126.18764159975, 21618.50295765402, 92675.03373205198, 64015.56997737414, 99383.04428278231, 787.181255492575, 51783.87406271455, 56026.3460466518, 95120.29153653626, 21807.484151365563, 23544.786160121013, 63072.343191099164, 12464.771789262375, 51567.210889957416, 89433.43934613162, 17954.632419914786, 88938.33089017501, 17524.94936993447, 31501.485547287757, 48855.871350675625, 96444.00215410737, 69098.03124785205, 48325.81546597625, 77899.49415226642, 43879.57104081599, 18197.028283544325, 29987.691744810953, 28907.458182367674, 74804.38095917983, 76699.39933033397, 90346.01180557808, 74174.9667155028, 40325.43391117696, 59.33372676765192, 38538.611716985004, 314.15000042774864, 55291.02779937292, 43893.35871620161, 23593.83031046798, 53670.44571586991, 65185.9679315291, 63250.90486751049, 79609.430092518, 43105.98083526321, 47838.84263567859, 14601.794415725577, 68533.0379767417, 42529.77305950166, 84161.68141508831, 88329.42541251073, 8387.354139986059, 89932.65530073155, 96611.9251305613, 26196.818784612897, 62556.899626273866, 65870.46723410212, 67863.550114088, 68899.0101666216, 47004.696886522324, 76869.25798094933, 71594.6689708973, 87601.17113053257, 69408.7802663672, 41246.23336270907, 46386.54018672169, 64093.269699316304, 67167.79490890949, 74323.20149886819, 91922.29445637364, 87143.49336921619, 33811.8809268811, 78420.2137585506, 90734.0807633159, 63985.39137512696, 71607.55655513858, 10163.410209830392, 42277.04494493806, 31365.360228262773, 77178.04062182312, 65437.567416961996, 66565.34407692624, 19015.294033479768, 56005.63013330769, 48533.87899581605, 2437.9130026477847, 52322.24726702005, 46366.347379280494, 98358.01887519022, 58535.406732627016, 41929.02250024052, 99822.13697292567, 18745.376097645916, 1696.120949208535, 14135.551640128919, 7748.806122489415, 61127.43886589987, 17191.41641045445, 77993.28911007964, 10721.726786691599, 27887.64131602355, 75015.4074689113, 31829.51521998296, 31216.969825558048, 30097.948333124725, 3153.006236434552, 58219.21576657118, 37795.86606076247, 3189.3097592474937, 94644.59757347639, 26709.64334565946, 74662.36619941074, 53570.50804411701, 89870.43321511109, 3686.0881410333836, 67139.17417100603, 41648.26706181746, 84905.84047177041, 7216.691053068658, 71450.15716320316, 80992.46107690452, 14751.509123673944, 88271.5606362226, 17768.95597472754]} +{"id": 1710, "vector": [92706.97182672205, 8255.13258689572, 12868.285322099648, 59738.74558018398, 56295.90182977935, 6312.579056209933, 20447.71575856954, 97944.22201594184, 78018.54050381036, 7928.134282177013, 75921.99162576522, 12240.190797997986, 13534.944147153705, 29173.916824301337, 51906.226513960886, 29094.540845895113, 45589.59177374231, 41199.6078700432, 21306.217924103064, 4580.832427601677, 90949.2625811364, 71729.66693462951, 80854.41444329087, 79860.67349272605, 10470.17016185986, 86149.58189636719, 52894.581419592025, 82730.51233418308, 95322.80890778251, 73661.03561078149, 21741.694315313285, 77673.02622140152, 49657.46550223128, 33820.01385977577, 90334.43966519773, 66583.1494780478, 38352.0645712893, 1751.206944264727, 40575.85164031492, 95143.07247185017, 29873.730017262802, 62870.49210427116, 48498.679704084156, 22076.36565114971, 14054.761630849443, 87710.13428529828, 61259.377142021454, 6556.471546684317, 43137.47802935393, 12771.291034650634, 46790.61117852493, 30632.411030003437, 30609.12398566804, 20589.74035976926, 98669.97151560018, 14033.029547852017, 5510.513274686535, 12583.78804800997, 64159.57617885384, 68397.65608943257, 24389.936234448174, 60341.867505393646, 1179.676754498038, 80869.4485148561, 70079.33317628507, 49931.24155068899, 42020.56819758686, 99331.54662359884, 68276.33382660708, 40585.39466057965, 63376.86941031924, 77856.05151617597, 94595.79199273024, 54549.28498799073, 31857.724387394413, 57479.82607952765, 52712.883438454795, 52851.8488425164, 53673.065494258575, 4921.654776484718, 65780.90770057487, 10776.613043601912, 30144.666387559715, 78312.3586122167, 22567.78026476951, 49916.24721781892, 40545.32128716466, 44155.70618377241, 28245.876527520108, 40902.33546883104, 1139.3763337039343, 85252.46695130582, 2115.5284950255427, 75712.25801476945, 83622.44074960821, 29257.01822033918, 9629.025839140004, 64453.321378458306, 72493.73038490316, 37936.984141109184, 39189.54718194411, 53558.91579489925, 60116.35736706177, 8570.365540443525, 10653.425131979133, 26734.158720771906, 79212.96997445106, 80006.14694696601, 82101.904223528, 56280.69383104123, 96802.45737405731, 83382.75309021676, 89163.80062882492, 67457.24598506067, 9425.278351174082, 85237.38091240167, 33681.09947413804, 997.617317680044, 81491.61368449025, 87236.07713632974, 85773.26851874296, 12135.70236170669, 45081.45913314361, 83833.61428549165, 29444.703499293257, 72445.83105711432, 49824.6796748752, 77554.24378392562]} +{"id": 215, "vector": [97724.65415668432, 14356.55272123949, 3993.0640650411297, 35391.865706239245, 8244.98539286761, 32873.44700680624, 8012.145265160709, 20409.86221191713, 95236.73815786433, 22954.02523001463, 28216.847885619867, 7834.379934165936, 67573.18917474234, 52795.363872178634, 1597.878644671602, 89580.95784216897, 32663.70419780642, 83251.38536023212, 31480.766939418838, 6893.377833890435, 83355.5070620505, 20788.94084942533, 28816.670429569203, 64780.04288967515, 64247.90396407273, 72856.54143274718, 63202.47334437804, 41547.98530480337, 50472.54629988276, 30613.82164354224, 31957.166480511147, 45174.945225395866, 53994.065697896054, 62625.601466352186, 35171.43698808647, 4462.964469897246, 39208.19573294504, 50240.49654491647, 86057.02701923881, 73839.7490837036, 34670.38459572089, 87052.3996223552, 33235.36038356098, 93378.4697454531, 71827.3141757477, 64184.17471695569, 83486.62761340679, 65993.7253253653, 96579.38640584244, 82455.09403415173, 71234.59459147321, 72608.64300874385, 38638.256241505675, 86488.94770557193, 9336.925485265323, 63959.89510139133, 25606.56180409967, 3975.066294775087, 57324.48220491588, 85176.98287995238, 60516.705155886084, 59090.032384532446, 69996.19213825406, 40763.473985651886, 51240.64169200273, 90833.22621436726, 52808.27226478434, 35525.193371425245, 64797.98406133824, 66278.29785754354, 52395.14805082205, 96538.11545418837, 78840.15457979757, 42648.2996561977, 56550.416137482294, 62558.195952145456, 87856.01349530717, 45401.827437778265, 75723.50274924411, 93086.73788717526, 13877.689863594, 60517.14672864998, 830.02101471773, 83661.69762109606, 27589.05319751449, 41711.56971349522, 24496.346273249048, 52803.492974731045, 52721.81215308609, 29164.648587141073, 38931.88895606354, 72026.46974097793, 76821.75276728664, 92010.06730045524, 50615.693083938204, 73366.45085075863, 75611.4150859461, 89590.43444771432, 66410.87186205969, 46647.75693370633, 64955.7425730943, 99727.20926012915, 81449.85515870579, 87887.58789836049, 99418.5183890491, 9609.544841162176, 93047.3700268245, 45774.45768879405, 35671.824256459935, 17188.296961742166, 72.93776161340082, 10400.509729825491, 89727.43820825336, 45571.52356914893, 32235.041133546616, 50373.1990916265, 92789.27680515654, 80461.79012933609, 30356.550344129584, 76767.79299849326, 52484.65346077728, 49593.26254977713, 81691.34850778444, 45357.61453024715, 35268.219393309904, 1660.7693047098194, 32606.778539597002, 92360.09835009994]} +{"id": 191, "vector": [21938.296693970984, 27641.0645927728, 94919.54248448655, 42407.19591341866, 80495.7616619754, 39530.712392945265, 20227.28600213436, 59685.707807630206, 93695.20684458014, 43600.390192925486, 20039.492635245493, 61304.406109077914, 62225.55418857535, 50943.53693270208, 48507.76553512165, 90553.19264288296, 97951.1076136879, 6115.157296858598, 51045.37480900416, 55600.23741167626, 72474.28628406845, 42019.02968215834, 96733.08870565267, 39664.44134404507, 73699.77666880372, 14075.735798437938, 5971.817817747138, 41842.36324725409, 66392.13359009322, 43048.78057522705, 98480.61356510391, 81341.35082149004, 58519.26777833681, 64491.33008026483, 66594.35944408677, 40934.983531963095, 91228.05019588927, 65363.61763489076, 90048.41249840501, 5677.955758373043, 2930.8764761112548, 12024.90527358817, 6421.411690054469, 11380.65509600824, 49288.18929794794, 21050.997399333937, 84882.03966569659, 34525.08811052839, 98372.58867875078, 22680.49311135566, 78569.32468849071, 7509.637781077016, 84381.18837013203, 12512.89862255015, 74950.68478996443, 36039.664580257246, 42632.55783791732, 1868.7571440633267, 57948.214693430855, 10399.72142248593, 49174.955635592174, 57447.380095970846, 11699.811615136003, 14089.150929007765, 43133.31488697079, 92904.21393561801, 22519.03502492061, 43463.15577577204, 30198.06491892868, 14352.411668249266, 97721.49709598132, 53989.34434429763, 62952.23846232755, 31216.678862474622, 59203.99235411192, 17836.079047789786, 44109.729204487936, 80009.75511664124, 84864.7498563863, 97085.74481472919, 90338.21489560067, 13564.68203004234, 27172.02622499999, 97692.57466252315, 64170.26688934367, 58973.762443347776, 21499.288500453782, 16031.355175364759, 37329.22316911954, 26351.358610356467, 82147.4712553513, 44020.97167605391, 10310.154248459714, 68416.75107492931, 38617.808550557405, 62470.49073163701, 40586.25099713241, 10079.147885440865, 63883.90531177888, 67894.19567740681, 43515.865022808466, 95563.8116183028, 6300.944769876382, 77506.15000523918, 52232.12344333155, 35234.586613216445, 97299.45012660307, 19996.042424368254, 55900.508635265374, 71642.55071644676, 49051.58700570848, 86912.63394271297, 87351.45326743134, 30050.590023712386, 28064.090171667278, 1690.2393076205003, 83255.54378077765, 64927.44530860285, 64623.00573449616, 33046.1993918668, 80993.54474686044, 34872.52374210198, 34063.19423793821, 81142.46376330369, 50764.82021969005, 23613.094033842874, 76466.75244000638, 85011.42543916624]} +{"id": 1994, "vector": [62995.29597499739, 47031.430282623245, 49978.94941431676, 76863.2467677845, 85493.88530012988, 49796.260979189676, 31341.24895987481, 92642.34633993609, 42102.64089050872, 82892.45947936614, 1390.626260941308, 80241.86479649592, 15549.978679142207, 17846.59447320256, 70064.40797579853, 92764.29400212308, 61781.90366627575, 13558.052360314676, 17740.72630670004, 85626.1991523425, 81255.37061823676, 49075.180924869586, 35757.51931313068, 90930.31230247674, 96574.68132128885, 29567.630922226206, 96454.80775700268, 48940.564806620976, 19333.159866172322, 8715.898050742544, 58190.25087351032, 3133.3202842998785, 39668.928943609186, 60226.30070444432, 82489.90360678955, 4680.718172931098, 87139.60863316024, 2792.513326393875, 4654.474477102232, 25774.197446006507, 76127.51570593892, 97967.60393597183, 65728.67653090155, 38089.8126570717, 37331.528168176286, 17024.324494962195, 26451.387614668052, 40595.21213478686, 50714.25205767749, 13967.73540893953, 44641.72467148259, 35168.108327959155, 10970.87678506723, 9379.129086114246, 85518.59427914943, 71109.59196567592, 59133.44538889604, 31035.615286283024, 24529.083135490437, 38416.11578756662, 65067.48765536778, 3318.7715838132294, 50499.480369951874, 27261.887581830702, 24031.64962250156, 55110.02094423889, 95774.05171518544, 8358.804494922833, 39100.38719772564, 40226.51149277861, 59410.11337212702, 84951.98125217728, 50423.03986267368, 21198.510155290107, 27889.74033294741, 14498.707877749239, 79242.49879226412, 35670.02105661578, 21386.251819756253, 44621.96844299714, 32856.42478663852, 56542.74552010968, 89798.07966602898, 36884.97512397154, 94357.64491925022, 79869.70983814418, 18723.141774280437, 54800.910805850064, 91799.72079760264, 98760.39801176522, 2609.0032364858293, 43683.67015795982, 66749.77461553192, 21562.36144934377, 79561.68276655035, 19520.03160413274, 40622.543383775046, 68960.38563024145, 95094.79831351247, 17614.867720334492, 11097.686050565359, 35946.57196941516, 82251.52333699683, 40495.198448681556, 48309.41661062341, 57482.95145744135, 43039.36801508773, 2706.9689944834277, 48147.78474360433, 21698.76106617288, 43794.58600167349, 29865.809765502603, 1264.060029063685, 39658.43977504685, 22526.41560816826, 51942.97749151487, 64365.516161501466, 75281.82002013132, 10365.508471020035, 73427.14192072669, 18266.24676039943, 85188.0511184318, 48825.286061247665, 64835.333328270484, 71663.01033982911, 34566.254955447585, 50077.79964250974, 76886.96818971237]} +{"id": 227, "vector": [89178.7105949131, 59052.44257131098, 78255.651338544, 6207.955481792304, 91288.24832417844, 59441.08844147611, 84439.05549854053, 44254.10120822643, 65200.2264939187, 30482.517766076868, 89800.28270687196, 26281.431007804535, 36029.538553208906, 28205.481820949874, 20641.0893899006, 39002.9856091155, 46092.704919193464, 25562.446041906616, 71735.66110141716, 10125.108679581062, 13921.906862529398, 83094.86925498978, 97342.48349426418, 90931.5266997293, 23612.723157200257, 27038.883489373922, 57181.94914150867, 66705.00978252264, 60267.82608482773, 8702.951952714744, 55219.702086244346, 84507.89910861348, 59793.169138791316, 97958.34441137614, 18911.793607781823, 14067.249308219698, 12847.201610847436, 64078.57337544636, 41312.73424095584, 90956.83702866797, 54328.14594270068, 92823.93113014058, 90429.57417840631, 59866.75199466662, 63381.73494126956, 35304.87328548597, 91687.84428120754, 34407.22466892354, 4502.233031751235, 93158.63002101042, 84803.69092297368, 80175.80868813983, 61852.303787378856, 89138.46259945232, 74033.7783785823, 13223.805880534444, 82237.39447285402, 97785.51700267226, 99632.00994276282, 39018.6574973513, 9399.635859000398, 517.6972367069154, 25251.31070515757, 8437.257437586299, 47642.26644142073, 43331.823945441975, 37403.64728184208, 22461.479380171502, 4589.317476832622, 52768.76090265945, 19422.109731152672, 27295.34511117736, 75996.11626171251, 91447.34064839767, 39387.8201989043, 4087.5786134371037, 48670.93800360455, 15415.869755628697, 86409.70973663611, 82262.22156946495, 12498.72436374666, 4063.5689362267804, 4666.721990252476, 13974.132974026421, 28748.500409752232, 47416.94364472475, 87373.66056445526, 33655.499213125826, 89488.2968773263, 75934.79605292135, 72228.71525201971, 87046.09521359151, 19225.855857854956, 66720.45974950545, 51794.65215950794, 3530.914557764253, 32793.446526990636, 22405.528240911564, 1567.554656010217, 7932.416687401534, 44877.36994830963, 58661.9234236094, 62674.391640754824, 96558.69692781127, 21143.00409652139, 85469.57124662043, 10778.112568471477, 91864.55150455963, 42906.81476324435, 69322.29843164774, 61065.326310112774, 17989.909199976482, 33722.161197623056, 60735.676672024, 33811.5520959824, 45014.3573421841, 67391.63696593467, 85395.90180246059, 46142.45995447912, 85299.49117250783, 68216.2931776029, 98302.72061271002, 36629.94138475112, 29099.332485379593, 79293.8348353465, 40109.836309456856, 41519.54191610986, 22944.394010267788]} +{"id": 610, "vector": [63968.80683934203, 81956.17525437348, 29492.439099826563, 8089.242411467112, 87569.44917424611, 16536.384983617114, 89601.37683966926, 66304.98581762877, 33226.381257883244, 8382.253818884677, 32258.507606126786, 15565.407652776841, 51271.23705156438, 41354.65627779655, 21388.43779644061, 31116.385448621477, 22018.21918786233, 89573.49722837671, 78328.65618260321, 15146.953283336496, 93002.22583653682, 97133.17625001282, 24648.857771521838, 43537.62226757665, 2156.0965801908806, 18909.20944409843, 1846.9933301815345, 25773.98451245456, 39787.51681753434, 76464.3826827976, 37541.587424184356, 64718.025646361355, 40886.83789122342, 16424.34440987789, 74296.49590522365, 76805.7217145085, 65081.04547227719, 33873.93633809349, 1940.4006648441664, 81815.28163661774, 5262.570198850258, 49387.8163173936, 13167.298686363216, 21965.591675942233, 55123.51177038529, 72145.31134448251, 87534.76268765883, 53519.611539939084, 91231.47282902636, 17704.91631771046, 91187.52009886007, 20967.356645464908, 77881.27841813357, 92342.98130743597, 7697.333865622624, 51605.95545388843, 59613.00563821897, 98412.34782905596, 13329.717949281694, 97141.90518480017, 94304.52235504378, 9349.625113491722, 9853.462020900572, 62863.36731758633, 2430.2886136738143, 24712.008502316752, 37328.46254543999, 87051.3965678333, 77585.4550566706, 77057.63012948242, 64646.969918110844, 38111.319104173155, 18099.34342153817, 94330.12098442465, 90751.39450648744, 43266.401462603135, 73582.70085529388, 17941.14992746645, 87476.43369946376, 17725.250024681714, 11238.129420600995, 81237.53157811951, 46022.964286689305, 29907.941594137854, 5076.317020638121, 26004.474790301156, 46803.03822043177, 7473.081082696531, 71661.70415950152, 45206.658798971825, 65907.85602526774, 68842.4718472345, 49593.95702065468, 7559.074965181856, 16088.752796808514, 48477.91090394293, 63653.351952187164, 53111.809286349395, 14773.324421171807, 78081.43616280917, 10286.434880079876, 71051.1012575513, 62823.50426790124, 57438.244051518406, 92400.89041247257, 55159.51783410239, 40547.18969772347, 50995.244888384914, 80075.06029607527, 42731.75902468336, 86353.43117242125, 74321.81495404211, 74380.32401142114, 50338.827012730326, 17883.527974413093, 42393.25595324549, 61836.62578828788, 94104.95478190496, 87190.37278062303, 61780.407681525816, 54324.79137687972, 8176.754901583261, 45844.32726109074, 80627.99323365901, 7789.268943568472, 13918.925923052228, 8205.573233800113, 91706.97205361638]} +{"id": 202, "vector": [8889.216934508348, 93005.51628782411, 69448.33828415404, 91823.41223749888, 73466.33505220506, 31551.094554684245, 38213.37555302126, 88035.61884549663, 11143.782231920186, 59340.81880001082, 70797.07484919227, 59904.9039441663, 27226.62702507822, 86525.29364755919, 30074.84354554084, 81755.41722166826, 13819.794945282281, 50889.0468907795, 24928.537210594226, 70365.73352526383, 75549.45023603001, 46901.07593156434, 51244.16678533571, 81888.52444181584, 94019.6097026798, 39106.229451474785, 79131.02845482824, 78538.8381069703, 39931.436200333614, 10458.409856332795, 62726.21867064892, 87214.76953620191, 24380.007228587365, 24029.880921792577, 89281.4373852549, 90361.20040795807, 88570.07933242913, 47675.37323906699, 36553.07798009735, 10292.983173843428, 72565.08888878647, 62876.54509134251, 93800.81843245224, 4576.412414295627, 60678.04297989538, 4976.672256230197, 58039.14277123527, 25158.541265875832, 88254.6159038019, 62397.32611840843, 86526.4719550453, 27391.747109713604, 89258.15346063073, 46247.90567991153, 51101.675455712866, 72932.11238330028, 53220.577804928995, 14057.90866628922, 40259.52497078824, 80799.97840920063, 20552.800202757815, 43178.78590021722, 52340.38055788076, 4570.977771695561, 44820.59683622478, 60173.07792013956, 8725.709650912628, 33051.89171499737, 17643.732718910578, 35013.578794588815, 3542.7786234957794, 61745.84195201981, 77654.87734970106, 55891.13660464351, 94092.49150178347, 59509.414269943736, 79177.31255248295, 11257.36758583371, 83112.83566925659, 12749.942657722035, 80753.13700509586, 68037.9542894574, 6367.915488226461, 39567.49770751404, 759.9319989170849, 63831.23989121788, 31560.317145084828, 19585.566452034676, 24885.56139115956, 28039.641182627307, 49965.32694238425, 47821.72797111034, 46633.887540655094, 37964.16165591282, 42706.10584432318, 10363.175620938991, 14886.507573281871, 70048.78992721258, 94202.53933634766, 20214.057313591027, 46787.991575403954, 50779.04315605218, 54119.654489463246, 31559.358408297732, 75230.64561842402, 70271.93378798124, 51787.2278323349, 53214.41887141328, 366.7862671499988, 77438.03012042439, 78219.34889590032, 15327.691989554338, 34560.09905676292, 79152.45907180307, 67654.73500365079, 63581.97400708253, 82018.76094676001, 37600.846061896074, 20230.1482137768, 38607.04717949842, 73044.6769930952, 10518.917141782058, 20822.316748256984, 58449.45465881009, 97976.20100196575, 27953.183759317613, 24065.58248257962, 13696.2217795783]} +{"id": 233, "vector": [41279.910023810684, 42097.63828739248, 4778.972327351338, 76909.2679297949, 81229.42256203116, 93123.70896163343, 93602.1926161233, 56187.386556451136, 94541.33555074486, 56297.584888425066, 34140.342817734505, 16951.897650599723, 16106.5084828373, 64993.62714576182, 87204.39961341655, 26226.04450866991, 89499.54835563568, 31080.90637344747, 72242.45767423908, 70136.98459540005, 44623.09821493043, 56334.292940541694, 46801.37248084275, 56698.11286843551, 10467.409852299392, 48555.492568610694, 38814.31814159011, 41020.851617369466, 87961.05643665156, 17589.216107242533, 20890.6051410527, 12452.778237442253, 5575.44390904241, 24308.94090064152, 12795.20813877285, 45693.047842516986, 44538.19655416691, 36369.29749302572, 75051.85955995737, 74213.10973050154, 79970.66549341289, 22961.829975053395, 42984.08254403231, 33429.7605015622, 72658.18798127265, 84888.29497292735, 62243.26442783134, 32362.702324900038, 74683.19262417496, 93988.95056992701, 99934.01819980233, 58492.383170082416, 51736.41606236281, 75410.2377467154, 58696.36864319728, 20731.828548157915, 94572.68632943105, 19859.62778114697, 99727.2247635628, 6740.796364884171, 97873.84255211246, 11651.311123391917, 55640.20568811673, 74083.02973630845, 19489.723763308342, 24716.948698841934, 59430.69324814201, 78100.57704500372, 51070.05516448714, 23142.06568188436, 47240.58173116799, 27451.73979509905, 55269.06982844215, 52528.00375900478, 79150.53972442696, 58688.21749719214, 45698.2673225999, 82321.5489949317, 24793.47459293295, 59177.968380980354, 71225.14907324096, 29543.80169715688, 89555.92894320984, 7525.211373717844, 26475.258807540435, 39048.20572511131, 32567.67777021703, 69822.28505355195, 73030.19534295105, 20884.317132408414, 99160.90529824315, 30448.533317147398, 19763.613672674506, 35538.612415692616, 33196.86356965258, 23509.815672206692, 57731.508827080455, 32196.874980374658, 16944.29135767559, 71502.35406200704, 6425.101058672644, 7844.203873680755, 52344.87967124414, 18819.345195562266, 13348.872801365453, 51308.12783950961, 32793.1545358219, 22709.644343824275, 1983.1518982034058, 44032.65367956521, 60188.37663762122, 40290.927104995244, 19896.995535943217, 16851.985985743333, 12802.243538705648, 64502.72309535698, 95987.25805638722, 69631.81972874404, 53673.04531379893, 89527.63363166856, 59292.41892527292, 60539.93607098405, 4770.350416647817, 30933.597510236777, 11508.595392123121, 43667.417062485605, 19499.760771854202, 55293.445037983554]} +{"id": 1234, "vector": [54343.35477576142, 72795.32958573765, 53615.152403547785, 6483.1083681738955, 23613.789551345355, 94097.40864587211, 66286.87664500836, 32672.431258018096, 13427.533582889517, 14449.853898514586, 21287.461263970563, 15191.531800183966, 9823.595857626155, 31228.39741390805, 41436.72110048396, 27340.426926896867, 2056.339805274787, 29898.15926056264, 37914.415195853355, 36117.72782295768, 52063.71089375679, 37466.162878998955, 39350.84837984163, 30122.98346102462, 68889.58930460083, 70335.94297846657, 61334.13406818291, 44246.898856463566, 80547.88293405781, 71621.45139424212, 90844.08768469638, 67447.45860868445, 21192.27961475627, 56302.37651882902, 19456.865322425663, 31069.71458091732, 74330.46467302689, 89820.99109920528, 26228.666602991423, 69268.40559688683, 32793.89486013656, 70145.77830980904, 78233.78615621492, 77672.3126074879, 88440.26403723564, 17025.432937773898, 13677.833965274243, 50828.38384608994, 42895.97089963968, 87543.94275265878, 31353.490011466, 45601.11834834787, 90865.13862837444, 37059.839781237904, 41293.259241808075, 88457.69705612221, 32119.603103395842, 47493.721537609126, 14541.920092104821, 32824.4137269333, 72309.18681844728, 70515.08412075872, 45084.979901656596, 30498.55767009847, 80764.77044681838, 26684.60660844736, 8139.62714892279, 95130.45969075218, 98992.53567943843, 86491.47455667813, 47472.24725205042, 99766.03999487124, 81237.98790482493, 5235.071166066019, 85205.73563221132, 13441.261832314343, 10951.259684285886, 57854.27609397182, 92736.03370187001, 11168.946540656032, 11229.475251896525, 54298.810891498775, 25510.691355906467, 609.5058360852668, 40016.35441915031, 50734.6246594042, 68616.4099119748, 86210.63827428134, 76671.93249919522, 5346.332725672442, 25920.935663110522, 97180.66147833606, 79524.05540748122, 18794.409945866064, 15490.19552643316, 20676.226762617945, 18622.983052135845, 2519.1494512378677, 16975.25463156787, 12939.57454728264, 1890.7680236946444, 56615.93314158444, 73489.53579493245, 78273.99085084401, 42932.289772957454, 53109.29481035892, 33539.28775869276, 46332.76890444224, 75858.91507228381, 16388.503290472025, 44820.77491498179, 1887.9034326489964, 59194.964917250756, 79340.27887207852, 74639.78430072044, 68008.93461832435, 69388.31183444608, 32184.985246168828, 1203.9477008974343, 86422.31969929258, 3124.282626253216, 88363.14961158832, 44344.162860323544, 63516.75051300295, 68782.87742473792, 10930.668962609347, 85039.45284433042, 87724.57888869997]} +{"id": 1969, "vector": [6021.827673339663, 9083.528679346753, 62733.484229429036, 94008.51372626827, 91828.80816313832, 18789.92652179938, 5701.52920855993, 69039.95174898942, 77359.52037132447, 50755.780114854475, 6763.521621856438, 12444.228369354048, 6872.753524472641, 18833.108835491486, 11886.268942621648, 91655.89175218697, 28564.671606404478, 99761.57019157504, 72339.31635419717, 84325.42605796638, 8508.78716749449, 61971.13437047999, 76872.77660045122, 21385.321641585564, 96087.75953640944, 89251.63588127373, 99416.1987065155, 26132.15071232444, 5332.400215813826, 78857.12569164293, 11858.24604603799, 64433.213524658095, 38669.335110322965, 20665.499966096868, 63222.60697002155, 36074.27965642923, 87571.22489930742, 29186.928801195278, 58143.97911773309, 74758.68896238391, 59493.25306435856, 7389.005114964453, 81640.21130381152, 11515.940435593397, 61150.83184679881, 45244.77582808756, 21934.53372173768, 99161.30492289209, 51861.511281846026, 92171.80593622947, 12958.9009005321, 38471.60521408502, 59026.482544739825, 81586.22279046457, 62901.77320788407, 80262.92732215043, 62713.16747037991, 11421.2955136202, 85894.86440560999, 33728.07899398631, 86609.15232877436, 49590.228992646815, 58077.595037153005, 82993.63567839366, 99250.3624293396, 14344.74815176312, 12041.85422145061, 11131.265447522099, 95456.25739494443, 16319.693155998772, 84987.43233642759, 45130.231458742484, 38963.99338817812, 15069.77877580269, 51856.90667850409, 71424.18226171931, 68252.79111742569, 97421.9959161271, 92892.12929386293, 38914.92015885466, 22352.245019931805, 69210.23688063434, 52795.84471661164, 75513.36783700666, 99915.42591670848, 48688.25819108532, 9478.764418913388, 31494.613967737485, 23676.033381253936, 19530.857365998567, 42471.26729197537, 83244.53769125388, 1699.0919419056504, 15305.638966369506, 56176.32616530811, 59606.013584739034, 48742.4145966899, 34297.132607497384, 5154.854677503584, 31528.43226475792, 87280.51252861254, 62667.1042755771, 59168.72716012127, 42585.116322294634, 26593.692960402946, 11317.935438439352, 6993.571197445803, 75366.82829607888, 28745.075230709972, 61021.47395212964, 5919.334825468392, 791.170049107448, 62503.463504829524, 23219.615681080642, 10208.622952490265, 93668.20825451969, 29684.93394896088, 83817.93202880789, 65559.47899515477, 39941.444233576054, 92584.21072986742, 68284.87154333747, 5048.400946348442, 18019.905357388587, 20730.47183125095, 17118.604999335075, 98373.43419199184, 47908.956009079986]} +{"id": 895, "vector": [20224.006454241906, 33766.43167504913, 10381.746438978202, 98845.7195642245, 83733.51204346135, 18130.79004081538, 12335.426968367046, 17785.452182129357, 96553.53311812435, 12505.184807962665, 20543.06673944668, 73764.63176675046, 26890.390879124938, 9463.719753340627, 84330.33939781082, 17543.3967491747, 49423.599180160236, 62176.354804448405, 84274.9556686216, 28019.281092253055, 26601.1279449877, 49404.38798470554, 63706.36000372149, 40711.947353133815, 20014.492069813416, 28471.73180681559, 98610.28743680617, 64256.92794046291, 83620.85038175256, 44488.985358345686, 4527.601996389497, 48941.249065949385, 99430.1073160311, 2783.001393508255, 80631.46603806452, 54466.9358400895, 27893.360168479696, 72870.54732167254, 3315.899779465814, 20687.447703255424, 23827.059016299947, 59241.442457021265, 19397.23488563525, 83327.48377070892, 62415.689606381355, 59538.023852454484, 99917.47994295583, 86.55732099622782, 72847.71963754077, 93665.87919335708, 85424.97585114914, 46653.460883809516, 91035.99209890177, 83241.35186566693, 64626.88902970931, 80271.33910113024, 42488.13550404549, 200.96954313656434, 75377.38223010385, 59381.31372391127, 69446.36103317114, 23639.56504125524, 62705.28450293825, 18494.869861534135, 70488.36495306027, 52186.8549580897, 48961.385053065176, 89256.00844873792, 26636.39659089616, 71573.48208513047, 41980.98759588772, 92472.1343426943, 91756.39491329614, 82006.49512343906, 94700.2864960774, 93697.4190976019, 93722.29585900133, 65256.23301299376, 36554.16465616882, 8749.362295676567, 28048.472738166587, 45022.77017814461, 63070.11676296107, 36367.70393976596, 73934.47362094735, 1019.854650551888, 42722.45147599996, 28518.315198639564, 17760.93947490903, 34418.114537832735, 22945.08799660974, 32415.20802195803, 67870.06690189047, 88440.91331266059, 23760.617078053747, 99146.49730751861, 26530.533706488633, 74612.40636756284, 29572.18779437819, 90510.95614396897, 75337.89538776778, 86293.12949814263, 55798.17342051975, 85836.00070809278, 4114.5689726447945, 89631.33574700235, 60271.5889057444, 39086.27758366008, 99972.89214242889, 22186.88837207763, 13405.809554444759, 33040.006635775564, 44348.430586943636, 52840.11469489294, 2305.937489909715, 47457.6480242191, 89938.65520096912, 51350.99147068087, 27903.5874834052, 3555.809863051418, 48345.05095495277, 70889.5254506136, 80921.8438857106, 9885.343706949257, 23454.345515371013, 59440.25422003096, 53596.548206645064, 84422.61763269559]} +{"id": 469, "vector": [17704.485619238385, 37789.792276272674, 73510.77752681273, 23260.985915706733, 48257.54304998293, 62973.79417814752, 41693.756863286115, 6017.251282982239, 75963.89592109031, 11985.900046653807, 87268.91380785676, 67033.297265871, 77164.86278565404, 98077.68316149974, 75021.19870070506, 42720.88700142722, 41242.295712518295, 86620.21741010777, 41477.146827409604, 39402.94438163845, 42295.32103793223, 51277.345184679216, 5282.470326238542, 72144.04682499463, 69098.98582315611, 62538.24449541554, 63786.74302146161, 18733.52076460284, 76036.91685594401, 59118.22444493162, 5230.269013714672, 54512.55117674941, 77639.59929089554, 51087.71017886548, 92243.51366058543, 32611.638290796673, 97440.05984596074, 72311.99502232933, 72330.4206289443, 56834.87320369656, 13246.475451639682, 79311.77157792318, 56239.65318825864, 66929.72431412077, 25303.348772974576, 95042.88941243687, 66282.91635333245, 65278.27346010907, 1447.3693257620491, 31534.894668899706, 5492.605321910704, 72515.19007870542, 82916.75045974695, 33401.88647376579, 22513.89867081861, 54737.75622635328, 875.0604721989852, 6842.866197941222, 20335.404449386264, 32278.262355876508, 72957.42841081848, 22098.67786169164, 66105.73322734488, 76500.63752011833, 40120.19880215505, 54067.45279230204, 38033.248552579345, 58786.7241508135, 3323.6416471855046, 70136.59858460583, 28887.561931827222, 39644.31219764657, 14408.099733241053, 12897.741270565222, 62565.91215583105, 48963.96653039463, 21239.588853828183, 11051.546675148216, 56621.93558419115, 14000.73697300782, 85363.54274386141, 98224.28642525984, 31166.609442842153, 57803.33008812599, 78597.08533923661, 61980.82230662421, 17091.7442196175, 93454.65345681514, 18949.64551023822, 83201.71769967278, 21018.5220225386, 49375.93888273184, 28327.853020251237, 76261.28412205263, 24895.883920065164, 21865.3676144026, 68761.05120251606, 95164.51477899018, 95085.78744393287, 49647.51261157988, 82483.78265610199, 12062.949502256037, 64758.26824345233, 18858.50757327938, 40472.5977591011, 72754.34430736215, 13601.843004626402, 9921.40860902846, 97595.73759532097, 39427.429213874624, 99277.64117086075, 82100.75043879997, 70988.46533870923, 36947.99476624954, 33004.98480915173, 74139.30655780641, 86025.24381471735, 9562.440490174828, 86040.02468880313, 57.13335355151949, 54867.306395398205, 32126.174578848222, 67922.02631964842, 53850.0900468879, 1861.8944710221963, 94293.18390690736, 96473.82900910382, 93109.264866419]} +{"id": 837, "vector": [23322.476572965512, 75318.04355770786, 57722.81735854665, 5810.499972768046, 89305.08328350204, 42657.792843970376, 5099.323397615219, 61614.45008251167, 89574.70173377811, 29461.050432927084, 84949.5989651119, 12836.598879250427, 25172.613665579756, 45938.1178216695, 18415.982849614265, 73437.9544523725, 45514.17158371108, 84038.15943577801, 38774.72262799251, 43226.551755605025, 9787.458783007785, 53841.28699990434, 81765.22602846548, 90522.36200136787, 55320.527189287546, 98920.80798438303, 2022.21209057859, 50153.60150401492, 58556.55150961285, 5423.773749422944, 89530.9061054112, 42293.743700291954, 28590.144393487913, 59015.94138481338, 41107.052762344734, 12782.702496086396, 23400.818423107194, 89409.18180699217, 66147.9894393338, 29527.968307764608, 49791.41301656556, 65412.260663940244, 8792.989393866812, 24587.542449803, 33072.78758367458, 90659.80976838281, 21896.303368271176, 62000.9850558885, 1899.9572976029278, 50942.62189731459, 22978.60112936243, 42624.01458156582, 28327.35178334851, 46744.483885056274, 16647.79559742908, 81718.32261635616, 84127.99632105623, 46545.79950633233, 58977.17234142186, 123.80602863318879, 83757.01289744275, 74428.57448765732, 53867.19832557229, 36087.367817308266, 42723.39088926662, 25322.147991801525, 88051.30854429508, 59700.4193074289, 77502.45350923987, 45804.01133140283, 34608.56860662862, 76777.50219884489, 70178.97525502538, 4660.507205608955, 15668.103906279463, 9752.383970662448, 28683.091666318083, 59014.496394696136, 67306.39653644004, 74039.57705568773, 94663.61314012637, 86550.8668932318, 33158.080979109174, 51680.26324644083, 81645.07845686287, 43451.04585414331, 79651.49621669867, 3548.863634913324, 16905.394644784854, 62845.321922118426, 35695.872615364955, 39682.19102423368, 18142.87632094267, 24385.101634280414, 1673.7906248407098, 69874.68349643744, 75268.20127595574, 78013.328394453, 24870.941884825483, 16624.193394304122, 20818.026658509636, 12496.124911845896, 50458.56846619446, 24232.008703564068, 80189.97974412121, 52999.894822993556, 58923.31941548367, 51400.631038207415, 47109.89022221679, 13634.338893958597, 60782.989199237905, 69846.13052802485, 31421.03124901796, 92622.37171285266, 77164.4608257932, 7663.179902800554, 38909.45497448143, 22005.964552795722, 69756.57943931056, 44940.57847806184, 25234.76862521803, 82276.51055695786, 25615.4337750545, 13135.498192453388, 88760.74079747322, 10032.688501038134, 99936.11340841479, 10133.780459333597]} +{"id": 1856, "vector": [71231.28962183004, 7817.911645600883, 83768.97186563516, 83078.75572637518, 16535.343043621608, 93800.90568012466, 82743.42617209487, 45841.46836069894, 31125.768633966887, 50239.95492280805, 59154.07528886969, 98856.94304728016, 52458.39908386487, 34636.90512081724, 63765.10661523562, 87079.39665959946, 51374.57810811966, 48720.98176696517, 26364.527723412157, 57864.2841539099, 47699.63048180026, 28193.549430520103, 66883.03249071847, 16590.139738159003, 2325.2958944820266, 17450.66743207986, 51055.47039675448, 98223.51995869029, 91066.38747234999, 34439.05438983762, 93488.93309554603, 58901.43023863929, 45014.78535395561, 82688.79777618907, 71919.23798410503, 34749.03744972708, 2278.7846680874745, 53406.114992038754, 30905.438346839754, 42400.92081524614, 98108.1650757372, 78016.18043063357, 90276.06304942707, 60073.29252350969, 25122.56950697789, 66117.98245169542, 78739.25968741729, 9114.44252429109, 91374.30254173206, 66136.71430164909, 24499.333018199377, 7828.239649635738, 32085.692735819604, 4535.819094707461, 30206.566125362744, 79469.21579563826, 25535.35292879876, 8143.831788063949, 8339.59385092925, 52588.84674447112, 8341.588751753981, 24226.19350150228, 17157.579408422607, 38427.32483984904, 524.4272272702966, 66744.68963726368, 63369.495144649656, 76845.82027865144, 28633.037016300234, 1601.0349990207008, 24015.363641604235, 78351.0235596806, 19458.678716727285, 84036.40537104348, 64887.44486729117, 95003.97461451728, 39866.96230704635, 7005.604379994435, 5213.9969029450285, 42045.757206832124, 67708.8477545155, 70187.63866468742, 12220.594535511786, 2141.061249221943, 77992.4736885811, 55500.19905911808, 85319.23790250397, 40604.68355316229, 60575.547672914654, 79498.88060265928, 30687.090705887476, 47654.773125311825, 33401.81228548198, 77008.48532014142, 64712.74701333022, 61165.257275631535, 31895.220878698994, 91690.42090555864, 77808.54270200012, 56572.70815026614, 10326.962621848335, 80885.13686599347, 5805.974387363255, 6822.466387745463, 16458.273139127443, 24017.283159145463, 81376.26632774799, 9682.350278866847, 82371.6909490029, 30751.345073427972, 30787.3365344379, 76110.80134534457, 38166.62499320856, 5796.93884041903, 37862.681443745096, 35354.75828300577, 29405.76405598976, 9419.769572315618, 82328.57617302529, 15973.68542623605, 86076.16955906645, 99086.45566760887, 96232.58501494092, 29908.243159658865, 63761.79004351724, 17358.706502895417, 26980.577426727003, 18539.792410724855]} +{"id": 835, "vector": [5468.279455492653, 26000.776387119007, 53103.96946913375, 88485.69910752932, 42776.12744777381, 32392.364767329895, 37754.088405669594, 11857.580736497765, 95440.0207238368, 14636.404583568885, 70607.35072204874, 30007.845460524262, 22860.4732772445, 39175.19439518236, 26501.921264107077, 9426.886950945667, 56324.406710497366, 91968.81409327731, 87294.11789193357, 31810.50315297648, 85489.97798698909, 53046.637929980534, 31276.640325733406, 98410.78321220202, 46348.811514318964, 38290.264363809954, 65729.76416838009, 56816.767656732016, 32894.82122399617, 86517.79299239183, 93395.36301657112, 90862.33909249175, 58121.09967902654, 26437.20800135939, 4720.075234834964, 52850.183139201814, 25955.234661667793, 15628.289897658598, 88351.24746646678, 20046.017645970238, 83547.52604696728, 30162.140060770336, 81041.16184257473, 41544.09211202836, 58453.968806752186, 10915.138993896257, 50453.19569341885, 1626.8747475792322, 36403.94853580163, 22674.50869389763, 6979.547486844795, 31459.13176354339, 19075.059449550125, 62447.00330275023, 56877.65931676313, 24073.589817761498, 51498.72671107795, 9825.382372848811, 88600.56114452392, 83321.69748029269, 62376.520509096066, 33664.40552152271, 27692.18075730757, 15682.572017263108, 43806.02671246692, 79287.43932189394, 36747.294807138074, 72076.03114322692, 38725.64156748592, 22331.492192068094, 75224.9537632682, 53242.760459559824, 959.8331750235944, 57791.07744494154, 52226.41280196545, 2634.4615995242402, 26443.138091509456, 77682.27442768702, 70357.09207254456, 22863.445141584125, 63870.46424509939, 35387.227658794654, 86849.64906317896, 79462.02652727523, 7173.1335231639, 78208.84947619014, 49746.43246896073, 6916.690253270785, 29635.722838559584, 27823.407824433634, 89373.70140782665, 8522.984660849741, 77498.18651095584, 11443.890457472062, 98533.91381921188, 25357.056729694195, 73191.34621184174, 34706.67787875969, 48200.719170327364, 97518.4430946017, 13683.946576683604, 28593.410389733486, 47559.856197390705, 76996.66997237138, 87590.93247918136, 11675.902551146512, 71542.94865790768, 12139.460468628049, 15054.761765892654, 61335.70510835546, 35172.563828493796, 55570.42733119126, 58234.100101392636, 69276.63091994596, 76479.19262219727, 95527.46116810199, 97660.42648333637, 47874.49381225109, 21222.93520115601, 46575.82292254618, 853.2583850544895, 15728.496754819422, 46376.5948461473, 27486.534214452273, 72764.73954122778, 30134.460245648588, 86827.54907332986, 9520.182682794088]} +{"id": 218, "vector": [90409.83388671675, 25605.100031286733, 78451.54401915318, 47533.71554588752, 72431.65473729136, 19946.53780112058, 48111.52947613411, 75015.09334996581, 77309.30900839333, 27921.539594202204, 9450.178209595384, 20288.392176466707, 91347.10703140472, 75917.50778821844, 17081.60801266918, 39269.22259528244, 52878.83141186239, 43730.6927809763, 64150.04616729165, 61043.71899121652, 7911.613865481182, 30800.065942214904, 75617.77619476621, 86380.06967777091, 81937.30439803192, 90772.7691131664, 59805.23788487537, 89887.32292513881, 40065.79282629553, 81083.95483381509, 3756.2355664881643, 7193.5920026142685, 46959.41743395224, 41799.918979584305, 27404.571443619276, 30594.59498675946, 26278.617669738767, 67479.56216000681, 51946.76098857856, 62087.26028474183, 69424.83287452464, 21695.659335716853, 26824.70363289574, 9598.378156956622, 82896.81154233674, 50444.48828903081, 24363.027243518332, 51891.41668565671, 22153.736717171603, 85081.42818559674, 15698.912050957591, 15950.39802309416, 61289.13967105999, 60426.26871650313, 20530.387955174247, 50431.04360192392, 35477.952736482024, 56664.91742173778, 54620.71353657989, 33288.58620895235, 96069.31995101493, 43843.09385224252, 95285.96217438609, 9949.238612925205, 78224.6070847263, 69363.49599316168, 80158.93791326023, 96460.84614269005, 26712.844780094358, 54041.97192626745, 59397.68707384757, 29671.271873351547, 93773.21943125567, 78728.43310010145, 82463.0206636544, 39459.75570232827, 23298.439927087733, 61764.24400175655, 75989.78503767627, 74283.36992538645, 72861.41415217202, 2211.0596449432805, 40097.195806295786, 77076.03644908608, 9655.538670592745, 21056.48300255343, 22037.551665690313, 62256.8000332083, 22463.125064792523, 90950.97439110825, 76677.85116736668, 53020.75835711938, 30554.39297048923, 4131.684417009063, 69114.07252179385, 4931.286075800812, 24016.843393543943, 58926.84403511722, 30861.024079093648, 7954.9898518036625, 3398.306949546448, 63913.11461291367, 87616.56984041249, 77267.79128026788, 92632.48019653028, 79970.65574090394, 35246.48118609835, 71045.01613026962, 19029.519424041686, 52699.52896209631, 95969.18704986702, 1325.9018795987677, 82023.78812102429, 23763.738382425126, 8936.056103557177, 45236.17067055731, 52096.744867046094, 63317.176529176475, 21778.21576673975, 6225.901199612782, 57785.3414056115, 36197.36822427968, 93338.85336986535, 29471.22476905375, 39200.58693016234, 24224.409966057825, 43897.50598077667, 73468.33468371182]} +{"id": 378, "vector": [64517.77359071109, 16559.893646266453, 41820.77790246006, 46422.05451386139, 80663.42076761465, 54669.72728544678, 46763.65529393489, 86454.12244342022, 1284.747146444909, 53876.16651943023, 18402.204979359693, 19209.64417737918, 42122.61657174082, 51271.49916381527, 33265.86319733031, 26716.067225983163, 56887.92626303066, 90275.76743712946, 11816.25049113778, 34384.78451042426, 6661.805050603697, 54977.24530373415, 33203.137453265364, 13441.999965409523, 75754.87904665392, 7012.093105596595, 43681.820538001026, 41418.04374877519, 80465.85215864182, 73857.25317305411, 10660.022786710953, 17001.39624349839, 4519.269849973928, 17911.06852861852, 28083.098129235063, 2739.190045738771, 21210.294044589118, 63067.626439383326, 63720.01015015142, 99492.70992279943, 23797.71879033935, 70614.52888876224, 21558.244035827967, 3804.8011109648837, 7681.525364537567, 16926.508883964776, 66814.6675482168, 95771.77379266529, 99973.27196281565, 34602.4916800107, 70408.08976313466, 26373.070823204915, 42543.99712496441, 71006.2989315396, 58072.47337761406, 17574.124250780253, 59023.82333148022, 28762.127327831222, 31561.111359763217, 52371.265157001515, 8146.6409377149885, 56136.00494370159, 89995.2064000276, 51957.11866696145, 16201.909964801685, 87494.21824560613, 49887.29631487526, 84567.1932382148, 76522.8288581649, 15861.230126174187, 4815.446137129342, 77911.6088394098, 45676.67094203188, 75254.12803195539, 41103.28512039081, 51206.47055583961, 95056.36210176178, 23452.988041516586, 73942.11694024796, 33105.12152702746, 98190.12356612216, 13045.434584011773, 24494.485870963745, 34532.62356244109, 21861.63216892668, 48070.37874579555, 36432.39067205269, 9654.280592610576, 87956.96594539384, 22928.967498980703, 59819.14075700493, 37019.94808904724, 71063.19585174702, 88253.46064802902, 68366.03634003458, 6248.729136918063, 36576.69807845827, 20309.011635595718, 71575.01958440535, 86884.87116165643, 96501.70611934457, 45621.826286910895, 91330.45522909386, 21941.10221460691, 30536.901429802154, 17723.10732815673, 12762.987437544427, 33998.54965446896, 8806.742816152546, 58345.74738518896, 24341.173950646957, 67879.58567760688, 99282.83989789846, 9467.497298774786, 41885.10916198489, 26625.359527180502, 13622.289318055458, 93974.67172052272, 88796.94644974424, 8033.057266517507, 46752.322078901656, 16857.31246633887, 9409.832034159537, 46537.56739068907, 23820.370813648708, 99924.77299587087, 50802.99393383748, 65903.40956229155]} +{"id": 259, "vector": [77220.877203854, 72511.4663710996, 29894.327536798082, 28994.118413427437, 813.942671400092, 51722.445898568534, 82417.58765656497, 54830.81347661675, 16357.649663833785, 76440.43008350137, 52509.90614597657, 36642.91128553443, 53415.87563385322, 38717.98124756314, 77517.55163023579, 33151.29788343687, 91193.9809214009, 74181.935201057, 50986.12415086979, 7011.927852390642, 81545.58065890284, 82318.73000691047, 30609.20728189408, 52464.953465619736, 81963.78394715303, 14757.031751465143, 7690.3920600930205, 92025.7644375983, 23846.400916319188, 29163.18894222233, 36492.435726547854, 34744.33390732645, 22133.49823480465, 54890.065707350375, 23542.58584011828, 32597.352365423492, 55755.70838220445, 45780.28580992498, 50380.17723086677, 37803.337124240796, 87524.6566985859, 61428.1827782197, 5557.749015888558, 82139.9887533014, 28787.108542460093, 7127.020252703164, 57625.0963661307, 23973.09197730566, 13237.759970617803, 58623.08749669499, 1548.2768205837272, 573.7458517514505, 10127.731519703553, 2554.476253768223, 56786.67001789364, 32412.815166543198, 92364.3159050994, 79968.54218652591, 70987.83906637436, 58627.191807844705, 1922.1013479183146, 10313.207564392502, 89663.85439481656, 5136.227582397157, 32543.333021755672, 87699.96894697766, 87287.36727268649, 15711.622303948325, 83119.66725801535, 30964.401772162488, 66119.24097914404, 70980.0347358713, 74061.31883117418, 48076.510841295276, 21674.53532419026, 95229.83332370826, 3022.725578092189, 50781.56016717449, 75563.22510589211, 57367.65609797651, 62040.85946319655, 31529.955591311744, 78526.62358924675, 43003.78886553127, 25640.240494498234, 94645.33679729358, 41903.85497587798, 18127.365320029097, 98636.6409003194, 83897.49995467514, 69678.11258586994, 81862.38546058728, 27309.897499014834, 34295.84201184268, 91323.68467575625, 22666.788812126226, 96103.59995499135, 5656.140760463113, 61318.40508317433, 72616.18606483456, 86743.89549297997, 70140.69730591936, 27053.04803189128, 87703.58598165572, 57912.74154316802, 90418.85775284655, 33778.350298937454, 97815.80276869332, 11148.041828767342, 43973.23328999678, 78457.73679302834, 12686.461094722279, 35926.990061852826, 53398.93499224202, 18503.010076349914, 96372.94389486311, 17907.752569101376, 39964.79862208359, 3983.420002924498, 18058.861731174213, 40433.52406868481, 67564.59042739043, 46229.51296739647, 47406.78452074387, 11150.087997318802, 91610.59377473674, 27479.15782474768, 86065.64789086874]} +{"id": 148, "vector": [56737.00682606419, 5963.485805624547, 46481.21054121609, 64191.61602584069, 51277.77894092735, 44335.20299680812, 10995.221164437951, 53944.20738907869, 80945.55578574738, 75368.38673953663, 4691.867393709636, 30563.792491728393, 76965.10443389809, 66492.66685551706, 40344.13219621978, 67249.8808874405, 59199.07478806292, 56754.48630219133, 29772.217144659397, 97135.11786139218, 34465.45751652736, 63603.96438752984, 7609.422072525851, 32482.401942515393, 70323.72044267507, 42493.49957564023, 51193.910071071645, 81286.24363159512, 22586.13092320615, 22344.66083915089, 16088.369718846785, 90809.2656601393, 22029.831259852504, 7747.293365002872, 61465.97007340268, 77445.30598657178, 42606.614087042806, 25509.19273860821, 49981.51175894761, 36513.98110277875, 38977.16785529866, 55543.33795631865, 25671.680571710942, 60821.37475055204, 8424.475440876655, 79856.19695479787, 63583.1532091692, 83678.91323986971, 23763.846613478156, 74852.05804094707, 15170.513652590933, 93632.98722403767, 39958.50959847883, 44521.12771543133, 21876.24170071304, 32279.518408919772, 59785.48326467416, 46196.580221531294, 77339.95234027957, 23069.058698708457, 21491.190134920467, 60950.572359630416, 49839.60537565423, 82515.0422501804, 95094.06410249391, 92149.31764074869, 29436.10988851928, 51170.73141740803, 12082.73673992627, 96945.66014892588, 97940.7644985023, 93157.17282243932, 91394.97498710112, 59628.31053308787, 73956.9494003435, 14733.401684013692, 20432.42568033321, 27860.775578111698, 38973.79150391528, 75836.66956905769, 69928.19677537183, 56468.91804960718, 77535.43261237498, 39481.14462367001, 99751.70334691169, 15433.900943891398, 53498.26636229876, 23037.808868672695, 99899.4126364532, 54258.873984352766, 98945.21763698936, 9995.766659377658, 94426.27009439353, 201.3726747201572, 38624.02768625996, 44658.458933591784, 54936.214621370906, 46081.968379063685, 14027.112306577905, 37303.67251693175, 3822.3075925507, 65638.0237964116, 80620.59156560768, 29808.074447083465, 54951.87070443488, 545.0124347140539, 74459.60964682233, 58119.866211374814, 8504.415026508905, 71405.81706340522, 17126.91209031827, 648.7735411696339, 99321.94186984537, 13376.362389184715, 92959.67424616792, 30227.150137245164, 26504.92090123976, 4802.394698523238, 58867.05997329893, 61288.842183723136, 94492.21382730233, 29752.00651655412, 64476.458000812905, 74642.01794807923, 98638.80673651827, 75782.46955732924, 2637.4122195668815, 76896.590309143]} +{"id": 1768, "vector": [32006.213485580716, 31394.17258861399, 5462.010435665432, 28575.607277854808, 28870.8778097297, 96370.19789624613, 24930.22956368106, 14105.213186912091, 67999.34462884437, 75059.90822071162, 54810.66228532022, 2764.0414680630274, 62160.13096329825, 27667.47483572939, 97270.89312086995, 25554.159851596614, 74905.14619828083, 94875.90810451828, 67430.57954590395, 16788.30912108762, 64370.56662613505, 72688.49474358567, 21740.88230165826, 27536.704522906573, 7288.895969090859, 84923.14153442501, 31595.74770760587, 37522.17455372035, 76530.33739315932, 34353.9453520717, 98645.32349883999, 74504.30114888068, 76194.16526281969, 38784.00568496228, 21220.899206573962, 77858.27578071064, 30121.84765842334, 92117.13447678242, 65831.93672505544, 81614.872378199, 93369.1215281208, 63087.02601045825, 76459.19042438026, 4335.319028754514, 44884.82788631596, 81813.63990765803, 25677.98058658135, 33893.236719478904, 74259.96499524012, 59113.59185521501, 21388.192654227933, 47217.15905409904, 5074.949725056033, 13413.779204329201, 79715.75476765256, 91090.64783888306, 7666.58048719836, 15878.696807128079, 23255.175412847573, 76009.32915321304, 86610.28036697788, 61046.528653753296, 53991.985965792985, 53332.48683878321, 61321.88145159703, 17875.239466900493, 81730.76856030441, 35916.414409167795, 46292.69552949862, 49673.835406936516, 76847.96165930924, 56945.41880649917, 5116.032598902065, 80034.14815907487, 59899.14965125029, 5789.5701380887285, 84173.26820553245, 61310.21338176528, 9319.954067868663, 25657.789270063146, 59784.16889308715, 41568.83728172067, 36661.330052581994, 88028.38228692421, 3744.9301615739337, 37321.25643642302, 24183.48794472005, 61784.1457417031, 29915.464972340968, 30682.442911730766, 64161.44306751417, 48214.85594030492, 66297.5332333746, 57809.82830736293, 90749.03748666303, 13951.92130509555, 96647.98091794823, 95288.51036973963, 53978.86486664886, 24897.963032154825, 78080.5287490547, 25597.102413438468, 54916.75546986904, 74794.36511753395, 35934.02589705784, 25347.169866909604, 71850.36947885872, 89003.669505229, 52733.61649817945, 99767.30338113412, 66.79495423882464, 83383.38401053914, 7191.003385719497, 65548.00536183339, 20137.73793860375, 69226.48218569998, 66430.30348942068, 31056.35404792495, 40613.39970218077, 63490.959469938556, 99903.97989121325, 75764.85643438876, 23934.401122730655, 22710.301129354702, 37162.94639631833, 63795.876678566034, 16059.874811611851, 59470.441952497575]} +{"id": 264, "vector": [17826.85557720096, 31231.98988632292, 45377.90548114449, 927.1439646565849, 35707.10122175279, 53787.066409711915, 78688.06687827641, 91385.82364369709, 51851.89929852504, 94732.17981396783, 58394.02774524907, 66033.73070381237, 53786.72394395903, 65118.672858010854, 24536.220988599867, 25313.650541636758, 61415.7524739675, 10293.231760500632, 73448.74442227882, 76143.71816748692, 20986.493443572017, 49170.00797189755, 72305.12516765256, 36183.36550654263, 94541.50106586474, 23935.276998322406, 44400.282264695365, 91559.9965592014, 25007.6245040583, 83206.54386222355, 93775.65704609297, 36879.77625668949, 31525.84696551891, 66163.28734047544, 44152.41066398917, 32654.874681271194, 74230.95974008681, 45490.77584960706, 91699.36810727461, 23706.045742859606, 6111.285312853487, 74928.61960941402, 7151.892918096148, 84737.2069223645, 29129.322367037246, 40411.75146561876, 22237.6283192548, 85635.98007542251, 98228.05039503558, 83938.60607706849, 62832.599793684196, 96739.64240174826, 27155.269450694264, 45898.15118601993, 56376.89063176001, 77040.52495670784, 80517.04962980344, 55321.46344969412, 30100.763146642996, 89302.04438641503, 8237.169724867388, 87255.90477135283, 12216.266900711293, 69355.58732372541, 99994.0024907227, 47423.457738589444, 59981.22059853173, 72706.70331509445, 98771.06727026426, 16273.831722335563, 77006.71388181107, 36034.24233751748, 34202.75867407015, 1687.75028910505, 3108.1052217955876, 17351.89644187606, 79479.95695838651, 4218.065317595243, 66236.16330707773, 32071.69657122755, 29919.791112165418, 42215.509620837875, 73822.15125144793, 24873.472707928755, 10393.30233578818, 32469.373210780715, 66456.6371027509, 42659.58336665922, 5525.468227946461, 42041.714580308086, 35595.592303588564, 21225.076412914066, 31778.883646786806, 23295.797737442892, 99345.50790241593, 53879.48400030514, 19019.020313381487, 65954.24068428871, 38787.77644350844, 26571.762273778986, 71161.97124790397, 4545.809881620244, 52043.07273750721, 74921.64008536203, 5328.779762599523, 15124.014574133216, 10548.276629984455, 9815.03293744318, 12129.370078612012, 34719.74685602086, 58111.609143541806, 1532.1927718921713, 28905.14182053672, 77268.77563340269, 10804.251427519963, 45840.99770008849, 72356.24436424192, 58038.43737864002, 35341.72462453544, 35106.26887050895, 53788.30806745305, 73102.9353435308, 22749.94054709949, 24728.115352399305, 40076.98171968509, 53741.28052758472, 61119.60496675477, 40715.443768162564]} +{"id": 623, "vector": [83062.86468031969, 82788.59217344629, 68630.55043733766, 22347.36631463965, 97010.79702075972, 15645.753019170194, 97887.6716199544, 82618.26312374098, 68209.02969315524, 75596.76124867234, 87595.3127214022, 93366.92350356998, 68873.19985925159, 81974.83071916258, 94041.14311962404, 15531.931428911017, 27747.38885700366, 39100.099381982836, 83707.7614476576, 54057.07327754053, 11563.066496779895, 89921.79739163448, 68110.57619691522, 38554.701433129216, 57289.52196155753, 68234.43275131092, 30712.057942280368, 31645.688813128527, 51223.75739532151, 35051.55753684276, 45609.58487202994, 35499.203188108186, 19462.749882764463, 56075.85759652829, 37207.32934259016, 24629.87438615457, 27397.57744435175, 29011.783120963864, 67189.1931800887, 1133.4293459700827, 90648.88398297498, 14837.330300184003, 92178.2022873454, 4081.9741519985796, 57797.50813133258, 91517.7756136822, 57910.20071221632, 74169.5591752546, 68356.70626840403, 69698.01525855107, 11907.192608990757, 52479.3759885422, 8508.989782596132, 96250.67503458359, 58434.39470230277, 18276.109723301604, 58875.44601212591, 71192.23830750183, 86973.17124689551, 72329.77664615668, 44245.665942068066, 69179.49886959005, 72956.26670809496, 81060.234003428, 49985.897577411844, 56219.00923809465, 95837.62760340757, 67490.74162975667, 56984.715492357296, 67546.9715293473, 32331.800689307212, 37646.03621408932, 49579.07590962115, 56851.93230710551, 37802.262423116896, 70008.4039673007, 94861.99522435975, 77635.66455182881, 91918.76860262084, 65132.87501916487, 77790.13019303698, 95985.24576022376, 68032.25415416721, 6676.090969730486, 23735.15070622566, 30340.07442066964, 99401.63710455495, 59024.75825792027, 73871.46787082344, 51533.9407291287, 58157.32221061822, 3119.3473955647423, 85906.71036641105, 16634.718537293567, 62911.4410055994, 68948.64891695279, 20145.458199741373, 97314.65159830998, 84464.39684501194, 4439.833017029638, 26112.218059010618, 61381.17255025705, 87822.89717411035, 86799.77146074896, 2378.035985315152, 48323.19950235272, 77759.0788971082, 86860.38238732031, 8885.021084444345, 909.325027781016, 75742.01013820084, 92907.68003167427, 46541.370826686856, 50208.69651187026, 33411.27809495314, 39079.09000017755, 7494.324504489069, 28615.12718344683, 8128.39151127831, 35056.92986127589, 40680.7456024146, 2502.304174121728, 26491.413555381037, 62800.30848342487, 87196.41823359547, 69827.17035034084, 47330.29549311542, 17043.71445208023]} +{"id": 1973, "vector": [62130.45812060618, 82545.1841038795, 37975.8766058874, 19809.216945945107, 3341.618882331454, 50947.32700105751, 16771.552442541128, 18653.236486915026, 75051.4150748477, 97920.43791078548, 4910.876498519623, 64163.737962405554, 87572.71853797876, 77017.70549948703, 57162.05422813547, 49177.16266892639, 85111.1091744662, 14404.452195326756, 83903.05856210971, 29132.62503930781, 21995.183648562743, 68372.15571221069, 74719.17142879045, 67305.48771271136, 82594.73514884136, 79297.63295745374, 72867.64874539495, 6653.794195108265, 6246.430548880444, 90467.70528600166, 37151.89126903384, 2365.645475089351, 39539.611056039845, 73243.1132707739, 7374.304982253288, 83518.27292045561, 29711.75646278327, 23780.544504969035, 2372.2401552195406, 25039.68284709319, 7313.081941449895, 29498.008785417274, 24440.13447270319, 27055.97732138958, 24036.23269462193, 5251.984151661959, 60061.0303057318, 12894.304417698888, 91404.30790535294, 50854.03316472386, 84859.27102813195, 91543.97313348624, 53300.121487333854, 67243.90952422566, 8650.768195879065, 69867.0639395213, 11472.913116793105, 11519.324539535026, 9732.47590386065, 37792.33680558236, 13656.050596497493, 80997.22556829096, 52765.64992729784, 24328.774164803544, 18718.196281369383, 52699.28591321165, 92999.28955204654, 17586.816123874494, 3817.0827331699074, 51496.653370720676, 39837.9551443923, 9526.3679291264, 19808.171526132202, 34941.253401227026, 44969.573753277116, 36963.54552741788, 26398.028830147035, 18628.722625767135, 6043.411958066269, 80122.29786029925, 8528.995306380983, 84417.67534758218, 37034.79589318419, 78396.49898623457, 58481.60217327246, 44709.6626815862, 1532.9304185583092, 67810.06124287283, 50256.622576469555, 99303.53841989396, 72660.35032827484, 16880.609046828788, 33165.40994997756, 41879.65228532745, 33120.382633156994, 66411.06850058795, 64721.06812435483, 1258.2792648590635, 22517.326605138744, 95444.14001547697, 82722.64885062508, 39756.2845096053, 21668.269690148445, 87423.11121368044, 97054.22242323187, 91617.20564533802, 27144.207429000046, 83807.35473724417, 34978.921127797934, 57943.81077866587, 91805.43259508688, 17197.16025588216, 8185.077837454069, 56562.65671996273, 77335.11231055064, 64115.72049138543, 5801.012039207465, 79629.99976891832, 21135.048078993914, 65011.69449215211, 88815.91832213622, 76773.4883216553, 14744.422646985555, 48224.034087686596, 48143.073213947595, 9164.846101355895, 77669.95297660548, 65901.53282524429]} +{"id": 1367, "vector": [71651.67296177922, 29531.224148913614, 11175.54898129205, 87623.80207883293, 18392.12187678385, 73551.81730563902, 1697.5448718923426, 62731.43511492889, 79784.72545466281, 7642.279041925038, 80071.41791947043, 2166.92717005087, 10040.168056845954, 86527.32431783767, 76346.0624477045, 18011.530363847673, 26120.71418752766, 75218.3253868654, 7771.3974886489905, 29267.64068410871, 22985.461604197066, 79848.06380274246, 95778.06172487185, 37021.18877727608, 61355.50387922784, 59679.11364574529, 92823.75092991552, 83131.09567663167, 72978.83882339438, 13245.636107710945, 65615.78630527937, 9739.497299266153, 14655.445775053111, 59339.973143395575, 28447.46824826191, 23779.80573694073, 58125.521009040924, 56890.91572578805, 53059.91062968478, 79081.89537732568, 39662.37075300697, 3946.377945166446, 21003.436913765407, 26963.76724777908, 65531.69173705463, 11916.545674580226, 25277.881240149523, 43679.48108124452, 36656.34871892907, 90125.57057056602, 6861.996995873532, 75986.32312927597, 74021.52707107412, 58905.19141584825, 2654.2569377651757, 29961.229573855908, 422.8869637330934, 63232.13906170594, 99539.9952290467, 21946.92840485989, 19397.063334097264, 88893.51146375449, 98586.860544195, 95272.99105623456, 35836.66098407902, 36276.22829640712, 34126.470836270026, 17806.6820704978, 48040.62264970994, 58714.145587002684, 77193.53681770265, 32235.591256640462, 95544.57977331009, 53.517296934813, 81151.83758324586, 51468.779020129055, 96295.65016475439, 16147.474391943317, 75730.69001144898, 20586.820544989336, 10346.47039908465, 25039.836133786608, 81266.76230984453, 8519.342450936983, 4202.407398357833, 6414.954553874141, 63153.83196304892, 6412.854030835524, 38420.691723619784, 50873.29394717466, 14424.354433766273, 74905.70725193649, 99419.25597065844, 95999.53339577296, 70771.18994674351, 34369.425674230115, 39390.307815439795, 46961.914011024375, 91960.6085291667, 24953.27283922216, 5193.57623212815, 80393.530483365, 41375.33704209301, 12956.489670869509, 37095.99950257727, 87566.91664404764, 21258.691682798704, 28422.034741254676, 39536.94100631184, 28887.848543619843, 22694.134927455765, 64311.60654742328, 12297.04773111948, 62529.02892825383, 14778.05503290356, 76895.6336785828, 69919.02005146364, 73010.86638664047, 49288.3984403933, 23093.837295066645, 33641.583730276725, 15020.26546352776, 84872.6203822837, 27921.268745767957, 84687.36132881307, 11463.47124748367, 50604.35899189616, 56108.820273351455]} +{"id": 612, "vector": [12010.155679664647, 31862.356534738647, 19562.284389396857, 62240.1234168372, 63639.85620799511, 85748.26876989473, 69923.88992149812, 59668.20633903173, 43216.89039564793, 65683.6789552878, 54723.11158845047, 15472.476897860888, 35652.65344476607, 78667.73852852682, 99079.61516252656, 80176.0535994968, 95353.05954940767, 63302.996550582146, 15816.872831931638, 44896.92246472118, 96802.53443731762, 78287.31666943697, 7672.057518443531, 99433.64139837353, 53169.78313624689, 75016.29419774236, 24026.37283787945, 41378.10847435246, 20126.410763708813, 14627.599097301358, 19619.91177634973, 23415.64422056819, 35767.549640424855, 70716.11608646745, 19399.373193210133, 38512.949406375796, 16470.40133668599, 39415.142170670224, 86221.3690089283, 65114.17675540392, 32042.94181644297, 17281.845343137982, 1500.5457882283247, 6534.2611899804615, 14517.761763272296, 87272.20151263602, 16460.53565508372, 44702.96492285841, 49756.53072218099, 49553.72467676381, 84149.83288481808, 1369.6418222446382, 28484.044218879844, 419.00748122211115, 74098.2873887813, 95048.72929766825, 95587.5969146287, 10042.025146288457, 12141.290208393852, 22011.816301240895, 98221.26084755518, 37404.06834301998, 34402.38782526161, 63633.61115776492, 11138.950132146298, 72980.84286646606, 89460.37097660247, 60388.99671252599, 81576.39475940148, 79588.99034758096, 28123.94799778004, 38541.67735596208, 15240.674614356352, 70385.4525443714, 92717.09046179456, 43218.4640906736, 73151.424884812, 18964.720338621788, 46623.56947295544, 46913.14777630462, 20950.266611397717, 97365.06269469432, 49119.29748666309, 90325.91753959106, 62906.848957030816, 44766.91440509507, 64041.43420769929, 81500.2994771805, 27949.229980480006, 95085.17094657078, 56821.949151087036, 88751.75315038407, 58975.61526883269, 20729.941572815947, 31798.642758287355, 30995.101011386094, 8349.80578468666, 17085.0838025668, 76463.27305222389, 99126.92896824019, 96750.96146717145, 52228.057349860646, 99298.29568786571, 4353.541195221255, 79639.3024048937, 9765.071750887344, 75944.78425861939, 97780.95502252318, 1177.0433888268262, 16185.023596988978, 15491.781910059888, 82124.7391599446, 83688.0388929343, 58009.42572432218, 31234.934462032903, 98193.53002651218, 60251.613447573414, 52795.612136114614, 19145.863308171753, 27847.91327070999, 9661.36170980303, 58475.28813602922, 84643.84834907179, 3996.235248751534, 47890.80048958083, 20232.393509972924, 20390.826811620344, 48269.762564157136]} +{"id": 1972, "vector": [3546.2139554963246, 93395.55954892602, 16086.203161313539, 70883.67534987061, 38062.96364326968, 49672.92069744155, 76591.73286877642, 5089.923628998216, 4933.5086320901555, 48271.78142790122, 25667.968909236348, 4283.66643204543, 16247.67695611753, 42791.69207641018, 91500.4224529051, 89803.91581786927, 18062.9859729344, 13348.722761128829, 90494.28783071181, 33586.90984762818, 21729.109968244287, 29144.723441045706, 46887.47922933196, 69494.52815389146, 1703.5836045729557, 93750.06840153663, 14897.789033081677, 36091.05346787921, 40529.16884332882, 79505.87935727714, 33425.79703182845, 26296.608129416276, 76210.58599574566, 17811.46701344307, 70794.44253462185, 43301.22711471889, 53440.78002056132, 44130.07710538613, 64718.43921098027, 30116.055490437033, 90002.76614698513, 40833.43676000903, 93314.71334202433, 75367.02535794023, 10933.067964162512, 80301.2125053825, 14928.516691194294, 46864.676463985365, 88423.94370689131, 87451.31551286773, 10718.302621804054, 95846.83838384652, 49022.75210843125, 77461.83923622107, 841.386662213528, 14745.394114065113, 57737.969375410634, 83415.69655497199, 11053.482450147734, 48535.69819961261, 32746.119999865696, 30326.91529295187, 67249.44109780983, 41033.57865623286, 84315.20333039522, 34123.20113149624, 32923.30981443629, 17005.578653867327, 68488.77711317071, 58015.07349219957, 9338.319675437779, 96077.03539661733, 24267.29957022298, 6776.837449506934, 96287.15558164872, 70036.6260851642, 53585.604245750976, 47999.59062839287, 72564.06446367514, 24887.008070292093, 25858.73021898807, 65233.717676341796, 13968.547984136103, 86670.2944824957, 51743.75912144289, 43713.51294042819, 81497.01520056523, 15405.660307902113, 35677.55002504432, 6432.898131643805, 32435.278196856976, 75528.30299203034, 91913.48966173783, 29267.87345396362, 51156.23532538753, 32956.329721103284, 35869.73837838836, 99010.7736582725, 72006.5916463065, 98541.82360534617, 94957.29233619462, 14938.627124039072, 48092.06103431418, 33019.688171774505, 96908.63884725324, 35903.847488807114, 9700.768050015018, 68812.20029243107, 74224.92935211086, 68441.08212910313, 73675.0465577829, 30918.527485688875, 22031.942561895656, 16785.184752837944, 84349.44397881061, 48814.57117433045, 75721.35538673382, 61686.25694936708, 41926.96709704824, 20279.911778301506, 80713.87304463085, 75192.36763774569, 5172.886620911665, 61534.06406403659, 80502.52559293246, 77305.4339927702, 98959.6505981917, 10484.919748286504]} +{"id": 484, "vector": [49754.977818834144, 13081.620947773898, 79685.02500974246, 39905.44392451659, 53281.44853477008, 39048.760552365544, 38598.056281995196, 48008.39084371944, 825.2424899361399, 37439.567211528556, 56978.85156953285, 60870.08677523657, 33017.10440636348, 46002.99518841905, 24470.832331468027, 4465.171723230222, 73495.21647189835, 28573.768996702966, 83362.4004680179, 49330.889820297074, 16829.74447149955, 28441.0347309787, 62239.16784096634, 28642.10675091279, 10691.383348057216, 60761.23716660687, 81168.21911252513, 83249.03077087167, 67110.45907337019, 78346.64822765112, 89891.73887392445, 32110.17000149775, 76910.07501327686, 36697.65912407855, 61743.962122812925, 92196.64864565252, 73019.18214402322, 87237.21288477205, 70349.89255576166, 89010.26400711376, 87021.66602787198, 26844.291113536754, 62021.398513865286, 43085.11138974166, 98751.8583785268, 76011.17233673754, 17431.694835288457, 53895.09778259395, 45855.86667059942, 57178.17659131268, 16171.860918111324, 69390.1020130885, 75412.54276823551, 94845.37335447311, 83706.37462972564, 45051.72181690509, 9218.962893290849, 71557.5592216605, 40638.86016622006, 35859.511674592235, 60103.00095960884, 35522.90226155469, 81852.88133457495, 79399.0840061249, 76924.26156728469, 34319.65007338202, 9337.99059258451, 18625.896061415137, 95370.82719291425, 66920.62311785345, 46429.667820246315, 5192.707580045575, 46939.91265766788, 14132.635322156728, 66701.58890306916, 2584.7270951658174, 56478.05865063463, 50175.58510131623, 47727.98439605546, 5630.184362143853, 10136.431838422055, 72584.07883610835, 40419.195776025306, 5490.186127978313, 54528.334558625866, 59115.47214288692, 71925.8027867407, 75595.42556559296, 75629.27298667394, 59055.352748895275, 66974.55031919354, 45265.29368101099, 4835.644731493582, 73679.67522520776, 6764.387749766088, 75664.92857273246, 98659.68599172453, 39484.44991039179, 91085.1119759872, 32963.71438228607, 41140.16668384445, 34379.60560183862, 96658.62282531627, 76536.15789012318, 78777.1498793094, 50435.125096946, 72343.00554128595, 5964.106219435039, 63519.62939609041, 90088.5550257987, 99283.67872565288, 57152.32773637847, 57599.973175251354, 20008.467224709315, 61329.26057555822, 85870.05606290232, 26187.69834360959, 35221.753180524596, 34243.3555648706, 7328.083912951544, 84879.59777261467, 15356.778844064012, 71234.74927364079, 53748.94348782538, 34996.9924030082, 75104.78606539743, 33850.90907487257, 13012.577597604159]} +{"id": 372, "vector": [91433.26599670094, 13211.117819244178, 1691.307802386488, 60832.110684408835, 25720.472393540007, 58643.1955347543, 54843.913045242196, 82026.60445503809, 13218.725682346721, 57887.33199954761, 28595.28013443553, 25977.218384873537, 54145.948111015976, 10075.19493132557, 19965.64863692204, 22235.09380918923, 99765.467068348, 80099.94509335345, 93623.59772743027, 15929.51949221375, 2709.700722267172, 91349.91790520135, 1642.2349406148396, 16593.874125441045, 29049.88985322989, 28101.5850416655, 20533.86813362411, 30541.272549873265, 43638.72037033095, 27083.093683212024, 49350.450703439805, 24111.65614759705, 34919.66948709909, 82741.33774458837, 45963.69831872779, 84729.73203860893, 99605.43455044723, 96299.30748971157, 45906.876984410315, 58602.48454722256, 29326.890423998753, 39800.85525937624, 94784.08478475943, 98206.50279281601, 37924.20953931319, 73939.6912726458, 52336.495105702255, 85136.50809038975, 66902.39561532687, 12759.622536982351, 91810.45741577528, 3526.942295107072, 8785.713136009432, 16358.800981100452, 59927.318065992484, 43863.29444212909, 54444.393631107334, 55670.75839342994, 35712.52725962393, 18739.148092223677, 56704.36689209966, 82769.09208081414, 47480.000393442664, 28814.158413006717, 78198.2180379962, 71820.47241066214, 52778.710286366535, 74257.00427710795, 99773.49593768174, 28311.148018678145, 31888.436208058323, 49190.159733516935, 46951.09729054946, 94511.59306382638, 52323.31905684805, 24735.073674337138, 78453.87940338202, 43054.585187988705, 67355.01515660217, 83696.32178245451, 95666.03181223883, 49473.783942044436, 79426.50762252248, 80342.04522744345, 67830.79978963574, 46933.07935518538, 37335.569286530204, 73472.8362329794, 37699.14047685988, 640.9372695531767, 19975.358802565468, 82414.78044638677, 39736.168528837734, 42937.84183215237, 94388.08203742548, 97843.04118794373, 52499.895799729566, 29382.884217456816, 99376.18922575653, 81459.39421401553, 31206.112035410504, 80572.38631250254, 64286.45959766002, 21654.492001942926, 32955.14184463993, 27027.392628698864, 88438.60850072389, 38640.69035794757, 94743.0740148994, 44472.47944302044, 20936.07266904155, 50234.38171502573, 38428.829379367235, 51401.29717411946, 8405.373533142623, 21682.567777453853, 64760.008148608875, 66203.027980002, 80794.74868064167, 27345.1390361765, 94174.1510667108, 60688.013855405945, 91099.26327105955, 7482.272805850088, 22630.51118346132, 39.736701485437735, 83267.30723284112, 24563.85299906716]} +{"id": 1868, "vector": [49651.6337069608, 94848.93827409954, 79107.87027438032, 50475.17748750759, 96409.77851810353, 56206.16261586911, 3329.619607452361, 20540.00942866453, 77898.11630551526, 18750.95004896161, 61628.72036628637, 92218.90113789089, 94891.57526176363, 88095.3260539967, 10650.499324973962, 21956.354834527792, 44870.81771132458, 4257.374309966655, 37871.275274192674, 36616.1901744127, 89132.37551255137, 83774.61070430765, 62805.101891160586, 51650.402304905365, 1721.9463141069923, 75424.52593742408, 17547.952832347746, 27116.415514593005, 37232.89268949178, 1154.409454411176, 89370.27479154587, 92325.76284147984, 37172.155541488115, 13773.173687143537, 73099.00704808338, 20993.506253700034, 84632.4966656301, 57575.392089210545, 26840.784312388267, 89122.3498346747, 50537.00182508156, 64062.75470861135, 48188.78900662883, 46056.80837970162, 65121.89075968002, 92182.01118002363, 89466.53837335762, 53682.46693392886, 34767.957437099714, 97671.83519116216, 68912.9200859563, 7021.185953174847, 78852.24874955529, 89774.82072667583, 99964.26360915111, 50873.13511650931, 35459.56184734358, 51982.08660385608, 10285.780035982727, 49940.65952741201, 14695.719257873585, 10921.018517317338, 59864.82402604796, 35325.457700897525, 97930.36705675775, 68276.31818131394, 7469.041356472428, 84693.31387082447, 60303.787294123314, 15529.218479147023, 16144.48160841654, 44569.94653934155, 33874.525529916464, 9651.686733153087, 42205.47918529749, 20853.208252881283, 99560.38900272358, 14482.730835403112, 45982.29450104025, 8113.075378247103, 21429.261755345462, 27930.743573635784, 2719.794253297414, 14292.821673462031, 72181.53502724672, 4734.400853343168, 20193.08997678302, 69304.13079781146, 10584.302503291232, 86623.43631075225, 84243.40177273497, 12482.304038705639, 67494.93463916543, 47339.512163144784, 93131.48903800405, 40053.72555120134, 68173.81949097865, 64429.9054992789, 55260.18237521888, 64246.348275799704, 67945.75180158082, 26464.920153602245, 61284.31682480732, 144.42147025579555, 35939.635277521054, 88115.4904949085, 91908.99969068718, 23995.861244354488, 44655.7140473625, 91629.41481721547, 82004.19385811353, 93594.98340484386, 60255.93148789678, 99163.88345171468, 24808.18759466006, 96998.30829742532, 21803.288722636404, 5512.696101452741, 66670.47027580891, 94083.47503056629, 80482.15063422263, 9379.582336135329, 2324.659602022727, 56036.889141100735, 59303.13734435795, 4380.2631438196295, 15511.798220071827, 11674.49508243792]} +{"id": 1241, "vector": [89712.18433041449, 9434.791192334013, 27729.312894098133, 92904.62190695544, 69946.70110380457, 27249.084172958792, 7299.717412693718, 44436.40864818856, 16689.91292998061, 55665.7328409289, 59352.60468728157, 81861.26333930872, 20437.121565461402, 9774.530449387665, 72211.76220003719, 29794.55434493079, 5360.4178684582785, 14617.826273024748, 85073.3952083606, 62610.47561465444, 90923.76252818183, 8474.74091318774, 94099.8337846553, 77777.57624627612, 96263.4216509917, 7161.040449152912, 92524.51773094949, 5074.439575102707, 74628.23338368317, 68952.99295875114, 17639.201011982088, 83933.33085959528, 45502.22737338281, 92725.1242010722, 73702.60452330679, 24625.515449641323, 63467.73835122421, 82785.47121957631, 42871.44343847012, 11556.102720893257, 38542.420011118484, 47594.19939030192, 79342.72000437502, 93579.0139318998, 84314.73305296226, 19752.304590553882, 69753.51117894969, 71338.02043281253, 13213.308210116515, 44595.76724703417, 89226.59641374262, 45031.20696447965, 99331.73029675386, 97682.91823167284, 88687.5420810164, 68582.97839826772, 48080.03975052657, 16234.187009255174, 58345.17680352544, 96527.19069399036, 16215.780260707746, 5517.75954218815, 91882.0063253081, 57186.36960402297, 7781.201885963163, 54070.40554535404, 16165.056018275514, 98992.01266144894, 57031.15552120166, 52833.91173771978, 13237.872222461645, 54560.04870300808, 74606.66542108027, 6932.838594764301, 6718.766611960758, 78716.00103492718, 408.24960996974636, 11067.850674627356, 13341.868526479117, 90109.42141492595, 39513.194321742005, 83492.97810739334, 94201.14076907506, 10410.407040292746, 65475.649320695906, 91125.78177547503, 82038.14201284992, 49984.59146798669, 93541.5535455851, 74461.59331400732, 5677.436329839092, 32456.384216385715, 40505.736007970016, 85169.83754028229, 27155.856594647932, 5138.187016177442, 3775.0431040613375, 51672.83741798836, 45180.738267618326, 46969.897623655364, 7218.270641926328, 94453.40366296622, 94710.77642526818, 8220.302212134611, 90164.31198716455, 76454.00694797507, 23536.280988431135, 13405.030969763155, 27630.497924037467, 44546.952918377225, 32805.14878259521, 43801.18165912437, 15218.69351302112, 89704.54371726561, 76757.28679185714, 46115.4812068645, 19933.941460268423, 65173.70667360602, 29636.960670772238, 20220.045096238893, 48480.45323072051, 27219.306008203203, 28530.4946322403, 14346.992729248275, 38299.16600027847, 70616.36618149349, 58733.10781286467, 11926.750877316961]} +{"id": 462, "vector": [88436.47232906352, 94218.36935192939, 60146.623553245096, 48970.989716866774, 1954.8964325369234, 36371.41614261266, 25201.239400837607, 62976.20993011509, 27275.411941329476, 45469.60972848344, 50781.38595332301, 50949.017219169735, 32828.43284893731, 1279.3438282942548, 72836.74180598602, 79368.63350652994, 55177.7468743218, 22369.518187060832, 85351.47277585004, 76009.88641068769, 97063.71497384309, 19774.252074809396, 61631.664487465845, 12263.847665437277, 97295.57722323376, 85230.54513146346, 79496.55614263582, 29259.277079497213, 44853.2796835363, 92538.43538316143, 63508.55755134733, 53998.9142690835, 78854.12762632033, 17108.807813975767, 53794.605261945486, 188.3606875487831, 16661.833632107893, 54217.66767151357, 4019.6568741828864, 41267.7953509447, 54095.2179481189, 12242.350848155958, 73823.15336801611, 174.41027200761906, 68199.13534151088, 504.9250273493011, 1659.6774393932922, 89466.50169135362, 85062.73004660232, 37777.39123273957, 39399.14462015299, 47791.00443188567, 46297.86456240832, 79989.86793497819, 68498.03822966886, 94690.46170080999, 91697.81306303608, 53758.12405872976, 2422.3504516896146, 64212.9252781411, 25488.64775254972, 65922.08382453457, 64675.47551792254, 19961.47041251197, 76598.94196492658, 84643.79065724314, 70431.00543612492, 64577.890082985425, 97689.10017649955, 24100.545367075843, 46198.42393393653, 95608.59489561239, 7543.921367393169, 77569.43150370075, 82173.65684360728, 8420.13179837845, 58930.79152153817, 20046.806893545956, 14545.732746173146, 71271.14514531243, 45876.416131874656, 60325.40880528565, 62019.546206307605, 42956.4389898781, 78627.92313146891, 82427.65280079067, 99157.15126843652, 67524.556853865, 23811.1534624315, 12725.60251646816, 9790.519171123102, 6896.540468757439, 23964.126544933395, 50104.35613143576, 83513.95447844893, 66350.73281432144, 35463.567385923045, 78435.11176320558, 20019.74455616279, 31050.959760805687, 55783.77693905924, 9467.289231452192, 34258.13227704514, 82844.87835176814, 13347.478193004714, 88729.9751908716, 4588.1824800323875, 6838.187571723264, 62230.587919554324, 45030.09184473653, 99182.18914119042, 48758.130329073814, 65157.9978126928, 93044.12639658577, 22691.5744392845, 20663.69237636173, 8735.693935139088, 8301.879849361094, 31375.08454645026, 24526.677138162868, 54106.805809339385, 4533.551958902726, 85601.7353104183, 44436.07097282875, 18898.343757915416, 45838.13666203479, 84332.9187402544, 75067.4907722543]} +{"id": 222, "vector": [30988.25391317168, 90169.79692953483, 37500.20846085623, 59328.088279780444, 87410.11710983007, 32906.09636869351, 3868.3860206932595, 70213.49242446755, 84037.13261121261, 29262.77683852252, 26142.015138440056, 61917.40394807197, 38330.47044156127, 78073.24829559769, 83640.07201369118, 5588.5881980983095, 42746.08434059613, 97426.78918797914, 35799.476908377284, 42522.96376640661, 37990.79409203022, 24280.294101040854, 17921.970449340406, 93784.94349663124, 81554.58558449501, 98960.14886621936, 52227.14955313838, 89643.94681759013, 37356.61286731011, 31544.3344856266, 57611.76918159583, 31933.887979909814, 45828.37651709617, 7176.786209363783, 50146.370352402024, 15811.765315789216, 36076.426616929166, 3279.8782306124763, 83167.09457321136, 23260.392810903508, 11493.143535222995, 91992.53804997398, 89819.90092056303, 36119.62510573516, 56123.717223924454, 90366.83040495073, 32750.49686187089, 2009.0048363578082, 88886.22400543862, 346.8750014990918, 36194.76326799482, 29549.789857697593, 7276.721066040836, 3284.955752308094, 90164.92404692985, 12288.050120312211, 80203.28162971538, 39483.41994784083, 4581.591853912748, 13143.32100288239, 83267.8976459316, 53156.88306384677, 20396.32485199342, 94459.76254080934, 76373.32506571167, 71379.28465749322, 55149.756496354305, 36831.65164291353, 50795.73897095072, 28381.273660631745, 64804.10461890189, 57994.82480116056, 74115.77564586823, 46443.27283211388, 59720.578688748246, 43780.08086080466, 89513.19100552071, 94089.68092900021, 90551.78258210333, 61109.5234449013, 11478.323884720732, 73132.2117099543, 38191.68046726237, 79150.21873510565, 74460.72850468704, 54956.249760697974, 96067.28766766922, 97358.83896774879, 84431.7949943242, 90969.66758107342, 73911.10286370946, 60353.25316189172, 76783.7822664324, 95466.52960659919, 19315.587379401644, 95170.46861389857, 48291.73293256917, 27578.40520817534, 23983.247093537208, 40715.584661782246, 90605.65154898567, 66046.97851613245, 59810.46187893756, 87439.57667162704, 62413.22850444181, 38669.029527634804, 98052.06494162371, 60362.072530173275, 76175.99948926117, 80656.00680176911, 7402.750381349721, 36537.23670990581, 21855.828337089257, 36955.6261290938, 16966.378133606853, 96570.20718604334, 2526.5080382182446, 17463.916174575865, 54075.82296441399, 83536.78879477637, 5384.092279384223, 91701.83785811791, 41226.83041830366, 53558.26130031923, 60790.775633533645, 62701.72716087091, 79084.08815238335, 72985.00502907294]} +{"id": 760, "vector": [97533.28591533657, 97689.74196222353, 56678.53635854424, 22339.03466475642, 72837.45433130227, 3996.1152333553905, 86361.14095594326, 28328.94261887354, 69473.15077977764, 18142.43587679595, 14917.25494125612, 74919.27499099149, 98379.68969400827, 94951.96183969035, 56792.87519315597, 37190.18991089908, 6450.465160584373, 24880.61462989153, 94015.53279138131, 17477.588556043043, 84004.64098891224, 69497.80980901452, 28094.81836845209, 36160.21349939978, 36063.678677929514, 40842.985338292456, 21714.54800415239, 34885.24348787312, 36446.16371052077, 50667.548905657466, 78951.00494175316, 48245.90989691984, 64567.52711473519, 93432.64388440127, 878.8299229355978, 28102.74426946362, 41807.048870963845, 32767.478424212302, 71260.2524755628, 93647.71693869396, 38354.24575809528, 82284.9416281857, 98731.51006858135, 49057.50967863733, 36238.68998583162, 63428.13819175197, 94594.76868634432, 67736.08017212259, 62232.76919974898, 87267.34782272896, 15190.367103239589, 15547.48053983619, 82558.77140954712, 38355.02863807264, 44220.602133959255, 29575.377569373275, 19185.599048883574, 52349.57836020631, 90020.72761521068, 7539.435177578035, 91159.27572152461, 12687.943371140387, 1271.0511829304162, 4655.223478866877, 81864.04155445095, 47203.335083505226, 32002.049689298794, 9660.739882579704, 42519.77795794527, 37153.86797829314, 67346.40478981809, 19735.555104717172, 75845.50216292785, 86221.86852953667, 18982.83873696314, 80540.5287376303, 89197.13020238244, 26308.813478721637, 82318.98609871272, 61139.4896071355, 41237.18189166842, 43257.06141110225, 4588.992309771756, 98481.68272023596, 67555.01877092446, 85160.75466648591, 39838.99023622844, 15821.127846263716, 15333.559599070744, 96694.33725397267, 53087.77700665897, 51866.31111799145, 6393.993622174443, 10786.26738070103, 35391.96233737751, 89347.74918137491, 97322.96372878204, 99555.59978440162, 35507.108997097515, 4595.853484940882, 84991.56904997802, 10134.130607810132, 14653.192019184069, 22393.4669086137, 89091.00007032823, 74687.08289802838, 10369.167276365255, 34968.91360665708, 75191.53579584403, 62619.15451064233, 52544.645456562175, 29207.05257230414, 986.3874178877907, 73551.73700525309, 16701.552863473422, 88742.7784254377, 32849.697165491765, 15123.393552693731, 12209.329160355053, 45967.08543485063, 69746.51576525447, 91510.8756115592, 96751.93904955193, 94218.92515216142, 50046.725664535865, 71879.09226886912, 50804.61019871566, 1634.8788827225792]} +{"id": 907, "vector": [32831.84805891142, 96636.89221237434, 39079.39951927555, 1367.9150115402329, 6055.256062313974, 38290.3043345458, 83596.35596137012, 55071.096999698886, 97238.07072860502, 68666.3714689061, 19278.185320577544, 97874.36309196662, 8040.078430369768, 99357.49914745598, 22921.975634521485, 54987.201404416155, 89797.59459724382, 93635.171953735, 67650.85817112964, 23670.05517185634, 83657.09782768214, 68378.65108172096, 28151.069150269224, 29342.20166186976, 22459.03806282733, 69257.30984155825, 60614.1541567787, 83246.30775573538, 39999.79300041364, 65697.56962666934, 99705.79477440674, 74163.06692485556, 40870.77735348499, 18858.839418762174, 8627.87769548775, 72850.72369680903, 66006.88718114905, 36414.673087644755, 19713.53461910752, 49594.83152563084, 11089.127218355045, 19591.280994436846, 82144.89954528256, 93119.68959703959, 93918.47207891775, 12933.030170578008, 32466.899052679353, 64479.31886079889, 49755.19410640812, 79936.92491398536, 35870.65083743677, 72421.20964717773, 670.6286944974215, 67286.12686549874, 11688.64937022216, 76627.48094492598, 15596.059767483905, 19331.71087543962, 17100.102704898047, 84052.42321568487, 98524.77046813567, 85899.36337852027, 47572.38281934404, 65148.76850024831, 3882.594487753099, 8531.442371452735, 6298.332310615929, 36167.91425125848, 41819.31507231144, 12814.028632395535, 41848.81320919195, 41411.30991090813, 65964.4518778955, 49935.820963984836, 30497.760541816264, 21003.765478963265, 72838.64761264887, 91433.43388737728, 87395.58652050044, 32511.96448095883, 21924.588390873458, 4249.474712440649, 27737.82046012947, 315.902764750553, 15766.394721991317, 49908.142229583194, 58041.68493279813, 238.05225151932729, 11143.868449877715, 99459.65578409102, 73818.38419557891, 499.25425744972165, 15046.704365659169, 41165.49169494446, 70927.67260060005, 10582.725003094583, 36438.03132731579, 63750.14536853253, 61577.32084177048, 50507.93400265779, 99559.17640998567, 95367.90332474007, 71628.64035148134, 39265.88472339717, 77314.83166488528, 10046.02671244358, 39255.77829360808, 29791.157593676566, 84035.53910881972, 21831.97585423189, 30780.168482151494, 49861.156820640754, 10314.608075702125, 51857.18795829316, 93826.25427558065, 34593.41867795841, 81469.42261725693, 94549.44544162438, 40426.11286692511, 85164.18603689922, 99306.5923254279, 69777.80125360757, 22250.482420624918, 2236.1982063180185, 14675.825873649928, 61462.02169478116, 87465.33901204144, 79205.6217706251]} +{"id": 1261, "vector": [49835.504729338274, 687.2054968888098, 5355.37634465959, 66162.17120798587, 31151.031162980104, 1417.0547864198802, 7014.29322725241, 40491.78287310583, 52199.607358771624, 20059.77357313523, 58178.475903937, 28627.9304476624, 27528.28159395664, 8302.088018175058, 50198.07888022316, 96097.57604140809, 23216.552458188766, 38656.18325859522, 59299.410628892394, 55441.07852641602, 16853.166447517055, 38855.20074797468, 88536.21970758637, 11617.450805606255, 37755.23242618333, 41801.7643210152, 34532.086444386136, 64305.7177386491, 3565.2492342170826, 18753.708759996145, 41464.008134695396, 6542.693224803664, 29087.36099394764, 24920.081044743027, 6305.307266989568, 83753.82153227789, 28388.508748592645, 67545.30893782341, 3770.3402591232616, 17943.151278655056, 17727.8546313259, 71133.71958209682, 50710.33174335063, 88711.6785454553, 72098.66180641654, 13480.972355578846, 20296.941798522537, 36984.059488761035, 88900.31857708446, 118.81932782207284, 49169.56400828316, 84662.47288724637, 20557.667159471748, 79499.42607078872, 65806.93641657212, 6584.089092582257, 8669.983263145043, 76607.20200213947, 21227.356299040057, 87752.9500561298, 39659.35920617369, 91068.46888271632, 93519.24155081974, 1871.973163580498, 17507.354524355855, 13259.246338780562, 79302.76813159634, 56791.75119323394, 23941.296270498446, 41129.10772910244, 68804.10107270215, 70883.20344856012, 97630.12083581608, 2591.1593359263074, 93530.83768632583, 79567.4653268231, 7485.192438224053, 4906.709368828766, 84476.61988194693, 65128.89095107251, 39667.87549734052, 32160.4916934207, 39893.93478097228, 27841.295614642502, 32867.14543525453, 36033.85220129976, 6997.422210900994, 88097.44876148143, 12917.966261385294, 20661.77459318592, 68472.81255600361, 77893.29188983908, 7149.862389441142, 64865.56907731979, 69385.1345381679, 83123.68443064191, 49492.97501314124, 39720.26389486503, 65267.20209951121, 35766.38371189774, 49774.727507341624, 9860.15641721163, 28143.928126707186, 27638.021569916247, 45416.62391825648, 9996.74325019555, 776.0286656024018, 12139.404412442313, 54431.7297363432, 72611.37387823076, 86347.46894788744, 156.93790473475522, 61726.81070669507, 22916.799777274744, 66581.73432952503, 11745.27402319624, 208.8271769235428, 34735.348647687635, 23000.3336645475, 64556.60554194415, 5173.337032674197, 8752.460768020608, 28753.739924529385, 7181.2081276980025, 5744.573645865636, 44499.92971678075, 288.7454558296088, 14664.130613667281]} +{"id": 449, "vector": [8305.609211884246, 54717.318042742794, 45619.75837660015, 9990.22663085567, 70457.00148603332, 78054.56975752677, 17765.19596353089, 68172.72849428015, 88880.13350812961, 51592.07229017564, 36514.48157262346, 95853.70947155151, 87880.81180077005, 57641.666899528675, 41036.427556617404, 63883.69883981043, 59863.191948068205, 11202.820720945294, 70206.32952752634, 54124.44949141646, 31336.262851813102, 41941.43316382504, 98527.89904028464, 92541.92062193509, 75431.31081434734, 19044.745221901383, 50204.4843020801, 45241.46732322609, 21755.338342257357, 32008.064015653392, 30673.60279966247, 75539.8296320953, 7672.235042691422, 64942.97941354489, 49772.427539169104, 89145.91972016077, 89055.31189944093, 39130.15234240822, 17007.115982767453, 79432.81228012938, 59298.113835260134, 40888.08246521695, 48024.94678454827, 39370.48331480322, 13463.346191795556, 43792.64820408783, 17594.781895023236, 4628.396498357745, 67321.49826458731, 2422.510310352943, 31838.867913176062, 84039.29148611194, 11951.544964204119, 91415.84331270216, 43725.38082774264, 35523.25327203208, 38171.60151550578, 75669.38977614304, 63459.694211679605, 55638.02968046684, 86825.7518338873, 22336.243534339304, 27472.276649696923, 11984.975645623708, 12768.637360400791, 75956.23324825041, 7060.005318173668, 37723.805451624336, 35410.36009521792, 79870.81370823343, 52771.78980521796, 51113.10951519381, 27965.28678756891, 23066.69433095817, 4685.849050877944, 14596.539357660266, 31791.696075680764, 62641.10672894453, 4676.089065930289, 10380.928178164806, 22556.005076113404, 1670.859254601742, 78116.29558931796, 83533.81058321169, 98924.07906289355, 23748.799169488866, 26981.67447762001, 12469.36128421543, 51999.98085587777, 99916.34698902648, 12274.738228192151, 17505.907939225828, 22236.678091492333, 29215.39114706847, 99167.11954267764, 86119.43530417503, 59576.92968294235, 37717.40459441328, 17089.62221619311, 53885.78161337204, 21834.42998621945, 85389.17473167957, 95335.96731359982, 82784.05170430116, 2600.406027865798, 37543.25848207836, 30918.906915099153, 11391.646114634092, 99698.98846839397, 47438.32195132497, 92444.14103230972, 15457.048143719654, 47560.80754904204, 69134.58956837644, 93958.75429616607, 95096.23319808897, 90820.49596530775, 38983.23737709717, 4637.442206207665, 40417.27574107321, 52183.05718590828, 59804.629698529236, 62285.58799511122, 21337.236743447218, 60311.24885971345, 16847.544019062054, 1595.650797011383, 13834.515822593097]} +{"id": 144, "vector": [35809.135042283124, 46274.130286084255, 35924.768376693406, 17933.37183065672, 38739.88306997078, 54654.407129886895, 18987.180337263766, 61527.9549436821, 50300.21778417977, 84538.47223333042, 72362.93931804052, 93019.17791708166, 26687.84526207706, 94183.4939769703, 35657.03929888085, 65166.971866577784, 30317.281975054, 72818.48307918395, 25013.826105310232, 93932.68126547075, 25219.169562488176, 33070.922608345, 36971.64935845245, 3608.6955084779215, 97996.8827707817, 82066.57997302122, 60549.257157782624, 28672.627981410413, 74742.41350578192, 71662.39139809115, 81415.8288865073, 47479.23664024379, 9073.301088542119, 83438.74926644158, 49982.26395259965, 5709.776630012553, 13213.230690497669, 41501.099457098346, 44593.62370997691, 8780.480035930905, 89372.40715907273, 362.0019913146644, 54919.70538736067, 19996.763248760384, 51528.83055815347, 91663.27734101859, 20240.596550221835, 65398.214072322255, 98514.17639853856, 89967.9297728347, 96273.41932117946, 39516.70366719967, 25820.558732279762, 82853.38796790289, 8904.437299204126, 30964.98436178976, 89640.52724774968, 43443.32737571553, 77870.8491857638, 59402.88016040035, 5194.1758176361045, 43446.197557192936, 48467.21635752878, 84749.27931513602, 88752.31693155572, 2420.535088697884, 84993.54027783584, 51699.35844128245, 45028.83883769888, 51092.42470863359, 68701.10986414345, 37870.80663399148, 8737.210779717308, 22985.45758337649, 12345.58333901482, 58707.10969594631, 58052.67952423009, 29282.31057000289, 12309.813470518206, 71657.00810983761, 51986.64725334642, 58069.633816172995, 37422.2843749084, 51521.06718201084, 48669.370790279776, 95668.09684886875, 99150.99869275598, 52708.39106788455, 87046.85224203786, 12153.188618337996, 85379.77707977429, 50995.544181479694, 61269.885403054934, 17126.188744610616, 18456.810331866825, 97177.10279981527, 72164.53958553498, 30254.706871189064, 84923.92790840936, 5659.6701068728935, 17487.2585808152, 5503.482498447021, 89032.23825534263, 9876.905129237723, 66151.3742307533, 61297.08072222162, 95744.0889643421, 8696.18090526183, 15104.07721589111, 52844.17260388127, 83096.57029400696, 81134.87558648028, 48626.79242448694, 2339.2351020309898, 5821.074094816836, 5822.185624691157, 94864.94000274081, 70144.34675161062, 47677.81641647032, 37297.0191817774, 62740.645828626606, 91849.3876536309, 29226.509192001693, 63536.01462150803, 94224.49511347106, 28222.37509080223, 6624.2660174508865, 2575.4603514954842]} +{"id": 1205, "vector": [76626.21657042863, 96550.71713463234, 51940.71433187126, 74901.7954566461, 44375.10428913025, 20538.79865090673, 79404.95795420939, 4507.0792906581555, 22480.38989218796, 41592.59883858368, 75785.62968994207, 87197.94023148157, 50283.912448408606, 43731.540695900316, 32031.00406788254, 21652.077478451516, 28936.74829360695, 36140.99306875382, 72065.29480665253, 40522.50440712648, 79452.41249702474, 94998.55298683714, 4650.323011338942, 34355.464121130906, 9531.386178347124, 89505.14992487757, 86585.06605765226, 43313.18663508813, 68293.61163310599, 68113.06964262469, 91340.79559141478, 80.49874418213365, 47906.31986753165, 63492.68992266629, 46192.07186060443, 95256.44291590675, 58552.42104659406, 41632.984335963076, 83426.27467268181, 48039.3022514098, 90103.69687737543, 48082.37622023552, 78588.69102688461, 49735.04619247997, 72982.04293440086, 46049.16444212914, 66963.43998018168, 38391.885004098214, 54086.18389113466, 34794.90249645958, 50310.16452056135, 29453.145008199077, 47730.4299766566, 78759.49100935043, 13391.341990741434, 22446.068325694814, 23538.60783530857, 55107.4351277127, 78973.0365867004, 83903.69571606671, 15472.008846002216, 11884.642077541863, 58255.91559642639, 76920.9577748138, 36269.294895866886, 56986.5172989257, 65682.69499344943, 2398.0792414185826, 70174.00843095628, 63700.59061707627, 70339.85451613116, 56963.03934814632, 46979.0561984727, 33274.45516442118, 93742.51803155168, 57618.04516896699, 1451.4430173777182, 64792.53176730911, 48290.34828249955, 90889.11490292755, 56869.048060541994, 72722.03690108431, 39589.7049188202, 96614.53256602412, 18892.77795123181, 62470.033756314246, 98920.79729930981, 93305.54185605353, 92397.76108567776, 48869.07420888651, 38278.7119574098, 68515.52848749781, 58054.352054369905, 82875.10616897576, 70444.10003092859, 63325.545126232086, 18951.93058403537, 78739.01395802609, 6213.51968634194, 95828.49805673797, 79335.21080357002, 86757.58520335447, 7823.037963905, 31479.29075663638, 4382.756484471107, 22003.80254022136, 95513.50529058323, 22535.465749376937, 93819.21481062165, 13194.4813136337, 70357.65070668694, 20386.633382331755, 42755.64975542582, 38783.48992697295, 27226.845146181044, 94735.88318966223, 33203.28387867679, 3740.1438562103763, 44519.910414040045, 22130.364477136787, 31532.74579376646, 31273.153437360947, 54202.97637849182, 66104.08141254437, 56896.35114466084, 97012.8926201077, 75143.8154724218, 62577.23565548705]} +{"id": 715, "vector": [47508.90058709286, 44613.12143971522, 18918.1177256287, 31719.86212831286, 31246.124549175936, 54922.598458269254, 83168.4102970702, 39767.21334854413, 91651.70831962184, 23090.99288690566, 76406.27776153553, 42041.58844324164, 30870.995717922277, 1599.969585659944, 24265.892355757078, 26947.96782365583, 68499.31721571375, 92146.38590607395, 38130.50262248503, 17829.06990944879, 44906.49812417713, 72246.81056104331, 63501.067086507675, 31390.4894712324, 69840.34038947803, 16190.092539299894, 87090.94257483867, 86027.96516993653, 70539.10322025343, 55877.4147824813, 57027.88655293116, 19006.702028675572, 92126.09085366288, 29523.386491616566, 40320.268703042675, 79738.41509659105, 45118.28485392107, 58208.184355902435, 42696.96646298081, 58821.44790947314, 32171.031149814233, 70437.79791484331, 69283.84328384441, 22454.598668354563, 86955.6882846896, 54695.838452420576, 82164.64287901709, 76071.35849401986, 29827.8633052801, 10899.952519536837, 30373.89922668743, 18869.267103637245, 89639.97449954889, 29620.9107231891, 63614.39829869733, 23250.175702930654, 91714.67024613022, 89365.79066371823, 21414.64801687125, 78189.3234862405, 71462.0851130027, 3201.998613775725, 5851.372753442963, 4512.734746312619, 56302.45065924002, 86402.48871379625, 60016.48793341187, 80383.9568736477, 81539.00823626175, 69718.37205103881, 18291.49090471731, 11441.02607479318, 75379.23243466763, 93623.01434723851, 9671.754805323362, 34317.42291500709, 598.3231216828311, 69943.73549995793, 21097.469014652193, 22644.950919214247, 11084.08107961889, 33966.14557696271, 27642.23480021174, 38161.62477858037, 2701.5840829801573, 27852.728853043183, 84037.94024759886, 6918.407688987105, 20439.6410622229, 39655.0297336486, 80349.93140066204, 35613.557760756376, 93544.5315881534, 70134.8389778199, 59361.51599479983, 97427.04665203483, 48321.633550023325, 55609.567643971626, 71190.20622740699, 14813.786747472346, 22973.24667229025, 10833.116846680025, 94257.25443192867, 78952.23967172139, 45628.27142469949, 18205.883909053977, 64296.01399901544, 57622.395240463644, 51339.170725836106, 90453.37426545049, 40045.08707820822, 58013.17826690699, 80698.79920208917, 29206.76429488067, 18068.92256652043, 56146.76702693325, 19381.27487629622, 15349.506407836345, 60188.3010163442, 42197.37724720436, 85427.96071246202, 35617.17937495839, 52080.89842499105, 60360.29924847295, 38393.80476081902, 23442.823867871575, 73524.42237148229, 20943.904681661395]} +{"id": 843, "vector": [47722.81230370038, 59100.3866235697, 58180.311839778384, 71735.7480660164, 93567.81682865506, 24565.388697290968, 18319.04998727194, 58143.082351872865, 99125.11708681988, 19947.05058847437, 97957.92786957494, 84293.93810172097, 94609.29088257848, 56556.04779024813, 23131.44342701966, 19408.604960860066, 58877.33799180673, 98052.51401806322, 69492.03434420054, 19796.32575091348, 89204.79718275533, 15480.16134081751, 60034.29190536111, 3231.1025892818648, 21894.067363394977, 45423.527827025864, 9258.033360689376, 42164.12908260987, 19409.903316144373, 938.4754817945651, 39271.151319423494, 45526.55345008688, 44520.729747685225, 83722.2252507662, 61463.852517604675, 51793.94286952427, 33203.753016407645, 53937.23830563144, 24460.579107470905, 89122.08552607519, 9685.475704725399, 57915.94615567479, 54582.414307564766, 22738.618443708623, 21977.73957192508, 23015.256095837976, 77365.01676504084, 26763.2919055535, 23678.54133954438, 47487.568274330515, 42208.46014040013, 95849.91960532215, 38531.82129252412, 72897.22564178696, 35341.22427756864, 27833.76962567683, 7553.755098887927, 86590.86996145004, 62398.903779552886, 44611.971076440015, 52020.341279833396, 38474.26371620686, 4667.023421113358, 32283.87134240317, 52659.19452614766, 98808.34568907038, 36099.14375105358, 64036.6581297288, 1466.8936080788342, 55978.17491542974, 88935.22161162371, 24807.82895510103, 72468.21541116487, 71527.58370296162, 92237.79050984816, 85092.34445009367, 77027.59770216275, 57991.025788185216, 92022.73174088931, 89793.26936415145, 26294.269896533817, 52793.028312859744, 26935.144105233754, 88115.95290214822, 28985.650636281214, 56976.8820396376, 10532.819202768473, 87831.10913120037, 94993.70832676133, 62693.49011108703, 56421.4308580444, 52535.99604794802, 26342.950939449194, 15934.719680776632, 41342.925707130664, 17018.080326078765, 82511.35936495758, 75833.55233423247, 75756.87062786297, 19882.61628956317, 40613.76907068193, 54940.913013639605, 62156.61820505921, 47505.08221867917, 82783.55633515885, 37989.51820058789, 71386.93579781172, 29320.939998585825, 58428.332272366875, 87124.79486502858, 41978.761002042185, 41467.85706472152, 90979.03851248213, 12924.805427037667, 71460.92683568945, 15082.781382769117, 43226.263058334756, 20422.387585173517, 26244.054468936527, 68017.30424975809, 13052.472770545242, 74863.60126717892, 27560.260597225526, 97573.39701996934, 50410.28351589447, 42064.13872752871, 90130.92583978588, 17146.850885036103]} +{"id": 454, "vector": [84322.99132183549, 74303.93429858616, 26282.949081289044, 106.61831536759037, 43116.816080186996, 43743.2046980763, 86268.72785281355, 78838.59367887753, 47664.9581889589, 56214.94762905464, 21006.82882053084, 98365.18160442807, 6470.95861129473, 27862.491171050162, 33310.685041532415, 83661.80429097211, 55018.4642486713, 57923.55635089886, 26922.459378960295, 78117.5166469012, 28346.58551843048, 36418.81671315901, 64079.459071757025, 15367.493788159958, 36099.81450561195, 20514.03390834954, 43007.06280767701, 92480.03583606001, 52239.89196894401, 95723.51987111325, 65567.98449043313, 97557.47720870601, 86795.41517742761, 40009.28663150619, 35955.98394095658, 1955.0774334919586, 50578.28081882668, 29188.36888374039, 6281.507736634606, 2170.2509736289066, 41115.123446511185, 21417.606862025874, 99417.73694998217, 75710.00143451367, 848.2024480089057, 56280.55217190267, 16325.562112440772, 72093.03756261489, 1879.1374688471474, 53795.510454213945, 59838.21142205028, 43785.43989360707, 42878.45735496187, 59898.77921788974, 60648.21635981228, 33557.31857548372, 49423.86927991361, 11327.387256127553, 62437.492937988114, 4548.398599937032, 26600.286299053234, 95816.18076593222, 43120.503129237964, 31536.39123009696, 55270.058869449946, 2527.4196852049126, 94083.77133098271, 38698.7840254956, 11526.432599002945, 38365.91792047932, 95666.92776624745, 66040.03788911931, 30868.205365739508, 42278.784063335996, 25910.48587795478, 99478.6351483482, 92959.40818538223, 78213.24293928976, 72018.69946865288, 56760.75824261297, 44225.201323840316, 19324.282914629108, 14702.023647602358, 94951.37671084862, 69675.74396870482, 5347.708691455388, 200.6007066863824, 86889.39480393029, 75709.55106107079, 28757.71794904949, 22812.314128874943, 25812.650364383004, 66298.23514216552, 33456.21244385424, 53169.43790083694, 92043.19684384251, 27051.143516979027, 58250.2030241474, 7971.321338614778, 40436.21106294505, 77918.3279442999, 9424.176906307392, 33241.98071999359, 52817.787854149065, 83770.35288842111, 26509.6653093176, 73724.15173074529, 68576.07356543283, 61274.72146062516, 35793.91881158216, 61489.252280588014, 20988.066952068573, 4891.878543592354, 84132.59743098209, 17100.072146483548, 45569.68407707732, 94216.0791784218, 43334.04454544838, 26533.681166681465, 21625.917436547337, 72302.57724216119, 59025.94378099021, 18162.29523787276, 34979.24150922171, 83229.37778066826, 3724.37350993714, 44607.1847498707, 46138.52426903995]} +{"id": 857, "vector": [67432.77697444157, 78835.03051968408, 74870.83524766479, 83770.04177258837, 47030.050730810646, 801.4632201118976, 34393.910633581945, 54074.57731243237, 99405.45401157091, 61516.412695442654, 90050.75111847666, 87045.432527033, 269.09050153749445, 32046.811112980457, 47680.08418984063, 21724.365610266726, 25878.525561193677, 2236.6364276514505, 48284.055359068734, 16569.11530537629, 40120.98136030122, 1910.4025095018962, 27846.163350147766, 20455.014200233712, 75197.13255408478, 63684.87136862933, 10849.375781777515, 21876.300910490852, 85738.72482959276, 65166.26389785075, 57137.29905580348, 65985.87834361896, 83458.87936882209, 4803.0246988763565, 1101.5765310463444, 55.59854366697925, 17471.435065512353, 10607.267974065837, 62995.5434967428, 73699.96537885127, 88740.33385123734, 3416.809834536694, 65312.48684636789, 26059.29686951809, 52993.204505258116, 21970.76120930609, 67514.46213197499, 24263.47932948846, 29251.903492034926, 67558.05660612063, 12317.15483887632, 76528.30622256565, 73917.91463313495, 2300.366814457766, 54923.21030116988, 8498.565268984059, 48125.24351566375, 82630.58769260981, 34681.04553287964, 10633.751066025465, 39723.95266948006, 90169.95446040295, 7632.5365495081505, 12871.851301545812, 98113.5513934215, 68093.20239434548, 1223.8274323252197, 8421.906771556798, 40215.50076735233, 29536.064837796162, 70718.9519487419, 94560.68750399756, 70144.87664916067, 20668.80225846741, 78357.77309530028, 76826.69895729952, 42010.822836792635, 88123.80282338618, 98923.50201956289, 66738.44871155825, 51881.289393178675, 57491.51103736759, 4252.022866298832, 62200.349339085056, 76121.86754151751, 67798.18077624495, 62918.7057354018, 54915.627969794565, 83955.8026528365, 31386.09073194014, 81265.70102765012, 21519.70736870219, 7653.955910132382, 87289.1481359519, 1880.6203285517076, 98734.2413669986, 67737.28600209924, 43711.05718923811, 70649.82680729742, 80556.53242576937, 45927.44531583612, 31586.473774569335, 98029.45331978933, 2997.1768260518947, 73779.53648743652, 97783.96557698786, 46333.31155019154, 12330.353145032224, 27419.84754571093, 62339.27421167459, 44896.09601072988, 84962.2317336148, 88343.561164329, 95235.30099618403, 35988.75670630709, 23639.552303639288, 83911.9630200726, 70038.0393126469, 71805.14347106461, 50093.68125482343, 99517.86043146517, 47785.97479446874, 27538.55885648443, 85996.23663723798, 46424.74091934034, 9452.719875707671, 9878.0670454943, 35848.563662606495]} +{"id": 1712, "vector": [25989.117981048348, 32036.555506894292, 12732.011478715798, 29335.75668324192, 42098.13899738031, 21677.334014469307, 4813.389283047753, 25095.524692273975, 1308.7779966332703, 10156.502157533543, 86332.09752373365, 61127.303875829195, 41061.851861944255, 57623.24947205273, 9816.535164120654, 97365.54582421642, 21165.17256135654, 84423.43529679884, 2548.2904515512605, 13881.187079464053, 14205.928097861643, 44090.49723163204, 26974.747935307176, 64610.26523675919, 46283.303053999545, 76298.55734963503, 76004.02669620002, 52121.681946770834, 85514.52580654685, 60113.198609856736, 53603.159158327195, 79748.2250244708, 36563.413247444856, 47955.6511902259, 74414.5641639046, 90217.9516707196, 58148.33362920117, 72199.8515325684, 71165.88424073772, 85528.64461900346, 40470.84524772044, 58959.16724346747, 90554.27656123819, 48729.18613314228, 81788.43851891955, 83589.06470578404, 73987.17152130655, 78743.62324385685, 18061.611953979296, 99473.36279437834, 17131.263024086697, 67623.17595003426, 98071.39196468156, 14233.605467871725, 17549.340222621955, 90399.52035306243, 94652.11316761986, 20839.568540124375, 18843.562915164424, 51199.3784808946, 24572.205641447264, 5546.811683940234, 89031.79355857719, 38200.31638767376, 59834.99667375782, 37573.696659611975, 80318.6581955855, 32547.420643572754, 8770.732016464899, 33493.266430902266, 18828.95786683918, 30896.948154116442, 36951.14881720097, 55365.40765085231, 61209.739579315334, 10848.115204584441, 89674.69492688045, 40122.54198348192, 76128.18375136366, 39387.80554557755, 37125.31460461934, 26258.759511735043, 8795.836070334428, 65975.64032640545, 55609.73786863554, 58882.48717309301, 64668.62137648356, 8136.38611240367, 47925.77883413977, 60306.238818839454, 85153.98301062606, 88067.20717288824, 92032.87793421706, 43021.62342385989, 16639.35214807524, 92957.23181215965, 20130.316785267954, 69673.87903011461, 82815.00294089061, 98257.42713733742, 88818.91295511815, 6349.251796350896, 15354.106154851943, 19860.0852044292, 25690.361655166438, 5480.175058804371, 76612.81958995806, 88442.47885762055, 12585.16340495347, 51145.372672986814, 2480.791538688276, 23037.142392290556, 42303.19775608929, 18255.25105248703, 3533.5033728968624, 29562.5441501721, 66283.65587483035, 37142.13903486033, 25495.67548503162, 75023.87795562425, 69855.12572316281, 97352.77899214442, 53560.79642798708, 21965.757062410783, 14365.188420108854, 41822.59820363907, 85521.10132247317, 17650.548361935358]} +{"id": 184, "vector": [57083.995905352895, 41847.9615032909, 51776.14528919662, 5833.563250470397, 88932.3711759158, 89258.36424182385, 37043.197091759495, 58678.962592673844, 54774.384460201065, 27932.516094449944, 2223.4575462226626, 60264.565683538676, 32001.89393152525, 82861.07714000135, 45673.25461339401, 81471.03869175912, 43588.02820304999, 98741.58741253344, 9405.521707404674, 74649.91502433739, 18056.03304825125, 8853.305659590505, 1532.311748722115, 18096.959017307367, 79460.03123174676, 60160.2130513148, 55230.78572299646, 60921.825393411746, 20075.86830912853, 3241.0045513961095, 20426.009324703897, 67259.86297052607, 59766.46762191527, 7873.145652030955, 51529.48172536754, 88643.52621720919, 6090.829261849295, 82653.68198311374, 48831.77004965774, 40865.16744011128, 84217.09020842313, 61294.658779460646, 26198.842112766764, 61228.167605970455, 31184.850827046517, 80215.67337552339, 81348.66416272389, 3632.3351686803453, 70537.17864532936, 96498.17107980466, 80704.3889332872, 23206.82854060989, 20588.228267251783, 357.3737216665318, 20180.92744821515, 30581.257374155222, 74468.69519100568, 94129.00517661544, 76855.5679108876, 89655.7002567325, 8997.634499181706, 23878.771814295542, 28350.94381881147, 32503.665396080716, 5661.667191217579, 55980.95691181865, 72550.62255779085, 20679.141764345954, 89744.47116468137, 68184.71070570635, 88589.6396429494, 93357.72042704665, 16779.470828335852, 82491.70218501611, 57910.80375073558, 32006.517122586054, 16945.52403165419, 91117.63922035557, 61059.32547131983, 70471.45764839728, 12521.431508890135, 28336.77332612069, 95077.62191178802, 26399.256535479064, 68801.18117518339, 51391.43009091999, 77876.85340981485, 16742.828771189434, 66226.48719392708, 94797.01995390766, 75317.99277770828, 57109.66883247641, 89926.6648957027, 45575.823265703984, 44663.11855553436, 47374.11412165985, 5124.068464834297, 83610.78043018443, 15641.97932092246, 96187.52896084334, 21563.701710311045, 65038.162252615315, 41762.013052303584, 69773.21616257106, 13915.855167819236, 30983.681968228215, 84223.33282177603, 40851.61022871287, 68090.94105503234, 24841.71231821636, 63907.03547729523, 426.7013312569956, 39163.73307632655, 60368.674748512494, 15136.94241435254, 2632.12074148329, 42685.83750095545, 79507.19439926653, 93685.01316380003, 20186.786500861843, 82933.22053266437, 57007.07460373436, 63096.358578578715, 79687.15965488326, 18132.503032641267, 47636.97819423526, 21088.642066914254, 25179.375412997317]} +{"id": 181, "vector": [64465.62635440076, 95652.92347485265, 17372.70345280202, 98991.89318790648, 87282.71253451263, 13756.926715572481, 82446.8474600116, 9100.582556365343, 72132.92930224525, 96907.81770574216, 8931.802577445358, 65965.01792489893, 7436.914375722514, 16062.848179495226, 39829.9923720961, 47809.16938430643, 35468.3807909033, 2079.363964870573, 97118.47883371828, 35723.64935676421, 75088.66872070447, 45454.37351998184, 20010.584728441383, 17129.839514629675, 51059.32961394221, 71378.75796088802, 23695.988822785275, 34776.03757100275, 99554.53928129196, 56214.93223772489, 17879.266362758484, 85092.85616211101, 37672.26929241151, 91866.85616272742, 1742.8379032057073, 95420.67808376474, 70613.91967758026, 52305.816721418174, 94067.6615523124, 65941.2938650058, 58779.979431733984, 25519.977587398913, 37688.351023881136, 85922.60895357752, 95055.92497012665, 55640.83298257876, 19382.207311432576, 48885.240709565216, 56903.18171465546, 67360.51239756015, 88293.13991248899, 69851.72918676275, 83004.68845847709, 88912.56046924688, 86897.36042900199, 31212.43591299474, 54577.966010748925, 20936.177560235104, 90183.21085023877, 5164.108820129454, 30346.35548146738, 86396.15156601601, 30603.40526973737, 72246.82025351844, 5282.583035637988, 74898.50922558081, 4550.880265353585, 38201.1668118295, 35897.89003189711, 96401.29081952284, 14260.682046072981, 33142.790133507435, 59977.765021555526, 59423.6186733348, 54931.80109592166, 54633.75085335552, 8890.213286788407, 47029.950333108914, 86143.58179352328, 23587.645641149335, 5073.993762152251, 18777.174603592273, 78227.97510443979, 75069.04617778993, 91792.20451616183, 24960.474956046484, 78648.10283405584, 42890.2557178348, 86052.90847076249, 17193.607106963795, 10243.973169366971, 26160.230238022752, 9800.933174864467, 98008.74972034886, 28480.877949322003, 48063.311413892174, 19899.517224876407, 64073.1594271878, 46681.690542018514, 16077.908448377542, 24344.908834919544, 62958.564620990495, 41839.095172052956, 87883.66964647704, 32304.042774198195, 95221.9962020438, 59550.293222303364, 89026.33758908283, 29647.849349242162, 70218.83980188906, 62102.73057766714, 55059.139917152075, 42412.75315907677, 62898.52355285826, 83532.95711260923, 7951.89265062487, 57248.8875577095, 65652.83610287293, 67899.31980172778, 50181.06853230917, 87701.34649530693, 20051.00579447936, 10905.288104741529, 98061.41781396928, 84072.48749536877, 1152.6138669965435, 61947.68287062303, 11605.491048450622]} +{"id": 46, "vector": [89244.10394730131, 30024.737964729666, 42877.97217367206, 10501.02539160036, 58701.075712084916, 6488.621026325059, 62915.742507022456, 84904.54647192461, 73776.21611011916, 49848.44173236879, 46041.11341119964, 27684.024679982434, 17230.835109310683, 98533.97350264913, 88805.68872847276, 40047.6288808552, 12210.891421862623, 9887.776833372585, 86735.2813053166, 284.1075711144558, 82625.1977520983, 97007.42669824757, 11693.610932354837, 46801.68229456715, 20944.549910207475, 569.3360224224953, 87983.44140941258, 18068.03930610674, 8147.475155990347, 1898.490376787554, 36089.76600685633, 63384.08554407738, 99220.4532287103, 2690.4374435985146, 20919.526231525164, 40363.038588712305, 75237.20153568759, 24604.364769411924, 56929.7450527898, 97454.428230553, 91183.35479827315, 8652.05721442327, 5454.23872344134, 96638.57649621503, 56330.56824364872, 34384.221738942375, 90110.8525541454, 54936.151865120984, 92836.92577854634, 3448.9088832277635, 25368.420272105042, 28543.281681472832, 80310.81841906364, 78234.23109971349, 62431.0629368609, 49356.9194501809, 32401.25628320992, 99872.52297117715, 92494.03752100423, 23437.43323795563, 52261.5360696427, 41102.35157991352, 98352.59212918827, 50099.246413502704, 49623.28885246977, 13185.825680450447, 5353.663764020311, 83813.56855085258, 67022.1133803507, 45203.76180632205, 41511.90236819648, 70354.14844994846, 21615.02899077923, 41432.76829615321, 23540.22009482917, 51758.75257333548, 22996.291501057243, 13417.312094876388, 79701.15873847871, 93252.6364225494, 21905.359040018648, 3009.6741300312924, 16937.53383935894, 59033.26290596902, 44117.13236002887, 32088.750585633665, 70147.55257007752, 68972.49076470772, 29012.165079618644, 20250.660398562493, 69625.44390625553, 63782.526476289626, 74320.39779821054, 90539.40832385307, 51846.12810788214, 42285.38863385388, 74927.50795898621, 64573.2458115115, 82679.15148673169, 29493.47603550492, 69400.04123911283, 65493.30834839567, 12653.134205184435, 41656.356705046106, 65228.358576093226, 52933.1064464026, 54775.138422958036, 22706.095782363645, 23071.442702838995, 17273.20757937335, 3725.647911659746, 10570.014332901766, 56695.967013924266, 39065.811846020784, 60208.46106107342, 96652.29173306868, 88769.15889237197, 48971.5239506502, 69296.25861327662, 95473.54643245935, 88711.56278683389, 35136.48443404689, 10736.716129565782, 20199.31358986251, 1491.4898880678318, 71635.42561616172, 73673.90082002731, 69301.16374626264]} +{"id": 897, "vector": [55138.32600267063, 91298.54039300932, 88700.24296627687, 37903.62271413579, 27923.903361783065, 45194.92970977475, 50568.33177840877, 24556.580357623392, 93021.8003262423, 15938.209532569957, 3749.4795035351913, 85126.36988280044, 34404.86488568005, 29061.312578905407, 46809.303017249156, 94628.83446133391, 10977.635037366817, 18228.419545403794, 97861.88100700441, 24188.228277688562, 46098.624145046204, 95018.29008498746, 24049.28062175745, 10544.899851536582, 31425.1424877079, 37358.0909563241, 7063.548530519448, 79810.44544462829, 92227.1607723229, 3202.915037832177, 36138.93967151266, 67008.672228034, 23530.255819430447, 27593.804879707906, 38353.346559629885, 75107.81383664164, 61846.972597672764, 17340.307208996164, 19394.99722221031, 91781.50039941302, 79028.54105666297, 11509.325985631114, 11037.856338527607, 96297.49001481394, 34178.65323950309, 35564.24615463405, 53731.39674923328, 32891.04599832805, 58997.79784374477, 29309.835244582893, 78529.52335333214, 82302.19132159566, 76410.77819732454, 60489.482912905965, 89196.78834400137, 92650.90188881796, 66387.22439472307, 6371.785686508302, 94947.78606631575, 63241.00674309532, 73176.9844361008, 14088.548428415927, 40758.749099376146, 76570.00978165441, 29450.35427599749, 64828.0152420653, 25728.148726198775, 67112.27134764522, 86384.80437553379, 96821.48929287172, 50569.25950227744, 8353.31997327039, 51679.70812213162, 75448.72713659173, 29538.0618265631, 49336.42961314462, 4439.414871028346, 94298.0292562792, 10068.08471651144, 44098.74552899239, 29996.066038530178, 88422.22076568787, 39414.233300636544, 35502.38197898472, 3302.6969290847032, 22013.06268041665, 33295.227626542466, 58233.45508185378, 16953.20045259777, 91124.87821076016, 73344.07228080943, 4089.7329701748085, 36295.44713660224, 72347.68547868944, 77381.7361327351, 9195.97453857951, 17184.43020332735, 2898.1903038567957, 12117.799396782902, 80239.75654777452, 31924.463173184136, 1524.771387859858, 71187.76216177804, 42823.78688111165, 78598.5178719053, 56970.758548130005, 75444.46652086229, 84495.64115595077, 35684.62846003751, 97185.90007739817, 84870.22677612382, 70.42176914009434, 36794.699533134786, 5017.921927767011, 93057.9940571605, 20188.93792708567, 80295.85365905643, 98145.13374147993, 76496.46391936873, 17419.367673927165, 41153.25052798239, 56777.72315691082, 19539.17794241239, 43433.28401342236, 2976.9667182903304, 17254.494977393286, 22944.31431802858, 52980.844971475184]} +{"id": 1206, "vector": [25284.975379896758, 16703.943300628453, 62625.456638653865, 41098.05639692059, 22279.299348655037, 16157.296547875965, 22928.10217133201, 55109.77357876161, 45949.815808283456, 96170.11305825463, 16980.818372070527, 33690.49952801414, 21636.642621067593, 70805.09944971358, 61110.613417348526, 65929.89343950717, 86648.88333213005, 36965.44155079865, 48073.62722837926, 72710.57396662873, 36524.79296137385, 81416.40673176553, 72451.46105431157, 36775.038094742275, 25273.33498592188, 9489.903875372329, 95842.63861108848, 8618.63934064082, 79951.2385665022, 86117.0048664779, 63546.557574962724, 13316.485282241785, 63803.52275338491, 3357.9071652392377, 57788.3354998597, 39405.090122142574, 62221.203925027505, 13765.767585467736, 95127.66673908522, 23461.039428331977, 56244.02699513288, 33518.06414864271, 48066.24764605547, 91335.52732291791, 10622.307059811197, 60495.534140697804, 9058.454986385612, 30858.636825662357, 78034.6892426487, 74472.9769450905, 30107.034226987307, 21635.819439536397, 8923.289254709809, 27467.10163250621, 28453.942060040692, 55332.10482110635, 96798.36099929869, 16330.753304031165, 99525.25972466478, 153.5650055000004, 59133.80005142309, 91866.95143382321, 52146.31794453731, 68034.50377500507, 49749.57972235985, 32513.59467918782, 69988.50923168361, 42225.72316047895, 69301.49369240606, 35473.23527838786, 18086.030797912455, 31771.161208358946, 63628.60988405474, 14799.432075892539, 32003.42844247377, 12346.31441497116, 97620.47794317825, 20113.35910469796, 57540.2435899354, 95113.08226383873, 58768.56884816518, 63219.020553523216, 39327.421999824364, 60228.398207967795, 94200.65267913514, 25158.36833729278, 17486.74167855675, 10841.32608152767, 43854.449063006905, 64459.675608941456, 14674.763833467397, 77841.85554225247, 76812.07582264328, 97735.86598106346, 91629.81090309698, 65287.99286060506, 3961.92037181583, 52022.55765278923, 47399.956553844626, 89018.33381607583, 53415.94165652802, 97579.13738357868, 50169.53604138633, 67275.75668719164, 92127.40011314575, 88945.84341556164, 98159.88685897748, 24023.34884611973, 4527.2222343114345, 21648.446695190316, 45336.64216346733, 81197.4843370104, 81597.09147132565, 43377.90915430924, 8813.481960657122, 44904.9139121001, 12751.590041622074, 7887.669808111719, 15873.433405425763, 75120.77945975735, 28664.762948426436, 24492.108580884254, 58897.67217967061, 62726.5960140305, 45586.51792411892, 70474.72393273705, 13183.766982882727, 18413.836624141055]} +{"id": 1953, "vector": [69230.12916778636, 44362.32571474029, 85643.6127773149, 4560.896847391916, 33178.639740637096, 13635.83805307803, 23258.597319678032, 42328.80906674091, 85315.27503788307, 56897.88329409459, 29620.701345718437, 6059.588654736325, 67959.92945539302, 79034.78585765151, 80293.05312494517, 66625.69442801118, 32453.992993008218, 26550.01093966164, 2262.469121992583, 57201.72716866443, 52569.84723465451, 52122.26170711758, 16646.871564364596, 38842.78914965844, 75635.8639969647, 68365.71056512515, 5670.613027315241, 38239.184042065324, 66484.33806182696, 19173.24887451588, 14844.542014138762, 76539.2845420122, 32178.57443283385, 58945.233839987355, 38865.604878266815, 71458.87516630329, 83737.845274292, 52656.92551858576, 75179.68595100801, 65908.17312385947, 76288.17752561861, 93567.35132566546, 27962.555696052183, 62193.57607246434, 95993.61387638604, 98083.86034662554, 56431.23664452724, 64613.04255473963, 92700.5932549833, 88975.8194573601, 59025.94064575831, 89715.50087416219, 33513.248678761534, 52911.98079885545, 33995.39935735764, 37226.59765924199, 26578.288221393952, 10704.148244206346, 10898.162013465939, 31697.398123653165, 75300.05687770853, 63468.09826530535, 42402.94563285787, 79828.9321416944, 8958.586592443684, 73719.24312731044, 15140.072164842555, 85596.37965227406, 25657.220282529324, 23408.899261832838, 57341.45269256351, 846.175215407552, 56166.26878965889, 40553.15263406678, 28062.65759374673, 9789.53640233049, 97598.46190243545, 27926.52665070693, 24606.232208926314, 70916.68807586246, 62162.464572888675, 62654.208765729694, 33961.454210230855, 46044.405162126786, 89695.29262324159, 8239.830678431637, 24921.261239629766, 60389.73729343238, 18776.71711617629, 17591.73089741002, 34984.709378420055, 56372.21868543695, 71295.63151506273, 80539.01716508972, 66444.98018559339, 34856.29989545926, 25326.093200585354, 30580.312092344586, 66626.34865996578, 97586.07239646395, 3824.922192229374, 80321.07349546683, 89255.15393648214, 15844.157531846547, 30070.24335638685, 8720.94766476057, 54422.49905516821, 24331.94931741368, 23336.671939169497, 69767.88787713181, 98442.71498629362, 2038.7097124879294, 74328.42020184276, 94273.74593695403, 8893.584502350304, 90236.83610804506, 38828.08442275296, 2446.007042981191, 56972.21741039643, 30356.186161249167, 12276.864226611073, 8759.709102955727, 2409.043074103001, 32691.99380170411, 28157.27962206378, 20323.056356491674, 32931.21843707245, 78518.90840555451]} +{"id": 887, "vector": [59707.3724819369, 9106.464228421162, 61839.73982155857, 6919.44113515075, 1900.6145863392665, 24412.95863885621, 97120.22417240703, 99286.27902512293, 6887.813739162574, 94804.44166243705, 20108.439235570397, 7653.641268117773, 87251.03479819196, 9348.223568311387, 98420.18360228387, 568.8635509412566, 70923.15486155862, 66855.61111914119, 83102.05337757066, 18223.73248602811, 54297.31146823314, 51106.45075198791, 30773.651466699357, 49220.135149905516, 3956.8235248470305, 64728.762854837805, 8237.931288162992, 35321.972064430185, 12936.623931855773, 84299.55056933599, 58192.14459839318, 12659.999936131384, 54718.27241297588, 94784.08431468421, 85395.13268613616, 97391.75757371596, 39115.451361398525, 81800.76983483137, 15588.541577862146, 52638.49728888248, 90209.82746278295, 66450.08528141682, 40033.88094259885, 22555.477518913824, 62153.159133531066, 98139.88044264859, 31433.317202574708, 20494.931917373237, 98088.60091450156, 77462.46898346282, 73675.47272720645, 73927.49414009592, 27316.86716206675, 78730.14306654107, 43571.04568649604, 59338.56852873034, 71904.42941525596, 13618.585695714813, 85758.45559517673, 66538.24835297602, 13913.100149218626, 11921.845269245012, 45887.78572913811, 76972.75215296235, 18085.319197051642, 20716.464459477636, 78502.1481064933, 67372.77788428945, 76443.99739005414, 6448.986009648216, 21317.4079607959, 46448.43963395592, 20869.1609752791, 93729.83119495242, 58029.74099889301, 70286.71758947552, 45615.23118505322, 94428.6363689829, 71885.97826323666, 22615.365108619513, 75143.59030361383, 671.9374038924086, 35896.03261940427, 88020.35622241396, 46312.781348331366, 25782.737844579064, 39659.451802242285, 33307.69466053589, 56401.10723657564, 23362.656656646617, 88278.10413496538, 3422.0140148042533, 59166.67572952051, 57391.49339769569, 16956.498674090748, 15711.413214554072, 21323.76299578801, 21100.03637517882, 19175.306939698632, 35369.08922090586, 88888.54383796675, 74143.97077451953, 45795.03314709743, 82654.81835837115, 4107.377362775877, 2187.2932538596456, 83879.86853092545, 55431.75265971876, 58412.25022151156, 94309.3523980173, 50376.42485957965, 6076.837776178734, 77798.283102094, 8212.076582486017, 41570.33994549589, 40038.84939641305, 95061.35784415025, 79161.86397867922, 62749.38538469871, 11114.798671351457, 44574.01023179827, 27814.492385236223, 21885.401013754734, 23930.925460494913, 14567.628234927954, 49704.18384979305, 56520.001659104004, 94065.85916814038]} +{"id": 1190, "vector": [75534.47738487177, 34155.15861852408, 367.30520150547454, 2443.4687109590845, 5966.577818141861, 38848.266269855514, 8036.16253341286, 89898.12124135054, 1759.725808447965, 8161.694031090594, 20453.855079709072, 12182.810794610854, 73495.9924368787, 36889.54696167905, 85610.12961092846, 41538.52784625687, 30310.930425127714, 5158.323421936461, 66902.7323401817, 53647.652585894735, 68782.46141752631, 12826.56704859505, 15363.205194236461, 78279.46872411897, 14825.145538967432, 21009.351039748602, 89077.9840785379, 96997.07173121486, 77827.73945262507, 66875.47291334922, 78945.14944722671, 8981.915762372295, 54944.074046606904, 58277.28269611441, 46915.54244932249, 30398.61211338787, 33225.212358471865, 71536.55441656985, 91788.86889561996, 91485.00211064226, 14703.512113876215, 29498.474006266817, 65324.54748589334, 19219.295816124195, 94109.65125752277, 99842.25639171479, 64052.74178529473, 24532.415436002055, 83746.4594406891, 53834.421214368114, 38105.77874861378, 66154.70944088296, 87309.6109840594, 81356.11484471135, 85387.05607732416, 78939.94027519095, 6262.317630097802, 55511.96752909304, 34099.57996309201, 21111.126669174762, 46348.09138852939, 71567.94780973153, 75539.16964172247, 14696.922770015697, 97365.34798107031, 44132.73186894894, 24785.90199415701, 36863.01432392579, 41425.52887192235, 29939.160136613486, 91557.67594170201, 76159.32059224673, 40426.63721218662, 50785.669660078194, 16892.65361626081, 75477.3032324645, 85588.09616161385, 31509.134852363153, 16727.70565599223, 81825.3177807502, 60688.99621002357, 11010.180627908605, 25731.85798926171, 56366.83827944655, 34468.32503559134, 31775.977757753004, 97330.85123417576, 61767.93816463885, 80457.1294971464, 31329.23645072553, 79694.72457084683, 85091.61571820923, 77327.22131297542, 36306.646063519256, 26554.142141489367, 210.45646125980034, 60614.405722618336, 25937.73262987722, 39911.34282127834, 1366.1476703611063, 23670.422759002107, 48862.893886131445, 29086.00740006404, 51873.289166453455, 31150.860369893442, 52478.3904010437, 61658.036618648504, 2693.0100108053857, 748.5227124262361, 23081.521852433863, 95908.40583976795, 96307.92373970368, 93653.25797191919, 7699.940885648227, 25693.325399682988, 81519.21103526928, 41876.56288391745, 22826.659178054586, 95669.89338103855, 72634.46084114144, 17428.58678892699, 24958.83968919964, 27587.496107005903, 56773.69425216491, 92676.6910955944, 84892.70880584542, 36912.895645225806, 32461.531043937484]} +{"id": 1487, "vector": [77235.81592214612, 32723.770591525492, 47425.68559049756, 58082.8060789902, 64009.74164010305, 74955.25254006594, 33009.65727564977, 69290.01665683347, 882.6980197218614, 86749.6064666994, 21306.93091774468, 38945.58280033078, 78510.93456418309, 29931.734994931612, 97432.18744695082, 69381.88512979785, 66293.84059091764, 22603.106232587645, 59446.74005942775, 80991.39020528985, 88472.51668434254, 89881.52257818064, 80117.74894573084, 51633.5459299211, 85212.26926305977, 30423.139280297783, 16416.593527955814, 1720.7686867301786, 84878.03713160264, 57645.78426789507, 83161.20053942077, 46469.31266040804, 14300.23329044755, 39452.15771353774, 35295.58874762281, 25534.948707122963, 54516.74158801138, 28711.373501403283, 7016.6422671054215, 52749.2032869683, 78555.59489619354, 13238.361768888319, 33816.91432657514, 29172.294070233762, 20585.966172031378, 85262.24698870936, 14130.18933910085, 43204.27423905473, 75939.85940512345, 85113.4658407709, 14798.123980318145, 96519.91677768038, 20415.7379365459, 20016.968194881556, 4062.181900497597, 97750.2288545697, 33312.196119913984, 71351.74833033548, 79211.00816828327, 18652.331554525725, 56403.80286660204, 37776.66378988701, 50206.57082369478, 47723.944201119564, 35402.53076987614, 39551.53959498238, 8226.490717335833, 41366.14926923612, 34433.818312238654, 46727.20278801171, 29309.631114491574, 9687.451328010522, 65529.34799196005, 78934.79114790032, 63766.06257086051, 63622.34770023258, 91153.28017425528, 38427.478313601205, 72572.69211301024, 5868.542962367574, 40333.50667707657, 89393.03914935187, 75341.56843235524, 64565.6497354577, 24391.223482906964, 12039.895830093139, 23676.971049826367, 90977.6732006842, 11873.217662370573, 37384.55845175372, 90289.10049564956, 70294.78514987946, 30949.644262340826, 65126.57056568668, 16898.38977868423, 23478.278318722045, 14755.25872452632, 88631.73150753522, 15619.091293941168, 68409.96086733388, 98734.01237873331, 2328.266820343394, 57555.15921570235, 15404.518869277761, 92494.77374157021, 80810.36796854757, 47825.00862812087, 506.4647573550629, 90221.43778557204, 99390.29596432955, 70332.65938047762, 88751.49446802038, 55314.66886677589, 22651.34964444977, 70404.55716126125, 46672.023021459216, 45536.035969137, 16544.279280802242, 58168.78449235948, 37517.723727296274, 26673.544358079358, 71356.86924916395, 50035.681128574506, 54820.32858652381, 81865.9586860544, 10887.167291395983, 58671.55131902764, 61821.17496545955]} +{"id": 1767, "vector": [834.7673106421261, 8033.9524934752935, 89069.66938248268, 26591.858968404493, 94959.0375033018, 5001.337622641889, 4279.308594444775, 96843.75135469795, 64617.372246668936, 96762.50949332847, 58905.97386536348, 37134.095049347896, 91511.22077176133, 90764.0529423591, 15402.303448045417, 4787.983714555755, 45480.1269480647, 20093.786267101066, 31798.517583979035, 2893.7767847735386, 80141.11732537202, 94523.79524868468, 15263.02520227647, 84474.15682636769, 28205.74120124951, 63568.46933804622, 58923.9443048937, 42719.01097496925, 74699.41936410908, 43148.382303511666, 21337.917631451175, 9339.227403824169, 24831.034612376647, 56273.20782103158, 61226.485368018846, 57652.27462757908, 58543.8622886687, 71398.45690233362, 70857.98237381235, 61707.24034495316, 3252.148297903135, 36220.85631655102, 34835.46509120787, 20100.39052111876, 78189.10706265828, 651.9068529062366, 95921.48321015059, 86542.00664567736, 58464.0010878139, 4200.765897345038, 68302.32734903072, 68606.79664085389, 61980.73898209426, 45919.17405392753, 87906.50319777642, 85807.26077744458, 32531.535124297352, 72755.9347022226, 70393.66775365722, 14592.559571959684, 86367.58388412115, 79552.49618736182, 96416.37865485171, 61975.95602917434, 7705.519906457248, 59812.199372565046, 80901.90408807828, 44405.2536398407, 40756.58426892429, 13168.485021359433, 92435.15875152861, 94216.62296287953, 58152.262788033935, 96422.79363991771, 85390.3098781543, 58859.81775905415, 73714.27273026988, 27729.629098592424, 16757.532694118894, 57092.547588284695, 44873.9423739249, 16866.96668252645, 50282.67391538445, 71381.32192905784, 27557.679632581854, 15482.29742516961, 17910.66200279371, 39257.607006284, 70078.59889756687, 16659.579861477116, 29937.192453118143, 77463.03440670457, 99243.24160282883, 89564.43970157423, 50333.93269373695, 10161.782774685546, 27371.408942916063, 15400.638724985694, 94552.82946482976, 32972.13703898363, 81308.23234816175, 55251.14295265128, 38702.126488941016, 24075.657429487674, 26271.836972085017, 80457.35964113324, 17496.095928968203, 35668.5972428469, 9980.552899874461, 92188.37817612762, 19484.331590422622, 95057.82243531871, 33611.856105819985, 48439.07281643207, 17586.084426016325, 21.790108760899596, 2124.7764199613493, 81141.519069495, 96926.71366984851, 17637.54048064389, 54270.86461579189, 34510.20302194335, 93804.65321945777, 64763.08074305774, 6393.557448048415, 39336.32523189656, 81878.26491709687, 81121.68444191161]} +{"id": 586, "vector": [84320.50821455737, 23524.14350344485, 42796.80249751635, 26392.880877161795, 78380.31739496668, 11193.049889331098, 9197.656452359071, 99735.16881547112, 16301.375991552903, 90883.37631368921, 29303.36412734632, 72218.27349007282, 10412.139903940753, 97990.14603498913, 70298.08510516252, 76100.82100756554, 43172.63768372134, 4405.540221083859, 74955.65293821707, 56370.037137464176, 52522.919814131, 20527.980273909496, 8645.670500875458, 39751.411020983694, 45037.19010473691, 62581.48953415762, 30595.223460951827, 57498.646294566344, 70258.08347909176, 6730.922466610756, 96484.29493542807, 51302.74906835276, 77742.65773089668, 1194.5710712667924, 98912.20390337416, 860.0304624526989, 59803.04665461534, 61842.28345669819, 96765.06581742124, 6813.56162525657, 83049.89669963879, 97368.09630195377, 25321.98095681644, 94489.93912537115, 86035.53701128428, 85585.21996335841, 10274.688731197102, 63581.79494057373, 40705.84131112087, 26331.34557634995, 96621.52223000325, 34650.225306270855, 26230.856601811793, 37641.832923859176, 54526.91515234507, 34614.678806306, 91962.40478020893, 1087.4311329384989, 52432.35501383382, 94687.83195694744, 55610.3183309633, 46570.75688544296, 91041.26642801813, 92311.82395519454, 50257.18988244495, 75086.01297543298, 70836.46222886971, 46886.64060650405, 4525.935118982694, 93339.56785719123, 74619.21373918137, 13527.719153012951, 84710.31816787722, 24628.870146530156, 10584.717915037401, 80018.68854107348, 65706.19158964757, 9452.103180298733, 61517.82943572389, 21608.061863043447, 53462.89017641648, 51990.35221614807, 26329.154908989516, 3693.799514841212, 29471.98845392124, 80708.78255148107, 5852.279074452349, 75521.82105829113, 77980.17141896769, 30705.02962231837, 14875.454619328266, 21943.101151366194, 43319.731741375086, 97541.65359025083, 16354.637084117574, 76379.10142746996, 81328.46277570668, 33198.34062084076, 38776.725549723844, 98750.16633694954, 72187.52497149524, 23876.677892813626, 13365.37575265857, 33183.44358079983, 24040.328314695314, 36077.200051034706, 11422.321098681332, 9203.394783181462, 30545.531802903148, 94551.72756896359, 51618.7391418031, 68334.9607553841, 19162.771620677875, 65343.811507251135, 9226.518081720948, 69499.60675961545, 22356.691816907827, 30038.137531926433, 19228.971026360152, 84585.09935852671, 93253.40450727099, 78749.74655100467, 81021.52815966531, 20865.761031474594, 73146.25178320738, 77602.13895747233, 99111.68879971873, 1540.7412201669745]} +{"id": 1087, "vector": [17544.91905832273, 15441.759592288141, 40470.13132037526, 75007.21530862106, 64706.238038957454, 96918.52151094613, 64809.11916849481, 34366.54944525404, 92743.0873819417, 56005.640003296896, 72068.50488961181, 69867.9858537854, 23191.93466304652, 49525.78412292714, 85393.43111093948, 19482.412359938106, 84634.6687421182, 6497.329843895416, 12495.293467322044, 66425.1236477642, 27127.78360682069, 57223.63845988777, 91623.2403511652, 32929.9651711626, 22920.503454259655, 32065.50501819274, 75887.48313518392, 48724.73515871773, 36265.67733494477, 8874.584416823684, 93109.4354430675, 78712.6153649427, 35954.33641968286, 210.8421576363262, 89852.65070826287, 71808.74666059967, 54372.26557842425, 58983.49301785792, 95287.40535608327, 82856.05255324017, 1221.1715545976776, 11095.438424274873, 29080.991962694603, 83356.73332081627, 37682.90051150481, 69522.67869729396, 79145.34058941454, 33638.52580248478, 71260.36279641768, 91469.9575841744, 59640.36051654363, 31941.631434977924, 89538.63583140667, 7819.486407699894, 25002.711623303785, 46990.23383612754, 20795.181182634493, 62231.51355447842, 88581.82430548707, 99720.73379759591, 94158.64615253666, 60672.44795072159, 98487.02767340474, 88145.98717331157, 71584.18859591136, 14494.796549053424, 34512.32839287998, 46262.85547939468, 28402.092462385808, 28731.276852578503, 5556.075372564906, 74311.34541948879, 67843.25083171112, 68353.4427907269, 39335.310165278424, 98475.23739750798, 81001.38495146335, 34338.16714320064, 47748.40554237147, 17376.23096669961, 8216.387533963687, 13753.368321177339, 49027.450086311655, 70348.75131145734, 70613.9968399931, 60821.20965371928, 95367.53403420132, 19574.76073463209, 50463.028934120266, 325.9761619972101, 60902.40371254251, 89745.24452716345, 60464.17189179942, 40332.098003720464, 74845.91998857741, 71801.93436984251, 65612.40700931214, 59754.21324728989, 93005.9234970421, 86506.3176942845, 83255.43909974911, 71600.93796683768, 21979.622284491972, 51195.17859928067, 67880.24262852392, 22088.058449814696, 77524.55820363002, 65526.10511198763, 53161.24576889483, 85857.18584198799, 74109.24387133363, 25730.72682621944, 72207.5219743714, 87361.04509956318, 96580.39494382315, 62128.67591733554, 73615.46509226163, 46417.54818719564, 28102.763771376925, 67005.83426802685, 71903.18990687655, 87422.29729520054, 22343.641296536276, 34871.70072710551, 22914.870675628274, 4033.9447880408775, 78597.60085902027, 82567.86498590538]} +{"id": 823, "vector": [12162.944955281053, 10721.604630756987, 4700.241739902677, 90940.70245564712, 99032.56685307497, 61237.429667811906, 49475.34207112867, 38699.835103655365, 31719.043542551826, 47199.80584345053, 37559.152930112235, 26259.55794839412, 12950.485752964025, 34531.15456464271, 21902.195578890947, 9692.01179109458, 23339.036874844198, 54127.51251847121, 7620.034050151814, 7975.368376162862, 34261.868237488125, 97456.4143689891, 13916.010583311721, 69967.83513471724, 40361.66502180892, 84480.06502754035, 56812.42240262554, 93292.3967792701, 8922.001200529583, 89174.6379041419, 44751.8332516388, 41744.50450973076, 43477.91924013862, 95104.98502781708, 66086.44885536032, 85674.43390982556, 38951.9629176688, 44080.04376121655, 37126.87459806069, 22583.393358695525, 39505.718936559286, 5538.769575386848, 15420.947818138431, 9109.99770541645, 9147.978244169886, 50442.491169691384, 69998.74820007136, 91281.428811098, 67825.78319218225, 56591.26324517291, 2181.1655078408985, 6930.866375491929, 89484.48612565052, 79695.00908086533, 64954.859029454106, 75675.94101638098, 51367.8914761556, 96276.21239365624, 51446.228229254055, 99016.96684023783, 72405.40723070661, 87391.34614492569, 76001.46086362767, 16379.183670157694, 64902.89758700138, 36499.075650697756, 62861.56255259005, 19283.72181164625, 12079.095378958304, 86199.92556423666, 14989.871686417899, 37820.724136128425, 11414.207271162613, 81061.81703982536, 57612.75345784209, 6713.935073690524, 45808.223362926336, 45035.727711665655, 25542.70900269494, 22636.879718984103, 72067.32670208406, 35417.879986019594, 81881.22876770508, 8330.511605893975, 18521.428856115395, 7217.434825540914, 93160.45382560026, 18195.933667507157, 42694.18881488495, 89155.66314711966, 5227.881849223181, 20087.970612282614, 29203.356219150635, 89024.1724271887, 35662.988126053955, 49608.499798243975, 35425.44756661567, 63486.5506548974, 64590.09278468964, 92419.84611069354, 99708.00749703437, 86910.19146709009, 24619.619487745826, 38023.70841568495, 42299.53519325896, 90654.32684620311, 21383.263904359297, 92377.0495906546, 46966.87845561592, 27495.495753818577, 57447.87830063238, 80654.12469259273, 16314.887039539672, 62969.70400019406, 93875.66666065152, 91745.23507283542, 98640.83778989215, 94683.21903002486, 97058.04366364442, 12722.27421293267, 69800.9812910599, 15042.36919943872, 10500.23573698219, 82599.83584929044, 73888.110377489, 85069.3190650356, 93718.03435949793, 53441.813019438414]} +{"id": 1363, "vector": [27208.12577940983, 92072.2343773239, 38948.11463759251, 78407.24248353738, 72235.79800619956, 46008.37332339805, 6061.286984602742, 63242.35668975666, 83182.74767121905, 6737.326707881397, 19669.83620289553, 19352.327245035573, 5980.645132138718, 36564.07215457237, 97338.82075399668, 6467.808107891471, 87952.34949097953, 43651.69293685064, 50710.04868831246, 24984.537333761757, 86137.41303358295, 15934.871157905483, 27268.97097523494, 5149.324502379804, 15076.773878037597, 94841.74851682632, 63177.43660186943, 87559.3301600584, 98623.80638898436, 97767.19612759957, 5952.527662498097, 8266.495746729197, 52211.219048446066, 82102.14072109548, 4544.060899962288, 8963.29955606594, 23414.800032764793, 4240.026654291495, 14054.382658958131, 95383.39434395739, 72842.7096815871, 35633.491608684046, 21188.655451561655, 46874.6214530481, 73048.4332044055, 22091.050218038443, 99777.46171557752, 6418.572681390955, 98594.25104842443, 80801.4369296466, 25386.863962220817, 57235.27252052203, 67280.22821907891, 76259.26113639079, 16279.992424295331, 74189.8286479717, 53799.78380089836, 50909.66897444088, 58216.48591837419, 27678.77126247239, 50705.56376790475, 47538.152135033095, 64287.119289963346, 22836.33258709765, 91568.88430384597, 61654.17843168948, 39758.459749237205, 49591.88468119259, 41058.57108950292, 60402.516863084, 7357.647630740427, 58712.51866107235, 21048.503351032512, 72441.53570177505, 37318.73646331788, 71672.01743493031, 6083.593250882924, 67898.20905416115, 87493.52529698076, 20938.424151125557, 95027.8054756054, 23151.086696445756, 2003.8507641943258, 9871.37302775407, 62354.31706138308, 39399.03411231216, 22347.61068510388, 16377.309438308484, 4463.233443009684, 99704.68721151598, 74578.16806738968, 36237.294071929435, 64801.875246475785, 35798.51332892294, 92518.44407553322, 68478.83111556918, 59028.59641674256, 77292.93896788129, 84286.27426228262, 31706.686655308946, 47421.318673392685, 30094.950910770047, 61068.50784796807, 89288.05469628217, 36509.615438381996, 59965.91052036175, 41365.55862695999, 19469.970443594753, 57912.43891102018, 63484.96016795948, 5957.666834100562, 12715.888213851978, 23985.019106649554, 34062.95635508869, 22335.576052096527, 26932.131776348557, 39751.9226317823, 46048.82494816615, 78196.18450999193, 42905.39012988501, 51594.33848092714, 71469.38123927267, 55916.60795422447, 88645.36834428503, 65579.30334769082, 93254.25799123038, 64266.56388403631, 54777.712607646354]} +{"id": 1717, "vector": [63321.099817847906, 19815.23755074397, 29442.169817959897, 82513.51160147434, 62650.34499767078, 75991.0777675871, 33909.645104392235, 21890.500965442337, 15860.05526705967, 55081.23364674902, 80523.6565640532, 65458.7006949374, 71962.60065562082, 93771.73542588009, 11096.950850943022, 8918.634220662236, 90394.52204913343, 79108.37257770424, 46032.65852597111, 15167.559348619352, 27930.65239941307, 61603.70959822412, 69609.10987807732, 4503.578154466814, 34912.48064496482, 80501.05277181459, 15011.03812852773, 87508.34722965788, 37533.82688993778, 48276.47872271348, 35398.75562085304, 56558.595505377016, 56847.52515186329, 59329.134974644694, 6907.422181350764, 22162.785761713443, 87727.91540234601, 10353.238049741132, 74222.96828246623, 53103.547887556124, 44684.358573389436, 77317.41595546906, 47394.37341584221, 91339.44023263108, 31777.42110662003, 17375.923883890944, 32016.568509750887, 76080.98683641206, 10885.950539594247, 17756.75738621153, 12790.39344051891, 78224.60819712465, 68533.08881384034, 29092.8718356928, 64071.49258018335, 51344.17204273715, 50798.140051602706, 68737.24754129202, 18603.495651518366, 91187.09706926814, 35406.535477108344, 25993.171275784487, 98431.26612431539, 96409.05515982879, 87817.40040967609, 77327.34667365701, 79073.71928533955, 57494.02583343592, 23781.194555950136, 94258.43246835095, 52473.38066541376, 98135.02800373199, 68377.92190897491, 21674.86676630378, 42455.599330428915, 1528.492599373399, 62915.95088090075, 79255.20439222101, 17850.013117821185, 58902.76014827912, 31330.146774382316, 76390.60455758894, 12906.181245240301, 60222.4529467715, 98252.12133332448, 34806.56566182684, 19074.30031593046, 31298.56698492709, 69162.28624361397, 59923.44513781379, 64200.085096847346, 36575.80789253634, 59309.726618663924, 57873.21824860543, 66388.59950288241, 76311.42740125982, 81186.09830563921, 87906.28079012925, 68186.8889480663, 62947.40667871213, 12241.360783575283, 33605.80124561176, 52769.592033696164, 99972.61452055245, 55449.01751435736, 30449.23146924424, 61250.438145009655, 55379.44255675295, 6744.878764990991, 83194.01866421527, 25257.506104529715, 60902.91557483677, 19509.944781868406, 60584.15434724723, 9503.555407555552, 3151.750151694277, 21213.61864275191, 18183.14781739875, 12930.488232622383, 31255.9829838012, 75895.99996966476, 59153.79448518175, 33853.97761440201, 84641.48721548016, 4319.21273574366, 28365.96814896175, 46643.50230460853, 42676.599956677775]} +{"id": 141, "vector": [70496.23762070748, 7763.178031669915, 72682.19004038375, 68579.48283559285, 76073.43738541196, 49573.49106076419, 27554.698809432888, 48114.334840253905, 65261.183249582136, 89530.44663419656, 27265.220794747493, 49820.774861881415, 7445.6583720657445, 79433.32016234793, 78958.51235987108, 81342.11179929797, 11182.265508513023, 20178.32530744592, 63086.23800017118, 65889.77781304289, 74538.30364025301, 73583.3290440338, 60394.23624492517, 1325.0320177852327, 21533.84650173469, 16071.374409482363, 26332.219442448622, 74458.9800701093, 93460.01548442681, 6697.581960571019, 64119.80280169499, 50674.08178469635, 91577.2225395562, 4283.781390441432, 31957.2617992996, 46706.56471896213, 51107.07523194066, 78412.31160892521, 80902.33601197552, 36525.66657654691, 15771.767557471716, 86145.52606279936, 47968.70660697178, 87485.57707461964, 42048.691688551975, 58372.01573900369, 15711.371751723369, 75544.20118803169, 32681.613027810043, 80421.95426196483, 44588.148015445615, 9006.237122997307, 44112.79392472385, 62644.3216982523, 46045.18921220483, 79809.52388784254, 16550.01048468838, 65666.90714163374, 45351.893613456705, 48322.8476467063, 15965.582450937676, 34166.58702532555, 70275.13621532242, 44255.01739373643, 8349.14164656063, 57081.90130438762, 11923.359286576595, 9777.64385306299, 93569.42324391365, 83352.68469581487, 6235.403675649176, 40509.90337036522, 8536.873376448628, 58344.26766940179, 93009.7598391896, 28908.41858885964, 88637.76912307143, 71046.36660889405, 85167.3481186624, 48084.740979565664, 88939.61296015214, 7036.381516498469, 93472.26096895536, 32781.87852815569, 90491.85825105174, 1088.3842848185666, 51752.857087160155, 69710.1323178252, 5646.480241561314, 39869.61965428231, 99350.61025308412, 30817.70054816223, 74891.62027974232, 87867.6537639999, 50468.90462014631, 38409.214242313195, 38946.30949525352, 44691.08013166122, 42243.79794164662, 27251.451294938277, 78428.35470117867, 55518.947679285004, 20208.3765064894, 93171.21404983506, 8939.94090506679, 25998.78372884289, 61864.765847414346, 45977.83207890301, 68331.98619681045, 34471.009174927836, 31021.897378644124, 19671.418316442014, 20944.16548209054, 19115.24498841508, 12212.427674880422, 54507.11234901009, 60871.26681714224, 21694.048542932596, 55669.90131069762, 85505.83779975842, 94790.56738286374, 5786.984243002847, 96843.32592938463, 40110.74104735068, 78683.42708037783, 41494.95236692231, 52724.109231940754, 84265.678454412]} +{"id": 2025, "vector": [51713.6377533822, 29035.668158793225, 7258.2316071885925, 42803.86850432177, 88041.64641179133, 85521.61796485135, 6242.3255764536, 80458.5462461377, 43218.663914897945, 59317.741073522935, 2107.3299306452964, 24881.768941681425, 19355.464261182155, 14644.143783720132, 52318.111395073, 89387.66280668967, 57575.69486427883, 20365.305313505633, 31157.631937906994, 65663.94949016182, 29582.98725329168, 51776.954761636865, 67927.38845538838, 69111.02698104175, 76225.68351526815, 69648.46738300491, 6127.435711293916, 77960.05586827213, 48005.41497200376, 88158.38667203282, 86277.31606795074, 81369.28414729962, 64825.66637051901, 47157.64108920242, 73430.63224348745, 79427.88621425859, 38208.29837337085, 11295.008780465289, 99762.16207590957, 23581.310883705464, 55756.65048778834, 98378.7653299877, 70062.55216263648, 4869.0566172106655, 93282.27069438525, 40715.356679080825, 34377.23218299104, 62765.56069319814, 72996.39584536641, 84411.92347981934, 37792.610022627225, 75086.21681205145, 51907.3673646496, 4477.283842797497, 32051.768053926422, 17862.414795500394, 88847.31087980277, 36270.76167647095, 97632.26253121087, 12703.747984585789, 52965.631629263786, 87860.71081918453, 50512.023310218545, 27670.10777805624, 3812.241416394324, 44657.46257217531, 20411.93340947868, 84350.90688775228, 41987.31613508071, 15943.582603463958, 56766.20501795194, 78932.3571781233, 87290.53532349755, 50954.71125684858, 23614.398692306604, 34940.78251011273, 18280.25358924913, 689.2844430618661, 98337.42452320739, 50277.129717238255, 54745.05423487288, 71272.1451455029, 89096.0390459674, 38603.725500164284, 64988.968597003615, 85952.77547692644, 39861.39549137519, 69674.04710386347, 70768.45113030291, 91903.14086621175, 41316.880406420154, 26222.137211565354, 70233.53804400994, 22280.128434857117, 50721.7408882693, 42900.89574472833, 9237.73819061493, 92015.96713294463, 15526.924470087799, 7738.762863235771, 34338.718275753374, 77978.5989530569, 58523.98048041488, 87855.79292336392, 66429.95028450395, 21977.089567550178, 65454.40633409843, 24880.212661371104, 69666.8411897523, 5654.8500358677975, 84077.13722690057, 56001.61855904016, 21244.329323347756, 10910.366018886409, 15486.609726120349, 95466.5463083956, 94065.54640013946, 97022.23428269284, 96834.5650196729, 65999.9234674524, 2829.7646520755948, 8928.509637840043, 68635.37041589266, 3867.556776982983, 17586.264588217637, 28137.915616193997, 5055.156858810706, 45215.30877669669]} +{"id": 272, "vector": [6997.316967210587, 45824.89076384605, 12726.606168591426, 76141.56977596389, 33097.29968841628, 77780.93504284383, 73471.64913948336, 92026.15025819339, 5497.888123239525, 63579.60192053768, 9040.64577740713, 64832.485692308896, 50250.34445215541, 69239.14952739917, 73245.96560087445, 70951.61393822642, 27472.00825484808, 25859.166766593466, 4777.001483211585, 71461.02971333289, 83409.61050272874, 37137.317522240155, 58490.31023949171, 47756.32146605401, 75522.57760390898, 41321.124662068956, 38564.47874014833, 10187.097057862815, 25249.691523048812, 36358.30016028758, 43180.16589806134, 59721.46150999045, 35953.638810455945, 84610.78033038946, 36701.805784858196, 45280.64709951547, 65188.77526849471, 61070.438095467674, 29361.39003145557, 34314.795844045184, 5535.688907144021, 8954.269129288494, 78604.99777399436, 13977.891257381592, 91459.3929547903, 81002.65886515722, 5060.487868162133, 21710.50824172872, 18815.302983975125, 98334.4651407557, 33196.56400687581, 34405.9954765358, 59234.250617685604, 5933.611135478201, 25820.4921717082, 42958.122168488364, 96166.4066648535, 1511.2667541750336, 88112.54134616897, 45158.92920215125, 89259.29939737073, 61419.03390083842, 25586.20167346689, 94362.58289355735, 69021.48300948353, 24908.619977960232, 22973.41817377816, 900.4208734841535, 88292.83533167918, 9598.955661088048, 12720.069235919518, 31507.092552759987, 25505.459441003786, 21945.24250513664, 16246.01464173302, 14520.449574110695, 65182.2942117132, 25552.940235796483, 27791.681845862182, 12021.490007068525, 35266.96817033804, 77905.02110841517, 39705.67802553145, 46589.29295011467, 75880.04300029555, 16732.262209519224, 1728.7554809262583, 59592.120431917836, 96070.94028449309, 78039.36658743229, 54530.084959097214, 90378.42593308943, 64031.4438338178, 29483.538213752247, 4194.5601378259535, 51533.78402386, 97667.25676354868, 50197.77933542537, 60624.81807332264, 97971.05456712481, 52671.164908660365, 29136.120713788438, 71574.29510415636, 71419.60511192557, 16355.57473021596, 57236.58928750033, 72655.47181237294, 69842.82656376228, 94820.75505622191, 16673.714273306417, 5708.929371358562, 1973.1046922777052, 63969.519307782175, 33765.94764424455, 56764.485627074755, 13906.439129597537, 10515.235829062187, 8532.616424862494, 89097.82766102464, 94065.92420389489, 2149.840853264229, 60923.574487458, 76838.55895079034, 78368.94978504807, 39030.51148071621, 1631.573911254125, 90268.85460601795, 62989.278915244206]} +{"id": 1959, "vector": [66073.3311888053, 2546.075458177166, 93236.68504208472, 56667.18912071999, 30248.254023747824, 28411.368049618803, 39900.68730478401, 302.996264153188, 97878.1739862483, 62695.70880903815, 21868.304185133915, 27144.64458783332, 46461.62492107989, 7945.339961070242, 68355.90960057409, 11050.864429229234, 99457.68659781775, 63279.29878978205, 11656.421741627453, 14739.036029894192, 88978.74858785569, 5688.767928779692, 45075.914175957056, 87739.21691441024, 62206.19136354848, 14148.540124193587, 36036.208527559, 29539.990862134637, 48457.78253928983, 20151.447955356503, 69409.16872909361, 4178.000145624161, 71533.87348972344, 54476.80667221251, 85309.80837724426, 18293.41516529195, 51892.56416650271, 1589.544444519153, 70186.9230313556, 36455.58548410431, 1612.59469932713, 59161.73346567705, 63793.11403116218, 25343.087063864543, 61764.21576420834, 2339.8114206556797, 51162.96252424093, 34653.967081477545, 54429.17835561558, 61640.53019114826, 43951.05907720651, 24833.319687352374, 97904.9581547272, 77762.66415890856, 72523.59897044754, 6323.032299496534, 58404.277839610695, 32096.644932137508, 16202.344124919333, 20953.8018629834, 68736.38645725342, 78918.35783083217, 85707.56166624601, 93277.8939678454, 16828.992693485325, 69089.91154614615, 51763.11235958596, 28496.109607918617, 88679.86211775464, 91388.57541480169, 96365.51455603204, 70606.2439404768, 45549.61973255933, 82000.59048101991, 55991.251980927016, 15705.19692739384, 62578.121956323295, 30416.012886523004, 81155.14052465279, 29850.856109080338, 6406.083724719058, 32696.98494332275, 81415.12362998254, 33239.27003909405, 30729.603236724866, 64085.050064396135, 98492.21026624412, 98824.1828055529, 54641.313720670536, 87873.92371283214, 99187.85177271313, 20203.56259881221, 72850.77341053622, 70378.71625183568, 94251.44037189333, 93286.57952128343, 85890.24953050088, 68655.72855861524, 79530.93352115336, 46141.657667918465, 26462.431215116223, 45795.421404089684, 8334.515263087373, 43458.63104157284, 15756.569828627642, 92958.93247061175, 49717.09815268217, 70056.37988280595, 28240.179087611006, 4263.46271001995, 61916.42047857996, 35220.640810889534, 65833.69828727041, 33240.160270175365, 67021.22914561855, 66835.81700401736, 43924.69246578471, 24199.62010644774, 43868.75431649181, 53913.607736193844, 80543.78355604107, 3282.81793161308, 64555.60485386206, 97702.27200613268, 64896.08084884158, 69671.59128014382, 47801.612739367694, 10032.83625581175]} +{"id": 1947, "vector": [62282.02592566113, 35638.328345931855, 71783.55634989662, 71629.39286569, 90294.93882437135, 40842.75712423035, 90284.21665668544, 90187.19152794726, 71204.93351188263, 9799.141419115975, 19502.393669620276, 77003.07152912328, 15327.946791487202, 21692.07692237688, 77365.40526153713, 93187.55410972217, 54234.71534477317, 77998.17952632284, 51876.00013548955, 90173.92329980846, 80501.7791009122, 70959.31196356381, 49115.33243417313, 39498.090865535494, 49842.99487881745, 80165.88953353491, 76506.69646234282, 20911.04104752062, 55411.93639110142, 72276.93191191061, 51115.34345075967, 77256.38341942099, 55825.979219001274, 52761.919782362755, 62405.30406476159, 26483.773995002113, 5039.696053966347, 87429.15266649575, 89027.57706162454, 92669.81328051358, 48102.82574184679, 1231.9091177997677, 39352.270861989324, 97350.2950010848, 12876.390320886689, 79216.23144284559, 76344.7744973813, 78559.98263728549, 72206.3932998139, 67220.3726005732, 37789.48014907804, 13684.945447027452, 87215.14779110519, 13294.457177017039, 53910.451604182555, 92544.8297247515, 68923.06952179386, 67834.28203751869, 83034.83389550568, 2408.0206804021896, 15435.726704845887, 45107.47241713656, 73445.25083819196, 39306.9443673783, 79470.34739336974, 49622.168510591444, 51034.78447487632, 38185.338492965006, 40766.27758685694, 4594.450901519309, 36253.44725522285, 67069.6507969608, 23534.311558635367, 96783.3132562093, 50289.21622950404, 40544.15295995749, 49252.04512970091, 39736.95342441168, 5996.8561531514415, 46285.31383960875, 50063.18299055436, 1997.4466553952852, 95104.92370938414, 69349.08428048363, 54949.35981274849, 69513.55166774071, 53893.44918112373, 40603.35276681717, 9634.377337078704, 79271.901590396, 41247.11910500628, 10339.687430330447, 365.77104531628765, 70845.41852393605, 15546.620539721323, 9127.961884505576, 16003.976380213315, 4060.992955047815, 82686.74673872377, 94533.07439152942, 7747.960036769452, 86121.80742173704, 67943.57713709102, 74809.73695691425, 73142.36942622073, 86666.99454833081, 63481.700824099644, 84615.37773841314, 96596.38045529845, 54575.57203917783, 85099.11681351461, 13152.758800158737, 24038.133008990324, 20683.450531703427, 60741.39434133392, 38576.83863531459, 11556.39783396676, 18245.251449098443, 29629.609568713367, 15362.62221505651, 8394.350259479188, 82284.52356642512, 96598.79668612004, 48580.88524008198, 21216.949578189924, 52517.33276300159, 25200.0297273528, 15635.409341252549]} +{"id": 1699, "vector": [65908.98511917319, 42490.46915948661, 182.63190572921363, 72658.36474885198, 5683.120363582372, 65070.01938912171, 89558.31186360538, 44732.64738583171, 41925.96395518794, 34154.96334915632, 60368.33467094751, 32418.727907848923, 66868.7880695048, 20162.41197284986, 72162.31303793543, 97234.83578776923, 39198.94119725026, 42725.19196584439, 91861.522580857, 80107.2017860211, 43710.76889216572, 593.7354890381607, 70658.30331121365, 27016.461561950735, 61035.18091661656, 47931.4195427814, 31253.771627040227, 42988.88197012762, 30591.80510567272, 2420.6628388617646, 36943.63529573625, 6947.933968173669, 23457.819108663138, 28351.50117804336, 46033.85798833276, 57207.575377205, 19532.898318395586, 2672.598438051088, 59152.90046710101, 622.2153759657845, 57271.510876065644, 38543.33838121655, 29886.748626183602, 64456.36120024044, 78409.41510421564, 53294.468361785584, 4660.334791859244, 79843.96841030219, 52644.06741016493, 27375.786011659355, 12595.949321266242, 92386.66996570934, 36341.925129456475, 62497.24776345648, 31466.47785994392, 65975.94418482704, 90335.8522520209, 53213.00007824218, 39517.240254323515, 43948.30400043839, 95969.38741752834, 24624.95293269673, 77010.73325043186, 19604.558942584237, 71046.30876914985, 13067.32155402429, 55011.33048004194, 67472.27884664637, 69576.45745291708, 91152.65948388247, 83941.68488385492, 40777.886523695604, 89945.42600580337, 63786.207302674455, 71676.42146311939, 18023.318620977745, 25611.669572385297, 94957.66626425224, 96044.96145502213, 48317.50681448534, 16009.466084640833, 41977.726534271664, 75132.21957666625, 83926.75274545273, 34920.46235631598, 51945.75109990772, 58529.97343058603, 43373.2461652858, 2305.9861233512556, 87088.46509320232, 43047.53375120536, 9272.42859566454, 57028.85773035499, 75600.97929274227, 1702.4657360846684, 50480.25149371428, 49640.30830991475, 30120.023269321093, 68525.81322228014, 72201.9916410731, 58337.46947664607, 41452.10648830177, 74526.68700778202, 82467.40410471102, 41414.26175259487, 76502.94769943511, 51953.1125808333, 43962.89923033254, 66918.33690650652, 43386.4828561676, 21164.116649391708, 32593.577922168293, 85621.7193878007, 14025.060071560612, 21835.995014123666, 47963.85146662895, 58789.68832409913, 7932.546641451377, 23347.232124128557, 97816.15648585791, 31518.21971619301, 95411.8346703938, 6229.8760367663135, 57322.42908966827, 9588.134275449867, 15082.22742331139, 96367.47644495196, 19114.7526535431]} +{"id": 250, "vector": [50549.08235511158, 73015.8460909989, 31428.450794021435, 41667.31895318209, 31074.61608450566, 25050.236919328527, 15623.589219651823, 66080.70799024477, 52189.5677877087, 21402.06452423361, 70956.77111166438, 26628.955569810885, 61589.39884666846, 18373.501231177135, 17367.244296138906, 76089.9264115886, 42211.97441673388, 27607.926238989345, 54734.241484522165, 4692.87145220415, 98725.91188586666, 10302.282991884182, 2719.57735373014, 87585.69104568375, 14007.30444494217, 77991.88208961056, 89344.09617988367, 91630.03261260231, 62567.014091138204, 98898.6035356629, 9399.202559039166, 64625.415189782296, 96079.01541408015, 74260.30620589374, 91844.79606882203, 44975.392366584536, 36142.60729175718, 64171.10199382822, 6121.399654202631, 53289.48059177722, 7397.87065757842, 89073.55250610429, 54845.623581594016, 34605.54331816238, 50904.65964397216, 7074.794900425119, 89907.96673480567, 14994.919651928029, 11261.706300342534, 73302.69519620274, 73899.65734363897, 35176.32150638984, 94194.22034877686, 54075.07687750274, 84087.76631177208, 39202.682338317005, 49760.760797521405, 49380.950506781286, 10954.614625992754, 80659.63825238647, 6534.090366675038, 42301.52364705318, 19202.90502354164, 24450.866320041852, 90346.37398009431, 71084.48482580888, 98918.01005136626, 17974.654823918136, 37061.688025590425, 61543.52121974792, 86690.97662517008, 9403.018954125819, 32748.507535908222, 68755.08108548347, 52046.5136724886, 88904.33547698658, 67649.31516237516, 27749.967646862915, 36785.51020368626, 54468.87472969031, 24338.086163494543, 30289.17874013869, 28215.364748665615, 99822.31816665926, 43430.420292507166, 77804.01273867198, 15563.78565902382, 58384.36563094566, 60259.193909326204, 914.1372117396851, 59195.69977049269, 18546.115750505698, 28895.107657028573, 88011.33857898555, 89466.629127691, 56464.63763143248, 21316.811744417108, 86190.7701831787, 56787.195298778424, 25771.864871816706, 9601.674541998307, 58950.36398325502, 38978.443713646535, 99024.39237666428, 7889.4103747845, 95360.71288355492, 88735.80888681111, 30440.166853425653, 55012.12338133258, 43262.09187711303, 50217.08840963032, 32857.32834202704, 58505.873481970004, 30292.908018044072, 76848.36690365271, 57606.85691265088, 15271.028196250236, 23811.990182343667, 8965.592263261724, 52234.08476052009, 36475.56679283281, 20724.064532258235, 20199.878882879173, 73002.36720881895, 8974.029365699387, 62160.89732698022, 19486.32150327053, 45176.66025039671]} +{"id": 1730, "vector": [73848.40321845523, 38930.31471595588, 10421.381418290764, 49855.11606411901, 9763.195722588269, 68894.61360803833, 32721.062085325582, 4091.8148309978974, 87657.83400014821, 76299.76864907343, 65606.63556279925, 69918.54770741293, 13207.114624898142, 32393.582744235406, 71353.59537586977, 90257.78026986135, 19849.579795461057, 15647.241128450529, 76179.69175027557, 4901.038510052303, 33443.407029596485, 40620.41634290714, 12542.12914479671, 67747.96670942871, 41684.77029239521, 20370.811719915728, 31543.69017716705, 98877.31633480296, 84347.94334157767, 62142.80821699997, 23049.02639989502, 78136.22262377915, 23901.60717768155, 50014.04529144633, 73646.48335852355, 40650.40381531734, 60512.58405053883, 63056.305307105424, 53918.05144587005, 85728.95379826381, 12253.098816264262, 29935.355696368602, 73143.08785769175, 52236.365168482116, 98172.45619145106, 32808.60693154533, 87549.03161844573, 50217.17056026754, 4282.4015735481225, 13705.101228075267, 86061.32033879224, 58591.75335575335, 33947.33361485139, 5893.48853940882, 77143.79490759525, 521.8257112216685, 48761.89723848929, 18802.094007583557, 28083.814708249, 75482.19581399344, 73317.11458616961, 46401.76137002059, 48264.045387484155, 76489.81441949337, 65809.5887832898, 13390.369496289612, 27985.69908405315, 8741.228024518532, 35029.76398838282, 57208.45364751578, 1077.8456777905453, 7342.00226857723, 64618.71932534674, 16211.468402407814, 74408.47628464534, 99878.71694065508, 50135.70687627753, 28134.45805345217, 13092.531386049566, 19140.848937158105, 82280.73648016131, 83648.59717547659, 69741.32909946748, 27755.452501429136, 14944.469677609573, 54543.88469788557, 6314.848282334818, 3761.5377969817064, 15584.345473886297, 98275.62972361676, 74890.32691573279, 55769.7838012058, 78584.77393670315, 15963.861366431065, 27910.81461473145, 25689.26024233198, 80337.67213504361, 60010.72943953933, 40229.66818418273, 6052.699931998551, 16797.59207786781, 59298.502536204745, 60299.16275921514, 93558.91995275633, 49047.89156010861, 43226.97043671109, 75034.35701496773, 65351.043943331846, 33073.478068771, 56119.95958311997, 89591.62801059, 53263.18440859272, 38180.51650570643, 16721.578885084422, 83597.67105553181, 79634.43711896536, 27110.823328948598, 42538.93172746822, 51639.578387068585, 4591.9630996054675, 61680.74798198965, 53983.7212430746, 77970.53366841325, 53601.64827291252, 56555.24885911759, 58154.924856005906, 39702.614564447416, 67730.73163649855]} +{"id": 1198, "vector": [69842.9378274169, 63080.80673086619, 71703.92628132126, 33169.493154619326, 74590.95037355943, 17286.130004514576, 18988.656365669864, 95533.45802182394, 92059.31356332959, 88564.15319763887, 49826.29227089803, 43602.390451776904, 31714.263060573656, 74556.85164641074, 22833.42241832672, 58665.38200783219, 89438.31890473541, 53838.63969200451, 46973.61807412453, 92068.28181999213, 34170.127526453376, 72801.30597839535, 25806.454611712783, 96003.58424723214, 39825.582765174775, 28478.840281983965, 65127.237815018736, 68873.09501289375, 38118.03530426393, 11397.757344769965, 57235.32786086777, 92487.21648922657, 62365.31148988531, 59213.66090500912, 5338.215853534612, 6521.4732401691845, 44264.05500081968, 69461.77560658558, 57510.02900171752, 16069.092342414915, 86774.99431496959, 2503.543659509422, 28006.11848922685, 24405.83335817016, 67021.7990779295, 48010.33831355509, 18530.783921070913, 45083.79249684552, 97616.12791637813, 81631.29283344037, 60377.826237988585, 94408.52402039163, 85236.30175327821, 21766.9403528847, 5653.778322007852, 45333.61953631905, 37392.35061995781, 36444.059495984, 62427.37803544512, 86450.93239831307, 14696.570152726084, 74846.42571944607, 19732.936243011056, 95169.99718419633, 19789.342483095952, 21095.47984166755, 15095.837209083751, 98000.2633391523, 24837.430396422533, 9482.722979983093, 90591.68109156008, 81701.37256500138, 59214.678760630726, 37214.421470459965, 20868.648783652967, 36762.88165000254, 74065.7825021279, 87624.12169503805, 2456.328421759657, 34855.72133045036, 88632.66618036164, 48072.00586507851, 16637.007660460356, 69387.74416007104, 83513.56597121259, 819.3283423398756, 65011.83004832335, 47848.3012145659, 4105.544470220857, 60593.2593377264, 30838.504349643703, 49826.30330071881, 25796.079778778803, 93168.45977125021, 46824.83685143432, 11959.911822302249, 16309.591880640739, 61980.02229967379, 53863.8704264053, 61376.65758732271, 30483.310023193368, 48619.07123461917, 27334.58883737402, 34799.040834877735, 66419.00220677082, 83924.36496538582, 93940.99270966939, 55337.07049883202, 82674.671136394, 51081.38419138553, 57799.97104117516, 30854.017060453265, 94351.08562125199, 14822.319171970355, 36819.440983651184, 44048.6624859898, 68009.33543584384, 45056.23270718286, 55215.61590584389, 46151.36343721354, 36531.07672336242, 3043.009275311215, 731.8375662384469, 41700.93307100981, 15118.987752807967, 40751.964834464416, 85935.40732031527, 82533.82379572664]} +{"id": 49, "vector": [35147.824057305676, 49867.532991284046, 91481.44289251453, 94011.54186287014, 83828.76293996378, 81755.06439691626, 15069.85959942414, 97359.19731422502, 5757.570054227134, 70935.39561932029, 62722.1241710328, 10045.660264329548, 68502.83874245515, 60230.86401629519, 9306.473164029561, 28156.98751738209, 27769.013088208772, 99979.07837320995, 39845.485644008135, 74104.85142612699, 337.5258290623018, 71988.9922039699, 93588.94078367313, 61487.41563877032, 986.992194431835, 48025.34732980295, 32689.208895692045, 76356.1548546269, 7427.1491349540365, 30200.999241307123, 4754.606310200382, 98750.33365015588, 53923.16782936923, 28458.728431605894, 89604.56479516125, 29979.465935229422, 6335.359976479215, 93799.79544647517, 72320.65871294246, 73338.5702883414, 46159.20614842517, 5996.455556366153, 11605.399650565429, 25192.651073810546, 40389.09193008022, 35706.64606626383, 58240.28753858421, 11690.069195673235, 56262.714739573836, 47059.27385084152, 71938.81881474076, 30376.885602119495, 53896.55562656815, 941.1444671983404, 51246.80761479762, 75384.0066884659, 98435.66059901529, 33747.127710962675, 30991.994934038015, 91854.47748100705, 55420.93529664384, 91352.0956070505, 37051.142605985464, 69920.84425049512, 47244.378766447546, 58661.5199803169, 79315.83578719161, 5128.154713618216, 90248.37960230611, 6586.968131194748, 99575.33567975213, 19754.933473442492, 12252.900176179694, 63802.049560745654, 92390.84859003228, 4083.58851218924, 94196.3643769232, 27845.922063439244, 34244.54558062385, 1058.3697374019873, 58150.100738182686, 90271.3398856554, 39917.9579045798, 60371.65874824441, 43990.851852345004, 29690.970505465953, 52973.78216595621, 14203.911128236958, 22277.958644140883, 69004.55453085028, 9077.189909658955, 76623.92531775434, 89808.90463969651, 17627.746808645996, 53232.26044409213, 12922.470570680889, 48558.952475703576, 45079.221028834916, 94552.85353094262, 14064.895041582404, 6848.308196491493, 81093.56637975811, 6680.5950861820065, 25118.04268456256, 41602.82921185532, 75349.66788988824, 52109.45519661449, 68255.74494038941, 87321.2601434271, 79406.52670849023, 30860.760567569512, 47990.907951415575, 88215.63497515359, 44692.511555202655, 2528.0624518275617, 12736.809836169505, 91165.77286807362, 63593.98999477728, 34172.11599101371, 11062.391966475383, 41241.25619307381, 66851.84540594889, 86655.13284312031, 7480.283375677088, 87048.60149539389, 79142.11040950014, 61563.949913849414, 71789.14495603937]} +{"id": 277, "vector": [51322.706325351544, 14251.064517125456, 46606.92685209487, 50391.3107137817, 73940.82003044858, 14996.390805358917, 87785.64562583122, 91759.55727587604, 44839.17565969995, 16625.847364873323, 90856.77169904821, 24872.64861716826, 51779.33025899453, 43875.18785510747, 85188.25799576966, 24034.640788764926, 44811.71060533559, 6075.873871647031, 64814.92431928657, 70515.64975592779, 39491.09388193196, 33606.82921190531, 19787.709172256316, 45321.70716850507, 7532.604555772282, 34035.30881353096, 48343.40793856016, 16290.229177074967, 66029.01647902941, 53754.386280031395, 32538.153080299548, 77383.96885004248, 79723.1934269253, 33152.52957949174, 58977.699885908885, 62754.61216794321, 19649.34638646152, 24609.002668190016, 43062.15380683913, 69908.71900041567, 79102.87049559545, 13566.624500814283, 54069.405206288226, 39814.88248459159, 31409.087067168995, 8515.628921191243, 95206.17076046152, 765.2637916320871, 14328.846175561504, 3797.290919532226, 65456.783028292, 52771.19756976528, 3278.8269550104496, 21952.655222082572, 47139.15406080075, 46265.6568266273, 47344.28253735342, 82692.82842444748, 71411.20205241622, 66120.17460017659, 75179.11161470864, 90735.59954644012, 39508.73160455283, 30918.69116923378, 85181.1564463161, 44022.55803389406, 12445.61326598772, 77370.47850154615, 77483.3296793612, 48556.59288652029, 8529.859520603644, 11593.026012328999, 31230.360176931372, 58743.529173160736, 28748.876092902454, 86335.46784405697, 39594.95656574733, 36838.08946820532, 78418.41125571892, 6058.2600393315315, 32051.880762969464, 6404.487923309665, 74290.09247887459, 48232.832876304536, 51906.6082298302, 55709.31069362347, 78804.74804107832, 13618.284670733672, 41273.84337223541, 43194.32852723511, 34633.59854792397, 98375.35490946555, 81026.85307420378, 15670.893215128912, 75761.09904361336, 65645.36358438052, 47364.3183308868, 21331.192470242288, 39841.14873024866, 72001.69890782112, 57450.0906313572, 63456.286834860606, 2973.012666943087, 99581.49463418656, 90942.19283005016, 29005.60537597705, 19504.22195335827, 96009.55134870189, 41626.40335921757, 68918.69530061154, 64051.276200170534, 62713.18363282114, 8436.197920551003, 70209.23938462975, 19508.21851027621, 21333.290717853582, 639.8978914793352, 925.7075723372088, 44848.68239269133, 13167.976621472277, 38589.981974158734, 30521.820665824085, 98836.67063303248, 39081.29799600906, 42009.21038064938, 96676.49820708731, 15990.015459903705, 60597.70887948324]} +{"id": 2108, "vector": [73072.62508478401, 93710.27797041343, 6776.1584856181, 59472.38450334821, 73565.28059579455, 4912.199714090082, 62919.462481425624, 2554.7170010745467, 56992.7629133307, 45462.05566155042, 29028.228753214924, 47963.03821546225, 37362.806747586284, 2844.285387458423, 19633.424452450963, 46560.598172416954, 42560.10223871622, 65392.76638299143, 64228.45240046683, 83651.88607899989, 54787.65798113197, 84868.53199512674, 27766.718513992782, 79732.81459772751, 8245.469969850872, 24722.45668778398, 33775.86186494996, 11908.080795076514, 16645.31536978204, 83667.16108469709, 96438.99029609913, 42631.619430926905, 84061.71596854189, 23018.950865056453, 49180.88678725099, 47134.94578132916, 63010.974805176425, 39303.870619601636, 7344.289676814908, 16330.089835883999, 55076.89366303353, 52424.51171594994, 20727.64143494944, 16254.522631302925, 70166.28760927945, 27803.17949099518, 40267.56066957167, 52518.22979342683, 96614.55187718401, 83235.95078175137, 7688.097495857638, 97570.7875452794, 58190.14399732614, 19672.200089130933, 65785.96272227325, 20930.247913077783, 73277.15784258598, 45343.57459207122, 60037.69592852254, 43152.201216680405, 76329.08965961215, 35272.18022092484, 77903.65504984558, 19471.150312971662, 41165.65375051149, 31625.692931201145, 2004.9438472129143, 50250.708809912845, 37040.62499662434, 41429.34793403826, 99255.93111478124, 9709.322784272, 12089.708194743864, 2867.0666545314916, 62024.540528230056, 24678.29468318542, 44480.60923517446, 25713.921504022364, 94043.6699573262, 10441.880879252209, 68026.70620725976, 15476.19002957782, 92029.64204058274, 22571.82172071507, 60491.43615786131, 27540.20653972111, 18350.301147412785, 68115.90162530832, 30778.79606340559, 83644.51251585931, 44404.617788407864, 52786.71251952578, 72263.78321837036, 47443.237409210415, 48724.37083562283, 87560.99696194059, 37395.94438709296, 36165.11515941856, 84909.42752690887, 12725.325426385847, 6242.877223086052, 53866.95888536107, 83750.00244041487, 81155.68495421195, 97873.41218458729, 53039.92647531199, 96506.71894955823, 6250.00862242171, 49564.034142603676, 5139.530305381246, 34038.03442131732, 1882.488032652385, 59705.92945487356, 55028.19840852884, 15437.025290022499, 42010.228277103546, 97155.3735818856, 55655.327523685395, 54730.71721598603, 56191.29062437548, 97602.6196574308, 52195.03426055476, 11233.713124097532, 90173.67106071863, 15353.228000210684, 95032.38212447298, 87636.98764958128, 54472.991901787915]} +{"id": 846, "vector": [48455.31562269788, 39262.225931294684, 91028.09902675552, 96396.51372324438, 90669.77433081862, 91003.05589547857, 75389.17050823708, 74290.04861390783, 71859.63091497621, 45272.6944243501, 95672.09700127483, 16270.3709649195, 51741.88856519197, 49453.902450979534, 49247.42627485049, 82123.78416857419, 8324.912407466567, 58175.12437455471, 32527.464212483414, 16792.53432425415, 94519.84584601299, 25478.40998949562, 8544.589808477065, 57956.15917630323, 38153.27682693423, 14224.638465655282, 74034.00368603921, 68270.03873200655, 98643.65348931335, 45703.76034029664, 29747.359512455852, 26348.81752002316, 48140.62089399349, 31302.076875073115, 24533.769867022194, 18440.413899000785, 50868.05511263123, 19257.56305118317, 37363.92532315676, 81545.70170169917, 53267.49895828757, 24664.26452808802, 85360.93303341443, 57447.8329681405, 14941.349595207997, 36829.43337582297, 18519.120923338716, 36063.453575352054, 69631.08245644729, 16621.144104999854, 26200.052335667257, 10722.36425619354, 29429.87914512035, 56470.14702060741, 90289.36426788269, 12471.790621911594, 64395.10063257555, 90413.09747194086, 71962.20663987906, 67106.21416937717, 41493.32332607862, 14166.552823266731, 54642.60420985231, 22969.874953221824, 91257.16204987375, 33356.9980530092, 6776.472548155787, 15138.508931091987, 96190.69402091598, 40028.161528106975, 14121.920738839111, 81179.02464934612, 97945.91295056943, 60248.6592995126, 28292.17466891675, 54514.89802954661, 27755.129456289797, 85250.89297532878, 87492.36704269623, 19244.924315157663, 43684.784873922144, 19982.78146639536, 82526.08367119778, 54776.82515077877, 60466.76119249147, 33640.7410764718, 44348.64468033287, 94335.00930379449, 29618.05047245094, 17990.727282826647, 1970.327027908403, 97745.84106956447, 2488.9114925146005, 84952.12598248528, 96450.87410915141, 5347.864090252774, 17710.76917173663, 21328.87536009165, 15524.129126529973, 26232.994743588057, 97096.97634266924, 6206.746868511958, 58704.42721007818, 59051.48429636726, 16109.376819149767, 72123.43516494882, 77546.51866884544, 17673.462720016865, 53367.49819299017, 3660.309564950093, 85107.30417084612, 45919.53177596471, 51639.22254119483, 4217.625401210079, 22473.246830956505, 34879.0039951261, 18412.517785485295, 22091.59997147793, 81588.73443391529, 86115.76088495828, 66884.96214187887, 52409.763872515716, 66317.35480591799, 74073.71667245675, 60052.554804379375, 95043.07762081064, 8409.39300092025, 99923.37812933483]} +{"id": 1428, "vector": [17448.920643119058, 14684.824490336568, 69017.80699305156, 75227.89102760403, 45138.8231763049, 37212.15713829569, 93337.42334800371, 84275.19982245401, 92531.18924030192, 66802.05707587682, 36412.81102746087, 46433.32735324604, 36730.94078997712, 29417.784992090666, 39354.48779588625, 47243.07861345957, 43755.73513415149, 50235.52534559489, 97032.77276234707, 43909.95526326964, 76474.82812108463, 20913.03878754397, 67479.31729653217, 71030.96130476968, 98166.0565471283, 73102.52834294587, 44630.005820335115, 19904.85929274415, 72799.59957503904, 14930.926346659257, 26050.046113711735, 26314.205962157168, 25287.354446771704, 1677.0019740851683, 29633.930842652368, 19514.969362658176, 87552.82001430185, 77497.10217104049, 78539.15800124568, 55013.343384377426, 61241.38127772873, 52267.10180140518, 28784.588422128134, 20538.602759553083, 93141.29288687104, 76613.16571576644, 42821.27699585454, 55270.21398904201, 54143.978826130115, 99523.81998324116, 46483.16012463511, 48237.33341991335, 3911.6047463871873, 67727.76288424162, 10372.991493929207, 91285.49151839205, 92471.69249312698, 64152.50587045292, 4854.912297258862, 29766.25608494161, 36867.44697140608, 42370.11518463435, 81494.57588963627, 28628.455618380143, 30439.679932788833, 60849.736065807534, 81520.6202476738, 42231.55688547206, 88728.91839946057, 54507.824293082194, 75246.63784732365, 12232.418213531737, 11982.551676729436, 63429.50310376425, 44262.030311082315, 78488.36311814509, 59429.63550104595, 33969.63991530412, 85050.05943870156, 92713.7162881958, 84733.97196440698, 6789.385430231554, 52027.84439932986, 83560.32984401888, 41902.98717089067, 22410.489273844993, 80719.41066830726, 95935.76568490999, 6999.774012631188, 30537.784223965125, 77254.02535449226, 50013.16070861596, 60317.97315658354, 34640.30802356577, 85937.76956912402, 91720.92600187707, 71862.91935205016, 15017.40632208387, 30638.74777861362, 92900.68170623001, 8522.717782635169, 49906.25519282191, 26854.179690185854, 72597.61867831262, 78591.31302782307, 41271.26573622022, 46826.96719467031, 25256.83807510293, 34112.474711033756, 16186.733974645906, 76893.28494181487, 80191.20200430088, 29500.246119491734, 50323.10221608759, 29291.228595200835, 90056.42023828179, 22734.378244843178, 88746.7959138256, 99676.62877886363, 25176.977363995724, 11013.041258982581, 74244.7794208947, 80868.18814397529, 84353.35736646812, 65435.96481985253, 94384.26665974622, 97285.89330747406, 28435.945525923413]} +{"id": 1401, "vector": [42013.92553874913, 11031.30963545973, 22591.6065560268, 66449.74365859016, 79024.8445669453, 88927.16452711404, 9562.831727374143, 32756.38591427249, 67591.06182802364, 97483.29754404761, 15203.925873917779, 21800.41812766047, 31567.497236523835, 54364.79171760448, 17327.474080272652, 91125.55825891756, 1260.6974162082895, 98281.52348302669, 70569.19616250752, 55554.31133557188, 88881.70029625499, 59667.66652855113, 58429.98628990096, 28007.20184116112, 4459.527778894346, 1570.5670547738416, 54299.7797634742, 65344.11502670179, 54122.19394166724, 2790.5417104460817, 36774.2436803091, 25244.96326840351, 23196.125029029525, 98275.35963609163, 5423.233394007232, 9450.659996739718, 900.5031336822622, 84574.78079016691, 39719.920559490914, 24877.96320465926, 73296.08124408976, 5319.762379593407, 57600.79394626457, 39125.1775718899, 16455.199667778197, 3863.0873302042733, 27211.8533372919, 57796.41074733227, 75350.98597253901, 18488.3173805059, 44095.54470671195, 44867.85043860506, 99606.28875217031, 96419.84660362678, 16067.99748152391, 34176.01321688146, 89118.04213268236, 33239.43447031476, 4598.390875379832, 48254.82007043215, 7938.743679339544, 15244.13258140136, 26234.679311951404, 92839.81627969077, 9716.973308339682, 85668.53227289482, 93916.25616141716, 78339.68881895354, 16766.85447019505, 48212.12900955079, 21811.18273477357, 88760.343187503, 96546.02723995858, 11890.516316158672, 63677.51272871529, 76747.22392972252, 63592.4245649097, 59548.08030934836, 77176.61977677037, 49535.28654118076, 67141.23668693464, 15978.716875295295, 10757.64428973911, 72768.41419600589, 62920.9819865896, 64874.08483792824, 76787.77802205228, 5554.4292372259215, 27420.27248037626, 19467.27512041153, 1886.9921502132959, 10729.188741626116, 46407.79891357205, 80440.9827218383, 5786.90849442709, 29995.357742041197, 18333.71059297354, 17803.74258780303, 49848.4736293843, 40992.36297239287, 40404.8132493245, 18685.36092920179, 35126.543764097674, 84920.85330921259, 66777.24249092065, 47842.63042175575, 70633.35777382269, 26609.529629826877, 12161.454429655383, 49551.113830520924, 21341.924908453104, 88257.56697129563, 68734.29828114217, 65390.21184123321, 96437.0937541726, 84926.23020485773, 45766.30358858974, 38834.15533355894, 10715.820851928493, 54217.09984985148, 84009.65340519566, 71983.07874419416, 72938.12387248044, 60893.76970874775, 40399.20804262531, 62796.66228491286, 87517.74661344937, 90525.48094407428]} +{"id": 607, "vector": [51496.34279118935, 24262.43878839679, 27658.25629107326, 75651.93908225624, 38484.56034991034, 19720.073510325277, 43209.94775843061, 27102.347063676192, 77203.84778288915, 75604.87100780506, 53148.963967877695, 56513.330298481254, 35784.06391283944, 59799.011364942264, 48885.5896343159, 4434.434980058822, 50309.76861465213, 72790.2855788338, 57811.100006758934, 53136.08042907727, 35542.19070774688, 74413.00992005524, 21384.85736513477, 64535.19513161959, 72305.31105676727, 10613.580677679645, 41557.616883760325, 91891.07784457481, 59270.5611744594, 47350.31063797737, 26857.25133256127, 32235.444257307543, 41549.33059806071, 49327.4888856516, 16716.2485903684, 18613.05697033445, 59946.90153332639, 77603.18561601028, 85068.96248348485, 26208.393403243124, 55521.155018227284, 16328.074278751381, 75568.2404757337, 2820.4427746476244, 9062.731510286538, 69948.46219685598, 70341.12008577478, 33673.39084018244, 68105.73177877895, 78202.64405915263, 7048.510818160303, 9972.325598973697, 3716.136802564685, 47226.45583011292, 16400.667891560228, 3728.2075362864543, 75311.02176697468, 19318.954230047148, 25047.97363103931, 17440.78810095411, 30480.528748814984, 20658.53282161718, 35397.86578256262, 27576.006148589906, 34624.529480567726, 95366.77658271819, 33216.724182336264, 71723.4033721977, 69410.12197350261, 59383.409137323826, 42247.30557532498, 3084.937414501332, 11416.176708350968, 22725.648435138944, 67919.97901777511, 12341.739239464821, 40339.433682429604, 47361.33244721312, 29260.00080642266, 15664.454828252938, 76853.0959065133, 95382.73369425148, 39375.50877055632, 34640.27423873052, 60888.08681231869, 3596.9080763668226, 44624.2463151597, 19764.396982388764, 50764.10075643518, 39267.21981970743, 35039.70170706194, 51589.82294458927, 33525.145266888714, 91099.81683915657, 302.20157459533993, 35873.05010438946, 50647.94521057213, 47207.29346611807, 10399.078123794003, 31078.676195007793, 34042.503824404856, 55435.75139224094, 86294.07466779089, 6889.029667723267, 24454.50223218666, 39697.23751875311, 97204.334708796, 40655.58230642472, 67185.37966474418, 25420.944495528096, 16724.082101179218, 98180.17596183736, 51144.645773819306, 25345.765710082058, 36145.78890402047, 59764.302669993886, 64326.60056391639, 42098.08032268591, 65493.824577763226, 73561.30496009551, 32499.965549769462, 85452.82322476443, 27315.194124446563, 84591.23298873617, 42603.79996300381, 69933.83624073086, 41478.24575957969, 62656.47171611279]} +{"id": 1373, "vector": [7218.430146807031, 12604.029719034903, 81469.34474641467, 28184.76422837706, 8855.831303602168, 27779.258161716723, 14769.568037426561, 10684.228594400991, 76679.19334627286, 65963.6368846739, 4196.291683392317, 8593.852453327321, 40195.54633386049, 7146.0709775518, 11587.702805504374, 17307.147685322165, 42723.2065853762, 6570.598739992705, 50394.78278321239, 37377.26257330757, 51604.07108288707, 6186.990110724255, 88157.61771799574, 13784.510663673922, 11915.085128573988, 22543.75781862705, 76810.23697934962, 65888.53015774193, 69716.11182187166, 37499.536437178496, 60801.63954194589, 77065.69705177085, 62521.2702892927, 15662.351115754236, 13734.979136629954, 69801.29537851425, 5362.105221125846, 36144.30104051418, 50119.41730629965, 25070.076072289838, 87948.18860664013, 12377.000148703986, 16883.24690288742, 76320.2448924033, 25501.48573936882, 40701.14021500003, 55544.09557237928, 40509.04957121374, 89622.44077034152, 23543.378935859815, 30081.861049963478, 94757.46576246338, 56843.00292850024, 23823.91071493951, 61280.82387033793, 39247.62935204721, 59717.98962524798, 42923.80994400694, 4297.078271218169, 53597.85253648669, 43514.45165070903, 86466.3095237503, 82975.49607324591, 85796.5153881029, 66000.01302768724, 63309.51069532238, 64164.39980020321, 15508.725512275678, 47640.04880803947, 58144.795669585845, 8343.90230785549, 62104.89466945568, 69407.02266099819, 77945.17114861602, 17935.60645407487, 1843.8608379090615, 19475.406595741373, 22838.865347576033, 94252.09385014125, 67258.40168385887, 74895.04704595235, 29181.252898616007, 50109.6568971109, 36119.94510170617, 50838.84877406888, 49638.30163424663, 23899.36256082623, 54161.462471404884, 28786.61539848216, 13429.692958917183, 89091.31054759123, 52360.70405890835, 58471.714055054515, 22980.5927606039, 43620.40267244647, 39036.83558027784, 31102.54444866407, 50096.20146335998, 34404.38166662935, 67709.88919940274, 40501.30164282769, 52270.10490936179, 17133.74578671404, 28633.884097160244, 65319.04777471255, 27216.00942967093, 61204.452511343225, 42452.62353382386, 77915.8623707345, 15251.135493338797, 7395.106347471625, 83786.69332662904, 30831.762024543186, 19298.042097194168, 65030.05847232909, 23265.656558279636, 49235.92795109962, 26840.149405794855, 57134.45525207139, 19234.495279943432, 74469.63762727083, 28904.629204911012, 87089.02925737284, 70034.30623078973, 18397.333234549806, 16740.499988970914, 53758.01296114564, 17256.932136581916]} +{"id": 1414, "vector": [52310.150703281, 23951.191156464814, 71629.69092247158, 96043.5347229317, 86779.42200092744, 94217.1908183299, 94119.15636522225, 29804.121104270387, 5246.607429516182, 32824.78740269622, 20574.932664255386, 77697.83684355048, 96163.14876296112, 55488.43508251928, 13214.253226532492, 47983.21094820861, 28282.22114701675, 59548.20509617794, 26182.04212140621, 37195.768939236485, 43018.65576702277, 73616.50453905198, 4993.523739969152, 69172.80612704338, 44747.073109006094, 88432.66223919239, 53422.06767354265, 90171.25172869215, 77.24900134353786, 48774.1397928747, 17892.45733616449, 3899.706419677107, 22121.6624526555, 43802.70898143621, 1394.252388699835, 53683.338952297185, 69336.01280511032, 95111.48065838251, 17108.483304400666, 45266.870548926185, 77269.02792457442, 34291.70938565518, 73195.9822525565, 59074.54543907444, 64658.91757078607, 4855.893677698564, 5039.491574347643, 26886.904484550443, 51081.06036156017, 33086.889582781274, 74609.7287666919, 99293.99542618857, 48159.48567408475, 38662.44618111582, 96331.73183551087, 60259.9894374399, 46500.62137958978, 46858.99829674324, 1347.9332084887828, 53057.379846441785, 73817.11343139285, 82680.51410456994, 65800.37725499737, 87148.96098988829, 74176.1714016833, 37183.495762630824, 1813.7632950367188, 63390.34803056416, 27061.707254427314, 29448.30989638264, 90480.90527330857, 80302.72178870297, 43678.895659344664, 30143.522586767947, 13427.604097022084, 96113.8501268283, 98207.15445706992, 8336.98596550918, 53930.79145054971, 67627.63860527711, 57292.05937587114, 57898.12630841082, 2542.34615641693, 72904.40619571175, 85101.09314293723, 6670.95772809807, 63207.40474138384, 35611.69770984083, 20175.696586727197, 86307.47299957549, 66850.22906229687, 83934.00103354835, 75815.10343542765, 47163.69367673557, 35642.87507376987, 27832.04652102084, 22743.84165333494, 77315.1511304488, 38548.214417605406, 55025.30241940563, 37785.859584560436, 29035.060421393864, 13222.606740249354, 19219.801539608095, 93088.53284665945, 50338.94475106216, 5535.710958914264, 76079.17749542303, 5714.832137318937, 69437.62629088524, 33043.9071048201, 98994.0429162836, 97802.97198329755, 34462.57232241938, 80434.97057614205, 79460.51352640304, 6428.712532849401, 1358.0210536028314, 31297.882224581743, 22001.687324723363, 28074.455525164365, 52452.370414936, 16667.00059530388, 16560.04353162357, 24392.78878192922, 80920.45925822278, 73964.65838355313, 89066.0331359026]} +{"id": 898, "vector": [2053.422526531423, 55547.25258591441, 63170.561906233845, 76580.84800936066, 42841.46392390843, 61435.55368026628, 90532.33967727273, 23543.600651959252, 44287.20190506886, 29987.10219157278, 72729.33845955171, 40526.406109004274, 68946.86788761304, 79813.27867787643, 22795.637949238124, 36552.026834231314, 57883.60805728215, 95124.43678257216, 27757.358941030365, 31376.17378373887, 21375.054254826064, 14105.896286233788, 89344.21923440702, 3887.280061370635, 7621.157358769959, 6822.085428689573, 78758.8457875039, 32340.09641248371, 3228.0726749404075, 7334.023396424683, 17116.431184236648, 84541.51578295791, 59359.23655533043, 17103.431125976953, 93163.67993044491, 58804.57284850185, 641.2266563370572, 67173.5566605734, 39572.81172833359, 90474.04061411115, 26627.468260210764, 28908.739913870217, 12094.164773841387, 71920.92588615752, 43089.26307096144, 97040.45292535624, 65925.56789193914, 40827.35421061722, 58213.11175947159, 99501.89465973372, 46242.371418179726, 5820.226466128275, 37135.62665481513, 60148.08078068239, 58759.485906195165, 55854.149768851414, 63125.699879690466, 98609.77606312213, 10752.818740099712, 60350.11247578071, 29775.77116232636, 87182.51399361785, 17253.64526952621, 15130.89614900276, 6030.6921719388565, 55920.86231902293, 54670.363129448306, 7809.420953829782, 19455.35411082716, 64755.905228129486, 60557.599287383666, 81363.31253636786, 93748.04120352666, 10390.57332525911, 34681.17699854828, 63756.98226034005, 27895.561898935706, 6868.144071119764, 75169.37156771359, 50799.90933534733, 23389.450567616655, 22167.800149108407, 35967.74062916315, 29710.346959749244, 12159.737763384072, 53617.89936567646, 43095.788904909205, 24719.812131904127, 54476.8823061405, 30638.795654984464, 67007.68275574711, 30924.055823734365, 39046.84398916134, 77935.5949945571, 58843.047667523904, 74536.1615976776, 98564.19259683258, 24787.47130766763, 86658.35185932781, 98495.73313180335, 7513.0225008614, 89161.52168601216, 61677.51560131471, 38565.966279901484, 44342.468659391656, 9815.200091312005, 38470.24579228927, 84756.15401487153, 30868.420883516857, 33091.288204207805, 34649.06760747791, 75185.00646146084, 78325.64776051116, 67570.13990029036, 23208.990144099884, 36186.81094928316, 52957.6725004146, 52894.77867033387, 35790.25420084838, 43768.913252036975, 59373.28608802629, 17072.787848454172, 16113.182508717595, 45278.16251913651, 20824.800994483816, 79039.17822561086, 32291.153548710237, 80280.73486550304]} +{"id": 190, "vector": [13377.590693793562, 25448.250944536754, 98903.0632282085, 218.38823587086998, 12865.43066835485, 62419.45031072582, 32888.011953826535, 33567.23579047652, 46122.65773465545, 12933.75953422622, 17992.397519231396, 89123.82346929747, 21017.58917128902, 41955.66771616143, 59482.450654494576, 39975.91001345211, 54865.66886415212, 51752.52414983547, 79695.68904073829, 29974.871855798145, 48107.79674175971, 37345.41064921617, 86584.99577419854, 45676.15356759043, 60116.47874473787, 67388.10749955349, 66599.94221664686, 29253.59593024324, 83325.37470886971, 46318.07611458729, 97425.06420612313, 32093.304499078567, 19960.1686464018, 75674.75899825494, 62323.4623679782, 58011.83188513338, 82508.88119251838, 85941.35338041582, 7336.786457128186, 82712.24547081972, 87370.12466187985, 89964.86667189159, 75144.87694807214, 10338.97921235335, 3415.404252075149, 95015.8319374624, 45941.13200045184, 73496.43365066066, 14237.225964240895, 25477.23962089462, 80441.32983405475, 60012.787735365426, 366.0317649384903, 38851.380077694106, 36703.40892143505, 52272.561697937526, 7421.542425605954, 15641.193549606558, 83638.78708251065, 88034.53874281266, 16830.53739256525, 51117.15661839117, 3887.660513372426, 29908.259060634635, 77484.01791062875, 66632.93892893301, 90233.75327747963, 83137.84293559726, 42682.741025625524, 68450.11418448285, 90383.3756128628, 83439.07913778585, 42091.63007224473, 91174.36204990043, 27128.415116415494, 93440.54589432747, 64353.325413513965, 1984.096247059164, 41796.39107626502, 22936.72151659456, 42399.21444455542, 91679.27418589043, 1468.6201581392222, 31607.734849605196, 5908.290603351729, 83973.9220140007, 83688.1052419833, 69689.75761862472, 67124.71693974131, 47300.69677968233, 58261.16399991787, 32551.544210186923, 74824.25280488917, 70173.16578215329, 85872.94683841955, 22765.65190518218, 948.2883699877153, 22089.507714967516, 57861.49943913638, 83389.78043060306, 82547.28110893797, 92354.76985497487, 47780.7312727688, 76199.42161512627, 61121.886067281084, 67060.38237133397, 10899.920560167875, 84979.82298792955, 87301.45929817417, 11081.912195140376, 68729.98234622357, 37909.49725912608, 80581.3930535351, 50689.27444444889, 69979.18396462176, 78880.8590923388, 24074.98132478152, 46385.23918025144, 88550.54977125335, 23489.89692276503, 95257.36544819573, 96337.5192071925, 51513.7332221295, 13585.789776424606, 31531.993541553027, 85888.81002617304, 27012.089433074914, 98642.3030036171]} +{"id": 195, "vector": [60306.03237320676, 71840.39709858362, 53242.45080800408, 90330.60500953464, 90549.60295391112, 62259.08804555409, 94823.24993818316, 17400.93436418544, 64554.46827678004, 58187.5424814437, 81695.80319572258, 537.7110163532927, 81944.70569788698, 90862.20707067783, 75252.43445759892, 31510.67967566645, 97352.9199316358, 74217.79634108933, 97436.75276153102, 27596.396125940915, 84422.3272234784, 45956.57696605042, 70156.57647635892, 23022.69698248217, 52090.10013023605, 60532.93944996495, 44848.22323954794, 74372.90460662967, 66327.97790821102, 28444.435330255546, 53449.02784388643, 61185.533419361695, 4848.815477794721, 76305.3832152868, 65560.32254621784, 1041.8464282189955, 12703.619180121328, 6159.087789729545, 95728.43941495595, 35926.704955775815, 35864.0979527174, 64677.86017636403, 14922.71682427896, 4377.6677238709035, 74087.56300131133, 41426.0236160858, 98587.57061105709, 19195.702939313796, 17318.33703182487, 67801.68567467578, 82013.06674274248, 56267.97600080044, 76138.57946400382, 44363.574840959875, 43561.671323184324, 4837.019596534631, 22801.901883568953, 91602.55533849722, 3517.0161555751365, 18665.28338282203, 96291.2922949029, 89491.44006599607, 47606.41363068486, 3535.7771244762694, 24768.765753544918, 9833.911663198269, 92068.79219101346, 98366.0210207515, 54469.748266457704, 37262.58328726932, 97556.39477374426, 54110.65534921673, 13914.188700133634, 40793.07518537728, 60897.53544739993, 16595.599087475333, 66733.71127615488, 6334.827838951662, 65142.37300908863, 1841.456302006006, 25851.506152865, 98629.98329587479, 11677.83138626991, 63257.069387406074, 87813.7470347693, 85895.86688449726, 15842.940648562986, 75502.70013130583, 61484.73840876436, 12119.599774440738, 39239.48886469011, 60192.56535516943, 74244.04569791311, 50092.83035189478, 41418.67088153446, 88829.07120247843, 29095.495934257375, 62941.486458593245, 30583.18350089404, 65045.63116506431, 84353.11129897377, 61241.27802771312, 52194.4339070036, 45362.38927797469, 70447.5265036163, 11589.012386547425, 47997.6099068974, 9189.001709602484, 86061.66772815127, 66884.63964499414, 19676.55093407514, 75549.06008061577, 63012.6914089979, 29210.960767794557, 52678.14593995342, 59193.001641577284, 57958.77374340953, 33795.82787908892, 78650.18197655011, 30583.825167220934, 90520.76213363439, 57301.0388197081, 48708.48051320348, 96455.55623424337, 8043.060440854865, 41878.19553820184, 48083.46413591782, 23812.863188836196]} +{"id": 866, "vector": [78921.49748667153, 44237.332077350744, 40544.247258382406, 89287.4450763056, 47326.589132550165, 48239.68615156505, 23592.614800111154, 28957.395587380197, 5207.418316838508, 96100.65086609493, 82245.8515804704, 65236.15389593225, 63741.070997542345, 71041.61169187131, 2742.979916477728, 59585.59239362118, 64795.77842000857, 554.9123255022437, 87842.84793967189, 88765.67055696019, 53145.36291667951, 80088.09040078668, 19121.108847785985, 81964.38672005151, 96675.97238929385, 64395.82499686408, 4655.726447269748, 79353.00746531831, 47689.02586330815, 59962.96769496691, 27295.59491863538, 87404.41581178182, 15819.272379957405, 63979.16020122841, 90456.78389916338, 83915.76681557138, 72471.74814858474, 59071.0565016345, 28933.09939538833, 63454.854118078765, 30081.088826226864, 9042.783952716793, 7889.511876522614, 7245.77862336141, 95149.04964334819, 15466.063257205353, 97492.25051454779, 17569.36238645371, 52851.29091380098, 80229.8181349642, 69657.96002756504, 33500.609367208664, 3251.9794111294577, 70909.6802724902, 91548.94874896422, 15003.118593740694, 30138.82727008338, 39407.27275664876, 25902.247406157232, 9162.488474578067, 34787.9289165675, 18285.97400288684, 72502.63948899222, 10104.623226323594, 27705.54540064678, 82634.42882848099, 7698.203725765207, 83553.67262051482, 40155.68512410245, 13511.299318611469, 28284.66059721121, 58290.964728718354, 3702.8110715879748, 870.8231094168739, 99864.70943378846, 96902.57029318353, 23170.737744974835, 9860.415284465174, 55700.965596752416, 40811.975839634164, 22655.352176491517, 43160.290600231, 56383.36998576312, 98571.70737501135, 97633.75714563763, 1497.7036277960653, 93835.40362629479, 3165.887177131943, 20549.480163276534, 73308.68140588311, 66159.54208323176, 91065.46126032095, 68151.38377389556, 543.184788641804, 19790.029357052663, 49091.22948589321, 74840.20924450652, 68167.9464420007, 89612.88875527092, 23080.849197781972, 17866.224286322507, 19415.98187991723, 8121.189261713502, 61495.374826497275, 874.8620830289489, 12921.925257107192, 99005.07098435867, 77238.52547718542, 80947.5967586166, 67418.77802129008, 63474.17149789304, 64116.495392944686, 65038.70193471767, 36100.02054013046, 3373.1229127240804, 79860.36174262805, 99240.44430927135, 16552.949561290287, 93760.26452591817, 66935.89356107601, 39419.31873620093, 61457.307524002106, 33811.85849269869, 8269.036498630456, 60132.92862805073, 959.277904167688, 87440.9359896249, 86676.61154604027]} +{"id": 1249, "vector": [65551.02582816823, 65305.95478055258, 62373.18028362013, 46209.420692578606, 20475.47894320353, 76350.64174696367, 34631.44711800024, 44074.47582400945, 51913.251440283384, 34675.26672613095, 58734.78519790836, 70244.42804590435, 45036.76187065565, 87664.70645960272, 84761.40706815278, 14028.658420034933, 44841.47003455429, 72761.15312035632, 21731.44220382679, 65466.46769336202, 39139.75480581997, 24732.54456436449, 8375.004922585806, 48472.222429199406, 76660.05142617047, 18965.500955250158, 59223.25507707877, 85031.11582276803, 82694.16857594403, 46282.98377126688, 37675.971069385516, 24737.26665959517, 7848.907614124933, 42.1166136388984, 5218.392951035433, 90104.62830380698, 15541.675451390014, 76637.3928049535, 1618.4610390903908, 35989.03400037442, 85235.1876925696, 52549.85345118367, 9018.928913550151, 25161.557776612786, 46082.39020445819, 35838.45890296913, 13827.112633489969, 3732.298992268068, 10011.98812868781, 39618.50494007342, 94007.45003623828, 87243.85684667267, 13770.833712295394, 68500.50971657707, 64790.3287836858, 6872.021342722567, 29494.373381221205, 46457.63883675945, 98070.04078241443, 38868.43779187703, 82021.65264557392, 11491.325794473318, 68424.38185583141, 1725.5428263668994, 29648.086973910416, 58529.67274098762, 86243.36107799596, 34327.657555434256, 14509.586390928385, 47340.489810976316, 7242.51800237441, 49215.0588225284, 30780.019233488474, 61710.36222187071, 76908.84000794544, 90740.78663351282, 22011.001405871844, 80015.47623348734, 12509.335298336111, 57807.314379986565, 91596.6515866803, 57044.47672436662, 33412.907847961505, 37786.17133395924, 70255.39068238297, 82931.21157300756, 45733.99985917872, 95816.6517931904, 10048.529144475637, 36713.767471885585, 33927.096799380095, 45391.242018579214, 13097.522300475017, 97251.58906085645, 47670.80305701899, 32338.849063271813, 89177.52712799833, 61365.33703287954, 32474.740224424593, 32546.337302903306, 48998.7420907552, 23190.002423700607, 53888.9936508672, 37966.306501765335, 75352.07696247852, 96784.86116184307, 6755.405324781627, 61532.99978892249, 34904.051961404024, 29541.485667298795, 99032.45080406235, 62498.678512221784, 76985.35206026418, 1945.061866507869, 10999.181772236454, 59930.34931899443, 61559.02918545709, 49076.17632774601, 15652.768019556985, 36806.09100892114, 12022.595331259066, 47654.124230860274, 17021.301998263196, 73518.1689934994, 67740.098152297, 3412.5282784364995, 18130.271573560243, 35035.048117810984]} +{"id": 590, "vector": [42910.85263778592, 21848.269965810807, 91464.9098040276, 72678.96827388214, 18664.283027606376, 76977.4425890895, 82024.34790726835, 29647.61125425882, 41181.249652949184, 41353.292482084034, 37078.70330168355, 22921.06317984648, 94385.77472984795, 37043.75613041463, 703.5098878300449, 92016.33518742047, 45423.8616587587, 40029.287299502714, 85776.06255414952, 20999.003698424578, 7530.919252321, 82678.65996421286, 3872.5269337105296, 12206.073572174702, 36210.287738342304, 31693.872515160692, 98198.03758857062, 87568.80529579551, 42445.28533257032, 19614.359756313104, 36166.8800675481, 25193.654127016827, 55411.46305508293, 77421.3364490991, 98571.4657267318, 53984.27747487585, 30791.146746546605, 56479.800011881445, 83406.56192136058, 17783.302564538473, 85005.37442608875, 73157.45810362336, 49122.82065151741, 30408.549641927995, 94477.6570537773, 68533.85079070475, 70483.94887529119, 6040.881864073577, 10390.640516526639, 34465.736483734836, 98273.5659107734, 75227.77654655531, 54349.80096187019, 68724.03438922866, 19639.67168826235, 48249.56975146861, 22277.4006143466, 96620.644019781, 9306.217297161224, 65927.54325418043, 23751.401544188855, 73872.36535776075, 82070.06352279459, 17290.414991092017, 4405.349247894929, 1962.654429283428, 99909.85620578035, 8009.267340635828, 92496.9430715673, 80818.76090797516, 50516.393707637464, 97019.66598214938, 39189.846805501285, 10456.541890403092, 58187.591588274234, 20169.753567792326, 36531.292608504984, 12450.126995422572, 27165.56976773514, 12539.963382961805, 65210.24775391676, 19858.976849691946, 16268.261032431075, 15281.83648316831, 2352.788763892122, 83638.0376311848, 80583.49365000414, 53999.17106427328, 84933.26897521091, 64688.216570097655, 90590.6087916786, 88886.6850974849, 90770.00869349985, 70706.92552784344, 76045.79124571246, 81053.02654721402, 45270.60135096751, 7937.565057448681, 24285.165327792725, 63876.48108374865, 15055.888165177756, 9677.373771054275, 10453.151504460167, 60057.45562427338, 15287.017389977842, 81908.85594696514, 30446.54523213062, 52625.259017973745, 56585.2206757225, 4698.7045138978, 55685.08250456101, 98105.51810389085, 35393.52337537968, 76267.8575801918, 13837.893437773819, 32658.009185142546, 2821.7412748499537, 34408.49627014011, 66180.30587465032, 95719.06790056077, 73635.1532421779, 82286.17624481833, 20500.2969233421, 75696.85640015212, 72240.96697770765, 21627.393845190036, 8418.993894670135, 60033.510269561615]} +{"id": 1086, "vector": [79414.11432325549, 35367.614724828854, 40059.41197107364, 10191.70216507832, 57547.38891558154, 72054.74390925348, 87544.85202190769, 25897.608904334134, 68995.40187587668, 71977.50756748611, 14878.365601160947, 18100.13089861334, 20159.422079939915, 4080.4199540710683, 88783.93476402907, 37500.14350700266, 640.1086628859854, 94096.82214728632, 77467.30250805661, 41211.557895538965, 17854.80276661725, 82626.97122319182, 94111.18974454317, 27313.505035432827, 97239.59150768703, 94133.51467063985, 78181.26747664362, 51114.71541871908, 47812.31276876859, 40448.37341034695, 63843.93838240464, 59296.44422338085, 2762.6443799932067, 70110.12965512204, 9102.509857556828, 37034.73033198631, 72100.75428854612, 29452.867491190173, 18848.419124910386, 77392.62829131902, 18892.52954570969, 41779.04276264009, 39975.468004480594, 33861.87602967441, 29772.77099301704, 76290.27643903489, 13661.835330412208, 75244.5122635658, 92836.50347646282, 30229.392561874247, 13758.290874465274, 99386.58754915847, 99726.86611421754, 55137.54811463486, 79346.11868147537, 14466.30141931048, 27134.616594613846, 34473.80858360929, 79064.03291252167, 2612.382160889781, 70852.4457206314, 38387.05020808788, 24460.482603421362, 70301.9621637443, 31634.25903695264, 30426.536414655402, 38776.782815783605, 84186.10108860681, 31810.400046625942, 46407.851353983046, 62661.8404464688, 26174.105644289313, 8965.145919898354, 92802.00730879037, 68473.11197144058, 52232.78916903327, 69265.03250431357, 98632.62975770513, 56403.341586464376, 72020.74927069954, 38058.05483134305, 13163.490682780775, 20074.508038673368, 31829.69300769545, 97352.11098543083, 54128.64123179526, 37780.82572855113, 22252.69329975351, 44350.77562344309, 4576.003657639505, 26079.90859081489, 93360.00595913365, 19156.47096321409, 18369.656747592722, 89682.02570032724, 64297.58721980756, 66969.19820033779, 20042.74130977284, 44536.36430418964, 55929.74544032569, 11202.943573938073, 23706.56484986786, 82617.40174597921, 53318.79009176379, 25141.745543601413, 35303.29454471002, 67754.03421550391, 80392.46773404792, 14114.187573466086, 61581.74357159689, 37431.10464881527, 73955.8099588088, 60358.49427570622, 57147.76609616598, 697.1401050659742, 72229.72114146597, 67926.66621233552, 7797.314231135566, 5925.556744399363, 44135.16412872477, 20375.124575587368, 17391.837300028557, 62618.86467153298, 31004.830102614178, 94167.75170707972, 43223.146045117624, 72362.39041815943, 44841.729321251514]} +{"id": 1088, "vector": [19926.286301493936, 94654.34213833879, 23257.08660378917, 50997.09062262764, 77016.91107578004, 38208.80464693635, 45985.0384950469, 67670.15707913262, 56924.29167892359, 59975.3450122836, 76857.1035475689, 26651.435364142297, 92545.99292334031, 34066.964986592095, 31448.247381540274, 62874.3621083806, 94403.4919443501, 52509.542662542284, 18743.61054706184, 97660.27108364798, 91831.21189293942, 4168.004261663572, 98493.90483565404, 65723.9475009701, 89265.35384654469, 6638.235857219798, 95633.11348039056, 53446.9890793838, 37352.48089545353, 21832.95503231655, 55321.169358525054, 8256.960669042968, 33182.80625107356, 46315.549736484194, 14435.293884768353, 3427.9291291884006, 73437.68548508998, 14799.856029158798, 41550.15322662712, 87480.63395206127, 29137.333842729797, 26265.18085136893, 74336.24278882, 18287.628589914162, 45469.69340392302, 97439.73966278542, 9612.597967904658, 4918.636774955987, 74397.7058800828, 87029.37211031365, 29293.39441409844, 29905.488254238022, 6923.779534776176, 38156.40856400798, 70599.42215010771, 57989.31986379916, 89811.50986318757, 78017.59674711138, 76591.70673599353, 59699.46209297043, 66461.44512840826, 91031.29351775366, 52221.15963108855, 73087.05328009052, 12468.142698249818, 12290.472378208384, 78630.36867183718, 27912.420542499305, 84066.36763721741, 56549.618605144, 81657.49411442768, 26380.03091919525, 39930.72877168079, 81992.48419518827, 78652.85646178492, 60473.480236201474, 39035.563112636584, 11623.74985531831, 31002.97162376592, 12306.982451935866, 69109.34061221275, 56736.339288209405, 27218.333062532583, 49749.030271004056, 90232.41815300145, 21848.478773166746, 79525.510090249, 3221.6449130772594, 55264.55109798083, 48569.73486232667, 16629.54421257372, 78733.19491753973, 1103.3009467687039, 8552.171559704424, 50609.981902052226, 38969.20485568522, 34392.281884997996, 21350.389054860374, 72511.63921625575, 96383.14723399094, 4751.732107082785, 53849.39020901867, 15452.56684230093, 21257.411943186577, 99022.58505739215, 62712.55271635261, 58329.6384719625, 30644.46452016203, 76471.6211022795, 33468.03293551718, 55971.23171981889, 9587.948893432353, 72333.22245448765, 9480.438156584603, 63795.06802670114, 32956.27189558994, 17943.858863046946, 72249.68741330637, 81333.48028402883, 77694.04846411955, 144.05244009829676, 80073.20378482032, 54509.83283222295, 53213.02517018525, 91689.22944278075, 84387.29985595266, 55273.453323212394, 78268.5610166554]} +{"id": 853, "vector": [55065.4944618459, 71682.9617138658, 39435.07263852629, 75516.03401611482, 15072.030308222362, 86380.90743703919, 68040.05708129807, 20942.199945344175, 89135.3976301375, 90714.78514061142, 61455.36514589473, 40910.1924418178, 66600.66720498772, 42939.78570316903, 72446.97157016904, 90072.74464161386, 24036.360063762673, 22993.875749596147, 88683.13104852186, 93693.70209387886, 69032.1591645854, 27568.9834935942, 88841.75525168654, 2584.0148666719356, 85597.06095537513, 52580.05240318543, 94447.63242806013, 43105.06803846097, 36650.051917541416, 49146.139980277345, 35771.233973331786, 10838.75739514366, 5752.257438189745, 91810.03191594723, 22269.569395492937, 2047.7829626253663, 77156.31369132503, 2564.130030962786, 90830.90146492721, 65109.309682826344, 40082.01349199417, 96451.61330361325, 64166.98045511801, 53719.15615788091, 72434.37794034176, 3846.619512684446, 63551.77466442465, 46023.14039924553, 98525.99183446435, 42074.61911157287, 93502.41833799036, 52613.51075034146, 73324.2401261817, 81941.67499479202, 86431.40854626504, 33763.86105713713, 84090.8716237826, 32325.93129744151, 14059.476921776404, 55638.649773655656, 26814.199574789487, 85679.2808352092, 14406.445671726287, 70357.52477023697, 59708.75963065147, 4943.69318772876, 95514.61849304347, 92980.92427273683, 51031.44633317791, 40072.45738545223, 88313.99100068242, 62289.61278298077, 76344.48327382068, 59481.24716925356, 40936.71543462865, 49303.836824944156, 42383.77809792759, 55686.098058150346, 43639.325763684974, 1203.1601989461738, 5016.226541634561, 34028.2135312903, 10568.782881563831, 80068.16018550792, 2663.3393652115255, 75897.30480846323, 44335.041521943385, 24719.622900375492, 65188.67295562442, 20271.87977959086, 47228.10165830492, 10978.670022407867, 29638.00520411203, 9886.256099160784, 50759.48573435215, 65344.541208864684, 91807.80535255844, 89460.07727531712, 54635.62511725385, 94957.35989619161, 22446.494922680016, 95166.83812068657, 76837.38159451746, 438.77901055932034, 86143.16853072002, 68659.59391822183, 27801.80837079674, 67879.61648891638, 95887.83087236754, 74154.35036226411, 99869.83761575494, 36909.29921595714, 73717.49211263513, 85229.8605766319, 2462.036984860438, 77179.16750920162, 55750.17245296824, 39563.76430992129, 28153.570925200987, 21403.602697147162, 78970.71972259402, 3870.8451384697582, 9507.632087018514, 55569.766310562416, 10930.69275187618, 98449.51412890997, 35257.06923412182, 29998.90630469979]} +{"id": 290, "vector": [80772.01536635491, 81638.19031275366, 99922.78575151881, 75685.75093775232, 62993.70706421082, 52430.78697261586, 60573.58445612395, 81493.59597704533, 29941.88973865999, 39559.93559319802, 16534.93765217241, 81301.68766250541, 58955.91199021946, 61453.77212860459, 41702.83769938412, 78852.1840810207, 33660.62779866994, 60244.716365534376, 46807.87096839674, 8779.408318258686, 31764.18562828749, 97487.90194092589, 76001.83829567273, 14091.329090115456, 83992.91169518423, 81126.01200312364, 5535.831105317168, 63795.76032750124, 16225.732368028888, 30897.745147787948, 84595.11603820746, 93131.95919418124, 42364.85506698818, 91523.00965187077, 60622.564894950025, 57869.90655359034, 52965.72624256084, 94144.47842361008, 22338.49238026955, 43167.89391082308, 46282.89042077063, 93080.70632435205, 68468.30567224097, 12920.684989283338, 40899.70047849258, 16622.751936912005, 34318.126111637495, 38585.42532569045, 69517.19515175058, 13290.440689244388, 19181.812567451074, 25378.055857242787, 5202.666637261233, 19192.257096523925, 71575.28737946993, 86985.47127240802, 46907.287430916374, 88738.56504248464, 67543.20253447858, 26169.98221891125, 99754.64742272088, 88991.757074803, 17676.282596565117, 18303.641809657478, 34595.417561801965, 42754.719679664624, 89340.76269415281, 37385.17195602248, 81241.89909509507, 68011.37289061934, 87802.77886328305, 69559.45372687005, 2064.838861883178, 43343.691306691726, 10317.977562579428, 55048.47577272922, 77243.34069521795, 43160.49059098134, 34200.412472028744, 73588.11283944345, 95952.45812349154, 79807.2601362099, 41903.600001749706, 51431.19431571887, 57136.95984329722, 9383.354867731996, 33025.44279269622, 77969.1828761828, 36832.5577148339, 54560.677991407894, 76613.6715919724, 96726.45847213, 4587.903284980799, 36942.84834889454, 40764.6793835171, 73431.01429172077, 32612.23411190396, 37075.30856395018, 3159.8846819646533, 27901.472427235185, 73690.21002472978, 98236.9771287349, 11290.061227322467, 3880.454345799278, 59607.68291797882, 65865.61675813187, 23902.24630228345, 60325.2480130948, 58713.71042333845, 2276.4638962211147, 14697.788850956738, 98356.05627906426, 32818.742613488794, 87022.12519427044, 14514.657112960138, 53749.7773802514, 90795.99656873217, 32686.348704819568, 75170.33515892956, 10969.450593338237, 30217.384636305498, 98240.48162756892, 85342.69258595555, 7772.766047365165, 31923.4847278571, 26427.512060675108, 90889.43928823542, 97528.36935520548]} +{"id": 1400, "vector": [11933.574304635242, 36472.49654189326, 33348.013237546504, 72552.41439390766, 44851.13778745741, 91128.61540768265, 71126.02746513774, 10678.850831394426, 82549.27554509562, 25342.203770194094, 84732.11352261223, 26000.7487114276, 4933.546946927791, 33703.01060057629, 99338.69664484206, 75913.80290505798, 49493.86716467218, 83008.9353642145, 94366.45789660142, 86177.03338150714, 90894.03805401681, 20417.05691523461, 97104.31633369162, 16778.492129959188, 44913.74741420371, 79776.32494414237, 14166.73139310677, 3472.952728120704, 21078.58142619665, 41641.41804872967, 67680.93182811765, 15803.585666644549, 60829.679388157165, 55541.44417952308, 62730.86114704275, 64211.67575862563, 69406.0439392554, 26456.928228598954, 31397.856693207304, 47672.52884724316, 69876.51490675389, 31619.97875258361, 5294.229513448112, 41336.06362383766, 91486.1671524692, 18432.36676883445, 21173.24554839991, 24091.554705398867, 41220.160327854406, 49861.2424223484, 71622.20222390587, 59271.17937683302, 60601.8707412525, 94234.72751692712, 22817.87961554048, 81646.3709234062, 21495.148897979467, 30281.57435949057, 24879.494735895736, 21503.687390007864, 14127.710963893493, 71432.54023723638, 14839.112136783882, 62970.29881965269, 86168.07161190177, 60196.87372181375, 88368.105112653, 63324.36768197006, 83218.91994915107, 39357.34324905974, 90827.523515588, 70432.84717386703, 13026.335803519107, 52539.35703265029, 89326.9762371341, 44552.82038268084, 68689.47997552963, 14018.177647761331, 57220.27480889402, 12339.06649502582, 30319.00622391429, 29393.64600704448, 85229.2011883223, 57895.54711889391, 2347.639661615641, 1521.9620882756412, 74661.93173581322, 52969.22816885812, 58710.94538561894, 77887.09703679054, 57089.572499376765, 34264.50014131992, 88954.87991509016, 70819.41038859606, 81363.97125935166, 16941.25973872055, 22425.916061875163, 32247.11529121903, 10267.520997827372, 32169.38891603215, 17733.971229251732, 49843.9415394123, 87327.49292491486, 46669.865228785864, 78715.17260883379, 23899.728802804733, 77971.78895391355, 50459.600634849165, 86348.22012967321, 86408.81049620945, 29676.11266957676, 85921.45754039881, 71505.30424893391, 36756.6714789612, 9421.397432296675, 53887.75712483228, 31443.390825332073, 12009.20779306588, 22664.559710712863, 36763.86131698884, 75965.1136562535, 89858.09321523414, 9906.33518481392, 17584.36068493664, 9685.973676498494, 49657.41101624192, 99794.7589281941, 55097.16328433603]} +{"id": 1726, "vector": [76795.7028315272, 63823.974073820034, 2150.8247180434983, 28232.802953086244, 41131.43635850435, 96432.83689913613, 33164.947249757606, 45910.31394270589, 65270.33378812571, 49235.53186085961, 16311.85613112205, 88186.4325034894, 44788.22983073885, 14408.83706937859, 86446.61464318755, 41777.78131281543, 59502.52244508869, 11887.286617014835, 1476.3594562491655, 58903.70064123882, 50003.05146658845, 82723.75446632401, 82508.30258863974, 43217.636651152505, 11426.252044254903, 79146.27449231711, 82608.78139576115, 32396.415314450733, 72449.1400453095, 6995.617597359472, 40274.49935159736, 67200.2933463156, 57126.23496836237, 75103.87127330023, 93972.763130421, 62867.661242810966, 32768.652505488084, 36589.632682817166, 23105.371726383462, 75006.9849449473, 95951.1691166369, 80938.5808168599, 18285.046546744477, 72571.93422314299, 34355.16189424127, 13241.953101161153, 92525.5303197532, 88746.75349465164, 42216.46520234681, 34173.55388706743, 47970.264184292166, 54378.08175745868, 31615.36274941107, 68641.79739785037, 97975.10121424215, 20226.4979472387, 302.15898546599453, 40457.77724281418, 52516.40857260429, 96690.89342782485, 94390.89483606993, 19640.634906979925, 21046.307753252157, 8592.371964658918, 49913.34449496685, 76494.87233727777, 79245.8236091784, 84556.25435824586, 74026.32477765287, 68321.89931307103, 15565.304018510295, 70856.7402904442, 41140.55146429677, 50515.91287317688, 17713.988148383574, 65274.80314463579, 52482.30390418235, 78581.13829593046, 49445.54266705621, 52188.32562696779, 53660.69787293594, 93221.62911385637, 12937.176862439603, 61119.55199082803, 33080.59902040119, 93689.97574125086, 84290.16414326215, 43951.84464632513, 22114.7079714487, 34689.74512984629, 60920.501883967685, 34593.08054959628, 11510.265291436983, 6705.678994156916, 44026.185574454066, 46470.498098152726, 76368.60680270192, 40881.040190877335, 68493.17674476965, 49265.379062329375, 19449.670570165912, 64594.37336698927, 40279.78069641177, 38727.14777971108, 7530.31752618718, 86319.13154549489, 29029.664101347196, 59185.0567965194, 91457.39201844613, 9461.118913573551, 3225.132972947675, 15006.417269157968, 66022.26518054142, 61765.26728249982, 2081.9495311774626, 40748.41050511949, 47923.5260868219, 43968.74042711751, 51748.18530123561, 77302.39060728773, 9146.658907116578, 12953.89811844977, 35377.025247892816, 1879.1286363102788, 11219.791473452411, 81370.6161539104, 72497.82683641736, 12382.218358077691]} +{"id": 367, "vector": [89407.45658180641, 67126.9948528328, 26166.463689878983, 53535.7549527147, 31298.356782072402, 80656.42867893478, 75478.65050352285, 21620.113434278166, 20304.6646888841, 90077.7441754392, 93943.10456828715, 31033.24297760237, 40393.43767222413, 40834.77366486321, 73627.84398207006, 94403.35866531613, 22935.612890091284, 33569.08878084891, 11573.94099559349, 77505.0425344229, 61660.647007288186, 34952.84132941925, 94262.91282552354, 65313.75566687318, 70351.60262841971, 10724.223734392946, 49627.71333982098, 77189.45866682482, 35390.22243740668, 74488.88332695897, 58888.285219639925, 97716.32868346407, 96167.28746885005, 56382.23790657172, 1396.8860025313834, 81088.07347492762, 97419.76873978306, 16737.030073529113, 84387.52600955665, 23062.254574641327, 86665.65298780163, 70923.61153645476, 22835.263655010072, 47549.30928322509, 18559.377257549604, 36656.75851047555, 276.83642176332677, 72095.23096867531, 96343.50318441636, 12539.372659995928, 86060.86156082571, 78458.09048648774, 69485.66063969256, 54164.203928462484, 93077.04939394706, 67840.15782096436, 49683.01144771392, 6473.3726063297745, 51733.7969262311, 53426.86244313266, 63156.33456400057, 18647.13170343162, 72070.45382633107, 40046.78691215113, 75934.72482879007, 34403.98634052543, 15785.155860826395, 83108.14384082268, 58052.790494504115, 79303.94667270972, 17880.270807052868, 64694.74925299493, 39488.63730535864, 77752.65485180538, 63224.87415564853, 31167.960594422395, 38119.99315632212, 14253.619770939018, 63270.64604900915, 38114.54980479175, 93414.61955671741, 16995.769807940786, 8289.633426408083, 509.6908090842067, 2158.580760016815, 85519.28293233391, 7069.020655997127, 21968.020691776568, 22274.427614882698, 35651.85805657183, 14703.832990528275, 84310.82893006924, 89022.85376529401, 45413.79246795726, 23733.75399177503, 14611.564600409421, 3264.826739333471, 23263.40598633737, 16701.510272337528, 56004.22712046722, 52765.399715657135, 23338.313748731387, 20065.479878867565, 75483.33623482338, 24922.50431970705, 1962.3622031272769, 1682.1544690547973, 60102.22834698439, 93606.5841760544, 22423.372434841494, 1964.4991165493009, 36451.83133518599, 4320.360049809424, 74360.15003089678, 83562.95489968866, 22811.53457053502, 47332.7756304233, 55685.222602141796, 21179.100279400063, 27908.302982281984, 58082.0680949868, 87420.20857275928, 40338.10848512701, 90747.26303755889, 93408.71884719474, 69064.46466142446, 28527.482213775547, 17943.066449982736]} +{"id": 873, "vector": [96706.78593844501, 71302.71291106973, 64857.71065416421, 97108.97192618005, 82574.7255312811, 97101.05183309458, 15685.428554125536, 97798.60361220455, 43710.04911530737, 5598.989655120335, 78371.57995748587, 98763.55034282562, 22826.8188969274, 33870.03642132278, 36626.12480570994, 53024.87973311621, 96656.38313899892, 4602.128113708371, 77440.00040555265, 45122.80836778275, 64390.35609788362, 41347.73881886259, 4748.328969468674, 37977.978453052594, 90832.49913068904, 65228.2191103114, 44886.284415456255, 3526.2901072200757, 46189.572118932454, 35049.11766750905, 33932.19222388343, 92644.40678199664, 45937.251696122796, 22759.673225327006, 18424.502523879328, 2246.5019975863474, 83728.29874020084, 18345.055218955797, 62051.89885088589, 86339.33133845736, 1645.0249507491853, 44012.00699060732, 77447.07181340025, 71688.50078026869, 11823.030907824483, 75666.29869337074, 57834.24712846942, 66363.80770508376, 22719.878523366788, 99973.02929427905, 76661.78019208342, 75848.9815223038, 25002.558585318347, 11465.139412874503, 10736.7297220762, 58507.34158112798, 648.7499896730808, 74618.89673312475, 58154.02898535622, 17196.49018752475, 12266.693091349112, 32259.640648388577, 55578.603934223844, 36843.63523515718, 63451.7539333335, 59527.400145490705, 93525.0026509987, 25358.696207305766, 68847.2137137792, 24809.814800680473, 67725.99843855736, 2724.081767961617, 20022.03374360292, 92858.73245439869, 82279.08301584254, 15208.290116802704, 25054.46993038669, 53897.35873175697, 86196.55146234899, 36795.00016326904, 85462.91822643875, 20394.284793189876, 26756.846219352472, 29627.360678628665, 84014.13125020528, 13505.512544482955, 90714.21145051229, 71377.05298553515, 81223.0546687274, 48455.59654197931, 70738.74585557755, 11137.848887908585, 67197.73321660617, 72611.50198121602, 74200.17631646532, 48451.327347247134, 6370.772399911184, 83856.98044488898, 18154.978183224157, 148.3578232366889, 98016.22863306338, 71768.14244365509, 90706.15319363229, 81628.04696371019, 99657.19487209455, 60695.74589709338, 27422.915765576618, 42011.50981199401, 50057.190926235264, 31217.773787122984, 10288.158405834658, 16788.88581248398, 39689.22948471025, 85274.00287812564, 62962.28082857099, 18709.00684457346, 4137.15531470753, 31777.156348368306, 2699.9778355626836, 77514.01083229312, 56861.45541436857, 1437.7729018691166, 73153.12567550912, 40028.17153603017, 786.0406941601839, 25352.10831772281, 28675.754198740044, 84499.07731924146]} +{"id": 745, "vector": [38837.92956098898, 66814.44187852627, 53019.70774481149, 24082.5413486787, 31656.87985221557, 81679.08169280353, 92883.35500411724, 37730.514249642765, 14224.605148426828, 58721.68749407888, 27503.36112315359, 59210.67837040914, 53668.971271266775, 6365.271461558186, 35353.91002615934, 88239.13875781606, 36530.28463312352, 6777.979379570842, 41158.81151756052, 82201.03368311617, 84340.38425544006, 53223.66999486704, 76111.10596250264, 56574.4509602615, 8223.651204461257, 33617.085226040246, 65094.136545327674, 11238.212777430645, 63234.6826725637, 74093.16989916957, 61944.32340130604, 73196.96183396451, 45005.178728274666, 88341.74430465169, 3735.7033137023323, 2951.9689022552975, 52234.25525736777, 35962.34533658781, 35385.314776630235, 62185.1098932168, 90431.33075361927, 78076.4985919305, 91266.88939647253, 1878.5438694060463, 84302.26887418873, 78833.10278211578, 86990.14297175083, 90364.81460400828, 41143.39281019187, 19594.861816123765, 15614.585025334105, 48937.59837099945, 75938.10487821233, 20231.067564421268, 85888.8900416765, 63789.88537490326, 26875.609080475206, 43418.02489464734, 85779.78050947066, 41861.41055589138, 69062.89691917642, 48579.9933321416, 15158.69462179229, 81590.38745246915, 89379.66511202387, 48520.66192584006, 99981.32969999549, 2949.0367334258694, 85894.09414781921, 15873.583266048552, 60342.0907821413, 95309.68111316278, 11224.91519352129, 79707.33955924268, 5704.2038524369955, 46771.68908498912, 72099.74365518198, 39490.9687169538, 32701.653911983074, 39679.79981248931, 40878.43023689793, 84807.52072092313, 42419.78523312211, 55222.577611308, 81912.3121058406, 13333.79223175334, 99678.64718838612, 91717.90195310659, 4080.589519619593, 31237.549577828995, 52185.374648834826, 31930.430749353887, 31736.314021160284, 54792.56800844996, 90723.10551230298, 12881.760145024778, 31186.34690622585, 62059.70860671078, 90169.1661992788, 24109.21934059418, 8260.90190954163, 72800.85885872428, 34465.04547810878, 84419.14766260111, 57415.56413252214, 77335.33864421019, 25786.21078969492, 80826.78605617047, 13007.165430030087, 74551.92248753189, 21979.962089946992, 96973.47013603062, 3140.89376602118, 11297.895884336594, 75454.76451212676, 30201.163672793384, 70520.28523400017, 117.32594121847129, 6713.469080201507, 65694.19417294738, 2309.664298720082, 10693.486814864373, 50777.70331041227, 45248.9226856467, 74804.74961294915, 68322.97810659056, 51357.24913482666, 84146.35858113362]} +{"id": 266, "vector": [23573.934634791403, 72340.36301047864, 60990.945202041934, 78350.97521226911, 86025.35460721068, 18060.16337620925, 45445.618729524394, 33764.200850343805, 79040.37790336736, 38226.21799936487, 68139.29270306177, 8990.198553174556, 73440.2135197692, 70631.76590191029, 18432.191168004254, 6637.986194506762, 18874.707501817888, 31058.38570209599, 79432.45269603156, 83272.8398687441, 22082.614582868177, 4969.44318752891, 91857.2123182838, 576.005041642702, 46146.06441920247, 78787.48737903357, 65331.271287414114, 94998.9457203223, 60640.457494523456, 7333.305556429237, 21848.777263123196, 98078.78353599389, 61803.50239788288, 3503.9634139577893, 2611.4815970014747, 60642.146685703505, 8261.396418759181, 74054.5733067366, 60915.1270792674, 51041.73673627581, 77431.10263720156, 25161.7966920459, 2553.4352979807663, 29941.965384404233, 99612.29637304728, 51508.453371207506, 52391.60742712284, 25445.139064327115, 78213.17646566754, 98533.94602911266, 22825.189437155135, 90405.60070900888, 89517.1902714018, 36457.37715288542, 2857.8000726459995, 37186.91985552273, 88281.01479435882, 65886.68061314653, 6998.607463688389, 22392.619718576658, 42674.99609823295, 92594.39732384494, 72418.24633380953, 1200.4428013862034, 33534.34335709021, 13528.498959267443, 47086.82359207467, 81238.7878504529, 29229.587101555753, 37359.3595542037, 88972.33780049911, 89738.24934733419, 2175.202579099922, 46294.79746727767, 93381.83367130319, 34782.304141417, 63236.329250464, 99963.6889908465, 58169.77186503734, 15173.834820126742, 84639.79353495096, 40898.97545802737, 79243.52933107912, 75314.83971795772, 84988.1565461013, 58550.0714786704, 54522.55423814315, 91978.081261943, 44009.055492806416, 23611.16546745983, 71908.73541216223, 40115.37936066374, 2064.3319494762836, 79759.71981075809, 80590.03717966712, 84062.10761044033, 52416.305153499095, 21431.70787044425, 51613.18366023865, 29098.873073737897, 18873.45188354389, 11746.703880617826, 37632.87373046153, 8859.104887004498, 6414.106659697538, 33049.68464026905, 38204.356977902054, 52788.76403841639, 13596.294789028228, 6977.190702352965, 35314.55536524878, 26115.681614326902, 29069.42486589773, 86578.13107309573, 4483.320674156422, 34519.70333721437, 89068.91815968874, 78258.92650661082, 54788.87817377321, 52773.89072745805, 72601.37930242444, 12431.763910936434, 52010.38937223279, 4449.825874015345, 48097.58914039781, 95140.76009831384, 2999.89441208951, 32974.54029343579]} +{"id": 429, "vector": [65732.80682367657, 76586.84909802294, 33487.610016233186, 39196.62065891407, 62252.57215719772, 26075.832828224677, 4063.1103396289504, 57414.93451522618, 87744.85932814727, 30593.516085140083, 69604.08246233685, 6829.786899310852, 28315.98535536228, 28456.278983267446, 22910.694552641166, 85506.48257139807, 9958.145808690477, 83586.43878209175, 55942.32914539279, 18685.449052898693, 73872.89893324679, 63138.35387202018, 49613.35238678859, 98991.37445537541, 39907.23900957025, 90754.89655478792, 76751.19147423269, 60638.80668878488, 45529.42678968147, 13182.682676041346, 57819.73126483912, 81381.87101380616, 11128.706665631738, 7845.87544065789, 52964.10559050213, 93744.03130784366, 33430.51272331109, 90523.4304417712, 62543.702003589075, 76783.01655867437, 46097.14375208118, 76188.3280476484, 61228.424443805794, 29064.62737540453, 67527.29614421776, 32806.73905074095, 80713.1754463792, 35604.298787414635, 43473.33925829177, 58345.13271350211, 65556.04579332468, 94747.54886107346, 49720.105102476286, 35267.729273666504, 17496.08491567696, 52120.070264675065, 93868.00120084238, 93550.3220567655, 70361.70340899909, 47058.33093590105, 22101.972045913142, 29337.922480320944, 44721.447819233406, 58869.35678757353, 6200.802614058775, 55155.93974006734, 5296.1467411623335, 82084.07696353423, 6122.127963431023, 98248.3946788837, 59175.675144507426, 32175.484705609637, 60444.00407412225, 97397.36810753385, 28346.828419425652, 83002.93455107365, 6731.23229197089, 21810.992664281413, 24107.736285309333, 40102.86425093437, 86.37849338889447, 8563.600619305234, 14905.676295531712, 13936.326052690385, 47635.003443699155, 32851.1176235066, 94946.34371371055, 91258.22594368961, 19695.220994340634, 72849.49722656074, 98732.6125869262, 27537.237440796336, 25024.519792783452, 71977.68282297249, 81748.28970735458, 18051.88985226862, 82120.84484036564, 71763.21714248222, 27043.76056427352, 5968.392928107125, 14881.916226621084, 47713.2403171303, 95568.65971339858, 60835.02875623044, 32253.173327135064, 56629.103709464835, 98126.98103031653, 38873.80064381347, 74132.63307209856, 84499.86313142952, 42914.11687290603, 30358.368905546053, 62895.54814694832, 78227.77870609985, 2663.981033158469, 77130.8446114808, 32897.306028226056, 5312.844318923837, 81537.78966185692, 74215.43802089605, 72462.84449668437, 43113.431778812526, 80627.65228682019, 52099.46981394986, 75976.1038784266, 86217.06012633216, 83502.60080846402, 37405.224671934564]} +{"id": 1991, "vector": [96651.43562098876, 39084.78630092314, 19102.426501420767, 50158.94027388288, 1386.4930759369677, 54406.53598457998, 29061.161194570428, 35311.1325694341, 23629.874126636252, 54248.57296683517, 76662.8852481461, 6424.424717570288, 5044.995889541692, 18864.640702864675, 52458.90093868343, 60338.95531679657, 89110.4943421475, 5530.441876802616, 73816.97894503865, 66411.24556265923, 8920.959259359584, 56012.47122087104, 14078.297284658669, 74743.8696028807, 55881.59070932519, 67278.39037398885, 9731.16438010595, 31305.68898202083, 20207.775491359804, 67526.58494952644, 59011.8559171805, 55909.29760732715, 68496.77484107234, 57217.365694521184, 85180.3953882937, 68765.49557100647, 42960.42674106686, 51828.49870081363, 45055.60262314712, 68790.05849968904, 83237.79010759197, 19437.973973644384, 6572.845728443688, 91543.94687589601, 24435.694552649024, 70139.28885412346, 58404.32036927058, 67960.57657667945, 81483.95958838625, 85550.75801161659, 98639.43755305457, 72663.1445233081, 26467.856714798665, 81880.3250943181, 71231.92658072493, 79644.43685698556, 52011.36567229563, 75193.77575169387, 97018.94882967355, 63321.25885398477, 1050.2744468294577, 72142.2092033565, 51279.615556456636, 98792.31868904726, 16587.020294304268, 31977.41199210189, 86688.98711388298, 56550.92750904117, 57410.677850394866, 91201.84392318959, 19805.704772969002, 99018.76693923232, 78348.60842473937, 18258.29373210658, 67855.03495352833, 63413.96730612064, 17087.77383196576, 41121.47001992622, 86975.27259879194, 16433.125617265752, 4193.007872113263, 53951.86637752947, 38368.73316670479, 87309.68096630074, 40849.421962828266, 78177.3065671462, 85301.64502370017, 20799.80102977057, 89764.86002491931, 1171.2923851949886, 9390.082449442383, 72171.5049029729, 62113.81725418901, 58412.19970272783, 97079.16921116377, 91650.47437764378, 46330.512945660565, 97792.60372127248, 81832.38869936008, 70216.96710369986, 75049.3774497857, 76336.23100428925, 82664.03085107666, 92719.48998700742, 83014.35111526672, 69489.80708313039, 38889.42324895647, 44236.81538548384, 11920.584379343136, 60330.3003273735, 2735.414798572633, 79553.87100728003, 60959.789943221534, 70809.44314132564, 31997.97205072421, 70073.04690456607, 44684.67953500731, 69117.61982589566, 37113.064143243304, 72323.86355931615, 77593.61766759332, 72432.32870626566, 47011.9091796248, 56110.19070420559, 86655.76718554064, 24613.578404552914, 61221.21061640161, 33279.94078487036]} +{"id": 379, "vector": [51605.389259792355, 10196.810839751048, 70959.63936455928, 37413.23907186563, 20724.700037168266, 56964.72869515052, 25029.65011801235, 50795.377341334315, 17704.305507905694, 83075.82690697622, 86582.44286440157, 50190.27997393111, 80214.55222160472, 54386.68126524389, 97684.06290443098, 33139.82824891043, 4485.757685697001, 75164.97932657889, 3385.4655302870574, 70684.58317230122, 80704.39239167103, 32757.11541214731, 45709.967107245066, 78064.23316208078, 55253.26069975912, 58001.99481747957, 77472.4461232703, 46208.78657630878, 36403.181972274804, 44930.28040745356, 36834.26953650204, 10225.686715271453, 99221.58913695137, 24673.836246849743, 85604.11518409612, 2020.0926686810617, 16542.731155277113, 65892.0820684969, 8504.468357917538, 50390.32415420509, 25979.893792413135, 39923.16354527046, 943.2250269952202, 18493.91258698446, 97829.02828044882, 6416.406164132793, 51737.84092136311, 43441.09783625899, 89635.14158985938, 62632.32314681008, 88195.27232448562, 1487.968310791421, 9162.822179534525, 18669.770812070787, 18951.819462938267, 59724.0685756754, 9956.996747668023, 8577.602559657782, 7107.80956229784, 64369.09279294897, 26132.208013455573, 9925.739554275537, 57169.496273007826, 80297.17380500076, 38576.13947311837, 59400.582076027305, 49879.18210923552, 21229.823210316845, 60196.336158372644, 7423.333349034445, 88927.45151987493, 22968.78475987999, 14556.358405693893, 81156.41003815082, 68002.81363923711, 54677.5185035299, 85585.41723833239, 80335.01192840398, 5632.207665607447, 20631.019911431402, 23958.594301948022, 33984.28240846171, 64316.97689582842, 23197.475119284994, 99897.97950942574, 41924.291822519655, 97531.16808969394, 60146.695448056256, 14180.726165628987, 58764.918952826185, 3567.897944076337, 71871.03650658298, 1481.2217969308538, 48978.458944790706, 79478.48904874346, 81128.051637342, 71156.95242298179, 8533.651259220787, 58402.64117982541, 63252.16552688934, 39561.553696668496, 77162.26532038827, 99258.47168235756, 56889.706074920134, 78228.0973171879, 32327.16149652217, 55204.041908309366, 78364.55796714257, 79125.44010948157, 62603.60327374238, 49523.31058391586, 7818.968210753829, 66136.52072419143, 16781.83256641942, 40280.50210620127, 33539.09538311737, 49339.28517339767, 84.49032632238396, 38644.999447055525, 66600.14315623198, 71426.26040136945, 44350.36283037075, 25007.783440481213, 79640.74613985469, 12.980954605257278, 50679.238391504245, 36335.62211076138, 19712.554878788203]} +{"id": 437, "vector": [50992.504663903026, 25156.510363176156, 42295.09963414081, 37560.58329709752, 66129.38685987088, 56976.871443968965, 1075.62225758856, 72899.57718395135, 52893.507100860515, 18012.070194747575, 45485.65746287088, 8464.539763121049, 89580.70089117554, 72967.98440102428, 12634.00682682425, 59556.93843695144, 49084.30338327055, 6792.720723258639, 93534.11577142548, 66657.30629839552, 75521.21637056502, 34817.87588416706, 4838.984474694641, 94263.89437936012, 55238.4977295614, 77852.8242815822, 46727.30223966522, 31086.371664659153, 66666.0224967921, 55989.05794702559, 96253.88803027147, 49636.66856029576, 90800.45556918596, 73359.03055927661, 39866.428029460585, 95221.62989811006, 63060.72346961934, 57206.261650792134, 8281.014834668098, 61045.01176052674, 67355.8435536594, 36969.200460274784, 74909.14190735895, 71761.00118161064, 70973.85317671478, 46175.388607818735, 93261.98125907833, 63271.9565095092, 91499.7974180078, 19483.642988230353, 21447.25314615711, 38097.16048453296, 93992.24671370815, 35800.907644803236, 87663.40735506969, 67571.92544968547, 9962.389834010477, 25684.98188201759, 92945.43068489435, 10662.308982033564, 19971.38552046004, 48702.60339616832, 19402.326833841045, 26433.22946562523, 21106.255694943677, 19977.544833526717, 67488.01500427388, 69704.97330237075, 96035.44433029504, 31594.60921949314, 5868.576112033841, 32912.68850493698, 71354.06773596823, 23049.56114779092, 33073.629469750034, 54028.986529739384, 58127.485457006325, 60413.211091055644, 3997.808879366438, 63567.955331753765, 95331.29292316953, 87159.77964537696, 25553.6006960579, 78713.03649495344, 51442.80242483124, 46005.85396620562, 24545.847654330675, 37343.873114570604, 1406.5386052236618, 2913.9153992530532, 74715.54575089022, 35082.16482277932, 119.70265424066096, 21139.921089516432, 10919.207733672964, 57477.428936672826, 45845.58223251928, 11797.46061158261, 80091.28519775736, 99847.12875361784, 27903.945923871655, 16823.379218128888, 86607.16971794868, 88344.10863295842, 38942.139987646726, 25972.486560486217, 1461.0261676818227, 45849.18925598082, 36006.90830185555, 83731.05877468085, 334.30368868886393, 74417.3136846611, 661.9931583066174, 50701.794503610174, 56013.772490435455, 51081.104433606684, 44953.957184680694, 53595.10251666336, 67747.44521098454, 2508.027517965983, 16649.390264666774, 76947.60437271972, 77090.97352031193, 8770.39326012814, 57883.048425758934, 5102.591022214265, 27540.84420378905, 6725.331124844402]} +{"id": 161, "vector": [17410.720148925462, 77099.2242317752, 70698.71509818366, 77692.5960332828, 58437.60769035402, 19102.182632108743, 9055.28384769244, 97613.1699916661, 2238.4541778968314, 96756.29835208926, 90453.53609494488, 89904.5666815946, 79262.71203975522, 34115.387293409185, 98215.73728548177, 63930.702082294796, 83368.17775393529, 93333.77272999028, 98040.21140515478, 95989.00633573762, 70313.95956271731, 81510.43634631882, 7872.113776890955, 67350.05291082963, 50275.09700453187, 69685.6864105898, 25612.72338495443, 81170.18901934191, 21428.752185927104, 64041.628597617426, 90237.42944334431, 49995.72791687892, 52702.89490565701, 36707.72524201583, 93485.4445963595, 50131.395548950975, 57776.62228545379, 5020.038377724379, 42243.22321015784, 63129.71466029984, 64778.21518907456, 88153.53830294902, 64565.604498244764, 82551.32264916378, 4132.763328743694, 56530.51463935063, 88687.97399508768, 6082.350298108808, 96155.94475908561, 56608.70045456194, 77516.03485516689, 24634.80169834006, 52428.0254764509, 91321.91995536843, 15802.139452150044, 55120.65055430698, 69270.7962775688, 32390.29828790414, 97989.2740447598, 36590.70946184573, 78269.78544424316, 50132.334501701735, 33839.82988472992, 97543.64033028072, 18633.604969065265, 33372.075007050684, 42955.69547614137, 79663.18084866974, 76687.94957192117, 80509.85908800543, 22231.210562966287, 84930.89168754347, 87581.50043147765, 68129.83439309328, 64787.48402992624, 54498.94277091072, 57878.29777828426, 11319.729650772859, 85046.0468575581, 19409.542255746128, 28149.159500677255, 85165.68327089939, 19734.06885234623, 13643.466269043314, 81357.58034420443, 59448.266668910765, 34586.83863627632, 19373.32787401156, 62415.50830227045, 9727.579007907372, 58156.06984786437, 17666.04987507543, 87571.92310698614, 92770.89997002807, 82304.77595192936, 73527.68750141555, 9536.67344359651, 15376.56025921893, 83300.15708779635, 27769.6156020516, 36646.57039980492, 32270.84724597182, 78508.57088430165, 15592.490444756557, 42747.35505594069, 57138.80067664979, 33260.48966642244, 6123.249637706241, 22033.270403671555, 45990.72721401093, 35768.93355105096, 49934.19113463617, 97724.42831002113, 39500.897139309454, 55196.87068948526, 71987.46759843153, 40576.762085460614, 45624.98393894767, 75613.04828226884, 37155.05317734192, 83775.69536151104, 32081.806523386014, 19068.197973460476, 43468.813746460466, 49360.034987516236, 73869.97182950184, 90757.9298735903, 77129.94057164472]} +{"id": 833, "vector": [25315.951996168373, 11816.144708216181, 90523.3944597384, 17791.09712590764, 61569.2826480749, 62463.46428875464, 94676.2950496543, 42933.95107425921, 30446.364871990372, 99974.90546162834, 57107.0061923633, 68335.77993117165, 21045.258522764365, 43135.440980252315, 69817.80051538414, 23536.4780052274, 60462.34610742235, 30812.653245139874, 55510.55661047289, 65799.09405287147, 19576.266030724288, 93227.68762373649, 67085.45020776149, 86860.02929657638, 82664.10506724784, 14258.996607983832, 38190.295888864755, 80311.40825971593, 82689.85724616231, 6806.946569715166, 6910.378815777318, 67730.93901863878, 81879.56404154579, 14435.113287600954, 2235.3403315174546, 86629.36330167361, 80603.48211267724, 83709.68289143895, 1269.0066726070204, 55749.147577220625, 87424.70360386085, 75153.18921893775, 23898.686636016155, 96942.81055921793, 13165.215087680115, 9386.89373978725, 62611.873371900314, 37744.734987454045, 91410.20266092083, 12476.637630704601, 97573.23123088716, 9143.428248951825, 57136.33510719321, 74267.14368584035, 59966.312926010345, 273.25967227438406, 16032.656142907232, 5104.606110350085, 86411.91839356445, 95499.7326022923, 84750.79507352757, 49362.47869719665, 11520.503876777522, 45609.31654299508, 27575.366787630097, 97267.64560897033, 11974.722384230063, 60542.93141855919, 4385.867851424497, 8263.695174361917, 76886.32698854423, 34360.09390081707, 29103.46870781927, 96824.79341530227, 4782.858038319093, 7174.811186570684, 41540.210303961954, 12110.277365550692, 29781.216611360516, 54592.3943689633, 59498.70236146985, 52614.12459317236, 32321.5992368734, 50782.74217270935, 71876.3616711199, 61618.53595146195, 70623.3829837989, 8297.90956129548, 880.4020691216441, 77024.30307139194, 51639.26788138245, 98054.98815828327, 566.4037380767151, 54077.16738491321, 3052.054390951986, 87167.72054096658, 50832.241755502735, 45274.52099525857, 26901.72895512829, 50446.78207456379, 61822.18760845802, 96519.4582501811, 57501.11244621181, 32682.98148066492, 83387.40815855129, 47880.42523272401, 51645.25612003165, 16167.460666328981, 97127.8858904358, 2951.617755499081, 81317.764003705, 13694.284305610316, 50097.83001952014, 91774.86801119574, 59147.69322596629, 91680.54259658494, 73494.40334897964, 48039.92903240263, 73400.80753950484, 93887.90639470099, 27597.893750232895, 81942.17608365281, 99721.54981875145, 17494.81172030999, 55681.863598206146, 35803.279293496314, 54793.21490864364, 90441.50959060609]} +{"id": 276, "vector": [74182.25093108274, 3421.0776287141866, 51402.295468437274, 59132.1215748222, 72783.25463761527, 7557.576340091843, 28047.383655871872, 53253.988212549484, 40636.220347921124, 54843.06525523745, 36969.01732792462, 69984.47121375895, 44380.8505351973, 84097.63203310048, 23150.43172043665, 94515.28070710509, 53290.86579502237, 26863.14498923751, 25389.328299054214, 19120.941172875628, 23122.268992698282, 83060.47892204084, 1348.4020687415389, 57175.546977229904, 14417.255780128735, 5597.516800835045, 17206.847058483123, 22451.322103499748, 99283.50823901223, 72243.07657715683, 74698.0335462794, 15708.681820590242, 50233.988956428744, 61426.809883713344, 48781.487422157144, 92070.6494230634, 84951.58751025276, 63420.74137454244, 96301.14138533796, 1749.6085012369101, 62493.28626592575, 49993.79890938672, 25543.16754147289, 69726.12399779764, 45561.8548786404, 59129.80942447572, 48413.77247841975, 86390.79925567852, 80206.95127219503, 5945.02698507825, 38749.123162706026, 85467.64084429141, 69260.68115070734, 96416.67973744978, 5339.933948821185, 68408.0543717248, 77441.34934105435, 5068.2961355382195, 79284.77333564589, 82571.93705105987, 15670.0236702775, 49695.97453537443, 5720.887951675202, 6195.765332887071, 85251.5996118346, 6609.922246728294, 14367.57925903187, 94580.84830482338, 17838.575636320187, 16867.269771770676, 24802.43287358053, 57224.75216518205, 70989.99706312288, 48187.58014210077, 25768.611151585807, 21558.846012289512, 10381.011226926417, 3522.34681719823, 21247.304897425067, 31660.435094468998, 45229.29099689153, 97148.20316789746, 1344.3704925948264, 20891.671046864347, 64336.94981791182, 63717.6124887524, 976.8165598482481, 39175.32003497566, 14241.36264752065, 27795.14297989356, 75126.39794249422, 27427.854194274903, 62849.45388429402, 1296.4384364943514, 80341.76718900935, 74641.2071068208, 34168.817138324994, 46208.56528602687, 96728.78990604292, 84440.31286844607, 59771.39442615981, 36820.67505013563, 16930.88667983027, 26339.853945308532, 36307.899747365635, 29091.362085856497, 27323.019514380554, 8070.70997951489, 17852.079662275366, 81021.65001751053, 29225.06013609045, 64122.437709424375, 1703.718915860164, 13976.49635967142, 44968.80881799143, 10635.241505129388, 41266.21669032983, 71921.43246132342, 73275.86448467908, 32816.512239113494, 28013.521850689736, 48662.63903028396, 40549.1992142885, 5243.018326262028, 31854.860602710545, 66514.21663841787, 83516.82603653806, 46503.24503713842]} +{"id": 599, "vector": [39655.77102719962, 23149.767625856133, 33058.47597930258, 21190.57346196336, 96413.74498773756, 41983.64382089806, 62263.10053275946, 31512.104057810775, 31086.03899900637, 85020.13021709849, 39843.625971052046, 73307.68275479894, 89531.04539660417, 85032.2352059019, 12580.40743422465, 41004.629759503776, 99165.65282239176, 25160.651773752106, 29190.625107610802, 72748.97777617573, 94947.14350254176, 75283.23102629109, 37093.51480909731, 80124.7159167926, 59265.83063345079, 66521.58946688611, 85720.82376059494, 26713.42932291162, 75306.61819387671, 74204.39326952162, 28853.461789686342, 18722.533118599426, 9255.989660517062, 98991.14254292325, 11370.169158193356, 49707.346255174125, 56406.96223533771, 72930.50223466854, 7395.394271534761, 30612.342109750578, 70123.67566617035, 63805.251684792776, 1773.1341739555928, 47270.15835507957, 85304.87620718873, 97167.40599082735, 81009.06555151835, 86524.82019387477, 96615.68329175557, 30338.43043388308, 10486.287509381786, 10882.034863668532, 42786.365762245514, 87809.33041416238, 51983.012484883504, 50413.65918585172, 36951.61698119147, 32691.666287762135, 43573.00538485724, 52102.0022280176, 53167.54052223963, 8280.755929819328, 49345.450110150516, 29375.531195850413, 28759.44820496158, 47889.45572352172, 21739.94588614857, 42149.49410837319, 16180.620105208809, 65577.54178854352, 2452.259435786941, 97681.18631365808, 96025.4511449572, 58881.681595556955, 42683.93847161296, 41283.824671043236, 5672.881446906886, 67785.45658002711, 91664.38995757439, 29682.6063326748, 46459.492064845566, 81802.79987798071, 52823.547949017404, 54505.67569936813, 26138.999710444587, 67873.93943225114, 8617.23053926472, 89480.41436881103, 71197.89024895291, 24040.91195858081, 44475.464998706324, 74797.97614049552, 22519.01311209089, 983.5293625676145, 32369.432970208578, 98382.92871116562, 60036.8387152536, 27557.621861523374, 72259.13823335206, 13926.418423363928, 370.51390207309964, 12811.49020904686, 26453.257446349486, 21940.521553947678, 85734.74339759327, 41871.5694390643, 70755.11757699103, 37402.685527900205, 56640.58607927478, 68228.66209843256, 68431.05105319808, 48107.4176074492, 87485.14180239615, 66333.79610788345, 2353.7239178806344, 88103.18934692317, 83645.99004955325, 6424.382877850743, 65201.80044484231, 44630.775239160794, 47466.91163281869, 70372.1880725621, 58622.15780338538, 6867.692440540185, 16464.469522875115, 21503.48661981345, 66481.96581874683, 67606.75078380032]} +{"id": 467, "vector": [31101.423773507497, 45632.66637561909, 54452.597365397596, 96139.41353379982, 94040.60404327347, 66504.01060252491, 73799.28122678609, 89031.0091633331, 83622.84140133743, 139.65696895525957, 4459.982064897883, 14578.16500137179, 29475.92168067773, 7902.563475952995, 42668.83161544539, 73993.93322573445, 33746.51843835973, 77631.6995711227, 90104.67554914813, 45408.70511576883, 35982.18976699681, 32467.615194265654, 31009.95982586181, 5394.032566152218, 48256.837029104514, 6355.717358127644, 71823.83221459606, 98383.30760710636, 16136.82203672252, 67052.2288538433, 36282.02025317004, 19137.73160518637, 19802.05409589928, 70510.79298419468, 94378.37377284946, 77128.40252391897, 38829.64541474598, 97714.03804752782, 74226.3368029483, 95289.19791766835, 625.6321324063952, 73845.38893244024, 5374.321507531876, 31100.72978720606, 47220.03541691231, 33840.98818057589, 64439.646876154344, 93423.83225836995, 81662.87333331125, 75723.17910915945, 30430.294725077267, 92925.58836125657, 24169.58963421554, 89297.74035087565, 10221.672255091884, 69288.95499646958, 49069.42967305632, 5414.062845210709, 40140.4285189439, 56064.35290667808, 4029.5076890009996, 21936.665888995798, 56176.8117160734, 89833.5896366163, 96771.42510133867, 7602.040232432328, 15350.689293343456, 38757.44171435136, 78630.08108407822, 52972.63692667449, 16462.432477808332, 71768.80329540503, 12126.51081168018, 37877.598941547476, 85629.2566190092, 59648.629508587816, 84822.07841422084, 14528.580538294778, 27892.935003041064, 16555.555965693668, 82213.41506962056, 87865.38291378814, 86981.85228554574, 55961.5072319394, 68668.05662591373, 20877.837298845992, 17881.65051977133, 34547.245137444515, 18309.803418402116, 97055.23904248791, 87927.10814958382, 16027.322568442227, 88918.31208713562, 30648.159910284823, 97939.62868589202, 5007.659804608644, 23298.3944973431, 60595.114370406634, 86268.67270327685, 90784.06010133159, 75634.05326144426, 97142.75404734882, 15374.15402205411, 27890.90555192616, 66100.84751286817, 51949.1229956399, 93395.39745148104, 13233.568096019842, 87825.81270084665, 20878.089564408398, 90278.37657273933, 67896.7024395442, 5241.9774304380935, 97193.7132342657, 11811.324354688546, 4781.535772675372, 85214.34831680908, 39502.37707609858, 59017.94099743546, 2185.523730026573, 90584.64447537686, 19225.23106607036, 17646.474598832883, 11870.454658662833, 27712.345937450522, 56202.09924790919, 88307.5279651005, 831.6534089392813]} +{"id": 700, "vector": [95978.61248663117, 4210.705259179482, 52410.894220391034, 8956.423590818196, 45593.75519898408, 43629.85628438901, 50648.466773747845, 76621.66832110043, 46620.631228128754, 59615.90256690111, 95723.47185380977, 80431.72618921178, 14904.024605586097, 62178.744848250135, 6247.149383693562, 32058.48675285069, 45414.614785739635, 63250.95080897139, 87024.55902363299, 54607.56433243927, 50907.5522074661, 46276.66794081163, 2724.560035482304, 51870.52593971455, 7989.5120085493045, 21791.520141160603, 149.29248092490076, 58414.058635620146, 52977.058043083736, 48723.068178056215, 47000.73990948286, 45552.027852816216, 70989.78381424393, 64080.16004618322, 30325.234737732186, 77849.12843627538, 16612.180030330426, 13653.399859857807, 63565.22214827743, 2262.1082121978666, 52594.709953392616, 68507.63593488999, 21289.75198728165, 56637.64447405466, 96418.39230412696, 99431.20554781769, 31655.990446731706, 12193.96139162513, 39738.671107090115, 86463.87669509264, 53160.82759801837, 6084.791965976699, 25308.780432228406, 80614.39085751922, 37532.66638819732, 50468.18149662811, 21348.69242399825, 6720.930233012578, 19317.67510774871, 71451.48706147728, 48058.62039970924, 69673.79699988372, 40882.83264563, 6222.076634954455, 31902.462091121943, 61275.09260448303, 517.0172011508379, 88399.52541678211, 88399.55950978676, 59464.71446334552, 26546.087554444443, 72972.1325167188, 16240.61345485609, 89687.42605698777, 62140.894894789446, 41702.97857887345, 81336.66294793651, 76907.76181354214, 31463.835128425744, 79542.97544737346, 28047.04435653499, 3193.8653124075845, 83046.13277854414, 42229.90402554335, 51.85741095591334, 1524.7718863463988, 48292.64684290745, 49478.74637782213, 68139.48351848288, 96498.92576391298, 74347.15479490538, 75008.38370825046, 44657.08663429202, 39389.48758731729, 40964.848601481805, 46785.51820432185, 81623.37897294566, 12954.721781955037, 70142.71858868678, 59389.54330529961, 83462.6643008641, 88503.8620788691, 9076.56212283282, 40507.58104752754, 43462.268220102116, 92723.05264885256, 83288.93112998766, 71358.40229085468, 6942.863119885934, 50985.84660114675, 44806.73949363817, 97789.37461934364, 65364.185764224356, 6055.673874780731, 56762.03512858512, 95965.48691872103, 60849.216293073725, 20838.513187545115, 62053.52833411003, 37055.72143859523, 48192.26308142968, 65645.48407282148, 10535.718930273018, 72732.90157559312, 76785.48384107946, 14557.574987131227, 87012.99250367508, 91427.47193410907]} +{"id": 2006, "vector": [28375.984567947897, 90379.92844032437, 56005.42629930611, 24454.951430349247, 85411.92380982841, 84297.74273697163, 90574.88547837806, 71259.15045704054, 93696.88919072083, 14709.2248043871, 84172.46925149573, 51800.2679271488, 5914.554144058481, 23265.28705579387, 61929.79274081235, 64339.571282909215, 30629.492051838635, 42107.61754048272, 51937.02439688548, 66449.5062417298, 57852.69716264495, 45701.196811054455, 38994.56366387024, 16695.646302443834, 44521.340407898046, 35204.565688195355, 97623.64318811234, 76983.83569657632, 68044.52144890865, 82744.02270156874, 37800.830355541584, 26297.28663471399, 95015.16625544504, 64293.20760313899, 11236.03787950862, 59827.69956778031, 97006.00545309315, 64848.311731599104, 76946.48359404107, 33617.30304395024, 89332.81056878802, 36789.13402829582, 51731.212459525486, 70552.98789304505, 92133.82794600505, 24507.523201788674, 52031.58133561307, 77271.41880044204, 54580.32245975271, 16056.97414631263, 89136.77165202422, 21515.403486677485, 9499.580996824154, 18510.62659000897, 46618.02041143983, 76055.97578803188, 76039.23454405555, 72235.97236776313, 34452.68318626498, 92101.8418819809, 36610.06782678129, 70894.44373981681, 28336.520081483242, 44784.03901674952, 79189.87623586725, 34707.69967861303, 33067.56969678035, 74898.25527381824, 9005.936415700244, 45386.67947776095, 47767.43492235803, 52863.997407215655, 17062.736986825777, 82468.67645083253, 75145.27777159013, 20812.07042629718, 30968.548792975038, 57926.66508433581, 928.2326987846478, 62025.499012019936, 2851.7369507788158, 94868.18286067621, 12492.059732586846, 32791.65087946555, 34063.512546351005, 10143.440786642455, 27120.89662915246, 26300.839644143336, 57995.01355678219, 14737.84294820325, 8562.692091745372, 91128.43603351261, 59212.271960599195, 80717.77648507355, 92414.34920850847, 74331.09454173993, 54679.346557737306, 9166.469914248688, 26735.226666164024, 9415.278079346168, 82926.17789625222, 66118.0938290161, 53680.21374126826, 63316.05362598936, 16598.715147351373, 47317.338009730935, 72620.85915572086, 25027.43852225937, 6037.673312158398, 68395.01820101716, 15600.491446324839, 74368.66107840906, 23449.75192429777, 80947.19243055132, 69215.10979827569, 73310.21482886733, 72752.92146211279, 91478.5974437304, 22714.621700104708, 22199.461456104764, 99710.6715223058, 2238.53767894977, 11529.070489367987, 8072.768191416136, 3087.8049862079206, 88367.62002629307, 4516.163039763266, 97901.76209447006]} +{"id": 283, "vector": [9643.893538216253, 59047.75859376957, 87517.3019916277, 1905.7027043503872, 11161.49268858435, 75126.90519961779, 25638.918289929403, 80853.33343627895, 27568.814507916795, 95620.71123082854, 88309.70936844866, 30128.48374464504, 29578.483277918476, 48877.288762592565, 65245.66558110668, 42794.00708230781, 65371.34684106996, 74628.37883631258, 61288.189970561856, 34447.60176006392, 24475.652628162792, 88132.17079970933, 26189.19219569461, 16146.714933741312, 27193.790605314138, 79800.60746403522, 74848.52636232949, 46891.09983979218, 47667.13225179068, 55207.27935707901, 40539.94873922697, 98640.16568165093, 79121.63047950178, 93532.87761939292, 1703.995273741521, 97671.29552848681, 73353.62683899552, 58918.84561890938, 15978.363400507578, 98678.95493095987, 53161.136861276435, 39778.062782446665, 32896.288507608515, 79903.28930465711, 90293.76655774095, 58755.35762817281, 1213.3678020382788, 51056.291939113464, 38144.82229163321, 9769.113935449313, 51581.776582152226, 48796.56347856851, 30694.412689420682, 14426.19141536523, 47844.185190818156, 47919.38958933626, 36981.72613845469, 22223.206081951197, 38792.084237558585, 23165.568290047435, 30352.019358517413, 30273.79551159457, 10041.714395709578, 86453.29734467143, 94001.2165704186, 91102.72003346546, 48980.548158684614, 10526.651298908662, 28059.70626966021, 47316.11701665852, 82440.53816471319, 99125.8537939847, 95944.01018344604, 97234.60168431968, 53766.57957555402, 17909.05263238175, 23186.488253137082, 93574.20998624475, 39483.91198789987, 97445.69148632193, 1399.9709365488554, 61362.80198601488, 75749.28713512729, 32116.08035933857, 98852.4721951831, 2684.9082914277233, 77301.01129486434, 37265.24017558675, 92579.67563175615, 26825.082112414224, 491.34773938516173, 25681.54766455354, 49805.47336447484, 85284.27439276973, 39184.99046938886, 88857.70907675877, 25133.914322666762, 84839.9650218429, 61621.23762814031, 2944.409433752038, 20620.444382226022, 74674.92456584315, 99996.99917049405, 35563.2951673147, 50076.64033213528, 91013.27437134858, 50509.528513833444, 84763.27909252817, 11241.088104421904, 50431.34594989229, 13428.33623771954, 34508.91979119506, 14909.615801222299, 58674.80292169923, 54719.63302305577, 10773.443901977287, 88186.3427548878, 19216.849889868714, 73678.42383454344, 97823.69733784146, 6860.426923162644, 29684.847711388273, 42195.56778158823, 96738.6752568796, 80611.3760269239, 77886.58989205629, 85933.49002449699, 61389.08714827005]} +{"id": 678, "vector": [52525.896093410716, 7671.3463917927775, 89035.62012767015, 25749.55232762347, 5192.7853283201375, 42878.48504840353, 49275.140924984975, 69971.49768783651, 20456.588330526392, 40606.54649231179, 94586.77381251738, 23253.9101610141, 73430.155540973, 80565.37625382247, 8612.135890540007, 16870.810052650166, 45981.83824346092, 40167.08052331972, 84696.35132232585, 5851.783136765265, 58473.32047173954, 83292.83908349344, 23165.215167940765, 12731.801726533331, 70079.63931385623, 319.4789237156548, 12233.80054323817, 22683.651771241308, 15354.956692413201, 61362.63758771245, 95934.60937635493, 41856.799585196604, 53816.23932658761, 92792.73850563316, 69929.75103924975, 23357.105590445106, 47232.38975543568, 8952.848655621037, 37289.79139146717, 9138.257906488867, 27049.163829090005, 51275.50223097732, 62413.04753372172, 79658.0609476015, 20450.89423729509, 87701.0004740028, 13441.581551605408, 78615.66359412929, 20927.865386766698, 11823.763151995703, 59241.97277286235, 73793.48952714882, 55159.32602843898, 52460.78495057809, 73270.74632984221, 43386.24874538906, 80711.85495128095, 68675.63702670774, 72449.23299214568, 7266.759043767556, 75160.69785492525, 89686.3003225389, 56381.774357294125, 95421.5852052406, 71575.46143217027, 14263.16575280765, 53066.8061372232, 95529.13716149701, 95526.1938558753, 96279.58034815494, 26063.34155970039, 20683.816871737436, 75749.85572187798, 2693.4061841961343, 18631.510788230033, 23349.24305613417, 51554.41300905414, 20107.796701663927, 90666.25253954041, 31405.250969796583, 83016.6262986476, 24427.89093122575, 46005.570802572336, 6111.370062334898, 42057.703057805804, 75724.82900437007, 17567.905067914737, 99748.39115949567, 71545.20814103648, 54048.733590431555, 10189.410947701572, 74535.81734746983, 97215.46749911898, 91978.28729637763, 15411.304904602663, 64863.46080286665, 65497.55538042399, 84254.03588303651, 8995.212448904, 95618.45491819903, 81738.76621191944, 93262.29636206044, 22660.63989105834, 97029.51342752865, 76957.31932305379, 20711.309785882313, 20759.996053667175, 14023.1783723608, 81029.54101570992, 26517.440642412283, 5467.512133403785, 70022.00282407868, 30614.65489808807, 2362.127485051979, 56596.59510828704, 15240.309601375713, 32765.328481496268, 69538.98225019687, 65105.6665445695, 36442.3120693245, 80193.20162096537, 97161.61342216554, 46090.02087102018, 62638.58666284853, 25581.063743343548, 34119.56829142944, 39417.243063703856, 61878.355723331246]} +{"id": 1495, "vector": [76569.91099506289, 16032.508233680686, 51820.781160212195, 69998.44378575437, 36605.66292917697, 56172.50853033897, 6343.688969407579, 93486.6970869915, 77276.2641219042, 96441.93260082696, 94138.23954249256, 37115.483936267316, 22587.518726188348, 15725.46935669884, 82229.90399964337, 72676.76281384383, 94371.91253436917, 12203.018450382586, 3976.5778919749528, 32268.9667119347, 4198.34955992221, 44515.984913367945, 91875.1678343372, 23169.189996583507, 77085.03218510553, 20222.728727072816, 8479.989046458846, 83941.9256286877, 36005.71875230554, 83820.95553771527, 51381.16843831395, 87495.27387768921, 22223.707960020954, 9183.308960225744, 69803.15351285641, 12174.449127639531, 64544.42493471779, 47281.74390435983, 73438.05070232674, 38234.695085467305, 14138.527085950569, 44279.62952666937, 57837.80160943154, 54553.451613778656, 56315.0663724685, 84877.27363861713, 56874.10025332211, 76359.74250119827, 74517.90557907546, 15148.906053497712, 92885.3137698423, 4109.560411745861, 21016.105584924193, 29202.811363334567, 75011.33087916646, 6825.7223032221145, 94935.44937489608, 939.8037278270155, 37733.651571789764, 14248.38499804969, 5775.6175560121355, 94551.92184904114, 44208.1697537835, 55086.82193419208, 43409.889474212745, 4166.850156449064, 96867.27622742178, 81748.84284678086, 34205.805953357936, 47828.06032251178, 92716.49225270869, 63962.71682251549, 14515.817707259605, 72468.83561233214, 55125.84787290206, 27015.68582778321, 60437.437937728166, 74923.78296153466, 53092.20151967167, 52638.84531063091, 30178.757008849312, 51523.22062071304, 59160.925408994925, 19452.449292623787, 48369.42878681183, 78224.11135127823, 27310.966219512433, 62267.89257056717, 53761.319636228254, 4207.511916078077, 87440.90461434021, 51478.24090817147, 96614.56717156548, 10387.731259433198, 84928.96858918539, 94739.7334978625, 76268.33965244966, 77801.04358216973, 68716.05476786214, 83619.82774927124, 44176.48200507096, 84501.04067903668, 903.1401661453109, 45768.81620682471, 6848.270535004574, 87478.98335324638, 15154.54911262566, 95303.72321983673, 94605.15343646704, 34360.15658148385, 8716.560683832186, 274.97573642141225, 48524.51137591989, 75932.23409961845, 80172.59782611196, 97102.86534062595, 48856.171175945405, 54275.41566991335, 82285.4478942716, 12152.089710065917, 2969.754943078362, 41596.98704715459, 5720.92370820424, 28761.95272418828, 63231.223122453724, 53027.715603553304, 55108.12341850893, 30129.1882273376]} +{"id": 1719, "vector": [14446.545608496852, 99175.92671432618, 38701.23148481902, 51014.54153959851, 76778.06907456192, 41583.58329574696, 77586.03908081519, 39640.68362506342, 38066.23976862803, 17851.261961987973, 69.13341060079681, 49226.64235776032, 60909.858660940154, 95230.28927217724, 14560.963326117471, 35252.08779924446, 24970.525060176184, 22974.360604250633, 70112.4121624971, 48667.45375273512, 68015.76653789009, 44718.43167567283, 76107.80531351671, 50744.438559242786, 40174.65787258179, 59440.92543090501, 30175.436865648353, 89010.87630513073, 2991.0951189044745, 91843.92548938713, 732.0742332969533, 67549.58561534811, 92441.53482061949, 26405.622713208486, 75585.98331742203, 84128.15799784791, 87447.70211882443, 55753.450004363156, 52108.85004716287, 45447.864838800764, 8931.854682631334, 25406.28964585798, 38730.328133211566, 56112.74555068061, 61393.47301228626, 10208.09511586993, 52613.68223039159, 50517.05977192946, 29699.073478288017, 844.6154100799963, 57408.83880308584, 90872.17509602265, 53867.0598266184, 67664.3719514141, 57872.46026445181, 2150.962170219206, 52054.48465941392, 86767.22330106991, 36137.31390934533, 55312.623736275345, 72143.78125976217, 65541.23640528994, 65366.77822015467, 71638.63942930661, 83561.54861100727, 75144.73097235849, 62131.982930458915, 21601.678995202077, 72426.07948473946, 90645.9620906745, 40359.071722829845, 26843.70545280935, 45962.51504172234, 18905.586010841478, 75766.01549738206, 24193.662520145954, 46810.55532729951, 68703.2605541338, 69116.37070312545, 63719.05734240035, 59067.29549922673, 98165.80994117775, 36319.87442176876, 67030.28632963159, 43709.05100481751, 31546.5551234411, 19214.060594985727, 16408.702363246353, 4004.030991406915, 26435.523874909482, 45039.931384742646, 64484.341816338645, 86181.31452606988, 44679.1197377491, 55639.720305326155, 54815.28820983779, 65521.39676693293, 44844.207044757226, 84706.05243395928, 44832.89478334918, 80644.63566037612, 98219.33771018639, 92355.51881464859, 39354.14601716952, 91139.84045827591, 58584.12617078763, 4119.198792016954, 96856.05879494583, 61732.775858637026, 41522.52529094318, 24200.324823771913, 37620.875616680205, 64161.56856600943, 26123.348955588986, 68057.43218862251, 28058.34513573778, 95958.23974147081, 80662.04169512689, 8945.202349043857, 48750.30303361524, 95176.92395401807, 60436.028607037886, 64366.4058841044, 34826.0008309433, 30868.072352985055, 81720.86784675471, 62424.51229512581, 10814.9889209458]} +{"id": 1380, "vector": [68538.29128266133, 22747.459190327936, 80477.12872107026, 59081.17934693463, 86051.58381514312, 23759.56864219897, 57423.34694386176, 26033.126738314448, 56066.98548675497, 1822.049050200336, 34666.41300323387, 97890.16120007464, 59865.029770066394, 35091.27543835059, 70892.91786765645, 62730.22948029912, 96102.90218847922, 43920.945496087304, 56293.414926709294, 80893.96949005255, 71858.15086650965, 62313.33072097007, 26596.065130461542, 72122.46400021734, 99232.8033520474, 84891.93588364615, 75642.7812081865, 85597.6936174652, 89880.37755006572, 89282.64109794433, 59575.42473990005, 42983.22146465084, 94467.34951352162, 13650.401120889266, 73669.0347686975, 49042.85784891059, 92529.24224533833, 91679.91843794803, 81273.94925586799, 71753.63082754966, 71873.58750145973, 23522.04025998298, 43856.129900084175, 1777.1896102447893, 66255.50715010057, 69063.17373127905, 72281.9669456531, 20853.130504930894, 56573.24033811039, 30941.005726012183, 63648.90129897803, 37366.015259791566, 83669.10348487894, 50350.349668902796, 9995.952345920856, 83622.42509740412, 35665.021873943406, 47503.2821186665, 8462.988725988207, 89781.13458672584, 12328.858515626762, 46959.19832544772, 48815.778455128864, 28384.136616988453, 13520.930468338933, 66857.8005639905, 5512.641154287301, 41279.19293318013, 36213.157324273736, 61283.45149527119, 72380.32154391939, 91214.94871763227, 11727.666613564048, 29229.884026998876, 99572.75488901544, 15750.939594718084, 73681.22320893621, 63940.73739932084, 15436.40982624418, 84258.83555269949, 35558.49787780856, 93667.02986143058, 13326.933787372907, 16015.259380645575, 45503.40313432192, 36014.77287862397, 76800.43656954946, 18577.454827687034, 39292.78009364936, 4493.042187287111, 5064.931770286929, 21219.210807244082, 61430.673899451416, 42330.47369810401, 605.6776509722139, 48909.457955609934, 29485.89892749903, 66077.91938078846, 99359.7870040501, 96156.04938963591, 46459.35556210864, 8129.363560891323, 26631.865684657296, 13321.991529226018, 16352.683662996325, 35810.55463643632, 58971.01445131464, 17630.581997167315, 3926.434910971821, 7964.63481978702, 5349.760382139046, 73689.14747864325, 38154.02212100618, 17154.127672622955, 1681.0756219039158, 34159.262040761925, 18580.912572959718, 54075.82203000403, 80455.0714359831, 42222.167379409024, 13625.79378471569, 67763.3609160915, 54140.852670652464, 25761.249888133563, 3652.272469787088, 97465.72480607772, 55028.77924462264, 7204.556609170742]} +{"id": 859, "vector": [34767.46945557594, 99727.79867878238, 9935.811640395965, 14321.255407697043, 89807.53893158691, 94846.81058768563, 32248.117633293772, 81702.91848537208, 61748.142112584006, 47031.911364098676, 81547.87485104265, 73629.23687430078, 14601.510584713651, 5035.160121593541, 97498.18262292662, 76882.6449055839, 66549.20083844464, 10373.643767768992, 36506.694189219656, 7584.864565106764, 1426.8446162468028, 73465.72877368382, 99315.5878445534, 56767.969710008794, 92543.62401454334, 79820.90119687126, 11567.890307422513, 31278.16645782645, 91685.05870337397, 34485.11420886777, 34052.28199879369, 19128.65610650982, 55804.90209576528, 76560.58928588414, 67834.22757380422, 86814.5894337634, 29048.35285984504, 60083.877372901705, 60604.26634573016, 11100.054549343851, 54617.932261469214, 67913.7248454668, 21064.284484849893, 72346.62431848497, 946.7273727597592, 61741.21629104564, 83581.74033632387, 14809.018994989132, 67714.31096701055, 86175.44476310628, 5176.898294636023, 52377.83293738818, 16440.305267088595, 29576.67937199545, 43528.901841709645, 49100.252357520716, 83732.6390184506, 71757.25466416721, 50637.1006607852, 70166.76148991939, 82958.70469788759, 7071.874205804729, 68343.6762000352, 75893.50855253745, 56039.71850902376, 89713.42546340195, 1340.846096394066, 5706.879193950743, 65711.6455839014, 28356.745555073336, 21694.035688507818, 24665.27998651544, 94841.02750502728, 37623.062875509706, 50234.04050696316, 20157.132890671248, 72433.2313993931, 32571.184886863535, 89162.38184983138, 2235.75764801317, 70455.09629572366, 8838.903100326479, 48042.438519271804, 5477.8974488472795, 41527.186149394845, 13163.972670828229, 76903.96277125784, 91203.53405384898, 95017.16147846296, 72247.73706750154, 44461.00627627011, 33167.52151869268, 68849.86925088629, 64684.97891317624, 7993.204990399106, 60930.37765584148, 6820.292564806607, 37975.91103472529, 76941.35900071103, 58526.847729817746, 25470.33696729929, 32229.91632525595, 45091.9969370298, 36292.9541762938, 57783.71721806247, 12261.719523175374, 16944.01992961283, 98587.49497079435, 74683.03624181582, 32586.942641956663, 64071.58175658438, 25725.957713966363, 60481.37034355009, 65448.99711440659, 58961.72869021047, 39664.193616414734, 45169.7766448848, 81091.73230173152, 41461.82414424045, 99256.77131464599, 31044.698131508674, 62668.32793532362, 32473.739308204375, 39459.78874492052, 64711.01219998735, 93404.57533675525, 75256.365104717, 83706.22451326178]} +{"id": 864, "vector": [65955.34603281386, 89873.84167112042, 8340.697087288574, 65309.70177519479, 8730.41917944779, 64047.01442483082, 31699.156859104947, 62160.005549156864, 67625.98066643252, 73434.63713779136, 32051.336260232056, 32914.95873992686, 40051.31793172507, 8720.66915533537, 81762.73186049196, 89064.75067298702, 64399.778490772616, 96740.21491356575, 4345.844309732827, 42764.61142005753, 47491.36190005753, 33052.16830569325, 21025.95641869849, 78293.2648757314, 28443.923441878906, 47165.962723675955, 41024.96070783422, 4330.5968912553335, 25089.95574297107, 71181.80742655526, 71334.16831212168, 37570.57855229073, 77165.03421654532, 22489.582459562705, 64459.97150494492, 90616.46418684645, 35449.48417030353, 99904.79756356971, 19152.971862709488, 49927.724913164995, 30945.730000083306, 22096.771152865735, 4359.181748432006, 95602.99064292124, 85893.6990945649, 45839.155153747226, 61477.209853844375, 52626.534505121504, 35919.121189322366, 51284.39809756028, 26425.986311690285, 60872.61770932636, 86861.24827371984, 41366.64098568522, 11637.966572153968, 7941.5022979218875, 79570.04155021282, 83951.484422263, 13929.678574470083, 32446.298387566076, 66495.92192607983, 85223.71434473185, 42833.0657180797, 54494.11421722403, 83248.6521662102, 71578.40234723444, 31356.448786376834, 87177.16913531846, 72336.66611338299, 4613.136420849706, 16561.418104101744, 7268.747098771766, 99064.78411613002, 28247.83989576636, 71207.76262398697, 423.29806886297615, 68162.33927373725, 2104.173788092645, 62205.04078924586, 94015.69076489046, 85976.34798627117, 28949.161074955486, 30849.73361164858, 28513.850762140213, 46920.74607142949, 60213.0011855477, 67186.21313735074, 24210.402140536236, 13625.750513389046, 62177.66444319613, 2684.233537010805, 62388.62675185398, 71245.52261717939, 2643.6564959278708, 94347.98478539508, 58012.828962334876, 69320.53814361256, 2165.3227484120575, 81691.47365625012, 89636.77289360772, 31702.73496895809, 69891.81047447764, 45871.55816708622, 39908.52478828669, 90932.08157031013, 95641.27253830459, 94083.66337613195, 6643.422808064858, 82762.55998418544, 62677.62494598257, 95960.62697001197, 89417.20221075918, 68004.94625618534, 72110.70348399274, 40143.0516625012, 36811.38088370327, 5323.223153164991, 37102.152828119106, 82012.91984279822, 81802.47721531102, 20828.999364443756, 97679.03591100765, 17257.375528604258, 47850.346838430814, 47127.509070830274, 90255.47355444486, 27981.92673298714, 28273.98646958088]} +{"id": 1084, "vector": [20200.98576812904, 92419.29822383796, 68667.8727448156, 70074.48316836891, 65289.757453107464, 21157.807151223664, 88898.1558582897, 75218.63208799892, 46174.88487196648, 9688.762491977199, 53477.345576838554, 44586.861219865146, 80643.87865713162, 33320.8731952719, 54826.62343062092, 81833.49960035752, 66572.00795804503, 73080.27796109508, 41285.43014279225, 60353.339490640625, 29899.42706904387, 7247.117316833007, 92716.37208091785, 83281.47482054471, 3625.622152793495, 24420.479129820083, 15493.53716261066, 2922.119112982646, 8249.108202516985, 89557.0512556998, 92666.20153955514, 88525.9036621667, 84608.64897624278, 26656.467275859854, 72799.75844718434, 86735.87546897835, 43208.151361497214, 86274.83057322275, 60731.409915828335, 63719.05360595832, 18758.56865097981, 52928.60102418831, 1145.2653956901981, 32375.9349008516, 23339.52280414321, 23956.84244330253, 87783.41673471728, 5253.1106695376975, 71402.5382714104, 80817.55228601543, 7841.16553776425, 8963.793146922339, 44871.96054162069, 91861.04822270687, 15255.448056036725, 40911.534195405475, 79432.86520498709, 1493.8071301321077, 39459.45855615898, 57792.198845095634, 73251.47452777311, 18977.115930792697, 22446.783731870146, 39425.90420058555, 56443.628584772465, 90976.52602120336, 89189.6343631789, 60636.987583606264, 66514.9462264046, 95532.81829919518, 86720.30118817432, 49338.346254970354, 83452.2916636316, 70235.89001292872, 777.2383005676397, 66848.56788086252, 96762.47548783408, 26304.901023061953, 68295.20883115761, 87793.45514350614, 63196.034328482885, 34167.41295913305, 52147.17704933991, 36151.85842051904, 79519.41254463347, 75085.55063571554, 49046.00826408708, 19891.934906878527, 40258.72580701393, 64950.05866119776, 7073.618897427947, 43608.45631131445, 80988.87369547908, 14519.718018667638, 24113.14313630425, 34253.972665295805, 52496.620883844625, 67329.67129473286, 4406.359567161444, 20829.052149530446, 99430.20457264858, 11284.408898809572, 79023.93556393264, 11547.109378624176, 23184.164129482044, 10094.175807492078, 31018.931293472073, 53335.6039112019, 199.85566412694666, 51922.513085539125, 85397.88942681703, 45457.030674428475, 22731.954983781066, 54881.80354634885, 59331.59821288054, 19283.160978488366, 15783.464814230763, 96070.56366327064, 56391.1040327809, 35462.53025548236, 16776.4341216322, 51455.262036692686, 44494.15692066293, 39019.604153660344, 38074.707428661306, 33761.100413764114, 81313.84788802842, 91011.23827451332]} +{"id": 1385, "vector": [5949.188651354553, 73275.61260535251, 78955.931765294, 14294.040223699456, 70653.569205389, 54521.384243540284, 56920.80281278138, 51875.97843186251, 77771.93532184746, 50157.68855735271, 13895.019157424427, 22309.178272101228, 14195.55017195535, 97476.34836375168, 80656.3714950247, 47069.42650170412, 325.61538431103986, 98014.77901565268, 19714.27350232844, 27349.148459768458, 32781.95922841104, 51620.473799154934, 34302.78815081746, 52061.899269525245, 49143.62063871111, 87492.98619669917, 17358.026080576517, 68821.18766049092, 54717.78191973489, 7437.7042163163205, 90842.55328053936, 64795.296288450074, 68576.09964063975, 65920.21050442586, 97787.66687579999, 11290.261974266246, 34609.246080196186, 27997.637644371152, 78617.56360264936, 81491.65538377025, 71173.91634683023, 89973.7159048984, 52564.19677714169, 99372.94332587633, 3727.3665483393725, 62739.20834444911, 82227.12262180682, 89117.76454298996, 82667.7971189037, 75894.62872097421, 85072.61008156338, 97582.87642361323, 94807.08684115777, 52967.02861666006, 35673.31152636323, 71092.1303021353, 639.3322136766689, 14658.014059173296, 12739.834527159766, 30833.42480539808, 54678.48102039865, 26525.36069546736, 68722.07521216666, 68050.7360627654, 33046.99507186566, 13137.358852216496, 57504.562126684665, 55107.72020533394, 53991.59736161797, 98362.8012616259, 38680.30360987021, 12843.297735030557, 13675.47703299843, 10899.573527745477, 34307.698726464885, 71894.21147395558, 77837.47545777976, 50330.581828466115, 67909.66742664055, 56508.265048712194, 72506.6041305289, 61019.37440525025, 49322.00426497225, 84556.14016177991, 47918.818760065675, 32744.88072349101, 64542.56206845904, 13106.377316546914, 68243.09332743699, 94854.0542447022, 24867.39729352322, 340.24305354631855, 48476.58772217668, 8361.651539163473, 57348.806472291246, 19051.5195721907, 58409.180018730476, 53429.17170121, 6629.814769053854, 13151.519918468292, 71063.38663068728, 65989.56813595004, 20110.44514169419, 15073.720562287008, 23190.52362192585, 94018.33902119078, 66340.48057400274, 22271.102257670616, 21229.56916404255, 55974.197421147146, 98908.60799647195, 73451.35747076284, 24371.084360789297, 23324.31829682413, 12968.071741743714, 18888.437278654867, 11968.679192894726, 13779.034960928582, 32853.4386569442, 17938.45448778577, 52511.361457067396, 4667.745867672746, 69980.7207848087, 53874.935142190014, 47420.77244938679, 57223.815001635914, 28688.733661141752, 93255.57074719247]} +{"id": 1384, "vector": [17060.37236184925, 96828.38602073515, 97410.17382332291, 59214.22727593434, 75371.31784754412, 97052.22557452084, 9554.396194612624, 18886.435957029735, 76777.94164830902, 21493.154940848737, 63268.5469097513, 63614.5892239279, 10334.755682120722, 21116.305195224206, 96678.30738062297, 67634.81151284711, 26220.160848373365, 7100.212915935811, 51360.16308421579, 62735.219335062146, 30367.48060239195, 44273.628945487, 42604.88880697968, 21296.313152923973, 55896.08708166164, 62966.960903896106, 45921.2911639475, 91426.5344949067, 31130.10095616051, 97045.8117310338, 80805.14515360352, 59380.15748357212, 82762.48488762388, 81472.74726722106, 94287.16001847199, 12012.534058337942, 45015.71283356197, 30126.981833513822, 67751.54382691812, 10808.5692576393, 79970.5693750186, 24142.27980021186, 92134.68462982462, 39708.35444861074, 31482.992959286614, 53254.70268609246, 87323.52576926598, 76521.34267493663, 5107.577669016206, 60583.59058722994, 8570.927449047416, 48578.48028793609, 84925.3383275512, 41785.50564775645, 16925.209760343583, 40492.81991479446, 28578.443052831626, 47248.51098179964, 7225.137264930859, 72613.39289418785, 30389.533875864705, 79623.3195756842, 82662.19119564329, 945.6720767094806, 67259.18227047646, 6717.220123277933, 62959.98157237776, 10456.506211220174, 63876.81877665241, 86051.19058637119, 22687.745143949865, 88869.4520854228, 98793.55147719142, 94335.82181856947, 79176.29989515078, 96393.74914715368, 25913.44036943194, 46464.7747496726, 45362.74554146563, 96534.23557947202, 44329.43889791232, 95098.12804404125, 13204.663709831233, 96872.46375709266, 77939.57211177972, 30596.733310805124, 16299.945173135055, 98957.90045643903, 79368.10085787598, 55239.758072697165, 15666.042790912605, 77208.64848872699, 7260.0459762234395, 23714.65126953852, 46466.60425590413, 45479.37488820049, 65309.6049854487, 11844.820503666597, 16695.31826234144, 65782.7957037622, 59301.337147891565, 28066.59955702242, 9453.74342395926, 83063.4355961752, 70799.50046115226, 86965.01397067093, 19538.073230637732, 28120.080689557304, 39642.560955955894, 30925.166849757912, 20378.465949247737, 14005.996276597265, 84948.29453122921, 98972.08796717288, 27617.3891857849, 47143.93425495837, 52498.70647782585, 726.9236374358612, 82039.33673460281, 88183.77761167871, 16023.550211608783, 45342.50696992334, 12683.249562933408, 50739.159019260915, 9958.462437001703, 28706.49431965563, 95461.46156641713, 73199.42914376102]} +{"id": 1211, "vector": [1865.948379839244, 67827.41262014586, 73124.32236757243, 26996.043447869666, 87291.88052774026, 78772.65811888399, 14830.745761852804, 91226.47026434958, 26957.752192863372, 81870.66791158612, 11772.477177746088, 23910.55479600176, 84788.40880003544, 29851.874058439742, 96016.11876137332, 48256.423096071434, 1152.9390900073545, 77141.418632585, 54879.09908715471, 93387.08748787173, 16143.87473653135, 6780.234602381485, 45208.994702421114, 77431.1295555812, 82771.4508693558, 52769.12944496698, 21304.809845771477, 25005.96741376212, 2254.4758369914875, 80518.81825311981, 87913.63240015807, 24431.56595514747, 19241.447042817374, 56737.61544586413, 95911.70435774596, 34038.43638607915, 15452.127055479548, 62001.41183888318, 89772.74621399019, 3992.153713779356, 43808.50482616159, 23029.677320522846, 24464.781700929972, 17960.58341206782, 63893.4687627011, 82964.27330333613, 87536.48008129276, 10377.473812923821, 59755.383669474184, 68483.33150068329, 67505.73265197054, 65989.74766403357, 3399.566405464616, 31599.365056172766, 8247.582433079448, 41294.56369710488, 15306.958027006778, 36091.21188093848, 18613.75648833554, 47818.500481239, 67292.61996489459, 6561.782553680307, 74422.60363380032, 42939.158583187535, 93850.65563398716, 34713.56305181118, 28642.557730510543, 69671.00739679934, 67457.34363452267, 36135.550863649405, 16120.766415210985, 63942.044395155885, 16785.212949606153, 44496.38393360021, 46939.470376196594, 93422.88461907617, 65412.71283307324, 36131.3717479668, 61192.5208695207, 86092.46690076776, 57488.51537853424, 97938.50118458696, 67979.11555621239, 52732.44816451986, 97983.03570474924, 26667.181219676593, 29552.26379733913, 63016.05369543593, 70523.75320797431, 55254.3027019263, 74213.51812294073, 53673.63494527849, 13494.93759328425, 13900.000743589202, 63514.74533773468, 55199.46523590556, 26967.908353721792, 61412.49008720619, 26203.920673640412, 59711.16206422019, 1758.6228661494063, 20057.376203251253, 20617.602904409825, 38209.44836919349, 19465.75147373649, 39123.47429519582, 62561.0649541275, 78621.48478387631, 92929.51166983144, 79945.95093814677, 28432.799164770928, 89988.4606590722, 46221.823788941365, 49425.72160915496, 60983.86310783944, 16818.741822815053, 73100.53555605115, 92502.41610547148, 98165.05830049104, 40135.01728088167, 96049.30180551515, 7674.166166906482, 36474.002175399786, 35554.868546237914, 18286.030956437917, 29181.11398720572, 64320.181429725744, 8296.50040390012]} +{"id": 1265, "vector": [18551.52840330171, 2659.69302562582, 7052.7947029134675, 33512.0600525738, 85668.1358375876, 9774.040740782086, 62693.22946453167, 12314.562111446114, 14759.698194598424, 7611.133458894515, 18361.34657384727, 68910.12373019003, 50880.351300080365, 66225.57701190336, 42248.980203734754, 43890.08785227526, 74025.32658405633, 95665.85575110867, 37225.53275984462, 99019.1138613067, 3436.6732812279865, 71396.56228479015, 7950.70381291223, 26071.886896662723, 59494.32293851139, 62566.08350357603, 36687.876024210396, 62790.308583246515, 89431.71038961559, 2831.87080798315, 49024.26458170962, 54346.94995733208, 60475.691050242494, 61280.61373093797, 52202.42635565602, 27632.29776025561, 23237.83569427048, 88576.36230288736, 63941.37999339044, 53758.645381324444, 68268.20386609895, 75780.41310937706, 14458.982948708177, 19798.72555161436, 38961.84070974169, 17684.327252881314, 22243.031032786886, 16527.68446592955, 10390.897384293696, 44748.431937131536, 85328.89796244595, 50927.99312730679, 14057.800652006414, 42402.25355076123, 21512.37920449285, 34356.81769322958, 42957.08718919313, 73409.04745768006, 63830.33123219246, 29631.34612257151, 15557.805589024743, 61911.73963046015, 18264.819083793558, 29171.687737645636, 41481.35823167312, 90731.45519522228, 57139.20600878327, 13789.114026091687, 12349.777878072488, 30429.075518829417, 84140.55075280332, 63572.71922727041, 38995.157402353056, 11030.602651811761, 43342.38391502446, 59459.18579868118, 96516.49513198397, 72918.21222951656, 50189.54978544847, 46261.26436677566, 15325.329280381084, 67311.1239032152, 20558.14620476504, 13430.346895270073, 27330.678293472432, 51079.52273587809, 17890.409180232935, 84684.53097559192, 61854.98498841836, 33032.66738294004, 10189.075705809903, 60894.78399450207, 24989.619033963605, 79435.78704755325, 24286.0443954923, 68375.24800709408, 54655.070347661494, 17501.435997221037, 94370.40330915916, 50641.02538405634, 98445.31433838018, 59138.75594603285, 73880.85679748471, 19957.60766658469, 52652.8241081844, 62514.10515942192, 6984.856292453412, 5428.066118617625, 34412.02546896855, 61000.49769363736, 66495.22060091043, 33256.1024469109, 8218.779995578918, 58181.87649377343, 47402.30248920176, 6951.187932588398, 66529.21091780727, 70154.71510506963, 18405.19524117924, 55525.1234687522, 92695.21478119667, 50524.49771629042, 33797.29716609325, 26714.831605422973, 49090.15416691549, 74313.65996995555, 19678.836004033317, 25905.36272958822]} +{"id": 867, "vector": [10382.220385930896, 52291.10029452492, 87371.14356765567, 74015.2024099997, 1694.3984535774614, 12596.40340517999, 73678.33058626867, 3590.601156544804, 43978.98543482225, 71018.29659198057, 97494.0045522639, 70609.62180262757, 916.1409102619089, 95322.2373242783, 17792.126250925998, 35701.823391362566, 7575.215897007537, 49931.05729834413, 87905.69114951625, 8533.177941980597, 37998.63478150509, 98374.79338680983, 79484.13742886507, 67196.49566760835, 2061.358262197155, 19908.56602018811, 8256.037080361579, 2972.7072007730726, 39998.92114953551, 10686.964200410865, 56913.096792458025, 36809.60411489064, 51189.8067632231, 5432.694320918774, 69611.0572894159, 73892.05891238949, 66430.00121707628, 96886.31776418132, 4234.0089935438145, 5122.571170652867, 6533.301874779784, 67222.3131866077, 99275.89706449276, 86331.43111171824, 38903.50909958445, 26481.175864538585, 55706.46776075698, 94807.12185901648, 37014.92182231517, 22111.154852202162, 43874.25812570638, 65371.618248375096, 82853.16803681682, 26935.426580879284, 99496.38788772536, 50343.55647434746, 62983.32601317624, 72479.14036101042, 24984.58849903844, 72718.69708070051, 72886.97866528381, 57708.1763786295, 55800.012477299635, 90608.42693775093, 86094.4713035987, 46086.748550099364, 7939.466687318486, 52307.08901156463, 62702.336586498175, 87759.04922632521, 28713.960388758864, 27137.25979957814, 84996.94382466159, 54424.619484051174, 23278.73852319018, 89386.78464633443, 67735.17690879414, 84740.308116706, 5861.25946094247, 7557.268659664252, 90040.42229345803, 13016.685311385678, 6761.22416170123, 69307.97574512202, 7356.2549707289145, 4277.821958014949, 28812.30131816852, 80059.60516239774, 28537.39536987596, 60115.909814591585, 47709.99674461065, 95764.87850228821, 44238.93088509888, 6742.544690553731, 69841.20930535966, 31264.888061277295, 50262.23188295984, 81952.91354930862, 68968.29172404256, 10402.698728916004, 49172.858009033196, 67276.22935398362, 66013.60265950052, 50648.68326174016, 82072.5681259459, 47643.261467518816, 7149.3075101460745, 74919.48575203256, 43132.73592732876, 95196.71166278044, 56763.68095700667, 30887.87865454389, 70474.14202644705, 17443.80258055479, 18912.3468082673, 82489.53533970534, 5444.991780306041, 59445.40910922078, 47539.93171456988, 41408.46459643278, 35039.298609621546, 55070.04371777139, 7003.41962115062, 20466.383229285722, 32316.175113786692, 44379.26014625733, 64176.590312397755, 68841.50467977063]} +{"id": 1498, "vector": [7100.3276697763185, 22551.25682928223, 21029.370741725295, 70868.1535292378, 9428.460536329252, 80742.81392145673, 68457.38381091398, 53419.107452605786, 2698.9958638951152, 61451.91965719564, 85802.68751221188, 91798.29702359159, 95555.75399490389, 43380.249535762305, 14090.372600147837, 87043.20867243603, 99835.92534447118, 85843.66155737253, 40412.91227287019, 33906.79813156284, 64797.65320908315, 20608.48409732935, 30600.82847792519, 98971.0743029527, 41920.811837927926, 83135.53844588906, 71144.75258431552, 68114.62147503596, 38594.826082808184, 32427.061851002392, 89087.39015892612, 61054.66901495789, 32096.188152093917, 63912.78925706263, 73383.20566921863, 62125.7107901897, 81872.60919131736, 75590.8022869488, 93522.44528648333, 37249.73007224367, 17900.750212576255, 18126.87763852555, 98049.76449089355, 24576.2151416174, 49014.90707875973, 59963.272772803335, 43941.79202354696, 79884.9883020943, 38452.04062845229, 34775.95444034868, 16257.256388810238, 92691.89526128751, 51998.21652843228, 32839.984112104205, 97999.55819348148, 25315.28702928898, 59617.9424413511, 76442.9836963734, 67520.73919945853, 35297.84441287172, 2092.6638530225027, 77759.07160998428, 5292.379221351617, 95988.06831492326, 81503.86875069609, 18853.709492726666, 65574.28858608374, 20665.839897713224, 56127.88599404041, 83311.68350402647, 55429.24166821415, 59901.42402270432, 45347.56811241265, 15949.213689177212, 57791.427378589244, 86642.77682408963, 8840.01977374671, 80255.82649409576, 12657.90287528642, 52851.40551858056, 27039.992227656774, 49887.935074211775, 19180.856335857465, 71627.66679323402, 28312.069271960372, 8192.023979437712, 4550.483413786055, 89888.8463726671, 46355.92998835025, 5379.7327636238015, 99022.77465767479, 50053.26527458033, 67787.51364542845, 97423.908095433, 69915.61233861133, 86115.51689478988, 45680.20770838852, 52929.165585802075, 96150.22292746765, 66597.40851351149, 9103.790650536337, 57403.01581257098, 66465.07951234854, 9222.59156044808, 83964.33724596913, 37636.64177986693, 92913.06331332518, 53034.98664270847, 36070.61534027499, 67473.05871543709, 65602.26416606463, 34811.94016477624, 90326.27602173704, 40596.91892685426, 30129.499752530985, 54384.36289491021, 84065.98976047922, 39066.84561945584, 29913.412601912838, 38045.995397158935, 53977.25556793621, 26901.444098058513, 72399.33254500313, 33078.19164362885, 92479.41553811496, 97764.24939839756, 47069.107611410174, 14903.792509357272]} +{"id": 1743, "vector": [54917.388823475056, 88367.26391783515, 18515.796834414454, 76775.98164508253, 39366.3375609257, 68593.92287112286, 96693.8497512909, 76490.03000953692, 38482.47155129361, 53114.79316474327, 29212.471484577472, 40991.121588414135, 63164.035208712325, 58718.84268838794, 29545.629506869453, 16373.471071786782, 36081.715284432416, 62066.0458928027, 82165.43181142166, 34745.17461377622, 94542.17904621501, 72019.93630932797, 25916.90783634847, 52197.0722695992, 8748.742950184464, 14057.589602839116, 2677.962612237217, 20456.714212995175, 18019.246694469526, 71310.53965031914, 29700.56730284526, 25216.814403661658, 23505.87514448448, 70667.3517858552, 16989.25922887857, 99140.78167610134, 47876.1220724529, 18914.226092802888, 90824.3856442248, 781.6174925931008, 98692.21640518217, 53123.549119310344, 62620.463083339404, 25308.911041015912, 64411.56403986588, 43598.78767903924, 37404.32309479859, 10050.935259920967, 53631.97275497383, 88438.90626373875, 9378.167557097628, 68090.72411895033, 24825.176011696447, 62831.57989654102, 59368.849881667826, 39960.376454523335, 11720.890977493758, 10028.894079916528, 77681.54495082193, 5424.77026303122, 50352.55866369892, 37951.41959311712, 8585.206470273466, 7849.9528069287835, 77195.909016439, 12585.037952423905, 81127.00671055516, 84610.39666636538, 14717.783996386868, 40220.07373927674, 59053.69311882864, 80683.7358465036, 27219.218801915813, 53131.66614280827, 46633.75065823313, 24821.26792810324, 92.41824065098214, 73291.64030655881, 67414.06360670929, 71543.28598408178, 1016.4227073394704, 48869.469191032586, 43876.953042483656, 32816.57007847547, 18577.691243443172, 32180.54415328041, 36425.31646931702, 88671.57128903203, 11394.67738573281, 31160.885160607533, 26841.104062294664, 47905.75525150529, 63451.37881868841, 15072.004997643251, 235.72512681176994, 58867.18268120389, 36602.33440127124, 55838.80726389196, 12485.903528022613, 72590.73837515367, 49651.53386307532, 38269.8345631051, 22287.44987200122, 17029.678583266283, 68719.46821209678, 7440.21151056441, 18130.215910855473, 72053.45041025375, 54724.50223092491, 83749.51616805868, 17181.777067698444, 82172.0244869711, 11274.23559808859, 67330.96272935368, 7877.533609612475, 365.78934288514773, 41033.53917552561, 89407.18929050243, 25713.984854895632, 37304.530966637416, 45864.50664905779, 57727.77443041276, 68219.3050181991, 65905.9836498247, 49856.88042320902, 23117.870868355305, 57693.549749456295, 43435.582904035444]} +{"id": 1257, "vector": [77948.00819755277, 48049.26234898686, 94944.7849141049, 49220.83414839108, 10304.67197703041, 45402.66152687037, 42786.88048858605, 85270.39451469167, 64896.86992796611, 33794.259577647565, 44238.43680085486, 22945.605246222745, 54204.02765000563, 98576.93726688827, 33216.49756874736, 41039.14516849353, 56822.95345775271, 643.675609533756, 61151.11066324954, 60212.32348565688, 1066.972062439442, 88767.82019932059, 74632.05497694811, 21439.578248057656, 91937.7290798339, 71449.5977391398, 29688.752266218165, 37408.60043672617, 37890.01424311158, 23594.100177553122, 73817.83031585676, 74923.39214411321, 12333.480659887353, 4526.658507419878, 93152.52466502452, 11132.709030393627, 66977.54060245918, 25818.073169872623, 96270.14191299009, 88189.80606995532, 86110.8412796625, 88191.42215409064, 67688.6325771519, 96540.31069059017, 95516.59194216896, 28200.831814676785, 80493.60426352742, 89045.1949236902, 13002.703626508961, 13256.404466371441, 57508.21378944483, 15286.826390443453, 17870.993002960044, 52106.664493363256, 82733.03943025324, 25376.22027672767, 44015.01431277938, 68003.10748161793, 36990.14499206434, 47288.72318500116, 75436.96830408947, 68769.1665222313, 5842.96643991663, 83392.36689901933, 45678.59048580588, 49450.49754477545, 97507.53634185338, 23329.12934512679, 96184.24897862389, 53296.41536242654, 26497.536895778838, 10519.4977183257, 80145.9147563904, 23436.78358772788, 6961.75297615097, 32979.12809178024, 70232.02128258048, 49416.238824938926, 91985.17521910921, 90657.77244265385, 98715.07720472003, 64933.504927383176, 44932.21888056107, 54103.47936738614, 57158.68225782117, 10240.300414167557, 85322.08374907669, 33076.10366582635, 50386.19070689193, 41283.15704952896, 25064.478775040832, 93201.84937816105, 10519.600893773595, 63493.81812411893, 8289.680441395076, 35991.766185162334, 97587.493604617, 84279.78889081547, 53586.80537096422, 2768.044676396553, 31904.823118180182, 91146.04715167651, 4058.0678036997942, 91114.46081523703, 49202.368001460236, 43436.954833173324, 92891.32339276781, 91898.40455398824, 76061.6781224608, 70717.82119367193, 60948.6962144384, 20622.559802305917, 79710.20258392852, 81383.38216728707, 11673.47129399574, 4487.645793551776, 29409.028437638906, 57037.02650440989, 87299.19767834569, 36243.710346921485, 490.98897062880286, 36070.93877386912, 31398.434613142057, 2694.5295262241207, 30217.598894532915, 49501.20155651834, 1969.4584182660235, 70276.98083750824]} +{"id": 2001, "vector": [59523.37921591393, 54395.60785987871, 28728.582396299553, 40222.65217027595, 53832.38551536754, 9331.804390766507, 64437.08036609821, 85484.65141233729, 69034.20641781022, 26056.237442876427, 24668.735940364593, 79028.76538158623, 72514.92022039325, 22382.957118706003, 45725.32528117907, 27151.725839226136, 3094.1820552010445, 15184.797931243265, 26319.053821962167, 73577.8535857409, 41321.85714321919, 95565.84391496149, 99813.27797153074, 33404.816293990334, 17490.313023717907, 44933.96409292869, 36938.38786656613, 23622.385634905975, 64034.80059861734, 63642.06297727405, 26141.896638796214, 38968.76769856107, 16415.55788088752, 65267.96466514202, 525.4200090969218, 42651.38626891424, 83750.08556688139, 84511.50073113132, 54925.71966208573, 77233.51461891852, 3086.772952338923, 2831.9199738301436, 827.2154758375616, 45060.84487564903, 48946.64670343494, 51103.71725793739, 17053.533586037473, 704.9700625591182, 64913.742134477536, 61557.08657216561, 60755.014054245505, 94663.0145534865, 8361.76273593171, 83925.38900707608, 28104.01178015527, 92284.87609143999, 51279.796635697705, 12981.453217993654, 59872.85401388357, 92466.69914037886, 57918.67324461979, 26785.683952659634, 27944.41512258613, 35119.231186796664, 97835.06946904416, 31350.084948916778, 95913.42508418244, 55837.28847694258, 40478.82282755627, 87680.56085804256, 66973.00876412085, 80710.73672463272, 19481.383159171804, 19467.061028689768, 92721.42427830782, 76243.50927994013, 27201.954288663375, 76380.89084628264, 87377.51126700139, 36799.55543471673, 98901.28548096048, 16851.197801959715, 47569.89972448493, 98487.53058362246, 45726.68091615002, 9162.423117412132, 80715.24847357777, 48842.720164396844, 94950.7856741748, 95304.75646032015, 45516.55736118228, 52649.293442328904, 54238.75451727511, 21955.28048561304, 64028.55523659792, 43941.749533058224, 50034.548903144794, 397.2620997650833, 47839.06905247035, 66909.67438315833, 36023.66057989629, 78620.29848500309, 45303.963428045114, 80366.955891065, 4454.740887057785, 67493.25997673604, 29697.5463609498, 98459.17691658276, 21513.704491829045, 50626.82033962167, 61855.39374295511, 91722.46158553947, 24358.923410244704, 95628.8401861342, 28249.114328956883, 59381.43647248805, 67634.78445190156, 32053.364293370723, 34027.140441252144, 48969.30740326756, 40091.23714974684, 51632.12314019984, 5482.994442371003, 26732.491272387815, 59719.465481654566, 92414.43718263903, 13461.625708428171, 13072.332535459564]} +{"id": 608, "vector": [62235.54138861024, 28684.279188108518, 28288.493941058667, 42111.099027857905, 4558.526138441177, 89669.07399396058, 36188.52874887518, 56402.7676291983, 79771.91026949215, 9639.164739571637, 57629.919666477515, 95343.36820283244, 26568.939296039094, 73363.91198137679, 63790.898336717684, 41336.854864997775, 42999.598222085966, 77959.99304490836, 96739.25355926032, 1685.492781448139, 82595.48519790133, 14251.363131659145, 26859.684965548226, 5935.365468194486, 2190.0324356884826, 40644.88809050021, 8393.203410352278, 26799.79248427057, 89410.66247040764, 61161.81130777111, 53894.32475837265, 26153.161891781696, 40416.90035055505, 4843.343863489802, 22555.203458195618, 68805.98495610773, 91254.57355571933, 36927.83303196868, 5399.9087550633985, 14183.189093070747, 10663.990560172231, 97943.46054493127, 36811.03668249144, 86910.06674754595, 51955.46834282606, 99033.90009848673, 7710.486152966967, 86127.42906846352, 75845.4426016443, 25557.1062453833, 99657.23615040272, 81156.34737555774, 91921.21229121652, 81457.61011341476, 51016.44563854212, 6534.2193096806, 59046.65218453884, 87045.71730715211, 56059.03016784824, 45948.1790132355, 45940.455582637944, 27106.632379179606, 84133.0789115584, 99545.983153674, 36835.63366992772, 67627.45855409192, 23127.21156791858, 79961.51512619393, 15448.180451396609, 38599.78267453275, 13904.1483093846, 22153.071126756273, 1282.7759514410063, 28168.864962512664, 29980.44982327722, 75580.89219354406, 60053.38031679277, 47596.969945203346, 46855.94717530275, 57254.999386081596, 15498.250623589882, 58067.49774689443, 4039.0272084050107, 59697.38668381453, 96506.39861086229, 8516.602631247517, 82292.53811124107, 92146.99437604222, 74861.14648926647, 44888.31049505877, 43291.32541296433, 43235.94937124503, 40321.630442630085, 63136.03924669598, 62056.59775510134, 86594.67916125225, 89835.88430174958, 43992.100113821194, 20127.493718924496, 16722.51554807804, 37120.94650471905, 23371.074464762143, 9096.67424504087, 61787.50884506744, 18811.855696288305, 14781.129475725607, 6587.084756501194, 65420.463333153086, 94692.28816918776, 8548.748122083305, 90830.9336439349, 24416.5406104107, 93434.59117278382, 6671.017050906925, 27229.30261287941, 6401.87570064028, 92042.70804915241, 58872.327220159525, 86362.94699179532, 2514.763018013788, 26532.99506936566, 288.58282454889974, 47914.94685088516, 52756.96508522235, 61985.728637378576, 27210.45450729589, 95117.42690721204, 49387.35038698605]} +{"id": 501, "vector": [22109.2013584983, 16716.10616959934, 49846.272637847185, 61908.044683068096, 19096.51305004578, 51786.521972427865, 81382.85761132902, 726.3561887042135, 89220.50112270338, 58584.31112390384, 23481.10297920293, 41526.1128158384, 292.9670988981892, 67997.35651170854, 39147.882675666544, 35065.47951049405, 32346.00397603371, 99851.71007818729, 38330.45057545289, 88047.1916701476, 72507.88487252458, 96354.77424191158, 74280.46156766986, 19579.19348756938, 3551.598705773662, 78591.07305403436, 45830.780186787604, 45742.927629593476, 14689.602527820743, 56748.932962147235, 30690.93969386284, 73004.43460545191, 44278.97170477112, 70784.77510943498, 52064.0000976551, 55005.678396384224, 9889.727313796891, 59226.579158046334, 84100.33724978207, 50307.09378494476, 89200.58981279617, 58433.89455289015, 69896.69748902365, 63846.89315302793, 18675.886586126646, 82291.78189760588, 43844.83815084685, 51764.619098393785, 69269.63471850073, 60189.23794608303, 53011.68632604026, 5478.629100890397, 80140.56641739204, 91944.51257535831, 56669.84698866933, 26787.666625793583, 36255.58622275906, 56294.39976663093, 60765.38196891165, 43319.82652221842, 54204.147721508336, 42729.92268895893, 14906.999301371381, 99994.70091942183, 93218.7760625057, 58298.87500946978, 87351.47359140028, 40321.97655221508, 85086.14420581567, 85738.5916345764, 76972.96599413293, 68221.61403448356, 49086.09637938935, 29374.701972313465, 77410.78315930095, 11516.896323504145, 19.43761946557876, 67224.23860958415, 85759.87195214049, 75589.21829484649, 47085.20387707316, 9356.649427048136, 20279.003882289493, 85142.33461928516, 4136.03606510069, 50908.02599599371, 91122.00863152012, 6970.951055823893, 28120.476986362264, 11080.448611455618, 46970.45580867241, 49887.30226432818, 10261.267114671968, 74063.0449308721, 5707.804900550717, 97799.90803392493, 53664.78353272793, 37260.50253144255, 54748.88027710943, 39020.62131833913, 97784.93759845967, 52274.039335488946, 77708.75562134209, 76450.6592894624, 5501.796292627725, 69994.48126432253, 90437.43734425797, 14096.34020772198, 51379.8051810047, 74573.5308376055, 43443.09165168299, 25878.98670723777, 76958.10688831394, 65358.6661710584, 32728.875120031687, 83445.0568368887, 39378.543525329536, 88326.36884548728, 6222.817739879716, 92609.67930138859, 75382.90650202634, 15347.236281501142, 92580.50524101117, 32537.98097319429, 61880.12141325402, 74666.9731812824, 4633.954427065334, 41144.11731981124]} +{"id": 832, "vector": [72603.4858181977, 91405.61111834514, 58635.59166422604, 83209.20412900353, 61367.28222087984, 14867.777819587569, 38043.493337376065, 16831.98471533576, 29373.391173965036, 91189.70216686772, 54904.214837644206, 81178.6485809915, 81911.7552973517, 46146.5743988682, 55558.32559944568, 76609.0223888244, 32737.05540486326, 94727.33663130132, 92064.07003171447, 47429.182971085116, 34725.07796618802, 80529.36978707726, 50478.50138754768, 63021.99691253299, 75048.95975695342, 82583.0186055351, 44224.727739765345, 21825.038415155228, 42285.61854096804, 34816.32470103679, 87477.258384322, 40185.26773148654, 63950.66357433808, 49887.60659101642, 58825.78595230992, 28253.60185520659, 15354.819206918457, 67968.6492004955, 71321.10720444002, 80071.62428530207, 20651.951466115905, 38654.193749480815, 2459.9221504751913, 38840.849810142696, 90514.6229679074, 55890.345577214684, 89634.78494384435, 66490.23315338006, 66121.93470698183, 48896.54128707661, 23734.294919682365, 43329.97503633896, 55386.69245354195, 95856.45777422175, 72778.65967277299, 3756.7337797179357, 80462.3870147064, 24583.64420788044, 456.4386416177846, 74094.37430613699, 17488.907985600934, 67210.46639744146, 5315.240288961442, 32367.57520843333, 71281.84351681938, 24133.351038151017, 22720.591852184203, 29963.883412639403, 50437.398118602185, 76433.27487221887, 50658.51341595226, 52276.03760348963, 46691.23561425058, 17022.139377908054, 19549.268272960617, 45943.924679550626, 63097.7534131854, 77635.59728645388, 57362.988923728866, 98316.54933008773, 16919.622658924673, 57803.817668204094, 61127.02117617526, 44022.07662321681, 4684.128503535834, 92603.7865048578, 83668.58168776252, 46744.33541189805, 38130.0139803214, 90084.38332874601, 88888.34716527943, 66839.68432700603, 69217.32709447724, 12646.899496865693, 93806.21512132799, 58471.74716317679, 87025.0969625147, 74802.96425620305, 46988.713599101626, 36192.71044431211, 87496.96428466296, 41354.726554988665, 80261.48419469396, 54214.623002798835, 61769.94334828374, 57047.305363590436, 17618.65379310775, 47764.942264713485, 32527.563839863695, 46291.02971902451, 13688.645240217378, 36771.65235165174, 48069.614175013594, 15957.557192275528, 13401.535791658669, 25071.402762791262, 38376.1888561364, 72515.32861483155, 11456.811801156808, 1157.3886024023316, 7623.562534385231, 19054.60300110492, 94057.06681805305, 17316.510376334583, 70445.22070148522, 1505.801596775369, 68809.78795550612, 80371.10732142488]} +{"id": 448, "vector": [90441.93289278728, 61987.78164273261, 98454.04885240432, 43034.59957615165, 99894.94111725464, 19708.44445487885, 93923.44873355218, 43277.143742585155, 19009.92575596788, 64279.623913747055, 80614.53205712682, 93682.90883989673, 9758.722781627039, 72810.72657506385, 15653.515241619021, 75715.96778391144, 91795.1086569907, 86512.41303796123, 37291.27521412923, 29088.875614622, 15452.878345369247, 75442.30411764972, 16922.5777275548, 90930.22415996809, 47927.81785571844, 38925.986750318545, 31367.36820531383, 52579.405881292805, 95640.4742686451, 58277.713746611815, 11474.502107202967, 53694.7861763022, 67117.59765067714, 16108.359008231753, 30145.892794832052, 26336.336675866423, 60715.451178875745, 1431.5648478625765, 16278.62706155323, 14581.46912043008, 72813.1279627842, 49784.922037736826, 63155.32471732968, 224.319572466547, 43476.30085864074, 67461.28578516071, 67183.42347522642, 90315.94345299732, 73355.31830844714, 87602.82108313103, 24966.34562894108, 16978.8966749324, 17843.61984435654, 13397.3640338387, 99196.09387566053, 61548.07661071238, 58997.20587426126, 19900.61961131032, 88161.54151360432, 65348.686765342114, 88031.53235787952, 59378.39312570639, 866.9963726380403, 8164.085563078172, 627.7709164028589, 62161.98427755724, 13977.843209118246, 99181.17667732178, 23750.074577971158, 66850.40694183453, 41056.329030108485, 73239.94577403915, 7815.461755410047, 31844.05620098878, 76585.95836310614, 84459.81153165881, 71383.03646819349, 61031.70634240381, 80097.40503394224, 64237.46204244972, 64731.04866079631, 63980.126220774866, 93290.98227031973, 80597.70359608985, 75800.31846137617, 25981.758668579536, 30941.89816982431, 57175.27106034733, 79667.19354811378, 89509.63403631333, 72713.23074263406, 12164.657533358237, 46170.81631538994, 39735.792313007434, 13957.012802796287, 50415.34138084437, 3107.5681148266863, 33114.93205974081, 20272.0085680423, 54.49743585227429, 26565.736551150752, 86882.67874734131, 38594.24812213249, 86751.98317727144, 99943.59436219317, 28547.144899502953, 96002.3589895672, 26870.62477446338, 97719.2274133863, 53679.53543601004, 58054.35354516546, 74180.18314640746, 53264.78783997836, 75897.65999485708, 5595.0611146602605, 37383.20033272029, 91941.75717528332, 94555.07776005365, 91861.35626632496, 85495.46880235833, 19519.129086666642, 71351.81952033033, 97187.91812024312, 96481.7583968201, 55535.08634211777, 39557.631881559544, 12068.975209855003, 35660.39821625672]} +{"id": 911, "vector": [24050.261527519735, 75846.89158357326, 53593.126839602, 600.3342875480544, 59831.16736696717, 88158.72388418106, 2550.970660083185, 61143.535910223254, 57415.11768701313, 80182.88230807624, 93835.78323904089, 41988.10344067753, 99838.03192821745, 11274.693982127259, 48109.06102915795, 88773.6824917209, 92690.06931752124, 52910.61552999584, 67836.1446918842, 25518.199644683602, 8428.217648224334, 7011.199553646219, 42015.14309540372, 42111.463639067835, 29529.44280162736, 57337.52631289394, 76344.5610120606, 69951.9991713559, 76450.0601833859, 28809.59804003629, 10374.844386921923, 27257.736651938158, 98452.04651267343, 8020.615937038722, 83801.69864845368, 46462.69743082097, 41323.581760030014, 52124.11742360716, 14049.268464617482, 55492.527175447, 23997.946375082967, 31843.09973613456, 95381.84578066715, 69985.3356601341, 48533.209594352666, 71450.42460732294, 49962.849557954694, 4660.810135497084, 64497.31172889147, 88183.60680003697, 16831.806724043196, 84437.29027966927, 19407.11341290724, 95679.70467490428, 81395.84277815458, 27136.74544670096, 36442.671139281556, 25052.38114329521, 38086.17330149205, 76113.93515325527, 81684.65359620494, 45612.48717167139, 89506.06721928144, 18158.440987440383, 68314.759736686, 99875.31926922537, 46448.60081837556, 33808.89318122915, 48125.18435502038, 56621.35144768049, 70658.19814527505, 34392.232734118414, 14393.724344340264, 20988.453491494696, 28136.255415558975, 33395.01828244644, 15506.879229068094, 46038.40249631253, 51036.43689765234, 35537.2326074896, 43733.74061523865, 59258.63437486426, 70221.65688745624, 68233.60106978336, 42756.06361337282, 6281.708829805543, 19880.856378069755, 98584.38618584507, 73122.72007045794, 62239.5814707731, 27009.64385612631, 67207.0452504999, 47101.42229981887, 99652.63390059299, 89736.41430199612, 90847.79375124267, 15097.594364678935, 26970.29554416216, 59325.08308583897, 37742.2142604373, 60090.67277511848, 35715.79718983875, 10828.42552841734, 39578.800549099025, 37722.93408809998, 59175.10602913094, 61884.269032097116, 90194.04827538606, 5860.219515683962, 52288.18249114858, 90270.75691426374, 34091.60121801973, 14773.16175472545, 43968.57392096773, 87655.72160450324, 44689.76841620829, 68033.55710732313, 62251.22529131767, 2640.783844102823, 98368.49345707687, 1223.6682362804063, 96355.12310317945, 81858.88589829174, 85730.38156935165, 93221.47348551232, 93019.11666096988, 59857.36127685354, 85936.71688863591]} +{"id": 1983, "vector": [64478.69407310053, 69282.59780894115, 55897.230030726234, 49511.61722945956, 90789.08510599361, 31625.000284521997, 76856.69675934342, 88462.02727871228, 93888.54401852976, 73118.01296670845, 67937.97454193521, 4781.970132423474, 66932.94486748337, 66001.09468920807, 3241.348005918865, 27798.876247264692, 21194.079377092035, 33765.434436936404, 28152.41584005026, 18396.29007975294, 12604.667744715236, 34719.11131552835, 10263.227099459527, 40829.18669041051, 73115.38403327244, 35779.35018625381, 50456.5556890855, 26380.428241898568, 28687.94655869579, 66071.7146824722, 25787.50323562943, 61926.653482124006, 19709.426599868984, 73381.46702926783, 68105.97173824767, 38722.93973601034, 93786.05448905699, 46646.28810676862, 94460.91340904363, 91612.81049626504, 39487.79077474217, 64709.37333393412, 27475.509876448144, 93084.58245933436, 8953.793738309112, 10586.818157713196, 96130.38786922667, 11037.092495096345, 8086.481419846259, 34468.40650674287, 72809.26766017578, 59290.90988872146, 56119.03499897407, 54018.337822355425, 27083.033663221646, 91104.60821354786, 42818.88547165118, 950.4581420381796, 14841.724530545696, 1629.979935848025, 37081.521977708864, 11852.406367290147, 13962.709427579533, 16446.623780599555, 65417.614509082814, 90264.40764820192, 24707.465412480724, 19006.38905066889, 69355.8591840329, 67507.7306347715, 8056.5215908549035, 49171.15487669568, 1859.7323301203783, 68621.18712851082, 99807.7611399468, 17647.096681967887, 60951.80542093712, 12946.54806639366, 11206.57050097702, 77705.22692106677, 4441.068048386776, 60669.17546479916, 45383.919453001574, 61018.64795173905, 34377.52643757172, 11651.138188673782, 9595.00513870133, 54072.23847346711, 51655.794621571484, 18572.6450687179, 31915.677577089584, 51624.37186560835, 58665.19947078807, 199.95723209418338, 23608.575033122725, 47878.803785058764, 87651.36002384455, 6875.085485426491, 81210.90634503725, 90171.33725758606, 77268.45826489729, 15292.119367768397, 52898.51248822089, 56337.137213365815, 71832.54964185525, 43889.555235161395, 59059.44871685594, 45776.786760752664, 583.0931456588751, 95476.36970890376, 25766.53842275114, 89009.10509251745, 55371.235693880335, 38917.60713272719, 82346.96102945066, 53347.16044888883, 76210.99122019655, 44425.884979785624, 86883.92100402183, 37393.49924948571, 81568.17849602202, 57428.08136300549, 74200.78696534708, 18547.21249807918, 13813.697426949135, 94195.35455452664, 11672.853910367332, 16426.678836062492]} +{"id": 903, "vector": [76381.3006064883, 56795.35205457208, 98730.82762721703, 93760.91981428929, 91084.76646054188, 70799.74078311957, 57719.642469626655, 70243.57386369472, 29171.578572655955, 2496.9416454666216, 89959.87955433446, 38836.110028336145, 23663.37055091543, 5463.693141850379, 18576.408436740843, 32258.651926337025, 18423.609517726956, 21040.10722808638, 53065.172495074155, 24871.55248656394, 68734.67583016088, 79290.28528186532, 40117.13179067801, 81104.33496256756, 11982.354554053454, 44348.82998823425, 44438.80192151548, 70333.33247759784, 35342.63195004862, 58297.35425759951, 60227.629046576134, 22105.216034572084, 33904.19110642267, 4476.819008543842, 24844.405207937558, 39254.792063688496, 37848.3402231474, 11403.743850692605, 40287.81920617357, 13829.984106223714, 40178.617005330365, 10573.781680322514, 31312.20565777648, 24528.62051595267, 26376.685749115826, 96096.61088635915, 68494.58273727335, 21053.177356501783, 19816.31955372073, 85691.90845139023, 67677.40044888988, 2892.4275486588713, 10903.754307390212, 26681.63768371711, 87249.21040727853, 7225.184637859516, 84395.50444151666, 54480.40776711478, 34531.427112627345, 23237.812843982763, 89628.28059418689, 40653.082751357615, 3522.444587414997, 63954.88618239027, 24053.31157124635, 34696.60527422999, 25055.72768487504, 82287.62903948569, 57541.9980961158, 19295.509878706995, 63914.596158896355, 62505.494740578346, 27193.588679908775, 36868.58556613957, 8324.510772232563, 35853.40267401046, 87142.18287968938, 58093.69056456821, 35998.485560623405, 60986.82744220646, 66994.63456347519, 58768.84855705576, 46724.56247203827, 27804.48429979426, 64505.16648071829, 15662.600251378, 80444.66831915038, 97423.2834561242, 89641.63650793486, 40788.15341923827, 44377.797923967366, 15702.320094851973, 95296.40970557083, 8583.54789663015, 10590.678589875557, 19611.25460368144, 47362.81336392697, 63630.38872242317, 44538.76916813968, 62202.46187875101, 58304.960714870416, 83512.55318386003, 97664.16884628488, 15564.833105603582, 93647.81231441673, 70535.56747184719, 14967.021752995213, 56679.76516594063, 57035.67207462, 37178.22442709906, 8688.221277675979, 89985.59285085664, 70747.8289668822, 45592.33859903779, 16451.91596104568, 50791.293648356484, 836.7634119658795, 59258.43023664468, 45584.41530482623, 40661.426177498506, 27548.768315144134, 91512.63753018517, 45305.40153394823, 17341.0604997723, 83489.31564554232, 82147.31845281295, 19455.15186878718, 74151.92998713724]} +{"id": 937, "vector": [99301.43133751485, 28063.390488304507, 16111.594455442935, 34364.77278720802, 12845.011588061905, 96688.84904784543, 34119.77056897066, 61867.901565518965, 61517.480277443035, 44504.66410993702, 13633.63605820428, 97993.49747717955, 83214.87954123516, 87689.85701741127, 81259.2982041227, 25859.13403903446, 53323.49566701543, 17552.157796460444, 72137.0315048865, 21152.74160632087, 52712.862723682505, 26195.004010023615, 81351.10880696749, 74691.66541565555, 11655.13204277282, 80528.81059560284, 18942.7673943113, 91213.7667079237, 702.9245670145401, 32717.123917445122, 12213.330555214076, 33276.168160415444, 74386.00032987622, 4783.495035963614, 1109.45753368048, 36640.81871086482, 30936.18611088239, 62619.00591493656, 44157.09982678307, 52706.42178018438, 44859.13685374934, 91064.84649893212, 35036.258737018434, 45745.28582285267, 10428.604767194705, 58379.82926266284, 54404.476890342405, 79138.83878143026, 13369.98599165351, 7790.942627034136, 84906.5912473331, 53606.4077629535, 19496.581611076657, 58364.63199197652, 12395.66921145825, 78601.4105902319, 92203.89031455101, 76931.23949087452, 6988.688122639664, 16292.820944790976, 5043.079054294452, 44322.28380769132, 46811.09349449756, 84628.03239179925, 59743.8517720182, 87963.81816834908, 9434.26750616293, 62620.22981583052, 11516.598920855115, 53814.9209545343, 17663.763401189615, 94767.00139866477, 34591.21848622082, 79704.59753453534, 17029.4958190634, 73899.02824229609, 5412.504520648309, 18016.66004486302, 27919.546456367916, 36104.75152574624, 95390.71597534648, 87697.23820116425, 26889.78667564409, 17102.649395035318, 75219.94200740822, 67221.74267626014, 62650.35064706765, 50124.130637200804, 31342.75262211287, 42126.34372807974, 48700.27609471376, 76490.03899209498, 24092.06268697577, 92969.54669577508, 21839.3720856505, 55854.33578285749, 94906.13126537064, 7627.606340639759, 37172.38167828689, 33088.736383526455, 58092.56997889736, 87918.80600317383, 8223.953747684343, 53389.7406169863, 5065.162413076973, 36340.488685076365, 78802.09210023028, 74595.1043434217, 886.709330739277, 39937.947801219234, 47583.30588435663, 17786.481870499083, 74792.53192907029, 54236.41920562068, 80315.34868152179, 85182.01648491628, 97801.03435671992, 77495.48865285622, 48579.804900550036, 17794.16122530819, 21186.94206001356, 66185.18930949472, 44260.40373294139, 26897.600510466345, 79947.7560321159, 15794.443074073493, 43005.42859347563, 97726.01064500622]} +{"id": 1214, "vector": [34220.66344388187, 59691.388562700966, 79824.91074514597, 10902.30886392637, 44211.2275079255, 51308.31984520976, 40041.40110008246, 30331.23980924446, 89549.71066353894, 23288.6291429128, 7541.325746210703, 79761.82935875464, 51871.81157960522, 11306.655913918528, 83917.05977746897, 1690.1103913388838, 56970.420442672454, 95768.06988310018, 70579.75300145902, 81124.38712943773, 71016.06562675616, 69884.67467439541, 38554.408162098494, 2990.4457480700585, 43507.44042861748, 71861.52380164409, 53285.97298074073, 54470.89114155077, 11787.339333040281, 25342.355075388055, 54164.942307945064, 75012.34244416471, 68923.6105104521, 7834.372848613902, 6954.2919507996185, 95890.88062447734, 11449.928358245998, 11345.032486575425, 43773.61621973041, 52748.04786859585, 44661.78435257151, 47215.70041813714, 7165.594210683279, 67714.50208995934, 55241.9356402559, 27657.785178567072, 90979.53022881376, 79927.23838699715, 83382.49561582762, 34462.43650274823, 6358.249559816609, 4960.291921328508, 74944.7509315906, 93391.3944603919, 48547.75516334889, 46203.79849267429, 7495.048880216637, 30934.157173647436, 92391.43043244338, 54971.24153919321, 59537.77990419912, 54508.8649327246, 82559.84197639953, 39410.0833701646, 43600.1332177675, 62017.92824403619, 69508.64500240298, 81460.24987051736, 71192.30708922065, 95374.31643239589, 81759.04462496666, 5934.475873768819, 54213.75449542106, 98327.45208959721, 18571.581229656887, 25224.422990240502, 32485.34339037038, 23432.81007044169, 61772.884116452224, 93820.73140586948, 49624.804792256095, 40431.88444539293, 73926.3061862751, 64296.40553847045, 87411.12614838887, 59928.52000182122, 52457.349308719844, 69857.80193430163, 71185.67848423129, 54468.97179174743, 5200.768099744302, 84263.47040483484, 9542.940902518392, 23300.48156987008, 26283.820677991152, 36882.86068724383, 24097.036613269916, 24307.43912805676, 50544.16975030842, 61351.50440544779, 48284.11300309359, 85861.0054104723, 1150.5379461371424, 44606.15854523844, 77095.03980564863, 73677.1189591111, 56257.83680195901, 58940.24976060198, 35906.352783928894, 69191.25207022308, 19876.325298032072, 19623.94366355109, 17985.96073597264, 54926.65704833457, 15514.484599647581, 49827.36900867728, 16206.372730217123, 23589.191475657834, 74656.76650520432, 75715.25802694788, 21840.852072292706, 70979.4390697128, 20444.164135289655, 26392.644068008663, 83488.32754973165, 86520.90577119624, 62481.94910802689, 82306.7085707068]} +{"id": 254, "vector": [29632.995466315548, 63528.19351993827, 69302.8515697144, 69567.00657726321, 22059.82075845745, 8366.177721530843, 36317.34811438364, 36604.31955245444, 9138.682215312721, 14409.352717349777, 78679.4396137722, 88212.74292777454, 92933.09943904649, 15148.597045278211, 21228.467439877775, 1950.9658851360246, 94477.21032975608, 31932.02576767439, 16413.439111045504, 25788.8851710952, 5910.13870803444, 86091.96496561536, 34293.97892328089, 3230.7477553194076, 73475.07921257261, 70195.93005355421, 80521.1636591346, 263.9155829989748, 63637.18792565587, 66476.60560333799, 31641.79099127624, 32673.88741698788, 45561.363842481675, 6560.012698062256, 96607.18371071109, 24972.43257016757, 4558.806099047485, 46575.54184705974, 83455.38937842975, 56038.63210589617, 78117.24306831895, 22432.454597451444, 419.160748355929, 71220.3173541965, 99009.47244776115, 92728.75342651716, 57330.69775946763, 36829.07327736217, 23540.110296595118, 91919.42023404741, 10912.29276316319, 12757.86005048236, 76729.34669498914, 28086.615816021676, 17298.042569753015, 77463.87464313212, 90990.94585542298, 71194.18208590716, 12886.112690330852, 54524.62192234538, 67255.70386531718, 57990.37231672345, 79953.95847034278, 25554.92785232636, 74728.9809466839, 24523.025106503803, 46035.594668160884, 58602.21944280931, 47312.74451847581, 84466.12900118755, 74155.23601131784, 84152.52096967393, 99609.57980147637, 79472.49775801708, 40669.34395570303, 4349.571369121197, 35447.78651202337, 61622.77542693285, 35165.523217228765, 59481.392649001886, 6444.38482518801, 16923.5208853324, 75301.4667247928, 88150.95092921094, 44700.969220812614, 50402.24739101418, 16813.292073690667, 8184.581852285977, 70903.40938454206, 75747.24965543924, 24931.264038772082, 23309.943248143773, 90380.12792597403, 49429.88229916817, 77479.54303068343, 66499.66377698885, 70023.97789491316, 36627.2374819123, 99353.07762332809, 9294.991875174275, 66913.10548425985, 38428.51166520582, 43392.25805351836, 24101.12834840573, 52805.60196904795, 23726.826465331407, 45013.03921647807, 99558.6056920456, 60123.561241496296, 25703.855480021586, 2305.343762370071, 91120.15866045868, 54408.79332061007, 83329.47433274404, 19743.15087854003, 37803.632606216845, 98807.80820301757, 13529.452942391552, 89506.04638851857, 95588.95444330049, 65638.2423262325, 27890.00559792869, 19750.82411053861, 70816.29732433843, 2513.1799004450727, 37885.70847264652, 47996.4949786529, 8937.454787474342]} +{"id": 1746, "vector": [30658.302482962506, 19708.437505994392, 97668.849665993, 12389.943439708939, 70613.55337938969, 58157.55033701644, 47555.595243357166, 82768.74078678545, 2585.308272087128, 64419.66647111525, 30595.277828652655, 82598.88023642905, 52721.90830811187, 97870.89025425029, 21911.677783620642, 49147.80185638162, 68476.04343535544, 22309.289085413307, 19623.498556731534, 53806.983832944155, 98590.8439269711, 20309.522111263468, 62741.29132497548, 52482.569978400104, 51539.29949312561, 97141.24863712813, 97277.54977735289, 82178.07978460846, 4778.044748563692, 52648.03423292036, 86747.24948532115, 43370.3591974807, 7935.47027661754, 26092.151512213237, 95430.4054903658, 88528.04153551122, 67485.10425478476, 95848.30595777837, 24679.028196339303, 57564.088346876, 10491.25228026715, 84676.96773033903, 42995.40815699477, 52419.4717943543, 88594.06958475815, 45959.42677838673, 25885.39827238545, 94952.25399524486, 14430.702633738545, 58652.34941460137, 99485.15465341679, 10150.915599917565, 69472.40208837073, 58393.4677526958, 81269.00695152605, 9016.961107405386, 59704.17823433746, 82265.70684930528, 93295.41275646968, 60603.24807768235, 65939.16874145957, 62146.41643228406, 41948.566968597435, 85764.63558799677, 21216.467435626142, 12307.575276697624, 68992.20000674459, 84647.7069403344, 95370.57557939482, 23721.498721017342, 68086.09490782989, 78203.2761431761, 87102.85204112437, 73163.46521379171, 7255.791802905098, 48733.631707191314, 86465.1272390508, 51511.334475676704, 51492.66449206291, 5210.9551037538895, 18456.62203838181, 6460.912464686586, 13292.453459344266, 48835.955333543956, 58332.562245904875, 75666.48526567721, 54985.31152271308, 10310.907741576824, 37189.72805807215, 16981.46556696447, 94257.8562022336, 81404.76137978649, 63292.75963140137, 13853.104966814788, 33311.06181500606, 50871.9028688712, 29744.228032839583, 92321.08640225207, 66212.80692747782, 19684.475998823968, 33188.68747297436, 73632.07950849502, 5472.625253504349, 80267.09959519477, 70036.18562412387, 2565.6916092575143, 51167.28631229335, 70702.63031800359, 78183.43198433034, 41464.282626780594, 28548.42200225777, 26160.838723352885, 11277.171784214746, 64509.130936277084, 84466.92915882614, 32835.63208810213, 85480.91060931154, 56470.99866644123, 3769.958134414819, 27388.98027677501, 73740.19730876482, 79067.7123087363, 64131.21850362159, 76547.15581947187, 33688.45255642552, 40747.303287488256, 5107.333290927208, 85114.70973466145]} +{"id": 433, "vector": [59402.046213776885, 58419.21675596865, 89839.7955226798, 13470.3401679377, 39612.262025345066, 90248.70798477209, 2093.964410094107, 82701.69332497053, 31315.583904812604, 67388.27134647116, 82254.8779071863, 16574.90043695622, 80484.11311095771, 31812.73051983431, 84657.00771273898, 15052.770216816747, 26880.247793965627, 16909.476904333966, 42368.11061583916, 75488.41263506147, 88439.50186864407, 61856.87611102726, 65458.19351498656, 43556.11817910282, 62942.53489624051, 15929.06608549205, 53910.4153740261, 59426.151350017084, 14254.921260014675, 40902.99487011184, 52043.66159996495, 84759.26944724705, 67590.05642852154, 36425.027286442746, 65948.74697807158, 35022.710578103666, 80864.84209283645, 89389.62566906466, 48708.63744778609, 8361.813460713418, 49353.58386290909, 16141.824715204944, 36772.166135855914, 55061.360184284, 8249.498790126476, 31903.1833846665, 28412.387372565918, 59690.18692027269, 367.80694257702027, 46929.802165907706, 49580.39432517984, 28919.606107738593, 18578.510204854305, 42151.58235341648, 64207.819298977345, 64702.125151023436, 27393.93903868309, 71434.97402973834, 20935.408779951547, 78263.00031914462, 50658.70708468001, 91747.21717193456, 81377.93915777968, 57378.74351067217, 29446.782529634853, 76283.16207397381, 95227.52350015452, 49525.576481375385, 449.9608621741147, 87143.47353231844, 45195.42825165984, 6948.360363831974, 70302.37298018995, 55541.98031714784, 98393.09492524849, 7751.985117704485, 82480.49406960803, 86963.47895314114, 81872.80944526884, 21249.0377994784, 6384.52085635981, 72091.70233030671, 75682.83360834283, 17633.967418108467, 17732.709237201037, 94623.14111189409, 11136.308435254694, 93331.83514632452, 47708.413524696916, 67306.13891969743, 58523.200489526986, 54705.12024286145, 75978.52615183979, 47796.25463352828, 88027.73013341118, 49184.501922972726, 93643.53902815224, 85554.74306654609, 36911.623010594405, 49656.57415568084, 35254.65499070899, 47488.70297696695, 13811.451994208068, 83853.58486217388, 53375.69111782412, 48557.750715329006, 1797.2258484060033, 40983.92524918199, 37793.23054434262, 8915.985443674646, 50993.844766666276, 24440.221133394625, 35457.17227669935, 43030.09343383471, 17179.872009255847, 80934.05086390887, 11581.572958503528, 72427.7315191492, 37550.54531161523, 82215.05354496265, 48334.108172495435, 168.9420364648586, 90297.64030927938, 3281.9533232710805, 886.2395556697544, 56423.95133538173, 26763.313890746365, 87848.54567510975]} +{"id": 267, "vector": [49771.102829818294, 53438.22093486682, 87857.37554985986, 92982.50276977483, 75746.11417929818, 65291.37034421075, 11578.216559407507, 40779.01852771263, 13179.040222269045, 79778.41210478787, 72798.67812595295, 61401.80637741221, 8289.479940006273, 66579.96032444802, 49767.951556995286, 45792.452753637415, 51815.78819411426, 30885.810417951863, 57654.95612553013, 60317.22587789709, 1804.6977153183375, 96734.82818240825, 32634.968228033922, 41497.26331546671, 3756.8253907279804, 47359.99042532258, 89025.29657867679, 49197.051501506605, 27619.499121095392, 92087.85632506294, 58187.41038898674, 14766.329368525421, 30267.749934453303, 95254.14951865766, 65838.54160222581, 2120.047013931925, 67687.0391171073, 7756.757604439668, 33606.846111917665, 67352.57344314974, 80270.4437908658, 53069.171508004845, 28054.147276536267, 16727.162833143684, 90394.91660188018, 53601.98080005253, 57153.371081382706, 25304.91205576577, 53358.1773170023, 89500.13306863495, 84113.40026134228, 78935.9760470589, 58649.83636147234, 21974.9592874557, 51438.05250131348, 55189.80756623311, 36671.025438440185, 83795.08124410454, 90143.56630383209, 18969.668616863244, 73540.22327081524, 35621.20591116927, 93769.89540748789, 55431.021689179026, 14177.013349323886, 57037.82939761375, 11710.063541704541, 18971.073856060528, 36280.38069733781, 6994.475809246259, 50257.640651609334, 22277.370833736608, 51053.21001155929, 98775.48182190541, 43753.179936979526, 71929.29635796911, 78693.71189094002, 41007.57359337707, 21621.919025143234, 63101.16090920622, 37407.56024903791, 45809.84903145595, 26395.554062737025, 90751.80735843253, 15017.490255718014, 90257.77405061269, 92365.09755660058, 9138.190747526663, 8722.957636717132, 16126.66103864281, 85005.45344202245, 34030.22186621757, 12861.019743346047, 63167.72478366155, 23920.707582940136, 75989.90609011847, 55414.274943774224, 97829.46746261042, 1100.611015791453, 2974.3145066113752, 55308.263417320566, 45069.582539582734, 54332.29140503384, 47239.34835443495, 49724.66039285585, 82630.80852694297, 4260.59455105019, 79442.51796477483, 41060.57641385, 55558.05500038068, 95904.9736538006, 53187.04323838493, 86789.21048968784, 94841.93062760076, 97063.73181144964, 83760.61121916879, 44082.721078604256, 17238.926205300777, 99306.46723195347, 90978.11398971082, 65784.62715327278, 55539.584936521256, 24921.218273732295, 87134.80180452311, 66899.99029592208, 46279.7610584153, 61003.32436909269, 14397.133717875544]} +{"id": 1857, "vector": [32749.17611595868, 45960.095774762085, 91488.62747848596, 98173.9344025078, 85080.46755393698, 45646.37394563655, 13710.206504148147, 5891.317601659851, 64713.66840462635, 74360.79935904218, 64216.63345261686, 23169.924645639105, 78094.5818976991, 75484.21842537103, 96068.32574201627, 59183.49786435291, 50997.90525043198, 7788.003818631894, 50262.51316906667, 22769.089606741, 89968.6026503941, 47304.24241322759, 14395.47616944613, 51915.080801792414, 46053.98467605833, 33769.62441142035, 33233.04291339004, 58303.20760691169, 58933.99088464969, 40961.04329065582, 59191.09292843891, 92073.63066952386, 44003.350401505246, 72274.99642464872, 31500.125927641155, 25605.510346975425, 43977.585364750994, 90880.6315940782, 13738.792572202985, 91686.50857079473, 20024.32697231652, 47939.6294224689, 40338.25103018833, 93183.13099034542, 87821.33166596189, 75915.39604078249, 1567.7146292763955, 76638.93193884725, 74379.68670110956, 72798.79304000332, 64945.25980483569, 78417.84782885737, 41795.44524592152, 43381.95656866246, 80184.83439546547, 97093.75614322115, 58651.44071415125, 85495.23153176154, 56963.626065836834, 32262.459911946884, 31710.60770014693, 44214.833329553694, 90006.65357366712, 28149.323819889592, 90279.99070195637, 96638.8706102417, 50207.12574648349, 86315.98191237035, 44089.32784191786, 88920.24144473865, 35911.51451601937, 36321.08798226783, 40383.31152261559, 23008.57722043935, 74166.08624396448, 36834.693598018974, 45726.471380620205, 47963.4162905049, 31012.052641604492, 66563.43399516758, 87236.54947049645, 44736.57218667466, 53358.211542704026, 84862.6675167663, 13857.351058360178, 2742.56750989732, 12686.220644112578, 52390.58888213088, 87052.34549407876, 67365.33253011625, 89940.97257365509, 3268.972794217062, 6781.625887434184, 61818.8973049704, 20968.53454950832, 37963.895066861165, 84424.87591317754, 50769.186058339714, 17887.08447393318, 10392.21939028947, 24101.48062941696, 36631.07402239361, 18276.237534935135, 32892.80763177205, 69219.70584747467, 58757.52378073892, 88262.85791467276, 21802.453475959803, 31853.407636131902, 9746.423810752924, 86059.77400266525, 69772.177827498, 41752.38808686004, 43406.24737943931, 1826.9170036387727, 85124.87117733287, 74928.82946284996, 39048.77019950363, 4903.900034044462, 5774.156163647426, 96718.16391285732, 20323.799476136617, 96466.25110467794, 38952.19371309283, 70298.16335732244, 64531.49772778803, 25063.904345004106, 28753.693883059626]} +{"id": 1376, "vector": [58121.99398810761, 9956.98935492514, 20671.091466287195, 35544.693308463524, 43718.205094936224, 75625.74112315572, 45215.14404433369, 30219.08395621764, 27737.206955700487, 25009.97232152814, 31332.283496654723, 16664.86873810169, 73484.3310895197, 72951.13596624814, 70671.77340392087, 14632.46474109896, 2055.650110309737, 39611.79340567218, 79804.65362051605, 58172.90253107999, 23534.424382877583, 54351.055770065446, 252.24877069643935, 9448.384522652408, 4666.153051301058, 41382.20924213032, 14035.316036557022, 38640.75548974082, 80577.49056495691, 51667.3058069687, 46960.81569679731, 48192.15647214038, 24466.189595342257, 8553.505139866224, 11405.362295962896, 32096.88938903733, 53449.90131117142, 65782.02319434677, 13905.820552051951, 15660.905366139577, 1799.5662763335197, 39136.05359481057, 40519.199532606406, 51714.36274318245, 21732.907022893534, 79823.3512182314, 10178.94248924568, 74327.43048912728, 18117.777692931624, 49936.09785714419, 77149.2626705237, 36366.03943729297, 97265.1635866425, 46954.00434005745, 78892.2502567159, 37793.01679563972, 411.0945925674292, 91085.6600558742, 98.3456930651161, 48988.95344986745, 63959.754870845594, 77154.38809358185, 55491.32860992116, 91821.961170736, 2253.41808828049, 35095.384939784344, 29165.921715929377, 93231.2651931908, 71836.05464883959, 91989.58968519146, 69215.62440287105, 39336.32017835235, 7393.541810392146, 84120.3644803524, 30062.518532103077, 19066.099039644778, 24713.99338967829, 13802.461355520956, 95981.48856062809, 17156.750010429554, 46916.41779912056, 1446.4362488142867, 58591.955257222384, 91892.18846983118, 37837.29552223779, 60704.849146283894, 58387.99141081822, 87022.8926871327, 38828.715654465785, 5733.333149239461, 74700.98989640202, 12737.078425951066, 3342.134818277509, 58247.125648711284, 62213.67555103573, 49340.76479700095, 68778.91569936661, 17513.47929804953, 76711.0332316944, 11161.389527040044, 21712.66537482989, 73260.57000966233, 70407.93356666509, 35692.28890222952, 99323.66563614245, 61517.377836876396, 18571.69627382288, 69885.1862124153, 11091.717993091488, 48198.85875934019, 60621.8226254465, 75162.59885081422, 12434.226843960927, 75039.78234065471, 90704.50712777593, 97169.43373233435, 26088.94887398291, 51733.91132703873, 52082.1717404396, 39582.63987426613, 3962.4298423838745, 19661.842332576485, 99353.95363112587, 10039.66684316706, 66285.34724647322, 61799.99226453684, 1457.6088334435467, 4484.221566386026]} +{"id": 2026, "vector": [42648.86398885916, 52813.613942936754, 15900.121160044822, 48345.72986740749, 76085.09815815015, 32260.078554669813, 59699.43908448131, 20534.282352172784, 69973.00958579195, 14485.745201797128, 59660.67774755175, 4436.098312665759, 77317.08661498298, 43870.38732604978, 39650.86114077977, 49182.13742877752, 99280.2265875563, 30583.82195182926, 13344.734339597153, 24705.52994406632, 55092.19632574022, 10064.852472001252, 50168.08624805582, 80809.04135790815, 3430.910958937039, 4826.017415707284, 46630.659912652736, 24574.531944863655, 20290.97507566362, 6773.013572500253, 65107.12988518822, 46823.15832353407, 51214.603130348776, 93665.30626936311, 91413.39035691816, 47405.73310313836, 13435.924413043433, 34414.06979839301, 95189.36882626617, 31476.681304988695, 70128.06806339438, 96049.59102776911, 74901.51306413775, 3440.7248865274355, 18137.169440555022, 10742.23523958826, 62178.77335545139, 63511.99457174697, 47853.06262838177, 86793.90757938969, 65637.5193514395, 24034.85298685254, 81835.88346655855, 90730.16107719287, 24796.59737327423, 37353.82023532203, 23401.722128139503, 1365.7592623240732, 73810.41916819534, 30161.17931100184, 65044.81915657335, 5098.068205586182, 97827.45772135034, 74647.33735726091, 39742.46334697329, 21762.766889575603, 3214.054504808728, 68520.83040309526, 30962.44936378959, 84737.87357462272, 30252.81658348885, 95775.95554317125, 8068.703284451606, 45463.39464695932, 50189.755404788164, 66006.70064053395, 11143.525042746705, 6375.726762620182, 34253.22973408328, 89902.21195141255, 69806.26379802104, 89352.4025767866, 31806.300178974965, 28100.928779853297, 14791.114884547318, 6806.0121523131675, 32397.674551736745, 26545.416995614236, 8042.294152273621, 4266.970748697873, 45189.40663589057, 6373.274573293042, 51305.472266988625, 90570.84444729262, 79159.76221300862, 23115.952821085593, 80117.24799843898, 50069.49291194122, 48083.22046273981, 43908.36015900921, 31229.027521800013, 17946.07218668678, 11939.448931373276, 78268.88151651731, 79416.9797088405, 49719.45032922449, 48874.76027364341, 46403.28711848259, 35847.17618445319, 85607.26457666977, 73848.96178024524, 20466.881073810328, 54494.06406053697, 17103.24076198696, 26682.269886363018, 34360.36033688058, 94707.6547765193, 85693.46395103382, 86364.56109313901, 28559.312496867286, 15768.606400134644, 44635.97706914048, 56725.322551281235, 61263.22201058244, 16873.696738656185, 63386.79867623374, 81931.35141688822, 62459.620889108046]} +{"id": 252, "vector": [83152.57704285008, 59011.23655799492, 63525.50507278814, 44779.97551250967, 21795.741969526603, 13562.681616519578, 56432.0388179929, 7904.816544021509, 61353.497799019286, 8532.685873764012, 2495.1780373551615, 98279.18995454532, 70757.00249641981, 30092.60341343718, 85925.91161467733, 52194.88715616476, 60739.77982032812, 95033.49687460241, 50568.71697769985, 62974.1201882364, 85207.25841164927, 38859.217780170984, 69629.00270212516, 76708.54825316159, 50584.157913913696, 14112.665393070412, 65431.81927961347, 80459.34463873506, 78916.38210512472, 76222.40588368266, 55929.47208486291, 75516.42218515021, 81434.30594028866, 17575.79431813666, 16189.074623333578, 27287.242406819136, 58921.328608302094, 17164.831383848777, 67865.4651597767, 80991.14564396079, 17730.820286173188, 82043.32631453083, 3910.7620221121574, 74555.925382785, 69638.5794501554, 19249.28757243882, 55203.604486408505, 3780.456752569472, 54299.59453017149, 28602.399757740815, 99114.15939965444, 35577.409342625586, 88891.69314005374, 72683.45608224387, 4824.752839755031, 29814.622435442827, 66701.03778134889, 18502.4200246669, 77713.47025243621, 57621.355282957666, 2470.7635568514406, 61551.6924679141, 10867.637237481775, 83443.6843119871, 34811.98631654613, 90940.33418120684, 98853.9420820894, 31652.15423936525, 29846.519207102196, 18748.85046699356, 17988.24535488642, 98973.82257529396, 88302.55256219061, 92631.0578400164, 61420.523178510564, 59436.91315860329, 15086.796525567548, 7329.737468081909, 68020.78144513667, 77261.34428828454, 62928.26275645548, 8940.082264144323, 90027.93331249959, 3933.6510641573286, 94876.86468463043, 28720.66820252782, 57992.099733331524, 27456.955931601256, 50697.26895707168, 71862.65355022196, 13143.422864829468, 71540.3604031469, 56626.83092034223, 4827.068043300397, 32008.521105136246, 6657.650999138765, 12030.575862826876, 64063.1127984454, 10192.406657037202, 57699.57799777059, 84341.69147395407, 55673.207998875354, 15458.014103519368, 48265.4859904672, 46132.72601527605, 807.9335826421508, 32992.91021985106, 90247.70645705055, 19519.61692766945, 55662.63864204088, 8085.968344675143, 70650.81001841513, 16348.86660164332, 76508.95188156162, 33417.237436144605, 28132.77406519752, 74311.94662588782, 43955.16577648264, 40455.46186742154, 84186.81815383682, 39347.775615152634, 90177.7406515993, 97592.55242099153, 67641.79588338631, 67397.02344000473, 35523.823645212186, 41387.19742583251, 58095.87875189469]} +{"id": 171, "vector": [4992.757600383735, 85736.38326276603, 1704.8765740181216, 7907.5960204207595, 42076.27221604241, 1053.2087910105624, 2225.0840877330093, 10432.892376270176, 16373.71850421484, 56772.91468601747, 82635.18975798086, 7587.3763150865025, 78812.61951826765, 61277.389830250126, 62766.76093994032, 12633.47694143766, 97894.43380894035, 30643.362701990394, 82546.21397614207, 49839.38028364977, 41489.38765393761, 51991.4515488053, 97314.10721553542, 11889.457136801573, 92286.58242891391, 45476.29815341989, 77738.9748152269, 29910.46871796522, 53428.76144700251, 94642.8540336934, 82379.86362399324, 95515.63464931771, 14272.182132874656, 87288.05135868158, 28804.41707229542, 94324.11684849937, 89274.83390250195, 89372.8878572945, 10511.273001386024, 57998.75439329683, 54701.90036390471, 21991.20835814802, 11252.432367990506, 36686.697790171485, 31404.13706269262, 14704.874356583809, 72634.06519823079, 82287.16959375917, 1992.2257971871638, 90723.8208723137, 21669.484287127627, 52274.505993543855, 66328.13016377235, 60151.335023049534, 83214.1105169968, 29020.10891849015, 24182.482755840207, 8846.911660323465, 94678.25983849644, 76506.58867176351, 67084.71934822392, 53383.97041252549, 36181.52561174737, 12926.825859278224, 86596.88179299085, 52342.35138111629, 11603.977469406967, 16810.49027120404, 11051.498396884308, 13807.796304037023, 57928.8522705111, 23708.6692951035, 3276.5328148421813, 54763.49376535843, 39074.234417977495, 62914.44604320632, 78400.23719484714, 38505.09934726427, 98619.55022354264, 1896.037939213713, 64904.22013232133, 55332.073336513524, 18960.081514688798, 4852.175536750314, 34088.83328502408, 31400.52594051995, 36554.782409842555, 79668.78756877022, 21275.136703911623, 62382.36042468396, 31612.717016120474, 92937.74017765706, 34151.86020246529, 48284.77237707764, 85873.98471543434, 5744.189205959382, 34439.743252595414, 8954.645165245334, 79693.28851661397, 86367.11967063739, 24412.21857872945, 79306.413236217, 84837.70444956866, 3230.952749187943, 15414.711664640769, 4926.978405657689, 6443.628904691701, 39627.43302592633, 97982.25556046973, 87563.50425571438, 99170.95611827947, 59521.42527855118, 13430.726651106263, 71672.18170479788, 3203.7168986363886, 28310.40733347714, 86052.08544212379, 69209.59937572914, 97184.44724986082, 25500.07569852002, 63537.79652495386, 8934.648608184947, 86222.09818647268, 59927.51195636967, 69840.79762474308, 72909.4989769798, 68599.03025257797, 5680.691500036783]} +{"id": 185, "vector": [49063.123619966296, 77034.2225008298, 91725.19996481668, 15492.33398201344, 27828.5534411687, 56279.77662377273, 97612.8547901077, 71745.09519403974, 35693.18421979268, 44199.11881605143, 57486.55105739532, 54747.156968369636, 27193.125087483837, 50417.83029348903, 13756.58843540104, 66851.12817684509, 47913.33735476952, 72541.14253507882, 145.95413567076722, 27165.152342731268, 83242.81155892578, 10305.347953541877, 65920.00693561538, 10001.321432086785, 12020.962704217041, 61008.92685978956, 56062.267387811014, 55999.167952158066, 5591.343104102675, 87644.29744965644, 75645.43439028149, 31243.46337065148, 24140.00945064517, 73061.649619722, 73932.00186210456, 7013.9605690315075, 32240.162944524396, 70571.53499063397, 55681.96613818122, 46233.43988660632, 92325.9983256044, 53246.3893609441, 6186.126196144625, 21495.72875251704, 61158.07266673058, 53719.64119452814, 92687.80212410568, 36754.50627313792, 39294.48361365104, 78523.16857944611, 94157.79092341167, 48430.41679616779, 68785.82349775487, 79064.61261695175, 97048.34837404924, 62623.04110514659, 43999.88378289698, 54385.64689406917, 4631.883857133168, 49714.34418981521, 76675.23924828072, 82441.78338744007, 71624.69695952507, 71042.25102590989, 82251.64734703222, 29503.18726644967, 24245.25072458249, 51078.84706317202, 84762.89382127271, 20881.859303303063, 35112.68064704405, 9331.113881736264, 89276.28222152793, 62797.99252836782, 60712.73200008703, 27516.278791069115, 97287.34167632836, 32386.848721021997, 31873.078040010805, 29410.323545182004, 32874.20452623586, 37102.44086439689, 21469.425276748476, 20887.303154929858, 5002.57320771117, 67683.55656450185, 70553.38171463738, 53076.05726315886, 95177.87185790423, 58022.16800792538, 31168.00392372838, 70790.11300576269, 16359.359646042738, 20512.286895105135, 36419.87636648011, 44597.382220254156, 34064.559792228545, 93406.28706012787, 88752.9760162578, 72388.82736137792, 50972.30163446381, 83040.38726860378, 1941.355365917452, 65744.68933630158, 85153.35884070754, 6946.3839430564885, 30722.41565050182, 70612.6433393226, 86500.47352463081, 77990.6082681376, 84695.9065382814, 33490.584868876795, 8660.06582977582, 23878.422018466015, 5041.567273756986, 52977.55140292637, 66980.03654543089, 48127.913539071444, 60426.4845801177, 788.2567077948321, 4220.707429983994, 70267.3463533453, 74064.87941866883, 3549.815407075696, 44754.80717424611, 61396.53443313612, 54597.939865418266, 63606.27419737631]} +{"id": 690, "vector": [98650.79868011104, 88478.14271040492, 85186.19691827447, 22129.90188313062, 37515.42645567908, 51135.56297127419, 78196.4367483309, 97108.41376000887, 17258.170032044552, 20236.513076271258, 98716.66043391786, 5241.81581237676, 74628.43997240372, 20663.679553960334, 39321.327794926256, 38855.543161121655, 51046.66724928161, 15246.394387801321, 30513.924737687616, 89974.95955902505, 86944.95853195523, 944.933512363788, 99500.92401255129, 14100.531680726968, 59352.995854554734, 79462.21547491822, 51365.06663167798, 77302.15269019497, 63156.30812399428, 46965.90957466105, 35245.01424412794, 7400.460339815762, 38588.40711784384, 97457.81370230611, 28026.98897618151, 38177.7643027701, 35246.81388802529, 5948.840743331729, 30884.794310768106, 2730.921467158154, 30499.13131882238, 89602.67351280285, 17729.531687341616, 82836.60491460006, 42610.55106557434, 66777.7716538818, 48538.175199278485, 98476.8248604536, 63862.24218858786, 8319.030709756104, 13008.541097785042, 5863.990143048037, 31247.83698977742, 55238.86250825201, 89954.85344137515, 34998.276003712635, 9568.691665877272, 71395.21792521716, 69951.17266359097, 35985.76721520565, 71282.99307689421, 29710.68455020628, 82339.60438367892, 88512.25101849697, 92556.31953637683, 44538.290753218134, 71235.13105269129, 91291.52323999895, 2357.7087208608295, 22274.370591030558, 2461.5842819916134, 86300.3486718761, 71930.10916119104, 96295.41641717534, 9908.592648669046, 25192.495274446403, 41777.93625243534, 68510.90100001464, 34326.225369441476, 95637.19641356987, 44273.47297957217, 65890.84059365955, 78213.6365078199, 73885.99731975334, 60689.17809709352, 69626.7336877477, 15539.318328246954, 27057.77123136823, 88545.5757168385, 15349.488856681914, 35421.4053194853, 20077.96834028266, 20673.382222326887, 91481.8624032419, 80138.74926992232, 40749.91320811974, 96235.17466871526, 97428.94054767434, 89315.72689919887, 49197.28491131523, 11966.074770548863, 89654.82730377062, 38893.45695999894, 59318.42438399862, 21094.974810165324, 41985.062939093084, 22828.068503572507, 81967.061856221, 55221.116598142515, 94939.27253006585, 73065.8717773774, 93289.44444537691, 54461.669490512475, 61131.17233275934, 16304.05326777793, 78920.48572489092, 33857.404452363386, 7026.433865526582, 24085.10053508799, 70078.88056209644, 42702.3413794178, 30719.603647222793, 396.72100996839265, 99267.97118328582, 87900.80821424496, 18531.795806553797, 28232.495618867935, 29754.02771679957]} +{"id": 860, "vector": [49581.77891439375, 47285.409428816114, 72755.91502621147, 76396.74478664216, 85467.90572840998, 31381.850828725142, 44329.43629420213, 98729.74027447132, 28156.793458833617, 13428.401946896707, 99151.21953310387, 3922.3114100233047, 86627.0527647772, 86053.40794598391, 46462.89348205075, 89216.74656789398, 77110.63597666072, 21295.405419062874, 25535.33122395273, 3221.171543736767, 35695.853422092136, 3968.9255143670944, 88091.82374287763, 18743.186989917584, 91645.5658708454, 5967.554417930787, 79900.49397222599, 82894.13420630022, 68147.14112258641, 89365.34575184579, 35181.90932704397, 18936.791164879996, 18015.229865052086, 48477.56319800519, 44399.43122105926, 70399.23999921507, 89949.63742516057, 98155.74139606755, 34251.222400594226, 48460.30181990822, 41279.19800415057, 32683.455168182198, 15847.359806642591, 65743.92717797241, 93861.51416753807, 26824.623775795884, 28416.012491300968, 49263.9702021683, 93513.77096174024, 10110.159672796803, 23958.825386779183, 16354.160864267385, 50702.10140907898, 60565.200113007486, 38041.71797958142, 37174.01311827701, 51875.570722260985, 76103.25598807017, 39671.104153944834, 26759.252811449795, 55509.081072428766, 55828.03660724759, 18149.102376000537, 61266.53590014292, 26396.956368754963, 78306.95050816888, 14017.89642713962, 92025.08610974724, 53691.3554237929, 59740.77860518381, 32149.430777333866, 64643.87228885711, 29118.724467559885, 87599.15652317902, 90737.57697475045, 53168.73982567002, 74827.8113250452, 73119.41074652079, 52475.631152390626, 45907.20235658983, 62608.00829663169, 49625.798994871264, 29343.438365481434, 95032.26939722296, 40666.49076472508, 87112.4451054674, 80475.2873661288, 53171.12381976458, 15682.642751144649, 40508.44138774808, 33911.37635316437, 88573.9111468707, 37815.75685587176, 68924.88502899869, 54151.22632901836, 12922.77863060094, 79029.33046993469, 13906.69518184443, 95047.42193156635, 80504.87529654615, 41010.11452849489, 47100.70143506068, 89335.34290258882, 43477.37740281674, 43266.675234166374, 71892.79690499489, 87382.74677696486, 99890.74620703327, 13073.17231805477, 59197.03589040568, 61199.36365019753, 75273.67459434616, 21153.327082073258, 1806.1532901885723, 17621.42061710349, 854.9892784795787, 45539.3884169082, 16677.981599691004, 12026.099342623153, 67712.10882624867, 94685.46127413528, 64264.12013002015, 84659.25509379776, 87758.42488860288, 97414.9193879909, 62673.329357384966, 4392.77789517486, 62571.423124969915]} +{"id": 950, "vector": [41615.080745869, 5407.754500658058, 67505.1061266728, 52003.4351192582, 61733.290422864266, 20856.87869250834, 70302.93121808521, 6803.026410621049, 63311.296273631146, 3662.7086961510513, 32836.950478650695, 23564.203055354017, 86908.44176312003, 34638.62041692062, 311.5054382623383, 83999.6824035517, 61671.063695218705, 54957.478938994966, 58560.48502492496, 98848.61591422789, 74567.57537273898, 20592.27509264998, 97860.1529457657, 65232.68566601225, 61931.1467530911, 31804.815537921237, 96372.47851521423, 88501.91649065058, 14493.495888820251, 9021.939353680453, 19294.831535749134, 70193.01591480755, 83974.69896841451, 22512.29972912829, 57214.56686809876, 52028.204930113665, 11574.448083199151, 8105.302229405109, 2393.2486770168125, 82712.93696140543, 60046.993202816666, 76431.43471384828, 525.2876514868832, 62364.59549842579, 27646.141012355518, 35337.7956114595, 30129.747346731438, 25211.027410599472, 73569.39136083897, 11733.19374634817, 5470.4637403231945, 67637.96816106096, 43258.78206358067, 50969.96749028493, 37434.50345845136, 28108.78705379817, 10486.292888152315, 99950.07308407356, 22058.77838816733, 48102.580678167236, 35198.707652873825, 75936.84987996219, 69923.95977964782, 26254.270172718807, 19691.514484815863, 62711.092279581295, 39806.7089062696, 88496.95311889923, 70195.49129677293, 70091.80127713048, 4000.7995292306696, 90971.10980710533, 55393.34943160187, 43764.19775644419, 60821.21883115763, 25551.218072177096, 67166.3491056623, 77647.27148436238, 43462.41369385606, 58846.13959874274, 14926.14995871233, 76561.73721083117, 67848.72497012503, 26262.76434029534, 77557.84425526601, 14099.399452543605, 87093.41496559494, 8474.185467475592, 40933.62010869288, 45700.67400426767, 16993.524655037574, 35652.01352111318, 28978.63421103545, 84497.89331429385, 59695.2679308292, 76444.49512279977, 52366.950940689516, 13360.154755170084, 77031.26737611636, 96778.14421991569, 49406.26274476316, 76417.52690712154, 91860.82770959211, 61761.63968745217, 99308.67811911114, 24890.735220353698, 70254.01363541746, 77429.06496081052, 14328.571472595497, 7954.744853760853, 46278.302237517244, 16524.895318224597, 7804.176088085635, 99535.1175078921, 66286.25658213496, 30919.78872154385, 27410.45597594293, 16017.618841917725, 77651.08604118129, 88987.63747422077, 63914.98119751596, 65144.557605160255, 90335.84396259817, 61909.6952086301, 69772.64252111295, 81447.88510611693, 83503.9942598998, 36587.26210036687]} +{"id": 842, "vector": [83057.69440886941, 60724.4847171275, 70805.32919745056, 64242.5630560188, 54142.28524742608, 10363.339353657875, 56734.34325423321, 85167.36337969599, 46448.05239819233, 22103.33058235362, 79409.5217309706, 39723.82127314041, 55831.183930385385, 68390.07448940759, 8170.48521028605, 68558.25939595183, 96647.9920127703, 36220.546484652084, 14487.322585622731, 16989.683896867315, 42328.66660460821, 80305.46516531978, 17222.383224294957, 55500.65653250263, 80947.60962317254, 2705.413964647174, 59925.97120157077, 2164.9073821532807, 5207.993286490265, 83243.38144580922, 24059.771628641458, 55109.82860129789, 34860.08178569064, 68472.34735089411, 79478.46621051326, 48807.80927377988, 57819.0356497728, 67230.91538752502, 7587.622568877284, 12412.650305801388, 81324.93778778921, 41513.66539623338, 85323.8926113459, 91976.10972925181, 74210.00597848164, 22907.414176276965, 15380.381141744525, 65936.1503171738, 48200.04702878066, 91319.37459438905, 98837.19454845377, 92565.11791320595, 3844.976557459601, 24691.024239897208, 84493.54274058143, 55954.12363047118, 40611.01427372276, 36903.17566102346, 9435.95931982193, 27823.44589423249, 94798.68561974159, 1814.9360365727007, 63462.727822651774, 8153.598474585211, 84476.49023106798, 99458.27398955272, 33932.00224331923, 48321.18109650677, 41553.11755811344, 271.3220197087307, 94588.23510989404, 75619.79945436545, 83509.6951125697, 86773.9032688558, 22156.50738437661, 70556.17660483782, 10915.298080996694, 72584.38756529099, 95277.21144513409, 17646.665532030158, 97264.34769134028, 38214.92402251634, 20500.24716455622, 55196.008449453046, 14152.473576966097, 54712.64536626709, 28956.308884051265, 39323.346247866495, 1282.710408462806, 15567.905060902365, 33293.81882358204, 15266.19090918333, 96378.77601517548, 72414.74604907093, 36169.86077502739, 98140.93688628945, 79977.92309503187, 57090.004016128296, 91344.42543934913, 47705.236507179674, 71926.0631161719, 21272.649853753323, 49389.85967303499, 60534.84663294979, 2637.5631307069325, 29503.128273640243, 88202.00721433805, 37195.8322079873, 79428.44241582829, 10196.374911696505, 85578.26086309012, 53002.93529960531, 96985.61617729074, 86720.71134930389, 34302.43746371651, 58544.31066293647, 13638.953215934269, 71178.1345025423, 31659.889786867734, 33766.20977935918, 91870.79301769949, 61984.54789100073, 84822.77694808741, 15606.353972107623, 89867.83219805702, 29881.27886687032, 43097.07883701281, 2545.920933545676]} +{"id": 1397, "vector": [12897.78923062791, 26971.762921967933, 42400.66365473425, 95740.77486918333, 58197.97966520126, 15343.902702982614, 35265.82933259977, 59373.17541527579, 34581.00988810099, 1660.5792224660966, 84632.69327518508, 93212.71030311927, 8339.715373390987, 42449.10768415613, 79554.35410697524, 1005.4156978287265, 52280.328851751656, 22056.69191342311, 24948.469456399158, 75882.87653261557, 82977.91768510612, 80420.78320653569, 82181.94654766534, 77145.83247801187, 87167.02015963275, 26418.11191036184, 14423.066482308834, 82250.30530603045, 2885.5359262771717, 90317.07022684104, 37790.37650269844, 97661.07329392931, 77648.34151401345, 53575.695167924794, 80058.6851006459, 59775.89286811703, 4287.3525445826235, 94168.74089056466, 76392.93920013992, 21107.511147986523, 19204.63450956611, 1626.3605706844376, 22889.432087147154, 40464.10210709559, 91011.27378662268, 81121.44383363488, 60353.16182534613, 4812.774177636569, 38694.794552578845, 47982.538910894735, 16870.502174420555, 71615.21334085132, 76190.21774233576, 27608.816627159093, 46719.40976475816, 28719.999263072514, 77756.64689194964, 79812.92057489885, 20972.26888132322, 25938.10066093343, 29902.148836801756, 86187.87852244133, 70546.85149484019, 5486.263768009092, 26619.285342085775, 68416.06158330097, 65547.67514080448, 37621.78534455636, 68352.21390544875, 70422.52942313033, 7668.085523275247, 85716.90623610446, 99065.34697868428, 55373.614402690604, 25139.080287531724, 2828.296631559113, 33332.87556842187, 34490.643429835356, 86323.0312494201, 92316.61254741225, 28550.85423726135, 62446.36282730845, 38683.96297768084, 37577.92064388562, 2814.4624286375097, 21252.354197137967, 48026.43726684794, 61567.48430404807, 58814.1401336268, 27742.130404032894, 4206.777433455833, 57954.00114966688, 47557.01304491269, 88138.4662649376, 4425.524326826913, 66031.87065773112, 25323.834509537824, 44924.578369696836, 42592.75445588655, 34273.66279130416, 46074.0284447504, 66692.95645086754, 49676.68117188077, 53416.33287260568, 11503.89508768067, 56495.20258281222, 87971.61215183264, 58785.43906083361, 68588.60726185142, 65560.18893816473, 12206.19312767609, 22293.561478886913, 23048.681021409022, 49151.46318457555, 88010.57889994446, 68377.61840177144, 31608.543683207292, 46122.92475405104, 67534.66748120521, 65781.32173804086, 96231.33034457022, 4152.594006599142, 40044.90979950887, 31829.418324536095, 38664.370593079875, 55604.04267305731, 23062.641253678008, 86222.25784524532]} +{"id": 2020, "vector": [76524.07673414133, 8409.102170218463, 15311.612496734206, 29683.960266373353, 41319.257474102575, 1158.5670190174978, 74580.36848013397, 36551.44610176367, 38286.98964675023, 62611.811130675575, 89983.0135978453, 94798.79043664757, 47165.04829898366, 88458.03993992241, 64963.61718395307, 53928.11137621604, 89024.14605402644, 21325.799545026668, 13194.00734795021, 64048.95067414425, 22470.183549910693, 55193.86360609611, 19985.536704418795, 39650.19843730148, 99911.11512511961, 7682.510657454955, 50704.07023816636, 33026.51743127467, 58100.79369449006, 18544.43476985689, 35105.048836583905, 3479.012044413088, 54521.77581149208, 34252.56760842391, 79357.753380614, 21139.666533320855, 91542.33888438815, 6260.35450270801, 89455.6861876457, 57512.674586711386, 13096.522336735283, 37467.779938127154, 5296.637601406529, 9208.776976156041, 63021.38818090835, 19584.48651378677, 67169.38058406768, 13423.192626045931, 67342.7599433004, 50377.85061443749, 81233.44021348083, 27834.48114742313, 31850.036070166676, 68426.79376369611, 14396.296183383962, 66333.86595623798, 85061.51471989424, 3959.9630387675666, 92073.86547164455, 96868.73505009341, 62290.88777714548, 71657.60672298737, 41490.76634146802, 14211.48837447328, 16544.485499630857, 64125.682517247, 14339.037938945332, 8595.55676699264, 98443.15195362622, 39785.287546000516, 18274.19896513619, 52698.40721286613, 71972.39968752862, 90056.07245578196, 15782.864054171741, 41426.36838842818, 80305.40058164358, 22014.293308186494, 4985.970747420932, 74428.30104501559, 83533.41450137855, 59012.85477773347, 4520.592396552003, 66364.50227350388, 74060.48085478644, 14719.226261275653, 9630.233049145208, 21286.55658749998, 98712.55117468552, 42195.948585483304, 59869.0208935544, 65010.11401443834, 72005.8095004072, 43539.56399148981, 13928.367224389782, 42512.30754015559, 16292.272519841112, 29688.298177033957, 26537.139139574083, 95873.16977998831, 42456.09578525216, 34135.27355288846, 47442.60782928487, 93646.94767909494, 59515.310729090445, 36349.68807473547, 71798.8298346869, 95960.75514084373, 15379.943327489165, 54349.79211475048, 90816.12604193324, 75108.72315996344, 84104.00108801702, 18452.927303628207, 88270.29137782169, 32305.54413975163, 6092.613768954824, 67779.86525940728, 3132.345996208885, 95673.08598987009, 65547.21991791291, 82174.8785263689, 82258.1635379419, 7466.98363610051, 21961.117582400326, 46125.79260630739, 9164.697622981355, 17417.604957734813]} +{"id": 951, "vector": [72540.24367568133, 19966.00366002754, 32071.470983355808, 76580.2961689042, 40926.25163725738, 34309.451921180786, 78357.45740631613, 9366.82713842789, 28111.474759731693, 85505.45703629218, 28836.92512008985, 30295.987569001325, 23016.042100446488, 37447.3256951514, 74980.06750372122, 10113.704584825733, 57736.424970191765, 63334.623964035665, 66800.81362520333, 61393.565471912334, 3862.478227121424, 35639.54420439034, 30950.863531835592, 86043.07741739064, 5371.028065967343, 61367.14624740472, 65812.55150804354, 89146.49549902354, 15094.66221498169, 97235.07622998561, 39.63929078074635, 65752.21771188789, 24138.712050692302, 83653.1660382145, 38585.411563573965, 69638.86379299304, 24973.863593291157, 86643.01960058998, 42995.61751904096, 32871.23417386541, 68435.02498296516, 21327.180241515274, 65473.2388245041, 50594.148747195366, 95339.77737846196, 53001.86987546217, 4174.317028309282, 81620.8058960938, 97971.24038617034, 15380.781112525243, 90832.00005385886, 21801.038679874975, 14711.116673074643, 74054.08651209153, 54768.702285410574, 77418.1952629963, 70845.7424080635, 34373.81572917128, 71729.23617400747, 3495.8850258440034, 127.30494365829071, 12362.569569019788, 2939.1962892323263, 425.38658067434733, 86052.85359088748, 86159.5746787923, 39414.29615435392, 4284.227454649181, 41335.36932268492, 31674.629985973403, 15692.287170711095, 25837.65571817618, 77287.09475523034, 74565.45470647469, 64688.61886209974, 26454.247821328547, 5765.34447039756, 81580.17981847863, 99167.62879798423, 37748.292473847876, 13094.885500479724, 83436.00668888047, 53344.75892036412, 6955.233962076913, 61929.25404357913, 87922.56706872497, 50797.4683511973, 67778.7976004522, 54051.710308420676, 57993.072810561986, 15293.231822799247, 72987.55055580966, 57027.46551972101, 24740.174764939515, 76945.60778031372, 40190.21206412184, 36066.71658257232, 97892.29212334844, 77348.01795544493, 41769.96518334405, 33918.2190265549, 7741.73731088863, 13902.036037440934, 6853.012776176049, 93503.41568168055, 37361.15240706119, 40225.62057549437, 32031.14608899048, 81106.42539074809, 75919.9361966255, 15017.082768259039, 55140.38489207269, 98537.57062712079, 65842.44243001218, 22384.289184164398, 27744.35386497275, 44406.48933296839, 61331.25878206436, 18468.855172171374, 73764.4604404478, 68797.83305728713, 30593.82103065751, 99322.58527182846, 28725.465535971227, 85429.41889850206, 85539.21797318709, 10976.437653071924, 79785.20850069408]} +{"id": 150, "vector": [21388.495905050288, 88006.21391960552, 85233.28096179274, 99825.82734240255, 90165.22524131376, 7031.460678101475, 99364.09927658865, 64721.639965014896, 68751.68959513417, 4651.1178796685645, 21997.218806619843, 79653.65497594072, 42001.908586311976, 6398.636302113825, 71499.74536411396, 66808.01752659267, 9031.422757084396, 88392.10149643847, 94511.31490077048, 79156.2636705839, 53815.16588642239, 19939.832684944548, 22544.823367450896, 59561.18806715731, 95493.67441230807, 98937.89719356204, 81747.09739551773, 64089.16844997183, 94118.32374631906, 56049.304482956955, 62129.83661082027, 19704.53046308287, 15571.838880753363, 19892.107987285613, 37026.05166310251, 55925.28853242196, 57875.932880483524, 8873.567825590146, 39037.58703860418, 41067.12908358087, 14229.155320733555, 35974.8581122615, 77424.00076566012, 89421.38282146181, 57629.83462469742, 41980.26027167531, 77733.91082462322, 40180.670182865906, 20456.44167264128, 69404.25453439835, 80173.67509177576, 71388.10160490744, 84199.90401558463, 89608.47545154067, 35097.362017310275, 25445.956427447258, 60358.52663126956, 27281.831479342934, 14825.844495829355, 55909.78267740091, 32544.929718731175, 67515.02206051293, 3710.8023843688275, 94166.03161296775, 59900.85518197057, 74368.8648617187, 68354.87011062159, 63051.86466003107, 62202.41167538692, 88626.85517368485, 88978.37485724667, 8725.87166288984, 9255.586512081849, 88267.40884123223, 52397.341557955566, 73825.75186613825, 8582.331659738651, 25250.75752637781, 66552.5704593822, 99820.84732891081, 1489.2093407055772, 6362.067914991177, 53625.47124037659, 89785.38572949401, 79291.19764382699, 78362.09165275158, 67589.56729156802, 8329.38025381912, 17956.676327013465, 35542.13738747233, 23384.599201938738, 12578.055699230717, 62075.723097575494, 75648.18773806555, 9464.867275989896, 20429.780991632797, 83014.773839681, 5797.840520998365, 91583.05867725008, 55737.40918198308, 8258.742568314581, 34821.47889441163, 2765.881308137874, 49832.47376529892, 8216.945781085295, 85987.61984170656, 3870.2781268150966, 1379.5925154554634, 9067.275769803606, 39119.74795282287, 80129.77233529183, 91165.23246497482, 85055.34259122694, 33278.22481969012, 20170.000587023907, 74299.29566464569, 18343.689173309318, 76309.64205574813, 32943.11594968872, 80653.37041091365, 95654.18098978604, 49462.265807536445, 46583.2087025719, 26563.046615587915, 64097.6512748354, 53750.652391164025, 69927.26294632108, 20552.802017741145]} +{"id": 704, "vector": [97030.09030594151, 6518.417342818239, 51398.55490902507, 57926.21784974298, 59749.02424692266, 4157.5913172728015, 94355.68218260919, 46137.55612321969, 82309.44233197357, 47485.83743584397, 6371.09487060995, 7441.907822584304, 84120.57865584668, 86120.84906526249, 53167.35753307589, 64538.691290286995, 10459.78829094638, 27962.37081996921, 9341.15971974826, 97226.92505637987, 34714.02152155748, 14152.891504219779, 81559.89593677495, 91285.55928995757, 53141.08846398171, 70217.28514793297, 96648.8615889766, 1130.7509939660122, 68757.48415607134, 88111.54276539235, 17092.055531699203, 87137.2579108476, 45475.3727643471, 89201.97976242617, 29914.157559965737, 56522.586476458615, 40322.1720292974, 18818.602256966322, 32673.58790815752, 37457.571205556516, 68974.32778793186, 31780.174011585703, 53934.62456839505, 35772.62242432971, 964.3614166689241, 40845.49474758613, 43169.70430440995, 18280.843955553737, 62799.58264394316, 67168.22414223659, 72374.93754541701, 75513.69530746392, 90973.92431681258, 71079.35051727502, 13887.610394433248, 91817.12898478819, 14582.134224164434, 94134.02153204719, 79909.428259739, 31996.40490832081, 12085.45352596483, 61040.176313063355, 50334.04589169931, 21848.135773884114, 42485.15715192643, 655.3064314764656, 87085.6733133463, 97029.44635271575, 72007.70999455698, 80319.18903329733, 66937.34552880334, 90637.86324971534, 24017.62765431531, 96887.4336854971, 68369.92151099356, 10120.225718664766, 21644.512708679333, 79605.84165451559, 63526.12041736333, 42499.66551794014, 79014.30207277044, 24253.166258683323, 16063.558377987198, 22938.844806908965, 66054.29681663902, 51825.6971824335, 45567.74865387109, 23666.464797603836, 54447.98002532932, 12765.839857400319, 41448.65620018438, 81726.84065162785, 52249.29721404594, 54512.23728265933, 78969.12138054062, 32747.62550150648, 35916.567614114145, 18700.895699533226, 59776.15621604978, 1557.1437508270724, 85080.48776283194, 49267.42579471665, 1602.1352933991207, 24001.865709548143, 3497.1383994771177, 77261.001718159, 53060.678302375505, 88178.1255415641, 25708.911402352096, 45701.65691196041, 5541.224423902103, 20329.534546749674, 72597.95492662098, 18713.43696951976, 72499.03252017383, 2138.3970733250603, 83717.73707030006, 79996.14979570222, 86933.42811753739, 27369.28144811096, 47396.51788192606, 7570.3130135941765, 1595.773215938523, 99338.18074240064, 73581.64211123515, 44942.55437071564, 47180.90735765306, 9539.826353729319]} +{"id": 766, "vector": [59544.31130782468, 99703.45062992019, 91170.14814182506, 45835.33079980138, 56362.387501273006, 19673.55116194267, 96857.81018162704, 85299.67536364296, 98689.29555822916, 21476.661723215253, 22822.50133361523, 13585.581591366557, 84628.81536468543, 1037.8299402341584, 22076.492391043266, 70753.31785902978, 45069.08813165663, 44906.38165963651, 51490.05600546493, 20485.67307464325, 48399.321181611755, 91113.18133350612, 28959.742220500793, 47674.41907543906, 62801.648404931184, 44567.891764257925, 14773.42535154137, 36178.849125378256, 40932.22012772138, 48355.138452673884, 68414.33979637787, 44930.5252392589, 43115.86700053003, 64641.296833014196, 43346.630650508225, 94396.26116686559, 49365.43776515651, 22138.11860566539, 17854.91338962504, 31183.84293426645, 43256.41549461229, 88112.68369286704, 51010.3015822596, 18073.372097368956, 22914.23900188393, 88127.42335840504, 1286.4679427931126, 15317.669399609778, 21467.660949014957, 3875.157677108243, 75509.48876235013, 73603.94629373305, 47480.62596711609, 10277.560353635585, 87463.53112399664, 76107.48179579455, 86744.11459999828, 6174.632265702651, 93132.22749975091, 4002.9165025340153, 28207.91797139409, 38988.48855412075, 60727.62406222091, 5874.373105832231, 52104.17895313581, 40069.316437713656, 1022.7872271203919, 91932.3367548556, 83690.49495119076, 59488.1945566719, 11713.829071455539, 44506.01990316836, 99859.22317527857, 72678.33693830481, 53837.09528177056, 37326.30444284552, 52784.4725878283, 30727.964620804905, 51840.02519207009, 50876.92828502494, 77570.48489404778, 21542.782082395308, 90323.55793327923, 88163.02599776, 36988.50397295467, 5814.168136745801, 88485.27229595579, 50879.08426150562, 40405.30863135695, 36845.885363035966, 59131.40975458305, 15191.381120242031, 21817.220143519866, 62764.31771978532, 10365.350896418578, 15704.623715300315, 78066.99736946431, 88265.17396563124, 39121.00626249685, 97579.20650181753, 64288.399794515426, 52169.64787386869, 53377.69392944932, 19764.630339892665, 79800.92446825342, 38178.619057976415, 86477.6026341738, 81906.60619153916, 13835.83042282136, 2824.8573322444236, 32575.61488071461, 24872.35450836175, 86177.57417958108, 81490.69573814694, 31156.60852151657, 38530.392672969916, 86968.68254442289, 68931.32944404487, 11038.269039374714, 70633.28728724454, 30308.91301191352, 28189.665438323318, 32992.99257439896, 50364.74543113613, 22325.52777842992, 50227.23695303465, 82002.63277350114, 38464.586778536024]} +{"id": 1083, "vector": [46544.88467885909, 73274.8142094474, 46336.677449901974, 10907.2042435018, 94577.45569512394, 29165.572449292253, 15353.457422707306, 20809.40968117716, 32129.40321543787, 62304.01580491819, 44134.11553185485, 14333.356144317933, 81234.87533901827, 64258.76304344024, 16778.3802600882, 85907.96612727994, 70726.02558318422, 38958.04024598234, 60869.539119805726, 85814.28856793825, 4340.76992595912, 34895.928805112984, 34425.58251592431, 90059.06594225042, 16378.576050840966, 27017.14687319905, 99956.60887077168, 94941.3715995872, 48674.714475853034, 4212.278081905574, 86921.37443362067, 98982.7247066606, 12419.569045387047, 33675.12270340143, 80260.03428250346, 85991.60774074036, 37594.81051427277, 96080.53270663494, 74800.25997148364, 64849.14792086866, 75653.7916369619, 43971.02056870841, 29537.829699429418, 5640.065224080604, 9802.962973149399, 68541.62078338423, 97600.44028422862, 81786.17400286556, 84250.93006767641, 90611.95779498025, 25075.28737685064, 5701.569965084441, 39394.70056521588, 14143.31355994265, 23756.73221161322, 10012.886263663135, 41220.24495946224, 61596.61019437157, 60216.04992856083, 40280.67386115165, 9475.70556382482, 98071.45897178043, 85787.38446947762, 31419.61952568394, 49658.46134834845, 36721.31411708457, 5109.114918399482, 1987.9071943137094, 36383.20499269488, 73642.59049355732, 69.66605081407673, 24216.06509296096, 32856.104827541174, 91130.75918324277, 96071.99835086994, 65567.74886574573, 42.621994187741485, 44533.88562554703, 63628.04405091455, 97683.94339906969, 23197.167200597713, 12692.096076154958, 84816.88607072156, 65928.31975919513, 54946.622716675774, 16911.553044506512, 9519.812684073759, 20886.014009111477, 67685.35634423896, 50697.69495275138, 508.6754232154211, 23554.372167121317, 44873.30233447132, 4869.528308701166, 83093.68453516823, 75550.29822493618, 96526.19203924376, 99831.21868583708, 95593.8318562903, 90783.86307556981, 43834.327949265884, 48193.48266136866, 544.7029512091306, 8101.746858490377, 69766.54172121482, 8149.708820432067, 26479.98385751298, 66887.81533830955, 74210.6123492349, 27613.015004411034, 95882.62684337277, 23241.547100032934, 34130.26403130827, 520.8901680903156, 9707.883948584094, 9071.655090240904, 90933.57883138831, 97768.4129114149, 79494.40403837893, 423.6873654816953, 94207.80623119016, 88602.23608923286, 15892.47562121302, 13178.57723537803, 30471.50318513263, 25705.114560473972, 63917.25818783993, 31375.89203546345]} +{"id": 1738, "vector": [77996.0951817715, 80644.0661817461, 35595.35804607011, 68977.8440598105, 2381.1641104962587, 22245.06927733355, 32779.52545090993, 13808.492479612689, 58288.43584830807, 74619.51071555741, 76793.35553514076, 8279.911042224054, 77153.65394270667, 31732.304006811773, 84095.29667228236, 56887.58322653736, 61481.644254951716, 46809.85326110048, 19642.85070104328, 13919.009025536954, 64247.1073382047, 53137.28158733652, 50681.313089531366, 48267.787733471334, 25979.070000363834, 62345.78996952796, 16514.958221835263, 14104.965532539514, 1403.1964687905795, 39493.85794148404, 95033.86916042912, 43614.681070271035, 87827.88443815475, 94772.74935988037, 77448.30559120745, 46090.74085904587, 29582.68638122851, 38607.108278335, 44980.71731962556, 45318.00157415942, 64432.366626784875, 67009.64086677629, 4873.942158320721, 19119.727995136604, 94821.21229335453, 4226.393900949632, 42699.17702784309, 30450.57679284584, 17872.01168217546, 61566.93959283655, 89969.01997619882, 32248.44611008544, 41836.66217754367, 88009.15907522947, 63571.75630440657, 11463.516039934873, 79126.20348855603, 25811.081343546204, 16249.386103318331, 12012.570896830288, 36367.222599105364, 79699.41446991425, 42355.56716980245, 91827.73008037322, 5319.860834783463, 35753.362596894076, 13834.628194779163, 56882.138701986805, 86503.7502047918, 31565.304627351863, 53805.89732406993, 24954.8407061296, 30795.341516600238, 74891.64253310436, 99683.47381031155, 78866.83457637439, 76573.37333996668, 6286.01232794298, 74665.0811733668, 84277.93581939091, 26481.481644758576, 56741.86933125948, 26908.233975398667, 30303.24380573317, 66753.4313307669, 73077.84918953925, 52255.227963720776, 83693.8667773128, 87585.5497295214, 34200.42762527456, 2776.2074030862727, 13773.019866718128, 20116.913070012866, 24307.18109001325, 5317.493105765736, 72738.75216114322, 22196.419073591045, 66609.29062239247, 10514.103176277134, 28538.44223499319, 81301.744717852, 35669.89724680535, 17625.786461603366, 19685.606888139195, 20827.493699129827, 61823.87219714381, 70227.40346188951, 98383.27813784388, 35630.87043846277, 2427.016357931333, 65373.49938681898, 99193.74834851763, 96852.7078126053, 20610.62246170423, 7866.993026674196, 78187.10897364229, 26750.97663580943, 31530.84518098197, 34250.769676531665, 96325.62366690065, 68163.78256988051, 93598.55953468347, 9093.612658966744, 11712.34560735962, 48466.46649965227, 26137.019745041067, 74419.55827622426, 84698.48476590178]} +{"id": 434, "vector": [24638.742575763783, 38967.939451771825, 92647.38503847933, 3724.7577963291924, 4329.982370261565, 88464.49308946461, 14832.839453668312, 91382.09867702203, 69257.32451397636, 1476.3964826647657, 97089.44505238072, 72315.4362105057, 71668.11518828534, 65437.07125460049, 67829.22738265326, 11672.58476705647, 4502.5070714930225, 39855.661748509854, 6393.827899795734, 41337.56962765117, 68411.91368123614, 59938.522263273466, 82156.52543405334, 6180.125069570852, 63799.293204682304, 25000.516753873282, 25556.598234955953, 44725.86883598465, 32445.569821517496, 99026.92893621688, 94051.92530816198, 98186.59588168816, 33049.16799686425, 11371.675379938573, 83772.17547778532, 52126.694833329624, 64375.51803166603, 52246.78277786272, 98410.73361912827, 92640.43535455098, 582.2481869324437, 97854.65716933814, 69585.11503016479, 3024.9190542263495, 17311.47372501227, 51497.77222314932, 1909.9202152456974, 88130.77355030482, 85113.68368523773, 56532.368218486816, 67498.4350653643, 37542.40694527331, 10927.513417764623, 98121.1038696103, 18465.544433964707, 31114.664300127894, 14376.177388220245, 95903.0782924719, 20221.107319815856, 46121.69784682323, 24444.65177364189, 11384.439597138697, 31194.263544585032, 5989.899059726822, 13528.495216397685, 49924.08836009579, 89886.20759230375, 9683.748643215073, 54191.83159674687, 57383.781475105134, 28062.206505994858, 92168.75452870972, 75374.70399240033, 43130.326044656205, 40451.83311188517, 85357.39554798482, 72512.72339607748, 59855.59921950061, 30627.522952491094, 74782.24117805599, 42285.1565937002, 72933.38562558514, 5982.721371932642, 71658.91090408017, 41850.65472997255, 54034.27520079598, 26813.322092812432, 92973.22601162562, 23312.181974370073, 1114.7828844769058, 21280.109141474768, 16547.230905112432, 57491.680015215796, 28813.015124588605, 91594.80342022197, 15933.962071803588, 37055.27833902385, 92990.7146643228, 87201.83299284976, 24868.473026870764, 96250.87932174582, 84947.63121408223, 60409.16138258625, 92269.19399972854, 57505.200972736784, 7063.462769964468, 77287.60723388304, 64415.932727694, 334.29598191618834, 42758.657839933025, 84989.84969929376, 63261.943460894276, 9873.424742438165, 58063.94125843896, 79432.59295583579, 69814.77073953675, 23740.246917566234, 34973.932196320166, 74521.96927422505, 50824.60901906125, 13295.56498279405, 96664.08567577033, 49896.705394073615, 56232.25580244271, 37344.001335986846, 27049.324680720933, 98811.54651746959, 6716.9127445276035]} +{"id": 1486, "vector": [8148.809395264256, 69341.75110329466, 7326.3571289156835, 90925.52780221746, 61259.13565004167, 70284.76608650076, 27363.12283742063, 96493.33490286836, 96847.57449026416, 93173.32975257745, 21786.300307372032, 11803.374223790897, 19603.58123862236, 85956.67340963995, 76999.2245149929, 37294.80416205513, 99907.22136679653, 74814.32357608428, 56694.10641187344, 2796.8805053420365, 15081.962152448914, 37880.454723668714, 84747.69212881623, 91226.39141099951, 89175.36911480283, 73195.69657948476, 60686.8209747751, 37489.78310419793, 91101.2106560921, 52968.464173227316, 16306.275582127006, 92047.31630549047, 56855.8137399415, 22194.46948468975, 46997.27578375198, 84582.01857984038, 83855.17208515509, 92681.57987575555, 77553.69839762221, 62725.74843133121, 46929.560898435084, 5402.480162030088, 5733.4861957265, 76478.9571631808, 73037.64189017637, 69976.00910406445, 67858.00138090718, 88016.35919405913, 77044.93296171527, 55393.28531160971, 15616.706102967459, 28072.418560609723, 83595.17079242598, 97471.1519954616, 78669.64354755018, 55836.50935370864, 14285.93609616644, 44572.359402601134, 78833.54009185114, 63146.82155947172, 79540.91729659527, 21145.925474702977, 67594.59383008721, 79066.46885079944, 37311.62802691459, 15330.134274100848, 42837.3611658802, 19132.2069326171, 50671.15535099496, 85686.84915079891, 97244.41745787353, 77512.95953490144, 8236.012675513093, 51993.52855380259, 22361.785613764783, 33624.80886728075, 98935.16808707095, 83941.58316611173, 98127.32180294931, 74848.76325721187, 40045.76601187013, 63900.049387357016, 99339.4435173382, 55431.59651401899, 678.7019519792436, 5999.842774520658, 97739.56568392222, 33627.24805507117, 57761.85041460904, 57229.327347670245, 58413.90177568854, 96391.1672803421, 24665.06393966683, 76370.15886231871, 84728.50347629447, 24480.481466855443, 5868.052119625399, 11167.244146301004, 54815.28400592699, 35382.025608390635, 43739.07280254861, 33767.250169285835, 11338.373378880839, 8143.703517482637, 88214.54392584151, 15229.670600044865, 67876.70326528655, 19020.96460477173, 7611.7252553518865, 82103.89266324507, 65325.512278924594, 44419.81292729636, 13662.056261828526, 57146.62403529651, 31505.28854474839, 47122.86573758655, 32510.423419460087, 87435.25635886223, 89361.51764537745, 29140.545509098036, 14346.806297005798, 28464.135688420767, 83734.69179087353, 58476.71103967527, 31796.028469856497, 69904.91780682017, 21631.154399497853, 68473.6304798474]} +{"id": 2021, "vector": [30642.031679798343, 70434.49094255308, 44068.47518397603, 26388.528880160466, 25530.48602485073, 74274.82273114628, 39901.92234687596, 48210.12540590868, 38363.358455858775, 98866.78468285354, 50025.1862430788, 30843.871323793137, 29323.022135605457, 99047.163527321, 19585.48771822356, 84972.11805329543, 57737.3618638696, 45836.67106608034, 81591.94410719934, 40949.89819822478, 34004.9683343768, 43390.227689156556, 30239.8013883748, 50530.97523131737, 41608.77021219679, 31520.119710782757, 90964.8925026296, 60944.691750998456, 52049.780028095505, 55851.22761838436, 6085.4241324934665, 17881.44337069075, 79448.90915274993, 15010.95410038389, 46124.02323785818, 78767.09985610067, 39092.61639177991, 57213.323897525894, 22533.313473281545, 89901.89864490127, 31135.303962582995, 49788.46960199518, 48383.505207735885, 27182.215429158052, 95479.64076234394, 95318.06378916292, 3978.9016795683365, 9736.194560287737, 26364.803466410725, 49912.43287651694, 75576.01857064098, 82605.70983397136, 14861.238696701328, 82194.9538984798, 10972.238326711504, 57880.597715332435, 83278.1200010312, 68257.79665494805, 36480.46002396986, 85268.93958897718, 98193.76111782838, 20688.356485899574, 79336.82631860639, 11944.782581000012, 91340.80685006545, 41221.04706562728, 38493.38791073804, 97214.29788143784, 42146.34419046511, 26597.62542574233, 97025.65825764857, 12072.483182183934, 1818.4215078612897, 55911.62207490225, 75918.41169212217, 86276.01990749953, 26775.74803723215, 31066.211561173885, 13030.735226351131, 4558.402920643434, 72327.62994628366, 7740.437107617659, 32375.77180366512, 83020.89398674537, 67953.00574429806, 14857.358255787523, 51178.61906647842, 14938.243197871227, 4980.418917771123, 50678.35890169412, 75817.45758989693, 51947.94742285213, 5041.765542381749, 69396.99884965173, 37367.114549738224, 69933.91208596769, 67598.90637126139, 76534.67401134207, 50263.731123282705, 72516.24879972759, 47022.326130164925, 82521.97148448408, 74881.19754951644, 80943.83996744949, 56462.12771169257, 32925.62801330919, 54097.08694351438, 11538.978741383766, 34029.39511459663, 45081.07777878227, 53584.46132462222, 62832.61412296133, 16433.633193548747, 96556.40012357854, 26127.410340352, 80455.36902500252, 92991.17923062686, 89782.83093139772, 99434.1091238322, 97022.14804843682, 30813.948235147425, 34205.19520499551, 82234.70069419807, 84457.23616045057, 65700.79786051477, 90266.85956705583, 51145.98933200719, 18079.956300792433]} +{"id": 952, "vector": [47017.19744923089, 86226.75796166918, 67287.00773873452, 9248.50809269735, 42184.60162794536, 94297.18545941697, 15914.712645544727, 25088.28493900719, 71135.22603509967, 20102.057281128462, 9236.047582811512, 2393.071086415521, 9992.953153053364, 41263.347504232006, 34823.619594860014, 27269.02047396309, 38744.79073674394, 56449.34809859521, 87734.1916765607, 57766.63276736135, 5365.227036302856, 29532.653932012097, 12754.33760845459, 19678.911530438236, 94652.18200063899, 10351.826500226214, 65354.93012773118, 286.30044486229747, 20122.394443787372, 1738.955640487816, 91943.31907664935, 5699.9121546191245, 91521.99330816334, 79125.59641229938, 87116.95719641574, 22003.40394968441, 5019.31702052858, 79920.21199263292, 99520.2219906769, 94784.58894208417, 67512.95422644053, 87904.81249234232, 84723.20281030769, 42504.85169073472, 9844.290076997186, 95167.98166778157, 10208.316346252477, 18822.598940709056, 66405.51136652777, 63104.17465419167, 5021.648503376319, 67953.61983454479, 67463.31788884805, 54309.02021532981, 68392.12143023463, 69276.09682040299, 75823.79504913594, 68218.81101728647, 8009.638471250191, 21861.69934399602, 42244.03932088912, 32758.12407239277, 48682.79991011709, 98719.51203138592, 55815.962433482826, 85570.55178499666, 54329.3343706128, 35305.47385916831, 6583.104813051755, 44829.49886428411, 25268.983576175087, 99127.83735575799, 32420.985613700137, 57108.38133844135, 92034.67036589075, 60229.57269302217, 81422.87488801125, 40937.92913798318, 44232.76233743313, 33937.656118173785, 55326.55702092964, 76662.02806646093, 18819.38843149056, 35669.46802462893, 77129.08416691121, 56984.11213499166, 7306.353826082479, 70866.34980171706, 1811.6003546294946, 68199.6231800444, 56113.11795027578, 73479.55230677262, 96951.49889421034, 58221.72528017482, 85385.40603777191, 34547.311917974235, 79261.24993807025, 22104.157822848058, 75288.33476986687, 98107.41686724183, 48494.93474125207, 11135.008993822616, 34602.80787711812, 92962.71824826568, 47632.291663833574, 93555.66121139425, 7968.255742072083, 12676.158421644446, 23922.18456198839, 86750.63086525255, 44494.48302677661, 57138.39877424127, 51395.33263365161, 2116.7561824498525, 27054.44731368877, 15963.454206022854, 49502.0592370776, 95178.00086183014, 25282.786536011237, 48525.801704581376, 71418.19729674401, 82851.80135732857, 37411.89477775311, 29601.905776831903, 40641.96192094977, 73092.85384645441, 80713.7581279065, 43884.0854627416]} +{"id": 441, "vector": [33279.14973461219, 57807.25975824883, 58706.37541092675, 83214.25858844144, 92319.80323883954, 64625.26487319385, 53314.080324166534, 57012.123188620644, 53276.68664071189, 32676.897962856056, 10598.419642081126, 45999.12620244743, 76064.77213592805, 98346.20008574752, 28044.81872632537, 61790.68888707886, 62779.58262645497, 38747.659712709334, 60731.55889871212, 37792.77822367204, 73709.16621697649, 60575.144703859165, 75605.82294563003, 5537.374645021764, 75677.63705278818, 25881.283204170944, 86135.049072508, 41922.948899970055, 64066.74560604034, 6700.292333188529, 92922.61441850687, 5173.257386693286, 73350.65214567445, 96127.15851371111, 34904.80486997115, 17406.019682659535, 36995.877294096754, 90410.06177105055, 36705.939370903485, 47429.72303655211, 97171.40559253012, 3563.8212801527015, 62622.86123925296, 75244.8106988183, 79458.6522297595, 82286.0677583944, 2139.610287944771, 29964.084703729954, 46174.416568015164, 82428.04398677123, 3679.6358511008043, 99957.97937183733, 51984.93520730165, 37696.923703395936, 52763.78133223011, 18553.725642783204, 22765.516167258294, 80616.83729305991, 78025.08007355679, 21686.463035302706, 6396.270836928264, 86407.9362761379, 35648.997783113955, 69756.63308276278, 36854.121170969156, 60222.9630194282, 51621.41374845821, 17902.90112310323, 43506.527936765626, 38868.030331092465, 2093.3212170033635, 19504.178655759595, 35979.172136034555, 742.067106915556, 20059.51828672319, 57119.24535979335, 68574.85770516226, 8314.642759175527, 89366.93726126122, 77556.68564444693, 96418.70007767093, 28574.904239189524, 23445.418683152653, 51956.89688379008, 93898.95722221007, 85709.32547995611, 82704.50351206647, 29421.16714462916, 93419.85703432088, 95091.61712039226, 89113.58726607227, 21512.41591323324, 49813.92746209214, 90069.72267328673, 56481.65333624357, 79200.69816735029, 31351.00028119303, 64863.52780812512, 92433.40874613314, 3608.881903158323, 48268.104063550796, 88604.79962345942, 9120.79630988243, 96809.78782102345, 49027.42168750273, 63352.705079461004, 23350.877991787544, 82669.19953646728, 91332.820914765, 85165.6305989284, 9472.530365065679, 40015.01169255289, 8366.775676603233, 80937.06118889082, 48177.409084092484, 1568.1007332703168, 64285.94168507893, 43406.616344164606, 32217.59709613534, 25234.952650538922, 67035.05880581195, 77968.79469960055, 74669.37790417162, 69953.22814391283, 61712.640197985194, 10815.147786179912, 15788.72104251221, 4776.846800994683]} +{"id": 1754, "vector": [79469.62728072748, 35390.971163628994, 93675.97783320928, 68439.19673344723, 55035.438640823355, 56681.77645213813, 79372.56822840929, 47618.17602121181, 80550.18887000563, 45787.53922504223, 73313.3784955416, 58169.127908726805, 99090.96784334519, 13091.561929211226, 6234.403513744879, 29150.175032989002, 35448.98703742819, 26702.48161222355, 1729.1494682077134, 19476.290126580698, 43748.872377305495, 5031.79181263137, 93415.05478073956, 59416.04582185117, 11319.005476948007, 85750.48785732909, 45643.51285713381, 40497.61655208254, 71509.96682556659, 65204.207687960115, 86264.0334576915, 29348.115351464898, 98890.67361558005, 18603.407848367526, 60211.01554023335, 32107.01366887487, 11170.368566752919, 10906.658980724682, 37592.367536225, 87497.78840172464, 61525.81139377881, 46054.588407821575, 8705.682722432406, 32287.460655517796, 29543.38443926757, 30452.65353405585, 12667.215086607908, 58753.339035281504, 7036.016341224505, 29722.229512591435, 52838.98899187681, 98795.04475380271, 9749.411976044808, 12386.708336186659, 28135.673222244473, 96758.96523655923, 95050.80665716672, 86263.32717998771, 15589.744637740665, 56571.76452218366, 81308.27393468734, 33586.079179819615, 10599.821913200103, 1789.169642168975, 44614.71058224159, 62561.15206744762, 90087.62427807233, 6702.823894464638, 13688.370994721754, 63751.45545131392, 39760.19486315566, 91940.2207836051, 10067.772578562695, 63065.26200600141, 57701.667391620314, 68533.77506868726, 70157.80142274396, 53023.25543260177, 13735.847348208474, 51420.949269404795, 28294.82747970091, 43103.82556986552, 93710.8690296709, 51130.05845252913, 62810.62999109294, 72747.78873165185, 44855.467901858894, 15307.411483326372, 11455.388632152364, 95253.74232181694, 28295.00451452869, 31077.32247597861, 5560.229317482801, 15470.055626909585, 18760.52851262776, 16625.578217961767, 21964.596552828276, 42552.59893585039, 10593.02442684542, 38238.247127015755, 77282.91656979322, 58043.20622992112, 48436.99480906283, 18783.716355208213, 85819.93115400012, 38549.916711531405, 23010.339263427115, 40372.339624328604, 90956.55515382219, 98808.22887411583, 93911.67328254205, 99121.62091160532, 36313.51689374782, 87962.43247619637, 42382.73848584634, 26659.216324960165, 88037.45077712073, 38780.76688361898, 75501.92406863952, 3565.845854982752, 85210.13516122085, 56572.629848670986, 93711.87984430516, 58412.263171167055, 994.4538036439243, 37526.078675728604, 27368.02466073861, 25092.919449792895]} +{"id": 1970, "vector": [99968.31924679058, 25760.622892862873, 42572.62303818558, 32483.269025909478, 21192.62120711145, 39214.13572223082, 68108.05415546948, 81763.46524408078, 21954.96524937147, 61225.208664781014, 92094.36558063654, 78884.67447088785, 50102.52460117087, 80263.28354828966, 90972.76834833379, 21317.151509268184, 878.3241431766786, 95963.69992457193, 63770.46536215496, 49291.25918608853, 32652.66686823034, 66427.90820739829, 83498.11982168273, 42283.79104553827, 12037.628162144221, 1394.544563040856, 69631.53589956355, 62569.468454756905, 92039.9786608642, 51778.84944064739, 83636.90417999285, 35649.21477101937, 32525.66813090515, 41259.03658039948, 56620.17045613783, 98527.51870948826, 60040.810997683344, 70654.61737250887, 67125.55047807869, 59.04548984670966, 6568.050470890152, 86384.3208802959, 79657.1455425013, 1098.4549704995407, 42680.91858172097, 5172.877885355753, 62886.35112711706, 99967.6439905226, 67975.28538673231, 34825.52687116676, 67921.54507837279, 61981.2347714949, 93986.9857807206, 1970.2267205813916, 97293.52265149262, 16500.19685492231, 50333.73610090504, 55697.579862897874, 9248.580038806575, 23086.203040584118, 738.1275783400864, 71473.46974835815, 16978.542596988667, 52362.34178563894, 95379.22776496697, 29468.43937462772, 60078.932773419925, 71997.72436927326, 648.1694135966886, 34040.409540957706, 34238.339167637554, 72545.18874037896, 92825.53731264772, 59308.6557060706, 85480.4786661191, 26865.16919928921, 71489.68674268312, 89690.65050893874, 18123.049731512976, 12076.51698911244, 9777.668548580188, 90107.94981916028, 78720.66862676489, 44431.16431976916, 47392.59858325249, 94798.71457500357, 40296.46942125716, 43965.22023659756, 97078.94831184299, 92076.90541488171, 58364.72095195011, 56796.72907685089, 419.8972783439614, 1039.2023778209802, 6363.845448341943, 50083.21901678009, 84722.14282451311, 82211.25381493429, 6768.079748544586, 71588.47803396663, 50848.913017937004, 72596.10223639883, 82437.41835037955, 68762.69087705102, 26840.99239032591, 42521.9061349454, 25223.996056203945, 51241.52098485634, 33568.179173171906, 82672.79764137097, 44565.87593573595, 66457.74107261437, 65938.15112435263, 32158.21056282796, 57724.891485826156, 99013.54380246668, 95080.4477135505, 11485.588078279641, 19785.463107710388, 4011.356375917441, 64120.207827200495, 14299.608328562286, 68948.0236323607, 67359.90091632922, 11706.827930015985, 22299.754827085628, 63443.196846807194, 68658.03517770866]} +{"id": 827, "vector": [2329.5274008403035, 59699.61232131632, 90796.7466437154, 51550.572510644684, 5715.063205503956, 83620.46079523541, 38023.181363560776, 99860.51333722817, 95737.3522025843, 79044.39531383602, 6190.937306379729, 81213.18466556206, 81643.08312879367, 53349.13206808809, 10373.714509174215, 72331.74873858044, 10511.52168301296, 13316.578644370613, 37388.737797882364, 48107.34470548689, 89022.01052019541, 68367.43982822189, 22694.61548310581, 12302.220229720917, 9530.809848123034, 73169.64783571466, 82176.95734423322, 60195.26493966475, 6909.7387174474225, 83409.8762851221, 23354.351814290087, 99196.94494343741, 46638.603617608045, 60123.45241063848, 96615.41999554972, 39850.91745006494, 72290.0978454811, 14876.612457052463, 88873.34799771571, 12243.760096083966, 49497.99489914109, 9567.685835737482, 21254.601935050865, 44643.57823297793, 11257.595441482881, 26246.53740160844, 98039.71228820087, 9350.422579850392, 1476.3474333663207, 45741.57227819299, 6020.503514346198, 28798.704680004637, 99016.786432836, 95270.78302185159, 35139.10320803127, 73812.43955217679, 61560.69178911675, 32763.012123580258, 6953.029813533851, 78455.20500767423, 32458.351796651852, 17673.55419143266, 75768.55922006808, 73650.19843084108, 23644.26279877574, 55742.519079375255, 28005.35217235731, 74615.30712384252, 97256.63655613294, 11563.302641833117, 21327.952246188263, 42201.18331714039, 45691.94190852199, 76123.67449152972, 89240.01090048443, 42619.08271878417, 10245.693121487631, 5193.686546771525, 36322.546253602595, 83082.03457282096, 81270.99421003756, 38334.469941805226, 23185.232869193005, 20697.9649925508, 79079.7802602634, 5292.802172179167, 73114.50678207994, 60199.31758721181, 15715.121500232743, 18403.3561796053, 75632.51815675704, 96079.10703420252, 31036.78012596709, 99380.88175102559, 59730.80639592243, 80382.88844669756, 72594.04249173647, 70133.95544806117, 71613.0674453291, 72192.5256563594, 98484.4920634025, 4559.296516380018, 79400.78986396221, 69826.4534446639, 76776.06739983767, 96308.30823021648, 72172.48097640666, 15692.303513820982, 11775.898500099802, 83189.60942620343, 67201.15133769126, 40171.13719498293, 84619.02333120696, 21793.750133257083, 62829.23480621009, 94932.89100078431, 70592.11028242437, 70259.0323655425, 634.6869581826064, 67512.5026793506, 72156.8302012635, 8561.482089331284, 27802.708365863982, 31303.94604424355, 73178.67265307176, 11369.019057382257, 59949.6164937657, 85699.77075928736]} +{"id": 841, "vector": [5849.875137314309, 18953.515421376054, 42388.53051726765, 42121.269683662875, 62106.43734464876, 91789.45334210164, 31914.87992808416, 66671.15869411753, 54148.39968434484, 93317.77543212987, 96210.33651537637, 71957.79940408071, 21518.875058849364, 83939.64721933955, 38381.56891808462, 31803.51014571218, 84233.58715661897, 99776.93084522821, 91103.84818220421, 74535.48250479292, 43887.31754379527, 16842.035621661133, 18199.918078178434, 93198.58529066519, 76010.27071925405, 1714.1359374456488, 64349.08337536407, 96819.87086722246, 65691.70907328822, 5068.946643580341, 36292.92626751518, 60903.76433696193, 48618.80925569543, 37657.11536404331, 12374.542731397054, 75617.72355148153, 67866.72982157295, 93059.60141354833, 44526.913097186094, 29730.79940235228, 8195.658324749844, 69790.82408503981, 89680.70893782441, 36807.7593575525, 76999.6864614808, 16569.479457308367, 54223.04164364602, 55011.79578229971, 29306.72633642465, 24386.238764015212, 37775.984545556916, 74610.32434164497, 3538.2432109153815, 40827.62775820324, 15818.641561021695, 67371.86277518161, 71343.84802820561, 13324.502448060293, 60546.00998194581, 35422.935293809474, 98059.16828695306, 56623.35613082072, 38477.144516450426, 8574.71406279864, 43434.271577951054, 17650.35303425583, 90743.27165112921, 73539.10603065955, 46855.61544063669, 11626.210055781172, 88258.82685806362, 47209.825110356505, 36391.39268242723, 17850.54350587375, 67541.10491046812, 96964.89726315944, 91436.01899602536, 45299.63694433094, 39265.75647075975, 59042.21624284882, 93276.90902845109, 21719.928066584936, 59456.01218712816, 4080.9549237021515, 49094.98268160743, 43237.6835574414, 32510.686983982352, 83829.80142400115, 30645.154417596386, 50114.431506035275, 94839.56012279901, 10180.063136690087, 66662.43206490402, 28071.655507246684, 52216.26394273106, 12346.100001536275, 40945.84099712706, 39412.07802200053, 12951.64437586721, 79095.69407304685, 14776.856839754038, 33645.873275004844, 83799.4702583455, 12559.694385351006, 90327.66998277734, 94869.12960509639, 18265.20251921674, 92283.09609556939, 32794.853352638675, 48625.55607691521, 57931.349916842155, 20760.88916157568, 2939.552878387619, 44465.49806173252, 87224.9950808009, 22241.881372849635, 99495.27420360094, 97014.00949597308, 81918.76244757113, 47787.22084520953, 39288.67125377924, 24205.105340660317, 33777.72550200083, 4844.158858283965, 80784.80959515057, 22853.262513601967, 56804.00647086482, 66837.21086064857]} +{"id": 836, "vector": [89300.64060580322, 57456.93705866094, 63957.762849420804, 49382.54507290665, 21478.020876918858, 67825.63764668921, 84859.82310379825, 83612.8466814625, 43514.250046407076, 35531.9638479018, 18944.453529214345, 94801.33701130941, 43155.3897410113, 42648.33136411538, 41491.02862604561, 78135.97619440392, 39461.63014664561, 56357.249954977284, 11783.95761490295, 30510.528364392398, 45667.462585631525, 20489.459828949784, 61721.88900730562, 5331.105648145618, 35575.474596545755, 47315.91553464205, 47391.66549729975, 82677.77724063885, 63217.6069167117, 24688.530706942514, 34236.67813527266, 13888.726653363303, 85136.37073675061, 4499.458193900919, 98811.19855151889, 67833.80174567187, 74090.13408331432, 99661.15262832062, 31613.541912442255, 76739.34667777302, 53170.577792874865, 72615.68758687958, 51825.38735260502, 66429.77820969683, 50942.9798640169, 44915.53209505754, 37068.55180335489, 3738.7809231798296, 11926.677987461931, 72544.24061966424, 77054.66886254026, 52186.20331105207, 48449.03124354377, 77318.43021445845, 87187.53848538767, 23037.393644515116, 33433.792926982576, 65295.499167906026, 6409.64601418973, 41261.749739627914, 78998.46844300049, 50641.98747740313, 95935.88820774508, 47371.668859960126, 95050.66153771429, 6554.485088291851, 43428.15488261718, 8280.907763539435, 54865.98686262175, 7699.448478233783, 61808.51304171923, 75223.81178682072, 55467.35971113632, 4825.918708958332, 50025.146490355364, 55388.58117258117, 38503.24605282627, 30231.45125038712, 619.0598165366712, 41018.58737769076, 43499.2383115817, 87235.18615188867, 76646.69566662205, 53781.53028675231, 81294.52483905792, 3958.061298178317, 47175.62969401633, 73227.10150718143, 1384.5900601403448, 30644.12052241321, 47899.34086515516, 48752.554169712035, 67869.1628452765, 60203.20726375921, 69607.8394580641, 51273.36154552444, 27993.22281885389, 55146.72906817264, 59868.81144361293, 93415.27992308067, 71895.95544666571, 28062.71078698843, 42022.26760099412, 86342.77034648054, 69301.7017738145, 43244.063617163156, 26427.610614836205, 62504.217655522785, 20052.634103960165, 42288.6915259683, 39064.09747949815, 28439.455952778913, 5255.74602403186, 96469.91335094151, 36138.89360773731, 36114.07468563496, 7399.870126714525, 94213.08065300375, 60038.020165676564, 83983.39023844374, 12538.20428923217, 93481.420360265, 94544.44214422962, 99896.74065935992, 43222.99783878794, 1688.876029717634, 45005.879128935674, 14481.951796383019]} +{"id": 1350, "vector": [2545.30688986786, 43928.06863442607, 92606.38516934922, 17695.723090983338, 46262.75132788853, 12249.910809724806, 43573.540786882615, 22345.522731414978, 82376.48407116896, 98436.66615855653, 49334.12532164029, 4669.044224464614, 53781.904217358555, 65511.77023948972, 90158.93130542146, 99108.43092107614, 22010.4862104363, 5632.169279826194, 7040.862528558611, 28670.294076514725, 24325.021385863965, 59891.572277682084, 95249.37068798317, 14153.527450486481, 74830.44306468677, 5077.569764408674, 37812.60660735921, 65196.65791264729, 56207.96088585125, 45632.401383537, 7750.665841781767, 81981.41131719381, 19671.854064518968, 95138.45134890318, 51416.36476547401, 31036.102856309244, 61594.89122576628, 28071.316582390828, 7047.7818192527075, 897.3127757724519, 59591.371230440614, 3743.433956138298, 86179.24304644155, 11728.911258506847, 69786.53156616463, 77679.51394744667, 61619.597868871046, 21775.920525302474, 47504.86531635872, 30235.34774042235, 53255.1316318083, 90491.89116342734, 24747.5825337233, 71026.68882750718, 46528.68416258855, 23117.748590565134, 77470.14872906181, 61685.56344510116, 52103.349573543834, 44520.91716769101, 6551.415371291158, 83056.55588555455, 24632.729927455788, 93936.52579310024, 34860.45535259846, 67628.80970327037, 85286.9477561156, 34837.18399869313, 67345.2349153672, 63507.87914126365, 75428.42569997732, 65986.36104324907, 59380.45506776644, 83712.00881444915, 59008.421489708875, 44565.17737656481, 60036.39482336499, 5070.44673192052, 62629.40050494006, 40999.60524932248, 75272.09175015653, 43340.86591753681, 80527.64021672717, 50021.041775639795, 17794.73965409819, 80165.44220493681, 10218.792353193363, 82142.7979469315, 53197.20574814643, 95371.63605643429, 76494.85095936118, 28082.629275023875, 24675.67018065162, 5060.540277020109, 87509.58165213441, 98997.3380833333, 82330.95973760763, 816.490077380172, 79864.04826331786, 99995.05070015436, 87302.59462544833, 47271.859831331276, 54019.235859230444, 19754.935137864304, 66273.43801296351, 34627.68731352034, 43813.98712882199, 89488.82838768723, 93023.69248253564, 75516.0635541249, 64199.69174446435, 52622.44746237286, 33884.94003530589, 71872.17979054144, 95542.83911838957, 41815.51694336592, 43606.78619482798, 8890.367296780032, 70912.03024630825, 89583.765481088, 98694.1977703384, 9104.641576474649, 93129.15294611498, 61759.66577936814, 54457.1242317173, 21222.918503381592, 24088.791125161868, 73411.81232951056]} +{"id": 255, "vector": [72281.89632454133, 72525.56457699595, 63222.89309045221, 28449.29159135098, 83071.815960989, 75936.75499955044, 46636.47915515543, 99756.69636852636, 34260.47584653636, 21976.320221947466, 83207.75033023002, 33874.12277655098, 48644.59306904048, 8453.360536262511, 48215.58206152077, 6762.374436337815, 55363.46726442912, 69274.00211548484, 2764.524182763839, 26365.75313000442, 322.7218671986609, 54156.514037890876, 58343.754686525, 65601.19546906017, 70135.58925871999, 80106.63778895109, 37610.11864508969, 8461.739404215108, 88189.4045212079, 93134.45833164942, 50195.495812067835, 73554.86090778171, 92169.12331574419, 84055.03208738966, 68319.7159859788, 99831.32899751818, 45659.411557958396, 40718.229754413456, 47153.89184125564, 49888.82054866667, 38080.73340059991, 42228.60774133354, 6614.5554403225005, 68945.62641354265, 96707.47420498048, 14260.549545392409, 38263.4616097022, 68104.10450132802, 12328.555393402685, 75097.41348760898, 69164.7420167559, 86229.51888177638, 28655.114422246297, 61010.181278513366, 35477.682858323744, 73570.08889819217, 70301.05414715504, 16948.827518864586, 21355.758675967863, 98445.51236748323, 234.3786769439249, 66716.67305409731, 45841.470622989786, 54818.89073890549, 60167.70731041914, 40017.11377396294, 54980.89033156893, 27131.87492569018, 38333.68919116836, 43998.46988774528, 24316.078997798086, 70906.50826056309, 98964.12706535094, 84153.23550766973, 46427.00656869061, 23707.86620353371, 22114.45725069284, 51727.06777869934, 95656.83374761885, 62685.60318743532, 28472.741141321923, 6315.973175753697, 69546.46844257756, 52408.3513121068, 11893.140129241032, 77759.6691922777, 76691.4438661782, 25677.857579815878, 39300.659692305526, 54577.14911968888, 38813.523375900295, 24810.893449910433, 97856.03280173276, 23008.586818705888, 37635.5353586721, 38585.78851900618, 82361.92579330664, 94335.29511702433, 21009.52662600429, 16915.054340855528, 80042.45142445517, 94042.56750264374, 22178.48131795358, 22533.994689359184, 91656.74875220335, 44299.56977597779, 51003.75870092716, 86075.3913545466, 69803.14471566997, 82097.1341116841, 26554.294240973508, 8581.934332680496, 98692.63766656311, 22473.919443337254, 40006.33454423662, 69798.57388944706, 62577.47110018481, 39789.166989165715, 83568.22811990799, 56031.09378015004, 94300.6887024549, 12839.572106446383, 82111.18187569024, 23283.69605615337, 74163.27668222609, 43496.167912021636, 66908.80720882375, 87540.99766937991]} +{"id": 771, "vector": [11067.408852975403, 26890.90909073898, 9907.722386867224, 59342.21959403577, 29562.08420565535, 56737.85833997603, 10625.867133936561, 1736.2850758326665, 46392.9245294969, 74116.0863942978, 65246.34112848683, 82362.04346051709, 53369.134225470574, 92595.03416957174, 63842.75467950534, 94277.25148512857, 88405.21791979451, 97674.05955261533, 59841.72789626024, 94710.47924047093, 39565.59209183296, 31930.789951458162, 30170.657276599977, 92293.10304557432, 32058.966723657155, 6034.213128067856, 70515.05206867101, 6484.252027078852, 4793.288747686586, 70012.61358930111, 57759.3673383379, 9333.406517356801, 10529.437633800564, 94334.4220768046, 4954.757715870206, 82303.55132043017, 11951.359799600692, 21840.213129684118, 45216.16402256854, 54856.15115913533, 37691.63947736637, 2331.327188219956, 29299.458083382237, 7891.085361214967, 24765.17286066684, 14.126498465694137, 45372.66129671865, 41271.93843444803, 46115.001124681374, 55571.73073342988, 464.0851275860447, 47555.853627156444, 81936.19945509189, 75308.26184583268, 36484.59828120942, 44744.62492640834, 27801.280993877986, 32619.43608500435, 21709.16791993449, 54599.73399293806, 62104.36271452362, 27078.339256331972, 85336.19145092448, 62837.978402459026, 14286.167898523772, 79409.32956041182, 92124.74018122179, 43632.49356962656, 97382.03467039138, 27381.65029563484, 29481.427112767255, 68024.44328560507, 29676.786080786544, 66760.60559366237, 33069.35504105525, 30813.678890083007, 90187.70983763838, 33021.78971071352, 34767.370479405436, 94059.50478717894, 94498.94858235076, 45610.92906433177, 46017.22201296371, 4859.408232291673, 3129.8957573065554, 73781.40079984623, 27009.04619804232, 77424.8588997864, 88024.36541503342, 34916.16911600316, 46844.07816991694, 1898.6636637402078, 15998.625255438092, 89766.18482928444, 29558.262652336274, 37839.93445414304, 34910.364041425215, 38040.05320067611, 43310.33869372663, 2218.7548413221903, 33458.34331714625, 5926.219442785241, 2524.085633837281, 33967.41359170395, 10378.565182704546, 29083.34250845025, 88612.14949724032, 73476.42644842729, 44375.79490004534, 74439.10257956054, 13315.908130054233, 53379.768284144135, 80431.10150727733, 98281.6211909862, 94853.00771834355, 24056.067231386845, 91487.44750764602, 29219.3183263203, 10868.414850756548, 66910.7904977937, 3612.1505558716294, 89165.32028653465, 10231.831832785698, 19370.754757487397, 68260.60776812378, 85812.73408290053, 25560.16145408033, 19061.63541435203]} +{"id": 2004, "vector": [75553.07161326273, 34478.87321033984, 30565.22095911838, 46710.19968621705, 58476.59044097767, 59051.23447349466, 22621.503093057127, 92174.47664401279, 8496.895984918063, 20493.54088695521, 1493.1893510890748, 22498.960291007486, 18627.99409158481, 71751.77217783309, 32768.858041094296, 95433.10985340568, 61632.18913530675, 13313.953379366605, 69161.71135274008, 6502.772504861187, 94302.6919004098, 91274.79603616598, 52407.290250054706, 83894.4140311249, 38869.27285150071, 10275.257705138774, 50309.71226142871, 34630.453524099226, 38189.28721273789, 72501.58361208958, 11343.43691097819, 29943.63403104674, 47653.52325896811, 11376.264867127738, 52238.03689891119, 24862.014158803235, 20222.77199727893, 62666.653655255825, 7037.512023665194, 73783.96102589036, 24873.081123116237, 90112.19237158682, 95722.4113796523, 94275.25467353968, 83215.37989556944, 81740.61176552593, 11180.599574217642, 74227.16474399742, 15464.256349397987, 99486.33192794665, 20768.19857236787, 93014.16053955178, 38198.67428155934, 32022.897375503002, 72578.496406841, 56084.78475462741, 37903.67073967637, 86455.09801259225, 42853.18629468717, 64258.30383934301, 70209.97994127803, 54713.30591652869, 46416.38454392273, 72634.98275576947, 92358.89106732719, 53103.87760324874, 95413.56511073066, 32629.995736118322, 22403.980689499826, 92037.50588564586, 46563.78852436396, 35207.884258108235, 3436.5472994551947, 20235.409179551923, 45004.01698886053, 4761.3188390594805, 23925.315431970062, 46227.84642108133, 55273.22179846772, 17558.752318292314, 78577.80002312188, 17080.260007877067, 81122.20061796055, 18816.003000958703, 13762.077863172895, 19502.95755401973, 30488.7041115689, 2345.5700195320173, 90088.77793096846, 24291.492935410188, 67288.5081416587, 13435.83557582122, 53695.58622043936, 48695.3185199204, 11249.555519144262, 12568.091910101242, 31765.82692148279, 48002.669950537354, 81501.08433524011, 23495.00827996136, 84098.07475374606, 61259.77683328023, 94024.22948697822, 35544.67813693488, 73806.63605339183, 43809.499197996905, 11155.125448255421, 87301.5132202103, 26651.018040959607, 39023.84051918608, 97290.21698459407, 76403.42621918494, 94671.86704125065, 95831.9458106951, 97899.01426345373, 49931.30841624074, 71246.96412810781, 49491.227945792125, 94655.24524461875, 78726.63314811962, 69121.93313542416, 57128.581342214144, 35183.42651767411, 4783.582849087397, 58676.87027967179, 13018.016098737695, 92595.1725693356, 64910.62398792833]} +{"id": 1755, "vector": [11052.046504426195, 23893.61021522065, 45459.67697534038, 42497.53466427313, 10186.193337123606, 63273.691167037105, 82842.43229200636, 95070.62399629988, 92339.92986202477, 67699.61277626755, 31.3084570454758, 90812.49831040332, 10494.872566072045, 11580.993331417478, 99103.14853764311, 93022.5760763005, 84083.9654895174, 54004.529482071186, 52361.20455642449, 94469.70859793856, 79225.2445130526, 17447.090261965615, 87715.07371350708, 91307.09225496881, 74514.0754864689, 7262.559788178313, 12321.532500038746, 93389.68624347712, 3974.6036033207142, 66957.77926141757, 58437.966939803264, 88878.94204678397, 43437.04021384183, 77593.80076285337, 4940.626538486647, 79909.30753965597, 41038.53694648657, 64602.04694579769, 21561.453512316762, 80388.06633668231, 35284.05793456582, 17709.825135935065, 32590.504659627393, 83068.11268051255, 44707.74388389591, 79105.93245480629, 54865.48347260599, 7007.996175023945, 96970.3258278208, 94094.23355779983, 43762.76785020373, 44698.09287126015, 5659.782123770951, 99990.90216842237, 79101.55956167188, 57274.341679381876, 9672.776063639976, 56177.299388376, 41856.84560341875, 18193.132023043203, 58332.53630572569, 89749.46713523682, 24587.841568591328, 52184.46920684727, 13390.14908951497, 29165.312121100797, 6674.700902370245, 6566.247163958538, 77109.46762198761, 59981.523491059765, 60555.19443105403, 21014.418235615805, 10358.741928731151, 24308.287179042585, 58252.452797880585, 9919.904955751057, 66055.30622182619, 76026.16288580642, 116.75606876242472, 94555.70436462386, 52315.20856078351, 37726.2667042818, 54684.167410974005, 56244.12558983911, 68392.72713933248, 18098.4613737034, 14417.433336503638, 50615.43039216575, 72388.84435257349, 9137.48862700312, 66487.69029106745, 19263.741796016475, 92941.27275945281, 57301.216816212196, 80087.87201473977, 33358.32983180669, 32490.494172025374, 22490.55481467651, 80416.00710252034, 47537.47181600242, 98434.80984937561, 43286.27893816146, 44723.04514961775, 61030.02165484096, 4560.147167263084, 21640.885732084447, 86246.42296198089, 6685.88394915226, 24563.271220344497, 57392.86598265878, 58932.31821716597, 86852.56932425882, 27484.349850476687, 24906.596227819355, 87317.45702594123, 99198.05972097265, 16468.345650084106, 79996.22429995162, 53976.15012441177, 35131.98232812075, 62345.66052077906, 43372.264037095185, 18631.07229838901, 77074.07861220847, 38525.90972099782, 99272.94703532351, 9036.710182175722, 78069.25650946239]} +{"id": 672, "vector": [25364.391985712842, 52922.348933665344, 90028.2166129547, 88815.64561652696, 99686.54246669516, 46834.91641537265, 7232.889038918255, 91223.10638088732, 95710.4384178087, 81634.99159979932, 20317.02269602881, 83884.71845692208, 45252.912894150235, 37249.462166425285, 49567.95292014503, 4547.440269257708, 55975.20561956968, 70176.95565113246, 16606.64434010313, 45262.52850723602, 66365.25386940487, 18696.231981698853, 68504.46396010036, 8695.410710499962, 63026.72603915629, 69171.93682602301, 16400.862421192796, 63265.065907914744, 78263.77072448241, 84400.54956628574, 66123.21411437502, 29770.77080863334, 37198.42334802903, 51695.03511933137, 27670.6921721946, 83150.67485206551, 45608.311095160745, 30585.925039606587, 24184.374162236945, 53981.733858897605, 75298.50018835437, 96477.11923986195, 26129.221979844442, 93932.25041321707, 91149.13286268107, 75647.23414028322, 61102.7320897875, 30512.56848547469, 2648.855300215336, 34607.03543219785, 66354.78799379447, 61274.1601640199, 28527.516409687436, 81385.84732029658, 94769.75680321662, 47759.11014570039, 17268.176628494435, 84979.7953733157, 41848.63903622518, 66502.82421203604, 73298.12428336007, 2410.707744986729, 57216.297059250275, 24988.583404700705, 984.283619295645, 72375.56056993168, 66544.45885842798, 7096.075063288731, 31286.387391885273, 86465.06513340145, 85555.01255036826, 8373.541669494878, 58537.7240860096, 61084.09149048114, 47996.60997104242, 33141.33434762134, 60399.32489547749, 34704.63927292575, 52618.59189047912, 91081.45743842388, 53398.2053278532, 88282.33911327788, 53157.992142636256, 43878.82039447724, 95607.71883635777, 52112.66724517889, 52521.457166975895, 62870.563897459775, 6758.744957642671, 31934.365769150707, 11271.665297687505, 94448.96278878363, 23685.04697589384, 70742.06344030997, 44392.46645820386, 12268.683907076105, 98566.22466493894, 73482.69510555954, 90883.36527635496, 26472.14935228621, 64928.06310858742, 66363.06662251084, 23722.102376572162, 17205.15146983419, 58439.88204642483, 81647.2604203493, 7284.399978766187, 38118.36493661015, 57920.016804312734, 19933.310742255682, 28820.074593246547, 38905.87929688511, 54127.51790358391, 82030.87820465937, 9776.030219489463, 6845.208838374739, 63070.074004251175, 55050.00196034044, 94394.02035608552, 89364.23759589967, 72264.54819936273, 86956.6308351394, 68910.53365229268, 5659.639088340363, 79120.28089013853, 54679.10795673569, 78457.88963749204, 43930.16153672899]} +{"id": 738, "vector": [49983.38133791061, 14828.811319063729, 2062.669113540183, 8838.13617403394, 17409.834451260765, 31933.42457977685, 26446.09184220038, 7036.225210945557, 21115.628188518833, 81609.92666049003, 55429.958786526026, 40513.48292451552, 15069.42429368181, 54618.81037225205, 5883.188020335284, 59899.40802067311, 76310.11238963666, 49182.169671453725, 96914.21810798711, 3724.7991153811054, 74128.44006542636, 92194.12156338601, 71776.44830180467, 12836.844941945246, 69217.5775679554, 59656.692655822655, 84465.89562467672, 21533.866789829204, 61440.620994122066, 33260.82932295995, 54604.88369303781, 45887.04110826737, 78500.03774713952, 97128.53300645141, 52723.48105046364, 49538.89757422249, 69179.6844359009, 80713.65882246436, 65163.90820375605, 88913.64037041311, 82084.0072099031, 3151.967624136409, 25185.467861366153, 73602.5053287439, 97874.87957711863, 93615.68892292079, 47041.783597288566, 53747.0690795125, 50777.2963026461, 15541.970004490002, 42698.991132011324, 32792.11423044391, 21908.88547961449, 98354.71141011642, 13992.063446136637, 27130.418135731583, 62943.74880937272, 6531.947294388374, 79471.20780685681, 70602.75494129158, 48574.30872608261, 53430.91760007197, 94594.03921829075, 27390.158376467265, 29952.006589047374, 90703.23417685824, 67907.08847593018, 50594.04890681838, 25539.504123244405, 89029.04555598859, 22605.50655404796, 53336.684448329484, 90249.79947949755, 20313.714242976333, 66525.1477201545, 60736.92441303263, 3063.130228206268, 83356.97220034234, 15154.696054001026, 3815.695149649667, 58003.70225093807, 48239.3741845909, 60515.89103599679, 62070.79471018429, 84146.68951160098, 50054.02644579215, 51928.72936078994, 10390.362394336928, 93650.20797553918, 57421.694229070665, 81738.8767885871, 65633.8401804195, 82941.1363991866, 92469.59220016545, 16394.770077786412, 18261.818139754647, 4195.918465374093, 13226.958735195838, 81417.29293685498, 19269.552853902427, 7917.434981255722, 25767.313503077483, 77193.53297897101, 24327.943282439468, 38510.94645985408, 3695.5703651005647, 52116.28564272912, 58149.2490194084, 91340.20255905963, 5679.917849216187, 26422.851745940166, 72717.71826212545, 73807.74382545816, 43713.918387505735, 64496.19334553439, 84443.38468461549, 44099.8005822985, 97804.35703785798, 65083.45759518852, 48371.5126334255, 41748.35876054724, 41365.514514424685, 13479.196308662034, 32451.140901529317, 94886.05593756883, 86009.47604877708, 34558.44830953773, 30144.920241273165]} +{"id": 2023, "vector": [63323.237185763755, 39283.44491342186, 44415.43787103223, 46984.41512819165, 86829.00818153044, 34356.559910731354, 74108.3965269157, 6593.745652904004, 59625.319884976, 57140.7025830849, 37905.07929006527, 99499.7126080601, 60490.797638498836, 45216.878313376794, 88555.65580586925, 46242.78114335367, 16992.997022927957, 14528.106351883363, 29881.513128421368, 36994.15131891959, 45310.3593896656, 3614.2252291179734, 75609.69316370692, 5119.595474473837, 42487.01039578064, 44774.361330539505, 65118.70622140953, 99914.06714866075, 2586.287838080681, 53496.58072772167, 29641.13565095614, 77009.99123773341, 24493.73937454894, 38941.42814156163, 46227.069536585885, 95764.57526272773, 78426.15979421153, 29868.07099788048, 26054.556790001927, 62076.321426589384, 62754.77821783702, 47689.0533246842, 58809.67551128887, 33395.948842685095, 64057.68704596411, 4682.151304646342, 42575.54125895593, 54108.62871556307, 44592.87402188684, 59195.90424910469, 43743.44031235266, 25287.19528242844, 20776.293875348973, 60449.11289172828, 1682.5864041647765, 41301.58330863308, 69173.88029303802, 61395.53196239746, 1115.3596591281346, 47871.6530331447, 94890.13224979983, 2407.7687485246856, 98004.1293047692, 75301.35192825874, 33455.83476273538, 52582.28213349965, 22036.460367692624, 56508.290897055726, 32211.996534077945, 77877.62809381777, 66826.97268761373, 85150.87823989391, 16220.716357640518, 18796.42656267656, 54396.316012325784, 8614.9866549868, 91117.90702225192, 38154.92642886494, 8456.27319569614, 65898.1987230469, 43425.82906741474, 43906.594522592866, 28680.059402895862, 42656.68377490428, 28579.727146427325, 24485.07577508342, 57678.346650006366, 71316.13418760356, 42631.84841398777, 40772.01184398179, 87033.15604036013, 76694.11747416026, 95891.82683485709, 80881.68947951484, 72361.01194352037, 30182.62800173219, 872.0624887316841, 17062.306307147333, 52394.07664087095, 9884.005952521667, 22185.82607829793, 5513.287533573197, 6174.632225957688, 78301.41623052908, 66730.91672163816, 59325.23233878135, 9366.954550879569, 44470.265695097136, 21907.171083600828, 22971.650322010482, 16886.45657821799, 86977.12203742545, 50012.79418286857, 16228.244461652352, 80678.94226685012, 86380.7720220789, 26590.28608806675, 68081.54025338225, 69437.31330941337, 71811.89489937502, 32772.134339297445, 82171.41539182603, 789.1032337374094, 38341.23388151871, 47814.124074861, 39850.89297911014, 63017.706454619096, 65281.56734721005]} +{"id": 714, "vector": [721.4175708493253, 5591.23955309726, 44380.91971577205, 1013.8836226041615, 51622.466056341334, 43214.82206286148, 36895.106039876024, 65809.32758821537, 40132.66844174861, 26844.518816450534, 48558.92506737421, 7760.290398283376, 4304.275029419257, 24577.395800641276, 38262.81585332819, 91153.76183129428, 26156.16267604901, 79551.7335203002, 15614.289959391059, 50793.31579479827, 25516.311894123166, 58778.01196758542, 88390.0796937794, 97629.92587513194, 56709.088861007906, 2784.555482836726, 51322.70750415699, 12658.157028455664, 94237.19012023688, 12145.587106576706, 91830.85230847243, 46415.86796767559, 7327.800362852666, 11236.53241742092, 89183.3047944495, 38830.154906806456, 12861.440176206306, 18614.19123148549, 79880.50464352629, 49909.924928252156, 22157.202507687958, 61291.89585708238, 35586.58104927387, 23441.450720488632, 66151.9901439507, 160.98589260372665, 46337.34721936924, 66984.94906193945, 44825.94222997382, 712.706145242159, 45260.48574524793, 736.2403887323566, 2244.3312571326924, 82041.03684838602, 86433.15811681475, 48370.231680746045, 97513.82994846007, 78253.45904443532, 35043.25068558307, 23214.113696624372, 43350.88272422899, 50449.06111565451, 53990.3497103374, 35070.819201581784, 96316.10917940308, 24409.100307936536, 46388.374853294765, 66769.68544129707, 202.0278594356717, 24022.41738881017, 24170.229801234356, 40233.596976141336, 27736.183872242036, 28317.24687853915, 75887.49217034827, 39106.66234582105, 29170.264366237352, 52210.78266621209, 54571.18341760595, 11515.576418440187, 23971.49450082453, 99737.13728661397, 19199.023387204372, 93110.38294621494, 17690.27714741026, 10623.830178370652, 71938.60035431219, 16362.00044636763, 59337.143998208965, 23051.199608711937, 652.3129642429071, 24379.987387001478, 31548.57330132468, 10127.507052688334, 93018.28102515974, 94364.95007071605, 42673.290820919196, 8787.437145275722, 66743.96351725492, 91645.08169796833, 47876.738419668494, 52603.730023940334, 43609.5542474349, 52606.41561985917, 40552.24456423766, 75540.18857899509, 6188.461758937447, 28577.77888737634, 98595.41496645643, 17014.965302841058, 13464.28389592772, 98729.90841978547, 81290.22243409278, 9899.737082125359, 45547.84326145514, 81863.84726549353, 8275.095894857575, 89953.83187154627, 25368.878342260825, 18140.3438190874, 43147.582713734075, 14518.76879602868, 63032.43707968396, 13459.713558791342, 99221.903234143, 70770.60347504259, 66434.76848829225, 20835.064448921257]} +{"id": 265, "vector": [77900.35954681096, 21669.889756492623, 76490.98548810075, 608.7887533276381, 92870.40822547002, 12603.643457111702, 13906.020718197231, 53095.222051130906, 92857.87341989811, 8836.533951077252, 1869.3853090722357, 86414.11168371368, 88084.91904710323, 61207.8402826864, 10789.31982066652, 78593.74776359249, 51874.79436216823, 86207.65660383669, 57898.25390662029, 8374.783260923146, 25233.34843437074, 43775.647734556966, 72582.33797627388, 86550.88357044564, 17260.01480312108, 81553.99643348635, 1970.408165327786, 53158.79701622062, 30143.68850245377, 34836.698263214894, 59485.21741082081, 1603.7658037129643, 61779.378285747014, 72437.1629931997, 87529.97229469821, 22009.72082592757, 63936.75182803045, 12829.186008945759, 91593.20418699189, 76648.86482228695, 20941.288841053796, 3479.953886692777, 17879.274600828045, 330.80625569712475, 65297.124124411734, 47295.35396282442, 94348.50907381666, 85350.83661596585, 30995.99233196463, 51765.209050062156, 14513.229587499844, 39784.71835974188, 12939.721484193922, 24582.34506142608, 71854.93589225746, 4410.931838251242, 14848.324522672607, 12612.77872170562, 99719.61735671936, 83077.3061847358, 95015.36932206592, 50906.727452639745, 60290.679396637606, 48257.62070998775, 44243.45769170359, 56656.15348707601, 8716.572998290883, 87390.6221102908, 18683.168034512655, 81926.00413743798, 43519.665766867554, 49500.01534918316, 14308.521598450452, 48318.88673295621, 13989.333759931154, 80282.54187557001, 33592.80812780038, 74419.08103501209, 98941.95718030972, 4087.3425441914524, 31128.957380815624, 14853.62539811681, 3644.9228396168933, 74975.17773738307, 11510.477538789431, 65540.54134941501, 45442.6510166677, 83736.34109499404, 61306.51316860996, 27745.999756339213, 31214.501148784046, 40022.86949008661, 77712.54411700257, 91882.29694774044, 2496.2267232981008, 19277.050677073414, 57313.08881527604, 74721.66021794679, 25408.658893688083, 30688.751966230255, 51578.755630096384, 12923.823547140157, 34922.73875794132, 72363.2664672512, 53541.0381492469, 29010.13337701899, 80459.64286124652, 3402.3603192644414, 74585.35195377166, 1853.7399442318913, 35122.78354774268, 37119.50048217226, 87980.99341968652, 49879.84510808593, 60709.02273116906, 62653.63503787974, 78447.93684178451, 91903.07571777349, 60275.38214603676, 94390.94209176651, 68527.39618071345, 13948.995362769401, 65970.03861989414, 2959.062934871315, 2153.9425301797423, 23305.991210011533, 27698.05334746843, 15607.190165983364]} +{"id": 1408, "vector": [35219.05473651853, 41428.97622302334, 86077.73677608071, 88998.662825926, 80605.60481601243, 383.9439639611975, 35394.35766883655, 73.6515484801803, 12294.518024354495, 89970.77173947025, 74789.19247204825, 16061.056755999436, 4008.166962786841, 96856.35700505799, 2001.387022183454, 90204.35260546823, 5211.1663066841675, 72466.82275667573, 87850.08676197467, 39404.078009774916, 50195.064279627055, 27833.122822739242, 28796.558116302695, 71980.72543481234, 10220.028784626356, 1159.9725119656123, 62356.30789659631, 75347.78869638611, 18063.146997756165, 33700.856096534095, 26638.379542865066, 48376.977798732136, 56366.85889538285, 25524.342162357218, 62828.232118384716, 39237.902747831075, 91595.95286246773, 51107.74522082632, 97936.17059088961, 34879.33181976325, 9756.956089161584, 78256.44640937822, 59466.691887327535, 92441.78553026411, 51518.981769280515, 63531.003236195415, 30549.382510400723, 47074.67796987725, 85073.91191485038, 93515.27661506836, 96504.0744582334, 67675.721891274, 18397.418503373152, 10284.503422274838, 32341.08341794497, 76007.0347018339, 15151.02775659788, 15429.700103701172, 53766.03201078843, 25349.148511067466, 42887.56393141046, 61219.41828448011, 29859.029293182448, 50103.49015474675, 93763.61204326345, 16898.632759924825, 77898.47131453348, 98250.7958622207, 33881.845954639946, 90584.9518405265, 5235.690227421119, 60046.88616908498, 99216.53598552, 6324.913440832313, 22526.98022582933, 96954.31182181374, 1542.0754178228635, 12726.080800472306, 38996.809707370805, 9900.06928863857, 63497.372939858775, 23136.483563910802, 4294.582838550476, 78304.0485967978, 7956.783950920377, 70235.03463979426, 93356.15110394442, 76440.23402920313, 40056.27151385497, 91707.56693281465, 13075.08545295193, 3411.0122012269485, 41315.141412538156, 60564.968993855684, 69391.79211804498, 43668.74334506376, 41389.08292170959, 87864.05856225562, 41966.486621621676, 86448.94174067395, 4462.253179494024, 10136.844165174296, 45931.66981336032, 78685.02434223656, 68103.67224119722, 92329.5129680638, 80937.11982463679, 23472.465836211486, 46233.30342167254, 6691.78777222923, 17990.486013970796, 49842.430310628275, 48754.1842680582, 82633.02846576615, 82922.36140781251, 87594.64029585868, 19633.93536680673, 15746.87987395459, 81471.04750329562, 59655.467903189296, 54173.37706854734, 50127.60943312534, 9362.269358373598, 34742.29420902463, 57583.73177922969, 1719.0964255319298, 62056.85207359849, 66424.93401723147]} +{"id": 146, "vector": [36082.64068319637, 7248.5918926547765, 57989.494938050615, 10435.402849906306, 85466.63397223086, 24705.663567745683, 31006.22983926299, 69559.9353854253, 22998.04878015386, 78853.47648582746, 77102.28155924763, 84823.79152826116, 16184.227475596124, 74335.65834444563, 94748.77259926274, 53554.76810689704, 6874.005238802716, 61522.42923921284, 68030.04784012969, 87826.6167874149, 76340.07946228204, 51207.62979244534, 82331.73225059797, 98706.67752106847, 31015.23033811997, 34867.122619639114, 96109.91221645777, 38314.03603225577, 26353.458395219244, 68102.89281925805, 71740.33907856049, 74709.37515762215, 73611.62613971224, 51602.2578137715, 55767.59985684875, 96713.30276783601, 56019.701575533, 83160.21349487212, 72490.7076133855, 50425.2650958398, 22868.81787823816, 89627.30843504418, 86488.29918822605, 88315.82836653739, 52017.8979183737, 4861.209818723866, 48789.69557642754, 66913.316992283, 593.4691785170476, 14511.886472238379, 80223.91853200727, 37757.596477837316, 85227.71452800787, 13160.4881855911, 17057.354293466065, 97770.66992890918, 32072.710176906337, 57730.10341811598, 90920.03253881782, 24006.215891711756, 41827.672404097626, 30397.58146657462, 26406.561492554094, 9476.893106015683, 5383.699635820316, 76037.52664304605, 57070.51950640598, 43674.90791432356, 88215.82360716553, 12510.758480578921, 80743.53801851455, 11205.169808645443, 35752.43808313705, 65572.40684596192, 96280.40820878623, 39063.01591882718, 57719.82131237521, 89441.85159260736, 69374.2827600384, 16251.181424380957, 11175.24268968384, 97856.64051673576, 18408.144743188313, 47029.01339954098, 49162.932472170876, 49707.56920463094, 33627.99299615083, 3774.8381523191265, 5638.12723946272, 20636.540496521116, 62287.38112548573, 93162.4193940579, 96670.00617055407, 32468.68700961796, 78400.33104272393, 36527.52231671205, 40845.17973839482, 25123.1032882819, 21092.89511232787, 96459.64151020886, 70916.0055979851, 1941.3707065977114, 94247.90606261567, 85316.72772696013, 89503.84332269858, 98838.43634835827, 48054.26477217448, 93165.23772613498, 15177.197695827905, 49655.74517469444, 35191.629234403044, 38437.16777642796, 48566.48028516462, 67215.89751871204, 94628.04921757548, 5649.066440409578, 12158.674497465949, 97771.11081971062, 30666.85929904457, 85361.16685158633, 90257.60858499688, 75663.22421332111, 6837.0691266825825, 80030.35869668412, 63011.741566499644, 66830.76917524482, 19752.37863290583, 91717.9280984985]} +{"id": 598, "vector": [56386.671199300254, 57357.479534197875, 92119.09747324085, 98988.29805239609, 51636.09901222408, 84178.53635641291, 94812.12610525242, 87132.45090262001, 3936.4130378178097, 48107.67915988313, 652.12723057414, 82569.92215946785, 30028.417985833945, 1814.8311143066476, 243.16024851168683, 27341.913868385414, 42448.7159263089, 4269.18215252734, 33810.23154030705, 21382.06352870834, 94188.56660436865, 67358.77814650214, 61223.520897402675, 41863.32024452245, 61735.08805063492, 79685.99737387178, 91222.34397453559, 464.38843289291486, 26870.032336101678, 93918.71318137569, 79962.96825648856, 45812.336895733264, 59004.09363594591, 93793.04709722898, 71841.12210825349, 23108.974197367304, 39045.392298402796, 1730.7456547359657, 54431.15036496443, 48966.452648056016, 35427.049722644966, 69475.59028536321, 57321.35243207741, 67063.61667819475, 79171.02499316269, 52939.31826682526, 32927.48402243971, 86839.77619469677, 62113.95688762301, 15096.679129192504, 41089.63783462243, 79090.38591613376, 5515.801322508917, 11253.849925065762, 21584.211522160567, 22340.842254820236, 78733.66120710628, 14942.735177866383, 2936.64382597969, 16458.21275318272, 74156.86050553473, 62458.6296649544, 62693.466317051505, 56944.844866356216, 35334.963598601855, 61612.999430876946, 64122.395558338954, 71542.05672930514, 92357.08027440227, 24627.972196147086, 33650.13445375913, 54082.153138103306, 56576.09322399124, 43174.447969620465, 1783.9887756106232, 82849.24784350391, 4050.4483790134827, 36632.4067089918, 1766.8792929436684, 54598.98025046223, 55779.81579853984, 94819.86131678241, 30899.821187185582, 61406.77157655674, 43055.89077258455, 33601.21215632508, 95926.20403833085, 87168.17492884902, 80586.3543740974, 84668.20182239277, 69967.6658387876, 92358.55586923659, 38110.16205533123, 3207.491164530174, 14618.155424637602, 23230.60976499761, 23894.060435437383, 28987.9543111631, 43067.44281262187, 11473.962698427209, 59367.631928074916, 72748.08782170917, 8737.35480693154, 91317.82264020863, 63239.32949062896, 60411.835398914525, 88041.32374514293, 45167.96223017228, 96875.87308411668, 43529.386714481276, 98902.04649162901, 10225.309580413645, 79403.35924210846, 44528.08276139635, 72213.17580857873, 19710.812775455055, 91575.64785145031, 27825.0675371872, 11947.67209111407, 6154.6870227515265, 32352.062172598395, 51547.33429530532, 58713.293154076186, 69958.07010521859, 87101.20280318063, 81425.30301818388, 99586.8083839923, 55344.40401673506]} +{"id": 593, "vector": [12246.510036405733, 39979.763092284185, 49090.00335101324, 70333.10852199503, 90601.6306390362, 85385.21524170526, 15547.987825470776, 25081.690735259876, 31199.144862664398, 22102.77511412838, 30988.539718398923, 20276.68900098264, 38460.38168311035, 17804.733566069208, 97066.00386045742, 22160.820285570713, 59406.67820433647, 87450.27003474406, 29385.555387698125, 40135.26578905584, 77470.05391721304, 92905.88599011922, 90968.74650742783, 16776.031874869957, 29753.242837419548, 20831.883388576578, 52394.981736440925, 53849.497829102635, 90542.8222832303, 90446.37297856457, 40735.459140500585, 90838.3296396896, 21241.539206721573, 21400.66290090321, 35941.971797332626, 76892.20071081385, 51042.05402504972, 58083.15020798707, 78654.1548981125, 27559.002193322867, 14230.029215514372, 52999.33921849092, 120.19193975609798, 5007.479695272754, 50242.73969756762, 77631.90193460888, 85336.52824472728, 29693.352653095397, 36455.95988669179, 4237.894863053093, 40988.31368920977, 14601.674140818233, 928.3176405904592, 93187.74811792687, 92897.59969191313, 37718.55524146227, 54359.08637225623, 8725.916880145978, 97351.36988619372, 38278.747542605626, 63703.22246380328, 24207.619235743718, 20221.640988870993, 61655.55001449572, 40598.83515638356, 84432.5924447554, 75055.98350709322, 8572.364580439451, 88173.70427460589, 70903.23422074073, 74531.31320045734, 52793.293637711446, 83269.92361033616, 65382.485902894, 22896.46236603505, 36549.985149177846, 37740.539240614344, 63007.2710667408, 63578.91780481564, 82981.85832431319, 95263.99169020544, 42978.654209203945, 59527.0783761765, 75290.16726663017, 55695.03003934563, 87766.72630922457, 48175.72135008026, 61257.159149282306, 55817.50089860921, 47571.03699146304, 88167.84572683688, 61152.93171877885, 92717.13433878258, 70099.55870631426, 26280.945294806002, 80219.7128546373, 30251.52110086201, 30172.564327037875, 79795.52840388409, 65685.36579460888, 82431.42880097995, 17817.165806799207, 58695.08251377304, 86018.73590980869, 24738.72888957076, 50014.69403686819, 33169.178799060406, 61249.02128837465, 42316.17676726539, 35079.626009829626, 48608.323777731224, 60539.878557406104, 79559.00821031573, 49757.397448316486, 48783.07497468731, 67378.5680996562, 62.07803778394138, 43040.246363862454, 30294.370500477762, 93105.90890323662, 46962.37005470626, 59751.79232351911, 42098.35510881865, 61174.67863562618, 3640.74091569645, 82901.88497814616, 10990.595573596496, 84790.13426354858]} +{"id": 929, "vector": [46493.01898686915, 79391.11302362592, 51661.51631992715, 91734.09607825604, 58753.602583214495, 58620.044237065806, 70006.14246768855, 68114.1635188529, 96253.83401640404, 97021.45085877257, 61178.76740471885, 58462.443904228334, 21511.11306986102, 42937.829333810594, 7495.390311771577, 36186.01398669311, 84304.9252858319, 78583.22404521305, 49879.38015166194, 33943.59796316253, 37362.90522685103, 90510.56292898873, 50512.39649918912, 20543.334756153352, 22119.435541563136, 98733.2470451284, 45020.54902652678, 25451.718962574156, 98997.10982238784, 47658.27182155824, 8583.651027013306, 50310.66082860595, 86485.44939354248, 11804.49085132116, 53073.99962215528, 97620.29665003307, 85127.25685556431, 65033.576654941106, 86664.91864548493, 27998.55399312312, 25146.628597247378, 88018.07534637554, 26729.66626133809, 75542.74070946669, 4547.159334949546, 34787.20652569485, 38835.7312894293, 75373.42032746776, 17837.855493508046, 3257.580538750371, 9970.18438624061, 79925.99154580839, 74187.43180759512, 14479.623970426037, 8739.793668721684, 336.08961882060083, 43927.29769130612, 11229.12330276764, 90690.92359456066, 28878.347280221205, 47010.04624188828, 64865.46311484812, 65014.21134226966, 96452.56154312784, 8282.54448185186, 32818.69647401504, 11383.474811917027, 91068.99054817743, 95098.5383537159, 56454.64205441126, 8995.461329398735, 20838.40538748971, 26187.00654377256, 20896.763377943116, 78749.58689703976, 44943.676824667236, 19102.951909112242, 56794.350333348695, 6869.168441460727, 10165.155764868172, 25109.234393196533, 23218.75388007062, 84399.33770418125, 86926.53742190248, 44941.36991268716, 40446.01944877806, 78213.80886586213, 47119.98314380243, 40064.489566123186, 52741.90579492117, 61879.918068404186, 36369.57837297672, 78342.69096088725, 29973.170167806817, 60277.28961599075, 19037.81441499973, 94241.93971892908, 85344.17299386747, 10679.35923946609, 14168.514860214254, 79518.07309281024, 66855.99642177147, 90323.72839372943, 66203.9951247108, 42247.802340803384, 29998.506186023144, 31826.889246903113, 65547.4977878964, 51891.39684690354, 49835.40150337495, 9424.747547617595, 68674.56330176607, 40087.39526426708, 15239.301460289445, 8246.327602494996, 19745.055872364836, 62199.46146492544, 89464.94108162937, 3531.432392652778, 56814.303800353686, 4263.197401473917, 77306.65614749334, 53727.49990365345, 82165.29211208543, 23365.444144501023, 93953.54966686867, 14773.403474346103, 67821.77042815076]} +{"id": 37, "vector": [98989.61578497729, 60591.678364911524, 48144.1636567828, 33908.71413369881, 65854.99073974071, 91614.98975834061, 47884.97041920709, 86648.6417012352, 90966.9468044291, 40715.07889166661, 56255.52629502778, 95932.0740362713, 16209.13335534262, 21639.598620768185, 26606.621037030985, 49630.03344181014, 55379.74677710129, 78145.92004166341, 36515.27585715835, 66028.02958718107, 24841.623602118147, 2206.150398649398, 48907.06847154364, 11312.33434388611, 38798.86640651593, 96911.31740169639, 91455.29522544809, 30179.111766005197, 95757.407852919, 9753.375878227866, 98076.6953565427, 19465.073196932648, 41863.86515176026, 78761.20470427917, 43779.90818842951, 46044.831625461324, 78284.2661510005, 5656.106769658609, 45743.72536579715, 51724.80493313099, 93033.20248781855, 88475.81571959406, 31660.584181274553, 61527.58443842391, 33614.421825697704, 54043.3722515426, 80075.06779360834, 81084.36061718327, 89188.15515458786, 87762.28770852448, 90603.76769021069, 73481.42494340077, 54827.764552662185, 28860.106074156345, 71615.07492125026, 36580.079003026796, 99071.28267096718, 50418.280761687194, 81933.00810933292, 23645.959778437296, 49203.07675154267, 4781.553499737168, 58482.2683362756, 48993.09886555517, 99884.16014970912, 13434.453130919133, 12947.376012475386, 43597.85549547921, 75727.47788749172, 66597.25439360474, 36771.424126343634, 46435.52319296902, 79102.33792021767, 69375.9989836466, 51564.30190320312, 81728.99674916134, 63833.30921119721, 31434.94532030805, 65342.46555370861, 60449.73070952363, 89609.98707496129, 47355.938417163954, 7338.100626688182, 8086.009408595673, 64058.921551225736, 97501.96713084322, 32723.27622528044, 90469.47178479176, 79877.79698735519, 74875.96236544922, 59878.60205083591, 46877.378668464495, 80651.56630754801, 29134.52670547574, 2717.108819257119, 15172.629035387008, 62783.98139110788, 90262.34149493875, 35987.36846753565, 52578.38798564687, 26508.422954549027, 18102.961011283303, 98876.88156250879, 92322.96987831156, 40225.8613118564, 10649.011592402736, 30741.14511429784, 14943.035074142175, 1515.92334197862, 82688.85329413333, 50575.101483338854, 95094.5698105198, 78766.33850704494, 16960.545458281882, 68124.11315088774, 59360.51579555, 54229.48065665586, 63483.96545368297, 67432.7921405494, 74706.75354243294, 94417.23508493867, 36648.97436411416, 94276.30680067654, 17707.679966547206, 91646.52926822084, 9436.868211210514, 27208.952134260177, 66077.20813152163]} +{"id": 1364, "vector": [58193.9366922324, 59650.13296842957, 19957.320259663826, 15634.525855113956, 1437.6379857529043, 22767.360451320063, 13002.450212744876, 85667.7085197819, 42384.58503570456, 70408.73492117057, 96890.76609415356, 74889.32727883129, 84505.6571085141, 20064.87867218485, 48057.10907510905, 998.41076740117, 94131.56571555586, 89953.09391672532, 58061.48439950841, 74289.88829635463, 15701.462139564659, 59752.46400215863, 2553.961897515711, 63595.89472879671, 3798.1028676103756, 89349.33180201164, 91245.00114535051, 29110.17676908646, 59200.46540596418, 77984.6966600541, 49843.807962537845, 39472.95130246057, 9575.61143509511, 25739.990590604324, 8623.815107879329, 50904.5748099376, 23834.939526932896, 93756.27074586232, 97202.31306538991, 31375.091337195572, 98280.33667495339, 3978.8426184438677, 63580.223813273995, 87028.13835609255, 43980.387000931856, 20426.2960643132, 6.337541033785143, 17555.705677371814, 3654.390675805708, 80821.73971328547, 57197.71245203504, 80336.64004852007, 39577.33316022671, 1007.1013131840667, 60359.09986106172, 4568.46573551809, 15034.629405227484, 61184.61652223112, 11420.62584981146, 84769.53797823186, 7695.661109125107, 8156.434393435697, 34623.553074846044, 33047.32338977605, 96717.84178573728, 14315.487612746292, 25879.66305895506, 33538.16721781825, 70890.39264970335, 10929.903577879651, 56892.21123845457, 58405.5101850752, 27110.785956112006, 77437.56957329146, 70991.29131850881, 94249.44867915982, 75579.12699128965, 13522.78511333751, 58268.85898339742, 28706.254792621145, 37948.66237954165, 33944.39516610555, 22144.714884701232, 81045.0907644337, 96068.76196478364, 44646.47239804928, 40597.54699575457, 90461.1004968589, 88322.83040403175, 53753.68504431698, 25896.59741107623, 10281.540320642735, 50862.37326594254, 71435.23916542651, 22211.04966652453, 77189.30706109609, 39939.467788111935, 53303.59853935084, 26487.458650232355, 90696.85646814057, 13125.930060997414, 63203.99437449563, 33895.68340419312, 57900.18575504562, 81962.18096910557, 13476.733024948873, 92754.64436103807, 18158.5325264627, 91328.74141105758, 52678.12829888739, 40671.90673822987, 31309.05735151832, 19153.365449691173, 83127.71238336495, 51920.74773971974, 48488.795369377636, 98599.9381825462, 43775.41906869245, 19722.06145711496, 18445.214402242473, 91268.23590115659, 25118.923483496892, 53448.95400438865, 70110.03702626833, 39962.30930537231, 51990.38590596831, 60798.9251040554, 38764.42651053712]} +{"id": 200, "vector": [13978.218387055153, 45379.77543481807, 18856.87437772977, 98133.10223163177, 25657.393263340076, 7957.543907190024, 94292.78492071632, 82497.4248297265, 9149.705217536663, 47504.45480323692, 91092.86767247072, 96141.46988959769, 96501.43761718507, 96113.00097519736, 85204.79710014247, 12704.350836394784, 68468.09938293394, 82888.57056862018, 5669.369842711558, 86356.6640579374, 58076.01643436624, 6327.4931440389755, 6715.524045854427, 46556.899651487925, 53310.531175683085, 63906.43289985551, 61515.06861207575, 60337.33592694609, 95339.2145812181, 44677.66055683331, 90697.4725465767, 45720.66091112866, 75860.86031649083, 82541.38456349661, 33383.861850292975, 42998.29870521712, 50217.592426463394, 62873.939521410284, 32400.98745237128, 17403.053341450115, 79315.95798645445, 84302.92918165604, 46408.756622622896, 40193.156991454845, 96124.22104161218, 67697.20840375658, 16851.470369095023, 72536.63893681581, 52873.51988071416, 71170.52968705256, 5642.383004864715, 29155.16895734175, 45988.770408376, 17466.929160678603, 7648.715017368535, 11689.31462890408, 62308.576153055306, 3659.97887115066, 33293.80011105284, 99021.30020088084, 86351.63355708976, 21877.46862643487, 30128.450711530684, 22494.96758309617, 62264.48225725568, 38367.56070628216, 20932.69405054545, 26137.03091548024, 23990.38340479307, 77360.63122590043, 42093.45333850384, 21130.074511938157, 32976.865761463705, 87595.66316866067, 29903.33173660532, 99595.18651620767, 46924.79572724465, 48851.86941440278, 70248.35376722224, 26541.814938274587, 26182.437147887373, 35144.68562018122, 27565.217940599905, 30178.282389261858, 59090.31056295504, 93118.341955773, 91938.05566700944, 60993.09026932023, 3797.5525537292997, 13575.793300311068, 28142.860814087922, 38099.3237713182, 34623.03726940059, 75557.23075060648, 61414.43190705893, 1536.4777432703702, 38919.318288216156, 97717.97417170044, 4324.7450368977925, 95860.49346385908, 6241.495121103946, 70255.42731911078, 81492.92930662942, 6198.781165130218, 93523.6048844977, 70319.62813439737, 10201.521548747061, 27271.692748841004, 8927.275732701046, 15202.616090543053, 78606.88447312107, 33611.27212465795, 78646.58117624886, 58794.75342167524, 69815.18658040272, 94350.62832427106, 42331.421092657496, 68139.40908119494, 54937.677329114886, 80516.60612992833, 38028.582734044736, 55364.75973030326, 95205.83686298768, 48994.8860525392, 81641.0880604245, 32614.44661577233, 64214.35900173339, 8259.634763331369]} +{"id": 1681, "vector": [73124.49325113781, 24069.057029140207, 40929.734453016456, 28972.353197528853, 20493.017156898175, 22.45370836228, 18387.493468148285, 88554.38352813454, 3071.8021398141727, 51965.57185142734, 92098.81847299472, 79413.27837731996, 60300.45484279157, 65687.86204601647, 42462.60254668155, 31136.059623561763, 69624.27651711754, 54811.41614319435, 73366.51275444182, 98827.58475968921, 7016.734338007646, 31269.537141542824, 86728.87569961704, 78741.06462668958, 2423.0509946576494, 83427.83555796593, 57825.05325372376, 22236.75229005603, 33440.29052226126, 18140.94600006424, 80176.8032406251, 36771.88237441911, 32375.650615452632, 64844.5353748914, 22833.854264138077, 81338.16078937167, 99633.48425826279, 1942.642975025266, 96163.78374189354, 86736.8356823781, 63135.612198376475, 33094.836973831036, 70201.45737075071, 47532.23317347824, 12340.904303371226, 41393.53980130895, 47896.64025256159, 42326.415293655016, 64488.71943537505, 29522.034267009843, 4916.883717103337, 82503.57322664263, 42484.80074567075, 67925.39048452914, 56103.18630032716, 57625.517096412346, 72510.4462824983, 10259.04476446239, 96084.60495857903, 50001.81017694366, 56115.72439595135, 25626.383548721264, 37863.14784151823, 92411.03099810371, 7184.525951211141, 43196.99182309934, 23258.502281810077, 76757.47250873412, 60417.791774644844, 7362.931333696254, 11635.807995465586, 23206.35090633706, 74958.50740515185, 56716.488273594856, 74133.99172242802, 92611.13042330596, 96509.4103362959, 54686.937526588365, 85540.73329450822, 49280.79049078241, 15691.731462091862, 92855.01436614848, 30085.883590524987, 51569.62092417653, 58630.73449739944, 63632.31962652337, 55329.870983879104, 21702.15480899891, 95505.42845067341, 96363.55851146395, 55407.24313507299, 42577.20885090789, 65672.55151263757, 69517.03685435714, 84475.46372802001, 83318.15624927312, 31317.650435378906, 80752.08391963525, 16728.5202975071, 25694.874475250417, 21217.230800325626, 46066.656374252554, 92943.94668320492, 77432.5136688849, 53472.92373896841, 92344.02019124765, 43170.014840172655, 65189.459512940506, 70320.62626292193, 17557.956119550887, 74671.3168981646, 15328.488798419827, 20176.807959092213, 94985.84250344096, 83781.26722523864, 11088.183843304656, 10895.485249652236, 93546.72938010358, 48311.30377231152, 83298.63529192006, 95857.77835267776, 95257.88249267032, 52846.60027817117, 79991.07670485445, 62512.64547505113, 91640.60601750002, 36748.843006057454, 59008.83897935569]} +{"id": 830, "vector": [5633.015566135302, 90045.64409580325, 82636.71864190065, 53731.83796708397, 45239.995021776536, 57015.573446963994, 27913.332003019208, 4104.5462533255695, 65445.96039670085, 32622.483471348696, 39725.62541259037, 95412.77860747432, 58567.61737686109, 13147.5710805815, 5855.994346747207, 5519.8847982324105, 22332.198623999942, 85895.30293107363, 62028.15032448096, 62408.55189132457, 33318.536929637274, 18770.0813725842, 6437.189908265273, 21678.475758641947, 22039.450886429146, 71266.40108998437, 47141.785269492306, 7467.483298473643, 95091.54184534142, 85350.98485430056, 11998.372179286454, 6468.729820667696, 54444.67535596646, 82027.12148007222, 7330.395202369, 11858.551290665653, 37611.04948536061, 15773.518675334908, 76330.30659086375, 65701.62706636303, 76373.76745828018, 95368.92628783469, 64290.253078405156, 77772.11202404276, 85003.25188225927, 5006.064818831868, 79076.59853706651, 55529.82331458255, 12366.290007270187, 87536.11135276052, 11557.52414508504, 50865.05469881741, 23804.086143239, 65371.823928385675, 43386.61209400765, 42853.88750529991, 77347.8886273459, 93860.71667384902, 68245.6618789413, 13231.428924133827, 92358.50281820928, 35743.87101422826, 14411.496308978167, 17159.64886052267, 11200.619730389095, 68945.66583817669, 79435.7862216832, 55724.554613131746, 23027.293398265858, 97203.74340512026, 87676.36895232444, 68575.27702841138, 45358.36488451982, 6393.6560716150725, 72067.26598109353, 926.4594206612409, 11499.127251004214, 78454.98652946118, 48182.18165855064, 50642.690138613725, 4918.622291911279, 21299.088500414608, 26024.44541052418, 86659.99863816075, 55434.440767795066, 93211.940822103, 75715.22535502937, 65259.726967809984, 48355.14726796578, 79118.94408881164, 92341.02933440017, 63603.50912682539, 68314.04695692597, 87687.63689115716, 87554.23517020885, 60420.907108499465, 6849.525921851796, 59339.933741944464, 44716.04275308843, 98826.6998506507, 63117.03575400257, 80971.19737816454, 55817.90388072437, 79309.90976773766, 50342.871288030874, 49783.05761800221, 67785.90658593652, 51725.00502013426, 21074.792567525157, 50211.33690193508, 85785.61553718182, 19170.809481893524, 34322.77189336152, 44854.255794979246, 38039.12325244987, 64426.961175979755, 66329.66312646863, 48783.84883453536, 992.2150643561167, 89858.36175048913, 75077.32659692453, 86730.64809486805, 39891.05728495808, 3046.8811400986783, 10139.465956110516, 27543.89361145225, 83753.33592483569, 21725.20972117249]} +{"id": 175, "vector": [3982.3839904068127, 98502.79891211903, 14067.544458509341, 37934.084878034955, 73122.31252226653, 5767.215278164505, 27145.493185439485, 52653.57863536186, 50844.4384816389, 74970.47289510927, 26500.31385726509, 7547.1780000579965, 43179.254319264146, 25842.14860768914, 21017.531437644044, 28579.802661860453, 91892.97484284647, 69965.06885447976, 74176.12301136921, 2031.9158619054422, 25203.576031507426, 31213.740776860723, 45534.382768720236, 74843.0405622935, 93538.9670541524, 67868.92709748299, 65600.26540418962, 25040.363733076378, 36973.43561087824, 66376.35014406999, 23524.212565480662, 48466.851518929696, 93950.80075387943, 74190.99128162292, 79259.12032724539, 361.3673133072259, 95871.90452616017, 4874.6008917850195, 89823.5344774021, 44946.07919394876, 34117.21565869109, 55146.02443655272, 2647.764857368573, 52156.82406965779, 67455.33983503749, 6491.051405365189, 6012.88988417813, 65768.01427881073, 41311.786906824746, 64969.15831221982, 18254.725736419354, 12271.98259792337, 81115.21236836942, 84424.73979964509, 16568.72257213311, 65832.94009674115, 28820.668570725506, 84781.97984074295, 58273.932322329994, 17671.809524940818, 12258.806949651358, 66215.30519481465, 14023.080385185893, 5224.826367859104, 58705.105867426784, 10307.574325347558, 48972.80903410505, 23737.67740150051, 79212.18955005864, 54925.41539425812, 1700.4690266526402, 22473.92334482402, 50386.67387936111, 77976.45874312009, 2408.862790113475, 80091.74786174533, 68377.20729337158, 80429.67883678425, 94329.81143117997, 54314.102408532126, 15639.282593310034, 50187.87550747591, 630.9914212023871, 8069.9904504699, 66037.06006692682, 53257.50565492686, 92322.55481626285, 98481.09672429688, 14808.809500993946, 42533.10311948784, 72491.12608178363, 51271.12908650597, 3570.1232643977733, 13820.799915373816, 2938.1159417858794, 19002.389123895235, 33294.96436450795, 94808.16846604018, 25974.282348460485, 37411.19325711426, 74024.51593590394, 59788.501740398715, 72196.35078908068, 92706.07336707374, 41606.56820915663, 89995.34972215164, 94893.89810020085, 96889.46629579249, 73777.74663576722, 70197.68686105407, 14922.518778598604, 33506.85971954083, 21248.51153360412, 10466.892246538684, 64615.1018965584, 11236.532880831219, 18403.66271882512, 93307.92855801847, 14448.153401737207, 89347.54503467839, 94858.62410136305, 1829.5169516089893, 29766.769584577945, 2662.9985309702843, 67268.49929222318, 55458.20928809495, 16260.079568735919, 80527.08243367224]} +{"id": 2015, "vector": [69219.5234616024, 71781.40273143952, 85121.59587082248, 24065.7358840106, 32856.31777607814, 44532.624412477526, 94481.5496698559, 78745.94210164827, 78079.644771808, 61246.5795728235, 60084.53379437737, 76272.79685977542, 25880.522966713437, 16636.808315437367, 12951.548273540991, 74833.13427472267, 9350.147209046678, 23105.85355876211, 47435.78446879215, 56229.70622294683, 78082.11450806119, 65986.3860991575, 75182.1446503879, 98934.33384517583, 4982.141710022281, 9309.67122459233, 86220.15249253671, 19382.72401182415, 70309.76321733666, 49450.72983191383, 28208.657730905506, 14320.799837548348, 79065.19262221767, 88451.18776643238, 25517.89929038698, 3696.822287745627, 83665.8238891648, 57583.23511535822, 42543.31008521145, 23151.989478767144, 61805.00697643968, 19019.346190895216, 42791.2193429007, 71516.02885131715, 68166.14106914707, 91822.6875850344, 15794.93796473781, 27087.442898323377, 94266.351932344, 14905.516424588815, 52496.393343624724, 8295.066633280147, 77312.0486267619, 96463.23287300002, 62206.273353234355, 45491.730207837725, 38009.35868035774, 60342.84292789896, 31748.10290465382, 62425.259414262146, 58455.469758158484, 818.0960657486191, 93524.96091774463, 14905.48579592108, 727.4036857530585, 25758.425302828815, 3292.8026836759104, 80385.93818992992, 56428.07886103206, 82916.99160839179, 99719.06030667004, 79872.81357620511, 48990.070513407925, 16478.351530987802, 95976.34358978561, 67996.9631096632, 73201.49480383348, 99547.02913585678, 16696.411973923696, 91843.11659352727, 6564.60658564828, 58370.21627671831, 57376.77439102791, 41674.865525209716, 18758.339517720335, 73134.35503316122, 2803.795133294229, 28532.555363450694, 89803.3269337464, 38560.087718490635, 37985.59406329692, 49056.7160005251, 84027.64501274454, 33941.48443955055, 40047.452497170285, 1642.2802683925709, 46737.76290231627, 82578.98822562906, 6844.565728161156, 24277.43311078182, 12564.117417103304, 27957.614507972605, 25153.312008944096, 32859.25566533141, 15470.049245368678, 73935.2648304479, 40837.769211779254, 58830.43647112937, 2816.8558742242444, 8149.207213284227, 59235.41099625327, 1760.1596538361796, 65622.0096715267, 9884.68660016546, 64572.54792733909, 48174.57152810737, 92504.65931660392, 88389.88523023366, 14772.096305185767, 96787.29770265454, 74835.8136191805, 65911.814973987, 26410.79558733269, 36429.717072879386, 64301.9137674542, 73959.93074229953, 83602.87668958968, 69944.41162539362]} +{"id": 573, "vector": [3829.930915957802, 5077.446766488203, 16239.268858522182, 78491.8355946346, 2179.7652880151563, 7950.847716708143, 33735.215797308214, 98387.38012853405, 68232.45876984499, 45548.754153392656, 46300.72523550018, 81320.95378925694, 115.07064077014294, 16426.727231275283, 40744.195536849125, 71366.17094396811, 2227.0180251573456, 28576.327851798, 60131.670134865075, 20394.82832556775, 28041.942404977483, 6233.806004680198, 4880.952197126553, 78005.26708592725, 69560.4607750407, 52740.16280374482, 44924.3386723581, 51828.99984442859, 77465.25739760751, 35321.645728227006, 42629.39226654925, 64478.48487698876, 29433.401570590133, 79390.90744102182, 12114.985265798729, 48588.66070849268, 32579.311041172397, 59616.218413008806, 37228.90563153728, 41808.0743247789, 75897.66674657304, 81279.8880893508, 61422.878253191615, 27531.125110914712, 23519.891514178016, 2213.0477608189426, 1905.1908603950674, 76747.9755748405, 88874.56387426432, 55935.14338252621, 81916.61798096371, 60784.5208602096, 96515.61563911843, 4972.618318614696, 7319.3425052346165, 17350.040422916092, 10906.334864772682, 92669.38107233465, 32654.32800104937, 93564.78179918605, 44212.063553892855, 53718.41815591447, 9701.06477829069, 36983.941694801426, 41384.13566153898, 850.09591683336, 42479.43990671581, 8185.074781149427, 44839.62684282216, 33398.65209158055, 30133.20821700142, 86964.1535946142, 14162.17329996089, 3742.336485059838, 30085.4048478306, 99811.66203854921, 88627.0390274559, 56417.17673469245, 84783.68007627036, 2663.5497200208856, 51532.85524681253, 96127.6675818168, 4055.896713740048, 65189.795241383785, 26078.240456061376, 5501.688861779897, 31571.11526265597, 49875.46675887714, 61868.95225667799, 15683.11273775187, 2575.827703866318, 12835.501110117231, 20886.968502724856, 35284.60124275868, 19769.223785735292, 66344.35183954945, 88530.20218588586, 46098.991721207385, 72734.83667744942, 17963.041830635495, 85157.40083347548, 22523.929584899826, 39913.135120858715, 66050.12305182661, 28222.424554553018, 64701.68223772149, 3235.3968107182272, 43905.995110585296, 7906.206811570571, 64837.87343185079, 51715.450083251504, 54703.65232450178, 54259.206222197274, 83389.22516064995, 12186.10338985423, 88763.11926754785, 37405.89047556841, 27549.933720225774, 21581.310536419074, 57397.00916287508, 26156.055366362907, 72209.75385808501, 11731.9477591597, 97957.612646846, 93443.69744765856, 46532.529606216674, 18026.27487433891, 41587.8044081646]} +{"id": 899, "vector": [14610.301549264626, 17092.99778367105, 30528.14577206019, 13667.312783375684, 64706.75346181808, 19673.587964218965, 52647.31383749799, 15043.200018129033, 27155.43069080062, 807.7862653310897, 68097.01225360278, 16691.04014151005, 1915.3503229869195, 47289.39068229056, 57693.579994852975, 33788.41542993305, 59164.463228831155, 94344.99993670301, 43663.646048591996, 81246.2679641503, 26337.04845150846, 95501.59839093852, 66901.8692471896, 30047.47300178826, 14575.710557010923, 55292.27403291669, 89309.19995708701, 34852.145960406226, 53818.45706759327, 30040.492908446835, 49456.743465636966, 70099.5307741254, 95644.68230423998, 81925.2584614586, 50257.74426075643, 79389.07828825343, 24204.13771014578, 64010.21961293764, 64143.31355733823, 6762.060972014061, 3801.429254563993, 81139.31628429037, 70919.73546971777, 19659.246123554043, 11787.781898397432, 23025.6217648927, 30443.33743457357, 68368.22656596507, 28175.712106370676, 11336.617852369534, 47454.146630211515, 73087.40189532797, 68197.64927771989, 87382.16997413855, 42938.91482755727, 11593.685680916122, 6904.004667578567, 26448.383336901537, 73324.71106628794, 25808.326061466556, 89692.56440877926, 32789.70614013225, 1247.7831491513602, 43804.96639334341, 95933.98435817564, 61206.83282459751, 30037.897341122978, 11755.032675658606, 84364.27596551366, 27072.203602924794, 74390.23702881501, 24096.47532900808, 395.4234075297047, 79581.76117963731, 85641.936081137, 79809.75435687277, 33266.6425717138, 70057.66218202608, 20598.54272210595, 29076.511718811314, 74582.53599176185, 54984.444591908396, 37556.58325727986, 1924.5276599267668, 7243.111260043367, 44167.85368853289, 54569.64359719946, 81779.54441267227, 82705.85005197974, 20706.929487655467, 93413.48593316354, 95015.58269337239, 23793.223144327756, 88235.20240868989, 48775.81969778062, 36709.24772546127, 14771.572710809156, 49405.96821929225, 5560.397458110078, 19432.671153886004, 49095.096092135434, 71864.14574932793, 38302.89972865231, 57435.11287671739, 20133.94089709738, 15929.917607517818, 32180.263720814695, 88048.69563530126, 87569.8268551001, 49061.73259380289, 68140.87711126365, 57641.58764492696, 97690.24205725716, 1489.9665888330803, 9781.44139579733, 53741.895632083426, 87415.38428642512, 14238.794423613199, 96137.43852549352, 7116.682877866842, 91492.10076097843, 10510.609579461838, 56092.42164669203, 55146.80024336541, 64696.621901807375, 31505.508169716413, 2981.4169188015603, 71671.44838864534]} +{"id": 572, "vector": [41615.1474467926, 32614.78749962845, 49613.185276371194, 39573.72589399062, 82511.80245843391, 45647.09277279593, 43451.089051153016, 99372.45641234613, 23843.254441986537, 68294.98210136191, 21855.10122870472, 82009.90725791047, 10257.88771478423, 63740.20667012163, 52284.843416382944, 99161.1448823989, 87649.53539186595, 45626.133017653956, 9366.293849717787, 15790.604820122357, 11393.097524013485, 44369.60094804814, 58183.17312128779, 38454.205947481765, 84527.61621511573, 4630.691231364825, 97475.2402553174, 52176.3084354961, 82882.60115281302, 54901.54988591049, 93987.72581794333, 31651.0680688981, 89908.76370855595, 58260.53265851962, 60210.21379331109, 1940.2915559989299, 1213.4803076310652, 74808.13547277886, 46159.10688276201, 21010.813355485425, 74527.34516874014, 78248.10436062972, 51325.02542592845, 73448.1631272319, 97198.8426207936, 52160.27601795501, 2305.2263712585286, 12518.945308896356, 36151.70513434759, 84380.1060417132, 5567.127828265217, 53877.714790377686, 56956.85613004677, 68686.85615844218, 50769.11786323756, 76734.18970891806, 4284.347552966184, 15399.043801746737, 47278.08183596751, 41162.132615122246, 65742.35882499984, 2654.204271963767, 28227.383399249018, 42877.44840774446, 31207.886412921758, 58618.62668557217, 25413.537148971587, 26924.732787243065, 56010.0959082631, 58790.56659210307, 47776.62401590715, 40910.63865652975, 54999.53082589593, 65772.8244824983, 3528.0232302718373, 4850.56956130937, 77728.73649568985, 18501.155336168627, 93771.37538359169, 71808.95718281412, 53432.68021007359, 31912.41075505883, 81512.12990431082, 39330.255055121444, 85417.05209440495, 57432.32387127148, 74860.96271451113, 58681.53179350164, 6480.741263925238, 10198.355154975536, 3900.6304391911217, 89477.10587305305, 88450.29839439149, 55288.4897661301, 99843.21344620998, 57979.065352725935, 848.6384326501728, 2460.7811479558927, 49773.80306867667, 69232.99834204846, 83114.70761312544, 69622.20506941264, 84516.52989250152, 28414.75267775373, 67197.90777878238, 9321.635654245309, 16885.795063594865, 88776.25535949974, 32079.149150311147, 52296.02208469789, 28423.450168613683, 8853.28328804934, 30454.526067832954, 4184.57450857983, 74059.97672711655, 67770.86539942346, 73856.03496436683, 88212.30237458802, 76144.20076697133, 39508.731105165454, 43691.15837561994, 51979.91137562731, 6115.699878204384, 28938.189265711746, 68457.76243928431, 71657.06603413537, 64552.49742006608, 74557.66423058276]} +{"id": 1219, "vector": [38598.047152103754, 1297.7436763284445, 59919.44001251858, 29398.089330689658, 19125.64817356428, 84875.37931977202, 9740.58639373151, 59265.0645247056, 27070.223277749115, 88340.48337307374, 411.6345052786907, 57475.37147567724, 60518.873438796225, 49669.63662389029, 51955.52970576595, 95805.94286082948, 27232.624076365253, 82208.97124795246, 84177.00656805317, 16637.20321214002, 17104.65395623303, 60415.33370837642, 46521.118827915634, 25247.03390170945, 54439.23421039606, 13902.475807548031, 66073.51395930427, 55476.12887513016, 97463.08050899424, 35577.80141963779, 56800.65894787014, 37293.806062606294, 44642.005618069634, 56609.811757858595, 97507.18825169769, 14140.782437223643, 3512.337707376967, 1195.4720855936962, 96370.24065981348, 19089.49513064524, 88088.70281477674, 36947.7318182539, 23680.15261356723, 16600.115825033656, 70251.87125325683, 11150.190743650224, 28792.78159562704, 4224.896894555941, 57015.60655864634, 72452.40211173423, 44832.05037679123, 28726.16838817407, 74386.85345022696, 33271.54237677972, 77615.40582317578, 99061.4542136707, 67726.54342923095, 2601.3494394800273, 85369.10128987402, 82575.54550483549, 13926.630127905604, 7278.773927804849, 65315.49820211942, 57620.025549596685, 80493.32293354784, 53415.16559333067, 81243.89920658404, 93649.00820821479, 32836.19976528186, 12918.08007139229, 95216.99321608417, 12158.03777813631, 65952.90602230738, 98507.90326994624, 4816.142751352959, 20422.109500384166, 85474.54488026052, 32964.745179946985, 87645.81370275939, 52657.8008718464, 80588.90208463212, 99116.73907721577, 65582.37751553001, 28336.809136507734, 94372.91789157018, 62247.119524439084, 15029.662483138873, 25247.910403251582, 32284.32151658911, 41440.785261349025, 23664.852694906393, 40134.141111094046, 23136.704699068912, 27925.08191608316, 67093.81294528813, 94014.75121333869, 9653.528455394855, 11088.8896097746, 28010.257174328868, 98384.86369297084, 36657.700322060904, 93741.42362938462, 86142.06056171116, 7385.233734048524, 74913.96767632003, 66783.96899652235, 92203.40185806096, 55796.084005766024, 52315.94299793232, 24354.070638625646, 55241.1646613249, 66382.32571873488, 15885.35269451422, 66573.59111463868, 64590.253182978144, 36553.88653671118, 28111.9707453787, 69787.56186644334, 68668.3761870733, 89205.83655229646, 73441.33696145655, 54696.24922968731, 91848.97715816624, 27807.924855735022, 93069.28799209405, 4476.683888230604, 27646.664090951977, 89753.06516084287]} +{"id": 1415, "vector": [65698.86874697653, 34286.303818689135, 81559.40497665684, 16333.104237224361, 27234.034256143368, 69916.15719335529, 37288.14395291927, 49628.93986387227, 85304.0457904325, 29867.264160811756, 70341.84859855854, 92230.55099842504, 74486.73664543846, 16001.190346072957, 8653.70185303207, 45775.32499621767, 80530.35252059306, 16483.822173877496, 35279.21536697255, 17597.053621029514, 56774.039281215126, 16159.426901887042, 65140.90950948472, 89573.72902247928, 90695.85157456594, 1563.3485587153496, 70909.35796953557, 73246.66361353415, 56443.62190063156, 71504.72709659339, 79306.67710883163, 33115.84291007036, 73346.51223328784, 41271.22610452052, 56818.07128280791, 61429.016282506855, 67.83363466037473, 97995.89750725655, 23353.775310608326, 68197.24818543342, 23518.523549057045, 85765.92824326926, 40293.69944552991, 41114.1334757287, 86963.54600582109, 49642.61363880274, 14123.586923865838, 67387.7306567843, 46845.5729271177, 72097.21850721547, 98741.71358615902, 13160.54966886594, 42141.80036858752, 31217.739423675095, 53365.00077640118, 10636.531995278441, 75915.249652673, 74536.85802732853, 92898.18788478516, 69646.21561289423, 48791.22470662731, 94656.23232939214, 44474.50304776825, 22945.296750240308, 18985.00283949134, 74654.35738923788, 70493.30336405318, 64248.72199105353, 58489.02787635817, 6637.299820368236, 47071.732167834954, 54054.45204319625, 95809.1371456003, 65829.61768768997, 56968.688659736865, 22922.086302537726, 19961.02569564866, 38354.94853290266, 13336.744168568115, 78400.13673573543, 55608.53950965688, 93072.28496261867, 4449.629346594675, 31326.98143842938, 13236.058849538635, 44256.99710197379, 14356.679385238845, 2775.3859651885373, 9986.107743370409, 99520.90515394465, 72895.915075982, 66053.77558367058, 87267.16359814267, 12786.548976742595, 88483.65313896611, 52257.88930862395, 16022.177567211482, 58966.475770788886, 95828.56064070624, 33914.41911676674, 11606.152220139254, 9340.598838920656, 63250.86396594982, 81701.69158982422, 96746.46949487567, 97222.53464740774, 45208.20148180728, 83738.58323664806, 2959.272497547716, 8994.271744812333, 15791.064728875004, 34111.00203203146, 85992.38515772775, 27390.402798016068, 55496.82510703323, 68923.92817724074, 58200.216579832406, 50570.83113374472, 19619.154895943204, 93204.77759331778, 88738.16939744883, 88247.73483919857, 85822.25436891524, 603.3318846458191, 86266.35648422242, 51370.20595016463, 20523.929714440703, 73885.19946126566]} +{"id": 743, "vector": [97067.40936104725, 98089.33041475361, 43836.09174714274, 15440.183526764495, 11804.793084769339, 46634.40946402196, 31919.02088176868, 11087.530502455267, 49260.78209624824, 35151.193404754886, 69265.96720261668, 82053.24825498513, 60244.66058893339, 55191.83963530713, 59017.345447065185, 54886.66571510231, 93030.45240036532, 82772.38471480178, 98011.99593596799, 32897.20021468463, 69055.48292600182, 55404.37471914669, 30123.35773206364, 39589.81249758674, 5398.162760062397, 87544.40138746287, 31795.692489905035, 58565.405424559256, 14807.526882202626, 577.4977500657918, 29194.16630158913, 69421.37735976516, 51291.578387407, 9112.461009711304, 23135.816914403273, 52670.00114027838, 8568.910467933943, 71906.59133511569, 94131.78058552612, 77995.75111252876, 86178.95260282131, 70166.99786105975, 89387.00537395307, 31867.906328769324, 31540.182538389505, 21796.767113854177, 63979.38529422372, 55293.49803922483, 26436.682319067993, 55408.520494695076, 68742.78448664394, 5223.231166985298, 94547.11845572555, 56402.01230672967, 12841.237585693332, 85185.17706068406, 2603.3242887857577, 11968.195999012354, 65568.8409079415, 91179.72626173164, 58461.34261907178, 43188.97272779358, 48160.3644645873, 14182.107479517892, 26959.568841417757, 97908.82526581791, 74777.21064260488, 87250.53221573876, 93941.37669764161, 67732.94520470139, 24620.39616175313, 57101.47406354221, 38552.91693324435, 7118.806543528511, 9215.727163586851, 91885.90143335299, 84747.84153673169, 62441.74636578038, 4222.37016123922, 58187.67345754681, 32888.78423401114, 18962.665184387217, 90985.96665719441, 3718.931695686445, 78935.52306704958, 91238.9233684516, 15210.43966076261, 57350.79334456219, 27668.644953974275, 47022.83074546387, 41249.654576714966, 87933.74449362193, 6907.008741877318, 85365.5782512309, 46933.08956934644, 13363.267623770136, 57882.32001897914, 17275.020731049593, 22031.483972619735, 55786.440323751616, 34136.6236731149, 84725.48989812571, 29930.009444986383, 92001.90414016976, 15203.03898392793, 83506.96874338776, 74931.10617946611, 33660.71247863015, 67992.49873521748, 59794.343109261914, 52426.91878581741, 21198.293777450104, 59441.30971515168, 85281.86062821349, 72130.90998222929, 25591.17173894685, 76685.32058483451, 66056.16997485909, 11697.194732363014, 60763.180551020356, 49103.38861597996, 70104.52203641956, 2471.2324146227706, 23105.766825532402, 85211.20180651838, 15597.920051612602, 11585.461222055272, 22663.66608347111]} +{"id": 380, "vector": [16963.740535522244, 6950.933031326034, 94537.520604267, 4888.476983650958, 93919.16026292047, 60294.6414018958, 9524.231728886423, 17844.2279195413, 34504.90321651915, 61404.630070462954, 42458.64271680344, 56641.30034471827, 37972.568611445044, 80871.10604146803, 87662.76591242464, 19297.67753284307, 33513.905364585975, 26668.75157374127, 39737.88940164934, 92645.83517094512, 14285.215377439154, 4957.599742871044, 50630.85801915398, 9521.56378801955, 82567.11946052489, 83.20585636442112, 2075.939948657257, 26842.096003151415, 49140.17262876613, 33861.90970929642, 61918.48054166373, 93361.26696433757, 46991.239656256745, 34569.37484978081, 63067.13143276377, 90190.43209928862, 48474.85189133477, 65148.88972059085, 76791.03666822928, 53859.31969945302, 49913.56538440021, 59660.72121991969, 21676.124873144476, 58144.14951829345, 3339.358565460382, 73395.17034978683, 37037.5190154701, 68642.59392059414, 76952.14284637329, 51976.17897478476, 45369.08054571692, 74789.32900291817, 77425.88828852538, 39265.48425640357, 42757.96905796143, 6556.683510331763, 95624.92823274876, 19099.857993775317, 78007.26916352041, 53319.44899869479, 17179.985346111214, 68270.90538610864, 263.6225961501815, 99696.72950297002, 68620.10323805914, 70360.02899640637, 9332.804565044462, 60260.571300055286, 74019.83868342599, 70587.72414208196, 97753.99841611447, 36360.76129057075, 95877.25703315878, 2880.4244005369405, 11974.53694128302, 41516.10027088983, 8743.040350987707, 11800.860592771245, 22189.25179142254, 77501.974418735, 94184.30419100364, 84160.21306380728, 9840.330589215951, 17466.218378794805, 95770.76908565513, 72661.01905669246, 99921.42961488775, 72243.82376796425, 70761.8191941448, 92389.05349599863, 91179.33753159779, 51950.46721631847, 66427.3230098066, 5037.264798789476, 57930.27054386314, 99579.42286755468, 97648.19325966065, 91495.42064804409, 31328.98924861608, 61614.43612288157, 91211.96622725301, 99457.10051738589, 29830.970626991893, 87179.70408075405, 99021.144581478, 272.3708697385252, 80102.12439157187, 10752.781011902745, 7444.677254332044, 50711.88663904427, 26659.732014550762, 47271.237676043565, 17160.330992564588, 2252.037103832283, 98852.58028302614, 16858.141825730898, 31022.75874628343, 91676.54109742484, 15750.044054204782, 14250.382501175618, 26067.834885172582, 16967.39353300436, 86924.29257553957, 882.7332233696583, 31458.74375937183, 31855.008746857893, 79207.12974388264, 59638.39575093949]} +{"id": 208, "vector": [57239.41496654201, 48808.22046102673, 60593.9299014856, 32038.78357369686, 86746.32034796293, 67610.58515245581, 46490.03500519513, 13541.006514699804, 21105.4987275733, 23870.136143242158, 76328.82904200755, 75843.22070744647, 32422.367811303044, 90334.39787891421, 8284.054155915976, 20657.14662713707, 72009.25022707722, 70921.5031229882, 87969.2797695276, 41201.45560184116, 16561.00992381775, 91807.16120878841, 99323.04825394467, 74708.92192804681, 21291.75822840339, 74681.60265434509, 1446.740863075502, 35863.5945381186, 57890.98077743054, 7411.140059233545, 27073.108046884943, 47731.06015133647, 18814.26651488378, 36687.35466305876, 9341.331768042672, 31748.1667777049, 51488.762453157186, 53523.21096066685, 29973.285316941136, 91914.09327262796, 67559.51691027993, 26475.378434309827, 76219.18871224712, 65753.99279543826, 5733.451378569676, 90377.74659754573, 86691.82625563882, 65014.032674168746, 38443.72277047246, 51947.20576760003, 99382.15545704737, 25.2096778212918, 92765.47142233879, 34236.684241528426, 87750.76000094914, 39620.27113672969, 9012.00229839727, 89298.16229971961, 29108.265916768938, 4500.899323177987, 34509.38825604799, 4258.771642794523, 49007.91994261574, 35683.01205342711, 50955.09715635369, 71142.29495444606, 88531.37144247872, 50014.10938366597, 74784.17558647145, 73228.87199067662, 79487.40842158294, 22013.199458245304, 98048.08728137868, 74895.91817355953, 9665.042856017104, 94614.63769954664, 21550.734459884403, 7453.445758490707, 87562.58149701731, 89142.40472013009, 41430.751858256095, 34802.39641345802, 81283.25109644841, 75137.28120157546, 20679.273161130663, 9968.141544446375, 16891.13095339867, 70417.11885600623, 52854.561468971726, 61399.34441438506, 73478.37424723025, 61658.22028773494, 61645.22412137115, 63171.81324981126, 96863.44605379368, 4253.017964736683, 80850.4146637684, 86912.81597881751, 68110.25266346236, 2264.5881652377684, 93671.42489347851, 71206.56103631537, 93962.58289951614, 96176.5863128069, 896.7015446426085, 88503.35865640269, 42633.50779995275, 5872.317085269163, 93711.07610818458, 64834.332926752395, 88464.62918187778, 38089.86308980734, 47220.99620593533, 97302.25361213811, 16873.14038458029, 53224.590454877696, 65813.6504698759, 12693.620303740161, 19038.446774400665, 11777.676960098293, 31389.89459959447, 88946.61446966176, 89674.58661323437, 94182.62407619772, 97491.33649569999, 69223.78212966499, 86570.4370490243, 6435.404694061875]} +{"id": 1335, "vector": [78788.05624169607, 84787.6535939966, 3077.429710923585, 35918.53010033866, 71968.72999403808, 91639.30613113786, 90293.62427433555, 85478.0026805648, 8388.167510768373, 39415.05540165813, 87119.75522446183, 28793.34283365884, 71250.60211547886, 36742.4214127049, 49662.050209516165, 59693.753210678144, 15923.42558093136, 57996.81501193824, 17385.37323883941, 80176.09759182534, 74844.39277669367, 19872.459200644298, 19383.28935667846, 27748.17383163185, 56748.93688668035, 36779.21263545057, 7561.372160672586, 20443.197859080632, 58064.926686600695, 97170.14889982359, 84349.84062525009, 29570.96129904976, 79976.81954293179, 18479.29044140407, 59464.692298096576, 37190.9477565573, 80052.6379772934, 4957.303038547456, 21490.86082399594, 93940.31102712962, 17790.72719589424, 38751.921337326276, 27152.805912490297, 34661.967279890574, 39062.47145050728, 91138.86956168903, 64041.38970823471, 94982.13047269169, 69834.12983369916, 8630.965378317756, 40553.875070436094, 10217.571798758972, 21299.636542669276, 97039.19845246561, 37345.13325945366, 71274.38492076252, 44945.53349472477, 56385.84000520208, 33637.50417868604, 70758.23256653464, 88505.11442942916, 51956.850938658026, 28071.37956158604, 13748.71271862873, 76566.06783981796, 5044.202464291802, 41421.890554982754, 46132.807155295006, 40482.39933563905, 28345.455868335666, 65534.80617826053, 93026.8369425719, 25621.999301323238, 74614.40048523727, 30755.109797057557, 60661.877699736375, 48635.35845226985, 12047.770096436383, 49639.70041880236, 99169.90143932788, 19914.171849754137, 64546.17495660059, 43245.80310632282, 38014.36073616168, 19718.802618196794, 78256.27354498263, 63898.29623042982, 56839.05475849818, 80684.43909770511, 61975.202197645085, 20104.22685823553, 44769.52011855306, 65805.23228408872, 67728.89423043613, 32645.909141190754, 28122.27145132079, 61741.91270273665, 57087.00656485424, 96031.17679657372, 50657.56131799115, 72904.56794525543, 61100.75009245789, 61395.1301606369, 82203.99755837803, 19843.46435445027, 5520.220916370277, 29278.172314068994, 97483.34399472123, 12520.414572064032, 68304.41507012924, 62840.99226695397, 45846.97739751613, 17457.585955036415, 20581.80984938163, 85358.37681888852, 90042.00524857687, 86449.1548686904, 482.89766916465027, 24919.401949033516, 82109.4375548335, 72373.30478026149, 63939.08188424393, 37191.41672261173, 30283.937795656002, 39369.322882172666, 74827.6048637143, 38984.81341629905, 31964.16599289592]} +{"id": 2019, "vector": [46965.88004055488, 22683.00941564577, 15330.691279749542, 43454.89223074111, 85582.86249819909, 10800.665635589945, 30276.747780223035, 94028.46673589986, 21609.468891122364, 39726.01962972019, 64807.81045630611, 30263.239320285607, 64732.829310069064, 54103.19861993591, 12249.795913035821, 38641.275394064076, 41734.16994071478, 50036.00441572026, 56550.742688623046, 42036.43681591207, 5415.416715223487, 68686.09815108615, 65902.05175383209, 72719.73109326289, 23864.261287309662, 60954.22370673611, 41902.47663586035, 93033.83020177955, 86216.45272038945, 25445.09972228448, 20671.69592901088, 86637.26590242285, 88986.24128215606, 88714.84674444719, 42833.45979212669, 10239.038349761264, 71232.87306534819, 26334.018332326035, 39512.56718472807, 90855.74742902053, 58535.47028955757, 3781.1716639311044, 31216.83900124124, 44897.2107699438, 23927.858455181173, 75136.72887777674, 74496.38881577119, 62927.791276843745, 83265.08394196192, 56911.6500505473, 24495.2757241379, 37611.44301368693, 69984.91176984896, 43114.09488561321, 22090.941653684804, 94847.34955080965, 94550.40463299333, 77542.9169470135, 86781.85985459892, 41859.845570417296, 90389.09806298354, 47531.072254040184, 34843.423349399025, 31344.730055581404, 75601.0616066957, 84228.07784364122, 91078.14953044611, 76937.03909513405, 34360.89555013904, 79060.12248298925, 21016.144788988, 42636.743513460264, 49095.26681124567, 94378.6861389442, 57670.216082539984, 28608.25262373704, 23024.77794939798, 1714.644112412045, 85263.73507694014, 17018.941902112172, 28278.319905499717, 70182.74882974372, 22255.880833660536, 57406.193630324364, 26484.65637674483, 75726.86274673071, 20142.988614815327, 9358.205110662188, 60729.93188936224, 24892.796464480994, 97962.23022710496, 3071.802954145708, 85696.44779858887, 26134.86709433883, 34865.33496838949, 30901.630321081884, 26836.72166602612, 97794.86719995829, 15147.359041709973, 73527.60490282299, 93829.71278055164, 57592.81941796857, 71430.34781319057, 16357.805509572798, 52044.13740698921, 77745.52587066425, 81129.22985677821, 6475.689282027397, 79760.84053150294, 77904.07679412821, 94182.45574864178, 66605.16935291722, 4766.425960306619, 85143.14743044531, 49920.196014782035, 32254.81116285125, 12481.592952037501, 2274.1810461972545, 74962.61489485523, 65872.10227013218, 7502.564812554912, 12251.668545198669, 46831.56797445934, 1730.2062613602786, 53972.697441328324, 37690.125656332595, 88747.22866981631, 68889.2218896723]} +{"id": 702, "vector": [19368.58398555611, 90066.18669889511, 53077.510054858656, 47900.61636051962, 16665.200995278905, 49442.464964034705, 95146.86659882941, 98301.30294279041, 1400.3826305144628, 39122.29267396479, 45925.58648554894, 77156.05730481372, 96467.36212298331, 34662.91523224744, 59064.61114107879, 6422.264293726421, 74891.56437377004, 99748.1147085257, 50785.58020785019, 72350.34831154569, 66886.05254535092, 35333.21043922798, 73263.35324393245, 65672.71664839078, 33008.68829364586, 29020.62822955783, 58041.8119721077, 61222.791527834, 99457.77770957882, 84743.40788529544, 56309.51571692674, 66812.28576558761, 21349.153489270044, 52200.68985002679, 90889.32488255715, 6425.368590919078, 75579.22625208713, 58891.301993705456, 4074.8024454978827, 10189.555641897252, 72441.5606611576, 8831.82266852378, 43650.87812624406, 64004.58687265505, 37428.253845213076, 93657.03158823967, 76074.2796312462, 91163.42451511804, 13520.130899057847, 56482.41211188789, 71247.6211408854, 26079.51055124508, 36746.28411748746, 58640.981705280734, 83430.36052186404, 32931.45942485073, 87990.64315618115, 11480.954022629752, 52139.75086499692, 50866.81287042171, 97188.77215272235, 50476.30994487863, 33397.726650938544, 11794.71358805545, 76879.1397657348, 88045.24909428561, 85558.77001119358, 42680.37624653093, 54362.61473935633, 72984.27498538618, 8808.308505816309, 23690.230213734696, 10038.643466030539, 80310.7136218609, 5254.538561764411, 8470.531733411579, 53929.19401983406, 20108.301741531497, 30658.26241394176, 84498.19204601766, 42113.92303681645, 91791.221067994, 34118.29681082176, 41057.808594894304, 18833.61323258681, 64250.714062593215, 14591.786364700522, 48722.68902363063, 70690.96574940329, 63005.78372887152, 68168.66732481007, 35880.69659802502, 46391.38222980164, 37249.58169579452, 14892.705185214583, 16489.520145769588, 16130.764656065121, 10309.794884941937, 45785.5667290457, 18239.656097226198, 9427.84715388436, 48594.16312884956, 39573.25486104185, 22915.950096769033, 7980.663339436256, 66817.01964883335, 86704.29870881469, 65216.089598055216, 76728.2615592044, 80987.18779805173, 14248.517711138586, 14364.172034107014, 40455.08345554274, 94476.13511414015, 58415.428801891656, 6007.172256628545, 44033.195368502944, 36551.41613035994, 30233.33159138428, 95579.13168139308, 55793.13193104568, 27282.719162588608, 69376.53272764817, 83394.16873324855, 22534.095654643916, 46105.27427991722, 29335.341421600282, 43765.395730915]} +{"id": 2016, "vector": [97009.51722497288, 61205.88222246527, 93745.56173728047, 66368.41032416429, 42288.7737448958, 25471.666451783658, 99930.08030016192, 23985.01761055144, 92860.13590673535, 79763.1037797182, 10187.28003732714, 87389.95113709461, 89872.14035686228, 2321.2213010328055, 581.8528676702517, 41393.76223575993, 81424.72798460978, 49616.921639065, 14570.593816798095, 68784.49255131478, 4336.568708172162, 51238.48582894718, 10921.92058062662, 51947.084293595566, 37501.61267913208, 96647.30555045904, 12523.651314296447, 5988.662694388758, 59867.58197392471, 3339.54029933039, 64081.28701967423, 65916.33744343059, 69272.66865140176, 1073.4418731573326, 54712.77963941208, 21782.581222381024, 88256.7597289087, 29245.641145077538, 85791.0360839624, 89764.96457455667, 74844.3664202293, 35669.754738044234, 30670.164508342103, 33497.80878740827, 18162.491398120717, 72742.65922663959, 95679.49172484459, 61775.24668407056, 48242.85723277822, 7505.875044567145, 3785.365249416306, 94838.2868807461, 58389.861780140694, 69868.00896309792, 99075.99841449711, 55289.630243274056, 78633.85293935616, 29402.97933688838, 739.3076035185908, 34054.87052185647, 81559.57811336973, 79082.44888258283, 44075.88541020998, 6462.533542380012, 8692.788942250085, 8087.469151788207, 9934.390139986026, 49389.91415025471, 73649.71545973672, 78190.17225995573, 1409.5925081336502, 99162.96374517292, 76476.76459800119, 85299.469399972, 38655.534259301996, 52891.716973485934, 35777.14995414878, 13249.141836529066, 21902.73074677547, 47624.62642506439, 57219.242251856616, 34207.43608231088, 45977.8213496121, 49610.917500783406, 62175.25459669434, 70298.42998755664, 92585.97306953586, 45747.636376233466, 98448.90070906718, 55324.28722717635, 82259.05591177358, 28187.206554301425, 61509.00704128323, 71797.35148301162, 59275.26530343276, 55056.020595300106, 33821.319231661895, 13681.974197718493, 12796.061400797344, 47510.33938846602, 21849.77259437685, 47659.89151107021, 40238.94000784797, 49085.34354291675, 73402.46275642522, 97631.52508205638, 91456.00313470214, 52816.09711845915, 38870.58519696794, 88617.24455271008, 15152.664783770675, 4101.881486676307, 68021.20226686979, 1454.235810890081, 98310.7485312147, 43545.62880905576, 36865.42115157211, 44036.61929531856, 83192.94189033855, 51756.819774099204, 69767.71446435682, 28666.813117878664, 46205.5946963021, 49447.42118983538, 48706.45818784733, 24860.9647589934, 8970.153572946238, 51651.009881965016]} +{"id": 1244, "vector": [17395.176490029007, 25188.574419960096, 76858.33696805722, 46991.98944391474, 26843.842484849156, 14034.325870492703, 98331.51355353204, 7832.504111082639, 48750.29382349032, 50961.78337713074, 25364.667267734585, 65396.27550952333, 53023.59086980528, 6183.087688835143, 27786.143321092237, 42599.341690581714, 1062.987182020858, 75844.45075878868, 25867.054819318037, 16957.03985553483, 8441.269006242313, 19397.999595064564, 33471.750057148034, 81871.97145293167, 29849.933097025503, 82604.59866650797, 20826.514835880826, 5309.674508217543, 74496.71248370287, 20066.282270132062, 2589.5851412912953, 50167.83118117535, 27669.915191357562, 84333.23090461567, 65257.3695780344, 7413.520329028933, 47047.3821232494, 22886.578337453368, 15190.842522522818, 85858.97265331433, 15123.904246928832, 83061.12833852686, 20586.79040275828, 21108.106909905156, 66450.77894301542, 29006.88513244275, 72748.28278869465, 93417.29186408495, 7864.1563825323055, 41423.88229708084, 45042.844651308, 83158.23785426936, 22424.56701015817, 15733.288803228317, 15406.204175035298, 99010.15447054316, 41058.79666888244, 62876.325530899034, 75436.97708925184, 67174.33516336835, 42423.53649211421, 77788.22685495397, 82063.1731478688, 92054.31298182103, 89809.57767293033, 80010.70771906257, 65187.966469126135, 91594.36728638857, 94459.86012284168, 23679.716366146775, 6570.029567605051, 12782.805176269718, 30188.854137988652, 81202.93523487635, 10464.84275514562, 7873.197292803436, 16299.153383516174, 97669.37874713885, 16818.958490809666, 61403.57014000577, 61668.44244604293, 91392.37264567339, 552.9538640996035, 34704.000757003254, 68856.44083803432, 90678.63009245998, 68531.02452409046, 11641.237775094593, 91032.19144426333, 89311.76906542858, 441.80377098074206, 34306.31841180923, 30061.97178337532, 69738.42266411611, 99450.3787992071, 98583.79578351406, 75615.89475741267, 29555.98950974606, 94879.04856508272, 7118.445612909984, 56511.39835955389, 22918.968874386846, 34933.31832151486, 77522.43810196663, 14697.43465248039, 56027.945152712666, 65995.54683133322, 82020.05663977456, 95256.92475363144, 30606.30294489717, 65443.52882603655, 79899.13894310144, 54548.74277496001, 23060.607068444693, 15651.914112106979, 84083.7294732407, 91545.3674357419, 18828.61127016836, 67064.14994722613, 47645.69647137795, 99428.91834737117, 90287.95891261515, 54062.03712248585, 34837.92339712677, 36164.9086641472, 2474.4757583177957, 39603.21610130579, 34660.05848385607]} +{"id": 941, "vector": [20484.890187572568, 1063.6634634539787, 17478.32691518115, 449.6368064242584, 15435.07432917952, 5130.004526999388, 6544.764862078278, 99689.19642826234, 30452.433729922555, 97817.26966482608, 33932.50964518044, 37666.61509389615, 72957.07340275825, 36475.008495693204, 95121.0968780101, 58803.02627997074, 7023.176497836803, 11541.765202194632, 41699.41645423644, 86393.91173087289, 80335.62575144239, 13509.810483310013, 76247.19126852496, 32423.43230038417, 11362.990892043912, 8431.43991878258, 27617.45593624525, 47002.22835677409, 24538.33298038801, 39741.64789918877, 21477.085552531404, 12120.880253789124, 20131.416003229686, 12503.519740509051, 24358.838930052385, 38858.95451826717, 93499.33532941624, 20067.801080806712, 59828.9925569763, 67269.38053196477, 52809.08782699591, 5784.105408209861, 65037.769025712165, 95749.61771184663, 75392.69176972097, 9669.336356642933, 92007.10191903333, 55067.16964058306, 69631.77426498431, 19039.47268984638, 26035.934406019976, 56660.09666010478, 16177.747627781448, 81459.97930292685, 91110.93997828061, 57548.23014037402, 18728.207984717228, 45631.29511184068, 53237.17627248751, 51191.773685584965, 86436.34894005182, 74449.12600092392, 22792.80855452317, 44281.02215339381, 28336.095387488047, 79195.5449874576, 81724.93330962796, 33838.79022125214, 11747.155241569108, 72337.32821944614, 62577.481606547, 10526.527161631238, 59075.55483145034, 99485.34526087389, 79619.46516058058, 52446.03265315246, 70134.93916241909, 36480.50827343493, 56783.81986246551, 65393.48875510662, 49431.98685939926, 40468.78056073258, 19312.430762955333, 12297.286440357224, 26687.493599943078, 62328.44795848242, 13301.209012713134, 62099.18617675042, 20647.80517727981, 1949.0097934683592, 95577.93360736068, 45118.38856980998, 74350.83583093729, 30573.81855302457, 98653.32349396838, 35953.20215527967, 2588.5050435757394, 80442.11626262436, 86326.02888180515, 53580.47358657509, 94317.16037954397, 82615.98127692123, 85804.9082937977, 66772.72907340708, 46044.18701694057, 38688.679236781856, 78943.71105926932, 28546.386363050824, 53547.81924945406, 19900.46096270195, 49050.064534259705, 64606.78530661741, 41998.07726580828, 25029.6116273918, 94591.7865291093, 91221.23259024754, 28413.18036707464, 75029.89245705964, 84050.87717586721, 33206.86807434554, 60314.89346547624, 69287.72449501981, 5186.272804008574, 31852.675696864295, 53245.1622241346, 33818.48614011326, 6493.766887357122, 64436.76276997161]} +{"id": 500, "vector": [83864.80537141228, 87438.95794805247, 40821.313076449595, 68706.08921941595, 80980.16599074355, 80274.44487490808, 25390.99295311583, 77908.2014767218, 13011.180345336015, 40922.504641247135, 12036.633349026604, 45437.58599742509, 75703.08827919578, 99595.23718766098, 21631.7607274919, 24531.965023222303, 82045.53756006365, 48059.70119540911, 38168.78293668221, 27433.475213594404, 32841.26393424993, 15168.294646000724, 43987.11592035167, 24188.801614753953, 93874.48313199026, 59914.23884834366, 49437.466383388804, 24483.983793702846, 1952.3110986073289, 43456.522498173836, 55536.67840981763, 67116.40301199257, 87432.75891628469, 75994.68455545242, 16593.944526869243, 53113.65045070929, 3824.9479982600087, 87413.25506912479, 68887.08994390588, 16041.12081413126, 9809.264713109456, 49372.712309799426, 47930.61749087916, 22879.500383901373, 90438.6186851172, 24378.33578826887, 69790.59259318044, 96254.15327537, 61586.21063937983, 2740.9476904756257, 82474.04805458608, 77553.15831390773, 92514.0899951092, 98344.43328917059, 60404.896351107825, 65082.72786068556, 89913.47467768694, 93690.24767581679, 13960.02793942812, 46164.42157223117, 56103.13339467024, 15242.881381968975, 87383.50518671935, 62431.507747793854, 13361.06567602724, 66765.1960903694, 66483.04230510857, 43083.56404858502, 93974.6854594296, 60955.50258689918, 87922.65771922609, 89914.62510889224, 7006.495769806653, 56259.04388021826, 45501.69622632825, 53801.62135920738, 43477.12837477171, 11747.60049096294, 47178.89915975045, 10944.169170610385, 97091.90037047002, 92104.95844064334, 39123.26518390645, 78209.45994551224, 49764.17674091947, 91949.82160659693, 35269.371628843626, 73527.68488718777, 56224.25387076847, 48504.93973136528, 75149.29407676146, 77328.38495977517, 82624.16966227246, 44350.342691940015, 60380.693296210164, 10913.681531519615, 5384.39518911179, 25128.482796566255, 92851.34757232705, 84818.99563598588, 83678.32440128234, 72956.77546478856, 47617.443081763355, 12703.165596680332, 46036.79158835277, 46411.17299598779, 33938.40139798574, 71640.40954062049, 88044.71489410136, 11909.596598418071, 88171.39891155144, 6184.562407898408, 79766.97535426423, 33074.72934539298, 84167.8127377822, 25736.019376865228, 78266.23206194451, 73571.578679299, 90764.9884078309, 32496.734612296164, 82621.58679928265, 39555.774515224584, 41158.571396153166, 54007.51653178251, 63018.015118601565, 22415.6609461042, 69946.19414335578, 5441.812635364329]} +{"id": 1763, "vector": [54525.84173374506, 69117.6434972707, 48858.57930976823, 81883.53634766937, 45906.270628240745, 32272.564701383944, 28562.201656962494, 21902.42506416271, 10811.649839282556, 24566.965175161517, 50653.92797010669, 73764.9182496511, 94428.69535717058, 29754.647188146933, 40091.81326013992, 40223.920159367764, 64982.38141089772, 13184.289338017863, 81396.77067073669, 72203.17004827497, 86385.91008166129, 60440.5792312254, 18438.842933661126, 97201.2159402271, 40251.0201393689, 48702.71547266956, 16588.824945077773, 15096.377789234893, 70756.54611697461, 3324.052498258845, 92826.75205284385, 24111.510061474495, 92759.99226065252, 99172.57011747107, 40676.296162073464, 5771.005654991101, 35736.906531278415, 78978.9688166137, 20814.481292947297, 49969.36211570535, 910.3350193443549, 8927.83528709038, 25544.236609047777, 73266.8028472121, 61743.140182597024, 35088.58928230083, 77842.96622833674, 80610.75779897766, 54040.50749507701, 98898.98842465863, 32461.46470639709, 37676.50479948821, 47749.280740122165, 37879.4322188586, 58378.87209282098, 66271.8337903463, 53533.26077492512, 60874.97283067278, 56599.48585314829, 70990.9140277911, 10686.874577067518, 73478.57976096662, 2113.3076183764365, 9935.461654185607, 57289.74994006739, 29071.349859663165, 75560.52866407121, 36983.95062249362, 44055.82348603312, 15622.587080092864, 57436.422634485585, 82734.55309106437, 90894.52025972061, 45579.73283418085, 34649.57388395867, 28710.63594664631, 55.56598809840496, 81169.57336552, 61184.88565894872, 49662.60706963056, 94953.73392707153, 38613.04613787641, 86535.73757665112, 27734.29129994872, 95622.63095624425, 49141.61020857584, 14328.12216100382, 99935.82252794501, 75365.23829544248, 93337.53435405814, 25276.090279219, 76024.28301939009, 82967.91623306155, 11420.940435668836, 74903.69901938437, 97115.10531736568, 56452.70042468403, 88864.76259871326, 65323.496741465184, 95173.82101293525, 70900.54948607054, 77924.94335763695, 42773.28768639027, 64786.96334132579, 2185.8760566571545, 48486.38298421632, 96071.60642290412, 16224.578474270213, 96451.0688582711, 12059.946292153978, 72894.08279378798, 43659.191401454336, 59614.6540521281, 52293.00888609245, 69506.28361093218, 96943.6942720557, 82803.12807815029, 41111.50794089541, 72893.63305630081, 37009.755155017556, 53862.33444406455, 72718.56886440067, 86151.24151137772, 45131.47302151432, 67443.34659123865, 38553.34179402357, 86229.78053061095, 16674.145203907075]} +{"id": 231, "vector": [90021.12802178819, 4729.603368969038, 9671.620675391236, 65392.21194608502, 93404.05963729518, 58470.98393273865, 72971.00880191415, 11403.598977621532, 67755.57482970301, 46847.64995403842, 64556.711654404186, 39372.57293049228, 87936.76134789531, 2283.8887611365653, 84212.2950962068, 99920.99148319436, 40364.50679410717, 86115.39645577608, 95050.85682340771, 21961.801988826624, 90448.28892682989, 82137.60036664903, 43607.804632948886, 14989.460444266779, 84674.66728350919, 44954.67661066136, 38566.90876032848, 56777.22511193245, 70633.19994994104, 57443.36489595445, 64167.348737085806, 94869.17077501463, 10727.37794861529, 84344.18266157979, 21572.76999005979, 78232.56627689187, 78297.58974649022, 79467.46880317663, 82942.18719903458, 24377.481943189207, 43374.76077169764, 34884.17187714155, 74841.24244788052, 64767.25492456825, 94110.4792170656, 34692.918990267004, 8796.005021247267, 70050.66322952048, 2982.228875122772, 90086.99230949601, 22586.83866823835, 91698.39439278671, 79706.26495894979, 5550.357718173115, 70589.51946516396, 21727.17191556043, 57985.83004825182, 70422.43827867863, 91492.16743908508, 82991.2176573733, 83847.72640210141, 29753.47626450223, 9948.505808797381, 53541.18471349133, 88237.84097112746, 13168.184124356963, 33383.11429817518, 50681.07178267967, 60487.363780272775, 69075.88306847379, 68109.0014433326, 19245.320290166102, 21171.584723545366, 73781.11776601902, 79435.21681859512, 44280.024326012404, 41197.737642071275, 77657.77233410045, 37500.4900504453, 89019.70906700597, 30833.262699701423, 52802.39983561431, 72823.29147569546, 60958.513916657874, 30539.723000006914, 542.3331518698449, 83034.99336558252, 6581.094936367104, 96277.14960378365, 50512.71120516606, 66763.01110709779, 42746.07532944905, 86635.80770292066, 16760.94708042948, 66278.85877797683, 82055.535203151, 24141.48060505117, 17881.36047926747, 25653.193334939282, 51141.75664419897, 49102.258678078346, 12707.799656038676, 50465.27803126909, 80036.05740574225, 46980.951089641974, 37354.040418976256, 31873.38315604108, 25244.19818029914, 93530.59886525858, 46546.33711834637, 87893.44787100826, 98279.71000445985, 36925.48551359942, 86748.61712787904, 1333.0425900358823, 62819.844767959985, 23592.863549125865, 49333.78589413002, 17558.535457096514, 12486.395092157787, 27855.600738911544, 11274.991201687744, 62920.443358111246, 75941.83951270909, 39526.52112093964, 36649.80179647058, 1848.4325832492932, 36572.06237371978]} +{"id": 769, "vector": [99476.36441619846, 95846.03425360809, 4888.321624875969, 16405.1704623274, 58510.29312383312, 14046.594890092456, 66377.01817214747, 58809.93498091652, 18891.41020080877, 52466.026534567834, 33916.23158912702, 63872.063151079026, 16987.233481237985, 16110.876199365854, 79978.5901195405, 9256.173031969895, 25898.168383641885, 66704.78610882565, 29724.931801766474, 13424.19991487972, 42996.19382255513, 60584.29905566245, 59544.22962625628, 30052.2167814682, 78778.36899338732, 83369.07056027021, 33266.52521705095, 49842.07141499643, 89120.44720807638, 27871.586200351263, 19344.750463305736, 17331.209119527335, 62688.990146817814, 45056.14040579149, 85169.99111123089, 44030.14574680108, 37241.550790630805, 57838.03706669621, 79390.60800624579, 26203.885877609056, 7091.205228145536, 54961.02302237858, 474.6862323561252, 62589.41967126065, 58559.06956589448, 56180.482773760275, 3686.063111743365, 69962.275412041, 94862.73088492284, 84902.6193456507, 19096.19628075542, 52555.714065106506, 79432.94111018786, 42188.421780383534, 70901.87840391116, 99849.49326581076, 95110.41221127246, 40592.62883919447, 20427.31131023019, 78717.35064403898, 39435.799399111456, 5940.611630146264, 70601.41815438011, 36959.06757483209, 41707.18141785551, 17890.61470302197, 21069.010853707237, 97368.55817766464, 10446.214091268546, 88190.81016529702, 61849.6379846064, 93333.52368990799, 73277.48233660829, 77949.87861516546, 82649.16910509182, 77235.39127021644, 48416.88407797141, 34658.358540138535, 38885.90063808207, 36225.09981389302, 37763.86332031948, 95646.18954562205, 57418.61335471302, 13347.901657164208, 75493.4979295757, 37000.652503430065, 65635.04159995011, 37050.46417758202, 20421.380940033683, 91815.15216103327, 12286.853596595038, 44882.10619061649, 59881.01143929111, 1225.6545225656778, 75603.17458395143, 55146.368392258526, 15702.666489815887, 79299.05424735717, 72569.96249497023, 50739.937212421326, 14240.525348149113, 99254.25080231068, 95260.0854745531, 3632.803878054136, 59060.367571640716, 69042.70059337073, 21258.75626077196, 20922.338639280526, 4136.9195340895, 58442.288461870754, 99065.7424383235, 1519.3591953274522, 56327.3581651945, 36361.02390145, 51351.668622467885, 79052.36231786807, 61169.709646654504, 24386.57641043317, 35227.877942038365, 69885.93806626285, 68713.5075009658, 36058.612773053144, 40398.40653500516, 35836.04584083049, 54457.867597725526, 95765.94187108437, 19214.525116400604, 20339.522160758395]} +{"id": 166, "vector": [36025.6090606534, 38128.46750515826, 23010.41700936898, 46443.74985729915, 4213.36228439787, 69074.33844548234, 64587.22769801415, 82005.18549327727, 83374.36905914739, 69367.5355558658, 75703.28970612277, 96106.22874924992, 9902.838701048822, 14743.837957641015, 91942.09882998258, 82906.30208517065, 15601.874538969263, 32895.62532617457, 70615.41259503995, 66422.87500692831, 88740.67066410376, 50195.42551348409, 40648.85855298309, 33542.74069145541, 72748.86391027913, 77846.21002808945, 38870.366168359236, 59307.25907016888, 95322.32377620914, 13425.536353326239, 91706.44012976545, 87424.62377438322, 46298.77298285309, 66040.47678569108, 81258.28433502097, 49819.801985465805, 2859.1735729854518, 73970.58183322838, 23361.32605091937, 48307.705769747045, 50746.43909791692, 54620.76728706555, 22069.30533589607, 24223.52431022643, 75640.26403487736, 56014.858681503945, 79615.70877601851, 71257.387908646, 10003.448548428983, 48238.06255202546, 61164.51791337343, 26607.564486752977, 47726.15872116319, 44111.87994768077, 46944.56231527503, 7893.6935099161, 71231.2848930638, 53872.02904992156, 35658.55306626677, 20236.160159547955, 75816.68588412393, 93033.10049588446, 28083.89372289215, 8451.748221640342, 2060.8809948641783, 12944.895390191003, 97255.55856161981, 97509.93279009279, 91017.3986770986, 94605.96974537782, 69251.67541565736, 77375.63228448962, 7623.96570888082, 32679.969065104342, 39419.08108776567, 69348.02423020329, 68333.83499972778, 47831.796252816726, 38988.62624527092, 91337.16176371787, 99250.4331517991, 15653.694578078559, 98436.09340739479, 74946.12448234738, 44059.33074526376, 75102.98690638698, 68447.54862657622, 29719.911286737733, 14798.714329107943, 70081.65953431341, 27362.200246477176, 58847.153706298974, 9426.916315812872, 32686.82771354767, 62178.514747677815, 21729.641872315286, 56859.87967696494, 88195.4060731694, 46285.89497403761, 27810.404171258673, 84523.4227498889, 62263.89931815391, 53983.58580128577, 72978.89579417012, 57773.522263200204, 10125.045677029331, 47259.86262449988, 41019.548086276045, 11344.218060228584, 12966.494642999938, 7371.973417062217, 11068.250624877473, 50251.65975464214, 4074.257956216809, 95965.23384332609, 42026.32229807142, 63716.59218651239, 35529.062702829164, 50272.674600056285, 92492.74719214368, 25613.120180672046, 85510.83803223728, 36651.11344719509, 86099.3572112877, 98841.00646960169, 80289.21909491743, 48984.811270283644, 65722.18868298028]} +{"id": 1988, "vector": [8752.353995214835, 33200.481693300666, 49113.46878680411, 31781.159256801715, 69251.59490994686, 47881.672711501655, 75838.05884351092, 54590.301527764794, 45701.74350423342, 66668.57183530601, 73825.7532753018, 40704.417140916696, 95634.70509011821, 95884.78498180595, 76909.694668384, 97650.06352418261, 96494.45330527106, 65242.90503686384, 9637.424000905925, 87119.36515958085, 36015.65194330879, 75292.90020823968, 31717.902331995585, 59656.574394988784, 49658.16116376858, 91750.2731960479, 24860.22520502682, 91011.0290117895, 69619.12958703439, 78969.5476734114, 91686.8614324554, 49709.285022536984, 62434.8221735037, 61536.88707019661, 54176.40147524251, 44600.91548950642, 13203.115803199127, 15060.628903988016, 90654.0900555328, 71287.28159515724, 38999.84145134243, 72613.86302170024, 65403.879352245975, 26720.73978289903, 83784.63576221373, 68371.657461061, 65777.49262847529, 29203.310155982475, 44958.48795902925, 86769.39365772554, 22767.544748024637, 19801.148131348644, 94761.79271191718, 78000.53537253804, 43934.480400547545, 51725.107651828686, 36848.96192585881, 47243.59457606114, 45776.29107428156, 48489.107389778575, 11194.086910419599, 49481.707219157965, 70022.75418276167, 69207.57166366318, 11775.208525602276, 81274.7740655887, 22315.738649237515, 17257.61177794212, 41582.55398759455, 86680.10200133645, 58238.97347069046, 68039.81717154967, 9822.790060482466, 44412.15179707188, 22532.891996763217, 25372.73814545504, 69107.86711749194, 10777.129848956101, 77561.3491694363, 53214.868653479985, 79706.45960291465, 45749.412322868935, 50499.63834414156, 83133.91131810899, 19328.20895454673, 10489.842083682688, 93081.43984545479, 86333.26923267214, 7721.308737675492, 57320.65479840809, 91895.67264467604, 94795.47732729137, 39492.54393153602, 15978.577199965905, 44092.33785587454, 17933.97330346771, 52987.35642412282, 58189.12958406134, 26848.05237615263, 52803.7168694995, 17805.398768460112, 37747.77321665744, 59901.08796032045, 80371.17843393488, 53159.791428007586, 38191.45236881667, 6100.477990660791, 62432.857746502545, 31096.29367657031, 58147.14932425309, 55511.77893995443, 87438.74809235858, 88821.57764106893, 61347.60513396916, 18072.931094809286, 39175.143602615426, 13376.110025130638, 68054.83987344912, 98874.06466244171, 17124.672426198984, 85950.64474882379, 39236.1310737887, 84601.25683377423, 64300.59216481538, 31660.329412344847, 2818.997323514083, 31653.060238365983, 87470.28217947282]} +{"id": 253, "vector": [64461.12669041973, 83930.52638711297, 90722.34999837863, 9038.470436614565, 36551.57998106028, 46766.803551742676, 17902.3935943739, 47827.79058071014, 70583.19257418776, 67037.22405019263, 94730.63484973962, 20649.438023489198, 67704.8738441533, 13174.002059340195, 52707.10053783535, 39096.69551464118, 20126.708445806475, 26979.309817381745, 56894.97813762588, 76271.21080257655, 54388.9872276184, 51393.037786774476, 57197.35014974395, 37081.788548489516, 59407.62260424615, 82694.4121287161, 27844.140736198697, 48251.982196328536, 56020.985790546874, 16165.379499114973, 50600.882945854355, 39775.32882918443, 41337.77165905107, 3406.519138709885, 77194.58034525925, 48851.22077938353, 53440.478606105666, 4875.31752170417, 72703.40153799336, 8625.26079917245, 60599.13407355496, 43091.13101021025, 68438.52328807973, 9987.346024562938, 24917.29983004861, 58647.89847236277, 26319.67930658059, 34978.07379089931, 15238.974739186184, 29677.007055833048, 69832.76363789692, 21807.044759814307, 34027.58547561896, 29140.189231822977, 72151.28028171194, 94070.43426601558, 26102.635940596607, 84604.61166886064, 17995.111052563472, 87954.46608639038, 65747.73163159857, 12389.447087047523, 17502.218031145778, 81359.61399258042, 1460.9838144792998, 92969.18840210717, 3797.1990543080315, 66917.28559076962, 29792.03118907213, 62657.59554165291, 2894.082910801088, 65509.54894981955, 43058.565399653315, 26434.577278345907, 5658.816474757744, 84600.92595924297, 24481.798806710707, 21482.10649540041, 44551.25163364797, 79995.72941453119, 18588.53708348318, 87655.47889075245, 14347.780767270191, 41135.81829216885, 3053.625078819866, 20011.705954688365, 31728.666209322597, 2175.706888012363, 37212.53805742563, 2301.499957513342, 80643.82617357066, 47640.99606555698, 39437.47157385218, 27080.626858085943, 55502.346855940166, 65163.24996856683, 24023.45952015994, 3849.890526941202, 49476.35148147139, 99161.06742279623, 11458.963802861112, 39061.339234761224, 69218.17740101255, 43920.1412154128, 63995.34395942753, 8991.735988672033, 48575.15956003517, 45139.50455845651, 97049.59740556542, 29624.875822798815, 89646.91635891647, 91874.63876275737, 7156.025908758356, 58794.05552070186, 34311.683603274076, 14633.537080296099, 3098.3823711023483, 26819.440439807673, 93328.12393787487, 61093.3161438683, 71696.46972862542, 88596.28619855746, 58295.447984773775, 39443.81399322143, 97514.726198085, 82790.82079829757, 39725.17849485341, 65107.445364495965]} +{"id": 1209, "vector": [92285.61053546626, 3052.354260801149, 18916.114619110678, 75819.9565989592, 57985.22012679357, 17982.873727606286, 3521.323916863095, 71411.0967201856, 85901.70880217604, 65882.68029232608, 95450.13940252275, 69640.05792278702, 85665.08353816232, 20686.111692034214, 34227.18395773143, 26445.321568445324, 80096.80181900274, 45573.673632237136, 9970.352803848737, 17676.873472518673, 32896.35997130698, 86392.79684087358, 60932.19497864921, 51675.47848282111, 39563.64578887019, 33604.89927359337, 16462.76053937922, 24374.46913112421, 94930.5553014853, 37906.73338409462, 72118.60379345638, 23649.031650212328, 84893.7080488111, 51605.21242484396, 95262.96179040287, 35062.49928790201, 53924.52699973791, 90488.92616528358, 51136.43701633711, 27610.611459111235, 26668.578250391085, 96312.98640755368, 89982.01975276397, 49951.79478188506, 35304.943588820046, 25777.19236767867, 45396.73135418213, 33578.53747582654, 63959.87564792303, 21697.34768030912, 17370.79500570221, 5927.877991002839, 53553.11330623835, 66319.71146902924, 19894.336916772638, 77737.49780274628, 7702.3934410491265, 82594.58964780686, 62494.31704899918, 69961.96189948666, 42530.27609517689, 92853.74271463399, 76103.67628013516, 30181.77617682345, 96665.85487948627, 67383.4635628107, 71863.46468685483, 82046.84160455714, 40080.45273990799, 88994.90355370544, 76919.20735655967, 7122.272815723895, 49020.34996924835, 475.8196655857083, 89649.68259751462, 44493.119772403734, 47860.562133314066, 6429.225515365811, 96209.39190998841, 84265.71416541288, 35141.38698149801, 83599.31609625311, 52451.79320236613, 78573.47146793362, 3605.131746166368, 80616.76059348253, 62996.102373983944, 15866.356282649096, 4050.6308916471335, 55844.707053179176, 16146.85546774486, 88676.24572481193, 17416.203288165365, 40646.09357899335, 62754.40754979609, 42081.83023493079, 78377.88855974934, 16888.926069836973, 46108.761092238994, 87169.0120665991, 24687.984895633454, 47380.40988131298, 9945.510432751125, 98919.07311793757, 98684.571625156, 51933.04795306315, 8129.357070917554, 86361.98238354806, 47597.222128543974, 95034.63851292328, 5235.6056431482775, 88463.02649069313, 21596.603257890878, 93045.15906403135, 97489.34096461556, 63256.58136097105, 53512.44790740602, 79006.58857124408, 36463.23222284029, 83423.20047581311, 72521.57689892835, 79441.93233414042, 44884.48194527682, 42676.05515803036, 62662.20856260749, 68146.81247052008, 43627.032807053765, 45241.86393739846]} +{"id": 2105, "vector": [75982.13313580272, 19352.497659210898, 6048.979577750401, 35029.12642218773, 9958.37083193225, 48048.5932317105, 66022.51648443736, 49674.84171314984, 60872.28560188698, 53037.349321432994, 75102.26781726032, 74592.38888254947, 9750.062524971847, 6979.843420094234, 65743.56137647308, 52350.66982921446, 87878.95060017362, 17373.326010874778, 45724.13911964607, 78099.07415347322, 92960.04559085458, 79928.9984049541, 44074.81843979088, 80423.26437347203, 8943.964088613599, 70772.18162800166, 81311.94014581593, 15783.044887208931, 55647.21058029907, 63776.81656826742, 60130.3648680093, 65830.05912573158, 43190.715636905334, 71792.32934675852, 66639.6449660216, 66320.03908536998, 22557.623342106148, 20536.971913665413, 31084.88413300271, 17563.96403594177, 48489.86772761732, 65834.81495914023, 2676.302910338735, 11205.61319667731, 20764.873787966044, 50622.84740970965, 46443.883470217974, 71206.81653658493, 29531.05135704366, 79801.67479125137, 68629.56038838181, 79288.94073115228, 98750.70776376639, 31276.804962898565, 24643.368588996495, 38633.16156246268, 32266.784694117334, 82451.2174184494, 98833.87694637285, 71969.91651428494, 32201.7528209094, 54996.81956503659, 59608.29088738787, 20855.417244306373, 89316.69488023534, 26730.06511025019, 46196.59946246965, 66849.11417547945, 33316.747490994734, 95117.67153363653, 67861.89984329695, 63664.34098203357, 1564.1607701188054, 40073.73612586894, 58348.88411065674, 91967.51384810616, 78205.47711886953, 558.0234118431671, 22944.028849631515, 13214.84513609552, 34336.334524343445, 27492.741362217064, 18734.243125143224, 79730.75411804329, 70258.82653468834, 4783.504851641595, 16956.448841162965, 48567.45308511644, 86729.78374201001, 75801.65632009822, 9514.703221167143, 57241.2874845766, 27404.735038314375, 110.79457828178096, 12828.079487177902, 34681.71587136054, 76599.86241553145, 67493.93426991002, 97992.15501792847, 3567.402445897361, 2122.518680666308, 81469.39790050473, 78249.00374973482, 98679.65188475499, 82838.76661054387, 77935.18055464103, 36604.55157677697, 40784.19300712618, 83805.14551678958, 25494.90682258624, 85611.2409866325, 43894.84469119921, 50490.67940587453, 48826.15774703869, 97694.81174842967, 70000.33573994692, 14586.270894290832, 67465.55357497282, 38076.84286235874, 97216.85749303599, 22296.433462101573, 88783.91244641131, 23634.16255409784, 18141.653207197807, 54609.05051166165, 23228.57244697333, 67828.19186197453, 28675.749398580298]} +{"id": 156, "vector": [97508.60852338656, 80127.77584559927, 14119.342297592451, 80472.42851383102, 73786.15333895199, 6594.253490603707, 84009.6381359923, 37090.21523739866, 67065.59872947528, 67493.83228619272, 34840.50077880202, 71614.68740566925, 88662.12620028212, 98330.62460655675, 74377.81656374143, 89617.16824103503, 97249.95154336301, 15464.780522614841, 58557.69296124411, 51304.73419839164, 28021.298423998163, 95255.92768039578, 23960.606964264163, 44943.05684977302, 47136.16241061778, 61683.799005408066, 78024.003725717, 1056.6459090995627, 69907.12265299626, 99363.64505076985, 17236.197412865808, 4946.583248398928, 30331.59117835784, 52919.61608817813, 82709.06665599652, 71913.2192648, 10137.647817655115, 30651.298052900846, 2016.2046090392516, 43503.451437798314, 2140.036197513251, 96108.63084286625, 35788.46197263728, 28799.265510903926, 36610.84271879128, 4976.59376120686, 14121.126779567317, 59125.719110687955, 75885.26297102805, 63357.98602033267, 13972.098832959511, 25093.981922305986, 54745.10617912325, 65261.697409795604, 35601.24664566745, 63056.367224926515, 83425.5543078066, 231.9024478202203, 40461.73910780158, 55977.54150755815, 8295.195805516809, 4366.918499810291, 64236.79837225221, 64531.93747302608, 81661.31778293285, 78773.74469978895, 88948.63583498415, 21539.654686602316, 79920.85629419994, 52927.89786069566, 61573.03390034914, 87920.58188250102, 45760.13973109392, 89623.44106900618, 4332.186820976902, 85366.79699598101, 50708.34290755519, 67858.92785311324, 72965.72749560182, 6452.069073538003, 37413.716871549375, 24600.626973642247, 23396.935877884185, 38780.69756768834, 33516.67232543896, 67663.11294252955, 83055.53084064068, 70953.24579629133, 83676.39839947966, 66331.47563494656, 87891.01105951778, 50877.48331125225, 89047.83719659103, 15303.750697751982, 50532.87207302818, 13592.251254358656, 48669.45187018263, 95864.48230645775, 75218.62081284568, 10453.829441132333, 92508.31250687315, 47212.42019801561, 46108.57294666415, 30942.08809977138, 877.3587660883097, 39451.363977026354, 72656.36093258818, 724.7322643935905, 82346.4359385235, 4026.181211522206, 62978.604012855365, 70019.61771940668, 90072.48149878619, 34858.37572846435, 14474.82520744856, 33789.860537907814, 12727.559211705851, 87195.16360855312, 52559.845391403185, 99674.72926050537, 4554.937113133217, 96018.46570683042, 57581.61352210478, 81163.37520841659, 34673.522842741026, 74949.54269360953, 11033.139354305777, 69865.5149888051]} +{"id": 1720, "vector": [83727.14749422227, 28963.615847881087, 82644.34196984718, 71253.2675826791, 69534.23626816987, 543.0762295311142, 20051.96628338618, 51842.943626000335, 86117.75362415082, 19319.99444339172, 4507.196153471083, 82726.75903918379, 94088.46017536949, 14376.997371754607, 41345.961599132905, 52502.13292370232, 80196.4388365855, 55029.05294610124, 29023.79937705122, 25518.604181214545, 92610.26977277655, 64380.357513112394, 18826.76135591801, 13194.837929565594, 56951.01304311382, 61394.39871328595, 24307.809394594406, 328.97846169737124, 19833.03422211766, 56196.927717395614, 81653.7277610002, 11576.15827883447, 81225.57937874868, 23998.99265792762, 27630.87743502549, 79216.47556609909, 27801.624848216856, 77906.38256526784, 8633.654606182461, 70174.08586674761, 14438.00220385697, 36741.13508893541, 27948.139225535928, 90545.10984700584, 72406.1744720919, 70577.39375011243, 6607.824828444719, 42805.62959730375, 78311.6517109074, 88800.51411068898, 47644.978047854405, 21185.38973089197, 43351.0251545566, 35142.73768919946, 37222.02323651509, 64966.994339313766, 47777.68304378278, 42624.75965722149, 27651.12385126368, 50260.26356976709, 68952.12364240379, 639.9307870803783, 40769.80121364732, 17944.54062888131, 5081.326230171324, 59573.70349236164, 43088.4399292305, 15290.44168468261, 88360.47224030188, 87463.17716079354, 49581.44291746619, 51291.235348411305, 98382.77488666601, 46429.17746529285, 11726.77378930027, 74444.23176739672, 88178.4759826733, 16901.313851091592, 25348.3223857193, 48418.82822292631, 63686.808720278685, 45776.81935276294, 95696.04282759076, 1305.5498540617273, 50270.20299889248, 29678.41078099259, 78284.41868654778, 50123.663545102914, 28570.24864651241, 82505.33171005678, 15612.975606814549, 98143.40390371457, 47981.04353787868, 29307.837122907433, 69651.54771536824, 4483.7865375653155, 40293.81030700575, 76920.55571238844, 26083.83185989821, 16265.18853367881, 94646.98614263452, 23544.814089689648, 42573.13216843347, 73265.91605431204, 72569.36479575929, 69418.37714447963, 2095.68968868965, 4962.572295289414, 84739.43061086883, 45092.01202566449, 79123.69734118863, 36894.92695726271, 35740.20146418718, 39602.6261207496, 60629.733932539144, 56945.000381651, 71699.22766763858, 93271.57839028462, 10977.782298714068, 10348.896723264334, 31564.14377526349, 9478.74359597276, 19769.404871652474, 25451.126456872065, 48423.46909695996, 40514.59344300229, 98812.641759476, 34047.14940765306]} +{"id": 436, "vector": [17847.52969509098, 52938.38365057365, 88176.6851008809, 36534.920269376504, 99368.30143557346, 78906.37494503941, 77411.21328423297, 42798.3880749483, 44052.171510779306, 96262.50738122161, 27760.208302098, 14693.561313521974, 21927.4601891848, 28983.581321443096, 79991.31444606594, 78522.76196194874, 84184.00193369882, 86023.48126114659, 18635.644443360823, 88151.31897528215, 9165.394417547912, 35467.0420867542, 51110.922957058225, 64156.72504698088, 45763.11105914934, 80055.89791679001, 1290.379590131674, 23315.934066357357, 31672.625651281905, 7350.85916160525, 24278.032295381003, 61462.72047196382, 54739.06605529759, 70855.77000790785, 47995.88998851463, 90395.79619617807, 585.6272305266308, 76927.85804885767, 39923.96205975074, 841.754862917421, 9476.382204256784, 12345.10877629319, 489.780649110616, 61437.89028780619, 63401.74769304277, 72680.36948861109, 91220.09343936267, 98292.73398251124, 54803.654655761115, 75774.73856014328, 42687.687674460794, 69332.03831272075, 22951.301784147403, 3628.697063748121, 96167.98966809992, 28149.151772303947, 75406.11138801812, 45055.17151161462, 43154.68513444786, 29438.943485659962, 23630.025139753987, 21431.5574242244, 47122.38332923523, 96683.0072513357, 66309.2566454707, 3.354998960558575, 91701.65729381642, 18289.724534012654, 96276.9598905076, 44454.64752166671, 61298.42087769977, 63778.05664284798, 41098.7336149068, 80895.82718138579, 52865.609219327904, 22263.103177751353, 6883.943223486588, 6772.595950804083, 24884.71380312751, 664.8577652427767, 53371.343347836046, 86465.13546007336, 47120.59288570882, 22964.310909121465, 97521.4253663139, 29460.315850718234, 39399.551848610514, 11763.769796630719, 24741.305179824456, 5282.796969745207, 20954.541226771275, 69888.15822652588, 68597.90865515881, 26399.39354745178, 72352.3727149548, 45163.12137612727, 56198.08277367849, 41639.84715453265, 99748.24696437479, 72093.97724546593, 68089.67547007238, 95965.09083903694, 20859.746640414767, 49267.8733997033, 78721.67041165958, 10215.660754909284, 22704.701838252895, 55377.41678228349, 4124.305224243996, 75591.15754025806, 19522.52054724234, 96643.90778648402, 9400.72467685651, 92734.39071918366, 64710.031345137344, 81583.06493886426, 61435.39140692784, 97031.46635295532, 77053.50193795902, 45855.0765090943, 78199.99034063893, 67475.35201595204, 61964.787791997456, 64866.923344700146, 40763.013321705264, 17331.45411845599, 84589.3099631648, 1927.2985767019968]} +{"id": 251, "vector": [68509.53158954208, 48231.020056589405, 28940.100932461566, 3676.9171749961747, 62418.00882067444, 1656.0072839922557, 47907.519175852554, 61494.38835656358, 61437.15938148372, 28096.22496204166, 28059.73771732354, 14816.185162195128, 47732.7272993635, 84160.71196746003, 57560.900862796974, 52393.6533589073, 43553.16620704619, 820.8355371745912, 48909.31865032808, 53395.42324285114, 56664.6531218945, 26424.642484435324, 86380.793761307, 85623.25358837523, 99775.6891459825, 40109.216669580106, 80986.91730819564, 50270.38206377371, 69847.03040219552, 93673.11445886639, 90047.16757072674, 82319.32310758869, 13902.441957398425, 10375.85355171371, 3993.2945564899014, 33920.1792279242, 74350.27038189396, 26729.683269357964, 71696.56232789789, 21981.287379672565, 14092.46873545703, 17692.47547165972, 5587.857914972927, 96943.41033949447, 92427.47648405223, 90458.61672348669, 75990.1676179478, 69442.8161430637, 81699.92538773446, 15571.311570505408, 62993.05775587748, 6961.730051082426, 38139.925602040414, 85513.27688109885, 32105.499201867515, 67417.74216490539, 94987.13695876689, 28695.107434224865, 7540.120235672399, 90005.89643422111, 7749.280233813028, 69008.3195604828, 53268.54886739184, 45265.66353949501, 44508.480178669284, 50064.0306505182, 2884.478326237738, 87338.92312940324, 39022.087972522466, 14048.508273891424, 76422.42532609243, 66044.46393598572, 7274.247832091074, 14641.437923197787, 6898.17923520667, 74405.87150440755, 65716.54005289119, 24950.84306315699, 87466.14293981672, 20196.6653011449, 58425.11513879779, 85868.01400691774, 73331.5108957941, 78863.35154474739, 6675.930642633277, 72992.55509758793, 98396.3366241933, 62772.98502078459, 91327.02101442656, 52985.60211460824, 36861.527511743894, 72326.01459344615, 99006.94806043392, 23047.31040284981, 67979.05664646243, 87364.98632834754, 19896.285106563017, 80923.70757035381, 59592.009972062035, 17814.09541830785, 62696.253054472705, 91221.27434752056, 85949.61557188845, 32039.791380862094, 54902.651662796, 68827.28409720257, 4839.344371916865, 31725.388152226074, 38319.788108877474, 67565.64182490627, 93593.3268731304, 19770.121379123175, 2965.235636354713, 9384.757518323982, 62218.5270706825, 92134.95782021494, 93524.1752785248, 93830.2104797632, 22474.476756246255, 44026.497148962626, 61671.404766309126, 1202.2251437796006, 90396.08060253324, 40721.3312743726, 74102.34341957024, 13227.499974630131, 1821.8856020207231, 13334.129402777995]} +{"id": 47, "vector": [41098.159625398286, 51818.561354755075, 33797.87670333868, 26671.835174375203, 42572.231447578124, 76022.12794687033, 93449.57672948057, 81395.9051781906, 49828.823185372996, 74337.86137186662, 28630.115310611236, 81687.44142059254, 51315.50566264264, 20432.964288637344, 67569.34588169449, 8183.295961412873, 73479.83333027987, 49167.752035836034, 59955.331329008855, 39082.57798050192, 94018.54349469904, 39726.232600669806, 87352.03760599365, 55456.893824710714, 78852.15343828681, 41478.223382405566, 97523.6870016115, 49466.10888915067, 62312.178897498685, 89252.35520019552, 47613.269986232786, 18748.69069722661, 28121.252885564107, 26916.710483248673, 33366.6777393338, 61406.633304693016, 17480.206986915393, 44241.29494374643, 64299.550657184176, 84882.04170049621, 85101.18147471116, 7184.312626036104, 7991.329963802096, 6368.789747915504, 50605.48735270909, 73003.2301413435, 98977.42498311037, 95657.33298939097, 99770.69731027095, 34426.4555806643, 28034.903240985153, 40881.029591764505, 95255.74481946259, 88384.14568451894, 97784.509686343, 86823.87196953387, 47350.89891241385, 56541.78569370662, 41816.19485058009, 47216.09787188345, 51546.81625117933, 87317.03455852885, 81677.20794600058, 64619.30688357743, 75329.9933122652, 22715.166163443646, 27034.916754303253, 6103.771795208446, 73965.57353693293, 10598.473610612313, 44064.707484726496, 91800.50495070047, 19058.41186646904, 78542.29066724521, 17589.63653274486, 78620.78413440583, 36523.95649915389, 26989.171239490915, 73681.84468576875, 80085.1694618645, 40122.19960103007, 25100.752743749665, 57592.21685434298, 97313.27762939005, 91855.26357021314, 36021.642086189844, 13451.518466124367, 28586.137330239835, 46573.38916385485, 76093.3572744252, 36474.75804868336, 37233.56666245251, 28893.41640294896, 28783.504251200065, 7802.105576881224, 6496.055586373927, 8140.808955252821, 68453.97728593051, 10167.10237081091, 2802.6972741463487, 82839.33033419187, 15615.453641579625, 16730.80093270045, 13054.518179124974, 8568.27160614193, 57194.125252296, 26566.016919524893, 14580.126851601872, 70298.57078913305, 51245.01445381939, 19352.908498837274, 55861.86224288716, 72807.17404114088, 13583.968202551743, 11556.803852523422, 9110.291282997106, 31913.836143036267, 48577.20128780761, 62569.28975075594, 60744.84294976468, 81579.78131850294, 94662.62339840815, 34130.39077029664, 25694.0944753158, 90132.83151394298, 9436.647052062752, 19597.61777585263, 13960.53778789147]} +{"id": 736, "vector": [10673.257772600708, 49198.4980428616, 31860.95804498339, 87982.07175103563, 21586.773016193827, 22134.774018465374, 45381.30725409054, 67541.85352473104, 18684.795637230967, 99737.48315691872, 75340.44136502755, 3903.0120031936067, 62003.53592101344, 27640.00629751695, 24029.941100828444, 42959.47211340111, 7012.195346640082, 87100.93706224926, 2809.4955801351684, 7198.058853989242, 90891.28818682686, 87345.3491504746, 1274.3393691830418, 50115.31890786024, 9794.576003258471, 46934.35718869771, 81854.66196396777, 39947.459371655634, 93290.85866873484, 67747.15646959029, 50879.222558857626, 84446.20689947854, 46227.99260098249, 56728.34665468425, 17511.20016629315, 24630.179828694752, 88117.65356816484, 71732.82853762306, 2524.4642890593805, 33171.38061280286, 78873.54315885332, 23311.40157790542, 94009.3092113048, 92780.60517454853, 57942.88047676724, 39889.255007155036, 48979.49061009835, 56417.63909895863, 94727.2478396306, 94064.38186700306, 65040.2047690028, 2628.934101454339, 25149.474729871967, 10486.256611423762, 60873.853741232095, 33800.01924616345, 80567.65468117026, 32557.143515311083, 61197.04053598245, 57615.340634313594, 27524.75104826486, 42549.08935970787, 41718.981554112725, 8166.686253844901, 91305.3566642168, 66499.07567071324, 30411.137364241215, 40434.96652738623, 83172.39859743006, 46711.92109323048, 14294.567797732905, 18455.086741194315, 97012.67474223087, 67574.56338881988, 53351.80698247429, 87994.16757056663, 68289.80597060648, 16312.425724179036, 96436.86279824453, 30954.444062616858, 53386.901835796576, 6986.218999888904, 51359.46962705424, 11417.932098876938, 72742.59386103168, 36176.341246765274, 68727.5598372024, 83487.99574325587, 75056.81034143436, 57100.87756273724, 13667.854325305074, 65617.45324876855, 64394.32510218709, 61094.83632561101, 84079.46528019736, 21372.960719180744, 84176.25845392434, 66757.33326492632, 21729.84530833737, 78819.99667026145, 25461.034310082152, 61354.579302437116, 62073.38098578466, 4344.938344607241, 21116.81896595331, 59998.26469841277, 29125.79715250806, 26559.301478544163, 4530.826658963804, 19019.53428126836, 66986.31791455636, 10569.800118397021, 88038.93471404126, 42023.00406045081, 45385.617967457634, 36576.33997833682, 33519.57612777205, 16561.79402224004, 74847.77132832863, 75508.77952444513, 63302.9019988866, 81892.24223849882, 28159.28106460742, 72870.12246964176, 20036.278971286934, 94740.36176476139, 26948.889912185416, 10951.986358714716]} +{"id": 1208, "vector": [92777.70476439172, 84032.25843004267, 54439.08113920964, 34842.40344039764, 32.791171270685204, 14823.856745073905, 44906.29740247046, 67114.50726117588, 6855.992072864803, 3784.5463220503084, 1464.2389101112929, 2761.7953958305907, 6240.546314743556, 66954.6348395992, 96443.18556927503, 81191.57528647664, 45320.42480884116, 14248.919665684578, 89911.64769241564, 10619.512129943143, 82016.6941308709, 42068.295915186995, 49502.34267154552, 76135.68359628353, 1678.8168548807826, 50168.26501228461, 23287.412450998392, 71930.48255334301, 6836.206316460647, 32556.189152195013, 46869.837655701784, 30082.12849575791, 89120.29195870015, 65995.05109845393, 10460.707573108952, 86610.23945316554, 51315.28827221381, 53075.945022044936, 5263.583670492711, 11000.156989392095, 63058.931115851956, 46786.36823966187, 22143.451679861515, 14447.482427405323, 93345.10694858438, 18678.24014968176, 96144.69330781017, 99713.8625691491, 47905.854927641056, 36955.2818437438, 15668.210475632526, 43193.85477996606, 88743.71087101754, 74810.49103650011, 95069.38043257168, 93141.36190613084, 40177.55010475287, 22292.095918619616, 13207.705414715987, 2605.404042508741, 79604.48797940394, 2963.725680953888, 96412.49796016324, 28636.88036781967, 50107.418528281334, 85045.77390494992, 81316.15165722193, 68021.8845056696, 87187.79664850613, 48396.82784253645, 831.7665974550747, 44952.87451825116, 83440.98707916842, 41378.01759163365, 2905.574892878338, 63995.153421552, 89950.38876294476, 92141.84859781186, 34272.70351300276, 26863.985785766432, 96508.52976265125, 40491.536902175714, 23574.39742616003, 36646.12661821119, 71069.81656389862, 52532.386465982694, 23669.274804087494, 17506.10061239248, 30146.818437358503, 12438.988981324006, 45354.2440233279, 99286.72076491363, 75554.11063535388, 8139.892251730529, 23767.081054414106, 8046.596744320255, 92410.24103952588, 4280.3987646515825, 88846.31539224644, 45934.1445185612, 61547.276847119116, 10271.28622912311, 45049.42660346402, 84750.10304096944, 57664.78237206315, 89252.2890307967, 57077.871727744, 78223.07861490703, 54201.57554405889, 77022.77633824492, 50853.36102421243, 94067.51672119964, 91148.97931763825, 38008.98484484853, 33324.94696260914, 4734.607352419662, 40759.69284042996, 79301.8989420138, 29018.857357014906, 6307.568265018593, 29024.411679886587, 38868.421253963104, 21220.421127438127, 76553.47237340733, 56161.47042815314, 92608.01705756104, 17075.087734097306, 48045.01096796287]} +{"id": 957, "vector": [73931.51929118953, 3112.156389127474, 4086.602344790091, 43222.06794673044, 24182.773846970627, 97356.1421574098, 93901.74175546935, 16931.353052810326, 13428.076561695323, 18717.652059590895, 80053.4865405, 88320.89693693153, 56491.33654619316, 56281.01158723614, 12719.759201818726, 52624.22384121254, 83519.19000421197, 38234.031319730544, 4013.9243336312693, 20999.45544224907, 12481.638340843814, 27333.436012410217, 56731.41234676866, 52104.70285560427, 6649.519386005898, 24831.056095674063, 13146.60252438924, 91241.04337035732, 5407.429932777863, 41238.70144242995, 93965.99934416819, 74953.42978153493, 66813.13816177791, 51809.30806214734, 79600.36875275905, 61283.34887369514, 2151.696377841239, 91705.99197804987, 90104.54534587725, 24547.853989372114, 51430.127084464824, 43694.123494354084, 45367.39626387312, 3179.6396907929457, 93625.22173927996, 21884.232145759197, 78016.81746175558, 21526.384382525164, 87692.11069214386, 27763.069145982776, 43040.56635838484, 48365.860064770364, 37585.58207347423, 86639.16318383896, 73198.42412897204, 19961.817473452193, 84985.41969772095, 65849.84070912759, 3886.473384779143, 71649.8874281177, 33905.19439104911, 63725.867296837, 28618.62527611967, 54171.23039952905, 28537.07921711074, 20661.264943937764, 46972.38801481337, 30066.322611071828, 6337.1724359910895, 44864.87231905042, 80828.16619540301, 59441.87868101803, 10281.75279024629, 31367.721864848674, 46460.81961294373, 38758.58894497686, 38491.53877310738, 45510.66241204728, 99449.34238026099, 72196.37351879598, 74588.82427290305, 45599.05434220541, 4136.646762857943, 15182.53636968887, 18574.310764933045, 2779.447672412849, 90298.15523101765, 26875.680709689554, 76694.7687564977, 64973.1775523584, 60137.11860543891, 46060.03748454826, 79979.20661783824, 93135.20130345713, 21310.26915673607, 25508.687750721514, 2335.331505260929, 94005.31435607576, 15127.87894423222, 53866.758919698754, 9467.152561629366, 28171.783146597052, 14873.461541796807, 12784.053997893885, 27209.60385991583, 14012.610652377378, 40419.53978064084, 7606.808405927756, 42253.79761722335, 55519.8663255015, 52029.53740643368, 31931.40243404449, 88837.60281109576, 98422.3809593589, 44148.15407317188, 91095.36807207215, 44314.034575882935, 19999.21844165463, 5585.474276221769, 77574.90295176115, 61758.904180671925, 61913.15386476411, 77762.07975225152, 59480.83778871822, 42656.71570052084, 54080.720281974645, 43183.295961102296, 29352.110595212987]} +{"id": 706, "vector": [59865.24814499479, 75546.15412815538, 67745.02571797092, 62285.52806077101, 37511.60920405343, 57455.47840889626, 12000.341758310518, 18065.18253985231, 60476.15843944897, 39859.329972275584, 27523.434652939526, 75921.51489928171, 66959.96841397653, 81185.16451507124, 5004.773827803421, 91897.08423591068, 4571.3399947207245, 9423.206572232923, 97713.14732065791, 67521.52619802025, 45573.27051799924, 98543.06578716818, 63556.34134635916, 33094.85626665892, 30786.530399611966, 59333.0303705611, 44361.12277377851, 56184.24229212499, 73445.55834713583, 78931.89550207922, 61047.133841370385, 15275.340636071332, 69162.88924704172, 63891.506221279036, 8871.833123447093, 31325.242881397862, 87814.25610640539, 91300.47196422679, 8840.636203757147, 93132.47253916445, 40791.693757297806, 38682.35701870495, 76270.49269249222, 59431.23789025498, 85984.52415752642, 82497.89623203535, 43550.70213162601, 72572.98201897829, 65879.04798874374, 8862.613032718513, 85830.97625790788, 28181.136429973507, 89624.82750756026, 24850.66622760366, 89483.99931424875, 83131.1671418576, 33605.03644858367, 23906.454947590682, 98737.86454421317, 45030.51673963938, 68268.97514533691, 91669.61378451515, 9086.800618305724, 87004.40573222187, 79613.72244456268, 14344.081886083404, 24191.063938618307, 39419.74635995209, 99806.02218557945, 67123.95272029798, 45763.93362527059, 98623.67605989378, 8521.015142212018, 99139.4227478669, 10511.852264243904, 20429.37277119402, 5171.992186169083, 39758.74516573018, 86487.16272480409, 98338.70148150416, 87434.37718293822, 58078.15131018711, 9660.373649725596, 22622.89826299293, 1739.318919009314, 58361.959674197074, 81101.00351276688, 95416.1041127079, 70817.13594489364, 54369.785459044004, 76574.30910143525, 37390.784833912585, 56882.25448295626, 30556.13372229141, 63836.245515124756, 89989.70784821917, 75353.35970847044, 24492.165608982796, 16856.35885014498, 75495.70788172842, 99119.71751422781, 13991.181326563374, 5024.390144467939, 45783.79710480224, 11956.075923703891, 95884.10662321556, 18826.520881359975, 63269.7307358433, 84093.5951657762, 98213.94585190558, 82826.71755950752, 90344.19980877536, 23655.70949107343, 99972.64860358209, 45396.51991018227, 54515.77293477709, 44743.222846478304, 10525.699858250404, 90798.52915878322, 45061.97266303955, 1472.8480007353494, 29116.517196380097, 9614.33505468854, 34462.841313473735, 59017.99711077243, 68525.56415915536, 17556.423207433127, 76897.84900161845]} +{"id": 1213, "vector": [4009.66092266245, 44643.020279379416, 552.0725846732244, 96835.85122060624, 5097.485684146131, 32111.432089512637, 23508.51739823393, 1093.6941571901282, 42520.69298313527, 45827.51721082303, 83385.97761881362, 33272.908899697286, 69907.51633214833, 53328.911437509705, 61850.67925931657, 76399.77135687732, 20253.513363437203, 96266.19672592393, 61932.657542521396, 2501.149032690719, 76921.2309167859, 71302.30293017092, 93142.90062134563, 3470.059744584042, 24561.183216446138, 82449.39909164874, 94998.47683889608, 37957.42048118428, 70980.60173380031, 48412.975384614074, 10995.81025055948, 34624.06743661254, 67759.43970549818, 29745.29491437412, 59598.92934470211, 41192.52320769281, 89936.66225572025, 13033.022600777444, 57494.504627460476, 32927.031462567415, 52440.71191734081, 9512.456775040479, 36890.91567455039, 54379.19554052834, 97747.85000411529, 42348.31786292376, 68699.38663004908, 44493.21876316328, 86445.2753354467, 71143.15427716193, 39602.548020741, 70407.16902952206, 36524.79232499717, 72705.8544201651, 13187.474634395425, 5466.868434647132, 4345.714083512075, 84824.32821287416, 24150.091625870427, 84836.45944244631, 40838.38767641096, 71232.8321877419, 99790.08434939747, 74867.87227799596, 43471.06427862071, 31049.49100706491, 5575.578971507622, 6201.019768260107, 72260.75179096292, 25197.79054384409, 75234.66736337326, 52137.55143717321, 31358.865949431747, 10703.850038021468, 61338.19825884034, 66775.29061258408, 86540.7308372466, 56932.31816877208, 19764.18831513116, 32407.697067734465, 42416.596738178436, 36573.68883939584, 68126.41573405868, 98227.67449563382, 14588.637616122624, 83091.60629344235, 62631.67158513945, 42399.459464643274, 1586.9907116450088, 18846.804931301664, 48185.07634798776, 97760.96641396212, 32925.724756611686, 5661.562474445669, 47181.770564487044, 25279.89054742351, 68513.91664846934, 13768.242686380872, 71989.7673954884, 18424.046571905907, 66133.98713502112, 57277.58548612832, 46536.07606218986, 4193.909279577501, 87647.69190553323, 25899.33284908511, 81893.00548686556, 44033.940234202164, 91396.45613423052, 69505.5777875403, 32767.62889295873, 98937.3636727077, 79204.04880432488, 34321.81614098717, 77298.08958275424, 20845.435562014525, 56291.891543035876, 79089.14509882158, 47614.03161682608, 57852.26821675298, 53842.92978434937, 74236.7270621964, 93222.10529224276, 38391.80027109428, 2002.0973268533583, 4440.058408533431, 61866.80304143189, 26463.097232679665]} +{"id": 879, "vector": [12440.730613570395, 42447.2649237546, 60160.266083931354, 32468.8061945833, 57278.59589418933, 14287.46552379373, 61631.07699023415, 22783.986556432512, 15082.903390928637, 85138.25780804435, 44483.36362754993, 28085.2319982971, 46809.66410618479, 82138.02988429434, 46670.94850739998, 85992.65920906169, 51608.3380413621, 44722.317459074875, 35487.361680962495, 26146.655972333578, 35919.61602813111, 85284.80356423208, 43787.47807496284, 92885.93027129523, 17396.466360420327, 31897.336953875198, 34190.11739145201, 17230.87384771238, 43316.19514457041, 28312.207096687725, 401.7206061643241, 38476.90272454598, 86712.01730177157, 50033.83717808431, 59896.26621449988, 11924.982416884866, 57498.37676826862, 33587.81244095568, 15041.105942796317, 76212.29996877952, 95032.05001576013, 69206.49670308907, 45963.744006694906, 18328.05295566058, 41350.60734096135, 15337.696881227925, 80844.69506344812, 72285.49847910566, 28859.325652908974, 10498.559444288569, 78340.31071596178, 25456.17184406368, 1804.273933654521, 50421.0046510681, 49355.05674967913, 42721.515673635826, 37645.37090598304, 96500.33675619378, 32712.140391542656, 46975.07257810444, 25657.65471719348, 49667.5908929331, 33514.35077051915, 93576.41185932604, 78032.50804550515, 14427.891740134059, 82060.04854507503, 43185.20292623611, 82525.11262704691, 53800.376673157734, 92212.26098382086, 72505.68355865483, 61284.65334259533, 15610.626003305617, 72154.14360459374, 9213.693048974481, 34585.10979314812, 51503.05174105223, 94203.31329695536, 5017.278135817948, 88869.0571208474, 58386.543537464, 85968.00739323492, 35415.795147597, 44332.0960395817, 52815.4833284875, 1880.8311268766697, 99030.07428458866, 66982.6374832118, 30842.14908783105, 94134.61327409653, 55250.57804113375, 98330.86048975872, 63066.04710932839, 57331.29940188917, 11455.758608417898, 56651.646221078, 89300.98175835621, 12729.07007920664, 31781.925444650984, 33139.510352527745, 35612.937428549754, 64232.27554763673, 25012.05391421856, 84780.57545373522, 9390.037545123885, 78231.33484617271, 7511.760506517684, 60678.47811534013, 26998.830140041795, 38818.61090438017, 97931.61464840053, 670.6276393592448, 61946.748450125, 34387.416353818575, 50274.2757284664, 49989.385273547705, 28304.615070978743, 98796.54152153958, 1044.7787541999153, 73025.08559237358, 59283.58394380084, 89974.96038714302, 42677.30811572951, 10231.633604418244, 17281.52170721766, 10051.321279583635, 64653.90916736674]} +{"id": 2012, "vector": [53266.29823915662, 67430.21405428334, 88944.49256944803, 72895.6032709078, 83214.39193061643, 54417.37879752888, 44552.427785396256, 42695.02344229331, 4985.4489608830545, 1307.4359695687265, 24856.30266662583, 23981.436781686294, 20754.74323044205, 29834.621942925532, 47897.153211847435, 25520.35424349509, 28447.3865607742, 56752.98256547372, 36032.36834172461, 15619.368426125302, 86319.38736010517, 60789.861775936006, 90083.70307264065, 45222.83159868762, 84292.05840308257, 52377.85546468143, 49855.53144461992, 23967.20391694167, 44591.11507698122, 58428.95476574267, 96452.61320196695, 78306.38614776816, 74220.09726414361, 28925.826109960428, 35575.03757840769, 86859.98897476948, 47821.001630142135, 21242.761788788266, 82700.1294254713, 67205.34612115701, 27920.562454582654, 66823.8619300919, 4758.035674899441, 8315.206670306385, 26554.79639384144, 24157.40289042899, 72980.56079332545, 80143.74390965617, 20996.01508870379, 84494.81196815292, 29148.64093501396, 21792.145603998393, 45043.45117262915, 71284.9739903915, 47958.384652726956, 95401.54324127651, 13736.081717551519, 37932.937663268815, 95229.58142213785, 82236.48304578033, 29968.500394509647, 75246.45760669737, 36978.406641573994, 22867.991816469446, 1557.5153737690362, 11862.10317244809, 52293.78166256779, 72845.89769019604, 36237.47655113986, 5955.768964476082, 64700.16262086797, 63306.563127642956, 29732.18492684181, 90633.53359500045, 39153.58537908308, 9706.40522212478, 96591.00364793098, 66312.44429750206, 74609.73455047909, 47182.99985608862, 35097.2864035976, 54802.13947165056, 69003.3909011115, 377.92413458389575, 69868.19667115368, 57210.769453026565, 81068.85316450591, 50629.01125581693, 59788.94059924812, 35254.21847943094, 13324.351817425006, 76586.47347592858, 82615.2225819626, 48997.843907460214, 23103.677099282926, 49427.9837884052, 88018.19479569043, 77626.6274635571, 40820.56595980216, 84220.62490732572, 469.157840311063, 11299.84794042479, 98788.1824360063, 87097.73411250947, 2652.928168251634, 75454.37816917653, 4725.571157751962, 14449.447511097991, 91562.45111808735, 35359.71564456736, 56861.8731279958, 72303.99568116598, 18597.72617957105, 30326.956664667305, 79717.5603846221, 2648.0741214607083, 93107.29447334763, 33583.791860852296, 70192.46066208882, 86632.91460474065, 28538.77253791567, 24319.826206831363, 79860.00813396407, 86203.0050527705, 49351.416397076595, 77771.23350148101, 79082.79904444366, 10892.408514458408]} +{"id": 1735, "vector": [82658.56496478384, 49508.88798450339, 97808.95537877004, 38584.572767737634, 65567.67828762588, 15444.789305447837, 83396.59911639593, 23946.625178462255, 28806.006383295236, 83883.05155571041, 8888.866578503319, 56936.780095989525, 65169.83495796431, 25522.553767756228, 78643.54589259894, 92355.00577805792, 44450.878289842476, 39454.87643032168, 97722.5707731191, 24719.837839192925, 41498.32019013536, 80415.24672004182, 73878.05770876762, 25379.020120057517, 55202.80779906575, 86104.64732615366, 66790.20308192521, 5516.081456274435, 41651.70517860339, 43472.200167254596, 35488.120059917295, 93325.08823324557, 87931.75467919787, 97670.54341856754, 66886.43093295932, 34580.63653793316, 46253.353611268656, 22730.96305814991, 13832.614677747923, 47311.93699565514, 5528.973693958228, 7836.569399714266, 37131.02570692352, 31011.154258398165, 28039.740387058955, 12345.12301088896, 63467.46319639598, 14442.817401398866, 60042.1785917855, 55823.46324423789, 96695.66684463674, 58261.4276014288, 76321.55279524934, 64276.124067796045, 46663.099891912454, 80332.20306728309, 75422.56576128113, 18049.890603876116, 64458.323284502694, 55338.77613763481, 88602.7109362546, 69538.88995016321, 93425.20826395751, 90615.75660311375, 34246.05027731147, 12041.706387238904, 64403.32373961456, 26198.931132840018, 7408.274396318259, 47243.01170350717, 13467.410488876441, 35174.03615433146, 81136.5068391849, 39029.60439865364, 51252.207526404134, 66167.19803685525, 43727.18642081527, 49450.59889350366, 67646.41018229305, 92724.08245358433, 22882.694409926753, 16171.961176962535, 60487.30503613246, 33635.349772748545, 46497.32175039784, 77363.75215259388, 25061.484549392222, 18140.59936490052, 93089.76848924746, 9405.436461088679, 92713.16841650377, 11682.963367811904, 5651.401773236275, 92336.13404702852, 22967.422041730024, 98146.32483931116, 16797.80000787213, 51789.298091353034, 4592.061427680316, 22716.539696948246, 1749.9460636172582, 62934.86271622174, 54179.8441013833, 60581.79445027867, 39062.22098298715, 11304.848268979506, 66895.92947005384, 64128.1844423971, 90976.56375204485, 54665.81322701544, 66812.20609970698, 71952.78916486062, 56012.31600353768, 63961.508023906455, 67737.82541411351, 1453.3694337088398, 13695.707591055805, 93291.02937949225, 86905.66514934924, 38252.758617307736, 61528.99036541864, 29401.08023356638, 22290.803770181654, 36740.31830432448, 36681.88584974565, 59728.720266391414, 94804.6791654143, 9644.102361126306]} +{"id": 461, "vector": [48455.63627419506, 3022.918340422198, 38419.02304537721, 21308.885854904038, 72350.57800875022, 49610.76622844718, 18725.923783724684, 24193.057149903085, 98381.54586451681, 14019.13456568068, 52287.00940502312, 23040.466268590586, 68395.33151589421, 10891.504754221793, 50134.90732480924, 4091.3928441351886, 954.8065105262738, 60965.437319809746, 24032.06440954635, 55499.61171315426, 81697.04276199818, 37845.93212463164, 47470.46337406656, 92922.6074432963, 10868.568273646917, 95492.6629085537, 92403.73936560593, 72341.71253793665, 30533.28822964504, 19415.38635808614, 27731.906571405896, 41319.85926608114, 42963.688911414596, 79939.66737449348, 34526.74258867247, 25833.562109051465, 72622.32694591017, 58804.35075207327, 85826.7173508938, 92336.49740470859, 32987.795850603754, 4263.997823187715, 44355.77231037803, 71973.04939333128, 79085.44057617984, 8055.490678522759, 56655.23077179545, 25363.840341093448, 8027.296444976084, 25789.46632937076, 69150.46486379052, 86107.38921690302, 26467.901874707233, 31578.093495338322, 6946.943489764412, 46461.302972158635, 33275.99331304676, 34769.34214924893, 34646.91353320891, 40406.55187252072, 2798.7362223676814, 96335.26636596669, 37979.66332936967, 5427.014812816499, 32994.83760629864, 79987.07127387085, 64065.14290307984, 35597.694284380545, 26899.120211225734, 27485.1621075032, 13189.030526945045, 22500.263942510035, 22283.663645516215, 26448.940024533884, 6652.441308014701, 23479.978612592546, 99393.88818770331, 35129.929128323434, 9659.868903541292, 6441.532565434593, 9556.58494427889, 26613.808594246402, 53551.5493709477, 75915.95248775954, 77260.86989975219, 52459.36695422914, 45926.31329790068, 60399.14429693905, 72946.8057333558, 26323.648851759095, 49264.15710863099, 71941.61069584233, 59860.7909604163, 26367.769815497453, 26041.18233949332, 47963.55048179936, 17853.668143204326, 61970.07343507837, 90774.30679925275, 90709.88470542051, 99755.62633573345, 20678.458538781706, 45104.838695367245, 26852.171178008633, 45474.79883177279, 96180.1704354458, 20300.34036342473, 62428.5702750746, 86853.63362323974, 59172.65388939432, 98238.29439709081, 88227.5631471327, 37384.36193003205, 55945.037572008696, 57621.90799057966, 44637.53191044107, 33559.04981167243, 86017.26203507451, 55585.069519335564, 17791.523674305066, 9922.288809009284, 27173.85363388738, 80387.38915235919, 19804.648448571315, 67394.60281192968, 12244.949680839323, 70269.3859518562, 3268.163876754837]} +{"id": 566, "vector": [11596.88124441569, 58604.4316609148, 43036.59189699744, 57257.412435743194, 62493.368456697026, 60602.91212928922, 69561.33741116563, 29160.458540183034, 72253.0411843612, 7594.42313988774, 19410.68988941712, 1233.317220454111, 60754.412395524756, 2329.5019691516263, 37414.52576373624, 74478.18673111242, 58740.5077970018, 42694.93200500999, 55077.649685881144, 16663.352374658014, 46444.809460100914, 50541.35132796616, 79732.35459659698, 43345.42521914408, 65004.06976245703, 71084.49228282108, 77139.21833996275, 84345.92082599206, 52082.07359558921, 63668.60572991212, 90000.94912690143, 43781.93757776383, 16561.8404465657, 91530.37442737728, 19059.450744151975, 60543.81160017017, 69621.1488647281, 69242.44275833234, 73314.17951112473, 37695.047538918305, 52700.43712781048, 44923.48788486166, 94557.95825941862, 63896.90103233809, 72174.6317906847, 41205.38779167997, 38675.62935588087, 25082.857490800758, 70512.28955345174, 53235.794008768426, 75835.04000460824, 24517.488356125883, 89447.39439000963, 55.6296457908223, 49425.323121677015, 898.6088428008077, 20563.54909420941, 97443.43609701726, 70945.04125447712, 54999.99902976509, 7081.911544751829, 74082.43401245764, 60806.29280180495, 96472.938286845, 86135.60473489686, 22307.45039904738, 44877.412584045436, 50894.28779063236, 63953.81439604011, 39511.012234093214, 47162.89954860358, 64238.7200235406, 98178.41198938093, 89006.7040152996, 57845.34087018643, 91362.26349876178, 62480.1954423988, 69359.12112106838, 12957.368650496404, 32002.82645039112, 6961.418678768528, 74685.45095302093, 5970.075701571232, 12945.928820473473, 85419.60739071069, 64069.87430798978, 36739.10862197313, 93464.76712557254, 79615.99377314643, 87960.3515554349, 89593.1016189603, 118.52051995119295, 92466.62830460671, 31089.26574349965, 88300.18095194388, 99520.3205841668, 75574.67492784567, 37080.82230101027, 96369.79238775632, 75084.00389900552, 8327.406194470566, 21800.631574025563, 1292.320067950925, 76133.85006668766, 47262.62427825285, 24481.945975925122, 20901.016401043005, 33554.74335959442, 32132.334659882534, 59821.37812347045, 8697.32536132839, 31795.427889711314, 8371.804641871016, 15379.68809900443, 28079.965265830087, 1161.1146070193424, 12540.759832481985, 42162.337004916924, 73013.08248577749, 9303.894632543941, 38398.32007386408, 29584.30865468239, 15792.956060381435, 10421.817027891644, 27809.546049841018, 87010.90040842684, 99402.33841808044, 89984.87654185711]} +{"id": 196, "vector": [7486.076259742369, 59730.10866165234, 14498.395502726713, 58272.580408999405, 14740.183758969939, 94339.2504881926, 6714.993173237249, 66881.1647387293, 51049.15061739402, 70530.86311831472, 86414.20516147828, 49114.73963377908, 52858.19300249028, 3164.980092104619, 69581.65009704488, 72444.28444845967, 51596.91137274766, 80805.1826222708, 54334.70823234202, 32480.331618048396, 24693.554153555575, 88415.89338368284, 35940.41774047362, 269.4920298730308, 32738.23108645607, 93792.6446375939, 5253.0490958121345, 58866.13292200746, 35057.99477072155, 883.7212865322352, 45220.25714476407, 64033.13274243948, 40024.53634969607, 28241.040047674505, 89092.61398101258, 39533.20935737914, 19567.91215634881, 16261.023370680028, 88546.14221674093, 52361.28329872455, 12141.277746056523, 83420.05029891082, 79544.84514679747, 97318.26617233582, 41778.58165255185, 67616.53342381497, 47041.75533387804, 18102.101821384487, 31044.130860488607, 80741.73164844414, 86846.57272850687, 11554.17331451477, 16822.59772308131, 25317.58488393171, 43147.201484948, 15947.250736609652, 98926.53103298703, 43495.52013547757, 50002.86516773147, 60770.45196377295, 16488.176116245657, 30442.869682357577, 65690.61058439006, 37539.501301124226, 31701.10246671146, 66250.39772040889, 44381.71288834412, 61432.15427932812, 23549.83230124674, 99029.05323966865, 15176.060275384873, 37674.834441168394, 15754.27726575972, 75510.89272066967, 66665.57125858351, 52534.70969655907, 3849.6986554791056, 41708.62784446908, 11223.865635053731, 58225.47848257905, 10718.980085107833, 46987.986784007, 3957.8640609523986, 18853.731811034835, 14272.341339366702, 14288.217990925079, 7581.716239564873, 78267.36450414528, 90207.89038626257, 10561.28360365015, 92643.52840528675, 44751.17401058991, 41177.874639527035, 31234.05728553964, 51762.0462258319, 40079.35872927552, 94339.98748709551, 6742.252559187412, 70269.16113101694, 56384.45815822387, 74742.08270478272, 69974.960502343, 32260.812038502594, 78197.48752502633, 9218.295704797463, 38593.65788414857, 7102.386021556539, 50503.23708336965, 72799.69526918598, 40568.85235716411, 26345.023155817158, 25042.46439658998, 89178.26456878214, 16638.6934051316, 66152.05677372168, 59300.04498572722, 56678.82950412707, 77564.34190226844, 38290.560615592796, 62669.68618321271, 74201.87541415627, 28022.24950513906, 59162.408353560546, 8183.840957520682, 17823.17787976213, 59750.17284395325, 39718.25332724532, 51448.824645970446]} +{"id": 188, "vector": [99574.9768052108, 97303.91986065244, 93639.137747502, 92883.43573487662, 67761.24186217126, 55373.24592527363, 58968.28284236737, 74724.65478060202, 21590.84439496419, 32529.552624282398, 71309.63793133266, 43605.725067680345, 72209.61971280168, 47784.64604707847, 94556.12265037071, 37191.148631679716, 51187.98761022883, 7086.062429059925, 19716.506823609958, 3875.2479334980603, 73185.99693007754, 73762.6908416201, 16485.084012646712, 34014.979619095495, 96412.11304573031, 20728.280699797364, 98042.39871559321, 60888.37972036897, 44079.66307273108, 24115.758759719552, 50044.33141680724, 39822.44337196903, 98988.11428347931, 47618.133684662665, 87670.50089130903, 87059.29390558899, 52481.98607201544, 64090.679900875904, 97917.2002003943, 40201.86059947418, 34886.71791353901, 92350.65886178536, 77578.90895802886, 82921.8227699599, 61777.53363294265, 14725.034256805713, 55694.223886831765, 99891.13120859589, 15344.995548045727, 24202.230230766465, 25515.762162143918, 36195.53485607724, 23484.783160865452, 82685.15879107936, 2045.5218143692, 30439.373143305747, 69947.39521787211, 40387.5806878917, 88110.41425006732, 21932.581735746004, 59690.95265886876, 3644.641695105755, 79642.75124558153, 52645.01844319063, 35483.7488016162, 34786.3694582638, 12733.686889057972, 45772.555321820444, 42791.2166712357, 37824.727986530226, 35849.6598513656, 79364.04312011927, 24985.53499001108, 81063.62938756388, 21451.25107490975, 72943.26830596775, 63530.37842973307, 45777.70686139455, 87122.48036213158, 86380.71000562624, 81432.74668159481, 58047.994344322404, 73225.32149108914, 6907.666455276296, 58528.57188445672, 7755.68722285408, 52229.09716640858, 50234.807779270144, 20646.655513770875, 6490.347041331656, 94054.3664624159, 11137.769299298317, 72543.97460520462, 14144.444064376316, 20420.164158662978, 70850.99035110894, 51519.332482911996, 2005.018557202598, 15673.996459854556, 94950.94937912881, 7256.478663692312, 50199.56946884269, 31885.53315637932, 81207.39781716726, 59079.58222419937, 60065.14589483355, 47016.320872041804, 60019.650112249336, 72546.48810620465, 74195.155797621, 15436.358785328008, 95830.792381495, 54996.53736423474, 3956.1179369055453, 30811.368289550766, 37851.641168976355, 98601.17484298916, 58991.84705713557, 48014.6419467675, 52461.67540542233, 73066.78804778232, 36527.892508302764, 31869.933906958526, 53917.450339070325, 41231.93672532223, 52648.4901151728, 53357.895396791064, 86870.42725029356]} +{"id": 1354, "vector": [7218.44969408516, 45159.64137611622, 79449.17415527834, 50170.16534109379, 86285.19469006502, 26777.498151936485, 25280.336442583863, 39643.83512173315, 99860.45246503214, 36091.237837461784, 54917.768518537356, 1650.4061946255865, 65219.621609772614, 77486.04307569818, 58310.34867028271, 24420.030944907423, 48296.01340408238, 49391.90505751416, 15440.540624434429, 59829.849080210515, 5178.30803818774, 8134.390013362314, 76208.96353252523, 53502.168701987306, 12889.211779258636, 76615.20650958143, 56363.40382130666, 6172.641397507028, 51913.89242775012, 10702.974246127194, 45700.76371018402, 11116.350035784384, 521.6260113209969, 81083.64675751077, 69662.89258910826, 61422.73137402793, 73160.9687723313, 68544.80182445336, 54172.16871717868, 57788.61419828261, 90548.60889362133, 87253.04169165526, 17735.18926852179, 32066.88367985696, 24125.13404289338, 350.3595984197583, 23519.099888864548, 32386.30393417162, 94482.3335898631, 71665.97475641839, 69441.71086229938, 34182.45584277552, 72494.0494202262, 5201.783561759865, 72832.81943332586, 88741.41467151727, 78050.76205688628, 30942.50106466331, 17891.29521384182, 32220.59908482825, 88646.96830591698, 27356.415474548357, 97783.65386599854, 19732.8089259297, 72764.40354005716, 98671.99492594211, 38943.34460787069, 19365.059734689337, 60816.407455487744, 82435.7461979648, 39223.26963918713, 7565.689293602929, 3050.395726468125, 43533.43629065762, 31865.795037091993, 44330.49355812432, 19970.181778209495, 63313.329297495744, 21183.630039937107, 99078.68892228579, 29168.83160267004, 4110.677435208509, 12755.173634569828, 94151.11912290502, 7593.88170095262, 64746.7165301733, 88980.56568004584, 50366.07867170007, 92236.68132693542, 8194.62709431209, 3074.0303165938785, 59127.30905343434, 62936.01073953592, 18099.620095124414, 35626.48897261447, 8325.378217098578, 99339.49600608932, 83768.5374211996, 56665.196537605414, 95762.03849673441, 13453.871659352923, 97947.777359366, 20468.050679470718, 26534.797383079844, 71710.87607181793, 5660.919799017849, 90064.39473562667, 32181.75451086439, 24007.69420735003, 41169.05612147055, 97516.7519238451, 66566.72236448884, 94779.67517653722, 46441.96623887108, 59356.7051479894, 78624.63377827132, 68606.0742333035, 82437.5423360513, 39067.75114130233, 30338.70748637655, 58098.46066554412, 48854.6029997795, 30411.414942571057, 75212.19447449679, 88758.84856059645, 30163.123604416644, 65257.38376454779, 62357.72391337029]} +{"id": 182, "vector": [90535.94986013269, 93686.6600360115, 13031.03572169254, 70305.95515837413, 93998.04004821314, 58101.49298677992, 18168.45012869869, 59117.37709959618, 40545.39306978567, 52357.00156032861, 66663.73724403774, 68129.6303223842, 38678.85414131566, 90829.56485575765, 67383.48150168883, 58595.57981724869, 73307.91262295454, 29149.446551663692, 32829.79499243195, 95004.25482724879, 65543.81977410697, 8002.472520365301, 42811.42122250573, 88091.11013124966, 12450.697112797172, 37993.94118478863, 29812.929403714737, 45306.82053562099, 17175.18269025463, 69219.88845117217, 83001.74997160764, 62727.23875467665, 83406.44314040749, 18751.595736744188, 25157.185778283296, 43112.42373748059, 75341.30438183881, 55879.90459118274, 69224.55369677836, 46708.457267175494, 41610.46444111383, 36351.31955209858, 20480.44329409695, 59604.563055413586, 99308.93616099856, 94558.56997391739, 11450.725186559297, 3050.442469819803, 79990.79099983515, 86598.81259941007, 39003.08532340311, 69382.7414769849, 65684.47854211248, 87839.58053918173, 35668.891889098166, 55999.57503073189, 92105.9977617917, 33477.9682565678, 39174.16594910399, 65149.594127497076, 33439.49896603885, 14363.6762761576, 51756.604234260114, 47339.26058878926, 55630.32269377715, 17034.08860845901, 92905.37948931046, 25558.02953192907, 56700.543626522434, 35544.33527285243, 22276.163950487848, 69875.3035979573, 85878.4815342405, 85897.46023315395, 87865.06144100851, 9358.370896803302, 57795.864746174295, 62486.99215294956, 41351.43956165863, 64153.57947210013, 28403.644599898882, 48691.05045609028, 58596.44103088026, 61419.12654026114, 22010.58557585963, 36996.82010790426, 66892.8650275325, 53745.374375539046, 17343.79360890229, 15201.763252299028, 47637.026177431195, 23487.78977238364, 28868.72630775269, 37235.41805830898, 8351.569386181134, 37622.93298955428, 13971.511672075532, 57374.94382876505, 34596.47762051852, 19272.849458991892, 56632.967762301436, 3764.696254040756, 85658.88141209513, 75141.36016120005, 26289.034763660435, 45104.66511896259, 77074.18972312317, 2060.4264238898163, 60270.36006832718, 53875.491215857415, 81475.61683550995, 49389.18178488589, 75280.80722410322, 83591.46846684856, 27959.278273276777, 64722.10012763486, 93309.28976410521, 9361.616777663461, 17735.372247929226, 47844.399078767405, 26799.253184239657, 98484.42075769149, 60429.27901990231, 78233.65696166435, 36423.560855439915, 44049.498065570755, 71256.47645401498, 80054.2520372678]} +{"id": 829, "vector": [89125.53130447157, 79985.39953798296, 52962.970177939016, 27744.86138038056, 42176.69005347309, 23691.395056526977, 78531.49457434738, 9086.573282314392, 66665.9857912967, 37261.277745678875, 27729.440374468562, 18594.57343822597, 78865.23078154991, 2994.758756631466, 25991.77200029307, 51972.77638965985, 16417.853611600363, 92592.46469354795, 7151.125854473084, 91690.61598626805, 97456.55679676245, 38702.99686174119, 3322.931611617397, 90661.27598780036, 56871.70072860289, 81866.45900516574, 62474.18022911611, 20307.472569228114, 5274.151250191483, 23324.14704557789, 42159.65298490501, 32989.877185863326, 28049.018135034297, 22404.26881681954, 20972.424395272847, 35435.23783105839, 20043.644621852753, 24438.747387715997, 52576.5234766183, 55138.09651443354, 42099.473343359474, 28684.106165995327, 85712.47074643531, 21783.61225491956, 92490.88061030758, 62019.20978121958, 58166.43987414644, 30725.63701491965, 1767.5588288860733, 6237.80987754563, 43974.3101608828, 69110.2088334122, 70996.7076747733, 1887.1728528856102, 55918.417762950434, 66591.39533101559, 96876.50756400659, 47018.97848018545, 97231.52026166837, 34383.79861014412, 93404.39170888181, 47060.99380693899, 71073.36183326057, 64306.256273000894, 52183.71262100834, 36424.002730788154, 84908.48679148992, 12834.382319651739, 8553.069717351025, 60999.46725503473, 86582.67946930471, 17340.340899106854, 96570.90447190822, 89971.15615829319, 89799.63880684818, 73511.61616607492, 34182.407796161875, 65854.31612625504, 78342.39025736503, 66289.07513796739, 9683.293683536753, 77151.68510224589, 62230.91760679613, 64174.82052453464, 41797.21957717908, 70004.90201303127, 28370.29787699299, 72492.17750535755, 48551.86074551483, 43790.44379161999, 91084.9274541588, 5478.763921897734, 58998.983426096805, 61026.96428657449, 51677.09329191419, 17843.584965371818, 34877.26761584865, 21692.904019138616, 6216.181467845417, 51020.07047748614, 45937.328342696484, 86314.06280280657, 56922.90854081656, 57448.440429213624, 28679.292961576597, 41048.747211652495, 61789.680487865495, 6713.670581764275, 95022.93393423119, 82131.01229194022, 55602.98279055275, 1961.911477207512, 40989.84072993415, 40711.20478653446, 61856.619392160326, 20662.280648656473, 12374.751531758166, 55743.15644659445, 86031.55776861419, 55516.677268249856, 91143.29257138123, 54192.77830480322, 79553.72560499546, 72241.36436396153, 51077.74570842766, 91667.7315194278, 67223.98747632977, 23815.176243153]} +{"id": 495, "vector": [86965.07397312914, 22573.50655608206, 96755.93505114084, 55288.52507676094, 60598.96414890415, 58108.321834535425, 36501.074391657894, 86399.80440513055, 19978.31241782695, 32419.61835097478, 69823.16272141977, 13459.617704880146, 37083.75674716312, 7110.457569491435, 22127.731504814597, 86495.31757980425, 90870.6568300283, 75922.09819706394, 95938.52379540062, 15589.722540104078, 27919.69808975433, 68003.5545203444, 56169.09442706656, 86866.86856814467, 93931.7056745447, 25192.825731432, 30141.457412042073, 30705.473650466185, 70761.0964271256, 14585.328270255783, 70305.57666554443, 86045.52425149069, 74928.1299103136, 26658.458480271995, 57933.666101058436, 15451.045556183673, 97535.8306988159, 88225.18695642208, 48800.6064325368, 33251.06268472613, 34485.31469621236, 85661.20377842362, 11694.996618028963, 22417.545524498528, 77075.19838256798, 97037.89205651115, 44942.387124523695, 57466.82692236654, 29934.16016113739, 69517.60510864585, 67109.16294093248, 18976.456810177344, 7285.396056639248, 21521.137696609738, 63019.57383454465, 36145.343828465615, 66431.76168623581, 77197.05189322364, 59401.47250445272, 75129.187615149, 64485.25478006646, 7666.547357938669, 73851.33340097252, 76309.51834345066, 79903.37534189744, 98169.51621356684, 89127.09673626933, 15323.868155141774, 9594.333303157366, 25002.78276217671, 64628.01148213997, 41886.46495362341, 78723.68348770046, 66060.0251131142, 91189.86081775579, 46886.96529785196, 73498.59479808493, 6858.210102092244, 97659.21070389202, 82627.86799085104, 93518.04778926299, 68695.41178698688, 93974.71134513775, 77120.08326803446, 35485.58893499552, 32596.47172909319, 70207.49965462637, 20906.50178184098, 74256.3391219777, 38902.980954428036, 96306.31560507797, 42626.070605591936, 24326.74761389577, 97593.63497398015, 45778.26028245547, 28581.175820266613, 11682.6121732567, 77536.63120710652, 95667.87872444552, 51614.43852703653, 91321.7006179771, 44990.03598828625, 8528.90277347358, 90973.90857894935, 15462.57373323554, 48061.63261055838, 54227.11155434449, 62993.42526660756, 81340.97489492704, 64574.85155239947, 37433.639822558296, 85620.9977456014, 95295.38385540621, 90871.54073228792, 51602.17069386166, 11653.190522975454, 44811.6805901913, 29034.82449989446, 4834.079151895842, 26361.334683388915, 8671.345530957853, 18912.07141103972, 19904.57057786209, 48529.53572792762, 76239.95867913179, 89126.32918964257, 25078.236905464448, 132.78110454877367]} +{"id": 1960, "vector": [86750.31564088534, 71829.43014866141, 84109.01419634416, 61005.52278735886, 22958.407266600057, 215.68956614440583, 22209.99651202167, 3052.047680197667, 52325.629737942756, 35636.43863276563, 22711.64101960863, 55347.741962478416, 34713.10841847215, 48263.03487755585, 44175.84469511753, 75929.60649677046, 5207.921520398362, 74515.26626570699, 40218.865140930495, 74203.81579222239, 17933.845493221812, 15802.121367549527, 44134.952552088725, 66675.10112606816, 93591.35856299117, 12214.338033474392, 38821.56342556049, 19724.113496177597, 10370.34310832875, 22767.979281227137, 33567.38612115482, 73944.27615284889, 23460.38488726573, 84819.66099287911, 97378.53137653918, 27053.11826856327, 99155.87642436518, 97155.80932180941, 97868.9329183252, 86781.4693554114, 21096.27656770656, 2053.0508992529726, 60435.030084446094, 17886.87084499505, 81764.78522040756, 82717.87333387203, 89963.95542404673, 96291.82097699, 83466.1628223472, 29918.712242104328, 32018.173681721993, 50552.50695529706, 96371.62689396211, 66907.84042750999, 69779.28658311232, 28043.053826234885, 51449.69101984598, 57250.09342911991, 66651.52705200382, 50596.74288870146, 2895.569475216464, 25323.089615143825, 90692.49531814436, 23373.135705162375, 54619.17802526632, 98696.00234771933, 27473.71901216633, 13020.517297776623, 33156.98350488494, 47370.04117250141, 99675.19084866026, 76763.35076343149, 65345.697856932886, 70771.6364787954, 95253.62032617694, 99453.26351001444, 62735.20399326152, 29679.351957435563, 92439.77018989003, 81695.18887337453, 945.4220891610521, 77040.41063953438, 19172.419381058782, 42679.850797344545, 96736.03205301495, 90253.03201160977, 42138.65697886441, 30769.551452297062, 85478.72354321134, 47659.64443133951, 47341.53887335459, 77321.57146552838, 51110.35587441861, 198.59314275557648, 24614.506879815202, 76565.06244892212, 45779.91687073617, 33954.54709708864, 25682.473604243572, 36227.52430279622, 70302.7363984674, 92276.92202949866, 94476.92093715646, 44190.27423703279, 99207.58962214881, 23853.834377679315, 61589.24675433709, 76068.06895033778, 43167.37950932676, 51412.16234651471, 41567.12007282216, 10654.458732906336, 69540.40475134569, 38610.55526515034, 44518.382056664326, 34352.75090123482, 31531.295905852396, 5676.768504285723, 4849.733212964346, 72362.11456413937, 98906.98260750774, 46522.84155645195, 86490.56902016688, 58810.09156070528, 70395.14175147076, 37413.52231437761, 92447.14071663693, 55142.11977006629]} +{"id": 1264, "vector": [25539.771744324757, 75146.68878830469, 31982.295882465995, 90951.55601591725, 49289.821153976976, 94246.25570804771, 498.7756611618876, 98596.59836176778, 15075.78195996393, 43266.88990523777, 13852.299952344716, 79834.31031943034, 28430.562162216633, 66035.93330185908, 37083.53534124645, 39693.56224947026, 88902.62742933296, 38176.24881716927, 26197.550501543155, 46019.39551262525, 44938.30047462196, 28627.82679763144, 6617.232950479057, 25813.608675546508, 24086.18208105936, 20267.13644146657, 60954.04852395109, 89593.27150816427, 86734.64372517925, 37328.91468069274, 95735.29209457705, 28043.15866339452, 40182.27281506862, 71066.91504697254, 41703.73900249382, 18303.940991773958, 86110.45970218125, 59264.71638199179, 55946.720342591085, 35861.54045852053, 44935.27973650845, 19731.03031503548, 62958.693587755806, 14270.201963793383, 57282.76291535067, 88510.39073153763, 5852.900089185564, 79920.87425298491, 73405.22509786545, 52551.06863611223, 93085.43489205618, 3005.9786983845706, 34658.063167409884, 22213.585299253326, 27837.39277000775, 93741.07683204461, 67283.04294151593, 81768.93706848573, 58376.60402570418, 48160.104440237104, 27157.877600781678, 10796.761229666774, 30874.21664719203, 67747.45684167476, 63444.310183127585, 36648.14473348502, 8994.821959880495, 80041.53126153866, 73808.96548920227, 39430.801615622724, 81505.52793605863, 58705.950176223545, 61370.569666405194, 54260.78513945093, 45352.76756179608, 86012.7747703237, 82044.52647738453, 27940.945729397637, 54921.902162582206, 26272.948471709777, 90748.92887222738, 15206.31840551867, 38618.21033831541, 80215.19396994008, 58812.031912997845, 5953.158053205587, 83699.5979667007, 72654.60951285344, 51124.70466927902, 31510.009512718596, 88850.68139224031, 35914.04126501333, 76302.60080477936, 5444.851216587687, 62458.33651191142, 39629.787424029964, 71077.03037979317, 4114.564691602917, 95293.68660958973, 38378.80991089604, 22197.07892974032, 46742.28654208272, 24369.49947969491, 73551.06782844347, 34018.01170317289, 17478.621212077218, 23688.471080112173, 26912.02709254652, 67995.4474554972, 5598.579184749164, 7673.930998933043, 82693.66020085073, 78253.32442534628, 73762.00127590004, 7949.691047957263, 6153.396089533192, 83380.2733865499, 81994.26979582294, 54370.84451476471, 74441.32095023777, 81231.08209115264, 25885.346049279866, 47754.30505034503, 61334.81365182127, 79149.96510508898, 16027.487298619957, 76492.34115348126, 14667.589692228888]} +{"id": 752, "vector": [21073.439468689416, 69930.30350741612, 29284.337268014184, 37442.46200592809, 78877.27309737189, 11278.492471774649, 3955.241970508372, 44634.675993917685, 12109.921910156052, 68086.22094730075, 60245.18043094201, 44167.33562323458, 10178.133792511735, 65003.57865909942, 71917.79678382588, 4308.6813282676385, 50971.526895985146, 30645.657422459983, 35606.24937920494, 38002.761163431234, 44544.0210289696, 46829.15995371354, 5531.336679874965, 3931.8359989779506, 36582.27487987622, 26011.591947233457, 3201.4493762638986, 16840.22778722599, 32465.714755371668, 27190.822054371056, 31102.709396009377, 28935.007979329042, 28223.281283119384, 73134.90039305744, 95491.41894283402, 23498.501014318983, 91887.44688626227, 91511.79227316457, 65215.14267349331, 70737.12096087215, 89137.07490776475, 14323.117157532772, 32550.8525779599, 96827.4186204387, 49078.11222148416, 9426.256670440225, 15564.8449608654, 67872.78236547168, 50817.53411612158, 65451.218864116556, 14668.362001982992, 8356.891723527682, 90170.88180029887, 68206.85468940728, 85970.02136342438, 78599.07042603605, 56957.02251602113, 62270.357446263195, 82694.76750603532, 52951.81601388614, 2828.8451134722113, 47565.47549941371, 9715.149595250738, 10267.564858233014, 53371.85091738465, 89973.1394441722, 35148.11156802272, 29589.687834405544, 31250.330682670836, 6890.306248472178, 2105.9730422050784, 36597.4059895206, 27915.664024874688, 86328.00316237162, 68413.94288383053, 74697.68937583503, 25443.1133254729, 30051.088193116404, 91113.0178573689, 51773.16194975755, 30394.517583528268, 80334.50952667963, 6234.331191226572, 46641.10533406887, 47771.57636901736, 75758.3387411727, 22432.450409445202, 30481.181704311122, 93681.00948118535, 43577.231053345946, 14263.622993820025, 32884.839423822385, 2334.7217183700363, 81340.56746712184, 67988.79016965926, 43605.523342070075, 37827.54155958033, 60808.85015655734, 6956.4985882946685, 55045.75043169757, 68907.5411886202, 87766.00042984328, 69117.73783757842, 99973.97221079831, 44384.35352451865, 49886.473545085464, 49330.88368174704, 48928.38039432754, 85333.63778434564, 98707.8094940157, 73985.5803737662, 28757.65177609114, 51015.13148555006, 88504.85655279805, 9011.33944546232, 74257.71567358279, 60145.74191960702, 17012.845516339083, 81993.96575913519, 10520.619764884786, 18659.094959849564, 66170.33590238, 37282.51783360711, 46425.03727821984, 37958.85699857357, 36582.68311238161, 32363.185834475673, 22697.41944634074]} +{"id": 925, "vector": [78402.8217901924, 9922.483242785485, 91562.54373595589, 48951.41637850975, 53713.49387894778, 13414.03518300508, 1561.915121463542, 4084.3567065897537, 63551.27487778426, 82765.06841007338, 36081.76793036531, 3257.101578359012, 62671.15239312453, 99579.99868514757, 84599.14217975234, 63298.09613995549, 89368.88033773992, 46425.70284250352, 44152.76695589061, 48316.71926148604, 58869.27729774155, 72478.526812123, 55081.59163385745, 41763.08506665217, 89999.51058469179, 48232.91439528704, 1653.7917267288037, 88670.68025451989, 78979.46202631555, 14040.36371117957, 14787.598836692028, 31505.120221047433, 16312.036213472069, 94399.37936093865, 9300.734487826678, 30980.18543546036, 44942.25183861949, 12486.944170453351, 393.1700157651319, 13271.999903770271, 80123.75135021197, 2483.7316453738767, 43493.73158303178, 25427.31734592344, 77651.14748937394, 71648.31505872935, 39109.14110550994, 70964.26714818394, 68955.19372216894, 35625.607875373564, 43139.18707503451, 59279.5392659599, 12445.307194714505, 5665.928644426111, 50283.58717065624, 79512.29540154841, 4268.523793342593, 83765.49516705367, 73508.22747712003, 41688.546805454906, 35521.23207375705, 14250.695764488275, 84112.58211713047, 9276.717076616847, 93695.67150637628, 59164.23524774243, 12698.577488238672, 24558.47258889412, 66491.55833363874, 74377.45543482726, 80855.65664552445, 15467.778660017795, 42484.50849402138, 68852.4575565995, 40939.25902550809, 44764.6136619117, 45524.062848226866, 72075.39638585613, 41073.10625547682, 11705.207627427084, 93709.97472267303, 49945.96735396688, 94655.14896452261, 63224.93481062917, 63883.24541494945, 84485.96857364119, 69966.5132305618, 47043.46943818144, 94521.29381664025, 6552.947245404827, 64466.44313376257, 13085.486559611336, 72592.95985365212, 15648.221337682078, 34544.72056178105, 78524.37080028481, 15488.148045365913, 48343.174293061405, 91997.3688316404, 74034.49010225714, 67264.4660144334, 62614.96814734244, 82969.23175754306, 38869.48283361018, 76729.5271133144, 86969.3419787947, 22870.39049648851, 42434.42662921431, 76429.2283093767, 19410.12712403062, 15193.147513063943, 22757.384360467626, 22335.720547354078, 51705.91372402307, 69722.6471849829, 97138.53051536881, 87704.38452911982, 82066.73310012376, 18345.295434851872, 68330.45719810265, 56659.22935803823, 4039.1047844658633, 14735.353838185949, 66030.05745881738, 19282.96683700528, 34012.056265024745, 91493.9750975555, 91292.79665916231]} +{"id": 1695, "vector": [78763.14455140058, 61729.994151041064, 73493.62431509778, 66457.10441592716, 17096.655161432984, 85059.26821251259, 64529.80130785546, 66122.20084879744, 49079.17754216535, 87724.79505999477, 54369.7983410606, 66832.99577903832, 76268.04245903554, 16238.110631068625, 11363.305461590478, 75489.15357715773, 75476.51515902519, 75226.86348353302, 55023.095265696575, 75802.78964271028, 85362.69528770483, 70253.86890791953, 35593.905746592915, 99609.73377180335, 97553.89289049146, 76239.0145788069, 12178.636334134151, 66396.03738393568, 16628.81451993551, 5449.571575797751, 93153.7699170525, 90976.18528077664, 36660.524702895025, 63514.16564833197, 45661.41205278078, 71704.46274559232, 57191.26947501353, 95929.56621731046, 77894.36545216867, 99709.2212786166, 4879.862128493962, 19013.942195519616, 57094.09538223666, 90002.40885494367, 79191.85237638533, 19694.79682758264, 49250.59876818002, 54665.405052598806, 58943.40050217163, 34737.23838225834, 959.822078398187, 19137.42569945538, 89691.5237913098, 65428.03916785954, 39397.56572451255, 3102.9135525077577, 2651.302018238977, 81602.0098029013, 81849.97537089862, 54221.08645770153, 23945.958586211735, 45585.99130156119, 31362.93687901601, 82094.29566026818, 86756.96139645332, 77073.50752134262, 32984.33821443897, 96236.32797566515, 94959.60210439203, 11769.509279844548, 69780.11844758311, 31122.482059383226, 57353.72014193102, 82490.59676640625, 81308.61654402033, 20176.476653768583, 858.2046207675997, 29910.86562087081, 73460.68678881014, 88040.93620785451, 14804.142701766697, 68645.2242753195, 98031.17644096901, 84895.52702405583, 99490.55444772617, 32071.818776586282, 38508.69994548529, 73117.77277861565, 46048.997435893856, 22599.28595569767, 80853.86736931218, 77955.86649775035, 67416.80129488379, 17799.084305829838, 11243.268977302712, 26836.852253900244, 42034.80289258549, 71669.49548618183, 49649.717600544885, 74331.11506003352, 38709.75850754759, 5426.540167612326, 45480.05174561058, 47139.65657106656, 53838.629083494096, 87431.85210304063, 19807.671245203863, 97434.02979304249, 38751.97215502939, 67428.11231609533, 94016.60411850591, 12733.02644733787, 25020.00670675737, 34841.44146496836, 37795.71616294841, 1073.3217305958776, 6829.915295518763, 74915.57158420277, 70519.25191413432, 72758.49724600955, 58441.38831396981, 75149.6395666768, 50187.4163927614, 19348.27602431496, 31483.131776689832, 5117.428878691666, 3340.309072258285, 44869.297686072576]} +{"id": 376, "vector": [52983.51439023816, 69748.90648126788, 91615.4221810193, 94895.43249546824, 43781.560287924345, 55476.627326650574, 15236.957564927834, 61574.37717047981, 75346.30785043663, 771.9673319448561, 63005.627514907195, 23045.41508239549, 75946.12518947525, 91987.40018649341, 37849.20359238939, 84432.35083212677, 9651.276995716318, 8171.322085442345, 70184.8385045819, 94822.70582095176, 5476.152095448628, 55060.75028045957, 70868.54133754507, 61237.61468981569, 98107.55183455607, 96031.52724200455, 24552.272577150514, 65228.41099293729, 90030.1993998546, 67066.50346767949, 24046.31271509081, 93019.54649728061, 46709.36400696531, 52523.808410636186, 18620.37727882926, 22038.658049153848, 39806.02928347566, 90133.61060866348, 34342.087342690065, 7239.960103539677, 13264.428228612513, 37547.86571143309, 10319.030933606655, 41661.618036446234, 22949.108308776355, 79427.34683573186, 79573.37755240001, 56356.05107643998, 19832.813329494402, 39299.32103997011, 14194.183066844702, 91723.53991254602, 84263.88174509617, 43589.440122230524, 6833.297437636698, 40562.216013006844, 18769.325515813794, 87846.38942793453, 79197.93509791148, 73632.28427330722, 29881.976039201076, 59396.71549972564, 39740.75461713117, 70755.73633834993, 7845.210739480734, 40060.58983988895, 57146.59795048751, 20058.269731743992, 48079.24018515034, 29386.83375657888, 68842.5368242201, 40863.03748298208, 15466.187032383526, 60992.11796403157, 57481.20602897867, 45411.23734314935, 54600.36155363767, 77021.24198295617, 25145.69995491831, 18569.65743086455, 38605.92102506545, 60734.308245372835, 44283.67805384579, 82910.83240963616, 16739.187942143395, 48337.724558171765, 94582.93014685022, 10998.785777261011, 19437.940057526102, 62517.76612575288, 3961.200484521965, 6119.161338341639, 78648.2644917305, 77944.25206632887, 2732.0703655019483, 90045.73538905033, 82530.41741916623, 3488.534089014861, 53952.37836065921, 67879.39697293336, 3832.949842326394, 86475.8116391073, 9421.060794583502, 32617.655946421542, 1084.262384557022, 78177.44918653024, 4074.659944002268, 55742.85934996335, 73501.75274542018, 58127.31680677832, 89802.27502765729, 88961.39468635996, 41925.90542556484, 32826.93503766743, 19276.237566539556, 31598.06129237933, 17221.201748312164, 86551.67794909487, 39593.42847296636, 69288.34025695211, 80743.208667074, 83332.80876570933, 36156.16247185043, 39759.139536980285, 59677.25142375631, 64248.01308758536, 18654.727703144657, 79116.30249232499]} +{"id": 1243, "vector": [14403.15030391237, 22105.433347351987, 3212.035705178973, 62176.539802949934, 40643.55015247414, 44018.31122614477, 61821.27239935427, 8083.927580864669, 61310.50749611964, 39378.07054614158, 99588.17898390355, 16540.98611924384, 3255.2689582739936, 18034.914793952437, 39029.03305657294, 7208.856569433875, 18411.452857754884, 37217.53695420249, 53929.451820809074, 34705.90033976787, 53185.467872658075, 85514.37977167816, 44867.30798380981, 92858.2398724861, 77013.58184697476, 76431.4526389879, 64994.68188296287, 76340.4957188616, 23739.636897575412, 49214.09113369751, 28181.479319927006, 96906.86985573039, 15725.081593406498, 64594.85642487342, 54424.97839152425, 6804.010304008945, 68018.83290557448, 72338.26246084987, 77785.32127529675, 92181.44946370958, 56881.594586283056, 45571.23100563183, 99206.97809508175, 87014.50204121071, 60876.45683379024, 41883.752369792135, 25085.749353573094, 78748.59922358219, 25462.02978546018, 40370.173352522135, 96210.5982159049, 21994.03541456966, 56124.51968618349, 12344.766752696112, 52692.895371644525, 18195.597806507856, 80481.30737728503, 69095.9880186341, 92092.4728880312, 70132.34398346137, 53839.12098965037, 35490.67699736289, 27635.956450586284, 99974.62210687842, 88565.5235885044, 70469.18232123299, 1015.7308292046973, 62658.07728237312, 33088.517186472745, 49502.868938828484, 33655.792826631325, 58966.784386851956, 92451.34169278826, 51408.31926975372, 34010.32694400895, 45323.90186410363, 67903.57232610113, 12528.406155652905, 74699.97687268036, 28703.538361484647, 53849.50531369304, 54949.27672305269, 99791.5668826693, 42836.11598816796, 17201.932149720644, 8877.912637551932, 45135.36068947567, 16903.104594164408, 48872.3569017809, 47083.46255261815, 55918.211324410426, 80990.84981695419, 17721.83599115409, 38263.66712286915, 883.9758309941769, 23548.890237860443, 77829.5426711654, 81770.72239366567, 95061.98244229014, 11891.025496132912, 28759.24153277768, 15375.054325799176, 41362.59947542804, 41625.67612351621, 76791.22323824263, 79067.72855553543, 43277.453339609005, 20432.964396390573, 75495.892656137, 82234.27380621014, 17741.98663101826, 18555.96200769164, 86876.49918506102, 61509.04493280448, 14679.552239807559, 82767.41151210816, 91340.74073870407, 57051.930677168326, 14552.430636887591, 3420.7184518191493, 52859.366679662424, 34755.388061638456, 11312.317855353836, 87887.63613073545, 41444.72630825992, 28822.426630739938, 16603.938066953582, 36832.70662877581]} +{"id": 691, "vector": [22978.879973216637, 15357.383097454536, 79281.77968720847, 93496.34863647055, 85380.87748938153, 67139.02592595536, 55078.931897844064, 67667.56179152103, 91994.25888264325, 65999.99423295085, 61476.77224570346, 49281.108906304835, 82860.24186563818, 91999.48620081187, 91999.93351687863, 65991.73209183941, 87182.05778720895, 6072.313504324667, 64349.356305692585, 28190.866422886684, 83400.8014650178, 24947.125817254688, 17213.20431379432, 80652.28333976971, 17343.018222071005, 25683.413835164614, 37010.09079793931, 41318.14409284507, 26173.14401199632, 69755.97993640286, 18468.226823228073, 52492.10579477773, 3082.063227960241, 60681.77856769674, 99410.31963474743, 53696.12871195684, 65927.78355975852, 44553.35114178707, 45729.97284743341, 80560.09408484692, 71245.14261981004, 65742.63171040687, 23528.90188412208, 51098.9452220747, 77701.77205950802, 52893.616424415246, 10535.54846749386, 32.78489638685045, 51167.35330603817, 44026.36456263199, 68577.63893490918, 21896.10106205161, 60051.65213021002, 83236.13327897368, 40436.61777230976, 67719.88730288672, 83459.98938895152, 65461.33155009131, 94393.74576488958, 51774.4777857453, 24154.057954967946, 24411.322629843013, 72717.66040745018, 41686.72265008223, 83273.46247058109, 89890.1979437877, 82696.11054385491, 93481.0843090645, 45296.5317795408, 23244.186902526442, 12836.900685696495, 80993.20452066459, 10637.737859205654, 46525.24133417197, 37037.0068664074, 15592.653986973648, 7807.398156008249, 13578.18444720037, 58387.65052926351, 79295.26865845849, 14806.116642466905, 33177.93814621877, 13307.544203537258, 14098.827896839572, 46218.03728218455, 98809.9510651164, 68667.36262801324, 24563.812154229614, 69958.00484414099, 40345.62338864447, 51450.990888564585, 19929.358719003132, 21399.017085714568, 76278.95037513036, 22695.0644775586, 53928.80699982442, 14666.040339915531, 97524.37352540159, 24488.446036316658, 96862.91171707495, 2968.892642741061, 47152.69933348938, 7664.260598786354, 80654.92252941041, 32799.6714614604, 5280.84685746214, 59413.65327893776, 75530.69469098462, 89800.3278863698, 30891.839385420673, 68370.8801167292, 52308.988354797235, 30620.779763160823, 221.05620700993978, 93289.07570385854, 64428.92213287517, 69636.61783308425, 4026.408506504542, 77699.7075293864, 85984.94064758727, 68776.01955117717, 70923.46990070304, 11703.5582666406, 93032.58995851791, 76157.2897841761, 63003.5945483353, 77483.81504810802, 15088.22334931299]} +{"id": 1724, "vector": [96560.95091502991, 56301.59274562364, 71169.40980561159, 18604.24349717632, 23860.51996028441, 74899.94787524598, 88900.41620441499, 90049.0398912815, 8461.122894413487, 50398.42309619068, 47418.5363282699, 66281.14324301401, 44134.94019275776, 33589.529243318895, 49334.253490404626, 6522.4972772922165, 91752.23613983889, 76949.10059603078, 14865.271254789037, 13806.294881753722, 86719.59513538267, 41884.78031361135, 64523.05345690895, 94904.57744993843, 83663.25511934856, 35227.39631681286, 2058.0079703831243, 84274.94667554453, 75663.31124031181, 62890.68800347468, 74958.69234964706, 70004.40900888323, 3081.9292802877762, 20572.982926706474, 50766.52859222802, 98840.7882896717, 46003.900122525796, 99580.23673335469, 34056.04431475052, 82268.67454247203, 57227.62179625926, 53730.40824480301, 18544.075435140316, 3523.72897889891, 31596.458549089923, 57398.5265904344, 34942.258359667576, 2231.1658543171075, 39198.66568736973, 37109.44232349589, 57262.31986449605, 65068.01905869876, 34327.957584776945, 87652.23693376686, 71197.53057629016, 42821.920850901915, 68100.82298923335, 90268.29647990549, 24841.2365258174, 58200.74517138582, 70970.89463164414, 36264.881084619716, 39230.77911826348, 43234.26257268049, 36765.827154495746, 81438.28235123954, 42234.5542902523, 99116.84350165857, 43266.799277954524, 35235.86753228731, 17302.989940545387, 83916.50585740538, 7901.066701380888, 50492.81880155859, 11353.145316376067, 84460.92801717418, 27691.903983595857, 30581.28645371533, 49157.081899965604, 87788.36233646823, 57954.46615309487, 48269.851127320166, 49977.229611720366, 55509.38311240285, 51167.32149090293, 41175.54060334737, 32644.963425153263, 10622.325586030402, 66201.92983552837, 72809.06628751002, 42772.165451652334, 29836.90347298693, 52287.381914507, 30778.667159781515, 16004.790837078543, 69219.94761072655, 44515.836321058865, 70962.35449602705, 80542.98054664151, 36629.56624483495, 66031.47097875807, 33242.960962751575, 37821.52890378314, 56350.215114645674, 47852.80860294781, 71289.39248750218, 15885.525099561237, 24457.45764364572, 85105.16151617799, 20674.630538682635, 62849.82423142976, 24424.234803990807, 66962.48803091387, 26711.276968858, 79692.63576334243, 44883.882193666104, 68891.81246823138, 14114.059812103342, 50504.98396128246, 76313.72118614386, 31689.221902824305, 75451.36406685531, 49433.96100032694, 45306.647509739305, 12709.160479561731, 33411.62166385415, 26837.887963816574, 82055.09991495236]} +{"id": 1980, "vector": [55773.1350826222, 33440.284261594155, 76425.12421670703, 91926.83941558341, 10751.22053765405, 7336.997536179102, 18883.04181848037, 23187.87733558141, 18590.7514736386, 58482.30829441988, 95095.11333676316, 8913.344345718888, 87074.32098744657, 50036.359685412455, 20557.829516416103, 60537.76187750565, 8722.760861810164, 51500.85004186815, 16883.72045542221, 49574.72045098318, 51282.00384849122, 97533.2947786958, 23953.329146126267, 29091.396821235106, 11511.506747398658, 40657.60355861981, 27384.52394543751, 72352.66065697443, 2799.7758414564864, 4548.998339768329, 31650.42834009474, 12865.482910364357, 15670.664382565481, 19568.738421926402, 73493.33839797325, 13853.68395391291, 78097.18837458978, 13451.637287019936, 41944.6281070972, 16803.735544087027, 82647.82464135259, 17976.78752882137, 20864.241763838763, 73438.12077378033, 42418.851180264486, 39119.79604940179, 30699.282283707973, 84161.11645221876, 53552.68927706376, 55132.8444340072, 42301.67655837721, 64580.17299411345, 32570.596888198965, 48924.19986326351, 89787.61188683331, 63413.751223754836, 14994.022049097744, 49584.48137303271, 16696.274326266048, 41859.51176606058, 643.7486691059923, 25837.765402760728, 28433.231924865744, 11924.789459663876, 85457.93068586476, 59678.9420891554, 81122.2442260931, 87306.58839722964, 57652.187965676814, 51989.11602035271, 73867.88549711561, 76824.5263193595, 59112.7524252394, 98340.49597403109, 66502.11456946288, 89427.50278990318, 23814.00982886429, 33269.756156754025, 45980.49422700292, 70059.14906551692, 79511.15498282542, 7976.402772501235, 71322.01938110402, 95957.40774900788, 43661.22306652796, 33678.327290777954, 80908.15801896776, 3940.3518028833905, 42210.67529961274, 30426.34235438766, 84352.1964776613, 26123.34291301658, 78553.80025374846, 94556.08690112812, 58401.472944673835, 39091.579768728116, 64946.656004569915, 88154.56191793788, 85287.51596963206, 73250.07546368471, 26851.775157187618, 56200.168870745445, 39750.114312922015, 22600.87292841173, 32186.17361065289, 58913.6653668908, 98203.7258960859, 41194.402242465345, 14273.860534011996, 77978.17662184597, 63196.430787467725, 6874.124300007945, 40248.49560644805, 71260.63650818459, 29532.103906500386, 34185.329767141884, 10124.049576703454, 82072.18232667017, 37933.432447244, 66656.30819405013, 80252.92828338098, 536.5414664860957, 13002.456526090178, 69835.44519227491, 12388.673491322867, 13956.929084207304, 3734.0837123822744, 59354.682706691434]} +{"id": 1690, "vector": [82376.1492512955, 95168.8875961675, 6457.772338523826, 18963.851683740908, 13541.128126476653, 68433.84739044314, 69317.92713279299, 53917.11723712869, 99793.49898514645, 4900.155581185161, 21790.525191360855, 7697.101855319855, 95185.36843362823, 41212.68769868268, 18405.302072905604, 4250.870194368206, 44916.22591526322, 16366.292615212808, 80315.94648697057, 21260.358445562044, 41739.60835454573, 86685.0766789074, 94850.72408398487, 82252.0803457803, 15887.32092938524, 48043.58400010233, 59116.274639933654, 49625.161147691986, 35644.87665465286, 84955.02369901614, 17352.958772404458, 26705.798410301897, 86783.58592613493, 1591.6328868019637, 83415.26400934892, 18803.961600866736, 47311.98178576951, 3173.5572705993254, 22763.718494258344, 33887.02766883928, 95291.96260757571, 58924.98282751459, 36414.437397825706, 51893.54476320641, 45090.50068304794, 65843.93949109098, 69857.07533146278, 3260.99734235209, 76425.38282939303, 46456.0465777534, 26823.214942707553, 13391.469700546055, 37059.133868621, 30687.21083223126, 36235.99751174077, 53838.960034567026, 75526.48807608089, 56396.31246580088, 64387.8692438512, 77937.61991233584, 83054.82795560057, 8936.248102243106, 76091.19891859485, 41158.76092987688, 38066.31060289665, 93323.09371966105, 7358.607528322469, 93300.25647203051, 39843.268071774575, 6882.45877063941, 80972.24480313245, 84752.37646003139, 25106.280749225694, 25126.948498774636, 46543.67336778901, 11040.425098445605, 36313.902712401905, 64148.42293646044, 92592.71935469864, 4878.556822963642, 64024.93946240335, 65478.96796481948, 55260.897322272795, 24860.02451080521, 77359.79852851917, 66608.4461536551, 17376.83572813764, 87268.06707517501, 20481.638019803362, 68007.5523699815, 1050.5465351254961, 17413.89070870897, 24117.985890411263, 90247.43350488856, 55938.684806522375, 8034.729800198082, 42860.36448549514, 46588.27365679996, 8702.528569183798, 92430.75341633716, 28722.941500850917, 60849.398179495016, 54757.324041226, 39191.451066403446, 11615.32389257709, 33675.98253202402, 28429.058229181257, 11809.042580627127, 95912.31276434925, 66599.90975295899, 98003.97257306092, 22682.553200079004, 72017.43425123843, 33174.16061763905, 52068.37780411087, 78403.37473765848, 68697.6967191147, 84644.43171099995, 70974.36330581162, 70815.4896236138, 50702.680436593226, 23601.184634381778, 21041.247997069702, 3550.5641356565643, 3278.1524008781757, 99987.12120596315, 20470.4255076288, 62110.50908556467]} +{"id": 817, "vector": [95456.65842292676, 46647.46334392917, 24591.66819650036, 96507.08257298217, 60505.00453858053, 47299.477578739155, 560.1084777459909, 63059.430959699734, 91242.00483096081, 40097.79991203501, 28235.657459182574, 93598.25876415453, 31362.47943422905, 98237.42631451819, 97050.2369090161, 17543.012230910903, 70790.9086035908, 88144.48028684368, 75869.12475082939, 48996.53473515176, 38006.77146766954, 29649.129750304583, 12756.614529528899, 84199.05247652064, 15723.220332391564, 23127.036244191135, 94628.40701182531, 44445.93860486481, 98805.2294182127, 46919.38706691187, 82221.01974028077, 75834.15222350853, 44605.13167480281, 71841.19448204816, 79092.7715317475, 27808.233983108654, 94234.2903753157, 32300.35371116624, 47167.51501236851, 19419.634234230885, 6661.365597247815, 45245.76751495813, 17920.979545955495, 65698.11438836758, 85126.87621015105, 48382.32446446058, 14396.729749243197, 56617.908801521786, 26460.272576194187, 29352.91704151303, 4388.743586388433, 79319.17341998544, 31125.605580234427, 25091.577345040394, 75055.57479987714, 44197.66428646076, 41464.77686106511, 62496.03045817399, 43573.525334121085, 47258.70719101209, 81234.98689592209, 9555.783347596458, 15712.992376183776, 1531.61146510008, 83820.97568207548, 45679.54313217122, 88925.11014907894, 54946.36750180471, 49155.61199259644, 63609.15878533708, 89615.40400503237, 30841.645745704038, 67981.25095640504, 12992.565404161827, 73121.815635045, 19174.207475881733, 8302.750997092246, 66082.4272081044, 49513.95515707821, 20365.225892016526, 67980.27906605009, 45545.44657512467, 48358.460210446705, 41146.8058514906, 70195.94186421829, 48050.58323082928, 21946.518015297857, 54608.058667855206, 46383.75964835444, 61419.204380241, 15003.055296013501, 71598.71466741922, 8043.577877449903, 59869.6810972523, 35950.164205229754, 37619.786166053134, 87792.7368695153, 22114.80250162039, 39006.20618648336, 13319.775909350074, 67807.1059575001, 15793.19155520682, 44290.27236401625, 87569.52390598247, 93631.79389313194, 7162.321641951874, 72848.58110099276, 37364.25460232981, 66853.22629496135, 73554.81687888197, 3179.0615678660374, 42060.60360654222, 32188.938203827543, 11855.48495033909, 16394.019095487678, 89434.41494302465, 52837.2184992144, 97843.4078416223, 43179.80499424419, 31625.268125897877, 38047.24021850374, 62916.71053176148, 74738.88167065437, 54807.731356047785, 25761.790762615343, 16090.523994134497, 64514.137757785815, 75711.01255419258]} +{"id": 1689, "vector": [4779.985457349922, 11890.112994757863, 60359.62250190571, 57742.31989525967, 15097.458354036542, 40072.36101341869, 16196.484742796036, 36198.076202786535, 89037.84250701989, 99570.43330583061, 43374.4740453884, 17187.51846608868, 20625.645648741865, 63996.71995736297, 33633.09626719859, 77896.1311680573, 86547.37319085962, 81257.11959847284, 20323.444026897185, 98991.23815804707, 51984.1923049705, 10613.126260064166, 55462.49308487818, 58777.453179239594, 78050.71431313171, 65098.31516947808, 505.519201265614, 37825.05105722489, 49583.1585249511, 36630.46245653029, 50102.083474682426, 8877.111259360914, 28118.462598553306, 91166.51046946438, 29796.590214809083, 72868.10784790717, 99725.80182103845, 62519.975457484135, 16231.674637257409, 59394.170361340584, 68051.04914850224, 12203.41938591839, 19192.13153918259, 70483.75916559422, 11311.588207654322, 48933.80853568908, 43852.26624444034, 29240.35408037907, 91629.10375261893, 72286.85899231596, 73753.65747644409, 6060.223832672728, 52039.40167542634, 13318.356446740952, 86196.94964919084, 87613.51030696742, 97016.48262939387, 1961.7478569003642, 44652.720704731284, 96445.2435285927, 78731.95487648212, 46124.29618271872, 4239.530723352569, 65575.13181623703, 62945.674394768146, 76559.36645716098, 58599.76739133196, 99436.62658978849, 54207.05949626557, 42980.9352713448, 77049.24373178078, 47488.241964938585, 11223.662195115669, 50471.332036138374, 70172.28090749866, 60596.999337289184, 59915.58927826594, 31352.39438769194, 39072.13488370116, 36244.37836240659, 84864.84608963295, 86803.22639259104, 52778.748685201026, 62001.65110768978, 3291.956448190736, 78493.71209793811, 87844.39095385393, 39515.55914312453, 76758.88728716667, 40615.31798710366, 74021.26158734514, 82257.79292690392, 13417.589399593577, 28451.096777689032, 51327.97638573532, 49427.72814545425, 96408.78086827994, 25334.24388297346, 42723.629044673675, 72798.10603759432, 6664.028274876943, 71685.42415158202, 72307.52536390818, 48092.18637692694, 66908.37405015362, 65298.41135404347, 75322.42815626108, 38141.83951088934, 96136.21150226198, 4057.1701387558833, 82694.7070937217, 97031.92064639878, 80108.04572280096, 97176.2633537659, 31093.796072586378, 50006.666068098115, 60323.758372146185, 1484.8154951194736, 10190.194178973155, 95274.98235485608, 4505.1910489864895, 94751.80858436413, 60220.33040813875, 69159.51932973252, 48566.88592021592, 66351.36764810531, 49014.26410487111, 20393.182749534255]} +{"id": 753, "vector": [32875.92478256415, 66886.68942199904, 83249.15522043461, 83396.79921115912, 78329.76242183258, 34678.89016980282, 26906.591389135236, 2760.1633620436437, 75859.84457392985, 80600.58515046425, 46721.44236933825, 7141.042554705013, 29926.807775841557, 44240.95559101942, 40597.282486222, 83592.69364719685, 14438.5975659152, 97761.12773916488, 30358.856127616596, 42027.00254926207, 80546.76979209008, 94785.92960570865, 35408.427001743235, 47923.76167059493, 93719.94147232667, 89677.25478914769, 81811.22953361459, 22338.671575930024, 95869.83215882392, 52919.521771267995, 51237.65950344631, 49272.688756171854, 61023.7782684927, 40153.76504055764, 79302.26709288248, 88181.52498343747, 51861.29222158899, 33337.19006147595, 89323.52347358232, 4511.483669641947, 69201.23262982213, 83630.2193175822, 42806.46984030846, 72257.94779257194, 85968.48736153371, 84060.7187644388, 52258.90995116136, 94595.46532889477, 64497.4052392845, 15438.150587588729, 60647.86952965881, 33303.04325800131, 42981.60173611979, 73039.23615061812, 13110.066262979735, 43508.52377304095, 32788.2008455322, 93330.98387863448, 25240.438885046544, 96077.66187714288, 66774.42503134104, 60175.23252787772, 35077.890399979915, 42497.89082332777, 53549.54773805951, 15530.044002206645, 47208.62144295591, 28666.186392799453, 58427.76924217352, 17828.76213853387, 63609.477823570516, 49722.278054643546, 90042.98262020241, 39919.75345552102, 9793.500601640537, 31090.762416889484, 81325.2352333485, 37360.49764823591, 64609.31209579239, 63976.08592712495, 41746.44844517269, 46383.299755195185, 45501.65030800289, 56373.10023859895, 3217.706917819063, 27760.811605704737, 9111.671375775642, 24808.02397324422, 94178.71712980582, 71347.99328657285, 93189.7245711404, 56595.09735355911, 81306.90135604293, 34630.88498194495, 67946.09926911246, 93641.88250166755, 77410.30521716666, 86756.47565616862, 68903.96488100498, 50664.325611233005, 53186.50566696194, 63455.45337048658, 82222.31210481179, 56131.42456393462, 97372.46207122551, 75171.59378351728, 53494.556894319445, 8148.540660061066, 75966.66074335066, 53009.10141567471, 6723.712802786897, 36363.418261649815, 52857.5550631314, 56059.73860456358, 75137.4308343062, 13848.75419028091, 36017.65306699635, 76747.19879245207, 68688.80576751805, 9744.746751428735, 34730.670792343946, 18029.739339671345, 39347.120215837174, 36506.98805585079, 33467.95414284891, 44857.3040827428, 24770.871107286297, 85989.76569802749]} +{"id": 1579, "vector": [28780.39063391318, 30617.724582191386, 53993.7072188228, 18284.920696942896, 60580.32334190598, 12330.241552664611, 80491.26914841786, 34723.638558853774, 85647.66694655501, 48253.936902380134, 80594.2612535669, 98661.1967691369, 83511.15474106128, 92327.80883727386, 96342.87349517662, 31719.590171904445, 45264.280436730754, 11865.908892982003, 85848.73686675586, 92758.16663314996, 63002.181229763475, 74682.77666963234, 86321.31987671345, 71022.57446830808, 68695.76990873256, 30887.26281557016, 73193.57893160415, 4354.388035222423, 20750.12887431258, 23825.259810137977, 77220.81795832163, 28326.78746450986, 8575.791229042972, 14094.266889099848, 72732.31244049812, 76668.21890016191, 67854.47725693983, 90130.04411499389, 60282.87015469999, 53088.976751906135, 74207.2898597693, 20857.410877947936, 90263.9329775764, 17420.189296031498, 62597.13969887393, 68335.4810640524, 7212.7248790257445, 80290.83178114766, 99731.72568367117, 12700.332821517235, 94223.98164276412, 82226.09935150456, 65194.65711974764, 46157.26916436601, 78736.14468975998, 25484.785657989585, 66269.2567279457, 35758.90560062005, 87837.69619125564, 92455.77097577481, 10170.840612235732, 14431.470050993044, 86907.3298346298, 3143.4805337524076, 91582.1554342394, 39793.78986577953, 6907.063793608481, 62213.57494877797, 44661.36423305111, 6237.019706737201, 51090.56459350709, 73008.24343851971, 76032.27395216594, 80804.82055919516, 84946.99958756556, 3240.160164077055, 38319.5337114342, 50438.1728831147, 86480.5185634736, 76132.69937088751, 36224.511983299024, 91805.78269541418, 22949.21180397471, 68548.77505151402, 37377.69100089732, 36004.30427485155, 81205.67842319963, 48452.131664339824, 65381.68491460933, 99975.0890054676, 37129.345961393265, 42470.47053645193, 5088.8653132210475, 91245.6329904799, 6701.689803177491, 3535.228527044665, 38319.69525576051, 22460.14816666709, 81436.02810890977, 40141.274977971574, 55612.15610266655, 3504.6574814897813, 14672.74533091395, 99569.4901228972, 16429.54361394997, 87122.16596848, 5697.458032337788, 27728.22667972851, 5238.173604694374, 81587.4406365771, 20579.320584512494, 92611.56036987294, 15217.139628362564, 69825.22730200957, 34286.40806037283, 80497.75621493068, 22732.077556755736, 16624.828351660904, 31092.856867839157, 25565.598647911323, 3450.1720790610093, 66249.12210089597, 70539.35927244484, 65764.13055235267, 71644.5503146153, 67099.92311291352, 65619.41831336054, 72710.97886920074]} +{"id": 2106, "vector": [89365.97610690338, 64757.73946830344, 24153.58610081163, 66659.66243545737, 87560.26666663382, 57535.66814327909, 66808.7773591618, 80616.20887350684, 30515.899577622742, 86177.09747376856, 8891.673745162365, 87252.67116577059, 84179.58377977097, 68420.1635727129, 83659.4140854452, 57874.68511050132, 15027.213712450572, 50691.09003602315, 68663.86233962816, 16239.362765406773, 89374.9266926974, 17993.654245077818, 1470.42282646338, 68649.90985200205, 76858.08253125793, 97065.01072491573, 81921.99947614875, 91347.25836855781, 8792.655840365682, 29141.189463174745, 70453.5428354785, 76830.31301049095, 82529.82720063362, 95746.22001137276, 72504.48220148565, 69303.84124957111, 24684.921497291743, 55951.138387478815, 3428.7900569534236, 81534.10314485562, 47040.09402182164, 15439.097630255439, 80680.13503513054, 61261.23884860395, 74777.97211354692, 3951.3771804132625, 5715.961414679927, 78607.27703978587, 79487.1889911069, 3458.573840926804, 47567.184348714465, 36135.207944416936, 55339.39220609455, 15428.264214698529, 40884.4591221448, 94984.99208878128, 22568.682508094338, 94249.60364507446, 86234.4025920297, 64306.008134619195, 40133.888490301, 39927.04854024007, 28690.074052951488, 7304.740619481798, 87901.33361003491, 62102.15997282015, 90921.301382764, 74450.58520324159, 80632.79499488162, 70776.95049931757, 22024.067260337866, 61955.127017408355, 81267.07185387787, 21325.711659938872, 13813.679962973336, 46649.64618150984, 26474.27214017346, 792.9982068696106, 98340.08239820017, 94641.82801662986, 49999.17137047245, 87547.71083980742, 68724.48351671005, 80421.60563703573, 43891.063258592934, 70599.71615706656, 93286.77374015193, 50733.78558848234, 50515.62085664526, 28504.675296398298, 53851.93450580376, 51326.36262078469, 23176.717236303, 27253.666595584204, 66953.3111812254, 89060.89824982999, 65124.688894556115, 54850.41305273337, 85511.79847395045, 26270.224427318335, 56076.25350742923, 31327.476297373523, 46489.48682080907, 41002.51600067334, 4346.083043750859, 2258.0588607662435, 92140.39655347216, 34491.3235441051, 74169.1218010526, 74081.54198936757, 53612.836355505075, 20691.264553137466, 33687.096167124655, 68671.88318369574, 99157.38100539053, 86955.50933243021, 89423.08037506844, 74224.58532817627, 91767.17554745986, 8674.228353816616, 59425.16048257286, 22258.539204852124, 20432.422004576623, 93211.11781716443, 30289.436650889613, 86523.11870073125, 69886.2931652694, 2696.1010570927256]} +{"id": 667, "vector": [67347.16401307448, 36700.704407256315, 55558.18424261916, 36859.3618902211, 1886.2138153619146, 4648.438157495116, 10883.010978616137, 64003.86871701379, 89260.1827069014, 85113.22545165096, 15631.889553664147, 81156.71988476445, 24814.279167572608, 93221.33309973747, 64858.95580080123, 34710.4963173266, 5130.858888443157, 15781.30507130282, 33636.266830697845, 37422.728903207026, 29527.428298376435, 78985.66010092168, 60232.55186510388, 56237.91744598791, 9642.34330207887, 80828.72834033679, 70220.13767012929, 83426.33605263372, 8656.453725610258, 2835.7613439996876, 26.1894576845334, 23355.557638636405, 6528.071111609157, 62394.390219796805, 9320.707956655406, 66643.60628645546, 53618.86918230407, 26339.07650978885, 23418.660796533575, 91930.75337757506, 5649.897656349923, 41555.13707948037, 4687.139322004785, 34506.92129402117, 38538.85264592068, 42894.89806407118, 8202.457213430036, 21655.612430111203, 105.48390198876767, 95913.16123815531, 22550.327910114633, 46382.50856955544, 98575.42502809431, 25971.578235942507, 84666.7101252146, 50793.7171506513, 77259.12915586702, 88822.04537312314, 91385.26032327475, 14405.327419700765, 23728.400725642074, 30547.739973100674, 47120.79509718001, 55657.34342893828, 11930.786841129615, 45842.899045820406, 3599.7412683895445, 53205.46813849212, 80911.41607392518, 47497.52684806523, 98519.76354208266, 39792.80106831669, 52198.08475580108, 91523.43013989639, 36701.687113246284, 59128.162980450004, 55377.8196028661, 25736.0930380909, 16532.69273746186, 86510.29135890286, 91345.41083819068, 55979.44904601624, 95150.58977257997, 65542.62096235022, 32420.787790435734, 5471.648441226828, 9923.242376360975, 39136.683136797365, 98683.47293006374, 37161.72316248243, 86184.87854785219, 10171.791425816067, 47140.24671934025, 78860.08363908889, 99622.07113478163, 48123.70588494197, 6332.198929884925, 49704.00976095218, 45369.84479995348, 3988.689725334782, 41856.19623928125, 75374.91319832436, 28876.580985003453, 86294.59245919848, 88321.37891631649, 357.5782768224234, 91964.74782749699, 53948.69547582252, 3285.8837439398326, 77076.98124326536, 14172.981820405861, 83918.04760167669, 13375.320430618609, 3398.350185578591, 87808.37759420958, 32388.55464524075, 8237.755152895032, 68261.60827106748, 50306.628196558864, 30046.560087193964, 11290.860933271806, 63225.01860815516, 1799.145978520511, 43720.03622333424, 81673.33922176536, 36929.62653366388, 82573.3351366262, 94984.4898589321]} +{"id": 1683, "vector": [56898.72897379705, 92235.87116213645, 42148.77378611861, 73129.91885354035, 95887.24277442253, 2219.1399208848716, 9411.384262426947, 90202.26766108819, 3333.9515793968876, 87818.38032375503, 735.5988482856457, 81694.64695945085, 37001.69266398365, 63180.81267919645, 36017.4201489459, 28618.925894613512, 71755.24776474849, 50420.20878752528, 30390.944721285417, 66898.65179616737, 81058.81285553296, 11210.686948454484, 7952.851613459977, 6146.087007892298, 81480.07466155053, 74261.47615978758, 65642.26101417509, 79987.58743905276, 75714.34778787642, 51040.36146911182, 25769.13600632641, 56400.06436388787, 59459.307197300106, 80766.98803034147, 24386.31346783526, 31511.44547531274, 91368.22814692717, 49865.132041894125, 3225.0912791181418, 50794.084603750685, 72564.80436030882, 12907.86344046897, 56617.340480447376, 59713.04352517277, 61454.307452465975, 77977.45233172122, 10304.832059082892, 83684.18964791328, 43547.19189837085, 7018.952775363352, 17731.455864601998, 61029.95690063093, 66255.16058500913, 73837.96730954412, 38965.24221781238, 62181.84600731835, 97570.17608860225, 6165.868327761826, 80886.12898661298, 17548.457084897105, 38694.076271006474, 87230.95710211528, 99304.5278425819, 16287.794472894657, 73039.4315967484, 49174.8769865321, 26015.152230656124, 49467.445200156864, 80278.70243956018, 24797.453234279088, 21619.500319602124, 28963.343891902605, 76378.37501246636, 61739.476213172886, 544.6694179670475, 2446.2110067152375, 26730.238697696164, 72713.02751544352, 58740.47522575615, 95636.56654489718, 35811.107392666694, 81877.80584154117, 23908.363967830493, 32217.60186171585, 115.70281195516507, 57223.156063329974, 15280.139454042108, 31277.419051223053, 75887.49755865839, 99323.07840534249, 47155.04418674473, 59343.326722736754, 45967.70788529106, 20035.047214179434, 62739.17628182269, 64772.36654968977, 97609.11022622262, 79380.20627189522, 61552.2163078437, 68784.43227901908, 61903.473260955936, 31851.83329965734, 81176.0666911839, 92352.58287724484, 13040.172368120018, 58764.32217812666, 53309.04493766956, 23408.143785724267, 53219.2925375876, 57816.06446180714, 88141.13622039753, 22271.720310070596, 64226.454717266235, 91054.50491211165, 19547.565781275578, 63070.20671234017, 15468.93742624016, 92450.33643286687, 23639.522809560796, 34942.11023842617, 42027.996925361454, 51246.65706308221, 89651.72243564803, 29740.969606114853, 65098.37214131631, 52477.68328971449, 75808.02098224593, 9578.43340566813]} +{"id": 1859, "vector": [83592.82077175305, 67741.7331025484, 59791.34923228849, 24335.324067638565, 8524.389989283254, 84767.98794894952, 86627.72920010563, 47116.50160886354, 50389.6046032242, 50049.55312064494, 39687.609001487624, 63606.794125968234, 76307.53623669606, 4046.7150894400893, 4645.072349609547, 96603.7989157825, 33802.50087391264, 65828.10340731734, 13186.045310297268, 33807.561116679586, 94241.72695598347, 24437.39662988974, 3735.074391529225, 12873.36802958543, 56057.274271250324, 45165.0870753033, 87218.5273926604, 30463.72278000844, 7181.297744614101, 88089.31520701695, 90734.61132287711, 55525.21849147162, 86243.15192125468, 63073.63527194843, 99950.20785121039, 40256.09407372257, 61515.55249654144, 37409.301511473524, 88409.11956252746, 86385.53295905425, 33146.15880452434, 34701.04161312464, 88153.41693580885, 27030.899732013157, 65978.76232314782, 21194.825281833706, 4771.048824491942, 36043.55241020486, 42355.15340490954, 25039.124755319564, 84280.15482793412, 37345.935718905675, 15452.999532936406, 63678.304592405024, 99962.20419050596, 91880.1550070965, 84138.3985057301, 4599.114810574256, 82559.25515026065, 81976.60931578345, 64575.684811903775, 17027.973287496003, 25937.24800981647, 35086.427999076266, 43961.63491901609, 21645.1004473579, 41155.84697333063, 36190.638369697284, 47749.854864124, 5885.366303287309, 30817.899957197547, 30063.77324766788, 23615.46772166001, 9742.310133989573, 11487.519974099014, 94761.75211609503, 34094.53366092733, 69686.22931125197, 3827.641188108488, 8184.820433620011, 34062.39806200159, 51993.79099905742, 83531.64774336918, 96865.26944223445, 15611.153832919721, 76868.60628900772, 60440.460880654515, 27402.022368684353, 98608.64855980281, 19651.88212851793, 84639.24407786269, 78763.9817177718, 85179.37945331002, 12405.909608122745, 2327.37329043724, 9372.174204071882, 50481.45162714279, 47359.811753515365, 88702.73504318795, 91636.00155795731, 57725.876459327905, 63772.99249769182, 82231.48104161909, 27435.867271439507, 61433.79542422638, 34916.66305549625, 44486.707979906634, 36982.47300735798, 92417.32163628675, 38941.1497178305, 22801.973937150964, 71507.62401282342, 95499.18899098746, 25789.917688834397, 54044.465810911934, 53633.819007490914, 17405.699355877725, 40781.611489939496, 28075.065539745305, 38708.147205623485, 22916.71688302368, 86828.62766050859, 77533.70091392145, 79413.5612202319, 96433.60776398731, 12947.719371345167, 94022.01249832772, 65819.74774979107]} +{"id": 1418, "vector": [37996.21604927514, 57361.753100352995, 36378.50062567459, 9635.48254290758, 40960.873702958, 16065.54612671336, 14843.52608659515, 11227.64887197506, 35225.445170406056, 31192.075839922218, 34048.83324565643, 8265.330147358973, 81018.29202841771, 6734.915132634145, 68.20033328932551, 65302.96179929019, 66889.84677446508, 35391.11521545849, 42544.763811084165, 73829.61179006573, 71103.28242191623, 46607.63834492205, 13877.70554918426, 65573.33383172995, 51956.14805863376, 79056.00915216954, 87608.63268304452, 94889.727148934, 96655.26267523882, 23972.031770123704, 89196.38778504865, 76744.35162161964, 98755.08067292502, 52715.4651098609, 41441.61450535783, 38315.769742767116, 99929.79879273602, 60946.687420305316, 52079.68550144102, 30396.809035016126, 69644.63456686439, 79380.266479976, 49497.339446528946, 99265.95466127327, 37911.62187802488, 18868.307521036877, 33850.43807264329, 63958.401624214835, 94797.13359211062, 77789.8333899047, 62919.59241087864, 88595.75014907363, 41601.641785541644, 40156.08326386628, 54275.484369077894, 29429.83761198238, 42746.66454602172, 16214.823254890898, 71218.91305031997, 77574.05311066126, 71816.0160241032, 21778.38308978368, 86266.67642314221, 59034.40519822378, 33152.497852256376, 33789.70543303122, 25796.135585135016, 30538.980337748235, 64566.71467277651, 1860.2293173253681, 88693.08524402614, 61888.10623678056, 17201.78646355621, 95198.52256067957, 26293.31147013686, 94257.17032698833, 51546.4274392048, 26906.488546419227, 75135.90022443736, 49914.92558573187, 69818.20054649655, 7959.608219262759, 31708.951192495548, 2452.0561740308035, 9940.984237434359, 6214.564742054041, 30642.57628804503, 60877.28276916761, 87626.03705677639, 82176.83807563536, 87517.78662161942, 72585.25736973913, 1297.6791937308496, 84124.8584261879, 53268.51119555, 34876.01209370524, 60867.13244691636, 81116.01744247698, 91857.81097780501, 85054.83793031563, 69099.0289811801, 99158.50742804875, 83824.14336379948, 14241.956982023295, 62156.31082354792, 80709.44349803893, 63854.06035214373, 89608.58035586945, 48012.03906180882, 65719.33795103987, 65767.141186227, 21010.136604957628, 84918.72248639275, 74449.27960301941, 63716.186321013854, 74568.53731287703, 28321.207948489613, 75224.8052433715, 91971.35777988388, 58664.249498532416, 28139.571824791055, 61038.93850234697, 50389.41866838399, 54057.76381515586, 34859.90855094953, 26880.582764172002, 35573.76350341075, 45113.8281968049]} +{"id": 1216, "vector": [69194.84155189493, 40807.4083719895, 41677.156789958135, 81761.85981591252, 31497.59621665221, 72997.51056293299, 75659.69862024573, 86417.38387164682, 17663.487121974864, 41167.406132470765, 18688.13205150195, 44135.943743245596, 89364.1955644526, 35124.69382540538, 59880.40078877317, 72620.2677358421, 65601.04178127898, 88411.13444969465, 14019.386550166857, 62381.483669557056, 45106.72286869304, 58253.49841820494, 83365.88718315726, 8230.982554904409, 6203.166345058142, 98456.65210487304, 61666.51511934871, 44036.56427220952, 88363.40036492437, 94668.2709085022, 40990.911629969596, 20935.518743382287, 35144.59488671722, 50514.03653145481, 14904.18601584007, 40443.713193969, 99172.93867137402, 22525.979052386734, 52740.12534229634, 19511.72809013464, 958.2875300607086, 34392.53643437328, 93425.55386753632, 39441.613074766625, 12364.739449226136, 98984.57364242541, 53287.544953800614, 86683.71040932414, 18581.33789452674, 13194.087579292136, 81885.91783583046, 87543.46040477925, 90540.58828418488, 78163.8328264669, 27190.53668790259, 70049.72558481802, 70500.26550024237, 46126.25635866553, 67543.84177341529, 37744.53167645756, 10376.979912558105, 8802.198679126372, 53213.27574100603, 18690.621915580254, 17675.046731853807, 25151.600032290546, 82411.30987419872, 78839.89258871732, 37162.73139513286, 26376.906858324757, 31137.53553694316, 99885.22014790504, 80462.23178638915, 60622.60253754865, 29363.636670476455, 28496.792628636667, 90272.72510722291, 57256.883901368004, 17170.987278693672, 71758.31474443438, 90734.30814133807, 16003.094070951229, 88891.509893459, 74726.22092491602, 23282.358575746064, 96678.70755753262, 24608.674641911144, 14532.307284489498, 25272.938811980785, 74206.1569300524, 99955.7277605204, 74156.63550101363, 45512.611630073705, 15017.519548210134, 3156.6949840008297, 32945.051656688964, 27000.451893103393, 32731.433757645922, 4827.244280522269, 49563.194123329034, 62155.01373103293, 71144.6202524842, 96189.47056044413, 9805.982532446122, 71291.08125160537, 76181.86333873663, 18524.582329617035, 17206.983162256372, 99247.90741711934, 23005.47515650615, 28885.147808875256, 25786.454170295958, 5334.864421178653, 39551.19645694049, 80800.08553421008, 75002.89036412447, 12150.454717646686, 63803.2218549315, 52750.02661675068, 1393.8793985844188, 43167.48448835809, 49414.62310999156, 89340.30864224379, 55274.43334495864, 5313.06479936352, 33445.27837419899, 38920.94005638757, 48227.93212257239]} +{"id": 1736, "vector": [2828.7439657016744, 94276.45967675553, 95195.64714235892, 93261.59700902623, 20304.459569945477, 79822.17000868938, 30975.108440341413, 52212.84613174247, 22763.82700751226, 77745.25705388469, 67916.36575262887, 66293.86498101357, 11028.828788958168, 52015.51985226044, 77179.58789545916, 61809.83342609223, 66693.5203542694, 52163.071977179934, 72786.58498469413, 73614.97661862733, 89367.67493075147, 90353.85072081845, 97873.65613231384, 78288.31244391078, 47288.652741943595, 86753.64497200576, 98415.08358509495, 68396.20680408612, 42700.46479800198, 41848.93796798851, 45678.40446614867, 44418.98071565098, 39477.64973143136, 89777.08888929033, 1162.2464422618716, 44826.28701711741, 70226.23341290129, 20480.10716938079, 36985.93210884117, 85867.4472515005, 14877.074247648114, 19180.097144062715, 27897.46769293041, 60922.71060826085, 91478.33859457723, 6973.047228021423, 60152.13857253641, 51850.62245203441, 21576.045362692843, 47812.47429331028, 43973.2360997642, 59366.753065535115, 74291.51192979954, 68523.42335539342, 75479.0528978435, 34605.16411895001, 47116.30557097596, 94593.70562550261, 46068.096031187364, 30781.76949090623, 52662.08875353278, 87274.96557627714, 83512.43201086046, 67068.67077458686, 14204.074651407838, 65524.59708016118, 62096.36187445426, 99685.62418836378, 60460.006823917654, 97470.66723770044, 50526.335035245625, 50043.84973420886, 26911.79254864832, 95356.59451989166, 98162.68249501243, 83047.05787893735, 11597.35730746464, 62433.479570389885, 20728.855321269613, 49381.35395552778, 76548.9296262816, 2674.447618834319, 45220.8063004074, 97784.38672850555, 90391.86052877628, 59474.8984568645, 98209.6955818858, 29776.42763166083, 61984.037691965386, 29460.784996402777, 51726.33382178031, 85324.73282623547, 67112.59570368829, 29778.46989259977, 96859.26214060637, 55766.430440700795, 76099.83169809051, 68117.43828223909, 68669.02015604956, 6999.364455554979, 28683.399302759128, 12346.988602019072, 54573.00277138679, 20853.345594785966, 6531.619064253003, 79159.98576523761, 80621.59434658241, 80255.25795925145, 24124.06712116687, 46846.00179162671, 54247.77148673171, 34661.569235361145, 67018.65892822124, 30953.106889698258, 86110.88283464557, 15863.151924561615, 98065.30728748177, 44890.63599933294, 48536.75316243393, 45690.94124688283, 96607.81491966148, 51227.082526950915, 5871.628058427925, 93382.19039246204, 34327.293974555825, 47412.075241198516, 80582.53874195754, 52504.22601000386]} +{"id": 163, "vector": [336.2727320592618, 46853.7785350821, 10819.8284490985, 96398.62996711228, 45357.32233848626, 51276.93390079156, 87670.88641313624, 8489.910170415182, 32766.341246071373, 6519.5249871958795, 64481.31352535691, 16820.942039953745, 91331.40094783256, 23496.913786117933, 85402.01374427341, 50529.769005139024, 95645.14457324184, 27935.954498368155, 84209.15185695654, 75631.37498057524, 79542.12323918784, 96921.12780445388, 13993.969056712629, 9109.167552379882, 48340.99082775013, 94607.20100717437, 99967.8208713853, 60404.89997510726, 79653.83788068284, 7400.422857718336, 75056.42301754242, 16318.43198428442, 95509.90060673442, 80100.26696608808, 19549.539317491115, 41765.53613464854, 55965.6584273311, 78540.92916750857, 261.1858843620696, 24107.918504210345, 75741.64107100978, 64359.77948993311, 75374.08779468034, 88219.14638185431, 21643.79931957272, 98377.16467301064, 58089.56447345328, 83077.93634541315, 70906.25106259984, 87949.81091074206, 79538.23916707114, 6067.718135586464, 8295.950968363686, 3861.486308863582, 28575.43123666041, 62254.92794516328, 53421.64596097367, 98860.22343447385, 31430.728037559176, 87308.23472045123, 1360.4549457636006, 69216.36578181556, 64677.55106360227, 96847.62955552014, 99859.72371707774, 59823.79161841783, 41802.0221461, 26852.669726410084, 89525.28693132516, 91911.7940925203, 76444.48144402546, 29523.812106332436, 46432.78787263819, 82238.13411435619, 11257.056762471906, 82359.454376251, 36788.024828897345, 43898.327553176976, 69591.51884007461, 67444.65239788062, 36880.76006915728, 84648.42483759575, 54602.25955436005, 3873.541382085011, 83666.42298069717, 5113.422890490826, 23454.42015810839, 36152.55503744746, 53621.93163276144, 27951.471598264143, 51726.42947605693, 2815.2755374064477, 68517.63564067658, 1782.11644956342, 10443.859460011661, 92880.32684913313, 61754.79287117578, 65471.490182601134, 62883.58251247079, 48321.134484435745, 64846.1251101415, 85639.5082990132, 32288.237103240324, 81508.0058358523, 99200.9361966822, 10002.543103361106, 41649.351236136936, 59852.373179442795, 49785.19007015705, 55167.77114334572, 75640.5096198887, 65246.777285244716, 87628.48475300084, 94995.58036584403, 60063.86232628163, 11844.790343848255, 97604.62924745421, 17032.777770890007, 37531.99316857926, 5220.125822682598, 86842.43745330331, 17420.397612051154, 77655.55718337564, 64841.62666372677, 98231.97672453469, 55788.1361414719, 11095.455667129383, 79728.23732476961]} +{"id": 949, "vector": [12439.504426221825, 23265.349833502813, 79600.66344048736, 44352.395189110684, 55998.23082183569, 5584.443494483205, 53005.02704296858, 44493.05579822629, 67830.2399541558, 52237.711221505226, 35718.94234880325, 75720.14920345167, 44207.84681672684, 22333.182219218674, 36179.67713998235, 5081.33005192919, 71921.21644840974, 38225.813597759174, 74057.46510056118, 33055.85050434741, 57061.25109044633, 40802.59550931409, 43284.37003061474, 51510.25836287391, 12082.692122336546, 3029.339707348655, 6556.992823988495, 31112.795321151476, 28135.695472239375, 25020.663520834285, 10022.795760365954, 92624.3987239303, 27632.9075171605, 41392.853658239284, 95036.76916404993, 8417.651100369461, 3937.9369925311435, 96528.19403164953, 64203.331243465946, 86045.06681154804, 85646.32382699876, 52294.410628229634, 78024.95421566343, 28392.38548909059, 88771.73046945047, 8105.243272106444, 35764.35518874906, 74874.87208912201, 44674.03335472186, 37892.85493344813, 77280.96724686734, 41258.556157550185, 28333.28180342066, 92848.94690604194, 54881.721817674676, 61382.00752738123, 70980.642777368, 77339.54085048377, 67482.22836577523, 36579.84331510554, 21915.188527307062, 9450.84012925117, 51792.5211216474, 12728.161658174975, 75980.18272599307, 88074.0082895394, 81642.3967462008, 93859.09383050217, 46942.861532530434, 64443.02422220314, 75938.2919221022, 93819.18992686029, 89157.20549739564, 75202.51987192716, 15659.369906113807, 70534.06336639733, 65986.67024612025, 1902.0780999736587, 46742.491172811504, 80527.71521872334, 46979.67923935724, 67285.54902371728, 18662.59565904709, 94698.06572600953, 67063.61582084058, 26625.236378331017, 32862.14937026376, 65640.02830075222, 73377.76235311397, 99155.97709466162, 18668.202748767802, 1031.7810291339047, 57515.70921161141, 19548.330708489415, 3598.788551340193, 10277.524709163345, 94002.08958460175, 8770.477391681352, 93533.62661449415, 38385.572073387986, 72682.74930541909, 8996.018609428413, 62467.46719514568, 23967.29908466846, 48144.51815501766, 22303.471956217003, 19034.946305695965, 31492.651622333546, 6299.035084169402, 43354.55228354287, 55807.95636749738, 66236.7383809539, 31526.277973398865, 51652.28967343645, 23398.463457039397, 66590.52158933149, 3338.217977235558, 108.85354356107158, 54754.286107620996, 10756.031806496869, 31611.754561937578, 6715.050005843326, 33679.06019482125, 51643.20442871804, 29169.93429631981, 44090.521104606705, 90896.3228364407, 38100.28668006957]} +{"id": 1945, "vector": [78587.28764776373, 25143.217693165465, 32441.870605920543, 5528.1399281916, 66100.65649215828, 99824.80970581331, 46919.93035969188, 35611.95629474926, 81451.56714557034, 71479.89347659673, 10211.082056796173, 47559.825145372255, 96518.34181599144, 97780.01356846163, 67549.97156502052, 82709.68535454584, 11167.736037161047, 68679.22660014383, 2678.1112045116574, 86642.73217187062, 38732.7413420489, 68689.85512367096, 3121.7678152679064, 7827.137238582549, 96726.22607951034, 80743.39960156447, 42545.54966152028, 92868.45374665623, 180.9433716448594, 51060.49312500981, 79917.47199844976, 49638.47030965507, 73800.4917882274, 88594.69084862298, 24373.153250092706, 96500.9449796937, 86314.23430124634, 23159.24775698345, 72664.3814786866, 76296.01081145438, 15055.58700365731, 29583.749211084632, 61792.31851316743, 97052.84718579432, 41758.02039170793, 22633.78292483471, 36250.45088962785, 6982.211379373737, 6310.894306040826, 68103.91541376634, 79514.85251722645, 48177.97145648082, 14846.218758710738, 66885.54614944782, 99424.76681072939, 36729.5791774339, 70200.45486959058, 60806.761543405904, 60007.17862961243, 80665.51990960081, 77848.40099422091, 50351.97336217842, 69153.95254275242, 59844.0791661995, 54430.54274792201, 31993.992974056007, 74.43821561972098, 22309.279367130806, 77773.060137871, 37640.65676613747, 19009.132748370306, 97023.30596642358, 68339.02123091841, 47918.87171511105, 30819.188496930037, 89553.85877823771, 20325.197924977456, 17703.278342345373, 74411.93790313503, 72979.5656356809, 60035.38435344947, 65675.32340158788, 18142.98315911751, 96066.76090197648, 63098.867924756094, 41918.03127954197, 26.148058118047324, 11412.674424257662, 12749.02885146365, 78303.9231840481, 30427.044506578404, 24658.853605964003, 24328.316074275146, 28194.36148723524, 51778.97082356196, 80649.30916825088, 43883.79364245889, 50650.24444454358, 79104.49571278728, 21394.33831356453, 23453.080752170972, 90306.1206630643, 93250.13087084168, 89324.94209739154, 51494.08929947769, 24279.751948246754, 91674.84988555385, 53831.91040520502, 91963.35046590352, 99061.59801790825, 94309.38191927127, 69770.83294660941, 94845.76127580574, 27033.324510822775, 57417.113950693085, 76663.98913683026, 94598.21079876403, 46012.803663810475, 14647.566608831086, 79430.53469522583, 69564.90128006125, 11420.149814750568, 86710.5986462809, 74941.70330551459, 4528.1511552997645, 73371.54370145831, 44680.58066173684, 98760.79809083289]} +{"id": 140, "vector": [67336.54287806875, 21921.46783653354, 60723.66629279801, 77653.85970845073, 40664.962409915854, 11277.941734476293, 64519.71627302724, 74695.86961095373, 47001.9863136104, 47276.48449563931, 39133.3620158127, 18190.60993314946, 56267.72041489935, 45022.788885564354, 82429.19524642585, 9658.502420243376, 25331.372278100596, 93350.31415945284, 543.6705427940059, 18748.285395039988, 19417.36230585239, 35417.60893029758, 69558.42364759212, 65985.60711775016, 75687.3671473813, 63705.464057926154, 86571.94429950177, 54467.42400291266, 41990.25303866367, 13268.86873458607, 82501.3660612269, 33864.38856661078, 30483.67523169194, 64832.79422719527, 56854.94072469557, 21074.747515047577, 35312.690174751006, 832.97333039839, 40687.44845112198, 7129.918895700594, 89615.87329907194, 41616.81437173777, 12313.81393017299, 57127.25085658112, 89649.2900484437, 73482.54129392967, 75883.30098782509, 65698.85566001365, 2723.7748201501154, 61906.93771695601, 90134.07169917462, 14303.798555934654, 94997.5183388283, 83181.4428143375, 15477.515787174012, 63656.17787474152, 16498.446128150303, 44972.886754551735, 56584.88833977259, 48888.71393121275, 49207.06605383586, 70857.80879733885, 89195.59071443172, 11655.77435248102, 91556.87908000458, 28930.55031825419, 73272.19052973531, 90576.03897312803, 1635.492450312326, 58771.2610564002, 50029.562084758014, 70666.44021367168, 17788.10440495111, 68101.29235042684, 93980.83279732129, 30238.200721887588, 8439.940913753386, 26298.167712603026, 71080.17184595861, 94539.67046979092, 20330.042157873286, 46505.06044201571, 23557.524006169417, 19325.47306498159, 38681.71942048974, 47444.68887990191, 88784.92840124172, 62719.45970089632, 33114.5556383326, 34549.64807482018, 98511.2769053621, 42435.50612272138, 62350.18894287664, 59773.03207221089, 58723.00573831412, 30717.187910430886, 66355.56651915015, 70524.22509866676, 19874.673831869517, 12225.620913542702, 80946.24207365993, 5102.935136234177, 56619.00016130172, 22815.069365092346, 92363.94469939868, 45936.47936456061, 53120.794558805326, 37906.760639078406, 22414.94132931874, 75558.8907842369, 31002.7766759439, 60958.76104775215, 84381.64508775793, 51720.61180503482, 6417.168276526808, 89935.93231667731, 85774.40696776149, 65121.39322270935, 85255.65986905771, 97430.65639268993, 89553.52585973694, 9512.012836910388, 49316.57396607097, 78393.23742424558, 92024.27003469656, 12938.59336269092, 5894.1746772411925, 7630.564704967213]} +{"id": 934, "vector": [26361.2617943887, 21562.286570118562, 45245.03598702022, 24927.133847336492, 65618.75074138094, 6082.890876503222, 15486.801895468905, 53286.59763559138, 59670.22230527708, 86072.71503945196, 78046.58142810289, 53815.30801821509, 21043.442462220606, 5238.983306319378, 53864.30753731549, 20087.535999359974, 4020.3618059796, 34217.390776662316, 83075.56268702149, 49232.350689476276, 44755.64714930744, 96336.76538063705, 94404.67990156883, 29991.826342406268, 5773.676805543926, 8585.838171324867, 34980.84936010212, 99454.24183710913, 51942.791808563525, 157.80515058084754, 94424.23738242757, 91070.0591459369, 60716.15601871978, 70050.75620490649, 69243.6317403701, 53055.12617496061, 78284.53274435186, 97871.9596865361, 50893.897605107355, 93406.71993979029, 86914.02550410423, 88769.13969877924, 82703.72957467189, 38431.430867441595, 71932.23559628171, 59273.75806343175, 3686.3971178826673, 5274.777165149491, 51989.2161747962, 46212.75290799226, 19129.553787973087, 90764.70006016454, 75944.0314077316, 4077.1843024358213, 18601.65327798624, 58456.8517999281, 90854.9395021249, 253.44811821523817, 23274.235973526436, 15107.96316441877, 71003.03821683102, 29572.707083905803, 14210.229817903786, 46773.15223347385, 26493.215579425654, 26556.966542886563, 51930.068857119324, 23179.55267799179, 18918.333199409553, 57344.13757860442, 18820.02620496973, 37018.70147327422, 29732.767738440456, 35768.584279731986, 75066.30457306512, 84895.0197695763, 80979.13605100838, 98797.83231883726, 81826.0843023058, 8823.152074323027, 99664.44065027525, 42548.774950115156, 8340.039799999688, 22739.940840641804, 4450.212703385403, 83881.55685597651, 52916.86254026644, 42800.890900492515, 26131.788040236424, 53331.071535051145, 10182.032124139883, 94742.08559049018, 21940.61029343659, 26767.264361191035, 81048.16500438115, 55578.56432893801, 42022.27121018356, 1281.344492101577, 19279.232132982084, 2168.6702618975205, 7750.023266224948, 12590.304424439535, 82565.24611133774, 4203.964603776778, 19059.115922289584, 15542.401619105462, 81377.5917644855, 98620.08866921988, 90489.32872450404, 99346.9078764714, 43960.561701574254, 74262.32716525297, 29065.29349865532, 26290.612775759593, 5973.282739605656, 3898.605742849337, 38296.62001021714, 34635.40577713597, 91435.96733418028, 47085.04272836964, 56576.27495869918, 86658.478450657, 15584.951227705302, 29793.398869270328, 79669.47542297115, 21590.001625455247, 99826.10406718325, 38605.4686286462]} +{"id": 168, "vector": [58967.21615711897, 36547.84392308719, 19225.903862387917, 66370.82569183031, 30806.193655460844, 83582.58125038836, 12546.44604170998, 85479.38843423208, 99629.5155382756, 19701.510010145008, 79488.35172539811, 78698.00536085141, 327.06555839493444, 87277.42092181044, 21585.531990283558, 34926.58131647694, 23677.863896225983, 17532.650507441682, 28121.209771776612, 57904.17112701686, 91552.54617431469, 70734.85102705135, 43685.443343451305, 78536.26313004649, 71061.26118717241, 20779.58399299703, 96842.43365435897, 12327.54859132481, 31010.738028640662, 47904.89760553428, 79103.45708760395, 27811.975353854647, 12277.632919898108, 69611.73423429685, 25795.841057731726, 17053.833409208808, 9925.134278551173, 31986.24942672347, 95496.98931675777, 17261.14161149307, 31889.185670320385, 70456.35196737677, 23037.845094046606, 98162.20681764289, 52323.050490525566, 75201.48566348037, 29234.213268850883, 14426.622972480707, 30430.705737382013, 2210.9144149624726, 35886.70176124735, 41809.274317673364, 95097.54573847883, 48255.33931713331, 20407.562890199104, 79852.02275277935, 30943.588274458256, 76096.13722193784, 10397.228887391697, 24501.27950757357, 2677.3719526654418, 7741.505624719381, 23532.444946538777, 31050.093903136054, 57372.80297688878, 17992.10691055475, 37778.31816811081, 11695.254736805904, 26716.132412128525, 35459.15494288705, 3055.611208936404, 60881.17389843437, 65200.71547331611, 64288.46710381618, 28865.866656804617, 75341.18276589042, 63851.441617083416, 90734.37083522925, 72629.54591863444, 24046.990461079677, 67255.56543822901, 77869.21267239502, 23767.031181339404, 60001.20334934821, 53089.43630497266, 3307.8597049920777, 22324.37514299659, 14843.650404047392, 7596.648667499717, 48661.90487476071, 45498.47133135002, 52097.120922498165, 60967.30807787436, 46465.51465864258, 16677.467165049242, 65117.79627663815, 41677.30446406166, 94550.5842788509, 30206.269967556043, 29914.604314865646, 68097.44337043223, 77257.95993806938, 60544.71342022871, 9368.70184863251, 43184.37901729886, 12954.161166342603, 34839.55224673334, 55451.77726896898, 61113.85057098514, 62041.05263532387, 33339.57051508061, 15030.44303393216, 98564.1977320135, 35229.3342283425, 47666.43967707187, 60498.42750345486, 33240.528510844306, 38967.96802596176, 20769.622817843592, 98226.39207897363, 23599.401723799296, 80802.87505413948, 39544.3238759558, 91936.40835892677, 60377.088999367676, 30832.175283257147, 29191.880328821262, 68342.0463473804]} +{"id": 245, "vector": [86052.16131660064, 8355.693180994906, 43086.71490561946, 56520.19515029162, 77002.53229376778, 64283.18282739265, 16860.882000580677, 30958.251909850685, 78662.99990280147, 80065.92487527899, 24100.741781995326, 7196.446288889724, 69126.52595588124, 92003.7160580872, 50618.75817705811, 12454.741537414615, 17408.082625539846, 81365.98902275095, 51848.44791699918, 98733.58575194124, 34837.03684951006, 48179.1263322093, 44164.587885521876, 73820.77497116681, 30694.023710224716, 75559.86368916457, 57975.845615090526, 9776.716637993377, 45169.32613213193, 67067.02748571258, 72818.96437736735, 31880.96613055903, 7421.224306286922, 27609.16719702172, 8323.328842697752, 74074.01310355384, 22786.81414238528, 17783.08769986876, 8360.482636402678, 80280.87012517675, 44288.53341762016, 90260.00132766871, 7071.245574482721, 69854.48883871426, 71690.52896000829, 13784.28588870838, 61281.06734041963, 16215.95965466951, 97632.54571240398, 69165.87561357429, 16682.487396868186, 18289.831964387904, 10349.365224972862, 98200.0497268201, 75926.97094100811, 55018.75479727806, 21823.28769522939, 49245.25569093356, 72429.81157396591, 33541.39092241813, 66658.24079197695, 47326.11956451984, 87233.44440138488, 60677.22814648302, 18187.641146539325, 21330.622499717756, 9857.98461899362, 69073.46073374299, 3444.58013590947, 59982.44512567523, 57953.34338340711, 47014.614417265766, 13058.603886250698, 66644.50361888383, 15640.765921497523, 46446.72379636349, 39294.18701869839, 70294.05070281145, 3886.4416327089257, 7557.15641677045, 49899.27031856979, 80524.47932867456, 60989.92043199597, 17718.032346387612, 87094.2982516542, 22917.730691545134, 64826.98836650897, 24919.885474604198, 88738.58711385766, 47662.33400632631, 74573.20397865516, 88306.54417801503, 63600.88493255851, 92317.56825272497, 41210.7299944482, 16785.614715088646, 18448.39473893116, 7767.858999579525, 9563.508411369881, 81009.57704228339, 47177.467384441974, 89820.43754505494, 774.6848964501618, 48473.336552782996, 40222.45681508715, 65648.9991939727, 14408.596907520998, 5373.581304709818, 45196.19582663824, 96274.25442006426, 43617.60986705979, 80723.97218641058, 83922.76342585744, 63526.05559981851, 55725.027023865194, 97229.82860650973, 77606.67344211478, 37499.24295700985, 8717.391573150713, 74737.52702509818, 50751.67771908128, 61940.95802876352, 82091.464879749, 99332.38239749441, 32279.237795755234, 91117.12630417557, 45296.28486009407, 69119.22070507682]} +{"id": 1759, "vector": [11374.788788535172, 87889.07628728781, 96980.16301260078, 26330.447426177605, 86995.41354392424, 5113.440772629352, 34314.91779451179, 21552.711199640285, 16830.025495702117, 14364.992278919754, 35411.16435305559, 38145.45515213424, 35793.6380145368, 78201.86419007469, 55853.341280204164, 56408.38147226455, 99957.83771825334, 11624.074651031191, 71509.08290437798, 61045.938639333166, 5774.70768922621, 63316.12003067978, 91467.36774363567, 23953.12091601166, 66657.1904389761, 10567.60692290113, 44353.216080399405, 60761.528881522776, 62411.27270094925, 89811.81124843797, 61209.047105669575, 83865.99629572842, 84231.40555722675, 45424.67853386142, 52527.099530101186, 46593.92653373049, 34615.00923713721, 58261.87148934875, 87499.87071544265, 37317.8328897207, 959.4288977817223, 18555.584746378983, 90352.9092260859, 96840.4685677599, 61542.0841848257, 71459.37276857482, 49823.25796685067, 68415.46602873965, 86777.23910371597, 77481.13174094447, 72450.89346286027, 60109.47659817673, 69016.38866929385, 99915.30544637363, 41016.44655940049, 54563.95166672532, 73737.23504015511, 97337.77866449035, 12440.06649333761, 68538.91039855806, 75813.55320344842, 24451.927933446237, 50934.95450926705, 74163.63612878378, 27067.19631467761, 53025.613866972264, 95511.66969373239, 21894.735149808832, 49717.86197371168, 10007.727381881105, 46436.06382200486, 44764.222073008954, 14961.508301667414, 28049.86121632461, 18861.531558492385, 72836.46029198296, 98084.33419390558, 6200.1639364941075, 89211.6199403193, 20207.686622259935, 15052.888962262856, 95492.5217108272, 52057.65540789519, 90211.8891923514, 15753.889817201705, 7535.124726846542, 82633.37775756721, 20777.25703504828, 73251.43956636157, 55248.18947823979, 13875.389075675837, 62759.21489125086, 67531.92750419561, 53260.4994729834, 41325.398195870555, 71282.15515444873, 79738.47795249228, 13340.472182662277, 22014.06475520611, 22489.845402842213, 51791.204996980865, 62389.75012198933, 81437.97089587497, 34.80581795414217, 47713.92013960248, 36205.85165140707, 61074.18795335949, 61202.61620992466, 74759.5330449913, 78832.22226020924, 94854.82978301575, 896.9101761360454, 52412.13523161473, 26403.051119836095, 3768.961009043104, 59575.8926995679, 83319.27025645645, 3406.741700557792, 3660.342244886017, 27102.82513573826, 15423.144904911556, 58139.512109509815, 49720.04951951137, 70139.93271453636, 16707.727181653598, 99018.73862653927, 36874.67707550225, 28312.215437387043]} +{"id": 943, "vector": [83378.70292633251, 23054.185408734505, 34096.42966923025, 50744.82010885307, 50494.4566095154, 45339.44491514613, 80361.8621106869, 47655.66083987637, 75458.47049194275, 54088.52703469479, 81431.86520558264, 42597.20206880383, 82568.65405559726, 29999.03384491487, 6481.074362707218, 71704.07351166673, 67582.59044075043, 80144.42743663456, 40254.185149742196, 50502.780815254635, 81331.96559753094, 79365.18061368901, 75948.31561857207, 34492.38494062695, 51761.81404571763, 41224.92176794361, 92706.17272213969, 43855.5531839294, 82.23068948588485, 7405.524933356666, 33306.488717168024, 45346.13175281077, 23391.719882471563, 16803.182868997446, 12548.542093416116, 36804.67671750016, 74027.20627777565, 99566.57383294181, 86898.2211504162, 29223.567167903762, 42761.575240437145, 84816.0163888945, 4889.71698606685, 92291.20849993223, 88561.87430839779, 22095.525461740763, 92134.87483221413, 75750.04100769766, 70882.88922184236, 33330.83588175462, 75504.18240151541, 24536.132819629842, 42640.21020556299, 61091.71537558034, 83911.78381649996, 70003.77603164424, 10328.59828035122, 62548.94007369898, 1892.9890221532796, 79081.98811932116, 9629.875214237349, 52005.442051878934, 30798.813286714598, 6563.605575176434, 96931.87936533746, 23941.37135085416, 7510.87758013036, 88520.41084144423, 25682.73151543301, 81286.16053215171, 95268.67121233286, 48896.03586930266, 28178.510653744735, 33513.8670716005, 37368.82550281392, 47429.07493073433, 55338.68476724458, 76668.18451198713, 17044.084500414257, 22996.422936996787, 40709.45208763162, 48320.76738228571, 68570.35445163587, 28487.407438314272, 40672.839782637646, 13671.394247072389, 23531.628907089696, 23068.32595635615, 18704.166633610384, 33590.416236998266, 90214.60932247824, 59915.116528830025, 26431.37847812026, 4628.584362280841, 69759.1291698433, 59182.93347825443, 57414.96478822642, 6785.112556180883, 4484.1887128143635, 94698.25401867971, 52105.90149944485, 81474.16658537969, 57263.24010103292, 6172.564905799282, 98671.2499959774, 99098.53099056745, 82391.12491558056, 77623.4998213191, 15893.42698879097, 69561.0332243577, 78751.91245695266, 81117.04071838675, 56116.62291684413, 14630.984620039333, 83627.36073883215, 65576.43745961225, 83864.29373720585, 41040.52498535751, 75770.52389510265, 82984.50873330391, 91945.2380693594, 9078.602731661245, 49163.28984789925, 93255.90763096936, 1206.2413009123918, 56132.29854275236, 33597.010980540406, 85.95426019492037]} +{"id": 278, "vector": [21645.492119475406, 1904.7191761015881, 25888.80350632654, 57567.81980756658, 32310.92474630316, 74642.04363988302, 77590.87083177728, 30788.999558492047, 65238.01181961417, 46007.34496400626, 97260.87329956176, 7748.523914418714, 20009.730598143437, 61094.78015182852, 81779.40668673578, 34591.93627376595, 91706.1825011705, 34103.464649092064, 88456.87170745638, 50724.69700961903, 64466.16098374003, 77783.75641236275, 84293.0987780197, 886.3324329769662, 36606.1598323168, 65321.74552242895, 79875.67488672718, 68504.87077062979, 26463.108133327663, 82201.61728773706, 33030.270816523036, 95353.48582871791, 30372.576319557153, 42683.30477934395, 10699.711516059884, 1621.1330013800707, 51358.209698456805, 27684.533764027197, 13616.022491686297, 96635.81979397502, 29052.637571213778, 18039.340919421666, 54586.42862045382, 98593.35651606768, 42619.63056994235, 28106.55254025204, 74614.86563564654, 28856.82579338693, 83503.64352737738, 10095.409768188101, 59235.39234292641, 77224.29297111435, 1545.7306969591023, 11106.676850757813, 63573.05745813491, 6246.299271694112, 66011.5240403561, 86504.52911716928, 30927.855239594217, 70989.62939143273, 72411.97729351437, 24647.87834299229, 76709.95736825033, 4084.7857643522166, 20296.984400274065, 90017.07851854737, 78984.944282941, 16024.932019215965, 68961.73545210245, 99935.89237099474, 57300.84878725499, 27975.746078767937, 77940.69374375089, 41904.74219029666, 49800.89610299686, 59705.05837186201, 69525.4771016659, 5755.639509009303, 60128.449903892004, 52302.551451570966, 49273.59277143116, 1861.2184994463933, 90235.50048772183, 83354.97617816181, 23141.491600049678, 3329.802302467089, 6732.573325791113, 2134.838105831216, 5872.369973109903, 88488.94331064996, 89483.00001206885, 67359.2803578497, 1282.6401239349304, 22603.53823016149, 9386.054770028806, 32368.539119877227, 81351.20741459614, 58241.906771570786, 63286.169453323906, 8611.18974797065, 12083.410824686724, 1639.3973796681616, 81714.00319225116, 16155.376917590802, 26247.437131223116, 40613.01602552693, 85573.49540276926, 9450.558025459666, 9441.483962640528, 34290.71641617598, 82981.68898576753, 3989.33016235935, 34489.98555672504, 97700.40286871632, 40362.90206508616, 87315.62197163262, 35992.65511572505, 32307.66244898382, 8855.91403508046, 59506.5234558121, 39516.084574761626, 59054.761434358596, 83036.39430069183, 37285.21312924215, 24420.138376816434, 76706.22014006483, 17665.00138292869, 97498.66129686519]} +{"id": 746, "vector": [9650.839694253833, 55064.36107324849, 85505.62005772337, 53900.39543587555, 82445.0138046029, 64993.79602773741, 31610.717667381483, 97109.35837152922, 2949.9378418500255, 78331.66792888303, 1638.18828327571, 9563.027002438528, 93684.29955464516, 35810.20418761993, 48488.51763898824, 52427.188416009405, 12130.265753969783, 12891.411161695498, 3519.3838512725397, 12016.70964559305, 40842.83323316646, 5293.482746696143, 8780.305498827202, 13025.704862768129, 6648.6171594023035, 60786.03112179177, 95133.37492516874, 12049.811185097758, 2608.685888524698, 14290.050308769409, 36037.74438124444, 28329.58599903107, 19685.107520600264, 93646.36570928142, 23440.692510337736, 81198.58635025313, 88787.41753959381, 10679.723299264298, 52683.32743430887, 64536.83772260228, 25777.18706545057, 1226.5781175026525, 83408.54891795706, 62967.824685276464, 91353.75660640078, 66093.34889725634, 86577.31401997425, 60313.60867222046, 12167.34453973728, 154.95634944339142, 25994.615294259103, 24739.738892311958, 2660.0547592042776, 76556.30125738746, 86095.29115044388, 84767.68073396823, 10642.978850986829, 56035.95047644447, 71716.03866995324, 8025.896803502464, 40353.20963701066, 80405.89499527216, 33012.959032198174, 34541.28835378248, 29875.05014963556, 38625.52345437724, 55156.302295913476, 42636.00638372632, 75278.3938483063, 51107.104251653145, 8529.150543800446, 82197.4451755957, 49849.01709639905, 44844.58255019665, 22090.216281433728, 79239.27662140963, 93227.17676501867, 49325.22201464492, 26662.834446762572, 9242.282521807832, 93538.22068835888, 498.2508039591105, 29933.177983218153, 47204.549975662856, 49223.6173992304, 59636.999260739496, 50072.21655236964, 15909.159401852869, 7420.224414607679, 64879.54927750468, 91115.35685755851, 54114.6692203136, 52907.373539600274, 18770.07005122312, 65637.48919087654, 77560.37848348715, 95427.16150586003, 80130.25537780255, 18614.739715871554, 93910.23326453702, 91034.28425724425, 79640.01077965704, 34993.58492845732, 17329.12289281866, 90845.6539314271, 33343.08066025431, 54359.20129353283, 35106.224762304184, 94099.41244687716, 34657.68880653527, 84651.27360953094, 19319.28273776171, 9479.663539074789, 68534.43386584107, 52173.798475357384, 87263.59601355113, 53282.59221101654, 84751.80356204172, 62916.753096878034, 10678.986592585537, 68239.32444491885, 34068.834392185374, 53562.7383729215, 57497.25392356033, 45411.69023934575, 16550.98416519736, 86944.23681489658, 76244.39722454347]} +{"id": 1252, "vector": [9331.51627845592, 82343.08054392121, 91925.5672326142, 77867.39910456415, 49898.21689825369, 71407.96163813402, 69559.61771757201, 99023.77416541595, 96760.64265396944, 9217.277676160063, 1337.243195869242, 11780.83275464199, 27108.04461013987, 47094.38108260375, 90423.23325439593, 11207.22998715592, 15680.87722397914, 143.50165518677295, 82806.61124977305, 90266.11770966316, 36543.13131107071, 6908.79297258733, 7944.0474454537725, 30693.38038681276, 75684.8887715996, 17687.282043426232, 621.7405418458943, 62028.65229897333, 29364.416582232177, 20404.030698184306, 77795.49808060563, 54937.244713372034, 76853.51362094708, 89969.10885440296, 18312.189395627996, 32155.876349939907, 73517.74655513107, 35784.021212475425, 30582.200636618683, 12982.074954130918, 42783.541201433596, 99001.85005590801, 18360.976407166472, 36970.16463130928, 79183.15601971539, 67021.0530814561, 51626.60264110086, 8310.794123175425, 74420.08901211238, 48979.60622389539, 24828.502351165327, 56395.88618031376, 28592.00978659976, 86952.9604601484, 58281.39799195309, 63275.089678176235, 1433.838211169791, 79627.43083849225, 3459.354196045239, 28544.22098922614, 62127.90710205222, 33143.58547881381, 51781.078422361956, 35073.797052306734, 14425.004681473409, 40458.91130827337, 89307.8295010513, 46744.52508202717, 16105.294362073064, 90463.59037510138, 82215.58482427959, 24247.478482153274, 58030.592022587356, 34992.95424984767, 83883.67524136668, 64466.72585557155, 72827.59802420778, 33292.28028442598, 38853.3708408912, 34849.66956732911, 17957.91908296165, 21786.770271367404, 94353.5010741559, 63491.327802612184, 43176.25537046298, 39431.46360327766, 34237.739256311375, 15483.39051042399, 32240.04527175156, 91020.69983534866, 79871.0099993497, 72140.96042318172, 75043.39370959684, 79293.38461575727, 43862.078774584144, 80226.45071008007, 94924.13636335314, 83946.5951424026, 94234.10495950215, 5941.87939437717, 99654.14765320036, 41462.07274005348, 64151.51570566257, 30024.605406596962, 87052.01385320484, 44539.12999243653, 44849.01104966735, 68554.0364369731, 62145.7218184908, 90651.92199122773, 90783.14777361543, 38488.28710996354, 82046.64786785911, 20513.32782920382, 11004.612647215517, 36634.76743522317, 24794.171128047237, 82559.92218896642, 57289.62466389549, 27308.267402448106, 44062.66628901645, 99568.09942747053, 34946.2435380256, 71534.32419516156, 32684.056991012356, 8362.949997929914, 23964.373408612206, 26109.547472108552]} +{"id": 1371, "vector": [52343.562033253445, 39359.554095338564, 49575.47434687668, 57875.481044907814, 86240.76606400378, 9333.149647027783, 47629.29241702923, 98892.46197937832, 39747.538215300985, 72102.37880838342, 22386.62446982401, 59213.4449412432, 30735.789668269543, 85231.03357840996, 41994.768212602285, 92801.99184695588, 40232.366876500804, 35993.77429568647, 1967.855322805745, 17727.19840758986, 89263.71446332177, 51339.45891588978, 10395.104974218239, 47782.7606199847, 78045.03997357265, 71654.144497534, 94639.24386061894, 15384.51414098242, 26542.1235901964, 52708.47494111358, 23199.340753705732, 25656.427721692577, 98706.97235916997, 35121.244463453426, 33180.16165500715, 33779.9817693013, 80482.7407412188, 14642.29046876575, 83873.41910090909, 92431.25809453789, 17708.16115029149, 57406.095057323284, 30969.5006112802, 39904.035895688125, 56585.31342028097, 22267.47389577185, 58180.87517255178, 54595.043951053005, 21991.27355013988, 53023.9723320074, 28058.229029365422, 70993.38506874377, 27166.23742045551, 88730.8872417469, 93185.66653657894, 16976.859949821366, 39625.0168094296, 40412.108226480814, 74564.02722966972, 12500.039583477785, 95596.894976687, 13919.073203696385, 9104.054410064065, 39080.939140144386, 5671.849313898591, 43613.39823841076, 88113.29859198732, 20631.841439795662, 58630.73636374355, 46513.21787836596, 4515.473370165535, 64377.84298400995, 79549.2358497412, 92159.32699658896, 82023.0390871065, 16763.42757226562, 38600.54073636756, 52561.63411304305, 45013.30093515793, 85294.69490019894, 68409.35337058893, 32516.395562319565, 64573.04357293443, 45639.34062698498, 65385.99252977776, 99663.54550635743, 27677.650722170387, 37861.42298360526, 63778.33919535856, 82173.51392567722, 59848.4621415232, 43530.266481799474, 34780.21739183386, 60806.189641395395, 66408.30161796194, 28310.254731636454, 22551.926856170478, 297.41069565597525, 77024.68104552529, 1140.3077134826024, 58605.55221598677, 28941.822429761833, 41128.65298398949, 4555.0234887027145, 48378.46586874629, 3799.6070851489817, 86179.89963073237, 57424.38525027279, 24652.663591763434, 35193.50737672306, 83237.28837219458, 211.0133046854812, 6799.906019095181, 64438.67007509943, 49442.73148480558, 72660.80816144726, 15797.252962844155, 21992.698076112614, 1044.2200132498747, 77085.29645757501, 26578.738358926723, 19914.4502859256, 24077.68098525447, 24732.45887212603, 27134.813963362692, 31075.134472922684, 39081.606469683706, 56857.099944080815]} +{"id": 1985, "vector": [20645.494547702114, 29603.6639548628, 97268.83399783997, 51986.75300578741, 43282.248637382916, 41370.963090024605, 50611.09142827017, 48485.62788770332, 18611.8489566227, 70173.7048045087, 3048.3130923380954, 37582.696333458975, 36172.38315074921, 57999.96993175007, 93540.39047456146, 81828.83434205677, 40784.52497061754, 10306.613851206426, 35001.127715983406, 55552.12787635475, 13735.400584587476, 81869.68543523544, 54107.884603427105, 69069.10651549812, 69629.8976285467, 22618.58698908915, 23922.233356755994, 11056.554774927152, 52429.67769509316, 59527.28240454091, 21492.774957959737, 40818.979489147234, 31435.946575650498, 74936.91904732576, 93294.22705138517, 74914.72999585138, 58920.8277850958, 59957.14423928002, 28282.60559827549, 53152.19240066218, 77882.25194113646, 73054.48343589374, 83762.30788658146, 8549.128305293641, 949.4532455944338, 17296.210164460346, 19588.29837812528, 30378.98369166816, 76668.60825871165, 71418.56560903652, 59683.34330554874, 12765.824066392339, 59937.13533094919, 6720.720892229437, 39145.48132744733, 85666.70099911385, 94326.81547575013, 72934.16581481477, 68571.51767622014, 67220.03747417923, 3337.806100615948, 7661.7479564727755, 46114.41203458848, 33121.66462939212, 42293.66682323736, 17484.147669769467, 18427.570424302896, 95687.09031593052, 66274.74658447367, 86277.76522189975, 12315.513658689402, 14256.472117429998, 58672.45438828924, 9998.65586709363, 75215.85222256534, 11124.16404753932, 75390.72167060804, 72293.90586526763, 20481.68232049481, 26565.47546900597, 61857.34539479519, 63456.45919577542, 92773.54347623458, 58988.24659126073, 55847.19734620166, 96817.50487508158, 69807.22074219791, 56112.18647341536, 40094.1279412222, 18494.96160398517, 405.70326010531676, 90874.83377140177, 12003.59904327366, 53215.47662134375, 70274.78852638394, 84607.10117421077, 45642.92103567464, 38251.53759577887, 99216.11018937328, 47615.564434600776, 24920.122686883617, 62467.27493797032, 19163.278650846794, 9097.379928212846, 91579.87114214526, 46452.98015972639, 9416.693502347762, 65137.058907238876, 53599.03329477821, 12837.646888294474, 34628.88367368262, 20804.908828168456, 37699.81451179756, 85855.14384598163, 41930.718819921385, 32849.81710499116, 22696.921700524363, 94009.32426534398, 49513.709970200645, 66504.3889457828, 56394.63692293073, 22223.87192039149, 52254.1608142545, 16162.284510198044, 89862.53540031271, 268.2334524856889, 61695.89733784153, 11358.78716951535]} +{"id": 692, "vector": [10585.782602637062, 10194.37754613739, 8749.529567192792, 47777.35673855149, 65030.515244909984, 29208.33864958777, 51788.491963126304, 53817.22237628076, 28901.572159303945, 80394.1459440534, 41510.51259368707, 503.61652689058235, 50086.242479926535, 30030.543908247364, 1250.3551477185404, 92346.06708803424, 6641.095056657753, 4597.115982698218, 78681.87975254045, 10066.579717515522, 29506.682659527538, 49687.555676161755, 40300.012641789006, 36703.561670885465, 62887.849133126205, 95288.58978583317, 17300.340168924657, 34645.052049562844, 69476.19206625894, 30204.52626210819, 76921.25328391974, 92141.36589208509, 71748.28989700985, 57145.68114442584, 86472.46090779989, 56206.82310689126, 94037.1260337289, 11416.627576597604, 68927.8975942547, 87611.70005043209, 40691.94548684121, 62081.28876867309, 79125.66621147442, 23527.86243548628, 9945.24633726488, 40119.5721326556, 97717.09859639575, 60443.90523000925, 26853.6744454171, 21612.66009499896, 25436.037192747674, 71148.67786453491, 80060.45462003315, 74458.60452177386, 23391.62663920442, 84093.8799123184, 87654.06367214482, 35057.67961403517, 41734.41223558052, 88111.22630912665, 24333.58566335837, 19050.291250883834, 38509.68393721335, 49803.35893982399, 4516.450550624162, 66154.41574086019, 16785.115629103577, 73740.5409995674, 32202.065878882768, 84960.03023619993, 27979.749605723904, 52511.68345450232, 60420.54095102025, 3173.765674840057, 44600.10136901441, 89374.17878840583, 27877.223129191785, 55205.62630959395, 7333.070076047643, 37907.20379875952, 64713.84826943497, 62905.09651932908, 24988.237294523307, 86904.09049177702, 97470.88606391563, 86897.50360171258, 19252.72273723828, 78689.0571059336, 57395.58840645056, 12709.211378802544, 61036.60867984375, 15966.552418579227, 16558.516033428117, 80679.756331308, 61743.33898843127, 80734.06800066635, 46780.04314433257, 35909.43281746377, 66684.09625646376, 38661.539453069585, 39736.27924647462, 88809.92584295408, 43362.01670728625, 45720.96479290515, 88602.69224408115, 71511.89420754641, 16773.000169252773, 42948.297013165335, 61661.55268536269, 72854.09137769224, 19684.714853192785, 52936.40833708682, 1931.0789463733213, 92125.34444873942, 44176.360137272466, 44273.431176600985, 23317.13264299321, 31304.997587893748, 373.796613646038, 25396.197094027106, 23493.391539295717, 7308.457920693134, 62181.35122626486, 31639.064284439322, 69660.6237268197, 11351.290818649895, 77243.08887792926, 75177.6640953183]} +{"id": 262, "vector": [53079.076087401125, 59696.892723433426, 72836.72919178185, 70672.79325700011, 30170.480604100547, 18965.061103944292, 39182.33192980576, 87412.43588836951, 71493.74462787247, 13192.568045929953, 5988.852930008504, 78835.03468871585, 63577.25197586511, 96241.38139098961, 80545.73541712044, 33102.92805899431, 41351.303458336864, 80785.11196242433, 10951.808629381265, 27175.626713862122, 96333.04156026397, 82353.8883190327, 28008.70360876586, 5223.271825061826, 54053.84435310959, 89104.83946393411, 98464.93319508119, 43005.162257241915, 26902.342478634266, 4305.257786469408, 54583.887715480974, 50776.286552889316, 50240.5515754585, 98471.52406793355, 36536.59558939819, 2439.2445375891803, 39239.54158870093, 82421.56928394327, 6202.337087906096, 92496.84983435723, 47459.65850435261, 61907.31244668383, 69478.30034420025, 86863.68139410698, 53036.82400176724, 87139.83232419011, 12552.578971908302, 98192.15769012638, 89888.28689291111, 63772.34242133383, 95303.31665559864, 29036.691418485294, 31442.046948356274, 54756.49382933272, 62476.19320527677, 32909.9690412732, 12482.274130941329, 48805.56589896689, 53116.08774838124, 49172.315784234364, 10655.487601463754, 66255.02976855039, 22614.755492726912, 11444.822621572448, 14864.05966775528, 38727.467699247856, 72579.80376818168, 51571.52987949216, 60976.51998278879, 50249.88970752582, 39178.648571698905, 71233.23218814352, 33991.47978381072, 9584.843534955346, 93502.14366302485, 73520.74468040647, 29304.430060206378, 76366.65464567624, 16498.817964514034, 15324.470749243435, 9592.439619325332, 31741.29394563381, 5384.713712146095, 46376.69980833605, 62829.53205613643, 24459.672974302805, 45528.13171285409, 62131.71792594169, 39033.49033742295, 92489.36853747543, 3733.8193782873395, 59997.910470795025, 4851.736671469053, 80941.77802801937, 40665.84696542684, 87494.58186043894, 71708.05324384518, 13525.806770863814, 60057.297541536456, 84103.78472952114, 9238.55847942121, 89541.84330443772, 26345.720724740597, 82920.76410131429, 90309.27474649658, 7993.073964665132, 48977.07921429615, 1119.1291340059806, 94390.82402971813, 49029.73454642066, 89954.08740725044, 59392.365554670636, 82695.97580570324, 29927.31141635595, 41114.56957460972, 36196.240574735304, 19001.340427515457, 31090.138226464638, 84606.3496269864, 11127.32207229159, 84350.06829351159, 13683.448903048811, 58447.29339764161, 8719.164410020274, 92947.99091166921, 23808.086219547364, 36034.87180019292, 13630.204609671593]} +{"id": 681, "vector": [22800.55498939556, 36308.970937259655, 60432.76748587808, 91935.847720056, 3044.4091494441604, 85240.57680936006, 73390.06992600216, 72369.91600316468, 98639.37951871387, 5477.508990876767, 31592.569445341134, 18090.601680086216, 68158.63169229303, 68614.74668132603, 6090.297092053298, 77445.41894822405, 91930.64093253619, 96209.59265028899, 16432.303143424186, 15540.56172647973, 21118.25250519179, 11966.88583707738, 99527.36909080016, 91119.51763974679, 81578.85270588553, 35741.997308159436, 72307.48967883509, 38823.38541619433, 91906.63266206742, 80398.15953698487, 69283.52983182932, 2797.268644478368, 79789.65482810666, 24656.02691312486, 65088.55798189151, 17094.326054131438, 71440.77985007425, 76531.21590145658, 47739.45292946498, 48241.14359753966, 9203.553463296277, 68337.11715254327, 12095.043597223343, 18712.688274704436, 71063.12307462387, 59944.85882483559, 28454.616092163575, 16139.502286970775, 15697.078594407287, 29600.508203954334, 32363.915798668786, 85989.67089664999, 83202.65705251337, 38781.25353174836, 34865.83591939398, 91700.35412535084, 28449.199802648283, 75410.89883542925, 63472.888040292884, 28912.607363202824, 48122.85806844458, 6257.858676906647, 86651.90297121227, 5354.582657963847, 14070.832356735453, 94320.5276062911, 62100.29995933376, 3374.0666671993713, 75350.624067902, 14963.0561566794, 49492.190808122985, 72238.6147853808, 32536.85961001348, 73167.89677602673, 88415.66781416198, 41519.632441489295, 27488.080028777083, 40224.18690442918, 43256.53119706383, 46988.54245206209, 4740.955969765059, 10027.344264522786, 90301.32849751771, 13584.576240360957, 5584.055897334883, 31142.37798944972, 6825.573391545759, 33122.80850296565, 60709.35016162287, 85383.78549134806, 99365.49586814016, 44905.47610508968, 82721.6995320329, 18201.77568135115, 56740.79407295293, 85554.89254001615, 83260.1746640264, 76848.9193071439, 31019.14343000247, 24137.28115073348, 91497.6284620721, 23673.196802243514, 79782.3924263707, 44077.252922198364, 93515.3350076352, 68387.21573354241, 73184.55946164433, 15289.203092242198, 45219.16201275684, 19759.40366870651, 37092.96668301929, 89861.03038868084, 64979.14359221008, 99727.39813989363, 64435.88375447079, 35180.734066440935, 80425.24503170737, 29133.075330855983, 34181.3241034421, 83845.87397192513, 53749.677918260386, 37803.00495518732, 89554.43887735024, 94565.85784093419, 41759.90526150502, 15029.51664990886, 83150.13901406937, 13102.342949569502]} +{"id": 432, "vector": [46382.99954408609, 26570.269532108916, 86275.55223705363, 42528.51522858373, 32813.42799974415, 74867.68014248037, 13577.604753238214, 84106.57317926367, 76374.50648534382, 90089.44470807789, 98137.56466036291, 36746.35355898373, 91106.48115835019, 92214.51734222885, 50705.972931690856, 71626.34067221367, 86953.71901707281, 97792.00846061578, 29568.924420581312, 59435.144900844985, 92308.37839206446, 38063.975000403174, 62035.24647146307, 98382.13148364177, 27771.35463420398, 65582.91112318754, 85870.21877347695, 5799.585458131795, 7706.028488385164, 48989.909834662736, 68321.5434730263, 44545.678684289895, 95035.66612934603, 54906.06698571143, 56707.4466981917, 84599.7848851493, 44122.08356707047, 51098.52223046504, 98978.85415363713, 26245.17939348765, 13703.087310839124, 78328.1378098802, 95274.93195914259, 95681.91295673768, 49398.86425060278, 90809.05179827852, 20054.455407909067, 87067.44216387594, 17065.54116918452, 91849.86233683319, 78904.69773261143, 83736.37053623334, 84120.4009595787, 13082.47876100025, 97838.91913213789, 76802.3938988786, 28968.439351037166, 33325.174648879976, 2005.7949939694674, 67926.93397958545, 23598.481137442795, 63825.47528411937, 92382.09255954457, 26126.58028561662, 84406.89228285501, 89643.52893960837, 23356.340268275377, 49787.49715644247, 56979.93138576325, 93437.67318541679, 20938.138574575383, 65980.66785405402, 80569.02670698035, 89593.17373554122, 84817.91636889074, 26804.903043817296, 90463.80780079632, 49510.34712424665, 65678.88119993563, 57064.2241618456, 2186.99061996821, 23773.35686386567, 21085.90469518684, 25564.140021418312, 69192.91183346606, 40669.704539958904, 90326.2539134437, 52276.31186124233, 37562.75586938299, 23381.903653992907, 81559.25511897822, 18606.338709749405, 82573.21248576952, 29380.640490848255, 38660.04681040832, 94846.27741650246, 36049.48200095036, 99971.77527069447, 98634.59526378805, 50269.43656353284, 26698.172762198723, 19749.551697109713, 23574.18597197213, 71349.60903042741, 47708.06682636584, 82287.77422231135, 79100.76261938237, 17237.040917875944, 29121.00816847416, 81426.27209125266, 80977.05625756338, 61302.44148155513, 85313.13182391672, 6290.982126834355, 32298.66404494872, 95641.9934128926, 98491.67487471351, 63398.231898056016, 57172.58862487028, 55950.037753661716, 44307.4850323937, 69124.476162278, 20225.783406651888, 1263.2102272144064, 68297.51960131012, 4223.165375175253, 98957.3937924112, 4130.15363131104]} +{"id": 204, "vector": [45278.410219388585, 48800.70137904327, 30984.15772454899, 75062.19791701644, 47990.60011262404, 83548.97735504547, 57429.22966568317, 33132.525640120555, 25574.593427214033, 39738.06668235881, 99345.24124554596, 44892.43235518773, 2644.2356221251353, 69429.5879558785, 99587.51917374879, 92944.36103582, 92684.65205625925, 41215.232577828865, 24125.958676026305, 51266.53173188884, 8494.652935857916, 93410.0341327588, 17454.76849304537, 77967.9250266545, 18472.077818049736, 30239.700932375068, 4111.746429853602, 58274.150170078254, 64775.529213506874, 94737.25297309288, 17828.212624057684, 38638.510892452185, 10360.740046057781, 78739.0521691063, 19999.26836166297, 10917.50067749675, 31478.92896305575, 20113.643391096113, 74822.57404889798, 44221.7130226639, 10498.787664782561, 50618.93411746525, 46153.586388991265, 28151.720693581294, 26591.68694508558, 99602.4054478617, 65937.15112823274, 67863.55614759281, 3985.7842408322354, 11100.636163299949, 83696.08475156639, 33750.1158920773, 24471.20771940525, 42657.65753000292, 72812.63699662595, 43.474624493189395, 66779.26285217251, 20094.11709463257, 77980.23695645237, 29297.06042174066, 44082.31568619506, 37903.42270373217, 21715.792827597434, 15864.852310463873, 2092.127586823511, 83719.48688712342, 68474.1270254576, 34851.13769526535, 73511.88434725396, 54718.753449768876, 94413.92965936905, 2032.945411429088, 3516.47199638494, 23277.636809421278, 62282.462592781616, 43472.6483166862, 24543.403947383304, 83879.44407403239, 84654.36391960389, 74827.27344948702, 41247.38335882182, 62081.208404396995, 98207.91410879337, 17180.86086878934, 83423.0603777329, 66688.64288693125, 38093.683143477465, 66297.53404395912, 60905.931933241955, 2992.689833300044, 58148.53036884357, 98141.91173078526, 31588.75478306563, 94403.31924055786, 53360.84558755099, 22002.263409437106, 16362.634128275266, 32430.42585375616, 91984.80589757016, 60239.55649242813, 66181.73828501638, 23997.804882144846, 20841.80397998079, 55839.62394420245, 69257.55619527666, 86892.36596249611, 75489.50211656527, 50613.890578269136, 49050.70874927991, 30904.873085029038, 6852.814697638088, 23961.97683810767, 52898.586866109785, 72683.26606637427, 58859.30036453141, 39284.675004450044, 4783.863982883818, 26326.420017172048, 45435.932313718884, 49318.26310906371, 9767.155882450985, 35299.478253413676, 75450.8472257975, 74610.37006062364, 86025.40641334333, 26092.311200916763, 32405.314086697334, 23248.245804013]} +{"id": 1771, "vector": [86788.97219338891, 59020.648623613626, 53984.17560073849, 50446.82057665596, 35339.0664483959, 43825.08388886735, 77691.30696051083, 59650.70369683333, 99861.52306265349, 68105.52473024714, 48997.75310745789, 19531.387874886463, 90268.08035802413, 35049.53022621179, 69892.77722604203, 19671.512131900636, 48573.050019530114, 42434.78813193555, 44666.840736229016, 15463.029769005998, 722.2530880796829, 2080.027039986798, 10103.664739309348, 48073.07312157845, 91462.9239066708, 14018.543582364462, 1421.7736975047912, 75954.53161374245, 15455.706439701833, 44314.019040124054, 7119.086605699054, 92203.97153829332, 6391.929373655103, 73951.2544780575, 19662.82369469684, 69451.05847829046, 70173.01107439224, 82348.26930853311, 20183.650894117323, 28788.54245011543, 72210.45035398187, 84848.24162047789, 48273.153973816305, 58315.581966640384, 71210.30825343869, 73767.81009761695, 48954.41749725965, 13160.396852987533, 14091.764436214282, 10236.587817189291, 52449.005848971894, 10457.544591343893, 86220.44776746284, 67580.46369684956, 38761.873204137475, 86111.90840310535, 99247.36169076526, 65059.64893272435, 85639.30802810284, 56692.81156849641, 52364.80729992121, 75448.75321921012, 40950.67608834331, 79061.63727478962, 12740.547478597286, 36565.67149866814, 89373.72237140246, 69975.55220030819, 1402.9986251573146, 81267.77642679939, 22622.069286610535, 906.8885760843636, 34018.466025318994, 44389.336204252875, 85560.3735561335, 97739.2780221626, 14227.27610550929, 76526.19537561356, 70344.07879008101, 16979.695945131578, 30278.15780127231, 32378.32776310775, 92020.78429518292, 72781.21566069395, 1968.0026792579963, 31716.451618014085, 98710.2803909178, 7490.3350878408155, 84729.38314775538, 54470.35930723659, 98897.17418347234, 43154.10306184886, 16100.281860918674, 74724.14031954999, 65921.71583388347, 50635.57320250756, 36704.57171092317, 96175.69093946162, 28626.503136839365, 75570.37624766593, 1016.683555482778, 91404.44801381647, 55318.44449690674, 50468.17760186433, 23659.79279478323, 18735.001624419358, 79352.40307116989, 33274.65181640454, 35601.017099943965, 93299.30879515228, 63744.50572232774, 2950.84698478999, 93457.66111145275, 15134.201479557141, 84578.7396773984, 30917.428512240196, 81041.78168619158, 7091.206282884921, 4454.8920421672465, 90008.38560442174, 67899.39702193362, 25369.833248625306, 43215.41242631115, 94114.78716535764, 45855.00000748375, 50867.63763152359, 14553.679739118974, 59327.955722208346]} +{"id": 946, "vector": [3220.205439182089, 32314.894444660724, 9708.924361691374, 51237.223735886226, 46733.588967019125, 95835.4746522402, 88054.77326758215, 76459.31218081969, 36698.231103730395, 93045.83788282961, 65946.92584262567, 16608.17617437299, 20503.592038301354, 2445.061509474078, 8697.430660748818, 23255.948333408327, 21766.435570132937, 78580.45873687985, 96244.41008272344, 87377.74857002504, 15364.859515586626, 81403.93718641679, 31926.875097176842, 87612.18592118124, 54530.12885166419, 35740.72925137341, 62121.560190077966, 13873.352810370898, 4895.46229541955, 77138.06377449687, 30223.94185844488, 3404.019682834747, 85223.74938692892, 77652.2298029818, 14846.260280333656, 43673.94871692999, 7854.518028467217, 21483.62950057142, 27462.255615580478, 48720.292836659, 44618.99657308539, 73311.70531496887, 25198.66081177612, 68431.73825787684, 88309.84779447655, 90127.27486221316, 39869.192985336136, 52340.947954872056, 43909.62104363035, 51805.483669905494, 32694.432062693544, 71491.14121221896, 21767.20596537115, 60635.827630894215, 14396.225343782931, 66946.21822441, 66590.10506160314, 8085.232106502205, 60562.82414948606, 90877.06520436158, 7277.515061727479, 89564.60325526778, 16860.11867398972, 42679.79828856432, 28511.808937088223, 98902.72584693437, 24474.91666276671, 5561.584570794942, 53023.668353011955, 50339.82545404396, 52454.23322429498, 73714.7969206306, 28948.28362010814, 51701.34608356637, 31558.91408895609, 50050.02276423591, 24303.686238502054, 37624.84336369218, 61297.79429167577, 34770.100031617025, 36341.976741294144, 53806.593029014584, 37908.85035363597, 3408.89596592735, 42000.64608639601, 29144.443259436448, 566.3563604596966, 45489.3442739941, 80218.72427512983, 63449.35095120366, 35518.88849082641, 88232.94165791642, 64089.48796316051, 29255.846785237947, 96173.73319809974, 11305.414315320617, 70371.4780614358, 5894.164959344861, 3868.572330126063, 58362.4789910621, 20045.313663592413, 64553.06916163648, 94107.48987138412, 31948.32734203741, 97000.74982045453, 43646.075619264215, 14803.840472729356, 73032.19106428148, 76880.04617372426, 42452.42154646695, 8638.626072409716, 26858.993824152643, 75592.33925358996, 54823.97989073169, 82669.86587478146, 41053.16591401762, 54852.23196993277, 95517.85629057928, 91779.14316940271, 34652.77812759435, 41076.71586607107, 27996.771709711542, 97680.20892470924, 50737.225929454224, 26700.910898960617, 93358.90305306416, 71937.3833110875, 8120.222111021647]} +{"id": 1220, "vector": [36309.08511299641, 48041.28030735126, 30349.79820506648, 30897.212289540486, 61420.543635545735, 70916.16311452577, 84778.9196398892, 69592.9446206461, 59249.38262322462, 75953.11108545156, 64382.830407102454, 8961.537865389979, 85083.03287381014, 86670.07536056063, 54333.828352601, 646.9710321167277, 71075.23019159377, 50321.885714008604, 68677.99876575734, 69401.9395264469, 70922.51834469632, 5287.075277600051, 81458.43338061581, 50149.59808604331, 9500.817666510153, 54311.59271356184, 38459.16075737368, 4994.368400626603, 44403.13804367682, 71152.6305037379, 99483.43449631365, 35712.62573805715, 23679.08606370488, 81258.06398711502, 35619.7218241115, 24270.39967383948, 9584.495559378693, 3049.4451473304316, 42206.146854014994, 80389.8254428096, 69298.90852717096, 87654.62983187127, 20678.788329576837, 18144.83584041826, 93754.32275381213, 56831.385501910714, 59568.29021711741, 89429.90939261975, 32348.928338913385, 1994.0141671637934, 77787.69245565061, 24031.770043104338, 67160.58993275376, 15113.622566769336, 32646.99925863065, 61813.32822306373, 52250.763691725486, 97858.52256617758, 71427.81266589563, 15367.326972816487, 72571.83616755376, 87158.20188301454, 29854.140601662817, 49249.97070074346, 21266.76239585692, 31101.503178902225, 54562.573222961175, 68002.58813196201, 6470.52770855755, 96283.44893641281, 53626.38566502015, 69751.19976498965, 10772.583352062104, 24583.147592696852, 2755.7219482895134, 47489.115650870495, 29541.82180009928, 56772.801277826045, 10479.29435813899, 20543.484315773487, 2410.8362242840853, 27608.516420553042, 53675.22489478145, 63585.03796595664, 47886.768486428686, 13657.652216033555, 94260.64099306936, 85957.08192217913, 31338.98767309925, 63038.14435177094, 68618.10014023287, 55965.77908293065, 14471.739295151476, 44329.82640262036, 73022.22021961294, 17972.390810746307, 62395.618685690926, 89934.44469373855, 30401.32456742015, 36511.17265464391, 85125.17751245435, 16192.785024691226, 18850.232861271143, 35839.18617242461, 28093.32907025466, 88502.9370069743, 37145.720943996464, 99384.36427971111, 14486.32925981177, 46653.46251151038, 22065.67715023986, 80204.3171101538, 35735.24702615885, 73973.67493554928, 35004.86831006537, 52140.05485786872, 33259.660651301114, 69383.1866901668, 57170.81595888419, 27584.691237915904, 78377.06130799673, 43692.1091335282, 47536.43306944859, 16266.756223087243, 44119.2091975817, 63160.349391110074, 37575.64733027533, 94326.58122975893]} +{"id": 882, "vector": [79919.2167525092, 75511.92030184159, 40204.30351331998, 9566.586606326255, 66608.84563149197, 70819.59187186397, 61748.05664671521, 25466.16819571229, 66341.74179584745, 16388.762385705068, 77015.77136714244, 36390.72654341537, 32872.64280971341, 60999.437848588466, 28603.38502459333, 77943.97514733025, 3973.9864424516045, 37781.928771978055, 67935.91043494834, 8867.14440328562, 29123.98426826538, 10189.280774109287, 7098.8553903526545, 23470.86209710395, 4324.190941432505, 34546.647621413176, 80011.83525450193, 63525.34643325064, 22356.591322323606, 95467.0953830917, 86102.71243254471, 31520.45195819314, 14597.050191698014, 8474.887171052114, 4794.535508679176, 62246.40616043211, 7318.448663155375, 87649.11611430018, 72993.02365382957, 43795.564337671945, 43576.625437649695, 41888.72583477714, 33151.83976524858, 65687.2543928588, 1763.3857562638245, 18938.106354831994, 57081.701072398006, 21452.89666396012, 90818.55458822299, 94093.90379799657, 34952.718992002294, 86043.75552867583, 7500.995985535641, 60072.867442806244, 16772.19739191289, 49718.66908275121, 14925.312550595516, 85952.51444090864, 80248.14666210994, 77333.35702501635, 29652.889510675173, 66208.73220342779, 60388.02119955924, 11300.706491263234, 14719.104109339132, 22558.524695019412, 1.371374321457175, 1407.812222706062, 95204.01614806964, 59650.64475782811, 69941.75002890688, 97063.1549074822, 45116.241684712666, 92958.74992737478, 13079.457596203138, 30121.375911919524, 23120.62562086621, 46657.59253125582, 38017.0033076695, 64303.69518574425, 93290.05582288493, 64955.280312950956, 37603.758390711206, 12093.705341502813, 20534.661994958893, 80149.00607949942, 51445.14073522396, 52113.50090334381, 1877.4179245500843, 38979.02523087648, 25331.674809956505, 29887.373823796992, 31750.916016575702, 25073.368220398097, 89260.01929018153, 90366.59637851722, 35871.10184168093, 63413.60301700928, 18173.331637416988, 84175.96949500382, 84724.09366182233, 90667.03899675522, 61946.356888212176, 82713.12585056276, 70489.13605885352, 58108.08638028917, 95674.19255230317, 62886.01687291666, 27878.162170827614, 57440.305786743615, 80373.80359352745, 5275.518330801144, 74400.51980068836, 18005.293755488074, 54099.93530318649, 45864.75422337372, 67837.18260456341, 69486.99795565702, 43240.91118294331, 5412.402400848515, 82626.66033964315, 69416.70929935171, 38556.41638069872, 77994.41859242268, 53835.87806634558, 2149.82469910705, 52446.56668289964, 40616.17383465903]} +{"id": 1578, "vector": [99479.64272493693, 9554.064431811172, 59783.18543569236, 12345.126606914058, 63520.13126499412, 81415.2623824191, 4072.6458577275303, 21496.138295354438, 75543.14972989698, 56773.96070556781, 96132.93809263686, 19670.127136827774, 38671.80801144013, 53789.593677752564, 11593.88807214351, 17574.970846125725, 9587.39885411204, 69382.12135152405, 45254.1537797229, 7399.993385527392, 92138.09861361938, 51453.84290214614, 73951.09765912923, 9668.233352060251, 3013.1651131449667, 61800.63339490097, 16007.987457406669, 44153.07833053828, 33261.70681871625, 18917.660736300935, 50593.318192886494, 57762.412892902415, 78508.52855543919, 51606.10703366567, 98912.82744471553, 89672.1879707277, 83725.78750623744, 55753.683348187835, 81985.36935071489, 35966.12513472055, 2699.428003029813, 3058.9996793836226, 26016.750985381033, 88951.80652175823, 42821.23835087592, 86779.30035023781, 15842.339151276274, 13709.769918220249, 86007.4285251395, 35267.570244755254, 46621.13329381059, 20004.072436340226, 97065.22651764395, 56049.07254969187, 55256.41277767365, 41382.49540740443, 77552.17886946286, 83259.57814799646, 71442.81909492635, 94292.93957014814, 40101.814231085475, 77099.28662972215, 57376.95730195663, 4256.496991710068, 13354.647117684648, 67966.05999624057, 86328.08318576777, 34661.06793043777, 70564.12587086752, 98496.83299493298, 29551.73173468162, 55133.24800792163, 11856.179918399446, 53154.8459896227, 38607.553772813786, 53987.689011196395, 60147.89042956112, 74316.06678132349, 81710.13971589279, 30272.373156233956, 84286.66431821286, 71883.83610178757, 51135.25601585476, 99080.65268790393, 17789.87992384723, 74963.05746236799, 23157.418663210916, 84674.1565666425, 63520.8333066718, 3632.31173125369, 83877.29505866134, 30607.50949810882, 22110.9767038255, 94210.06447319541, 49396.09485159623, 6717.685619170732, 99198.03913301232, 66709.51820266974, 34185.91138788799, 4720.642815265685, 4434.716616452417, 59521.59117976263, 20406.35027656609, 47759.22819256959, 57131.57453350137, 28659.860029611984, 31233.16830988625, 88341.98600999237, 19888.976347667376, 12433.937787628534, 87449.65895965384, 31995.320042665975, 66412.93731253064, 1033.0903358433675, 4821.0482305221185, 30048.131506038568, 78752.83893076025, 3862.692629832587, 59966.503808906105, 9005.656861029376, 25579.247938271266, 98752.03539920517, 69960.34687604442, 13127.876861769771, 30174.616007906374, 24924.162135068185, 76471.84388349351, 81154.80029744425]} +{"id": 1575, "vector": [18185.4149252057, 71251.56955242847, 48424.157929445544, 36426.283686196606, 24074.248323423773, 46973.43458175115, 99722.82818583689, 12946.172953812296, 20008.930917087488, 21594.46336560097, 83444.28846817605, 90530.13796563268, 39498.6988395076, 9781.421067310646, 2953.742302164897, 30353.136144950156, 53647.75086552345, 65364.63632875493, 65162.20875956131, 6773.876644761678, 44435.500021646236, 20881.889469659054, 81372.18871331564, 47068.317343245435, 12692.133176143827, 87298.92909130965, 76056.92912045745, 41735.31891497545, 68910.22997136247, 27702.64308438232, 4382.956885444478, 34088.94678095115, 4440.319745542421, 29082.093869091917, 2682.906494493942, 90235.89154335055, 96718.53124852867, 46719.89054388841, 9974.720409945116, 27443.253789703325, 85471.6217291986, 24099.879325251062, 65715.0695691337, 7547.5274902444435, 98575.82326311481, 45715.76532955526, 46850.53143043404, 73390.68980019433, 68777.98987292605, 37994.35362801977, 36665.61146805377, 96064.85388863327, 13838.675147465228, 78874.05805488786, 43017.57231949016, 63610.354513822334, 34421.80220506397, 73212.7644221382, 46657.93960348992, 66360.74483960512, 70271.15223211836, 93700.07643737282, 32929.731295107675, 24888.657000691983, 63414.19575824249, 44754.61945264939, 86599.48643380792, 11953.909604916778, 73538.32682015418, 6606.184737355303, 19653.868759637917, 43673.303141785225, 10442.88425861053, 27368.28649913674, 33418.57008867889, 8910.721256266163, 95395.12905456197, 55535.26880154667, 83185.28065892046, 97824.07627006949, 6070.894768992852, 26199.019464541463, 33912.98377753834, 20612.27842372494, 61543.192903812764, 22154.39301489556, 29866.897836231055, 51089.00935202735, 35418.71188434631, 96012.0475969277, 46919.504394224365, 80510.32450204357, 53950.94395878858, 90588.92321416263, 2999.7356393862697, 85849.58750468852, 87073.2037743723, 30158.602394038913, 30736.81600181429, 11477.997891751957, 73624.37474226157, 73448.03151836699, 42500.160818811695, 39073.96008965488, 88380.48504310376, 90494.2310754719, 36092.67655393359, 79267.54784872475, 36936.791602838246, 95278.62541495422, 90413.93811945818, 43705.63046567058, 58315.485712834925, 87130.56966752108, 78694.82440305877, 59855.41665768834, 68965.78027678726, 4345.266950547688, 23217.672302420033, 12039.362516628993, 25851.187184369694, 47434.72493649419, 55109.075905775084, 53444.453284509706, 98218.01606250618, 27251.82053730589, 6437.284640383689, 89688.74847716764]} +{"id": 1979, "vector": [45216.56546303309, 69462.78181622403, 35554.10619992353, 87720.71979522976, 19769.437709834514, 2639.4231883665743, 49170.304603468765, 65330.03602801492, 54671.85338007007, 99491.23069414408, 20296.19037717787, 73282.48271509183, 33145.44778911975, 85908.94913383643, 14549.101430679411, 51813.73214396515, 33244.36055063075, 11415.155178271918, 79043.90301582277, 16525.32022296922, 28212.453141524897, 89296.95676871885, 40550.39067159341, 70356.09642939732, 48936.105922448725, 66746.66767416467, 41270.30450856969, 61296.35743122039, 58051.104127951134, 71516.40335474299, 65482.898525765246, 75040.45316095793, 99776.62347512686, 84847.46232227466, 20918.81321019281, 71008.96998558917, 90543.1752391681, 92395.86390038078, 2043.1903617872615, 39400.14343387058, 11477.089322904056, 25810.651046714695, 73173.42460101159, 71950.04171124089, 49482.641039801754, 67967.92933019753, 25580.236049077754, 57043.14433505264, 75674.4058121496, 50341.3017858921, 6946.114255345137, 88487.13777062189, 84485.83705573068, 16382.910524092653, 17804.21931096361, 37264.50056559041, 52535.24266998963, 96927.00258262688, 15928.69767326771, 51909.56728302981, 60692.108057040874, 46767.3846390587, 51680.58430748852, 86603.9888298251, 44438.98034661039, 93992.118597579, 46986.405803067355, 74954.99410105398, 59931.03662820738, 69154.35715181075, 13803.222510697, 57975.98112066904, 86459.89169827537, 96035.48654448181, 68697.84232946509, 50709.16473200989, 65402.06854701559, 60404.70640540553, 41156.983762683674, 33672.818791043246, 23239.590316866597, 17556.322869085907, 90056.61727197577, 80418.54846062581, 6591.491090636848, 10422.947721326193, 42789.37141405417, 69962.99171995759, 40904.684139643054, 40721.397239549275, 16465.647589042608, 93131.47805885907, 28740.14266798981, 39141.85098745915, 92195.13218109733, 84282.77905197455, 481.03849086525986, 97148.8623852955, 76333.85542463166, 54014.470023967035, 68975.93431955988, 86009.46045436316, 70149.59965957123, 82568.99763669592, 40264.03454906483, 62963.809911317956, 39163.90734566453, 11545.120323603498, 44860.93369974531, 4035.635897998513, 43758.86890143137, 33388.027452800714, 61955.25705018093, 3031.720834783358, 33160.50919986398, 88381.88674884429, 10141.10474915858, 35659.38867021166, 89847.05156866123, 72744.33971248347, 66610.56399522925, 56320.407534346705, 57193.86528763966, 41376.01005691314, 71965.32728888051, 28683.492294103762, 8955.433924564748, 16656.888889879996]} +{"id": 217, "vector": [15234.889362418091, 78629.5592059779, 89731.47109652606, 83817.03438739404, 85151.12736808999, 90277.98794335524, 44142.181434213366, 40735.39327636214, 36441.72866443902, 96751.85157978829, 69305.56237810456, 92624.52411327175, 19470.156690996744, 96613.33328627468, 6018.822288580317, 75535.05579814785, 42430.56261990957, 208.82590615789454, 95283.48186046463, 89253.26918754088, 88385.82110960802, 58772.802335882116, 34798.67465099419, 5869.145855274971, 20956.959060361678, 14738.019025558913, 22.19184875580371, 61790.774499926934, 58342.289975478176, 59777.71164845128, 25754.76306768596, 5172.730624190413, 21583.96186552639, 35104.985860732915, 67930.98172197708, 32288.436965372213, 31196.725264064262, 35867.02974518587, 67027.47086217457, 25573.72411633385, 73359.97618691444, 41009.721515790174, 46794.12191458325, 89744.9736197951, 72140.59386933233, 73516.60255034929, 54298.44337718107, 22306.0778857878, 72021.75680098943, 77582.23552541372, 87384.34555869113, 67680.83492476938, 32231.28427359945, 719.9077352306405, 66327.69179312195, 67893.39681712814, 14097.18408858075, 43488.99941824498, 46366.2132766143, 31378.936669184677, 26816.751609971034, 89561.43809085095, 96511.31409914746, 80070.78200252533, 19483.215310436954, 951.1030433612566, 45187.035162760214, 82587.10628561115, 87528.14168724138, 2066.5015717307724, 659.660227118275, 21361.65675644096, 58126.19575828005, 94729.54128574803, 72840.59605864233, 28664.35405587546, 80374.51912522381, 57868.87762232482, 55695.506980372666, 86866.29954958976, 41768.15574828219, 28869.094629746094, 46380.34210633314, 40492.079966062876, 67277.07724942347, 9376.39441292375, 36020.00930848205, 86765.23006737677, 125.37851391112298, 14676.377152686138, 18262.739456210962, 38027.51586788899, 73076.28865185355, 2818.0701703201194, 21443.721260226033, 32245.257451170008, 35765.62291209935, 52196.767441941694, 69949.08802806919, 784.506465597401, 45822.443845728434, 92199.36770380787, 89543.29413878416, 88421.9690011878, 76408.2494652335, 58518.57022212273, 39068.819098234366, 71529.56933977242, 66997.7814817666, 363.75224850567764, 64869.840111189944, 57306.6199594858, 23015.79064643774, 54283.98535851332, 32004.412223749023, 43760.13186035796, 4113.95927398489, 97880.15273294829, 59106.904121561645, 2678.9795219488324, 75259.86713933258, 52997.53278173085, 12128.048269626146, 52809.01701310075, 61181.611325791986, 56174.04261388224, 44333.73255129554, 76589.62504193743]} +{"id": 1490, "vector": [58070.14498447154, 72725.82664079634, 69653.45592050934, 72138.02639898048, 52542.225361634446, 53076.796885954325, 42334.67387802733, 80212.96374146437, 79740.70485596903, 5430.460617440225, 67170.8715141898, 76475.14341716286, 18858.46346035008, 78939.26387911223, 36795.22794330168, 48092.57630033805, 20802.45646910258, 2389.161604881207, 34151.984886248676, 27868.736593625566, 10977.427472797273, 41316.68401754881, 16232.24727509045, 15155.411267412012, 46882.5448486005, 32922.95399750647, 46741.957686853, 63817.25167011183, 95899.65150731297, 14289.657846009684, 85157.97387712439, 95349.51833322294, 1275.5528109223978, 97391.92191242849, 44202.156762256396, 4131.627117482905, 63517.99580909904, 43593.85164650525, 53456.85185147666, 8741.776999510586, 46343.56001689923, 74774.42723620948, 80836.38893368108, 6005.707198486487, 13047.920811954162, 43737.46601801536, 55185.02932435485, 92943.83479425653, 83697.32652569827, 74111.08291279452, 35407.83591548176, 49126.7374309798, 80605.14200118431, 14885.154042809578, 57203.0813072223, 37009.57679411876, 91628.09929451795, 92968.31510543551, 57763.589298944775, 13622.820850473994, 73851.65016942659, 75253.51300908624, 27750.903684208595, 49434.76345589939, 25202.96762966813, 45624.7254473645, 69634.97169813704, 67662.81745020665, 63305.1282712559, 9665.481418138588, 74433.78399653321, 52820.64071490266, 2221.022437490794, 76797.72725913607, 49992.30550577907, 11089.971196030257, 61304.80030008537, 51296.734664019474, 21794.294542755633, 20311.775333488036, 14099.487205260575, 36281.70899939099, 27963.4204187859, 51807.440892640385, 65078.73730034131, 2691.335466178546, 75019.49902294033, 28180.562845071123, 32115.194593325126, 56347.601557489324, 5625.4337155007715, 27582.937057369905, 11018.052586824268, 6990.570363456894, 36817.43205324962, 47423.68935061998, 88547.29021957127, 28249.698062534502, 92764.06119834732, 58085.25585124519, 83406.97401547998, 89469.10553706306, 89590.17380396008, 75547.99841101673, 65389.20591191613, 15508.044670396348, 6576.2742923425985, 7708.0107981009505, 93441.32117306066, 72459.9781383333, 98693.01209278188, 38247.859395039915, 99612.89922351614, 53015.097907662064, 68828.97755225135, 65971.5647244218, 15580.391676510286, 63739.97745000117, 90235.89169550544, 97949.07344635016, 47416.15275154358, 97373.50550689951, 10039.477869812563, 63717.82532811008, 13062.851525506492, 24461.873709537096, 98521.21977482978, 94129.4604647456]} +{"id": 1429, "vector": [61216.210403781544, 54108.632732768914, 98823.9349661335, 78436.44000386437, 69745.62369828497, 26426.674425847254, 5358.185551113847, 24883.578418907593, 58448.543997870685, 27485.293269523103, 41046.10490904729, 38032.66169870948, 21033.190474578667, 80029.11890008695, 87870.12102935098, 73151.02919601585, 39705.2799646322, 62997.368125819106, 86349.54973209185, 18429.5653127193, 11097.800150960635, 94445.06748783593, 81725.92847350323, 1975.6676380668137, 62682.761399363764, 45643.880141903835, 40341.927379217435, 10778.353330915203, 10056.038484583407, 27648.300215312294, 10282.53325783327, 32187.45075333724, 2787.990001076801, 3707.395011577652, 37931.78624131743, 2715.27550305114, 43489.93098818202, 75801.56300329263, 50612.771275864296, 49825.52968487123, 24396.494525143575, 86020.45571671812, 71412.3299651119, 3119.25790805776, 29325.384909498454, 169.55734685457634, 96761.96673678418, 82081.59829162102, 98503.39533695222, 56255.158436128615, 75681.64542626213, 18393.799583331573, 3456.24198995228, 35796.8460677136, 6957.608134081083, 94064.92917445039, 84632.3638262087, 81105.4470884073, 51326.97158349493, 88794.21641274435, 31846.91966291069, 97282.78501536738, 84823.3098742419, 69878.09672067598, 25831.975818723142, 63587.13706799503, 53017.28669783469, 82719.21269562385, 35733.98968409376, 9742.329494443647, 96737.32747902392, 3720.725784984924, 45394.81458925495, 14973.257859448497, 56634.26248331385, 92040.73532271296, 1103.2045140305424, 33098.18760796091, 19676.257939448216, 8549.928447006938, 3283.4089267819277, 25038.478702728105, 78763.74164422983, 9628.588412456818, 22019.846542335687, 41617.99372818768, 90425.28648079858, 2431.3204147883184, 85282.616302124, 20209.15056176349, 24255.760389008054, 56968.34428205866, 39310.02560368481, 19380.888989593135, 21628.993025291886, 63798.891309903614, 31057.085287292306, 51741.8575830937, 72529.7797330534, 21441.9212625387, 35888.93147854127, 17239.866168595985, 50954.47120749249, 45383.58216232834, 78926.75690836838, 97358.50590692314, 8624.982125921988, 24777.967743412388, 66635.9537100121, 51827.94788337808, 99899.74733221222, 99044.93417442797, 6485.055688995755, 29457.22670204767, 58226.036214434265, 20211.860349255807, 82136.18105552971, 2718.8828279982813, 47680.67163460037, 65525.6263429315, 90904.61620298692, 41025.913757026225, 61294.57065722722, 77357.75418440536, 8847.786747864538, 28364.544903767495, 63469.76909574261, 98726.50262007615]} +{"id": 1725, "vector": [35579.09020128309, 41235.605532084584, 40264.80675901557, 24527.639271429725, 3669.5826614073444, 67428.55676238477, 12413.03278949616, 97155.81767473134, 21206.66057316808, 99195.18624490767, 77673.3609452823, 45246.12405805993, 19083.91699866444, 13593.044287836108, 37844.55399211049, 13135.277195252414, 59794.53833854617, 39548.420607337175, 51191.85343935024, 95157.193114032, 40219.252636759076, 15745.012768582788, 74951.33999099617, 70998.01274343503, 97303.79722154618, 94252.01814033171, 83405.25456971703, 78273.1866820481, 44613.70235395411, 33860.35365388024, 31787.12249750647, 81321.294399037, 78385.58571821533, 78602.49288130012, 80644.16297428632, 53391.464150816704, 35732.239358252686, 53570.3364838088, 32998.33349229444, 99112.04475159428, 36719.75887332187, 56248.6476170517, 66307.47152221845, 98110.30091069838, 40054.431085403354, 13936.291474869866, 74744.47494241457, 37118.94869188819, 43994.7794800329, 24634.143041414824, 54346.02040185629, 95460.56974314454, 78110.35928108876, 44747.670197673084, 54249.51900977398, 64580.11172642896, 73122.64753519424, 46160.77223649634, 91863.45579557655, 58364.376409628836, 24863.742318683548, 29262.11109900023, 52210.638547801835, 28695.992310181795, 52105.76705765425, 77087.18134788997, 27985.86438094849, 6790.653540591818, 81012.07983209904, 95123.16399002742, 26891.051044451975, 97345.51523013519, 74170.53953322634, 5500.654859458298, 34892.30143957322, 6999.983481068905, 33518.65580252937, 17790.21190529161, 25921.741434455915, 15900.823357015215, 93767.54276816183, 4757.589285156294, 35626.63981916932, 19097.1141811634, 48046.83789986867, 92030.20800056169, 51534.507037298295, 53823.00086383444, 11827.918457683296, 3077.517084945236, 33253.40198930581, 2528.177573942403, 83706.40245557501, 77288.80392754289, 8900.22493187017, 38134.323152013574, 52804.72220111998, 57533.27009398769, 54897.83022794095, 63524.13493544613, 39153.485633243305, 41362.449392929346, 31667.54100845741, 26078.711224489547, 715.2915420674976, 58464.70282840466, 5403.072562753664, 75476.49717604545, 88412.891032023, 38543.06974672006, 92029.53074778701, 5937.9815549606365, 92936.17537379658, 74238.47489467065, 65949.22386706011, 34391.4338581152, 54773.08141661961, 18747.3050107124, 47607.18124098552, 56830.660286668775, 23933.212004465575, 96648.63445738584, 43791.676005122325, 29301.769658232435, 8145.755894434603, 46277.37839074134, 57639.95252173962, 50023.63620722223]} +{"id": 1704, "vector": [98826.10101810936, 28938.811616497405, 68658.34235852894, 17272.51269884328, 78765.37674558922, 81100.257734264, 55209.68255278528, 61546.12966894384, 21676.09204309625, 87857.67187120103, 68167.22856519675, 34055.268162662236, 6027.772388201757, 3149.923411593869, 54883.82124186904, 30275.514255331414, 50701.29329804702, 87399.91283327689, 53234.01254950248, 61994.12252549296, 9526.399761304194, 69948.56960625085, 40690.33249272698, 48911.55449607691, 70252.85000662606, 61342.945440173826, 74672.2816699523, 36257.78862848643, 30809.706703309294, 54859.018824863524, 88802.09645412762, 80550.74820382023, 14695.73740228537, 46566.246974682, 22185.596360614567, 841.5613669579546, 88563.59008236797, 80596.04034063738, 2359.4590879918333, 36376.12909035199, 93827.23856770218, 76783.47731578529, 64581.00454740477, 44127.89923269685, 14713.859045961197, 22572.22165635997, 18811.92905741026, 56597.38212416002, 11560.978931074573, 19943.240828040554, 35163.11373889844, 7790.16481428082, 76868.02502435152, 38386.041710028796, 98940.32982392245, 38383.38503052286, 76466.76139254254, 56084.26819251079, 34372.721561042206, 20257.71793214137, 59575.37649437768, 47921.22360210712, 84224.67934764095, 91330.52140300594, 74706.92498997583, 52986.712667505184, 38904.21640508339, 90631.92984236081, 93978.41658117346, 30347.382345391117, 62917.16685519501, 77787.01866803522, 74007.62755418467, 24545.711749273723, 23305.714097680862, 67586.00728563327, 75089.04612942811, 39900.07980104887, 71994.77480647879, 85192.54496578885, 44724.745542563105, 40850.03737533744, 68331.40326829832, 24537.367589135294, 25629.860545152605, 38110.68059717651, 16269.056005358063, 95728.8159127646, 23027.38213313219, 80528.5247727283, 76306.32605440763, 87673.07594696176, 96464.14393279933, 3465.715537654812, 76172.15701469392, 74601.33219479871, 9997.740082420514, 14597.141424550475, 80866.71329717325, 79865.95628542866, 99106.12315930905, 12900.467300489016, 12201.845550304679, 69110.76777113647, 42813.92571118319, 95928.97442173402, 5093.660245417464, 11986.0850271996, 90966.56958357619, 72093.30709587851, 64218.677380309164, 87000.17236967142, 60378.70755072089, 91260.03006017825, 20170.652940591295, 3784.8667632227275, 34890.51665484242, 81784.9247169218, 3704.458792357523, 19034.33986502062, 57640.08708173848, 86312.89989403421, 1492.6381575624869, 12771.62046333652, 3868.284864751459, 80744.45232479299, 3437.9785054575173, 97882.68689138639]} +{"id": 694, "vector": [47206.92795246954, 43925.05040652525, 33485.959505474835, 12702.238403876887, 63582.08849328063, 34704.459876828485, 73594.87514120471, 46167.591012103636, 84567.53429308489, 42060.340418368534, 87049.6572777459, 24913.597324918057, 2300.721387183624, 33063.5667340544, 83234.47734571852, 77961.26270437846, 60692.425064715775, 13223.907875246643, 70995.41947446889, 26096.96753756251, 87442.65716166419, 35088.8761659128, 94554.82954267078, 76925.62633610617, 54365.53452472806, 79083.41971642054, 99439.10550591936, 43029.80807387252, 44466.822867427414, 81284.3271440947, 74916.88983139304, 50599.459659336666, 2047.5572947561304, 36716.78527689948, 22515.4819107946, 96788.94908847094, 93220.10146491809, 49270.60349622622, 99712.41193042147, 68955.56595541192, 76747.61275521545, 25345.82255294834, 98123.69819968438, 956.6388616455979, 22510.679889383413, 95307.6050558143, 15805.936572166402, 73357.80143309286, 74629.61273681627, 15709.985120289894, 30201.38321221789, 75619.02790807713, 76602.30263361498, 10181.166707312472, 54368.788680033176, 78251.98045686627, 98636.355729907, 62328.003633838656, 85803.98021682163, 54737.42151829826, 31699.84400397312, 55934.74585091115, 98604.73217979365, 10810.823028173432, 63082.15318340329, 31276.133310243127, 26028.197990547586, 65122.10981045719, 90160.99501766401, 75320.61368375509, 18420.97328383675, 62875.12564874198, 44928.54683595348, 89324.10968478494, 39681.29257033733, 46544.01396089144, 22543.5675640381, 31173.940588631354, 83618.93572881783, 92983.77838668438, 68742.44046104136, 36741.99861309156, 75879.11437084763, 17375.216069163536, 32868.83220308444, 60457.845567201395, 29001.76854860297, 63334.14820492875, 87190.45647260091, 56763.79322076074, 42227.30200980559, 71344.80220896944, 19586.644193878878, 80355.68730843128, 74718.1518894674, 53203.42921107586, 13702.262463377812, 55710.51456696611, 5832.507948600729, 10287.162083301082, 42843.113926772414, 70344.03746127019, 60778.75946767597, 44720.18875888653, 92085.42980299419, 499.08620197215424, 469.37773875691533, 67650.81392341264, 36271.66844436766, 84147.1163328676, 44590.12243289065, 51216.902307238364, 56475.13151080702, 44145.28933287307, 10731.288288372387, 27170.41784080829, 70091.8393213511, 46694.312719833666, 92316.6720068588, 20635.412125112805, 96707.54777626906, 70312.80457628137, 96049.00387783367, 23755.048163729454, 97126.44286474132, 86787.7667315542, 44858.56393639522, 83341.0046509502]} +{"id": 927, "vector": [68753.20265388952, 53193.20321952051, 33708.26001804099, 2417.6201815868326, 62619.81720380113, 63579.25696830134, 29200.918566991473, 93817.420309919, 8996.201573848584, 54940.86676999388, 58967.77409951027, 27162.263283468736, 59327.9422816011, 54208.00816160246, 33804.68246241838, 5752.414656110594, 57280.23145735118, 90283.04519049976, 52504.09494238726, 25480.812159838883, 74527.11105060678, 64095.47210782764, 18723.018235885458, 80774.72390629318, 78619.8130821741, 85210.62933412292, 49067.72195108415, 62828.495727423004, 78829.67428903427, 66949.28915511351, 69983.78989445824, 1492.3985261003802, 42323.888547568175, 51452.01814676613, 48630.175911426435, 57132.20710432225, 18457.701349170886, 23651.878060380528, 33817.00915458095, 29385.418543415122, 40213.82566874692, 11643.8516408509, 96376.3156693943, 79210.23364680346, 37346.65811947059, 58823.224395956444, 24354.43403607034, 60455.72914525439, 93135.70518881445, 24167.495931634276, 36621.18035048252, 22541.72490290638, 43437.168654277535, 18910.20813283161, 1370.8509485670372, 46319.5115943176, 25240.595722815207, 66531.1366797683, 89537.28320453953, 45709.62590938562, 75370.90676246419, 92851.04688950455, 6263.34766108978, 72153.28977477133, 93584.88559341342, 40797.46148868401, 4033.878700362781, 85558.1683689748, 25192.754780874682, 14340.777934814409, 1242.0959327362757, 25638.67436029006, 1767.7315389981297, 78881.86757585149, 84089.81989156773, 15798.067239809943, 40477.745937700805, 51985.05052019038, 93337.44567342909, 9274.159900576662, 9865.62919884063, 74126.79734856529, 12300.411802395838, 13003.55906912678, 69004.30938677918, 34075.154180374964, 27848.72670705951, 48106.451111543945, 90866.50797934324, 11912.02939265199, 78011.88898809071, 13995.439531289889, 54801.30426098945, 2073.8999908073706, 52610.00955527988, 75363.76450170975, 37893.934091753465, 55350.21801331169, 41760.78423590518, 89967.07782048947, 34466.21974369636, 89807.68186996221, 78762.095380282, 2277.645857698063, 20502.51374470986, 49325.57073221666, 86798.57084329776, 39979.44653499554, 50212.542817259484, 37973.826868499, 749.2729471204274, 33708.8423110539, 97847.13040790391, 18091.923202216174, 49370.588722331246, 67733.70800060342, 63994.578467802174, 72715.59170736276, 92042.0959338878, 29491.664643259595, 77209.12473850414, 86240.89836646178, 55834.07722779463, 92134.67215720742, 27991.60468762134, 82499.50239701718, 86898.62257481506, 99751.26812637592]} +{"id": 744, "vector": [28450.720019199947, 88718.2154678419, 63114.78099846753, 40526.79488666432, 75569.90623378157, 76351.85962940111, 16241.566976125476, 72017.04373965622, 15719.567070806052, 96457.08582873594, 62845.709608642916, 75028.0217309979, 31983.611130329402, 25899.681435635935, 79742.08004891577, 29216.14933234712, 67306.0920027188, 52195.83759423838, 88084.16863981471, 86957.78707036801, 83762.40171740527, 15966.784789609555, 33407.46121692415, 16635.11458915955, 39031.650678657126, 27803.01684761016, 76083.32774463555, 35511.40944382813, 24093.085617740497, 36070.50614153613, 66655.36009548495, 93578.95794402006, 39455.815516297065, 87679.92125633341, 81652.68312551377, 53295.621202157294, 43992.692854955894, 36408.85508944726, 24001.046753921208, 33271.98159110505, 75334.62813011029, 95065.3629520411, 45904.832278293994, 48228.91367845953, 29754.760602717033, 13034.741335009114, 243.41686431643694, 74300.40131305016, 34662.221843503685, 43774.9878031197, 53488.818585831534, 76428.58082197017, 6212.7918667646445, 86058.55110674228, 2310.343169551277, 69377.73814120427, 3690.62278333695, 41247.50712785718, 87826.99882789585, 40654.63520410397, 34626.51300338391, 9727.65659803121, 38498.65131531441, 35286.28026653995, 36109.72069490745, 38846.35721833564, 30939.246408153842, 11703.1584816067, 45701.178853574645, 99118.47895150109, 34888.62447105546, 27243.968311118617, 20911.92521016373, 39791.18277219434, 65329.46095371507, 19028.73236795276, 15032.204863096065, 24169.10624833103, 64609.438884603755, 3558.3050236170698, 58891.7304337952, 56531.10466112149, 99458.00822157993, 93507.01384126146, 71370.60763524604, 18163.156754025433, 25469.007793821806, 67269.7949195285, 33918.2371901929, 93728.08221240695, 72830.55448849418, 54624.49182752933, 65992.37303221179, 85711.18113834628, 16770.646513225485, 67498.12600375219, 87272.57828664436, 87740.13903374593, 15808.369850589675, 803.9279014114387, 28243.849351398243, 66260.37254983204, 32034.817771635517, 6568.815205614343, 80430.05677703262, 59319.63539672452, 89213.51228589515, 23033.219408541107, 6724.631473283749, 69825.62336442425, 30572.650136684188, 72508.17647358833, 41886.8420100275, 61431.01158197888, 31529.154949045856, 5787.125239762625, 28657.4713342162, 18350.843434985774, 27888.887887569115, 76470.07317073845, 67724.16806655192, 87417.54782490882, 99064.36662796557, 59041.97561658483, 84461.16932163532, 12031.928191681973, 86951.57072253454, 42153.55304872244]} +{"id": 1224, "vector": [73922.72924691995, 36602.316079062904, 34419.399117491135, 51605.68177592072, 11021.18347563782, 78279.41806008221, 30931.259963858793, 8789.315123798891, 60832.29859267394, 27174.81487955985, 41161.523074690886, 50066.26450651867, 73536.16451834506, 4307.262506763832, 60644.68734165644, 34791.54363042275, 19615.0164884509, 49438.56948877032, 59021.6679901281, 59354.71523273144, 38058.549942843434, 73114.79530530296, 76006.79860397188, 87146.32217926938, 99601.10232531354, 97429.79421871736, 89179.96772923588, 18866.643779905935, 97569.01105751951, 21164.645406220927, 25753.483809531153, 50839.68567237569, 94802.49397278288, 20948.634490830063, 631.7268459111625, 73039.43516399496, 59266.68779006465, 85775.62757458954, 87142.11936751811, 65010.10910136653, 41886.35734444882, 24219.10274379769, 9556.028213217527, 67045.62775216233, 30450.258699129663, 25447.95233381536, 15481.130949968214, 56735.27662337176, 7163.54345579594, 6239.9497221486745, 92873.09309697157, 59884.20598969957, 87851.36296011663, 36424.16039979027, 95106.85596557139, 18725.240511751705, 69912.17365626608, 21295.0888669721, 96472.61454854895, 18582.612510617768, 89337.02712954991, 89938.74518270473, 23183.01393028105, 74304.99195673018, 7767.267410951284, 89553.81816614777, 31376.679550188623, 2035.9681716786372, 43036.000900281644, 3869.91123789483, 53337.892253279795, 7960.073201934714, 21485.174262001918, 96140.64817455747, 35450.05481019857, 11108.294124137197, 83690.78092603698, 96595.31069771903, 12391.735863594477, 97410.60457785659, 55194.03089875554, 18962.430785367513, 70554.81351994719, 92873.25824943882, 14055.9753905113, 62939.092659187525, 35563.65371116182, 94915.86141797858, 29209.27516270183, 47521.42157423499, 42124.470780153, 61523.97230018248, 17422.934959945345, 2004.7178117774345, 60720.246226181494, 65932.6000828063, 27160.366182944108, 17446.685539904418, 30106.331052884307, 52402.30221131893, 57589.802749676324, 95272.95647346987, 57031.19035747842, 70425.81441995142, 62057.662669533485, 7166.420416818842, 17465.159599962233, 83987.22903138699, 77189.41695260387, 14856.050569236157, 93069.970345567, 93069.83973560903, 53600.94267198425, 34289.27962519832, 4099.26124369675, 4025.9352734758336, 53021.8473295633, 47851.898669670845, 15049.221801684309, 4544.15349654016, 63817.02196070573, 9928.338059216168, 47751.11038414864, 8414.846724254166, 67507.197117624, 12479.362444629005, 66371.86529262875, 13227.154699062827]} +{"id": 764, "vector": [62659.60075234859, 18507.596610595945, 13225.644730231368, 9460.865971183652, 29246.663545386808, 19911.90644764178, 49498.12298690982, 76720.84166245817, 10183.73974333563, 84009.45308626092, 75100.52067307036, 86869.60506586856, 92095.99160813447, 53580.24664280331, 92230.7862342158, 82878.49677051579, 6710.096054888292, 33704.25165682037, 43347.63713701806, 3377.858291840752, 58622.36816658458, 58632.207784472004, 52316.25154707902, 51873.469379916285, 51266.066997128255, 4507.7978168434065, 30898.23419607154, 57056.28868099098, 43939.38936406714, 51582.20999843224, 56250.282379333, 44605.91364029646, 56147.26766609792, 38100.013222780624, 61845.81014412691, 57524.307386926055, 50304.571533351904, 6117.931862073989, 97919.47929963189, 19903.135461894606, 63444.158289683706, 84568.47527055397, 45775.64259744482, 25656.523502594187, 88734.45667456397, 72429.46619878532, 36801.82561075009, 75929.7085127969, 48038.04064377164, 2368.628292651664, 67982.47913678386, 43981.24203764373, 3123.142109821697, 15466.154541311827, 92745.80458925782, 4099.129903032228, 21510.299270399057, 60441.35475801084, 20010.813166883356, 33083.3291798962, 7508.056935662577, 93379.53080909482, 5310.057613043517, 40936.394298402614, 59705.26961315412, 76917.40502230519, 37962.82533150285, 71928.17543364299, 60480.05696550466, 74578.60718964999, 61523.588902031464, 59331.22519450384, 95940.75503760239, 43301.19760712099, 58841.11795475281, 594.903505385691, 84494.60267779393, 21841.116033741204, 57946.477459889524, 37127.75708863241, 64048.72177245962, 99318.05119406237, 24162.520980427526, 32313.879831765345, 75109.86325911974, 52039.15035597345, 34110.60875955495, 78732.21750373612, 24615.420006715893, 41692.43857228013, 97206.78045930658, 51790.41470835292, 51528.134394014734, 95207.38079953195, 51429.26344077516, 33360.121911319475, 6606.629498400507, 56173.3370959986, 95349.8220562002, 87295.53818610538, 27444.250278048345, 44052.715475115125, 2054.5387688862716, 36607.86878211725, 21668.326780994073, 96376.28529629923, 8625.247442348516, 33898.484042948265, 64973.611604849546, 72489.75506045451, 89966.48005579646, 42762.58366973832, 85915.94217058184, 58444.26859924774, 16314.703794546793, 58301.9420616205, 35086.339729996675, 83467.67440819573, 91736.41597490643, 22785.636263386466, 8900.940995891959, 15745.350397429504, 7007.38928746606, 79047.25925331691, 72433.2980830033, 44684.13710680911, 18679.521545016098, 96378.03794060266]} +{"id": 1388, "vector": [61532.393693243794, 70294.20191390674, 10293.31036367589, 78771.72489260808, 73427.402285155, 3838.460301598501, 82471.75016543677, 49027.68632490644, 30756.940683667068, 21382.298834991896, 98885.86772431595, 93263.61312465406, 47364.25744061521, 40522.4600628738, 39925.499996880506, 37242.308310949134, 45074.67359777245, 76364.41716814302, 28408.194856756374, 94729.06617737543, 76644.49180684472, 87852.75775613467, 41789.58074749317, 36068.32737991001, 93091.96286078663, 16925.894727457737, 16309.707825981568, 21751.729455899625, 37629.335041433376, 25805.428730380965, 46190.538772886146, 49022.07129108913, 49750.58992812891, 91336.33737615652, 81351.80771641868, 92220.6224405247, 53592.17415560486, 4218.406575830003, 64683.88658013487, 95378.76351485986, 75375.46353171307, 57077.513232041834, 2703.214324401704, 76390.20136512582, 47728.42903856601, 51719.33646360781, 14921.370874388074, 70474.08083809346, 51578.48551541081, 45872.80251019322, 45978.402875375534, 82778.9321747374, 77246.56291104542, 86024.21494421344, 78496.62264602477, 84851.61495121832, 71009.91376485782, 53936.59176374097, 66452.1755729829, 73872.70921574946, 85217.85507738068, 76578.47127452027, 1906.3124886965154, 88915.0966401807, 6942.459987481042, 34855.65019741065, 64318.96762396691, 76292.04711493719, 14640.722392081729, 53994.323725122675, 51838.17778196291, 18896.75125025573, 34046.64289562414, 13202.67632938167, 72286.52364425917, 60130.08806109853, 71941.02866147163, 79072.26256756228, 45030.969894904956, 9137.863526480638, 35235.638257159255, 42388.43135103666, 79205.67070137497, 50237.342918655435, 41358.8005228394, 26660.629656255096, 3411.6565973019488, 28366.143196980564, 48390.573428105046, 51049.339953017414, 95420.94849855344, 64387.26814710092, 1490.4927442270678, 69173.69973750142, 41841.79234879369, 40097.455000650305, 58572.8769636461, 94762.43191089893, 9211.84898063303, 63654.897660241884, 8903.258860145468, 24853.087667672182, 82252.93896852242, 38755.13035938549, 50065.683417014305, 7462.983520758237, 51214.25384924228, 43620.17030860706, 90690.81868358835, 12158.512065248784, 44083.3070111761, 29965.911607396567, 3197.5490039993338, 42022.26308399885, 50551.38026368579, 11155.584194041912, 1748.2020898564542, 48967.430064362816, 73044.70094816553, 37084.352322243874, 17545.43626121644, 91591.96901688362, 28155.491079220807, 66270.28687914278, 99906.03071985873, 25120.804197364356, 35748.50356627558, 82447.01756020228]} +{"id": 147, "vector": [5639.626897254868, 56944.250374492796, 34078.84903920756, 63217.85019370434, 7448.3121818073705, 37924.508584736184, 29913.155927047676, 1286.6841478499703, 44468.4069192971, 12617.850600190372, 12303.900788402889, 18585.71848680419, 94383.46585790595, 49125.55037253749, 73338.17215624466, 35075.742529585244, 92500.56244182213, 14010.43469515516, 73434.91194824326, 62208.975584739965, 85825.9814325909, 47619.6418674388, 73986.81498565675, 82384.18225790984, 2536.1631589796875, 40965.37860303354, 30610.101415057477, 90371.22578915987, 26874.146650463947, 77022.56830774719, 72466.71444253258, 34550.459688711344, 2360.2856235604986, 10678.095632676965, 15040.084075879979, 95641.81585626747, 54006.127273615275, 56935.814544595756, 42261.27093535604, 15011.029634195449, 81449.72577853313, 23613.953463535086, 8732.56486564371, 72434.93290617478, 93284.63480632011, 95131.54531495349, 69718.27528069973, 39901.193679729855, 73943.30403183284, 57805.56041227154, 19604.611807275283, 6637.227551740354, 86841.59476219115, 1571.9576806394575, 62230.630184515336, 56030.284713404624, 10115.119192002076, 62405.78574961601, 94447.92492558046, 38141.598293296665, 5256.630163920139, 78008.00664482769, 9637.922551281752, 39768.94575078489, 40705.497012839354, 85767.26403576716, 39489.63551199469, 52879.97511276987, 97911.12642608421, 43041.0723288289, 27672.459837122664, 81073.34774331006, 86602.60570841136, 77167.77163535275, 21128.591221909777, 7553.331740389924, 27380.12382160634, 46427.02190464286, 28092.569587271377, 91979.63917610711, 62701.998781874754, 350.9978023109484, 24517.13783482662, 14310.984845338471, 53307.05638982842, 15985.320543704385, 58952.57070909366, 41457.90142970663, 38765.383975459554, 37398.976216823954, 75721.46449389211, 41645.572744063065, 81754.78437746174, 34534.5895032156, 13139.878441971054, 48337.68880919823, 33534.01949893055, 29349.889281025233, 56091.94699751151, 81939.7801243832, 1048.3963236659677, 27954.770264435247, 42742.586238690295, 47489.762150544244, 39171.20399107226, 30444.86049098015, 73517.54871625376, 73043.59098120751, 13180.15819395547, 78957.1968130577, 5712.070981647732, 48986.257577399825, 77407.71246936734, 15254.208098489353, 12835.215371767095, 95174.5771043069, 14490.357343459003, 97231.9350412876, 14529.470483040352, 25601.17597310155, 75340.92533253747, 20796.29506990628, 75364.80504691231, 31893.02799732846, 35240.79457705349, 4492.689253097826, 20524.872663083315, 3044.121064123195]} +{"id": 1957, "vector": [76870.24886368097, 25732.437186308478, 45070.95716951591, 99784.64605733231, 70392.65082477833, 95189.30796336687, 22168.184679624468, 19470.84108199193, 80043.87953927949, 94030.80204163204, 49606.290129238885, 58460.97407756997, 55243.043378814175, 33771.27229265126, 22092.997860016894, 58740.40050265038, 10800.697504304802, 15795.121007331647, 26912.60142222063, 98211.10011042735, 50020.51323179022, 29474.93715075651, 71155.29427890068, 76620.90533851204, 94604.55433878457, 40212.61489000548, 6833.250881358799, 45042.6348395795, 1935.6215963883483, 12254.736194535399, 5491.095945036539, 76057.93363680721, 50623.59898570135, 17513.84362880074, 81443.38312028666, 99888.28513283361, 35040.53170895156, 27408.436455722975, 83103.25446453432, 77244.53320960382, 65032.684078733124, 65719.84361876763, 83040.72296722732, 17429.564647854935, 11664.23461812357, 73432.39357443503, 84779.5204393201, 34829.80177049454, 99933.66628631433, 85838.35247758204, 81554.41545401403, 82061.16134889491, 76877.9929408798, 87628.74037323595, 90687.01448892005, 13527.436433835472, 91770.56930221879, 4808.556580802814, 77130.58685517957, 39266.75184760328, 80440.39911518185, 5623.183590624925, 23996.42920078865, 83179.0264330305, 26694.496764731968, 36491.87169887177, 64634.76934827116, 52790.47215488491, 56465.28495066408, 33.37440058976959, 34449.0473879982, 9280.720357427585, 52091.2130369281, 56704.39564381198, 17000.534905481345, 20293.162411312416, 83828.2659172446, 79046.16366085329, 86308.9648516217, 71561.60769865158, 72068.10831673372, 80857.11060963945, 65590.18514205533, 25627.774824986038, 67539.28763180424, 20425.780596351407, 53286.55942968717, 32996.48763770284, 25249.754244590906, 29840.530424367927, 6908.975737352463, 45242.48694889998, 14146.240714127167, 71114.76756242418, 31092.308950313163, 27686.668372022505, 11309.012444609401, 79986.0253080312, 43409.95918649924, 99816.4830207932, 16674.049931354028, 2241.49228229471, 56325.40256350764, 81270.27587433778, 56110.74851244251, 69858.45829660304, 19648.074414558403, 5090.887600028748, 55691.16010897695, 99893.0868875753, 73794.73886824593, 73226.28537971157, 37627.054804614854, 11160.443586095558, 61380.00548327796, 6202.569309893546, 32125.773388636848, 58169.20337319511, 32760.983377163888, 48925.57582712137, 85565.41483855722, 10670.436336383826, 75948.11006704223, 44154.33555144217, 99586.27505883516, 33800.50495339234, 97185.93251451751, 31329.862317872747]} +{"id": 1191, "vector": [19903.50963543773, 58345.560767653136, 72110.26272126305, 73746.02202130141, 25959.523557107477, 69196.97098291534, 81202.3744295385, 52742.93510108484, 48655.24634406119, 60210.82281584779, 39673.949520928916, 75184.77175467412, 67298.28166134942, 53423.912104828516, 8778.825516650002, 78365.57677160934, 17872.494025201646, 89489.0245606512, 39884.424619998, 30243.067863357497, 29350.582657566603, 97422.37837390331, 53796.64060009261, 34309.853032879626, 27868.547955593614, 58725.4038560303, 75141.30356116728, 41212.749807501044, 11332.445025241766, 28236.36595484994, 24738.14630005459, 6699.52786816157, 14598.074401457483, 24603.424961637753, 96837.57594897354, 49661.37693245253, 70478.20246010239, 67331.40999140752, 14911.877581451783, 45773.7612476779, 54132.80071756996, 93490.31157489336, 51962.98363786858, 71673.71849669357, 14674.53726447132, 50008.73694916138, 88090.89118645708, 21880.79071015636, 99844.95085750548, 46557.05705330308, 81710.0591646911, 66670.81206135747, 95858.99583149921, 19675.322319510025, 51777.06599784063, 67589.02824397835, 86954.8621332568, 14650.92558079547, 24681.299810109736, 33717.684081656094, 10759.182780903797, 71427.16670064378, 26366.474152650444, 99212.22347051346, 11062.295937914923, 92094.14310372084, 75957.06821116302, 95463.23201023036, 33710.17244992224, 31749.719778822517, 69215.51649622672, 37865.706398880204, 25005.748352639355, 87109.13896971889, 2700.254906611965, 27874.617299510417, 30940.089126383686, 24987.3431946384, 16478.773747832664, 66699.41405097293, 30808.07244268612, 19444.17844535381, 20642.031669095508, 64327.934197808136, 54993.11412817038, 94767.70756980278, 42681.78255065247, 77283.58772050946, 40891.49914065225, 26615.046188599066, 53941.15962344417, 83878.04605996705, 63640.59087152557, 96279.90749341535, 62066.45919637297, 91389.28313502252, 90261.1089158923, 70179.41123167273, 15305.673396106424, 55265.38204868558, 63943.358414548326, 24085.798835389905, 2072.7226922064856, 52756.29136104359, 76821.7395191862, 47731.32825250937, 29360.68768547101, 74155.76596093738, 69818.53995719935, 40521.460454938904, 7311.062538546243, 78949.58762183311, 28041.156403154633, 20037.446569909756, 31886.603686424387, 90466.92354846522, 18057.215233496183, 62221.956258673476, 82658.88925977176, 83898.0730990084, 20804.417889802353, 90865.91980287006, 59464.621792350255, 58801.75538660016, 48982.61775562815, 48480.220337293045, 40819.84852122847, 72662.38089236103]} +{"id": 248, "vector": [45763.064020902166, 50346.161988345986, 53575.41557134875, 12259.554832794318, 80402.31557320229, 37211.21004519148, 86109.48618921475, 31545.794421509898, 19351.132746931864, 46570.87877100099, 63811.358673735755, 94199.02920885207, 87402.03091171428, 37723.77555019419, 56970.06661327186, 50313.95182299837, 43519.99147749601, 38307.009114806344, 53230.86343592016, 97790.40757064107, 15251.832892764472, 43170.309243756325, 61162.78655123275, 9311.30908306368, 32886.96590655925, 31704.97977757124, 23676.01667812692, 95529.59975069927, 34281.328109607886, 20750.346583470557, 99941.94849358148, 11556.706320998534, 82601.57446023206, 81562.51434968894, 65913.41276672618, 41370.712620811, 68002.45074281718, 79181.28898475015, 64566.84602240021, 22614.360524668275, 76989.05147805513, 79160.18453372749, 36178.19771482659, 73850.98725870388, 58292.42940345336, 9969.124340562474, 540.7112252721613, 18169.807011651952, 32291.0132887967, 68066.71105287658, 40341.955000946495, 18681.911841864396, 83333.94972604487, 91645.28690600597, 61693.36120979626, 50208.14625842187, 34256.13087359402, 44218.502173527544, 19134.832275217796, 47245.76273222013, 12003.668752160524, 76137.22555419094, 40281.40029143833, 66879.5370585948, 75227.0417481126, 33331.523260518006, 11222.034862494635, 19731.59747824045, 3337.162116727077, 95679.39282896263, 42733.32679057535, 4909.017381398184, 6757.008598815261, 33711.40810663993, 81607.58146652342, 87178.80150989008, 21643.153049720644, 53558.803328910566, 17735.59614553476, 33549.23981777213, 92654.47845050432, 81453.4669091159, 96349.83133552506, 87633.21360437587, 82497.74578454277, 23215.84204682662, 79022.33184339768, 89804.07558068281, 37425.11245235952, 15232.638315483793, 37381.23925999564, 56847.21289077911, 1852.9514574431416, 2321.479755315492, 81020.17789934839, 60238.49753675777, 37296.88945759962, 60494.03888511099, 92974.98763222853, 66854.27413074314, 62779.24764846579, 88405.05599330112, 20557.993937887688, 73510.275345316, 28483.44339250508, 34397.847549773884, 60134.655550073294, 27367.154912879963, 38446.75611075009, 48269.42551226957, 32871.05880264379, 92286.2763224061, 78695.1574577259, 41865.123120151824, 50157.24691603561, 69929.99913682402, 99727.53580734582, 30328.09709257843, 46016.584037761175, 97275.75695631912, 37906.73171956847, 81879.14430170559, 29332.536199477534, 34715.81994084571, 98818.89817421733, 76078.62359003499, 23153.595532982163, 33459.90858472201]} +{"id": 502, "vector": [19129.8581721552, 45511.26475299948, 87980.27900187588, 81769.4563135387, 71612.76755296772, 87361.11483358426, 9307.717409894556, 69363.60474606916, 69914.4485544661, 21727.91310290949, 34641.400535237866, 98410.59290802875, 18851.119080437562, 76254.85420973928, 75705.36652956298, 685.4413567984441, 94579.58927365091, 73316.61601390991, 29439.886956953178, 94790.23000320236, 85877.80511046498, 68073.2942890773, 55318.71096330996, 61852.20585293909, 97708.23320491234, 44402.35563478856, 58300.60882013862, 57852.14580856994, 23274.727025908458, 94162.22367111419, 24341.31494136699, 21461.343453519166, 14846.369203222686, 74690.85731418345, 86549.3605322277, 32200.303821012, 27040.990904709648, 38755.25416041387, 80166.43434215883, 35509.5188041918, 27214.873844769205, 23416.84902401532, 1700.1584421922344, 88889.5819807063, 13382.983947050054, 4582.488231431836, 36181.083913530565, 14163.373705231663, 51329.76244422416, 52787.228634650055, 70255.6617686544, 83544.87426044534, 44979.55793855292, 71865.39323144728, 92531.09868622526, 2916.0132906047575, 17029.00491078264, 7570.400178295733, 65555.454733588, 37564.34139391275, 75992.44233940377, 13610.184643565948, 90516.62124768551, 41931.60160923065, 55659.08470324945, 89041.5914541536, 21533.423361742454, 62570.479422551194, 6450.210156149771, 31034.972131947812, 11278.920519855596, 99204.90056218689, 70539.48906389711, 92594.64121064669, 89478.11249640516, 7327.244605127647, 20541.18638020622, 84466.16671235074, 10566.43476094078, 63472.80622160326, 46152.49918014066, 24726.69871952724, 74178.48711061344, 35885.98707606412, 40455.0466845976, 95801.29660872232, 63623.839001155546, 5007.868040094421, 96419.86567737901, 81008.765404072, 29299.924209461202, 76104.7553663049, 93406.95905470062, 13519.039964045165, 63152.41780676283, 78581.45668117651, 79251.74989076467, 98418.84882153124, 7120.680086039921, 34798.776840168044, 11534.012519744874, 6265.621893629159, 35603.417058137864, 86475.77316054827, 70023.71435304648, 25694.189640475808, 20352.397097067143, 90276.09557535847, 3475.8956705561172, 68985.57408220576, 58218.76948786762, 23876.70985804642, 70787.42512923619, 41770.165216301466, 40121.02476746942, 54447.14693096566, 67173.90072369255, 72900.81536158087, 63541.67071240167, 51345.81768956957, 20704.348586682852, 91198.34099514264, 57541.97944532502, 22554.96303905852, 79268.1691843732, 85177.70711010715, 94407.81176583571, 69324.84637217912]} +{"id": 1577, "vector": [58992.588429536605, 48156.817690605, 79295.30698427565, 56335.72285074851, 53325.38884168901, 78283.12625085187, 15631.29695033928, 73431.5449079214, 68895.62476190194, 51525.04886569157, 39263.032112215165, 64301.68602211064, 17778.323437099585, 9691.400663666627, 60314.744270308365, 61588.925049595164, 50972.81145718916, 24331.559423293937, 42755.75847855169, 86260.66592405735, 49876.46174774575, 76051.66618655522, 16766.854600296545, 86924.0093478091, 20102.66663051645, 41513.48686822329, 73750.09929250754, 37289.592274786264, 75978.46913017456, 55113.80592980267, 47317.58352787284, 83579.67915574445, 96166.41739021958, 35199.06823721807, 27068.7013957849, 96856.54438770485, 9158.228184463802, 74691.55838742055, 12519.973284832786, 86202.1023826689, 99041.34727453708, 46829.148777824405, 98051.4980322194, 38672.8831772416, 41643.68037121773, 80224.2497971627, 72545.34222879249, 41716.64780384832, 91550.57614732684, 97600.05567973033, 49247.05042980475, 12986.72943485667, 32891.755031913795, 23044.88177863704, 82059.09963772674, 19908.028744533356, 48505.02682745109, 86319.32169408826, 91747.17676407755, 32076.341043898716, 35496.96130689338, 45919.97331250269, 71035.84655528178, 75401.49928802637, 14638.420708293997, 60190.363925819656, 30868.51870673465, 68959.88882125102, 20632.161528691206, 8290.089689695456, 81415.46785490905, 32247.402544552926, 24975.554089029916, 57721.95731946339, 58470.98018967638, 44954.023025739596, 85466.34111394861, 11481.4083247481, 82547.44421305298, 98431.72827846676, 66792.34595512933, 82625.24039571536, 90280.73078280996, 41013.52684336167, 31725.215525473628, 94910.66296058915, 28527.872014242672, 77345.1729101575, 31476.009194593047, 29223.445321025156, 62395.213819285374, 90821.54920223953, 48293.25030392478, 84548.10525342333, 6126.7813954025805, 81462.17633895321, 79901.56580386219, 24409.95166186197, 87978.81676282671, 9759.424692694296, 89831.5683568566, 91150.41171772005, 84026.72226886128, 72666.16302811197, 33406.22740494394, 37769.6795818568, 56638.22862487362, 54257.46081575582, 54858.22868224325, 69252.2357610981, 39048.048242755736, 23045.249852397985, 59384.38005761531, 83398.36548021022, 13125.647812420028, 67620.56698128724, 63865.3223930249, 73791.20038577823, 30539.862105258886, 2411.924121860609, 46545.29807565035, 64287.56891047329, 91383.41623315627, 84311.69093818443, 48874.44429332051, 34937.95709362882, 59729.26670749032, 13087.241466936593]} +{"id": 186, "vector": [56861.44338255042, 42908.92752126985, 3049.632183915463, 74250.44287551755, 28033.737685004635, 84871.10283338907, 26867.709905232306, 44896.072533008715, 27597.145232730345, 15803.762183800296, 98759.02207791887, 41788.444158073966, 3833.7571385122947, 60853.91752956532, 81414.00262650511, 15654.879713831104, 18069.133057477393, 79862.4528601709, 91859.05957177457, 89132.65168226216, 11710.49598895545, 88329.59771510513, 54807.72133412188, 86726.4602030865, 64764.77309148058, 87402.45319677093, 90010.08555745, 261.5855138190737, 83180.73510755213, 35402.819751178, 46950.00255406726, 85321.43854336064, 5237.981138322245, 12222.974186067459, 72681.20187663089, 95872.94253900606, 35768.410622411706, 36440.659073051276, 22875.72322312652, 22229.509974290897, 83707.73749160508, 3044.277747397206, 32311.84175119799, 66079.51851923102, 961.5272290057542, 75300.53373154716, 49644.410594533685, 64101.07838376501, 91285.6576082511, 52299.33292924912, 54950.21729929982, 83417.00166981827, 22317.958161530925, 17384.905312891096, 58602.91764948001, 57359.88688547037, 3403.196683306486, 55488.7698544226, 44118.74598651755, 15826.682709334695, 59018.001942330746, 23783.87067732185, 55758.58760383885, 35666.909212284634, 47127.36234895972, 86295.4049026411, 35403.087695851566, 21997.277884005383, 74381.41465670752, 38423.12408447014, 24010.603237213647, 81384.90271998751, 12303.026939103456, 97141.06406522018, 42027.23105978165, 7314.509666310964, 64840.176778060835, 72756.41903977765, 36624.55201393097, 31214.026354457048, 27050.680495196055, 10303.430520191492, 30047.979071575115, 52750.112804277436, 97239.15560545541, 97900.48512448356, 13603.715558874852, 62052.0223485664, 64459.358144737744, 34782.61877069653, 50028.89674358252, 59278.59671946485, 10814.630038092355, 13864.671354487757, 61967.2244727874, 84956.97290959355, 71938.36040749337, 24157.65576358745, 10116.178243901952, 65294.654651089266, 45730.932041166874, 62201.16636603281, 18545.527291993978, 79757.75651396318, 53012.6310302209, 51556.43411219758, 57660.59896793859, 24066.092394372907, 77852.46907260695, 93213.22380914974, 74922.6839762696, 8259.28749543713, 6164.179725196972, 42127.42800256058, 4192.456057090021, 46042.65811832131, 42412.22267147585, 32437.29995683312, 68052.99753192873, 42473.2185830316, 77135.86645329451, 37242.381868131226, 22580.6646412813, 5849.959101006585, 3967.636314887324, 34980.599007393655, 5933.626370451739, 49178.228346187905]} +{"id": 1976, "vector": [85604.11860198017, 83463.70339431295, 67296.68794117673, 76968.9104668735, 91417.44873349192, 14964.174803034248, 75669.32532313085, 35473.3381698109, 69110.15102115762, 61557.79580375118, 65657.35321684496, 58968.46557890335, 51429.18663073186, 90250.78161188298, 92897.7513185151, 7561.046984542286, 59960.99098550085, 18513.02433426121, 96752.33899279134, 18992.237111399736, 53477.75626797413, 11590.875855695092, 453.93491153278285, 39684.53415886153, 72764.86490492588, 59588.68038629307, 51857.451653908116, 10484.88314221231, 83173.17660730534, 28325.71755675717, 68683.74637289927, 63183.53053872238, 47656.854089901244, 48299.969230823735, 53862.27361996696, 7682.672761710718, 10221.433624767418, 37590.16836285014, 98262.43606696109, 8821.266889145674, 84413.52240376372, 54209.846161026675, 22409.40218916051, 79116.97575877729, 17650.168134249543, 55408.47741951775, 73902.9660563261, 28073.821793185714, 79913.26797683106, 68528.52626429012, 71898.36173038595, 92166.65570817339, 25308.128103718074, 39404.37367810142, 8837.847414108812, 45842.861487849965, 70195.51821008376, 11882.729991334296, 65341.36474451629, 54458.385339639695, 64703.94637797421, 64044.82651094408, 50706.25022968052, 59382.43549041634, 10906.109205293034, 65487.50435703533, 82275.32604331148, 87257.12972762393, 35212.954760906476, 27249.663111198617, 42955.86434956005, 81811.06634730594, 78987.2145664166, 38992.48245246741, 96775.39177888019, 36009.68982435229, 56356.582356037645, 61736.74821663742, 56449.64401963646, 8509.436376671676, 64205.32235406308, 10770.700769093488, 62099.42860592779, 43680.49665119657, 30656.632547420348, 78620.00083129841, 73468.1334321439, 90883.95621525777, 94647.2184912898, 38502.38827542151, 51114.905650231005, 19224.01999581268, 23386.633191188932, 10839.7342570003, 27523.662013471574, 14843.937981710076, 13427.047200128562, 47423.74059668272, 47141.351791144305, 31693.46215942812, 44311.094289700406, 9184.755022273783, 50981.90697247665, 21804.312935888116, 34895.60785464641, 56257.386475001746, 35981.893040525036, 86496.20248688864, 52195.67505230098, 22522.635101119282, 243.6719874788884, 1990.3711076564746, 92844.10402204223, 47117.4172966475, 47489.64212614647, 90828.31734848951, 33467.77319537405, 4995.418276523677, 14856.671934890775, 75619.3061272732, 76332.62299533999, 78406.17357544966, 56417.16235282884, 69067.04133429033, 45383.53432225822, 61159.403472099315, 69700.90278872554, 7228.747325372631]} +{"id": 935, "vector": [97979.09704791493, 75610.05828118179, 41628.66193867865, 58301.364038321226, 44693.9816931459, 1094.9019945799555, 94741.84170489406, 38980.114175318704, 64191.355811393456, 82432.13837016605, 32966.68342855981, 40904.91252498234, 7611.39764732941, 28405.151771614957, 63117.15720557288, 29939.388478059624, 46729.90828980736, 178.08736460024343, 68406.50664910435, 11432.311753644808, 80694.17010076785, 77454.53753240274, 67486.35700031536, 22721.455944076963, 15785.633541416977, 90847.73829157517, 87904.41122153745, 86467.89413074146, 51789.20249090445, 48976.50594812491, 11501.111877860116, 58474.43927270901, 17520.831838273552, 88084.42724497963, 35288.09694527621, 70555.86421913185, 32975.07312261686, 4497.686894308783, 52739.00762643015, 16418.71867902571, 87613.10133968733, 56679.77042152921, 36226.907126283855, 4871.07408954286, 20485.994520831686, 60615.48488302479, 41105.14050289672, 97275.6258992053, 16213.299949419923, 65778.20766841221, 71440.51642813021, 37319.48904768484, 80166.35634151433, 96258.11968316099, 60038.061761824625, 71222.98325876126, 25922.96872648945, 18026.43298897537, 59331.01751396002, 5488.890689741655, 84961.85249339446, 72552.33057472874, 49445.48664959786, 16883.435348752686, 13419.112043580084, 16974.269147189312, 7799.196422650356, 13326.044136580305, 12690.033435110137, 60765.58159721587, 99628.05080968275, 68029.31983389692, 58837.61959110533, 25795.99833249715, 35650.11773523632, 86301.43789142776, 19529.637126063302, 20992.398950056533, 30321.887157113557, 95577.38720882291, 3060.2514040916517, 81044.8977841857, 88164.96265962759, 67803.15939898913, 52320.504652312906, 23786.406901177194, 86167.17021378977, 76919.97865000773, 87250.22438066834, 54865.34030319429, 92814.77190109192, 94832.24513100855, 21140.07204195789, 54216.83140917465, 221.89746650208565, 31686.57399659084, 62184.731889509705, 64379.53982727965, 53756.78376029061, 57870.88216959956, 84287.97187885674, 12862.2738230329, 21852.127414832878, 65352.45196386602, 92965.71654638373, 68509.12109224263, 5082.122493378039, 79934.17556618324, 97312.41220095391, 64138.95005687079, 15875.469893566884, 76162.1055551551, 54411.840294258815, 82518.05731436139, 66309.42892263811, 19198.93483729991, 57060.44879142153, 11224.553189879383, 83809.6280506086, 7626.063572616715, 76383.41819724749, 11943.204721645074, 90914.70945207209, 21246.3757377792, 50143.18501771293, 88233.63047932505, 51781.797982851815, 19412.73935281128]} +{"id": 870, "vector": [24151.12802884589, 64807.037870595894, 66006.54502015973, 51169.10195712825, 29714.393646386226, 51704.146936376404, 90158.62570563269, 84942.75835108917, 42906.863961508425, 15988.161810216894, 49424.31913849835, 9863.299253131707, 68583.12226187314, 9159.769372297811, 80460.57055719542, 99054.23735550922, 89061.69265460224, 78347.22362719923, 80875.58811855585, 68054.67088925482, 49878.43986864572, 45592.906454624725, 55937.57178894698, 63189.936920832944, 33624.00266303687, 23590.690012012707, 69346.28220878387, 45669.71111336899, 72906.9488271177, 81934.5684301588, 47145.02062536491, 79099.62628233778, 87315.4619287734, 41914.37516483339, 3765.660566021678, 47343.19322313125, 89984.55058683084, 72843.53465803464, 20891.52013706205, 63596.14110401315, 25061.42261282117, 77423.03422571065, 76932.86916659394, 88477.06063862555, 19750.169276286335, 88154.74413323108, 31188.25988511157, 14905.575210810362, 30797.09448970499, 80184.28376321158, 2732.01441972345, 3517.3735347296843, 62646.9447772927, 92030.09532708424, 8246.698018216903, 7858.03681937477, 72894.81829181043, 82412.89637498681, 5530.156844347977, 13421.383500256046, 20147.472389170864, 2047.7062840721483, 91055.99958894901, 6007.013941083584, 7203.677608871695, 77218.46955516291, 39473.07352266421, 33809.12557128247, 36465.5413206, 9417.326884748367, 64551.36830757685, 43671.44855255589, 85086.52667536895, 19017.502890062566, 61968.25852811356, 3425.068838310852, 74338.40097539926, 45312.340304852696, 23990.003469646625, 82119.25497865729, 5123.554267305419, 26064.34251839951, 22324.883444150946, 48098.487878293585, 70510.31538421132, 86456.56897533682, 27375.670893048053, 19431.431527067067, 30139.153324882318, 87064.06023672866, 23434.021914255452, 52200.249484898864, 52320.92437664432, 19588.395483114306, 4949.745709532372, 62079.86745677424, 87364.43404263929, 63899.35520086045, 73034.3313369264, 1051.3153070120352, 76055.23291854553, 62876.29803646127, 56605.919832848085, 80530.88705447194, 31116.857446843795, 59521.34815772131, 1018.5220226488823, 75195.27350357789, 94336.93265637395, 43612.07244864662, 43795.818161768715, 59218.25612781994, 88959.39165937771, 11287.946106839741, 33960.81304358642, 9498.86504097731, 14741.210766976088, 14827.082428400496, 20035.249175839253, 6379.875305802685, 87844.52740258626, 16138.690793084443, 71681.48873641258, 75649.4808095152, 52442.05819991858, 89209.18039828792, 76230.94613944202, 421.2750324128711]} +{"id": 1411, "vector": [5314.766689157246, 24774.33722610307, 51493.65889231992, 36385.58220979925, 55638.64968817601, 56223.70599203306, 54691.368436978606, 88816.02330086214, 12893.096131869997, 75427.62609188507, 14639.137626188593, 92125.8078205107, 63084.22102607968, 20201.388862100044, 57340.3746400139, 86612.65785251264, 64673.11004320014, 47349.59594464617, 67174.84116425167, 58873.51041077539, 46700.86709673448, 21616.20294569768, 99320.60396819688, 30912.83642257553, 68117.27411042, 17590.17442775609, 97208.80875507141, 67566.62340630648, 43676.57155873491, 38508.73615355726, 37150.08289654459, 28317.762175940064, 61693.81482795429, 96029.39261222455, 32594.42557987562, 98602.36915782062, 50176.355724493296, 42699.16676210865, 47844.125905095854, 75737.29033667097, 20301.818570561758, 66256.5803999608, 94263.23527538404, 56715.88739579917, 43470.34004469231, 17275.98125901313, 71216.63895991225, 16149.864351954857, 4751.00769581972, 21633.902636430015, 87361.84684917057, 85866.09702795606, 42606.746745938784, 29221.71566858933, 19262.615245281755, 16394.978402374207, 32802.623706872546, 10886.120231415374, 31669.1361946676, 66870.66885268984, 50112.56360053824, 83157.06306584642, 84541.36796388321, 80512.11929226258, 59303.35306926176, 16346.580267434363, 11421.533002536544, 80695.19315688699, 80228.3100257721, 66191.9238014798, 14308.565579797994, 41542.17417370158, 97978.93515411473, 60080.68669149781, 67438.93310022388, 85955.589051918, 96809.99000935859, 90830.59245919889, 13556.010453926026, 16431.446272826146, 50830.96374390753, 65403.9494228952, 45104.71881620626, 47502.75062344594, 41304.30988543626, 74380.76411596575, 46928.395340059134, 6866.914903193322, 81395.05981003167, 70918.57677497182, 93913.55663724865, 41698.344046165235, 4257.248684778759, 97380.73993279526, 56218.4474370722, 14436.595186652368, 34900.677315647445, 64773.92011385916, 56899.65380926335, 84205.57620717937, 13507.366539677823, 25218.603318517286, 82614.2401607633, 67647.39312094991, 37037.00529830019, 42926.615851428694, 53465.85556864429, 68604.46428620821, 42398.364066729766, 74679.74062640767, 21908.784609336075, 73534.15746548372, 88304.10913359353, 58692.21423541599, 57647.092361033145, 22911.498961332156, 71095.46793468411, 31827.670323124767, 44850.12757632242, 67074.79814812075, 12619.035554178925, 8383.83246461022, 43220.982182265725, 65641.25740462143, 64194.211900806054, 72006.73356371674, 99766.26490711326, 39665.6644689776]} +{"id": 1742, "vector": [30351.831455976087, 95151.06258110671, 35077.25299667569, 7656.4983565884595, 26078.371054959094, 37265.30747114481, 6420.217426157004, 10631.537652310008, 32579.96661598963, 14902.354172982179, 52318.573065703065, 59758.72139824849, 35362.85521294391, 17475.97315378524, 29329.9827463258, 84329.20246453893, 63086.4023166921, 33006.464260229484, 81813.34080998851, 12943.53519308622, 78772.21249773115, 31243.28765607144, 34144.04590945114, 69982.12990291578, 10779.088403028869, 48315.287404063565, 57718.70855892009, 14064.682259663152, 227.8673637952644, 15397.737221729802, 77742.98061235728, 32632.753743774843, 98636.12554620177, 9949.146621391836, 69608.55152591511, 40388.65017105902, 97809.59217546649, 26144.44901111388, 73368.99732867473, 67173.26201945945, 30849.434810637387, 13949.4874803178, 27362.87199352797, 64269.73016966624, 29746.449319933334, 83326.66456085395, 20484.22466972838, 64554.45549481501, 88660.40361940557, 51544.27019005584, 85588.06375882112, 81156.94382348626, 26966.31895714926, 81797.47884231363, 83634.00067944273, 85686.25332581045, 80178.81365724062, 50835.00045973739, 30537.37134099792, 94731.80930219164, 13882.62770562907, 734.5202723820221, 98133.03345547874, 86150.69303052171, 1231.3175215297179, 92584.813841752, 72515.01318843925, 51324.86817837184, 98903.41148609802, 54945.540609647636, 89205.55711275726, 63643.8105185288, 36339.002428173226, 52741.493985706235, 99918.46426387315, 42734.819023416094, 57985.94385855672, 90731.25632650674, 92101.57930493151, 68363.21189131604, 77117.93113075932, 54303.81756542112, 48956.07070640997, 47450.26793333313, 99678.59734805622, 70918.45441683345, 82005.10627630922, 41083.61455124024, 17888.339412828347, 61564.942523249076, 8743.979620223263, 7533.157590727713, 13423.145990256102, 79649.3094601293, 96452.84707665603, 42756.27054834641, 97669.63250109772, 50303.38822026239, 34550.35510275063, 16769.59419942369, 13164.650315654613, 92328.13525458395, 77514.05321057876, 63758.823724735084, 98735.97339944511, 3599.3577089475816, 19564.6371815703, 52558.489379181294, 97971.0993528978, 46980.30106497438, 60263.84475127943, 32793.342541453305, 93509.28817205997, 73994.33141573615, 13290.455297896253, 72887.310337581, 32749.142587030445, 63483.08149147409, 29950.484696604428, 41117.438085414484, 28104.848662979464, 18854.34356761714, 81377.80711852486, 14285.047903594772, 16941.957955100617, 16330.711237956019, 74461.59374109501, 95793.99091939654]} +{"id": 207, "vector": [94990.60889589632, 70312.09933913952, 70973.72344549975, 66418.8189423319, 58738.74512693675, 4502.896765852649, 32247.975785326245, 85976.51650765473, 47473.19754240914, 37498.72884233205, 27246.132810858515, 39186.70292073659, 82776.31354264337, 57482.35779348348, 81494.4949642054, 92968.40749763178, 48106.92320042927, 80620.96095208185, 30301.68354150614, 70595.2711658179, 42976.21668102811, 66012.83756669157, 38676.946434355166, 43490.307167022525, 99817.90014542303, 8929.014779131161, 61855.889312391475, 52109.52045725306, 30933.110891063043, 82722.95177261432, 39699.827785031695, 47113.47643969478, 65779.09444125574, 13737.317265527148, 58135.48618155325, 74288.92277547749, 4611.579354140272, 44205.65208446381, 37871.05562803396, 65345.24688175808, 93800.04998466173, 24225.276445395593, 94495.85773629886, 87925.29312027022, 83399.52057312976, 19516.923823546374, 24769.773917187966, 20447.66468650695, 73760.22340553693, 70291.58893435632, 80212.040344786, 65718.94259390909, 7380.46384054768, 41286.4284230735, 50551.414883898724, 16106.307922593998, 694.0376578190577, 30397.224204099137, 9886.310431775813, 69232.05967763609, 26375.476667334675, 60050.27562655255, 75499.94943841199, 18310.098235958616, 65979.89071304009, 390.5521758360453, 83849.73295038746, 19830.85730704791, 83198.75627756248, 9831.141184121927, 56306.11408120756, 2247.8198645195844, 37172.67185220011, 45980.09266176982, 39855.57220624816, 81104.69012661664, 77956.99955114411, 85685.78182067805, 90332.52802923511, 75160.49918959363, 79881.49990259799, 52828.108309631214, 16823.38249928853, 9592.629962350253, 62490.94086292756, 71514.46382274407, 3891.7293515369656, 59084.154034272215, 92986.78437201711, 70792.57015654683, 42870.79552254182, 50988.80352080035, 362.25173170976177, 34383.28170774143, 21482.237349237876, 26481.137666545994, 92436.12953115288, 74262.8933888918, 88606.12493667426, 8466.654206382163, 59068.00591204523, 18437.523059937834, 51752.518488194866, 63332.38486018909, 41765.41890436996, 47492.07091507215, 7448.370961239648, 5131.522271624278, 92611.34751126809, 59870.32679731882, 7569.264423752742, 32916.426086645726, 68448.29220375634, 21717.616941452357, 46407.10872840269, 54308.53731361534, 33386.59473554002, 25544.89418428031, 70349.62775923392, 37505.351592490784, 72995.02440816272, 65545.06367415837, 97289.85505318282, 31235.857817280797, 83925.45942586329, 16672.12744903883, 70710.5522287887, 90540.8714374164]} +{"id": 1369, "vector": [64031.35322375559, 92940.26400633767, 16551.069159808594, 99485.06294257569, 97006.70106974208, 61014.72790292852, 30246.186666511778, 35674.73018399527, 58194.81884365676, 91858.30363676809, 34907.40777229311, 75930.6248113584, 49599.29468291799, 47984.04154820193, 23324.694195751927, 32982.45559693925, 20767.71692996432, 64429.766896027344, 21723.34064174445, 4465.338908459071, 39101.08550586475, 4771.166814529737, 68151.5076951949, 49917.49699450304, 85539.49929189461, 5433.918037810737, 93864.08222944773, 5303.466299141913, 33411.62196493709, 52845.74847064697, 14641.442742559053, 59274.860491403124, 52038.75860086489, 41409.40552450474, 65075.703111905634, 87352.18920858052, 65298.26740241539, 70098.57261861951, 58179.28948817581, 75648.03150159575, 55119.285740976135, 69717.0348870097, 17910.702618317286, 7372.069012469317, 75229.69105437695, 47841.7033476925, 76370.59011312854, 8125.371391494274, 51319.87068634265, 27564.04835133657, 88526.98550807679, 57167.33548979377, 84384.20054286625, 81174.41896901769, 71945.35503433456, 7664.186849842469, 84519.48329229381, 48580.68783838114, 81174.18800648027, 64711.937791516524, 35030.283143530774, 97752.95413245067, 3479.510048551393, 21943.09147291914, 3074.8428647646488, 19872.0363797926, 46330.97745897163, 774.5408359244421, 1802.8899451445257, 74161.76070871724, 10226.14135434744, 30196.49186110117, 71786.27919517917, 40677.18592862591, 47593.11212307173, 15862.537926999908, 77690.48173971598, 10257.935529811757, 52804.13336263864, 20450.033388370637, 82429.68337590768, 5287.525939065474, 92111.61241554287, 63074.2247693978, 38880.67517488274, 38258.52458417385, 2773.385044422638, 19927.15960533967, 29980.8452742239, 9534.676580317602, 2292.3689127439916, 42368.5702259802, 95386.18586812845, 17263.76484011629, 14450.071996230463, 8221.47799751406, 47812.887422863125, 84388.43915977688, 80653.92370383545, 93903.82861585909, 93369.19097053996, 84066.49952835485, 55031.70543020629, 12376.948086739381, 46810.335050362904, 62583.689268119066, 61964.54643934348, 65028.01028328062, 75812.44973074127, 43154.90333039026, 44347.96335772405, 79362.84776363151, 18883.38069628892, 78228.69945113438, 59182.897509300536, 92160.90211188915, 51898.07432579121, 97673.38180399404, 91130.18890554477, 93211.91918719135, 39577.93085017628, 91220.63937957108, 5501.320769303486, 98616.4549712409, 95688.61976780726, 94345.20862168266, 32367.482660951042, 55294.6776714094]} +{"id": 1416, "vector": [18693.61019625344, 14716.861022001027, 51249.52646155453, 14518.487325546836, 78538.40313483, 98793.71019144879, 93208.55207845928, 32369.74440731023, 90840.8034847334, 8070.440404435597, 28584.08005922526, 13812.850156813394, 85802.46379239473, 5572.543460385282, 19480.085088767395, 33744.446526849795, 74796.2537227988, 65990.96450923398, 75292.99124723123, 29793.43192700283, 12512.766711318069, 55972.42458362372, 82466.6844393439, 67230.14676162438, 93995.49126724055, 96794.29929802817, 18883.7552245382, 26214.228724900822, 25806.578977186946, 39535.83778313518, 90091.46201231335, 65531.32332537728, 89911.24015863502, 47740.691708582635, 89910.9113024358, 63289.79943969134, 28796.159788426, 70475.59204794442, 16298.935336047782, 42082.63348966956, 14987.471863113999, 5706.671929447593, 83867.84675945852, 83410.57915203414, 92591.21812334523, 72563.30214830466, 20991.946607382994, 71525.89990888513, 45662.08575918146, 23947.819380056113, 35147.64403350107, 91101.39065295254, 86035.69547557649, 11734.923617447357, 1147.0923011428158, 82228.65724512936, 28644.72194566665, 10345.925027639236, 78792.9665068326, 20560.76780076599, 76586.40828409744, 5377.614257293139, 60719.98897025731, 23562.016846707567, 57809.020708042844, 5993.214889445164, 56680.1690775107, 82095.39803830537, 63588.98086947499, 79795.2648951243, 94432.06722449619, 33973.792957679005, 30460.613756029874, 7329.954281250817, 64277.46220349297, 14580.88554775624, 79789.11314087668, 74178.47842716696, 43877.54854747921, 79679.22535122892, 2931.156965138004, 59919.76020822023, 58126.45541968007, 98913.95540025982, 5640.694669063307, 11796.370709995595, 15426.60105266568, 49584.74988552779, 3513.292892574671, 78471.38378954113, 37744.517229248384, 93479.81143378367, 65517.344772912766, 67902.74665144282, 57625.22124789151, 60230.87739101106, 2090.1138658895757, 35348.67629138979, 37955.823517878795, 45748.569521000936, 30364.48963224747, 41035.69201937729, 12839.412101026182, 46856.48732132021, 50972.35397016443, 32040.34786791412, 11618.344364254152, 91917.50931990948, 2412.6460360282763, 56796.83963038531, 99978.21670776495, 38776.7727450424, 64441.50344914941, 90197.15323739519, 47797.80108816616, 65592.84238665621, 75173.23317308091, 60362.711177654724, 42168.020335951725, 93761.92597092745, 97056.81566165965, 42990.25774565723, 10741.219759957921, 44578.54166326647, 46447.33348834086, 47205.47569467121, 66975.55262890844, 6628.647387460452]} +{"id": 1200, "vector": [85125.1607142566, 46843.03449828633, 50903.804272635825, 55648.364170583096, 75499.53866164152, 9693.204456042637, 35774.28075511612, 84523.57574096725, 49604.467873150716, 49099.576615342565, 518.8708787997487, 19076.76224176357, 92559.50756084667, 32043.55425365182, 94430.90627665048, 80038.74641876198, 55921.95361021516, 49390.94999187034, 16023.278730253354, 39130.27470327013, 8018.1278515570775, 35759.59998377578, 82789.074350659, 5644.358105303848, 12105.852414703011, 1528.0711606412533, 75823.4636779202, 80735.58275359904, 51411.065700454994, 7828.611252338935, 32645.51987460367, 16127.440915804258, 22715.28023730681, 20357.923289052447, 93220.12971560453, 11019.312490501798, 85668.2336254665, 58010.02208537992, 34181.58347565924, 63851.19989191599, 54178.5299129951, 21576.92864127213, 1582.136143638102, 51615.71525827722, 89391.57738587727, 98182.60038166108, 50066.87380991767, 85150.09103457986, 18053.50196532135, 52491.20047477405, 24523.7586122558, 70807.33550966975, 15041.105799898824, 32000.243894376003, 13973.45998266487, 7702.029930110055, 82221.95160693588, 42675.32495501659, 41196.82709247523, 50885.91879913835, 54486.05927109289, 85088.6796460817, 55278.698071827246, 87929.11168214091, 91320.46961394152, 6466.802996349575, 53570.547281525636, 97065.35708502088, 19791.69521667531, 89192.87750576716, 33988.47794997472, 48488.2423007246, 78848.29754037094, 40549.44249472598, 16682.91749760661, 88912.85756208873, 99061.31385216457, 72380.06742470952, 16198.273122973438, 18560.309083055938, 7078.806807921134, 59143.759278744525, 46034.82095454645, 77599.48993529988, 91574.84740181216, 47195.738036106326, 87951.43545858344, 50385.41954478447, 86061.8019332458, 83483.23998953898, 20206.177540642846, 20664.986964322918, 83417.22173954803, 40168.05828381204, 570.9005955669033, 60529.86193893491, 92577.95965247699, 28723.389299982784, 61255.95068186024, 87773.46244129576, 15497.536497235109, 19386.19081015922, 96187.90167784382, 48277.41924729092, 23314.058635944333, 51743.680149672226, 56198.205266375335, 72309.79797417138, 22769.230903191794, 44058.82736997452, 75488.66504834432, 6117.932405783577, 36609.324380133934, 91357.98613290119, 19837.939785658444, 60140.36613069311, 75381.1972208498, 14325.72220604117, 50082.40249955318, 94104.50376832929, 13247.223118356833, 4655.841113028203, 53747.50238894283, 30251.739198892636, 90600.98477043769, 81879.63841064014, 11346.307868932181, 52989.45625135203]} +{"id": 697, "vector": [4901.086070838801, 66435.33982271263, 17357.819288191935, 10865.142621193247, 47057.936452905094, 17819.267786217606, 33690.14166288576, 98871.2249929444, 3005.511758381296, 71304.2262785339, 46139.52763293712, 91120.9926750661, 30719.74531565016, 16123.097669225683, 39356.7298430315, 96065.90787216938, 96658.72990025935, 7429.700053483157, 67035.93072884351, 13515.518148426265, 18131.490364925718, 6127.597843264476, 56155.02179862983, 7652.663298280382, 50353.28287262941, 1771.465086838586, 99804.95962940017, 77061.99958512904, 97894.01140627268, 27971.66285708065, 628.9085631869251, 12462.371170398534, 13372.984889410322, 30456.73603526248, 30154.45795066316, 26891.97671268381, 25259.863650690484, 28580.27501000717, 14371.693788554874, 6467.735602374314, 97269.20018755605, 22314.79100632553, 83604.01621585328, 11441.775184842561, 54232.00378258842, 38959.58548952522, 24484.280236141254, 15320.387699225214, 70232.0843618891, 79471.52685640132, 54873.02072523942, 76300.99535193031, 24922.41126133775, 42697.21524683989, 78322.00794468072, 87049.25179064026, 5408.363301091102, 75251.62733211384, 76921.4812305247, 91384.78625235996, 85153.08052444387, 19759.857489338017, 79573.06744653679, 3879.568203486472, 85533.63594987508, 50621.756316176936, 58982.739493831024, 99927.49141504832, 82466.44319468053, 14769.803576132701, 9236.79604531944, 64727.50836096614, 72029.57945728822, 16572.424131423348, 73990.6459378353, 844.5088185687788, 67956.50238584902, 89829.71140812265, 13930.218012251438, 6377.581524243093, 60008.03981814617, 16921.91500522937, 57495.94578591565, 3305.261977260798, 18603.830346858296, 79384.39612720857, 84168.682845419, 24949.322204881886, 30686.211313892363, 58960.96672565759, 51334.76256159617, 72854.12081769724, 41877.034275525504, 64857.559353864446, 8911.82387638163, 19815.43371704305, 42251.90930099859, 30580.48895705482, 68801.54189020181, 60855.73469276565, 99270.57076915009, 58636.91020706751, 39918.76363213992, 55311.26589619581, 14740.56141182183, 31181.701998142074, 5205.643686121153, 75332.19997037196, 26345.708127730217, 51859.22846438543, 34507.77932940522, 87670.91316163285, 54453.353481745595, 90966.7126978082, 90581.49481415979, 926.433918611358, 57674.35398618899, 59070.93276262115, 96100.9142513424, 82109.78255236632, 65619.43346032934, 28115.8876709657, 5862.981980044923, 57338.404988046284, 95816.79232108661, 7711.0710965443195, 59814.267822780246, 55995.739573686464]} +{"id": 585, "vector": [45901.86969594419, 37832.91374915543, 28410.79447160001, 35379.20859793267, 37715.962738599694, 19892.144098957, 46136.07329064251, 94238.50068336677, 24069.184979689893, 7725.321852299582, 58969.16301484486, 20721.978437704936, 69955.86827088527, 79313.02143528579, 95936.50989340675, 75266.42782862597, 12573.489975767616, 25652.01018860126, 19462.86838644583, 5500.562656400542, 70011.26304029414, 5752.082502212142, 21717.2464114334, 26051.581417834546, 54061.62293355583, 57078.072594570185, 51532.044865193384, 95583.1365184488, 54161.766165485446, 92635.26529852043, 80273.75907416704, 35420.58841806988, 72816.73714425431, 74610.97590634094, 56664.01098623436, 57370.652203545724, 71864.23990109049, 13033.320553655747, 56170.387551897395, 62990.06231822229, 85500.26184306489, 36751.74855348082, 86560.52422580747, 30361.004574784656, 2500.2143218281426, 64506.91713780556, 46351.08557299447, 46824.68330539674, 69168.39628986607, 32621.422403675315, 93061.55421659283, 73496.05145665472, 64014.14832287059, 50579.04859231866, 91011.14200349586, 31209.196034289555, 9789.803199801683, 37371.94538019797, 24610.547903428782, 4880.338717872579, 19653.626434656668, 68162.82211366962, 9653.936232466376, 12961.515055186923, 23256.30814052555, 68489.2142016459, 84605.60282642962, 41203.36054564653, 45859.6046995146, 72308.20096140735, 4200.4768047891885, 28873.519789780945, 72772.026980634, 52353.324492525986, 48432.20389466456, 13069.595266909884, 72073.18359411962, 88120.90255218356, 41934.945617847865, 12996.522475401584, 44022.511986555946, 7450.628344552501, 82807.03994633263, 2214.307174505903, 17908.689685143363, 64757.83854747416, 3111.7383544516542, 78085.03242050351, 9376.578588311713, 86774.44567304372, 48240.03307025768, 97914.45606271872, 6880.485213700127, 12947.799523021886, 97439.06937616388, 56398.49249439804, 77054.38711733925, 82531.40427413616, 31802.597538488142, 79757.00729796325, 57339.8830238292, 73999.6966205039, 20728.56955796558, 46713.23648907876, 10073.016431450065, 65858.43172486339, 55269.312831647556, 96756.45742549145, 17503.37643273542, 14701.064254781548, 13488.075318167892, 65137.4060084281, 3308.732394914382, 41524.10506263468, 55525.45675910461, 75057.01662367895, 62284.75075307247, 36814.20817938876, 29253.64789567938, 56491.42838918383, 61565.26893070853, 19720.568631990278, 88257.42736276404, 20979.573199818147, 35947.885784365484, 32830.90525544088, 6990.725248987939, 80766.0167305759]} +{"id": 1361, "vector": [1855.738803768947, 94262.69228440752, 62792.1733982673, 18962.557556654603, 87907.39510138391, 72623.99170618856, 54578.61203969312, 78310.63788909305, 76014.27060182374, 11248.457986430494, 65141.496590080525, 10617.18791494981, 67397.61254415792, 98396.90560921405, 13663.733658291278, 95674.02659705437, 10477.484558576145, 45269.8819946463, 33202.86085574557, 77665.64141507541, 90034.53423009459, 28416.41734150261, 8246.4177149559, 98930.69019827402, 80797.37228209208, 95172.69526174039, 81142.97537060273, 2790.6143678540498, 29072.54107589876, 38047.71374176317, 6754.360054753028, 41833.71014071774, 43608.476520977056, 17880.448049547125, 62981.7916314852, 54578.0497905912, 17091.89478993378, 89459.9862637442, 49596.25978146122, 35894.286946516055, 90033.38802139739, 96834.72242084703, 85388.08030907957, 77193.49163489662, 39080.369971836335, 6869.373747404661, 73199.75227139193, 89456.82423562517, 37149.318060605685, 81071.06578265116, 62071.61756138785, 85979.2798698282, 71519.02594984265, 20800.121964260576, 64358.75393178295, 59604.50250715976, 51688.76745252843, 5125.539027101667, 15927.813535209889, 22073.433739794767, 27226.971262954612, 57232.08194254302, 70184.99902618979, 53413.32927716117, 15250.342418766139, 22151.797458446177, 51368.7334548983, 68436.02650122628, 73565.09580971302, 7336.142315533589, 7103.090081854435, 70944.11237738692, 2381.1844961267316, 41461.855327733145, 4564.387672497305, 56082.40715084656, 45262.275158820456, 60959.8144625482, 85326.75058368687, 23951.12847513896, 93436.86571722435, 43533.13293476394, 23277.670468007418, 12478.180367251902, 55357.92082191734, 88385.80175977471, 52626.96898146912, 94906.01596126321, 74536.90176989094, 38184.121631605194, 39325.536237449924, 49039.67846920682, 43406.237083064836, 70820.24384945974, 58743.44603917462, 6777.251280811914, 77227.422102291, 97310.18591014108, 78669.72468033183, 42996.70842581582, 82232.05165150508, 3437.445214437962, 423.46134354073195, 37116.16140393587, 97923.6482762706, 57944.521471259235, 96182.9147831795, 41748.66548006078, 33235.281345759584, 80619.5257460187, 85530.53200675061, 54469.41803366977, 18772.221180465753, 30443.149375658486, 67065.25390540076, 10893.692301017722, 3766.6558735518497, 38097.60472195206, 76627.50923789223, 74404.60815706571, 89073.32869333294, 71894.9202116849, 79371.81705724484, 38454.54176670456, 35121.56657119647, 96575.71771745005, 30233.568041017388, 21916.21627721254]} +{"id": 770, "vector": [76434.75042435841, 48989.569780841346, 81382.14993831718, 32925.17515625503, 37771.16572639587, 8652.800059527577, 84285.40882047305, 34861.673421937834, 94256.80022816526, 13695.703401315817, 91405.11251727698, 62731.25656803692, 47630.141054081374, 13857.24759861332, 82398.82405522886, 41702.0060301086, 48495.85578780351, 68181.65640642792, 27735.514766557135, 72581.64418059196, 67295.11527290006, 10747.193434993018, 13549.015374832208, 80871.65850580658, 95545.73723589476, 39230.153180141184, 87470.3659507005, 43642.31340819616, 43094.67097328584, 44397.38519706552, 15878.25693821726, 42124.15248773752, 15734.160916484074, 16889.365675930636, 3476.5612260893477, 33084.497317887486, 58412.14943307909, 75785.08228107354, 8160.916023298337, 79032.60417390837, 48756.44712354706, 87985.39314960084, 80546.31983657043, 64350.26168480662, 55657.59914659986, 24021.672607421606, 87617.9150903746, 99623.61665442538, 7594.993042529297, 9800.783233046574, 38525.715334720466, 55932.80481959149, 4641.698186352716, 56465.245739723345, 27011.047959468426, 96293.92236098276, 73041.21602865706, 70189.77645424323, 3419.5447830299377, 99370.97863928924, 8029.58880824679, 82267.09766300298, 59253.74001397339, 85320.97009174628, 82490.23997562351, 78454.3939207444, 128.07393981973948, 52848.34017648878, 32481.87661909211, 50612.83811916316, 55759.757514540965, 61080.680663226005, 20915.29047373043, 45078.618747158725, 41683.214843262016, 74796.38435113634, 90423.9735217241, 32442.76575961865, 81153.36146040096, 20777.465868370604, 3171.1066291143843, 56336.61247384179, 86592.48978545166, 91300.04884870621, 9847.147047326176, 62558.3392601871, 83190.39115599816, 2120.303583280847, 58952.50038318467, 27661.885263184504, 99503.65289778882, 79927.77019462133, 48116.03432430379, 47198.46302963408, 29339.741451141064, 62756.616002414914, 34307.0265727365, 61476.35405870856, 50212.69709154226, 4922.622277296207, 16688.96964871056, 83408.62170808858, 4301.395835656641, 21432.265239090688, 86901.25396499656, 3786.8544422125615, 6910.231685505874, 22983.37447867379, 24458.202771675064, 2139.723891418277, 62964.125784307966, 65784.23412810129, 53564.372223034705, 4368.517037216701, 96006.51260184737, 83403.2908623963, 90144.42347874452, 10071.358788018215, 67162.1531109719, 2852.0413795227296, 85908.77411978977, 12471.045034573248, 82055.21157880075, 84476.72952632568, 30936.137026433287, 68117.25669181121, 59038.450187072835, 13701.090504194346]} +{"id": 1702, "vector": [22366.09101516166, 89490.09599925838, 45939.57677853069, 20246.871198571327, 78521.40809181407, 95550.16533504041, 85876.17230690477, 45699.9716381735, 8710.996220333944, 70513.83557258631, 85758.79586753162, 4646.7935899598415, 86558.51562136802, 33546.393525673266, 2198.26701397835, 68074.72760441934, 8985.289371781779, 33546.68844293458, 11011.435458788788, 4046.39878704971, 27522.739508858707, 71979.2626202393, 77229.71316059255, 50963.49533116655, 80545.06783183153, 89122.79566065726, 77416.75693635934, 22492.642061752198, 53636.180447164596, 87676.0816855635, 84259.5085859956, 55788.32801486028, 58893.425633066465, 30682.326524321456, 39194.952701118214, 30583.021101600294, 52198.63148507721, 30291.885141929266, 12620.767105843266, 87507.52682255421, 99383.55913824022, 63030.24946318002, 20197.188519713105, 4509.7309017182115, 95447.7470533077, 24625.200435059458, 72138.43613902354, 61891.34686848833, 55638.99815529317, 80194.55482209905, 5812.653881833496, 86791.89248415477, 31485.386717242258, 64861.43414838804, 11791.849303629542, 91698.26256142274, 13376.900368476641, 52982.80855750597, 74880.09194854295, 35369.11870227726, 90175.87349821492, 74719.16715295825, 61844.951681922954, 82389.47816936298, 10643.25559975895, 81023.07223078239, 22284.604103730697, 52705.911512946055, 91849.80271366313, 14177.682423438786, 65820.01762723281, 63090.515396651135, 76981.0448877154, 80305.3624298258, 79140.22141554255, 86971.96016434369, 65339.59935044953, 42450.85911578112, 21225.49362490389, 21009.68241189396, 99136.49057117528, 33697.98133064917, 66554.02081449445, 27176.35064665971, 43406.32026309126, 88873.4607334685, 7392.175353218977, 73561.46026567022, 13985.117545817127, 26564.66466003008, 16666.977377000992, 50531.091987613894, 69211.29426571666, 21348.556480367075, 89348.93453334451, 21944.15380530448, 52882.888843020104, 14063.232987164798, 87435.68652547148, 38774.27105382426, 91684.76295822466, 87082.7224970525, 79989.11886883096, 17175.326195323592, 45285.30471998837, 50445.39666048994, 36561.99730387788, 65222.07307157528, 67916.64826060356, 27892.416016645315, 66754.53685643717, 80231.32907220241, 50787.12503375252, 2508.7573896072413, 33444.537821758844, 14766.7742523096, 55450.98676967476, 5845.586780421408, 48583.524611173016, 88663.3082950718, 94393.71512929384, 75084.4930761576, 74407.30901679386, 81929.42188213757, 32947.96467542569, 97116.06028294633, 13418.579353863592, 2912.6124367806037]} +{"id": 470, "vector": [18741.745659520217, 74883.56960144121, 95403.63623740972, 66064.19225309517, 80989.08598564476, 43775.64936302193, 94959.8587735472, 3150.797795611904, 1918.7992567062051, 37637.480550813925, 88446.07910886497, 6590.642750515441, 42977.31751960876, 8740.605408976799, 69476.8076837501, 48343.210292271055, 87143.20724849871, 71148.3021050325, 14282.784636018852, 69119.05251404009, 30639.78952013221, 34057.71721401628, 4775.266260595812, 21393.923718821552, 33777.16993442602, 72252.56291643383, 11207.301067976616, 81250.06099953326, 11543.300378646414, 51029.50790812947, 13091.478211843854, 95057.2190208213, 75727.91045606104, 32899.60753172959, 20653.12016326012, 68283.53893960796, 28914.836967768486, 89058.72488570496, 81102.05987153771, 54730.9102405561, 93380.73833476614, 90492.15321871934, 37935.816229338896, 54734.51915138708, 91078.85377667144, 15765.826568061104, 91257.69108537481, 81163.09409370378, 95901.63210276325, 87641.20678707631, 11615.327028243471, 61112.77715970521, 11554.483688895967, 22953.309886070216, 93980.52423990547, 25562.18269263928, 30826.7747691071, 53603.120099042426, 34599.865206748604, 28763.648835531974, 91301.63970729485, 36693.33949469958, 87085.84391724736, 35470.80975350318, 74526.97172230392, 61896.69488858366, 10678.132607112479, 10152.752780437957, 75908.1335980093, 42019.083740918824, 28342.7031974543, 76518.78971144896, 32931.11244765642, 64010.06155678309, 60531.66383130345, 48394.89991578808, 93992.51621438234, 31434.15573107783, 82058.86706188845, 4167.9246086753665, 93272.99229656311, 41395.39428957201, 14139.79237150721, 37015.47332592496, 11883.501067738289, 52673.12485771055, 34189.0510386563, 80529.04825551355, 1501.5112568799616, 61302.74888586087, 98183.91098310953, 59633.02562435413, 50624.82282406044, 22360.39756834981, 93502.67772598339, 8551.714808860734, 61468.47611956939, 51934.34864272572, 96898.0384700915, 16311.69233663511, 55037.75155194485, 65246.9143778278, 45401.23770735017, 84423.1115123673, 91217.89786440924, 45480.98699974813, 99516.45704895217, 30345.189307840716, 79579.24915887228, 26451.814006860342, 13070.781244419428, 84589.27363717643, 19425.52814281345, 87096.97254117987, 34828.35700259, 48601.77142075293, 62076.84394020748, 48555.05596250033, 58680.163622652035, 56228.274535931785, 85401.48232874894, 27030.782903501273, 91498.0061800999, 15518.826937935193, 90056.15170315781, 15181.376582634997, 11570.577518602688, 1877.8109300563783]} +{"id": 779, "vector": [17094.941892399253, 95657.85034641273, 71386.78976461678, 5288.975100479321, 90027.06340791252, 90346.25286713919, 39736.15177692327, 34696.7733806195, 95653.27893399535, 18255.186944538214, 81009.13026329794, 87743.81588778042, 27997.87536773062, 10952.606938052655, 46120.81516932338, 68820.18964929151, 64425.90350933158, 74628.7815838856, 19690.95444516401, 23964.412043744953, 64163.94363922856, 3710.963358755348, 91106.68716208277, 6157.8701568886345, 35576.66217043916, 67048.80024322476, 69494.25403815488, 64980.21858199955, 95013.54928803736, 22043.08835192561, 22992.427142804583, 23912.972729619774, 3066.2022561363856, 82706.10108358711, 34728.5759883192, 23266.422662079043, 35702.850451669154, 64116.04906182862, 5558.212668163209, 88361.48798044532, 786.1027038342461, 3025.5215797882483, 6359.364104521114, 74738.34579403076, 53576.64823743668, 21444.331524964455, 74162.86735427762, 74569.64521784244, 26959.402307819924, 73343.20005686709, 46824.63664048867, 22796.71505457763, 62684.71115893266, 15316.5320479286, 91227.78223914455, 83334.36154736314, 60594.56023292784, 24859.090054480694, 23009.9503139937, 44458.34071468813, 76895.0645153719, 92858.70729369018, 34328.01373508222, 15976.354832948959, 78550.99685164214, 60742.792278664536, 82548.04584228473, 30481.411004790458, 8574.016416087527, 57245.32901814344, 66434.1830416663, 50600.61083011844, 22958.44528733474, 96606.16832134538, 95923.90910936268, 75831.23894678762, 46764.803133055735, 53177.73545598401, 34885.057901010456, 3609.7267291157254, 50634.83680609432, 19772.646785388137, 31373.190034642706, 16061.626685612251, 51692.59724937332, 3464.537102906018, 19088.340465612062, 29341.44777826664, 82804.82110740198, 13445.908575217303, 69890.05047842188, 65477.85484555819, 72635.29441080667, 862.538373142796, 66351.02944169451, 32176.315245086807, 55636.87773119266, 25614.187168614655, 18168.667109650993, 64802.27074804232, 91389.22015872263, 35358.80991268338, 78415.46667473148, 95804.3313635596, 12106.387333587865, 62750.60453176435, 98607.32100762655, 3887.139080776292, 63422.73424242032, 30660.88016077263, 53119.10056141799, 33728.0491528194, 13556.307472148199, 34414.9328856897, 80017.89329247309, 4407.033491432199, 7712.572881398272, 68784.4802218895, 95679.77731309303, 17115.361068689283, 71517.6451628674, 73846.35341837417, 83208.50541964156, 86661.00960278841, 52431.01208104467, 78859.72518954096, 81568.51489213326, 48879.22043366114]} +{"id": 41, "vector": [94854.67285860547, 46205.16798748658, 70837.12220818877, 57828.1778171093, 10960.623880710562, 58765.97321173626, 82867.92873614687, 1412.8286402148915, 91942.33210240256, 22499.48506747127, 56516.416502255066, 84580.11361405166, 3727.561390367928, 86868.32832685197, 61790.1753229784, 52102.20452165006, 47659.39154381302, 61092.125275765175, 97323.33715321208, 69263.87689374533, 34862.7838335302, 77478.58689525897, 28983.713617236805, 83298.84171291377, 22940.853492148817, 74770.31845727375, 68826.21377060187, 7679.928516518253, 4780.97550417883, 39245.00475162889, 21248.324874890757, 56517.98633290607, 79062.69338782685, 41099.741502310804, 68374.38635085891, 71655.02823763459, 76840.1471348003, 94230.90289947379, 7044.525980075012, 78536.63556928077, 21799.76239050626, 9665.556926756291, 52233.56718073123, 64316.27843534054, 93125.17837682988, 52974.06026567473, 90075.47916586226, 42963.92654727673, 60584.398247332836, 49960.666603911915, 73515.33387714131, 147.46023171166024, 67868.27779420803, 52320.95793308053, 58798.17438667764, 80668.5963308748, 62863.979222347225, 77840.99328340159, 57009.92099411938, 68243.37006295378, 61436.058613092115, 7187.037485964587, 81987.32664175103, 3050.0894049669046, 36691.570084918305, 69466.99614666963, 8076.666533365528, 85932.89592133297, 49933.13901516827, 72126.29458857182, 78033.90906622498, 27208.396127051015, 6027.305081882095, 95186.69787947816, 51703.50309662357, 47157.2987904227, 74759.62017865446, 88703.22428318732, 4854.920753558756, 934.7267834239559, 74253.14922247233, 15675.25402951281, 19455.896591617526, 74970.50261024325, 59002.23055680596, 97824.61631818561, 12079.155854794299, 14276.131523698421, 83575.08352088733, 74737.47499876429, 98932.7977041659, 4259.232850887451, 16962.071204402386, 55556.32788173339, 73399.36129323905, 23409.99023227147, 8002.860126896816, 7320.117857262232, 69588.87033917084, 31386.822003142046, 64056.59337190417, 91883.79919843827, 36460.26591149014, 11710.74523009854, 3820.0053718824843, 15877.769489400362, 58542.48269877265, 20931.995435043627, 70640.54088089617, 89490.31437012088, 51925.77654925471, 64168.326216459085, 70224.95919208453, 28829.969128326004, 31840.45574418901, 74280.95877485514, 62871.60827933415, 46729.89555440469, 9516.736663180625, 50870.85300479708, 89416.88900280546, 9536.10503934399, 23238.319569860032, 20679.82592000649, 64564.923053323975, 70611.78232637144, 14568.185800563515, 88489.6104806902]} +{"id": 1207, "vector": [95274.64689471916, 94726.6312299184, 12014.183446666515, 55112.67406650309, 5523.953573940122, 5546.169524336908, 86395.77438811625, 22348.946993342044, 42689.29924612835, 53755.8650342002, 48804.43694719048, 62270.636160228896, 73228.39530240909, 81071.8401886427, 57259.82594864355, 73120.948133342, 71137.26258368904, 27655.579701687162, 93838.7515927486, 55990.31202266221, 49859.17963997639, 32360.290146808245, 92603.03800074567, 4579.43280872073, 17805.65288769612, 95584.8042729791, 8513.696994305652, 38548.696638936904, 14892.711335262198, 2494.0816344306118, 97556.83087161579, 75966.54022307883, 38237.90601095497, 7514.896009326466, 48255.66359170067, 55113.301541114786, 66464.98933081905, 12738.64824035671, 30990.278628447697, 25294.67469436738, 54893.785903287484, 79048.54244423818, 65574.94140874612, 93170.10177705782, 99396.40514981057, 44556.24678593942, 19195.249126184644, 61182.41663787268, 60845.41941967194, 33932.86896763204, 82432.44897082509, 7975.988612038265, 24475.5067168143, 62589.21780261111, 1029.627442671943, 63299.35184583788, 55771.64081496261, 92909.1362466704, 70684.37230076281, 32326.540197836584, 74167.92957380298, 77756.60791709684, 7429.312786437447, 32530.060776765225, 62781.21689593049, 44447.24538028527, 97930.03737570829, 48307.86969416259, 33359.16649940759, 77391.89450114718, 20333.612556451997, 82573.47151282315, 65643.08740483667, 486.82837145727075, 80011.48985475405, 822.1806104045992, 93932.4492200358, 73798.77299315215, 47911.75371231632, 71237.10503561154, 2784.996688803698, 47949.74147433484, 64487.3525929048, 40943.983136117975, 18632.67848186, 83740.83455319428, 35546.66861026789, 7502.674489261752, 38903.116873127095, 8975.59335665744, 53677.55259144485, 25010.705511595865, 88471.93199618655, 45442.78170518051, 45784.06735800405, 17925.454783328863, 68666.30725886975, 92526.35656312804, 20155.70642112875, 38296.55538136316, 44398.553177369926, 13053.81701254622, 43637.3471603764, 78577.20165932408, 6222.473239788284, 99160.35562580918, 60955.73399278087, 79574.67370830008, 16165.252412653075, 80102.55531491862, 11618.190682555274, 80505.95362062844, 47410.09543512782, 45774.93134443894, 760.9988461984685, 51493.082956035716, 20865.755725409163, 88213.65561497465, 23035.962267600185, 45727.255578675155, 57248.91391745147, 33552.43854247106, 16763.039268573622, 29230.738510013864, 24269.30336810823, 87159.48692771475, 78634.09833036816, 45802.51689446057]} +{"id": 1948, "vector": [76555.92577543446, 47408.80798236761, 69202.97197912131, 9395.064510291118, 4058.290500293205, 74665.07782349944, 17320.210400272717, 3830.0213679665185, 32002.200746894472, 73673.18134336182, 81235.47837391064, 98227.7302749774, 46136.85278700313, 86333.58915382317, 49405.27207442268, 62081.64410753611, 21474.214693617254, 9151.98598833249, 76334.40110189644, 50310.01649958959, 22093.44056047855, 29950.262278134564, 85420.16654579724, 47315.325737226645, 11312.928507052433, 822.1721134059168, 37161.004444982784, 73293.21673741397, 37722.22074543109, 66439.74353687912, 17263.80389317551, 5294.24101291176, 14262.486810907138, 88480.7246467968, 92959.45010842387, 42980.4569946917, 22166.767751362026, 97694.4227249317, 14879.034193434836, 88805.66307256863, 64252.9088732954, 93824.41979527856, 83157.7566281524, 42134.09088822159, 34773.69761909684, 8091.659238249438, 24254.943030957988, 19137.624894793014, 14057.068675554196, 22411.57237060537, 54569.15403122491, 77001.06163103417, 73665.700651011, 66034.09094174598, 63688.653129256, 43820.31599663707, 10463.727585094528, 56767.0726744733, 55201.809120280996, 12760.633307334047, 56009.01118008458, 82001.22457411258, 13338.463334169104, 57228.00748370244, 14814.836463037384, 77540.91105683729, 15038.525916423096, 37754.34619057837, 69981.32023241122, 46918.28681649921, 32973.75030569301, 21057.27431440383, 47779.92669895061, 41082.696976453124, 40752.94419928233, 87398.70633333933, 21410.81922440887, 72542.20210580272, 2115.918990151866, 38082.96886374686, 69500.48000834588, 63443.32579719611, 33980.437742812595, 82837.82222261057, 94482.1635306299, 20705.213433353143, 36743.40210478876, 60049.108870469914, 58856.466458919, 52593.03360040412, 24022.25549331116, 36281.64018274653, 72915.71460997887, 53538.13221715688, 8427.690991858828, 64698.61938996495, 24054.770313524445, 32470.949918670998, 54173.93429545906, 76473.81586003014, 94452.49727096292, 76094.8405749172, 18943.08841581972, 11258.722120530629, 10378.161594832934, 86162.01472923082, 39568.123597599435, 23990.183045746184, 10664.99296135094, 71560.35396564398, 71385.20829904816, 21416.39239385178, 24605.03342405116, 5118.62958210314, 90262.05392573097, 68605.51233669627, 1059.2330675094242, 85768.96833147672, 64269.498338744765, 78432.78755185593, 33025.33581226856, 4689.3982385131985, 54322.525692539646, 95840.8653555114, 26656.032443408363, 40565.988575814285, 52127.35378976368, 70560.42881538607]} +{"id": 1218, "vector": [1255.0250105440864, 41815.05804745118, 55462.66014946708, 63917.154062095375, 81980.54856523951, 28148.154031788876, 52631.66235101956, 10601.558979155101, 73663.46152124793, 82885.06854746537, 43628.05938790911, 19529.407400191834, 18502.853602422387, 28836.714851410215, 96537.58614671441, 39809.986326538, 94312.81096939692, 62927.93606149252, 88060.86172693099, 51442.86531574072, 49980.67611056896, 79832.7602756661, 94493.1549123777, 51270.58892334773, 12380.132773393549, 4603.828794365761, 6615.9955242477645, 82977.71061610406, 22775.440869009366, 30806.926843512385, 65543.57663386852, 48753.46782724632, 28582.029862442483, 10546.556590155953, 16245.704208006895, 79757.33016629964, 24756.681041033746, 30240.733388524077, 78724.48805297974, 23338.262302935942, 28289.02020658912, 18963.26548943652, 79522.26100071355, 81302.3949281893, 17748.805551862333, 44549.593051227486, 98194.44557073378, 10343.720695234893, 34734.42842417822, 18169.63270805537, 17158.85678730371, 54964.12476971553, 41795.19297844868, 89136.55205534892, 74541.41486478312, 93688.61426696187, 13511.60117304393, 3365.7860937298724, 79284.71885685415, 7430.209328230985, 13895.162016948092, 88570.90873901859, 15589.142263506106, 55255.10245720462, 78364.80348057448, 65286.39167880575, 76363.90078284786, 22859.820290192645, 9448.424525933118, 15511.586999378791, 48859.29119951025, 98432.03594364153, 22258.613995329113, 17975.65023583614, 48927.95346591414, 85422.28898000509, 33396.99208432835, 38239.87185541932, 99244.1128492942, 68425.88339916487, 32816.81894876493, 17579.373807731656, 63208.354148349434, 48972.00364067789, 21983.356738649018, 80212.458707341, 63080.08273519882, 80098.0220446311, 18744.005150401765, 46480.26093343702, 37506.89604030869, 46150.64946566985, 22587.54998405518, 47439.73707398278, 59754.297335433126, 26531.399087825215, 32841.8924596821, 79706.05421026342, 9536.505121191185, 20934.224219309726, 82253.22076519715, 22442.29517620072, 13899.641277027109, 23308.743765610594, 23350.586265289385, 97023.88629959775, 62182.93783971659, 8179.69226859957, 46819.38625748619, 80364.3566456233, 15294.039699557261, 13641.22254577771, 29783.173911354697, 79108.59385898155, 70876.04435571776, 43014.63225233973, 27425.855235575436, 47043.49667366483, 28979.816387487732, 49640.10026155256, 26384.82091043245, 55569.764056724816, 36745.67901543089, 6044.307355512624, 45047.9613489829, 12519.804739627416, 64513.53654470699, 90858.09023525828]} +{"id": 876, "vector": [44524.730996301965, 59020.71804622887, 32227.990762609348, 87416.35140937546, 31086.153517640414, 23787.618977940983, 45151.66373824654, 86938.71098488543, 8884.042203133491, 58905.50632144565, 35887.324694196366, 96193.40405914867, 87895.63150591095, 2522.2023451820232, 55444.27465920616, 45893.58807902552, 58574.440847582264, 13189.83639284138, 79637.99610499205, 19411.461490213078, 25536.666758722327, 17461.614559932925, 37955.84002217457, 81941.64998833706, 3628.2935091136337, 10199.936998119209, 99820.05327140543, 53571.43235832258, 93048.08461074256, 17553.260872898856, 25664.028982136733, 52187.537192258606, 64235.10834990586, 84351.62974517095, 10267.736962981788, 32305.310262776522, 34968.17656082586, 16300.446109670274, 70167.59967119267, 98317.18902510307, 22660.302856999493, 33033.42963569715, 6781.143099999354, 75539.07746924904, 77743.37787024051, 16401.821042875752, 19230.588033946195, 99151.1406471949, 41392.42422370851, 76584.53909163299, 80918.76099719292, 30797.520422046797, 87698.60893020166, 83986.72251864956, 17745.658158137954, 42749.40171513761, 30881.05586107902, 22864.26700991544, 14915.928731433425, 55476.436793600005, 96351.07617986057, 10439.291364202929, 95686.39553733708, 4798.705416818205, 23826.10503566249, 28104.524126352502, 4463.671432170036, 20860.079518697283, 4963.026660255532, 78455.40377617069, 74790.1404684398, 60390.15214626819, 80821.04024621971, 35718.1863148203, 86934.81412369925, 33152.640466150864, 93405.89606942807, 1351.0294731443273, 90831.46638681139, 10112.67019499572, 1450.8701485238528, 23680.692216115516, 16888.544369340274, 22085.245448694157, 95996.92556166755, 84431.27820276526, 57494.50537719457, 41461.88894605632, 972.7325913892026, 71530.0175104507, 82205.17109992963, 2275.1172067787315, 2675.371607781496, 16347.725211743858, 43926.50448107921, 97994.90659931231, 56639.047943452904, 35417.374603409815, 20903.017760094866, 85118.73021994662, 34470.84782954668, 36290.75036797783, 38261.74612676937, 54488.236204892826, 88870.14404679515, 77032.1295292745, 82014.77743800882, 15101.898159835702, 4326.33671719077, 34337.126234276504, 35873.90946496044, 26688.32509541008, 69710.68871289783, 99922.49731062273, 45678.10412972959, 60748.348810492884, 76104.0461041461, 83470.1973627124, 2528.8352675607252, 49573.593953995696, 26691.973808020484, 86952.42119911268, 89699.55113901524, 32634.108422089903, 26219.761679620467, 33308.77431177105, 28312.479393506484, 90042.3172812886]} +{"id": 1226, "vector": [7576.7408646918, 29796.555112113343, 29036.93116729673, 64599.33125583688, 12434.937233847098, 26569.962523551872, 96719.64404545903, 92770.5017523275, 58008.42457608692, 12755.172111854585, 48867.29805340904, 43166.2165864646, 29738.50229965773, 24132.670822896063, 2689.120832579306, 24076.307881322355, 42010.256231951425, 74147.43452453273, 14438.983479084332, 21075.725739819194, 70703.69235665357, 11399.981413604975, 63626.325978167966, 51561.214658955956, 60810.97168095932, 69738.00810104056, 17009.59428254207, 73865.74375829563, 81412.3158662824, 19077.04987143498, 97871.007906176, 20389.43014763781, 95764.3071984767, 22668.580905786086, 32083.00654401929, 74190.46242102218, 53265.86584219817, 57594.11540567573, 24190.91763177489, 99714.66469315006, 91995.85854419599, 2199.4955666310047, 3140.9182331649577, 82667.7471353023, 34675.283859103736, 9926.867914080118, 19085.071726723156, 66270.36845484872, 12214.735571124369, 40341.94829862758, 1107.5355343216575, 87985.88170814565, 39851.37488078456, 75624.70316956722, 22114.63500788432, 30294.475365378326, 73099.79145401706, 51996.068630303096, 49215.51323581945, 23749.552086296288, 15818.30674961653, 2463.4459498334318, 30111.257894787435, 7751.9344881019415, 67630.54670569969, 60470.16973963942, 53531.74037483126, 60562.66995530206, 74013.48194929943, 62369.08769940529, 28161.91683520134, 52921.324099500525, 44754.373948830194, 35588.28848478774, 60759.63623650063, 49700.72158859646, 4104.09886034212, 41718.41872772839, 90721.8123237796, 32416.107644474945, 95461.69419429165, 48932.62320498009, 35622.538017073246, 65503.36942728119, 37796.108805841635, 41720.65242911107, 85965.36757723527, 18970.2367953015, 43946.93990453458, 17217.47420619427, 49776.889994834724, 29028.208074265116, 81400.91032077745, 69045.80880634878, 58821.96938213452, 55650.943935940486, 82927.02458475955, 6047.111473720901, 48592.44580015878, 55742.20560366134, 68163.4946695955, 99992.75017514687, 44953.103714347264, 57556.852512565085, 69512.91730531737, 95765.1752261611, 52473.55877480102, 44346.91645475692, 73700.04227858021, 58982.40334288067, 21434.511775361563, 60066.242529439885, 74349.64390390267, 99144.3737020482, 18466.11180066069, 57612.70764996871, 37602.90763860028, 81504.1127437397, 72302.2025724186, 29197.50038213912, 39652.06346100015, 92894.01047733767, 15009.834698342118, 43359.32033198668, 72016.58059234364, 2856.8369207976784, 40863.42917100681, 21668.52101490182]} +{"id": 926, "vector": [65164.25094846699, 606.8557578818079, 65279.602929570916, 78050.1150584859, 73691.19488171043, 6418.271496054539, 97862.97832219317, 89854.19417292706, 65263.14941066037, 49445.34456899433, 89589.27802753136, 4199.112219460832, 39549.23953697601, 21590.29593140086, 75736.71381548382, 65939.39625633962, 53611.13605327744, 83404.03205938423, 54050.70697785539, 15413.992816086653, 30240.048452243573, 65122.023176644274, 28034.14613820111, 7178.950105977821, 29327.265446029938, 99553.49146271902, 64686.879459401855, 37516.39886846666, 42117.44550333628, 52034.15612207533, 60066.0566857333, 62244.08442649338, 656.8162405659383, 23648.579168637352, 88658.6934267779, 92880.67303512937, 28399.66672368598, 70537.23313648452, 4753.810611578479, 17367.024053729707, 58687.04802185223, 18944.470113669842, 49801.38128303435, 46916.56296789324, 53078.8506088379, 2334.6836729645215, 11048.916663523556, 43660.02885668151, 40765.56071672265, 5989.729972502211, 25783.354854951558, 86783.14893923586, 66242.82615543915, 38312.42908448079, 54410.03240784591, 13753.14729477961, 87364.92359726618, 45614.18155778075, 39841.99978034633, 61173.87611697174, 50890.45963792272, 31093.64999995886, 34616.37860580528, 53393.09388643873, 89284.63150279783, 43358.041211501986, 51752.32692426007, 98871.86160515598, 91120.99219498974, 45957.014906061646, 60518.83029797185, 67694.65395181405, 96235.64599369539, 36334.20130939988, 60291.16065530996, 48933.77796057094, 74345.82145593014, 88772.57365625357, 43314.89423360656, 80241.76977984825, 8332.78730264685, 4748.641969023526, 67603.7944253331, 57823.182893888195, 511.88946940239344, 73508.83373831856, 49812.691575571145, 3522.511592217925, 39288.06777966247, 45878.96015113251, 74438.39796475114, 95219.68514107676, 46731.21871232006, 29713.119425250457, 76389.15343725985, 36567.85975951287, 59225.128047564656, 15143.072083248322, 21900.545542746473, 64733.477244854555, 86365.80368966458, 41232.01369573879, 13919.526932836912, 58018.55047564798, 15244.384738015893, 92804.00013774252, 22310.697759914798, 96237.07759645168, 81445.98172384498, 77060.1872857355, 24255.228351073532, 54404.44009278373, 63348.51310274403, 50191.303606964066, 62629.497197064455, 63706.97873098522, 6392.047955858527, 56091.1213397397, 61004.39363920164, 57633.034678304684, 1228.0505093688387, 6844.411612679868, 81952.0852459812, 9340.529172285673, 40701.70496075415, 55259.74506955582, 42919.8753107384, 83005.71952238209]} +{"id": 940, "vector": [93783.66884445463, 70407.9951480181, 42875.51106052218, 66801.63987637068, 40887.56709167526, 81543.17677907656, 59100.004823675015, 37810.01046337034, 54605.52633815934, 62076.19939480775, 25789.96727653584, 70801.73462568774, 76880.90196252997, 82736.68074569634, 74454.14786972199, 91598.83731631176, 3659.877647808751, 40755.01925625655, 48144.01703243, 81565.70230226371, 53669.16503279751, 19647.333415414836, 46734.12869360489, 55491.896467677514, 4917.749729069709, 1876.2432676007522, 88207.78668262543, 92196.92391547536, 46883.6539154014, 97471.9460194799, 38875.27306207087, 61770.27328174926, 81730.08661840764, 61026.508111416064, 45390.85076515207, 96260.74382476757, 66939.8177964158, 42026.65572474423, 5370.971236975097, 50107.724974235425, 88219.1439602482, 77886.70385245918, 73092.86456906192, 96807.24650780993, 49459.73174696197, 45131.01656196661, 82244.11926308555, 75002.74992855577, 17937.186058881794, 87358.17280926584, 43534.898017955056, 17987.555740634918, 49684.375130027234, 65751.36615479979, 13592.840389523886, 86881.49489371364, 45019.86937305389, 94853.48113538674, 97659.84863146381, 40524.160757234255, 84020.70077562697, 21375.933499387334, 74830.79268265456, 42539.11450681059, 731.9281163361558, 94167.46333869577, 48518.24773894, 68523.69947784931, 67747.70578793719, 15739.108975612482, 52504.05519771535, 79177.05723095035, 77159.84354286916, 26825.15560624944, 42442.26077510553, 45478.89079571824, 88393.86020626199, 86629.56979157157, 84935.63201341734, 4964.450640430907, 98117.03549718023, 6020.888903472976, 13718.915527617837, 92099.43984872552, 83235.16027420401, 41877.0691353526, 70634.01910685854, 58511.47355387053, 19401.098801893833, 38168.50608015031, 68977.62986188935, 29189.605184319724, 74343.3980384224, 15884.47060184035, 12168.842393690704, 41017.85459168324, 14102.166900651058, 28615.733177174374, 99070.40261322372, 75486.38831520009, 7129.001999592466, 54964.23269270824, 10084.121391351242, 83522.9566940699, 23758.77847044302, 92498.45230852615, 57597.824491813524, 10325.516527001299, 77534.79139026234, 21859.448363033294, 60512.187985975565, 57936.57421217867, 98840.69437419137, 46536.46086215463, 75295.91168085694, 41450.92595792703, 44404.74796987135, 67665.10396891575, 52928.00860328384, 85832.42136134367, 24823.46963605344, 58572.51433646348, 88328.65984487822, 53909.83151392229, 20523.984869511358, 64798.46939095239, 3919.232953277718, 92336.39697507813]} +{"id": 936, "vector": [92279.2071005517, 93810.79349400794, 49119.99205961395, 74337.0269025168, 15505.779350211023, 31643.600466383592, 41374.98896997035, 15594.211196778817, 27727.3758002914, 36394.19824332655, 11718.311302028595, 6053.265635488847, 59771.281591297346, 19755.391759455786, 53074.03447271458, 21681.765668794862, 59063.79819641293, 2599.9545120933167, 62221.95587262355, 52292.35101144102, 12379.121717569096, 60391.56038739383, 46396.44791758349, 21309.66812724028, 4338.482420912848, 51062.447466354824, 92211.77078683252, 28345.72839580417, 45790.77841547111, 18253.955861284532, 53203.60192212754, 21948.260467244396, 79158.54241717923, 88760.05128679829, 56581.43654821876, 24844.820276211598, 65630.94191703567, 46601.25048140943, 60558.847062655615, 22643.771722294405, 37809.12827409146, 9162.383166335609, 64302.226388398034, 45331.62593640496, 51288.55024194086, 47630.67660545357, 28475.617520634267, 48041.011927390224, 89098.33882759507, 47708.39145751651, 66530.20448235612, 38511.59346071177, 13201.376874456582, 30129.172500183122, 46234.088963033406, 78516.82518949204, 73463.93392282655, 72649.05204210259, 50417.231519120134, 35276.03471338505, 86265.10358548404, 25650.65339642859, 93215.23251962275, 31653.47083652076, 24220.860517134824, 56538.33866590427, 92991.88720906326, 74941.79398849298, 37007.34754435992, 28110.70303733604, 73835.35840892134, 58426.517842465444, 80497.27771900252, 2481.0407009826176, 70490.3587282085, 97560.55672667618, 20197.136314116447, 59084.48284990774, 73998.7476049127, 48670.20447645791, 20715.519351091625, 65.32701894751413, 85733.61831999291, 38136.98991206272, 18259.4889923841, 89700.43516540364, 24538.924366444804, 16734.076733940517, 76750.72227090684, 51512.28932159112, 87658.39925977236, 16996.295493753845, 45287.24699934683, 62363.86068474591, 31736.830457337674, 96729.84090644206, 91825.3774342, 33048.083950848806, 16382.772027088333, 9914.1214813263, 17280.781983638717, 31634.101361099732, 38485.31087983564, 33233.454553666954, 80151.63050586412, 67310.20022040955, 71968.14232756027, 9852.677448324053, 94344.20347712688, 37938.85992252282, 17595.08893619022, 42653.511117121445, 59241.80100851628, 37446.215945712924, 21951.477702630305, 6313.598150217792, 4120.881309767388, 32128.21757362089, 13788.339478388656, 20797.2115795234, 41089.37197290543, 54178.30303902741, 84098.39499052944, 32265.813798979594, 65762.50031587115, 938.5075626878514, 39662.457840910305, 33624.48795507485]} +{"id": 445, "vector": [88788.8120564028, 89426.87958527954, 82146.1096295986, 34399.25620213372, 67697.06428663568, 28504.03585338741, 39862.489770649256, 9463.442369292163, 35004.41959759788, 63551.83014046153, 62855.44589757921, 42170.4352327264, 6168.16416740924, 39258.58447540509, 48555.49704756354, 49323.967241030994, 25658.75745078786, 56894.82619216065, 90452.55509601336, 7791.018522825621, 65581.10485408941, 22281.73265382658, 49744.20008284577, 43380.15206726853, 67520.76279666675, 64033.93004018771, 75860.45430658231, 57816.89866412656, 95473.31901806149, 45511.810839903, 33798.40783191439, 48916.30545583474, 16897.781119165113, 30937.37479313091, 20651.837263158013, 51318.77276881037, 41524.24223355731, 66758.65904794555, 2301.8230799449866, 80289.82181307282, 8113.123597014138, 6915.997241933047, 55061.29326984897, 88130.70028197081, 65588.87911951142, 93870.26612343128, 70873.39789782882, 24174.540274668023, 41134.38139498589, 65135.412256990996, 51788.4547265316, 33786.278728062716, 37731.52618120974, 33469.86885006315, 42441.54429065239, 95043.95043835959, 8254.299646726016, 98847.34415553792, 99264.43354480843, 26201.91244921023, 30228.57651181352, 46014.8717221196, 77784.50612888102, 94984.58135475057, 3215.044391366584, 35974.080009145226, 53346.164086045865, 1044.5375588052252, 96674.73479089285, 81190.787992326, 12550.677893487727, 22937.065306155324, 92541.09577331104, 56901.40474402148, 30521.999975658877, 15650.286144558811, 77846.75712574861, 52606.443455719884, 54820.5204522029, 21931.41039898938, 7464.017837403559, 28800.954114528144, 68508.59642680302, 42005.906535576374, 89408.31903344036, 27182.058267176424, 37398.92861477716, 10411.690636731108, 82037.32611538291, 45255.802670597965, 64447.73821700108, 37685.57022086149, 98945.01467138401, 17198.78700892593, 60940.58962360957, 72614.67226899839, 68987.80098680455, 94191.0347479074, 6862.544173843077, 50540.51249720082, 67070.9150264198, 55594.929076731845, 26385.18960479912, 1541.0084805761005, 33068.74336287912, 37290.24049612144, 14998.343683926263, 77871.72270486415, 14546.981555408045, 83857.68460827712, 74533.6662668242, 59886.09366605786, 61185.921186709566, 20797.03906591829, 89645.89290780888, 4741.582510222853, 19930.78477950254, 54576.206236409256, 54145.51034196684, 83857.18701910214, 93070.44889399256, 44074.03079522816, 3965.933455860826, 49404.976583048934, 54538.68729297983, 92990.8531062935, 82809.0511440419, 57773.03495167549]} +{"id": 591, "vector": [35655.160234041236, 70534.4671840067, 52244.89209258654, 2708.78194862878, 96280.32050662447, 70038.8977640118, 32717.14146717121, 5099.239258455179, 94957.46687468525, 9101.046952480441, 27125.814188240183, 14017.280370038054, 11610.77733075153, 71244.81097350597, 77566.38226989297, 47037.250364989006, 7571.992023453189, 6535.620945660358, 9634.764815342567, 36444.98399951416, 99136.69475745132, 69265.46311586423, 74546.97337784142, 74762.43100783703, 17923.23265047825, 9390.975685906977, 60707.598060546145, 15722.353397368704, 1034.6884651159849, 48287.102101412434, 52398.81218777192, 59562.580148725465, 93311.39755503133, 80924.1917989984, 49233.52229113248, 24.05203222765051, 68022.85679696701, 3640.787654947253, 35395.8191766419, 5768.98595981108, 10031.01063998344, 68530.75326751138, 25744.866290718117, 16963.386918432756, 18019.42262678129, 59167.44714075335, 73602.46727063038, 35738.645923583426, 2941.586007514163, 67301.42133832935, 68453.07559012607, 65114.18494919035, 2640.621126255127, 90914.01670149947, 58401.81120633483, 54269.390910097296, 10155.061479682692, 17088.43822993059, 4643.832960972327, 17890.33801367509, 15958.130397936788, 34926.851479021934, 7062.1241728241575, 16848.13975509395, 23275.328804440654, 23496.8963285751, 46383.15778279817, 36290.853883990305, 71914.38352572975, 90146.62665666491, 86944.46543613488, 81443.45096827808, 16545.500461514963, 90211.68840643209, 48782.12417928974, 78752.52643813289, 82751.25701978208, 31743.293658269777, 50854.060531476905, 64898.28507942888, 90513.13759591167, 26131.640522565423, 39281.27634737394, 96616.11234814441, 74868.21696965437, 10307.434191075692, 85148.6472153038, 71602.47088040746, 4685.4438327992075, 80745.18103666231, 592.6981802947373, 71368.71589950418, 79879.21620196948, 13941.60872829796, 27970.474549269864, 25479.935465504732, 17348.63153648365, 62011.95814325555, 55862.528232530494, 34583.289441428824, 22979.88978332691, 34885.209182474166, 77912.90093655392, 43402.54136093154, 82404.45345276345, 79613.96463380083, 26396.273205641086, 73886.48260619509, 34303.2012831372, 54205.513435169436, 67388.20779689013, 57865.43140149756, 35085.63247045964, 13536.664843966562, 12457.289700305373, 98591.37779059465, 82634.3760028237, 17142.936856590397, 47248.8528678367, 72276.85088841205, 49207.135705139146, 54365.40585015828, 47523.77769552641, 35130.89398875579, 82215.13792532124, 77277.0331327322, 18274.114729004177, 24255.30743809069]} +{"id": 874, "vector": [11128.044724983254, 24742.10317468275, 26587.910564877304, 64632.43897050516, 71385.06089367435, 94277.68510622469, 16628.830894928105, 69228.43707380732, 54824.04392911381, 48118.396235768305, 55506.9654090919, 33141.80094200611, 68982.10608947986, 35962.544827889986, 5588.866784055269, 52845.57195131863, 30980.98010295206, 23005.19829032739, 2105.8646364200627, 65214.81066317116, 95431.42327793995, 15165.67609084144, 39021.41013126346, 5425.525786949847, 99224.31888587966, 48424.94916705457, 24753.54121904173, 7867.722940568278, 23402.31451473116, 58610.89735278042, 42865.345801073876, 76223.2745302826, 98570.14577063739, 45861.42914329994, 61266.72778100168, 61978.679769558934, 90570.49998623125, 90011.13046626518, 92064.97508870263, 43072.464520002904, 94953.29841793112, 23843.83967656636, 77256.87517077621, 52475.12929618882, 7004.902612375275, 1453.625437025352, 15579.506376804942, 90081.12599165134, 89143.28422821796, 80949.33995585293, 40252.350532621975, 13549.527027161956, 91361.34513021776, 22915.01767250518, 40196.560628147774, 37320.31510323168, 84432.99376530432, 23429.91856515697, 61939.00249531468, 51640.532221352296, 38097.2769796476, 73142.75947985954, 64341.93365901766, 6217.978614267694, 60080.10561266196, 23679.416299462308, 15772.89305791203, 56665.54468647146, 61093.725761143345, 5761.535504946324, 52895.372961928224, 84920.05664763048, 71561.01987955382, 73943.13401971951, 41891.41015395104, 4989.364043716016, 44339.95901015948, 37039.96021393343, 37771.59011904181, 36382.59958517257, 38571.972099806095, 75552.02466962239, 7279.860521050497, 96932.15846569903, 41350.14480502009, 48022.78252481053, 80580.17334674574, 76200.91627751813, 29456.268660504393, 33038.03929538097, 82641.7700921256, 66652.53052382139, 74088.18511767845, 17026.074857798467, 61665.34151946312, 22176.06261391111, 70749.6123120782, 99545.69413913201, 86883.81632237411, 7604.100386539026, 73250.24894960265, 95695.50875550475, 65709.767370495, 38665.13001086015, 43811.356084203624, 87715.7730613923, 431.9487804619881, 59942.74611706047, 36404.241274537984, 685.9225675231339, 19005.80571669641, 93079.85437858614, 69772.13839338506, 43195.70924050562, 6507.242767979271, 53434.93943550798, 4413.576258515773, 4940.570797930044, 32183.914505427012, 22969.93008607923, 67591.24656709458, 88151.85817780372, 38693.70772861925, 42156.01786850547, 60892.92720255821, 16939.36259854678, 77377.66053919183, 52407.15034355537]} +{"id": 206, "vector": [71822.4986493735, 74922.45157315164, 87600.3506537045, 82115.37777517938, 60341.94341397727, 47155.45352778043, 83470.34648239007, 43184.65700474625, 36841.77599672973, 64885.75692922607, 79283.85810738566, 79682.09692388188, 83864.75783292361, 4935.279748703125, 85175.18092568089, 51348.81125033526, 5186.939124388346, 59707.37658623829, 49563.55028199329, 48899.45336442173, 45866.76805858114, 79566.24184545505, 41514.057320206986, 11817.175049834837, 30606.02975053812, 46813.59224838086, 95022.39057944079, 40621.696626839024, 70436.09297945292, 79348.59578936537, 47287.321013761466, 20486.573599869273, 96865.93012426585, 34848.37567475714, 7249.528416696705, 17455.667411668084, 26586.272158918124, 54097.00533692946, 32014.816696570404, 15942.206843056461, 77623.60224653914, 81395.24410877294, 5590.956697748028, 45903.27343316183, 48582.675745326844, 24358.79946076053, 6861.339854893756, 40738.31063089086, 96280.72745990251, 53907.50946289429, 77422.41281007735, 25844.66279603668, 4361.845713469615, 52790.630728443924, 2381.716243998588, 3341.3888094551926, 54346.82942724608, 91861.97344878364, 54259.389031691884, 58528.38122154824, 37953.115232505515, 87664.16572900393, 95880.5992593176, 60942.202103157884, 22629.827692835224, 94143.86989452937, 5303.606847876729, 58772.842842658094, 14707.454429948375, 595.1786465151488, 93422.96265335164, 78468.89122257975, 87911.58469637016, 99488.82509392187, 77852.72910966437, 87428.14957114647, 92669.86774661376, 62741.9479951382, 84465.33163193498, 7098.075994803965, 3971.761188624845, 92171.01685248883, 40855.40390436897, 36256.02253880984, 96921.71552389582, 48305.402136184406, 229.04755410628752, 72853.28496938488, 832.6060088962751, 55496.57753851666, 4170.741436928982, 69102.26382534268, 97407.1915157311, 82994.14681343704, 35956.486618353665, 81347.66265448707, 15854.136370459815, 19235.946246311174, 62162.498550710254, 8388.3362458679, 70449.45002965722, 22381.62213265248, 76836.56543288947, 11954.907158807482, 94237.18843670166, 53024.91401579798, 21572.412855534072, 2176.8803403150573, 70579.13927800457, 43093.90932292579, 79636.7854988786, 88148.65291506577, 74962.45673755785, 99055.90889003946, 95796.84305642513, 63927.32415543135, 8337.13670780739, 24030.901895903055, 63246.28735318501, 74746.6907608312, 58186.73414952138, 98094.33859317124, 58621.37164145984, 26308.033146779242, 5576.272678330852, 1032.7229205560063, 15460.945586752829, 18458.082028007473]} +{"id": 1499, "vector": [17320.993972118336, 71113.14169922953, 85578.5047804288, 59953.34449789379, 76823.88274105031, 2323.738356978944, 75155.98645517825, 86531.99657542538, 57679.18001152945, 78611.51075402311, 25134.377195796696, 51720.911919273225, 43902.05543307527, 98228.04778799116, 97753.55871811695, 22486.863562199534, 36401.45084459295, 89377.36613800273, 29969.38378039493, 33993.031018865084, 65877.53614969272, 83368.81377603533, 97363.84161043564, 5271.207426823221, 58483.971851884984, 59826.312469787066, 56396.85401473658, 43559.589515423504, 59160.085552420736, 77887.22807404787, 42324.19163427765, 1158.131218033731, 91764.4506534955, 58854.13013069969, 70070.66989389964, 50302.024828346606, 98276.34578596584, 63247.88934951219, 72510.08313279362, 71403.0807572054, 39158.19532870716, 77448.7421618806, 28714.71212193435, 80329.24208220496, 77192.8668700625, 673.8300984524659, 92877.43640882778, 23639.296178779445, 70177.47246279457, 99941.05705802907, 67805.76735142033, 53483.19000401043, 82785.35022834409, 85451.05988330887, 66566.71920286215, 56907.11888746049, 12857.101761763433, 91685.7405083294, 68758.50342489587, 7198.23034423992, 48100.275338026964, 42459.94117754007, 67811.32961953472, 68204.32596433446, 1877.3840381310358, 66090.38291962475, 48334.98025851633, 23665.120535407623, 39640.0276926267, 84946.57342661885, 12397.397160257306, 6574.967149324462, 36300.96570416069, 87780.34908859059, 6228.200303217357, 71224.27721177672, 9469.943046861095, 35861.758374953766, 15028.86853523383, 49574.664895954935, 34153.11349210623, 27278.938242263328, 51507.24628114067, 69245.1568725752, 83597.11483620423, 84826.41740456616, 72717.9769980636, 45923.88806034149, 48274.39073006015, 3766.0464490786303, 53234.76976306109, 94733.50234763091, 41711.726172155606, 63754.58357294934, 62434.375520359186, 58428.005611916444, 65442.21418653597, 64031.973637437775, 56401.01103634412, 4938.180851009166, 44364.36292255186, 72609.36905460144, 92468.06024850914, 56538.71562350674, 92867.91085527763, 30905.404151648185, 31.03165072917413, 53193.41405273389, 53817.5223718551, 55048.252775156456, 44834.15344939165, 83887.70703508325, 47784.8818351638, 81014.93700578572, 63670.86796877898, 38888.74702110026, 80302.58262816312, 88140.15923030808, 82554.60961692901, 13138.109075289294, 75563.13184546126, 58896.91988253496, 41300.01250088308, 9136.927964228193, 72586.60400442271, 26256.84126000135, 4439.368580827774, 4103.793023882085]} +{"id": 747, "vector": [3762.701097654486, 56046.996543297624, 84325.87146173543, 52697.132454011, 36051.68789944723, 71947.43765431964, 97403.71484902539, 74568.10844815304, 23013.23928084483, 87681.09766536037, 5818.30367480225, 41884.01283057727, 94191.92476475993, 4620.820157304462, 41380.3266226144, 36440.136354703856, 65798.3781750345, 26468.18820819541, 75954.27586294747, 56135.29667909849, 12911.348177984815, 48935.45391427319, 91205.64329040385, 51354.11009594954, 79977.91174161748, 13016.575497891048, 60190.147779229366, 62254.16948798187, 26870.621770950587, 59.01057828153755, 25148.07321650333, 77958.96055943528, 71070.94511156542, 88224.4697721596, 20296.772300489396, 60777.56469446334, 80796.11957689872, 55817.01294736853, 37710.89123976953, 86503.09235526859, 10509.308755375125, 538.1766091243855, 46364.01044794116, 39862.81828426708, 92098.94788972412, 79864.48931064837, 54346.78352167667, 60236.44036973237, 22661.2307866082, 3704.3673184750883, 60775.42788602474, 2212.632344751364, 59756.82543001079, 87819.35689260565, 23247.778023063558, 59736.50855067004, 1170.7376046350305, 2826.6816516052672, 98912.96432696153, 39560.083655984956, 31906.918944031848, 27423.64494082529, 19262.787042612916, 26518.71970014701, 64484.29961620174, 77807.51296197467, 36965.9582397147, 10602.64140751379, 31917.102899498994, 14055.684381753552, 34214.26852941145, 71582.0182747751, 12813.043244978116, 65204.34858494022, 50384.68685072295, 71010.82583328927, 48032.004899972766, 23359.575655616947, 90052.0049571143, 49727.127659119185, 63754.48683947411, 62155.53765724215, 76829.26735763228, 77016.84980970966, 78707.8233055367, 16361.113042998875, 88191.38759788492, 77613.14009400668, 70247.54608789226, 67154.82311628925, 33855.09612822782, 58612.40938390025, 89079.25461002381, 38641.71424084275, 34264.55622828145, 40460.21765357083, 71222.00583486637, 83551.4092822926, 10654.344014407125, 5772.489993563623, 70710.6497347869, 98727.84072962428, 88245.24834597463, 10162.534531566036, 57133.17562466526, 10446.998081019654, 71556.54411376802, 74637.05911035326, 66959.0316958538, 22465.34498675743, 48907.018583163794, 73515.89962624964, 57545.80808996502, 63740.323285384315, 75999.45785763365, 72530.7427598102, 57599.5757186175, 29967.12913221725, 82920.79976600334, 95996.49098995078, 77221.053444283, 44656.29808747933, 74427.03150979527, 24578.848060089855, 82686.17348844853, 83546.15365813991, 46260.39020786802, 65277.7864620828]} +{"id": 447, "vector": [39574.13499829076, 18587.160666015745, 75486.27519781352, 28251.89842709794, 52140.079116505556, 94948.37441472358, 45307.98735056813, 78573.44912999876, 90581.01916392538, 55093.67309360143, 10417.902892708818, 27233.461560730255, 33864.919144504755, 17690.559640158444, 64044.09191582208, 15491.206305615457, 56824.303701437, 61747.12626338597, 59931.926386441424, 34681.97802069441, 49652.47556879504, 53946.99372998792, 44029.229798885324, 56743.66813946349, 8110.184704850009, 37927.025445975734, 99610.08707499538, 15554.862248671241, 21729.27520931406, 31819.071284651745, 81560.11872125653, 89934.46824391697, 1023.4205104456385, 63508.34063566967, 84784.66501178335, 38984.224225078855, 92774.41041819422, 39607.26381248199, 58613.880302854406, 43381.867161339105, 3479.2410844116216, 18857.688032550814, 33058.18490151441, 82679.8036438543, 14598.339618205047, 76126.50809847134, 44789.5718017929, 26442.0860015419, 73135.48676395687, 19930.150420022906, 88424.02411760166, 48050.68426266842, 26028.22079845657, 14988.420417076464, 34605.08332452429, 22920.404369100965, 99739.08721481606, 40547.414503003965, 52138.97302388521, 84508.25050908676, 47627.73214160099, 41223.81187195918, 13806.181623169967, 98139.91776386647, 55529.8439570097, 31482.876798383495, 89849.7005431454, 54418.67377911519, 72735.52287495999, 98854.03733354244, 1966.626709254038, 50125.074289844764, 41457.18020660434, 97194.67477333856, 93758.77209371557, 1377.0583017017611, 4005.235844314292, 61494.20152979966, 83729.40819634231, 87753.22030117134, 54651.16057565286, 48859.48679486619, 68682.90945597696, 49953.19550234627, 21983.379101772138, 41121.76678397669, 63959.92199640046, 71047.03947712596, 74690.82079178678, 10664.011888928504, 46531.11767222573, 88355.24309669544, 50020.52585893195, 9832.403941072187, 20227.940858508777, 50009.90444846105, 81409.63482263051, 42561.16844006035, 60055.798972812794, 59924.00850686882, 52781.74471290094, 19777.15555265247, 36233.74533950014, 1373.7794915190893, 62889.86362648008, 24627.69858813686, 58628.02327625882, 7219.115064187198, 7663.863751343636, 15150.147894447096, 60854.50508944299, 46915.84726895686, 88796.72226985548, 7892.450428648212, 13578.449692315864, 1253.3367530377925, 92941.09985543297, 82819.13382763688, 79605.8573875982, 61453.90332214131, 43297.92052743944, 14327.092702141652, 30725.463442023847, 50281.91539338871, 77719.68951840134, 27513.465872429522, 26140.149580431138, 20724.624477155685]} +{"id": 160, "vector": [53134.40992967704, 5104.48283090289, 16494.136343816645, 10882.345975663033, 87296.4940877093, 39522.4700302763, 37381.57669061732, 2716.168383534212, 58944.40302454493, 44659.00919695672, 77317.78089073428, 2653.578830404546, 6705.394243129436, 7094.704229129212, 25642.578260038972, 85203.69336479928, 92594.32006543776, 74173.87121772332, 88434.76208958609, 88054.19100246596, 40625.06513983536, 91418.03599981578, 75715.76498328341, 34727.751428930584, 74399.6580781568, 83484.72298371428, 30085.55124634299, 29688.94134739606, 54603.53800848021, 98196.59603231918, 58934.10688061037, 85604.23366778447, 40173.7113695124, 76484.75101722093, 66066.07220099092, 53249.36480793911, 97725.39524317259, 85251.48696540587, 47469.11595439627, 12623.928067453782, 37291.08572497843, 75709.1790978483, 1102.4796607255305, 7761.197748239734, 13219.088911972454, 51758.33111669133, 81038.11136283462, 69244.23476392559, 66873.46601645157, 95697.97247890089, 91576.17887457274, 36295.82957781553, 78511.62039916009, 8749.569846242011, 79278.20194787956, 16218.746543007122, 94449.37187086594, 50426.303424884114, 61035.80251608884, 36603.825567462445, 79011.06706284946, 84971.79798586499, 68700.51313211533, 9767.467131597074, 6843.2624325515, 36383.35468112581, 60873.76904654971, 26476.490851280454, 1853.9305997675503, 75529.57370752133, 17062.18249154634, 22993.181005394214, 31693.74183550169, 80787.11633653325, 88625.18835273097, 91624.95959396497, 99289.68933064844, 58790.139020067254, 20449.1464436266, 51583.87314090226, 87382.95273053633, 52735.274590111105, 73072.39477986447, 1878.016516737624, 66623.32592829212, 2671.2057173819635, 88581.9464465877, 46593.13170722851, 55421.302682683774, 24735.055796349505, 62234.65185754336, 45660.0665459885, 91229.45988644547, 38304.48564301564, 26906.97301941418, 44426.160768053465, 13120.038205989193, 78846.22612809586, 80086.13114374658, 4976.823655980045, 34403.200993616556, 38848.23724504531, 93509.2807221128, 72749.17949846742, 77935.09252933564, 4490.287802009451, 59409.610592230274, 64993.38624623224, 20563.697724980502, 23301.537186762478, 12683.169222666935, 81241.48328699986, 12478.975669267078, 61473.52634094263, 15389.019377627832, 35508.30175562384, 98694.56321222837, 3294.780634437533, 99795.57674745294, 80510.17817383823, 45456.67963179017, 54405.00444378169, 66064.22169678361, 13679.417229969493, 56174.433457759784, 709.3396200819724, 61195.470310596, 95954.55623982828]} +{"id": 906, "vector": [88016.71976639904, 27436.788209511076, 58262.054484733395, 18007.2171138009, 89480.42216593922, 34568.99062789584, 82296.63038210938, 51845.939873858326, 12176.791823681866, 96986.05173423831, 11554.051268781996, 14162.962376568621, 80738.45297399996, 57756.77364175803, 84078.6029646576, 25637.344828856014, 47102.290415027404, 1865.0144414006475, 57688.68109838226, 31740.2295352752, 78040.84510049973, 49709.57653142528, 27412.06778672508, 42506.58772999164, 35160.077638062474, 25646.66062504314, 53887.419219430456, 60464.98244510881, 43558.643529278765, 48107.69435387988, 19039.07669901297, 61903.54651485769, 61093.85824324898, 65890.17646322622, 64065.73543481459, 19237.90314287117, 72579.21266584453, 93101.1856922398, 30422.303319182854, 19636.88704008406, 24024.404051012127, 60538.423742781924, 86498.59278136717, 27659.657476372733, 47270.68986789186, 63757.98045613885, 89029.3321117093, 24203.68365205592, 22876.306581296, 37322.552997990766, 12990.668267939542, 86714.34123692763, 78214.08130310308, 1167.7386966944202, 35308.16576222722, 45524.85975593834, 98963.54953708497, 89725.73826699691, 74789.96493807853, 54465.3314027964, 70475.02784725543, 44956.41185068053, 77771.55413038991, 3639.5879014409884, 38546.044008815814, 37834.44530212307, 73816.84505463716, 26785.771345443874, 66230.9286813626, 20187.422140224196, 11125.528553183673, 35650.34112962217, 76276.89553336934, 40373.970869579236, 13757.544588519111, 32139.29177650471, 50138.829154625586, 61791.43221977, 87584.51268499345, 76421.99371365047, 19706.498081837974, 89002.93439209815, 94836.261067778, 74589.83259979174, 13825.140214251363, 53153.03071536504, 91219.94335554274, 87803.51289789277, 48128.85257913605, 92570.11581650336, 15717.694874865962, 13166.446880293302, 96215.83757938076, 27809.914106294476, 30707.824627524562, 86703.42825047906, 97189.37042845902, 73537.99587992141, 91953.8829277647, 20206.743202202702, 34211.59569796438, 74962.90851786971, 6074.058121097281, 41442.11829396629, 1802.0200541030151, 85680.1703763383, 96126.80723581968, 70521.56166282795, 99855.2836914799, 25820.341501111176, 92442.86995964126, 54756.21707785023, 21995.496227117394, 85231.09667536472, 32573.31704361619, 83194.42926968184, 19172.691018805344, 76582.13124519776, 49595.237276284395, 33964.44345894376, 27530.971224819477, 78287.65903144176, 89838.26331435786, 32407.392000169788, 72698.78719883217, 6227.100874317714, 64906.27596707517, 94371.694120972]} +{"id": 374, "vector": [21342.352251083008, 3434.111323013156, 9440.403229025651, 47993.13385835934, 2412.858143656682, 2152.1578252565955, 90103.47905988521, 86544.10496236016, 29289.076506612855, 5626.355906486524, 44603.22423844231, 77657.15465415262, 731.5223941884885, 36311.95504222138, 83474.25396655567, 85669.10572778477, 4331.594529802274, 58381.71826756333, 83748.02199080569, 84865.85133492737, 79277.8408693184, 88001.2672188657, 33209.91374272825, 90021.15220055012, 20314.77835124098, 35147.47069144683, 79011.62813488286, 74154.66495538187, 63062.83454310192, 79341.49121827284, 41713.61425444762, 58223.11797734585, 43858.98291221479, 11331.496452150248, 69961.74622252981, 87603.3006778137, 8877.76773700577, 93331.44201651726, 97645.14134614877, 16208.209288654485, 82392.12526617397, 76777.82198161175, 16401.712245230203, 24134.020315891135, 13990.37596510463, 20642.44251625428, 24298.3840969918, 12986.430209335864, 42140.037163362045, 88243.16100438575, 71560.392314323, 42243.947953590636, 58111.84307139896, 48521.36107880114, 18693.02365506794, 72724.68268277094, 43582.09064036953, 60428.4292828313, 55537.59011682715, 13159.198152032559, 68804.07303068771, 98653.88185380846, 88671.39455873327, 35600.474729123365, 42855.628385076685, 50510.96447270305, 74156.52697369327, 85600.19499790133, 99798.61089308906, 5934.15394983946, 625.0823224115965, 44378.57949721562, 19661.763941177247, 68031.43852990134, 54367.99767386678, 70257.29044361717, 15682.302298143914, 42656.3108903811, 32587.34515663102, 28870.959638787175, 1821.184830137479, 71822.815483808, 86510.32440424565, 28576.82073660962, 98944.25901206635, 81474.73895477249, 55342.55030343313, 95177.23984125812, 24668.18897652845, 59698.5922127232, 50547.09009965889, 85018.53761269058, 11984.10512434992, 59521.78230063073, 12139.132989549094, 64476.30083834358, 2342.0365368268526, 59102.72063038062, 49474.69745637959, 91376.4085136766, 21791.95597178364, 34856.53075549384, 25282.987727323558, 65163.79735760261, 88684.43507271148, 12304.714338601741, 72909.01116604173, 60436.35439349265, 12405.460525419787, 15432.799509705808, 14463.215191680567, 53801.207425791974, 8139.774639557673, 84164.67619338619, 59141.53409629221, 98185.94655619356, 52245.30460706377, 37175.18550989236, 84827.21192851188, 39241.913029783806, 42522.91185713382, 41581.512608748795, 65678.73160709391, 50004.30125938399, 72343.82537079282, 88664.95134154621, 93178.55975240601, 25753.277305663723]} +{"id": 1389, "vector": [95699.15591180848, 94470.22212940533, 75551.58678693473, 96849.51213467661, 77943.8057569401, 46891.20803571188, 20534.68949641899, 78022.49562111431, 6681.575967735631, 43873.25457482249, 3497.4272373475833, 43214.973428682904, 79045.59232297876, 83980.16306292973, 44690.021500091156, 49063.20530922405, 17764.580082122415, 34824.15313525769, 54540.79436365455, 98824.30767446976, 16856.95743623348, 16607.372120820506, 71154.67428609238, 29179.774342412657, 56111.70762307255, 83946.43848946632, 60848.015785944284, 32794.71765408135, 60374.55138316928, 80641.83538697474, 4064.120322849929, 4220.430152094834, 46477.63420878301, 9933.264405064101, 40765.19629181132, 61083.58767139006, 3650.895258604647, 61377.65457756138, 98309.45695049381, 20641.138269796356, 66062.52312868892, 47481.203727621665, 28755.217203559445, 2111.162803050304, 79988.26536072057, 70822.53397149226, 73381.6242716318, 49303.5792208687, 33240.769110419955, 53341.2099306974, 11031.327589444718, 45.18272182405525, 49539.03368365158, 12996.139075059777, 10412.042327611782, 16468.643806212734, 39254.19962408222, 69180.93762540695, 84270.16602216239, 98496.01310816438, 15173.411187384978, 53539.69490884001, 33706.021114209005, 90952.48225104377, 92541.8853212381, 41486.386669214684, 72646.01725876705, 57759.45150256006, 49636.40424560582, 67724.4701821063, 14940.34908464864, 72940.89890424855, 17906.39470673778, 87831.98770716097, 87989.28289713392, 70579.37424205078, 93523.06395485027, 57984.658791295755, 27967.414934018896, 88776.25586718424, 39336.80138603045, 43776.57941590942, 74673.49798657035, 45670.07461911246, 7174.128951087544, 6375.45151438077, 64594.79781348212, 52145.07580307075, 98819.21463959308, 65884.9634067738, 19453.420639492568, 96903.2756218309, 69694.87885972786, 52657.08488428666, 85771.48815376798, 42132.73812416128, 65167.87825210235, 2261.024305978543, 54078.81337001882, 38147.68771571672, 37533.72403433567, 86056.54839408502, 86445.07417218098, 43742.94918559194, 96861.16991271594, 23686.68926097833, 59991.69648863374, 51920.87222640761, 54592.21948637525, 86680.64608939785, 47820.011303698484, 25315.742192818747, 48689.49168213235, 88502.9302052118, 51141.366838488546, 74012.56306786987, 2024.292327325461, 41654.80319141368, 16768.246057592096, 11545.151250257224, 1965.62390307784, 66682.49333582164, 3823.206787346434, 3314.677717801584, 45782.20584527372, 90202.90988641042, 67786.07792478887, 95878.28837722947]} +{"id": 824, "vector": [18195.25990166393, 61088.16132459695, 36860.111592498724, 12121.07392400591, 64047.87359760988, 48189.39143028651, 25593.480198583962, 70448.30790758657, 94892.18962016913, 31580.809806673482, 18317.745991844637, 86094.00069813321, 94197.31660124994, 21057.666227124682, 50802.01748193649, 13508.235233474508, 9547.0634180729, 42912.94488183771, 85495.10709157353, 68042.74775994723, 35206.15907705845, 25189.052769616505, 836.2738771703348, 91284.87616590124, 9528.080029335728, 94013.73400424325, 14929.447562164854, 43223.47821168198, 54919.32569964028, 39454.49087079288, 71319.03495883496, 48546.285074092906, 49209.15111038845, 5698.697162358979, 90283.36567197826, 34749.0412835495, 7605.853487692549, 18242.47840049076, 33071.21687369094, 69615.7069421537, 23486.47003908614, 18976.99966431977, 66906.40884835673, 97745.10077626845, 52754.676103017104, 7519.852699523877, 30596.9488879002, 29545.402294154766, 77102.99685975787, 39051.86961572811, 14190.56841941515, 92252.38128959364, 97840.06040202077, 59149.676600151324, 93703.88811489311, 21966.771765047353, 46641.54081304787, 39910.163601765635, 69101.87861873963, 61575.01623517301, 77593.05069990305, 41549.09104632063, 94353.52846203386, 38849.14681997641, 65300.839822694164, 5135.3598668806735, 72190.38274770795, 17127.854260922548, 57418.2842439082, 76329.65768372819, 88625.26688604934, 90082.86728241463, 31513.92891278624, 45039.883122081184, 42622.419650194875, 35787.96848818198, 16700.641965724317, 2833.4898513125318, 11306.254633515666, 32544.626850174962, 28233.929758934373, 27257.95861025193, 55151.5572535026, 61881.74216886455, 26374.604776656684, 69156.22843258733, 85779.14348455609, 61450.35710036482, 41014.80503399448, 15179.012453954876, 93025.82943871903, 44347.3669712756, 90524.81473906824, 69655.40898617206, 11136.77427667622, 45993.631695998316, 37307.81512672948, 52258.29471514106, 74399.27977633243, 1390.3886828637412, 71947.91527072941, 66543.52031114498, 91841.30259385551, 34362.166131293714, 24406.212229382218, 74756.17189538834, 90162.16447801246, 59593.89293611225, 41069.101707763286, 27734.555020059193, 77802.56657027412, 10385.432903463987, 77389.8299567893, 16412.34699337496, 56458.668294796335, 67570.42525714068, 70968.20881670175, 95611.69540355777, 43139.67736171391, 63908.98088337752, 99296.98986639091, 93062.31848746205, 2801.562468698715, 93030.69476975696, 82588.60361454694, 53519.495276176145, 82207.40995894131, 43850.71318499255]} +{"id": 232, "vector": [95597.91693309988, 29768.775591135778, 15352.133057276918, 64084.228315258675, 43096.893783320724, 19732.511631443238, 79676.65995382752, 20552.073491129842, 35846.23753695239, 93895.31901465383, 49771.61251898552, 40847.664192705415, 92856.12220119512, 58227.48292059233, 99406.48003079287, 46166.80500697664, 29913.58625605499, 96277.55422887053, 79375.89762257256, 68994.70772594732, 70933.39841732624, 20105.58245043782, 10278.827995096373, 51820.891252604495, 21147.326941950927, 8773.5462106997, 66504.38037161499, 40255.652427171895, 14496.279358005348, 39450.01480461393, 24979.521166072816, 39810.24342205835, 17507.618734468655, 96978.57273537517, 37514.89183211306, 72807.19065522196, 61906.65809409848, 9469.252535102212, 83805.42974782671, 5672.379747395195, 95129.28931877723, 36377.49786044824, 7376.923317282746, 62428.607045491124, 63621.955658486964, 9121.586635168433, 9610.82167750762, 60107.64482625807, 21384.670378843395, 18467.550809963894, 21834.80494262836, 95893.597468903, 91018.79951003173, 82520.65787130242, 12270.616500150532, 67973.15606601744, 89392.40047025094, 60047.91381240459, 40659.68953508432, 72286.06605221021, 34430.34219733367, 90524.92290629649, 30900.89733213727, 61304.32560837575, 53034.48141395337, 83920.38048192816, 39075.06002038465, 27066.106509551057, 8105.6963854478245, 50867.64309022045, 67640.40018946456, 71933.03479590235, 90345.825254323, 80478.63707718923, 45248.86202057225, 38050.9220197289, 65846.46443575727, 97257.26083264951, 2759.802033260017, 62094.9747352444, 72482.49786628083, 67585.97146722561, 6177.265094915274, 50817.2316752072, 98370.24251652203, 46174.540089710405, 99766.78166443818, 75600.04114610354, 8388.16634775199, 46337.605557851515, 57678.98825336172, 89497.70752271723, 90565.00650815878, 15498.74114251021, 96685.71389840955, 52816.721895216804, 24996.256829893617, 85474.47842931551, 47474.42872622374, 29054.954060561133, 16039.79162436815, 40066.37709269888, 28380.155173161125, 82977.57426081167, 36564.69534751547, 57550.694629175916, 56656.53433840783, 92024.74963587169, 41816.27381623356, 70163.78111827005, 24741.758830980933, 49028.34865879001, 77248.87496902636, 81658.81390504495, 33370.63665556061, 39288.69808211367, 58260.53283955126, 29822.618050979432, 82380.76356017773, 69951.72616797408, 3792.6161610307927, 15530.556512238447, 77339.99524844124, 1065.436998341207, 95857.76049131673, 80536.61414230897, 96129.07568761753, 60454.59959262829]} +{"id": 1990, "vector": [58357.68592846522, 8740.107817860555, 96269.85479490556, 20078.77642523882, 88222.86483255035, 97597.35099333599, 3378.194844110727, 89925.94134160539, 98514.4305573549, 95467.798668783, 3183.290305910058, 27353.915241052895, 44064.6183857366, 35776.75749250322, 32428.54438052124, 68286.30466653072, 90924.69866229799, 49631.669932891156, 61034.03553533102, 90977.6147005288, 20952.548938849526, 82062.85690610891, 58698.291580151395, 74869.6869098164, 24048.23211832152, 32066.52203960144, 21448.414575760955, 33541.509955124835, 39240.77959566048, 9065.97425679826, 3038.6904533846405, 52863.91017295799, 8204.522848010565, 98001.41929026168, 97914.65520576025, 50941.518772488045, 39579.42970190895, 80522.26486378671, 3467.141523960315, 76480.18642397315, 87275.80656705123, 10118.497989452257, 37030.21569970794, 3280.694999672351, 45687.698991550475, 39276.18534080266, 93746.16027717789, 93345.22836355021, 94577.89702334268, 68076.30093968743, 55239.956436991844, 43587.09168687989, 85357.9812851162, 59039.076876792715, 71022.31619725194, 64477.25680716666, 87833.9798068876, 66906.71092826403, 47541.12573482693, 81132.42815539223, 46413.16962281068, 59988.594276056465, 12105.741743850373, 22990.43387786951, 62481.9801204221, 50397.56169450881, 79827.23039906561, 6892.515219268258, 41284.19761078811, 32693.224142838495, 77612.55789765478, 62990.93000478757, 95359.4751864261, 19175.028741328017, 19547.198992510806, 76170.57286539306, 69120.46860214014, 79595.23339724082, 74583.06911670958, 76373.94820443646, 2282.628410501386, 69135.67401831699, 19276.502956206998, 36497.74848821455, 62286.714585231595, 9106.18311232222, 58535.422669190186, 45570.96850355995, 31415.933549647558, 876.1244182654449, 99109.89974329603, 62786.5783059963, 62427.49480202613, 49667.12236533405, 6772.163076171778, 97386.45943058371, 14191.340699171717, 12117.49283468242, 98044.58250614665, 75949.11062081525, 77074.83446263906, 83438.38775534547, 2397.4795931879457, 91535.17242765827, 74638.94588794488, 78376.64970065636, 88659.3875050988, 88350.9603532122, 47755.759291398914, 64276.150225128236, 93508.55000390514, 68774.45439180051, 24310.27864167582, 89472.46354778737, 21171.234742009594, 31739.804126712123, 67597.49754490657, 68874.75218143078, 90207.14490355014, 15228.267049218424, 2425.3353368482667, 63265.571197427074, 13449.702276657017, 22589.1472485906, 87180.29703586224, 44024.69013231962, 64262.17836939343, 80520.7059516127]} +{"id": 228, "vector": [69814.74423384662, 67506.74091635145, 685.4555163691822, 67169.62703056009, 41969.14058668315, 7668.412191035523, 74539.5395143709, 47045.652431215116, 77264.23209688977, 35003.41426972547, 66689.74427969338, 38758.30049555804, 30784.344285217736, 96770.67337702318, 81457.50932697431, 91290.76168809157, 84377.78424218806, 21027.058318314317, 5748.258046094401, 27727.76400071646, 22153.248422255743, 40511.3459471291, 92039.63197538449, 44183.77574903094, 7787.494399922279, 74066.62106324044, 89308.49219949808, 67664.74863881421, 33437.59089573105, 65272.9709646577, 40668.38605213541, 91107.1785707501, 23078.272252122566, 95442.57780909394, 15072.171620921792, 87443.36530172708, 57324.44622524023, 31585.86551574124, 65072.380700202535, 25715.3142318719, 49909.13113062843, 22088.90069613868, 20036.62258067429, 30128.848538543774, 83802.11677710361, 77367.09305137054, 42319.23186192857, 18573.23385412102, 61082.671839951865, 95418.34153236111, 53602.600012521485, 27639.560397938778, 71275.27855003846, 23623.199370359493, 49015.11081752871, 80376.96149738565, 8459.072149489333, 25094.860847242362, 35266.85430877331, 55416.58188603808, 80514.79658745496, 21701.723628256954, 46093.25122435336, 46419.81832272822, 18981.92101422278, 48925.51165025225, 26167.413969746845, 86301.26646566535, 68491.12958802219, 66770.7459755389, 50105.34811288489, 48045.31486552786, 1220.2330855680898, 55859.959542045544, 24256.07527100464, 79734.04987877744, 1951.6906193770667, 43582.374151037606, 45096.09687306678, 46938.06109075916, 47266.95243391863, 19217.551408564646, 33556.954231281896, 23412.34707679547, 97741.02933853671, 10004.573909033787, 44044.55631695744, 81121.09048055176, 83364.33768470968, 44361.6780771942, 63517.20302698214, 64991.66413362324, 22031.126451911976, 64564.82680942498, 62635.56563772523, 27637.326108946225, 45988.18044262214, 415.618519178973, 2593.840852392482, 8257.937351532373, 70096.75119544017, 41123.29339610686, 21573.875633468255, 7667.378007911297, 81103.65187138741, 43638.09902880804, 94233.96215527112, 91374.83184201019, 24478.769291433055, 41885.93398282963, 59089.19377315329, 36105.314354556525, 34212.87516027165, 15388.589387158547, 16512.502903994642, 13306.477207960166, 85235.61485696524, 8915.135779818938, 94324.46986715452, 34811.528661117954, 9662.879398185243, 65013.95124299454, 98759.46407107759, 69192.68284552387, 20699.869131069005, 23348.110289780154, 54665.602898757315, 8617.090241215898]} +{"id": 318, "vector": [29347.91970463725, 35095.71129114549, 57830.26686694096, 46812.35155090814, 66449.53131983508, 31399.60817075025, 28219.80956247575, 20747.39664096451, 6432.289643336231, 80923.53757941369, 42412.44827053009, 42502.070238127235, 55530.00379115878, 97374.63187068995, 73993.74502798793, 80551.41766393984, 29663.73410295683, 14642.161066262282, 71263.9861613949, 61532.16071983743, 64382.798815989074, 70276.71106555882, 40794.224522629665, 65208.95802746669, 38915.61839799257, 77879.54281415444, 53359.21145402524, 39386.402291349, 25880.969508030437, 84591.99575444583, 88564.3186108406, 9638.180530398387, 35719.1830410578, 5261.2823131715895, 53708.32729001362, 668.571137799745, 12976.69628851249, 43142.38754168309, 73391.1282268108, 48636.98494684844, 20074.950011670324, 60659.68989817796, 73508.7795870518, 86212.67053944485, 61685.63720234943, 62344.555931910276, 9248.174169107371, 24690.916270003625, 61319.66623151297, 76726.97183376827, 55300.729497558365, 77503.81497820019, 13675.080307264887, 46052.19349338463, 51083.20243438176, 94860.70030009538, 583.8559443046232, 94151.27197807084, 17454.941034561554, 62970.407864194836, 2672.0006285676945, 18093.438067473246, 47357.4613253622, 69417.60379952924, 54323.51377981883, 15581.80872631696, 97167.79794732945, 3053.7424209319374, 22958.534387869535, 4310.09662564219, 59300.42826629658, 52098.924034900774, 75835.35558053048, 36195.47401842786, 38036.71970964918, 55440.508746092535, 26636.96689500802, 28622.323477870526, 92158.0756683253, 69545.1424543752, 54149.167466920975, 37358.678470643834, 12292.441684878164, 21979.487988258305, 62953.55743094698, 72964.99830852277, 84918.3631171317, 87961.59523542931, 93449.97343315843, 62802.7074753597, 16182.46245431242, 80390.10495166574, 91576.23202647059, 54161.1739586962, 53361.216888055766, 68309.2413874676, 45336.62678400734, 80299.07546253527, 95128.86602117952, 68653.46690954831, 27334.43397840827, 46137.869665956874, 8.322177297104272, 5216.83016899005, 39431.43801466568, 92951.2571809046, 23752.667538533966, 51301.7405575742, 18872.22599308249, 7459.928817937555, 17768.19189329952, 94103.12487174149, 16909.255795030163, 41241.28925133781, 6199.791650528863, 11128.472783454601, 90713.70105766764, 94351.80223787246, 61033.75075192219, 40322.81689753154, 56383.20982779732, 58730.185825832436, 17145.891326624507, 62165.403526490096, 86498.0454924217, 65246.00492717335, 78390.83878525805, 26257.761464738414]} +{"id": 2007, "vector": [77692.16494266147, 77306.835059311, 5913.748919750372, 89117.14261990947, 77819.18699527581, 87780.89278747767, 60221.31196153261, 79287.19827517447, 15187.01379143732, 79917.5516142202, 1080.149336122327, 46460.50108537102, 30893.093786140467, 65676.82502512968, 10921.714742978495, 91476.73421069022, 41245.84017413105, 72230.06837499056, 99716.9883920113, 19405.952812787997, 54492.6929207191, 23864.645296447106, 23895.673912027483, 95858.58302288786, 66170.00002680876, 33538.71016725868, 95762.72089994978, 41494.3074223469, 52529.30171499209, 50467.07535023645, 6600.696557081087, 9938.899077034746, 8140.938946320786, 71392.42079923334, 76474.61645237534, 15950.255950875237, 88333.10869703705, 20417.66012745003, 15499.924291713785, 3055.904403013099, 77252.29829654862, 68036.47001663517, 76048.61691177377, 26549.045859718524, 28949.21410993606, 17487.436085264162, 59636.77087757165, 19749.293196825078, 66386.6944372991, 81049.5889053514, 85250.99035993291, 52727.14602363742, 34975.791585339175, 23505.269062243715, 78605.65349474459, 78207.6806172754, 50083.053238948436, 53420.881651425734, 43175.37766241253, 57462.01711652207, 98935.86049714076, 53776.691069051565, 4470.12455886977, 55248.587042766565, 37090.171167674634, 66376.19517446142, 34993.92586171228, 96612.68893244553, 16479.83183767242, 19496.385254897774, 63331.39775553741, 79761.56338298073, 54683.70462889639, 5508.719271723295, 24081.008663255023, 98370.11783359208, 35307.24273893997, 91556.99223626236, 12609.922535004036, 20049.95277245801, 12480.8170604921, 66274.50973437862, 41224.105325586854, 47438.55811052603, 99414.94188668419, 77643.30323917652, 58922.25635718527, 52271.211106386196, 32721.737456990384, 4582.192430113075, 29673.562357831117, 85157.81216736467, 29372.665015728384, 46737.42815281052, 13139.612968133397, 7495.5774800301915, 35287.094329426836, 24056.473478430307, 10060.755300204915, 18919.160489169684, 49872.01102369352, 30980.832975619844, 14959.671236564509, 79063.04497198974, 91817.02259694155, 8566.943594810238, 79420.17359905317, 37831.063064494876, 67293.14673786539, 5317.183967153771, 83065.562933449, 98072.38821928613, 63128.14154357752, 41401.98342849515, 86146.36907103703, 85805.8199639052, 12510.704039776465, 84134.28272460584, 20414.421209237855, 80769.55574429124, 26854.214937019227, 81139.78092372982, 54952.174063626546, 98225.04094093294, 68026.85430411492, 48508.16178656455, 84600.2023684345, 74645.48994897815]} +{"id": 1394, "vector": [23970.097128259393, 15777.749496082404, 80744.5965376259, 33233.052196623634, 54020.17657270439, 38397.14210234626, 21372.569283005505, 78094.88731794384, 36761.45953629936, 7676.52984389654, 55991.31194952709, 45261.04043131493, 86160.83713752935, 74441.33357724774, 76941.42515465936, 72151.12419693233, 69232.73797047834, 49384.04664734058, 40320.75249961039, 79530.65031623557, 86561.94300488221, 3033.3392494684585, 38578.967811701084, 17738.165200351865, 86072.86361020555, 63917.61274297051, 69622.41424113537, 9347.148567191954, 78901.68230442236, 5427.479983237371, 55582.19977652778, 41544.696624546654, 67543.68725471494, 14842.2839379592, 89421.46165756069, 40477.47185659468, 57629.012714360186, 38635.18290336313, 93202.31576349417, 94792.77252185144, 2224.6154674148142, 25829.929432306697, 64426.95298320248, 63934.2235392766, 32296.548004841086, 55191.51672777425, 80813.50663161758, 15786.264115342774, 1396.8167311615098, 91110.08659154235, 29583.32861835622, 22106.17615616801, 24622.489228163813, 56290.65192513464, 62990.592314264395, 69765.44726535423, 51300.85257944492, 61126.77322594473, 12534.214117276033, 20834.72818438711, 90624.29069494066, 51638.046084518974, 48785.66800205475, 62501.805277711566, 94196.77979413648, 11017.585312734802, 11021.264369262828, 45244.48425097215, 74384.17857781614, 2566.2108513796798, 91862.42965634387, 7353.553024535431, 7864.015860740792, 94783.72207253145, 85406.2174394704, 77940.1565900356, 35112.31594540272, 19932.79631120236, 95784.28727770048, 19844.75714827667, 76993.55054907144, 26560.0635010677, 63580.89825881306, 41540.23365791796, 78015.06137704669, 81501.60874397882, 10979.67114929438, 36846.567386710725, 7129.367355771288, 16008.303804494573, 41341.36800156403, 75594.55560578709, 49933.143461509935, 27447.623360534824, 1223.0126899968275, 84395.42251078025, 64454.62737866395, 45001.87984837649, 22753.64157269708, 5259.74341809723, 17804.008006831064, 59879.72018500471, 70301.33355832493, 83932.4189293768, 26322.48514543287, 91749.76323014016, 35008.62673404349, 37076.43357741806, 89502.05496029506, 70197.70641217519, 76254.71091426419, 2747.4121604219026, 1397.6941507503948, 85277.31149094646, 81306.86655387843, 78675.99873482798, 34400.203105876135, 15892.095096137782, 20307.93304415125, 5565.082351702966, 39791.89972589436, 20679.119120669708, 74212.07201643054, 42917.352871684656, 58534.911740617914, 89330.44884800317, 31785.167147122982, 18536.678352719748]} +{"id": 1424, "vector": [41142.03254292625, 38483.85083197605, 20190.896046970673, 1480.3427345807972, 7834.175950283473, 8754.554798737956, 92106.13603660956, 7453.991423263528, 5308.441147041732, 14010.268048055219, 93169.28439591378, 28529.445011078347, 44128.869479575114, 76714.63961902885, 72818.63847281864, 88395.89366040418, 37745.36679048928, 81559.1058603126, 60931.818278096674, 239.41308649634286, 74351.78764390651, 22497.905692682118, 61302.22513822941, 5382.103759714585, 65079.91865741983, 66630.71292169082, 96834.90026342476, 25497.7010006995, 30592.79028163997, 93699.80827895326, 33802.984242838706, 73045.31676077022, 69746.4801622551, 97155.21942431855, 4523.140054791875, 22100.74774634918, 54123.96844922352, 92015.74759609048, 71355.25530835713, 29611.598696456676, 50474.42105271087, 20445.35715135315, 78542.13783489614, 9107.17014703315, 49932.042395672135, 19947.870994413053, 55468.81774550836, 54837.91429503201, 98701.72503965316, 92129.17180639283, 84978.12797347065, 84447.55959122414, 62729.77270296686, 28587.916435648287, 80045.36534610181, 68501.1844892598, 23159.793759184166, 45521.74688492706, 35498.4540788401, 18477.14534884569, 1432.070333756008, 65174.96795007856, 54795.5079158989, 49949.771062017986, 11866.576297196307, 79658.59380951484, 14883.438589529685, 27611.55933173265, 51091.62624009075, 90471.20828685735, 43122.87614454275, 34726.25929796808, 83240.39245310008, 46775.106800582624, 73871.46805313023, 84320.78227353208, 77437.46958715728, 99537.03897097694, 63147.46577525314, 48563.42900058699, 31310.640780379108, 4888.762583071948, 80946.16537146286, 67699.51515307608, 9345.908607791265, 87925.15287290889, 14523.792917907696, 73100.87653953116, 22397.484836341795, 48549.215714693804, 37108.58689949583, 42800.48992036005, 30582.37753657038, 78047.33045677705, 47608.42535518182, 7183.245343581079, 37748.66872223227, 46994.938996411576, 2839.1243176769553, 21329.44253773201, 77458.20344362014, 6677.457614103178, 39767.4275177197, 16030.909037672569, 38908.541147633914, 90096.36346387543, 21174.85615501773, 86417.10182350283, 69914.0282026905, 82450.67057740751, 95630.17155319496, 85851.51750013801, 11009.588114440905, 47281.54389858981, 91770.05755059174, 42852.096729359044, 30371.396466091905, 69885.93420448968, 12026.191531234053, 64281.59979053198, 67901.43973930056, 40180.04425317773, 32765.26255061757, 47500.055677216165, 46170.54717402268, 40622.2943237632, 88080.72100413515, 25498.714204261585]} +{"id": 775, "vector": [47598.21542540879, 39626.52869151566, 52008.33965119738, 70727.52799226838, 13396.719370704246, 15325.235407491833, 78317.54029634013, 74752.1570528612, 40038.19755923067, 35514.81456208638, 20399.81712207637, 43561.33601209058, 49932.08278362352, 16895.15935521916, 27042.463585839017, 64677.1459711287, 88086.09790989269, 8515.797006190973, 30043.04110393129, 5148.3013232882295, 6466.694169762288, 99863.04995579257, 1618.8925552356404, 42468.34699095453, 88595.82047607678, 98500.83560647011, 96335.11892316682, 57847.551880383406, 81242.11471861637, 53207.921890829726, 17017.22185332365, 86397.99348803384, 43749.50449676959, 45795.07834516363, 42859.54736632112, 81971.33609670174, 7607.062854606305, 76197.78041493054, 2486.0858062993652, 10112.197714299542, 72905.34392280669, 10072.385027727149, 8621.599158486815, 78559.36750803115, 88998.69722614005, 88430.00713901779, 52878.862784970996, 68107.93669394968, 11676.967371242508, 79009.99173226474, 18699.524948227976, 33016.77181213661, 92674.11113662123, 18751.53812659961, 17354.50822336543, 32805.15237261231, 43008.57242130165, 54711.5541560517, 55058.78044027893, 28220.388820973276, 79847.13881209918, 33145.8521058449, 63899.12373609543, 11000.617738685669, 58971.32265593287, 47491.40422832825, 7731.915802969335, 76411.61523819056, 35418.090928834325, 142.79561327175117, 95771.57416142989, 11760.593333915936, 16968.309358046117, 5757.033432540071, 77640.56686870178, 97275.24209416361, 12620.762262005303, 79647.45490679394, 32562.96533593369, 19166.713303918335, 62544.41427660747, 72907.20918119993, 22704.16283932398, 41296.435906568775, 756.4912856604766, 56325.3961823309, 45156.62678359337, 66979.84439301952, 19783.726717080765, 96919.89522060195, 22713.351521537927, 151.94406516582103, 47105.8352328358, 61854.55212727782, 22792.43084894499, 13648.886930665205, 4590.483432059944, 56946.454024641134, 86576.73725728849, 74989.67086388325, 67702.16502379204, 37654.55470371104, 96961.6006690334, 30661.1861831245, 31346.63827344868, 3740.8217597995176, 63442.12414340245, 27817.99419584321, 43650.831448598015, 67142.14460112943, 55392.407047204186, 70013.62701612095, 28086.197495890065, 96183.18679436181, 75855.29267680152, 8333.295734704427, 33959.15818145317, 29489.517197463843, 47450.53214740067, 87804.37914992984, 12950.358761747382, 50164.83421373124, 10483.158060477337, 19327.37020435519, 36526.389133892786, 18514.881491923596, 88972.217019913, 52936.534125174505]} +{"id": 239, "vector": [12821.306000909848, 30335.58573270785, 56599.93895243325, 18993.181677958604, 9366.037725833608, 10111.92321937845, 4702.201847522314, 59329.73778448869, 27577.708830066505, 11413.263130704787, 60631.50003869061, 6911.503474375325, 18182.12720340082, 30676.58435933377, 26994.204202853944, 39454.430044204455, 72419.35422856736, 71973.4702534437, 63320.59399996992, 30357.429464894016, 55584.63728889717, 8899.453193975081, 92773.03606507389, 31400.669307609784, 80580.76577925675, 42163.95436705776, 86422.33538557844, 47614.38465560446, 80556.11363221948, 63424.06543361977, 24246.52662535317, 36658.91578403056, 89989.31520832371, 6639.6521822506775, 92039.29228176357, 40388.691329485046, 21447.559141935257, 96434.97301700922, 94178.40196568322, 43162.867372058514, 20628.196035594516, 43338.816575026736, 74199.46826981303, 72523.11309156376, 55892.72698688025, 34495.59853496229, 3605.9086488672397, 77327.97206209075, 10510.384162636432, 44561.04335001212, 44690.06491131382, 22128.95944759573, 70242.12324069405, 39028.26634403186, 57319.11725795129, 87655.76911342912, 50633.571883974815, 33201.09451770235, 96673.114596163, 64525.16183542092, 64051.271892690485, 54548.231168073835, 99820.13189902884, 27273.030432861022, 71922.69775844344, 20099.70504123867, 79361.92527521244, 46327.04222033131, 46149.25020090752, 59236.31637330249, 96838.68597116775, 60648.46878101847, 74006.29448290057, 31573.38441263199, 91672.73307617653, 34649.6773778479, 57529.89325893925, 35260.81695621607, 52476.594449936696, 83851.89673627957, 27477.81407950616, 66789.74802247089, 1825.979046810855, 26205.14232876201, 56354.36601042356, 56385.768945711534, 32734.945053206986, 96454.15503760845, 52768.05688802897, 53652.144593714635, 2161.6399621995465, 68039.30203296289, 97219.69689332652, 81673.46604680395, 18087.318217476124, 71392.60212386103, 1969.632637207086, 89429.15646256346, 62442.251099271794, 1014.1422114601118, 43491.728402985755, 93292.55742595166, 90222.98390464947, 1613.6110332662822, 4350.687929334729, 19962.159453874083, 20677.327217853293, 45387.52244969888, 70609.64364202633, 67647.65997048105, 36819.16553659478, 33412.512107090144, 29112.75637451306, 66856.93462214108, 11907.32916619912, 43747.06446198315, 27479.488827738187, 6165.238905098114, 18428.081021703725, 40833.10338817401, 87035.48817338885, 40991.72654906454, 2033.338075883473, 39760.00549716774, 66650.2145084263, 67102.11691117613, 92945.78094788552, 44085.33582346468]} +{"id": 440, "vector": [38701.29683041607, 90015.99188056382, 88483.73412310531, 2360.9009288554807, 59180.1931208578, 75773.00508767091, 85967.35376200962, 80115.43429964897, 41711.77792761862, 40581.58148482175, 75488.88477037406, 77914.58894164354, 12354.083058673405, 68508.67505163631, 50935.437388435326, 87623.93811612249, 16163.488860402696, 87927.86677075762, 40625.15969121463, 96998.3368899675, 72115.84595842694, 67839.24014420752, 24355.625297906892, 88774.70320307743, 12921.915542105444, 19808.021613402303, 61486.16946509395, 54729.80994131343, 4768.436139136989, 3163.219050828403, 70929.89275139435, 46548.836049488076, 46521.88176568637, 82167.7707653373, 43184.29634631034, 64679.0474710099, 87012.143219639, 11569.134861777553, 83522.14569957154, 86641.21720786855, 40022.134685776015, 23452.952509693892, 63301.544008956655, 29708.00865846365, 64328.502933207834, 55451.78721415309, 76655.83385887579, 93486.8128084629, 10247.011609080591, 68331.43532463642, 59956.30578888253, 29865.957715435456, 22594.941502389032, 99369.3063322215, 10378.978433863473, 43104.004577530904, 58034.12807034443, 195.60178682412888, 15373.524310822795, 4181.7989948951135, 59162.24155234018, 27364.274504693385, 61241.92325748033, 13110.032366032598, 82646.57798506308, 81160.70954652783, 40151.15006931973, 93949.73747593882, 29920.21876015376, 49862.33527323919, 95141.47936851, 54345.5800242431, 58941.6886973645, 4142.02070625711, 45866.42624057494, 59080.68283188108, 65025.3378866064, 80618.129041337, 77578.59624414275, 69093.79204043544, 23318.976461541406, 99915.2200900607, 72444.55587569675, 87080.34033522474, 54073.22710079846, 66842.60761587005, 30762.09317371169, 23264.64044013824, 8450.161391343203, 63492.82364478912, 96885.79423406081, 48394.71557037505, 75246.27660449538, 44788.535703082496, 31041.01371151653, 24249.391603189186, 4345.243809861987, 85628.70591041364, 2150.951245742372, 93292.56459101081, 3375.5420486283883, 53141.756574536215, 56912.07904569758, 27717.490574845084, 68612.9448873245, 46875.225522742716, 68378.30090179645, 43289.3315793449, 13376.71335749685, 65004.8311841795, 75307.56951993231, 54778.96634066396, 11309.64848854471, 11587.160002094244, 25117.71622931921, 32501.601870732433, 86956.78513738346, 16493.663715173436, 75698.03713095227, 23739.88133916265, 96954.10464402384, 82195.96787852596, 84014.19118436768, 22622.079800644868, 6542.81474974614, 3707.6846250085337, 30018.1408944009, 10127.558159245353]} +{"id": 288, "vector": [17701.25473901176, 16478.15188535564, 15530.03312588942, 79853.0338092488, 47078.17437316201, 79119.6146044207, 60451.039957930174, 30610.478512543203, 75413.6387218514, 95575.63215926118, 93193.63853180081, 50637.47572416505, 6557.461410607423, 83759.0779157722, 51585.16362982086, 7613.728420423893, 27106.254627057104, 14444.9281439603, 444.05536957637935, 58434.341813479674, 53980.80136081197, 24284.065237790266, 892.9865730692654, 3621.1946398831074, 13643.135451529786, 96585.15673740859, 57398.10938335349, 25481.355096116476, 67842.25867838021, 9885.747809614808, 45930.31435035501, 68877.23011308131, 36711.44289871808, 45387.79512191835, 62480.92370679606, 22666.34583528283, 64234.621340015365, 933.4278051916444, 25978.8015334948, 83842.53208697689, 27654.86058061477, 90080.45989764338, 9795.729809403741, 36774.537423101414, 27081.43019268442, 93534.83165855556, 90161.47514903302, 3976.169023853626, 16664.16363526887, 66034.17706966863, 43265.64895103963, 84708.69763020004, 51467.476506800595, 84726.31577870675, 35561.040226269535, 89853.47527425826, 92804.22265603737, 81798.48833131685, 61795.536687060696, 27374.49300518969, 65691.3424103759, 98539.82526689599, 58061.03832862408, 39577.909369203226, 36630.912852577, 24402.82929448676, 40777.66985967377, 99488.8677018002, 40329.07944110172, 66878.89352444927, 80186.38847578617, 48031.092142649126, 76167.5868661781, 56600.720312612706, 17367.99247561259, 73470.21670664797, 49090.15916038601, 75835.98446949506, 24032.1788819555, 11385.55017500379, 3161.1046143188883, 44648.66839723219, 47928.610250902704, 56053.92145571175, 21317.032358424804, 3057.7110646123783, 13486.463112629588, 76441.8046405399, 65047.25707119896, 38519.06543449774, 68625.34682652188, 22214.83079691313, 25821.5311581251, 3006.4970609997245, 11463.35961149162, 63097.167638874365, 58429.74409730175, 41495.90152967007, 42151.32492341722, 98649.3826579246, 80466.75375979068, 50326.062531132884, 85967.71943055117, 32165.47746687606, 47097.64242967328, 83001.55015036598, 43827.576377706355, 65626.55029280773, 58003.73044908948, 83161.95740474934, 4923.928207900163, 77983.87802378804, 15902.338841912011, 79125.3724358809, 3918.1071662670374, 17974.464200864404, 85009.98134196717, 77407.1479736469, 34035.86447986969, 99303.75118717956, 6463.700912942716, 15683.177336076526, 47151.19238323933, 35820.87892778334, 97569.63676232022, 96195.62546852611, 18418.904426471396, 4904.791696495814]} +{"id": 1340, "vector": [456.97956530277304, 96756.7482400255, 90013.38829658364, 54676.00624064556, 20911.801480231683, 80282.94897405225, 64480.72074826553, 12554.697007608196, 76644.24780163629, 96967.27262938773, 99971.3549631606, 31481.859220946717, 70568.18823516314, 51124.367641460325, 42521.02410312904, 32316.080742245125, 39384.05888091021, 18605.119271802894, 65462.35402901273, 64797.91287520844, 99737.13073127093, 7256.3809803197655, 94650.79011219366, 59799.37299726047, 68488.19357103868, 79185.6240739432, 197.82397485613723, 60578.031517548116, 69571.86141023238, 83979.09811050966, 57827.02102423652, 94009.85528616709, 50084.59912904545, 37456.45396263092, 57035.13261524761, 17707.55727505614, 2391.9817758382146, 69144.75655024985, 51705.6012481672, 68514.84214707761, 27213.00274323739, 33408.82364560301, 13517.16529237934, 2602.018130221162, 8493.916956982128, 34238.70719681702, 27687.374633802254, 45359.555497940804, 67782.45882774222, 61278.37915646756, 91879.94632099263, 19007.232821322796, 44700.649709326666, 65729.60052854124, 4690.7295776442925, 58628.3277973693, 1742.4517662700368, 6370.8072475376575, 80714.12032541851, 51468.02597922929, 70496.4392685055, 66187.06057614647, 60877.89669833252, 34657.92063686735, 87135.851716133, 34952.448265461986, 35570.0117150082, 30853.03919880801, 48694.11130866935, 72323.79879288885, 70929.2510980392, 53299.39115413811, 26095.966937792426, 52920.42852358154, 61157.84560316343, 66816.67823069208, 72235.13242539624, 97061.33198371409, 30359.084829278992, 9548.480220252843, 14481.036825932691, 75537.4437090897, 88701.89487360707, 77501.34592069438, 45983.67531246626, 80479.29395212227, 73825.10130489801, 97867.93287833041, 41270.257249590766, 50857.05163819731, 75669.97776482462, 93832.76253852379, 44926.779895757805, 49348.014656851905, 9737.587716017926, 2917.746659209852, 23307.13245338425, 68554.73493249378, 34946.99861827748, 14610.221128520729, 30465.881720939924, 603.1909842372895, 72158.16938018039, 9792.783120401271, 21953.84847478663, 57154.77574190833, 38082.5608035928, 47371.1484399352, 93423.05818595305, 72856.57081228828, 96593.32155503784, 19179.29361363383, 75026.56250052626, 74604.9509272431, 51962.88522100111, 92152.15430972133, 89425.27087725943, 7916.054908785164, 45898.556176666694, 71176.70836745106, 14884.175761327557, 58822.804073612555, 43601.213668660355, 21042.22622332943, 94970.47175866713, 44850.38487541445, 87498.75527617657, 9054.857478828659]} +{"id": 456, "vector": [40129.524278365025, 78482.83298640356, 55679.38932911549, 28000.09917710843, 12291.494004601078, 83370.25338910495, 86171.66548358717, 67704.05991113604, 73393.5743054033, 38403.970539678514, 63646.21727829168, 16352.16096782174, 75039.63255773498, 46137.568074324874, 79315.955934033, 75212.11667415917, 81533.27663719862, 46587.76118021911, 66619.56922082667, 52685.4955941198, 1113.0498090297424, 64676.14625481597, 94589.15888627831, 665.8338256698148, 62771.113233180346, 64794.5269437546, 69488.35226798986, 61721.75381838116, 21894.103106758455, 29761.89258150106, 22487.930857472682, 28477.021257878467, 78517.5265544316, 43834.27514818359, 45990.62810277545, 26141.84698087253, 96805.67779868213, 57006.65264474314, 17671.059497054164, 84050.36484287499, 56102.21625639542, 53337.70665790861, 48921.98561150797, 91856.93975239154, 49802.604402840865, 20992.57706092077, 49489.34285573133, 12890.172962299073, 4155.436145153902, 96375.7274964778, 78961.60000009436, 91485.51948353641, 47070.01284549609, 50221.96112836431, 53563.562165851974, 65208.658092725804, 86101.53093257497, 64521.631752715504, 67374.77371051606, 56533.12228907509, 7750.635850120447, 21854.76346078318, 38174.74350321542, 74167.76497766419, 79918.1514257377, 40270.85709955851, 92337.08957148343, 76558.57656832815, 65401.789544278276, 16449.953432572438, 68917.69192404505, 6785.209525579617, 34951.12568844823, 14774.540326305985, 65870.48463026709, 40983.93471799409, 44906.88261565795, 66188.70512248448, 44973.86209571537, 30338.533750657716, 96067.60262723178, 29137.181676261636, 74324.8059215498, 84934.45358946177, 61012.79246621312, 46292.0984095699, 50525.68694107222, 64627.44523184357, 88102.66899708861, 89535.61281558621, 67427.18881209775, 99320.59796886388, 64560.79254332261, 74548.51918215997, 78569.29450360485, 25864.787313324843, 30574.527474924762, 49280.822170186955, 84385.76677902552, 1928.1751439507943, 97970.26890877332, 66021.1869419266, 54661.79314836498, 35374.09855788031, 28582.832947226343, 6074.505563415122, 95698.7576320544, 42497.691748098245, 6066.498856480984, 67672.35712721349, 56017.10442067756, 30433.309349896852, 92919.09901100729, 43109.68735586501, 50851.862250893806, 2875.0254046221003, 5570.927500508438, 92836.43691593182, 85827.83910886131, 84808.94433355954, 66455.58312786951, 2648.9165753479706, 77993.26825323355, 71077.53750658905, 23562.577897297022, 54344.89285747225, 95435.41501245172, 30508.110469727435]} +{"id": 1496, "vector": [92911.85151236673, 33494.72333472146, 46998.89831357687, 18137.63122832761, 62268.56284669106, 4218.767466468232, 88239.39728284162, 6063.857729491584, 96074.8619644578, 53136.74276427021, 34520.71718444163, 88549.6710227546, 54208.467743041525, 28452.724201465528, 89375.08745693127, 39963.30025916569, 37491.879320766006, 23400.58167624882, 27219.133971805666, 12716.289910970669, 66655.88598085742, 36398.28067796996, 67388.50804176253, 91309.43552263269, 17411.860624256948, 84939.8488234544, 90865.0627030698, 68267.32288964454, 52105.33738416523, 37874.19765064176, 25706.248773313277, 72765.47158769354, 92109.94301659336, 83251.89256750743, 65736.78751851064, 11058.204597352073, 66123.44637572506, 82025.00475020793, 61426.4604553032, 82702.40983486973, 51015.08855690091, 12603.344437947939, 17482.977826176837, 17450.67364868551, 340.52936233872623, 13166.938820974949, 44121.896750924694, 94016.38996113656, 79656.51765175058, 86841.02939394917, 84632.40384378452, 29419.080063879675, 39747.031397316314, 73913.47411403472, 22551.497440185543, 59188.178248514974, 53114.00030025208, 5569.5514262597335, 16425.383502517376, 11494.103413227074, 16703.364163012913, 68071.76994125255, 44269.96447863808, 74838.07082845477, 14970.39515901889, 57644.487652944845, 28051.79632713517, 95829.35227350978, 16674.606287785442, 27453.3881622381, 72952.29969283118, 23183.90412147585, 78749.0128249863, 22154.627580596476, 878.6109329092628, 63652.4032940767, 95116.89986307338, 30994.748909837243, 94051.31436616713, 28585.765050688227, 60881.09453122919, 34664.68876906551, 33929.42967774607, 9247.170054879161, 60752.213685833, 65026.62347599318, 68611.52985811178, 6885.317377363432, 77284.08073843428, 71326.74440647342, 81502.20798299562, 3550.372636640986, 1321.1933094993024, 41627.30877903736, 91616.17223975169, 34780.71493202617, 70623.50004096313, 88248.9414247405, 87827.46416316547, 62124.26516704476, 51885.37865539339, 20662.286073420422, 54443.916889870045, 74749.50440858725, 38359.01137631814, 72008.6814408675, 90224.54038862861, 37714.36256896292, 16299.702698968422, 89930.81589693521, 35310.34824924072, 46100.38027881396, 47448.88778222622, 83367.40019009977, 32738.32884896699, 18846.533373169827, 37493.4446061469, 29700.149177503754, 91077.32846116989, 11467.73889064121, 54140.204061847406, 64804.82500971355, 68130.7111151304, 23253.420775372637, 79663.08647332145, 43435.40521870276, 96920.28834644484, 64223.1760616581]} +{"id": 673, "vector": [65777.92331520464, 19377.749860173353, 29779.111652150237, 6747.333324400773, 27464.868186270218, 35876.61308621874, 61155.31870726422, 95566.8509754895, 83330.38236001384, 96732.17848568625, 58704.47603589521, 16195.698815453352, 92582.93113792596, 19654.44612788203, 77990.3717221117, 27324.042371734104, 89052.29346719352, 87834.01866449177, 53510.428434681446, 79947.23981049542, 43137.80262506966, 24144.66831256109, 79183.50985209295, 49908.31641178701, 17369.46978170436, 12676.780530057575, 33955.144252245685, 46292.66628937639, 15010.270387652392, 22100.579992074876, 64616.29120561745, 45893.85777985023, 645.5258087964322, 43276.28172900436, 89567.01286108581, 42600.944913204876, 67412.94500355172, 9686.717501216292, 26030.937172969927, 93740.01433738133, 83949.79245984001, 49723.97560967259, 15978.915923277991, 56092.56330901401, 10960.648392538786, 20835.705080325282, 99794.73010002065, 41668.143307859864, 13536.256111584633, 51749.24373425489, 69066.44549531108, 7391.819265574684, 29114.094751908095, 217.19190329226868, 41801.779416235775, 74066.63135490227, 35979.10371486858, 93816.5300580762, 99179.70649638143, 94515.88577125223, 56731.659493221276, 96056.17811878082, 15056.32877015788, 76833.5059957876, 39534.20481897557, 45321.29754906096, 84065.61000121137, 71124.95304306083, 47552.246234301056, 22320.090943043015, 95492.13377609728, 65984.96865844044, 69986.09880638027, 61217.79772590088, 63496.09313456981, 17251.618635883104, 8504.95968542927, 94710.04003051511, 83205.75264449486, 22812.836852894547, 82046.39274614395, 98633.81368453307, 98088.06706311, 36412.58225616224, 44942.30696848732, 61902.839101909165, 91816.94529886641, 45619.88355836166, 16632.3939842162, 98881.49187523816, 55137.901788995194, 48657.79227314862, 26208.88519478556, 86275.98252101777, 8971.48947719305, 35485.97888891305, 58115.13947662944, 67011.6447286766, 7768.632828112843, 59461.97852193048, 66060.52056660528, 23034.898754621758, 51041.41848095735, 16696.973221009783, 22917.428767631263, 24503.77617491788, 72017.00710718294, 4689.928251417885, 16554.604878879552, 43684.09533223838, 2888.9335390022698, 19888.048848567898, 24367.56992524478, 21660.71283536739, 45858.89686347336, 25419.83913500465, 83687.75002325, 70574.78340289144, 20771.06520691535, 69022.38390382014, 26729.48591890667, 9564.60721518927, 74961.73814229785, 76714.97223196026, 21648.944284043893, 55077.684899289394, 37458.713071311686, 76916.6676743041]} +{"id": 854, "vector": [60526.16364560385, 102.7307976717684, 43605.454166265, 80569.8443752287, 70274.91411821092, 53763.616875702224, 81106.94668326956, 31988.18096320698, 75786.52437392797, 15114.682921118649, 76018.59198213847, 92663.05649589846, 31140.979969798165, 59947.17703675878, 28641.113950883446, 22323.87228360535, 58207.55438221445, 44872.86761346168, 1889.9458263258784, 88876.04880793436, 14613.328012849957, 20155.748369325134, 98230.6867802407, 36620.26671595111, 73410.28856258024, 85185.77813663294, 41973.53179919664, 7169.695925044483, 89555.78838629591, 99506.24273057962, 11247.853923513829, 19056.628751607284, 82428.04066036684, 81151.27217560833, 53219.999849385844, 60518.5920080956, 20057.461209163717, 80768.5678326593, 69935.07279869918, 10350.920972465039, 19051.75833182434, 41426.02847240988, 65847.99478634397, 75863.49274246645, 45583.826679209385, 43513.85767045313, 75690.95159086472, 10071.420974751554, 53865.258928421754, 99473.56164689307, 24067.156424605197, 20891.782107667677, 57348.66166455857, 81774.02891747511, 56374.32563603897, 81973.27645456846, 64380.57212771146, 52944.95800594045, 47904.24094892187, 20627.961654348615, 78830.88015207986, 38463.3203767491, 95666.68914913606, 77791.21770301291, 13035.543069480203, 54472.454929678315, 27769.075142522193, 47675.26927818621, 37396.04436590187, 52203.78341340672, 69560.16106266015, 21295.009972874024, 56161.83787121226, 12804.745950980545, 75382.15763416197, 49030.635335205516, 75489.0610338191, 48834.34939654783, 98793.2196680614, 24493.03738388896, 44692.25450174459, 73709.92396808634, 77430.3623699291, 39229.80029656592, 64463.21832548584, 89325.15986789601, 93883.18017106452, 65938.39208494774, 59934.03831863926, 67962.83312052775, 5139.760184818531, 75494.0354128629, 3347.038156218196, 74659.13664751763, 54151.181500905746, 22868.041250236438, 59939.60774631106, 82494.44835607959, 80569.66048518896, 70983.82228809786, 78334.37184147959, 34809.485236018445, 20211.478783807026, 77349.10352314282, 76001.18190719797, 62089.04308113809, 69580.97210387349, 37904.16634507638, 82610.4294537915, 93132.94787395865, 36939.27945588749, 95244.89829164058, 27605.063586928147, 36141.0745433085, 67936.68199461182, 76961.8717249222, 83349.44717281216, 67100.94254104435, 1226.3323326260456, 95251.56604116075, 17478.544403966924, 45841.75889856661, 43667.75903878214, 38072.949663823354, 39059.658333303814, 82304.52072264919, 99445.90139191809, 92726.86722698499]} +{"id": 1874, "vector": [6429.017498488821, 30781.96094886889, 52901.66363146327, 74724.70469445888, 82746.98788475755, 11721.477920302692, 55909.482618427006, 81127.75713411589, 49866.189185287556, 53737.159549782045, 79881.48176538697, 5919.287937903694, 81339.68178781646, 3278.460189523369, 11400.600875041944, 58298.600610739915, 63650.69571174512, 32282.842106702137, 22945.03700217835, 4538.9613176554985, 90819.59044444017, 1713.152396443729, 55052.96123476297, 438.508314611008, 17572.312872991202, 20090.201829225174, 68406.42281332979, 26466.946608973783, 73281.77378765243, 12278.499317819447, 2593.187595479918, 92570.99424013801, 74212.99099683533, 88857.18185924146, 24499.59783036626, 16577.873680372322, 80176.99615813994, 98834.86102466751, 59157.30537643482, 99889.84689098637, 95985.12789131685, 36972.70350854798, 37575.673506762665, 69814.5534452027, 1546.2852215227917, 27952.734293836733, 94800.90466839875, 52414.30655278907, 42691.44615208796, 42767.84454014719, 44166.100915429815, 71314.13322155613, 17125.735504665387, 16172.50004592241, 92319.75741556167, 26729.090705964696, 41821.52163377217, 71670.35487270927, 23983.92856918731, 13902.211121549835, 51439.492052844726, 3676.0014745831127, 5838.215515355006, 80951.528260016, 23122.254387763984, 70567.10385437036, 14130.170364663063, 79755.63606503057, 88456.63455282235, 75612.17267149525, 45686.47819943016, 84020.73858854537, 42087.21390343224, 99107.82482531975, 37417.29331073678, 84409.02716276444, 71721.81541602242, 34097.294481926125, 92892.70548773567, 20327.799519176893, 81283.1048320934, 72888.68502849397, 69917.39524164326, 81537.10653736735, 55528.16075969375, 64344.54910040739, 20586.83389236423, 9597.90298061579, 72301.26281489944, 36398.29319685403, 89168.29559148478, 58771.859351083134, 66715.1340256358, 55253.03220724037, 70050.79031965074, 75738.39068954502, 35995.94644222711, 10615.075182190138, 39983.8554410819, 59258.784746245365, 70810.04240642287, 91779.31585074407, 51285.95683578991, 97999.36517724476, 81072.25347678948, 97454.28591971897, 19135.464923870517, 66868.74276354093, 4674.55753397904, 24042.748746803245, 15485.532629858168, 21080.19281119352, 77750.02038971441, 83697.56132349593, 45799.11570135953, 23179.70217201132, 36833.74348150306, 83346.01353584357, 59530.91366355795, 427.9972812580635, 25999.133303934785, 67734.80534800998, 16124.446446290774, 80431.01630322919, 46924.21580814561, 57969.75972507478, 77774.57873918879, 93853.22671275186]} +{"id": 1227, "vector": [96126.7374551742, 55741.839915022974, 3297.4712250056327, 41355.85107256386, 28175.238118987334, 22381.253315463877, 20011.55111114724, 7778.02296957214, 35727.225343738035, 27237.91932885119, 67568.33915592398, 38465.76450699943, 96072.25472829232, 80240.29883352858, 58765.501718484506, 69668.54995114835, 50106.931666449236, 91471.12372398825, 48604.03460630781, 64366.673494108436, 77620.46432354089, 33772.34665225166, 16221.457335907364, 97759.27307153092, 84539.70950783147, 60142.31531554997, 71633.6729430621, 17180.1233130082, 64432.14282789565, 92610.5450675135, 40688.75976388475, 3385.871555461184, 34515.58562717727, 31414.791410556987, 59179.16250713795, 13677.365027892663, 19246.600079930253, 20792.623593953187, 13387.494739889295, 46784.69528965589, 45597.14190603045, 72493.37392677803, 67995.43221127297, 57847.78615502857, 57470.65205947864, 24987.05537437471, 8860.102611022035, 39844.70606373603, 27953.938837565416, 42851.9898607251, 85544.03195171652, 68578.46324947814, 46566.8091591949, 42556.140477389825, 94340.7477422858, 70519.23223319216, 74956.31296247718, 56037.050540962555, 85493.7512234658, 12195.839869999381, 95145.49754210576, 50349.994842900625, 42854.44993529659, 60124.014259285876, 18169.663617526654, 45374.74698742877, 26497.0781476193, 69453.9085044462, 2424.261325224364, 67639.65937830711, 36402.15661180538, 52249.26742892614, 74036.58330296667, 45810.86596109055, 96614.04508391395, 39573.61810166519, 41368.533044650576, 47880.48991917663, 52071.39052717771, 14986.345570476855, 49353.58642159118, 57482.03207332082, 4397.197227111927, 74322.64198490784, 3649.592139457947, 5477.621410817912, 55107.22063268682, 85510.458081821, 60991.56048094328, 74660.19772696873, 53049.55840274426, 5542.13598498492, 12795.149983858533, 93069.75085388547, 20409.449744130183, 92038.01999132782, 54623.07984472017, 61803.47084108051, 29306.045692680895, 39299.226839055314, 71485.44626175574, 82717.0096061326, 28227.150896811905, 40145.4287454465, 93233.53930119224, 92779.01193231484, 43957.9586861813, 92608.41313503648, 37560.281341125265, 46892.44052812055, 93272.10162005728, 7967.509524420735, 12128.95140276351, 37480.7852465838, 89905.77619341994, 85502.68869657817, 41090.18744528997, 43563.840962577706, 82473.17497299542, 75480.94020222379, 33595.72775262396, 72875.74111914482, 59136.04484649653, 2548.0862369195447, 52591.812881624224, 99470.09414380112, 22712.274059566473, 38923.901229575684]} +{"id": 1752, "vector": [68947.73413061202, 89658.1450054056, 18674.61762291466, 38355.97777240943, 41623.90718067633, 97079.85036071544, 15990.960472242255, 69457.38487478261, 71941.75428150078, 97113.05030626383, 2125.6198164714356, 15869.561338360516, 99550.40767219893, 54121.07719289416, 59100.12631774704, 84231.15258921903, 53413.16071941228, 36556.69315176488, 70860.23679186936, 6051.232038567678, 82617.31723317345, 46813.8048323128, 68322.02727895127, 33943.61889347504, 13878.941884211394, 92435.56090732999, 26636.113229759096, 88050.17037297145, 96002.89457816139, 28705.80206534399, 83838.88312292706, 44340.36531832446, 11683.802071098537, 66348.56349524861, 32479.119119324474, 58087.35394342444, 42873.93778156059, 7224.35043673958, 86254.57386610085, 1405.7130899946424, 43334.58885089809, 36072.829573286755, 18088.289856047657, 8146.170518573103, 70347.03482969695, 76660.20350155211, 87978.65148529106, 86760.57088460593, 9761.003163398285, 75525.57772608932, 21128.30549845943, 99742.60231042135, 94047.75601491686, 37448.066192887134, 6987.260590513578, 97566.6781415931, 25348.119560692772, 31876.302773685595, 65678.27458239258, 54263.97827871206, 32843.49367585056, 39494.17423590832, 5377.9403434993765, 91314.90133406752, 87951.76577854846, 40678.26514169904, 31897.882156433323, 82705.07158930748, 25022.279933188784, 83944.61468358822, 68170.23838578787, 4099.721643353449, 24632.97844930825, 80203.26286077927, 21570.61864616503, 8486.651397825928, 68127.9649126004, 7432.930806713811, 8284.829489573875, 81531.67368481941, 68851.83676120297, 49855.732593568544, 99376.81448460442, 63968.62060224278, 16325.329149970135, 42984.69166919149, 79258.05057757054, 77978.60434663764, 37047.412877399845, 78904.11611399693, 46835.99439734856, 87976.05610654903, 48094.68700493187, 87924.70322753505, 69798.32235017973, 9033.666293383325, 24798.715403065296, 97326.69680235599, 22620.067289713606, 91280.96796791276, 3656.091593436106, 29946.238482981014, 48081.08989725258, 40515.78729951348, 15902.086838310226, 26778.95488507094, 78716.07242910103, 62298.325988922275, 44733.894955731455, 72808.04524917607, 1821.493383068229, 82226.93817768744, 12570.190053581886, 94139.14333564893, 98457.90606308778, 26618.406398526484, 23838.645545520387, 28274.198823098995, 65274.76614728467, 96518.40321959528, 94340.09250081751, 87868.08716073146, 43758.256106009365, 53254.20700454862, 79348.9851342927, 71064.68652827111, 42863.803276822735, 94645.64047771215]} +{"id": 38, "vector": [45795.65178502033, 39289.317915421285, 93227.51812125005, 59387.449641669446, 23454.981784858297, 43213.61826247129, 15727.224520917249, 80587.12146379915, 81037.07266131375, 11855.34894791287, 29007.805924431883, 36077.93372184146, 5932.3723907582225, 72131.91731370286, 50409.37503894607, 55132.91246013962, 87830.4450684216, 15883.933425305675, 8144.9387494378025, 4152.745822347947, 54275.24410209661, 80643.14507801316, 86798.64028526862, 73779.87898648952, 16554.673473755676, 89040.54951996225, 47937.36133161814, 69042.68043160417, 52626.977709762716, 51410.58238693012, 7314.023208039933, 29578.774541729435, 72842.57394075283, 54456.19123480947, 51249.498251565994, 92447.7763986789, 83795.37591480564, 9463.881187863732, 21076.886975634712, 14194.919765434166, 48809.13464057743, 53324.42477053916, 28471.923009455313, 74673.69091849377, 62683.832122479034, 85441.81607220355, 92466.2780404803, 42123.20748965642, 610.8391430274751, 72354.52132032372, 27103.258656492024, 21132.114244379387, 58037.712696116636, 75344.73846369001, 16449.49794874825, 72858.04722868674, 72675.34698260094, 62405.97548535826, 91741.38571209728, 42053.93744395871, 49054.158582697295, 61850.23252686874, 61233.147237908735, 455.9619412564997, 73631.29628774211, 11556.105785937487, 46196.20713737008, 26492.52963196176, 80749.4359713904, 7681.4547864081815, 57976.918469344586, 79254.3499450145, 13325.046817564124, 59009.58877177937, 71428.79369250801, 77142.03104464633, 98888.40269211824, 63881.34647811351, 97491.24505820738, 30077.107926975023, 37443.96315429447, 10272.41913190774, 86366.93666967991, 3806.8116647704155, 54395.05236469904, 87103.27238280051, 30941.09784630963, 41220.624189523995, 18441.179458570357, 13079.64440263314, 30247.84276002166, 1596.5621194434675, 65286.5066879632, 94354.06379489237, 74185.17892399366, 80728.79631209512, 27610.245632704013, 25704.182663944575, 87441.2848787211, 36027.58099109591, 60653.924582845044, 84511.63421250202, 7189.1966878422145, 75569.94498993328, 97170.3589514782, 87437.8210043477, 100.0241580899064, 58547.598639065225, 25009.653909053563, 28724.235766892834, 326.18073772126, 39656.79245456871, 54848.98010497459, 41411.05671066551, 88295.74373831373, 68365.11843881165, 64615.304214180724, 19877.707261625765, 69927.58637658865, 38675.601635997045, 43757.66074498606, 60709.25029906659, 61834.52546082194, 25246.990765789236, 74336.4755170107, 25123.909303872184, 58426.03725492661, 490.91777522016764]} +{"id": 1751, "vector": [56519.91874790845, 8712.377954182448, 57348.94771652658, 39081.40144314653, 81931.54920636155, 81295.77435627245, 6872.182211463262, 88093.87409876987, 21518.35294494958, 4370.263223288684, 49674.76849035581, 94198.89054933394, 47416.57591483325, 3565.63500227195, 41196.578091619594, 65697.39343843775, 97159.32151726144, 52133.47357876894, 61800.58029292373, 74428.78436905694, 61099.0575314824, 15443.430458891893, 68147.63632682573, 88748.71013558679, 48888.186421208426, 46273.416959585, 94721.18156069599, 97657.60649346338, 15088.852007364161, 30483.606979292366, 29010.0945692882, 25811.447520119058, 60146.94017625397, 50890.16248507558, 87940.3877759771, 10935.58668341158, 73850.2795474404, 61911.238260268896, 72853.85247712042, 39642.062226078146, 24096.769699964825, 60383.52572301683, 47317.44704829295, 56834.983865307564, 17829.110322278895, 16390.256820838622, 93115.95672493597, 69934.05030387292, 1378.9539043171928, 40693.536618360384, 44146.94772338341, 59233.158854774134, 72512.94225467418, 96738.7518625875, 66560.50787261843, 14969.17321595661, 82295.5337138335, 45932.26144070629, 6125.410132114517, 39218.58631047368, 63947.45306879718, 34823.50979839853, 21633.419743853843, 7657.530466582363, 21113.23325920329, 9757.34151445321, 84727.50357827004, 61301.08015264119, 79589.47848804576, 91293.66350203186, 24244.452066981125, 96158.49237016744, 2840.2060863700117, 50155.70725830015, 14793.093719429151, 7925.755498965048, 20797.347713010127, 93431.45704506616, 66504.2772002276, 91795.16302156367, 20692.646950681024, 74648.71630333866, 84819.90729199792, 67039.1487452521, 38358.859430294775, 54517.98724306178, 27449.80652746295, 44628.43721253479, 80958.9920114359, 81231.00322617442, 77648.04127854077, 17958.372816171675, 77504.35228138466, 93910.1675468919, 67205.9210500337, 96877.59210236026, 22156.920002356663, 26258.644847961245, 67476.02134032686, 76112.18794437633, 12513.326829093952, 8930.23653116728, 51623.83277567878, 94445.72244219841, 32885.54894655835, 82324.61587488736, 71730.20218908727, 79309.03428403978, 62244.24576108382, 21401.51364542101, 58725.978028674705, 76655.06790021663, 18124.318665157978, 72997.59338119537, 8890.817984404275, 31673.028840170624, 74877.92633911267, 84072.59145074042, 31926.58965765758, 86443.01048178092, 83411.93424530998, 60776.27000504467, 26737.976531434826, 33740.11080508074, 97181.53738964371, 98670.61986445102, 17783.841283526013, 5642.698391025935]} +{"id": 489, "vector": [97762.34844815712, 9898.848227612856, 71547.17777861143, 30099.674346623506, 53126.59179573027, 1314.9334427460024, 64596.69816798745, 24223.83318459024, 26348.360424687835, 12550.020380772243, 54857.10331085045, 91219.49845878697, 42149.33137631865, 71715.4083616514, 29966.484590873697, 40345.91813765537, 58150.09509002651, 20245.028904902385, 12178.589916635774, 1074.1640666271146, 41128.09493015854, 62495.117944270794, 89898.24469291794, 12914.438518990057, 76977.53357117788, 26553.54001155211, 88939.70654720113, 79366.48806501231, 91470.71146657737, 91999.51708895256, 3016.9057494469744, 63016.58345416698, 59488.57872034729, 21348.342780444673, 90182.6934924586, 32665.347842936942, 50287.10796019738, 46809.397510265706, 49576.40592935839, 20443.861463530866, 73933.97079473738, 37123.00632564187, 68545.63165987331, 78193.02941146848, 91769.45700078033, 4890.2673221515315, 85899.07169216976, 93289.2347591674, 44528.72381842584, 66515.78802292475, 56353.437259526305, 22191.133208820025, 50433.52028371876, 62181.31929793073, 25002.5905921447, 61658.146940299295, 74905.18775285684, 16235.946091230424, 19077.41922077376, 12274.421827851378, 57218.5814706161, 40102.287648029145, 6254.664101492469, 10253.40233448333, 25676.838832203986, 14953.312354503656, 90636.27124591333, 64960.68283308705, 9914.785350659395, 72124.02913233367, 89611.38746699627, 36158.538244053554, 69502.1121302516, 20888.30646374138, 10509.616497268114, 89014.85150453837, 60577.648983942825, 80810.8513006988, 77889.53463659454, 75752.50824671832, 69566.95887103157, 27509.913633285232, 24506.871243531168, 49740.49094922758, 10083.60477549124, 79328.67874674624, 87047.8710274847, 22700.656338673652, 21377.87121649921, 54023.14249138097, 83337.50602654443, 47262.94897489156, 72508.6897856087, 85782.92931398853, 17242.18424501944, 22923.80566281589, 9313.414414572586, 72508.5240171605, 73153.07209703285, 24230.613569125748, 74357.45219592686, 51901.87913109945, 61325.92107589625, 62686.311487117506, 74751.07192544248, 36063.11182657843, 56614.68483133028, 50832.98948597322, 18091.746251473218, 27226.720630286614, 61234.388729857295, 56518.433374549895, 90274.46298276843, 77950.57726814745, 39034.97059671682, 41389.61167765777, 13531.743381635919, 2779.9827941885956, 79902.53591446026, 17097.202289081593, 82092.45827936895, 85424.54561237314, 67721.44257872521, 38835.406791995585, 1544.0955312298388, 39106.25544702124, 7967.312075559907, 41577.51967437693]} +{"id": 1740, "vector": [19889.459463697513, 30505.46478404036, 26579.678671590646, 66838.75795403057, 86414.63036136609, 64059.34757966185, 19280.784537287243, 8141.757271396832, 7732.726933216827, 32238.79494879188, 73724.25870876168, 72571.17004023494, 28075.963409130334, 26192.60462538956, 38209.4644739647, 66756.09909807905, 6565.353987621803, 43845.20524060025, 21773.374316724447, 138.08268746143827, 9099.615680894447, 99291.5769829556, 81026.39868238017, 77110.63197815021, 92696.36453079016, 59197.397847941094, 94025.59198285542, 88693.26313947036, 44836.075355510475, 14627.69116199406, 66673.42569106843, 36542.60516171439, 9235.632460684306, 49158.42883626455, 79925.06392727216, 93123.07281572463, 89594.40531659912, 63147.015317723555, 42946.14444400231, 59304.991426977474, 66300.4914078076, 21054.905476556196, 13235.467244518251, 15742.486828578662, 21828.79428665918, 27246.597273813255, 86316.26295474215, 3.183636999293249, 15254.408334857671, 17384.836973117413, 39208.70360656165, 9768.798537877166, 23456.135989102433, 46217.39536718712, 1177.039820711201, 5462.569460146172, 28011.13747263654, 55141.609464236564, 96406.46681568409, 53592.420293441064, 33202.026479996726, 84348.72613588396, 68165.28850200541, 5195.1314225240685, 81100.8226545485, 33144.519831976904, 60101.18323890075, 61360.5718591413, 83737.94896819227, 32622.449164544476, 61977.990716332984, 17175.54657236896, 44534.41047869616, 39214.281425774236, 96704.07316872936, 73692.15535629343, 57466.531072480495, 79932.38515210313, 24887.65620750655, 2119.7497947448073, 27741.04516872191, 12176.900648944478, 50687.60547055815, 59295.86926213295, 46001.96726968989, 22638.74720903477, 46156.03784212055, 35981.62707680107, 20280.605917613448, 10830.279869122895, 39955.62397327341, 98002.20929827, 49273.13415941717, 7091.8261344484645, 61887.58859054675, 67533.22442406528, 53846.53362512703, 59895.32713976727, 10828.993110615049, 31836.49755930302, 89971.59562719417, 19740.112704267853, 76897.73776344501, 72236.36272981555, 35577.01078975152, 92795.266624566, 64630.536553994134, 61980.36043203031, 18937.26900353433, 66695.55147802226, 75394.21875036399, 59809.32039843092, 2361.850539675803, 63208.43041416716, 68322.40635566686, 70726.53328018553, 97296.8935839611, 27295.910890763407, 41093.137768777444, 92077.88380364292, 67686.6182994248, 60884.84537941766, 71280.57337996212, 68322.23388443213, 66116.52533684253, 62090.674958834745, 51367.116453338866, 14446.108109127965]} +{"id": 674, "vector": [52192.25466679234, 42077.05778906279, 35170.412725896895, 30325.375099024, 5373.677704142899, 2836.226851893053, 9819.584329400743, 72941.75785531907, 74552.27856397268, 9776.256867167021, 87811.03371047774, 51674.663700634184, 42247.78142900736, 5759.59728367943, 97011.8035226858, 4989.554456789536, 35672.228060480615, 43032.61297778916, 27199.90146378587, 88541.11022130483, 80918.86441921856, 77058.69621174507, 62125.131510979525, 95071.29421190947, 29550.3636549592, 58424.40494124077, 4128.6937995186345, 53257.29213121893, 64020.175400885884, 27214.401350984517, 30443.950690057376, 94998.50884132869, 41047.286686510575, 15037.464487602747, 79504.79536365022, 81595.48629260025, 30989.405377878233, 5227.777782841647, 47679.69774461408, 49092.5564100066, 97657.37964257113, 34445.04781940645, 63672.90379784854, 44307.46243099573, 77592.51300594512, 65471.5064638007, 179.11106616775862, 49430.10115643868, 73315.45689447325, 75400.31382586538, 46411.07220705023, 43428.33650606962, 28705.227764224674, 1201.0191318820418, 13052.922934738464, 44276.747580704745, 19287.79710884616, 32652.886979836516, 33455.918164608025, 68144.3975273874, 16479.474293823794, 16944.59540834292, 75553.78278898077, 35011.43414407386, 56204.9148729373, 60720.926260575536, 50109.38134368602, 57435.849439819256, 96677.61550914602, 57462.60537942277, 91811.3575113373, 44456.51094153437, 76843.3171527563, 9926.09403862238, 61064.45588338033, 55439.46685508109, 54937.602100638425, 96084.538602011, 59660.21244248682, 44863.99862598186, 61955.15759838233, 41149.77363933507, 60312.6894969326, 68233.95539370099, 53934.229037978665, 55336.04085158265, 18986.168708528272, 74709.57495868334, 91229.61725161402, 14587.414713939961, 73751.25279863682, 72140.49476492307, 26964.93771078464, 63237.92905416326, 62397.109637676505, 2702.0377923478, 21395.005632672048, 11642.420571849, 51274.40254048463, 71288.76024623658, 81957.42452331044, 62322.577176463754, 62326.69458762774, 81069.32799136343, 33946.25580425027, 52753.819594972876, 9833.4045488632, 36797.15235573022, 760.9566015004842, 91025.46281151082, 28255.322928475314, 19583.340220822898, 30670.48259712095, 65257.58184199801, 60742.608109728244, 1116.8717834316387, 73628.7640582795, 51670.3428139627, 61482.073542744074, 24300.936317608444, 51509.50892415427, 57607.75057526967, 35929.63008904892, 66859.45594288176, 8093.238171714756, 79836.7573971981, 23623.67281852311, 19085.266071051556]} +{"id": 316, "vector": [78551.33354661577, 96269.88690866553, 72512.82889887856, 60636.83669956459, 54383.864648519775, 53990.49235455787, 93202.8286321022, 88146.64826945552, 79607.33075148432, 62623.73373426653, 37136.98970914462, 29336.148335834834, 29588.08958028969, 56189.73466213709, 1303.9617116262336, 89888.07731704565, 80950.15020972937, 32042.156072956695, 50384.97123043345, 6038.937694298174, 83343.08523446607, 98386.06016982802, 2055.6247831267083, 75295.53238298833, 59031.69241141289, 96113.36047412771, 12740.22696280126, 59645.232550081826, 62973.91010766491, 56967.207013748346, 16162.23578914736, 73053.2811683867, 26796.2907948344, 51363.53171056961, 26566.90512847426, 59602.92431542864, 56435.87579932782, 50386.02049543102, 42586.79529385096, 63399.090786500354, 50698.931151980876, 1524.5856528600998, 93083.6621063672, 44482.09340256406, 27215.503726689803, 97055.58836662467, 5819.995742641159, 99890.27758716336, 37738.88381914393, 10116.713048874293, 43851.400668639784, 88200.86109479287, 38312.6857283867, 50705.87654855747, 48420.47344970215, 49241.91671761226, 53364.696032987325, 48654.01251485288, 67944.70243018403, 14452.666982625527, 12786.679621293684, 17923.675106500537, 29752.83276441113, 98438.30812127152, 53791.0830250239, 97226.95018786828, 78060.83249968951, 97739.9758277714, 82666.11502334042, 69259.84513440896, 80731.11279311909, 42163.60754566982, 64241.027482667756, 42682.83839322751, 48941.245222922524, 29214.302373279344, 37346.03058757684, 33216.11295965755, 71180.30456935086, 29003.949715397714, 48863.174451856925, 68542.30979253078, 33715.21937944939, 88293.32102174395, 47515.14463230263, 19265.819860708245, 41101.22464880717, 14092.915963564312, 41976.52427049655, 49782.93755748695, 86212.59838064152, 12469.404958499641, 51088.376851762914, 84529.14585598123, 58109.580598480024, 85896.70547251144, 4906.201506809571, 45813.49362399473, 64176.050457219935, 60295.473509420124, 18791.759021390873, 6146.215754797579, 42866.15289901972, 49547.180070960174, 99845.92521757045, 11670.186523885395, 68690.02256134037, 54249.64798962177, 45499.72730525097, 88936.63287351892, 88583.3401617333, 47122.52719718039, 69254.78406077274, 27896.971725239207, 6404.060227844832, 72751.90299612537, 78911.28137173034, 33448.1917359008, 55384.07690762741, 55835.266378302025, 12433.312790584094, 96953.7944455067, 2321.777424761895, 52650.41387911121, 65227.333292009615, 73505.81807401094, 89393.86863419713, 14693.00972948393]} +{"id": 1687, "vector": [68047.3635505909, 3183.4598745197563, 38388.74975906572, 56197.28941031539, 10872.122629475589, 30402.279003684896, 20680.798355862673, 95005.45995070974, 38585.09546856719, 43401.76184504348, 40139.38016604035, 87811.07969289967, 95348.20548300153, 91992.12211067021, 38045.74359891445, 80742.96518141202, 64939.08330741502, 60538.45410574168, 3426.348069013718, 51502.93239874614, 30943.18626139748, 81862.19144808796, 51319.398317853134, 68724.37690788717, 60113.706599663885, 44276.66292956437, 12266.5357172565, 98251.76484232445, 37331.425725497116, 18837.29777939572, 40930.63308057274, 88657.58978097713, 80165.16470201453, 43993.507490438664, 52327.58677712033, 35035.80700470481, 82988.24800495525, 55632.97937912154, 10667.865243987451, 69033.78833469871, 47692.21951678565, 56714.95745869878, 1455.848673544924, 36360.68053826421, 33481.705971902986, 94965.82020604599, 58640.42607669136, 76632.0451463923, 50870.90027841287, 65717.3122192107, 70701.81282594014, 66728.0111540993, 8236.253278032635, 62428.20355129295, 96157.1584726477, 11163.584364949063, 32015.43752674876, 96192.72273722093, 67497.56622352301, 74411.04390437718, 28544.36666313287, 28894.281453239135, 78300.94015656473, 22139.71698291278, 64838.2197012418, 96696.37151072001, 68511.98779552565, 52474.827355144786, 70556.59436811358, 90404.22080953528, 12906.415452933406, 14161.168117439516, 53480.56646578598, 57157.861385601616, 89906.4865496493, 14371.93655955007, 37830.7102478824, 33307.46136811633, 34571.85175167059, 79773.19876001439, 75123.24554928255, 52766.80399761047, 87990.7615488353, 6094.469293166505, 22738.500312536813, 65827.33463948518, 64459.984348755774, 93946.80458420835, 3757.143515615835, 28993.70641295754, 69982.68228543454, 66952.165044655, 31891.633590204183, 11171.55697320903, 98893.99959104546, 75100.2876026281, 76288.04969885999, 41544.9784592923, 27965.858310006308, 32937.05528324743, 28669.699268422177, 21168.50725465884, 38338.853617718305, 42080.31992037166, 3461.6317967043897, 52257.87971142384, 13965.258180426665, 27840.69154966653, 28035.904706721005, 80631.81798795442, 40801.801810104975, 61932.50479475766, 3154.5982533793594, 24452.00862841449, 57909.826142026744, 86503.52627200112, 83460.90265807453, 69454.98705266752, 18220.372988974665, 36231.44089669508, 40282.63064195579, 17311.024245377048, 64792.683788296104, 58844.262733927455, 22284.5093245599, 84675.56931719118, 82466.69755962418, 99562.4812332098]} +{"id": 1251, "vector": [97748.51384217852, 78142.87611090313, 15266.019391093056, 28298.306070745195, 76642.06568223878, 39006.980494451134, 28920.140608369573, 94080.3069746531, 57012.92498167641, 69405.26913470337, 73468.53945234018, 14093.703538868729, 53484.69671977475, 53280.44834488023, 76327.64381016335, 77311.39722714959, 43125.198263645194, 25752.617937629806, 36353.45666869731, 10452.33847582614, 74637.0538024904, 84728.71402354375, 39773.51630139319, 8693.052796317446, 46690.0070034208, 81974.6280905571, 21758.28376872083, 88757.33688105232, 51313.98682733247, 53651.581480050714, 11354.305662152186, 22154.20245919004, 90588.75363536675, 42451.30693752287, 58170.84919907669, 99067.45904147445, 14982.672531735187, 45974.55398924008, 5054.377997127146, 11302.36905461719, 31207.707994091426, 4675.2088038442, 72400.91836224242, 24130.96205525368, 4867.189496762902, 94479.53208228757, 11621.16199284663, 62400.84891498563, 62525.69944910804, 47149.07211931344, 72159.2866761983, 80545.27779345962, 70543.25260761665, 97567.02132592075, 26140.076714925497, 57196.19450820532, 84418.8112532422, 76778.41731935696, 10282.96588941574, 21631.148836950488, 52350.74648909588, 73871.93550344835, 19269.8223452344, 83668.64089585903, 4608.924594776587, 54742.594576967786, 26709.109666791952, 4164.275360998304, 62007.91078790021, 42070.706541591964, 23107.264008293627, 4375.257627011231, 96166.06444985585, 96400.0056038095, 31288.790233311247, 2228.0995039193117, 68263.52756197125, 61838.336058036504, 86178.32858757944, 88025.97497973501, 78067.84113510494, 94027.4381067412, 14418.089585035887, 36343.40264723903, 53835.99128596398, 42556.00839997477, 52233.4498758251, 45759.64502450392, 38882.552660378, 15070.393262851478, 32182.597933588055, 2517.987619538342, 69989.62393994881, 26171.852440322917, 1959.0626928527643, 63675.98391911207, 38670.52588479756, 8648.263183171168, 21403.359502325926, 65604.09738521207, 27241.763934963725, 18737.429253539995, 92884.46267245138, 46831.07779349637, 74261.41775010015, 2607.2800819034514, 45415.09522179607, 74351.88049084046, 15135.46187394832, 55565.044314104605, 73879.02211564589, 51760.91859643921, 99493.73799802091, 37336.334584931166, 27715.834215878098, 90575.14329268063, 23120.005286905252, 53299.31429751412, 84668.79410194456, 16544.352064973213, 1253.988450510002, 97807.08269854168, 37665.56502753249, 19424.153374818787, 10840.278005662885, 17772.397347042435, 17771.445163240085, 18094.54767596059]} +{"id": 1691, "vector": [22166.05826942345, 16920.276850643935, 82496.11786202475, 20118.868945875078, 13937.333579534505, 12322.022928985154, 61863.659078441735, 22348.85086114747, 40419.3275299001, 78150.64105710865, 85544.55731355552, 80096.48104086178, 53361.36585401884, 40151.9876694405, 34760.522476588776, 34303.997761888386, 73336.83912217655, 87425.97442061576, 64643.10534682749, 40432.01907665166, 35705.36385731269, 58517.45281806146, 75299.13078116118, 32883.089772516214, 38471.15529998008, 49672.4356544724, 85894.60866733277, 83504.04719972248, 70626.51098233051, 46612.2479192747, 39314.484069525235, 92487.98211583532, 25320.350868484686, 57458.40391964895, 52082.594370663996, 7433.325007968728, 86429.42182985546, 29403.14216897525, 95747.49206030068, 25286.62528588468, 46785.42463318913, 88572.01691944696, 600.8040596099495, 95993.57586764963, 94128.10551589842, 34325.19954172537, 93908.3335997049, 75815.24248782507, 65208.65084301789, 60653.3976184057, 52170.77541370528, 47812.68720257618, 13564.560055161879, 95849.77149358443, 50722.82208501273, 43077.71414134346, 39616.64300748067, 56430.595149118955, 31971.673408049304, 78082.48137533375, 67994.24735533756, 41564.77036651899, 84777.85120951069, 7047.358892991085, 94014.13003310302, 27144.465454446177, 39067.95393804472, 32554.565850807427, 17356.23205653043, 92771.88422127222, 70541.98349447346, 27019.738942417105, 45592.954523097505, 87547.90773696404, 93428.13288015156, 49582.98725321436, 7972.637794776582, 87848.020389697, 1200.520344463485, 39172.34043969895, 59785.583532816876, 59518.83757681694, 84574.8999712969, 25422.0887193801, 81540.40708966556, 38308.42070148085, 78586.60765864726, 89762.21900209368, 54404.579131203034, 56147.15713370412, 52090.535872370914, 26278.203818556955, 14486.874469596423, 65421.85613801308, 43161.315488231136, 86141.09976942146, 47049.89073606477, 91734.68501890986, 42667.68913411445, 38181.01846488836, 24078.978599267066, 6656.749793330075, 75815.54151819486, 7667.329499033615, 28292.168108135083, 92605.7940840278, 1226.64190906282, 3608.7779779694683, 17800.031171590737, 43603.215258427896, 45527.186345189344, 33044.8135980068, 58551.38018991355, 13819.447651243245, 41940.51095374788, 20254.426698443294, 89847.89663297989, 5069.043834123743, 81039.67467922193, 7853.6209845259555, 94610.41356092707, 2691.772073529275, 24100.32570612757, 33251.90267507897, 71465.05631497584, 19009.706854633467, 40067.74291627198, 2290.8911090189554]} +{"id": 468, "vector": [75046.43756628035, 38536.12086391463, 30388.07339743792, 32871.19924077968, 1166.4450059912347, 17221.294014343413, 6529.747827686516, 82416.04979135712, 85559.480549961, 2851.545838924996, 28233.66525199209, 38293.85629140256, 82427.30028989108, 84845.10759508728, 391.69962231822007, 48841.797753081664, 22007.391544498867, 36386.93459131901, 47978.07893181115, 31637.005257375327, 77024.67227681466, 28588.804940850343, 6882.888140616905, 44498.438209600885, 62574.35935611485, 27140.99617310075, 16517.48144732842, 27606.701324994166, 34390.21167577483, 7634.390750696385, 86105.99159295876, 12466.40108120074, 32963.30037431413, 86900.56193478621, 45272.4145581046, 5185.7113777801, 14794.497445863586, 69026.4080488004, 74450.99131526223, 30470.53691020173, 19190.826362645887, 59477.385144994456, 89630.50432032764, 42742.38256781957, 24132.038703233284, 17346.21302902204, 64549.361988594545, 78924.4532043398, 28527.35580961362, 71065.08054661361, 50031.58029026408, 63641.16698903808, 62311.45847499372, 43277.844957438974, 5610.32129003185, 55851.71870742592, 59465.76462593844, 61823.46470441981, 35341.473788718235, 92083.11683575534, 21555.58826950429, 753.4426225518365, 83513.0468637567, 59512.82006350632, 86157.53566737335, 40863.42598802597, 70555.99833216437, 18577.248652153023, 61760.886598391575, 56775.893654005085, 72940.05406020093, 96598.98361561549, 98543.48756259911, 42361.997031888386, 18795.44375906601, 16000.650770125125, 25487.557385440185, 56836.23393485184, 23562.59566558031, 31460.566006599554, 45336.29638259711, 66086.74197701763, 48055.02337176274, 15291.837653603114, 91938.98251313009, 90679.75294230353, 9025.253247464016, 25875.37670684662, 10586.044164957399, 86292.29276534545, 64649.500462288146, 84671.88242570274, 21100.170356096016, 31301.942073586775, 64809.263467086486, 44476.70614797257, 42353.62263004022, 22789.665793830784, 6082.5959458087, 670.8905723897929, 7136.072045038555, 71981.51274030466, 74289.44383289415, 21388.93601801548, 24967.93335314743, 92271.56557164385, 94130.16255307135, 18885.983715416667, 22160.518712151177, 22367.607602479176, 95949.79411183127, 16774.072139988173, 39109.181750531665, 75433.37915154392, 37581.78133027016, 3792.094660246148, 18563.487114754986, 90754.11411324338, 51403.97368750412, 86826.66999756798, 47080.78907415103, 39652.22624865833, 33407.087043261075, 77490.18661741307, 22869.742484059017, 33768.77951781323, 51937.6979282396, 87244.88331781204]} +{"id": 178, "vector": [28179.17186368197, 42030.53232630354, 8093.226601984882, 44317.31598974039, 77659.57153354104, 67010.58715430406, 67021.99141153743, 83295.67620455511, 14555.790547680048, 1775.6983421655548, 23159.515371628226, 63229.09001941988, 41136.15942325516, 15937.01942500061, 87183.4998597404, 60666.65757378894, 67791.86785694456, 56802.22011830064, 61534.89318222354, 18153.332112996923, 27132.934920174546, 80043.51382380455, 32769.493890030564, 5977.924933461553, 6366.971318870574, 85481.10933984684, 43950.43097572366, 38932.08292756032, 60964.20371661812, 34823.49147084193, 33481.61386658638, 91456.73166192109, 65409.63450421025, 71305.21247842888, 77029.31686683919, 80003.7064063305, 33929.97203528287, 96770.91925348235, 60039.239469395514, 38704.71580069035, 43479.77079276818, 87387.3696460957, 2391.817635621385, 94991.80754047663, 99393.46063117958, 53196.94873440023, 88349.4054147366, 29676.07292304091, 72187.6740143747, 60666.45152758142, 82658.00221597959, 38373.11844156079, 98115.24301662011, 71981.04413731671, 79664.81938073633, 81243.70762980072, 79779.61941404613, 93388.36784648504, 49295.52232233795, 19720.801194407333, 31498.52677340883, 95121.71219829637, 87795.78962592456, 26275.16942195053, 99539.53275329404, 95723.55268546988, 15668.33974149614, 98878.93786023273, 69565.94625302785, 50332.325237674966, 73958.29928078158, 4033.7822576762774, 47922.792092272284, 56817.854126873346, 54619.275257410685, 59009.88199918664, 97601.69139427238, 38880.314084240796, 91299.65747500407, 79196.17717652388, 77424.93535565876, 91124.13646098743, 92481.36308569236, 23146.472838072885, 22062.082189795874, 28393.230074095398, 50297.938454285904, 88164.78508251611, 65489.843210374085, 28004.778332234826, 56224.46693262281, 98956.78138996677, 86153.94384109942, 14091.579301834501, 39467.61805929796, 70723.96112570495, 6788.804890044642, 4498.241378058409, 40850.4154431059, 25722.30536322684, 7185.634806341368, 45158.4181600047, 75158.333513677, 87035.5125226283, 88795.70547394692, 66315.5566137964, 98019.84085665259, 1527.693735259883, 8529.22820623747, 48022.82045738392, 31666.18944728441, 78025.64352379326, 77448.27437293404, 53685.459530256, 4604.256938736396, 11517.725711845205, 60180.17663766067, 11536.389820032811, 59957.71672885781, 73202.36811701288, 70127.73948053116, 5551.524955654863, 46415.00059952617, 4295.374233232096, 40108.79346322589, 49016.63202803672, 32391.28862940649, 6611.49462278191]} +{"id": 36, "vector": [20084.38442827034, 85908.75125987313, 61592.04774587349, 77514.41269206675, 29139.41907103301, 90535.5725095655, 71166.51790239612, 87967.41156165447, 30448.545541867778, 41008.2036822746, 75264.8729017518, 35480.24594857047, 83894.90053369309, 80982.02040278241, 88884.31419583561, 68991.47903497478, 43736.91052622409, 8389.092681308819, 36874.06271400553, 89729.75312561533, 86418.17041992935, 43022.70268835433, 91562.33936148463, 33803.01169334322, 43656.748614138705, 24059.427741737905, 91142.12601222415, 51731.17693340429, 6099.593712291163, 35970.38499890559, 25944.59859815821, 392.9542908311712, 76259.89814156578, 18088.341654961805, 52717.5695446981, 87503.09626245723, 17187.35735053075, 35776.27196819781, 98592.7237385488, 56462.358467739, 64119.4490473853, 48685.95580980235, 57565.38323404212, 90322.19410447708, 62612.760857921094, 19478.347935924023, 7175.286409892756, 52504.95136376334, 24581.883598158816, 873.9278669968487, 52400.166062110155, 79516.37641729943, 6196.935393481773, 3097.709734506893, 37375.81755138626, 81239.2063933369, 77764.30607823224, 40249.28735298842, 24491.137811577802, 91478.63559457456, 86544.18822382393, 27607.085439953993, 50036.63964514857, 67574.97757837456, 99212.15217488924, 24227.82007575032, 9876.80779150213, 78799.91413597798, 42095.71820318227, 10562.89917934432, 40348.540040266445, 1379.3836084648237, 82456.26107586361, 80361.00585860216, 84487.45953755811, 5978.513996902224, 19734.09595692346, 72488.25827973863, 3441.5431550965936, 87765.01258612679, 1368.2546854290156, 85678.91610065308, 77639.5298890088, 98200.66047971754, 50930.27279264086, 99360.44393702611, 51501.415730888955, 8628.362191634342, 51578.72899698847, 9890.711496188465, 9539.280335470856, 84864.51690877372, 32045.48867663669, 95601.94224980797, 34304.878568612585, 47139.2558316946, 979.7077737831339, 82896.20773108787, 41252.54921563438, 91864.89271656096, 80918.66110012961, 44888.70178731651, 16604.90689809946, 79757.08953765802, 53656.28666208785, 79205.58527156836, 65727.97295541604, 33348.2480850156, 19144.16452765839, 52676.309202524695, 21500.311336659972, 17465.587561673423, 62354.91894061465, 30342.672527915227, 55687.178545254166, 34385.1132401898, 35318.65058862219, 76212.3922823424, 79983.32479309864, 29798.825267215212, 53446.298384361035, 21228.881383382657, 50170.26546598043, 31852.437281447586, 90001.07461766929, 72249.03607646289, 18590.451451900914, 49387.010567711106]} +{"id": 370, "vector": [69320.09098465648, 98504.70099342735, 53726.32159549762, 16438.12089298665, 64857.69445970263, 3987.483231606237, 27385.390569715717, 60087.68231270177, 54989.06883072881, 95734.01490454227, 58217.191537336665, 71810.31769718186, 18309.44186836372, 65884.29617071834, 74625.74664603961, 56292.22073681102, 65757.79066193385, 79327.20094979087, 23012.535792631596, 57190.9067985325, 20673.421047877673, 3102.610198764355, 37887.053903625456, 36773.139083701535, 37212.30588637182, 52287.852759775276, 19415.05717593511, 65906.07943562295, 36672.196868640414, 72704.24412925336, 16800.209628568853, 17501.32700007585, 21623.600542941047, 45292.91594936528, 87379.88488518338, 3180.8381063288207, 85558.88933249358, 98615.44005144239, 78669.49075947069, 79802.83961878726, 45066.9694258842, 16336.691277088445, 99666.82949421009, 6531.230150652312, 63575.89847863734, 1702.2674986495124, 42053.69747262739, 57885.42338935966, 75363.38202709092, 81309.76526374923, 691.1670178508577, 55581.032713510205, 61083.610413527036, 34602.79252231151, 67299.2336807426, 49026.52433336245, 68343.52370998586, 93547.24351835455, 80380.98940239713, 22039.47681398026, 51690.29875115861, 29402.62983552292, 59377.5086433945, 231.6938506705113, 26855.805400454126, 87124.43373617656, 34650.54804994446, 31740.76152734574, 75545.77021299955, 33653.37280978766, 57473.57174167861, 33424.29078564462, 2002.5457772836153, 84244.7192125329, 42201.882591555965, 51172.52465785521, 10801.884122172889, 12545.448899863542, 9631.146864565788, 21916.11857912944, 41985.027637663276, 20325.81570075952, 61521.85776334176, 80061.85334806888, 47007.003715766405, 47747.5916085857, 33141.33040409608, 11767.162584437818, 20700.99907386993, 66702.91270435552, 60591.36598211042, 36828.30584290081, 44095.42868768897, 27933.240907953004, 62300.41780627752, 77486.19845804214, 13095.03167345687, 14563.764720977024, 51590.11251259625, 30478.5345177245, 1050.5829026448366, 11580.589172264321, 72580.26007425746, 30544.556243420375, 6964.445970442357, 38563.51248956133, 4623.8417011948195, 4907.744002159575, 66310.90722929606, 23689.686260471986, 71320.64705664897, 93086.6229668996, 13371.928208854455, 18203.92859813189, 14427.525904895167, 64004.112079705475, 73313.56239610832, 65174.632739381144, 91783.13600761739, 58379.86772097764, 79788.35682177488, 87735.5045828709, 84192.06952446766, 92302.5487137792, 84446.58115442403, 70118.384329762, 18862.822542950762, 97178.35716383928]} +{"id": 1095, "vector": [35106.39125960771, 92090.23507700271, 57908.80260789613, 11977.989809957968, 13372.88863835705, 92732.61659481842, 33602.432862719055, 76492.72445815684, 37209.39132289824, 70991.58005154668, 21781.250508164783, 94178.00167848362, 10184.076007561827, 67577.37037889527, 20179.903920992692, 86022.31156444567, 13852.913111547949, 44765.5923906259, 78892.28093459616, 11238.6961659537, 82366.09114912164, 44279.190768254, 50131.438673812045, 43562.647757373496, 73703.79527626577, 39736.4191629095, 66234.87261394056, 53747.05257764154, 97059.54447064485, 46889.3166325718, 36154.32773670604, 55854.199098034995, 69699.14112825661, 96864.19473288197, 11935.643214853508, 2723.513845887682, 55848.401179555076, 27814.76699133003, 82213.66612400897, 96362.22708428452, 1260.4976380309329, 6450.931163831119, 1625.0051737619176, 43696.6157774965, 21725.818780886795, 14549.779123356731, 39204.37738178348, 77372.38015935435, 93649.94174816246, 14464.58175796661, 12510.595728308093, 69560.55278596995, 73164.57617345364, 50230.92236686578, 70002.63129091758, 41123.4342098689, 77721.08087998901, 72243.4346969225, 31446.215510436858, 49557.01242160343, 51213.99231500695, 73266.51869130289, 83954.5803790298, 31362.921486243144, 14835.01628546582, 21574.84997526884, 8971.160294233505, 24995.513144802862, 69791.68754025083, 11612.27391629991, 50363.82091694212, 26608.411831509595, 67770.90221528673, 84272.81849320025, 9833.160884655945, 5438.403065039288, 49693.16110079558, 16495.322588721017, 38420.259285152366, 65969.2065892339, 55995.10368531283, 54599.65601350044, 39529.703229653715, 79442.46966931352, 28441.200870853445, 12360.426005416048, 81520.77622620943, 8346.704032537666, 93395.01326652465, 56834.71724573892, 92631.32032042813, 1157.9775780255707, 93693.61782641464, 7597.43870881765, 87746.2539525407, 92762.86638062763, 32463.03897934987, 67198.23183321534, 11715.510934024831, 14894.952039194775, 18719.72130188567, 61196.220101309525, 89278.74369392112, 33299.93487040974, 6042.442160375339, 92768.7615602551, 5444.881500765553, 58156.09270367428, 81598.86704627649, 56393.901636436996, 10307.201526714793, 94248.04314032687, 57044.086323413525, 3238.6920702138223, 8536.206055508366, 41404.703779049945, 95602.93576200739, 68474.72622318237, 97066.71917915481, 97548.5896947995, 90407.57826361754, 43798.691628358945, 1244.962569847019, 65630.58538309623, 98734.34093420896, 60753.7276264849, 93829.81383660674, 7974.662208893624]} +{"id": 1093, "vector": [38057.17028827687, 18163.353821055483, 62013.11826677142, 75034.92376574696, 29947.461313122105, 30310.851343420254, 9910.389264538777, 43357.09434780306, 80514.24260783668, 93809.39887976524, 94564.09973100899, 68790.47404396543, 54034.47059489643, 18031.3603253773, 46230.24432796073, 98175.28138012321, 80242.87221675877, 453.81181734545083, 1172.3888722688037, 37886.70605887761, 58779.721091100786, 40129.07242673829, 22430.36634687099, 24676.685008101784, 72063.3642040577, 89621.20844290555, 67919.6470359168, 57207.61888758136, 50722.29146783064, 91444.60698412852, 18025.933921631055, 23978.529014727046, 95321.52498750224, 29253.268810029087, 53411.28745434659, 82667.6286055601, 96153.47350030231, 75254.06754592882, 56512.679952009625, 98969.91642576215, 91639.01526260155, 54880.79018561609, 21049.136137243142, 53112.38790388062, 53844.00617368031, 43664.42474582243, 93064.02961971554, 82117.39553971733, 23755.417785234557, 9667.203767752897, 37968.06969406893, 96729.44869481308, 94151.29231607034, 34382.87656246718, 38082.43944797809, 36105.81912988705, 9487.698703089798, 41970.810140572314, 41332.969570021414, 39198.301478929265, 50973.48710383728, 98452.38441588305, 10276.402699854803, 7548.263507266651, 80085.0034107043, 51249.23934407144, 58206.10072830521, 11724.727345529396, 18705.31931147771, 83375.4209595337, 27928.225515154038, 94041.06274729293, 42537.02520865931, 267.23391980597586, 90306.2957432461, 56136.12583187063, 66064.60693817092, 69372.49385510801, 10507.380136655964, 9185.797054754397, 11804.970308397578, 62584.944196554534, 85571.0025310115, 82370.97923902754, 10715.266881832042, 92650.36714361145, 94553.32636772079, 38145.54129731217, 53854.88019305755, 1602.3369731687942, 79869.52041129133, 56246.200820361104, 93765.5057755192, 77225.3679343996, 68909.32923323345, 39428.631185072874, 82716.42690017674, 29791.27746165652, 44507.442415448975, 4218.941775984986, 84131.08867171024, 82661.20104947487, 6488.990444973142, 50155.390694546346, 80418.28167366557, 84798.6735100534, 92598.94109866513, 9716.231221330961, 21119.855195190008, 95528.1204488163, 42385.85330156722, 4573.892331243778, 48638.50202496024, 48427.15510634053, 27763.92668612998, 77538.5841080019, 44599.2074431428, 80330.94955731566, 59109.32542029683, 88881.57136383673, 30439.645931494753, 76193.283829026, 13881.059142940532, 24292.161368928566, 67425.64089624063, 95652.06159348071, 61952.61766803536, 78490.67405792639]} +{"id": 444, "vector": [30971.58984012619, 16323.430175806698, 38129.92510622294, 77349.83934897759, 1269.3043718768338, 36623.87206863825, 56026.664520251135, 72879.7029284308, 92538.44529448808, 94554.59119645126, 89597.12063171898, 22321.924433964592, 8602.961879187566, 20511.73320851134, 19767.81120989356, 14330.453934635101, 30067.028698328057, 35716.789904714795, 69042.77604506715, 18861.105919005662, 40320.08916498647, 24101.55243795058, 40650.396438553216, 7831.521224389837, 48359.12128937844, 13709.106529675708, 85428.09427306816, 65854.33859469778, 30021.079468720403, 11533.947526159238, 27549.54059425192, 76193.52120410324, 35210.81793402758, 70369.35696294323, 93264.9945418591, 2791.5120945625536, 15.696039497248293, 39186.50129729201, 25451.54091261215, 36661.93106640232, 65330.97232418287, 49672.02271195057, 52063.59062154894, 24693.80228065725, 55028.73907128097, 13646.666351057413, 6538.680562364308, 61188.156794443836, 53531.5801420702, 25650.198220596678, 54340.891214587304, 82262.89673536968, 82597.05351793293, 53900.28056195211, 24928.9270696895, 75747.11238654378, 80776.1917721387, 40033.80691581162, 54362.49560562191, 35989.96208547883, 76814.44560106426, 44743.22614487949, 5380.856771713694, 44977.028946257655, 20315.678813404793, 59622.964617426966, 714.8151344322229, 17519.844893069192, 40229.11650692641, 90541.18477755293, 1462.873486406868, 56137.53532740783, 86236.95693882946, 15591.237219881537, 24119.05012557025, 27801.170266942467, 38176.88352273186, 61102.287363113894, 14249.760053325777, 88980.0949805285, 49176.59038898826, 51387.75016465047, 70271.29018304501, 65547.06517766159, 501.68583960886747, 24228.14941852631, 57445.441790486315, 121.8824408861785, 96114.25355197648, 66030.4760068997, 30402.042796161066, 7261.848235945956, 11030.171238914565, 20622.048980817155, 6770.937297470814, 15629.644140324483, 57665.81009272067, 40022.600943635814, 16448.35410295725, 48121.034256069725, 2399.1717551112956, 12407.648226961588, 66779.81627646995, 71458.23160899236, 38088.621096515584, 37833.30994967986, 85437.27923739163, 47529.62091778792, 49568.539366388606, 14058.022519684133, 18709.646364325894, 39775.52321737221, 64158.51305082043, 46216.308816682315, 93304.58760304505, 37454.51213261714, 19524.242666396196, 90614.05469396933, 92475.14884841007, 37208.93367185378, 19506.744538296272, 42390.92613748668, 71885.50841385435, 11764.66220723298, 99645.43943986748, 59726.142170729494, 97301.19529450555, 62002.24763472746]} +{"id": 670, "vector": [72888.71490720312, 83637.47003990716, 65337.23935929314, 27071.491652375367, 21048.325764576668, 59670.74713561512, 45612.32535169478, 67353.92587501156, 1099.0251265114548, 49969.32381756931, 49686.677228257846, 28048.15410328537, 90710.50152501969, 16454.38773044593, 74471.71250315041, 76627.43418683676, 29442.540726097177, 54572.36297858293, 648.1249680518664, 41757.91989092751, 82829.07357962799, 82530.35046716568, 23224.57147510646, 80146.35765252158, 6508.822019520099, 83144.55699444344, 70219.19289679285, 78801.15133160738, 45346.01819570911, 14590.831411709005, 6068.098367942587, 97895.52992134361, 8042.910350444954, 9486.959618683777, 71580.9569391306, 26308.395014625617, 91221.5735691936, 75797.06025968387, 18475.018995178805, 19818.25342829533, 51372.3296308765, 50832.74906162736, 72406.90423071694, 74563.2989956673, 33646.779338461005, 32921.62571447856, 38552.983965168154, 9988.090428917962, 18308.196171969106, 66752.6874320731, 30597.37109799425, 85114.45294825766, 72917.63092308464, 85296.22976813524, 35893.83455967554, 235.27699137975145, 51589.29627605877, 50645.92732194874, 54868.67945099687, 58033.61579508155, 33357.99971910538, 13334.607100574602, 93028.86400422073, 44551.75631705879, 66125.5341828238, 41382.43667688403, 85949.97293828295, 24741.083022292576, 20762.09389269703, 93139.14795562289, 7795.840935576526, 99350.052774938, 69172.59099638493, 82596.38620872208, 12.374871165465873, 50985.53641295341, 54790.61125034537, 86343.20113917328, 23392.85630318364, 44769.343182892764, 39130.96201673539, 67166.60458298361, 22074.022417525186, 80719.2767996667, 92943.61436778572, 14664.147403176108, 72728.85501944386, 31747.940467337943, 81378.02049431833, 9298.7192522479, 4287.342567397401, 34861.61033492049, 17166.324343597462, 45841.28631902373, 48452.13674373095, 46968.08547747215, 97819.91688552784, 31693.212902500247, 38669.79471539578, 15525.073613057983, 77082.25846043308, 52322.187301779755, 32293.984612604065, 88311.47601228509, 86557.80518663673, 58083.524355843176, 21058.59354773565, 42267.346617371484, 44071.164487924776, 306.13040523798094, 47362.06958848682, 30713.27002525298, 11735.23055410075, 68978.72184982938, 14389.612508943472, 33960.25241580257, 37210.59120981225, 39689.92570240362, 45020.45679372836, 31964.890433064953, 55328.481081595135, 27055.280593486776, 45982.50035489204, 60789.6070560511, 14770.268501338813, 79300.41902948011, 56130.20214926623, 49681.937079550866]} +{"id": 831, "vector": [40632.67261659853, 43605.07145452677, 94094.97067477005, 25885.762703766224, 19353.075725847026, 19163.11599192705, 20331.256102283747, 6592.71547157314, 29982.385759855988, 44180.10656886648, 56640.50660602178, 428.6200063639778, 62379.79992431786, 28469.90466810796, 26509.744398673098, 20245.363554007756, 8724.16286492579, 19869.90414541877, 79547.43100937238, 37215.38903767066, 37256.463791411945, 25754.618518836814, 39097.261457752975, 89649.20728727801, 97913.76627370545, 3073.5811212688536, 49740.38990256383, 81359.4483256357, 26063.44209104422, 93728.40794521556, 70887.56588368973, 11274.6702267845, 68732.22203448619, 71470.83748679039, 40958.86975396144, 21744.879201582036, 42729.483724486636, 56559.535629946724, 58346.04727049083, 53751.460923783525, 75039.09694960905, 26396.138361749, 54548.58637231533, 97381.57611839463, 73479.72690623134, 98724.68968396074, 98917.79961055884, 85058.81815842472, 98595.49773398216, 11730.353760456868, 78950.13835007932, 76601.10726242256, 13240.096263841817, 87912.12041274527, 91883.95601087385, 64651.073858026175, 85850.8789134862, 81282.7874858391, 49315.45473769532, 2491.0021980111273, 40096.49119916167, 79681.43285037029, 38972.08281674031, 71287.58743293874, 74192.15552110084, 33584.21644526843, 95499.65433051776, 23169.431887625604, 32825.27590771773, 90901.95422480766, 42563.86182676587, 99630.1504462545, 14965.73520672866, 2938.806709059183, 75061.7310232739, 93087.25466850666, 96887.23876338881, 82289.0348358521, 91375.98696413032, 66587.72471536424, 22145.040598455635, 70747.60234094392, 54796.94534316561, 86489.65369210484, 18969.76433282418, 17634.900099685423, 47217.58090868895, 4167.474914001678, 34233.96486469591, 77317.59136142595, 66122.06525776949, 16321.663408366538, 85838.64906436097, 51828.56230245777, 66366.16024127293, 84178.1455866021, 52505.141415101905, 7659.4258932483835, 58004.2633643186, 29563.951938439426, 10474.766013072156, 59123.68405546686, 2495.048972513314, 94532.40172004406, 76535.82608936816, 77752.4295307398, 92263.94731561319, 16429.42749772892, 17948.1770755034, 45938.12136918751, 63814.41924580381, 94340.30528903649, 89050.95485842241, 87574.4989620897, 68677.07728894292, 53721.282576376, 97284.06518183424, 56268.058480066604, 66659.16613248059, 24545.06709421368, 79113.1262601805, 46273.611157624386, 25488.719362513988, 47064.65994011493, 33464.520562323116, 2778.5363583108124, 90854.64226146547, 76582.31656417053]} +{"id": 819, "vector": [32047.293466221327, 32454.66088084026, 57813.317166785506, 65614.31192623085, 29516.82479515646, 64296.41743061225, 55028.91896155173, 64466.9948641349, 50138.065896450156, 13841.246234672733, 63469.3722948699, 82306.26068363282, 85513.09456681697, 53414.22653124208, 90633.96665730955, 16854.188277367786, 57009.63024301541, 62676.901234986784, 21351.58473943456, 33763.18903646822, 4753.580691750059, 42957.20981662392, 58031.047424574164, 32955.04463086446, 17574.16215561456, 15990.535749436374, 75459.09501101448, 27216.566028862166, 88912.75898188894, 31344.444651767044, 76590.10860492544, 3920.988503011702, 43334.22806518862, 58457.25190426501, 95406.09627010023, 55897.65293178888, 75591.08045426005, 15350.540072189367, 30290.959893834068, 98182.7858605578, 57965.3370549781, 26990.013919107547, 59873.463642237766, 66667.7079520907, 30508.046925888168, 64509.81790847093, 5888.159670424841, 38869.822336452235, 13106.921005140992, 6525.37873619109, 4630.029900587329, 85413.14984330663, 69393.26201477098, 18135.384440954593, 26590.05566422011, 4629.459321026452, 54825.80158437788, 81121.14783045494, 29821.779062323672, 15761.56240352794, 80909.16456509747, 32067.271981012535, 60364.73545677522, 25507.344313658465, 38459.9587388613, 66441.99906924734, 49845.88950297986, 82515.41691130711, 87050.56447269149, 92684.24381901498, 33188.63745887505, 69100.03152677299, 50468.92110454955, 10501.93190476093, 611.6214418508071, 57659.27457323534, 10706.549409560052, 55585.118947989264, 55232.465637393565, 51832.486864449966, 5037.52527679594, 41143.86660851114, 43260.3203686577, 5700.054384446429, 98894.17531768307, 94058.92439303806, 81057.37513679761, 13867.138973446503, 55153.878065392746, 84791.80504124404, 7368.49400451971, 30534.035372743252, 64893.02732601233, 57064.95195697409, 95246.92418982199, 66528.94519611303, 31720.435048351927, 34411.398698543526, 38970.48518888341, 23870.117239810428, 8694.4227298148, 2044.3726066704082, 78488.69733360894, 36146.70992200339, 53927.11625095644, 46438.549087456195, 37742.11033826267, 30077.71029996397, 12975.948513480394, 11355.383753711201, 9882.23380249038, 82272.32715587827, 55999.066860813095, 59679.278939363634, 85414.26997466039, 61872.15657563179, 15162.730204657171, 65113.46451408766, 61601.98664285697, 22480.84282138847, 13849.51830785034, 28888.90533044851, 62776.50193114499, 59585.57535884722, 93642.00289374457, 57358.46647563455, 90086.72310892527, 99863.7914916999]} +{"id": 271, "vector": [15243.055724279697, 34822.93816461179, 16331.776663040599, 35378.03855929058, 14698.635349065293, 54056.264031632694, 59066.16592213898, 3163.1327623809048, 17938.866929174124, 89915.88157054689, 2844.6286335161, 35746.989326022675, 88335.87719744195, 39619.89173063203, 50800.72906258702, 10750.419361467722, 62614.74681250588, 67865.98855609774, 76202.35652112412, 27011.83969094445, 24371.95404605117, 38242.7103201476, 97606.55423171801, 53188.72723262091, 14668.836905107508, 4265.23055129554, 80105.26146848072, 44609.594233595475, 80024.38546797959, 29462.806732380875, 43110.69454108316, 60841.417318061394, 63729.254001898305, 51547.07834635393, 74449.87979665448, 55058.991378492625, 29785.58242526911, 34999.716194759756, 74935.8286867941, 55175.12555443579, 54654.64192007361, 86675.62118189338, 54621.78147148345, 6314.528778919793, 7349.4822445971495, 44745.53955543098, 38751.88917128286, 91657.91712069821, 31400.56532397928, 72319.5185091581, 30462.53303713594, 37830.10841625644, 45493.637475448864, 27684.113850786118, 52930.38232542049, 91792.20215707559, 26343.45359344039, 879.7899500975536, 5245.547608335921, 73296.59725468012, 77293.35659007975, 1393.400456706717, 68926.19026215273, 15192.65377475767, 20995.754269568122, 85704.56972276562, 58190.22548979147, 18091.851194873267, 86409.567805482, 38675.814427489975, 88202.97189538987, 24450.176731405947, 63914.97184206339, 91235.8758754596, 32910.241762167, 29492.399379569557, 22449.72962169902, 1294.5520833380076, 36519.263758642395, 28941.37205079721, 33879.16961587807, 27984.24380740977, 52067.517538863394, 51064.177948839184, 87829.95646736673, 68394.88809643008, 93513.43429187029, 72088.54629638721, 11667.702904344145, 41843.576740975965, 97589.19065049714, 24407.54763097083, 64515.517368810484, 8104.2348508349905, 28791.036675905634, 83636.89379619792, 81595.24693499996, 73128.84517775662, 79441.8348131328, 86146.59762972989, 34491.0104275297, 86645.74210273553, 94535.42802417302, 56743.18422784991, 2036.5440184575646, 42740.23187507922, 82855.36006511483, 97439.83438062234, 78772.61049680877, 80815.22886055858, 55491.04653313081, 77278.36290032393, 27033.78881926618, 44110.44195290175, 30877.15327541767, 53130.21727608044, 6111.033265524568, 71913.346449416, 84208.15163632066, 90339.3097365012, 47517.288576313666, 55292.36863646876, 98432.88131619112, 73177.41156514705, 96686.99559896064, 16272.259910221188, 7256.095555973218, 18311.10373053626]} +{"id": 485, "vector": [79164.56014036985, 53358.39149595378, 48318.248995684124, 78736.30831248134, 64255.96292878469, 79522.27627516467, 56557.249157404185, 2077.9459358238882, 285.58226534729505, 97267.26233391608, 68618.58201205939, 79959.90936116326, 14044.662508965677, 58863.50186092141, 57860.21567863459, 28790.223982040876, 57219.67541033682, 42724.6173707746, 74646.49631529527, 41582.63036739118, 68303.70411124008, 3301.087408221026, 38314.46552853863, 15369.741907565216, 39078.09599809747, 4740.285429941038, 71560.40902063088, 67081.6419282036, 61953.13973292362, 28599.569578284845, 50707.25224654041, 729.1772182965727, 84531.34896914421, 21074.640567819413, 26332.099653370646, 49379.26841054109, 17710.58294302107, 91854.7115057993, 65031.75946506399, 17470.701937054135, 92896.08903031364, 29565.128652322124, 82978.38223108271, 63021.22512662391, 44540.72665310066, 32418.587496876306, 58610.545512242395, 9152.053895987943, 31829.87354515967, 92385.4143624535, 6368.106840013965, 86811.72214719933, 29600.945999160966, 92999.39587156111, 13187.092987258542, 42147.74507529793, 67969.51739404375, 96920.26444218068, 48344.51607973064, 78875.74214451297, 51715.49803914981, 60820.05408128471, 14461.785310126274, 38698.053301573396, 55273.48683391442, 77002.97509231787, 39287.926926050386, 14589.281097277928, 61181.950975340085, 84037.51771977657, 76957.4807414984, 70979.03636986479, 61694.88407204551, 81297.78169349906, 12629.79431278135, 41134.16346046027, 38082.95125324313, 89004.18863628476, 21179.804362492494, 7742.072016334667, 17347.08733707159, 96055.8781097529, 29538.17471728449, 42226.67798073474, 3115.1360182289254, 46495.1512691663, 50410.31775013027, 74496.8567063823, 12087.98295742075, 77360.51296962672, 2499.771400183326, 5712.105906985421, 22821.396329381692, 891.7304789959446, 98328.52820963424, 50477.65451693844, 50460.81475560858, 64321.77397577259, 29856.259024483024, 31383.839498290377, 42016.957235330854, 6856.599863062041, 82397.31014356129, 84257.42418246204, 13243.731787199375, 77939.08258153271, 2590.0615555840113, 68552.37444345407, 61864.235377089586, 72435.37432174223, 44822.76574566583, 85026.98856102373, 42136.3243627521, 63427.01285194765, 95827.72905615931, 41521.09551192251, 61402.91216882993, 28552.757404228134, 54459.93233266196, 58182.320123236896, 23401.00418240545, 86252.66341668004, 38423.6995838618, 45598.39604230025, 10496.750427506007, 10410.71719140787, 2488.1782104772874, 55752.68843723033]} +{"id": 751, "vector": [82370.24190946088, 72894.30019451097, 68343.79820765412, 12108.369310818201, 73545.71949971694, 27179.039769436287, 15109.994480300325, 39891.281161191015, 55891.73652388034, 58435.55557556308, 88684.97233098876, 11960.583230226073, 89167.71072153356, 59136.28097955795, 41183.02467610818, 31585.338889199178, 12240.920654740472, 46499.99098908276, 36046.74966166887, 8432.543886022082, 80178.16050489995, 74579.19332030627, 36708.9284955232, 61967.495503058024, 2135.7606891651003, 24760.701948976926, 7462.6644013278565, 32309.883738628265, 51732.05249682398, 60252.556075774824, 2714.0589925753634, 71001.62610795735, 56744.011380645075, 85168.77759709614, 14716.350269932122, 1483.2185880497616, 54904.5201590867, 41035.688442515115, 28946.998525080282, 64908.14317854607, 12393.278490221726, 204.78588854584422, 63344.44076456814, 70047.1199749201, 49224.48646135288, 47525.44650897843, 97739.1647068764, 52973.18915105717, 6410.460786928696, 30360.887175696625, 24859.322483845535, 41689.55270120954, 72082.1613285535, 75885.34716136241, 2697.5412452186156, 6707.199429586774, 9801.769701091056, 69071.05815644853, 55234.2185681289, 73670.59161484492, 40904.89859840375, 57557.49949524571, 28578.766650252586, 40029.80717639153, 90651.83980718013, 40882.31156602822, 65376.387163766856, 85893.19332014622, 7022.1717012102445, 69577.64492591438, 17929.455554931516, 19859.609623072814, 99213.72184636463, 37705.21080835333, 66101.48473978702, 44888.530456009015, 63450.49975675604, 9035.135992765641, 8492.795810924013, 37911.2868486966, 79940.59227933551, 76287.7458907302, 72507.61323004909, 70255.70833497592, 95335.30259736713, 50418.17728513307, 64472.64298333418, 17746.40048124919, 41581.68480026005, 77103.10518995393, 31881.653607962544, 72832.28471182665, 2323.3544643898485, 5735.523615436045, 85981.78822564847, 44201.44149242402, 83909.22290193182, 69067.25169293351, 54367.6920410157, 84962.11760537901, 48342.29936782302, 86636.75909483588, 97944.71214973762, 95784.03227747667, 89221.75384802363, 37263.02543438283, 64673.91126494437, 44531.330168457884, 94413.78751179058, 94863.17403223454, 48846.20978780935, 58986.53023921886, 68686.3759475013, 43306.717074104505, 73954.82934813072, 12947.953157500237, 61903.56168053043, 43922.64885406305, 61553.73160501946, 44847.81091252258, 27147.408568819876, 83819.66853169176, 72336.87114649429, 16833.84029746493, 96623.54072032573, 51802.47903556162, 20653.869078704713, 45622.45003013522]} +{"id": 284, "vector": [29306.24972565996, 82861.34921714263, 71883.38548376856, 17161.317844019606, 41858.85079411788, 51199.836428295894, 48630.25930209664, 24769.15060276713, 38425.08386987903, 9278.414149925673, 14803.144202798812, 6183.099727788011, 40894.93926749888, 55128.66309402511, 61923.07662602632, 67982.12844329361, 5711.14677747534, 34399.0974709025, 94572.01097540883, 31982.436982709783, 56925.67711885035, 62396.62147235243, 29726.73077442194, 71986.8822321632, 99449.91791343442, 93240.82657668188, 59664.511981819065, 11115.285347774829, 10548.811618116872, 29587.16802860806, 22901.1271111062, 30483.397253818745, 76035.90274725949, 84548.07345191715, 95924.42290322931, 87377.9970315074, 34670.81481894389, 58761.64161739787, 26429.469034581245, 56591.61228923495, 53492.81320967866, 74906.24048572498, 38763.85972276523, 31028.45102022209, 4009.862283488352, 68865.7442689211, 1662.3024229200034, 8609.209679869013, 98351.97951385612, 44053.318399213116, 34405.62858689295, 40228.2774601749, 72088.34659156913, 74758.4486538535, 40428.97055049357, 42566.11639388076, 84197.40184405775, 64942.79777175623, 34982.86576435805, 95787.1786458104, 54226.864276270535, 91066.19481390192, 92546.80548967209, 34857.616967455586, 86103.49388169141, 96458.04039905751, 97709.54391092385, 64723.54523296241, 68887.6582029732, 39916.16287844778, 83570.98328920301, 15447.734169464866, 96503.8728429295, 22269.79925243463, 23904.746390225217, 30797.420389193765, 49585.87464985855, 26656.876016392194, 28492.64172874446, 90720.28430012485, 58770.16377991443, 71077.91684637628, 5430.50464895104, 19142.16913047403, 56731.847854664804, 9639.142496816245, 39719.255007956664, 99939.936054689, 87947.87988415104, 26781.723228902454, 14462.524748029848, 58546.51389226645, 76925.44973358091, 97886.46743765827, 12653.211261627861, 1948.4327475202235, 72443.13868316056, 7529.431974655176, 93960.45155914388, 33164.227302124105, 90755.07868772889, 54747.34702721287, 31914.504842064107, 99129.12568504651, 49209.83225855506, 38773.300215987896, 91682.53679978367, 4782.624475169417, 58248.09683198256, 71169.34430589038, 15445.089451458827, 44053.13937729408, 62201.45917292136, 77820.80117532526, 40352.61025833865, 72113.8468392626, 16750.148088453774, 19627.262265952915, 10497.298371858644, 47917.884710873426, 60382.16194798535, 94129.11110327055, 51884.22904169525, 10351.703104231157, 97923.21698103768, 22303.57330249736, 77752.02550796815, 2978.908498515509]} +{"id": 1353, "vector": [96984.75517890921, 36168.78561456134, 51764.602532313154, 41689.229390164626, 20032.188236302096, 85284.3079021805, 61413.49055629043, 39029.05408185199, 52608.335191127466, 63185.858271720615, 23912.69306017526, 32939.759774464226, 92621.83393651678, 65291.91249385047, 38028.44516372407, 26584.865959744795, 26118.039503596767, 36433.387963771434, 63474.31370693011, 28038.26352011338, 23300.64347879157, 55720.4009751797, 40968.97451788702, 2377.6537831563414, 7673.399650586299, 88988.54273470977, 5093.669055243255, 78647.54121383336, 1371.4461441177273, 69530.81312773318, 58828.67020205942, 99222.49446747842, 41270.461903324176, 59633.80287271359, 47708.1186195497, 55974.4826381476, 25099.06161090013, 80517.97027648495, 61822.51642847405, 7549.875589453747, 11675.09177819064, 88220.6592391333, 51184.866394646124, 32777.741300607144, 89324.49095675176, 90589.80761606603, 18299.775760951943, 24037.2610227063, 20603.628269784436, 96449.50097175567, 36217.62388976302, 82078.96541120642, 19620.393583538775, 63840.046513090856, 9.10612206024286, 81094.97813846379, 35356.67203453628, 92261.70800710037, 34170.42423998099, 57866.94961048175, 25888.32209805584, 6928.891097278045, 53848.63486621306, 18605.29546787998, 31102.008000444246, 49028.667885074254, 80017.19354869037, 53385.875158707444, 40073.77655756064, 80289.88135604654, 26709.09660529286, 20746.08391509751, 67578.16967531547, 83994.76532120659, 74724.93066965051, 24955.58498857361, 29549.692473410905, 81357.50548867357, 96673.43060651768, 383.95335184999715, 88134.76509085331, 60186.183494616496, 44232.39824140203, 79490.9507333731, 85289.69516489164, 5171.84247294451, 68851.81063590491, 57689.57755481208, 85868.40832940664, 33636.13860852465, 12647.436224969277, 5107.006757407839, 2926.7139577172306, 81164.11520918571, 56983.54485093222, 78133.42478815439, 27420.280249669493, 44718.90906417824, 97098.5364557662, 52540.22688095805, 18368.717825438187, 77965.13308558696, 32047.075993454142, 51518.932663154614, 23107.24905645589, 94959.95672719467, 76599.50640848435, 36500.55265205078, 98632.09618318654, 55385.77698469408, 28440.61803890917, 67830.60093270625, 15922.352153999831, 1527.9264383167513, 62013.6416036449, 44384.30073867799, 73108.98508035367, 23556.005886411556, 72018.83826676373, 69549.98710324288, 38456.001440228945, 69844.50836905396, 83492.7754428004, 66124.11081193194, 51100.3192065146, 25594.95896051338, 71540.7114388953, 16489.157961794397]} +{"id": 1713, "vector": [34539.15324123832, 54754.09227047302, 75906.34405920983, 71527.72747294231, 32363.049451350467, 29243.338581445012, 80804.78161730296, 83369.70119846163, 69929.98419022243, 97926.34032183589, 14230.461665483395, 87195.50803740081, 16803.50147330124, 93755.57542172015, 88662.6750469675, 70073.16674623577, 39264.36236515474, 57016.37950898696, 61571.52479372916, 94921.01033364591, 57148.19266228324, 52831.06718859685, 74434.43312310026, 88123.35663536278, 16415.605722706572, 98492.6040833003, 82151.13755654411, 9561.208598765936, 62460.1365154785, 8224.890919489491, 83457.40734888155, 54634.77633790775, 76830.14834079701, 96269.69418507392, 91665.96852605458, 78592.98122853419, 26243.230259877437, 49875.02716686774, 46910.852779982306, 73652.2254821485, 59295.59649264089, 59257.746746048964, 63218.03727000228, 30262.14634861425, 25522.456489109623, 84917.14883060115, 92248.79073630777, 80980.74863060722, 24661.291848901525, 32836.64098123847, 42592.21333780042, 29151.406967446124, 88869.31908004633, 80448.1370556953, 42795.411990708744, 31614.975325394444, 41385.07207871803, 8807.226845832673, 59048.8020021866, 19759.4412979767, 72897.46822674116, 83545.32187126247, 62568.42712132336, 16870.31427623499, 63933.33981001489, 45330.92939161218, 70842.0043758374, 98536.36311337833, 38958.94062762635, 98489.08074960778, 81120.35031107836, 67899.83028726395, 11061.846504633699, 99490.42808549282, 52739.561204647725, 83828.65283556884, 96750.30393358348, 93765.41316227941, 73860.0905637653, 8429.124024973344, 26234.487105170534, 92566.9740466702, 45510.739930495714, 18105.168721468977, 80666.18865462173, 41959.043547941335, 2691.988086803465, 27905.678924203305, 37663.191885587745, 33161.70659413498, 37703.136362688136, 7720.850423399573, 50132.27056427928, 76442.18240370281, 52811.01065632017, 98454.84424110154, 22461.792702857554, 29031.813438303954, 57865.07361555538, 29890.756561985752, 37729.833867908215, 74305.12351368512, 59687.76382080291, 8775.952193278625, 26385.959053902807, 70732.22875432565, 35560.9072505594, 1400.1981362467332, 88126.6168026914, 31149.52570042264, 33550.8773903384, 7725.702101356624, 7884.81208471905, 60819.3000400964, 52990.59910178386, 36260.66647508479, 25183.124066339213, 34823.46652288171, 59413.457695037076, 62677.95911937057, 89906.22464677435, 73616.3328795699, 94117.9656556192, 63608.01406422394, 5839.566231287763, 68431.26566716774, 72154.3097851882, 84284.436605102]} +{"id": 450, "vector": [85151.65085853335, 37401.2036391272, 68552.93438301323, 79909.28361459613, 61584.23623248007, 91691.78323679508, 29965.501580390974, 52177.49014828418, 16425.432445878076, 24116.601163533614, 20948.86027251479, 97960.86073653537, 70308.06397252758, 37863.56636884702, 12620.260110391278, 68447.36663386154, 21941.78330762866, 8028.3127184383575, 35632.36010606898, 36177.648994015035, 74773.15866184336, 33085.670853951364, 35576.26925984601, 23428.96395588382, 53151.371321600745, 23478.340935598062, 70151.15714222293, 32481.498480941907, 37233.58566818738, 21718.209185967586, 17963.988488097926, 43905.606050075075, 29976.380727835305, 20752.828149942325, 41317.90511830523, 73037.18289050092, 2016.280773706569, 53184.94127027684, 82778.31271961985, 41091.40049911805, 37490.19556661661, 3679.343674103286, 14038.41300296953, 26544.493407111848, 90663.94687053145, 11890.243363880249, 68289.95569837159, 30378.336083804592, 4082.3685830448376, 98512.76937110687, 73516.92701572235, 11294.233075544324, 60734.55265610255, 84157.89614727009, 43776.276092533684, 42079.580237640366, 26906.10144409389, 29347.39642597104, 60364.25659556753, 37546.641240735415, 24950.35937253298, 95837.76147158488, 96055.8488090441, 37808.529373423924, 23434.52594530161, 82091.08425480731, 48713.48908268066, 76900.09358555498, 93869.83338868857, 58313.46121987899, 94422.96871294042, 91655.02066887308, 89490.97764602475, 36265.96617816017, 68292.47021567829, 11258.01827841154, 58328.211094644575, 40711.36427197316, 58029.115402400246, 43640.53968335259, 76524.98721649274, 86527.65985461592, 55528.71776553794, 70907.51814872812, 58452.641477037185, 21983.026396773264, 77337.8391643424, 23091.579439364275, 50325.02792230636, 66870.20600222293, 84127.87862369532, 92389.99825390904, 88177.5347184161, 67457.7609796607, 28671.90405145349, 91841.12767204225, 71663.66414108856, 4613.440997665175, 44674.8342317862, 2818.722242770311, 11870.363821193108, 33054.93260460148, 13030.29678508063, 97019.7608118345, 35144.8164152095, 25223.986089959948, 97759.10884505039, 2511.500265079036, 63294.90732469873, 33749.61311030338, 48325.08744712987, 13847.913754044872, 89842.86335799495, 3838.249859768428, 59416.89604322207, 14929.829381769589, 22474.117803079975, 89917.45431607512, 69101.25360935739, 38240.90123842392, 95984.68335360056, 5939.653961713731, 14733.796121285981, 31950.906549624913, 98068.44234871783, 52085.00532098177, 80561.50211688949, 79850.65037451911]} +{"id": 243, "vector": [5244.58216161482, 89411.20648844457, 30222.458620536774, 93438.11768662136, 47208.32724912667, 99186.11145843896, 41500.15684008677, 90155.14482163035, 61955.48281941181, 57473.237115330965, 31824.81751852596, 56751.15231381426, 95436.39272292532, 54350.957979247716, 77877.12911658484, 20105.062602947, 75424.37895559931, 53705.26516887022, 65640.82376977747, 26695.050744658056, 8591.23574821522, 60260.34772428434, 56138.01735970605, 87450.32830547055, 34187.86492091406, 41925.25787926537, 96271.65250701603, 74611.71142894051, 95643.3297990225, 66967.27975417982, 19950.776112217718, 60186.852920994585, 21903.178575353155, 45258.88282748722, 35157.844631606364, 18989.98746849194, 15700.329464436834, 35310.159820265544, 85060.36621016028, 78663.50539661541, 48955.79164742232, 70889.8053643898, 93937.122309194, 36884.52498623678, 29773.813488823096, 88099.11242471712, 89996.08685949526, 34353.0511181555, 92602.0031024178, 87276.69402752322, 43657.17317472759, 19057.210582306372, 34206.092723870795, 47032.84778494078, 80297.53673137286, 44049.07023486925, 19495.41827654333, 72072.98894323513, 17415.42333375421, 96764.62184641355, 82080.82086255527, 25208.020000608987, 64586.89209218012, 2078.830820058608, 60748.094883290396, 15335.842257926146, 33109.43447504897, 52409.9300700769, 35728.51706409974, 45451.68067769941, 86109.62273439062, 13736.545583487014, 1817.1665663643143, 29749.11454757857, 63340.52430758426, 90589.54217916781, 64338.63321279365, 51806.0495376475, 26619.7976588599, 77368.24859092731, 56621.49795867346, 55604.55998107161, 38840.01893916022, 79847.28849085557, 89571.86264974206, 75624.67358975766, 19374.196029442013, 64660.087826462666, 66863.10177143238, 123.18804497251091, 35337.96580269695, 2586.0942668746943, 61870.27515429863, 79244.24699397231, 47274.480155114696, 24378.269347212223, 88448.85068061287, 38324.571611439926, 44283.905181204806, 33599.641020056784, 20636.843976130935, 82666.98957219138, 64429.48169022129, 64986.46183100913, 15443.144574532975, 9770.329163757408, 76037.98184462401, 35297.175262439916, 11657.815003859218, 47468.39837354843, 98473.9379160254, 13213.57545952101, 59792.61043085865, 26305.81456984331, 45785.77434918478, 10966.074445727525, 54291.27868786252, 63515.0174042221, 66719.35495411197, 19390.838831018686, 72035.28377352671, 69135.94861976954, 34857.14162867064, 56823.638438063004, 75789.55343051959, 40643.50122133413, 27973.530517843203, 40855.89264071992]} +{"id": 1484, "vector": [40468.69560341511, 50973.382096358764, 56495.470172048415, 84092.83712667848, 50506.179923666816, 11978.49629302883, 95495.13789994996, 58923.49863055363, 91889.71930211455, 3643.827181693693, 29686.878684292762, 88728.53005277325, 79113.38450288438, 32038.07142242103, 21809.16123611443, 57392.70360885526, 41839.08752767595, 28196.600685441288, 2335.174138132212, 12522.241560634351, 33069.54227775587, 79289.1744330791, 74042.93165927462, 56889.17329945472, 41578.599645995484, 36612.561858375026, 15537.959985152283, 13289.784492228175, 91109.87622768623, 52777.18014817012, 14483.31037260534, 1738.834844598791, 27201.296530421314, 19742.33673148703, 82891.93995495234, 46235.220349073905, 50969.806520143524, 334.83840614139115, 64374.08647696697, 75213.51072832292, 44674.03870518076, 64674.91440403618, 22312.873963065405, 20535.171795834052, 11230.61997210043, 7508.971826327138, 6244.346362486753, 72855.07062739371, 1051.977880465904, 59828.32890649238, 32693.90653784998, 19083.13307257299, 45416.57832262177, 82016.44253468502, 84682.65249406097, 56531.11341293204, 76278.85058869446, 58304.93618121196, 92554.34252895342, 88833.13915596779, 50067.42333983275, 54543.527743347986, 58514.7614569763, 42929.48819856092, 71940.80707854724, 76492.2256175833, 92680.11101804319, 3423.550158379485, 10353.817204357862, 63087.44584840204, 39964.48983565587, 3999.656280620989, 57398.902459792436, 78786.67087300602, 85588.13194046737, 15491.73946029716, 45568.75880893181, 4760.747994437853, 28994.475696391277, 49965.822632775715, 11706.771467685918, 14783.300554726486, 70681.06620616463, 20234.02642982243, 58991.70179911672, 94162.3405453591, 78501.97622314494, 17137.755788227416, 98028.67476132093, 26997.036085868054, 11909.587517849073, 66218.3038614563, 98309.88723711878, 10241.217547185344, 22661.026244027737, 95049.26698930358, 2781.9324211085795, 79465.58012386477, 65096.72121811637, 52155.135728562804, 42296.31771715281, 65665.12779194338, 47423.15945581692, 26519.356632845636, 62050.753414919556, 37183.31008957455, 37398.48126808678, 56325.23136322417, 11495.083326140231, 3104.807608289717, 62944.701059283245, 55612.730037736335, 97914.58784524155, 82255.97746147704, 71588.80052595345, 36067.5010454398, 1869.3904551205121, 57936.85625082138, 63092.040919354054, 63895.32833620963, 65859.48744695519, 28871.366053121194, 26045.634398474594, 64345.24712543882, 87267.55501513688, 37341.50782063042, 45738.724169699904, 25717.568324816442]} +{"id": 776, "vector": [20485.55541410536, 26723.267871631517, 81943.69948693766, 27174.657317008932, 92209.29469616372, 42232.14505506212, 20536.081971408672, 53745.65642636701, 98874.41805641478, 67493.3164290119, 82657.49061411589, 93910.93277100468, 50655.992320858786, 36834.17289452826, 71966.68279557007, 81285.08130771006, 10675.689316580294, 64693.445670941626, 25959.434425967553, 9543.082258309421, 9164.611253133991, 28534.93769096971, 80498.18693440435, 42939.7727892887, 67763.25744250906, 32204.164826556193, 54425.97897707483, 83586.90956150503, 44582.46395707238, 48705.191375782844, 32586.812603727845, 53100.710746551726, 69761.64661552024, 9221.890729054372, 16474.799146463225, 62760.61006920417, 68421.94881064091, 84503.97629486742, 10048.48641001701, 64071.11482971134, 92510.10547318144, 45077.51761839619, 42381.84122596388, 28496.366289670284, 322.7561294738335, 77655.97542740159, 92694.54346289732, 71433.21914123493, 40053.146164465616, 80017.31297904876, 76167.62421614956, 33905.662404050265, 76570.77842621402, 61109.563678758874, 76476.15401964329, 8046.262168645735, 4987.22913731926, 45953.66617117527, 26643.262745515094, 34079.7185465196, 23589.691019542246, 89294.55574648203, 62452.05085497421, 13972.498794040323, 53402.19459152612, 54187.61787504482, 22170.828238879436, 80447.40200045696, 23456.41872531654, 3695.3247570936765, 61892.664907392624, 87120.20519676502, 6857.135438568207, 40755.9786676282, 24374.397874793063, 76614.68756952374, 71185.10322154593, 8415.638855982355, 69701.77309703515, 54471.41623462518, 67134.79824782313, 23356.006698050558, 5181.208165954465, 48051.55933422413, 43464.27992387631, 73455.86984500589, 26822.487973271127, 45196.31790982963, 31738.895034222845, 88219.2912090691, 91931.57687472399, 66985.5857994755, 23341.723786021495, 85448.7147102676, 2071.504160897353, 31716.12877363219, 48217.544302538685, 92455.72699677975, 92433.6820098529, 44288.12651817648, 26034.149277011085, 32854.72649813426, 45890.35408076063, 43524.159485085904, 15103.799819774444, 90323.71528522554, 13870.17259071529, 41741.52481064629, 63007.792745543244, 65683.04833502177, 77096.80803951238, 64251.74137635954, 31317.537727478593, 74579.26306577565, 16964.405259403626, 37565.491473112976, 1016.3568386508448, 90065.52525268063, 99850.02834679751, 58292.389191377755, 83881.21256530398, 51084.682560351015, 71065.7236135378, 45448.50942052241, 85553.95165715918, 79610.32078712179, 48685.972240118346, 78069.814279927]} +{"id": 749, "vector": [82398.69524580654, 57108.31333768517, 15236.417747483889, 30066.473373237746, 55055.98069784809, 95383.9124754419, 16588.99324092967, 77550.68008679563, 61557.499042709744, 27939.108842973048, 39313.68813258904, 49623.38114981796, 46276.78353069826, 92262.87786316525, 25775.13207347798, 93881.9225393535, 30691.25651335468, 29400.37326252879, 25272.17633148333, 47672.31033484452, 77216.04576463642, 35629.821583875375, 22505.3887832288, 37426.79945922452, 52280.10762552075, 95216.5604729131, 43706.8108233867, 85526.03608492472, 35431.75994383896, 33333.72214329885, 50252.91789609846, 7068.694859879332, 85364.68248311809, 50868.9347462095, 74912.06721688586, 52149.54943914448, 13665.927905262153, 26907.252632754018, 57545.95369943484, 58994.310031848094, 69986.04981691242, 16580.093940340867, 17207.382375481728, 36903.07142001476, 72691.2851450563, 48696.24759633284, 33734.11953615535, 69561.49171616489, 63909.746092689355, 48880.137836181224, 71688.084759941, 65575.59054271113, 2695.215098612169, 44267.08754099239, 78192.55850185918, 71738.96179568098, 52046.873404834856, 45431.39420044341, 76771.477425586, 14745.661260566334, 62438.89570411556, 89737.82208350906, 93164.08924614891, 68520.0983018546, 95989.19622331367, 26320.811340244778, 37242.63966906486, 30766.8740441293, 49345.38198988488, 68335.00120745604, 52899.86162267515, 51172.05428521824, 68910.19259173486, 76031.93758103011, 25652.023778131006, 56256.36787652586, 56401.45176674641, 70562.42262924388, 23616.70574421747, 72544.42497180033, 7653.901265584317, 33364.12254161213, 70512.04672268663, 40152.01747893745, 13393.522645439392, 94601.86148240855, 41432.04306925351, 97540.93456230883, 22197.032337963796, 27295.46213461511, 3730.3103708547724, 37066.59077735419, 72863.0538778267, 89072.22651260767, 17386.867667794748, 96286.96535095948, 60978.96194230391, 27857.958724811473, 96874.32918024129, 85892.17739218086, 73625.28107063795, 91833.26878870706, 2053.890280587667, 75316.9274999342, 5146.964734143145, 19987.269788076035, 29004.582136147183, 70858.42109239934, 43252.6913110851, 33110.181508290225, 9332.515601353596, 98713.86966848935, 20972.06016003831, 40938.22577890706, 29600.267025499015, 96749.83592450467, 71313.34654151059, 10308.6158545307, 89631.84136628578, 37612.14942156186, 72983.31296712786, 85271.14467447397, 63889.45107982107, 54656.664598747986, 67524.62224968898, 85744.08979291284, 59813.95790777523, 17346.8389690693]} +{"id": 2112, "vector": [7656.692416777478, 18339.244047193704, 46368.29030129159, 33708.28061023933, 98212.81543618381, 8384.903600390136, 75619.62459722454, 91526.48321247917, 15746.75749632658, 85180.3813176473, 80015.55590417792, 86015.690218243, 84819.77508618175, 5341.191483771412, 69751.66544448395, 91651.8800967079, 34439.144419365904, 6658.515216420513, 36065.55760919374, 44448.34067521262, 68356.93898624688, 96622.91702635723, 93720.93379367093, 21053.17375473542, 47110.58509167265, 68070.14326411647, 52032.90202715546, 75594.5776927179, 61072.60745242255, 83695.91806581453, 71287.37685986659, 25235.187139486803, 57032.42050787364, 88058.5853631642, 22943.854792105754, 82587.09088628781, 65149.70944812772, 99205.69455053711, 55003.16474987288, 84014.69344065913, 92281.25180413644, 88634.3757261255, 13147.388740095623, 76935.72007855687, 79218.12888278987, 12745.408388635426, 30242.16060177628, 35385.87463228656, 14425.33865705684, 44271.97919819514, 22245.912840082983, 1360.1885371050337, 46370.7426488259, 8608.425850137603, 31551.489249086273, 26430.32983462661, 43429.02653130616, 47373.47837925797, 14450.980462667796, 1383.0155835551805, 70413.65181951382, 35192.52784961675, 65573.85584379197, 1750.4549376887835, 90447.74405576274, 21496.587071571816, 98570.62256654551, 21723.51158788678, 88735.77923400744, 80233.09245810173, 79201.52284896903, 16441.556531363276, 27403.14884841992, 70682.67166367384, 2549.092012283527, 77668.13932022238, 93302.9351925629, 57096.20537818769, 62538.1967758177, 11001.824481041678, 45302.02821237075, 91940.64243766521, 16520.240663743512, 6450.722247216645, 15677.68138968435, 42408.19018764007, 83933.94188228354, 14968.0714133814, 84932.98241081451, 82303.23398530265, 21802.553445026362, 30299.206142552703, 82093.95581828334, 5765.597533869204, 1878.586927218473, 52852.193634487754, 74173.70063755116, 28977.984730641194, 67957.38404067473, 4731.0537159855, 21095.63058222824, 51747.797347514694, 54155.90620652937, 97715.88795757412, 43967.11504877634, 59723.34890454961, 86551.84858528015, 45681.19891969844, 48270.26651755264, 8854.932081534505, 26838.814941366873, 75174.48119300335, 61532.715865992395, 46872.75551272455, 67373.37939165247, 64645.36970146423, 17663.184370954732, 28212.550083261078, 57286.0048857514, 51273.06212195307, 34143.43174385432, 5351.314119408446, 10105.94444063153, 89754.66514045218, 60864.99448003923, 52372.99326192673, 28272.88400243424, 39359.77825000632]} +{"id": 1977, "vector": [15272.550436462328, 9477.181400724678, 21677.618925428043, 69471.097920067, 46858.38056751464, 74135.44303667443, 54629.74853656687, 38357.70924041227, 92883.35393264795, 78096.96679213535, 33640.409865238784, 3963.946765819515, 1692.3702820845099, 92462.18918120734, 17654.119350216548, 93619.90673931935, 31596.595502002066, 66611.58831654376, 22605.106007619157, 94109.65260505782, 65063.95619850281, 55316.25937218486, 7214.043043185592, 37972.59190707118, 23044.51632949264, 57635.812888251436, 10345.18789608464, 26884.033572129174, 71471.64601349155, 47922.082783033715, 85753.53725641788, 10648.950306601102, 9828.952896787036, 95725.38821388553, 31896.123881738313, 69420.16927567696, 4354.666730145363, 65946.32150324568, 90387.2572452267, 75465.6240792457, 29250.393703566646, 60792.38150337658, 76171.34384650453, 2103.851342992347, 37757.72091695169, 11059.119794482942, 71591.9356485619, 31761.694383657403, 21975.227389829553, 50688.53403934117, 55934.78261220992, 34670.38169360248, 12370.45274695625, 73524.28490985361, 35166.992103809665, 45768.62276488389, 13154.88732085387, 15889.015361974656, 37575.7961266758, 5099.834874439502, 23073.824584448386, 6202.097394195926, 83243.32827676767, 56059.752434285394, 61797.51558072851, 80400.85150430669, 92290.91693377604, 194.04118440369268, 28825.63948245118, 60526.310471401404, 80654.45122423733, 70797.4792488649, 67564.9997915849, 98811.6611933498, 21589.24716548597, 15503.063986070942, 76027.73427245466, 61505.00607934697, 7811.86397493866, 68890.45867318759, 73783.48711140969, 45679.603581335934, 60542.50968542858, 10378.007074459527, 86782.09469552533, 32299.689204970084, 32112.83412872734, 50680.187458629625, 19704.43811306062, 69807.96937875022, 73239.97811391274, 64664.926979068, 15110.403093712566, 94227.81131059927, 33942.41447684771, 46751.71702741514, 20204.522428008175, 82652.52885096906, 58006.948486119436, 8668.354861835647, 89673.19535208176, 95264.60280331, 5438.488468130398, 96537.19723464981, 60095.20432493063, 73787.19288498512, 52532.87351228366, 39577.55317688207, 38288.035037795235, 89364.34095686034, 37372.98636465481, 404.7380669602085, 10903.602600714934, 27417.61571752598, 76493.28150480626, 83683.78397531172, 75306.45233182999, 53718.12821107799, 39669.74931209126, 48440.02102169278, 96720.19417559524, 90073.89154153084, 42008.52567899487, 44792.58284610755, 96314.6208424891, 10327.312164059898, 14991.656517677588, 59632.33991149008]} +{"id": 369, "vector": [34513.71662325294, 7707.363167664272, 37565.696562155485, 54579.94111603103, 16841.15757906519, 53627.31411609162, 88450.20012114815, 89779.89792443544, 24345.506671138715, 49504.536822102564, 2067.3319170849136, 29905.166692526742, 25115.709844400226, 33001.31382918198, 43792.43959897772, 81379.84412737604, 16903.44029859391, 19910.912690830562, 80195.10758415975, 13008.795171073729, 27665.75386123301, 519.3544370853842, 34814.487237377776, 86895.14413268985, 26450.364356930957, 76024.26391760734, 52630.45754522923, 19524.28514487584, 8787.835230217133, 18626.430391757043, 26100.312812879532, 40538.261115580346, 87154.75300497859, 72323.9055530844, 66393.47023306422, 16274.090946859966, 25552.91210689331, 87549.25316665514, 66618.43418308337, 91483.23007239873, 69269.44790172538, 74165.76410658236, 99956.37058892127, 50600.10057317204, 55491.20326396393, 71108.0775067562, 22144.919058833813, 42838.499190354836, 63623.62148972562, 32768.71984672256, 32171.29151631136, 82799.53815923043, 21709.032963621154, 53938.77730636232, 4438.1138291687885, 90694.17865847294, 39184.37660805648, 59169.3339643694, 60829.78131304814, 73337.70496676108, 31719.708407247195, 84876.02688634496, 85522.07942473199, 24412.781973932917, 70137.28079522125, 21884.2941579926, 68312.98620377957, 87083.90160916813, 60098.08440158617, 99742.28240216762, 94382.36605422906, 42531.12341929339, 72039.81549028546, 92000.58981522464, 71308.06605545578, 79892.2678156793, 56830.412900122894, 55617.29835609778, 97497.97153059731, 20217.760379633488, 7874.697778228113, 16012.131647965665, 77667.42373364151, 80017.50984106836, 72897.66365491025, 15117.992034179739, 57318.6241833251, 91676.60549054014, 88046.7065102615, 78394.58644371998, 34805.27730678124, 23121.138428886177, 85916.60539932486, 66473.23218330926, 69603.87474225114, 87934.68567999925, 48539.45153724066, 46158.42507362921, 74259.54071475157, 92288.43328086725, 73260.98400121191, 22649.562888983775, 45036.6198908534, 1626.8826420472114, 98635.41597955007, 86461.93790850771, 1436.7074179985061, 52033.57977341493, 95517.28260409922, 80755.5133350335, 97081.75308176046, 29029.92895042227, 48187.63206370429, 39261.5305466076, 16465.472738369892, 19861.050217313168, 63729.27898384785, 17046.778528558392, 26290.42004725093, 72095.120582212, 22765.667843617655, 86886.18373751252, 44092.51426255274, 83254.54767283727, 96162.10908813523, 81689.029924326, 85650.2004209312, 36634.24675760682]} +{"id": 774, "vector": [12909.965976995896, 48585.07945509586, 42733.2910411072, 11635.309971160845, 3795.6843326071075, 18749.088853923866, 60143.95048161323, 14409.057065482379, 39708.90893294329, 92901.71726204528, 17060.344482321343, 80801.07336091434, 4321.787400524247, 4364.869846037922, 31132.28339296379, 38850.84300169122, 95640.43670589277, 32692.626489655875, 78388.76544870604, 43157.06746223178, 47897.67494769921, 601.3745979468132, 37798.51283213083, 4281.335973256617, 23019.903327219203, 79339.82613716183, 83362.34131242987, 37379.89043784055, 77093.21392254921, 39178.840831434325, 21786.46392058583, 48600.22781217937, 61295.269383936204, 12774.537318144086, 62166.32109055505, 94945.5935794857, 67134.8005462706, 56028.5548204399, 92011.81252931371, 15469.223304492209, 74369.98510694597, 48379.789145870745, 38465.842581764366, 75532.19495933897, 34545.52146716972, 57885.35064300917, 1682.7762092329724, 99589.9023319177, 10036.249928370067, 28693.671634045968, 96231.5006002042, 1040.7518154827212, 74566.432581386, 52118.72688442294, 87705.58439795834, 97666.04695543776, 75664.07300454192, 85143.41685217812, 26122.62565253165, 53002.58298416552, 6277.751674480003, 17632.33447207695, 31800.289155126917, 2075.293921414878, 97156.24460962183, 15161.042801369273, 61044.444280740354, 92899.3108518401, 56983.825980928625, 80441.88421877177, 59567.15357325561, 45833.20766576332, 11914.672788947999, 74800.61358146681, 54200.57040401962, 71475.95614800068, 43472.15525339089, 40424.231211352, 26500.468054103465, 67975.0796252795, 50417.51948254882, 76924.36498063238, 46661.01029985472, 8970.606725688667, 18476.970398133908, 70223.25273414786, 55264.56587851196, 83141.8092554364, 45078.89503828951, 58931.560287982255, 52741.231026374866, 96487.6858265616, 89805.6046348319, 35898.45617045818, 57990.58583439291, 95699.54543583316, 31175.00138787357, 57017.75921944635, 32675.40929866838, 2689.7100967828046, 75849.74453584143, 96468.77258419836, 5120.905640302764, 17169.75314437641, 29608.014215998923, 8390.734915792475, 94150.13118142846, 27170.481202131392, 15994.739732893504, 28071.173985865717, 24957.056863302783, 40448.580902008405, 47923.69941647076, 94827.5639302703, 48690.24504645315, 54023.271079963844, 64069.79534860734, 99994.04607985844, 53032.90768466829, 33435.88412862546, 41808.88841110267, 19311.12106305085, 47960.91271857318, 39406.44440704757, 69498.09059176287, 26757.375234585325, 33409.74998821938, 75772.05971242044]} +{"id": 1864, "vector": [20206.05963738488, 86191.87164959809, 35906.613297183576, 60056.67435210175, 75708.60718947853, 17737.090459521798, 45403.57879797417, 9661.118370514221, 44565.44145812915, 75119.62485966146, 58595.923402034874, 75766.50926484137, 43858.72290077503, 95258.01515140793, 49947.863911936205, 27342.926643882616, 19680.950542982933, 15279.383517287137, 16916.432002529426, 46896.09068624897, 77461.31881000762, 35030.525068460614, 46864.481395243, 60819.24482632131, 32643.542604330956, 83977.02571100657, 97039.40305261106, 42584.90740642233, 19054.3973970132, 94677.84860022896, 58385.360125187835, 9146.025027688165, 33595.860950219416, 9554.551440461046, 72711.05799375956, 9295.397625093738, 8666.957138818765, 72578.20559260726, 97055.32192478361, 43401.40546714225, 77285.23995128872, 88707.82707762375, 15480.512614895315, 91025.36899325693, 89808.19302904954, 10355.806265839185, 60253.35742074056, 86321.15787783994, 49379.76322857762, 84401.87778008312, 15666.452742057902, 39968.35792913035, 84219.06198714406, 8933.477428783754, 88147.4990735358, 78376.0746149091, 30477.89764709953, 60128.11537037876, 81399.86843538353, 76077.01526282202, 70539.53305812884, 43071.95711852312, 99240.4829887659, 34760.94772885781, 64640.94971858767, 44312.3225316569, 57868.399485768095, 49799.15734615061, 8151.490645286197, 33104.5752585384, 68201.76187734201, 91432.2276159836, 82386.60281083948, 18291.13427426403, 87754.28722892063, 38398.93794319209, 32756.61124917556, 77909.09543275215, 10447.141226516254, 10539.258976631494, 22884.598176243686, 38637.44464881861, 9001.986410339157, 80686.358407982, 38618.110774443456, 44040.75193017231, 28564.894325592795, 73028.68086196657, 91976.32651588356, 104.7278264042495, 88556.10992809378, 38096.50853328816, 7948.485567191788, 31902.279476343265, 48696.61473758398, 14145.178305362615, 37476.090105284784, 39841.32537646769, 45627.57861134986, 94418.21003466743, 11641.398415098669, 46779.612068709284, 58065.709394538266, 20650.266445629317, 47590.90655849037, 79987.04925601342, 70474.36755974822, 78430.90948932622, 82472.77789370586, 45095.101387213144, 56665.20752321093, 47492.880413964245, 62294.15461148243, 21220.27895079328, 20109.949107408174, 50403.39299636899, 34496.273309313205, 34125.76621995813, 35991.68268962513, 29549.478243522874, 9850.633637402529, 85293.82094552902, 70415.42313748678, 76882.93325634015, 88870.97622618351, 87313.20256806749, 8007.674439188595, 41563.23211475057]} +{"id": 683, "vector": [12085.363508309232, 73967.65463652613, 80119.31782914267, 38206.64227964658, 44207.514792809954, 60248.0784030108, 32741.74256948953, 68026.22476055284, 40097.03440613051, 59730.30405979944, 94136.69636591428, 98850.12730831625, 86648.52901527227, 59569.87691433891, 2033.9470854714104, 35257.6545528047, 81724.9184999571, 31118.07628463964, 86360.01797689564, 66756.16226691466, 37116.18654692437, 92144.65385225657, 56761.306388722944, 97982.04505288004, 85459.96037944178, 10157.035584259032, 67299.72519276186, 30828.620827877694, 47620.86936978661, 20792.534948930996, 50889.75447861387, 47980.184205912, 99007.39848941231, 7209.987261798289, 37112.601509525935, 36002.40213244578, 16932.42434746265, 84820.01659723377, 51518.51526811338, 2174.701937358514, 27795.667739902565, 17092.885559111382, 56882.37484699714, 33170.290697591765, 80273.34566343096, 95336.44953929912, 45892.48099512433, 64470.240823497836, 23040.588553415266, 61418.35164309898, 13675.300326664308, 58010.967001625926, 54671.882278892444, 17943.912435779395, 85006.02965894705, 47136.24985911232, 62890.16408666683, 26147.347306274936, 26543.60302617398, 47324.596765392314, 17742.865070676016, 84255.92900168398, 15141.69143031655, 76984.90608410804, 76225.75209530616, 83303.86047506635, 29935.379574364328, 94350.18250765197, 39458.79984074435, 13486.521241518134, 74610.6891763545, 8061.510674471495, 7499.573365048073, 1315.9883253393011, 14135.634531888452, 66038.26566527187, 19042.28243216346, 91530.18258607597, 23725.802532779762, 46870.11049620574, 96629.74633861546, 86993.14283833337, 29769.548979338102, 44548.48294078957, 91926.05233891454, 72616.12252227718, 75001.77355488918, 82903.81117969827, 54424.40468644984, 79581.49828585587, 89854.09962992866, 65547.05721208238, 57343.0224969673, 95338.48962995729, 45910.835361206424, 207.38707419801773, 87909.58035585278, 50143.4014406164, 92557.96577664168, 7099.690844271234, 96239.79954314872, 61773.852374397444, 89853.9644982093, 29102.474708573765, 93716.86403382415, 17342.98396273476, 2861.749632355637, 74665.58571531712, 10203.28264818655, 29083.491326124957, 71061.91316946494, 80972.17727630847, 35948.98987142736, 25915.5138369919, 9617.011724794233, 68360.43524425967, 93749.34155282924, 65827.4891470687, 57383.96648197893, 59733.27973698749, 19368.21743206303, 92834.13486719297, 36556.400149259745, 74465.49068209504, 76617.69062351443, 32681.522755867943, 30405.949639797647, 15959.446827817781]} +{"id": 768, "vector": [4645.996983576295, 8261.207034203477, 383.14419388641994, 31159.10529945759, 24108.785963675706, 18608.7352958212, 60374.51063378905, 91813.42600022651, 44815.419824108816, 37512.19989833817, 13203.72490198889, 97827.96592204759, 68909.14268416037, 58642.79714286216, 68476.98377490387, 49745.87382241904, 45120.56715343696, 86117.78456721858, 98302.4291922881, 39050.12520321998, 33041.05518622994, 45456.29391286182, 76311.93978811051, 53066.56207997491, 20010.86997261904, 39561.94686458729, 89044.78845280675, 41095.534615593584, 90682.85272450006, 94088.65015697981, 14179.53439810905, 96435.79084601089, 62022.933342068274, 57565.59143009252, 43551.26040371152, 78820.82960838069, 13442.115465834082, 16676.063659555075, 16864.506549421887, 30494.277339325894, 16688.863446728854, 50663.4721736133, 34611.50143918698, 32508.718898282597, 91043.14541810374, 41732.5836899432, 43437.12068413333, 78291.09678631426, 2772.3297603084898, 82323.4107744834, 72118.56973147807, 59292.679981894755, 64611.24884785757, 46195.0901689399, 561.1983733121328, 23267.018992851587, 21917.381575167383, 9286.344331555263, 58527.79988946104, 47504.83288994608, 43151.26318834025, 6958.355879943878, 79841.38582040936, 94924.70417538649, 5762.574552485644, 95847.23191903431, 19218.813165001804, 55745.32329606439, 70392.80777645842, 40899.933167025, 98097.71573044457, 97351.07957358733, 47075.991873903244, 3056.586309212406, 84046.68842444227, 35849.76564786791, 24065.768427829626, 66935.31838393524, 78944.38722661643, 99240.62367885123, 79888.41601754735, 25092.594684375512, 73239.36148602537, 46926.074149315646, 41635.959233049565, 94182.01806422415, 18610.066967395724, 5626.036337929064, 13353.92290480023, 51197.05638680937, 52119.10047557103, 57403.32651997494, 8641.017095003623, 3591.7594346967176, 51115.357351580045, 9038.118199853629, 3026.459674318671, 95002.69239037206, 95778.38874739682, 34807.18454062046, 25610.206201820194, 84495.1742927013, 58963.11105224462, 3921.725616910299, 20307.08466584862, 80702.94353876429, 33223.03799789348, 10058.977406008751, 82545.62503417839, 81855.5609611526, 88176.87710545801, 76377.62179212498, 96018.82226680095, 13841.188852919462, 86791.06854049882, 89524.75777532234, 50174.95061736805, 38265.094570300505, 53543.66238943044, 38336.40851544916, 8448.543064430525, 45909.138149133665, 880.4761369157976, 62471.24824411513, 61039.85073031757, 89997.37478025623, 83390.70923354122, 91834.28129972132]} +{"id": 1686, "vector": [32187.155056633455, 25893.05505063777, 36594.50523371702, 35396.6266992036, 96178.4418755513, 29008.217771571533, 59365.11307263255, 73820.62707267716, 12012.218401393793, 10330.103272453583, 2896.781212465016, 31100.341492625128, 82579.93360250417, 43874.14021907957, 33529.16451592234, 506.7953549512727, 84883.41055764913, 42373.13457281498, 31292.910479564405, 44630.62660419255, 66671.73542179482, 77876.44858289974, 44360.54680519672, 99964.68214623134, 65999.3779144481, 76259.0193591877, 55289.90478292459, 60660.556081872055, 81533.17742927757, 36561.51817906699, 96051.63496697012, 58355.05731314433, 75322.21618318217, 20718.290282849604, 88357.85605560808, 18975.30337556691, 71637.79887948759, 340.66487266511956, 18461.0469476821, 8928.834121206297, 72106.02602861606, 25081.853222765127, 24907.830719940004, 78227.18190555288, 98884.6745490001, 33399.996400987366, 87803.77148277003, 31554.532423820416, 55925.66185333901, 69947.28891764766, 86828.87419616133, 70987.99551653692, 55290.99871669126, 3780.5079004682884, 76636.02185962057, 23975.507029122688, 70036.88799892756, 50430.57592245139, 63506.796051732206, 4432.577041361563, 85964.23713643677, 39638.100913153496, 36884.2023568386, 98722.02412623525, 65955.5937987086, 21100.06058229854, 52555.86759344119, 44120.4809467574, 82732.93523173689, 88534.6834820308, 80129.24164570715, 23856.062954782574, 61782.04580794848, 68237.83039433611, 56188.74915152788, 66622.95410631613, 52856.29066763161, 69591.87206813814, 81410.1780301082, 181.80322103120704, 15562.705164640955, 9025.76807471911, 4237.4534063054025, 16383.80681678201, 13871.401341783729, 78390.71915297571, 22314.977326602704, 57691.788579704786, 3907.26676648625, 72095.99115248337, 13174.48868232537, 24650.103744343298, 3183.845009950359, 13795.549076662195, 30392.084473155835, 71382.82195770323, 9127.652855149094, 65515.69122936171, 65462.52877311534, 59196.13523321859, 38035.11934039106, 92501.13224171617, 5121.096717719886, 55288.894734249036, 30883.21279095103, 89066.31544067644, 8249.719965705894, 6807.439910682733, 5698.195771578185, 58607.85780301271, 75884.88412992703, 49763.91289836816, 72423.96730029838, 79094.58538036128, 35583.10419331758, 45091.71131868642, 37783.91256666202, 84362.81893482881, 29244.496958161137, 33658.8456557552, 22210.693017104, 15857.719187674635, 31866.019938308422, 88566.45251220219, 72167.3197568894, 66761.4302517488, 81763.69962322853, 76488.19578571909]} +{"id": 203, "vector": [32497.94363302988, 82231.79471245664, 14834.338895454768, 87656.56395926212, 26804.413312693763, 86718.89373091175, 89495.82182048456, 58525.20658969117, 80250.8926612086, 36437.2380425677, 47994.969860210265, 1725.9137631815856, 20217.341234738327, 37130.068411360015, 41838.49681785202, 49307.970864107054, 14003.328691837025, 91040.11380483334, 86115.19134247288, 25511.62269255587, 83924.15619054312, 89046.9335491186, 31388.122302725074, 41553.54146952903, 3058.9614381626775, 64642.83772631968, 25355.76300112893, 25197.03785635048, 17812.491336448446, 44521.76395445073, 66609.79595507159, 69094.37703504377, 82467.13292433672, 17999.628357473695, 37948.935568880435, 87795.31814138232, 90684.92698185457, 11075.672519195034, 52288.56693025524, 88080.42651282041, 65542.18683221302, 77781.11481768482, 40012.808949227474, 9884.481506083865, 89426.66307461177, 55745.26327196792, 92692.87291602998, 24478.777353480917, 25142.21028614497, 54234.197979199205, 42068.05835653404, 77828.3339642914, 33635.1718817629, 48677.739201758275, 24304.175792182592, 74634.47883389013, 64421.766462067266, 85596.91713658329, 14019.054521134733, 40923.38364061796, 62046.95212293888, 70947.33945544348, 34841.021077092635, 76999.21743318894, 1421.242801541389, 51514.90546986841, 89570.05145342796, 8896.995847379507, 7427.201436440023, 33201.941100632896, 64278.73379817577, 69027.93649594244, 54062.832040637884, 31669.06099725122, 87864.04587175093, 36276.92899422085, 18016.48462790334, 51276.96646520099, 18353.57399843397, 571.9952149787156, 52932.80391062818, 18980.761779150336, 69582.73619047072, 28797.149263808366, 7754.627363682831, 13276.368697176033, 54400.88552549154, 46607.19806721054, 3474.963645938922, 2538.980244317546, 17626.00257748618, 60273.673909043835, 63332.919121565654, 71316.20160887038, 84179.92279930833, 33691.7295164728, 87840.85860825282, 90000.22746411442, 68205.45316965571, 36245.50126364736, 58288.460332936134, 15121.039750434673, 19098.938404901135, 84099.14623903098, 77308.34406346608, 70823.4815848961, 7760.178564647146, 77460.71322948465, 82387.37108825534, 45610.23944913894, 58451.02500985846, 37792.8643937657, 82650.1034336048, 24042.261882189763, 97351.6645139734, 39786.1936165497, 52773.302202066465, 54427.73404420258, 26911.166827550336, 49099.104521421345, 72043.65144333478, 77179.50748125078, 2565.259401716713, 80484.40074920129, 64171.37980741886, 43299.99554051297, 88701.35473085106, 97974.48741699751]} +{"id": 270, "vector": [88023.81611964913, 48165.17858516908, 64286.76891315029, 3430.680815525644, 5710.402515604706, 96590.78250354875, 72036.24270766687, 52116.84901849948, 74323.18116092414, 41249.137874920496, 49649.12097086807, 51453.60993345125, 96799.9742264109, 63899.43395177774, 57595.54159148179, 18721.29602504261, 43194.14120405921, 98592.66035653844, 34247.52280252736, 64059.11370281929, 78954.7639473885, 77946.34140200895, 99116.69228768541, 49479.69267308709, 30103.531817564988, 95672.90137740938, 20334.323863484995, 50628.51226962604, 80426.02726095254, 1349.2741898644024, 1185.9477471272694, 24811.947383005416, 83534.85958005827, 97800.15525363825, 39206.19189722241, 50595.147910252155, 17466.87735964968, 5534.316776598936, 65985.10963127388, 74885.4980313756, 17074.588120035573, 54741.21902015124, 86424.3463262398, 52593.67783956258, 60567.754641912754, 34306.54458930225, 9399.052416850061, 58962.58752354395, 43928.412743269386, 5947.702440532598, 3607.373369538236, 77974.03475730226, 37027.177950380516, 19920.716053559583, 27629.57103480971, 21307.234782528052, 57866.11082980776, 64606.944044737, 97096.82760634921, 23588.49497252321, 24357.007267761353, 23947.142552737423, 31368.616489564847, 46095.90570133614, 17362.620970260123, 69712.54435202347, 82028.96723983131, 83273.65267657625, 34940.51927804412, 80917.00857799432, 59209.87832959046, 48369.70110700073, 32890.82419162186, 69871.15686930709, 19885.232032814736, 88312.37862067617, 14322.58683682225, 62759.795991085266, 49226.14947801063, 40655.52704396786, 55469.00828748803, 88610.65018316686, 8013.028084540674, 37726.522149520715, 50718.99676133849, 99311.42214732355, 28765.236325759415, 44112.02512762842, 65641.56783243382, 8782.899439497938, 30117.645357875135, 87382.95612766239, 70842.06155961897, 41136.76356612288, 4337.250549063887, 37231.68788642711, 10327.330884864805, 47209.97229305923, 61307.20492149763, 41027.5442072371, 63298.467978074004, 4885.432223058583, 22641.78111452131, 83383.94895122426, 61580.663883085785, 78750.59355509815, 23601.539347029953, 43424.72911956134, 60813.29156393275, 63494.68680182276, 98293.32075511124, 18109.014863781325, 76772.6071911112, 3998.8773354176055, 54892.39846064854, 43732.60165906559, 10831.830318229608, 63882.98714042545, 8382.748515210204, 4863.463379229193, 40746.79925750056, 39767.52167557346, 29473.58145765576, 77778.06009731577, 5667.57373333241, 62951.22816143941, 87049.67117752228, 14063.396623674862]} +{"id": 668, "vector": [15785.15222440292, 84020.74975029725, 96774.51395829792, 29063.810537797817, 17892.955266198285, 50592.8244174716, 94913.03371410388, 67144.17887504671, 49934.29132996204, 51531.448298035124, 86328.18439569627, 83491.26134389691, 2113.2851529182985, 61519.55236659245, 14768.973200608592, 55723.844498360275, 40586.970942700354, 19865.076638296232, 36771.917554232656, 55347.37262945047, 23916.6228321678, 95084.54935295209, 61879.00103574113, 19376.22080739322, 7470.450853246624, 98334.60657902379, 82669.7351089661, 6150.797373839112, 35974.14561476765, 59014.10648102089, 94797.10521652624, 71887.45060145852, 63624.22551839498, 31488.021854764393, 6520.535866393207, 49259.626844120365, 97466.65067583707, 88191.35449179076, 86546.74788323695, 97381.41116245255, 30292.67067558933, 94444.36178510092, 22003.171215581107, 99982.69966665414, 93665.69483864134, 96139.27577153746, 4725.32104916632, 929.6698682066507, 86361.76018878388, 42517.806675301654, 91598.4385630984, 37033.205221304874, 11467.793356996935, 79367.94746631259, 2404.3336572050935, 7949.371666549787, 15325.6422545338, 72738.45246741803, 8598.038543560693, 16881.858179429597, 37619.38962249873, 22810.45114094177, 21155.53362999354, 78056.42797348664, 5880.536875458509, 77012.25907223292, 33635.91961792765, 55111.88066065031, 23690.066708941104, 86113.30043157144, 48059.389968637086, 86251.12320839764, 72423.0139975545, 24336.287997287054, 2086.244879391463, 85034.23718099164, 74723.32525367996, 45968.89348911279, 13098.01466264, 96085.22504529767, 11029.204930261016, 59129.893044495075, 27827.87364659003, 90058.04779111309, 68608.47097256972, 6120.174280380841, 54008.655161188144, 19494.48821376596, 95548.23760759938, 91491.59718157796, 3430.7519739152026, 16267.51690340832, 16559.447989167074, 54441.94956157116, 54701.2092237154, 81653.11704538277, 82001.2785341472, 30667.539927358124, 75224.13889915479, 91743.79091649089, 33399.18123836042, 6878.419680924475, 3823.425789998114, 29705.59668440449, 28388.906316595785, 15506.57791105512, 82307.28264600223, 29253.11255772477, 59649.47404827219, 44348.43229034858, 49751.3626787353, 59664.532770897815, 62593.76406009841, 38130.867542695094, 97045.67716696498, 99179.3097554896, 11226.426811366997, 71634.41031389919, 15755.119408280849, 93706.53857894731, 15466.226733915322, 13951.359936808394, 50998.40824773708, 95986.6543148241, 99640.12889254693, 89842.05346810573, 22784.46351324911, 47011.924483371935]} +{"id": 275, "vector": [53073.340089478705, 32556.017062196006, 49148.49151133113, 84922.20249448702, 55780.91476419882, 33386.70088503893, 94408.41367836541, 73459.4925982763, 35279.83038325815, 13869.038857700543, 68842.9857102633, 35899.573731484656, 75754.18778120364, 67635.3446596613, 30295.47787326734, 83630.69597191225, 17210.990188156782, 6945.314513782686, 56327.32177607066, 91891.2756288969, 521.251868556527, 2582.829243832785, 92546.30701419183, 97654.40169972168, 6784.8826213096845, 21869.929945996937, 97830.49097554212, 10058.681908497214, 37835.38368375763, 68165.73464317434, 54750.00451710597, 71681.36925896206, 28732.14222699665, 22033.10082639285, 37698.65070113302, 98310.89150221994, 10671.845498756316, 86098.40421475835, 76639.86799893357, 12686.025006072365, 37451.94894018849, 48448.29859395836, 34870.36074197937, 49688.59495969974, 34273.35315715546, 79043.90329175547, 81411.02087152482, 5558.784183055188, 32597.615958210834, 17817.704690111736, 56496.41140536356, 58162.808748508956, 56095.01387659389, 1885.424527238422, 6708.890745459961, 32232.17114142698, 72108.66653549012, 95735.16984190879, 4576.302259479492, 83910.36935010557, 23901.8419423755, 39083.648441810146, 27037.74785698053, 32543.99307688245, 23703.527018656077, 88073.11641895, 65923.05965548691, 34784.680661373626, 70437.61959714469, 68889.2798660633, 24614.88747055758, 32704.716216137396, 12323.524393331043, 16932.57455358367, 46235.347848238496, 30433.306627016864, 65766.71309340997, 9585.099689489596, 26945.527357314768, 91375.52037148266, 95094.35652794036, 10183.26388645755, 16396.621400376578, 21653.964266313164, 35973.52587582783, 9537.40820328135, 73333.26833644253, 94217.07927264397, 83224.12612035907, 63958.80778463735, 95080.02086344206, 73900.04409774642, 69000.28276058425, 99663.20201433671, 70164.65832993563, 26914.97681329107, 63964.25360886054, 38191.20395262549, 22718.312286995046, 91658.7625106156, 32187.63942345122, 33240.17369968514, 26910.17460231517, 86584.85894532032, 3188.3147110208456, 25915.91326164271, 78567.285996825, 23709.40468674264, 52432.645177294704, 45251.60703655215, 42662.50651713472, 11256.252865278982, 54588.60835112121, 96752.41157632724, 12473.94794247637, 38713.75392846734, 73399.75677317315, 10108.386222140198, 92547.58092980574, 72154.65720851596, 77006.52275350208, 98153.11246232936, 80337.12838046462, 70536.4374687935, 38812.12505841418, 2886.4259304286156, 51137.67030787583, 53547.90036838706]} +{"id": 274, "vector": [2834.6476735205074, 45917.28550501878, 78905.83067359874, 56832.35699509187, 79550.26742144098, 30008.69822657074, 47091.97990053053, 70188.12705233812, 2306.279430276592, 77810.10175857329, 86448.40914492309, 6524.9654032628505, 74065.25215118834, 20113.98136471141, 46117.7720550734, 82508.43786441641, 61955.951150530345, 49562.16284398972, 81872.330318858, 92015.6555252151, 82277.86219056525, 22277.387330559806, 18481.421089437743, 50914.30142144493, 27031.735843028226, 68176.16076383604, 31776.98224489416, 85153.15937061398, 3200.7313150857253, 3733.9497132935717, 80462.24734746966, 18056.453844313182, 68256.78901034825, 17320.101066184678, 30212.1213525521, 23639.50132971393, 10723.555178440569, 1399.9754308049116, 65270.473413229105, 32379.290772301338, 53550.23799294008, 36223.40972748818, 26248.57318003705, 81474.03325523186, 74926.9680384442, 35076.49450570542, 6671.778670266426, 72881.07079518095, 32753.71604802321, 73707.37341493016, 26132.84353852535, 25898.17368983167, 98806.40205302509, 77724.8002885214, 18467.821334793898, 35302.880415254964, 56855.9587249968, 72615.64619222425, 68653.52013920427, 43075.55690042154, 17530.77123581782, 31581.283724928067, 35215.77656438252, 66156.85817729922, 22305.617534640787, 29515.604468188783, 9307.260015918417, 72427.24205029219, 57466.09581800643, 5155.694681780276, 98512.41310285595, 63965.85991984548, 13283.03527525737, 42651.67777948339, 96953.22743350823, 5278.4830056604505, 34515.96124942471, 60809.065312942956, 96833.64501591468, 60534.4442509517, 38764.834024644224, 4452.83147369584, 56811.31326267817, 72159.26121041135, 69352.18538727348, 48652.52814524793, 20648.20280055082, 19727.62549376991, 6632.379804808541, 47937.65070490045, 56574.39406835184, 99137.14485424157, 55287.99415358854, 41745.78989193716, 91973.65143092336, 23696.84764758728, 49730.108046131936, 4814.801046738415, 22283.52561957989, 87813.02439018087, 24874.028978177943, 16284.697231470369, 16465.544041650217, 66255.0845916606, 27064.80833100927, 18539.62734211709, 85637.46741330276, 59628.24693817652, 97081.712243072, 1883.642444698652, 84668.05134730147, 29095.13244510468, 9434.689048263079, 68000.78880487346, 34860.110122563136, 55879.84833330696, 25216.277611899663, 73176.32525338126, 48176.78892136162, 66803.33640545892, 77478.67590883086, 32486.324159402113, 50445.667248724, 57228.17697534062, 81981.19325846872, 72425.04198734301, 55709.0658904265, 79956.29096910296]} +{"id": 1491, "vector": [10533.343710553234, 85116.88629896531, 95767.26414243843, 34624.11190498076, 72070.0536722211, 87648.58910687416, 37437.61524492024, 64463.64089245915, 90014.60748081226, 9691.3335818803, 66476.1220138203, 63889.47727506298, 65385.50585337144, 14408.072691298934, 67206.13681048952, 78288.4657198011, 66184.55758454528, 23251.683046610473, 14401.887180025697, 84287.68611083757, 84951.0654264614, 30164.17002170222, 7162.680565432866, 3392.7789025277157, 26461.104850944484, 9772.976884200702, 64575.120683306144, 9822.40663611309, 79704.72841225946, 18222.65861878577, 85404.12333863838, 94261.74374230923, 57092.32890271683, 75364.91851561674, 33052.78213024585, 99180.83624376243, 42625.78260052915, 44537.41395993489, 93291.58979226298, 9408.721373591588, 84735.81282650302, 61303.72135733983, 64653.57011288209, 43982.85733031688, 27627.02383741876, 20154.962833718095, 10160.503573247115, 13819.366026660296, 67043.97783618444, 639.6419103623318, 90467.82978606457, 48537.41350902412, 66632.48747688132, 18888.187882783513, 41024.693350854825, 16743.746618912213, 33864.31166432068, 36303.21926090684, 1650.2307536053086, 96562.32390455787, 27771.840377133994, 78147.74761598668, 6918.97535313013, 80952.49614584683, 43209.792898873064, 61793.760852133164, 88875.21586415505, 48897.118306812125, 4705.819925847243, 21987.85607971, 74537.50479191577, 96693.87452951109, 57790.477274917575, 93712.98844593149, 94222.89761813378, 29186.429439837902, 97974.78829771822, 14532.840413030646, 63624.691340073034, 92722.44966920538, 60975.72365633481, 41373.567934307684, 42703.28526787883, 24996.90972770612, 2571.8080993913372, 36792.08280559067, 83934.4389549677, 44186.16803148662, 96969.14068544137, 20002.7216017478, 9790.531855075867, 70902.96434979101, 18746.416599120286, 49662.12919520491, 91598.79993793531, 98780.05781830607, 21660.862957815807, 79514.42213049013, 74031.75090648312, 98379.08307983972, 5444.699980212476, 67527.56400066245, 95468.55507991996, 84293.22960952777, 92708.8157862616, 26351.444243925438, 76879.71674502497, 73407.82776411755, 3079.709326255986, 32638.43973601056, 34697.84999635337, 78369.39024182351, 84605.38002957233, 46710.77613604985, 98056.32328666806, 85822.18486556194, 47506.53607877804, 1418.7671031607786, 65977.3305280655, 60551.54367543216, 25848.202108683017, 30552.52077994606, 23926.915420578433, 12402.332297654306, 63680.996654723276, 71985.3705887135, 19579.776080227275, 79787.40295638652]} +{"id": 459, "vector": [88888.65532764555, 47409.1750629406, 2420.0860424372418, 42233.70331962424, 746.5517426573642, 16022.8929463614, 51240.98457935421, 34459.65059494036, 63773.80517631899, 81803.38361053795, 2543.6315193145488, 78660.80801241637, 42325.679901642645, 710.3954103711452, 88887.28734921545, 63490.134228295705, 53934.64665668346, 1484.118098815057, 62706.07770170671, 24009.583855860415, 46535.54518510835, 28969.476606906865, 79027.45550651851, 25763.63238370637, 34829.86989397088, 23146.83639114953, 67091.9304923734, 12710.9911988186, 64819.706281340375, 42304.65853240999, 81780.90746860702, 97603.52184125203, 68480.39513151106, 54936.85365024175, 33952.67310666397, 44843.597500532705, 55367.1139951142, 49415.3833052366, 50553.17444551825, 34572.395423667425, 60897.855606993326, 69460.33411536746, 91224.8489192725, 46419.67786102047, 29858.10028283291, 99022.98979626098, 8447.39926900916, 60013.79444550822, 14567.740617001878, 48530.061974034, 46797.76601876634, 43317.469977520195, 64695.93652554002, 35925.43092136834, 82855.15259231701, 38089.5132265941, 36759.19358901323, 40103.75523585182, 30097.914348528586, 69532.35791388394, 31114.429695691735, 3155.2892667532806, 51086.916765695, 97475.06591767663, 80687.8032632626, 75112.95573598426, 69602.3409257807, 89061.6008075813, 85745.88991431205, 53237.11186021156, 47091.36650379205, 59808.57278636896, 81738.82721486417, 24327.042367063423, 64670.09510556442, 43576.6758286682, 35266.767620323255, 30809.619115345067, 91665.51899970767, 44266.694366325755, 47745.28998641061, 19042.916939905517, 60947.51449933246, 29528.454295702166, 37185.88283356875, 93714.00079947744, 37880.544442237995, 62246.67815787963, 64359.549948805325, 67600.90680393303, 93968.968562578, 40325.77214360192, 74267.90419886021, 51651.642728156876, 78524.33644564498, 41901.69084730174, 89139.94087583333, 69535.42732036895, 44170.62078590431, 8122.2574364928305, 86814.31544459796, 2346.5672201494203, 13606.443276948188, 92739.08296915068, 87452.92883514431, 24354.035638709647, 4533.636418390319, 97540.97869897242, 13888.458114900293, 49811.97075549016, 4512.868650571178, 38134.03182374658, 19889.493181999605, 5947.264226329175, 62079.57652725511, 44690.41333645175, 56529.54926102909, 90349.21254698493, 73394.61448201803, 28730.188260933486, 95117.7620116601, 91167.89579399195, 14291.909808861425, 33586.855716746235, 64638.81286533558, 61675.03859251744, 31765.993246996284, 71155.31499806845]} +{"id": 2029, "vector": [30122.844157544372, 49612.027129524984, 7829.361249021571, 69282.0057918608, 15928.541465689961, 25366.7970687636, 97054.20998362477, 93053.75722074526, 8772.468844755333, 36083.43451793591, 42672.18084362216, 24042.963066669654, 15599.012429779514, 81631.25105261085, 19591.635723513322, 42368.32156571724, 43891.0596574118, 65970.13457722934, 91915.83218129762, 22157.610431388952, 61878.64082643829, 9673.341608179664, 49199.316116492766, 85437.3316319653, 22504.9840463149, 47940.48918134036, 67640.29621217612, 50305.59963581265, 46206.468638895545, 20791.62679740546, 99240.52832178767, 15981.042319135619, 86221.2143975578, 25750.42365749223, 70508.19983191112, 65196.14628112749, 46403.018092325365, 16440.37819672508, 13507.154057375548, 30885.161005614293, 10133.1346118838, 50887.83324697663, 1890.2376772044095, 36367.65328618082, 57087.2232105761, 15056.32068163728, 48268.99191163915, 20919.06366241314, 24826.110259183497, 54052.70802934196, 28611.23293216291, 22910.100610892157, 24818.12650041927, 50818.514847577244, 25738.21283355404, 80658.78003227395, 13426.622499783913, 80978.4170800079, 3764.4496210575194, 43722.10194019486, 9692.68083546968, 57994.572390975605, 10907.81552700808, 38527.21580352358, 56586.785813878836, 50107.79503972203, 14914.06355595395, 63234.397100019, 22378.29276241268, 71675.10913434, 47911.011617247204, 15931.183227577605, 35255.9594181423, 73760.76231466235, 20001.001348665315, 61447.83513571034, 92204.32940887571, 40598.64900261608, 41037.24255766533, 70225.134223953, 72650.53416703735, 68769.20818373119, 40538.16046753393, 37608.02755602384, 24328.18396646935, 8033.605150891854, 71691.19232266494, 66839.55088571404, 62019.7126643382, 72903.53522840742, 76839.12675691744, 8935.716984652687, 21980.503642616968, 34187.601533746296, 47213.12465539212, 238.9834690303516, 53514.18860478071, 38644.34401167189, 20526.971416232864, 35351.08970747342, 48551.63370099964, 36462.90662459104, 97434.76199583686, 66370.15881166288, 48711.82519591499, 5955.820678625745, 5434.564008257825, 74418.33227409933, 84593.65302460513, 34925.29964375042, 44655.87357802896, 38382.048940807676, 96332.65144318156, 71042.70991738547, 9748.653529210505, 19108.991145853095, 14269.491692459591, 2913.8591863396846, 14138.651971767435, 86424.75692145053, 71550.91968663021, 43357.8278657098, 57225.939905333675, 90645.89665678436, 84906.70291096113, 98291.8072058876, 36508.25970547513, 29863.804466799706]} +{"id": 754, "vector": [75570.856784484, 38763.76204415213, 76981.0157425574, 80190.57274392672, 76647.59794837797, 13648.543502030152, 99919.79333261546, 66141.90621704159, 17181.716811404225, 30927.479438737526, 16963.580713301064, 91097.9052816187, 35293.445922942, 60941.65658084686, 91334.5277764839, 97517.89437905718, 63902.32428468996, 827.8780559284438, 8794.14531679672, 86121.51250307655, 52360.18296710501, 23424.94924301295, 11903.718315514589, 76005.34380784603, 91218.53434068394, 90228.00411941113, 71946.36471667157, 49940.64257599574, 84200.01338342324, 3583.9417656200358, 87999.92346351602, 13862.226358729657, 59211.63988269294, 38189.5717459697, 52188.74806847529, 13274.301103042973, 51131.27592640091, 8657.894742915483, 66221.76794115987, 30693.109457209266, 38978.27651210969, 35606.02507830077, 81697.45915142032, 30672.795604280578, 22544.505138854143, 82619.75562586718, 22480.35977042422, 70220.26245770953, 64543.8526790045, 48919.41524488778, 44996.28891157008, 22669.461783358867, 796.472410239546, 58859.90600462828, 20232.188377588, 30885.68814849515, 37510.66364436808, 35384.54282815612, 82475.72963501973, 95017.35804628034, 63403.33504532023, 76913.11501289847, 17033.02817174822, 97438.67587433191, 923.8478133056449, 52220.25455897791, 66336.99852207272, 69078.33475400129, 98414.44293402624, 39949.21023160274, 74044.7352733595, 1964.9400618838354, 12636.715087253247, 16730.706041592402, 80398.9023300641, 47557.11788541267, 58037.75734660631, 65132.523950122464, 5027.308879628834, 46376.83802278244, 48662.87427672369, 38645.46953987341, 81901.87190534537, 25658.888632937284, 56614.561232857915, 57272.02534349639, 58850.0770551224, 92726.48273705965, 97340.07097086293, 44578.76396158609, 63188.426831380595, 17722.826092104304, 1726.7472474176038, 86995.67477091565, 45643.68515830901, 91456.89802312094, 33610.15366116469, 69911.0694454082, 99378.37635166585, 41858.84844587199, 58624.22654712392, 78975.39551516676, 64281.43701021166, 6170.049194810556, 67808.15079693178, 34849.058803905544, 32838.41296707452, 98474.73617450279, 48283.47520317358, 73872.34923971261, 31057.523885235427, 94291.6275285632, 92668.94411829728, 41049.107047343794, 97147.08539059997, 62780.14801113257, 61772.2625385896, 33450.375379320074, 93890.43786643536, 77976.39164960345, 81110.45493806092, 90433.31105776723, 18194.25188726067, 87868.69316179221, 73310.87070708907, 84597.1280756877, 25804.258111121868, 65148.903084644204]} +{"id": 1996, "vector": [73322.13899942895, 68165.81499567836, 98038.76445273147, 5055.5274944912635, 16916.088043119904, 13404.86243936726, 87612.38623928522, 19923.877275426814, 47766.394102453916, 41704.95231419671, 23575.250317799633, 19342.630994638144, 27983.306367317684, 38327.6236725001, 94774.27188390777, 93480.4131043918, 29656.88403787985, 43600.696115858176, 35359.983490295686, 93985.39345089627, 84990.99212140613, 56281.96494479814, 59738.35121997346, 94.15982185849714, 95458.16137088723, 77501.51982437518, 95678.55815610135, 86907.54441328862, 98410.32494560743, 38561.360248191224, 28411.435167668187, 39014.765509302684, 40488.233316013, 36589.91955060693, 6450.737106239501, 74498.82232063139, 99929.57698378715, 26311.536080134534, 52110.03755597092, 86189.09147740382, 34748.920907034306, 43111.21128674377, 23758.481010078693, 11563.005069537568, 78216.22964050501, 64737.90613890328, 64805.29167890562, 40388.47679543156, 85488.17889684843, 18378.63824892163, 16315.365487506439, 3659.1442994861277, 69242.44320759934, 16556.29160484994, 29996.099091735563, 10155.065764125271, 22401.422320060126, 58090.26969415776, 83907.2666420566, 78622.41069586707, 78961.91749392539, 6242.001313488588, 79957.5825078334, 14020.709081800076, 25316.60297834707, 83698.74271541458, 42820.54177018782, 84426.07097708677, 83235.38846930665, 93881.14752195425, 97820.56700039239, 62781.578767255356, 5323.435234143548, 3177.670670690036, 40434.446672869286, 18357.736593011996, 36626.11275299839, 26820.23150896451, 94000.20975049559, 19115.677892183216, 23645.252156931772, 76243.79880025818, 16800.833045618445, 19632.58845151581, 50362.13724998884, 55148.30387209951, 30729.53011268924, 52787.51210565128, 4589.342248907069, 68071.01865191558, 9518.477096192313, 81823.85123657093, 71456.9200340294, 75427.7602114593, 36639.6445904927, 37893.77998224602, 94160.82457160627, 39514.90494923655, 35342.58621516475, 49381.773383594176, 49297.72802513346, 74653.648479463, 95517.23127401136, 82909.15949239217, 30289.333572146636, 44615.71399525516, 26385.34426347572, 51143.366621331385, 31081.97812422696, 15308.464581317992, 51956.08333822466, 20123.49343516253, 93834.96463402457, 69707.78668984029, 99486.1903322636, 86743.53224168957, 61453.110927368725, 97692.7722414324, 94262.88645908693, 16516.525236754576, 61070.245146030065, 74275.42213049867, 35788.09047099602, 65999.81430809686, 21207.763125459376, 68346.19701065782, 21552.053862322562, 37931.59537178673]} +{"id": 209, "vector": [81234.70426436423, 74415.91434586147, 9106.904595529797, 50807.73050715377, 40431.41121498127, 1151.801856741419, 45601.600805498485, 95950.49275388243, 81267.63070009612, 50895.55939910616, 33748.75879717353, 37043.81474204651, 70624.13077088931, 12299.7604999176, 75309.75455474784, 212.87126653403155, 55886.297552078504, 89056.52404931227, 31915.142124739017, 81246.81542190918, 90888.50681266814, 15584.145367707792, 13794.248832028155, 21361.075323819477, 14035.89670156513, 70590.09509121985, 3009.9416903758724, 74948.73950364126, 24478.124496370358, 53982.35617145267, 2935.979637955077, 53558.71344955474, 94402.34113336435, 84584.17918270666, 95046.09821834283, 96109.85247529324, 44716.69835472397, 82267.3537105228, 52302.859877024275, 96785.58495827888, 18579.798613823838, 76298.84766038688, 89141.23225243115, 88472.11358502794, 42520.937995531116, 69161.8447193021, 63004.215365785545, 6896.254095528109, 57111.0545748034, 64387.94020797088, 34343.39586704736, 80368.53572588242, 6541.888357037562, 21222.614276543085, 33471.755671591796, 25885.69500143061, 13594.48768211582, 53480.2362590546, 31718.39371087153, 38711.71594985134, 70072.23555376448, 26658.436047395728, 85452.03142551269, 13343.341326613867, 35173.6766278402, 94483.4572296792, 54345.01402563733, 97684.2956045878, 55771.97543307176, 66172.1064902696, 78744.54119761387, 72892.43850313438, 39086.5802484342, 80403.84537651192, 13932.26256368294, 2578.2757756374617, 72381.79377769494, 12748.319297143362, 41086.61433635726, 7663.650893854346, 38881.28936640674, 92273.55278180841, 21888.190601728675, 71011.15169437033, 42388.28036446623, 60894.79210849405, 50288.94403901691, 16448.59556746774, 49447.536118138865, 84490.09166770495, 93957.8865591809, 87537.81132244956, 77832.70164554106, 47601.23675395299, 37353.58277637931, 60845.765563872024, 15774.774822432291, 47917.256357250815, 62427.37543912682, 34589.277535612484, 27036.24885079806, 17748.898212633525, 28202.818899106896, 8349.035542428963, 13175.077874819795, 15507.100541145213, 7874.1855309704015, 79340.24961065911, 27546.463560137523, 39397.74355498481, 84045.525396297, 98395.20232373575, 95417.11489803554, 52616.633707043635, 23222.990144279654, 97712.42569132258, 22771.545762968126, 47851.650228880106, 10461.666307790107, 81288.6390343039, 48651.405824976726, 7073.13373044951, 27125.381196627506, 59044.051368023946, 99263.7803014818, 56524.24864132582, 53294.46998305943, 72039.56758594321]} +{"id": 145, "vector": [52187.80993532055, 21090.90696356579, 64302.743151090835, 615.5733255605145, 25282.470292734506, 58552.068575879246, 30656.65875380177, 39749.8926338239, 16844.860611288226, 25533.993175731073, 60180.9817709779, 76749.37291001738, 10520.897117655803, 781.4632717616621, 25288.63403672569, 55485.54924398618, 70604.2882979632, 12242.71032769183, 93828.80144756736, 24267.544453560142, 68499.00338780347, 52237.62371647244, 12254.715816272099, 81130.48880037534, 71589.79501310013, 85788.40223526322, 57993.59884262258, 1766.6544291892405, 4763.114006908498, 83901.89149922582, 21869.26365946501, 55858.16938773651, 61096.75262682308, 93278.1237623912, 43471.13038115005, 48265.7542922548, 75805.3460178292, 72593.43778748334, 33843.02481092335, 97268.73309617226, 10084.238321426654, 96313.48416388294, 80222.25576525621, 46886.59823479678, 64395.533074273706, 3897.547997510531, 64745.44341039791, 63363.28420668609, 93157.73476406033, 31023.721361270418, 56287.089646372166, 86098.50564531291, 22449.938039286266, 58448.576370983275, 61425.52219836519, 35332.42629720962, 72215.04963939366, 87714.55451477435, 87924.31900026549, 24412.1067984149, 98098.06644395803, 56126.102020239865, 50649.96198631936, 14040.318142236485, 12394.893568908983, 37425.796838507384, 56453.86408379413, 37795.004301292945, 14301.239912776387, 81344.05327046984, 75877.11812771921, 47608.51627598917, 94272.20135844967, 37643.284825639654, 98031.94760538149, 70076.85425968573, 90036.7458409582, 13680.71184155326, 11148.246085636903, 57028.82651737874, 75702.84668349329, 17053.555701254474, 41288.56865739045, 36149.5719950342, 40842.81146026917, 12434.711992366132, 65265.120234404625, 86371.15854025244, 84832.77869820944, 8532.393044907616, 16793.576113208917, 95551.46081154144, 13188.870825625398, 84245.30377506428, 26842.374180606486, 7840.866703180849, 42755.16781679907, 63735.957083001835, 82325.63860465534, 8115.486804707805, 73414.90925029168, 21261.792584810733, 70062.0850754999, 13897.322346429186, 20089.18861319048, 55259.75482942204, 75012.24940885621, 1205.866940829048, 34231.70220421287, 98853.97994656615, 73642.56110247066, 59555.03251573092, 49961.68472839095, 27892.58469883189, 43178.37568175196, 82905.29331466091, 5135.981573195137, 89127.80861553623, 51311.65468892461, 51637.584701541564, 63499.38972452127, 49199.46269586307, 57888.334012470375, 85564.47720329491, 49730.14898869508, 14996.659307208416, 11295.68648971141, 6347.90768763267]} +{"id": 56, "vector": [25772.5774436112, 56678.44921796455, 58346.10542202921, 14915.149125039728, 63502.7994363353, 14551.341367706173, 59908.68670784786, 25931.082876302415, 1380.6974991989573, 63404.64632385517, 24792.991492609162, 86141.8962861094, 88988.04018810415, 37186.673245341604, 82533.02729616532, 1239.9959191227495, 42529.173982172644, 25484.90756196573, 42248.05135345477, 17369.608682760674, 35146.22671229484, 39442.626896567024, 18546.823539527035, 48147.064796670005, 99967.02813058914, 36730.17829860376, 81856.76698301228, 32582.74522538396, 59516.52967958189, 32920.59153859345, 75452.04461216087, 43279.564064450795, 16024.25987647833, 22860.34390459646, 13077.391268466465, 77118.14009428414, 24749.499217426695, 94159.94710879782, 59151.53186794576, 30506.22888298259, 70108.05905860044, 25942.045426628047, 10556.977634674824, 10440.21389545754, 64704.1241352773, 15590.398107466719, 63872.63604475152, 76353.46840214894, 53744.436390371084, 28947.621541368786, 15854.137435476134, 22226.51958872316, 1891.5435782687396, 19927.395520280545, 63616.47504857345, 1836.0705142415723, 57004.848878560246, 77746.02875511129, 94445.38781343983, 9020.174751657662, 2105.258904417573, 34636.05680207116, 78785.96471592668, 22282.030446066605, 65834.6813227994, 7828.225054745352, 77304.16432912216, 87960.00973571418, 52430.371922519436, 54461.34464215166, 18764.49892613029, 71725.60840115538, 52690.9487431256, 60378.78157759603, 99140.3691270424, 91791.96822180982, 72521.24383219097, 40323.806430884346, 50510.418042272, 93341.40386457666, 78258.87935641668, 30769.045371472093, 4800.067275413933, 23741.192485428575, 25810.93636558185, 84928.38202710003, 62140.67351376872, 8703.103570905236, 7020.31332715699, 80093.97812325387, 46909.470096060235, 2535.913421700731, 91860.10078463597, 4043.0021724600306, 38122.46347127756, 53374.80479289551, 70708.60137649311, 69912.59919255781, 75307.54343122647, 67513.1582434261, 9366.613377026133, 54133.62851719945, 10499.188520286518, 89086.151404733, 34441.21834587073, 35231.550961675675, 20109.670263115364, 60624.0740788068, 12854.89646458421, 68693.92214915235, 54645.68695103768, 1108.7460872661547, 19363.24367384964, 71531.0124021659, 5432.1478855617115, 53533.7377353899, 82060.87750399484, 58849.210800971145, 66523.20247308891, 56677.369913413575, 86882.12367420603, 23692.848274731514, 22038.52940869755, 79442.16335670919, 28696.92018144897, 56562.851561917836, 82212.28516854884, 94801.6758353349]} +{"id": 463, "vector": [10350.683758296009, 90634.21780358593, 59141.5454542529, 72383.39869777538, 65250.98210560855, 83917.40834144647, 58916.46819590567, 55969.84870336319, 63362.41024492068, 15166.377673680232, 80857.1380155647, 37135.753158456566, 9724.873879286522, 51024.769521341885, 48324.912009443564, 32170.93529563647, 70935.4783233682, 41955.093558943125, 80884.98926826894, 80413.89123014688, 79639.93993907733, 88245.82940139757, 6064.961045999217, 38655.0863311103, 51145.84951374621, 98583.35234711258, 85190.113044607, 43139.31766656061, 44339.547895052136, 89861.5116973441, 40373.6759764609, 1228.206271524601, 44402.399188441486, 79607.45031087109, 98628.49079871054, 51721.672978504204, 4299.176191876708, 19425.04381131227, 62909.21277068309, 97606.85082512574, 59375.872547270745, 43620.887761645456, 81650.3650362513, 89622.46068538324, 98984.74277754, 34786.81219242458, 45000.772195956684, 98635.82163008963, 31799.388581085474, 72787.16780988162, 46752.603776627046, 50233.042888006006, 94837.19822545456, 73452.27631255572, 47236.293099188886, 21151.42479894424, 779.4301866186237, 49087.556177019665, 4362.57331341835, 15967.208550627021, 46836.16124287347, 16252.086352346983, 28360.1616365868, 45642.53835635461, 7401.422093896137, 13751.579802043678, 79458.62582826836, 55803.70066616012, 23565.864651872005, 25987.855128084015, 46229.61891443962, 3922.1004427217986, 53110.62938750627, 95520.62304861416, 57813.89136338805, 72946.81392331458, 66974.81638508102, 18836.50564015237, 76611.6082758976, 79327.8150709982, 40555.42336322882, 31191.536137599585, 33246.57497969109, 37118.21186094112, 20861.744341807633, 25551.526135229662, 34591.311290416204, 42848.284727446764, 13966.39764850881, 50141.71760921901, 63734.42814219142, 94327.41809378217, 55091.952165234026, 34913.86697008927, 14324.63450722109, 20322.51302211754, 35506.92317411772, 19205.043754346916, 30683.190215721734, 36183.68303716721, 63708.197888319686, 98373.37824895837, 94348.27299482726, 57702.82079814647, 57709.88202661375, 73194.01958170837, 81779.72332159882, 35178.80475445064, 61731.67455641926, 22554.16241530994, 35337.773106801666, 31431.592717466905, 71942.96138511268, 47870.375427882114, 50940.04674340824, 30814.27628994947, 2400.973292510511, 11505.647338351155, 20847.824570153018, 92443.85893386668, 36257.476897292974, 96664.28048780134, 69305.1599694911, 60510.16421074083, 66142.06247399967, 53277.404631601064, 54558.70468037518, 85390.79798323146]} +{"id": 220, "vector": [84164.36908230344, 66056.74342840955, 690.7449521104447, 36187.827846243104, 22956.27930561671, 32.76800258894541, 39023.40571441632, 50339.804756549536, 37501.14467168897, 7207.501239754543, 89413.12774205422, 6956.384925656367, 47372.95121214073, 94753.78424309414, 92794.810149443, 45742.419834817905, 61318.764572120876, 11618.534756819465, 33789.92061860119, 82709.95652648062, 34510.019631734736, 2491.4916337940076, 75138.63368470865, 29307.55562286643, 69428.21407896018, 63103.7297818553, 68284.3160294104, 44632.68650418242, 38374.16828272, 26557.49799336988, 54923.23443588114, 81762.60946613249, 34944.004771251246, 49516.8340375173, 99178.94968316387, 98920.82591391608, 28184.14088634531, 89511.64849910983, 95083.22493721242, 23808.830383158453, 3280.462761360381, 6088.707250758019, 60812.64565013671, 45282.24399320442, 69491.77179023308, 96517.76072787712, 45631.213059953414, 47711.375437176364, 14578.08680404159, 1938.3015938489789, 97206.3951176967, 26094.203246761084, 24542.898727470718, 25649.626370860846, 35039.482698417676, 33039.49775440659, 48838.70525535068, 81383.30928463998, 39444.50125528103, 68621.38628091439, 57809.477677046365, 87828.07428464227, 93001.02562795067, 91631.6068667069, 25508.928154022426, 57036.08036339187, 71911.44836949566, 36952.43793766141, 54564.32517465584, 71734.88663567624, 96302.46558116526, 61045.2544433065, 42344.212028268244, 23550.623755210785, 87479.9496065505, 94553.87183850535, 7325.162285254371, 54788.08306397549, 67199.41013976435, 56056.453186232444, 93939.42532445953, 62030.82471307398, 93589.24142590926, 36848.24675914231, 10818.781851431559, 91633.91070891068, 76492.42903597807, 42728.05914010779, 53137.57071846423, 80039.29219940292, 96074.78083095, 27284.809504699693, 51911.312599280056, 34496.857040210845, 13613.535953581168, 93841.29704563784, 58771.47257577485, 16626.80950923884, 97612.64790843376, 48560.0518216948, 38631.15945003234, 37113.52570891995, 59939.01309261939, 98867.87769688864, 10426.925119012465, 82896.24929881578, 98384.48617304697, 18815.559880006203, 61989.632736150146, 7551.825723108185, 95151.31994214116, 287.6077132089816, 46444.67785136953, 72068.9068484014, 56426.45620142859, 44349.63646966422, 57455.3364435884, 77000.1614626908, 14823.50935021779, 71284.33433490366, 84051.48405392795, 14789.621072458714, 36869.31401351719, 5628.326090688185, 21147.68805232987, 46736.60289220705, 92880.43614488243, 29287.70870123918]} +{"id": 1731, "vector": [35956.235135835544, 43940.61555456666, 60736.04381931404, 12181.033172385236, 51922.00272985052, 24226.452011888443, 13313.632668128927, 31031.72811407329, 14028.230494188321, 87245.2997437268, 46565.293451285805, 67963.69285618467, 58721.43749176271, 48383.334660973276, 79684.406957312, 42426.99488177769, 37723.46766853615, 84860.29038698529, 68699.00975222308, 18556.112759382493, 24195.227676162933, 23670.308243244497, 29431.269929244132, 88033.7647416802, 56681.31965545779, 3691.8521434299746, 68522.20553479952, 15450.995714254468, 39700.19587182162, 62847.87879258272, 36448.643478438695, 56936.55346531315, 84323.22553443321, 48321.97888221602, 3478.38245082156, 53569.30975228752, 26967.05458072849, 58330.603982962515, 94598.94436945337, 45045.02226220625, 50004.89917009891, 85649.62793297949, 25976.778289196256, 62205.09176266591, 95234.05515884553, 22847.72121376841, 85412.61072016311, 82053.86541448416, 97401.409566586, 22151.007360749507, 6206.704542476759, 51544.14020875322, 71510.62596394429, 71673.21130885696, 77784.04660488295, 29590.30061729533, 81265.30379965561, 25067.469759354877, 48319.23719885679, 36039.366094270066, 52893.08609033838, 53429.85202980632, 64745.65322789274, 19063.104409457443, 50407.19815334067, 91608.54661496668, 50624.12400959373, 61900.83622779785, 6100.375944464087, 20548.681294792736, 69780.84488652344, 598.6156810147025, 95855.66237503152, 50278.200871338166, 74253.44689627417, 20309.240992035626, 57960.94156556386, 73115.86382659416, 22770.961633634968, 90290.6481024293, 40217.312267794536, 61080.55374454463, 70634.32577709832, 2666.6960245382065, 8062.6271713268525, 12091.190763787008, 48995.227350590154, 3679.8085042696216, 84207.60303742411, 56537.62272615167, 75222.18275169843, 44982.33022048309, 61318.292060177206, 92870.38346392852, 67317.9317661283, 3421.935073014781, 67288.75059094733, 54963.06622936828, 79369.0134910734, 98723.01507029621, 52392.051261566, 71304.81813138654, 84717.53006454435, 19049.011415390138, 46985.57015243683, 36948.58192289021, 99087.47791902468, 52763.77154042101, 62730.45695348084, 56310.776186885036, 50527.01957003057, 57127.84805127146, 2100.946783926105, 77806.27574656841, 72273.2364914955, 53207.10134687261, 93732.47134246744, 6994.2879143781165, 15761.557392203851, 55574.20065601295, 38597.09598344745, 35013.547289166025, 62366.198109285135, 6730.2391576083755, 45672.747932736565, 85373.73716759212, 57721.8905939159, 93249.633380978]} +{"id": 373, "vector": [29118.16961442524, 83428.58761746994, 67002.51245201637, 17927.618847378435, 94069.95448848697, 59009.065193669965, 701.164474761129, 51038.98090728043, 43160.53944708933, 45173.14397227585, 88383.20626551942, 43835.855338700414, 67309.60621375235, 57734.61777596785, 54181.77251776047, 93287.90413026774, 1336.1715501847505, 31901.613363112803, 14229.131769885551, 11926.047751606062, 65446.189287881265, 44354.05545012099, 46482.922749666766, 55249.63501325322, 90020.7649035581, 99244.77008117548, 78093.1307684078, 88926.03913032457, 30040.98066831369, 19831.6589112575, 84228.94252507201, 91747.24871710582, 74580.22288989503, 1670.1147553053852, 46049.705011609956, 38990.27841187256, 89615.79983815303, 80661.09300140908, 87184.173393582, 61267.3289574687, 31123.93748722879, 96.36269721257085, 17909.028316626485, 42169.22091840195, 65648.9559841492, 63336.068317041, 88179.51666468423, 75647.6982945589, 9125.181228307667, 74678.67626474067, 96588.12047801692, 35002.161756681715, 23354.146859901524, 1810.8143722688096, 68786.31253992156, 51881.88412063773, 86580.43732873302, 30007.37940969994, 401.9636346355715, 15911.162257769307, 94065.01342264442, 48492.394449215826, 82937.99800629854, 35092.2097434284, 92225.90358923686, 73240.84996015663, 45844.430569311604, 75060.4493111523, 56413.64597940952, 94397.94796072875, 50543.16108277582, 10969.70730584802, 48408.63911898841, 86282.00159837496, 11880.145578106827, 24960.60426578459, 14497.487326236336, 90548.40927797026, 10146.788856476263, 54837.10763611448, 7437.317233075713, 63565.44315971115, 91468.739748089, 9261.695993264186, 96222.85181074367, 26871.77097317772, 93334.50414389763, 90139.01805805387, 22269.27707099644, 39393.44696614049, 68614.42979805739, 77559.50934741454, 12099.780267442273, 29930.454434271523, 40838.13432145988, 14443.165993867224, 57633.741900562454, 70470.58459184977, 1038.5213399279446, 91181.35931447575, 61956.37174018804, 2982.1689538390037, 63604.02751573495, 64971.02303419134, 43994.66604053154, 46275.75663635842, 81612.38460684135, 24340.523699103054, 50624.2428632986, 44518.579171064084, 12477.283553324303, 58445.19397273623, 70910.3682846243, 49881.59036424876, 36935.84369193857, 81189.75647206591, 54109.61792197334, 96007.81487246913, 48843.75550009118, 50890.89030439002, 825.3136113302029, 31969.200246735763, 9879.0143152502, 10680.053615256767, 31371.34689290071, 8476.970094900915, 53846.93864412984, 66915.38429019919]} +{"id": 1729, "vector": [26578.138716902777, 80513.29936659485, 7397.515666183852, 10762.767012790198, 75047.9032156375, 42654.25164397447, 44787.59650076947, 25582.466700986504, 45799.24663128406, 44351.33800043012, 72769.329781446, 93196.88522689344, 33150.914585947525, 47877.44362903795, 96766.8324325102, 52342.809021467176, 7271.32582653306, 889.0330816257564, 91354.87799062562, 21879.168476114854, 19753.75336409788, 80980.50126300675, 88312.2512809601, 91552.3422710684, 31490.629287691518, 4194.346150314099, 22386.954342559096, 18124.21237795896, 98206.34795727384, 12633.56825988019, 29122.654209321485, 77905.1234456719, 10061.577149854083, 16678.34723538626, 57466.96973242983, 49754.82044822559, 64078.76634947937, 2269.443844780561, 24987.387439073784, 18065.070874626865, 97217.72799060919, 58342.6825284205, 75592.35436391554, 24896.473263301632, 52634.16661629031, 79230.87298044347, 92714.32087165072, 63953.9163218359, 4005.103462222559, 54028.01420681403, 9268.319299225735, 99680.35195480603, 37621.40799923721, 72793.61149441244, 62856.07382321337, 36804.36386928002, 64761.02744829598, 22242.31602528305, 86403.32237318797, 85246.59707928282, 23717.474126194127, 87275.91500595027, 27713.126735693673, 51873.315917921595, 99458.28079871584, 64762.52986990105, 85299.18677045824, 61930.60561144418, 26236.05507536828, 42594.9624317565, 97590.53708929202, 36689.75751795503, 96289.06869185735, 87963.18627624703, 41930.39819228194, 94926.46475513009, 12698.667629904847, 13742.379269458648, 23013.347871140733, 39231.951894026264, 25858.361536122844, 92646.54786232043, 78644.35382436887, 51729.894277101885, 2791.204445298434, 86146.72172669602, 4642.996309999991, 89426.9897512651, 24209.398813723026, 68419.04999688503, 94236.37027012931, 75183.85273450948, 3738.317015950998, 70744.20383063694, 24625.92359804722, 6493.572804378545, 21915.080165827207, 71848.30416058553, 43057.6800363662, 25966.858475723566, 92344.70315249666, 98185.7199380973, 24047.177238016404, 81377.22883959382, 75883.69451396404, 72804.2521528242, 49215.709786310166, 1391.2314613135025, 32428.99264359105, 45184.12726810146, 94289.27730560175, 74985.58898943664, 57468.68249401514, 46391.28231726656, 61345.61712561025, 99093.9062945486, 20579.223804007674, 45133.86804383016, 78286.71939153754, 50331.149295133946, 52483.34380889056, 63177.50225015773, 34937.85215016335, 3781.0344375321715, 96442.3586303011, 55577.894812703045, 82814.9815980897, 26096.216531199378]} +{"id": 1760, "vector": [95652.98321596542, 77006.79162986626, 92855.41540589326, 26803.03547011208, 87889.26156823778, 67851.19079222469, 46202.099282006835, 38869.50038204313, 1100.6798503771731, 83764.0540517673, 16617.885015193657, 2270.015513112955, 21799.24241263472, 78956.46437815763, 10768.480350212361, 31144.14686393989, 83857.45621706669, 42798.50929581025, 19357.84695603584, 30384.303009647807, 75885.96590725546, 54853.70963365803, 96471.83138373945, 82498.19345754287, 61966.12159280983, 25780.862944227745, 59041.9265151489, 31938.99041261625, 72540.59791191822, 31479.070111842833, 91410.11473019636, 53368.249304294135, 85475.13834181853, 60678.17513509776, 89265.451831662, 84014.68880903986, 87624.22354247641, 70810.03576210674, 62536.13956477625, 41345.43906534026, 60516.20249696684, 81350.65680126424, 74676.86920360278, 43248.64004752022, 15803.94823764516, 11093.250434211777, 47341.82803588623, 93034.7684111017, 85365.45731503097, 85392.68740000678, 69867.92035248122, 11490.254756082375, 85026.04802375023, 58364.04934761173, 17886.599769948465, 18942.284829344557, 20153.67413942485, 52285.904510876404, 63113.10537642987, 84506.86787984776, 66747.51392914145, 64335.19253766089, 82732.37433597242, 4834.6472895296365, 27604.841975927895, 42449.92566096359, 7263.714816328448, 95636.28800523252, 26817.16948323286, 49539.023276042746, 98329.61534799178, 55637.578307064075, 22265.011188650085, 99888.14064217648, 65207.20599224098, 51965.68474805671, 12267.102916448346, 26854.99202221967, 54044.73335657272, 61541.181532432835, 12199.66411216662, 654.453862816906, 69230.90396897559, 89240.18745128933, 84539.65592638259, 93811.29195504374, 36210.52287804002, 27963.290147269738, 53665.04752067514, 82014.12649640608, 96113.42796518003, 98941.13712136698, 49888.36202243445, 43119.26910658963, 60196.57743101634, 67045.66232149988, 46744.37934965076, 45708.53552094961, 85694.08348507316, 28706.005165438575, 65593.55690774463, 84422.77187597186, 38674.37038043906, 60129.56680903176, 88465.39544416609, 74133.51756802856, 19071.00642770899, 55056.752584153415, 77107.27779852747, 53877.21094402519, 14260.896577205851, 14336.323056218802, 80536.2101621562, 35952.87674203774, 59219.33105656816, 79672.41119708093, 9811.745383439018, 6680.615533318879, 95923.2039041639, 38006.426956538286, 876.0704102685369, 35871.51999602304, 86897.37409629197, 63625.36973014851, 70000.20517388465, 74848.88130011388, 2639.493382417146, 681.4264268199199]} +{"id": 258, "vector": [44620.176191612634, 10605.813863989644, 77946.54732957478, 57364.431334193745, 73581.05962021965, 58053.14654029916, 34343.2707463774, 58082.56249475755, 47121.33679097793, 33593.00201263458, 53829.003627005935, 83165.19722642259, 79447.22932594176, 73480.82400424298, 78999.18577785655, 60178.62313352214, 58473.9268806086, 50953.574728889595, 84096.85778438959, 71529.42285223841, 56357.75009979901, 48324.14731127562, 41304.38354768177, 85590.15879033845, 5796.633513349747, 5314.099467893918, 30566.46959425844, 11635.194194919774, 75276.94019875309, 4454.939929587931, 73403.09760996858, 18890.12262975206, 1641.6178529328085, 18104.684196978928, 42616.32098850396, 88802.31447744896, 87192.37460886173, 88282.2095201801, 85261.51944466507, 42826.43269633392, 55981.31442829563, 11822.30856918155, 18685.955849718182, 40855.35089363769, 87691.95279026212, 11287.441498564565, 66509.56807550784, 66981.09753480312, 81246.69722204347, 42220.19523220685, 86130.08152903055, 91816.64549302495, 89326.6040795162, 24515.59285717818, 31955.30459276803, 32397.963833551756, 64752.748885125155, 31765.308919126546, 19512.24423600305, 38337.52793684011, 6781.794696417532, 50210.70702362136, 69320.96109684564, 87416.08920118945, 54746.01003140685, 56467.590863036785, 15063.530123309089, 49733.86531493491, 11475.20834267446, 32576.84156303823, 92918.58252567897, 76752.88996107788, 78090.38261166002, 24164.507256734345, 97666.13519566158, 81273.30704271236, 56932.235452898974, 19710.368375902588, 76166.56040804173, 57484.97329721536, 41870.07451504622, 37323.45362742591, 32698.805322105418, 58500.20964743764, 71264.55386292622, 9477.925810898525, 50735.85014916826, 8253.134240472027, 33657.88126063267, 90593.95608361563, 82629.96677073845, 34058.993424629545, 51121.53512321468, 75682.72204424374, 90683.51748131232, 71987.64866339604, 94258.65245134672, 68586.37514685601, 64107.741363533125, 67647.98218741964, 10051.269466165657, 60804.81649483448, 13438.477301402907, 55182.95505114166, 9312.262252487491, 77316.30712048747, 74505.6385850407, 36021.82632846253, 91996.42810249566, 32783.80220740861, 42082.98573089354, 116.35452324216811, 44010.75257555601, 1710.155931977575, 50112.81657119022, 22062.35796957937, 75481.85732947962, 89238.29151409521, 72367.0430969245, 51117.42567561463, 57611.03139744091, 66351.46081437473, 47995.08053662449, 75966.63013616983, 19198.020599365507, 15908.65376677445, 82944.98254237177, 92055.0982082324]} +{"id": 1747, "vector": [35846.73528639307, 10092.297566998253, 56288.306538220124, 21428.26809344507, 58143.59941744525, 54720.31912452171, 14999.45162673758, 26507.523861720307, 88974.51512131165, 18855.875570837954, 3928.4650020032454, 82932.3276641598, 58521.748588070564, 48702.533780109705, 95706.84763890397, 90051.63913810125, 94454.94941772602, 16596.00038412974, 2175.5146837499906, 65984.75261931209, 12958.38338427211, 96842.4436604511, 87441.65676194741, 74866.31376752045, 7520.816258690899, 35747.46484692743, 30777.61304959601, 47734.12967303045, 45714.893566657054, 82857.23920477845, 81793.27723415304, 28124.18144710046, 3691.4625827744853, 87858.34315402916, 40646.11539988566, 16999.878291738623, 92374.31898039584, 78864.74401441518, 47820.32148479689, 66061.13174629236, 65706.71692591962, 42247.73401572711, 93795.84987728028, 11022.726073125767, 96197.31292801452, 15076.640204897263, 4071.5753525545574, 34882.339649032736, 31845.19853320842, 69444.30969747638, 60261.40281431444, 58605.4680423823, 70095.02501216017, 64762.11855923586, 36930.10080999448, 43445.172393918605, 72492.90127317251, 84302.75607017266, 47254.16446223585, 30581.736557776283, 71258.56384038129, 42251.32350728361, 38964.98445513617, 87109.53089968594, 99133.61901305946, 1312.9665886218665, 42874.96787593156, 21949.389161017683, 37569.8766691801, 64944.58544143742, 36646.90903371754, 8843.778386106016, 35881.017800244575, 56711.84534316543, 33108.637341903544, 86935.47284840664, 22099.59037779201, 19642.42705090775, 32395.83412374164, 85988.28696353921, 39934.87520812646, 96245.0787875388, 42522.73250847893, 23751.323421964986, 42478.95680848217, 8963.821078933797, 70153.78940974032, 13571.879794468445, 3809.8008114568984, 56.66596814090097, 22161.096295961357, 59742.42400791322, 63693.76546716889, 607.9547904443872, 71311.66028821794, 14439.037668588584, 37056.760868358375, 70691.01529557163, 18892.098519819378, 4487.894648713953, 99484.50686043312, 70992.43049797011, 27574.819688752395, 12689.274495557624, 59305.893709476564, 35475.495870071994, 41511.08407622462, 6837.705655529247, 33384.89834903193, 58978.451786567886, 72662.70526673793, 30970.77042040477, 64413.11912817437, 78575.57378518651, 94788.72422568462, 33851.95105467817, 72424.67179962786, 34902.53407656184, 87451.82658590529, 11821.3858118835, 84257.80124697469, 82849.75648248412, 49673.36294721667, 52224.51974373791, 28988.360944173906, 44720.75545234897, 19149.686234308083, 8446.714370949237]} +{"id": 1248, "vector": [88315.87305036721, 55600.63627779156, 51205.96746535751, 22584.444302886463, 56331.81884873507, 46239.58074424403, 94763.48045978941, 79502.82496997659, 94997.65147493832, 49232.269855853425, 37856.776873747, 12625.067525850087, 17528.367340654284, 68866.26338693946, 37730.24863137842, 69051.34116762801, 13949.90555165201, 39278.072505086704, 35755.00391240846, 89431.73808031555, 29177.694819622368, 52302.11605659403, 9786.021168825255, 49462.39314468326, 5537.1821307878545, 63036.63630738161, 68899.91392372551, 58539.78771262691, 9100.499592348011, 15493.475665983047, 38241.792037541425, 28192.604845492242, 98406.99176245314, 33503.55863601998, 85168.86782469485, 41442.80374106335, 36860.38375471834, 27699.64125949862, 17762.39168819925, 11335.619142034737, 42963.2325695244, 9201.00176245857, 20222.981915621353, 89061.08980446638, 11866.783407603054, 25585.651027007138, 23185.62702924997, 8907.208577865822, 72535.10814597824, 47846.773627042174, 29532.04934387721, 17495.54557268783, 14406.936431452788, 74519.96998470083, 4090.9231580657847, 14196.917004042974, 57860.55308700201, 2109.725142678953, 2278.172282441704, 73360.76729818656, 94223.12041064075, 91046.58002250786, 94379.99459860468, 50913.648500040996, 8060.51558683979, 16738.148660385134, 43627.59810366998, 3714.1348188279853, 78512.42050280876, 24684.108206255063, 15153.178408233736, 50367.50801091872, 73189.41699736095, 29052.759076025304, 31827.654628785862, 38410.63100255202, 76321.06569534341, 74772.98872820035, 33200.13774575852, 40742.962050780385, 17920.812421488074, 32920.82793185236, 37975.92481191338, 28645.38460169517, 47829.563075981365, 89481.4539748892, 56619.48547868435, 91209.84615155257, 8740.259096141523, 41956.881762162746, 46731.67053005776, 94813.86322122015, 85647.02173461528, 71193.88824296586, 20847.665589138887, 81010.88740001523, 61546.41382582136, 29210.87182440857, 47088.998798447625, 43668.15685199873, 73640.45944441571, 73185.7393480668, 93711.00119239648, 48939.761110427506, 76715.58929530231, 70480.18858404209, 86932.06736343454, 74071.36091391668, 44436.99886447356, 84815.4867091826, 72087.7720033392, 58677.05295252881, 43017.79260141093, 46641.22782449103, 5889.967488876679, 54341.77844808774, 29553.820431409706, 42322.20499647965, 85615.07369888958, 84120.75452884432, 26261.181937523557, 71323.93136770632, 34033.85107783917, 81751.70142223466, 43110.43013126036, 11500.49063551457, 16628.03664329664, 4227.441594568926]} +{"id": 431, "vector": [4208.392652384685, 12465.237887363544, 91238.31045894667, 16265.95531505538, 12852.395865637822, 4131.397918121593, 61060.00575771275, 33905.32102095533, 78442.35901643553, 70820.70039424596, 46878.490414049134, 40630.38722027371, 16648.963232636306, 50974.989376670266, 13144.493413745884, 43649.73693043135, 19697.87767076544, 21287.779812244924, 32394.744456925895, 91020.75461951163, 78690.44647489088, 74734.57344371542, 35791.942837279435, 60335.663818785404, 73574.62819206933, 15984.022689438405, 49561.20219326888, 30614.14982835804, 43464.58675884044, 99228.7952537888, 37038.02782705806, 3544.2629392265167, 92934.84464880815, 45970.66073253631, 19094.986711928475, 43336.331597945464, 75393.68862647029, 3553.8964763068725, 92770.7138482325, 99514.92711917256, 96328.2446629813, 64948.840609007755, 19603.838003223616, 16612.592748107192, 99443.30462855862, 3459.994487713958, 61624.44141944502, 28910.515654301773, 75431.95599025048, 24892.092409442335, 33873.44969438226, 47274.29852595992, 70679.82934667586, 54739.22590062777, 31209.254428354172, 28475.372322833802, 26281.9616238515, 69113.28036047483, 41241.82286502521, 24009.371120861255, 96118.33594382889, 67397.43444963443, 90886.3632865267, 97194.98073470694, 4123.807609287044, 99788.70124679821, 15871.24464192171, 72211.68238170432, 75528.7830251648, 20683.821711566387, 60560.5152587806, 86758.1145855541, 52203.130383062904, 51370.53145953952, 31482.033975521972, 29055.00721561157, 58980.497996349746, 20117.34280613685, 89504.5285199524, 27941.0961348874, 73382.1833151689, 85091.21245721605, 31427.674834312656, 24038.20001342921, 50168.090150519296, 26750.053354999414, 50930.809796854846, 13602.960979878553, 80570.47968574686, 5172.44620913736, 70797.05442450261, 11776.24786133794, 67381.83951341879, 92414.19406058377, 13542.989267212713, 26221.80497161174, 61711.12018220256, 94492.11856528945, 61406.776231076255, 69982.22532511945, 32712.407092193895, 46187.68247166006, 441.95124547875554, 85898.22850409578, 54735.94508173056, 53223.84952939283, 16169.19241540874, 51228.171157989185, 82649.72929578339, 31447.050953990973, 69562.53262615741, 60064.47447604405, 67718.37407776616, 92174.40216289925, 48530.850259089275, 93947.29080896552, 45439.86203930497, 41584.285294102374, 21050.14150118043, 84093.85760080132, 29090.08906805983, 25263.905518654585, 82735.70999226026, 73231.46856485948, 75816.52253956498, 61072.8867346065, 6912.169671080115, 3398.3818392861685]} +{"id": 576, "vector": [60519.408961353016, 75144.21576629589, 9687.38951198299, 48213.22860431795, 18016.48202723466, 82294.7968541907, 83064.35813904139, 6757.441634486127, 95131.94849817554, 48914.57882545763, 17151.015486353837, 84573.43364535227, 19737.4944985076, 91706.23317169042, 66264.38839474373, 13456.956665842668, 94810.86610374675, 99279.8330517725, 33559.45628647145, 14433.280601222265, 47269.101985448855, 11928.924451380086, 31419.61893621881, 61561.4542712673, 60937.913372451025, 585.5671732230228, 46782.745697387305, 11354.586346602791, 57143.792710690366, 37502.27627966167, 63870.19617576799, 93793.55962248189, 91352.79693782167, 49104.426977265255, 56381.66936205156, 75449.8441607389, 63044.70218264136, 51179.388796560146, 91490.50909536883, 60862.784605602494, 27312.77871600135, 69809.48713057977, 26598.351625522853, 86819.95783208989, 30565.193877902453, 50231.49485774605, 71760.03734431381, 99064.28200369944, 19889.783268306393, 37870.44267603832, 44727.759073602334, 19702.87575634233, 19472.504242435938, 35150.029290453276, 59840.444970014796, 95637.54825490771, 93453.4471930346, 97926.66063138399, 55160.64969588055, 1662.5175648294799, 78338.79601474793, 1615.49356966042, 66368.24865472209, 39832.4196392559, 40481.13258419, 37863.12560133685, 60979.58115182864, 10354.634323546541, 96967.88281228255, 24468.65104656334, 32222.001594238947, 94329.77047368426, 16640.557920827916, 91731.44519869998, 64405.831958128, 11985.436820708783, 10129.429600879103, 33393.73414890643, 57745.66625760659, 46076.4377545269, 9910.553055743976, 50224.87937552105, 285.06933619451, 43968.39958785474, 5739.313511757083, 28114.605097101452, 70967.98158789275, 73230.22816593779, 45697.48556064441, 81136.25835917922, 66761.83666695398, 52819.02424035545, 11498.292623799422, 82861.10818650648, 51363.912031680826, 13582.403153678602, 51473.67880267649, 25558.740626012343, 52552.887013447835, 21873.10209588571, 28761.62518625821, 65505.0306336521, 21401.7921543589, 66490.70693816316, 10636.131531274163, 9597.228843440986, 86579.02585465451, 71704.09400841883, 26986.721604750765, 41866.623888429065, 6154.835102594536, 70250.59877519486, 65021.978592191466, 70654.40947039258, 18680.288756746788, 92548.9248082787, 34982.013296979065, 19260.004571137124, 65690.56057378538, 77769.58851537551, 65539.75095492002, 50024.646983512175, 18697.71853926543, 20606.41774510331, 39892.993981028354, 59031.265404865866, 20119.053813418173, 49130.83948353483]} +{"id": 1091, "vector": [2860.3933634344658, 10920.411949954789, 80371.19038828718, 99759.11277024855, 96430.3099087451, 12826.317527176245, 54149.66942990759, 78515.59657130181, 72894.84192672525, 47108.46215856921, 78242.26913683559, 38459.53760973957, 9748.970208050412, 30273.440584649226, 69056.24720775413, 12892.836930456686, 83974.34934318956, 69498.66860072842, 92180.47075255019, 38510.50153322401, 54940.16207089229, 49911.70221282504, 88874.02572050397, 29850.206252798882, 55971.19586308803, 21593.970859725898, 45859.814887272456, 35357.76523752392, 50442.51007248712, 91605.33928280657, 8242.6515193327, 43560.42759253399, 80565.30549398327, 27970.788537781733, 44958.308692545776, 88351.05806208967, 51215.62481322885, 51652.291891077984, 37997.50902986342, 45237.24031531649, 10716.06223570658, 5253.148080793379, 9934.739374855228, 61954.37211893219, 12799.619790434646, 36718.39947121557, 59867.512826205064, 10466.946322343872, 5996.294742176356, 4510.213042464761, 74719.4552717106, 49301.16915965204, 46735.96245446835, 81565.49436726069, 97624.14749055088, 32964.893844173734, 90292.83774759265, 5637.578435432644, 29834.391913051495, 23799.14057577911, 32465.391803481714, 13314.087800513864, 53782.43499151882, 86191.88010130689, 65711.35957167113, 84791.83967615751, 82486.69632173635, 31012.214965942363, 39707.08223172859, 41540.90270349601, 94686.43867350587, 29417.409418162544, 76386.04896011476, 33665.35463183421, 84324.90749924976, 97801.46842862804, 32420.59322444679, 93010.42157183468, 12461.339324684628, 50019.89156583604, 73196.31059639489, 11278.218407030783, 7773.369795129037, 96258.3789975231, 14453.234260559433, 68982.4944064587, 11314.173534266203, 9426.663491185027, 66875.88919711411, 42402.218510128754, 3998.007858367381, 41120.281081487665, 17036.003146969448, 69662.91399770735, 88738.09580535653, 67186.94272038416, 39863.55331289648, 27612.33983025082, 17787.286390097357, 28123.435645062265, 58360.439477861524, 41628.51172544886, 26707.585980009007, 57348.29645716056, 74626.50049113527, 60953.2882142244, 24898.241182204074, 373.05035791566075, 87264.95783611071, 51101.40516168897, 3201.592111626261, 11911.87949616178, 23315.3026265699, 84683.24057020305, 87891.54657755379, 51450.491572965395, 26707.46642136008, 7350.552790199671, 88279.54924914462, 61355.57361084425, 28801.56902798249, 60587.66836252728, 28033.294368563445, 13964.552336333802, 87058.28955866878, 4689.475087556405, 5081.2821662184815, 66950.06284513093]} +{"id": 457, "vector": [87463.43654384231, 49099.93830320238, 7442.935477233059, 72329.27578385176, 7648.433266749943, 82176.4981848355, 57357.45783575643, 3509.882362379535, 99165.26226416092, 6322.2622931408905, 79681.54463977071, 2519.0159582906604, 57269.09183583746, 55667.249082937786, 38130.927581323835, 18112.77308794823, 28998.051113528967, 6410.839686374415, 91411.22930579666, 27480.918976655157, 87127.05700728233, 53974.94253963252, 99011.94802767447, 16439.39279510587, 55861.296817093564, 34485.21727483607, 84534.2672470003, 63182.23264756008, 45241.47419446438, 40014.231748353224, 69290.05325491159, 29561.15618018188, 17679.934251849307, 48700.04949933838, 80481.81245661965, 70885.98468500424, 40263.54221450778, 26623.97530865923, 25359.790735772647, 14006.100412470534, 90829.97008040744, 75889.8254450185, 20839.004472729383, 76102.0473035524, 78430.55607389998, 57027.50690762819, 18828.37713053498, 60727.814819672574, 63464.45111611624, 3209.2117412556463, 55450.31303785117, 13260.798778248938, 54958.28795955255, 22040.899804836412, 18309.53498138096, 90090.66415646963, 777.1964085902528, 91732.1308473799, 37055.31726110315, 58645.913269482364, 80371.17419990183, 18623.095906117916, 94772.29030810439, 95807.03754883847, 4096.450870806678, 6508.781375655559, 2612.5621499552885, 39842.43838365139, 56402.14340518661, 70290.71153124231, 63643.14834102926, 20494.961776796685, 66662.8866503094, 13570.214471719677, 440.396759865691, 19956.493915352614, 68546.07949749968, 13480.735794014221, 55088.044716790864, 78770.45476774716, 3687.096665227141, 33662.29402732079, 56392.25999746352, 64860.18911322674, 534.2366772207118, 35642.03654850654, 22203.285884357483, 58834.84389726858, 84667.34084189826, 51523.329720532005, 39397.664755861915, 87903.20315255609, 13371.440844095827, 9540.880318179135, 86121.70150145664, 30198.87887890281, 918.7163758073824, 67777.89368073932, 80049.55366244788, 10054.907711518945, 68968.60303927926, 2769.7549457242344, 34480.42983894081, 76215.036671713, 41576.55679514578, 96666.24211752426, 50595.48811189678, 93208.68515075103, 64486.240051168534, 66735.75339591276, 58868.91078890526, 92733.17313544147, 43372.85093065826, 28039.60486115804, 7908.0242233813715, 92291.71861986199, 55782.42227174437, 7011.940050095899, 31065.80638863684, 50157.40156984238, 33984.51505836189, 26063.748177541056, 21787.285160269752, 45677.13752351138, 5682.759510504831, 50477.44578611709, 98924.08822504754, 79011.58122287723]} +{"id": 1396, "vector": [60964.42304672106, 87122.06580797459, 19396.590686035863, 64454.54707836041, 55337.47313964139, 94758.2323164972, 8484.479178746851, 39873.06797727177, 85334.30568727333, 6820.803097977801, 75694.48294038002, 56076.81302588055, 86877.27776115527, 49150.487977367586, 84344.67390988892, 96761.29226926184, 6307.85940389127, 78965.89880237292, 34001.81079255984, 99743.53876976285, 96462.08958755732, 94936.17415132339, 83904.8190846978, 73797.7600484904, 93813.58831998953, 20864.931670778275, 17107.059196912232, 7139.92905924703, 27875.60599647607, 1374.245399558438, 10219.221026519464, 13339.508991765104, 38917.14986162777, 74078.87070918862, 52153.1384927511, 8283.631557669312, 46759.166399328176, 63384.60897797661, 9056.744655460025, 66730.5650249089, 69939.23492472786, 87559.43745529649, 86996.76213599127, 86546.04465733907, 74715.48209850905, 31593.18635356416, 73241.87998209905, 64873.772306523446, 3588.5070139245468, 9741.616296844113, 3254.130219217777, 14013.510886150492, 40613.54321607755, 14483.933329874677, 87989.07066676022, 60270.5134307726, 97043.2525524707, 71835.43535667763, 97782.98943277058, 31092.911752125106, 31563.496201039197, 65302.81720243688, 83808.07869985915, 96658.61652085977, 93666.43989404048, 75281.593890818, 44650.37510204657, 75482.94032928013, 68288.44436436628, 35075.91705188353, 75412.47513508184, 1957.7001125719917, 21620.332189696866, 14666.890997203274, 38794.22559186171, 64527.14503978522, 29077.533423747125, 12966.764945379184, 48100.4853100229, 6925.329089888443, 51888.49087133914, 30326.821994253118, 45156.99651529927, 24153.723447140397, 55307.57708745685, 17004.59410987283, 91697.3255591761, 5246.03371865453, 88776.13968568415, 46974.8407530975, 93980.69052489624, 84684.86675723252, 40955.70573346976, 21420.493571151932, 68707.03557004884, 25130.74198837891, 40670.23387006825, 50856.61916147453, 98344.39006812658, 70940.11103543136, 22906.399745485694, 24988.483819841178, 92828.18295496979, 80185.2322930618, 76759.02243559234, 55437.877091182294, 23763.36026787981, 59524.16752437328, 88298.49439979435, 11251.726756118009, 26393.182655713023, 43030.05922571712, 92159.74450855826, 77412.66382328139, 43717.033916518936, 83102.01177898825, 89795.57234294435, 9924.946689329661, 87031.22381502608, 99417.46079612362, 38931.16824775342, 48214.852134877394, 26562.189610940844, 92521.7295791014, 5586.77879808287, 12050.046080798316, 72387.80316818182, 71009.21124733698]} +{"id": 606, "vector": [81491.11894359854, 69257.5930312464, 52743.98848583579, 60605.531869700804, 28744.249257242926, 92258.05185395708, 53520.515052748284, 97053.59558923074, 46124.343156003575, 98683.11880249118, 65068.05197586031, 81033.69725019134, 75761.91716599144, 26315.001384706793, 72762.78963288208, 16240.959613599482, 36502.7312152692, 31840.670067533516, 29059.646907728897, 80973.08306930745, 99401.1276513742, 9718.779334112403, 22779.170357108123, 28630.72344986667, 75689.14498054226, 62370.10239152568, 22421.507363919467, 18726.871722595595, 70418.46076691175, 8804.385183545626, 73423.94053676615, 77322.8805529202, 81272.78931776623, 55981.31869228893, 56919.67213593917, 35650.657742850235, 92221.59784097169, 32853.56969758733, 69918.00743654373, 31515.22863159474, 72584.72653990374, 34324.283805255516, 83016.38860629562, 51861.48101317557, 29876.084316773442, 85751.27867698192, 693.6275939453784, 1730.4322151808105, 23847.727734451397, 10389.304870905991, 58166.06786162033, 67420.48757285708, 49320.5749327427, 56433.11902523132, 50550.44686418324, 95143.9410265758, 53596.83616662365, 3395.4405258374454, 92977.62768700212, 76527.43632628318, 55146.21668784275, 35970.73608161117, 9750.90755363428, 12446.895493359667, 96926.28217395188, 29535.01922795959, 52301.30324241559, 47284.49298599372, 82762.6288404786, 83441.99179194914, 29906.27337235556, 65899.16375001436, 64984.43688181476, 60540.167100196195, 73020.08178426806, 71739.52589722305, 46741.83060875218, 78242.02866229258, 91977.39943436059, 74825.37029023671, 76511.24813052363, 99025.6425495496, 57488.24077904408, 18424.348095062727, 12920.6656237565, 18363.879940734652, 72705.63109277355, 44851.41405995317, 18507.64985575264, 34564.958944887716, 66636.00052360597, 48268.55708155149, 88164.57871097211, 23293.920531106083, 5407.08773040699, 32134.23222282522, 64126.923156756435, 59173.16922914324, 73316.6046376848, 13806.583988977794, 74105.74036230486, 32505.665230629354, 68358.40101090109, 7071.092891011377, 74965.19740107788, 96478.1462506899, 40238.72514389105, 28094.9265713643, 72280.66661805572, 23385.58411825725, 15226.95211027516, 49434.94018739123, 39020.40086327623, 46902.321439891944, 31758.199256076845, 77195.4972418331, 29258.10532612987, 3953.822171573429, 44006.5565160721, 50887.33300702326, 65622.16914053174, 51742.64354269482, 2733.56718735589, 187.83131914297257, 27714.760440708098, 42757.532292151016, 91764.21587695219, 1049.478687262584]} +{"id": 2028, "vector": [62226.13680120925, 71656.9108134855, 65115.19951054019, 9077.033467719353, 21513.39017303602, 15999.862226062256, 77332.7264425172, 3960.4484827772458, 2251.281552776807, 22775.28163513809, 55504.68499766223, 252.88176047073563, 24387.415986856508, 71516.07884773581, 95029.01101022394, 16937.671559971724, 43022.840365077405, 18201.804744010908, 9262.826458186368, 99931.52049915958, 69989.84567628313, 52581.11231518158, 15177.496809067836, 71141.77817834535, 67222.2204111956, 81185.66956477275, 62130.25459377024, 59542.98291661315, 94496.76420878954, 19579.763747117227, 31875.82375730288, 26606.035690971174, 96433.96784591736, 82999.1792367282, 86519.4726049654, 78748.07348778719, 75970.65513378079, 314.77486738963336, 92325.48601018262, 28535.515644792387, 70223.51111178687, 52152.338313669075, 57383.666592015434, 62645.66134120611, 46740.6974647635, 41545.81862908273, 28313.200741308443, 89478.75732221353, 56405.93074030193, 85167.30726013453, 24874.67929838606, 36175.8450139017, 51976.26586792963, 66087.56645231506, 4362.520246313806, 32650.545064963877, 54033.67356355691, 19620.879540165624, 98048.19457241903, 34851.65046084449, 95839.68811388257, 30440.969871160694, 19499.704675017238, 98958.0295033669, 88075.81232252362, 25571.442664103084, 48872.692665470575, 18048.378524564923, 44514.90383139633, 99391.63169420767, 36892.52707462882, 93680.9840069258, 92323.46480617685, 80986.21704852495, 6541.0882394182245, 24781.165852859354, 21028.564051165966, 69388.22742349523, 44388.144717007024, 27460.287343052834, 16815.39256103215, 2290.3691005976334, 96144.42865699285, 95980.59942275366, 63081.709517722564, 503.275049999774, 82050.20957553729, 60644.489161166246, 89627.6846318805, 33921.68491997026, 38580.62766592208, 21605.545525031645, 24745.771814826712, 57124.3493632064, 11728.516210644113, 98631.32547468135, 58718.01796411662, 69417.36476358012, 2822.9640709785954, 75481.22623216079, 35949.43638694349, 53471.347548752754, 53338.60258161153, 38622.00130067025, 37837.66699192932, 52655.934958996775, 3015.3066935000084, 81363.13727533804, 62792.26268831645, 50846.07476586062, 21431.412391643968, 64882.51935238114, 57068.687862915016, 60849.80757830985, 26978.529767574964, 38320.23049760814, 11935.785450866888, 3613.909380312086, 14004.258142189341, 87012.53385885459, 95709.10068772902, 45516.05115021731, 64610.218076819736, 37426.83953845162, 14790.245086887455, 14413.899208944036, 28632.523898299234, 49545.43093552436]} +{"id": 1711, "vector": [56978.79424378206, 613.263270474429, 9607.667503070894, 52317.2087575663, 81844.61707685649, 61746.19781093243, 23696.41283843975, 37866.108795456574, 3805.9870266766716, 75928.65588101136, 83613.12056490769, 41443.70509788381, 44983.812087225204, 77839.62729076766, 92934.5151370889, 93905.63916714423, 92731.88821230207, 44279.467986964235, 34682.28278294804, 77781.70381688763, 58401.06266583951, 12887.757384863862, 28864.66488020687, 50668.185730335324, 98542.73045355498, 79833.73308751493, 55392.19278285641, 50612.204693561056, 95766.77507926997, 71769.57513644788, 75059.32896109002, 59534.7189838743, 67664.80415327934, 40610.68907964939, 99128.11788617611, 94832.51805969724, 77691.00970593204, 80346.31641411371, 92099.41131555433, 67526.77075738486, 10498.524274904676, 24187.075664992553, 60650.98474818117, 2131.1119343975784, 26257.096677691272, 8691.315594279937, 44636.84334635126, 76057.39942202228, 88667.17781318724, 34512.94051616145, 47273.5034404924, 33791.07578694597, 17865.110029274267, 24858.75284138215, 17828.6494287902, 57450.609549261935, 97283.07253081052, 4492.918709304128, 58987.69225186862, 63167.95837673193, 55292.63989607358, 37521.477764152485, 6188.77093590593, 70957.91040894, 61389.223525803915, 69978.54220385705, 70781.31772006636, 75918.11000187862, 34038.70715386516, 91302.73503414089, 3579.2129355754487, 80030.52574398629, 75753.00317129068, 13819.0626849367, 41827.72408725733, 58369.449189522114, 27070.029891219994, 9270.204832170404, 88474.29714232378, 28600.480197441313, 22390.21959845976, 78076.60460341937, 62771.797977586364, 30026.447645815348, 27968.284237216623, 6301.931198094057, 90066.77818469534, 53417.54135855405, 4568.335788733147, 55138.47133841129, 3741.806290613503, 6725.280072150763, 17583.169943587807, 23447.360129549554, 15571.171049451916, 7053.40227568938, 23918.95757087853, 30510.722623180976, 69533.72383411309, 48013.95761418996, 75474.35492390636, 36177.33523883585, 97077.37866619718, 23503.5582671256, 57136.481300617605, 48053.37549666254, 63629.642595612866, 56001.13766372957, 26053.389331215814, 89658.09087147632, 86291.57113214661, 17984.13779004956, 34705.2026389262, 14616.758113292793, 65131.900990931776, 6630.5816300546885, 79667.01471175606, 90175.56740146264, 29431.37557133373, 92385.69533189174, 49480.81366425801, 93891.21430329223, 77537.57095660912, 3248.3726435717062, 41143.33315340225, 5954.930688987292, 79485.4005557842, 33154.841362311774]} +{"id": 174, "vector": [36.724189532688634, 17757.315184291743, 59914.91820953318, 74717.48280716948, 96134.32626515032, 77670.5059628805, 90458.66704865285, 34481.45959539567, 80987.7872666867, 47984.49970758895, 4289.75928062757, 38071.06788739854, 85123.32034004462, 24433.84546324613, 86877.26963380286, 97022.50959282844, 4648.784914423021, 83109.76817595008, 81191.60557701142, 73779.99688718416, 22363.314786660238, 7853.963155096455, 24825.583551500717, 18314.88689089348, 89088.68804830893, 45382.087029285234, 47515.23064515831, 47542.04304907088, 2615.0653931914735, 32151.729795544736, 97994.87263851438, 37667.62398906045, 41115.66494217701, 35538.044921922396, 91185.51736597589, 57469.193656613694, 59792.80221007412, 70896.64565373606, 12462.765976785018, 69871.03664315496, 52076.243946334944, 34192.42052045489, 79246.23474083055, 40145.76059867169, 5389.38226107889, 37053.13772229486, 11594.029949964735, 1843.2153877593116, 29189.750494196986, 93058.79106739443, 21119.47199658234, 61202.46470949906, 72107.84627511015, 56165.99898103023, 57193.22452548189, 20021.59111249333, 46370.02674961826, 47066.76213426966, 83980.29147292746, 27296.162611405605, 32381.48284118917, 77002.25717478087, 50006.60333319875, 41345.57131497503, 67931.21943182503, 12462.134939186575, 13701.056983511262, 78070.76011992457, 97565.25040604168, 36314.56177703953, 27477.359893583474, 26655.038340039562, 23842.67549623965, 99994.03419505454, 64282.16009431675, 38057.7100750312, 40112.00857069079, 65223.43697063382, 32864.178341252045, 21808.496926656397, 74286.33786527687, 10326.834336346901, 72763.1717893753, 28638.718281968566, 26402.97762408307, 144.93142249858425, 34112.68230752211, 50886.866497477844, 939.8471824904608, 57203.88475854122, 6352.9355367187845, 14924.961216496402, 20000.97819733163, 5607.018491926175, 73193.85937245659, 2197.6705912030934, 25453.267411510762, 68559.41382173871, 58396.06489966959, 95097.25980267282, 14435.537744596306, 17479.59905527642, 68106.64297853177, 55159.73731037257, 89454.19876964927, 935.4469556884459, 89100.91107746091, 80692.21628516319, 45849.63829751124, 59319.82762022781, 26969.61380045646, 25111.33403108119, 88384.62828776379, 21968.955674763478, 92062.4038039432, 45782.85447799481, 40751.76482658829, 6639.460977379774, 67407.73548143405, 22309.797983134104, 36690.25777207629, 92697.58709505497, 10759.654637288451, 77368.63981237596, 87734.66874119427, 15554.686690255205, 27796.25085816577, 54378.16730799682]} +{"id": 1235, "vector": [73552.66092363816, 76094.30198609579, 69887.95070316846, 84737.50002826974, 60833.55112809049, 16699.370267840084, 96523.19031849787, 19217.70548026096, 31597.497740085833, 57880.12547477544, 11269.345548565769, 50079.79058409322, 44330.78207972039, 61449.35772594085, 10915.370131886726, 80006.82581751308, 27601.130691098064, 37657.428013450655, 44618.09010737821, 20803.863897445375, 57254.38960982774, 13306.2940356504, 58083.75860196046, 729.4488657685449, 6557.094220837034, 53070.12989901799, 84773.62125632878, 32826.43166386854, 73602.52481315605, 79446.8054964019, 31949.729408821026, 86593.60666645636, 22508.330960857736, 79859.9817643361, 28728.779787497428, 73228.8409767014, 25581.801226414424, 91833.94621092684, 31723.775873889816, 87673.0575196571, 37680.08160655669, 10571.528371839278, 51001.054126014045, 33208.472998742145, 43518.83462750591, 55073.66158548729, 20003.036321358937, 28485.939770669145, 86148.58177189673, 65303.488028474974, 3086.118708735963, 13944.271972590272, 78404.77389661047, 55037.180369347196, 8449.279500554507, 64610.60627863017, 75277.52985973198, 9083.318006774643, 42106.49965586282, 42472.062818374434, 44595.45468860181, 89645.16344445324, 23595.831161274127, 32077.65284852173, 60716.61510376458, 8754.929994913662, 59064.09396135462, 39804.96454754434, 46578.28779930127, 61334.827134204104, 37064.228218973796, 6759.121009512059, 45586.68004650536, 9848.084829057858, 36091.74697516988, 28671.8830480161, 66933.3107761668, 22767.664205151228, 40907.589383522034, 11712.924247178447, 73956.83008882619, 81309.44288359389, 24017.430119844263, 49872.651642342, 70928.98015926186, 98178.69319579475, 34674.39488576699, 48581.38951963973, 9529.024201286862, 1831.4583935248984, 28826.88241721295, 64128.5024550323, 3054.1697450833904, 98703.12713380111, 79693.1366705018, 25953.706796508224, 87557.11900027358, 47950.237948403585, 69936.76339108302, 89424.5050690133, 24695.45308152822, 87744.11088371798, 82488.71382713407, 38821.15804372576, 23178.616957339782, 41074.23193492336, 7772.917407751279, 37766.77520001856, 39228.109204414555, 92112.70049786712, 84596.41099451983, 18823.30328212052, 80414.04851010886, 81018.28571617721, 64237.56956817418, 20150.41678865209, 71721.37217410361, 7092.468223140036, 90489.53395759634, 46340.66879436979, 13872.214348859225, 7885.067128862455, 18041.61398201991, 70507.29459400217, 14121.563844421004, 38926.25401452227, 52635.80835391417, 53908.45159197275]} +{"id": 1348, "vector": [5614.426757358071, 62483.21538292719, 76515.38797550474, 54859.69261410153, 16153.539604236645, 47294.30559028688, 21472.00033779796, 29533.119737885805, 88958.32729022417, 43729.54834843146, 67712.13924596658, 63235.39466285937, 21098.73075071469, 74026.58631585841, 69059.77428195365, 84445.55279457738, 66445.9098517756, 87936.54470986358, 5924.060910347428, 95535.531391572, 13352.209828712746, 9862.932255954403, 56480.16900593006, 80145.43783244373, 84492.94215699424, 77318.70022397184, 97107.0499422649, 37683.71118726339, 71405.6923052901, 43034.89409138946, 25850.8596200247, 66463.10199928898, 19732.225554426586, 68051.02227201128, 89935.60253768718, 2643.5635658583424, 95556.3065329857, 40220.897248280686, 93630.40671013382, 55877.371569459945, 96741.00494019676, 27014.800389686243, 18828.47533530517, 47083.48612222392, 44093.08105766633, 21059.069584536504, 82669.72588743444, 94474.94216610085, 91081.72390519634, 95867.47693102401, 31994.46152502514, 71662.70560800981, 83883.81781918395, 57306.40775299294, 43237.047461090486, 80463.47718599356, 21177.038358904865, 74801.53228864077, 32646.59222655022, 74368.93538645598, 98750.68729630955, 10268.01630704145, 71839.71624367667, 89594.91449697179, 16282.457281854679, 55327.63848913323, 32845.14061171285, 63404.89075465407, 20922.108246067473, 35931.5801737328, 47878.544272261315, 64372.433500882544, 30450.005949727332, 32076.349691543837, 83962.496862951, 64970.13216952974, 81070.82095583851, 93827.52278513025, 50847.076339630396, 78325.93260216125, 5773.182408740807, 91997.59583827332, 7014.066407414043, 63131.835909938585, 56953.528193171034, 45330.29850819303, 67243.81020076295, 96190.50845240716, 79729.31967540403, 98394.37125770476, 62852.26053048597, 85331.41837659171, 65381.52896367352, 99200.07110594722, 94826.32381933309, 704.5051940887448, 96338.23954300981, 23629.783496427426, 3097.263808463402, 95254.56591999096, 12391.370584041882, 81343.87959821294, 41471.4870496218, 11921.067591759005, 62530.44162232142, 17979.172761479702, 67884.39468068324, 20070.896574347807, 61044.619370585075, 43326.27855699964, 73931.43535890954, 47081.71075510411, 69249.54045318223, 16759.99798790824, 12708.321834054559, 21288.283531037654, 95892.68498570062, 58083.236551953254, 36412.691987101905, 12208.740868182078, 78756.82269090442, 69364.92794700732, 3194.7800633226107, 45749.55276499334, 6994.3412828845085, 558.9591644597269, 10325.171537984668, 21312.746495607505]} +{"id": 1239, "vector": [25011.072415396095, 14705.461007845555, 93755.19097740189, 8247.306441407487, 27206.502649129205, 97454.6857311332, 70633.44857261586, 13397.061480474704, 47681.950973531806, 5871.253444043878, 90072.24702597233, 85928.6045198629, 86479.09553963762, 84822.92238869387, 86175.5947505315, 42114.86027233462, 27020.529698763774, 30084.543849188572, 83868.74618277994, 96977.6927636907, 25217.765806270952, 61445.810270315626, 25419.62984639927, 87786.39961839924, 24134.37855285433, 70125.58368944746, 12063.683727069852, 90444.73814151727, 5279.340346221661, 12588.494480684441, 65878.6678809587, 58894.29502371653, 68513.1743977192, 48721.55465830701, 35760.84794070337, 58348.38919756375, 85751.91220141776, 15636.887045104786, 61805.50001100811, 84969.11664243173, 2008.0294192668214, 21846.42375609126, 34235.2447671224, 9471.003888295803, 40187.660431457836, 43354.807052736556, 83659.09498394669, 30187.67215464342, 16619.404413966575, 95389.59196684825, 73510.45535227, 9826.571193870093, 80590.25487513107, 89307.56546412686, 24164.499011380158, 77216.06537844856, 78361.50613593284, 65990.7245923333, 53426.867939176416, 48828.00646437031, 83032.11695398777, 9234.936059299458, 65050.68512481078, 30317.002718138432, 30376.70687454689, 91254.7719750962, 59246.47904521815, 91574.38923922798, 37418.11134597716, 25837.23893841534, 17130.16852748116, 10026.43843938359, 9720.923295606266, 94276.0671949216, 56974.63972766891, 87686.43278148246, 45017.09246369121, 94603.1724195168, 42547.763867655674, 89713.91785398134, 24249.440740833816, 79440.71487752502, 78062.63442457259, 16046.486794774184, 28960.26094533848, 43025.58268733279, 6843.360846128999, 37892.38942812204, 12004.351093092957, 99344.94507163286, 24502.358794103817, 69123.11166867227, 64455.74217270735, 85720.25732674755, 84567.57674098483, 5943.001219900401, 59401.6606932352, 55490.30300162477, 71062.86085484165, 57634.385569875645, 49885.45968333365, 24864.975430584367, 88395.64167446441, 97127.50503982302, 61770.96790676642, 20361.34340481651, 65106.86644388235, 64709.90943982142, 47705.20837059302, 65439.91539963017, 25336.513261522177, 33807.717394426974, 79792.2210936595, 18400.83790204813, 68184.04637092129, 87400.98167049758, 25329.24735185599, 17174.21733501294, 21165.346543652995, 1960.4485646089408, 54476.34585446186, 4055.212924440943, 54003.37099708754, 7227.460407474829, 83069.96254302033, 10472.643766276802, 13823.818834138225, 45360.01127137479]} +{"id": 782, "vector": [70578.85669587934, 23913.237135456133, 87691.30086466973, 72295.75525259838, 91624.70838710714, 32557.271542147137, 30216.385093303823, 6081.5018389484885, 51054.29682945343, 30316.644836354157, 5623.442320500838, 84876.30255830615, 35840.26297277956, 66034.30471352508, 49086.46612439496, 72319.82067701612, 78976.4530328539, 81065.46176420388, 15242.647943971999, 73244.8224753086, 27896.732914751843, 70692.1704717538, 54766.61073341925, 89206.69752796339, 67660.24570701849, 64626.903724441356, 5102.710426584256, 36776.21046570759, 97254.57478579751, 82481.71471951953, 25763.66957940287, 23239.70104804446, 43462.34891097529, 58053.1986681191, 39140.354289751376, 23182.79049079467, 792.6867377667679, 33024.809301287096, 95794.99046655558, 75271.45057839333, 28498.261488869593, 6987.209313212817, 47043.03527909474, 98611.03409938703, 60876.680388152956, 79904.3088775168, 27737.092373675565, 70782.87886095508, 74836.07360355926, 53957.29119469328, 98301.08031703396, 97619.29790993598, 30175.272944472043, 26206.225224916303, 92015.43156846635, 4340.652005052226, 36915.50768100089, 693.9253015538882, 25877.089784202144, 74254.23859510971, 20642.252066807843, 91174.94064404766, 50301.62422980591, 56767.80988094448, 74894.87911201351, 82789.59433936547, 85344.47740740581, 22401.84579516683, 18043.995725193552, 21223.917789537307, 19948.767612445426, 54472.033120650995, 82657.56187618173, 16131.641298480781, 80871.11735394738, 22983.978023523323, 91830.76634678821, 48918.877871851386, 72298.66815394502, 47832.09724341877, 43275.69526340137, 61457.16468938486, 99215.76621576076, 83502.28443868119, 34975.49863505024, 22426.852676518138, 13242.202328102449, 80127.72362440286, 8014.044959599842, 3678.4687356951063, 51121.19039459308, 82087.8806579843, 82863.21822530752, 72093.67308711054, 49852.62317286776, 29386.19635141784, 13228.748792889777, 43598.57325227252, 75789.16500897653, 24495.673172478284, 36096.238051660504, 31908.806464095596, 73664.07766970378, 4306.342375197148, 13766.703611165909, 79369.38337533234, 39566.16204579061, 14870.322652435041, 25331.737151327692, 45257.51408023011, 71340.90968932009, 91812.4764113732, 9743.86559251611, 69885.58471235477, 7851.072176723406, 91557.19750132748, 8919.760342797967, 54967.91563312874, 85757.31760242539, 2297.792099800211, 75411.50972438414, 30827.876432581335, 13730.320694941967, 90520.5535326401, 58145.425203083556, 48930.57056805657, 37017.069196577635, 40557.412619473354]} +{"id": 162, "vector": [11485.393507491992, 13427.646713797003, 74825.89702629618, 16890.174781742993, 66827.86446877982, 18307.287112752412, 30888.28433734433, 96641.58783580105, 72393.89321570145, 80194.04127010147, 23271.795177859112, 70508.43937226679, 27611.89493103474, 86889.10115020801, 41718.79060674566, 84492.01906185797, 84646.84799434242, 56163.50563418038, 56276.2898100592, 40213.786622185464, 72649.36167661747, 24824.62714634398, 85586.35807513412, 84869.42896456229, 38595.81505282421, 68869.28147617003, 99924.03192225029, 66356.0220876814, 58481.53277565168, 74233.74295874916, 16821.327882694848, 50781.272472923454, 78812.04080941087, 83439.57449895807, 88537.40460665173, 79128.97993795066, 28299.210912350416, 39119.24229575074, 85335.78752104878, 8314.90412706104, 47509.94251439872, 20576.013723955966, 69120.73527586958, 97415.08411591919, 12671.174866416712, 95262.42943870123, 84944.02212531591, 47792.017890620285, 49888.799642238526, 17755.662346685673, 16397.267547822637, 6340.874301790566, 3893.8611360297414, 55930.119843432854, 45470.133288238416, 46054.191304250446, 50888.13863730864, 25514.823093477335, 39483.3973760112, 25615.863089292758, 28482.038952912237, 68773.2840856984, 58763.263160313574, 88311.37882734319, 15235.71278120821, 55200.015399973025, 77252.24255453717, 64811.52202178248, 39284.153768777396, 3589.069787922339, 83225.28170169568, 42030.092560229095, 32595.135139943588, 40999.256930155745, 97403.68659811579, 96912.85532737555, 71746.55000086626, 66853.69108129719, 85964.62454372506, 82563.99126252763, 21325.06338393071, 77566.52327807643, 68074.99314835254, 99303.36454247808, 86141.23729784385, 39132.12530089702, 8073.675778887413, 10083.642459150611, 55368.352251680175, 27715.52667592191, 33757.95607238138, 24034.706324169987, 60171.949357498546, 35593.25680617734, 3256.2071730293906, 88209.99513369416, 40948.425221535355, 99717.31915615124, 10238.539994993524, 76408.57227299215, 18201.58545402494, 22637.08042627456, 57567.39928702571, 21189.902367321345, 21008.90143532931, 96157.77254443076, 11130.23846847836, 54230.51529451777, 38667.65078436134, 38365.73450651525, 39050.17046279795, 89755.61972231066, 51256.778219571155, 20608.12062389231, 78229.75798702218, 27470.44755962137, 62103.3768833312, 74423.28127139767, 51241.317570911146, 37947.56468920611, 40136.610748009225, 51760.14260924559, 80530.22465119626, 75465.2499923132, 5587.940405750846, 21195.6194169061, 77320.13677472551, 99357.95568246143]} +{"id": 199, "vector": [85993.51714762485, 69793.5173891808, 11787.54282426977, 89873.25891577077, 6032.193912586336, 6534.908339743717, 44311.301739157796, 34402.01717370336, 46489.91647484319, 47775.73678795512, 17107.42892591577, 17586.6159669105, 11410.00036087685, 69023.21230115513, 158.64543252503972, 32256.01865445603, 34972.21720142772, 62550.86006540739, 18729.71536044864, 39716.25398071634, 74166.42103144364, 54990.79345788106, 58177.6143539154, 3324.0453385810056, 91705.13660824712, 99865.30148456499, 33449.68246993232, 96396.16123666032, 22541.980116527026, 96154.60377005834, 14901.42460118299, 26785.756499708546, 46159.144615984216, 90111.31747674975, 60837.14689946799, 89315.42078342673, 81074.03534459189, 77933.22779857871, 75718.28616841328, 64523.87213357539, 29444.489889315162, 17904.267073810566, 41750.642801319016, 10203.742740803113, 44938.87538023404, 82963.99005356125, 93753.35975502833, 58024.103251488465, 24946.72546311997, 8113.525659631404, 82712.49774153875, 99738.1488447527, 71055.39404356854, 57585.648244423945, 45070.41759695212, 81898.95115282341, 88552.92265115805, 6508.162952901075, 70500.64794627878, 24128.848315723306, 71915.44273833884, 15532.934053475312, 17911.74542305195, 56442.95017540618, 95446.66683697958, 81978.66448259895, 69653.50011293363, 31936.024929172614, 39052.72195502181, 40704.42479804396, 17338.783532406076, 56426.41292684143, 70958.181657695, 38730.882847168876, 51075.10572331133, 26052.982087897337, 77498.7760560492, 58400.00067568104, 72769.33800345106, 43440.82727043171, 25309.07316640535, 72874.60649579857, 50118.159226372096, 66605.03702435707, 84837.00444536265, 24774.15464152566, 63244.02810141403, 78707.176573838, 26750.552816171625, 14107.900970488652, 9309.553562396233, 84430.77669430911, 34898.26171263589, 58354.56530385705, 79879.78863383128, 12358.88987735444, 8787.141634500551, 37044.44963499559, 4926.489852503635, 84847.88019732894, 60017.14803095073, 59990.888970808744, 29172.27346866442, 58284.02739594881, 15450.27342962716, 37672.54212800413, 92.3320886334178, 12583.728955790375, 12385.784541355737, 73272.33261609996, 18002.86355612143, 35638.25472638944, 14792.677085927508, 9165.985401995902, 37230.274257016004, 36044.379562692455, 98225.36580184558, 44694.349088096154, 67055.90348878894, 72083.38999982584, 37606.728477745964, 46889.85006811496, 41633.28589048653, 28912.192657848056, 12218.905861430141, 5151.87110389399, 96349.49902973184, 75840.01695719665]} +{"id": 455, "vector": [51566.06430803903, 7978.925686686811, 11583.669383895034, 53221.04862355672, 19026.421889602054, 99167.62869555507, 65490.594850534464, 88969.44983386506, 74196.15694399286, 94275.1370084143, 13035.384711551767, 22932.155561993484, 48800.26768495938, 80975.5148093591, 35229.653957759554, 23433.78310771592, 64360.995791231115, 74758.78969181968, 39749.47261232707, 58728.17035697726, 98118.1767222915, 18427.479345557607, 81779.96311327131, 42747.73035865645, 39390.12188501877, 84828.82509401324, 18166.975936378305, 47574.75557759763, 51185.32294060958, 92392.69379783189, 3798.791792501488, 55838.52735592659, 59979.16924735947, 40856.173809133514, 35671.049289402436, 15997.938655506516, 58006.60162730757, 29504.091559730005, 71956.73373796309, 71735.05702226138, 30339.8294521798, 68681.08806273478, 51396.865348855594, 62291.23716415622, 36762.427146492075, 25384.51017413004, 95407.00423894393, 7058.352803969692, 53370.07244804458, 64173.23771115089, 57540.784633387135, 74255.49905704071, 30525.392799735928, 96733.48098558032, 74869.48153510163, 38034.83393946569, 93805.20652564614, 85100.09501591776, 59434.84956594104, 95814.21554753231, 21733.97129834209, 72591.26621055997, 34426.60734496874, 9636.036722353969, 75871.99688676122, 45279.446398186374, 87997.9073669138, 81180.61427102624, 61172.28558985015, 22712.710648058244, 45949.010308314544, 8018.645369228328, 1299.4299936897935, 31794.542361767497, 7371.031343196432, 95145.87871506454, 16316.466091178816, 12121.682983002835, 9892.059317195135, 25004.945431154203, 17071.61839387591, 37524.480692411686, 16815.337529232776, 4492.89370394148, 21451.66938278239, 26098.176641108916, 17935.506984565465, 70505.41035582009, 29180.664228437425, 3823.114252386961, 25583.174829208532, 37733.379717759686, 68383.03151761957, 12495.126277651558, 19107.945794374937, 86617.92513632988, 21266.748656774602, 64699.38892430054, 58426.89146507671, 67766.41726950339, 54225.03658762629, 90510.0584843323, 27763.638514484366, 33452.702200091844, 50841.37190662352, 46252.00855309916, 85075.55484284874, 22147.892774281474, 69286.20185276719, 84823.70138222195, 89130.92729788522, 79379.8321051171, 70372.7915915901, 7987.893615073738, 43370.35592170666, 79259.99059773314, 81059.59800799872, 96183.06876974733, 26696.546745295913, 89830.88592839468, 18166.526366005266, 9642.58786747415, 81773.38496411125, 48453.79742719468, 80569.91548001529, 98857.04450657607, 29105.00739022789, 54588.990078716735]} +{"id": 1488, "vector": [77547.75997381895, 45906.844393932755, 10827.787411404843, 83744.97139582055, 88387.79991468287, 41106.95982809466, 63311.69499446283, 16596.15783828874, 79122.89217614454, 26449.99323334711, 93481.01874839886, 72368.55218254778, 36823.6908614166, 4181.88040577887, 23368.805529613666, 90697.68219977569, 28527.763488743883, 28270.876825886193, 64669.282797584834, 78793.5807167853, 43884.09923097028, 60705.1905427718, 35971.574923077496, 14802.72762862731, 9727.765357450724, 38296.667885490366, 83898.15407832754, 34056.67231060177, 45741.97905281109, 97191.21799330559, 19509.028155827324, 43469.62473174992, 97967.74903378707, 48015.65380650096, 17538.762661809582, 30441.97503333944, 55160.83944013974, 20125.00800301159, 61895.27679113122, 65217.35016995901, 95697.20039631639, 32305.334439196355, 31702.091417820677, 79779.13715081054, 22699.6458864552, 68973.74450813456, 83884.88531243427, 43346.8578472108, 79939.18726765142, 623.8918588367337, 1943.794299230972, 40447.69060828033, 1162.5133682112266, 65775.90105760015, 1153.3474487446238, 83728.27823220717, 16002.067992251712, 19239.204523943376, 62079.777245198486, 37182.840463473985, 23627.100688465773, 98035.70064244553, 71256.14795012386, 65087.14863347004, 37385.20275846774, 32130.26707988794, 68650.03484868631, 29366.87753504681, 68787.78782658557, 84465.05221546101, 24236.367033910254, 53579.49462991869, 64724.062496779785, 77343.91213200548, 83357.49655517156, 37366.521520956296, 71219.00763468379, 53433.80591552061, 34172.423713371114, 83659.90404776657, 63578.21042235814, 38414.6769597287, 9289.417213325823, 8206.061155580901, 79721.17383840887, 68407.70614979132, 77940.21408960993, 7783.95891052831, 75874.46793858854, 44826.35863792847, 47973.94934683195, 54400.35461394723, 16828.5703516045, 53688.51517668093, 69331.01222870025, 31631.32447358653, 13666.933513778933, 47079.03713492412, 56032.717753739336, 44597.26848724197, 68032.28784693399, 26725.14187506253, 60530.53794682981, 22867.388497581742, 28594.164001592428, 60524.54768118325, 72193.8615223958, 20265.186981532424, 51154.27657420914, 22941.88195688097, 85668.49138933119, 68253.98130732539, 34101.21753783162, 1893.3547394206385, 20053.379842088158, 55000.48114121438, 13278.568237580823, 48513.90326657803, 77723.15454606638, 55098.79228514981, 78236.80947647012, 65967.58095217786, 95925.72539340894, 88561.25706975111, 94288.11902277007, 43771.66008121759, 91825.1114043054, 51279.505413102146]} +{"id": 453, "vector": [57993.93248696928, 86775.13293105549, 30293.806366172703, 29882.97420958288, 60910.572234431624, 28210.969991883805, 75173.48741044666, 99488.24854709955, 3094.8339809162894, 52017.60448612698, 9043.269714357293, 38781.185822533094, 2646.636342437592, 94669.69617969684, 27186.20530673089, 19424.844412440656, 99965.94779997425, 84549.97845878328, 79730.21891417996, 44808.11289275118, 42821.00362791091, 40226.41124086709, 33156.749647537734, 35499.01175619826, 85378.65661123152, 83274.01063414347, 83162.8262269464, 10480.500857883635, 55563.29891579392, 88320.95418325649, 42978.90977478613, 89105.0120135621, 60139.043960602015, 88784.5397518455, 4617.596283607805, 75668.73451703977, 16158.515177614507, 22806.90326151732, 22779.970695492313, 67801.2738025533, 63652.556256642754, 56193.427997271836, 48789.38065793909, 4575.782272737949, 54459.36445637254, 76941.62620335819, 59765.72272924067, 93059.69256738196, 21979.000807152217, 51469.74054261197, 50183.67569592058, 97823.98257160431, 19749.531037124325, 51.38943130231777, 15890.944975775812, 49843.38508934712, 772.8146357939503, 40694.927258834054, 74311.88459274457, 12076.56993005054, 65342.28687320786, 64373.69146797796, 74120.80158953916, 60003.43841712431, 48609.43715992624, 55619.39091766288, 90708.05827664919, 32217.468060191502, 45586.29912140555, 16006.187775069624, 29273.9050140682, 71198.62101563819, 5827.990565611673, 42298.55265429481, 992.209718228243, 54913.235214209446, 71994.95243494086, 66006.26436620507, 16398.141649573616, 18995.232902951087, 54384.19709578261, 51674.4554123464, 4906.824556197398, 77982.48898814202, 95736.67760660399, 41305.066402125754, 2324.191250135366, 55654.493277454465, 83439.91254332116, 25356.055542429433, 44874.0831842503, 39547.03462685786, 91886.67350731218, 8845.622090205241, 72784.0888666047, 5079.825580071051, 72037.11660121154, 16272.41188751496, 71854.2049159317, 49895.05628078178, 98961.03637964788, 88239.01375106111, 94597.49224824483, 25656.63755771027, 66978.80574106825, 23377.795088502196, 97668.04660851562, 63368.543246002315, 38598.009859696824, 44327.73931830437, 2171.3239156002096, 78966.43136702682, 89240.11576120593, 58876.74446197506, 88870.07592214028, 73909.10254986606, 12279.074699530835, 50887.04477316718, 84876.06993082957, 82092.42074206211, 84917.41731223097, 97533.64237882264, 73647.94488782852, 47778.02035969265, 94454.35216197239, 69525.24069545793, 94356.45176934027, 9870.8377750653]} +{"id": 170, "vector": [29737.252211459952, 50074.83084461397, 72654.82829624874, 94292.0450639273, 44886.177866205355, 50024.66844604606, 76500.86916779891, 54114.84602185158, 28324.369687858365, 90726.40741417915, 86360.6558242908, 21985.521393757514, 60494.648555119056, 65379.30051389017, 99149.3737348302, 95965.3634048438, 55771.67456953077, 14697.31313832745, 45479.11613058604, 33787.28866786708, 6369.486617385655, 8493.054251508991, 30939.223699042584, 55951.55716967824, 16528.535441638047, 33072.238043654215, 99351.92481771336, 92143.86316960417, 36297.34067848264, 30132.001211926905, 4850.958462454147, 18706.232500012255, 16323.094014713046, 16772.2195729324, 8345.5505310995, 97636.56136910181, 97444.59362788794, 7983.517273227503, 69482.92151556449, 36058.72767958904, 33549.43105078223, 78641.92460053113, 54706.897633794746, 80864.99990747073, 20505.382512749493, 59741.48042542693, 5646.874847912264, 3556.153332399714, 60226.57192409264, 17206.922046904394, 90831.28100692533, 79804.34676082735, 39729.20873784107, 40845.19173085176, 20491.871044888732, 19199.177793679668, 32661.22774866733, 12548.21769048423, 78971.64485100456, 69206.43975745706, 44038.98134256145, 73805.18535619437, 20104.54834008354, 187.97655023214955, 66159.02843340822, 42523.75132273856, 71505.50570722959, 79543.68839756039, 93587.4089474684, 54576.55238080536, 45773.41890502063, 6714.80697685829, 39462.2756204582, 60824.499097292384, 11409.835119910094, 72835.95372120281, 49262.21343096579, 16305.743285347618, 97855.03896462894, 56450.05950131822, 39499.70359914487, 37689.456190460434, 18016.92455714805, 3487.3394866110298, 30005.309523884705, 36197.52946339236, 72066.52962508038, 3171.485597755963, 88894.00865561068, 99280.00291051061, 1883.0949579163648, 96193.08259089715, 97752.94934749596, 14809.414744868187, 91006.97233144606, 5887.931049444306, 78731.0315340713, 43532.66647414779, 25006.860019416465, 42206.33476801444, 80702.50461036449, 38426.14690063531, 2539.1941987939927, 76568.46900915286, 17040.557804715816, 30232.399251903964, 87094.77236183264, 48875.24565822828, 75581.32321332113, 24056.085435030196, 38908.95864225909, 91489.33756374977, 1095.4730291993785, 99128.17729585216, 50560.22111393531, 33486.95836600909, 11668.775686222132, 12776.87910644919, 13076.80020078057, 709.9308163285966, 8109.805026858541, 89735.81539256757, 34456.66210760404, 64569.28836010686, 24907.728220877667, 8758.66896743418, 84008.66277010771, 41390.32825776656]} +{"id": 675, "vector": [10038.303183218977, 23135.44079437444, 62747.313113367345, 75232.75513742247, 48824.41047992154, 77494.96237336297, 90976.49365542053, 48303.789187416165, 78744.12447833511, 68916.47528552925, 82858.86646156482, 21157.01661587569, 61574.98447653555, 13635.506346290416, 59605.250820501795, 33020.34635441973, 31982.919642113706, 63565.19720728557, 57597.689632326386, 53779.11867314302, 97150.64818465925, 86915.83895884252, 66237.12593070292, 79719.16695729681, 92393.6956928766, 31885.818994017733, 55689.08021355658, 43207.898120869984, 22023.013993426364, 39542.69757449015, 99762.74159053488, 79405.96999762814, 5953.2617111485715, 33904.88457925196, 3055.0344796011595, 12720.679440458327, 34831.41747100661, 99906.47883142452, 69129.0014603982, 84595.89841191268, 83517.41569393207, 74573.28394849686, 85710.01434569598, 36557.43527056968, 80395.55447006332, 66085.89051084184, 14571.38717381542, 76956.14308607983, 29431.58874768427, 48987.02135025976, 79469.53631061575, 82269.44078714054, 91174.3378334526, 33402.13791025971, 53747.55012692668, 8956.980362467693, 20779.52695326366, 82321.88790498539, 69153.49217351864, 41669.838278122064, 62616.99078522129, 58964.66225348554, 16698.9072121952, 94388.46726332264, 63035.07173327085, 66537.19020466106, 15906.675173247875, 16608.282117323914, 94780.89222126645, 39846.82958508111, 50985.25909407315, 67762.43761920616, 89482.43699743463, 94543.65125844459, 24503.322725595124, 33261.01086820253, 82641.55634040301, 49622.81161867541, 49428.065128218754, 65342.881176184375, 6729.332765802743, 82636.08536197645, 66702.83252752917, 54544.233313616554, 1509.3809473216925, 11021.1484635227, 39063.23823653732, 63298.203803741206, 34316.572632665186, 9591.902738653169, 52622.57353800004, 57595.013283828135, 22736.557567908356, 13831.833957673167, 99574.38429000994, 80246.93332987413, 53418.0933162543, 41982.06696067427, 48890.50876429549, 9811.762909232435, 34763.49510801099, 73460.36836562213, 87838.36412630393, 5080.659566771084, 25746.51875886935, 29722.23987248218, 17975.164331089287, 6575.168289763844, 31493.118134101594, 77046.80300100464, 37683.616180801335, 83664.04302809334, 59486.75334274011, 91978.88567520186, 74752.75029594456, 91140.45801893566, 85220.55550071907, 7448.660371265969, 41246.56370188586, 17383.22499052618, 45190.075558118595, 58433.24471051876, 88029.9179806004, 21131.92628090781, 59848.52589689047, 32385.91706052971, 15005.016396367699, 14963.046974981786]} +{"id": 1770, "vector": [76583.91505897902, 83261.31307212632, 89283.32137808684, 19094.3324742713, 45126.71763905124, 72811.88030063188, 30871.924679093132, 99143.91353740633, 79708.8563504337, 16619.186422954936, 44940.3354316183, 80213.1140113909, 3962.9330101389805, 18085.427313657332, 93457.37608500817, 22871.435849726153, 95009.26870358805, 79833.24584849432, 31482.860732525864, 57594.32596589958, 50810.99758219545, 9710.278263712902, 72554.9209996637, 31728.4156159387, 5534.979070097778, 14348.31685066953, 83420.79359814373, 46773.798985580426, 39597.92287742848, 72925.55143382981, 162.00047042196354, 96119.95249331578, 7898.195928076535, 52241.87488083718, 89220.08009262902, 28214.390068258, 19801.11426350143, 15067.669500119351, 69750.54231959094, 57762.01619263026, 67040.79465025575, 98811.93366773993, 59698.26665414133, 51936.299732592364, 96362.88258818387, 450.8445273385009, 35495.692340656795, 67559.22688601696, 71476.137647335, 77493.36404462061, 62715.00322276779, 6529.071979542289, 19247.304176701753, 12207.851844180217, 59573.06927776942, 75683.66725140384, 25181.98858695566, 1137.9340595019328, 24038.801203785355, 98761.93192274426, 59321.11405817254, 59378.113375387, 49653.54206399758, 95535.7166214453, 94327.56231749083, 39782.93273395971, 8217.17805859532, 11503.42563575878, 5805.8021082283085, 43988.29255275293, 17663.214070708887, 86225.61133948505, 28735.148244367025, 93553.03559282169, 65736.0395118176, 50616.74275607089, 73886.24944617198, 79416.41790122277, 61278.86623566195, 89115.51581161268, 43367.649112780215, 20260.021331596578, 11873.433470784823, 72849.51363978088, 24997.20725361553, 30207.913872150115, 81429.07437145019, 59861.08184075469, 5060.671470845435, 29754.430607868366, 19385.725432577892, 20486.08004069932, 93657.01309830186, 12534.528623926888, 75736.8215810113, 98862.56923944793, 40446.66290949596, 45782.964708291685, 46970.959016476874, 49408.979700551004, 47175.49631357805, 69010.43560593316, 61745.40208532443, 1838.2793463798341, 20123.898716401367, 96894.64027392928, 74890.981618039, 75894.51802469019, 45644.76714157045, 84538.6046043062, 35136.31384560132, 37472.244858636215, 31023.49115728633, 66648.45647706206, 13293.02118044836, 59924.71843869156, 25020.325821607094, 15433.016694757207, 56360.1814095356, 94404.60933848792, 6114.745440688274, 71574.14635464858, 32839.95296089446, 50192.40927874222, 40797.92812888386, 29455.822852106085, 62028.394939314116, 96272.01245152557]} +{"id": 992, "vector": [16065.75850873606, 20182.529628063017, 14738.17125948662, 57752.67144648238, 71840.96013971018, 79346.87946967827, 84808.5738101287, 11332.411109204577, 90336.15247127583, 7120.251926838217, 87003.79925869578, 34275.2012562704, 6093.571101580908, 41891.97604296808, 51649.779461986865, 31072.956584347612, 16592.14185739004, 99121.75928025793, 2887.826622989742, 33308.96759669323, 92281.0437446866, 83556.48423767133, 79616.04944486532, 51720.45509424718, 3300.5661371276365, 77979.6108347934, 3268.1731334675555, 73657.53686921645, 63684.94305318386, 35681.01637229396, 60673.805135645, 97166.08075476595, 7154.935351528935, 72206.24578677675, 94072.49128601517, 28196.623588590173, 4452.9221339615015, 19540.268940618167, 30372.056668684367, 35416.68167118055, 437.06051513855425, 43065.12432877683, 42016.93660204351, 7724.457077043956, 14841.068267209057, 10166.154243429992, 11977.528799704063, 62797.958883192216, 50671.770411034144, 44983.87684261149, 7482.675039848375, 40806.63047251296, 72205.3245906133, 16821.400512632088, 99806.53022472459, 2150.1188389382087, 60193.18289803602, 39911.52356845834, 29770.811655623707, 13189.673988332595, 66027.53358614078, 41760.28584765872, 8678.89217458715, 79560.77023920644, 53759.98180441698, 79892.97278792936, 86920.66262787466, 42987.36180740229, 90164.97979082822, 58298.006959136226, 43910.54020584015, 67429.94153428373, 12607.36986327442, 63872.607479506194, 91081.20081553742, 91036.06208230086, 256.80872146172095, 99682.8818785952, 8871.312547177367, 42369.44641049327, 40100.84195480367, 60166.239505476326, 73641.64576779936, 50883.5044651124, 12436.435241260813, 64815.52046336454, 54216.77318140029, 76463.14283459232, 22470.17129395188, 45289.98386948247, 87601.20538656595, 58413.602151140076, 66785.5786676984, 49678.17862363354, 88140.61171786304, 60826.84229020432, 40813.20510693166, 99903.49615778505, 31201.064777743326, 71284.63931097268, 90222.36642875259, 62271.574114292525, 96949.0050837706, 23322.52006921475, 26632.950792866995, 64746.755251809685, 87146.44648903543, 52446.39775498051, 81594.73780571566, 38306.67335484696, 46257.23781450626, 43466.5783672874, 35171.964589662864, 54792.71240130685, 38411.5023367405, 98409.40561413024, 84289.7244603919, 37024.125021819455, 46201.024807313705, 92100.77809200763, 82913.3181519639, 71962.60122067925, 28353.092915700563, 9107.587601893296, 26977.15646916118, 57527.92580996994, 54827.514803918864, 11406.375397957469]} +{"id": 1366, "vector": [77103.15880427707, 37954.101492844806, 8897.416429993355, 82429.8928758287, 3256.5867931832913, 18743.143242522354, 84616.57433712785, 3383.4723156225045, 18608.364061180473, 21654.508858617137, 28830.25133678634, 68300.532890187, 22908.816850741077, 88671.7463595862, 50314.8014874292, 66420.79808531309, 92442.20295438737, 93648.27928425254, 48425.99904411798, 19339.406778689015, 28573.37849675897, 75469.94337633603, 23721.93839777008, 36140.033190388924, 72222.69796758163, 13629.550854007211, 64263.137578463145, 84361.15608835047, 8270.768701409814, 10407.722083331828, 43888.1802378373, 92177.5120859445, 7854.777569268445, 42523.50297773296, 5120.54462742646, 79140.62768601795, 96754.68776497105, 70986.323796649, 67354.24278718847, 63496.56248548742, 99010.0224126916, 42501.988664109806, 20567.498654439707, 91208.76593053053, 18176.963697482595, 84693.12164048935, 14158.22965690371, 21688.545634993563, 52672.9009824327, 67487.99557319419, 40904.70334877493, 94701.16182285873, 48267.35857293136, 4068.3415781585295, 67491.33039933852, 17130.866161774593, 50970.95900567229, 39062.4122733374, 95269.29813443913, 79707.14465234255, 20527.647496754544, 4805.933334371615, 55468.00159362418, 13745.847248581955, 59071.77313734148, 62651.21185216228, 840.4457823266598, 19609.54748171997, 3562.9183412547395, 99589.2757357921, 53492.966796566885, 80869.84868276613, 51190.93480147617, 25428.363961887124, 18503.68607545563, 70687.36387025399, 75776.75361038795, 85168.39236167468, 86861.34871586344, 14608.955299803472, 169.81827539653827, 36494.04866878756, 50054.00932880291, 13334.061030779198, 5441.329437947007, 82935.40134474891, 423.32385930272665, 86966.54955923962, 37228.422289540984, 36337.566443142554, 11335.664721234418, 90534.62068978228, 36606.22573312101, 40313.50015862124, 91031.29770341028, 27761.510385117483, 17774.799263954166, 15055.406077827593, 96358.52653388772, 55346.53672891554, 63386.82375613406, 63860.907217565575, 11190.698963924538, 1725.8897232484305, 5363.529160510483, 68325.09179014232, 83206.72922848113, 48358.898862990216, 9750.552858483452, 20845.708828031307, 8443.38829609267, 71698.96343489492, 39064.55152797575, 35339.01458602857, 75167.92756160145, 73605.7420090605, 59013.31126463494, 43241.07033999966, 15751.130421316806, 78741.06509961942, 15776.359549343111, 69880.55330005658, 64971.620427596274, 31255.918144775052, 8638.974442757486, 89480.22582976226, 45991.504258082125, 81696.69730501868]} diff --git a/src/test/resources/lang-painless/lang-painless-1.3.0-SNAPSHOT.zip b/src/test/resources/lang-painless/lang-painless-1.3.0-SNAPSHOT.zip new file mode 100644 index 000000000..1ea8b8117 Binary files /dev/null and b/src/test/resources/lang-painless/lang-painless-1.3.0-SNAPSHOT.zip differ diff --git a/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 000000000..ca6ee9cea --- /dev/null +++ b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline \ No newline at end of file diff --git a/src/testFixtures/java/org/opensearch/knn/KNNJsonIndexMappingsBuilder.java b/src/testFixtures/java/org/opensearch/knn/KNNJsonIndexMappingsBuilder.java new file mode 100644 index 000000000..817684f89 --- /dev/null +++ b/src/testFixtures/java/org/opensearch/knn/KNNJsonIndexMappingsBuilder.java @@ -0,0 +1,149 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn; + +import lombok.Builder; +import lombok.NonNull; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.knn.common.KNNConstants; + +import java.io.IOException; + +/** + * Helper method to create knn index mapping in json string + * Here, we don't use any of the internal class so that it can mimic user request more closely. + */ +@Builder +public class KNNJsonIndexMappingsBuilder { + @NonNull + private String fieldName; + @NonNull + private Integer dimension; + private String nestedFieldName; + private String vectorDataType; + private Method method; + + public XContentBuilder getIndexMappingBuilder() throws IOException { + if (nestedFieldName != null) { + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(nestedFieldName) + .field("type", "nested") + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field("dimension", dimension); + addVectorDataType(xContentBuilder); + addMethod(xContentBuilder); + xContentBuilder.endObject().endObject().endObject().endObject().endObject(); + return xContentBuilder; + } else { + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field("dimension", dimension); + addVectorDataType(xContentBuilder); + addMethod(xContentBuilder); + xContentBuilder.endObject().endObject().endObject(); + return xContentBuilder; + } + } + + public String getIndexMapping() throws IOException { + return getIndexMappingBuilder().toString(); + } + + private void addVectorDataType(final XContentBuilder xContentBuilder) throws IOException { + if (vectorDataType == null) { + return; + } + xContentBuilder.field("data_type", vectorDataType); + } + + private void addMethod(final XContentBuilder xContentBuilder) throws IOException { + if (method == null) { + return; + } + method.addTo(xContentBuilder); + } + + @Builder + public static class Method { + @NonNull + private String methodName; + @NonNull + private String engine; + private String spaceType; + private Parameters parameters; + + private void addTo(final XContentBuilder xContentBuilder) throws IOException { + xContentBuilder.startObject("method").field("name", methodName).field("engine", engine); + addSpaceType(xContentBuilder); + addParameters(xContentBuilder); + xContentBuilder.endObject(); + } + + private void addSpaceType(final XContentBuilder xContentBuilder) throws IOException { + if (spaceType == null) { + return; + } + xContentBuilder.field("space_type", spaceType); + } + + private void addParameters(final XContentBuilder xContentBuilder) throws IOException { + if (parameters == null) { + return; + } + parameters.addTo(xContentBuilder); + } + + @Builder + public static class Parameters { + private Encoder encoder; + private Integer efConstruction; + private Integer efSearch; + private Integer m; + + private void addTo(final XContentBuilder xContentBuilder) throws IOException { + xContentBuilder.startObject("parameters"); + if (efConstruction != null) { + xContentBuilder.field("ef_construction", efConstruction); + } + if (efSearch != null) { + xContentBuilder.field("ef_search", efSearch); + } + if (m != null) { + xContentBuilder.field(KNNConstants.METHOD_PARAMETER_M, m); + } + addEncoder(xContentBuilder); + xContentBuilder.endObject(); + } + + private void addEncoder(final XContentBuilder xContentBuilder) throws IOException { + if (encoder == null) { + return; + } + encoder.addTo(xContentBuilder); + } + + @Builder + public static class Encoder { + @NonNull + private String name; + + private void addTo(final XContentBuilder xContentBuilder) throws IOException { + xContentBuilder.startObject("encoder"); + xContentBuilder.field("name", name); + xContentBuilder.endObject(); + } + } + } + } +} diff --git a/src/testFixtures/java/org/opensearch/knn/KNNJsonQueryBuilder.java b/src/testFixtures/java/org/opensearch/knn/KNNJsonQueryBuilder.java new file mode 100644 index 000000000..c8cd8e6bd --- /dev/null +++ b/src/testFixtures/java/org/opensearch/knn/KNNJsonQueryBuilder.java @@ -0,0 +1,67 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.knn; + +import lombok.Builder; +import lombok.NonNull; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.XContentBuilder; + +import java.io.IOException; + +/** + * Helper method to create knn query in json string + * Here, we don't use any of the internal class so that it can mimic user request more closely. + */ +@Builder +public class KNNJsonQueryBuilder { + @NonNull + private String fieldName; + @NonNull + private Object[] vector; + private Integer k; + private Float minScore; + private String nestedFieldName; + private String filterFieldName; + private String filterValue; + + public String getQueryString() throws IOException { + XContentBuilder builder = XContentFactory.jsonBuilder().startObject().startObject("query"); + if (nestedFieldName != null) { + builder.startObject("nested"); + builder.field("path", nestedFieldName); + builder.startObject("query"); + builder.startObject("knn"); + builder.startObject(nestedFieldName + "." + fieldName); + } else { + builder.startObject("knn"); + builder.startObject(fieldName); + } + + builder.field("vector", vector); + if (k != null) { + builder.field("k", k); + } + if (minScore != null) { + builder.field("min_score", minScore); + } + + if (filterFieldName != null && filterValue != null) { + builder.startObject("filter"); + builder.startObject("term"); + builder.field(filterFieldName, filterValue); + builder.endObject(); + builder.endObject(); + } + + builder.endObject().endObject().endObject().endObject(); + if (nestedFieldName != null) { + builder.endObject().endObject(); + } + + return builder.toString(); + } +} diff --git a/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java b/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java index 932ae04b5..03a99c0bd 100644 --- a/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java +++ b/src/testFixtures/java/org/opensearch/knn/KNNRestTestCase.java @@ -5,37 +5,45 @@ package org.opensearch.knn; -import com.google.common.base.Charsets; -import com.google.common.io.Resources; +import com.google.common.primitives.Bytes; import com.google.common.primitives.Floats; +import com.google.common.primitives.Ints; +import lombok.SneakyThrows; +import lombok.extern.log4j.Log4j2; import org.apache.commons.lang.StringUtils; -import org.opensearch.common.bytes.BytesReference; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.util.EntityUtils; +import org.opensearch.Version; +import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.xcontent.XContentHelper; +import org.opensearch.core.xcontent.DeprecationHandler; +import org.opensearch.core.xcontent.MediaTypeRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentParser; import org.opensearch.index.query.MatchAllQueryBuilder; -import org.opensearch.knn.index.KNNQueryBuilder; +import org.opensearch.knn.common.KNNConstants; +import org.opensearch.knn.index.VectorDataType; +import org.opensearch.knn.index.query.KNNQueryBuilder; import org.opensearch.knn.index.KNNSettings; import org.opensearch.knn.index.SpaceType; -import org.opensearch.knn.indices.ModelDao; -import org.opensearch.knn.indices.ModelMetadata; import org.opensearch.knn.indices.ModelState; import org.opensearch.knn.plugin.KNNPlugin; import org.opensearch.knn.plugin.script.KNNScoringScriptEngine; -import org.apache.http.util.EntityUtils; import org.junit.AfterClass; import org.junit.Before; import org.opensearch.client.Request; import org.opensearch.client.Response; -import org.opensearch.common.Strings; import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.ToXContent; -import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; -import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.MediaType; import org.opensearch.index.query.ExistsQueryBuilder; import org.opensearch.index.query.QueryBuilder; import org.opensearch.index.query.functionscore.ScriptScoreQueryBuilder; -import org.opensearch.rest.RestStatus; +import org.opensearch.core.rest.RestStatus; import org.opensearch.script.Script; +import org.opensearch.search.SearchService; import org.opensearch.search.aggregations.metrics.ScriptedMetricAggregationBuilder; import javax.management.MBeanServerInvocationHandler; @@ -46,34 +54,34 @@ import javax.management.remote.JMXServiceURL; import java.io.IOException; -import java.net.URL; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; -import java.util.Base64; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.PriorityQueue; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static org.opensearch.knn.common.KNNConstants.DIMENSION; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_CODE_SIZE; +import static org.opensearch.knn.common.KNNConstants.ENCODER_PARAMETER_PQ_M; import static org.opensearch.knn.common.KNNConstants.KNN_ENGINE; import static org.opensearch.knn.common.KNNConstants.KNN_METHOD; +import static org.opensearch.knn.common.KNNConstants.METHOD_ENCODER_PARAMETER; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_SEARCH; +import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_NLIST; import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_SPACE_TYPE; -import static org.opensearch.knn.common.KNNConstants.MODEL_BLOB_PARAMETER; import static org.opensearch.knn.common.KNNConstants.MODEL_DESCRIPTION; -import static org.opensearch.knn.common.KNNConstants.MODEL_ERROR; -import static org.opensearch.knn.common.KNNConstants.MODEL_ID; -import static org.opensearch.knn.common.KNNConstants.MODEL_INDEX_MAPPING_PATH; -import static org.opensearch.knn.common.KNNConstants.MODEL_INDEX_NAME; import static org.opensearch.knn.common.KNNConstants.MODEL_STATE; -import static org.opensearch.knn.common.KNNConstants.MODEL_TIMESTAMP; import static org.opensearch.knn.common.KNNConstants.TRAIN_FIELD_PARAMETER; import static org.opensearch.knn.common.KNNConstants.TRAIN_INDEX_PARAMETER; import static org.opensearch.knn.common.KNNConstants.NAME; @@ -81,6 +89,7 @@ import static org.opensearch.knn.common.KNNConstants.PARAMETERS; import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_EF_CONSTRUCTION; import static org.opensearch.knn.common.KNNConstants.METHOD_PARAMETER_M; +import static org.opensearch.knn.common.KNNConstants.CLEAR_CACHE; import static org.opensearch.knn.TestUtils.NUMBER_OF_REPLICAS; import static org.opensearch.knn.TestUtils.NUMBER_OF_SHARDS; @@ -92,28 +101,47 @@ import static org.opensearch.knn.TestUtils.QUERY_VALUE; import static org.opensearch.knn.TestUtils.computeGroundTruthValues; +import static org.opensearch.knn.common.KNNConstants.VECTOR_DATA_TYPE_FIELD; +import static org.opensearch.knn.index.KNNSettings.INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD; +import static org.opensearch.knn.index.KNNSettings.KNN_INDEX; +import static org.opensearch.knn.index.SpaceType.L2; import static org.opensearch.knn.index.memory.NativeMemoryCacheManager.GRAPH_COUNT; +import static org.opensearch.knn.index.engine.KNNEngine.FAISS; import static org.opensearch.knn.plugin.stats.StatNames.INDICES_IN_CACHE; /** * Base class for integration tests for KNN plugin. Contains several methods for testing KNN ES functionality. */ +@Log4j2 public class KNNRestTestCase extends ODFERestTestCase { public static final String INDEX_NAME = "test_index"; public static final String FIELD_NAME = "test_field"; + public static final String FIELD_NAME_NON_KNN = "test_field_non_knn"; + public static final String PROPERTIES_FIELD = "properties"; + public static final String STORE_FIELD = "store"; + public static final String STORED_QUERY_FIELD = "stored_fields"; + public static final String MATCH_ALL_QUERY_FIELD = "match_all"; private static final String DOCUMENT_FIELD_SOURCE = "_source"; private static final String DOCUMENT_FIELD_FOUND = "found"; + protected static final int DELAY_MILLI_SEC = 1000; + protected static final int NUM_OF_ATTEMPTS = 30; + private static final String SYSTEM_INDEX_PREFIX = ".opendistro"; @AfterClass public static void dumpCoverage() throws IOException, MalformedObjectNameException { // jacoco.dir is set in esplugin-coverage.gradle, if it doesn't exist we don't // want to collect coverage so we can return early String jacocoBuildPath = System.getProperty("jacoco.dir"); - if (Strings.isNullOrEmpty(jacocoBuildPath)) { + if (org.opensearch.core.common.Strings.isNullOrEmpty(jacocoBuildPath)) { return; } String serverUrl = System.getProperty("jmx.serviceUrl"); + if (serverUrl == null) { + log.error("Failed to dump coverage because JMX Service URL is null"); + throw new IllegalArgumentException("JMX Service URL is null"); + } + try (JMXConnector connector = JMXConnectorFactory.connect(new JMXServiceURL(serverUrl))) { IProxy proxy = MBeanServerInvocationHandler.newProxyInstance( connector.getMBeanServerConnection(), @@ -122,9 +150,10 @@ public static void dumpCoverage() throws IOException, MalformedObjectNameExcepti false ); - Path path = Paths.get(jacocoBuildPath + "/integTest.exec"); + Path path = Path.of(Path.of(jacocoBuildPath, "integTest.exec").toFile().getCanonicalPath()); Files.write(path, proxy.getExecutionData(false)); } catch (Exception ex) { + log.error("Failed to dump coverage: ", ex); throw new RuntimeException("Failed to dump coverage: " + ex); } } @@ -151,36 +180,51 @@ protected void createKnnIndex(String index, Settings settings, String mapping) t } protected void createBasicKnnIndex(String index, String fieldName, int dimension) throws IOException { - String mapping = Strings.toString( - XContentFactory.jsonBuilder() - .startObject() - .startObject("properties") - .startObject(fieldName) - .field("type", "knn_vector") - .field("dimension", Integer.toString(dimension)) - .endObject() - .endObject() - .endObject() - ); + String mapping = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field("dimension", Integer.toString(dimension)) + .endObject() + .endObject() + .endObject() + .toString(); mapping = mapping.substring(1, mapping.length() - 1); createIndex(index, Settings.EMPTY, mapping); } /** - * Run KNN Search on Index + * Deprecated + * To better simulate user request, use {@link #searchKNNIndex(String, XContentBuilder, int)} instead */ + @Deprecated protected Response searchKNNIndex(String index, KNNQueryBuilder knnQueryBuilder, int resultSize) throws IOException { XContentBuilder builder = XContentFactory.jsonBuilder().startObject().startObject("query"); knnQueryBuilder.doXContent(builder, ToXContent.EMPTY_PARAMS); builder.endObject().endObject(); + return searchKNNIndex(index, builder, resultSize); + } + + /** + * Run KNN Search on Index with XContentBuilder query + */ + protected Response searchKNNIndex(String index, XContentBuilder xContentBuilder, int resultSize) throws IOException { + return searchKNNIndex(index, xContentBuilder.toString(), resultSize); + } + /** + * Run KNN Search on Index with json string query + */ + protected Response searchKNNIndex(String index, String query, int resultSize) throws IOException { Request request = new Request("POST", "/" + index + "/_search"); + request.setJsonEntity(query); request.addParameter("size", Integer.toString(resultSize)); - request.addParameter("explain", Boolean.toString(true)); request.addParameter("search_type", "query_then_fetch"); - request.setJsonEntity(Strings.toString(builder)); + // Nested field does not support explain parameter and the request is rejected if we set explain parameter + // request.addParameter("explain", Boolean.toString(true)); Response response = client().performRequest(request); assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); @@ -201,7 +245,7 @@ protected Response searchExists(String index, ExistsQueryBuilder existsQueryBuil builder.endObject(); request.addParameter("size", Integer.toString(resultSize)); - request.setJsonEntity(Strings.toString(builder)); + request.setJsonEntity(builder.toString()); Response response = client().performRequest(request); assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); @@ -209,21 +253,42 @@ protected Response searchExists(String index, ExistsQueryBuilder existsQueryBuil return response; } + protected Response performSearch(final String indexName, final String query) throws IOException { + return performSearch(indexName, query, ""); + } + + protected Response performSearch(final String indexName, final String query, final String urlParameters) throws IOException { + Request request = new Request("POST", "/" + indexName + "/_search?" + urlParameters); + request.setJsonEntity(query); + + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + return response; + } + /** * Parse the response of KNN search into a List of KNNResults */ protected List parseSearchResponse(String responseBody, String fieldName) throws IOException { @SuppressWarnings("unchecked") - List hits = (List) ((Map) createParser(XContentType.JSON.xContent(), responseBody).map() - .get("hits")).get("hits"); + List hits = (List) ((Map) createParser( + MediaTypeRegistry.getDefaultMediaType().xContent(), + responseBody + ).map().get("hits")).get("hits"); @SuppressWarnings("unchecked") List knnSearchResponses = hits.stream().map(hit -> { @SuppressWarnings("unchecked") - Float[] vector = Arrays.stream( - ((ArrayList) ((Map) ((Map) hit).get("_source")).get(fieldName)).toArray() - ).map(Object::toString).map(Float::valueOf).toArray(Float[]::new); - return new KNNResult((String) ((Map) hit).get("_id"), vector); + final float[] vector = Floats.toArray( + Arrays.stream( + ((ArrayList) ((Map) ((Map) hit).get("_source")).get(fieldName)).toArray() + ).map(Object::toString).map(Float::valueOf).collect(Collectors.toList()) + ); + return new KNNResult( + (String) ((Map) hit).get("_id"), + vector, + ((Double) ((Map) hit).get("_score")).floatValue() + ); }).collect(Collectors.toList()); return knnSearchResponses; @@ -231,8 +296,10 @@ protected List parseSearchResponse(String responseBody, String fieldN protected List parseSearchResponseScore(String responseBody, String fieldName) throws IOException { @SuppressWarnings("unchecked") - List hits = (List) ((Map) createParser(XContentType.JSON.xContent(), responseBody).map() - .get("hits")).get("hits"); + List hits = (List) ((Map) createParser( + MediaTypeRegistry.getDefaultMediaType().xContent(), + responseBody + ).map().get("hits")).get("hits"); @SuppressWarnings("unchecked") List knnSearchResponses = hits.stream() @@ -242,13 +309,40 @@ protected List parseSearchResponseScore(String responseBody, String field return knnSearchResponses; } + protected List parseSearchResponseScriptFields(final String responseBody, final String scriptFieldName) throws IOException { + @SuppressWarnings("unchecked") + List hits = (List) ((Map) createParser( + MediaTypeRegistry.getDefaultMediaType().xContent(), + responseBody + ).map().get("hits")).get("hits"); + + @SuppressWarnings("unchecked") + List knnSearchResponses = hits.stream().map(hit -> { + @SuppressWarnings("unchecked") + final float[] vector = Floats.toArray( + Arrays.stream( + ((ArrayList) ((Map) ((Map) hit).get("fields")).get(scriptFieldName)).toArray() + ).map(Object::toString).map(Float::valueOf).collect(Collectors.toList()) + ); + return new KNNResult( + (String) ((Map) hit).get("_id"), + vector, + ((Double) ((Map) hit).get("_score")).floatValue() + ); + }).collect(Collectors.toList()); + + return knnSearchResponses; + } + /** * Parse the response of Aggregation to extract the value */ protected Double parseAggregationResponse(String responseBody, String aggregationName) throws IOException { @SuppressWarnings("unchecked") - Map aggregations = ((Map) createParser(XContentType.JSON.xContent(), responseBody).map() - .get("aggregations")); + Map aggregations = ((Map) createParser( + MediaTypeRegistry.getDefaultMediaType().xContent(), + responseBody + ).map().get("aggregations")); final Map values = (Map) aggregations.get(aggregationName); return Double.valueOf(String.valueOf(values.get("value"))); @@ -268,6 +362,12 @@ protected void deleteKNNIndex(String index) throws IOException { assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); } + protected void closeKNNIndex(String index) throws IOException { + Request request = new Request("POST", "/" + index + "/_close"); + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + } + /** * For a given index, make a mapping request */ @@ -284,17 +384,103 @@ protected void putMappingRequest(String index, String mapping) throws IOExceptio * Utility to create a Knn Index Mapping */ protected String createKnnIndexMapping(String fieldName, Integer dimensions) throws IOException { - return Strings.toString( - XContentFactory.jsonBuilder() - .startObject() - .startObject("properties") - .startObject(fieldName) - .field("type", "knn_vector") - .field("dimension", dimensions.toString()) - .endObject() - .endObject() - .endObject() - ); + return XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field("dimension", dimensions.toString()) + .endObject() + .endObject() + .endObject() + .toString(); + } + + protected String createKnnIndexMapping(final String fieldName, final Integer dimensions, final VectorDataType vectorDataType) + throws IOException { + return XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field("type", "knn_vector") + .field("dimension", dimensions.toString()) + .field(VECTOR_DATA_TYPE_FIELD, vectorDataType.getValue()) + .endObject() + .endObject() + .endObject() + .toString(); + } + + /** + * Utility to create a Knn Index Mapping with specific algorithm and engine + */ + protected String createKnnIndexMapping(String fieldName, Integer dimensions, String algoName, String knnEngine) throws IOException { + return this.createKnnIndexMapping(fieldName, dimensions, algoName, knnEngine, SpaceType.DEFAULT.getValue()); + } + + /** + * Utility to create a Knn Index Mapping with specific algorithm, engine and spaceType + */ + protected String createKnnIndexMapping(String fieldName, Integer dimensions, String algoName, String knnEngine, String spaceType) + throws IOException { + return this.createKnnIndexMapping(fieldName, dimensions, algoName, knnEngine, spaceType, true); + } + + /** + * Utility to create a Knn Index Mapping with specific algorithm, engine, spaceType and docValues + */ + protected String createKnnIndexMapping( + String fieldName, + Integer dimensions, + String algoName, + String knnEngine, + String spaceType, + boolean docValues + ) throws IOException { + return XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field(KNNConstants.TYPE, KNNConstants.TYPE_KNN_VECTOR) + .field(KNNConstants.DIMENSION, dimensions.toString()) + .field("doc_values", docValues) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, algoName) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType) + .field(KNNConstants.KNN_ENGINE, knnEngine) + .endObject() + .endObject() + .endObject() + .endObject() + .toString(); + } + + protected String createKnnIndexMapping( + String fieldName, + Integer dimensions, + String algoName, + String knnEngine, + String spaceType, + boolean docValues, + VectorDataType vectorDataType + ) throws IOException { + return XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) + .field(KNNConstants.TYPE, KNNConstants.TYPE_KNN_VECTOR) + .field(KNNConstants.DIMENSION, dimensions.toString()) + .field(VECTOR_DATA_TYPE_FIELD, vectorDataType.getValue()) + .field("doc_values", docValues) + .startObject(KNNConstants.KNN_METHOD) + .field(KNNConstants.NAME, algoName) + .field(KNNConstants.METHOD_PARAMETER_SPACE_TYPE, spaceType) + .field(KNNConstants.KNN_ENGINE, knnEngine) + .endObject() + .endObject() + .endObject() + .endObject() + .toString(); } /** @@ -313,7 +499,39 @@ protected String createKnnIndexMapping(List fieldNames, List di } xContentBuilder.endObject().endObject(); - return Strings.toString(xContentBuilder); + return xContentBuilder.toString(); + } + + /** + * Utility to create a Knn Index Mapping with nested field + * + * @param dimensions dimension of the vector + * @param fieldPath path of the nested field, e.g. "my_nested_field.my_vector" + * @return mapping string for the nested field + */ + protected String createKnnIndexNestedMapping(Integer dimensions, String fieldPath) throws IOException { + String[] fieldPathArray = fieldPath.split("\\."); + XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("properties"); + + for (int i = 0; i < fieldPathArray.length; i++) { + xContentBuilder.startObject(fieldPathArray[i]); + if (i == fieldPathArray.length - 1) { + xContentBuilder.field("type", "knn_vector").field("dimension", dimensions.toString()); + } else { + xContentBuilder.startObject("properties"); + } + } + + for (int i = fieldPathArray.length - 1; i >= 0; i--) { + if (i != fieldPathArray.length - 1) { + xContentBuilder.endObject(); + } + xContentBuilder.endObject(); + } + + xContentBuilder.endObject().endObject(); + + return xContentBuilder.toString(); } /** @@ -323,7 +541,7 @@ protected String createKnnIndexMapping(List fieldNames, List di * @return index mapping a map */ @SuppressWarnings("unchecked") - public Map getIndexMappingAsMap(String index) throws IOException { + public Map getIndexMappingAsMap(String index) throws Exception { Request request = new Request("GET", "/" + index + "/_mapping"); Response response = client().performRequest(request); @@ -332,12 +550,12 @@ public Map getIndexMappingAsMap(String index) throws IOException String responseBody = EntityUtils.toString(response.getEntity()); - Map responseMap = createParser(XContentType.JSON.xContent(), responseBody).map(); + Map responseMap = createParser(MediaTypeRegistry.getDefaultMediaType().xContent(), responseBody).map(); return (Map) ((Map) responseMap.get(index)).get("mappings"); } - public int getDocCount(String indexName) throws IOException { + public int getDocCount(String indexName) throws Exception { Request request = new Request("GET", "/" + indexName + "/_count"); Response response = client().performRequest(request); @@ -346,7 +564,7 @@ public int getDocCount(String indexName) throws IOException { String responseBody = EntityUtils.toString(response.getEntity()); - Map responseMap = createParser(XContentType.JSON.xContent(), responseBody).map(); + Map responseMap = createParser(MediaTypeRegistry.getDefaultMediaType().xContent(), responseBody).map(); return (Integer) responseMap.get("count"); } @@ -354,6 +572,13 @@ public int getDocCount(String indexName) throws IOException { * Force merge KNN index segments */ protected void forceMergeKnnIndex(String index) throws Exception { + forceMergeKnnIndex(index, 1); + } + + /** + * Force merge KNN index segments + */ + protected void forceMergeKnnIndex(String index, int maxSegments) throws Exception { Request request = new Request("POST", "/" + index + "/_refresh"); Response response = client().performRequest(request); @@ -361,7 +586,7 @@ protected void forceMergeKnnIndex(String index) throws Exception { request = new Request("POST", "/" + index + "/_forcemerge"); - request.addParameter("max_num_segments", "1"); + request.addParameter("max_num_segments", String.valueOf(maxSegments)); request.addParameter("flush", "true"); response = client().performRequest(request); assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); @@ -371,11 +596,54 @@ protected void forceMergeKnnIndex(String index) throws Exception { /** * Add a single KNN Doc to an index */ - protected void addKnnDoc(String index, String docId, String fieldName, Object[] vector) throws IOException { + protected void addKnnDoc(String index, String docId, String fieldName, T vector) throws IOException { Request request = new Request("POST", "/" + index + "/_doc/" + docId + "?refresh=true"); XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field(fieldName, vector).endObject(); - request.setJsonEntity(Strings.toString(builder)); + request.setJsonEntity(builder.toString()); + client().performRequest(request); + + request = new Request("POST", "/" + index + "/_refresh"); + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + } + + protected void addNonKNNDoc(String index, String docId, String fieldName, String text) throws IOException { + Request request = new Request("POST", "/" + index + "/_doc/" + docId + "?refresh=true"); + + XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field(fieldName, text).endObject(); + request.setJsonEntity(builder.toString()); + client().performRequest(request); + + request = new Request("POST", "/" + index + "/_refresh"); + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + } + + /** + * Add a single KNN Doc to an index with a nested vector field + * + * @param index name of the index + * @param docId id of the document + * @param nestedFieldPath path of the nested field, e.g. "my_nested_field.my_vector" + * @param vector vector to add + * + */ + protected void addKnnDocWithNestedField(String index, String docId, String nestedFieldPath, Object[] vector) throws IOException { + String[] fieldParts = nestedFieldPath.split("\\."); + + XContentBuilder builder = XContentFactory.jsonBuilder().startObject(); + for (int i = 0; i < fieldParts.length - 1; i++) { + builder.startObject(fieldParts[i]); + } + builder.field(fieldParts[fieldParts.length - 1], vector); + for (int i = fieldParts.length - 2; i >= 0; i--) { + builder.endObject(); + } + builder.endObject(); + + Request request = new Request("POST", "/" + index + "/_doc/" + docId + "?refresh=true"); + request.setJsonEntity(builder.toString()); client().performRequest(request); request = new Request("POST", "/" + index + "/_refresh"); @@ -387,7 +655,12 @@ protected void addKnnDoc(String index, String docId, String fieldName, Object[] * Add a single KNN Doc to an index with multiple fields */ protected void addKnnDoc(String index, String docId, List fieldNames, List vectors) throws IOException { - Request request = new Request("POST", "/" + index + "/_doc/" + docId + "?refresh=true"); + addKnnDoc(index, docId, fieldNames, vectors, true); + } + + protected void addKnnDoc(String index, String docId, List fieldNames, List vectors, boolean refresh) + throws IOException { + Request request = new Request("POST", "/" + index + "/_doc/" + docId + "?refresh=" + refresh); XContentBuilder builder = XContentFactory.jsonBuilder().startObject(); for (int i = 0; i < fieldNames.size(); i++) { @@ -395,11 +668,20 @@ protected void addKnnDoc(String index, String docId, List fieldNames, Li } builder.endObject(); - request.setJsonEntity(Strings.toString(builder)); + request.setJsonEntity(builder.toString()); Response response = client().performRequest(request); assertEquals(request.getEndpoint() + ": failed", RestStatus.CREATED, RestStatus.fromCode(response.getStatusLine().getStatusCode())); } + /** + * Adds a doc where document is represented as a string. + */ + protected void addKnnDoc(final String index, final String docId, final String document) throws IOException { + Request request = new Request("POST", "/" + index + "/_doc/" + docId); + request.setJsonEntity(document); + client().performRequest(request); + } + /** * Add a single numeric field Doc to an index */ @@ -408,7 +690,7 @@ protected void addDocWithNumericField(String index, String docId, String fieldNa XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field(fieldName, value).endObject(); - request.setJsonEntity(Strings.toString(builder)); + request.setJsonEntity(builder.toString()); Response response = client().performRequest(request); @@ -423,7 +705,7 @@ protected void addDocWithBinaryField(String index, String docId, String fieldNam XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field(fieldName, base64String).endObject(); - request.setJsonEntity(Strings.toString(builder)); + request.setJsonEntity(builder.toString()); Response response = client().performRequest(request); @@ -438,7 +720,7 @@ protected void updateKnnDoc(String index, String docId, String fieldName, Object XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field(fieldName, vector).endObject(); - request.setJsonEntity(Strings.toString(builder)); + request.setJsonEntity(builder.toString()); Response response = client().performRequest(request); assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); @@ -458,12 +740,14 @@ protected void deleteKnnDoc(String index, String docId) throws IOException { /** * Retrieve document by index and document id */ - protected Map getKnnDoc(final String index, final String docId) throws IOException { + protected Map getKnnDoc(final String index, final String docId) throws Exception { final Request request = new Request("GET", "/" + index + "/_doc/" + docId); final Response response = client().performRequest(request); - final Map responseMap = createParser(XContentType.JSON.xContent(), EntityUtils.toString(response.getEntity())) - .map(); + final Map responseMap = createParser( + MediaTypeRegistry.getDefaultMediaType().xContent(), + EntityUtils.toString(response.getEntity()) + ).map(); assertNotNull(responseMap); assertTrue((Boolean) responseMap.get(DOCUMENT_FIELD_FOUND)); @@ -485,7 +769,7 @@ protected void updateClusterSettings(String settingKey, Object value) throws Exc .endObject() .endObject(); Request request = new Request("PUT", "_cluster/settings"); - request.setJsonEntity(Strings.toString(builder)); + request.setJsonEntity(builder.toString()); Response response = client().performRequest(request); assertEquals(RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); } @@ -497,6 +781,44 @@ protected Settings getKNNDefaultIndexSettings() { return Settings.builder().put("number_of_shards", 1).put("number_of_replicas", 0).put("index.knn", true).build(); } + protected Settings getKNNSegmentReplicatedIndexSettings() { + return Settings.builder() + .put("number_of_shards", 1) + .put("number_of_replicas", 1) + .put("index.knn", true) + .put("index.replication.type", "SEGMENT") + .build(); + } + + protected Settings buildKNNIndexSettings(int approximateThreshold) { + return Settings.builder() + .put("number_of_shards", 1) + .put("number_of_replicas", 0) + .put(KNN_INDEX, true) + .put(INDEX_KNN_ADVANCED_APPROXIMATE_THRESHOLD, approximateThreshold) + .build(); + } + + @SneakyThrows + protected int getDataNodeCount() { + Request request = new Request("GET", "_nodes/stats?filter_path=nodes.*.roles"); + + Response response = client().performRequest(request); + assertEquals(RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + String responseBody = EntityUtils.toString(response.getEntity()); + + Map responseMap = createParser(MediaTypeRegistry.getDefaultMediaType().xContent(), responseBody).map(); + Map nodesInfo = (Map) responseMap.get("nodes"); + int dataNodeCount = 0; + for (String key : nodesInfo.keySet()) { + Map> nodeRoles = (Map>) nodesInfo.get(key); + if (nodeRoles.get("roles").contains("data")) { + dataNodeCount++; + } + } + return dataNodeCount; + } + /** * Get Stats from KNN Plugin */ @@ -522,6 +844,12 @@ protected Response executeKnnStatRequest(List nodeIds, List stat return response; } + @SneakyThrows + protected void doKnnWarmup(List indices) { + Response response = knnWarmup(indices); + assertEquals(response.getStatusLine().getStatusCode(), 200); + } + /** * Warmup KNN Index */ @@ -535,11 +863,25 @@ protected Response executeWarmupRequest(List indices, final String baseU return client().performRequest(request); } + /** + * Evicts valid k-NN indices from the cache. + * + * @param indices list of k-NN indices that needs to be removed from cache + * @return Response of clear Cache API request + * @throws IOException + */ + protected Response clearCache(List indices) throws IOException { + String indicesSuffix = String.join(",", indices); + String restURI = String.join("/", KNNPlugin.KNN_BASE_URI, CLEAR_CACHE, indicesSuffix); + Request request = new Request("POST", restURI); + return client().performRequest(request); + } + /** * Parse KNN Cluster stats from response */ protected Map parseClusterStatsResponse(String responseBody) throws IOException { - Map responseMap = createParser(XContentType.JSON.xContent(), responseBody).map(); + Map responseMap = createParser(MediaTypeRegistry.getDefaultMediaType().xContent(), responseBody).map(); responseMap.remove("cluster_name"); responseMap.remove("_nodes"); responseMap.remove("nodes"); @@ -551,7 +893,10 @@ protected Map parseClusterStatsResponse(String responseBody) thr */ protected List> parseNodeStatsResponse(String responseBody) throws IOException { @SuppressWarnings("unchecked") - Map responseMap = (Map) createParser(XContentType.JSON.xContent(), responseBody).map().get("nodes"); + Map responseMap = (Map) createParser( + MediaTypeRegistry.getDefaultMediaType().xContent(), + responseBody + ).map().get("nodes"); @SuppressWarnings("unchecked") List> nodeResponses = responseMap.keySet() @@ -567,17 +912,37 @@ protected List> parseNodeStatsResponse(String responseBody) */ @SuppressWarnings("unchecked") protected int parseTotalSearchHits(String searchResponseBody) throws IOException { - Map responseMap = (Map) createParser(XContentType.JSON.xContent(), searchResponseBody).map() - .get("hits"); + Map responseMap = (Map) createParser( + MediaTypeRegistry.getDefaultMediaType().xContent(), + searchResponseBody + ).map().get("hits"); return (int) ((Map) responseMap.get("total")).get("value"); } + protected int parseHits(String searchResponseBody) throws IOException { + Map responseMap = (Map) createParser( + MediaTypeRegistry.getDefaultMediaType().xContent(), + searchResponseBody + ).map().get("hits"); + return ((List) responseMap.get("hits")).size(); + } + + protected List parseIds(String searchResponseBody) throws IOException { + @SuppressWarnings("unchecked") + List hits = (List) ((Map) createParser( + MediaTypeRegistry.getDefaultMediaType().xContent(), + searchResponseBody + ).map().get("hits")).get("hits"); + + return hits.stream().map(hit -> (String) ((Map) hit).get("_id")).collect(Collectors.toList()); + } + /** * Get the total number of graphs in the cache across all nodes */ @SuppressWarnings("unchecked") - protected int getTotalGraphsInCache() throws IOException { + protected int getTotalGraphsInCache() throws Exception { Response response = getKnnStats(Collections.emptyList(), Collections.emptyList()); String responseBody = EntityUtils.toString(response.getEntity()); @@ -601,49 +966,26 @@ protected int getTotalGraphsInCache() throws IOException { * Get specific Index setting value from response */ protected String getIndexSettingByName(String indexName, String settingName) throws IOException { - @SuppressWarnings("unchecked") - Map settings = (Map) ((Map) getIndexSettings(indexName).get(indexName)).get( - "settings" - ); - return (String) settings.get(settingName); + return getIndexSettingByName(indexName, settingName, false); } - protected void createModelSystemIndex() throws IOException { - URL url = ModelDao.class.getClassLoader().getResource(MODEL_INDEX_MAPPING_PATH); - if (url == null) { - throw new IllegalStateException("Unable to retrieve mapping for \"" + MODEL_INDEX_NAME + "\""); + protected String getIndexSettingByName(String indexName, String settingName, boolean includeDefaults) throws IOException { + Request request = new Request("GET", "/" + indexName + "/_settings"); + if (includeDefaults) { + request.addParameter("include_defaults", "true"); } - - String mapping = Resources.toString(url, Charsets.UTF_8); - mapping = mapping.substring(1, mapping.length() - 1); - - createIndex(MODEL_INDEX_NAME, Settings.builder().put("number_of_shards", 1).put("number_of_replicas", 0).build(), mapping); - } - - protected void addModelToSystemIndex(String modelId, ModelMetadata modelMetadata, byte[] model) throws IOException { - assertFalse(Strings.isNullOrEmpty(modelId)); - String modelBase64 = Base64.getEncoder().encodeToString(model); - - Request request = new Request("POST", "/" + MODEL_INDEX_NAME + "/_doc/" + modelId + "?refresh=true"); - - XContentBuilder builder = XContentFactory.jsonBuilder() - .startObject() - .field(MODEL_ID, modelId) - .field(MODEL_STATE, modelMetadata.getState().getName()) - .field(KNN_ENGINE, modelMetadata.getKnnEngine().getName()) - .field(METHOD_PARAMETER_SPACE_TYPE, modelMetadata.getSpaceType().getValue()) - .field(DIMENSION, modelMetadata.getDimension()) - .field(MODEL_BLOB_PARAMETER, modelBase64) - .field(MODEL_TIMESTAMP, modelMetadata.getTimestamp()) - .field(MODEL_DESCRIPTION, modelMetadata.getDescription()) - .field(MODEL_ERROR, modelMetadata.getError()) - .endObject(); - - request.setJsonEntity(Strings.toString(builder)); - + request.addParameter("flat_settings", "true"); Response response = client().performRequest(request); - - assertEquals(request.getEndpoint() + ": failed", RestStatus.CREATED, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + try (InputStream is = response.getEntity().getContent()) { + Map settings = (Map) XContentHelper.convertToMap(MediaTypeRegistry.JSON.xContent(), is, true) + .get(indexName); + Map defaultSettings = new HashMap<>(); + if (includeDefaults) { + defaultSettings = (Map) settings.get("defaults"); + } + Map userSettings = (Map) settings.get("settings"); + return (String) (userSettings.get(settingName) == null ? defaultSettings.get(settingName) : userSettings.get(settingName)); + } } /** @@ -719,19 +1061,51 @@ protected Request constructScriptedMetricAggregationSearchRequest( builder.endObject(); String endpoint = String.format(Locale.getDefault(), "/%s/_search?size=0&filter_path=aggregations", INDEX_NAME); Request request = new Request("POST", endpoint); - request.setJsonEntity(Strings.toString(builder)); + request.setJsonEntity(builder.toString()); + return request; + } + + protected Request constructScriptFieldsContextSearchRequest( + final String indexName, + final String fieldName, + final Map scriptParams, + final String language, + final String source, + final int size, + final Map searchParams + ) throws Exception { + Script script = buildScript(source, language, scriptParams); + XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field("size", size).startObject("query"); + builder.startObject("match_all"); + builder.endObject(); + builder.endObject(); + builder.startObject("script_fields"); + builder.startObject(fieldName); + builder.field("script", script); + builder.endObject(); + builder.endObject(); + builder.endObject(); + URIBuilder uriBuilder = new URIBuilder("/" + indexName + "/_search"); + if (Objects.nonNull(searchParams)) { + for (Map.Entry entry : searchParams.entrySet()) { + uriBuilder.addParameter(entry.getKey(), entry.getValue().toString()); + } + } + Request request = new Request("POST", uriBuilder.toString()); + request.setJsonEntity(builder.toString()); return request; } protected Request constructScriptScoreContextSearchRequest( String indexName, QueryBuilder qb, - Map params, + Map scriptParams, String language, String source, - int size + int size, + Map searchParams ) throws Exception { - Script script = buildScript(source, language, params); + Script script = buildScript(source, language, scriptParams); ScriptScoreQueryBuilder sc = new ScriptScoreQueryBuilder(qb, script); XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field("size", size).startObject("query"); builder.startObject("script_score"); @@ -741,15 +1115,28 @@ protected Request constructScriptScoreContextSearchRequest( builder.endObject(); builder.endObject(); builder.endObject(); - Request request = new Request("POST", "/" + indexName + "/_search"); - request.setJsonEntity(Strings.toString(builder)); + URIBuilder uriBuilder = new URIBuilder("/" + indexName + "/_search"); + if (Objects.nonNull(searchParams)) { + for (Map.Entry entry : searchParams.entrySet()) { + uriBuilder.addParameter(entry.getKey(), entry.getValue().toString()); + } + } + Request request = new Request("POST", uriBuilder.toString()); + request.setJsonEntity(builder.toString()); return request; } protected Request constructKNNScriptQueryRequest(String indexName, QueryBuilder qb, Map params) throws Exception { + return constructKNNScriptQueryRequest(indexName, qb, params, SearchService.DEFAULT_SIZE); + } + + protected Request constructKNNScriptQueryRequest(String indexName, QueryBuilder qb, Map params, int size) + throws Exception { Script script = new Script(Script.DEFAULT_SCRIPT_TYPE, KNNScoringScriptEngine.NAME, KNNScoringScriptEngine.SCRIPT_SOURCE, params); ScriptScoreQueryBuilder sc = new ScriptScoreQueryBuilder(qb, script); - XContentBuilder builder = XContentFactory.jsonBuilder().startObject().startObject("query"); + XContentBuilder builder = XContentFactory.jsonBuilder().startObject(); + builder.field("size", size); + builder.startObject("query"); builder.startObject("script_score"); builder.field("query"); sc.query().toXContent(builder, ToXContent.EMPTY_PARAMS); @@ -758,19 +1145,25 @@ protected Request constructKNNScriptQueryRequest(String indexName, QueryBuilder builder.endObject(); builder.endObject(); Request request = new Request("POST", "/" + indexName + "/_search"); - request.setJsonEntity(Strings.toString(builder)); + request.setJsonEntity(builder.toString()); return request; } - protected Request constructKNNScriptQueryRequest(String indexName, QueryBuilder qb, Map params, int size) - throws Exception { + protected Request constructKNNScriptQueryRequest( + String indexName, + QueryBuilder qb, + Map scriptParams, + int size, + Map searchParams + ) throws Exception { return constructScriptScoreContextSearchRequest( indexName, qb, - params, + scriptParams, KNNScoringScriptEngine.NAME, KNNScoringScriptEngine.SCRIPT_SOURCE, - size + size, + searchParams ); } @@ -790,6 +1183,58 @@ public void bulkIngestRandomVectors(String indexName, String fieldName, int numV } + /** + * Bulk ingest random binary vectors + * @param indexName index name + * @param fieldName field name + * @param numVectors number of vectors + * @param dimension vector dimension + */ + public void bulkIngestRandomBinaryVectors(String indexName, String fieldName, int numVectors, int dimension) throws IOException { + if (dimension % 8 != 0) { + throw new IllegalArgumentException("Dimension must be a multiple of 8"); + } + for (int i = 0; i < numVectors; i++) { + int binaryDimension = dimension / 8; + int[] vector = new int[binaryDimension]; + for (int j = 0; j < binaryDimension; j++) { + vector[j] = randomIntBetween(-128, 127); + } + + addKnnDoc(indexName, String.valueOf(i + 1), fieldName, Ints.asList(vector).toArray()); + } + } + + public void bulkIngestRandomByteVectors(String indexName, String fieldName, int numVectors, int dimension) throws IOException { + for (int i = 0; i < numVectors; i++) { + byte[] vector = new byte[dimension]; + for (int j = 0; j < dimension; j++) { + vector[j] = randomByte(); + } + addKnnDoc(indexName, String.valueOf(i + 1), fieldName, Bytes.asList(vector).toArray()); + } + } + + /** + * Bulk ingest random vectors with nested field + * + * @param indexName index name + * @param nestedFieldPath nested field path, e.g. "my_nested_field.my_vector_field" + * @param numVectors number of vectors + * @param dimension vector dimension + */ + public void bulkIngestRandomVectorsWithNestedField(String indexName, String nestedFieldPath, int numVectors, int dimension) + throws IOException { + for (int i = 0; i < numVectors; i++) { + float[] vector = new float[dimension]; + for (int j = 0; j < dimension; j++) { + vector[j] = randomFloat(); + } + + addKnnDocWithNestedField(indexName, String.valueOf(i + 1), nestedFieldPath, Floats.asList(vector).toArray()); + } + } + // Method that adds multiple documents into the index using Bulk API public void bulkAddKnnDocs(String index, String fieldName, float[][] indexVectors, int docCount) throws IOException { Request request = new Request("POST", "/_bulk"); @@ -817,7 +1262,7 @@ public void bulkAddKnnDocs(String index, String fieldName, float[][] indexVector } // Method that returns index vectors of the documents that were added before into the index - public float[][] getIndexVectorsFromIndex(String testIndex, String testField, int docCount, int dimensions) throws IOException { + public float[][] getIndexVectorsFromIndex(String testIndex, String testField, int docCount, int dimensions) throws Exception { float[][] vectors = new float[docCount][dimensions]; QueryBuilder qb = new MatchAllQueryBuilder(); @@ -828,7 +1273,7 @@ public float[][] getIndexVectorsFromIndex(String testIndex, String testField, in XContentBuilder builder = XContentFactory.jsonBuilder().startObject(); builder.field("query", qb); builder.endObject(); - request.setJsonEntity(Strings.toString(builder)); + request.setJsonEntity(builder.toString()); Response response = client().performRequest(request); assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); @@ -837,15 +1282,14 @@ public float[][] getIndexVectorsFromIndex(String testIndex, String testField, in int i = 0; for (KNNResult result : results) { - float[] primitiveArray = Floats.toArray(Arrays.stream(result.getVector()).collect(Collectors.toList())); - vectors[i++] = primitiveArray; + vectors[i++] = result.getVector(); } return vectors; } // Method that performs bulk search for multiple queries and stores the resulting documents ids into list - public List> bulkSearch(String testIndex, String testField, float[][] queryVectors, int k) throws IOException { + public List> bulkSearch(String testIndex, String testField, float[][] queryVectors, int k) throws Exception { List> searchResults = new ArrayList<>(); List kVectors; @@ -880,14 +1324,34 @@ public void addKNNDocs(String testIndex, String testField, int dimension, int fi Arrays.fill(indexVector, (float) i); addKnnDoc(testIndex, Integer.toString(i), testField, indexVector); } + flushIndex(testIndex); + } + + public void addKNNByteDocs(String testIndex, String testField, int dimension, int firstDocID, int numDocs) throws IOException { + for (int i = firstDocID; i < firstDocID + numDocs; i++) { + Byte[] indexVector = new Byte[dimension]; + Arrays.fill(indexVector, (byte) i); + addKnnDoc(testIndex, Integer.toString(i), testField, indexVector); + } + flushIndex(testIndex); + } + + public void validateKNNSearch(String testIndex, String testField, int dimension, int numDocs, int k) throws Exception { + validateKNNSearch(testIndex, testField, dimension, numDocs, k, null); } // Validate KNN search on a KNN index by generating the query vector from the number of documents in the index - public void validateKNNSearch(String testIndex, String testField, int dimension, int numDocs, int k) throws IOException { + public void validateKNNSearch(String testIndex, String testField, int dimension, int numDocs, int k, Map methodParameters) + throws Exception { float[] queryVector = new float[dimension]; Arrays.fill(queryVector, (float) numDocs); - Response searchResponse = searchKNNIndex(testIndex, new KNNQueryBuilder(testField, queryVector, k), k); + Response searchResponse = searchKNNIndex( + testIndex, + KNNQueryBuilder.builder().k(k).methodParameters(methodParameters).fieldName(testField).vector(queryVector).build(), + k + ); + List results = parseSearchResponse(EntityUtils.toString(searchResponse.getEntity()), testField); assertEquals(k, results.size()); @@ -896,32 +1360,38 @@ public void validateKNNSearch(String testIndex, String testField, int dimension, } } - protected Settings createKNNIndexCustomLegacyFieldMappingSettings(SpaceType spaceType, Integer m, Integer ef_construction) { + protected Settings.Builder createKNNIndexCustomLegacyFieldMappingIndexSettingsBuilder( + SpaceType spaceType, + Integer m, + Integer ef_construction + ) { return Settings.builder() .put(NUMBER_OF_SHARDS, 1) .put(NUMBER_OF_REPLICAS, 0) .put(INDEX_KNN, true) .put(KNNSettings.KNN_SPACE_TYPE, spaceType.getValue()) .put(KNNSettings.KNN_ALGO_PARAM_M, m) - .put(KNNSettings.KNN_ALGO_PARAM_EF_CONSTRUCTION, ef_construction) - .build(); + .put(KNNSettings.KNN_ALGO_PARAM_EF_CONSTRUCTION, ef_construction); + } + + protected Settings createKNNIndexCustomLegacyFieldMappingIndexSettings(SpaceType spaceType, Integer m, Integer ef_construction) { + return createKNNIndexCustomLegacyFieldMappingIndexSettingsBuilder(spaceType, m, ef_construction).build(); } public String createKNNIndexMethodFieldMapping(String fieldName, Integer dimensions) throws IOException { - return Strings.toString( - XContentFactory.jsonBuilder() - .startObject() - .startObject(PROPERTIES) - .startObject(fieldName) - .field(VECTOR_TYPE, KNN_VECTOR) - .field(DIMENSION, dimensions.toString()) - .startObject(KNN_METHOD) - .field(NAME, METHOD_HNSW) - .endObject() - .endObject() - .endObject() - .endObject() - ); + return XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES) + .startObject(fieldName) + .field(VECTOR_TYPE, KNN_VECTOR) + .field(DIMENSION, dimensions.toString()) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .endObject() + .endObject() + .endObject() + .endObject() + .toString(); } public String createKNNIndexCustomMethodFieldMapping( @@ -932,26 +1402,56 @@ public String createKNNIndexCustomMethodFieldMapping( Integer m, Integer ef_construction ) throws IOException { - return Strings.toString( - XContentFactory.jsonBuilder() - .startObject() - .startObject(PROPERTIES) - .startObject(fieldName) - .field(VECTOR_TYPE, KNN_VECTOR) - .field(DIMENSION, dimensions.toString()) - .startObject(KNN_METHOD) - .field(NAME, METHOD_HNSW) - .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) - .field(KNN_ENGINE, engine) - .startObject(PARAMETERS) - .field(METHOD_PARAMETER_EF_CONSTRUCTION, ef_construction) - .field(METHOD_PARAMETER_M, m) - .endObject() - .endObject() - .endObject() - .endObject() - .endObject() - ); + return XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES) + .startObject(fieldName) + .field(VECTOR_TYPE, KNN_VECTOR) + .field(DIMENSION, dimensions.toString()) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNN_ENGINE, engine) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, ef_construction) + .field(METHOD_PARAMETER_M, m) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .toString(); + } + + public String createKNNIndexCustomMethodFieldMapping( + String fieldName, + Integer dimensions, + SpaceType spaceType, + String engine, + Integer m, + Integer ef_construction, + Integer ef_search + ) throws IOException { + return XContentFactory.jsonBuilder() + .startObject() + .startObject(PROPERTIES) + .startObject(fieldName) + .field(VECTOR_TYPE, KNN_VECTOR) + .field(DIMENSION, dimensions.toString()) + .startObject(KNN_METHOD) + .field(NAME, METHOD_HNSW) + .field(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()) + .field(KNN_ENGINE, engine) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_EF_CONSTRUCTION, ef_construction) + .field(METHOD_PARAMETER_M, m) + .field(METHOD_PARAMETER_EF_SEARCH, ef_search) + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + .toString(); } // Default KNN script score settings @@ -972,7 +1472,7 @@ protected void validateKNNScriptScoreSearch(String testIndex, String testField, params.put(QUERY_VALUE, queryVector); params.put(METHOD_PARAMETER_SPACE_TYPE, spaceType.getValue()); - Request request = constructKNNScriptQueryRequest(testIndex, qb, params, k); + Request request = constructKNNScriptQueryRequest(testIndex, qb, params, k, Collections.emptyMap()); Response response = client().performRequest(request); assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); @@ -998,7 +1498,8 @@ protected void validateKNNPainlessScriptScoreSearch(String testIndex, String tes Collections.emptyMap(), Script.DEFAULT_SCRIPT_LANG, source, - k + k, + Collections.emptyMap() ); Response response = client().performRequest(request); assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); @@ -1064,7 +1565,19 @@ public Response trainModel( } Request request = new Request("POST", "/_plugins/_knn/models" + modelId + "/_train"); - request.setJsonEntity(Strings.toString(builder)); + request.setJsonEntity(builder.toString()); + return client().performRequest(request); + } + + public Response trainModel(String modelId, XContentBuilder builder) throws IOException { + if (modelId == null) { + modelId = ""; + } else { + modelId = "/" + modelId; + } + + Request request = new Request("POST", "/_plugins/_knn/models" + modelId + "/_train"); + request.setJsonEntity(builder.toString()); return client().performRequest(request); } @@ -1095,7 +1608,25 @@ public Response getModel(String modelId, List filters) throws IOExceptio return client().performRequest(request); } - public void assertTrainingSucceeds(String modelId, int attempts, int delayInMillis) throws InterruptedException, IOException { + /** + * Delete the model + * + * @param modelId Id of model to be retrieved + * @throws IOException if request cannot be performed + */ + public void deleteModel(String modelId) throws IOException { + if (modelId == null) { + modelId = ""; + } else { + modelId = "/" + modelId; + } + + Request request = new Request("DELETE", "/_plugins/_knn/models" + modelId); + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + } + + public void assertTrainingSucceeds(String modelId, int attempts, int delayInMillis) throws InterruptedException, Exception { int attemptNum = 0; Response response; Map responseMap; @@ -1106,7 +1637,8 @@ public void assertTrainingSucceeds(String modelId, int attempts, int delayInMill response = getModel(modelId, null); - responseMap = createParser(XContentType.JSON.xContent(), EntityUtils.toString(response.getEntity())).map(); + responseMap = createParser(MediaTypeRegistry.getDefaultMediaType().xContent(), EntityUtils.toString(response.getEntity())) + .map(); modelState = ModelState.getModelState((String) responseMap.get(MODEL_STATE)); if (modelState == ModelState.CREATED) { @@ -1119,7 +1651,7 @@ public void assertTrainingSucceeds(String modelId, int attempts, int delayInMill fail("Training did not succeed after " + attempts + " attempts with a delay of " + delayInMillis + " ms."); } - public void assertTrainingFails(String modelId, int attempts, int delayInMillis) throws InterruptedException, IOException { + public void assertTrainingFails(String modelId, int attempts, int delayInMillis) throws Exception { int attemptNum = 0; Response response; Map responseMap; @@ -1130,7 +1662,8 @@ public void assertTrainingFails(String modelId, int attempts, int delayInMillis) response = getModel(modelId, null); - responseMap = createParser(XContentType.JSON.xContent(), EntityUtils.toString(response.getEntity())).map(); + responseMap = createParser(MediaTypeRegistry.getDefaultMediaType().xContent(), EntityUtils.toString(response.getEntity())) + .map(); modelState = ModelState.getModelState((String) responseMap.get(MODEL_STATE)); if (modelState == ModelState.FAILED) { @@ -1140,7 +1673,96 @@ public void assertTrainingFails(String modelId, int attempts, int delayInMillis) assertNotEquals(ModelState.CREATED, modelState); } - fail("Training did not succeed after " + attempts + " attempts with a delay of " + delayInMillis + " ms."); + fail("Training did not fail after " + attempts + " attempts with a delay of " + delayInMillis + " ms."); + } + + protected boolean systemIndexExists(final String indexName) throws IOException { + Response response = adminClient().performRequest(new Request("HEAD", "/" + indexName)); + return RestStatus.OK.getStatus() == response.getStatusLine().getStatusCode(); + } + + protected Settings.Builder noStrictDeprecationModeSettingsBuilder() { + Settings.Builder builder = Settings.builder().put("strictDeprecationMode", false); + if (System.getProperty("tests.rest.client_path_prefix") != null) { + builder.put(CLIENT_PATH_PREFIX, System.getProperty("tests.rest.client_path_prefix")); + } + return builder; + } + + protected void ingestDataAndTrainModel( + String modelId, + String trainingIndexName, + String trainingFieldName, + int dimension, + String modelDescription + ) throws Exception { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, "ivf") + .field(KNN_ENGINE, "faiss") + .field(METHOD_PARAMETER_SPACE_TYPE, "l2") + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_NLIST, 1) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, "pq") + .startObject(PARAMETERS) + .field(ENCODER_PARAMETER_PQ_CODE_SIZE, 2) + .field(ENCODER_PARAMETER_PQ_M, 2) + .endObject() + .endObject() + .endObject() + .endObject(); + + Map method = xContentBuilderToMap(builder); + ingestDataAndTrainModel(modelId, trainingIndexName, trainingFieldName, dimension, modelDescription, method); + } + + protected void ingestDataAndTrainModel( + String modelId, + String trainingIndexName, + String trainingFieldName, + int dimension, + String modelDescription, + Map method + ) throws Exception { + int trainingDataCount = 40; + ingestDataAndTrainModel(modelId, trainingIndexName, trainingFieldName, dimension, modelDescription, method, trainingDataCount); + } + + protected void ingestDataAndTrainModel( + String modelId, + String trainingIndexName, + String trainingFieldName, + int dimension, + String modelDescription, + Map method, + int trainingDataCount + ) throws Exception { + bulkIngestRandomVectors(trainingIndexName, trainingFieldName, trainingDataCount, dimension); + + Response trainResponse = trainModel(modelId, trainingIndexName, trainingFieldName, dimension, method, modelDescription); + + assertEquals(RestStatus.OK, RestStatus.fromCode(trainResponse.getStatusLine().getStatusCode())); + } + + protected XContentBuilder getModelMethodBuilder() throws IOException { + XContentBuilder modelMethodBuilder = XContentFactory.jsonBuilder() + .startObject() + .field(NAME, "ivf") + .field(KNN_ENGINE, FAISS.getName()) + .field(METHOD_PARAMETER_SPACE_TYPE, L2.getValue()) + .startObject(PARAMETERS) + .field(METHOD_PARAMETER_NLIST, 1) + .startObject(METHOD_ENCODER_PARAMETER) + .field(NAME, "pq") + .startObject(PARAMETERS) + .field(ENCODER_PARAMETER_PQ_CODE_SIZE, 2) + .field(ENCODER_PARAMETER_PQ_M, 2) + .endObject() + .endObject() + .endObject() + .endObject(); + return modelMethodBuilder; } /** @@ -1158,4 +1780,118 @@ public interface IProxy { void reset(); } + + protected void refreshAllNonSystemIndices() throws Exception { + Response response = adminClient().performRequest(new Request("GET", "/_cat/indices?format=json&expand_wildcards=all")); + MediaType mediaType = MediaType.fromMediaType(response.getEntity().getContentType().getValue()); + try ( + XContentParser parser = mediaType.xContent() + .createParser( + NamedXContentRegistry.EMPTY, + DeprecationHandler.THROW_UNSUPPORTED_OPERATION, + response.getEntity().getContent() + ) + ) { + XContentParser.Token token = parser.nextToken(); + List> parserList; + if (token == XContentParser.Token.START_ARRAY) { + parserList = parser.listOrderedMap().stream().map(obj -> (Map) obj).collect(Collectors.toList()); + } else { + parserList = Collections.singletonList(parser.mapOrdered()); + } + Set indices = parserList.stream() + .map(index -> (String) index.get("index")) + .filter(index -> !index.startsWith(SYSTEM_INDEX_PREFIX)) + .collect(Collectors.toSet()); + for (String index : indices) { + refreshIndex(index); + } + } + } + + protected void refreshIndex(final String index) throws IOException { + Request request = new Request("POST", "/" + index + "/_refresh"); + + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + } + + protected void flushIndex(final String index) throws IOException { + Request request = new Request("POST", "/" + index + "/_flush"); + + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + } + + protected void addKnnDocWithAttributes(String docId, float[] vector, Map fieldValues) throws IOException { + Request request = new Request("POST", "/" + INDEX_NAME + "/_doc/" + docId + "?refresh=true"); + + final XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field(FIELD_NAME, vector); + for (String fieldName : fieldValues.keySet()) { + builder.field(fieldName, fieldValues.get(fieldName)); + } + builder.endObject(); + request.setJsonEntity(builder.toString()); + client().performRequest(request); + + request = new Request("POST", "/" + INDEX_NAME + "/_refresh"); + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + } + + protected void addKnnDocWithAttributes( + String indexName, + String docId, + String vectorFieldName, + float[] vector, + Map fieldValues + ) throws IOException { + Request request = new Request("POST", "/" + indexName + "/_doc/" + docId + "?refresh=true"); + + final XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field(vectorFieldName, vector); + for (String fieldName : fieldValues.keySet()) { + builder.field(fieldName, fieldValues.get(fieldName)); + } + builder.endObject(); + request.setJsonEntity(builder.toString()); + client().performRequest(request); + + request = new Request("POST", "/" + indexName + "/_refresh"); + Response response = client().performRequest(request); + assertEquals(request.getEndpoint() + ": failed", RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + } + + @SneakyThrows + protected XContentBuilder buildSearchQuery(String fieldName, int k, float[] vector, Map methodParams) { + XContentBuilder builder = XContentFactory.jsonBuilder() + .startObject() + .startObject("query") + .startObject("knn") + .startObject(fieldName) + .field("vector", vector) + .field("k", k); + if (methodParams != null) { + builder.startObject("method_parameters"); + for (Map.Entry entry : methodParams.entrySet()) { + builder.field(entry.getKey(), entry.getValue()); + } + builder.endObject(); + } + + builder.endObject().endObject().endObject().endObject(); + return builder; + } + + // approximate threshold parameter is only supported on or after V_2_18_0 + protected boolean isApproximateThresholdSupported(final Optional bwcVersion) { + if (bwcVersion.isEmpty()) { + return false; + } + String versionString = bwcVersion.get(); + if (versionString.endsWith("-SNAPSHOT")) { + versionString = versionString.substring(0, versionString.length() - 9); + } + final Version version = Version.fromString(versionString); + return version.onOrAfter(Version.V_2_18_0); + } } diff --git a/src/testFixtures/java/org/opensearch/knn/KNNResult.java b/src/testFixtures/java/org/opensearch/knn/KNNResult.java index 803c2ae72..ee2ba39f7 100644 --- a/src/testFixtures/java/org/opensearch/knn/KNNResult.java +++ b/src/testFixtures/java/org/opensearch/knn/KNNResult.java @@ -5,20 +5,41 @@ package org.opensearch.knn; +import java.util.Arrays; +import java.util.Objects; + public class KNNResult { + private final static float delta = 1e-3f; + private String docId; - private Float[] vector; + private float[] vector; + private Float score; - public KNNResult(String docId, Float[] vector) { + public KNNResult(String docId, float[] vector, Float score) { this.docId = docId; this.vector = vector; + this.score = score; } public String getDocId() { return docId; } - public Float[] getVector() { + public float[] getVector() { return vector; } + + public Float getScore() { + return score; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + KNNResult knnResult = (KNNResult) o; + return Objects.equals(docId, knnResult.docId) + && Arrays.equals(vector, knnResult.vector) + && (Float.compare(score, knnResult.score) == 0 || Math.abs(score - knnResult.score) <= delta); + } } diff --git a/src/testFixtures/java/org/opensearch/knn/NestedKnnDocBuilder.java b/src/testFixtures/java/org/opensearch/knn/NestedKnnDocBuilder.java new file mode 100644 index 000000000..dca58838a --- /dev/null +++ b/src/testFixtures/java/org/opensearch/knn/NestedKnnDocBuilder.java @@ -0,0 +1,90 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.knn; + +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.Map; + +public class NestedKnnDocBuilder { + private XContentBuilder builder; + private boolean isNestedFieldBuildCompleted; + + public NestedKnnDocBuilder(final String fieldName) throws IOException { + isNestedFieldBuildCompleted = false; + builder = XContentFactory.jsonBuilder().startObject().startArray(fieldName); + } + + public static NestedKnnDocBuilder create(final String fieldName) throws IOException { + return new NestedKnnDocBuilder(fieldName); + } + + public NestedKnnDocBuilder addVectors(final String fieldName, final Object[]... vectors) throws IOException { + for (Object[] vector : vectors) { + builder.startObject(); + builder.field(fieldName, vector); + builder.endObject(); + } + return this; + } + + public NestedKnnDocBuilder addVectorWithMetadata( + final String fieldName, + final Object[] vectorValue, + final String metadataFieldName, + final Object metadataValue + ) throws IOException { + builder.startObject(); + builder.field(fieldName, vectorValue); + builder.field(metadataFieldName, metadataValue); + builder.endObject(); + return this; + } + + public NestedKnnDocBuilder addMetadata(final Map metadata) throws IOException { + builder.startObject(); + metadata.forEach((k, v) -> { + try { + builder.field(k, v); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + builder.endObject(); + return this; + } + + /** + * Use this function when you want to add top level fields in the document that contains nested fields. Once you + * run this function you cannot add anything in the nested field. + */ + public NestedKnnDocBuilder addTopLevelField(final String fieldName, final Object value) throws IOException { + if (isNestedFieldBuildCompleted == false) { + // Making sure that we close the building of nested field. + isNestedFieldBuildCompleted = true; + builder.endArray(); + } + builder.field(fieldName, value); + return this; + } + + public String build() throws IOException { + if (isNestedFieldBuildCompleted) { + builder.endObject(); + } else { + builder.endArray().endObject(); + } + return builder.toString(); + } +} diff --git a/src/testFixtures/java/org/opensearch/knn/ODFERestTestCase.java b/src/testFixtures/java/org/opensearch/knn/ODFERestTestCase.java index d98faa265..efdde63c5 100644 --- a/src/testFixtures/java/org/opensearch/knn/ODFERestTestCase.java +++ b/src/testFixtures/java/org/opensearch/knn/ODFERestTestCase.java @@ -5,13 +5,6 @@ package org.opensearch.knn; -import java.io.IOException; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; - import org.apache.http.Header; import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; @@ -21,6 +14,9 @@ import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.message.BasicHeader; import org.apache.http.ssl.SSLContextBuilder; +import org.apache.http.util.EntityUtils; +import org.junit.After; +import org.opensearch.action.search.SearchResponse; import org.opensearch.client.Request; import org.opensearch.client.Response; import org.opensearch.client.RestClient; @@ -28,16 +24,32 @@ import org.opensearch.common.settings.Settings; import org.opensearch.common.unit.TimeValue; import org.opensearch.common.util.concurrent.ThreadContext; -import org.opensearch.common.xcontent.DeprecationHandler; -import org.opensearch.common.xcontent.NamedXContentRegistry; -import org.opensearch.common.xcontent.XContentParser; +import org.opensearch.core.xcontent.DeprecationHandler; +import org.opensearch.core.xcontent.MediaType; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentParser; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.knn.plugin.KNNPlugin; +import org.opensearch.core.rest.RestStatus; +import org.opensearch.search.SearchHit; import org.opensearch.test.rest.OpenSearchRestTestCase; -import org.junit.After; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import static org.opensearch.knn.TestUtils.KNN_BWC_PREFIX; import static org.opensearch.knn.TestUtils.OPENDISTRO_SECURITY; +import static org.opensearch.knn.TestUtils.ML_PLUGIN_SYSTEM_INDEX_PREFIX; +import static org.opensearch.knn.TestUtils.OPENSEARCH_SYSTEM_INDEX_PREFIX; +import static org.opensearch.knn.TestUtils.SECURITY_AUDITLOG_PREFIX; import static org.opensearch.knn.TestUtils.SKIP_DELETE_MODEL_INDEX; +import static org.opensearch.knn.common.KNNConstants.MODELS; import static org.opensearch.knn.common.KNNConstants.MODEL_INDEX_NAME; /** @@ -45,16 +57,15 @@ */ public abstract class ODFERestTestCase extends OpenSearchRestTestCase { - protected boolean isHttps() { - boolean isHttps = Optional.ofNullable(System.getProperty("https")).map("true"::equalsIgnoreCase).orElse(false); - if (isHttps) { - // currently only external cluster is supported for security enabled testing - if (!Optional.ofNullable(System.getProperty("tests.rest.cluster")).isPresent()) { - throw new RuntimeException("cluster url should be provided for security enabled testing"); - } - } + private final Set IMMUTABLE_INDEX_PREFIXES = Set.of( + KNN_BWC_PREFIX, + SECURITY_AUDITLOG_PREFIX, + OPENSEARCH_SYSTEM_INDEX_PREFIX, + ML_PLUGIN_SYSTEM_INDEX_PREFIX + ); - return isHttps; + protected boolean isHttps() { + return Optional.ofNullable(System.getProperty("https")).map("true"::equalsIgnoreCase).orElse(false); } @Override @@ -71,17 +82,14 @@ protected RestClient buildClient(Settings settings, HttpHost[] hosts) throws IOE configureClient(builder, settings); } + builder.setStrictDeprecationMode(false); return builder.build(); } protected static void configureHttpsClient(RestClientBuilder builder, Settings settings) throws IOException { - Map headers = ThreadContext.buildDefaultHeaders(settings); - Header[] defaultHeaders = new Header[headers.size()]; - int i = 0; - for (Map.Entry entry : headers.entrySet()) { - defaultHeaders[i++] = new BasicHeader(entry.getKey(), entry.getValue()); - } - builder.setDefaultHeaders(defaultHeaders); + // Similar to client configuration with OpenSearch: + // https://github.com/opensearch-project/OpenSearch/blob/2.11.1/test/framework/src/main/java/org/opensearch/test/rest/OpenSearchRestTestCase.java#L841-L863 + // except we set the user name and password builder.setHttpClientConfigCallback(httpClientBuilder -> { String userName = Optional.ofNullable(System.getProperty("user")) .orElseThrow(() -> new RuntimeException("user name is missing")); @@ -98,7 +106,13 @@ protected static void configureHttpsClient(RestClientBuilder builder, Settings s throw new RuntimeException(e); } }); - + Map headers = ThreadContext.buildDefaultHeaders(settings); + Header[] defaultHeaders = new Header[headers.size()]; + int i = 0; + for (Map.Entry entry : headers.entrySet()) { + defaultHeaders[i++] = new BasicHeader(entry.getKey(), entry.getValue()); + } + builder.setDefaultHeaders(defaultHeaders); final String socketTimeoutString = settings.get(CLIENT_SOCKET_TIMEOUT); final TimeValue socketTimeout = TimeValue.parseTimeValue( socketTimeoutString == null ? "60s" : socketTimeoutString, @@ -120,9 +134,9 @@ protected boolean preserveIndicesUponCompletion() { @SuppressWarnings("unchecked") @After - protected void wipeAllODFEIndices() throws IOException { - Response response = client().performRequest(new Request("GET", "/_cat/indices?format=json&expand_wildcards=all")); - XContentType xContentType = XContentType.fromMediaType(response.getEntity().getContentType().getValue()); + protected void wipeAllODFEIndices() throws Exception { + Response response = adminClient().performRequest(new Request("GET", "/_cat/indices?format=json&expand_wildcards=all")); + MediaType xContentType = MediaType.fromMediaType(response.getEntity().getContentType().getValue()); try ( XContentParser parser = xContentType.xContent() .createParser( @@ -140,7 +154,13 @@ protected void wipeAllODFEIndices() throws IOException { } for (Map index : parserList) { - String indexName = (String) index.get("index"); + final String indexName = (String) index.get("index"); + if (MODEL_INDEX_NAME.equals(indexName)) { + if (!getSkipDeleteModelIndexFlag()) { + deleteModels(getModelIds()); + } + continue; + } if (!skipDeleteIndex(indexName)) { adminClient().performRequest(new Request("DELETE", "/" + indexName)); } @@ -148,22 +168,41 @@ protected void wipeAllODFEIndices() throws IOException { } } - private boolean getSkipDeleteModelIndexFlag() { - return Boolean.parseBoolean(System.getProperty(SKIP_DELETE_MODEL_INDEX, "false")); - } + private List getModelIds() throws IOException { + final String restURIGetModels = String.join("/", KNNPlugin.KNN_BASE_URI, MODELS, "_search"); + final Response response = adminClient().performRequest(new Request("GET", restURIGetModels)); - private boolean skipDeleteModelIndex(String indexName) { - return (MODEL_INDEX_NAME.equals(indexName) && getSkipDeleteModelIndexFlag()); + assertEquals(RestStatus.OK, RestStatus.fromCode(response.getStatusLine().getStatusCode())); + + final String responseBody = EntityUtils.toString(response.getEntity()); + assertNotNull(responseBody); + + final XContentParser parser = createParser(XContentType.JSON.xContent(), responseBody); + final SearchResponse searchResponse = SearchResponse.fromXContent(parser); + + return Arrays.stream(searchResponse.getHits().getHits()).map(SearchHit::getId).collect(Collectors.toList()); } - private boolean skipDeleteIndex(String indexName) { - if (indexName != null - && !OPENDISTRO_SECURITY.equals(indexName) - && !indexName.startsWith(KNN_BWC_PREFIX) - && !skipDeleteModelIndex(indexName)) { - return false; + private void deleteModels(final List modelIds) throws IOException { + for (final String testModelID : modelIds) { + final String restURIGetModel = String.join("/", KNNPlugin.KNN_BASE_URI, MODELS, testModelID); + final Response getModelResponse = adminClient().performRequest(new Request("GET", restURIGetModel)); + if (RestStatus.OK != RestStatus.fromCode(getModelResponse.getStatusLine().getStatusCode())) { + continue; + } + final String restURIDeleteModel = String.join("/", KNNPlugin.KNN_BASE_URI, MODELS, testModelID); + adminClient().performRequest(new Request("DELETE", restURIDeleteModel)); } + } - return true; + private boolean getSkipDeleteModelIndexFlag() { + return Boolean.parseBoolean(System.getProperty(SKIP_DELETE_MODEL_INDEX, "false")); + } + + private boolean skipDeleteIndex(String indexName) { + return indexName == null + || OPENDISTRO_SECURITY.equals(indexName) + || IMMUTABLE_INDEX_PREFIXES.stream().anyMatch(indexName::startsWith) + || MODEL_INDEX_NAME.equals(indexName); } } diff --git a/src/testFixtures/java/org/opensearch/knn/TestUtils.java b/src/testFixtures/java/org/opensearch/knn/TestUtils.java index 56fdde720..3642752ec 100644 --- a/src/testFixtures/java/org/opensearch/knn/TestUtils.java +++ b/src/testFixtures/java/org/opensearch/knn/TestUtils.java @@ -6,16 +6,30 @@ package org.opensearch.knn; import com.google.common.collect.ImmutableMap; -import org.opensearch.common.xcontent.DeprecationHandler; -import org.opensearch.common.xcontent.NamedXContentRegistry; -import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.DeprecationHandler; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.common.xcontent.XContentType; -import org.opensearch.knn.index.codec.util.KNNCodecUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexOutput; +import org.opensearch.core.xcontent.DeprecationHandler; +import org.opensearch.core.xcontent.NamedXContentRegistry; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import org.opensearch.knn.index.SpaceType; +import org.opensearch.knn.index.codec.util.SerializationMode; +import org.opensearch.knn.index.engine.KNNEngine; +import org.opensearch.knn.index.store.IndexOutputWithBuffer; +import org.opensearch.knn.jni.JNICommons; +import org.opensearch.knn.jni.JNIService; import org.opensearch.knn.plugin.script.KNNScoringUtil; + +import java.util.Base64; +import java.util.Collections; import java.util.Comparator; import java.util.Random; import java.util.Set; @@ -66,7 +80,11 @@ public class TestUtils { SpaceType.L2, KNNScoringUtil::l2Squared, SpaceType.LINF, - KNNScoringUtil::lInfNorm + KNNScoringUtil::lInfNorm, + SpaceType.COSINESIMIL, + KNNScoringUtil::cosinesimil, + SpaceType.INNER_PRODUCT, + KNNScoringUtil::innerProduct ); public static final String KNN_BWC_PREFIX = "knn-bwc-"; @@ -91,6 +109,9 @@ public class TestUtils { public static final String ROLLING_UPGRADE_FIRST_ROUND = "tests.rest.first_round"; public static final String SKIP_DELETE_MODEL_INDEX = "tests.skip_delete_model_index"; public static final String UPGRADED_CLUSTER = "upgraded_cluster"; + public static final String SECURITY_AUDITLOG_PREFIX = "security-auditlog"; + public static final String OPENSEARCH_SYSTEM_INDEX_PREFIX = ".opensearch"; + public static final String ML_PLUGIN_SYSTEM_INDEX_PREFIX = ".plugins-ml"; // Generating vectors using random function with a seed which makes these vectors standard and generate same vectors for each run. public static float[][] randomlyGenerateStandardVectors(int numVectors, int dimensions, int seed) { @@ -134,6 +155,12 @@ public static List> computeGroundTruthValues(float[][] indexVectors, pq = new PriorityQueue<>(k, new DistComparator()); for (int j = 0; j < indexVectors.length; j++) { float dist = computeDistFromSpaceType(spaceType, indexVectors[j], queryVectors[i]); + + // Need to invert distance for IP or COSINE because higher is better in these cases + if (spaceType == SpaceType.INNER_PRODUCT || spaceType == SpaceType.COSINESIMIL) { + dist *= -1; + } + pq = insertWithOverflow(pq, k, dist, j); } @@ -164,6 +191,10 @@ public static float[][] getIndexVectors(int docCount, int dimensions, boolean is } } + public static String createFakeNativeMamoryCacheKey(final String fileName) { + return fileName + "@" + Base64.getEncoder().encodeToString(fileName.getBytes()); + } + /* * Recall is the number of relevant documents retrieved by a search divided by the total number of existing relevant documents. * We are similarly calculating recall by verifying number of relevant documents obtained in the search results by comparing with @@ -194,6 +225,11 @@ public static PriorityQueue computeGroundTruthValues(int k, SpaceTyp for (int id = 0; id < numDocs; id++) { float[] indexVector = idVectorProducer.getVector(id); float dist = computeDistFromSpaceType(spaceType, indexVector, queryVector); + // Need to invert distance for IP or COSINE because higher is better in these cases + if (spaceType == SpaceType.INNER_PRODUCT || spaceType == SpaceType.COSINESIMIL) { + dist *= -1; + } + pq = insertWithOverflow(pq, k, dist, id); } return pq; @@ -202,12 +238,9 @@ public static PriorityQueue computeGroundTruthValues(int k, SpaceTyp public static float computeDistFromSpaceType(SpaceType spaceType, float[] indexVector, float[] queryVector) { float dist; if (spaceType != null) { - dist = KNN_SCORING_SPACE_TYPE.getOrDefault( - spaceType, - (defaultQueryVector, defaultIndexVector) -> { - throw new IllegalArgumentException(String.format("Invalid SpaceType function: \"%s\"", spaceType)); - } - ).apply(queryVector, indexVector); + dist = KNN_SCORING_SPACE_TYPE.getOrDefault(spaceType, (defaultQueryVector, defaultIndexVector) -> { + throw new IllegalArgumentException(String.format("Invalid SpaceType function: \"%s\"", spaceType)); + }).apply(queryVector, indexVector); } else { throw new NullPointerException("SpaceType is null. Provide a valid SpaceType."); } @@ -231,31 +264,42 @@ public static PriorityQueue insertWithOverflow(PriorityQueue idsList = new ArrayList<>(); List vectorsList = new ArrayList<>(); BufferedReader reader = new BufferedReader(new FileReader(path)); String line = reader.readLine(); while (line != null) { - Map doc = XContentFactory.xContent(XContentType.JSON) + Map doc = XContentType.JSON.xContent() .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, line) .map(); idsList.add((Integer) doc.get("id")); @SuppressWarnings("unchecked") - ArrayList vector = (ArrayList) doc.get("vector"); + ArrayList vector = (ArrayList) doc.get("vector"); Float[] floatArray = new Float[vector.size()]; for (int i = 0; i < vector.size(); i++) { - floatArray[i] = vector.get(i).floatValue(); + floatArray[i] = Float.valueOf(vector.get(i).toString()); } vectorsList.add(floatArray); @@ -272,8 +316,7 @@ private KNNCodecUtil.Pair readIndexData(String path) throws IOException { vectorsArray[i][j] = vectorsList.get(i)[j]; } } - - return new KNNCodecUtil.Pair(idsArray, vectorsArray); + return new Pair(idsArray, vectorsArray[0].length, SerializationMode.COLLECTION_OF_FLOATS, vectorsArray); } private float[][] readQueries(String path) throws IOException { @@ -304,5 +347,108 @@ private float[][] readQueries(String path) throws IOException { } return queryArray; } + + private String[][] readGroundTruthValues(String path) throws IOException { + BufferedReader reader = new BufferedReader(new FileReader(path)); + String line = reader.readLine(); + + List stringList = new ArrayList<>(); + + while (line != null) { + String[] intStrings = line.split(","); + stringList.add(intStrings); + line = reader.readLine(); + } + reader.close(); + + String[][] docIdArray = new String[stringList.size()][stringList.get(0).length]; + for (int i = 0; i < docIdArray.length; i++) { + for (int j = 0; j < docIdArray[i].length; j++) { + docIdArray[i][j] = stringList.get(i)[j].trim(); + } + } + return docIdArray; + } + + private void initBinaryData() { + // Find medium value + List flattenedVectors = new ArrayList<>(indexData.vectors.length * indexData.vectors[0].length); + for (int i = 0; i < indexData.vectors.length; i++) { + for (int j = 0; j < indexData.vectors[i].length; j++) { + flattenedVectors.add(indexData.vectors[i][j]); + } + } + Collections.sort(flattenedVectors); + Float median = flattenedVectors.get(flattenedVectors.size() / 2); + + // Quantize(indexData.vectors[i][j] >= median ? 1 : 0) and + // packing(8 bits to 1 byte) for index data + indexBinaryData = new byte[indexData.vectors.length][(indexData.vectors[0].length + 7) / 8]; + for (int i = 0; i < indexData.vectors.length; i++) { + for (int j = 0; j < indexData.vectors[i].length; j++) { + int byteIndex = j / 8; + int bitIndex = 7 - (j % 8); + indexBinaryData[i][byteIndex] |= (indexData.vectors[i][j] >= median ? 1 : 0) << bitIndex; + } + } + + // Quantize(queries[i][j] >= median ? 1 : 0) and + // packing(8 bits to 1 byte) for query data + binaryQueries = new byte[queries.length][(queries[0].length + 7) / 8]; + for (int i = 0; i < queries.length; i++) { + for (int j = 0; j < queries[i].length; j++) { + int byteIndex = j / 8; + int bitIndex = 7 - (j % 8); + binaryQueries[i][byteIndex] |= (queries[i][j] >= median ? 1 : 0) << bitIndex; + } + } + } + + public long loadDataToMemoryAddress() { + return JNICommons.storeVectorData(0, indexData.vectors, (long) indexData.vectors.length * indexData.vectors[0].length, true); + } + + public long loadBinaryDataToMemoryAddress() { + return JNICommons.storeBinaryVectorData(0, indexBinaryData, (long) indexBinaryData.length * indexBinaryData[0].length, true); + } + + @AllArgsConstructor + public static class Pair { + public int[] docs; + @Getter + @Setter + private int dimension; + public SerializationMode serializationMode; + public float[][] vectors; + } + } + + public static void createIndex( + int[] ids, + long address, + int dimension, + Directory directory, + String fileName, + Map parameters, + KNNEngine engine + ) { + if (engine != KNNEngine.FAISS) { + try (IndexOutput indexOutput = directory.createOutput(fileName, IOContext.DEFAULT)) { + final IndexOutputWithBuffer indexOutputWithBuffer = new IndexOutputWithBuffer(indexOutput); + JNIService.createIndex(ids, address, dimension, indexOutputWithBuffer, parameters, engine); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + // We can initialize numDocs as 0, this will just not reserve anything. + long indexAddress = JNIService.initIndex(0, dimension, parameters, engine); + JNIService.insertToIndex(ids, address, dimension, parameters, indexAddress, engine); + try (IndexOutput indexOutput = directory.createOutput(fileName, IOContext.DEFAULT)) { + final IndexOutputWithBuffer indexOutputWithBuffer = new IndexOutputWithBuffer(indexOutput); + JNIService.writeIndex(indexOutputWithBuffer, indexAddress, engine, parameters); + } catch (IOException e) { + throw new RuntimeException(e); + } + } } }